mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-22 11:18:01 +00:00
feat: complete i18n support for all components (6 languages)
Add full internationalization across 127+ components and pages. 790+ translation keys in en, tr, kmr, ckb, ar, fa locales. Remove duplicate keys and delete unused .json locale files.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { usePezkuwi } from '@/contexts/PezkuwiContext';
|
||||
import { useWallet } from '@/contexts/WalletContext';
|
||||
import { X, Plus, AlertCircle, Loader2, CheckCircle, Info } from 'lucide-react';
|
||||
@@ -34,6 +35,7 @@ export const AddLiquidityModal: React.FC<AddLiquidityModalProps> = ({
|
||||
// Use Asset Hub API for DEX operations
|
||||
const { assetHubApi, isAssetHubReady } = usePezkuwi();
|
||||
const { account, signer } = useWallet();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [amount1Input, setAmount1Input] = useState('');
|
||||
const [amount2Input, setAmount2Input] = useState('');
|
||||
@@ -125,22 +127,22 @@ export const AddLiquidityModal: React.FC<AddLiquidityModalProps> = ({
|
||||
};
|
||||
|
||||
const validateInputs = (): string | null => {
|
||||
if (!pool) return 'No pool selected';
|
||||
if (!amount1Input || !amount2Input) return 'Please enter amounts';
|
||||
if (!pool) return t('addLiquidity.noPoolSelected');
|
||||
if (!amount1Input || !amount2Input) return t('addLiquidity.enterAmounts');
|
||||
|
||||
const amount1Raw = parseTokenInput(amount1Input, pool.asset1Decimals);
|
||||
const amount2Raw = parseTokenInput(amount2Input, pool.asset2Decimals);
|
||||
|
||||
if (BigInt(amount1Raw) <= BigInt(0) || BigInt(amount2Raw) <= BigInt(0)) {
|
||||
return 'Amounts must be greater than zero';
|
||||
return t('common.amountGtZero');
|
||||
}
|
||||
|
||||
if (BigInt(amount1Raw) > BigInt(balance1)) {
|
||||
return `Insufficient ${pool.asset1Symbol} balance`;
|
||||
return t('common.insufficientBalance', { symbol: pool.asset1Symbol });
|
||||
}
|
||||
|
||||
if (BigInt(amount2Raw) > BigInt(balance2)) {
|
||||
return `Insufficient ${pool.asset2Symbol} balance`;
|
||||
return t('common.insufficientBalance', { symbol: pool.asset2Symbol });
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -148,7 +150,7 @@ export const AddLiquidityModal: React.FC<AddLiquidityModalProps> = ({
|
||||
|
||||
const handleAddLiquidity = async () => {
|
||||
if (!assetHubApi || !isAssetHubReady || !signer || !account || !pool) {
|
||||
setErrorMessage('Wallet not connected');
|
||||
setErrorMessage(t('common.walletNotConnected'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -213,7 +215,7 @@ export const AddLiquidityModal: React.FC<AddLiquidityModalProps> = ({
|
||||
);
|
||||
} catch (error) {
|
||||
if (import.meta.env.DEV) console.error('Add liquidity failed:', error);
|
||||
setErrorMessage(error instanceof Error ? error.message : 'Transaction failed');
|
||||
setErrorMessage(error instanceof Error ? error.message : t('common.txFailed'));
|
||||
setTxStatus('error');
|
||||
}
|
||||
};
|
||||
@@ -248,7 +250,7 @@ export const AddLiquidityModal: React.FC<AddLiquidityModalProps> = ({
|
||||
<CardHeader className="border-b border-gray-800">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-xl font-bold text-white">
|
||||
Add Liquidity
|
||||
{t('addLiquidity.title')}
|
||||
</CardTitle>
|
||||
<button
|
||||
onClick={onClose}
|
||||
@@ -259,7 +261,7 @@ export const AddLiquidityModal: React.FC<AddLiquidityModalProps> = ({
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-sm text-gray-400 mt-2">
|
||||
{pool.asset1Symbol} / {pool.asset2Symbol} Pool
|
||||
{t('addLiquidity.pool', { asset1: pool.asset1Symbol, asset2: pool.asset2Symbol })}
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
@@ -268,7 +270,7 @@ export const AddLiquidityModal: React.FC<AddLiquidityModalProps> = ({
|
||||
<div className="flex items-start gap-2 p-3 bg-blue-500/10 border border-blue-500/30 rounded-lg">
|
||||
<Info className="w-5 h-5 text-blue-400 flex-shrink-0 mt-0.5" />
|
||||
<span className="text-sm text-blue-400">
|
||||
Add liquidity in proportion to the pool's current ratio. You'll receive LP tokens representing your share.
|
||||
{t('addLiquidity.info')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -277,7 +279,7 @@ export const AddLiquidityModal: React.FC<AddLiquidityModalProps> = ({
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm text-gray-400">{pool.asset1Symbol}</label>
|
||||
<span className="text-xs text-gray-500">
|
||||
Balance: {formatTokenBalance(balance1, pool.asset1Decimals, 4)}
|
||||
{t('common.balance')}: {formatTokenBalance(balance1, pool.asset1Decimals, 4)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative">
|
||||
@@ -296,7 +298,7 @@ export const AddLiquidityModal: React.FC<AddLiquidityModalProps> = ({
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 px-3 py-1 bg-green-600/20 hover:bg-green-600/30 text-green-400 text-xs rounded border border-green-600/30 transition-colors"
|
||||
disabled={txStatus === 'signing' || txStatus === 'submitting'}
|
||||
>
|
||||
MAX
|
||||
{t('common.max')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -313,7 +315,7 @@ export const AddLiquidityModal: React.FC<AddLiquidityModalProps> = ({
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm text-gray-400">{pool.asset2Symbol}</label>
|
||||
<span className="text-xs text-gray-500">
|
||||
Balance: {formatTokenBalance(balance2, pool.asset2Decimals, 4)}
|
||||
{t('common.balance')}: {formatTokenBalance(balance2, pool.asset2Decimals, 4)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative">
|
||||
@@ -332,14 +334,14 @@ export const AddLiquidityModal: React.FC<AddLiquidityModalProps> = ({
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 px-3 py-1 bg-green-600/20 hover:bg-green-600/30 text-green-400 text-xs rounded border border-green-600/30 transition-colors"
|
||||
disabled={txStatus === 'signing' || txStatus === 'submitting'}
|
||||
>
|
||||
MAX
|
||||
{t('common.max')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Slippage Tolerance */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm text-gray-400">Slippage Tolerance</label>
|
||||
<label className="text-sm text-gray-400">{t('common.slippageTolerance')}</label>
|
||||
<div className="flex gap-2">
|
||||
{[0.5, 1, 2].map((value) => (
|
||||
<button
|
||||
@@ -362,11 +364,11 @@ export const AddLiquidityModal: React.FC<AddLiquidityModalProps> = ({
|
||||
{amount1Input && amount2Input && (
|
||||
<div className="p-4 bg-gray-800/50 rounded-lg border border-gray-700 space-y-2">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-gray-400">Share of Pool</span>
|
||||
<span className="text-gray-400">{t('addLiquidity.shareOfPool')}</span>
|
||||
<span className="text-white font-mono">{shareOfPool}%</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-gray-400">Exchange Rate</span>
|
||||
<span className="text-gray-400">{t('common.exchangeRate')}</span>
|
||||
<span className="text-cyan-400 font-mono">
|
||||
1 {pool.asset1Symbol} ={' '}
|
||||
{(
|
||||
@@ -392,7 +394,7 @@ export const AddLiquidityModal: React.FC<AddLiquidityModalProps> = ({
|
||||
<div className="flex items-start gap-2 p-3 bg-green-500/10 border border-green-500/30 rounded-lg">
|
||||
<CheckCircle className="w-5 h-5 text-green-400 flex-shrink-0 mt-0.5" />
|
||||
<span className="text-sm text-green-400">
|
||||
Liquidity added successfully!
|
||||
{t('addLiquidity.success')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -404,7 +406,7 @@ export const AddLiquidityModal: React.FC<AddLiquidityModalProps> = ({
|
||||
className="flex-1 px-6 py-3 bg-gray-800 hover:bg-gray-700 text-white rounded-lg transition-colors border border-gray-700"
|
||||
disabled={txStatus === 'signing' || txStatus === 'submitting'}
|
||||
>
|
||||
Cancel
|
||||
{t('common.cancel')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleAddLiquidity}
|
||||
@@ -418,21 +420,21 @@ export const AddLiquidityModal: React.FC<AddLiquidityModalProps> = ({
|
||||
{txStatus === 'signing' && (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
Signing...
|
||||
{t('common.signing')}
|
||||
</>
|
||||
)}
|
||||
{txStatus === 'submitting' && (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
Adding...
|
||||
{t('addLiquidity.adding')}
|
||||
</>
|
||||
)}
|
||||
{txStatus === 'idle' && 'Add Liquidity'}
|
||||
{txStatus === 'error' && 'Retry'}
|
||||
{txStatus === 'idle' && t('addLiquidity.title')}
|
||||
{txStatus === 'error' && t('common.retry')}
|
||||
{txStatus === 'success' && (
|
||||
<>
|
||||
<CheckCircle className="w-4 h-4" />
|
||||
Success
|
||||
{t('common.success')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { KNOWN_TOKENS, NATIVE_TOKEN_ID } from '@/types/dex';
|
||||
import { parseTokenInput, formatTokenBalance } from '@pezkuwi/utils/dex';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface CreatePoolModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -23,6 +24,7 @@ export const CreatePoolModal: React.FC<CreatePoolModalProps> = ({
|
||||
// Use Asset Hub API for DEX operations (assetConversion pallet is on Asset Hub)
|
||||
const { assetHubApi, isAssetHubReady } = usePezkuwi();
|
||||
const { account, signer } = useWallet();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [asset1Id, setAsset1Id] = useState<number | null>(null);
|
||||
const [asset2Id, setAsset2Id] = useState<number | null>(null);
|
||||
@@ -99,22 +101,22 @@ export const CreatePoolModal: React.FC<CreatePoolModalProps> = ({
|
||||
|
||||
const validateInputs = (): string | null => {
|
||||
if (asset1Id === null || asset2Id === null) {
|
||||
return 'Please select both tokens';
|
||||
return t('createPool.selectBothTokens');
|
||||
}
|
||||
|
||||
if (asset1Id === asset2Id) {
|
||||
return 'Cannot create pool with same token';
|
||||
return t('createPool.sameToken');
|
||||
}
|
||||
|
||||
if (!amount1Input || !amount2Input) {
|
||||
return 'Please enter amounts for both tokens';
|
||||
return t('createPool.enterBothAmounts');
|
||||
}
|
||||
|
||||
const token1 = KNOWN_TOKENS[asset1Id];
|
||||
const token2 = KNOWN_TOKENS[asset2Id];
|
||||
|
||||
if (!token1 || !token2) {
|
||||
return 'Invalid token selected';
|
||||
return t('createPool.invalidToken');
|
||||
}
|
||||
|
||||
const amount1Raw = parseTokenInput(amount1Input, token1.decimals);
|
||||
@@ -134,15 +136,15 @@ export const CreatePoolModal: React.FC<CreatePoolModalProps> = ({
|
||||
});
|
||||
|
||||
if (BigInt(amount1Raw) <= BigInt(0) || BigInt(amount2Raw) <= BigInt(0)) {
|
||||
return 'Amounts must be greater than zero';
|
||||
return t('common.amountGtZero');
|
||||
}
|
||||
|
||||
if (BigInt(amount1Raw) > BigInt(balance1)) {
|
||||
return `Insufficient ${token1.symbol} balance`;
|
||||
return t('common.insufficientBalance', { symbol: token1.symbol });
|
||||
}
|
||||
|
||||
if (BigInt(amount2Raw) > BigInt(balance2)) {
|
||||
return `Insufficient ${token2.symbol} balance`;
|
||||
return t('common.insufficientBalance', { symbol: token2.symbol });
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -150,13 +152,13 @@ export const CreatePoolModal: React.FC<CreatePoolModalProps> = ({
|
||||
|
||||
const handleCreatePool = async () => {
|
||||
if (!assetHubApi || !isAssetHubReady || !signer || !account) {
|
||||
setErrorMessage('Wallet not connected or Asset Hub not ready');
|
||||
setErrorMessage(t('createPool.walletNotReady'));
|
||||
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.');
|
||||
setErrorMessage(t('createPool.palletNotAvailable'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -238,7 +240,7 @@ export const CreatePoolModal: React.FC<CreatePoolModalProps> = ({
|
||||
);
|
||||
} catch (error) {
|
||||
if (import.meta.env.DEV) console.error('Pool creation failed:', error);
|
||||
setErrorMessage(error instanceof Error ? error.message : 'Transaction failed');
|
||||
setErrorMessage(error instanceof Error ? error.message : t('common.txFailed'));
|
||||
setTxStatus('error');
|
||||
}
|
||||
};
|
||||
@@ -259,7 +261,7 @@ export const CreatePoolModal: React.FC<CreatePoolModalProps> = ({
|
||||
<CardHeader className="border-b border-gray-800">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-xl font-bold text-white">
|
||||
Create New Pool
|
||||
{t('createPool.title')}
|
||||
</CardTitle>
|
||||
<button
|
||||
onClick={onClose}
|
||||
@@ -270,21 +272,21 @@ export const CreatePoolModal: React.FC<CreatePoolModalProps> = ({
|
||||
</button>
|
||||
</div>
|
||||
<Badge className="bg-green-600/20 text-green-400 border-green-600/30 w-fit mt-2">
|
||||
Founder Only
|
||||
{t('createPool.founderOnly')}
|
||||
</Badge>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="space-y-6 pt-6">
|
||||
{/* Token 1 Selection */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm text-gray-400">Token 1</label>
|
||||
<label className="text-sm text-gray-400">{t('createPool.token1')}</label>
|
||||
<select
|
||||
value={asset1Id ?? ''}
|
||||
onChange={(e) => setAsset1Id(Number(e.target.value))}
|
||||
className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
disabled={txStatus === 'signing' || txStatus === 'submitting'}
|
||||
>
|
||||
<option value="">Select token...</option>
|
||||
<option value="">{t('createPool.selectToken')}</option>
|
||||
{availableTokens.map((token) => (
|
||||
<option key={token.id} value={token.id}>
|
||||
{token.symbol} - {token.name}
|
||||
@@ -293,7 +295,7 @@ export const CreatePoolModal: React.FC<CreatePoolModalProps> = ({
|
||||
</select>
|
||||
{token1 && (
|
||||
<div className="text-xs text-gray-500">
|
||||
Balance: {formatTokenBalance(balance1, token1.decimals, 4)} {token1.symbol}
|
||||
{t('common.balance')}: {formatTokenBalance(balance1, token1.decimals, 4)} {token1.symbol}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -302,7 +304,7 @@ export const CreatePoolModal: React.FC<CreatePoolModalProps> = ({
|
||||
{token1 && (
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm text-gray-400">
|
||||
Amount of {token1.symbol}
|
||||
{t('createPool.amountOf', { symbol: token1.symbol })}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -324,14 +326,14 @@ export const CreatePoolModal: React.FC<CreatePoolModalProps> = ({
|
||||
|
||||
{/* Token 2 Selection */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm text-gray-400">Token 2</label>
|
||||
<label className="text-sm text-gray-400">{t('createPool.token2')}</label>
|
||||
<select
|
||||
value={asset2Id ?? ''}
|
||||
onChange={(e) => setAsset2Id(Number(e.target.value))}
|
||||
className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
disabled={txStatus === 'signing' || txStatus === 'submitting'}
|
||||
>
|
||||
<option value="">Select token...</option>
|
||||
<option value="">{t('createPool.selectToken')}</option>
|
||||
{availableTokens.map((token) => (
|
||||
<option key={token.id} value={token.id} disabled={token.id === asset1Id}>
|
||||
{token.symbol} - {token.name}
|
||||
@@ -340,7 +342,7 @@ export const CreatePoolModal: React.FC<CreatePoolModalProps> = ({
|
||||
</select>
|
||||
{token2 && (
|
||||
<div className="text-xs text-gray-500">
|
||||
Balance: {formatTokenBalance(balance2, token2.decimals, 4)} {token2.symbol}
|
||||
{t('common.balance')}: {formatTokenBalance(balance2, token2.decimals, 4)} {token2.symbol}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -349,7 +351,7 @@ export const CreatePoolModal: React.FC<CreatePoolModalProps> = ({
|
||||
{token2 && (
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm text-gray-400">
|
||||
Amount of {token2.symbol}
|
||||
{t('createPool.amountOf', { symbol: token2.symbol })}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -365,7 +367,7 @@ export const CreatePoolModal: React.FC<CreatePoolModalProps> = ({
|
||||
{/* Exchange Rate Preview */}
|
||||
{token1 && token2 && amount1Input && amount2Input && (
|
||||
<div className="p-4 bg-gray-800/50 rounded-lg border border-gray-700">
|
||||
<div className="text-sm text-gray-400 mb-2">Initial Exchange Rate</div>
|
||||
<div className="text-sm text-gray-400 mb-2">{t('createPool.initialRate')}</div>
|
||||
<div className="text-white font-mono">
|
||||
1 {token1.symbol} = {exchangeRate} {token2.symbol}
|
||||
</div>
|
||||
@@ -385,7 +387,7 @@ export const CreatePoolModal: React.FC<CreatePoolModalProps> = ({
|
||||
<div className="flex items-start gap-2 p-3 bg-green-500/10 border border-green-500/30 rounded-lg">
|
||||
<CheckCircle className="w-5 h-5 text-green-400 flex-shrink-0 mt-0.5" />
|
||||
<span className="text-sm text-green-400">
|
||||
Pool created successfully!
|
||||
{t('createPool.success')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -397,7 +399,7 @@ export const CreatePoolModal: React.FC<CreatePoolModalProps> = ({
|
||||
className="flex-1 px-6 py-3 bg-gray-800 hover:bg-gray-700 text-white rounded-lg transition-colors border border-gray-700"
|
||||
disabled={txStatus === 'signing' || txStatus === 'submitting'}
|
||||
>
|
||||
Cancel
|
||||
{t('common.cancel')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleCreatePool}
|
||||
@@ -411,21 +413,21 @@ export const CreatePoolModal: React.FC<CreatePoolModalProps> = ({
|
||||
{txStatus === 'signing' && (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
Signing...
|
||||
{t('common.signing')}
|
||||
</>
|
||||
)}
|
||||
{txStatus === 'submitting' && (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
Creating...
|
||||
{t('createPool.creating')}
|
||||
</>
|
||||
)}
|
||||
{txStatus === 'idle' && 'Create Pool'}
|
||||
{txStatus === 'error' && 'Retry'}
|
||||
{txStatus === 'idle' && t('createPool.createPool')}
|
||||
{txStatus === 'error' && t('common.retry')}
|
||||
{txStatus === 'success' && (
|
||||
<>
|
||||
<CheckCircle className="w-4 h-4" />
|
||||
Success
|
||||
{t('common.success')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
// import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useWallet } from '@/contexts/WalletContext';
|
||||
import { usePezkuwi } from '@/contexts/PezkuwiContext';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
@@ -16,6 +17,7 @@ import { isFounderWallet } from '@pezkuwi/utils/auth';
|
||||
|
||||
// DEX Dashboard - Asset Hub API migration complete
|
||||
export const DEXDashboard: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { account } = useWallet();
|
||||
const { sudoKey } = usePezkuwi();
|
||||
const [activeTab, setActiveTab] = useState('swap');
|
||||
@@ -53,17 +55,17 @@ export const DEXDashboard: React.FC = () => {
|
||||
<div className="bg-gradient-to-r from-green-900/30 via-yellow-900/30 to-red-900/30 border-b border-gray-800 py-8">
|
||||
<div className="max-w-7xl mx-auto px-4">
|
||||
<h1 className="text-4xl font-bold mb-2 bg-gradient-to-r from-green-400 via-yellow-400 to-red-400 bg-clip-text text-transparent">
|
||||
Pezkuwi DEX
|
||||
{t('dex.title')}
|
||||
</h1>
|
||||
<p className="text-gray-400 text-lg">
|
||||
Decentralized exchange for trading tokens on PezkuwiChain
|
||||
{t('dex.description')}
|
||||
</p>
|
||||
|
||||
{/* Wallet status */}
|
||||
{account && (
|
||||
<div className="mt-4 flex items-center gap-4">
|
||||
<div className="px-4 py-2 bg-gray-900/80 rounded-lg border border-gray-800">
|
||||
<span className="text-xs text-gray-400">Connected: </span>
|
||||
<span className="text-xs text-gray-400">{t('dex.connected')} </span>
|
||||
<span className="text-sm font-mono text-white">
|
||||
{account.slice(0, 6)}...{account.slice(-4)}
|
||||
</span>
|
||||
@@ -71,7 +73,7 @@ export const DEXDashboard: React.FC = () => {
|
||||
{isFounder && (
|
||||
<div className="px-4 py-2 bg-green-600/20 border border-green-600/30 rounded-lg">
|
||||
<span className="text-xs text-green-400 font-semibold">
|
||||
FOUNDER ACCESS
|
||||
{t('dex.founderAccess')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -85,7 +87,7 @@ export const DEXDashboard: React.FC = () => {
|
||||
{!account ? (
|
||||
<div className="text-center py-12">
|
||||
<div className="mb-4 text-gray-400 text-lg">
|
||||
Please connect your Pezkuwi wallet to use the DEX
|
||||
{t('dex.connectWallet')}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@@ -93,16 +95,16 @@ export const DEXDashboard: React.FC = () => {
|
||||
<TabsList className={`grid w-full ${isFounder ? 'grid-cols-3' : 'grid-cols-2'} gap-2 bg-gray-900/50 p-1 rounded-lg mb-8`}>
|
||||
<TabsTrigger value="swap" className="flex items-center gap-2">
|
||||
<ArrowRightLeft className="w-4 h-4" />
|
||||
<span className="hidden sm:inline">Swap</span>
|
||||
<span className="hidden sm:inline">{t('dex.tabs.swap')}</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="pools" className="flex items-center gap-2">
|
||||
<Droplet className="w-4 h-4" />
|
||||
<span className="hidden sm:inline">Pools</span>
|
||||
<span className="hidden sm:inline">{t('dex.tabs.pools')}</span>
|
||||
</TabsTrigger>
|
||||
{isFounder && (
|
||||
<TabsTrigger value="admin" className="flex items-center gap-2">
|
||||
<Settings className="w-4 h-4" />
|
||||
<span className="hidden sm:inline">Admin</span>
|
||||
<span className="hidden sm:inline">{t('dex.tabs.admin')}</span>
|
||||
</TabsTrigger>
|
||||
)}
|
||||
</TabsList>
|
||||
@@ -119,23 +121,23 @@ export const DEXDashboard: React.FC = () => {
|
||||
<TabsContent value="admin" className="mt-6">
|
||||
<div className="max-w-2xl mx-auto space-y-6">
|
||||
<div className="p-6 bg-gray-900 border border-blue-900/30 rounded-lg">
|
||||
<h3 className="text-xl font-bold text-white mb-2">Token Wrapping</h3>
|
||||
<h3 className="text-xl font-bold text-white mb-2">{t('dex.admin.tokenWrapping')}</h3>
|
||||
<p className="text-gray-400 mb-6">
|
||||
Convert native HEZ to wrapped wHEZ for use in DEX pools
|
||||
{t('dex.admin.tokenWrappingDesc')}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setShowInitializeHezPoolModal(true)}
|
||||
className="w-full px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors font-medium"
|
||||
>
|
||||
Wrap HEZ to wHEZ
|
||||
{t('dex.admin.wrapHez')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Token Minting Section */}
|
||||
<div className="p-6 bg-gray-900 border border-gray-800 rounded-lg">
|
||||
<h3 className="text-xl font-bold text-white mb-2">Token Minting</h3>
|
||||
<h3 className="text-xl font-bold text-white mb-2">{t('dex.admin.tokenMinting')}</h3>
|
||||
<p className="text-gray-400 mb-6">
|
||||
Mint wrapped tokens for testing and liquidity provision
|
||||
{t('dex.admin.tokenMintingDesc')}
|
||||
</p>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||||
<button
|
||||
@@ -143,62 +145,62 @@ export const DEXDashboard: React.FC = () => {
|
||||
className="flex items-center justify-center gap-2 px-4 py-3 bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors font-medium"
|
||||
>
|
||||
<img src="/tokens/USDT.png" alt="USDT" className="w-5 h-5 rounded-full" />
|
||||
Mint wUSDT
|
||||
{t('dex.admin.mintToken', { symbol: 'wUSDT' })}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setMintModalAsset(MINTABLE_ASSETS.wDOT)}
|
||||
className="flex items-center justify-center gap-2 px-4 py-3 bg-pink-600 hover:bg-pink-700 text-white rounded-lg transition-colors font-medium"
|
||||
>
|
||||
<img src="/tokens/DOT.png" alt="DOT" className="w-5 h-5 rounded-full" />
|
||||
Mint wDOT
|
||||
{t('dex.admin.mintToken', { symbol: 'wDOT' })}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setMintModalAsset(MINTABLE_ASSETS.wETH)}
|
||||
className="flex items-center justify-center gap-2 px-4 py-3 bg-purple-600 hover:bg-purple-700 text-white rounded-lg transition-colors font-medium"
|
||||
>
|
||||
<img src="/tokens/ETH.png" alt="ETH" className="w-5 h-5 rounded-full" />
|
||||
Mint wETH
|
||||
{t('dex.admin.mintToken', { symbol: 'wETH' })}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setMintModalAsset(MINTABLE_ASSETS.wBTC)}
|
||||
className="flex items-center justify-center gap-2 px-4 py-3 bg-orange-600 hover:bg-orange-700 text-white rounded-lg transition-colors font-medium"
|
||||
>
|
||||
<img src="/tokens/BTC.png" alt="BTC" className="w-5 h-5 rounded-full" />
|
||||
Mint wBTC
|
||||
{t('dex.admin.mintToken', { symbol: 'wBTC' })}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6 bg-gray-900 border border-purple-900/30 rounded-lg">
|
||||
<h3 className="text-xl font-bold text-white mb-2">XCM Configuration Wizard</h3>
|
||||
<h3 className="text-xl font-bold text-white mb-2">{t('dex.admin.xcmWizard')}</h3>
|
||||
<p className="text-gray-400 mb-6">
|
||||
Complete 6-step parachain setup: Reserve ParaId, generate artifacts, register parachain, open HRMP channels, register foreign assets, and test XCM transfers.
|
||||
{t('dex.admin.xcmWizardDesc')}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setShowXcmBridgeModal(true)}
|
||||
className="w-full px-6 py-3 bg-purple-600 hover:bg-purple-700 text-white rounded-lg transition-colors font-medium"
|
||||
>
|
||||
Open XCM Configuration Wizard
|
||||
{t('dex.admin.openXcmWizard')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-6 bg-gray-900 border border-gray-800 rounded-lg">
|
||||
<h3 className="text-xl font-bold text-white mb-2">Pool Management</h3>
|
||||
<h3 className="text-xl font-bold text-white mb-2">{t('dex.admin.poolManagement')}</h3>
|
||||
<p className="text-gray-400 mb-6">
|
||||
Create new liquidity pools for token pairs on PezkuwiChain
|
||||
{t('dex.admin.poolManagementDesc')}
|
||||
</p>
|
||||
<button
|
||||
onClick={handleCreatePool}
|
||||
className="w-full px-6 py-3 bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors font-medium"
|
||||
>
|
||||
Create New Pool
|
||||
{t('dex.admin.createNewPool')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-6 bg-gray-900 border border-gray-800 rounded-lg">
|
||||
<h3 className="text-xl font-bold text-white mb-2">Pool Statistics</h3>
|
||||
<h3 className="text-xl font-bold text-white mb-2">{t('dex.admin.poolStatistics')}</h3>
|
||||
<p className="text-gray-400 text-sm">
|
||||
View detailed pool statistics in the Pools tab
|
||||
{t('dex.admin.poolStatsDesc')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Input } from '@/components/ui/input';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface InitializeHezPoolModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -28,6 +29,7 @@ export const InitializeHezPoolModal: React.FC<InitializeHezPoolModalProps> = ({
|
||||
const { assetHubApi, isAssetHubReady } = usePezkuwi();
|
||||
const { account, signer } = useWallet();
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [mode, setMode] = useState<WrapMode>('wrap');
|
||||
const [amount, setAmount] = useState('10000');
|
||||
@@ -112,18 +114,18 @@ export const InitializeHezPoolModal: React.FC<InitializeHezPoolModalProps> = ({
|
||||
const handleTransaction = async () => {
|
||||
if (!assetHubApi || !isAssetHubReady || !signer || !account) {
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Please connect your wallet and wait for Asset Hub connection',
|
||||
title: t('common.error'),
|
||||
description: t('mint.connectWallet'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!palletAvailable) {
|
||||
setErrorMessage('TokenWrapper pallet is not available on Asset Hub.');
|
||||
setErrorMessage(t('tokenWrapping.palletNotAvailable'));
|
||||
toast({
|
||||
title: 'Pallet Not Available',
|
||||
description: 'The TokenWrapper pallet is not deployed on Asset Hub.',
|
||||
title: t('mint.palletToast'),
|
||||
description: t('tokenWrapping.palletNotAvailable'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
@@ -132,13 +134,13 @@ export const InitializeHezPoolModal: React.FC<InitializeHezPoolModalProps> = ({
|
||||
const amountRaw = BigInt(parseFloat(amount) * 10 ** 12);
|
||||
|
||||
if (amountRaw <= BigInt(0)) {
|
||||
setErrorMessage('Amount must be greater than zero');
|
||||
setErrorMessage(t('common.amountGtZero'));
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceBalance = mode === 'wrap' ? hezBalance : whezBalance;
|
||||
if (amountRaw > BigInt(sourceBalance)) {
|
||||
setErrorMessage(`Insufficient ${mode === 'wrap' ? 'HEZ' : 'wHEZ'} balance`);
|
||||
setErrorMessage(t('common.insufficientBalance', { symbol: mode === 'wrap' ? 'HEZ' : 'wHEZ' }));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -184,7 +186,7 @@ export const InitializeHezPoolModal: React.FC<InitializeHezPoolModalProps> = ({
|
||||
setErrorMessage(errorMsg);
|
||||
setTxStatus('error');
|
||||
toast({
|
||||
title: 'Transaction Failed',
|
||||
title: t('common.txFailed'),
|
||||
description: errorMsg,
|
||||
variant: 'destructive',
|
||||
});
|
||||
@@ -193,10 +195,10 @@ export const InitializeHezPoolModal: React.FC<InitializeHezPoolModalProps> = ({
|
||||
if (import.meta.env.DEV) console.log('📋 Events:', events.map(e => e.event.method).join(', '));
|
||||
setTxStatus('success');
|
||||
toast({
|
||||
title: 'Success!',
|
||||
title: t('common.success'),
|
||||
description: isWrap
|
||||
? `Successfully wrapped ${amount} HEZ to wHEZ`
|
||||
: `Successfully unwrapped ${amount} wHEZ to HEZ`,
|
||||
? t('tokenWrapping.wrapSuccess', { amount })
|
||||
: t('tokenWrapping.unwrapSuccess', { amount }),
|
||||
});
|
||||
onSuccess?.();
|
||||
}
|
||||
@@ -205,11 +207,11 @@ export const InitializeHezPoolModal: React.FC<InitializeHezPoolModalProps> = ({
|
||||
);
|
||||
} catch (error) {
|
||||
if (import.meta.env.DEV) console.error('Transaction failed:', error);
|
||||
setErrorMessage(error instanceof Error ? error.message : 'Transaction failed');
|
||||
setErrorMessage(error instanceof Error ? error.message : t('common.txFailed'));
|
||||
setTxStatus('error');
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: error instanceof Error ? error.message : 'Transaction failed',
|
||||
title: t('common.error'),
|
||||
description: error instanceof Error ? error.message : t('common.txFailed'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@@ -232,7 +234,7 @@ export const InitializeHezPoolModal: React.FC<InitializeHezPoolModalProps> = ({
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-xl font-bold text-white flex items-center gap-2">
|
||||
<ArrowDownUp className="w-5 h-5" />
|
||||
Token Wrapping
|
||||
{t('tokenWrapping.title')}
|
||||
</CardTitle>
|
||||
<button
|
||||
onClick={onClose}
|
||||
@@ -243,7 +245,7 @@ export const InitializeHezPoolModal: React.FC<InitializeHezPoolModalProps> = ({
|
||||
</button>
|
||||
</div>
|
||||
<Badge className="bg-blue-600/20 text-blue-400 border-blue-600/30 w-fit mt-2">
|
||||
Admin Only - Token Wrapping
|
||||
{t('tokenWrapping.adminOnly')}
|
||||
</Badge>
|
||||
</CardHeader>
|
||||
|
||||
@@ -253,7 +255,7 @@ export const InitializeHezPoolModal: React.FC<InitializeHezPoolModalProps> = ({
|
||||
<Alert className="bg-yellow-500/10 border-yellow-500/30">
|
||||
<AlertCircle className="h-4 w-4 text-yellow-400" />
|
||||
<AlertDescription className="text-yellow-300 text-sm">
|
||||
<strong>TokenWrapper pallet not deployed.</strong> Please contact the development team.
|
||||
<strong>{t('tokenWrapping.palletNotDeployed')}</strong> {t('tokenWrapping.contactDev')}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
@@ -273,7 +275,7 @@ export const InitializeHezPoolModal: React.FC<InitializeHezPoolModalProps> = ({
|
||||
<Alert className="bg-blue-500/10 border-blue-500/30">
|
||||
<Info className="h-4 w-4 text-blue-400" />
|
||||
<AlertDescription className="text-blue-300 text-sm">
|
||||
Convert native HEZ to wHEZ (wrapped). Ratio: 1:1
|
||||
{t('tokenWrapping.wrapInfo')}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</TabsContent>
|
||||
@@ -282,7 +284,7 @@ export const InitializeHezPoolModal: React.FC<InitializeHezPoolModalProps> = ({
|
||||
<Alert className="bg-green-500/10 border-green-500/30">
|
||||
<Info className="h-4 w-4 text-green-400" />
|
||||
<AlertDescription className="text-green-300 text-sm">
|
||||
Convert wHEZ back to native HEZ. Ratio: 1:1
|
||||
{t('tokenWrapping.unwrapInfo')}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</TabsContent>
|
||||
@@ -291,13 +293,13 @@ export const InitializeHezPoolModal: React.FC<InitializeHezPoolModalProps> = ({
|
||||
{/* Balances Display */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className={`p-4 rounded-lg border ${mode === 'wrap' ? 'bg-blue-500/10 border-blue-500/30' : 'bg-gray-800/50 border-gray-700'}`}>
|
||||
<div className="text-sm text-gray-400 mb-1">Native HEZ</div>
|
||||
<div className="text-sm text-gray-400 mb-1">{t('tokenWrapping.nativeHez')}</div>
|
||||
<div className="text-xl font-bold text-blue-400 font-mono">
|
||||
{hezBalanceDisplay}
|
||||
</div>
|
||||
</div>
|
||||
<div className={`p-4 rounded-lg border ${mode === 'unwrap' ? 'bg-green-500/10 border-green-500/30' : 'bg-gray-800/50 border-gray-700'}`}>
|
||||
<div className="text-sm text-gray-400 mb-1">Wrapped wHEZ</div>
|
||||
<div className="text-sm text-gray-400 mb-1">{t('tokenWrapping.wrappedWhez')}</div>
|
||||
<div className="text-xl font-bold text-cyan-400 font-mono">
|
||||
{whezBalanceDisplay}
|
||||
</div>
|
||||
@@ -307,9 +309,9 @@ export const InitializeHezPoolModal: React.FC<InitializeHezPoolModalProps> = ({
|
||||
{/* Amount Input */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm text-gray-400">{sourceToken} Amount</label>
|
||||
<label className="text-sm text-gray-400">{t('tokenWrapping.amount', { token: sourceToken })}</label>
|
||||
<span className="text-xs text-gray-500">
|
||||
Available: {sourceBalance} {sourceToken}
|
||||
{t('tokenWrapping.available')} {sourceBalance} {sourceToken}
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative">
|
||||
@@ -326,11 +328,11 @@ export const InitializeHezPoolModal: React.FC<InitializeHezPoolModalProps> = ({
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 px-3 py-1 bg-green-600/20 hover:bg-green-600/30 text-green-400 text-xs rounded border border-green-600/30 transition-colors"
|
||||
disabled={txStatus === 'signing' || txStatus === 'submitting'}
|
||||
>
|
||||
MAX
|
||||
{t('common.max')}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">
|
||||
💡 You will receive {amount} {targetToken} (1:1 ratio)
|
||||
{t('tokenWrapping.receiveInfo', { amount, token: targetToken })}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -349,7 +351,7 @@ export const InitializeHezPoolModal: React.FC<InitializeHezPoolModalProps> = ({
|
||||
<Alert className="bg-green-500/10 border-green-500/30">
|
||||
<CheckCircle className="h-4 w-4 text-green-400" />
|
||||
<AlertDescription className="text-green-300 text-sm">
|
||||
Successfully {mode === 'wrap' ? 'wrapped' : 'unwrapped'} {amount} {sourceToken} to {targetToken}!
|
||||
{mode === 'wrap' ? t('tokenWrapping.wrapSuccess', { amount }) : t('tokenWrapping.unwrapSuccess', { amount })}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
@@ -362,7 +364,7 @@ export const InitializeHezPoolModal: React.FC<InitializeHezPoolModalProps> = ({
|
||||
className="flex-1 border-gray-700 hover:bg-gray-800"
|
||||
disabled={txStatus === 'signing' || txStatus === 'submitting'}
|
||||
>
|
||||
Cancel
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleTransaction}
|
||||
@@ -376,21 +378,21 @@ export const InitializeHezPoolModal: React.FC<InitializeHezPoolModalProps> = ({
|
||||
{txStatus === 'signing' && (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin mr-2" />
|
||||
Signing...
|
||||
{t('common.signing')}
|
||||
</>
|
||||
)}
|
||||
{txStatus === 'submitting' && (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin mr-2" />
|
||||
{mode === 'wrap' ? 'Wrapping...' : 'Unwrapping...'}
|
||||
{mode === 'wrap' ? t('tokenWrapping.wrapping') : t('tokenWrapping.unwrapping')}
|
||||
</>
|
||||
)}
|
||||
{txStatus === 'idle' && (mode === 'wrap' ? 'Wrap HEZ → wHEZ' : 'Unwrap wHEZ → HEZ')}
|
||||
{txStatus === 'error' && 'Retry'}
|
||||
{txStatus === 'idle' && (mode === 'wrap' ? t('tokenWrapping.wrapBtn') : t('tokenWrapping.unwrapBtn'))}
|
||||
{txStatus === 'error' && t('common.retry')}
|
||||
{txStatus === 'success' && (
|
||||
<>
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
Success
|
||||
{t('common.success')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface InitializeUsdtModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -29,6 +30,7 @@ export const InitializeUsdtModal: React.FC<InitializeUsdtModalProps> = ({
|
||||
const { assetHubApi, isAssetHubReady } = usePezkuwi();
|
||||
const { account, signer } = useWallet();
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [usdtAmount, setUsdtAmount] = useState('10000');
|
||||
|
||||
@@ -66,8 +68,8 @@ export const InitializeUsdtModal: React.FC<InitializeUsdtModalProps> = ({
|
||||
const handleMint = async () => {
|
||||
if (!assetHubApi || !isAssetHubReady || !signer || !account) {
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Please connect your wallet and wait for Asset Hub connection',
|
||||
title: t('common.error'),
|
||||
description: t('mint.connectWallet'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
@@ -75,10 +77,10 @@ export const InitializeUsdtModal: React.FC<InitializeUsdtModalProps> = ({
|
||||
|
||||
// Check if assets pallet is available on Asset Hub
|
||||
if (!assetHubApi.tx.assets || !assetHubApi.tx.assets.mint) {
|
||||
setErrorMessage('Assets pallet is not available on Asset Hub.');
|
||||
setErrorMessage(t('mint.palletNotAvailable'));
|
||||
toast({
|
||||
title: 'Pallet Not Available',
|
||||
description: 'The Assets pallet is not deployed on Asset Hub.',
|
||||
title: t('mint.palletToast'),
|
||||
description: t('mint.palletToastDesc'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
@@ -87,7 +89,7 @@ export const InitializeUsdtModal: React.FC<InitializeUsdtModalProps> = ({
|
||||
const usdtAmountRaw = BigInt(parseFloat(usdtAmount) * 10 ** USDT_DECIMALS);
|
||||
|
||||
if (usdtAmountRaw <= BigInt(0)) {
|
||||
setErrorMessage('Amount must be greater than zero');
|
||||
setErrorMessage(t('common.amountGtZero'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -129,7 +131,7 @@ export const InitializeUsdtModal: React.FC<InitializeUsdtModalProps> = ({
|
||||
setErrorMessage(errorMsg);
|
||||
setTxStatus('error');
|
||||
toast({
|
||||
title: 'Transaction Failed',
|
||||
title: t('common.txFailed'),
|
||||
description: errorMsg,
|
||||
variant: 'destructive',
|
||||
});
|
||||
@@ -138,8 +140,8 @@ export const InitializeUsdtModal: React.FC<InitializeUsdtModalProps> = ({
|
||||
if (import.meta.env.DEV) console.log('📋 Events:', events.map(e => e.event.method).join(', '));
|
||||
setTxStatus('success');
|
||||
toast({
|
||||
title: 'Success!',
|
||||
description: `Successfully minted ${usdtAmount} wUSDT`,
|
||||
title: t('common.success'),
|
||||
description: t('mintUsdt.minted', { amount: usdtAmount }),
|
||||
});
|
||||
setTimeout(() => {
|
||||
onSuccess?.();
|
||||
@@ -151,11 +153,11 @@ export const InitializeUsdtModal: React.FC<InitializeUsdtModalProps> = ({
|
||||
);
|
||||
} catch (error) {
|
||||
if (import.meta.env.DEV) console.error('Mint failed:', error);
|
||||
setErrorMessage(error instanceof Error ? error.message : 'Transaction failed');
|
||||
setErrorMessage(error instanceof Error ? error.message : t('common.txFailed'));
|
||||
setTxStatus('error');
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: error instanceof Error ? error.message : 'Mint failed',
|
||||
title: t('common.error'),
|
||||
description: error instanceof Error ? error.message : t('common.txFailed'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@@ -171,7 +173,7 @@ export const InitializeUsdtModal: React.FC<InitializeUsdtModalProps> = ({
|
||||
<CardHeader className="border-b border-gray-800">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-xl font-bold text-white">
|
||||
Mint wUSDT Tokens
|
||||
{t('mintUsdt.title')}
|
||||
</CardTitle>
|
||||
<button
|
||||
onClick={onClose}
|
||||
@@ -182,7 +184,7 @@ export const InitializeUsdtModal: React.FC<InitializeUsdtModalProps> = ({
|
||||
</button>
|
||||
</div>
|
||||
<Badge className="bg-green-600/20 text-green-400 border-green-600/30 w-fit mt-2">
|
||||
Admin Only - Token Minting
|
||||
{t('mintUsdt.adminOnly')}
|
||||
</Badge>
|
||||
</CardHeader>
|
||||
|
||||
@@ -191,17 +193,16 @@ export const InitializeUsdtModal: React.FC<InitializeUsdtModalProps> = ({
|
||||
<Alert className="bg-green-500/10 border-green-500/30">
|
||||
<Info className="h-4 w-4 text-green-400" />
|
||||
<AlertDescription className="text-green-300 text-sm">
|
||||
Mint wUSDT (Wrapped USDT) tokens for testing and liquidity pool creation.
|
||||
Use responsibly!
|
||||
{t('mintUsdt.info')}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
{/* USDT Amount */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm text-gray-400">wUSDT Amount</label>
|
||||
<label className="text-sm text-gray-400">{t('mintUsdt.amount')}</label>
|
||||
<span className="text-xs text-gray-500">
|
||||
Current: {wusdtBalanceDisplay} wUSDT
|
||||
{t('mint.current')} {wusdtBalanceDisplay} wUSDT
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative">
|
||||
@@ -231,13 +232,13 @@ export const InitializeUsdtModal: React.FC<InitializeUsdtModalProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">
|
||||
💡 wUSDT has 6 decimals (same as real USDT)
|
||||
{t('mintUsdt.decimalsInfo')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Current wUSDT Balance */}
|
||||
<div className="p-4 bg-gray-800/50 rounded-lg border border-gray-700">
|
||||
<div className="text-sm text-gray-400 mb-1">Current wUSDT Balance</div>
|
||||
<div className="text-sm text-gray-400 mb-1">{t('mintUsdt.currentBalance')}</div>
|
||||
<div className="text-2xl font-bold text-green-400 font-mono">
|
||||
{wusdtBalanceDisplay} wUSDT
|
||||
</div>
|
||||
@@ -258,7 +259,7 @@ export const InitializeUsdtModal: React.FC<InitializeUsdtModalProps> = ({
|
||||
<Alert className="bg-green-500/10 border-green-500/30">
|
||||
<CheckCircle className="h-4 w-4 text-green-400" />
|
||||
<AlertDescription className="text-green-300 text-sm">
|
||||
Successfully minted {usdtAmount} wUSDT!
|
||||
{t('mintUsdt.success', { amount: usdtAmount })}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
@@ -271,7 +272,7 @@ export const InitializeUsdtModal: React.FC<InitializeUsdtModalProps> = ({
|
||||
className="flex-1 border-gray-700 hover:bg-gray-800"
|
||||
disabled={txStatus === 'signing' || txStatus === 'submitting'}
|
||||
>
|
||||
Cancel
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleMint}
|
||||
@@ -285,21 +286,21 @@ export const InitializeUsdtModal: React.FC<InitializeUsdtModalProps> = ({
|
||||
{txStatus === 'signing' && (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin mr-2" />
|
||||
Signing...
|
||||
{t('common.signing')}
|
||||
</>
|
||||
)}
|
||||
{txStatus === 'submitting' && (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin mr-2" />
|
||||
Minting...
|
||||
{t('mintUsdt.minting')}
|
||||
</>
|
||||
)}
|
||||
{txStatus === 'idle' && 'Mint wUSDT'}
|
||||
{txStatus === 'error' && 'Retry'}
|
||||
{txStatus === 'idle' && t('mintUsdt.mintBtn')}
|
||||
{txStatus === 'error' && t('common.retry')}
|
||||
{txStatus === 'success' && (
|
||||
<>
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
Success
|
||||
{t('common.success')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Input } from '@/components/ui/input';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import type { AssetConfig } from './mintableAssets';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface MintAssetModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -28,6 +29,7 @@ export const MintAssetModal: React.FC<MintAssetModalProps> = ({
|
||||
const { assetHubApi, isAssetHubReady } = usePezkuwi();
|
||||
const { account, signer } = useWallet();
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [amount, setAmount] = useState(asset.defaultAmount || '1000');
|
||||
const [balance, setBalance] = useState<string>('0');
|
||||
@@ -73,18 +75,18 @@ export const MintAssetModal: React.FC<MintAssetModalProps> = ({
|
||||
const handleMint = async () => {
|
||||
if (!assetHubApi || !isAssetHubReady || !signer || !account) {
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Please connect your wallet and wait for Asset Hub connection',
|
||||
title: t('common.error'),
|
||||
description: t('mint.connectWallet'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!assetHubApi.tx.assets || !assetHubApi.tx.assets.mint) {
|
||||
setErrorMessage('Assets pallet is not available on Asset Hub.');
|
||||
setErrorMessage(t('mint.palletNotAvailable'));
|
||||
toast({
|
||||
title: 'Pallet Not Available',
|
||||
description: 'The Assets pallet is not deployed on Asset Hub.',
|
||||
title: t('mint.palletToast'),
|
||||
description: t('mint.palletToastDesc'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
@@ -93,7 +95,7 @@ export const MintAssetModal: React.FC<MintAssetModalProps> = ({
|
||||
const amountRaw = BigInt(Math.floor(parseFloat(amount) * 10 ** asset.decimals));
|
||||
|
||||
if (amountRaw <= BigInt(0)) {
|
||||
setErrorMessage('Amount must be greater than zero');
|
||||
setErrorMessage(t('common.amountGtZero'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -135,7 +137,7 @@ export const MintAssetModal: React.FC<MintAssetModalProps> = ({
|
||||
setErrorMessage(errorMsg);
|
||||
setTxStatus('error');
|
||||
toast({
|
||||
title: 'Transaction Failed',
|
||||
title: t('common.txFailed'),
|
||||
description: errorMsg,
|
||||
variant: 'destructive',
|
||||
});
|
||||
@@ -143,8 +145,8 @@ export const MintAssetModal: React.FC<MintAssetModalProps> = ({
|
||||
if (import.meta.env.DEV) console.log('Mint successful!');
|
||||
setTxStatus('success');
|
||||
toast({
|
||||
title: 'Success!',
|
||||
description: `Successfully minted ${amount} ${asset.symbol}`,
|
||||
title: t('common.success'),
|
||||
description: t('mint.minted', { amount, symbol: asset.symbol }),
|
||||
});
|
||||
setTimeout(() => {
|
||||
onSuccess?.();
|
||||
@@ -156,11 +158,11 @@ export const MintAssetModal: React.FC<MintAssetModalProps> = ({
|
||||
);
|
||||
} catch (error) {
|
||||
if (import.meta.env.DEV) console.error('Mint failed:', error);
|
||||
setErrorMessage(error instanceof Error ? error.message : 'Transaction failed');
|
||||
setErrorMessage(error instanceof Error ? error.message : t('common.txFailed'));
|
||||
setTxStatus('error');
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: error instanceof Error ? error.message : 'Mint failed',
|
||||
title: t('common.error'),
|
||||
description: error instanceof Error ? error.message : t('common.txFailed'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@@ -180,7 +182,7 @@ export const MintAssetModal: React.FC<MintAssetModalProps> = ({
|
||||
<img src={asset.logo} alt={asset.symbol} className="w-8 h-8 rounded-full" />
|
||||
)}
|
||||
<CardTitle className="text-xl font-bold text-white">
|
||||
Mint {asset.symbol}
|
||||
{t('mint.title', { symbol: asset.symbol })}
|
||||
</CardTitle>
|
||||
</div>
|
||||
<button
|
||||
@@ -192,7 +194,7 @@ export const MintAssetModal: React.FC<MintAssetModalProps> = ({
|
||||
</button>
|
||||
</div>
|
||||
<Badge className={`${colors.bg} ${colors.text} ${colors.border} w-fit mt-2`}>
|
||||
Admin Only - Token Minting
|
||||
{t('mint.adminOnly')}
|
||||
</Badge>
|
||||
</CardHeader>
|
||||
|
||||
@@ -201,16 +203,16 @@ export const MintAssetModal: React.FC<MintAssetModalProps> = ({
|
||||
<Alert className={`${colors.bg} ${colors.border}`}>
|
||||
<Info className={`h-4 w-4 ${colors.text}`} />
|
||||
<AlertDescription className={`${colors.text.replace('400', '300')} text-sm`}>
|
||||
Mint {asset.name} tokens for testing and liquidity pool creation.
|
||||
{t('mint.info', { name: asset.name })}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
{/* Amount Input */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm text-gray-400">{asset.symbol} Amount</label>
|
||||
<label className="text-sm text-gray-400">{t('mint.amount', { symbol: asset.symbol })}</label>
|
||||
<span className="text-xs text-gray-500">
|
||||
Current: {balanceDisplay} {asset.symbol}
|
||||
{t('mint.current')}: {balanceDisplay} {asset.symbol}
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative">
|
||||
@@ -240,13 +242,13 @@ export const MintAssetModal: React.FC<MintAssetModalProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">
|
||||
{asset.symbol} has {asset.decimals} decimals
|
||||
{t('mint.decimalsInfo', { symbol: asset.symbol, decimals: asset.decimals })}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Current Balance */}
|
||||
<div className="p-4 bg-gray-800/50 rounded-lg border border-gray-700">
|
||||
<div className="text-sm text-gray-400 mb-1">Current {asset.symbol} Balance</div>
|
||||
<div className="text-sm text-gray-400 mb-1">{t('mint.currentBalance', { symbol: asset.symbol })}</div>
|
||||
<div className={`text-2xl font-bold ${colors.text} font-mono`}>
|
||||
{balanceDisplay} {asset.symbol}
|
||||
</div>
|
||||
@@ -267,7 +269,7 @@ export const MintAssetModal: React.FC<MintAssetModalProps> = ({
|
||||
<Alert className={`${colors.bg} ${colors.border}`}>
|
||||
<CheckCircle className={`h-4 w-4 ${colors.text}`} />
|
||||
<AlertDescription className={`${colors.text.replace('400', '300')} text-sm`}>
|
||||
Successfully minted {amount} {asset.symbol}!
|
||||
{t('mint.success', { amount, symbol: asset.symbol })}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
@@ -280,7 +282,7 @@ export const MintAssetModal: React.FC<MintAssetModalProps> = ({
|
||||
className="flex-1 border-gray-700 hover:bg-gray-800"
|
||||
disabled={txStatus === 'signing' || txStatus === 'submitting'}
|
||||
>
|
||||
Cancel
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleMint}
|
||||
@@ -294,21 +296,21 @@ export const MintAssetModal: React.FC<MintAssetModalProps> = ({
|
||||
{txStatus === 'signing' && (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin mr-2" />
|
||||
Signing...
|
||||
{t('common.signing')}
|
||||
</>
|
||||
)}
|
||||
{txStatus === 'submitting' && (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin mr-2" />
|
||||
Minting...
|
||||
{t('mint.minting')}
|
||||
</>
|
||||
)}
|
||||
{txStatus === 'idle' && `Mint ${asset.symbol}`}
|
||||
{txStatus === 'error' && 'Retry'}
|
||||
{txStatus === 'idle' && t('mint.mintBtn', { symbol: asset.symbol })}
|
||||
{txStatus === 'error' && t('common.retry')}
|
||||
{txStatus === 'success' && (
|
||||
<>
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
Success
|
||||
{t('common.success')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { usePezkuwi } from '@/contexts/PezkuwiContext';
|
||||
import { useWallet } from '@/contexts/WalletContext';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
@@ -22,6 +23,7 @@ export const PoolBrowser: React.FC<PoolBrowserProps> = ({
|
||||
onSwap,
|
||||
onCreatePool,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
// Use Asset Hub API for DEX operations
|
||||
const { assetHubApi, isAssetHubReady, sudoKey } = usePezkuwi();
|
||||
const { account } = useWallet();
|
||||
@@ -64,7 +66,7 @@ export const PoolBrowser: React.FC<PoolBrowserProps> = ({
|
||||
});
|
||||
|
||||
if (loading && pools.length === 0) {
|
||||
return <LoadingState message="Loading liquidity pools..." />;
|
||||
return <LoadingState message={t('pools.loading')} />;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -75,7 +77,7 @@ export const PoolBrowser: React.FC<PoolBrowserProps> = ({
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search pools by token..."
|
||||
placeholder={t('pools.searchPlaceholder')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full pl-10 pr-4 py-2 bg-gray-900 border border-gray-800 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
@@ -88,7 +90,7 @@ export const PoolBrowser: React.FC<PoolBrowserProps> = ({
|
||||
className="flex items-center gap-2 px-6 py-2 bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
Create Pool
|
||||
{t('pools.createPool')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -99,8 +101,8 @@ export const PoolBrowser: React.FC<PoolBrowserProps> = ({
|
||||
<CardContent className="py-12">
|
||||
<div className="text-center text-gray-400">
|
||||
{searchTerm
|
||||
? 'No pools found matching your search'
|
||||
: 'No liquidity pools available yet'}
|
||||
? t('pools.noSearchResults')
|
||||
: t('pools.noPools')}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -134,6 +136,7 @@ const PoolCard: React.FC<PoolCardProps> = ({
|
||||
onRemoveLiquidity,
|
||||
onSwap,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const reserve1Display = formatTokenBalance(
|
||||
pool.reserve1,
|
||||
pool.asset1Decimals,
|
||||
@@ -161,7 +164,7 @@ const PoolCard: React.FC<PoolCardProps> = ({
|
||||
<span className="text-yellow-400">{pool.asset2Symbol}</span>
|
||||
</CardTitle>
|
||||
<Badge className="bg-green-500/10 text-green-400 border-green-500/20">
|
||||
Active
|
||||
{t('pools.active')}
|
||||
</Badge>
|
||||
</div>
|
||||
</CardHeader>
|
||||
@@ -170,13 +173,13 @@ const PoolCard: React.FC<PoolCardProps> = ({
|
||||
{/* Reserves */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-gray-400">Reserve {pool.asset1Symbol}</span>
|
||||
<span className="text-gray-400">{t('pools.reserve', { symbol: pool.asset1Symbol })}</span>
|
||||
<span className="text-white font-mono">
|
||||
{reserve1Display} {pool.asset1Symbol}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-gray-400">Reserve {pool.asset2Symbol}</span>
|
||||
<span className="text-gray-400">{t('pools.reserve', { symbol: pool.asset2Symbol })}</span>
|
||||
<span className="text-white font-mono">
|
||||
{reserve2Display} {pool.asset2Symbol}
|
||||
</span>
|
||||
@@ -186,7 +189,7 @@ const PoolCard: React.FC<PoolCardProps> = ({
|
||||
{/* Exchange rate */}
|
||||
<div className="p-3 bg-gray-800/50 rounded-lg">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-gray-400">Exchange Rate</span>
|
||||
<span className="text-gray-400">{t('common.exchangeRate')}</span>
|
||||
<span className="text-cyan-400 font-mono">
|
||||
1 {pool.asset1Symbol} = {rate} {pool.asset2Symbol}
|
||||
</span>
|
||||
@@ -196,21 +199,21 @@ const PoolCard: React.FC<PoolCardProps> = ({
|
||||
{/* Stats row */}
|
||||
<div className="grid grid-cols-3 gap-2 pt-2 border-t border-gray-800">
|
||||
<div className="text-center">
|
||||
<div className="text-xs text-gray-500">Fee</div>
|
||||
<div className="text-xs text-gray-500">{t('pools.fee')}</div>
|
||||
<div className="text-sm font-semibold text-white">
|
||||
{pool.feeRate || '0.3'}%
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-xs text-gray-500">Volume 24h</div>
|
||||
<div className="text-xs text-gray-500">{t('pools.volume24h')}</div>
|
||||
<div className="text-sm font-semibold text-white">
|
||||
{pool.volume24h || 'N/A'}
|
||||
{pool.volume24h || t('pools.na')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-xs text-gray-500">APR</div>
|
||||
<div className="text-xs text-gray-500">{t('pools.apr')}</div>
|
||||
<div className="text-sm font-semibold text-green-400">
|
||||
{pool.apr7d || 'N/A'}
|
||||
{pool.apr7d || t('pools.na')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -223,7 +226,7 @@ const PoolCard: React.FC<PoolCardProps> = ({
|
||||
className="px-3 py-2 bg-green-600/20 hover:bg-green-600/30 text-green-400 border border-green-600/30 rounded-lg text-xs font-medium transition-colors flex items-center justify-center gap-1"
|
||||
>
|
||||
<Droplet className="w-3 h-3" />
|
||||
Add
|
||||
{t('pools.add')}
|
||||
</button>
|
||||
)}
|
||||
{onRemoveLiquidity && (
|
||||
@@ -231,7 +234,7 @@ const PoolCard: React.FC<PoolCardProps> = ({
|
||||
onClick={() => onRemoveLiquidity(pool)}
|
||||
className="px-3 py-2 bg-red-600/20 hover:bg-red-600/30 text-red-400 border border-red-600/30 rounded-lg text-xs font-medium transition-colors"
|
||||
>
|
||||
Remove
|
||||
{t('pools.remove')}
|
||||
</button>
|
||||
)}
|
||||
{onSwap && (
|
||||
@@ -240,7 +243,7 @@ const PoolCard: React.FC<PoolCardProps> = ({
|
||||
className="px-3 py-2 bg-blue-600/20 hover:bg-blue-600/30 text-blue-400 border border-blue-600/30 rounded-lg text-xs font-medium transition-colors flex items-center justify-center gap-1"
|
||||
>
|
||||
<TrendingUp className="w-3 h-3" />
|
||||
Swap
|
||||
{t('pools.swap')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { usePezkuwi } from '@/contexts/PezkuwiContext';
|
||||
import { useWallet } from '@/contexts/WalletContext';
|
||||
import { X, Minus, AlertCircle, Loader2, CheckCircle, Info } from 'lucide-react';
|
||||
@@ -34,6 +35,7 @@ export const RemoveLiquidityModal: React.FC<RemoveLiquidityModalProps> = ({
|
||||
// Use Asset Hub API for DEX operations (assetConversion pallet is on Asset Hub)
|
||||
const { assetHubApi, isAssetHubReady } = usePezkuwi();
|
||||
const { account, signer } = useWallet();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [lpTokenBalance, setLpTokenBalance] = useState<string>('0');
|
||||
const [removePercentage, setRemovePercentage] = useState(25);
|
||||
@@ -112,12 +114,12 @@ export const RemoveLiquidityModal: React.FC<RemoveLiquidityModalProps> = ({
|
||||
|
||||
const handleRemoveLiquidity = async () => {
|
||||
if (!assetHubApi || !isAssetHubReady || !signer || !account || !pool) {
|
||||
setErrorMessage('Wallet not connected');
|
||||
setErrorMessage(t('common.walletNotConnected'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (BigInt(lpTokenBalance) === BigInt(0)) {
|
||||
setErrorMessage('No liquidity to remove');
|
||||
setErrorMessage(t('removeLiquidity.noLiquidity'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -175,7 +177,7 @@ export const RemoveLiquidityModal: React.FC<RemoveLiquidityModalProps> = ({
|
||||
);
|
||||
} catch (error) {
|
||||
if (import.meta.env.DEV) console.error('Remove liquidity failed:', error);
|
||||
setErrorMessage(error instanceof Error ? error.message : 'Transaction failed');
|
||||
setErrorMessage(error instanceof Error ? error.message : t('common.txFailed'));
|
||||
setTxStatus('error');
|
||||
}
|
||||
};
|
||||
@@ -190,7 +192,7 @@ export const RemoveLiquidityModal: React.FC<RemoveLiquidityModalProps> = ({
|
||||
<CardHeader className="border-b border-gray-800">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-xl font-bold text-white">
|
||||
Remove Liquidity
|
||||
{t('removeLiquidity.title')}
|
||||
</CardTitle>
|
||||
<button
|
||||
onClick={onClose}
|
||||
@@ -201,7 +203,7 @@ export const RemoveLiquidityModal: React.FC<RemoveLiquidityModalProps> = ({
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-sm text-gray-400 mt-2">
|
||||
{pool.asset1Symbol} / {pool.asset2Symbol} Pool
|
||||
{t('removeLiquidity.pool', { asset1: pool.asset1Symbol, asset2: pool.asset2Symbol })}
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
@@ -210,13 +212,13 @@ export const RemoveLiquidityModal: React.FC<RemoveLiquidityModalProps> = ({
|
||||
<div className="flex items-start gap-2 p-3 bg-blue-500/10 border border-blue-500/30 rounded-lg">
|
||||
<Info className="w-5 h-5 text-blue-400 flex-shrink-0 mt-0.5" />
|
||||
<span className="text-sm text-blue-400">
|
||||
Remove liquidity to receive your tokens back. You'll burn LP tokens in proportion to your withdrawal.
|
||||
{t('removeLiquidity.info')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* LP Token Balance */}
|
||||
<div className="p-4 bg-gray-800/50 rounded-lg border border-gray-700">
|
||||
<div className="text-sm text-gray-400 mb-1">Your LP Tokens</div>
|
||||
<div className="text-sm text-gray-400 mb-1">{t('removeLiquidity.yourLpTokens')}</div>
|
||||
<div className="text-2xl font-bold text-white font-mono">
|
||||
{formatTokenBalance(lpTokenBalance, 12, 6)}
|
||||
</div>
|
||||
@@ -225,7 +227,7 @@ export const RemoveLiquidityModal: React.FC<RemoveLiquidityModalProps> = ({
|
||||
{/* Percentage Selector */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm text-gray-400">Remove Amount</label>
|
||||
<label className="text-sm text-gray-400">{t('removeLiquidity.removeAmount')}</label>
|
||||
<span className="text-lg font-bold text-white">{removePercentage}%</span>
|
||||
</div>
|
||||
|
||||
@@ -266,7 +268,7 @@ export const RemoveLiquidityModal: React.FC<RemoveLiquidityModalProps> = ({
|
||||
|
||||
{/* Output Preview */}
|
||||
<div className="space-y-3">
|
||||
<div className="text-sm text-gray-400 mb-2">You will receive</div>
|
||||
<div className="text-sm text-gray-400 mb-2">{t('removeLiquidity.youWillReceive')}</div>
|
||||
|
||||
<div className="p-4 bg-gray-800 border border-gray-700 rounded-lg space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -286,7 +288,7 @@ export const RemoveLiquidityModal: React.FC<RemoveLiquidityModalProps> = ({
|
||||
|
||||
{/* Slippage Tolerance */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm text-gray-400">Slippage Tolerance</label>
|
||||
<label className="text-sm text-gray-400">{t('common.slippageTolerance')}</label>
|
||||
<div className="flex gap-2">
|
||||
{[0.5, 1, 2].map((value) => (
|
||||
<button
|
||||
@@ -318,7 +320,7 @@ export const RemoveLiquidityModal: React.FC<RemoveLiquidityModalProps> = ({
|
||||
<div className="flex items-start gap-2 p-3 bg-green-500/10 border border-green-500/30 rounded-lg">
|
||||
<CheckCircle className="w-5 h-5 text-green-400 flex-shrink-0 mt-0.5" />
|
||||
<span className="text-sm text-green-400">
|
||||
Liquidity removed successfully!
|
||||
{t('removeLiquidity.success')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -330,7 +332,7 @@ export const RemoveLiquidityModal: React.FC<RemoveLiquidityModalProps> = ({
|
||||
className="flex-1 px-6 py-3 bg-gray-800 hover:bg-gray-700 text-white rounded-lg transition-colors border border-gray-700"
|
||||
disabled={txStatus === 'signing' || txStatus === 'submitting'}
|
||||
>
|
||||
Cancel
|
||||
{t('common.cancel')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleRemoveLiquidity}
|
||||
@@ -345,21 +347,21 @@ export const RemoveLiquidityModal: React.FC<RemoveLiquidityModalProps> = ({
|
||||
{txStatus === 'signing' && (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
Signing...
|
||||
{t('common.signing')}
|
||||
</>
|
||||
)}
|
||||
{txStatus === 'submitting' && (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
Removing...
|
||||
{t('removeLiquidity.removing')}
|
||||
</>
|
||||
)}
|
||||
{txStatus === 'idle' && 'Remove Liquidity'}
|
||||
{txStatus === 'error' && 'Retry'}
|
||||
{txStatus === 'idle' && t('removeLiquidity.removeLiquidity')}
|
||||
{txStatus === 'error' && t('common.retry')}
|
||||
{txStatus === 'success' && (
|
||||
<>
|
||||
<CheckCircle className="w-4 h-4" />
|
||||
Success
|
||||
{t('common.success')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { usePezkuwi } from '@/contexts/PezkuwiContext';
|
||||
import { useWallet } from '@/contexts/WalletContext';
|
||||
import { ArrowDownUp, AlertCircle, Loader2, Info, Settings, AlertTriangle, RefreshCw } from 'lucide-react';
|
||||
@@ -50,6 +51,7 @@ const getAvailableTokens = (pools: PoolInfo[]) => {
|
||||
};
|
||||
|
||||
export const SwapInterface: React.FC<SwapInterfaceProps> = ({ pools }) => {
|
||||
const { t } = useTranslation();
|
||||
// Use Asset Hub API for DEX operations
|
||||
const { assetHubApi, isAssetHubReady } = usePezkuwi();
|
||||
const { account, signer } = useWallet();
|
||||
@@ -247,8 +249,8 @@ export const SwapInterface: React.FC<SwapInterfaceProps> = ({ pools }) => {
|
||||
const handleConfirmSwap = async () => {
|
||||
if (!assetHubApi || !signer || !account || !fromTokenInfo || !toTokenInfo) {
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Please connect your wallet',
|
||||
title: t('common.error'),
|
||||
description: t('common.connectWalletAlert'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
@@ -256,8 +258,8 @@ export const SwapInterface: React.FC<SwapInterfaceProps> = ({ pools }) => {
|
||||
|
||||
if (!activePool) {
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'No liquidity pool available for this pair',
|
||||
title: t('common.error'),
|
||||
description: t('swap.noPool'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
@@ -340,15 +342,15 @@ export const SwapInterface: React.FC<SwapInterfaceProps> = ({ pools }) => {
|
||||
}
|
||||
setTxStatus('error');
|
||||
toast({
|
||||
title: 'Transaction Failed',
|
||||
title: t('swap.swapFailed'),
|
||||
description: errorMessage,
|
||||
variant: 'destructive',
|
||||
});
|
||||
} else {
|
||||
setTxStatus('success');
|
||||
toast({
|
||||
title: 'Success!',
|
||||
description: `Swapped ${fromAmount} ${fromToken} for ~${toAmount} ${toToken}`,
|
||||
title: t('common.success'),
|
||||
description: t('swap.swapped', { fromAmount, fromToken, toAmount, toToken }),
|
||||
});
|
||||
setTimeout(() => {
|
||||
setFromAmount('');
|
||||
@@ -361,11 +363,11 @@ export const SwapInterface: React.FC<SwapInterfaceProps> = ({ pools }) => {
|
||||
);
|
||||
} catch (error) {
|
||||
if (import.meta.env.DEV) console.error('Swap failed:', error);
|
||||
setErrorMessage(error instanceof Error ? error.message : 'Transaction failed');
|
||||
setErrorMessage(error instanceof Error ? error.message : t('common.txFailed'));
|
||||
setTxStatus('error');
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: error instanceof Error ? error.message : 'Swap transaction failed',
|
||||
title: t('common.error'),
|
||||
description: error instanceof Error ? error.message : t('swap.swapFailed'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@@ -382,7 +384,7 @@ export const SwapInterface: React.FC<SwapInterfaceProps> = ({ pools }) => {
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<Loader2 className="w-16 h-16 animate-spin text-green-400" />
|
||||
<p className="text-white text-xl font-semibold">
|
||||
{txStatus === 'signing' ? 'Waiting for signature...' : 'Processing swap...'}
|
||||
{txStatus === 'signing' ? t('swap.waitingSignature') : t('swap.processingSwap')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -391,7 +393,7 @@ export const SwapInterface: React.FC<SwapInterfaceProps> = ({ pools }) => {
|
||||
<Card className="bg-gray-900 border-gray-800">
|
||||
<CardHeader className="border-b border-gray-800">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-xl font-bold text-white">Swap Tokens</CardTitle>
|
||||
<CardTitle className="text-xl font-bold text-white">{t('swap.title')}</CardTitle>
|
||||
<Button variant="ghost" size="icon" onClick={() => setShowSettings(true)}>
|
||||
<Settings className="h-5 w-5 text-gray-400" />
|
||||
</Button>
|
||||
@@ -403,7 +405,7 @@ export const SwapInterface: React.FC<SwapInterfaceProps> = ({ pools }) => {
|
||||
<Alert className="bg-yellow-500/10 border-yellow-500/30">
|
||||
<AlertCircle className="h-4 w-4 text-yellow-500" />
|
||||
<AlertDescription className="text-yellow-300">
|
||||
Please connect your wallet to swap tokens
|
||||
{t('swap.connectWalletAlert')}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
@@ -411,9 +413,9 @@ export const SwapInterface: React.FC<SwapInterfaceProps> = ({ pools }) => {
|
||||
{/* From Token */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-400">From</span>
|
||||
<span className="text-gray-400">{t('swap.from')}</span>
|
||||
<span className="text-gray-400">
|
||||
Balance: {formatTokenBalance(fromBalance, fromTokenInfo?.decimals ?? 12, 4)} {fromToken}
|
||||
{t('common.balance')} {formatTokenBalance(fromBalance, fromTokenInfo?.decimals ?? 12, 4)} {fromToken}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -460,7 +462,7 @@ export const SwapInterface: React.FC<SwapInterfaceProps> = ({ pools }) => {
|
||||
className="px-3 py-1 bg-green-600/20 hover:bg-green-600/30 text-green-400 text-xs rounded border border-green-600/30 transition-colors"
|
||||
disabled={!account}
|
||||
>
|
||||
MAX
|
||||
{t('common.max')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -481,9 +483,9 @@ export const SwapInterface: React.FC<SwapInterfaceProps> = ({ pools }) => {
|
||||
{/* To Token */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-400">To</span>
|
||||
<span className="text-gray-400">{t('swap.to')}</span>
|
||||
<span className="text-gray-400">
|
||||
Balance: {formatTokenBalance(toBalance, toTokenInfo?.decimals ?? 12, 4)} {toToken}
|
||||
{t('common.balance')} {formatTokenBalance(toBalance, toTokenInfo?.decimals ?? 12, 4)} {toToken}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -532,12 +534,12 @@ export const SwapInterface: React.FC<SwapInterfaceProps> = ({ pools }) => {
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-gray-400 flex items-center gap-1">
|
||||
<Info className="w-3 h-3" />
|
||||
Exchange Rate
|
||||
<span className="text-xs text-green-500">(CoinGecko)</span>
|
||||
{t('common.exchangeRate')}
|
||||
<span className="text-xs text-green-500">{t('swap.coinGecko')}</span>
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-white">
|
||||
{oracleRate ? `1 ${fromToken} = ${exchangeRate} ${toToken}` : 'Loading...'}
|
||||
{oracleRate ? `1 ${fromToken} = ${exchangeRate} ${toToken}` : t('common.loading')}
|
||||
</span>
|
||||
<button onClick={fetchPrices} className="text-gray-400 hover:text-white">
|
||||
<RefreshCw className={`w-3 h-3 ${pricesLoading ? 'animate-spin' : ''}`} />
|
||||
@@ -558,7 +560,7 @@ export const SwapInterface: React.FC<SwapInterfaceProps> = ({ pools }) => {
|
||||
{/* Route */}
|
||||
{swapRoute.length > 0 && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-400">Route</span>
|
||||
<span className="text-gray-400">{t('swap.route')}</span>
|
||||
<span className="text-purple-400 text-xs">
|
||||
{swapRoute.join(' → ')}
|
||||
</span>
|
||||
@@ -567,15 +569,15 @@ export const SwapInterface: React.FC<SwapInterfaceProps> = ({ pools }) => {
|
||||
|
||||
{/* Fees */}
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-400">Swap Fee</span>
|
||||
<span className="text-gray-400">{t('swap.swapFee')}</span>
|
||||
<span className="text-yellow-400">
|
||||
{swapRoute.length > 2 ? '0.6%' : '0.3%'}
|
||||
{swapRoute.length > 2 && <span className="text-xs text-gray-500 ml-1">(2 hops)</span>}
|
||||
{swapRoute.length > 2 && <span className="text-xs text-gray-500 ml-1">{t('swap.twoHops')}</span>}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between pt-2 border-t border-gray-700">
|
||||
<span className="text-gray-400">Slippage Tolerance</span>
|
||||
<span className="text-gray-400">{t('common.slippageTolerance')}</span>
|
||||
<span className="text-blue-400">{slippage}%</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -585,7 +587,7 @@ export const SwapInterface: React.FC<SwapInterfaceProps> = ({ pools }) => {
|
||||
<Alert className="bg-red-900/20 border-red-500/30">
|
||||
<AlertTriangle className="h-4 w-4 text-red-500" />
|
||||
<AlertDescription className="text-red-300 text-sm">
|
||||
Insufficient {fromToken} balance
|
||||
{t('swap.insufficientBalance', { token: fromToken })}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
@@ -594,7 +596,7 @@ export const SwapInterface: React.FC<SwapInterfaceProps> = ({ pools }) => {
|
||||
<Alert className="bg-yellow-900/20 border-yellow-500/30">
|
||||
<Info className="h-4 w-4 text-yellow-500" />
|
||||
<AlertDescription className="text-yellow-300 text-sm">
|
||||
This swap uses multi-hop routing ({swapRoute.join(' → ')}). Double fee applies.
|
||||
{t('swap.multiHopWarning', { route: swapRoute.join(' → ') })}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
@@ -615,14 +617,14 @@ export const SwapInterface: React.FC<SwapInterfaceProps> = ({ pools }) => {
|
||||
}
|
||||
>
|
||||
{!account
|
||||
? 'Connect Wallet'
|
||||
? t('swap.connectWallet')
|
||||
: hasInsufficientBalance
|
||||
? `Insufficient ${fromToken} Balance`
|
||||
? t('swap.insufficientBalanceBtn', { token: fromToken })
|
||||
: !oracleRate
|
||||
? 'Price Not Available'
|
||||
? t('swap.priceNotAvailable')
|
||||
: pricesLoading
|
||||
? 'Loading Prices...'
|
||||
: 'Swap Tokens'}
|
||||
? t('swap.loadingPrices')
|
||||
: t('swap.swapTokens')}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -631,11 +633,11 @@ export const SwapInterface: React.FC<SwapInterfaceProps> = ({ pools }) => {
|
||||
<Dialog open={showSettings} onOpenChange={setShowSettings}>
|
||||
<DialogContent className="bg-gray-900 border-gray-800">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-white">Swap Settings</DialogTitle>
|
||||
<DialogTitle className="text-white">{t('swap.settings')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-gray-300">Slippage Tolerance</label>
|
||||
<label className="text-sm font-medium text-gray-300">{t('common.slippageTolerance')}</label>
|
||||
<div className="flex gap-2 mt-2">
|
||||
{[0.1, 0.5, 1.0, 2.0].map(val => (
|
||||
<Button
|
||||
@@ -657,24 +659,24 @@ export const SwapInterface: React.FC<SwapInterfaceProps> = ({ pools }) => {
|
||||
<Dialog open={showConfirm} onOpenChange={setShowConfirm}>
|
||||
<DialogContent className="bg-gray-900 border-gray-800">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-white">Confirm Swap</DialogTitle>
|
||||
<DialogTitle className="text-white">{t('swap.confirmSwap')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<div className="p-4 bg-gray-800 border border-gray-700 rounded-lg space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-300">You Pay</span>
|
||||
<span className="text-gray-300">{t('swap.youPay')}</span>
|
||||
<span className="font-bold text-white">{fromAmount} {fromToken}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-300">You Receive</span>
|
||||
<span className="text-gray-300">{t('swap.youReceive')}</span>
|
||||
<span className="font-bold text-white">{toAmount} {toToken}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm pt-2 border-t border-gray-700">
|
||||
<span className="text-gray-400">Exchange Rate</span>
|
||||
<span className="text-gray-400">{t('common.exchangeRate')}</span>
|
||||
<span className="text-gray-400">1 {fromToken} = {exchangeRate} {toToken}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-400">Slippage</span>
|
||||
<span className="text-gray-400">{t('swap.slippage')}</span>
|
||||
<span className="text-gray-400">{slippage}%</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -683,7 +685,7 @@ export const SwapInterface: React.FC<SwapInterfaceProps> = ({ pools }) => {
|
||||
onClick={handleConfirmSwap}
|
||||
disabled={txStatus === 'signing' || txStatus === 'submitting'}
|
||||
>
|
||||
{txStatus === 'signing' ? 'Signing...' : txStatus === 'submitting' ? 'Swapping...' : 'Confirm Swap'}
|
||||
{txStatus === 'signing' ? t('common.signing') : txStatus === 'submitting' ? t('swap.swapping') : t('swap.confirmSwap')}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
checkBridgeStatus,
|
||||
fetchAssetHubUsdtInfo,
|
||||
@@ -38,6 +39,7 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
const { assetHubApi, isAssetHubReady } = usePezkuwi();
|
||||
const { account, signer } = useWallet();
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// State
|
||||
const [step, setStep] = useState<SetupStep>('idle');
|
||||
@@ -56,7 +58,7 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
if (!assetHubApi || !isAssetHubReady) return;
|
||||
|
||||
setStep('checking');
|
||||
setStatusMessage('Checking bridge status...');
|
||||
setStatusMessage(t('xcmBridge.checkingStatus'));
|
||||
setErrorMessage('');
|
||||
|
||||
try {
|
||||
@@ -65,11 +67,11 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
setBridgeStatus(status);
|
||||
|
||||
// Fetch Asset Hub USDT info
|
||||
setStatusMessage('Fetching Asset Hub USDT info...');
|
||||
setStatusMessage(t('xcmBridge.fetchingInfo'));
|
||||
const info = await fetchAssetHubUsdtInfo();
|
||||
setAssetHubInfo(info);
|
||||
|
||||
setStatusMessage('Status check complete');
|
||||
setStatusMessage(t('xcmBridge.statusComplete'));
|
||||
setStep('idle');
|
||||
} catch (error) {
|
||||
console.error('Initial check failed:', error);
|
||||
@@ -99,8 +101,8 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
const handleConfigureBridge = async () => {
|
||||
if (!assetHubApi || !isAssetHubReady || !signer || !account) {
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Please connect your wallet',
|
||||
title: t('common.error'),
|
||||
description: t('common.connectWalletAlert'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
@@ -118,22 +120,22 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
);
|
||||
|
||||
toast({
|
||||
title: 'Success!',
|
||||
description: 'XCM bridge configured successfully',
|
||||
title: t('common.success'),
|
||||
description: t('xcmBridge.bridgeConfigured'),
|
||||
});
|
||||
|
||||
// Refresh status
|
||||
await performInitialCheck();
|
||||
|
||||
setStep('success');
|
||||
setStatusMessage('Bridge configuration complete!');
|
||||
setStatusMessage(t('xcmBridge.configComplete'));
|
||||
} catch (error) {
|
||||
console.error('Bridge configuration failed:', error);
|
||||
setErrorMessage(error instanceof Error ? error.message : 'Configuration failed');
|
||||
setStep('error');
|
||||
toast({
|
||||
title: 'Configuration Failed',
|
||||
description: error instanceof Error ? error.message : 'Unknown error',
|
||||
title: t('xcmBridge.configFailed'),
|
||||
description: error instanceof Error ? error.message : t('common.error'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@@ -145,8 +147,8 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
const handleCreatePool = async () => {
|
||||
if (!assetHubApi || !isAssetHubReady || !signer || !account) {
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Please connect your wallet',
|
||||
title: t('common.error'),
|
||||
description: t('common.connectWalletAlert'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
@@ -170,12 +172,12 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
);
|
||||
|
||||
toast({
|
||||
title: 'Success!',
|
||||
description: 'wUSDT/HEZ pool created successfully',
|
||||
title: t('common.success'),
|
||||
description: t('xcmBridge.poolCreated'),
|
||||
});
|
||||
|
||||
setStep('success');
|
||||
setStatusMessage('Pool creation complete!');
|
||||
setStatusMessage(t('xcmBridge.poolComplete'));
|
||||
|
||||
setTimeout(() => {
|
||||
onSuccess?.();
|
||||
@@ -186,8 +188,8 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
setErrorMessage(error instanceof Error ? error.message : 'Pool creation failed');
|
||||
setStep('error');
|
||||
toast({
|
||||
title: 'Pool Creation Failed',
|
||||
description: error instanceof Error ? error.message : 'Unknown error',
|
||||
title: t('xcmBridge.poolFailed'),
|
||||
description: error instanceof Error ? error.message : t('common.error'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@@ -203,7 +205,7 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
<CardHeader className="border-b border-gray-800">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-xl font-bold text-white">
|
||||
XCM Bridge Setup
|
||||
{t('xcmBridge.title')}
|
||||
</CardTitle>
|
||||
<button
|
||||
onClick={onClose}
|
||||
@@ -214,7 +216,7 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
</button>
|
||||
</div>
|
||||
<Badge className="bg-purple-600/20 text-purple-400 border-purple-600/30 w-fit mt-2">
|
||||
Admin Only - XCM Configuration
|
||||
{t('xcmBridge.adminOnly')}
|
||||
</Badge>
|
||||
</CardHeader>
|
||||
|
||||
@@ -223,18 +225,17 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
<Alert className="bg-purple-500/10 border-purple-500/30">
|
||||
<Zap className="h-4 w-4 text-purple-400" />
|
||||
<AlertDescription className="text-purple-300 text-sm">
|
||||
Configure Asset Hub USDT → wUSDT bridge with one click. This enables
|
||||
cross-chain transfers from Westend Asset Hub to PezkuwiChain.
|
||||
{t('xcmBridge.info')}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
{/* Current Status */}
|
||||
{bridgeStatus && (
|
||||
<div className="p-4 bg-gray-800/50 rounded-lg border border-gray-700 space-y-3">
|
||||
<div className="text-sm font-semibold text-gray-300 mb-2">Current Status</div>
|
||||
<div className="text-sm font-semibold text-gray-300 mb-2">{t('xcmBridge.currentStatus')}</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-gray-400">Asset Hub Connection:</span>
|
||||
<span className="text-sm text-gray-400">{t('xcmBridge.assetHubConnection')}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
{bridgeStatus.assetHubConnected ? (
|
||||
<CheckCircle className="w-4 h-4 text-green-400" />
|
||||
@@ -242,13 +243,13 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
<AlertCircle className="w-4 h-4 text-yellow-400" />
|
||||
)}
|
||||
<span className={bridgeStatus.assetHubConnected ? 'text-green-400' : 'text-yellow-400'}>
|
||||
{bridgeStatus.assetHubConnected ? 'Connected' : 'Checking...'}
|
||||
{bridgeStatus.assetHubConnected ? t('xcmBridge.connected') : t('xcmBridge.checking')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-gray-400">wUSDT Asset Exists:</span>
|
||||
<span className="text-sm text-gray-400">{t('xcmBridge.wusdtAssetExists')}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
{bridgeStatus.wusdtExists ? (
|
||||
<CheckCircle className="w-4 h-4 text-green-400" />
|
||||
@@ -256,13 +257,13 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
<AlertCircle className="w-4 h-4 text-red-400" />
|
||||
)}
|
||||
<span className={bridgeStatus.wusdtExists ? 'text-green-400' : 'text-red-400'}>
|
||||
{bridgeStatus.wusdtExists ? 'Yes (ID: 1000)' : 'Not Found'}
|
||||
{bridgeStatus.wusdtExists ? t('xcmBridge.yesId') : t('xcmBridge.notFound')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-gray-400">XCM Bridge Configured:</span>
|
||||
<span className="text-sm text-gray-400">{t('xcmBridge.xcmConfigured')}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
{bridgeStatus.isConfigured ? (
|
||||
<CheckCircle className="w-4 h-4 text-green-400" />
|
||||
@@ -270,7 +271,7 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
<AlertCircle className="w-4 h-4 text-yellow-400" />
|
||||
)}
|
||||
<span className={bridgeStatus.isConfigured ? 'text-green-400' : 'text-yellow-400'}>
|
||||
{bridgeStatus.isConfigured ? 'Configured' : 'Not Configured'}
|
||||
{bridgeStatus.isConfigured ? t('xcmBridge.configured') : t('xcmBridge.notConfigured')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -280,7 +281,7 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
{/* Asset Hub USDT Info */}
|
||||
{assetHubInfo && (
|
||||
<div className="p-4 bg-gray-800/50 rounded-lg border border-gray-700 space-y-2">
|
||||
<div className="text-sm font-semibold text-gray-300 mb-2">Asset Hub USDT Info</div>
|
||||
<div className="text-sm font-semibold text-gray-300 mb-2">{t('xcmBridge.assetHubInfo')}</div>
|
||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||
<span className="text-gray-400">Asset ID:</span>
|
||||
<span className="text-white font-mono">{assetHubInfo.id}</span>
|
||||
@@ -307,7 +308,7 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
|
||||
{/* Configuration Details */}
|
||||
<div className="p-4 bg-gray-800/50 rounded-lg border border-gray-700 space-y-2">
|
||||
<div className="text-sm font-semibold text-gray-300 mb-2">Configuration Details</div>
|
||||
<div className="text-sm font-semibold text-gray-300 mb-2">{t('xcmBridge.configDetails')}</div>
|
||||
<div className="text-xs space-y-1 text-gray-400 font-mono">
|
||||
<div>Asset Hub Endpoint: {ASSET_HUB_ENDPOINT}</div>
|
||||
<div>Asset Hub USDT ID: {ASSET_HUB_USDT_ID}</div>
|
||||
@@ -349,10 +350,10 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
{/* Pool Creation Section (Optional) */}
|
||||
{showPoolCreation && (
|
||||
<div className="p-4 bg-gray-800/50 rounded-lg border border-gray-700 space-y-4">
|
||||
<div className="text-sm font-semibold text-gray-300">Create wUSDT/HEZ Pool (Optional)</div>
|
||||
<div className="text-sm font-semibold text-gray-300">{t('xcmBridge.createPoolSection')}</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-xs text-gray-400">wUSDT Amount</label>
|
||||
<label className="text-xs text-gray-400">{t('xcmBridge.wusdtAmount')}</label>
|
||||
<input
|
||||
type="number"
|
||||
value={wusdtAmount}
|
||||
@@ -362,7 +363,7 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-gray-400">HEZ Amount</label>
|
||||
<label className="text-xs text-gray-400">{t('xcmBridge.hezAmount')}</label>
|
||||
<input
|
||||
type="number"
|
||||
value={hezAmount}
|
||||
@@ -383,7 +384,7 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
className="flex-1 border-gray-700 hover:bg-gray-800"
|
||||
disabled={isLoading}
|
||||
>
|
||||
Close
|
||||
{t('xcmBridge.close')}
|
||||
</Button>
|
||||
|
||||
{!bridgeStatus?.isConfigured && (
|
||||
@@ -395,10 +396,10 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
{step === 'configuring' ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin mr-2" />
|
||||
Configuring...
|
||||
{t('xcmBridge.configuring')}
|
||||
</>
|
||||
) : (
|
||||
'Configure Bridge'
|
||||
t('xcmBridge.configureBridge')
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
@@ -408,7 +409,7 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
onClick={() => setShowPoolCreation(true)}
|
||||
className="flex-1 bg-green-600 hover:bg-green-700"
|
||||
>
|
||||
Create Pool (Optional)
|
||||
{t('xcmBridge.createPoolOptional')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -421,10 +422,10 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
{step === 'pool-creation' ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin mr-2" />
|
||||
Creating...
|
||||
{t('xcmBridge.creatingPool')}
|
||||
</>
|
||||
) : (
|
||||
'Create Pool'
|
||||
t('xcmBridge.createPool')
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
@@ -432,7 +433,7 @@ export const XCMBridgeSetupModal: React.FC<XCMBridgeSetupModalProps> = ({
|
||||
|
||||
{/* Note */}
|
||||
<div className="text-xs text-gray-500 text-center">
|
||||
⚠️ XCM bridge configuration requires sudo access
|
||||
{t('xcmBridge.sudoWarning')}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user