From e76bec3284d4db395b29a34dc23149a92f6627da Mon Sep 17 00:00:00 2001 From: Kurdistan Tech Ministry Date: Sat, 7 Feb 2026 21:31:25 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20add=20bidirectional=20HEZ=20teleport=20?= =?UTF-8?q?(Relay=20=E2=86=94=20Parachain)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/components/wallet/FundFeesModal.tsx | 372 ++++++++++++++++-------- src/version.json | 6 +- 3 files changed, 256 insertions(+), 124 deletions(-) diff --git a/package.json b/package.json index 31a26e5..cf69c60 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pezkuwi-telegram-miniapp", - "version": "1.0.159", + "version": "1.0.160", "type": "module", "description": "Pezkuwichain Telegram Mini App - Forum, Announcements, Rewards", "author": "Pezkuwichain Team", diff --git a/src/components/wallet/FundFeesModal.tsx b/src/components/wallet/FundFeesModal.tsx index a579235..9c6f78a 100644 --- a/src/components/wallet/FundFeesModal.tsx +++ b/src/components/wallet/FundFeesModal.tsx @@ -1,14 +1,25 @@ /** - * Fund Fees Modal - XCM Teleport HEZ to Teyerchains - * Allows users to transfer HEZ from relay chain to Asset Hub or People chain for fees + * Fund Fees Modal - XCM Teleport HEZ between Relay Chain and Parachains + * Supports bidirectional teleport: Relay ↔ Asset Hub / People Chain */ import { useState, useEffect } from 'react'; -import { X, ArrowDown, Loader2, CheckCircle, AlertCircle, Fuel, Info } from 'lucide-react'; +import { + X, + ArrowDown, + ArrowUp, + Loader2, + CheckCircle, + AlertCircle, + Fuel, + Info, + ArrowLeftRight, +} from 'lucide-react'; import { useWallet } from '@/contexts/WalletContext'; import { useTelegram } from '@/hooks/useTelegram'; type TargetChain = 'asset-hub' | 'people'; +type TeleportDirection = 'to-parachain' | 'to-relay'; interface ChainInfo { id: TargetChain; @@ -45,6 +56,7 @@ export function FundFeesModal({ isOpen, onClose }: Props) { const { hapticImpact, showAlert } = useTelegram(); const [targetChain, setTargetChain] = useState('asset-hub'); + const [direction, setDirection] = useState('to-parachain'); const [amount, setAmount] = useState(''); const [isTransferring, setIsTransferring] = useState(false); const [txStatus, setTxStatus] = useState<'idle' | 'signing' | 'pending' | 'success' | 'error'>( @@ -56,6 +68,38 @@ export function FundFeesModal({ isOpen, onClose }: Props) { const selectedChain = TARGET_CHAINS.find((c) => c.id === targetChain) || TARGET_CHAINS[0]; + // Get source balance based on direction + const getSourceBalance = () => { + if (direction === 'to-parachain') { + return relayBalance; + } + return targetChain === 'asset-hub' ? assetHubBalance : peopleBalance; + }; + + // Get destination balance based on direction + const getDestBalance = () => { + if (direction === 'to-parachain') { + return targetChain === 'asset-hub' ? assetHubBalance : peopleBalance; + } + return relayBalance; + }; + + // Get source chain name + const getSourceChainName = () => { + if (direction === 'to-parachain') { + return 'Relay Chain'; + } + return selectedChain.name; + }; + + // Get destination chain name + const getDestChainName = () => { + if (direction === 'to-parachain') { + return selectedChain.name; + } + return 'Relay Chain'; + }; + // Fetch balances useEffect(() => { const fetchBalances = async () => { @@ -121,12 +165,8 @@ export function FundFeesModal({ isOpen, onClose }: Props) { } }, [api, assetHubApi, peopleApi, address, isOpen]); - const getTargetBalance = () => { - return targetChain === 'asset-hub' ? assetHubBalance : peopleBalance; - }; - const handleTeleport = async () => { - if (!api || !address || !keypair) { + if (!address || !keypair) { showAlert('Cizdan girêdayî nîne'); return; } @@ -136,19 +176,34 @@ export function FundFeesModal({ isOpen, onClose }: Props) { return; } - if (relayBalance === '--') { - showAlert('Relay Chain girêdayî nîne'); + const sourceBalance = getSourceBalance(); + if (sourceBalance === '--') { + showAlert('Zincîr girêdayî nîne'); return; } const sendAmount = parseFloat(amount); - const currentBalance = parseFloat(relayBalance); + const currentBalance = parseFloat(sourceBalance); if (sendAmount > currentBalance) { showAlert('Bakiye têrê nake'); return; } + // Get the appropriate API based on direction + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let sourceApi: any; + if (direction === 'to-parachain') { + sourceApi = api; + } else { + sourceApi = targetChain === 'asset-hub' ? assetHubApi : peopleApi; + } + + if (!sourceApi) { + showAlert('API girêdayî nîne'); + return; + } + setIsTransferring(true); setTxStatus('signing'); hapticImpact('medium'); @@ -157,103 +212,156 @@ export function FundFeesModal({ isOpen, onClose }: Props) { // Convert to smallest unit (12 decimals) const amountInSmallestUnit = BigInt(Math.floor(parseFloat(amount) * 1e12)); - // Get target teyrchain ID - const targetTeyrchainId = selectedChain.teyrchainId; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let dest: any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let beneficiary: any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let assets: any; - // Destination: Target teyrchain - const dest = { - V3: { - parents: 0, - interior: { - X1: { teyrchain: targetTeyrchainId }, - }, - }, - }; + if (direction === 'to-parachain') { + // Relay Chain → Parachain + const targetTeyrchainId = selectedChain.teyrchainId; - // Beneficiary: Same account on target chain - const beneficiary = { - V3: { - parents: 0, - interior: { - X1: { - accountid32: { - network: null, - id: api.createType('AccountId32', address).toHex(), - }, - }, - }, - }, - }; - - // Assets: Native token (HEZ) - const assets = { - V3: [ - { - id: { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - fun: { - Fungible: amountInSmallestUnit.toString(), - }, - }, - ], - }; - - // Fee asset ID: Native HEZ token - const feeAssetId = { - V3: { - Concrete: { + dest = { + V3: { parents: 0, + interior: { + X1: { Parachain: targetTeyrchainId }, + }, + }, + }; + + beneficiary = { + V3: { + parents: 0, + interior: { + X1: { + AccountId32: { + network: null, + id: sourceApi.createType('AccountId32', address).toHex(), + }, + }, + }, + }, + }; + + // Native token on relay chain + assets = { + V3: [ + { + id: { + Concrete: { + parents: 0, + interior: 'Here', + }, + }, + fun: { + Fungible: amountInSmallestUnit.toString(), + }, + }, + ], + }; + } else { + // Parachain → Relay Chain + dest = { + V3: { + parents: 1, interior: 'Here', }, - }, - }; + }; + + beneficiary = { + V3: { + parents: 0, + interior: { + X1: { + AccountId32: { + network: null, + id: sourceApi.createType('AccountId32', address).toHex(), + }, + }, + }, + }, + }; + + // Native token from parachain's perspective (parent chain's token) + assets = { + V3: [ + { + id: { + Concrete: { + parents: 1, + interior: 'Here', + }, + }, + fun: { + Fungible: amountInSmallestUnit.toString(), + }, + }, + ], + }; + } const weightLimit = 'Unlimited'; - // Create teleport transaction - const tx = api.tx.xcmPallet.limitedTeleportAssets( - dest, - beneficiary, - assets, - feeAssetId, - weightLimit - ); + // Create teleport transaction using polkadotXcm pallet on parachains + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let tx: any; + if (direction === 'to-parachain') { + tx = sourceApi.tx.xcmPallet.limitedTeleportAssets( + dest, + beneficiary, + assets, + 0, + weightLimit + ); + } else { + // Parachains use polkadotXcm pallet + tx = sourceApi.tx.polkadotXcm.limitedTeleportAssets( + dest, + beneficiary, + assets, + 0, + weightLimit + ); + } setTxStatus('pending'); - const unsub = await tx.signAndSend(keypair, ({ status, dispatchError }) => { - if (status.isFinalized) { - if (dispatchError) { - let errorMessage = 'Teleport neserketî'; + const unsub = await tx.signAndSend( + keypair, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ({ status, dispatchError }: any) => { + if (status.isFinalized) { + if (dispatchError) { + let errorMessage = 'Teleport neserketî'; - if (dispatchError.isModule) { - const decoded = api.registry.findMetaError(dispatchError.asModule); - errorMessage = `${decoded.section}.${decoded.name}`; + if (dispatchError.isModule) { + const decoded = sourceApi.registry.findMetaError(dispatchError.asModule); + errorMessage = `${decoded.section}.${decoded.name}`; + } + + setTxStatus('error'); + hapticImpact('heavy'); + showAlert(errorMessage); + } else { + setTxStatus('success'); + hapticImpact('medium'); + + // Reset after success + setTimeout(() => { + setAmount(''); + setTxStatus('idle'); + onClose(); + }, 2000); } - setTxStatus('error'); - hapticImpact('heavy'); - showAlert(errorMessage); - } else { - setTxStatus('success'); - hapticImpact('medium'); - - // Reset after success - setTimeout(() => { - setAmount(''); - setTxStatus('idle'); - onClose(); - }, 2000); + setIsTransferring(false); + unsub(); } - - setIsTransferring(false); - unsub(); } - }); + ); } catch (error) { console.error('Teleport error:', error); setTxStatus('error'); @@ -264,13 +372,19 @@ export function FundFeesModal({ isOpen, onClose }: Props) { }; const setQuickAmount = (percent: number) => { - const balance = parseFloat(relayBalance); + const balance = parseFloat(getSourceBalance()); if (balance > 0) { const quickAmount = ((balance * percent) / 100).toFixed(4); setAmount(quickAmount); } }; + const toggleDirection = () => { + setDirection((prev) => (prev === 'to-parachain' ? 'to-relay' : 'to-parachain')); + setAmount(''); + hapticImpact('light'); + }; + if (!isOpen) return null; return ( @@ -301,7 +415,7 @@ export function FundFeesModal({ isOpen, onClose }: Props) {

Serketî!

- {amount} HEZ bo {selectedChain.name} hate şandin + {amount} HEZ bo {getDestChainName()} hate şandin

) : txStatus === 'error' ? ( @@ -348,52 +462,70 @@ export function FundFeesModal({ isOpen, onClose }: Props) { + {/* Direction Toggle */} +
+ +
+ {/* Balance Display */}
-
- Relay Chain +
+ {getSourceChainName()}
- {relayBalance} HEZ + {getSourceBalance()} HEZ
- + {direction === 'to-parachain' ? ( + + ) : ( + + )}
- {selectedChain.name} + {getDestChainName()}
- {getTargetBalance()} HEZ + {getDestBalance()} HEZ
{/* Info Box */} -
- -

- {selectedChain.description} kêmî 0.1 HEZ tê pêşniyarkirin. +

+ +

+ {direction === 'to-parachain' + ? `${selectedChain.description} kêmî 0.1 HEZ tê pêşniyarkirin.` + : 'HEZ ji parachainê vedigere Relay Chainê.'}

@@ -458,7 +590,7 @@ export function FundFeesModal({ isOpen, onClose }: Props) { ) : ( <> - Bo {selectedChain.name} Bişîne + Bo {getDestChainName()} Bişîne )} diff --git a/src/version.json b/src/version.json index 11ee477..04de19e 100644 --- a/src/version.json +++ b/src/version.json @@ -1,5 +1,5 @@ { - "version": "1.0.159", - "buildTime": "2026-02-07T14:59:50.270Z", - "buildNumber": 1770476390271 + "version": "1.0.160", + "buildTime": "2026-02-07T18:31:26.019Z", + "buildNumber": 1770489086020 }