مرحله ۲: اشتراک مبتنی بر allowance
۲۰ آوریل ۲۰۲۶ — مرحله ۲ صورتحساب دورهای را به یک SubscriptionHub روی زنجیره منتقل میکند. پرداختکننده یک approve در کیف پولش امضا میکند و وقتی دورهای سررسید شود، هر کسی (از جمله keeper ما) میتواند charge(id) را فراخوانی کند. وجوه مستقیماً از پرداختکننده به سهمهای فروشنده میرود؛ OrcaRail هرگز آنها را نگه نمیدارد.
خلاصه
SubscriptionHub.createSubscription(...)زمانبندی را روی زنجیره ثبت میکند.- پرداختکننده یک بار در کیف پول معمولش
approve(USDC, hub, cap)را امضا میکند. این همان گامی است که پیشتر در برداشت خودکار مستند کردهایم. - هر EOAی میتواند
charge(id)را فراخوانی کند. زمانبند NestJS ما بهطور پیشفرض این کار را میکند؛ از مرحله ۳ به بعد Chainlink/Gelato پشتیبان آن خواهند بود. - محافظت در برابر reorg با
lastChargedAtیکنوا. بدون برداشت دوباره. - مشترکان فعلی بهصورت اختیاری و هنگام تمدید مهاجرت میکنند.
این مرحله چه چیزی را تغییر میدهد
امروز api/src/scheduler/processors/subscription-auto-charge.processor.ts از یک smart account تحت کنترل OrcaRail استفاده میکند تا از طریق approve موجود از پرداختکننده برداشت کند. کار میکند، اما smart account متعلق به ماست؛ اعتماد از مسیر ما عبور میکند.
پس از مرحله ۲:
allowance همچنان از پرداختکننده به یک قرارداد داده میشود، اما قرارداد قابل حسابرسی و تغییرناپذیر است و هر کسی (نه فقط ما) میتواند keeper باشد.
قراردادهای معرفیشده
SubscriptionHub
یک نمونه برای هر زنجیره. سطح حداقلی:
function createSubscription(
bytes32 id,
address payer,
IERC20 token,
uint256 amount,
uint64 interval,
uint256 cap,
Split calldata split
) external;
function charge(bytes32 id) external; // any caller
function cancel(bytes32 id) external; // payer only
function subscription(bytes32 id) view returns (Subscription memory);
event SubscriptionCreated(bytes32 indexed id, address indexed payer, ...);
event Charged(bytes32 indexed id, uint256 amount, uint64 nextChargeAt);
event ChargeSkipped(bytes32 indexed id, bytes32 reason);
event Canceled(bytes32 indexed id);
اصول ثابتی که charge() اعمال میکند
block.timestamp >= nextChargeAt— برداشت زودهنگام ممکن نیست.block.timestamp > lastChargedAt— یکنوا، تا reorgها نتوانند دوبار برداشت کنند.amountCharged + amount <= cap— سقف تأیید را علاوه بر allowance در ERC-20 اعمال میکند.token.allowance(payer, hub) >= amount— revert صریح باInsufficientAllowanceبرای متن وبهوک روشن.token.balanceOf(payer) >= amount— همینطور، باInsufficientBalance.
Split از مرحله ۱ دوباره استفاده میشود؛ همان FeeSplitter و همان منطق واحد پایه.
تغییرات API
تغییر بزرگ این است که api/src/scheduler/processors/subscription-auto-charge.processor.ts دیگر از smart account ما برداشت نمیکند و بهجای آن با یک EOA مربوط به keeper تابع SubscriptionHub.charge(id) را فراخوانی میکند.
بهطور مشخص:
- EOA مربوط به keeper یک کیف پول داغ برای هر زنجیره است که فقط گس دارد. هیچ اختیاری روی وجوه فروشنده ندارد.
- اگر زنجیره سیاست paymaster آلکمی داشته باشد (api/src/withdrawals/services/gas-strategy را ببینید)، keeper از مسیر حمایتشده استفاده میکند؛ در غیر این صورت از موجودی گس خودش میپردازد.
- هنگام revert، پردازشگر selector خطا را تجزیه میکند و همان وضعیتهای SubscriptionAutoChargeEntity را که امروز استفاده میکنیم (
past_due،insufficient_balanceو غیره) بهروزرسانی میکند. - وبهوکها و ایمیلهای
api/src/mail/mail-templates/subscription-*.hbsبدون تغییر ارسال میشوند.
تجربه پرداختکننده در pay دو تنظیم جزئی دارد:
- گام
approve()اکنون spender را بهجای کیف پول داغ ما به آدرسSubscriptionHubنشانه میرود. این یک تغییر متن و یک مقدار پیکربندی است؛ جریانی که پرداختکننده میبیند یکسان است. - دکمه «لغو مجوز» که
SubscriptionHub.cancel(id)را فراخوانی میکند و سپس برای پاکسازی کامل یکapprove(hub, 0)روی زنجیره درخواست میکند. مستندات فعلی لغو برداشت خودکار همچنان دقیق است.
موارد منسوخشده
- مسیر برداشت امانی در
subscription-auto-charge.processor.ts— با پرچم فعال، فقط شاخهSubscriptionHub.chargeاجرا میشود. - smart account متعلق به OrcaRail بهعنوان
approval_spender_address— اشتراکهای جدید بهجای آن آدرس هاب را ذخیره میکنند. - فیلد
approval_spender_addressدر پاسخ API باقی میماند؛ فقط به جای جدید و قابل حسابرسی اشاره میکند.
برنامه مهاجرت
مشترکان فعلی هرگز بهاجبار مهاجرت داده نمیشوند. دو مسیر پیشنهاد میکنیم:
- هنگام تمدید: در دوره بعد، بنر داشبورد از پرداختکننده میخواهد
approve(hub, cap)را رویSubscriptionHubجدید دوباره امضا کند. یک کلیک. allowance قبلی به کیف پول داغ قدیمی را میتوان نگه داشت یا لغو کرد؛ دیگر اهمیتی ندارد. - به ابتکار کاربر: دکمه «انتقال به غیرامانی» در صفحه جزئیات اشتراک همین کار را فوراً انجام میدهد.
اشتراکهایی که هرگز مهاجرت نکنند تا زمان لغو روی مسیر امانی میمانند. این مسیر در مرحله ۷ با اطلاعرسانی کافی خاموش میشود.
ریسکها و راهکارها
- سردرگمی پرداختکننده هنگام مهاجرت. راهکار: قالب ایمیل از زبان
subscription-auto-charge-approved.hbsموجود پیروی میکند؛ بنر در داشبورد؛ پرسش متداول در /docs/non-custodial/migration-guide. - قطعی keeper باعث از دست رفتن برداشت میشود. راهکار:
charge()بدون مجوز است؛ هر نمونه Chainlink Automation یا Gelato میتواند پشتیبان باشد؛ فراخوانی دستیcharge()توسط فروشنده هم ممکن است. برنامه کامل در مرحله ۳. - برداشت دوباره بر اثر reorg. راهکار: اصل ثابت یکنوای
lastChargedAt؛ آزمون واحد با reorg روی فورک. - لغو مجوز توسط پرداختکننده بین دو دوره. راهکار:
charge()رویدادChargeSkipped(AllowanceRevoked)را منتشر میکند؛ قالب ایمیل موجودsubscription-insufficient-allowance.hbsارسال میشود؛ اشتراک بهpast_dueمیرود. - انحراف فهرست مجاز توکنها. راهکار: هاب فقط توکنهایی را میپذیرد که
SubscriptionHub.isAllowed(token)برایشان برقرار است؛ تغییرات نیازمند مولتیسیگ است.
چکلیست وضعیت
- پیادهسازی
SubscriptionHubبا همه اصول ثابت - آزمونهای fuzz برای حالتهای سقف، allowance، reorg و رد شدن
- تکمیل حسابرسی تکمیلی برای
SubscriptionHub - تأمین EOA مربوط به keeper برای هر زنجیره، با پایش گس از طریق Sentry + Datadog
- تغییر مقصد
approveدر اپ پرداخت بهSubscriptionHub - بهروزرسانی اندپوینت
approve-auto-chargeبرای پذیرش allowance با هاب بهعنوان spender - بنر داشبورد برای اشتراکهای فعلی
- مهاجرت گروه آزمایشی و پایداری به مدت ۱۴ روز
قدم بعدی
گسترش به چند زنجیره و keeper دوم: مرحله ۳.
مستندات مرجع: وضعیت مرحله ۲ · راهنمای مهاجرت · مرجع قراردادها · چرا برداشت مبتنی بر allowance
