Subscriptions
Recurring billing between agents and service providers, recorded on-chain.
A Subscription binds an agent wallet to a Plan, on-chain. It authorizes the service provider to collect a fixed amount of USDG from the agent’s wallet each billing cycle (once, at subscribe time). No per-transaction signature is ever required afterward.
Subscription structure
interface Subscription {
id: string; // On-chain subscription id
subscriber: Address; // Agent wallet
plan: string; // Id of the plan being subscribed to
status: SubscriptionStatus; // TRIAL | ACTIVE | PAUSED | CANCELLED
startedAt: number; // Unix timestamp
trialEndsAt?: number; // Unix timestamp, if applicable
nextBillingAt: number; // Unix timestamp of next collection
cycleCount: number; // Number of completed billing cycles
authorizedAmount: number; // Max pull per cycle (in token base units)
}
Lifecycle
subscribe()
|
v
TRIAL (if trialPeriodDays > 0)
|
| trial period ends
v
ACTIVE <---+
| |
| | billing cycle completes, collection succeeds
| |
| collection fails (insufficient funds)
v
PAUSED -->--+ (after retry window, if still failing)
|
| agent cancels or provider cancels
v
CANCELLED
TRIAL
A subscription to a plan with a trial period opens in TRIAL status. No funds move during the trial; the first collection happens with the first cycle after it ends.
ACTIVE
In ACTIVE status, the subscription bills on every renewal date. The provider’s system, or Scribe Finance’s automation, calls scribefinance.collect(), and the on-chain Subscription Authority verifies the authorization before any transfer executes.
PAUSED
A failed collection (most commonly an agent wallet short of USDG) moves the subscription to PAUSED. Retries continue over a configurable window (default: 3 attempts over 7 days). If none succeed, the subscription remains paused and a subscription.payment_failed webhook fires.
After the wallet is funded again, the agent or the provider unpauses it manually.
CANCELLED
Cancellation takes effect immediately and is written to the chain. Time already paid for in the current cycle remains usable. Any grace period is the provider’s to enforce.
How automatic billing works
On Robinhood Chain, no contract can pull from a wallet without that wallet’s explicit authorization. Scribe Finance provides that authorization through its open-source ScribeFinanceSubscriptions contract, deployed on Robinhood Chain.
agent.subscribe() does two things in one step: the SDK writes the subscription record to the chain and delegates a Subscription Authority to the provider, scoped to that exact (wallet, token, authorized_amount) combination. The scope is the ceiling: per cycle, the provider can collect the authorized amount and not a unit more.
At each renewal, the provider (or Scribe Finance’s automation) calls collect() on the contract, which reads the subscription record, verifies the authorization still stands, and executes the transfer. The agent signs nothing.
Creating a subscription
From the agent side:
const sub = await agent.subscribe({
planId: "0x7f3a...plan_id",
});
console.log(sub.id); // on-chain subscription id
console.log(sub.status); // "TRIAL" or "ACTIVE"
console.log(sub.nextBillingAt); // Unix timestamp
Verifying a subscription
A provider confirms that a request is backed by an active subscription like this:
const isValid = await scribefinance.verifySubscription({
subscriber: requestingWallet,
plan: plan.id,
});
if (!isValid) {
return res.status(403).json({ error: "No active subscription" });
}
In practice, the payment gate middleware usually performs this check on your behalf.
Cancellation
// The agent ends their own subscription
await agent.cancelSubscription({ subscriptionId: sub.id });
// The provider ends it (e.g. for policy violations)
await scribefinance.cancelSubscription({ subscriptionId: sub.id });
Cancellation is an on-chain operation: final, and verifiable by anyone.
Listing subscriptions
// Agent side: every active subscription for this wallet
const subscriptions = await agent.listSubscriptions();
// Provider side: everyone subscribed to a plan
const subscribers = await scribefinance.listSubscribers({ plan: plan.id });
The Scribe Finance Dashboard shows the same subscriptions alongside MRR trends, churn rate, and per-subscriber transaction history.