Phase 2 — EVM subscriptions (allowance-pull)
Status: Planned.
SubscriptionHub with public charge(id). Payer signs approve(spender, cap) once; the hub enforces timing, cap, allowance, and balance. Keeper replaces the custodial auto-charge processor.
Scope
SubscriptionHubimplemented and audited.- Pay app points
approveat the hub address. approve-auto-chargeAPI accepts hub-spender allowances.- Existing subscribers migrate opt-in at renewal.
Code touched
- api/src/scheduler/processors/subscription-auto-charge.processor.ts — pull via
SubscriptionHub.chargeinstead of a custodial smart account. payapp: spender config value changes to the hub address.- Dashboard: migration banner for existing subscriptions.
- New: subscription-focused indexer (reuses infrastructure from Phase 1).
Runtime / UX impact
- Payer sees the spender is a contract instead of a hot wallet. Single on-chain
approvestep unchanged. - Revoke is via wallet UI (
approve(0)or equivalent), same as Auto-charge documents. - Merchant webhooks fire at the same moments with the same shapes.
- New UI: "Move to non-custodial" button on subscription detail page.
Current limitations
- Allowance must be sufficient; under-allowance charges fail with
ChargeSkipped(InsufficientAllowance). - Cap changes require the payer to re-approve.
- Subscription ID is a
bytes32; surfaced as an additive field in the subscription object.
Linked blog post
Phase 2: Allowance-Pull Subscriptions
Status checklist
-
SubscriptionHubimplemented with all invariants - Fuzz + invariant tests (cap, allowance, reorg, skip cases)
- Audit delta covering the hub landed
- Keeper EOA provisioned per chain; monitored
- Pay app migrated
approvetarget to the hub -
approve-auto-chargeendpoint updated - Dashboard banner live
- Canary cohort migrated and stable for 14 days