import React, { useState, useEffect } 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 { parseTokenInput, formatTokenBalance } from '@pezkuwi/utils/dex'; interface CreatePoolModalProps { isOpen: boolean; onClose: () => void; onSuccess?: () => void; } type TransactionStatus = 'idle' | 'signing' | 'submitting' | 'success' | 'error'; export const CreatePoolModal: React.FC = ({ isOpen, onClose, onSuccess, }) => { // Use Asset Hub API for DEX operations (assetConversion pallet is on Asset Hub) const { assetHubApi, isAssetHubReady } = usePezkuwi(); const { account, signer } = useWallet(); const [asset1Id, setAsset1Id] = useState(null); const [asset2Id, setAsset2Id] = useState(null); const [amount1Input, setAmount1Input] = useState(''); const [amount2Input, setAmount2Input] = useState(''); const [balance1, setBalance1] = useState('0'); const [balance2, setBalance2] = useState('0'); const [txStatus, setTxStatus] = useState('idle'); const [errorMessage, setErrorMessage] = useState(''); // Available tokens const availableTokens = Object.values(KNOWN_TOKENS); // Reset form when modal closes useEffect(() => { if (!isOpen) { setAsset1Id(null); setAsset2Id(null); setAmount1Input(''); setAmount2Input(''); setTxStatus('idle'); setErrorMessage(''); } }, [isOpen]); // 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'); } }; fetchBalances(); }, [assetHubApi, isAssetHubReady, account, 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'); } }; fetchBalances(); }, [assetHubApi, isAssetHubReady, account, asset2Id]); const validateInputs = (): string | null => { if (asset1Id === null || asset2Id === null) { return 'Please select both tokens'; } if (asset1Id === asset2Id) { return 'Cannot create pool with same token'; } if (!amount1Input || !amount2Input) { return 'Please enter amounts for both tokens'; } const token1 = KNOWN_TOKENS[asset1Id]; const token2 = KNOWN_TOKENS[asset2Id]; if (!token1 || !token2) { return 'Invalid token selected'; } const amount1Raw = parseTokenInput(amount1Input, token1.decimals); const amount2Raw = parseTokenInput(amount2Input, token2.decimals); if (import.meta.env.DEV) console.log('💰 Validation check:', { token1: token1.symbol, amount1Input, amount1Raw, balance1, hasEnough1: BigInt(amount1Raw) <= BigInt(balance1), token2: token2.symbol, amount2Input, amount2Raw, balance2, hasEnough2: BigInt(amount2Raw) <= BigInt(balance2), }); if (BigInt(amount1Raw) <= BigInt(0) || BigInt(amount2Raw) <= BigInt(0)) { return 'Amounts must be greater than zero'; } if (BigInt(amount1Raw) > BigInt(balance1)) { return `Insufficient ${token1.symbol} balance`; } if (BigInt(amount2Raw) > BigInt(balance2)) { return `Insufficient ${token2.symbol} balance`; } return null; }; const handleCreatePool = async () => { if (!assetHubApi || !isAssetHubReady || !signer || !account) { setErrorMessage('Wallet not connected or Asset Hub not ready'); return; } // Check if assetConversion pallet is available on Asset Hub if (!assetHubApi.tx.assetConversion || !assetHubApi.tx.assetConversion.createPool) { setErrorMessage('AssetConversion pallet is not available on Asset Hub. Pool creation requires this pallet.'); return; } const validationError = validateInputs(); if (validationError) { setErrorMessage(validationError); return; } const token1 = KNOWN_TOKENS[asset1Id!]; const token2 = KNOWN_TOKENS[asset2Id!]; const amount1Raw = parseTokenInput(amount1Input, token1.decimals); const amount2Raw = parseTokenInput(amount2Input, token2.decimals); try { setTxStatus('signing'); setErrorMessage(''); // Convert asset IDs to proper format for assetConversion pallet // Native token uses { Native: null }, assets use { Asset: id } const formatAssetId = (id: number) => { // For now, all our tokens are assets on Asset Hub return { Asset: id }; }; const asset1 = formatAssetId(asset1Id!); const asset2 = formatAssetId(asset2Id!); if (import.meta.env.DEV) { console.log('🏊 Creating pool with:', { asset1, asset2, amount1Raw, amount2Raw }); } // Create pool extrinsic on Asset Hub const createPoolTx = assetHubApi.tx.assetConversion.createPool(asset1, asset2); // Add liquidity extrinsic on Asset Hub const addLiquidityTx = assetHubApi.tx.assetConversion.addLiquidity( asset1, asset2, amount1Raw, amount2Raw, amount1Raw, // min amount1 amount2Raw, // min amount2 account ); // Batch transactions const batchTx = assetHubApi.tx.utility.batchAll([createPoolTx, addLiquidityTx]); setTxStatus('submitting'); await batchTx.signAndSend( account, { signer }, ({ status, dispatchError }) => { if (status.isInBlock) { if (dispatchError) { if (dispatchError.isModule) { const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule); setErrorMessage(`${decoded.section}.${decoded.name}: ${decoded.docs}`); } else { setErrorMessage(dispatchError.toString()); } setTxStatus('error'); } else { setTxStatus('success'); setTimeout(() => { onSuccess?.(); onClose(); }, 2000); } } } ); } catch (error) { if (import.meta.env.DEV) console.error('Pool creation failed:', error); setErrorMessage(error instanceof Error ? error.message : 'Transaction failed'); setTxStatus('error'); } }; if (!isOpen) return null; const token1 = asset1Id !== null ? KNOWN_TOKENS[asset1Id] : null; const token2 = asset2Id !== null ? KNOWN_TOKENS[asset2Id] : null; const exchangeRate = amount1Input && amount2Input && parseFloat(amount1Input) > 0 ? (parseFloat(amount2Input) / parseFloat(amount1Input)).toFixed(6) : '0'; return (
Create New Pool
Founder Only
{/* Token 1 Selection */}
{token1 && (
Balance: {formatTokenBalance(balance1, token1.decimals, 4)} {token1.symbol}
)}
{/* Amount 1 Input */} {token1 && (
setAmount1Input(e.target.value)} placeholder="0.0" className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-green-500" disabled={txStatus === 'signing' || txStatus === 'submitting'} />
)} {/* Plus Icon */}
{/* Token 2 Selection */}
{token2 && (
Balance: {formatTokenBalance(balance2, token2.decimals, 4)} {token2.symbol}
)}
{/* Amount 2 Input */} {token2 && (
setAmount2Input(e.target.value)} placeholder="0.0" className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-green-500" disabled={txStatus === 'signing' || txStatus === 'submitting'} />
)} {/* Exchange Rate Preview */} {token1 && token2 && amount1Input && amount2Input && (
Initial Exchange Rate
1 {token1.symbol} = {exchangeRate} {token2.symbol}
)} {/* Error Message */} {errorMessage && (
{errorMessage}
)} {/* Success Message */} {txStatus === 'success' && (
Pool created successfully!
)} {/* Action Buttons */}
); };