المرحلة 1: روابط دفع قائمة على العقود في EVM
20 أبريل 2026 — المرحلة 1 هي البداية الهندسية الفعلية لـ خارطة الطريق غير الاحتجازية. نستبدل حسابات EOA المشتقة من HD لكل رابط وحسابات Alchemy الذكية بنمط مصنع + مستقبِل كنسخة مصغّرة على EVM. تُقسَّم الأموال ذريًا في معاملة الدفع نفسها؛ دون كنس ودون حفظ.
باختصار
- تعيد
PaymentLinkFactory.computeAddress(linkId)عنوان CREATE2 حتميًا. نعرض هذا العنوان على الدافع قبل نشر أي شيء. PaymentLinkReceiverنسخة مصغّرة EIP-1167. يقبل العملة الأصلية أو ERC-20، ويصدرPaid، ويستدعيFeeSplitter.split()في المعاملة نفسها.- يتوقف api/src/payments/wallet.service.ts عن إنشاء محافظ HD لروابط EVM؛ ويستدعي دالة العرض في المصنع بدلًا من ذلك.
- يُتجاوز مسار الكنس (
drainSmartAccountWalletوgetSmartAccountClientForDepositWallet) للروابط المنشأة تحت العلمuseOnChainReceiver.
ما الذي تغيّره هذه المرحلة
في جهة «بعد» لا يوجد أي صندوق نتحكم به يحتفظ بالأموال.
العقود الجديدة
كلها ضمن حزمة جديدة contracts/evm/.
PaymentLinkFactory
computeAddress(bytes32 linkId) view returns (address)— عنوان CREATE2 حتمي مشتق منlinkId(معرّف UUID الحالي لرابط الدفع بعد تجزئته إلىbytes32).deployAndPay(bytes32 linkId, Split split, address token, uint256 amount)— نشر كسول + دفع في معاملة واحدة. ينسخ المصنع تطبيقPaymentLinkReceiver، ويهيئه بالـSplit، ويمرر الأموال.isDeployed(bytes32 linkId) view returns (bool)— دالة مساعدة يستخدمها المفهرس والواجهة.- قابل للترقية خلف proxy من نوع UUPS، يملكه التوقيع المتعدد للنشر (Safe ثلاثة من خمسة)، مع قفل زمني 48 ساعة على
upgradeTo.
PaymentLinkReceiver (نسخة)
- غير قابل للتغيير بعد التهيئة. يخزّن struct الـ
SplitوعنوانFeeSplitter. pay(address token, uint256 amount)— يتعامل مع العملة الأصلية وERC-20 في نقطة دخول واحدة. يستخدمSafeERC20لتعمل الرموز غير القياسية (USDT). يصدرPaid(linkId, payer, token, amount).- يستدعي
FeeSplitter.split()ذريًا. إذا فشل التقسيم (revert) يفشل الدفع. - دالة
refund(bytes32 txId)صريحة لا يستدعيها إلا حساب EOA الخاص بـ keeper التاجر أو التوقيع المتعدد للنشر.
FeeSplitter
split(Split s, address token, uint256 amount)— يوزّع بنقاط الأساس. يذهب الـ wei المتبقي إلى التاجر (دون فقدان الفتات).- يصدر
Split(txId, merchant, platform, referral, bridgeFee)ليتطابق المفهرس مع حمولات webhook القديمة.
تغييرات API
أكبر تغيير في الكود موجود في api/src/payments/wallet.service.ts. اليوم، تشتق getOrCreateAddressForPaymentLink من hd_wallet_seed، وتحسب عنوان Alchemy الافتراضي (counterfactual)، وتخزنه في wallet_address. بعد المرحلة 1، ولشبكات EVM التي يكون العلم مفعّلًا لها، تصبح الدالة:
async function getOrCreateAddressForPaymentLink(paymentLinkId, network) {
if (isEvm(network) && featureFlags.useOnChainReceiver(network.chainId)) {
const address = await factory.computeAddress(hashLinkId(paymentLinkId))
return { address, walletAddressId: null, derivationPath: null }
}
// legacy path unchanged
}
لا اشتقاق HD. ولا أي بيانات سرية في قاعدة البيانات للروابط الجديدة.
تشترك خدمة جديدة، payments/indexer/evm-paid-event.service.ts، في أحداث Paid على كل شبكة مفعّلة عبر viem.watchContractEvent. عند التطابق:
- تستخرج
paymentLinkIdمن الحدث (مفهرس بصيغةbytes32). - تُدرج أو تحدّث صف
PaymentTransactionبالشكل نفسه المستخدم اليوم. - تصدر الـ webhooks نفسها التي يصدرها مسار اكتمال الكنس اليوم (api/src/webhooks/webhooks.service.ts).
العلم useOnChainReceiver محدد لكل chain id. الرابط المنشأ أثناء إيقاف العلم يبقى على مسار الكنس القديم إلى الأبد؛ لا ننقل الروابط الجارية.
الإيقافات
في هذه المرحلة نتجاوز دون أن نحذف:
SmartAccountWalletService.drainSmartAccountWallet— لم يعد يُستدعى للشبكات المفعّل لها العلم.SmartAccountWalletService.getSmartAccountClientForDepositWallet— غير مطلوب عندما تأتي العناوين من المصنع.- صفوف
wallet_address— لا تُكتب لشبكات EVM المفعّل لها العلم.
يحدث الحذف الفعلي في المرحلة 7، بعد تسوية كل الروابط القديمة على الشبكة.
خطة الترحيل
- الروابط الجديدة على Base: تحصل على عنوان CREATE2 يحسبه المصنع. تعرضه الواجهة كما هو الحال اليوم.
- الروابط المفتوحة الحالية على Base: تبقى على مسار الكنس القديم حتى تنتهي صلاحيتها أو تُسوّى. لا ترحيل إجباري.
- شبكات EVM الأخرى: على المسار القديم حتى تفعّل المرحلة 3 العلم لها.
المخاطر ووسائل الحد منها
- عرض عنوان CREATE2 قبل وجود أي كود. الحل: توثيق أن إمكانية الاسترداد تعتمد على ناشر المصنع. ووضع سقف
maxLinkValueفي المصنع خلال أول 30 يومًا. - USDT ورموز ERC-20 غير القياسية الأخرى. الحل:
SafeERC20في كل منPaymentLinkReceiverوFeeSplitter؛ واختبارات fuzz في Foundry على نسخة (fork) من الشبكة الرئيسية مع USDT. - الرموز التي تقتطع رسومًا عند التحويل. الحل: قائمة رموز مسموح بها؛ والرموز خارجها تعود إلى المسار القديم.
- تأخر مفهرس الشبكة. الحل: اشتراك مزدوج (Alchemy + RPC عام احتياطي)؛ ومهام مطابقة كل ساعة؛ وإدراج/تحديث idempotent.
- ملاحظة تدقيق أثناء التطوير. الحل: التدقيق شرط مسبق؛ لا نشر على الشبكة الرئيسية قبل تقرير إصلاح كل الملاحظات.
قائمة الحالة
- إنشاء مشروع Foundry في
contracts/evm/ - تنفيذ
PaymentLinkFactory+PaymentLinkReceiver+FeeSplitter - اختبارات fuzz + invariant لحسابات التقسيم (يحصل التاجر دائمًا على ≥ المتوقع ناقص الرسوم)
- نجاح اختبارات الـ fork لـ USDT / ERC-20 غير القياسية
- ربط العلم
useOnChainReceiverفيapi/src/payments - إطلاق المفهرس
evm-paid-event.service.tsوإصداره للـ webhooks الحالية - بدء التدقيق ودمج الإصلاحات
- النشر على Base mainnet من التوقيع المتعدد
- مجموعة تجار تجريبية (canary) مع تفعيل العلم
ما التالي
مع انتقال تجار Base إلى المستقبِلات القائمة على العقود، تفعل المرحلة 2 الشيء نفسه للاشتراكات: اشتراكات بسحب الـ allowance.
التوثيق المرجعي: حالة المرحلة 1 · مرجع العقود · البنية
