Skip to main content

Delivery guarantees

The unit of work is an intent: one user-signed transaction we were asked to relay. Its key is (sender, nonce) — never the transaction hash, because the hash is an attribute of one attempt and the slot is the thing that can only be used once.

The states​

StateWhat it tells you
ASSIGNEDYou hold a lease. Nothing exists on our side.
RECEIVEDWe have your bytes and are checking them.
REJECTEDTerminal. No signature was produced and nothing was sent.
ACCEPTEDChecks passed, Relay Units reserved, about to be co-signed.
COSIGNEDPast the commit point. The transaction exists.
BROADCASTA gateway took it.
EXECUTED_OKOn chain, succeeded.
EXECUTED_FAILOn chain, reverted. Charged — see below.
EXPIRED_LOCALLY_STILL_VALIDWe stopped re-broadcasting. The transaction is still valid and may yet execute. Non-terminal on purpose.
DEADThe nonce was consumed by a different transaction. Ours can never execute; the reservation is released.

The commit point​

The commit point is the moment a relayer signature leaves the signer process. It divides the whole system:

Before it nothing exists that can execute. Every validation failure, every entitlement problem, every rate limit, every "no relayer available" lives here. A client that receives an error has a guarantee: nothing was sent.

After it the transaction exists in the world and, by the protocol, has no expiry. So after the commit point the API stops returning errors and returns states: 200 when a gateway has taken it, 202 when it is co-signed but not yet acknowledged. "Failed" is not an available answer, because it would not be true.

This is the property to design your client around. An error from POST /v1/relay always means the transaction was not sent.

Never sent twice​

Three mechanisms, in layers, each closing what the previous one cannot see.

The protocol​

At most one transaction per (sender, nonce) executes, and identical bytes re-broadcast any number of times are the same transaction. Re-broadcasting is therefore not a risk; it is the correct response to uncertainty.

The natural key​

POST /v1/relay is idempotent on (sender, nonce) plus a hash of the signed bytes:

You sendYou get
Identical bytes againThe stored response, marked duplicate: true. No second reservation, no second broadcast decision.
Different bytes, same nonce, first still aliveNONCE_IN_FLIGHT — unless you explicitly asked to cancel or replace.

This works across hosts because Ed25519 signing is deterministic and the ledger has a uniqueness constraint on the slot.

The logical key​

The natural key cannot see one specific mistake: a client that times out, assumes failure, and signs a fresh transaction at nonce n+1 for the same business action while T(n) is still alive. Different nonce, different bytes — both valid, both will execute.

So there is an optional logical key, one per user action: the Idempotency-Key header, or intentKey in the body.

POST /v1/relay
Idempotency-Key: checkout-7f2c0a41

Same key, different bytes or nonce, while the first intent is not DEAD → INTENT_ALREADY_SUBMITTED, carrying the first intent so you can look at it instead of guessing. Once the first intent is DEAD, the key is free again.

The key can only ever reject; it can never disagree with the natural key. The SDK, the MCP server and the dashboard set one automatically, one per user action.

When something fails​

Three cases, three different answers. None of them asks the user to sign the same thing twice.

What failsWhat happens
A gateway is unreachableThe identical signed bytes go to another gateway. Nothing is rebuilt, so there is nothing new to sign and nothing new that could execute.
The relayer runs low on EGLDA balance watcher keeps every assignable relayer above a floor. A relayer below it stops being assignable before it stops being able to pay, and topping it up lets the transaction already signed go through.
The relayer is lost before it co-signsOnly then does the API answer that a new signature is needed: one signature over the same account nonce, so the old transaction and the new one can never both execute. (Handle a re-sign)

What to do on a timeout​

On a timeout or a dropped connection from POST /v1/relay:
→ re-send the identical bytes, or
→ GET /v1/intents/{sender}/{nonce}
Never rebuild. Never re-sign.

GET /v1/intents/{sender}/{nonce} is the authoritative read. It is answered correctly by any host, including one that did not handle your submission.

What gets charged​

OutcomeRelay Units
Executed, succeededCharged, at the weight of the executed transaction.
Executed, revertedCharged. The network charges the relayer the full gas limit for a failed execution and refunds nothing.
Rejected before co-signingNothing. There was no cost.
Not executable at all — nonce already used, fee refusedNothing. The network charges nothing for these.
DEAD — the slot was taken by another transactionNothing. The reservation is released.
Co-signed, still waitingReserved, not yet charged.

Units are reserved at the worst case before co-signing, and settled at the real weight after execution. That is why a transaction with a needlessly high gas limit costs you more than it should — the reservation has to assume the network will take all of it. (Relay Units)

The state that surprises people​

EXPIRED_LOCALLY_STILL_VALID means: we have stopped actively re-broadcasting, but your transaction is a perfectly valid transaction that any node may still include. It is not a failure and it is not terminal. The honest answer at that point is "unknown", and the wrong answer would be to tell you it failed and let you sign a replacement — which is precisely how two transactions for one intention get created.

From that state, the safe moves are: wait, re-send the identical bytes, or explicitly cancel or replace the slot with a pinned nonce.

Watching an intent​

WayGood for
GET /v1/relay/{id}/eventsServer-sent events, one per state change. Closes when the intent is final.
GET /v1/intents/{sender}/{nonce}A definitive answer at any moment, from any host.
GET /v1/relay/{id}A resolver that takes either an intent id or a transaction hash.
The account streamEverything for one account on one connection.

POST /v1/relay itself never waits. It answers at the commit point or at its acknowledgement deadline, because a held-open request would turn every slow block into an ambiguous client timeout — which is the exact condition that makes clients re-sign. (Watch an intent)