fix: migrate DEX components from Relay Chain to Asset Hub API

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