import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; 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 { t } = useTranslation(); 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'); 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; }; const lpTokenId = poolData.stakedAssetId?.interior?.x2?.[1]?.generalIndex ?? poolId; let userStaked = '0'; let pendingRewards = '0'; 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; rewardPerTokenPaid?: string } } }).unwrap().toJSON(); userStaked = stakeData.amount || '0'; } // Fetch pending rewards from the pallet try { const rewardsResult = await (assetHubApi.call as { assetRewardsApi?: { pendingRewards: (poolId: number, account: string) => Promise } }) .assetRewardsApi?.pendingRewards(poolId, selectedAccount.address); if (rewardsResult && typeof rewardsResult === 'object' && 'toString' in rewardsResult) { pendingRewards = rewardsResult.toString(); } } catch { // If runtime API not available, try direct calculation // pendingRewards stays 0 } 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 { // Ignore errors } } 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(t('lpStaking.fetchError')); } 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(t('lpStaking.stakeSuccess', { amount: stakeAmount, asset: pool.stakedAsset })); setStakeAmount(''); } catch (err) { setError(err instanceof Error ? err.message : t('lpStaking.stakeFailed')); } 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(t('lpStaking.unstakeSuccess', { amount: unstakeAmount, asset: pool.stakedAsset })); setUnstakeAmount(''); } catch (err) { setError(err instanceof Error ? err.message : t('lpStaking.unstakeFailed')); } 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(t('lpStaking.harvestSuccess')); } catch (err) { setError(err instanceof Error ? err.message : t('lpStaking.harvestFailed')); } finally { setIsProcessing(false); } }; if (!isOpen) return null; const currentPool = pools.find(p => p.poolId === selectedPool); return (

{t('lpStaking.title')}

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

{t('lpStaking.loading')}

) : pools.length === 0 ? ( {t('lpStaking.noPools')} ) : ( <>
{currentPool && (
{t('lpStaking.totalStaked')} {formatAmount(currentPool.totalStaked)} LP
{t('lpStaking.yourStaked')} {formatAmount(currentPool.userStaked)} LP
{t('lpStaking.yourLpBalance')} {formatAmount(currentPool.lpBalance)} LP
{t('lpStaking.rewardRate')} {formatAmount(currentPool.rewardRatePerBlock)} PEZ/block
)} {t('lpStaking.tabStake')} {t('lpStaking.tabUnstake')} {t('lpStaking.tabHarvest')}
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} />

{t('lpStaking.pendingRewards')}

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

)}
); };