diff --git a/web/scripts/create-staking-pools.mjs b/web/scripts/create-staking-pools.mjs deleted file mode 100644 index 22a4c50e..00000000 --- a/web/scripts/create-staking-pools.mjs +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Script to create LP staking reward pools - * Run with: node scripts/create-staking-pools.mjs - * - * Requires MNEMONIC environment variable with admin wallet seed - */ - -import { ApiPromise, WsProvider } from '@pezkuwi/api'; -import { Keyring } from '@pezkuwi/api'; - -const ASSET_HUB_RPC = 'wss://asset-hub-rpc.pezkuwichain.io'; - -// LP Token IDs (from assetConversion pools) -const LP_TOKENS = { - 'HEZ-PEZ': 0, - 'HEZ-USDT': 1, - 'HEZ-DOT': 2, -}; - -// Reward token: PEZ (asset ID 1) -const REWARD_ASSET_ID = 1; - -// Reward rate per block (in smallest units - 12 decimals) -// 0.01 PEZ per block = 10_000_000_000 (10^10) -const REWARD_RATE_PER_BLOCK = '10000000000'; - -// 100 years in blocks (6 second blocks) -const BLOCKS_100_YEARS = 525600000; - -async function main() { - const mnemonic = process.env.MNEMONIC; - if (!mnemonic) { - console.error('ERROR: Set MNEMONIC environment variable'); - console.log('Usage: MNEMONIC="foam hope topic phone year fold lyrics biology erosion feed false island" node scripts/create-staking-pools.mjs'); - process.exit(1); - } - - const provider = new WsProvider(ASSET_HUB_RPC); - const api = await ApiPromise.create({ provider }); - - const keyring = new Keyring({ type: 'sr25519' }); - const admin = keyring.addFromMnemonic(mnemonic); - console.log('Admin address:', admin.address); - - // Get current block for expiry calculation - const header = await api.rpc.chain.getHeader(); - const currentBlock = header.number.toNumber(); - const expiryBlock = currentBlock + BLOCKS_100_YEARS; - console.log('Current block:', currentBlock); - console.log('Expiry block (100 years):', expiryBlock); - - // Format asset location for LP tokens (poolAssets pallet, instance 55) - const formatLpTokenLocation = (lpTokenId) => ({ - parents: 0, - interior: { X2: [{ PalletInstance: 55 }, { GeneralIndex: lpTokenId }] } - }); - - // Format asset location for reward token (assets pallet, instance 50) - const formatRewardTokenLocation = (assetId) => ({ - parents: 0, - interior: { X2: [{ PalletInstance: 50 }, { GeneralIndex: assetId }] } - }); - - // Expiry format: { At: blockNumber } - const expiry = { At: expiryBlock }; - - console.log('\n=== Creating Staking Pools ===\n'); - - for (const [poolName, lpTokenId] of Object.entries(LP_TOKENS)) { - console.log(`Creating pool for ${poolName} (LP Token #${lpTokenId})...`); - - const stakedAssetLocation = formatLpTokenLocation(lpTokenId); - const rewardAssetLocation = formatRewardTokenLocation(REWARD_ASSET_ID); - - try { - const tx = api.tx.assetRewards.createPool( - stakedAssetLocation, - rewardAssetLocation, - REWARD_RATE_PER_BLOCK, - expiry, - admin.address // admin - ); - - await new Promise((resolve, reject) => { - tx.signAndSend(admin, ({ status, events, dispatchError }) => { - if (status.isInBlock) { - console.log(` In block: ${status.asInBlock.toHex()}`); - } else if (status.isFinalized) { - console.log(` Finalized: ${status.asFinalized.toHex()}`); - - if (dispatchError) { - if (dispatchError.isModule) { - const decoded = api.registry.findMetaError(dispatchError.asModule); - reject(new Error(`${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`)); - } else { - reject(new Error(dispatchError.toString())); - } - } else { - // Find pool created event - const poolCreated = events.find(({ event }) => - event.section === 'assetRewards' && event.method === 'PoolCreated' - ); - if (poolCreated) { - console.log(` ✅ Pool created:`, poolCreated.event.data.toHuman()); - } - resolve(); - } - } - }); - }); - - console.log(` ✅ ${poolName} staking pool created!\n`); - } catch (err) { - console.error(` ❌ Failed to create ${poolName} pool:`, err.message); - } - } - - // List created pools - console.log('\n=== Created Pools ==='); - const pools = await api.query.assetRewards.pools.entries(); - for (const [key, value] of pools) { - console.log('Pool ID:', key.args[0].toString()); - console.log(' Config:', value.toHuman()); - } - - await api.disconnect(); - console.log('\nDone!'); -} - -main().catch(console.error); diff --git a/web/src/components/LPStakingModal.tsx b/web/src/components/LPStakingModal.tsx deleted file mode 100644 index 9eaad76e..00000000 --- a/web/src/components/LPStakingModal.tsx +++ /dev/null @@ -1,474 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { X, Lock, Unlock, Gift, AlertCircle, Loader2, Info } from 'lucide-react'; -import { web3FromAddress } from '@pezkuwi/extension-dapp'; -import { usePezkuwi } from '@/contexts/PezkuwiContext'; -import { Button } from '@/components/ui/button'; -import { Alert, AlertDescription } from '@/components/ui/alert'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; - -interface StakingPool { - poolId: number; - stakedAsset: string; - rewardAsset: string; - rewardRatePerBlock: string; - totalStaked: string; - userStaked: string; - pendingRewards: string; - lpTokenId: number; - lpBalance: string; -} - -interface LPStakingModalProps { - isOpen: boolean; - onClose: () => void; -} - -const LP_TOKEN_NAMES: Record = { - 0: 'HEZ-PEZ LP', - 1: 'HEZ-USDT LP', - 2: 'HEZ-DOT LP', -}; - -export const LPStakingModal: React.FC = ({ isOpen, onClose }) => { - const { assetHubApi, selectedAccount, isAssetHubReady } = usePezkuwi(); - const [pools, setPools] = useState([]); - const [isLoading, setIsLoading] = useState(true); - const [isProcessing, setIsProcessing] = useState(false); - const [error, setError] = useState(null); - const [success, setSuccess] = useState(null); - const [selectedPool, setSelectedPool] = useState(null); - const [stakeAmount, setStakeAmount] = useState(''); - const [unstakeAmount, setUnstakeAmount] = useState(''); - const [activeTab, setActiveTab] = useState('stake'); - - // Fetch staking pools - useEffect(() => { - if (!assetHubApi || !isAssetHubReady || !isOpen) return; - - const fetchPools = async () => { - setIsLoading(true); - try { - const poolEntries = await assetHubApi.query.assetRewards.pools.entries(); - const stakingPools: StakingPool[] = []; - - for (const [key, value] of poolEntries) { - const poolId = parseInt(key.args[0].toString()); - const poolData = value.toJSON() as { - stakedAssetId: { interior: { x2: [{ palletInstance: number }, { generalIndex: number }] } }; - rewardAssetId: { interior: { x2: [{ palletInstance: number }, { generalIndex: number }] } }; - rewardRatePerBlock: string; - totalTokensStaked: string; - lastUpdatedBlock: number; - }; - - // Extract LP token ID from staked asset location - const lpTokenId = poolData.stakedAssetId?.interior?.x2?.[1]?.generalIndex ?? 0; - - // Get user's stake if account connected - let userStaked = '0'; - const pendingRewards = '0'; // TODO: Calculate from reward debt - let lpBalance = '0'; - - if (selectedAccount) { - try { - const stakeInfo = await assetHubApi.query.assetRewards.poolStakers([poolId, selectedAccount.address]); - if (stakeInfo && (stakeInfo as { isSome: boolean }).isSome) { - const stakeData = (stakeInfo as { unwrap: () => { toJSON: () => { amount: string; rewardDebt: string } } }).unwrap().toJSON(); - userStaked = stakeData.amount || '0'; - // Pending rewards calculation would need more complex logic - } - - // Get LP token balance from poolAssets - const lpBal = await assetHubApi.query.poolAssets.account(lpTokenId, selectedAccount.address); - if (lpBal && (lpBal as { isSome: boolean }).isSome) { - const lpData = (lpBal as { unwrap: () => { toJSON: () => { balance: string } } }).unwrap().toJSON(); - lpBalance = lpData.balance || '0'; - } - } catch (e) { - console.error('Error fetching user stake:', e); - } - } - - stakingPools.push({ - poolId, - stakedAsset: LP_TOKEN_NAMES[lpTokenId] || `LP Token #${lpTokenId}`, - rewardAsset: 'PEZ', - rewardRatePerBlock: poolData.rewardRatePerBlock || '0', - totalStaked: poolData.totalTokensStaked || '0', - userStaked, - pendingRewards, - lpTokenId, - lpBalance, - }); - } - - setPools(stakingPools); - if (stakingPools.length > 0 && selectedPool === null) { - setSelectedPool(stakingPools[0].poolId); - } - } catch (err) { - console.error('Error fetching staking pools:', err); - setError('Failed to fetch staking pools'); - } finally { - setIsLoading(false); - } - }; - - fetchPools(); - }, [assetHubApi, isAssetHubReady, isOpen, selectedAccount, selectedPool]); - - const formatAmount = (amount: string, decimals: number = 12): string => { - const value = Number(amount) / Math.pow(10, decimals); - return value.toLocaleString(undefined, { maximumFractionDigits: 4 }); - }; - - const handleStake = async () => { - if (!assetHubApi || !selectedAccount || selectedPool === null || !stakeAmount) return; - - setIsProcessing(true); - setError(null); - setSuccess(null); - - try { - const pool = pools.find(p => p.poolId === selectedPool); - if (!pool) throw new Error('Pool not found'); - - const amountBN = BigInt(Math.floor(parseFloat(stakeAmount) * 1e12)); - const injector = await web3FromAddress(selectedAccount.address); - - const tx = assetHubApi.tx.assetRewards.stake(selectedPool, amountBN.toString()); - - await new Promise((resolve, reject) => { - tx.signAndSend( - selectedAccount.address, - { signer: injector.signer }, - ({ status, dispatchError }) => { - if (status.isFinalized) { - if (dispatchError) { - if (dispatchError.isModule) { - const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule); - reject(new Error(`${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`)); - } else { - reject(new Error(dispatchError.toString())); - } - } else { - resolve(); - } - } - } - ); - }); - - setSuccess(`Successfully staked ${stakeAmount} ${pool.stakedAsset}!`); - setStakeAmount(''); - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to stake'); - } finally { - setIsProcessing(false); - } - }; - - const handleUnstake = async () => { - if (!assetHubApi || !selectedAccount || selectedPool === null || !unstakeAmount) return; - - setIsProcessing(true); - setError(null); - setSuccess(null); - - try { - const pool = pools.find(p => p.poolId === selectedPool); - if (!pool) throw new Error('Pool not found'); - - const amountBN = BigInt(Math.floor(parseFloat(unstakeAmount) * 1e12)); - const injector = await web3FromAddress(selectedAccount.address); - - const tx = assetHubApi.tx.assetRewards.unstake(selectedPool, amountBN.toString()); - - await new Promise((resolve, reject) => { - tx.signAndSend( - selectedAccount.address, - { signer: injector.signer }, - ({ status, dispatchError }) => { - if (status.isFinalized) { - if (dispatchError) { - if (dispatchError.isModule) { - const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule); - reject(new Error(`${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`)); - } else { - reject(new Error(dispatchError.toString())); - } - } else { - resolve(); - } - } - } - ); - }); - - setSuccess(`Successfully unstaked ${unstakeAmount} ${pool.stakedAsset}!`); - setUnstakeAmount(''); - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to unstake'); - } finally { - setIsProcessing(false); - } - }; - - const handleHarvest = async () => { - if (!assetHubApi || !selectedAccount || selectedPool === null) return; - - setIsProcessing(true); - setError(null); - setSuccess(null); - - try { - const injector = await web3FromAddress(selectedAccount.address); - const tx = assetHubApi.tx.assetRewards.harvestRewards(selectedPool); - - await new Promise((resolve, reject) => { - tx.signAndSend( - selectedAccount.address, - { signer: injector.signer }, - ({ status, dispatchError }) => { - if (status.isFinalized) { - if (dispatchError) { - if (dispatchError.isModule) { - const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule); - reject(new Error(`${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`)); - } else { - reject(new Error(dispatchError.toString())); - } - } else { - resolve(); - } - } - } - ); - }); - - setSuccess('Successfully harvested rewards!'); - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to harvest rewards'); - } finally { - setIsProcessing(false); - } - }; - - if (!isOpen) return null; - - const currentPool = pools.find(p => p.poolId === selectedPool); - - return ( -
-
-
-

LP Staking

- -
- - {error && ( - - - {error} - - )} - - {success && ( - - {success} - - )} - - {isLoading ? ( -
- -

Loading staking pools...

-
- ) : pools.length === 0 ? ( - - - - No staking pools found. Admin needs to create them first. - - - ) : ( - <> - {/* Pool Selection */} -
- - -
- - {/* Pool Stats */} - {currentPool && ( -
-
- Total Staked: - {formatAmount(currentPool.totalStaked)} LP -
-
- Your Staked: - {formatAmount(currentPool.userStaked)} LP -
-
- Your LP Balance: - {formatAmount(currentPool.lpBalance)} LP -
-
- Reward Rate: - {formatAmount(currentPool.rewardRatePerBlock)} PEZ/block -
-
- )} - - {/* Tabs */} - - - - - Stake - - - - Unstake - - - - Harvest - - - - -
-
- -
- setStakeAmount(e.target.value)} - placeholder="0.0" - className="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-3 text-white" - disabled={isProcessing} - /> - -
-
- -
-
- - -
-
- -
- setUnstakeAmount(e.target.value)} - placeholder="0.0" - className="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-3 text-white" - disabled={isProcessing} - /> - -
-
- -
-
- - -
-
-

Pending Rewards

-

- {currentPool ? formatAmount(currentPool.pendingRewards) : '0'} PEZ -

-
- -
-
-
- - )} -
-
- ); -}; diff --git a/web/src/components/PoolDashboard.tsx b/web/src/components/PoolDashboard.tsx index e3d5183a..6343d643 100644 --- a/web/src/components/PoolDashboard.tsx +++ b/web/src/components/PoolDashboard.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { TrendingUp, Droplet, DollarSign, Percent, Info, AlertTriangle, BarChart3, Clock, Lock } from 'lucide-react'; +import { TrendingUp, Droplet, DollarSign, Percent, Info, AlertTriangle, BarChart3, Clock } from 'lucide-react'; import { Card } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Alert, AlertDescription } from '@/components/ui/alert'; @@ -11,7 +11,6 @@ import { ASSET_IDS, getAssetSymbol } from '@pezkuwi/lib/wallet'; import { NATIVE_TOKEN_ID } from '@/types/dex'; import { AddLiquidityModal } from '@/components/AddLiquidityModal'; import { RemoveLiquidityModal } from '@/components/RemoveLiquidityModal'; -import { LPStakingModal } from '@/components/LPStakingModal'; // Helper function to convert asset IDs to user-friendly display names // Users should only see HEZ, PEZ, USDT - wrapped tokens are backend details @@ -52,7 +51,6 @@ const PoolDashboard = () => { const [error, setError] = useState(null); const [isAddLiquidityModalOpen, setIsAddLiquidityModalOpen] = useState(false); const [isRemoveLiquidityModalOpen, setIsRemoveLiquidityModalOpen] = useState(false); - const [isStakingModalOpen, setIsStakingModalOpen] = useState(false); // Pool selection state const [availablePools, setAvailablePools] = useState>([]); @@ -579,20 +577,13 @@ const PoolDashboard = () => { -
+
-
); };