Skip to main content

Paying for other senders

A plan belongs to an account, but the transactions it pays for can be signed by other addresses. An app pays for its users; an operator pays for a fleet of agents; a team pays for its members' wallets. There are two ways to do it, for two shapes of problem.

Sponsor modeKey mode (named wallets)
ForAny number of senders whose key your server holds or reaches: embedded or custodial wallets, players, the agents you runA small set you know in advance whose key your own programs hold: bots, devices, scripts, your treasury
Who paysThe account that owns the sponsor keyThe sender's own account, or an account that listed the sender on chain
Where the permission livesOn your server, as a secretOn chain, one entry per (account, sender)
Proof at assignThe sender's presence proof (proof.kind: "sponsor"), which your server signs with the sender's key; the sponsor key is not read at assignproof.kind: "key", made by the program that holds the sender's key, or a native-auth token from the CoRelayer dashboard
Credential at relayThe signed transaction, plus X-Api-Key from your serverThe signed transaction
LimitNo sender limit. The key's policy applies: receiver allow-list (required), function allow-list, units per sender per day, units per day, IP rangesThe tier's named-wallet count
CostsNothing beyond the Relay Units usedA fee per wallet, paid once when it is added
PlansBuilder, Growth, Scale, Enterprise, Agent Pro, Agent FleetEvery plan, up to its named-wallet count
billing.authMode on the relay answerapi_keyown_account or authorized_sender

Either way, rate limits and quotas are the account's, never per sender. Paying for another sender does not add capacity; it shares what the account bought.

A dApp with a hundred thousand users cannot list them on chain. A sponsor key lets your server pay for transactions signed by any sender whose key it holds or reaches, within limits you set. Your server submits each signed transaction with the key, and your plan pays the network fee:

sponsor-relay.ts
import { CoRelayerClient, type RelayRequest } from '@corelayer/sdk';

// On your server. The sponsor key never reaches a browser.
const apiKey = process.env.CORELAYER_API_KEY;
if (!apiKey) throw new Error('Set CORELAYER_API_KEY');
const corelayer = new CoRelayerClient({
baseUrl: 'https://api.co-relayer.com',
apiKey,
});

// Your user signed the transaction. Your plan pays the network fee.
export async function payForUser(req: RelayRequest, actionId: string) {
const { data } = await corelayer.relay(req, { intentKey: actionId });
return data.txHash;
}

The key comes from the environment, and a missing key stops the server at start-up. Pay for your users has the whole file, the same code in Go, Rust and Python, the steps to create a key, and what to do when a key refuses a request.

crk_<env>_<keyId>_<secret> sent as X-Api-Key: crk_live_…
Scoperelay: pay for any sender. (read exists for private reads of the account.)
Requireda plan that can sponsor: Builder and up, Agent Pro and Agent Fleet (tier flag sponsor_any_sender); otherwise API_KEY_SCOPE
Required policya non-empty receiver allow-list: the contracts your users may call at your expense. The key compares it with the transaction's receiver field. Single fungible-token payments (ESDTTransfer) to a listed contract are covered. NFT, SFT, Meta-ESDT and multi-token transfers name the sender as receiver, so a sponsor key cannot pay for them yet.
Optional policya function allow-list, Relay Units per sender per day, Relay Units per day, IP ranges
Shownonce, at creation. It is stored as a keyed hash and cannot be shown again.
Created, changed, revoked byyour wallet, through native-auth, never by another key
Per accountat most 10 keys

There are no browser keys. Sender addresses cost nothing to create, so a key visible in a web page would let anyone spend your whole cap inside the allow-list. Named wallets suit addresses whose key your own programs hold, such as bots, devices and scripts. A browser dApp whose users sign in their own wallet can't be relayed for yet.

Which senders. Sponsor mode covers senders whose key your server holds or reaches through a signing service: embedded and custodial wallets, game and app servers, bots and agent fleets. The server proves the sender is present with the sender's key, has that key sign once, and relays with the sponsor key. The sponsor key stays on your server, and the signature comes from the sender's key:

If a key leaks, the damage is bounded by what you configured: the thief can spend your Relay Units, but only on calls to receivers you allow-listed, and only up to the per-sender and per-day limits. Revoke it from the dashboard with your wallet.

Payments to people​

A payment of EGLD or a token to a person's address has that address as its receiver field. A sponsor key pays for it only if that address is on the receiver allow-list, which holds at most 100 entries, so a sponsor key can't pay for payments to arbitrary addresses. Send payouts and person-to-person payments from wallets you name instead (key mode), or let each sender hold a plan of its own.

The transactionIts receiver fieldA sponsor key pays for it
A call to a contract, with or without EGLD or a single fungible tokenThe contractWhen the contract is listed
EGLD or a token sent to a person's addressThat addressOnly if that address is on the list (at most 100). Pay those from wallets you name (key mode).
NFT, SFT, Meta-ESDT and multi-token transfersThe senderNot yet

Named wallets, on chain​

A named wallet is an address your account lists on chain: an authorised sender, in the API's words. It suits a small set you know in advance whose key your own programs hold, such as bots, devices, scripts or your treasury, and it needs no sponsor key: the program that holds each key makes its own presence proof. The account calls the contract:

addSenders(max_fee, senders…) up to 50 per call; the fee must not exceed max_fee
removeSenders(senders…) free

From the moment the senderAdded event is final and mirrored, those addresses relay with nothing but their own signature — no key, no token. When a sender is covered by more than one account, it can name the payer with the optional account member of POST /v1/relay.

The fee. Adding a sender costs sender_fee_ru × tariff per address, taken from the account's credits. At launch sender_fee_ru is 5 Relay Units, which at a tariff of 10,000 micro-USDC per unit is 0.05 USDC per sender. The fee is charged in Relay Units priced at the tariff, so the single price lever rescales it with everything else. An increase of sender_fee_ru takes effect only 172,800,000 ms (48 hours) after it is set; a decrease is immediate.

The ceiling. Like every purchase, addSenders carries a ceiling you set. POST /v1/senders/prepare quotes the fee as feeMicroUsdc and puts exactly that value into the max_fee argument you sign; a higher fee at execution reverts with PRICE_ABOVE_MAX.

curl -sS https://api.co-relayer.com/v1/senders/prepare \
-H 'content-type: application/json' \
-d '{"address":"erd1…account","action":"add","senders":["erd1…a","erd1…b"]}'

The answer is a PreparedTransaction: the unsigned transaction with the relayer set, the assignment whose lease you submit it with, a one-line summary, free: false, and the fee. Sign it once and relay it like any other transaction — Buy a plan shows the same pattern end to end with its checks.

Why adding costs something and removing does not. addSenders is never on the free list: it is billed as a normal relayed transaction plus the per-sender fee, so a loop that adds and removes addresses to make CoRelayer pay for gas costs the looper on every cycle. removeSenders is free because it only lowers exposure, and it is the account's one defence against a compromised sender key once that key has used up the quota.

The limit is the tier's named-wallet count: the Named wallets (key mode) column of Tiers. The contract enforces it in addSenders. After a move to a tier with a smaller limit, addSenders reverts until the count fits, and the service keeps serving the oldest entries up to the new limit (ordered by when they were added, then by address), which anyone can reproduce from the events.

Many-to-many, on purpose. One address may be authorised by several accounts. Being listed only ever gives a sender paid relays, and the listing account consented on chain, so no consent from the sender is needed — and because there is no uniqueness rule, nobody can block a legitimate sponsor by registering an address first.

Who pays for a given transaction​

When a transaction arrives, its payer is decided in a fixed order, and the first rule that applies wins:

  1. A sponsor key is presented → the key's account.
  2. The request names an account that authorised this sender on chain → that account.
  3. The sender has its own account with something to serve the transaction → the sender.
  4. Accounts that authorised the sender, oldest authorisation first → the first one that can serve it. This is the path a client that sends no extra fields takes.
  5. The call is on the free list — the short, published set of calls to CoRelayer's own contract that the service pays for, so that buying and managing a plan never requires already having one → the service.

If none applies you get one of three answers, and they mean different things:

CodeStatusMeaning
NO_ENTITLEMENT402The account to bill has no plan (details.reason: NO_ACCOUNT or PERIOD_LAPSED). With no sponsor key and no account covering it, that account is the sender's own. So when your sponsor key never reached POST /v1/relay (a stripped header, a key sent as Authorization: Bearer), the relay bills your user, and the answer is NO_ACCOUNT even though your plan is active.
SENDER_NOT_AUTHORIZED403The request named an account in account, and that account has not listed this sender (details.reason: NOT_AUTHORIZED_FOR_ACCOUNT). An explicit account is never swapped for another.
QUOTA_EXHAUSTED429An account covers it, and its units are used up (CAP_REACHED_PAYG_OFF or PAYG_ESCROW_EMPTY).

The response to a successful relay names the account that was billed (account) and how it was resolved (billing.authMode).

Reading it back​

GET /v1/account/{erd}/sendersThe named wallets (authorised senders), in the order they were added — public, because it mirrors chain state.
GET /v1/account/{erd}/quotaIncludes senders.count and senders.max.
GET /v1/account/{erd}/keysYour sponsor keys and their policies — native-auth only.
senders.changed noticeSent to the account whenever an entry is added or removed.