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

استراتژی 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 است که:

  1. اشتراک‌های سررسیدشده را از پایگاه داده می‌خواند (آینه‌ای از وضعیت آن‌چین که ایندکسر نگه می‌دارد).
  2. آن‌ها را بر اساس chain id گروه‌بندی می‌کند.
  3. برای هر زنجیره با یک EOA keeper مخصوص همان زنجیره یک تراکنش charge(ids[]) امضا و ارسال می‌کند.
  4. در صورت موفقیت، منتظر تأیید می‌ماند و پایگاه داده را از طریق رویدادهای ایندکسر به‌روز می‌کند.
  5. در صورت revert، selector مربوط به revert را تجزیه می‌کند و آن را به یک وضعیت SubscriptionAutoChargeEntity نگاشت می‌کند:
    • ChargeSkipped(InsufficientAllowance) ← past_due + ایمیل allowance
    • ChargeSkipped(InsufficientBalance) ← past_due + ایمیل موجودی
    • ChargeSkipped(CapExceeded) ← canceled (پرداخت‌کننده باید دوباره تأیید کند)
    • ChargeSkipped(NotDue) ← بی‌خطر (گردکردن ایندکسر)؛ تلاش دوباره در ۱ چرخه

از فاز ۳ به بعد، هر استقرار 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 گس دارد و هیچ چیز دیگری.

همچنین ببینید​