From c2f2de3bbbef83551b3a6c438752b061b7a85461 Mon Sep 17 00:00:00 2001 From: Kurdistan Tech Ministry Date: Wed, 4 Feb 2026 14:13:43 +0300 Subject: [PATCH] feat: add Native HEZ support for pool creation assetConversion pallet requires pools to pair with Native token. Changes: - Added NATIVE_TOKEN_ID (-1) constant for relay chain HEZ - Updated CreatePoolModal to use XCM location format for Native - Pools can now be created: Native HEZ / PEZ, Native HEZ / wUSDT, etc. - Fixed balance fetching for Native vs Asset tokens --- shared/constants/index.ts | 14 ++++ web/src/components/dex/CreatePoolModal.tsx | 89 +++++++++++----------- web/src/types/dex.ts | 2 +- 3 files changed, 61 insertions(+), 44 deletions(-) diff --git a/shared/constants/index.ts b/shared/constants/index.ts index 59fb739b..1ea10c2b 100644 --- a/shared/constants/index.ts +++ b/shared/constants/index.ts @@ -46,15 +46,29 @@ export const KURDISTAN_COLORS = { res: '#000000', // Black (Reş) } as const; +/** + * Special ID for Native token (relay chain HEZ) + * Used in pool creation - pools must pair with Native + */ +export const NATIVE_TOKEN_ID = -1; + /** * Known tokens on the Pezkuwi blockchain (Asset Hub) * * Asset IDs on Asset Hub: + * - Native (-1): HEZ from Relay Chain (for pool pairing) * - Asset 1: PEZ (Pezkuwi Token) * - Asset 2: wHEZ (Wrapped HEZ via tokenWrapper) * - Asset 1000: wUSDT (Wrapped USDT) */ export const KNOWN_TOKENS: Record = { + [NATIVE_TOKEN_ID]: { + id: NATIVE_TOKEN_ID, + symbol: 'HEZ', + name: 'Native HEZ (Relay Chain)', + decimals: 12, + logo: '/shared/images/hez_token_512.png', + }, 2: { id: 2, symbol: 'wHEZ', diff --git a/web/src/components/dex/CreatePoolModal.tsx b/web/src/components/dex/CreatePoolModal.tsx index 1fd3a355..414f8b70 100644 --- a/web/src/components/dex/CreatePoolModal.tsx +++ b/web/src/components/dex/CreatePoolModal.tsx @@ -1,10 +1,10 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { usePezkuwi } from '@/contexts/PezkuwiContext'; import { useWallet } from '@/contexts/WalletContext'; import { X, Plus, AlertCircle, Loader2, CheckCircle } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; -import { KNOWN_TOKENS } from '@/types/dex'; +import { KNOWN_TOKENS, NATIVE_TOKEN_ID } from '@/types/dex'; import { parseTokenInput, formatTokenBalance } from '@pezkuwi/utils/dex'; interface CreatePoolModalProps { @@ -50,54 +50,52 @@ export const CreatePoolModal: React.FC = ({ } }, [isOpen]); + // Helper to fetch balance for an asset (handles Native vs Asset) + const fetchAssetBalance = useCallback(async (assetId: number): Promise => { + if (!assetHubApi || !isAssetHubReady || !account) return '0'; + + try { + if (assetId === NATIVE_TOKEN_ID) { + // Native token - query system.account + const accountData: { data: { free: { toString: () => string } } } = + await assetHubApi.query.system.account(account) as never; + return accountData.data.free.toString(); + } else { + // Asset - query assets.account + const balanceData = await assetHubApi.query.assets.account(assetId, account); + if ((balanceData as { isSome: boolean }).isSome) { + return ((balanceData as { unwrap: () => { balance: { toString: () => string } } }).unwrap()).balance.toString(); + } + return '0'; + } + } catch (error) { + if (import.meta.env.DEV) console.error('❌ Failed to fetch balance for asset', assetId, ':', error); + return '0'; + } + }, [assetHubApi, isAssetHubReady, account]); + // Fetch balances from Asset Hub when assets selected useEffect(() => { const fetchBalances = async () => { - if (!assetHubApi || !isAssetHubReady || !account || asset1Id === null) return; - - try { - if (import.meta.env.DEV) console.log('πŸ” Fetching balance for asset', asset1Id, 'on Asset Hub'); - const balance1Data = await assetHubApi.query.assets.account(asset1Id, account); - if (balance1Data.isSome) { - const balance = balance1Data.unwrap().balance.toString(); - if (import.meta.env.DEV) console.log('βœ… Balance found for asset', asset1Id, ':', balance); - setBalance1(balance); - } else { - if (import.meta.env.DEV) console.warn('⚠️ No balance found for asset', asset1Id); - setBalance1('0'); - } - } catch (error) { - if (import.meta.env.DEV) console.error('❌ Failed to fetch balance 1:', error); - setBalance1('0'); - } + if (asset1Id === null) return; + if (import.meta.env.DEV) console.log('πŸ” Fetching balance for asset', asset1Id, 'on Asset Hub'); + const balance = await fetchAssetBalance(asset1Id); + if (import.meta.env.DEV) console.log('βœ… Balance for asset', asset1Id, ':', balance); + setBalance1(balance); }; - fetchBalances(); - }, [assetHubApi, isAssetHubReady, account, asset1Id]); + }, [fetchAssetBalance, asset1Id]); useEffect(() => { const fetchBalances = async () => { - if (!assetHubApi || !isAssetHubReady || !account || asset2Id === null) return; - - try { - if (import.meta.env.DEV) console.log('πŸ” Fetching balance for asset', asset2Id, 'on Asset Hub'); - const balance2Data = await assetHubApi.query.assets.account(asset2Id, account); - if (balance2Data.isSome) { - const balance = balance2Data.unwrap().balance.toString(); - if (import.meta.env.DEV) console.log('βœ… Balance found for asset', asset2Id, ':', balance); - setBalance2(balance); - } else { - if (import.meta.env.DEV) console.warn('⚠️ No balance found for asset', asset2Id); - setBalance2('0'); - } - } catch (error) { - if (import.meta.env.DEV) console.error('❌ Failed to fetch balance 2:', error); - setBalance2('0'); - } + if (asset2Id === null) return; + if (import.meta.env.DEV) console.log('πŸ” Fetching balance for asset', asset2Id, 'on Asset Hub'); + const balance = await fetchAssetBalance(asset2Id); + if (import.meta.env.DEV) console.log('βœ… Balance for asset', asset2Id, ':', balance); + setBalance2(balance); }; - fetchBalances(); - }, [assetHubApi, isAssetHubReady, account, asset2Id]); + }, [fetchAssetBalance, asset2Id]); const validateInputs = (): string | null => { if (asset1Id === null || asset2Id === null) { @@ -178,10 +176,15 @@ export const CreatePoolModal: React.FC = ({ setErrorMessage(''); // Convert asset IDs to proper format for assetConversion pallet - // Native token uses { Native: null }, assets use { Asset: id } + // Native token (relay chain HEZ) uses XCM location format + // Assets use { Asset: id } const formatAssetId = (id: number) => { - // For now, all our tokens are assets on Asset Hub - return { Asset: id }; + if (id === NATIVE_TOKEN_ID) { + // Native token from relay chain - XCM location format + // { parents: 1, interior: Here } represents relay chain native token + return { parents: 1, interior: 'Here' }; + } + return { parents: 0, interior: { X2: [{ PalletInstance: 50 }, { GeneralIndex: id }] } }; }; const asset1 = formatAssetId(asset1Id!); diff --git a/web/src/types/dex.ts b/web/src/types/dex.ts index 4d01a12a..9f215f8b 100644 --- a/web/src/types/dex.ts +++ b/web/src/types/dex.ts @@ -11,4 +11,4 @@ export { TransactionStatus, } from '../../../shared/types/tokens'; -export { KNOWN_TOKENS, TOKEN_DISPLAY_SYMBOLS } from '../../../shared/constants'; +export { KNOWN_TOKENS, TOKEN_DISPLAY_SYMBOLS, NATIVE_TOKEN_ID } from '../../../shared/constants';