mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-06-13 22:41:02 +00:00
fix: auto-verify deposit after TX sign, remove manual verify step
The manual "Verify Deposit" step required users to click a button after signing. Hash was already captured automatically, making the manual step redundant and risky (modal close = hash lost). Now verification starts immediately after TX is signed, with spinner UI and retry on failure.
This commit is contained in:
@@ -25,7 +25,6 @@ import {
|
|||||||
Copy,
|
Copy,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
ExternalLink,
|
|
||||||
QrCode,
|
QrCode,
|
||||||
Wallet
|
Wallet
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
@@ -45,7 +44,7 @@ interface DepositModalProps {
|
|||||||
onSuccess?: () => void;
|
onSuccess?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type DepositStep = 'select' | 'send' | 'verify' | 'success';
|
type DepositStep = 'select' | 'send' | 'verifying' | 'success';
|
||||||
|
|
||||||
export function DepositModal({ isOpen, onClose, onSuccess }: DepositModalProps) {
|
export function DepositModal({ isOpen, onClose, onSuccess }: DepositModalProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -61,7 +60,7 @@ export function DepositModal({ isOpen, onClose, onSuccess }: DepositModalProps)
|
|||||||
const [blockNumber, setBlockNumber] = useState<number | undefined>();
|
const [blockNumber, setBlockNumber] = useState<number | undefined>();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const [verifying, setVerifying] = useState(false);
|
const [verifyError, setVerifyError] = useState('');
|
||||||
|
|
||||||
// Fetch platform wallet address on mount
|
// Fetch platform wallet address on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -83,7 +82,7 @@ export function DepositModal({ isOpen, onClose, onSuccess }: DepositModalProps)
|
|||||||
setBlockNumber(undefined);
|
setBlockNumber(undefined);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setCopied(false);
|
setCopied(false);
|
||||||
setVerifying(false);
|
setVerifyError('');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
@@ -145,14 +144,18 @@ export function DepositModal({ isOpen, onClose, onSuccess }: DepositModalProps)
|
|||||||
if (hash) {
|
if (hash) {
|
||||||
setTxHash(hash);
|
setTxHash(hash);
|
||||||
// Capture approximate block number for faster verification
|
// Capture approximate block number for faster verification
|
||||||
|
let blockNum: number | undefined;
|
||||||
try {
|
try {
|
||||||
const header = await assetHubApi.rpc.chain.getHeader();
|
const header = await assetHubApi.rpc.chain.getHeader();
|
||||||
setBlockNumber(header.number.toNumber());
|
blockNum = header.number.toNumber();
|
||||||
|
setBlockNumber(blockNum);
|
||||||
} catch {
|
} catch {
|
||||||
// Non-critical - verification will still work via search
|
// Non-critical - verification will still work via search
|
||||||
}
|
}
|
||||||
setStep('verify');
|
setStep('verifying');
|
||||||
toast.success(t('p2pDeposit.txSent'));
|
toast.success(t('p2pDeposit.txSent'));
|
||||||
|
// Auto-verify immediately — fire and forget
|
||||||
|
handleVerifyDeposit(hash, blockNum);
|
||||||
}
|
}
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
console.error('Deposit transaction error:', error);
|
console.error('Deposit transaction error:', error);
|
||||||
@@ -163,8 +166,11 @@ export function DepositModal({ isOpen, onClose, onSuccess }: DepositModalProps)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleVerifyDeposit = async () => {
|
const handleVerifyDeposit = async (hash?: string, blockNum?: number) => {
|
||||||
if (!txHash) {
|
const verifyHash = hash || txHash;
|
||||||
|
const verifyBlockNumber = blockNum !== undefined ? blockNum : blockNumber;
|
||||||
|
|
||||||
|
if (!verifyHash) {
|
||||||
toast.error(t('p2pDeposit.enterTxHash'));
|
toast.error(t('p2pDeposit.enterTxHash'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -175,11 +181,10 @@ export function DepositModal({ isOpen, onClose, onSuccess }: DepositModalProps)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setVerifying(true);
|
setVerifyError('');
|
||||||
|
setStep('verifying');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Call the Edge Function for secure deposit verification
|
|
||||||
// Use fetch directly to read response body on all status codes
|
|
||||||
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
|
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
|
||||||
const supabaseKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
|
const supabaseKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
|
||||||
const res = await fetch(`${supabaseUrl}/functions/v1/verify-deposit`, {
|
const res = await fetch(`${supabaseUrl}/functions/v1/verify-deposit`, {
|
||||||
@@ -190,12 +195,12 @@ export function DepositModal({ isOpen, onClose, onSuccess }: DepositModalProps)
|
|||||||
'apikey': supabaseKey,
|
'apikey': supabaseKey,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
txHash,
|
txHash: verifyHash,
|
||||||
token,
|
token,
|
||||||
expectedAmount: depositAmount,
|
expectedAmount: depositAmount,
|
||||||
walletAddress: selectedAccount?.address,
|
walletAddress: selectedAccount?.address,
|
||||||
identityId,
|
identityId,
|
||||||
...(blockNumber ? { blockNumber } : {})
|
...(verifyBlockNumber ? { blockNumber: verifyBlockNumber } : {})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -211,9 +216,7 @@ export function DepositModal({ isOpen, onClose, onSuccess }: DepositModalProps)
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Verify deposit error:', error);
|
console.error('Verify deposit error:', error);
|
||||||
const message = error instanceof Error ? error.message : t('p2pDeposit.verificationFailed');
|
const message = error instanceof Error ? error.message : t('p2pDeposit.verificationFailed');
|
||||||
toast.error(message);
|
setVerifyError(message);
|
||||||
} finally {
|
|
||||||
setVerifying(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -351,67 +354,80 @@ export function DepositModal({ isOpen, onClose, onSuccess }: DepositModalProps)
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
case 'verify':
|
case 'verifying':
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Alert>
|
{verifyError ? (
|
||||||
<CheckCircle2 className="h-4 w-4 text-green-500" />
|
<>
|
||||||
<AlertDescription>
|
<div className="text-center space-y-4">
|
||||||
{t('p2pDeposit.txSentVerify')}
|
<div className="w-16 h-16 mx-auto rounded-full bg-destructive/10 flex items-center justify-center">
|
||||||
</AlertDescription>
|
<AlertTriangle className="h-8 w-8 text-destructive" />
|
||||||
</Alert>
|
</div>
|
||||||
|
<div>
|
||||||
<div className="space-y-2">
|
<h3 className="text-lg font-semibold text-destructive">
|
||||||
<Label>{t('p2pDeposit.txHash')}</Label>
|
{t('p2pDeposit.verifyFailed')}
|
||||||
<div className="flex gap-2">
|
</h3>
|
||||||
<Input
|
<p className="text-sm text-muted-foreground mt-2">{verifyError}</p>
|
||||||
value={txHash}
|
</div>
|
||||||
onChange={(e) => setTxHash(e.target.value)}
|
|
||||||
placeholder="0x..."
|
|
||||||
className="font-mono text-xs"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="icon"
|
|
||||||
onClick={() => window.open(`https://explorer.pezkuwichain.io/tx/${txHash}`, '_blank')}
|
|
||||||
disabled={!txHash}
|
|
||||||
>
|
|
||||||
<ExternalLink className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-4 rounded-lg bg-muted/50 border">
|
|
||||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
|
||||||
<div>
|
|
||||||
<p className="text-muted-foreground">{t('p2pDeposit.tokenLabel')}</p>
|
|
||||||
<p className="font-semibold">{token}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="p-4 rounded-lg bg-muted/50 border">
|
||||||
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||||
|
<div>
|
||||||
|
<p className="text-muted-foreground">{t('p2pDeposit.tokenLabel')}</p>
|
||||||
|
<p className="font-semibold">{token}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-muted-foreground">{t('p2pDeposit.amountLabel')}</p>
|
||||||
|
<p className="font-semibold">{amount}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{txHash && (
|
||||||
|
<div className="mt-3 pt-3 border-t">
|
||||||
|
<p className="text-muted-foreground text-xs">TX</p>
|
||||||
|
<p className="font-mono text-xs break-all">{txHash}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button variant="outline" className="flex-1" onClick={handleClose}>
|
||||||
|
{t('cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button className="flex-1" onClick={() => handleVerifyDeposit()}>
|
||||||
|
{t('p2pDeposit.retry')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="text-center space-y-4 py-4">
|
||||||
|
<Loader2 className="h-12 w-12 animate-spin mx-auto text-primary" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground">{t('p2pDeposit.amountLabel')}</p>
|
<h3 className="text-lg font-semibold">{t('p2pDeposit.autoVerifying')}</h3>
|
||||||
<p className="font-semibold">{amount}</p>
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
{t('p2pDeposit.autoVerifyingDesc')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 rounded-lg bg-muted/50 border">
|
||||||
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||||
|
<div>
|
||||||
|
<p className="text-muted-foreground">{t('p2pDeposit.tokenLabel')}</p>
|
||||||
|
<p className="font-semibold">{token}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-muted-foreground">{t('p2pDeposit.amountLabel')}</p>
|
||||||
|
<p className="font-semibold">{amount}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{txHash && (
|
||||||
|
<div className="mt-3 pt-3 border-t">
|
||||||
|
<p className="text-muted-foreground text-xs">TX</p>
|
||||||
|
<p className="font-mono text-xs break-all">{txHash}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
<DialogFooter>
|
|
||||||
<Button variant="outline" onClick={handleClose}>
|
|
||||||
{t('cancel')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={handleVerifyDeposit}
|
|
||||||
disabled={verifying || !txHash}
|
|
||||||
>
|
|
||||||
{verifying ? (
|
|
||||||
<>
|
|
||||||
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
||||||
{t('p2pDeposit.verifying')}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
t('p2pDeposit.verifyDeposit')
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -457,7 +473,7 @@ export function DepositModal({ isOpen, onClose, onSuccess }: DepositModalProps)
|
|||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
{step === 'select' && t('p2pDeposit.selectStep')}
|
{step === 'select' && t('p2pDeposit.selectStep')}
|
||||||
{step === 'send' && t('p2pDeposit.sendStep')}
|
{step === 'send' && t('p2pDeposit.sendStep')}
|
||||||
{step === 'verify' && t('p2pDeposit.verifyStep')}
|
{step === 'verifying' && t('p2pDeposit.autoVerifying')}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
)}
|
)}
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|||||||
@@ -1248,6 +1248,10 @@ export default {
|
|||||||
'p2pDeposit.tokenLabel': 'الرمز',
|
'p2pDeposit.tokenLabel': 'الرمز',
|
||||||
'p2pDeposit.amountLabel': 'المبلغ',
|
'p2pDeposit.amountLabel': 'المبلغ',
|
||||||
'p2pDeposit.done': 'تم',
|
'p2pDeposit.done': 'تم',
|
||||||
|
'p2pDeposit.autoVerifying': 'جاري التحقق من الإيداع...',
|
||||||
|
'p2pDeposit.autoVerifyingDesc': 'جاري البحث عن معاملتك في البلوكتشين...',
|
||||||
|
'p2pDeposit.verifyFailed': 'فشل التحقق',
|
||||||
|
'p2pDeposit.retry': 'حاول مرة أخرى',
|
||||||
|
|
||||||
'p2pWithdraw.title': 'سحب من رصيد P2P',
|
'p2pWithdraw.title': 'سحب من رصيد P2P',
|
||||||
'p2pWithdraw.formStep': 'اسحب عملة رقمية من رصيد P2P إلى محفظة خارجية',
|
'p2pWithdraw.formStep': 'اسحب عملة رقمية من رصيد P2P إلى محفظة خارجية',
|
||||||
|
|||||||
@@ -1238,6 +1238,10 @@ export default {
|
|||||||
'p2pDeposit.tokenLabel': 'تۆکن',
|
'p2pDeposit.tokenLabel': 'تۆکن',
|
||||||
'p2pDeposit.amountLabel': 'بڕ',
|
'p2pDeposit.amountLabel': 'بڕ',
|
||||||
'p2pDeposit.done': 'تەواو',
|
'p2pDeposit.done': 'تەواو',
|
||||||
|
'p2pDeposit.autoVerifying': 'دانان لە پشتڕاستکردنەوەدایە...',
|
||||||
|
'p2pDeposit.autoVerifyingDesc': 'لە بلۆکچەین بۆ مامەڵەکەت دەگەڕێت...',
|
||||||
|
'p2pDeposit.verifyFailed': 'پشتڕاستکردنەوە سەرکەوتوو نەبوو',
|
||||||
|
'p2pDeposit.retry': 'دووبارە هەوڵ بدەوە',
|
||||||
|
|
||||||
'p2pWithdraw.title': 'دەرهێنان لە باڵانسی P2P',
|
'p2pWithdraw.title': 'دەرهێنان لە باڵانسی P2P',
|
||||||
'p2pWithdraw.formStep': 'لە باڵانسی P2P کریپتۆ دەربهێنە بۆ جزدانی دەرەکی',
|
'p2pWithdraw.formStep': 'لە باڵانسی P2P کریپتۆ دەربهێنە بۆ جزدانی دەرەکی',
|
||||||
|
|||||||
@@ -1591,6 +1591,10 @@ export default {
|
|||||||
'p2pDeposit.tokenLabel': 'Token',
|
'p2pDeposit.tokenLabel': 'Token',
|
||||||
'p2pDeposit.amountLabel': 'Amount',
|
'p2pDeposit.amountLabel': 'Amount',
|
||||||
'p2pDeposit.done': 'Done',
|
'p2pDeposit.done': 'Done',
|
||||||
|
'p2pDeposit.autoVerifying': 'Verifying deposit...',
|
||||||
|
'p2pDeposit.autoVerifyingDesc': 'Searching for your transaction on blockchain...',
|
||||||
|
'p2pDeposit.verifyFailed': 'Verification failed',
|
||||||
|
'p2pDeposit.retry': 'Try Again',
|
||||||
|
|
||||||
// WithdrawModal
|
// WithdrawModal
|
||||||
'p2pWithdraw.title': 'Withdraw from P2P Balance',
|
'p2pWithdraw.title': 'Withdraw from P2P Balance',
|
||||||
|
|||||||
@@ -1261,6 +1261,10 @@ export default {
|
|||||||
'p2pDeposit.tokenLabel': 'توکن',
|
'p2pDeposit.tokenLabel': 'توکن',
|
||||||
'p2pDeposit.amountLabel': 'مقدار',
|
'p2pDeposit.amountLabel': 'مقدار',
|
||||||
'p2pDeposit.done': 'انجام شد',
|
'p2pDeposit.done': 'انجام شد',
|
||||||
|
'p2pDeposit.autoVerifying': 'واریز در حال تأیید...',
|
||||||
|
'p2pDeposit.autoVerifyingDesc': 'در حال جستجوی تراکنش شما در بلاکچین...',
|
||||||
|
'p2pDeposit.verifyFailed': 'تأیید ناموفق بود',
|
||||||
|
'p2pDeposit.retry': 'تلاش مجدد',
|
||||||
|
|
||||||
// WithdrawModal
|
// WithdrawModal
|
||||||
'p2pWithdraw.title': 'برداشت از موجودی P2P',
|
'p2pWithdraw.title': 'برداشت از موجودی P2P',
|
||||||
|
|||||||
@@ -1253,6 +1253,10 @@ export default {
|
|||||||
'p2pDeposit.tokenLabel': 'Token',
|
'p2pDeposit.tokenLabel': 'Token',
|
||||||
'p2pDeposit.amountLabel': 'Miqdar',
|
'p2pDeposit.amountLabel': 'Miqdar',
|
||||||
'p2pDeposit.done': 'Temam',
|
'p2pDeposit.done': 'Temam',
|
||||||
|
'p2pDeposit.autoVerifying': 'Danîn tê piştrastkirin...',
|
||||||
|
'p2pDeposit.autoVerifyingDesc': 'Li blockchain kirrûbirê te tê lêgerîn...',
|
||||||
|
'p2pDeposit.verifyFailed': 'Piştrastkirin bi ser neket',
|
||||||
|
'p2pDeposit.retry': 'Dîsa Biceribîne',
|
||||||
|
|
||||||
// WithdrawModal
|
// WithdrawModal
|
||||||
'p2pWithdraw.title': 'Ji Hesabê P2P Vekişîne',
|
'p2pWithdraw.title': 'Ji Hesabê P2P Vekişîne',
|
||||||
|
|||||||
@@ -1247,6 +1247,10 @@ export default {
|
|||||||
'p2pDeposit.tokenLabel': 'Token',
|
'p2pDeposit.tokenLabel': 'Token',
|
||||||
'p2pDeposit.amountLabel': 'Miktar',
|
'p2pDeposit.amountLabel': 'Miktar',
|
||||||
'p2pDeposit.done': 'Tamam',
|
'p2pDeposit.done': 'Tamam',
|
||||||
|
'p2pDeposit.autoVerifying': 'Yatırım doğrulanıyor...',
|
||||||
|
'p2pDeposit.autoVerifyingDesc': "Blockchain'de işleminiz aranıyor...",
|
||||||
|
'p2pDeposit.verifyFailed': 'Doğrulama başarısız oldu',
|
||||||
|
'p2pDeposit.retry': 'Tekrar Dene',
|
||||||
|
|
||||||
// WithdrawModal
|
// WithdrawModal
|
||||||
'p2pWithdraw.title': 'P2P Bakiyeden Çekim',
|
'p2pWithdraw.title': 'P2P Bakiyeden Çekim',
|
||||||
|
|||||||
Reference in New Issue
Block a user