From 4686453df76d16a70a3b6827044a80f37bd5dd26 Mon Sep 17 00:00:00 2001 From: Kurdistan Tech Ministry Date: Thu, 26 Feb 2026 21:53:41 +0300 Subject: [PATCH] feat: automate deposit flow + fix listUsers pagination - Rewrite DepositWithdrawModal to send TX automatically via assetHubApi instead of manual copy-paste-hash flow - Fix listUsers pagination bug (default 50) in 4 edge functions by adding perPage: 1000 - fixes P2P offers not showing for users - Add new i18n keys for automated deposit states in all 6 languages --- src/components/p2p/DepositWithdrawModal.tsx | 182 ++++++++---------- src/i18n/translations/ar.ts | 7 +- src/i18n/translations/ckb.ts | 7 +- src/i18n/translations/en.ts | 7 +- src/i18n/translations/fa.ts | 7 +- src/i18n/translations/krd.ts | 7 +- src/i18n/translations/tr.ts | 7 +- src/i18n/types.ts | 4 + .../functions/get-internal-balance/index.ts | 2 +- supabase/functions/get-p2p-offers/index.ts | 2 +- .../request-withdraw-telegram/index.ts | 2 +- .../verify-deposit-telegram/index.ts | 2 +- 12 files changed, 122 insertions(+), 114 deletions(-) diff --git a/src/components/p2p/DepositWithdrawModal.tsx b/src/components/p2p/DepositWithdrawModal.tsx index cddeb76..ddb0470 100644 --- a/src/components/p2p/DepositWithdrawModal.tsx +++ b/src/components/p2p/DepositWithdrawModal.tsx @@ -1,13 +1,12 @@ -import { useState, useCallback } from 'react'; +import { useState } from 'react'; import { X, - Copy, - Check, ArrowDownToLine, ArrowUpFromLine, Loader2, AlertCircle, CheckCircle2, + Wallet, } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useAuth } from '@/contexts/AuthContext'; @@ -36,7 +35,7 @@ interface DepositWithdrawModalProps { onSuccess?: () => void; } -type DepositStep = 'form' | 'verifying' | 'success' | 'error'; +type DepositStep = 'form' | 'sending' | 'verifying' | 'success' | 'error'; export function DepositWithdrawModal({ isOpen, @@ -46,7 +45,7 @@ export function DepositWithdrawModal({ onSuccess, }: DepositWithdrawModalProps) { const { sessionToken } = useAuth(); - const { address } = useWallet(); + const { address, isConnected, keypair, assetHubApi } = useWallet(); const { t, isRTL } = useTranslation(); const { hapticImpact, hapticNotification } = useTelegram(); @@ -55,14 +54,11 @@ export function DepositWithdrawModal({ // Deposit state const [depositToken, setDepositToken] = useState<'HEZ' | 'PEZ'>('HEZ'); const [depositAmount, setDepositAmount] = useState(''); - const [txHash, setTxHash] = useState(''); - const [blockNumber, setBlockNumber] = useState(''); const [depositStep, setDepositStep] = useState('form'); const [depositError, setDepositError] = useState(''); const [depositResult, setDepositResult] = useState<{ amount: number; token: string } | null>( null ); - const [copied, setCopied] = useState(false); // Withdraw state const [withdrawToken, setWithdrawToken] = useState<'HEZ' | 'PEZ'>('HEZ'); @@ -72,28 +68,10 @@ export function DepositWithdrawModal({ const [withdrawError, setWithdrawError] = useState(''); const [withdrawSuccess, setWithdrawSuccess] = useState(false); - const handleCopyAddress = useCallback(async () => { - try { - await navigator.clipboard.writeText(PLATFORM_WALLET); - setCopied(true); - hapticNotification('success'); - setTimeout(() => setCopied(false), 2000); - } catch { - // Fallback for environments where clipboard API is not available - const textArea = document.createElement('textarea'); - textArea.value = PLATFORM_WALLET; - document.body.appendChild(textArea); - textArea.select(); - document.execCommand('copy'); - document.body.removeChild(textArea); - setCopied(true); - hapticNotification('success'); - setTimeout(() => setCopied(false), 2000); - } - }, [hapticNotification]); + const canDeposit = isConnected && !!keypair && !!assetHubApi; - const handleVerifyDeposit = async () => { - if (!sessionToken || !txHash || !depositAmount) return; + const handleDeposit = async () => { + if (!sessionToken || !depositAmount || !assetHubApi || !keypair) return; const amount = parseFloat(depositAmount); if (isNaN(amount) || amount <= 0) { @@ -102,17 +80,54 @@ export function DepositWithdrawModal({ } hapticImpact('medium'); - setDepositStep('verifying'); + setDepositStep('sending'); setDepositError(''); try { - const result = await verifyDeposit( - sessionToken, - txHash.trim(), - depositToken, - amount, - blockNumber ? parseInt(blockNumber) : undefined - ); + const amountPlanck = BigInt(Math.floor(amount * 1e12)); + + // Create transaction on Asset Hub + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let tx: any; + if (depositToken === 'HEZ') { + // Native token transfer on Asset Hub + // eslint-disable-next-line @typescript-eslint/no-explicit-any + tx = (assetHubApi.tx.balances as any).transferKeepAlive( + PLATFORM_WALLET, + amountPlanck.toString() + ); + } else { + // PEZ = asset ID 1 on Asset Hub + // eslint-disable-next-line @typescript-eslint/no-explicit-any + tx = (assetHubApi.tx.assets as any).transfer(1, PLATFORM_WALLET, amountPlanck.toString()); + } + + // Send TX and wait for finalization + const txHash = await new Promise((resolve, reject) => { + tx.signAndSend( + keypair, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (result: any) => { + if (result.dispatchError) { + if (result.dispatchError.isModule) { + const decoded = assetHubApi!.registry.findMetaError(result.dispatchError.asModule); + reject(new Error(`${decoded.section}.${decoded.name}`)); + } else { + reject(new Error(result.dispatchError.toString())); + } + return; + } + if (result.status.isFinalized) { + resolve(result.txHash.toHex()); + } + } + ).catch(reject); + }); + + // TX finalized, now verify deposit on backend + setDepositStep('verifying'); + + const result = await verifyDeposit(sessionToken, txHash, depositToken, amount); setDepositResult({ amount: result.amount, token: result.token }); setDepositStep('success'); @@ -168,8 +183,6 @@ export function DepositWithdrawModal({ setDepositStep('form'); setDepositError(''); setDepositResult(null); - setTxHash(''); - setBlockNumber(''); setDepositAmount(''); }; @@ -247,35 +260,23 @@ export function DepositWithdrawModal({ {/* ── Deposit Tab ── */} {activeTab === 'deposit' && ( <> - {depositStep === 'form' && ( + {!canDeposit ? ( +
+ +

+ {t('p2p.walletNotConnected')} +

+

+ {t('p2p.connectWalletFirst')} +

+
+ ) : depositStep === 'form' ? ( <> {/* Instructions */}

{t('p2p.depositInstructions')}

- {/* Platform wallet address */} -
- -
- - {PLATFORM_WALLET} - - -
-
- {/* Token select */}
- {/* TX Hash */} -
- - setTxHash(e.target.value)} - placeholder={t('p2p.txHashPlaceholder')} - className="w-full bg-muted rounded-lg px-3 py-2.5 text-xs text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-cyan-500 font-mono" - /> -
- - {/* Block Number (optional) */} -
- - setBlockNumber(e.target.value)} - placeholder="e.g. 1234567" - className="w-full bg-muted rounded-lg px-3 py-2.5 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-cyan-500" - /> -
- - {/* Verify button */} + {/* Deposit button */} - )} - - {depositStep === 'verifying' && ( + ) : depositStep === 'sending' ? ( +
+ +

{t('p2p.depositSending')}

+

+ {t('p2p.depositSendingDesc')} +

+
+ ) : depositStep === 'verifying' ? (

{t('p2p.verifying')}

@@ -362,9 +340,7 @@ export function DepositWithdrawModal({ {t('p2p.verifyingDesc')}

- )} - - {depositStep === 'success' && depositResult && ( + ) : depositStep === 'success' && depositResult ? (

{t('p2p.depositSuccess')}

@@ -378,9 +354,7 @@ export function DepositWithdrawModal({ {t('common.close')}
- )} - - {depositStep === 'error' && ( + ) : (

{t('p2p.depositFailed')}

diff --git a/src/i18n/translations/ar.ts b/src/i18n/translations/ar.ts index 96eef43..3bf8f3a 100644 --- a/src/i18n/translations/ar.ts +++ b/src/i18n/translations/ar.ts @@ -408,8 +408,13 @@ const ar: Translations = { verifyingDesc: 'يتم فحص المعاملة على السلسلة. قد يستغرق هذا حتى ٦٠ ثانية.', depositSuccess: 'تم الإيداع بنجاح!', depositFailed: 'فشل الإيداع', - depositInstructions: 'أرسل التوكنات إلى عنوان محفظة المنصة أدناه، ثم الصق هاش المعاملة للتحقق.', + depositInstructions: + 'اختر توكن وأدخل المبلغ للإيداع في رصيد P2P الخاص بك. سيتم إرسال المعاملة من محفظتك تلقائياً.', depositInvalidAmount: 'يرجى إدخال مبلغ صالح', + depositSending: 'جاري إرسال المعاملة...', + depositSendingDesc: 'يتم توقيع وإرسال إيداعك إلى Asset Hub. قد يستغرق هذا حتى 30 ثانية.', + walletNotConnected: 'المحفظة غير متصلة', + connectWalletFirst: 'يرجى فتح قفل محفظتك للإيداع.', selectToken: 'اختر التوكن', withdrawAmount: 'مبلغ السحب', networkFee: 'رسوم الشبكة', diff --git a/src/i18n/translations/ckb.ts b/src/i18n/translations/ckb.ts index 080b312..591decd 100644 --- a/src/i18n/translations/ckb.ts +++ b/src/i18n/translations/ckb.ts @@ -411,8 +411,13 @@ const ckb: Translations = { depositSuccess: 'پارەدانان سەرکەوتوو بوو!', depositFailed: 'پارەدانان سەرنەکەوت', depositInstructions: - 'تۆکن بنێرە بۆ ناونیشانی جزدانی پلاتفۆرمی خوارەوە، پاشان هاشی مامەڵە بلکێنە بۆ پشتڕاستکردنەوە.', + 'تۆکنێک هەڵبژێرە و بڕەکە بنووسە بۆ زیادکردن لە باڵانسی P2P. مامەڵەکە بە شێوەیەکی خۆکار لە جزدانەکەت دەنێردرێت.', depositInvalidAmount: 'تکایە بڕێکی دروست بنووسە', + depositSending: 'مامەڵە دەنێردرێت...', + depositSendingDesc: + 'پارەدانانەکەت لە Asset Hub واژووکراوە و دەنێردرێت. ئەمە لەوانەیە تا ٣٠ چرکە بخایەنێت.', + walletNotConnected: 'جزدان پەیوەندی نەکراوە', + connectWalletFirst: 'تکایە بۆ پارەدانان قفڵی جزدانەکەت بکەرەوە.', selectToken: 'تۆکن هەڵبژێرە', withdrawAmount: 'بڕی دەرهێنان', networkFee: 'کرێی تۆڕ', diff --git a/src/i18n/translations/en.ts b/src/i18n/translations/en.ts index 2399513..eff068d 100644 --- a/src/i18n/translations/en.ts +++ b/src/i18n/translations/en.ts @@ -410,8 +410,13 @@ const en: Translations = { depositSuccess: 'Deposit Successful!', depositFailed: 'Deposit Failed', depositInstructions: - 'Send tokens to the platform wallet address below, then paste the transaction hash to verify.', + 'Select a token and enter the amount to deposit to your P2P balance. The transaction will be sent from your wallet automatically.', depositInvalidAmount: 'Please enter a valid amount', + depositSending: 'Sending transaction...', + depositSendingDesc: + 'Signing and sending your deposit to Asset Hub. This may take up to 30 seconds.', + walletNotConnected: 'Wallet not connected', + connectWalletFirst: 'Please unlock your wallet to make deposits.', selectToken: 'Select Token', withdrawAmount: 'Withdrawal Amount', networkFee: 'Network Fee', diff --git a/src/i18n/translations/fa.ts b/src/i18n/translations/fa.ts index 7563158..236eb04 100644 --- a/src/i18n/translations/fa.ts +++ b/src/i18n/translations/fa.ts @@ -410,8 +410,13 @@ const fa: Translations = { depositSuccess: 'واریز موفق!', depositFailed: 'واریز ناموفق', depositInstructions: - 'توکن‌ها را به آدرس کیف پول پلتفرم زیر ارسال کنید، سپس هش تراکنش را برای تایید جایگذاری کنید.', + 'یک توکن انتخاب کنید و مقدار را وارد کنید تا به موجودی P2P شما اضافه شود. تراکنش به صورت خودکار از کیف پول شما ارسال خواهد شد.', depositInvalidAmount: 'لطفا مقدار معتبری وارد کنید', + depositSending: 'در حال ارسال تراکنش...', + depositSendingDesc: + 'واریز شما در حال امضا و ارسال به Asset Hub است. این عملیات ممکن است تا ۳۰ ثانیه طول بکشد.', + walletNotConnected: 'کیف پول متصل نیست', + connectWalletFirst: 'لطفا برای واریز، قفل کیف پول خود را باز کنید.', selectToken: 'انتخاب توکن', withdrawAmount: 'مقدار برداشت', networkFee: 'کارمزد شبکه', diff --git a/src/i18n/translations/krd.ts b/src/i18n/translations/krd.ts index 0d82b0c..397d9ca 100644 --- a/src/i18n/translations/krd.ts +++ b/src/i18n/translations/krd.ts @@ -423,8 +423,13 @@ const krd: Translations = { depositSuccess: 'Depo Serkeftî!', depositFailed: 'Depo Serneket', depositInstructions: - 'Token bişîne navnîşana cûzdanê ya platformê ya jêrîn, paşê hash ya danûstandinê bişkoje ji bo verastkirin.', + 'Ji bo ku hûn li balanseya P2P-ê ya xwe depo bikin, tokenek hilbijêrin û mîqdarê binivîsin. Danûstandin dê ji cûzdanê we bixweber were şandin.', depositInvalidAmount: 'Ji kerema xwe mîqdarek derbasdar binivîsin', + depositSending: 'Danûstandin tê şandin...', + depositSendingDesc: + 'Depoya we li Asset Hub tê îmzekirin û şandin. Ev dibe ku heta 30 saniyeyan bigire.', + walletNotConnected: 'Cûzdan ve negirêdayî ye', + connectWalletFirst: 'Ji bo depokirinê, ji kerema xwe kilîda cûzdanê vekin.', selectToken: 'Token Hilbijêrin', withdrawAmount: 'Mîqdara Derxistinê', networkFee: 'Heqê Torê', diff --git a/src/i18n/translations/tr.ts b/src/i18n/translations/tr.ts index 16daf85..39637a5 100644 --- a/src/i18n/translations/tr.ts +++ b/src/i18n/translations/tr.ts @@ -410,8 +410,13 @@ const tr: Translations = { depositSuccess: 'Yatırma Başarılı!', depositFailed: 'Yatırma Başarısız', depositInstructions: - 'Aşağıdaki platform cüzdan adresine token gönderin, ardından doğrulamak için işlem hash yapıştırın.', + 'P2P bakiyenize yatırmak için bir token seçin ve miktarı girin. İşlem cüzdanınızdan otomatik olarak gönderilecektir.', depositInvalidAmount: 'Geçerli bir miktar girin', + depositSending: 'İşlem gönderiliyor...', + depositSendingDesc: + "Yatırma işleminiz Asset Hub'a imzalanıp gönderiliyor. Bu işlem 30 saniyeye kadar sürebilir.", + walletNotConnected: 'Cüzdan bağlı değil', + connectWalletFirst: 'Yatırma yapmak için cüzdanınızın kilidini açın.', selectToken: 'Token Seçin', withdrawAmount: 'Çekim Miktarı', networkFee: 'Ağ Ücreti', diff --git a/src/i18n/types.ts b/src/i18n/types.ts index 7fd1ee0..c45a32a 100644 --- a/src/i18n/types.ts +++ b/src/i18n/types.ts @@ -421,6 +421,10 @@ export interface Translations { depositFailed: string; depositInstructions: string; depositInvalidAmount: string; + depositSending: string; + depositSendingDesc: string; + walletNotConnected: string; + connectWalletFirst: string; selectToken: string; withdrawAmount: string; networkFee: string; diff --git a/supabase/functions/get-internal-balance/index.ts b/supabase/functions/get-internal-balance/index.ts index afb7fd9..240096d 100644 --- a/supabase/functions/get-internal-balance/index.ts +++ b/supabase/functions/get-internal-balance/index.ts @@ -126,7 +126,7 @@ serve(async (req) => { const telegramEmail = `telegram_${telegramId}@pezkuwichain.io`; const { data: { users: authUsers }, - } = await supabase.auth.admin.listUsers(); + } = await supabase.auth.admin.listUsers({ perPage: 1000 }); const authUser = authUsers?.find((u) => u.email === telegramEmail); if (!authUser) { diff --git a/supabase/functions/get-p2p-offers/index.ts b/supabase/functions/get-p2p-offers/index.ts index 923f694..0a32edf 100644 --- a/supabase/functions/get-p2p-offers/index.ts +++ b/supabase/functions/get-p2p-offers/index.ts @@ -138,7 +138,7 @@ serve(async (req) => { const telegramEmail = `telegram_${telegramId}@pezkuwichain.io`; const { data: { users: authUsers }, - } = await supabase.auth.admin.listUsers(); + } = await supabase.auth.admin.listUsers({ perPage: 1000 }); const authUser = authUsers?.find((u) => u.email === telegramEmail); if (!authUser) { diff --git a/supabase/functions/request-withdraw-telegram/index.ts b/supabase/functions/request-withdraw-telegram/index.ts index f7ab939..5a7940b 100644 --- a/supabase/functions/request-withdraw-telegram/index.ts +++ b/supabase/functions/request-withdraw-telegram/index.ts @@ -148,7 +148,7 @@ serve(async (req) => { const telegramEmail = `telegram_${telegramId}@pezkuwichain.io`; const { data: { users: existingUsers }, - } = await serviceClient.auth.admin.listUsers(); + } = await serviceClient.auth.admin.listUsers({ perPage: 1000 }); const authUser = existingUsers?.find((u) => u.email === telegramEmail); if (!authUser) { diff --git a/supabase/functions/verify-deposit-telegram/index.ts b/supabase/functions/verify-deposit-telegram/index.ts index 369b448..5d80b19 100644 --- a/supabase/functions/verify-deposit-telegram/index.ts +++ b/supabase/functions/verify-deposit-telegram/index.ts @@ -355,7 +355,7 @@ serve(async (req) => { // Try to get existing auth user const { data: { users: existingUsers }, - } = await serviceClient.auth.admin.listUsers(); + } = await serviceClient.auth.admin.listUsers({ perPage: 1000 }); let authUser = existingUsers?.find((u) => u.email === telegramEmail); if (!authUser) {