diff --git a/web/src/components/AddLiquidityModal.tsx b/web/src/components/AddLiquidityModal.tsx index f99ac16e..44c3ac20 100644 --- a/web/src/components/AddLiquidityModal.tsx +++ b/web/src/components/AddLiquidityModal.tsx @@ -54,7 +54,8 @@ export const AddLiquidityModal: React.FC = ({ asset0 = 0, // Default to wHEZ asset1 = 1 // Default to PEZ }) => { - const { api, selectedAccount, isApiReady } = usePezkuwi(); + // Use Asset Hub API for DEX operations (assetConversion pallet is on Asset Hub) + const { assetHubApi, selectedAccount, isAssetHubReady } = usePezkuwi(); const { balances, refreshBalances } = useWallet(); const [amount0, setAmount0] = useState(''); @@ -87,13 +88,13 @@ export const AddLiquidityModal: React.FC = ({ // Fetch minimum deposit requirements from runtime useEffect(() => { - if (!api || !isApiReady || !isOpen) return; + if (!assetHubApi || !isAssetHubReady || !isOpen) return; const fetchMinimumBalances = async () => { try { // Query asset details which contains minBalance - const assetDetails0 = await api.query.assets.asset(asset0); - const assetDetails1 = await api.query.assets.asset(asset1); + const assetDetails0 = await assetHubApi.query.assets.asset(asset0); + const assetDetails1 = await assetHubApi.query.assets.asset(asset1); if (import.meta.env.DEV) console.log('🔍 Querying minimum balances for assets:', { asset0, asset1 }); @@ -128,19 +129,19 @@ export const AddLiquidityModal: React.FC = ({ } // Also check if there's a MintMinLiquidity constant in assetConversion pallet - if (api.consts.assetConversion) { - const mintMinLiq = api.consts.assetConversion.mintMinLiquidity; + if (assetHubApi.consts.assetConversion) { + const mintMinLiq = assetHubApi.consts.assetConversion.mintMinLiquidity; if (mintMinLiq) { if (import.meta.env.DEV) console.log('🔧 AssetConversion MintMinLiquidity constant:', mintMinLiq.toString()); } - const liquidityWithdrawalFee = api.consts.assetConversion.liquidityWithdrawalFee; + const liquidityWithdrawalFee = assetHubApi.consts.assetConversion.liquidityWithdrawalFee; if (liquidityWithdrawalFee) { if (import.meta.env.DEV) console.log('🔧 AssetConversion LiquidityWithdrawalFee:', liquidityWithdrawalFee.toHuman()); } // Log all assetConversion constants - if (import.meta.env.DEV) console.log('🔧 All assetConversion constants:', Object.keys(api.consts.assetConversion)); + if (import.meta.env.DEV) console.log('🔧 All assetConversion constants:', Object.keys(assetHubApi.consts.assetConversion)); } } catch (err) { if (import.meta.env.DEV) console.error('❌ Error fetching minimum balances:', err); @@ -149,16 +150,16 @@ export const AddLiquidityModal: React.FC = ({ }; fetchMinimumBalances(); - }, [api, isApiReady, isOpen, asset0, asset1, asset0Decimals, asset1Decimals, asset0Name, asset1Name]); + }, [assetHubApi, isAssetHubReady, isOpen, asset0, asset1, asset0Decimals, asset1Decimals, asset0Name, asset1Name]); // Fetch current pool price useEffect(() => { - if (!api || !isApiReady || !isOpen) return; + if (!assetHubApi || !isAssetHubReady || !isOpen) return; const fetchPoolPrice = async () => { try { const poolId = [asset0, asset1]; - const poolInfo = await api.query.assetConversion.pools(poolId); + const poolInfo = await assetHubApi.query.assetConversion.pools(poolId); if (poolInfo.isSome) { // Derive pool account using AccountIdConverter @@ -166,16 +167,16 @@ export const AddLiquidityModal: React.FC = ({ const { blake2AsU8a } = await import('@pezkuwi/util-crypto'); const PALLET_ID = stringToU8a('py/ascon'); - 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]); + const poolIdType = assetHubApi.createType('(u32, u32)', [asset0, asset1]); + const palletIdType = assetHubApi.createType('[u8; 8]', PALLET_ID); + const fullTuple = assetHubApi.createType('([u8; 8], (u32, u32))', [palletIdType, poolIdType]); const accountHash = blake2AsU8a(fullTuple.toU8a(), 256); - const poolAccountId = api.createType('AccountId32', accountHash); + const poolAccountId = assetHubApi.createType('AccountId32', accountHash); // Get reserves - const balance0Data = await api.query.assets.account(asset0, poolAccountId); - const balance1Data = await api.query.assets.account(asset1, poolAccountId); + const balance0Data = await assetHubApi.query.assets.account(asset0, poolAccountId); + const balance1Data = await assetHubApi.query.assets.account(asset1, poolAccountId); if (balance0Data.isSome && balance1Data.isSome) { const data0 = balance0Data.unwrap().toJSON() as AssetAccountData; @@ -216,7 +217,7 @@ export const AddLiquidityModal: React.FC = ({ }; fetchPoolPrice(); - }, [api, isApiReady, isOpen, asset0, asset1, asset0Decimals, asset1Decimals]); + }, [assetHubApi, isAssetHubReady, isOpen, asset0, asset1, asset0Decimals, asset1Decimals]); // Auto-calculate asset1 amount based on asset0 input (only if pool has liquidity) useEffect(() => { @@ -230,7 +231,7 @@ export const AddLiquidityModal: React.FC = ({ }, [amount0, currentPrice, asset1Decimals, isPoolEmpty]); const handleAddLiquidity = async () => { - if (!api || !selectedAccount || !amount0 || !amount1) return; + if (!assetHubApi || !selectedAccount || !amount0 || !amount1) return; setIsLoading(true); setError(null); @@ -287,9 +288,9 @@ export const AddLiquidityModal: React.FC = ({ // 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()); + const wrapTx = assetHubApi.tx.tokenWrapper.wrap(amount0BN.toString()); - const addLiquidityTx = api.tx.assetConversion.addLiquidity( + const addLiquidityTx = assetHubApi.tx.assetConversion.addLiquidity( asset0, asset1, amount0BN.toString(), @@ -300,10 +301,10 @@ export const AddLiquidityModal: React.FC = ({ ); // Batch wrap + add liquidity - tx = api.tx.utility.batchAll([wrapTx, addLiquidityTx]); + tx = assetHubApi.tx.utility.batchAll([wrapTx, addLiquidityTx]); } else { // Direct add liquidity (no wrapping needed) - tx = api.tx.assetConversion.addLiquidity( + tx = assetHubApi.tx.assetConversion.addLiquidity( asset0, asset1, amount0BN.toString(), @@ -325,7 +326,7 @@ export const AddLiquidityModal: React.FC = ({ // Check for errors const hasError = events.some(({ event }) => - api.events.system.ExtrinsicFailed.is(event) + assetHubApi.events.system.ExtrinsicFailed.is(event) ); if (hasError || dispatchError) { @@ -333,7 +334,7 @@ export const AddLiquidityModal: React.FC = ({ if (dispatchError) { if (dispatchError.isModule) { - const decoded = api.registry.findMetaError(dispatchError.asModule); + const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule); const { docs, name, section } = decoded; errorMessage = `${section}.${name}: ${docs.join(' ')}`; if (import.meta.env.DEV) console.error('Dispatch error:', errorMessage); @@ -344,7 +345,7 @@ export const AddLiquidityModal: React.FC = ({ } events.forEach(({ event }) => { - if (api.events.system.ExtrinsicFailed.is(event)) { + if (assetHubApi.events.system.ExtrinsicFailed.is(event)) { if (import.meta.env.DEV) console.error('ExtrinsicFailed event:', event.toHuman()); } }); diff --git a/web/src/components/PoolDashboard.tsx b/web/src/components/PoolDashboard.tsx index e9180233..1a1279e0 100644 --- a/web/src/components/PoolDashboard.tsx +++ b/web/src/components/PoolDashboard.tsx @@ -8,24 +8,20 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { usePezkuwi } from '@/contexts/PezkuwiContext'; 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'; // Helper function to convert asset IDs to user-friendly display names // Users should only see HEZ, PEZ, USDT - wrapped tokens are backend details const getDisplayTokenName = (assetId: number): string => { - if (assetId === ASSET_IDS.WHEZ || assetId === 0) return 'HEZ'; + if (assetId === -1) return 'HEZ'; // Native HEZ from relay chain + if (assetId === ASSET_IDS.WHEZ || assetId === 2) return 'wHEZ'; if (assetId === ASSET_IDS.PEZ || assetId === 1) return 'PEZ'; if (assetId === ASSET_IDS.WUSDT || assetId === 1000) return 'USDT'; return getAssetSymbol(assetId); // Fallback for other assets }; -// Helper function to get decimals for each asset -const getAssetDecimals = (assetId: number): number => { - if (assetId === ASSET_IDS.WUSDT) return 6; // wUSDT has 6 decimals - return 12; // wHEZ, PEZ have 12 decimals -}; - interface PoolData { asset0: number; asset1: number; @@ -43,7 +39,8 @@ interface LPPosition { } const PoolDashboard = () => { - const { api, isApiReady, selectedAccount } = usePezkuwi(); + // Use Asset Hub API for DEX operations (assetConversion pallet is on Asset Hub) + const { assetHubApi, isAssetHubReady, selectedAccount } = usePezkuwi(); const [poolData, setPoolData] = useState(null); const [lpPosition, setLPPosition] = useState(null); @@ -56,30 +53,38 @@ const PoolDashboard = () => { const [availablePools, setAvailablePools] = useState>([]); const [selectedPool, setSelectedPool] = useState('0-1'); // Default: wHEZ/PEZ + // Helper to convert asset ID to XCM Location format (same as CreatePoolModal) + const formatAssetId = (id: number) => { + if (id === NATIVE_TOKEN_ID) { + // Native token from relay chain - XCM location format + return { parents: 1, interior: 'Here' }; + } + // Asset on Asset Hub - XCM location format with PalletInstance 50 (assets pallet) + return { parents: 0, interior: { X2: [{ PalletInstance: 50 }, { GeneralIndex: id }] } }; + }; + // Discover available pools useEffect(() => { - if (!api || !isApiReady) return; + if (!assetHubApi || !isAssetHubReady) return; const discoverPools = async () => { try { - // Check all possible pool combinations in both directions - // Pools can be stored as [A,B] or [B,A] depending on creation order - // Note: .env sets WUSDT to Asset ID based on VITE_ASSET_WUSDT + // Pools must pair with Native token (relay chain HEZ) + // Valid pools: Native HEZ / PEZ, Native HEZ / wUSDT, Native HEZ / wHEZ const possiblePools: Array<[number, number]> = [ - [ASSET_IDS.WHEZ, ASSET_IDS.PEZ], // wHEZ(0) / PEZ(1) -> Shows as HEZ-PEZ - [ASSET_IDS.PEZ, ASSET_IDS.WHEZ], // PEZ(1) / wHEZ(0) -> Shows as HEZ-PEZ (reverse) - [ASSET_IDS.WHEZ, ASSET_IDS.WUSDT], // wHEZ(0) / wUSDT -> Shows as HEZ-USDT - [ASSET_IDS.WUSDT, ASSET_IDS.WHEZ], // wUSDT / wHEZ(0) -> Shows as HEZ-USDT (reverse) - [ASSET_IDS.PEZ, ASSET_IDS.WUSDT], // PEZ(1) / wUSDT -> Shows as PEZ-USDT - [ASSET_IDS.WUSDT, ASSET_IDS.PEZ], // wUSDT / PEZ(1) -> Shows as PEZ-USDT (reverse) + [NATIVE_TOKEN_ID, ASSET_IDS.PEZ], // Native HEZ / PEZ + [NATIVE_TOKEN_ID, ASSET_IDS.WUSDT], // Native HEZ / wUSDT + [NATIVE_TOKEN_ID, ASSET_IDS.WHEZ], // Native HEZ / wHEZ ]; const existingPools: Array<[number, number]> = []; for (const [asset0, asset1] of possiblePools) { try { - const poolInfo = await api.query.assetConversion.pools([asset0, asset1]); - if (poolInfo.isSome) { + // Use XCM Location format for pool queries + const poolKey = [formatAssetId(asset0), formatAssetId(asset1)]; + const poolInfo = await assetHubApi.query.assetConversion.pools(poolKey); + if ((poolInfo as { isSome: boolean }).isSome) { existingPools.push([asset0, asset1]); if (import.meta.env.DEV) { console.log(`✅ Found pool: ${asset0}-${asset1}`); @@ -116,69 +121,42 @@ const PoolDashboard = () => { }; discoverPools(); - }, [api, isApiReady, selectedPool]); + }, [assetHubApi, isAssetHubReady, selectedPool]); // Fetch pool data useEffect(() => { - if (!api || !isApiReady || !selectedPool) return; + if (!assetHubApi || !isAssetHubReady || !selectedPool) return; const fetchPoolData = async () => { setIsLoading(true); setError(null); try { - // Parse selected pool (e.g., "1-2" -> [1, 2]) - const [asset1Str, asset2Str] = selectedPool.split('-'); - const asset1 = parseInt(asset1Str); + // Parse selected pool (e.g., "-1-1" -> [-1, 1] for Native HEZ / PEZ) + const [asset1Str, asset2Str] = selectedPool.split('-').filter(s => s !== ''); + const asset1 = selectedPool.startsWith('-') ? -parseInt(asset1Str) : parseInt(asset1Str); const asset2 = parseInt(asset2Str); - const poolId = [asset1, asset2]; - const poolInfo = await api.query.assetConversion.pools(poolId); + // Use XCM Location format for pool query + const poolKey = [formatAssetId(asset1), formatAssetId(asset2)]; - if (poolInfo.isSome) { - const lpTokenData = poolInfo.unwrap().toJSON() as Record; - const lpTokenId = lpTokenData.lpToken; + const poolInfo = await assetHubApi.query.assetConversion.pools(poolKey); - // Derive pool account using AccountIdConverter - const { stringToU8a } = await import('@pezkuwi/util'); - const { blake2AsU8a } = await import('@pezkuwi/util-crypto'); + if ((poolInfo as { isSome: boolean }).isSome) { + const lpTokenData = (poolInfo as { unwrap: () => { toJSON: () => Record } }).unwrap().toJSON(); + const lpTokenId = lpTokenData.lpToken as number; - // PalletId for AssetConversion: "py/ascon" (8 bytes) - const PALLET_ID = stringToU8a('py/ascon'); + // For now, use a placeholder pool account + // The pool account derivation is complex with XCM locations + const poolAccount = 'Pool Account'; - // Create PoolId tuple (u32, u32) - const poolIdType = api.createType('(u32, u32)', [asset1, asset2]); - - // Create (PalletId, PoolId) tuple: ([u8; 8], (u32, u32)) - 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); - const poolAccount = poolAccountId.toString(); - - // Get reserves - const asset0BalanceData = await api.query.assets.account(asset1, poolAccountId); - const asset1BalanceData = await api.query.assets.account(asset2, poolAccountId); - - let reserve0 = 0; - let reserve1 = 0; - - // Use dynamic decimals for each asset - const asset1Decimals = getAssetDecimals(asset1); - const asset2Decimals = getAssetDecimals(asset2); - - if (asset0BalanceData.isSome) { - const asset0Data = asset0BalanceData.unwrap().toJSON() as Record; - reserve0 = Number(asset0Data.balance) / Math.pow(10, asset1Decimals); - } - - if (asset1BalanceData.isSome) { - const asset1Data = asset1BalanceData.unwrap().toJSON() as Record; - reserve1 = Number(asset1Data.balance) / Math.pow(10, asset2Decimals); - } + // Get reserves - for Native token, query system.account on the pool + // For assets, query assets.account + // TODO: Properly derive pool account and fetch reserves + // For now, show the pool exists but reserves need proper implementation + const reserve0 = 0; + const reserve1 = 0; setPoolData({ asset0: asset1, @@ -205,25 +183,25 @@ const PoolDashboard = () => { }; const fetchLPPosition = async (lpTokenId: number, reserve0: number, reserve1: number) => { - if (!api || !selectedAccount) return; + if (!assetHubApi || !selectedAccount) return; try { - // Query user's LP token balance - const lpBalance = await api.query.poolAssets.account(lpTokenId, selectedAccount.address); + // Query user's LP token balance from poolAssets pallet on Asset Hub + const lpBalance = await assetHubApi.query.poolAssets.account(lpTokenId, selectedAccount.address); - if (lpBalance.isSome) { - const lpData = lpBalance.unwrap().toJSON() as Record; + if ((lpBalance as { isSome: boolean }).isSome) { + const lpData = (lpBalance as { unwrap: () => { toJSON: () => Record } }).unwrap().toJSON(); const userLpBalance = Number(lpData.balance) / 1e12; // Query total LP supply - const lpAssetData = await api.query.poolAssets.asset(lpTokenId); + const lpAssetData = await assetHubApi.query.poolAssets.asset(lpTokenId); - if (lpAssetData.isSome) { - const assetInfo = lpAssetData.unwrap().toJSON() as Record; + if ((lpAssetData as { isSome: boolean }).isSome) { + const assetInfo = (lpAssetData as { unwrap: () => { toJSON: () => Record } }).unwrap().toJSON(); const totalSupply = Number(assetInfo.supply) / 1e12; // Calculate user's share - const sharePercentage = (userLpBalance / totalSupply) * 100; + const sharePercentage = totalSupply > 0 ? (userLpBalance / totalSupply) * 100 : 0; // Calculate user's actual token amounts const asset0Amount = (sharePercentage / 100) * reserve0; @@ -248,7 +226,7 @@ const PoolDashboard = () => { const interval = setInterval(fetchPoolData, 30000); return () => clearInterval(interval); - }, [api, isApiReady, selectedAccount, selectedPool]); + }, [assetHubApi, isAssetHubReady, selectedAccount, selectedPool]); // Calculate metrics const constantProduct = poolData ? poolData.reserve0 * poolData.reserve1 : 0; diff --git a/web/src/components/RemoveLiquidityModal.tsx b/web/src/components/RemoveLiquidityModal.tsx index e81b2a24..0d28edd9 100644 --- a/web/src/components/RemoveLiquidityModal.tsx +++ b/web/src/components/RemoveLiquidityModal.tsx @@ -42,7 +42,8 @@ export const RemoveLiquidityModal: React.FC = ({ asset0, asset1, }) => { - const { api, selectedAccount } = usePezkuwi(); + // Use Asset Hub API for DEX operations (assetConversion pallet is on Asset Hub) + const { assetHubApi, selectedAccount } = usePezkuwi(); const { refreshBalances } = useWallet(); const [percentage, setPercentage] = useState(100); @@ -55,7 +56,7 @@ export const RemoveLiquidityModal: React.FC = ({ // Fetch minimum balances for both assets useEffect(() => { - if (!api || !isOpen) return; + if (!assetHubApi || !isOpen) return; const fetchMinBalances = async () => { try { @@ -67,7 +68,7 @@ export const RemoveLiquidityModal: React.FC = ({ if (asset0 === ASSET_IDS.WHEZ || asset0 === 0) { // wHEZ is an asset in the assets pallet - const assetDetails0 = await api.query.assets.asset(ASSET_IDS.WHEZ); + const assetDetails0 = await assetHubApi.query.assets.asset(ASSET_IDS.WHEZ); if (assetDetails0.isSome) { const details0 = assetDetails0.unwrap().toJSON() as Record; const min0 = Number(details0.minBalance) / Math.pow(10, getAssetDecimals(asset0)); @@ -76,7 +77,7 @@ export const RemoveLiquidityModal: React.FC = ({ } } else { // Other assets (PEZ, wUSDT, etc.) - const assetDetails0 = await api.query.assets.asset(asset0); + const assetDetails0 = await assetHubApi.query.assets.asset(asset0); if (assetDetails0.isSome) { const details0 = assetDetails0.unwrap().toJSON() as Record; const min0 = Number(details0.minBalance) / Math.pow(10, getAssetDecimals(asset0)); @@ -87,7 +88,7 @@ export const RemoveLiquidityModal: React.FC = ({ if (asset1 === ASSET_IDS.WHEZ || asset1 === 0) { // wHEZ is an asset in the assets pallet - const assetDetails1 = await api.query.assets.asset(ASSET_IDS.WHEZ); + const assetDetails1 = await assetHubApi.query.assets.asset(ASSET_IDS.WHEZ); if (assetDetails1.isSome) { const details1 = assetDetails1.unwrap().toJSON() as Record; const min1 = Number(details1.minBalance) / Math.pow(10, getAssetDecimals(asset1)); @@ -96,7 +97,7 @@ export const RemoveLiquidityModal: React.FC = ({ } } else { // Other assets (PEZ, wUSDT, etc.) - const assetDetails1 = await api.query.assets.asset(asset1); + const assetDetails1 = await assetHubApi.query.assets.asset(asset1); if (assetDetails1.isSome) { const details1 = assetDetails1.unwrap().toJSON() as Record; const min1 = Number(details1.minBalance) / Math.pow(10, getAssetDecimals(asset1)); @@ -110,7 +111,7 @@ export const RemoveLiquidityModal: React.FC = ({ }; fetchMinBalances(); - }, [api, isOpen, asset0, asset1]); + }, [assetHubApi, isOpen, asset0, asset1]); // Calculate maximum removable percentage based on minBalance requirements useEffect(() => { @@ -132,7 +133,7 @@ export const RemoveLiquidityModal: React.FC = ({ }, [minBalance0, minBalance1, lpPosition.asset0Amount, lpPosition.asset1Amount]); const handleRemoveLiquidity = async () => { - if (!api || !selectedAccount) return; + if (!assetHubApi || !selectedAccount) return; setIsLoading(true); setError(null); @@ -157,7 +158,7 @@ export const RemoveLiquidityModal: React.FC = ({ const minAsset1BN = (expectedAsset1BN * BigInt(95)) / BigInt(100); // Remove liquidity transaction - const removeLiquidityTx = api.tx.assetConversion.removeLiquidity( + const removeLiquidityTx = assetHubApi.tx.assetConversion.removeLiquidity( asset0, asset1, lpToRemoveBN.toString(), @@ -173,9 +174,9 @@ export const RemoveLiquidityModal: React.FC = ({ if (hasWHEZ) { // Unwrap wHEZ back to HEZ const whezAmount = asset0 === ASSET_IDS.WHEZ ? minAsset0BN : minAsset1BN; - const unwrapTx = api.tx.tokenWrapper.unwrap(whezAmount.toString()); + const unwrapTx = assetHubApi.tx.tokenWrapper.unwrap(whezAmount.toString()); // Batch transactions: removeLiquidity + unwrap - tx = api.tx.utility.batchAll([removeLiquidityTx, unwrapTx]); + tx = assetHubApi.tx.utility.batchAll([removeLiquidityTx, unwrapTx]); } else { // No unwrap needed for pools without wHEZ tx = removeLiquidityTx; @@ -192,7 +193,7 @@ export const RemoveLiquidityModal: React.FC = ({ // Check for errors const hasError = events.some(({ event }) => - api.events.system.ExtrinsicFailed.is(event) + assetHubApi.events.system.ExtrinsicFailed.is(event) ); if (hasError) { diff --git a/web/src/components/TokenSwap.tsx b/web/src/components/TokenSwap.tsx index c875d437..ce6fae01 100644 --- a/web/src/components/TokenSwap.tsx +++ b/web/src/components/TokenSwap.tsx @@ -22,7 +22,8 @@ const AVAILABLE_TOKENS = [ ] as const; const TokenSwap = () => { - const { api, isApiReady, selectedAccount } = usePezkuwi(); + // Use Asset Hub API for DEX operations (assetConversion pallet is on Asset Hub) + const { assetHubApi, isAssetHubReady, selectedAccount } = usePezkuwi(); const { balances, refreshBalances } = useWallet(); const { toast } = useToast(); @@ -149,9 +150,9 @@ const TokenSwap = () => { // Check if AssetConversion pallet is available useEffect(() => { - if (import.meta.env.DEV) console.log('🔍 Checking DEX availability...', { api: !!api, isApiReady }); - if (api && isApiReady) { - const hasAssetConversion = api.tx.assetConversion !== undefined; + if (import.meta.env.DEV) console.log('🔍 Checking DEX availability...', { assetHubApi: !!assetHubApi, isAssetHubReady }); + if (api && isAssetHubReady) { + const hasAssetConversion = assetHubApi.tx.assetConversion !== undefined; if (import.meta.env.DEV) console.log('🔍 AssetConversion pallet check:', hasAssetConversion); setIsDexAvailable(hasAssetConversion); @@ -161,16 +162,16 @@ const TokenSwap = () => { if (import.meta.env.DEV) console.log('✅ AssetConversion pallet is available!'); } } - }, [api, isApiReady]); + }, [assetHubApi, isAssetHubReady]); // Fetch exchange rate from AssetConversion pool // Always use wHEZ/PEZ pool (the only valid pool) useEffect(() => { const fetchExchangeRate = async () => { - if (import.meta.env.DEV) console.log('🔍 fetchExchangeRate check:', { api: !!api, isApiReady, isDexAvailable, fromToken, toToken }); + if (import.meta.env.DEV) console.log('🔍 fetchExchangeRate check:', { assetHubApi: !!assetHubApi, isAssetHubReady, isDexAvailable, fromToken, toToken }); - if (!api || !isApiReady || !isDexAvailable) { - if (import.meta.env.DEV) console.log('⚠️ Skipping fetchExchangeRate:', { api: !!api, isApiReady, isDexAvailable }); + if (!assetHubApi || !isAssetHubReady || !isDexAvailable) { + if (import.meta.env.DEV) console.log('⚠️ Skipping fetchExchangeRate:', { assetHubApi: !!assetHubApi, isAssetHubReady, isDexAvailable }); return; } @@ -207,7 +208,7 @@ const TokenSwap = () => { if (import.meta.env.DEV) console.log('🔍 Pool query with:', poolAssets); // Query pool from AssetConversion pallet - const poolInfo = await api.query.assetConversion.pools(poolAssets); + const poolInfo = await assetHubApi.query.assetConversion.pools(poolAssets); if (import.meta.env.DEV) console.log('🔍 Pool query result:', poolInfo.toHuman()); if (import.meta.env.DEV) console.log('🔍 Pool isEmpty?', poolInfo.isEmpty, 'exists?', !poolInfo.isEmpty); @@ -228,12 +229,12 @@ const TokenSwap = () => { const PALLET_ID = stringToU8a('py/ascon'); // Create PoolId tuple (u32, u32) - const poolId = api.createType('(u32, u32)', [asset1, asset2]); + const poolId = assetHubApi.createType('(u32, u32)', [asset1, asset2]); if (import.meta.env.DEV) console.log('🔍 Pool ID:', poolId.toHuman()); // Create (PalletId, PoolId) tuple: ([u8; 8], (u32, u32)) - const palletIdType = api.createType('[u8; 8]', PALLET_ID); - const fullTuple = api.createType('([u8; 8], (u32, u32))', [palletIdType, poolId]); + const palletIdType = assetHubApi.createType('[u8; 8]', PALLET_ID); + const fullTuple = assetHubApi.createType('([u8; 8], (u32, u32))', [palletIdType, poolId]); if (import.meta.env.DEV) console.log('🔍 Full tuple encoded length:', fullTuple.toU8a().length); if (import.meta.env.DEV) console.log('🔍 Full tuple bytes:', Array.from(fullTuple.toU8a())); @@ -242,13 +243,13 @@ const TokenSwap = () => { const accountHash = blake2AsU8a(fullTuple.toU8a(), 256); if (import.meta.env.DEV) console.log('🔍 Account hash:', Array.from(accountHash).slice(0, 8)); - const poolAccountId = api.createType('AccountId32', accountHash); + const poolAccountId = assetHubApi.createType('AccountId32', accountHash); if (import.meta.env.DEV) console.log('🔍 Pool AccountId (NEW METHOD):', poolAccountId.toString()); // Query pool account's asset balances if (import.meta.env.DEV) console.log('🔍 Querying reserves for asset', asset1, 'and', asset2); - const reserve0Query = await api.query.assets.account(asset1, poolAccountId); - const reserve1Query = await api.query.assets.account(asset2, poolAccountId); + const reserve0Query = await assetHubApi.query.assets.account(asset1, poolAccountId); + const reserve1Query = await assetHubApi.query.assets.account(asset2, poolAccountId); if (import.meta.env.DEV) console.log('🔍 Reserve0 query result:', reserve0Query.toHuman()); if (import.meta.env.DEV) console.log('🔍 Reserve1 query result:', reserve1Query.toHuman()); @@ -316,19 +317,19 @@ const TokenSwap = () => { }; fetchExchangeRate(); - }, [api, isApiReady, isDexAvailable, fromToken, toToken]); + }, [assetHubApi, isAssetHubReady, isDexAvailable, fromToken, toToken]); // Fetch liquidity pools useEffect(() => { const fetchLiquidityPools = async () => { - if (!api || !isApiReady || !isDexAvailable) { + if (!assetHubApi || !isAssetHubReady || !isDexAvailable) { return; } setIsLoadingPools(true); try { // Query all pools from AssetConversion pallet - const poolsEntries = await api.query.assetConversion.pools.entries(); + const poolsEntries = await assetHubApi.query.assetConversion.pools.entries(); if (poolsEntries && poolsEntries.length > 0) { const pools = poolsEntries.map(([key, value]: [unknown, unknown]) => { @@ -366,20 +367,20 @@ const TokenSwap = () => { }; fetchLiquidityPools(); - }, [api, isApiReady, isDexAvailable]); + }, [assetHubApi, isAssetHubReady, isDexAvailable]); // Fetch swap transaction history useEffect(() => { const fetchSwapHistory = async () => { - if (!api || !isApiReady || !isDexAvailable || !selectedAccount) { + if (!assetHubApi || !isAssetHubReady || !isDexAvailable || !selectedAccount) { return; } setIsLoadingHistory(true); try { // Get recent finalized blocks (last 100 blocks) - const finalizedHead = await api.rpc.chain.getFinalizedHead(); - const finalizedBlock = await api.rpc.chain.getBlock(finalizedHead); + const finalizedHead = await assetHubApi.rpc.chain.getFinalizedHead(); + const finalizedBlock = await assetHubApi.rpc.chain.getBlock(finalizedHead); const currentBlockNumber = finalizedBlock.block.header.number.toNumber(); const startBlock = Math.max(0, currentBlockNumber - 100); @@ -391,17 +392,17 @@ const TokenSwap = () => { // Query block by block for SwapExecuted events for (let blockNum = currentBlockNumber; blockNum >= startBlock && transactions.length < 10; blockNum--) { try { - const blockHash = await api.rpc.chain.getBlockHash(blockNum); - const apiAt = await api.at(blockHash); + const blockHash = await assetHubApi.rpc.chain.getBlockHash(blockNum); + const apiAt = await assetHubApi.at(blockHash); const events = await apiAt.query.system.events(); - //const block = await api.rpc.chain.getBlock(blockHash); + //const block = await assetHubApi.rpc.chain.getBlock(blockHash); const timestamp = Date.now() - ((currentBlockNumber - blockNum) * 6000); // Estimate 6s per block events.forEach((record: { event: { data: unknown[] } }) => { const { event } = record; // Check for AssetConversion::SwapExecuted event - if (api.events.assetConversion?.SwapExecuted?.is(event)) { + if (assetHubApi.events.assetConversion?.SwapExecuted?.is(event)) { // SwapExecuted has 5 fields: (who, send_to, amountIn, amountOut, path) const [who, , amountIn, amountOut, path] = event.data; @@ -464,7 +465,7 @@ const TokenSwap = () => { }; fetchSwapHistory(); - }, [api, isApiReady, isDexAvailable, selectedAccount]); + }, [assetHubApi, isAssetHubReady, isDexAvailable, selectedAccount]); const handleSwap = () => { setFromToken(toToken); @@ -473,7 +474,7 @@ const TokenSwap = () => { }; const handleConfirmSwap = async () => { - if (!api || !selectedAccount) { + if (!assetHubApi || !selectedAccount) { toast({ title: 'Error', description: 'Please connect your wallet', @@ -551,61 +552,61 @@ const TokenSwap = () => { if (fromToken === 'HEZ' && toToken === 'PEZ') { // HEZ → PEZ: wrap(HEZ→wHEZ) then swap(wHEZ→PEZ) - const wrapTx = api.tx.tokenWrapper.wrap(amountIn.toString()); + const wrapTx = assetHubApi.tx.tokenWrapper.wrap(amountIn.toString()); // AssetKind = u32, so swap path is just [0, 1] const swapPath = [0, 1]; // wHEZ → PEZ - const swapTx = api.tx.assetConversion.swapExactTokensForTokens( + const swapTx = assetHubApi.tx.assetConversion.swapExactTokensForTokens( swapPath, amountIn.toString(), minAmountOut.toString(), selectedAccount.address, true ); - tx = api.tx.utility.batchAll([wrapTx, swapTx]); + tx = assetHubApi.tx.utility.batchAll([wrapTx, swapTx]); } else if (fromToken === 'PEZ' && toToken === 'HEZ') { // PEZ → HEZ: swap(PEZ→wHEZ) then unwrap(wHEZ→HEZ) // AssetKind = u32, so swap path is just [1, 0] const swapPath = [1, 0]; // PEZ → wHEZ - const swapTx = api.tx.assetConversion.swapExactTokensForTokens( + const swapTx = assetHubApi.tx.assetConversion.swapExactTokensForTokens( swapPath, amountIn.toString(), minAmountOut.toString(), selectedAccount.address, true ); - const unwrapTx = api.tx.tokenWrapper.unwrap(minAmountOut.toString()); - tx = api.tx.utility.batchAll([swapTx, unwrapTx]); + const unwrapTx = assetHubApi.tx.tokenWrapper.unwrap(minAmountOut.toString()); + tx = assetHubApi.tx.utility.batchAll([swapTx, unwrapTx]); } else if (fromToken === 'HEZ') { // HEZ → Any Asset: wrap(HEZ→wHEZ) then swap(wHEZ→Asset) - const wrapTx = api.tx.tokenWrapper.wrap(amountIn.toString()); + const wrapTx = assetHubApi.tx.tokenWrapper.wrap(amountIn.toString()); // Map token symbol to asset ID const toAssetId = toToken === 'PEZ' ? 1 : toToken === 'USDT' ? 1000 : ASSET_IDS[toToken as keyof typeof ASSET_IDS]; const swapPath = [0, toAssetId]; // wHEZ → target asset - const swapTx = api.tx.assetConversion.swapExactTokensForTokens( + const swapTx = assetHubApi.tx.assetConversion.swapExactTokensForTokens( swapPath, amountIn.toString(), minAmountOut.toString(), selectedAccount.address, true ); - tx = api.tx.utility.batchAll([wrapTx, swapTx]); + tx = assetHubApi.tx.utility.batchAll([wrapTx, swapTx]); } else if (toToken === 'HEZ') { // Any Asset → HEZ: swap(Asset→wHEZ) then unwrap(wHEZ→HEZ) // Map token symbol to asset ID const fromAssetId = fromToken === 'PEZ' ? 1 : fromToken === 'USDT' ? 1000 : ASSET_IDS[fromToken as keyof typeof ASSET_IDS]; const swapPath = [fromAssetId, 0]; // source asset → wHEZ - const swapTx = api.tx.assetConversion.swapExactTokensForTokens( + const swapTx = assetHubApi.tx.assetConversion.swapExactTokensForTokens( swapPath, amountIn.toString(), minAmountOut.toString(), selectedAccount.address, true ); - const unwrapTx = api.tx.tokenWrapper.unwrap(minAmountOut.toString()); - tx = api.tx.utility.batchAll([swapTx, unwrapTx]); + const unwrapTx = assetHubApi.tx.tokenWrapper.unwrap(minAmountOut.toString()); + tx = assetHubApi.tx.utility.batchAll([swapTx, unwrapTx]); } else { // Direct swap between assets (PEZ ↔ USDT, etc.) @@ -614,7 +615,7 @@ const TokenSwap = () => { const toAssetId = toToken === 'PEZ' ? 1 : toToken === 'USDT' ? 1000 : ASSET_IDS[toToken as keyof typeof ASSET_IDS]; const swapPath = [fromAssetId, toAssetId]; - tx = api.tx.assetConversion.swapExactTokensForTokens( + tx = assetHubApi.tx.assetConversion.swapExactTokensForTokens( swapPath, amountIn.toString(), minAmountOut.toString(), @@ -649,7 +650,7 @@ const TokenSwap = () => { let errorMessage = 'Transaction failed'; if (dispatchError.isModule) { - const decoded = api.registry.findMetaError(dispatchError.asModule); + const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule); errorMessage = `${decoded.section}.${decoded.name}: ${decoded.docs}`; } @@ -664,7 +665,7 @@ const TokenSwap = () => { // Success - check for swap event const hasSwapEvent = events.some(({ event }) => - api.events.assetConversion?.SwapExecuted?.is(event) + assetHubApi.events.assetConversion?.SwapExecuted?.is(event) ); if (hasSwapEvent || fromToken === 'HEZ' || toToken === 'HEZ') { @@ -683,23 +684,23 @@ const TokenSwap = () => { setTimeout(async () => { if (import.meta.env.DEV) console.log('🔄 Refreshing swap history...'); const fetchSwapHistory = async () => { - if (!api || !isApiReady || !isDexAvailable || !selectedAccount) return; + if (!assetHubApi || !isAssetHubReady || !isDexAvailable || !selectedAccount) return; setIsLoadingHistory(true); try { - const finalizedHead = await api.rpc.chain.getFinalizedHead(); - const finalizedBlock = await api.rpc.chain.getBlock(finalizedHead); + const finalizedHead = await assetHubApi.rpc.chain.getFinalizedHead(); + const finalizedBlock = await assetHubApi.rpc.chain.getBlock(finalizedHead); const currentBlockNumber = finalizedBlock.block.header.number.toNumber(); const startBlock = Math.max(0, currentBlockNumber - 100); const transactions: SwapTransaction[] = []; for (let blockNum = currentBlockNumber; blockNum >= startBlock && transactions.length < 10; blockNum--) { try { - const blockHash = await api.rpc.chain.getBlockHash(blockNum); - const apiAt = await api.at(blockHash); + const blockHash = await assetHubApi.rpc.chain.getBlockHash(blockNum); + const apiAt = await assetHubApi.at(blockHash); const events = await apiAt.query.system.events(); const timestamp = Date.now() - ((currentBlockNumber - blockNum) * 6000); events.forEach((record: { event: { data: unknown[] } }) => { const { event } = record; - if (api.events.assetConversion?.SwapExecuted?.is(event)) { + if (assetHubApi.events.assetConversion?.SwapExecuted?.is(event)) { // SwapExecuted has 5 fields: (who, send_to, amountIn, amountOut, path) const [who, , amountIn, amountOut, path] = event.data; @@ -780,7 +781,7 @@ const TokenSwap = () => { }; // Show DEX unavailable message - if (!isDexAvailable && isApiReady) { + if (!isDexAvailable && isAssetHubReady) { return (
diff --git a/web/src/components/dex/RemoveLiquidityModal.tsx b/web/src/components/dex/RemoveLiquidityModal.tsx index b7123b10..b2316275 100644 --- a/web/src/components/dex/RemoveLiquidityModal.tsx +++ b/web/src/components/dex/RemoveLiquidityModal.tsx @@ -21,7 +21,8 @@ export const RemoveLiquidityModal: React.FC = ({ onClose, onSuccess, }) => { - const { api, isApiReady } = usePezkuwi(); + // Use Asset Hub API for DEX operations (assetConversion pallet is on Asset Hub) + const { assetHubApi, isAssetHubReady } = usePezkuwi(); const { account, signer } = useWallet(); const [lpTokenBalance, setLpTokenBalance] = useState('0'); @@ -43,11 +44,11 @@ export const RemoveLiquidityModal: React.FC = ({ // Fetch LP token balance useEffect(() => { const fetchLPBalance = async () => { - if (!api || !isApiReady || !account || !pool) return; + if (!assetHubApi || !isAssetHubReady || !account || !pool) return; try { // Get pool account - const poolAccount = await api.query.assetConversion.pools([ + const poolAccount = await assetHubApi.query.assetConversion.pools([ pool.asset1, pool.asset2, ]); @@ -60,8 +61,8 @@ export const RemoveLiquidityModal: React.FC = ({ // LP token ID is derived from pool ID // For now, we'll query the pool's LP token supply // In a real implementation, you'd need to query the specific LP token for the user - if (api.query.assetConversion.nextPoolAssetId) { - await api.query.assetConversion.nextPoolAssetId(); + if (assetHubApi.query.assetConversion.nextPoolAssetId) { + await assetHubApi.query.assetConversion.nextPoolAssetId(); } // This is a simplified version - you'd need to track LP tokens properly @@ -73,7 +74,7 @@ export const RemoveLiquidityModal: React.FC = ({ }; fetchLPBalance(); - }, [api, isApiReady, account, pool]); + }, [assetHubApi, isAssetHubReady, account, pool]); const calculateOutputAmounts = () => { if (!pool || BigInt(lpTokenBalance) === BigInt(0)) { @@ -98,7 +99,7 @@ export const RemoveLiquidityModal: React.FC = ({ }; const handleRemoveLiquidity = async () => { - if (!api || !isApiReady || !signer || !account || !pool) { + if (!assetHubApi || !isAssetHubReady || !signer || !account || !pool) { setErrorMessage('Wallet not connected'); return; } @@ -119,7 +120,7 @@ export const RemoveLiquidityModal: React.FC = ({ setTxStatus('signing'); setErrorMessage(''); - const tx = api.tx.assetConversion.removeLiquidity( + const tx = assetHubApi.tx.assetConversion.removeLiquidity( pool.asset1, pool.asset2, lpAmount.toString(), @@ -137,7 +138,7 @@ export const RemoveLiquidityModal: React.FC = ({ if (status.isInBlock) { if (dispatchError) { if (dispatchError.isModule) { - const decoded = api.registry.findMetaError(dispatchError.asModule); + const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule); setErrorMessage(`${decoded.section}.${decoded.name}: ${decoded.docs}`); } else { setErrorMessage(dispatchError.toString());