fix: use NATIVE_TOKEN_ID (-1) for pool matching and XCM Location for swaps

- Changed HEZ assetId from 0 to -1 (NATIVE_TOKEN_ID) for correct pool matching
- Updated swap transactions to use XCM MultiLocation format
- Added multi-hop support for PEZ ↔ USDT through HEZ
This commit is contained in:
2026-02-05 01:15:26 +03:00
parent a1df2fb6a5
commit 232a4d43be
+48 -26
View File
@@ -14,6 +14,7 @@ import {
formatTokenBalance, formatTokenBalance,
getAmountOut, getAmountOut,
calculatePriceImpact, calculatePriceImpact,
formatAssetLocation,
} from '@pezkuwi/utils/dex'; } from '@pezkuwi/utils/dex';
import { useToast } from '@/hooks/use-toast'; import { useToast } from '@/hooks/use-toast';
@@ -24,9 +25,10 @@ interface SwapInterfaceProps {
type TransactionStatus = 'idle' | 'signing' | 'submitting' | 'success' | 'error'; type TransactionStatus = 'idle' | 'signing' | 'submitting' | 'success' | 'error';
// User-facing tokens (wHEZ is hidden from users, shown as HEZ) // User-facing tokens
// Native HEZ uses NATIVE_TOKEN_ID (-1) for pool matching
const USER_TOKENS = [ const USER_TOKENS = [
{ symbol: 'HEZ', emoji: '🟡', assetId: 0, name: 'HEZ', decimals: 12, displaySymbol: 'HEZ' }, // actually wHEZ (asset 0) { symbol: 'HEZ', emoji: '🟡', assetId: -1, name: 'HEZ', decimals: 12, displaySymbol: 'HEZ' }, // Native HEZ (NATIVE_TOKEN_ID)
{ symbol: 'PEZ', emoji: '🟣', assetId: 1, name: 'PEZ', decimals: 12, displaySymbol: 'PEZ' }, { symbol: 'PEZ', emoji: '🟣', assetId: 1, name: 'PEZ', decimals: 12, displaySymbol: 'PEZ' },
{ symbol: 'USDT', emoji: '💵', assetId: 1000, name: 'USDT', decimals: 6, displaySymbol: 'USDT' }, { symbol: 'USDT', emoji: '💵', assetId: 1000, name: 'USDT', decimals: 6, displaySymbol: 'USDT' },
] as const; ] as const;
@@ -227,58 +229,78 @@ export const SwapInterface: React.FC<SwapInterfaceProps> = ({ pools }) => {
let tx; let tx;
// Native HEZ uses NATIVE_TOKEN_ID (-1) for XCM Location
// assetConversion pallet expects XCM MultiLocation format for swap paths
const nativeLocation = formatAssetLocation(-1); // { parents: 1, interior: 'Here' }
const pezLocation = formatAssetLocation(1); // PEZ asset
const usdtLocation = formatAssetLocation(1000); // wUSDT asset
if (fromToken === 'HEZ' && toToken === 'PEZ') { if (fromToken === 'HEZ' && toToken === 'PEZ') {
// HEZ → PEZ: wrap(HEZ→wHEZ) then swap(wHEZ→PEZ) // HEZ → PEZ: Direct swap using native token
const wrapTx = assetHubApi.tx.tokenWrapper.wrap(amountIn.toString()); tx = assetHubApi.tx.assetConversion.swapExactTokensForTokens(
const swapTx = assetHubApi.tx.assetConversion.swapExactTokensForTokens( [nativeLocation, pezLocation],
[0, 1], // wHEZ → PEZ
amountIn.toString(), amountIn.toString(),
minAmountOut.toString(), minAmountOut.toString(),
account, account,
true true
); );
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: Direct swap to native token
const swapTx = assetHubApi.tx.assetConversion.swapExactTokensForTokens( tx = assetHubApi.tx.assetConversion.swapExactTokensForTokens(
[1, 0], // PEZ → wHEZ [pezLocation, nativeLocation],
amountIn.toString(), amountIn.toString(),
minAmountOut.toString(), minAmountOut.toString(),
account, account,
true true
); );
const unwrapTx = assetHubApi.tx.tokenWrapper.unwrap(minAmountOut.toString());
tx = assetHubApi.tx.utility.batchAll([swapTx, unwrapTx]);
} else if (fromToken === 'HEZ') { } else if (fromToken === 'HEZ' && toToken === 'USDT') {
// HEZ → Any Asset: wrap(HEZ→wHEZ) then swap(wHEZ→Asset) // HEZ → USDT: Direct swap using native token
const wrapTx = assetHubApi.tx.tokenWrapper.wrap(amountIn.toString()); tx = assetHubApi.tx.assetConversion.swapExactTokensForTokens(
const swapTx = assetHubApi.tx.assetConversion.swapExactTokensForTokens( [nativeLocation, usdtLocation],
[0, toAssetId!], // wHEZ → target asset
amountIn.toString(), amountIn.toString(),
minAmountOut.toString(), minAmountOut.toString(),
account, account,
true true
); );
tx = assetHubApi.tx.utility.batchAll([wrapTx, swapTx]);
} else if (toToken === 'HEZ') { } else if (fromToken === 'USDT' && toToken === 'HEZ') {
// Any Asset → HEZ: swap(Asset→wHEZ) then unwrap(wHEZ→HEZ) // USDT → HEZ: Direct swap to native token
const swapTx = assetHubApi.tx.assetConversion.swapExactTokensForTokens( tx = assetHubApi.tx.assetConversion.swapExactTokensForTokens(
[fromAssetId!, 0], // source asset → wHEZ [usdtLocation, nativeLocation],
amountIn.toString(),
minAmountOut.toString(),
account,
true
);
} else if (fromToken === 'PEZ' && toToken === 'USDT') {
// PEZ → USDT: Multi-hop through HEZ (PEZ → HEZ → USDT)
tx = assetHubApi.tx.assetConversion.swapExactTokensForTokens(
[pezLocation, nativeLocation, usdtLocation],
amountIn.toString(),
minAmountOut.toString(),
account,
true
);
} else if (fromToken === 'USDT' && toToken === 'PEZ') {
// USDT → PEZ: Multi-hop through HEZ (USDT → HEZ → PEZ)
tx = assetHubApi.tx.assetConversion.swapExactTokensForTokens(
[usdtLocation, nativeLocation, pezLocation],
amountIn.toString(), amountIn.toString(),
minAmountOut.toString(), minAmountOut.toString(),
account, account,
true true
); );
const unwrapTx = assetHubApi.tx.tokenWrapper.unwrap(minAmountOut.toString());
tx = assetHubApi.tx.utility.batchAll([swapTx, unwrapTx]);
} else { } else {
// Direct swap between assets (PEZ ↔ USDT, etc.) // Generic swap using XCM Locations
const fromLocation = formatAssetLocation(fromAssetId!);
const toLocation = formatAssetLocation(toAssetId!);
tx = assetHubApi.tx.assetConversion.swapExactTokensForTokens( tx = assetHubApi.tx.assetConversion.swapExactTokensForTokens(
[fromAssetId!, toAssetId!], [fromLocation, toLocation],
amountIn.toString(), amountIn.toString(),
minAmountOut.toString(), minAmountOut.toString(),
account, account,