diff --git a/src/components/AddLiquidityModal.tsx b/src/components/AddLiquidityModal.tsx index 5b04df92..41e31b3b 100644 --- a/src/components/AddLiquidityModal.tsx +++ b/src/components/AddLiquidityModal.tsx @@ -5,33 +5,68 @@ import { usePolkadot } from '@/contexts/PolkadotContext'; import { useWallet } from '@/contexts/WalletContext'; import { Button } from '@/components/ui/button'; import { Alert, AlertDescription } from '@/components/ui/alert'; +import { ASSET_IDS, getAssetSymbol } from '@/lib/wallet'; interface AddLiquidityModalProps { isOpen: boolean; onClose: () => void; + asset0?: number; // Pool's first asset ID + asset1?: number; // Pool's second asset ID } -export const AddLiquidityModal: React.FC = ({ isOpen, onClose }) => { +// Helper to get display name (users see HEZ not wHEZ, PEZ, USDT not wUSDT) +const getDisplayName = (assetId: number): string => { + if (assetId === ASSET_IDS.WHEZ || assetId === 0) return 'HEZ'; + if (assetId === ASSET_IDS.PEZ || assetId === 1) return 'PEZ'; + if (assetId === ASSET_IDS.WUSDT || assetId === 2) return 'USDT'; + return getAssetSymbol(assetId); +}; + +// Helper to get balance key for the asset +const getBalanceKey = (assetId: number): string => { + if (assetId === ASSET_IDS.WHEZ || assetId === 0) return 'HEZ'; + if (assetId === ASSET_IDS.PEZ || assetId === 1) return 'PEZ'; + if (assetId === ASSET_IDS.WUSDT || assetId === 2) return 'USDT'; + return getAssetSymbol(assetId); +}; + +// Helper to get decimals for asset +const getAssetDecimals = (assetId: number): number => { + if (assetId === ASSET_IDS.WUSDT || assetId === 2) return 6; // wUSDT has 6 decimals + return 12; // wHEZ, PEZ have 12 decimals +}; + +export const AddLiquidityModal: React.FC = ({ + isOpen, + onClose, + asset0 = 0, // Default to wHEZ + asset1 = 1 // Default to PEZ +}) => { const { api, selectedAccount, isApiReady } = usePolkadot(); const { balances, refreshBalances } = useWallet(); - const [whezAmount, setWhezAmount] = useState(''); - const [pezAmount, setPezAmount] = useState(''); + const [amount0, setAmount0] = useState(''); + const [amount1, setAmount1] = useState(''); const [currentPrice, setCurrentPrice] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(false); + // Get asset details + const asset0Name = getDisplayName(asset0); + const asset1Name = getDisplayName(asset1); + const asset0BalanceKey = getBalanceKey(asset0); + const asset1BalanceKey = getBalanceKey(asset1); + const asset0Decimals = getAssetDecimals(asset0); + const asset1Decimals = getAssetDecimals(asset1); + // Fetch current pool price useEffect(() => { if (!api || !isApiReady || !isOpen) return; const fetchPoolPrice = async () => { try { - const asset1 = 0; // wHEZ - const asset2 = 1; // PEZ - - const poolId = [asset1, asset2]; + const poolId = [asset0, asset1]; const poolInfo = await api.query.assetConversion.pools(poolId); if (poolInfo.isSome) { @@ -39,30 +74,24 @@ export const AddLiquidityModal: React.FC = ({ isOpen, on const { stringToU8a } = await import('@polkadot/util'); const { blake2AsU8a } = await import('@polkadot/util-crypto'); - // PalletId for AssetConversion: "py/ascon" (8 bytes) const PALLET_ID = stringToU8a('py/ascon'); - - // Create PoolId tuple (u32, u32) - const poolIdType = api.createType('(u32, u32)', [asset1, asset2]); - - // Create (PalletId, PoolId) tuple: ([u8; 8], (u32, u32)) + const poolIdType = api.createType('(u32, u32)', [asset0, asset1]); const palletIdType = api.createType('[u8; 8]', PALLET_ID); const fullTuple = api.createType('([u8; 8], (u32, u32))', [palletIdType, poolIdType]); - // Hash the SCALE-encoded tuple const accountHash = blake2AsU8a(fullTuple.toU8a(), 256); const poolAccountId = api.createType('AccountId32', accountHash); // Get reserves - const whezBalanceData = await api.query.assets.account(asset1, poolAccountId); - const pezBalanceData = await api.query.assets.account(asset2, poolAccountId); + const balance0Data = await api.query.assets.account(asset0, poolAccountId); + const balance1Data = await api.query.assets.account(asset1, poolAccountId); - if (whezBalanceData.isSome && pezBalanceData.isSome) { - const whezData = whezBalanceData.unwrap().toJSON() as any; - const pezData = pezBalanceData.unwrap().toJSON() as any; + if (balance0Data.isSome && balance1Data.isSome) { + const data0 = balance0Data.unwrap().toJSON() as any; + const data1 = balance1Data.unwrap().toJSON() as any; - const reserve0 = Number(whezData.balance) / 1e12; - const reserve1 = Number(pezData.balance) / 1e12; + const reserve0 = Number(data0.balance) / Math.pow(10, asset0Decimals); + const reserve1 = Number(data1.balance) / Math.pow(10, asset1Decimals); setCurrentPrice(reserve1 / reserve0); } @@ -73,40 +102,43 @@ export const AddLiquidityModal: React.FC = ({ isOpen, on }; fetchPoolPrice(); - }, [api, isApiReady, isOpen]); + }, [api, isApiReady, isOpen, asset0, asset1, asset0Decimals, asset1Decimals]); - // Auto-calculate PEZ amount based on wHEZ input + // Auto-calculate asset1 amount based on asset0 input useEffect(() => { - if (whezAmount && currentPrice) { - const calculatedPez = parseFloat(whezAmount) * currentPrice; - setPezAmount(calculatedPez.toFixed(4)); - } else if (!whezAmount) { - setPezAmount(''); + if (amount0 && currentPrice) { + const calculated = parseFloat(amount0) * currentPrice; + setAmount1(calculated.toFixed(asset1Decimals === 6 ? 2 : 4)); + } else if (!amount0) { + setAmount1(''); } - }, [whezAmount, currentPrice]); + }, [amount0, currentPrice, asset1Decimals]); const handleAddLiquidity = async () => { - if (!api || !selectedAccount || !whezAmount || !pezAmount) return; + if (!api || !selectedAccount || !amount0 || !amount1) return; setIsLoading(true); setError(null); try { // Validate amounts - if (parseFloat(whezAmount) <= 0 || parseFloat(pezAmount) <= 0) { + if (parseFloat(amount0) <= 0 || parseFloat(amount1) <= 0) { setError('Please enter valid amounts'); setIsLoading(false); return; } - if (parseFloat(whezAmount) > whezBalance) { - setError('Insufficient HEZ balance'); + const balance0 = (balances as any)[asset0BalanceKey] || 0; + const balance1 = (balances as any)[asset1BalanceKey] || 0; + + if (parseFloat(amount0) > balance0) { + setError(`Insufficient ${asset0Name} balance`); setIsLoading(false); return; } - if (parseFloat(pezAmount) > pezBalance) { - setError('Insufficient PEZ balance'); + if (parseFloat(amount1) > balance1) { + setError(`Insufficient ${asset1Name} balance`); setIsLoading(false); return; } @@ -114,29 +146,45 @@ export const AddLiquidityModal: React.FC = ({ isOpen, on // Get the signer from the extension const injector = await web3FromAddress(selectedAccount.address); - const whezAmountBN = BigInt(Math.floor(parseFloat(whezAmount) * 1e12)); - const pezAmountBN = BigInt(Math.floor(parseFloat(pezAmount) * 1e12)); + // Convert amounts to proper decimals + const amount0BN = BigInt(Math.floor(parseFloat(amount0) * Math.pow(10, asset0Decimals))); + const amount1BN = BigInt(Math.floor(parseFloat(amount1) * Math.pow(10, asset1Decimals))); - // Min amounts (90% of desired to account for slippage - more tolerance for AMM) - const minWhezBN = (whezAmountBN * BigInt(90)) / BigInt(100); - const minPezBN = (pezAmountBN * BigInt(90)) / BigInt(100); + // Min amounts (90% of desired to account for slippage) + const minAmount0BN = (amount0BN * BigInt(90)) / BigInt(100); + const minAmount1BN = (amount1BN * BigInt(90)) / BigInt(100); - // Need to wrap HEZ to wHEZ first - const wrapTx = api.tx.tokenWrapper.wrap(whezAmountBN.toString()); + // Build transaction(s) + let tx; - // Add liquidity transaction - const addLiquidityTx = api.tx.assetConversion.addLiquidity( - 0, // asset1 (wHEZ) - 1, // asset2 (PEZ) - whezAmountBN.toString(), - pezAmountBN.toString(), - minWhezBN.toString(), - minPezBN.toString(), - selectedAccount.address - ); + // If asset0 is HEZ (0), need to wrap it first + if (asset0 === 0 || asset0 === ASSET_IDS.WHEZ) { + const wrapTx = api.tx.tokenWrapper.wrap(amount0BN.toString()); - // Batch transactions - const tx = api.tx.utility.batchAll([wrapTx, addLiquidityTx]); + const addLiquidityTx = api.tx.assetConversion.addLiquidity( + asset0, + asset1, + amount0BN.toString(), + amount1BN.toString(), + minAmount0BN.toString(), + minAmount1BN.toString(), + selectedAccount.address + ); + + // Batch wrap + add liquidity + tx = api.tx.utility.batchAll([wrapTx, addLiquidityTx]); + } else { + // Direct add liquidity (no wrapping needed) + tx = api.tx.assetConversion.addLiquidity( + asset0, + asset1, + amount0BN.toString(), + amount1BN.toString(), + minAmount0BN.toString(), + minAmount1BN.toString(), + selectedAccount.address + ); + } await tx.signAndSend( selectedAccount.address, @@ -167,7 +215,6 @@ export const AddLiquidityModal: React.FC = ({ isOpen, on } } - // Also check events for more details events.forEach(({ event }) => { if (api.events.system.ExtrinsicFailed.is(event)) { console.error('ExtrinsicFailed event:', event.toHuman()); @@ -180,8 +227,8 @@ export const AddLiquidityModal: React.FC = ({ isOpen, on console.log('Transaction successful'); setSuccess(true); setIsLoading(false); - setWhezAmount(''); - setPezAmount(''); + setAmount0(''); + setAmount1(''); refreshBalances(); setTimeout(() => { @@ -201,8 +248,8 @@ export const AddLiquidityModal: React.FC = ({ isOpen, on if (!isOpen) return null; - const whezBalance = balances.HEZ || 0; - const pezBalance = balances.PEZ || 0; + const balance0 = (balances as any)[asset0BalanceKey] || 0; + const balance1 = (balances as any)[asset1BalanceKey] || 0; return (
@@ -233,33 +280,34 @@ export const AddLiquidityModal: React.FC = ({ isOpen, on - Add liquidity to earn 3% fees from all swaps. Your HEZ will be automatically wrapped to wHEZ. + Add liquidity to earn 3% fees from all swaps. + {(asset0 === 0 || asset0 === ASSET_IDS.WHEZ) && ' Your HEZ will be automatically wrapped to wHEZ.'}
- {/* HEZ Input */} + {/* Asset 0 Input */}
setWhezAmount(e.target.value)} + value={amount0} + onChange={(e) => setAmount0(e.target.value)} placeholder="0.0" className="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-blue-500" disabled={isLoading} />
- HEZ + {asset0Name}
- Balance: {whezBalance.toLocaleString()} + Balance: {balance0.toLocaleString()}
- {/* PEZ Input */} + {/* Asset 1 Input */}
- PEZ + {asset1Name}
- Balance: {pezBalance.toLocaleString()} + Balance: {balance1.toLocaleString()} - {currentPrice && `Rate: 1 HEZ = ${currentPrice.toFixed(4)} PEZ`} + {currentPrice && `Rate: 1 ${asset0Name} = ${currentPrice.toFixed(asset1Decimals === 6 ? 2 : 4)} ${asset1Name}`}
{/* Price Info */} - {whezAmount && pezAmount && ( + {amount0 && amount1 && (
Share of Pool @@ -315,10 +363,10 @@ export const AddLiquidityModal: React.FC = ({ isOpen, on onClick={handleAddLiquidity} disabled={ isLoading || - !whezAmount || - !pezAmount || - parseFloat(whezAmount) > whezBalance || - parseFloat(pezAmount) > pezBalance + !amount0 || + !amount1 || + parseFloat(amount0) > balance0 || + parseFloat(amount1) > balance1 } className="w-full bg-gradient-to-r from-green-600 to-blue-600 hover:from-green-700 hover:to-blue-700 h-12" > diff --git a/src/components/PoolDashboard.tsx b/src/components/PoolDashboard.tsx index 6b5be52a..ca9c3222 100644 --- a/src/components/PoolDashboard.tsx +++ b/src/components/PoolDashboard.tsx @@ -580,6 +580,8 @@ const PoolDashboard = () => { setIsAddLiquidityModalOpen(false)} + asset0={poolData?.asset0} + asset1={poolData?.asset1} /> {lpPosition && poolData && ( diff --git a/src/components/trading/PriceChart.tsx b/src/components/trading/PriceChart.tsx index 75f5af01..465db490 100644 --- a/src/components/trading/PriceChart.tsx +++ b/src/components/trading/PriceChart.tsx @@ -10,6 +10,13 @@ interface PriceChartProps { currentPrice: number; } +// Helper: Convert backend token symbols to user-facing display names +const getDisplayName = (token: string): string => { + if (token === 'wUSDT') return 'USDT'; + if (token === 'wHEZ') return 'HEZ'; + return token; // HEZ, PEZ, etc. remain the same +}; + export const PriceChart: React.FC = ({ fromToken, toToken, currentPrice }) => { const [timeframe, setTimeframe] = useState<'1H' | '24H' | '7D' | '30D'>('24H'); const [chartData, setChartData] = useState([]); @@ -83,7 +90,7 @@ export const PriceChart: React.FC = ({ fromToken, toToken, curr
- {fromToken}/{toToken} Price + {getDisplayName(fromToken)}/{getDisplayName(toToken)} Price
diff --git a/src/contexts/WalletContext.tsx b/src/contexts/WalletContext.tsx index 8a7bf9a0..3cc14006 100644 --- a/src/contexts/WalletContext.tsx +++ b/src/contexts/WalletContext.tsx @@ -13,7 +13,7 @@ interface TokenBalances { HEZ: string; PEZ: string; wHEZ: string; - wUSDT: string; + USDT: string; // User-facing key for wUSDT (backend uses wUSDT asset ID 2) } interface WalletContextType { @@ -44,7 +44,7 @@ export const WalletProvider: React.FC<{ children: React.ReactNode }> = ({ childr }); const [balance, setBalance] = useState('0'); - const [balances, setBalances] = useState({ HEZ: '0', PEZ: '0', wHEZ: '0', wUSDT: '0' }); + const [balances, setBalances] = useState({ HEZ: '0', PEZ: '0', wHEZ: '0', USDT: '0' }); const [error, setError] = useState(null); // Fetch all token balances when account changes @@ -120,7 +120,7 @@ export const WalletProvider: React.FC<{ children: React.ReactNode }> = ({ childr HEZ: hezBalance, PEZ: pezBalance, wHEZ: whezBalance, - wUSDT: wusdtBalance, + USDT: wusdtBalance, }); console.log('✅ Balances updated:', { HEZ: hezBalance, PEZ: pezBalance, wHEZ: whezBalance, wUSDT: wusdtBalance });