Skip to main content

Credits and billing

There are four money-shaped things in CoRelayer, and they behave differently. Keeping them apart is most of understanding the billing.

What it isCreated byReversible
A depositUSDC arriving at the contractYou, signing a transferNo — it becomes credits in the same call
CreditsA USDC-denominated balance inside the contractA depositNo
A plan blockThe record of what you bought, with its terms frozenSpending credits on a tierNo
EscrowCredits set aside for pay-as-you-go usageOpting into pay-as-you-goBack to credits — never to your wallet — once you turn pay-as-you-go off

Depositing​

A deposit is an ESDT transfer of USDC to the contract that also calls one of three functions:

FunctionEffect
deposit()Credits your own account.
depositFor(beneficiary)Credits someone else's account.
depositAndSubscribe(tier, months, max_price, ref)Deposits and buys a plan, in one transaction.

The contract swaps the USDC in the same call and credits you 1:1 from the USDC you paid — not from what the swap returned. (Where the money goes)

BoundLaunch value
Minimum deposit1 USDC. It exists because a deposit costs a relayer gas, so a dust deposit would cost more to process than it is worth. It is deliberately not scaled by the tariff.
Maximum depositUnlimited at launch. If the owner ever sets a ceiling, it bounds both a single call and the total swapped per minute, and the API refuses over-large deposits before co-signing rather than letting the transaction revert.

If the swap venue is paused, or the pair or token is unavailable, the whole deposit reverts and you keep your USDC. The API sees this coming and answers SWAP_VENUE_PAUSED instead of relaying a transaction that would certainly fail — which matters because on the free purchase flow, our relayer pays for that failure.

Buying with a price ceiling​

No purchase path exists without a ceiling you set yourself. Every purchasing call takes a max_price, and the contract reverts if the price at execution time is above it. This includes adding named wallets, which also costs Relay Units. A sponsor key costs nothing beyond the Relay Units it relays.

The flow the dashboard and the SDK use:

  1. POST /v1/subscribe/prepare returns a quote and the unsigned transaction.
  2. You sign the transaction. Its arguments — tier, months, max_price, and a reference — are part of the signed bytes, so what you approved is what executes.
  3. The contract prices the purchase at the block timestamp and reverts if that exceeds your ceiling: PRICE_ABOVE_MAX.
A quote
{
"quoteId": "q_01JA7M3Z9K",
"tierId": 12,
"months": 1,
"tariff": "10000",
"tariffVersion": 1,
"priceMicroUsdc": "85000000",
"maxPrice": "85000000",
"pendingTariff": null,
"issuedAtMs": 1789819200000,
"expiresAtMs": 1789819320000
}

Three properties of quotes worth knowing:

  • They live for 120 seconds, and they are stateless. A quote is not a row in a table we could lose or mismatch across hosts; it is content plus a signature over that content, so any host can verify one it did not issue. Asking twice simply gives you two valid quotes.
  • maxPrice equals the price. There is no built-in slack. If the price goes down between quote and block, you are charged less.
  • A quote never straddles a price increase silently. If an increase becomes effective before the quote expires, the quote is priced at the pending, higher value and says so. A transaction that lands before the activation pays the lower current price and the surplus stays as credits. You are never charged more than you accepted, and the purchase never fails for this reason.

What a purchase freezes​

Paying writes a plan block, and a plan block is immutable for its term. It records:

  • the cap in Relay Units and the length of the period;
  • the pay-as-you-go price per unit;
  • the number of named wallets (listed senders) allowed;
  • the rate class and service class;
  • the tariff version it was priced at.

Nothing later can change it. Raising the tariff does not; editing the tier does not; retiring the tier does not. An account holds at most one current block and one queued block, so buying ahead does not disturb what is running.

Prepaying, upgrading and downgrading​

PrepayingBuy 1 to 12 months in one purchase. Every month is priced at the tariff in force when the purchase executes, and that price holds for the whole block. There is no discount for prepaying: the benefit is the locked price.
Unused transactionsThey belong to the period they were bought for and expire with it. Nothing rolls over into the next period.
Upgrading mid-periodA tier with a higher price starts now. The months of the old block that had not started are credited back to your credits, and the unused part of the current period's cap carries over once, into the first period of the new block. The started period itself is not refunded.
DowngradingThere is no immediate downgrade. A lower tier starts when your paid block ends: buy it as the queued block, or set it as your auto-renew tier. A prepaid block cannot be shortened or swapped for a cheaper one.

A credit-back is not a refund: it returns to your credits, which buy plans and pay-as-you-go usage and are not converted back to USDC.

Renewal​

ManualBuy again. The new block queues behind the current one.
Auto-renewsetAutoRenew(enabled, tier, max_renew_price) — and max_renew_price must be greater than zero when enabled.

Zero never means "unlimited". An account that renews itself has to state its ceiling explicitly; that is a deliberate refusal to offer an open-ended standing authorisation, and it matters most for autonomous buyers, which are exactly the accounts most likely to renew unattended.

Renewal is permissionless: anyone may trigger the renewal of an account that has opted in, because the terms are fixed by the account itself and the contract enforces them. This keeps renewals working without a scheduler that has to be running at midnight.

When there is no entitlement​

SituationWhat you get
No plan at allNO_ENTITLEMENT — 402, with a pointer to the pricing document and the x402 endpoint.
Cap used up, pay-as-you-go offQUOTA_EXHAUSTED — 429 with no Retry-After, because waiting does not help.
Cap used up, pay-as-you-go on, escrow emptyQUOTA_EXHAUSTED with details.reason = PAYG_ESCROW_EMPTY — again without Retry-After.
Pay-as-you-go price above your max_payg_pricePAYG_PRICE_ABOVE_MAX.
A purchase from credits, and there are not enoughINSUFFICIENT_CREDITS — deposit first, or buy with depositAndSubscribe.
SuspendedACCOUNT_SUSPENDED.

Escrow, and how it comes back​

Opting into pay-as-you-go moves credits into escrow: an amount set aside for usage beyond the cap. Only settled pay-as-you-go usage can spend it, and nothing else can spend it — that separation is what stops the same credits being spent twice, once on a plan and once on usage.

What pay-as-you-go does not use comes back to your credits — not to your wallet; credits are never refundable — once you turn pay-as-you-go off:

  1. Turn it off. setPayg(false, …) stops new pay-as-you-go usage and starts the close. The escrow stays locked for now, because usage from before the switch may still be settling.
  2. The normal release. Once every pay-as-you-go transaction from before the switch is settled, and at least an hour after it, the settlement key sends a closing line that returns the rest to your credits.
  3. The fallback. If that never happens — the settlement key dead, or hostile — anyone may call releaseEscrow(account) seven days after the switch, and the escrow returns to that account's credits. When the account itself sends it, CoRelayer relays it free of charge. No pause scope blocks this endpoint: a deliberate constraint on ourselves, so that no switch of ours can lock it.

Lowering the pay-as-you-go budget never releases escrow; only turning pay-as-you-go off does.

Who pays for a given transaction​

When a transaction arrives, the payer is resolved in a fixed order:

  1. a sponsor API key, if one was presented;
  2. the account explicitly named in the request, if that account authorised this sender on chain;
  3. the sender's own account;
  4. the oldest account that authorised this sender;
  5. the free list — a short, published set of calls to our own contract that CoRelayer pays for, so that buying and managing a plan never requires already having one.

If none of these applies you get NO_ENTITLEMENT, SENDER_NOT_AUTHORIZED or QUOTA_EXHAUSTED, depending on which step failed.

Reading your own numbers​

GET /v1/account/{erd}Plan, credits, flags — a mirror of chain state, public.
GET /v1/account/{erd}/quotaUnits used, cap, period end.
GET /v1/account/{erd}/purchasesEvery purchase, from the contract's own events.
GET /v1/usage, GET /v1/usage/summaryPer-transaction and aggregated usage. Private.