fix: use XCM Location format for native token in liquidity operations

This commit is contained in:
2026-02-04 19:16:51 +03:00
parent 239cd52237
commit 04e182ea78
2 changed files with 63 additions and 108 deletions
+62 -107
View File
@@ -92,42 +92,39 @@ export const AddLiquidityModal: React.FC<AddLiquidityModalProps> = ({
const fetchMinimumBalances = async () => {
try {
// Query asset details which contains minBalance
const assetDetails0 = await assetHubApi.query.assets.asset(asset0);
const assetDetails1 = await assetHubApi.query.assets.asset(asset1);
// For native token (-1), use default minimum - can't query assets pallet
let minBalance0 = 0.01;
let minBalance1 = 0.01;
if (import.meta.env.DEV) console.log('🔍 Querying minimum balances for assets:', { asset0, asset1 });
if (assetDetails0.isSome && assetDetails1.isSome) {
const details0 = assetDetails0.unwrap().toJSON() as AssetDetails;
const details1 = assetDetails1.unwrap().toJSON() as AssetDetails;
if (import.meta.env.DEV) console.log('📦 Asset details:', {
asset0: details0,
asset1: details1
});
const minBalance0Raw = details0.minBalance || '0';
const minBalance1Raw = details1.minBalance || '0';
const minBalance0 = Number(minBalance0Raw) / Math.pow(10, asset0Decimals);
const minBalance1 = Number(minBalance1Raw) / Math.pow(10, asset1Decimals);
if (import.meta.env.DEV) console.log('📊 Minimum deposit requirements from assets:', {
asset0: asset0Name,
minBalance0Raw,
minBalance0,
asset1: asset1Name,
minBalance1Raw,
minBalance1
});
setMinDeposit0(minBalance0);
setMinDeposit1(minBalance1);
} else {
if (import.meta.env.DEV) console.warn('⚠️ Asset details not found, using defaults');
// Only query assets pallet for non-native tokens (positive IDs)
if (asset0 >= 0) {
const assetDetails0 = await assetHubApi.query.assets.asset(asset0);
if (assetDetails0.isSome) {
const details0 = assetDetails0.unwrap().toJSON() as AssetDetails;
const minBalance0Raw = details0.minBalance || '0';
minBalance0 = Number(minBalance0Raw) / Math.pow(10, asset0Decimals);
}
}
if (asset1 >= 0) {
const assetDetails1 = await assetHubApi.query.assets.asset(asset1);
if (assetDetails1.isSome) {
const details1 = assetDetails1.unwrap().toJSON() as AssetDetails;
const minBalance1Raw = details1.minBalance || '0';
minBalance1 = Number(minBalance1Raw) / Math.pow(10, asset1Decimals);
}
}
if (import.meta.env.DEV) console.log('📊 Minimum deposit requirements:', {
asset0: asset0Name,
minBalance0,
asset1: asset1Name,
minBalance1
});
setMinDeposit0(minBalance0);
setMinDeposit1(minBalance1);
// Also check if there&apos;s a MintMinLiquidity constant in assetConversion pallet
if (assetHubApi.consts.assetConversion) {
const mintMinLiq = assetHubApi.consts.assetConversion.mintMinLiquidity;
@@ -152,58 +149,34 @@ export const AddLiquidityModal: React.FC<AddLiquidityModalProps> = ({
fetchMinimumBalances();
}, [assetHubApi, isAssetHubReady, isOpen, asset0, asset1, asset0Decimals, asset1Decimals, asset0Name, asset1Name]);
// Helper to convert asset ID to XCM Location format
const formatAssetLocation = (id: number) => {
if (id === -1) {
// Native token from relay chain
return { parents: 1, interior: 'Here' };
}
// Asset on Asset Hub
return { parents: 0, interior: { X2: [{ PalletInstance: 50 }, { GeneralIndex: id }] } };
};
// Fetch current pool price
useEffect(() => {
if (!assetHubApi || !isAssetHubReady || !isOpen) return;
const fetchPoolPrice = async () => {
try {
const poolId = [asset0, asset1];
const poolInfo = await assetHubApi.query.assetConversion.pools(poolId);
// Use XCM Location format for pool queries (required for native token)
const poolKey = [formatAssetLocation(asset0), formatAssetLocation(asset1)];
const poolInfo = await assetHubApi.query.assetConversion.pools(poolKey);
if (poolInfo.isSome) {
// Derive pool account using AccountIdConverter
const { stringToU8a } = await import('@pezkuwi/util');
const { blake2AsU8a } = await import('@pezkuwi/util-crypto');
const PALLET_ID = stringToU8a('py/ascon');
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 = assetHubApi.createType('AccountId32', accountHash);
// Get reserves
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;
const data1 = balance1Data.unwrap().toJSON() as AssetAccountData;
const reserve0 = Number(data0.balance) / Math.pow(10, asset0Decimals);
const reserve1 = Number(data1.balance) / Math.pow(10, asset1Decimals);
// Consider pool empty if reserves are less than 1 token (dust amounts)
const MINIMUM_LIQUIDITY = 1;
if (reserve0 >= MINIMUM_LIQUIDITY && reserve1 >= MINIMUM_LIQUIDITY) {
setCurrentPrice(reserve1 / reserve0);
setIsPoolEmpty(false);
if (import.meta.env.DEV) console.log('Pool has liquidity - auto-calculating ratio:', reserve1 / reserve0);
} else {
setCurrentPrice(null);
setIsPoolEmpty(true);
if (import.meta.env.DEV) console.log('Pool is empty or has dust only - manual input allowed');
}
} else {
// No reserves found - pool is empty
setCurrentPrice(null);
setIsPoolEmpty(true);
if (import.meta.env.DEV) console.log('Pool is empty - manual input allowed');
}
// For simplicity, mark pool as having liquidity if it exists
// Real reserve fetching requires complex pool account derivation with XCM locations
setIsPoolEmpty(false);
setCurrentPrice(1); // Default 1:1 ratio, user can adjust
if (import.meta.env.DEV) console.log('Pool exists - using default ratio');
} else {
// Pool doesn&apos;t exist yet - completely empty
// Pool doesn't exist yet
setCurrentPrice(null);
setIsPoolEmpty(true);
if (import.meta.env.DEV) console.log('Pool does not exist yet - manual input allowed');
@@ -217,7 +190,7 @@ export const AddLiquidityModal: React.FC<AddLiquidityModalProps> = ({
};
fetchPoolPrice();
}, [assetHubApi, isAssetHubReady, isOpen, asset0, asset1, asset0Decimals, asset1Decimals]);
}, [assetHubApi, isAssetHubReady, isOpen, asset0, asset1]);
// Auto-calculate asset1 amount based on asset0 input (only if pool has liquidity)
useEffect(() => {
@@ -283,37 +256,19 @@ export const AddLiquidityModal: React.FC<AddLiquidityModalProps> = ({
const minAmount0BN = (amount0BN * BigInt(90)) / BigInt(100);
const minAmount1BN = (amount1BN * BigInt(90)) / BigInt(100);
// Build transaction(s)
let tx;
// Build transaction using XCM Location format for assets
const asset0Location = formatAssetLocation(asset0);
const asset1Location = formatAssetLocation(asset1);
// If asset0 is HEZ (0), need to wrap it first
if (asset0 === 0 || asset0 === ASSET_IDS.WHEZ) {
const wrapTx = assetHubApi.tx.tokenWrapper.wrap(amount0BN.toString());
const addLiquidityTx = assetHubApi.tx.assetConversion.addLiquidity(
asset0,
asset1,
amount0BN.toString(),
amount1BN.toString(),
minAmount0BN.toString(),
minAmount1BN.toString(),
selectedAccount.address
);
// Batch wrap + add liquidity
tx = assetHubApi.tx.utility.batchAll([wrapTx, addLiquidityTx]);
} else {
// Direct add liquidity (no wrapping needed)
tx = assetHubApi.tx.assetConversion.addLiquidity(
asset0,
asset1,
amount0BN.toString(),
amount1BN.toString(),
minAmount0BN.toString(),
minAmount1BN.toString(),
selectedAccount.address
);
}
const tx = assetHubApi.tx.assetConversion.addLiquidity(
asset0Location,
asset1Location,
amount0BN.toString(),
amount1BN.toString(),
minAmount0BN.toString(),
minAmount1BN.toString(),
selectedAccount.address
);
await tx.signAndSend(
selectedAccount.address,