A Subscription is the on-chain link between an agent wallet and a Plan. When the agent subscribes, it grants the provider permission, one time, to pull a fixed USDG amount from its wallet every billing cycle. After that initial grant, the agent never has to sign for an individual collection.

Subscription record

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

If the plan defines a trial period, the subscription starts out in TRIAL status. Nothing is collected while the trial runs. The first charge lands in the first billing cycle after the trial ends.

ACTIVE

An ACTIVE subscription is charged on each renewal date. Either the provider’s own system or Opus Protocol’s automation invokes opusprotocol.collect(). Before any tokens move, the on-chain Subscription Authority checks that the authorization is still valid.

PAUSED

When a collection fails, usually because the agent wallet does not hold enough USDG, the subscription becomes PAUSED. Collection is retried across a configurable window; the default is 3 attempts spread over 7 days. If every retry fails, the subscription stays paused and a subscription.payment_failed webhook is emitted.

Once the wallet has been topped up, either the agent or the provider must unpause the subscription by hand.

CANCELLED

A cancellation is recorded on the chain and applies at once. The subscriber keeps access to whatever portion of the current cycle has already been paid for. If a grace period exists, the provider is responsible for enforcing it.

The mechanics of automatic billing

A wallet on Robinhood Chain must explicitly authorize any contract that wants to pull funds from it. Opus Protocol handles this through OpusSubscriptionEngine, an open-source contract deployed on Robinhood Chain.

A single call to agent.subscribe() performs two actions. The SDK records the subscription on the chain, and it grants the provider a Subscription Authority bound to the specific (wallet, token, authorized_amount) tuple. That binding is a hard limit: in any one cycle the provider may collect up to the authorized amount, never beyond it.

Once a renewal falls due, collect() is invoked on the contract, either by the provider or by automation that Opus Protocol runs. The contract loads the subscription record, confirms the authorization is intact, and moves the tokens. No signature from the agent is involved.

Subscribing to a plan

The agent initiates it:

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

Checking for an active subscription

On the provider side, this is how you check that an incoming request comes from a wallet with an active subscription:

const isValid = await opusprotocol.verifySubscription({
  subscriber: requestingWallet,
  plan: plan.id,
});

if (!isValid) {
  return res.status(403).json({ error: "No active subscription" });
}

Most providers never call this directly, because the payment gate middleware runs the check for them.

Cancelling

// The agent ends their own subscription
await agent.cancelSubscription({ subscriptionId: sub.id });

// The provider ends it (e.g. for policy violations)
await opusprotocol.cancelSubscription({ subscriptionId: sub.id });

Because cancellation happens on the chain, it cannot be reversed, and anyone can verify that it occurred.

Enumerating subscriptions

// Agent side: every active subscription for this wallet
const subscriptions = await agent.listSubscriptions();

// Provider side: everyone subscribed to a plan
const subscribers = await opusprotocol.listSubscribers({ plan: plan.id });

You can see the same data in the Opus Protocol Dashboard, together with MRR trends, churn rate, and a transaction history for each subscriber.