import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { usePezkuwi } from '@/contexts/PezkuwiContext'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { ArrowDown, Loader2, CheckCircle, XCircle, Info } from 'lucide-react'; import { useToast } from '@/hooks/use-toast'; type TargetChain = 'asset-hub' | 'people'; interface ChainInfo { id: TargetChain; name: string; description: string; teyrchainId: number; color: string; } const TARGET_CHAINS: ChainInfo[] = [ { id: 'asset-hub', name: 'Pezkuwi Asset Hub', description: 'For PEZ token transfers', teyrchainId: 1000, color: 'blue', }, { id: 'people', name: 'Pezkuwi People', description: 'For identity & citizenship', teyrchainId: 1004, color: 'purple', }, ]; interface XCMTeleportModalProps { isOpen: boolean; onClose: () => void; } export const XCMTeleportModal: React.FC = ({ isOpen, onClose }) => { const { api, assetHubApi, peopleApi, isApiReady, isAssetHubReady, isPeopleReady, selectedAccount, walletSource } = usePezkuwi(); const { toast } = useToast(); const { t } = useTranslation(); const [targetChain, setTargetChain] = useState('asset-hub'); const [amount, setAmount] = useState(''); const [isTransferring, setIsTransferring] = useState(false); const [txStatus, setTxStatus] = useState<'idle' | 'signing' | 'pending' | 'success' | 'error'>('idle'); const [txHash, setTxHash] = useState(''); const [relayBalance, setRelayBalance] = useState('0'); const [assetHubBalance, setAssetHubBalance] = useState('0'); const [peopleBalance, setPeopleBalance] = useState('0'); const selectedChain = TARGET_CHAINS.find(c => c.id === targetChain)!; const chainName = targetChain === 'asset-hub' ? t('xcm.assetHubName') : t('xcm.peopleName'); const chainDesc = targetChain === 'asset-hub' ? t('xcm.assetHubDesc') : t('xcm.peopleDesc'); // Fetch balances useEffect(() => { const fetchBalances = async () => { if (!selectedAccount?.address) return; // Relay chain balance if (api && isApiReady) { try { const accountInfo = await api.query.system.account(selectedAccount.address) as { data: { free: { toString(): string } } }; const free = accountInfo.data.free.toString(); const balanceNum = Number(free) / 1e12; setRelayBalance(balanceNum.toFixed(4)); } catch (err) { console.error('Error fetching relay balance:', err); } } // Asset Hub balance if (assetHubApi && isAssetHubReady) { try { const accountInfo = await assetHubApi.query.system.account(selectedAccount.address) as { data: { free: { toString(): string } } }; const free = accountInfo.data.free.toString(); const balanceNum = Number(free) / 1e12; setAssetHubBalance(balanceNum.toFixed(4)); } catch (err) { console.error('Error fetching Asset Hub balance:', err); } } // People chain balance if (peopleApi && isPeopleReady) { try { const accountInfo = await peopleApi.query.system.account(selectedAccount.address) as { data: { free: { toString(): string } } }; const free = accountInfo.data.free.toString(); const balanceNum = Number(free) / 1e12; setPeopleBalance(balanceNum.toFixed(4)); } catch (err) { console.error('Error fetching People chain balance:', err); } } }; if (isOpen) { fetchBalances(); } }, [api, assetHubApi, peopleApi, isApiReady, isAssetHubReady, isPeopleReady, selectedAccount, isOpen]); const getTargetBalance = () => { return targetChain === 'asset-hub' ? assetHubBalance : peopleBalance; }; const handleTeleport = async () => { if (!api || !isApiReady || !selectedAccount) { toast({ title: t('common.error'), description: t('xcm.walletNotConnected'), variant: "destructive", }); return; } if (!amount || parseFloat(amount) <= 0) { toast({ title: t('common.error'), description: t('xcm.invalidAmount'), variant: "destructive", }); return; } const sendAmount = parseFloat(amount); const currentBalance = parseFloat(relayBalance); if (sendAmount > currentBalance) { toast({ title: t('common.error'), description: t('xcm.insufficientBalance'), variant: "destructive", }); return; } setIsTransferring(true); setTxStatus('signing'); try { const { getSigner } = await import('@/lib/get-signer'); const injector = await getSigner(selectedAccount.address, walletSource, api); // Convert to smallest unit (12 decimals) const amountInSmallestUnit = BigInt(Math.floor(parseFloat(amount) * 1e12)); // Get target teyrchain ID const targetTeyrchainId = selectedChain.teyrchainId; // Destination: Target teyrchain const dest = { V3: { parents: 0, interior: { X1: { teyrchain: targetTeyrchainId } } } }; // Beneficiary: Same account on target chain const beneficiary = { V3: { parents: 0, interior: { X1: { accountid32: { network: null, id: api.createType('AccountId32', selectedAccount.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 (VersionedAssetId format) const feeAssetId = { V3: { Concrete: { parents: 0, interior: 'Here' } } }; const weightLimit = 'Unlimited'; // Create teleport transaction const tx = api.tx.xcmPallet.limitedTeleportAssets( dest, beneficiary, assets, feeAssetId, weightLimit ); setTxStatus('pending'); const unsub = await tx.signAndSend( selectedAccount.address, { signer: injector.signer }, ({ status, dispatchError }) => { if (status.isInBlock) { if (import.meta.env.DEV) console.log(`XCM Teleport in block: ${status.asInBlock}`); setTxHash(status.asInBlock.toHex()); } if (status.isFinalized) { if (dispatchError) { let errorMessage = t('xcm.failed'); if (dispatchError.isModule) { const decoded = api.registry.findMetaError(dispatchError.asModule); errorMessage = `${decoded.section}.${decoded.name}: ${decoded.docs}`; } setTxStatus('error'); toast({ title: t('xcm.failed'), description: errorMessage, variant: "destructive", }); } else { setTxStatus('success'); toast({ title: t('xcm.success'), description: t('xcm.sentTo', { amount, chain: chainName }), }); // Reset after success setTimeout(() => { setAmount(''); setTxStatus('idle'); setTxHash(''); onClose(); }, 3000); } setIsTransferring(false); unsub(); } } ); } catch (error) { console.error('Teleport error:', error); setTxStatus('error'); setIsTransferring(false); toast({ title: t('xcm.failed'), description: error instanceof Error ? error.message : t('xcm.errorOccurred'), variant: "destructive", }); } }; const handleClose = () => { if (!isTransferring) { setAmount(''); setTxStatus('idle'); setTxHash(''); onClose(); } }; const setQuickAmount = (percent: number) => { const balance = parseFloat(relayBalance); if (balance > 0) { const quickAmount = (balance * percent / 100).toFixed(4); setAmount(quickAmount); } }; return ( HEZ {t('xcm.title')} {t('xcm.description')} {txStatus === 'success' ? (

{t('xcm.success')}

{t('xcm.sentTo', { amount, chain: chainName })}

{txHash && (
{t('xcm.txHash')}
{txHash}
)}
) : txStatus === 'error' ? (

{t('xcm.failed')}

{t('xcm.pleaseTryAgain')}

) : (
{/* Target Chain Selection */}
{/* Balance Display */}
{t('xcm.relayChain')}
{relayBalance} HEZ
{chainName}
{getTargetBalance()} HEZ
{/* Info Box */}

{chainDesc}. {t('xcm.teleportMinHez')}

{/* Amount Input */}
setAmount(e.target.value)} placeholder="0.1" className="bg-gray-800 border-gray-700 text-white mt-2" disabled={isTransferring} /> {/* Quick Amount Buttons */}
{[10, 25, 50, 100].map((percent) => ( ))}
{/* Status Messages */} {txStatus === 'signing' && (

{t('xcm.signTransaction')}

)} {txStatus === 'pending' && (

{t('xcm.inProgress')}

)} {/* Submit Button */}
)}
); };