استراتژی keeper
SubscriptionHub.charge(id) (EVM) و دستور charge در Anchor (سولانا) عمومی و بدون نیاز به مجوز هستند. هر EOAای میتواند آنها را فراخوانی کند. «keeper» فقط زمانبندی است که این کار را انجام میدهد.
این صفحه توضیح میدهد OrcaRail چگونه برداشتها را اجرا میکند، وقتی keeper در دسترس نیست چه میشود و چه SLOهایی منتشر میکنیم.
keeper اصلی: زمانبند NestJS
keeper اصلی در api/src/scheduler/processors/subscription-auto-charge.processor.ts قرار دارد. یک پردازشگر BullMQ است که:
- اشتراکهای سررسیدشده را از پایگاه داده میخواند (آینهای از وضعیت آنچین که ایندکسر نگه میدارد).
- آنها را بر اساس chain id گروهبندی میکند.
- برای هر زنجیره با یک EOA keeper مخصوص همان زنجیره یک تراکنش
charge(ids[])امضا و ارسال میکند. - در صورت موفقیت، منتظر تأیید میماند و پایگاه داده را از طریق رویدادهای ایندکسر بهروز میکند.
- در صورت revert، selector مربوط به revert را تجزیه میکند و آن را به یک وضعیت
SubscriptionAutoChargeEntityنگاشت میکند:ChargeSkipped(InsufficientAllowance)←past_due+ ایمیل allowanceChargeSkipped(InsufficientBalance)←past_due+ ایمیل موجودیChargeSkipped(CapExceeded)←canceled(پرداختکننده باید دوباره تأیید کند)ChargeSkipped(NotDue)← بیخطر (گردکردن ایندکسر)؛ تلاش دوباره در ۱ چرخه
keeper پشتیبان: Chainlink Automation (ترجیحی)
از فاز ۳ به بعد، هر استقرار SubscriptionHub یک Upkeep در Chainlink Automation ثبت میکند:
checkUpkeep(bytes)برای IDهایی کهisDue(id) && !chargedThisBlock(id)باشدperformData = abi.encode(ids[])را برمیگرداند.performUpkeep(bytes)تابعcharge(ids[])را بهصورت دستهای فراخوانی میکند.- با LINK از خزانه پلتفرم تأمین مالی میشود.
- در حالت «conditional» با سقف گس متناسب با اندازه دسته پیکربندی میشود.
keeper پشتیبان: Gelato Web3 Functions (جایگزین)
اگر Chainlink روی زنجیرهای در دسترس نباشد (یا قیمتش مناسب نباشد)، یک Gelato Web3 Function تابع view isDue() هاب را پایش میکند و هنگام سررسید charge(ids[]) را ارسال میکند. هزینه پس از اجرا از 1Balance در Gelato پرداخت میشود.
keeper سولانا
شاخه سولانا در NestJS از همان الگوی زمانبند با یک keeper امضاکننده SOL استفاده میکند. سولانا امروز معادلی برای Chainlink Automation با پوشش مشابه ندارد؛ گزینههای پشتیبان:
- Clockwork (اگر وضعیت نگهداریاش در زمان راهاندازی قابل قبول باشد): cron بومی سولانا.
- یک نمونه دوم NestJS در منطقهای دیگر بهعنوان افزونگی سادهتر.
کارمزدهای سولانا به حدی پایین است که پایش موجودی گس در آن کمتر از EVM حیاتی است.
مدیریت گس
EVM
- زنجیرههای دارای سیاست paymaster در Alchemy: keeper تابع
charge()را بهصورت یک UserOperation حمایتشده از طریق منطق موجود در api/src/withdrawals/services/gas-strategy فراخوانی میکند. - زنجیرههای بدون paymaster: keeper از موجودی گس داغ خودش هزینه میکند که روزانه از کیف پول پلتفرم شارژ میشود.
- سقف قیمت گس: اگر قیمت گس از آستانه هر زنجیره بیشتر شود، برداشتها یک بلاک به تعویق میافتند.
- گس با یارانه پذیرنده (اختیاری): پذیرندگان میتوانند
gas_refund_addressتعیین کنند؛ بازپرداخت از طریق منطق تقسیم کارمزد برداشت میشود.
سولانا
- keeper با موجودی ثابتی از SOL برای هر زنجیره کار میکند.
- کارمزد اولویت بر اساس بلاکهای اخیر پویا تنظیم میشود؛ هر تلاش دوباره اولویت را یک پله بالا میبرد.
SLOها
| معیار | هدف | دامنه |
|---|---|---|
اجرای charge() ظرف ۱۰ دقیقه پس از nextChargeAt | ۹۹٫۵٪ | همه زنجیرههای پشتیبانیشده |
اجرای charge() ظرف ۱ ساعت پس از nextChargeAt | ۹۹٫۹٪ | همه زنجیرههای پشتیبانیشده |
| موجودی گس داغ EOA keeper بالاتر از آستانه هشدار | ۹۹٫۹۹٪ | همه زنجیرههای پشتیبانیشده |
ایندکس شدن رویدادهای Paid / Charged ظرف ۲ دقیقه | ۹۹٫۵٪ | همه زنجیرههای پشتیبانیشده |
نرخ revert در charge() (فقط revertهای غیربیخطر) | کمتر از ۰٫۱٪ | همه زنجیرههای پشتیبانیشده |
revertهای غیربیخطر شامل ChargeSkipped(InsufficientAllowance)، ChargeSkipped(InsufficientBalance) و ChargeSkipped(CapExceeded) نمیشوند؛ اینها شرایط مورد انتظار سمت پرداختکنندهاند.
پایش و هشدار
با استفاده از pipeline موجود Sentry + Datadog:
- نرخ revert در
charge()در هر ساعت بیشتر از آستانه ← PagerDuty - موجودی ارز بومی EOA keeper کمتر از
gasPrice × gasLimit × 100← PagerDuty - موجودی upkeep در Chainlink کمتر از آستانه ← هشدار
1Balanceدر Gelato کمتر از آستانه ← هشدار- تأخیر رویدادهای ایندکسر بیشتر از ۵ دقیقه ← هشدار
داشبوردها: یکی برای هر زنجیره، به همراه یک جمعبندی سراسری. runbookهای قطعی از برنامه کشیک لینک شدهاند.
محافظت در برابر reorg
SubscriptionHub.charge(id)شرطblock.timestamp > lastChargedAtرا بهصورت یکنوا اعمال میکند. reorgی که برداشتی را تکرار کند باAlreadyChargedThisPeriodبرگشت میخورد.- برنامه سولانا از
last_charged_atبه همین شکل استفاده میکند؛ reorg در سولانا بسیار نادرتر است، اما همان ناوردا برقرار است.
مداخله دستی
پذیرندگان در شرایط اضطراری (مثلاً وقتی NestJS و Chainlink همزمان از دسترس خارجاند) میتوانند charge(id) را با یک اقدام در داشبورد دستی اجرا کنند. تراکنش با کیف پول خود پذیرنده امضا میشود و امانی نیست.
کارهایی که keeper نمیتواند انجام دهد
فهرست صریح، تا محدوده روشن باشد:
- ساختن اشتراک بدون امضای پرداختکننده.
- افزایش
cap،amountیاintervalیک اشتراک موجود. - انتقال وجوه به آدرسی که در
Splitاشتراک نیست. - دور زدن
nextChargeAtیاlastChargedAt. - غیرفعال کردن جریانهای توقف، لغو یا بازپرداخت.
keeper گس دارد و هیچ چیز دیگری.