المرحلة 4: سولانا غير احتجازية مع PDAs من Anchor
20 أبريل 2026 — تجلب المرحلة 4 النموذج غير الاحتجازي إلى سولانا. يحل برنامجان من Anchor محل تدفق محفظة HD + الكنس لكل رابط: payment_link للمدفوعات الفردية وsubscription للخصم التلقائي عبر delegate في SPL. لا توجد أي عبارة استرداد (mnemonic) على خوادمنا لروابط سولانا الجديدة.
باختصار
- يملك برنامج
payment_linkحساب PDA لكل رابط دفع، مشتقًا منseeds = [b"pl", link_id]. لا keypair، ولا seed، ولا كنس. - يستخدم برنامج
subscriptionصلاحية delegate في SPL، وهي المكوّن نفسه الذي نعتمد عليه أصلًا في الخصم التلقائي، كـ allowance صريح يمنحه الدافع. - يتم التقسيم عبر CPI إلى منطق
fee_splitterنفسه الذي بنيناه على EVM، بعد تكييفه مع حسابات سولانا. - تُتجاوز خدمة الكنس api/src/withdrawals/services/solana-withdrawal-transfer.service.ts للروابط المفعّل لها العلم، وتُحذف في المرحلة 7.
ما الذي تغيّره هذه المرحلة
تستبدل جهة «بعد» الـ seed من نوع HD في api/src/payments/wallet.service.ts (الفرع m/44'/501'/i'/0') بحساب PDA حتمي بالكامل.
البرامج الجديدة
حزمة جديدة contracts/solana/ (Anchor).
برنامج payment_link
الحسابات لكل رابط:
#[account]
pub struct PaymentLink {
pub link_id: [u8; 32],
pub expected_mint: Pubkey, // e.g. USDC on SOL
pub expected_amount: u64,
pub split: Split,
pub created_at: i64,
pub status: PaymentStatus,
}
التعليمات:
initialize(link_id, expected_mint, expected_amount, split)— كسولة؛ يستدعيها الغلاف المشابه للمصنع عند أول إيداع.pay(link_id, amount)— تتحقق من الـ mint والمبلغ، وتحوّل عبر CPI إلى ATA الخاص بالـ PDA، ثم تقسّم عبر CPI بشكل ذري. تصدرPaymentReceived { link_id, payer, amount }.refund(link_id)— تتطلب التوقيع المتعدد، وتحاكي الاسترداد في EVM.
بذور الـ PDA: [b"pl", link_id]. لا يملك الـ PDA أي مفتاح خاص؛ بل يوقّع عبر invoke_signed.
برنامج subscription
يستفيد من Approve / Revoke في SPL لتفويض الصلاحية. يوقّع الدافع spl_token::approve محددًا PDA البرنامج كمفوَّض بحد أقصى cap من الرموز. ثم:
pub fn charge(ctx: Context<Charge>, id: [u8; 32]) -> Result<()>
يمكن لأي طرف استدعاؤها، وتتحقق من:
clock.unix_timestamp >= next_charge_atclock.unix_timestamp > last_charged_at// monotonicamount_charged + period_amount <= cap- استمرار وجود صلاحية التفويض على ATA الخاص بالدافع
عند النجاح، يسحب spl_token::transfer_checked عبر CPI من ATA الدافع (بصفة المفوَّض) ثم يقسّم عبر CPI.
الأحداث: SubscriptionCreated وCharged وChargeSkipped وCanceled. بالأشكال نفسها التي ينتجها مفهرس EVM، لذا فإن api/src/webhooks لا يعتمد على الشبكة.
تغييرات API
- يتوقف فرع سولانا في
WalletService.generateAddressForNetworkعن اشتقاق keypairs ويعيد بدلًا من ذلكfindProgramAddressSync(["pl", linkId], programId). لا يُكتب أي صفencryptedSeedلروابط سولانا الجديدة. - تراقب خدمة مفهرس جديدة سجلات البرنامج (webhooks من Helius أو بث Triton) وتصدر أحداث الـ webhook الحالية.
- يضيف api/src/scheduler/processors/subscription-auto-charge.processor.ts فرعًا لـ SOL: يبني معاملة
chargeويوقّعها بالـ keeper (مسار دون غاز عبر معاملة مدعومة، على غرار الـ paymaster في المرحلة 2). - لا تُستدعى
withdrawals/services/solana-withdrawal-transfer.service.tsللروابط المفعّل لها العلم.
الغاز على سولانا
رسوم سولانا صغيرة (عادةً 5000 lamport لكل توقيع، أي 0.000005 SOL). يستخدم الـ keeper رصيد SOL ساخنًا بنمط التنبيه نفسه المستخدم في المرحلة 3. ولأن سولانا لا تملك paymaster على غرار Alchemy، تستخدم المعاملات المدعومة نمطًا بسيطًا للتوقيع المشترك: يوقّع الـ keeper بصفته دافع الرسوم، ولا يحتاج الدافع إلى أي SOL.
الإيقافات
- اشتقاق HD لـ SOL (
m/44'/501'/i'/0') للروابط الجديدة — لا يُستدعى عند تفعيل العلم. solana-withdrawal-transfer.service.ts— يُتجاوز للروابط المفعّل لها العلم.- يبقى
SolanaRelayBridgeExecutorServiceفي api/src/withdrawals/services/bridge/solana-relay-bridge-executor.service.ts: فالنقل من SOL إلى EVM عندما يكون التاجر على EVM ما زال يستخدم Relay، لكن المشغّل ينتقل إلى البرنامج.
خطة الترحيل
نموذج الاشتراك الاختياري نفسه في المرحلة 2:
- روابط الدفع الجديدة: معنونة عبر PDA.
- الاشتراكات الجديدة: قائمة على delegate في SPL.
- الاشتراكات النشطة الحالية: شريط + بريد إلكتروني عند التجديد التالي لإعادة التفويض للبرنامج الجديد.
- تستمر تسوية الإيداعات الجارية على العناوين القديمة عبر المسار القديم.
المخاطر ووسائل الحد منها
- إمكانية تجاوز الـ delegate في SPL. الحل: تعيد
chargeالتحقق منdelegated_amountقبل التحويل؛ وعند الفشل تصدرChargeSkipped(DelegateRevoked)وتتبع قالب البريد نفسه المستخدم في حالة إلغاء الـ allowance على EVM. - صلاحية ترقية Anchor. الحل: يملك توقيع متعدد Squads ثلاثة من خمسة صلاحية الترقية، مع قفل زمني. بما يطابق موقف Safe على EVM من المرحلة 0.
- SOL المغلّف مقابل SOL الأصلي. الحل: يدعم
payment_linkكليهما، معexpected_mintصريح لكل رابط لتجنب الالتباس. - ارتفاعات رسوم الأولوية. الحل: حساب ديناميكي لرسوم الأولوية في الـ keeper؛ وإعادة محاولة احتياطية بأولوية أعلى بعد slot واحد.
- التدقيق. الحل: تدقيق البرنامجين من OtterSec أو Neodyme قبل النشر على الشبكة الرئيسية.
قائمة الحالة
- إنشاء مساحة عمل Anchor في
contracts/solana/ - برنامج
payment_linkبكل التعليمات + الاختبارات - برنامج
subscriptionبكل التعليمات + الاختبارات - إطلاق مفهرس webhooks من Helius
- فرع SOL للـ keeper في
subscription-auto-charge.processor.ts - نشر تقرير التدقيق
- النشر على الشبكة الرئيسية من التوقيع المتعدد Squads
- مجموعة تجار تجريبية
ما التالي
بيتكوين. أصعب شبكة لتحقيق عدم الاحتجاز، والشبكة التي يجب أن نكون فيها صريحين بشأن الحدود: المرحلة 5.
التوثيق المرجعي: حالة المرحلة 4 · مرجع العقود · البنية
