Skip to main content
Relayers allow users to transact on NEAR while a service pays their transaction gas fees. Users still sign the actions they want to execute, so the relayer can submit those actions without holding the users’ private keys.
The JavaScript examples require near-api-js 7.4 or later and borsh 2. The Rust examples use the near-api crate version 0.8.6, with Axum for the server, Reqwest for the client, and borsh 1 for serialization.

Client

The client creates a signed delegate action: a message containing the sender, receiver, actions, nonce, and expiry block height. Signing it authorizes the call without broadcasting a transaction or paying transaction gas. This example shows how to sign a set_greeting call to hello.near-examples.testnet with zero attached deposit and an expiry of 120 blocks:
client.ts
The signed delegate is converted to Borsh bytes, which we encode as base64 for the JSON request to /relay. The client’s private key stays on the client; the relayer receives only the signed delegate.
This example signs locally with a private key. To use a wallet, its signing interface must support delegate actions without immediately broadcasting them. Ordinary transaction signing and NEP-413 message signing produce different payloads and cannot replace delegate-action signing.

Relayer (server)

The relayer uses its own funded account to pay transaction gas. For parallel submissions, multiple access keys reduce nonce contention and failures caused by transactions executing out of order. The relayer’s signer rotates between the configured keys for each transaction. All keys must be registered as full-access keys on the relayer account. The /relay endpoint decodes the signed delegate from base64 and Borsh, wraps it in a transaction signed by the relayer, and sends it to NEAR:
server.ts
The outer transaction is addressed to the client’s account, which is the sender in the signed delegate. The network checks the client’s signature and executes the delegated actions. The relayer pays transaction gas; any attached deposit or transferred NEAR comes from the client’s account. Both examples read credentials from environment variables loaded from .env. Set CLIENT_ID and CLIENT_PRIVATE_KEY for the client, and RELAYER_ID and RELAYER_PRIVATE_KEYS for the server. RELAYER_PRIVATE_KEYS is a JSON array of private keys. Both processes must use the same network, and the client account must already exist and cover its storage requirements.

High volume parallel processing

When running a relayer that handles a large number of transactions, you can run into a nonce collision problem. At the protocol level, each access key has its own nonce, which increases with each transaction to prevent replay attacks. When multiple transactions are sent from the same access key simultaneously, their nonces might collide or they might execute out of order. For example, if Tx3 has a higher nonce than Tx1 and Tx2 and executes first, the earlier transactions can fail because their nonces are now too low. The examples above use multiple full-access keys on the same relayer account. Both signing libraries rotate through the configured keys. This distributes transactions across independent nonces and reduces contention. Key rotation reduces nonce contention, but a key can be reused while an earlier transaction is still pending. For sustained parallel traffic, coordinate submissions per key or wait for its pending transaction to complete before reusing it. Rotating relayer keys also does not change the nonce of a client’s signed delegate; clients should wait for one delegated call to finish before signing the next with the same key.

Gating the relayer

A relayer can choose which calls it will sponsor by checking the decoded delegate action before submitting it. For example, to sponsor only calls from a particular user to a particular contract, add this check after decoding the signed delegate in the endpoint:
We can also inspect each action to limit attached deposits, gas, contract methods, or arguments. Function-call arguments in a Borsh-decoded delegate are bytes, so we can read JSON arguments with:
For a Rust relayer with account allowances, allowlists, and other deployment options, see the reference implementation.