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 mode | Key mode (named wallets) | |
|---|---|---|
| For | Any number of senders whose key your server holds or reaches: embedded or custodial wallets, players, the agents you run | A small set you know in advance whose key your own programs hold: bots, devices, scripts, your treasury |
| Who pays | The account that owns the sponsor key | The sender's own account, or an account that listed the sender on chain |
| Where the permission lives | On your server, as a secret | On chain, one entry per (account, sender) |
| Proof at assign | The sender's presence proof (proof.kind: "sponsor"), which your server signs with the sender's key; the sponsor key is not read at assign | proof.kind: "key", made by the program that holds the sender's key, or a native-auth token from the CoRelayer dashboard |
| Credential at relay | The signed transaction, plus X-Api-Key from your server | The signed transaction |
| Limit | No sender limit. The key's policy applies: receiver allow-list (required), function allow-list, units per sender per day, units per day, IP ranges | The tier's named-wallet count |
| Costs | Nothing beyond the Relay Units used | A fee per wallet, paid once when it is added |
| Plans | Builder, Growth, Scale, Enterprise, Agent Pro, Agent Fleet | Every plan, up to its named-wallet count |
billing.authMode on the relay answer | api_key | own_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.
Sponsor keys, on your server
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:
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_…
| Scope | relay: pay for any sender. (read exists for private reads of the account.) |
| Required | a plan that can sponsor: Builder and up, Agent Pro and Agent Fleet (tier flag sponsor_any_sender); otherwise API_KEY_SCOPE |
| Required policy | a 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 policy | a function allow-list, Relay Units per sender per day, Relay Units per day, IP ranges |
| Shown | once, at creation. It is stored as a keyed hash and cannot be shown again. |
| Created, changed, revoked by | your wallet, through native-auth, never by another key |
| Per account | at 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 transaction | Its receiver field | A sponsor key pays for it |
|---|---|---|
| A call to a contract, with or without EGLD or a single fungible token | The contract | When the contract is listed |
| EGLD or a token sent to a person's address | That address | Only 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 transfers | The sender | Not 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:
- A sponsor key is presented → the key's account.
- The request names an
accountthat authorised this sender on chain → that account. - The sender has its own account with something to serve the transaction → the sender.
- 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.
- 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:
| Code | Status | Meaning |
|---|---|---|
NO_ENTITLEMENT | 402 | The 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_AUTHORIZED | 403 | The 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_EXHAUSTED | 429 | An 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}/senders | The named wallets (authorised senders), in the order they were added — public, because it mirrors chain state. |
GET /v1/account/{erd}/quota | Includes senders.count and senders.max. |
GET /v1/account/{erd}/keys | Your sponsor keys and their policies — native-auth only. |
senders.changed notice | Sent to the account whenever an entry is added or removed. |