مرحله ۳: گسترش به چند زنجیره EVM و افزونگی keeper
۲۰ آوریل ۲۰۲۶ — مرحله ۳ سطح قراردادهای مرحله ۱ و مرحله ۲ را روی همه زنجیرههای EVM پشتیبانیشده میبرد. همچنین یک keeper دوم اضافه میکند تا قطعی NestJS نتواند باعث جا افتادن برداشت اشتراک شود.
خلاصه
- استقرار
PaymentLinkFactory، پیادهسازیPaymentLinkReceiver،FeeSplitterوSubscriptionHubروی Ethereum mainnet، Arbitrum، Optimism و Polygon. - همان طرح salt برای CREATE2 در همهجا؛ آدرس کارخانه روی همه زنجیرهها یکسان است (تمیز برای مستندات، یکپارچهسازیها و
computeAddressدر سمت کلاینت). - ثبت Chainlink Automation (ترجیحی) و/یا Gelato Web3 Functions بهعنوان keeper دوم که همان
charge(ids[])را فراخوانی میکند. - انتشار SLO: ۹۹٫۵٪ برداشتها ظرف ۱۰ دقیقه از زمان سررسید، بهصورت اندازهگیریشده.
این مرحله چه چیزی را تغییر میدهد
چند keeper که همان charge() عمومی را فراخوانی میکنند، تضمین زندهبودن است. هیچکدام امانتدار نیستند؛ هر کدام میتواند برداشت سررسیدشده را نجات دهد.
برنامه استقرار
ترتیب استقرار (هر هفته یک زنجیره، با canary برای هر زنجیره):
- Ethereum mainnet (chainId
1) - Arbitrum One (
42161) - Optimism (
10) - Polygon PoS (
137)
آدرسها روی هر زنجیره در /docs/non-custodial/contract-reference منتشر میشوند. کلید استقرار همان مولتیسیگ Safe سه از پنج تعریفشده در مرحله ۰ است که از همان مراسم برای هر زنجیره دوباره مستقر میشود.
هر چهار زنجیره از همان پرچم useOnChainReceiver استفاده میکنند که بر اساس chainId در api/src/payments/wallet.service.ts تعریف شده است. فروشندگان زنجیره به زنجیره آن را فعال میکنند؛ پس از یک هفته canary بدون مشکل، مقدار پیشفرض روشن میشود.
راهبرد گس برای charge()
تراکنشهای charge() مربوط به keeper به گس نیاز دارند. از زیرساخت paymaster موجود در api/src/withdrawals/services/gas-strategy دوباره استفاده میکنیم:
- با paymaster آلکمی (Base، Arbitrum، Optimism، Polygon): keeper تابع
SubscriptionHub.charge()را بهصورت UserOperation با حمایت سیاست فراخوانی میکند. هزینه گس برای EOA مربوط به keeper صفر است. - Ethereum mainnet (بدون paymaster): keeper از موجودی گس داغ خودش میپردازد. کیف پول پلتفرم روزانه آن را شارژ میکند و با هشدار Datadog برای موجودی کم پایش میشود.
- گس با یارانه فروشنده (اختیاری): فروشندگان میتوانند یک
gas_refund_addressتنظیم کنند و keeper بازپرداخت را از آنجا برداشت کند؛ مطابق الگوی فعلی پشتیبانی/کمیسیون. خارج از دامنه راهاندازی اولیه.
keeper دوم: Chainlink و Gelato
keeper دوم برای افزونگی است، نه جایگزینی. در کنار زمانبند NestJS کار میکند.
Chainlink Automation (ترجیحی روی EVM)
- ثبت یک Upkeep برای هر
SubscriptionHub. checkUpkeep()برای همهidهایی کهblock.timestamp >= nextChargeAtدارند (خواندهشده از یک تابع view)، مقدارperformData = abi.encode(ids[])را برمیگرداند.performUpkeep(performData)تابعcharge(ids)را بهصورت دستهای فراخوانی میکند.- با LINK از خزانه پلتفرم تأمین میشود؛ موجودی پایش میشود.
Gelato Web3 Functions (جایگزین)
- یک Web3 Function به زبان JS هاب را بررسی میکند، IDهای سررسیدشده را پیدا میکند و یک تراکنش
charge(ids[])ارسال میکند. - هزینه بهصورت پسپرداخت از
1Balanceدر Gelato پرداخت میشود.
هر کدام بهتنهایی کافی است؛ بر اساس تجربه عملیاتی در مرحله ۳ انتخاب میکنیم. اجرای هر دو هم مشکلی ندارد؛ به لطف lastChargedAt، تابع charge() در هر دوره idempotent است.
SLOها و پایش
اهداف SLO منتشرشده:
| معیار | هدف |
|---|---|
charge() ظرف ۱۰ دقیقه از nextChargeAt | ۹۹٫۵٪ |
charge() ظرف ۱ ساعت از nextChargeAt | ۹۹٫۹٪ |
| موجودی گس داغ EOA مربوط به keeper بالاتر از آستانه هشدار | ۹۹٫۹۹٪ |
| ایندکس شدن رویدادها ظرف ۲ دقیقه از قطعیت بلاک | ۹۹٫۵٪ |
پایش به تنظیمات موجود Sentry که در مهارت گردش کار Sentry مخزن مستند شده وصل میشود. هشدارها:
- revertهای
charge()در هر ساعت > آستانه ← PagerDuty - موجودی بومی EOA مربوط به keeper کمتر از N gwei × gasLimit × 100 ← PagerDuty
- ناسالم بودن upkeep در Chainlink/Gelato ← هشدار
- تأخیر ایندکسر رویداد
Paidبیش از ۵ دقیقه ← هشدار
موارد منسوخشده
مانند مراحل ۱ و ۲، اما روی زنجیرههای بیشتر. هنوز چیزی حذف نمیشود؛ حذف منتظر مرحله ۷ میماند.
برنامه مهاجرت
- لینکهای پرداخت جدید: روی هر زنجیرهای که پرچم روشن دارد از گیرندههای قراردادی استفاده میکنند.
- اشتراکهای جدید: روی هر زنجیرهای که پرچم روشن دارد از
SubscriptionHubاستفاده میکنند. - مشتریان فعلی: مهاجرت اختیاری هنگام تمدید، مانند مرحله ۲.
- تسویه بینزنجیرهای (Relay): سطح بدون تغییر؛ keeper همچنان اجراکنندههای Relay را در api/src/withdrawals/services/bridge فراخوانی میکند، فقط از یک EOA بدون امانتداری.
ریسکها و راهکارها
- ناهمخوانی آدرس بین زنجیرهها. راهکار: استقراردهنده قطعی CREATE2 (مثلاً
0x4e59b44847b379578588920cA78FbF26c0B4956C) با هش init-code ثابت؛ کارخانهها روی هر چهار زنجیره آدرس یکسان دارند. - محدودیتهای سیاست paymaster در هر زنجیره. راهکار: پایش جداگانه هر زنجیره و هشدار سهمیه سیاست در ۷۰٪ مصرف.
- تغییرات مکرر ثبت upkeep در Chainlink. راهکار: یک upkeep برای هر زنجیره؛ فهرست IDها روی زنجیره خوانده میشود، پس ثبتنام فروشندگان نیازی به تغییر پیکربندی Chainlink ندارد.
- برداشت دوباره توسط keeperهای مختلف. راهکار:
lastChargedAtیکنوا بهعلاوهChargeSkippedبرای موارد تکراری؛ هر دو keeper ذاتاً idempotent هستند. - جهش ناگهانی گس روی mainnet. راهکار: تنظیم اندازه دسته برای هر زنجیره؛ سقف قیمت گس که بالاتر از آن
charge()یک بلاک عقب میافتد.
چکلیست وضعیت
- تکمیل استقرار روی Ethereum، Arbitrum، Optimism و Polygon
- انتشار آدرس کارخانه + هاب در مرجع قراردادها
- اعتبارسنجی سیاستهای paymaster برای هر زنجیره
- فعال بودن upkeep در Chainlink Automation دستکم روی ۲ زنجیره
- داشبورد SLO در Datadog
- runbook برای قطعی keeper (فراخوانی دستی
charge()از CLI فروشنده) - راهاندازی فروشندگان canary برای هر زنجیره
قدم بعدی
سولانا زنجیره بزرگ دیگری است که باید غیرامانی شود: مرحله ۴.
مستندات مرجع: وضعیت مرحله ۳ · راهبرد keeper · مرجع قراردادها
