پرش به مطلب اصلی

مرحله ۴: سولانای غیرامانی با PDAهای Anchor

· خواندن 6 دقیقه
OrcaRail
Crypto payment rails for web2 apps

۲۰ آوریل ۲۰۲۶ — مرحله ۴ مدل غیرامانی را به سولانا می‌آورد. دو برنامه 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 برای لینک‌های دارای پرچم دور زده می‌شود و در مرحله ۷ حذف می‌شود.

این مرحله چه چیزی را تغییر می‌دهد​

سمت «بعد» seed مربوط به HD در api/src/payments/wallet.service.ts (شاخه m/44'/501'/i'/0') را با محاسبه کاملاً قطعی PDA جایگزین می‌کند.

برنامه‌های معرفی‌شده​

پکیج جدید contracts/solana/ (Anchor).

حساب‌ها برای هر لینک:

#[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.

seedهای PDA: [b"pl", link_id]. PDA کلید خصوصی ندارد؛ از طریق invoke_signed امضا می‌کند.

برنامه subscription​

از Approve / Revoke در SPL برای واگذاری اختیار استفاده می‌کند. پرداخت‌کننده spl_token::approve را امضا می‌کند و PDA برنامه را تا سقف cap توکن به‌عنوان delegate تعیین می‌کند. سپس:

pub fn charge(ctx: Context<Charge>, id: [u8; 32]) -> Result<()>

هر کسی می‌تواند فراخوانی‌اش کند؛ این موارد را بررسی می‌کند:

  • clock.unix_timestamp >= next_charge_at
  • clock.unix_timestamp > last_charged_at // monotonic
  • amount_charged + period_amount <= cap
  • اینکه اختیار delegate هنوز روی ATA پرداخت‌کننده وجود دارد

در صورت قبولی، spl_token::transfer_checked با CPI از ATA پرداخت‌کننده (به‌عنوان delegate) برداشت می‌کند و با CPI تقسیم می‌کند.

رویدادها: SubscriptionCreated، Charged، ChargeSkipped، Canceled. همان ساختاری که ایندکسر EVM تولید می‌کند، پس api/src/webhooks به زنجیره وابسته نیست.

تغییرات API​

  • شاخه سولانا در WalletService.generateAddressForNetwork دیگر keypair مشتق نمی‌کند و به‌جای آن findProgramAddressSync(["pl", linkId], programId) را برمی‌گرداند. برای لینک‌های جدید سولانا هیچ ردیف encryptedSeed نوشته نمی‌شود.
  • یک سرویس ایندکسر جدید لاگ‌های برنامه را (با وب‌هوک‌های Helius یا استریم‌های Triton) زیر نظر می‌گیرد و رویدادهای وب‌هوک موجود را منتشر می‌کند.
  • api/src/scheduler/processors/subscription-auto-charge.processor.ts یک شاخه SOL اضافه می‌کند: یک تراکنش charge می‌سازد و با keeper امضا می‌کند (مسیر بدون گس از طریق تراکنش حمایت‌شده، مشابه paymaster مرحله ۲).
  • withdrawals/services/solana-withdrawal-transfer.service.ts برای لینک‌های دارای پرچم فراخوانی نمی‌شود.

گس روی سولانا​

کارمزدهای سولانا ناچیزند (معمولاً ۵۰۰۰ lamport برای هر امضا، یعنی ۰٫۰۰۰۰۰۵ SOL). keeper از موجودی SOL داغ با همان الگوی هشدار مرحله ۳ استفاده می‌کند. چون سولانا paymaster شبیه آلکمی ندارد، تراکنش‌های حمایت‌شده از الگوی ساده هم‌امضاکننده استفاده می‌کنند: 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 باقی می‌ماند: وقتی فروشنده روی EVM است، پل از SOL به EVM همچنان از Relay استفاده می‌کند، اما محرک آن به برنامه منتقل می‌شود.

برنامه مهاجرت​

همان مدل اختیاری مرحله ۲:

  • لینک‌های پرداخت جدید: آدرس‌دهی با PDA.
  • اشتراک‌های جدید: مبتنی بر delegate در SPL.
  • اشتراک‌های فعال فعلی: بنر + ایمیل در تمدید بعدی برای واگذاری دوباره به برنامه جدید.
  • واریزهای در جریان به آدرس‌های قدیمی همچنان روی مسیر قدیمی تسویه می‌شوند.

ریسک‌ها و راهکارها​

  • delegate در SPL قابل بازنویسی است. راهکار: charge پیش از انتقال delegated_amount را دوباره بررسی می‌کند؛ در صورت شکست، ChargeSkipped(DelegateRevoked) منتشر می‌شود و همان قالب ایمیلِ حالت لغو allowance در EVM استفاده می‌شود.
  • اختیار ارتقای Anchor. راهکار: مولتی‌سیگ Squads سه از پنج مالک اختیار ارتقاست، با timelock. مطابق رویکرد Safe در EVM از مرحله ۰.
  • SOL بسته‌بندی‌شده در برابر SOL بومی. راهکار: payment_link از هر دو پشتیبانی می‌کند و برای جلوگیری از ابهام، expected_mint صریح برای هر لینک دارد.
  • جهش کارمزد اولویت. راهکار: محاسبه پویای کارمزد اولویت در keeper؛ تلاش مجدد پشتیبان با اولویت بالاتر پس از یک slot.
  • حسابرسی. راهکار: حسابرسی هر دو برنامه توسط OtterSec یا Neodyme پیش از استقرار روی mainnet.

چک‌لیست وضعیت​

  • ساخت workspace انکر contracts/solana/
  • برنامه payment_link با همه دستورها + آزمون‌ها
  • برنامه subscription با همه دستورها + آزمون‌ها
  • انتشار ایندکسر وب‌هوک Helius
  • شاخه SOL مربوط به keeper در subscription-auto-charge.processor.ts
  • انتشار گزارش حسابرسی
  • استقرار روی mainnet از مولتی‌سیگ Squads
  • گروه آزمایشی فروشندگان

قدم بعدی​

بیت‌کوین. سخت‌ترین زنجیره برای غیرامانی شدن، و جایی که باید درباره محدودیت‌ها صادق باشیم: مرحله ۵.


مستندات مرجع: وضعیت مرحله ۴ · مرجع قراردادها · معماری