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:
2026-02-26 15:30:30 +03:00
parent 3e310fb980
commit aa7cc78de8
7 changed files with 112 additions and 72 deletions
+88 -72
View File
@@ -25,7 +25,6 @@ import {
Copy,
CheckCircle2,
AlertTriangle,
ExternalLink,
QrCode,
Wallet
} from 'lucide-react';
@@ -45,7 +44,7 @@ interface DepositModalProps {
onSuccess?: () => void;
}
type DepositStep = 'select' | 'send' | 'verify' | 'success';
type DepositStep = 'select' | 'send' | 'verifying' | 'success';
export function DepositModal({ isOpen, onClose, onSuccess }: DepositModalProps) {
const { t } = useTranslation();
@@ -61,7 +60,7 @@ export function DepositModal({ isOpen, onClose, onSuccess }: DepositModalProps)
const [blockNumber, setBlockNumber] = useState<number | undefined>();
const [loading, setLoading] = useState(false);
const [copied, setCopied] = useState(false);
const [verifying, setVerifying] = useState(false);
const [verifyError, setVerifyError] = useState('');
// Fetch platform wallet address on mount
useEffect(() => {
@@ -83,7 +82,7 @@ export function DepositModal({ isOpen, onClose, onSuccess }: DepositModalProps)
setBlockNumber(undefined);
setLoading(false);
setCopied(false);
setVerifying(false);
setVerifyError('');
};
const handleClose = () => {
@@ -145,14 +144,18 @@ export function DepositModal({ isOpen, onClose, onSuccess }: DepositModalProps)
if (hash) {
setTxHash(hash);
// Capture approximate block number for faster verification
let blockNum: number | undefined;
try {
const header = await assetHubApi.rpc.chain.getHeader();
setBlockNumber(header.number.toNumber());
blockNum = header.number.toNumber();
setBlockNumber(blockNum);
} catch {
// Non-critical - verification will still work via search
}
setStep('verify');
setStep('verifying');
toast.success(t('p2pDeposit.txSent'));
// Auto-verify immediately — fire and forget
handleVerifyDeposit(hash, blockNum);
}
} catch (error: unknown) {
console.error('Deposit transaction error:', error);
@@ -163,8 +166,11 @@ export function DepositModal({ isOpen, onClose, onSuccess }: DepositModalProps)
}
};
const handleVerifyDeposit = async () => {
if (!txHash) {
const handleVerifyDeposit = async (hash?: string, blockNum?: number) => {
const verifyHash = hash || txHash;
const verifyBlockNumber = blockNum !== undefined ? blockNum : blockNumber;
if (!verifyHash) {
toast.error(t('p2pDeposit.enterTxHash'));
return;
}
@@ -175,11 +181,10 @@ export function DepositModal({ isOpen, onClose, onSuccess }: DepositModalProps)
return;
}
setVerifying(true);
setVerifyError('');
setStep('verifying');
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 supabaseKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
const res = await fetch(`${supabaseUrl}/functions/v1/verify-deposit`, {
@@ -190,12 +195,12 @@ export function DepositModal({ isOpen, onClose, onSuccess }: DepositModalProps)
'apikey': supabaseKey,
},
body: JSON.stringify({
txHash,
txHash: verifyHash,
token,
expectedAmount: depositAmount,
walletAddress: selectedAccount?.address,
identityId,
...(blockNumber ? { blockNumber } : {})
...(verifyBlockNumber ? { blockNumber: verifyBlockNumber } : {})
})
});
@@ -211,9 +216,7 @@ export function DepositModal({ isOpen, onClose, onSuccess }: DepositModalProps)
} catch (error) {
console.error('Verify deposit error:', error);
const message = error instanceof Error ? error.message : t('p2pDeposit.verificationFailed');
toast.error(message);
} finally {
setVerifying(false);
setVerifyError(message);
}
};
@@ -351,67 +354,80 @@ export function DepositModal({ isOpen, onClose, onSuccess }: DepositModalProps)
</div>
);
case 'verify':
case 'verifying':
return (
<div className="space-y-6">
<Alert>
<CheckCircle2 className="h-4 w-4 text-green-500" />
<AlertDescription>
{t('p2pDeposit.txSentVerify')}
</AlertDescription>
</Alert>
<div className="space-y-2">
<Label>{t('p2pDeposit.txHash')}</Label>
<div className="flex gap-2">
<Input
value={txHash}
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>
{verifyError ? (
<>
<div className="text-center space-y-4">
<div className="w-16 h-16 mx-auto rounded-full bg-destructive/10 flex items-center justify-center">
<AlertTriangle className="h-8 w-8 text-destructive" />
</div>
<div>
<h3 className="text-lg font-semibold text-destructive">
{t('p2pDeposit.verifyFailed')}
</h3>
<p className="text-sm text-muted-foreground mt-2">{verifyError}</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>
<p className="text-muted-foreground">{t('p2pDeposit.amountLabel')}</p>
<p className="font-semibold">{amount}</p>
<h3 className="text-lg font-semibold">{t('p2pDeposit.autoVerifying')}</h3>
<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>
<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>
);
@@ -457,7 +473,7 @@ export function DepositModal({ isOpen, onClose, onSuccess }: DepositModalProps)
<DialogDescription>
{step === 'select' && t('p2pDeposit.selectStep')}
{step === 'send' && t('p2pDeposit.sendStep')}
{step === 'verify' && t('p2pDeposit.verifyStep')}
{step === 'verifying' && t('p2pDeposit.autoVerifying')}
</DialogDescription>
)}
</DialogHeader>
+4
View File
@@ -1248,6 +1248,10 @@ export default {
'p2pDeposit.tokenLabel': 'الرمز',
'p2pDeposit.amountLabel': 'المبلغ',
'p2pDeposit.done': 'تم',
'p2pDeposit.autoVerifying': 'جاري التحقق من الإيداع...',
'p2pDeposit.autoVerifyingDesc': 'جاري البحث عن معاملتك في البلوكتشين...',
'p2pDeposit.verifyFailed': 'فشل التحقق',
'p2pDeposit.retry': 'حاول مرة أخرى',
'p2pWithdraw.title': 'سحب من رصيد P2P',
'p2pWithdraw.formStep': 'اسحب عملة رقمية من رصيد P2P إلى محفظة خارجية',
+4
View File
@@ -1238,6 +1238,10 @@ export default {
'p2pDeposit.tokenLabel': 'تۆکن',
'p2pDeposit.amountLabel': 'بڕ',
'p2pDeposit.done': 'تەواو',
'p2pDeposit.autoVerifying': 'دانان لە پشتڕاستکردنەوەدایە...',
'p2pDeposit.autoVerifyingDesc': 'لە بلۆکچەین بۆ مامەڵەکەت دەگەڕێت...',
'p2pDeposit.verifyFailed': 'پشتڕاستکردنەوە سەرکەوتوو نەبوو',
'p2pDeposit.retry': 'دووبارە هەوڵ بدەوە',
'p2pWithdraw.title': 'دەرهێنان لە باڵانسی P2P',
'p2pWithdraw.formStep': 'لە باڵانسی P2P کریپتۆ دەربهێنە بۆ جزدانی دەرەکی',
+4
View File
@@ -1591,6 +1591,10 @@ export default {
'p2pDeposit.tokenLabel': 'Token',
'p2pDeposit.amountLabel': 'Amount',
'p2pDeposit.done': 'Done',
'p2pDeposit.autoVerifying': 'Verifying deposit...',
'p2pDeposit.autoVerifyingDesc': 'Searching for your transaction on blockchain...',
'p2pDeposit.verifyFailed': 'Verification failed',
'p2pDeposit.retry': 'Try Again',
// WithdrawModal
'p2pWithdraw.title': 'Withdraw from P2P Balance',
+4
View File
@@ -1261,6 +1261,10 @@ export default {
'p2pDeposit.tokenLabel': 'توکن',
'p2pDeposit.amountLabel': 'مقدار',
'p2pDeposit.done': 'انجام شد',
'p2pDeposit.autoVerifying': 'واریز در حال تأیید...',
'p2pDeposit.autoVerifyingDesc': 'در حال جستجوی تراکنش شما در بلاکچین...',
'p2pDeposit.verifyFailed': 'تأیید ناموفق بود',
'p2pDeposit.retry': 'تلاش مجدد',
// WithdrawModal
'p2pWithdraw.title': 'برداشت از موجودی P2P',
+4
View File
@@ -1253,6 +1253,10 @@ export default {
'p2pDeposit.tokenLabel': 'Token',
'p2pDeposit.amountLabel': 'Miqdar',
'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
'p2pWithdraw.title': 'Ji Hesabê P2P Vekişîne',
+4
View File
@@ -1247,6 +1247,10 @@ export default {
'p2pDeposit.tokenLabel': 'Token',
'p2pDeposit.amountLabel': 'Miktar',
'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
'p2pWithdraw.title': 'P2P Bakiyeden Çekim',