Skip to main content

Building a Meta Transaction Relayer

Relayers serve to delegate gas fees to a web service, allowing users to transact on NEAR without the need to acquire the token themselves while still retaining the security of signing their own transactions. This guide will lead you through the components necessary to construct a relayer capable of handling meta transactions.

tip

If you're already acquainted with the technology, you can fast track to a working open source example

For other languages you can check out Python, Rust

How it works

relayer-overview-technical

A basic relayer consists of a web server housing a funded NEAR account. This account receives an encoded signed transaction, which can subsequently be decoded into a SignedDelegate format and transmitted on-chain.

The client can then generate a SignedDelegateAction (a signed message that hasn't yet been sent), encode it, and transmit it to this server, where it will be relayed onto the blockchain.

Relayer (server)

Here's a simple express endpoint deserializes the body, instantiates the relayer account and then sends the transaction.

server.ts
loading...

You can easily get the account object used to send the transactions from its private key using this snippet

info

The code in the example only works from the following versions onwards

"near-api-js": "3.0.4"
"@near-js/transactions": "1.1.2",
"@near-js/accounts": "1.0.4"

Client

In this method we are creating an arbitrary smart contract call, instantiating an account and using it to sign but not send the transaction. We can then serialize it and send it to the relayer where it will be delegated via the previously created endpoint.

client.ts
loading...
Relaying with wallets

At the moment, wallet selector standard doesn't support signing transactions without immediately sending them. This functionality is essential for routing transactions to a relayer. Therefore, to smoothly integrate relaying on the client side, it's necessary to be able to sign transactions without relying on wallets. Progress is being made to make this possible in the future.

Gating the relayer

In most production applications it's expected that you want to be able to gate the relayer to only be used in certain cases. By taking apart the delegateAction object inside the SignedDelegateon the server this can be done simply.

export declare class DelegateAction extends Assignable {
senderId: string;
receiverId: string;
actions: Array<Action>;
nonce: BN;
maxBlockHeight: BN;
publicKey: PublicKey;
}

You can, for example, gate by some particular user or contract:

  const serializedTx: Buffer = req.body;
const deserializedTx: SignedDelegate = deserialize(SCHEMA.SignedDelegate, Buffer.from(serializedTx)) as SignedDelegate;
const relayerAccount: Account = await getAccount(NETWORK_ID, RELAYER_ID, RELAYER_PRIVATE_KEY);
const delegateAction = deserializedTx?.delegateAction

if(delegateAction.senderId == 'someUserId' || delegateAction.receiverId == 'someContractId' ){
const receipt = await relayerAccount.signAndSendTransaction({
actions: [actionCreators.signedDelegate(deserializedTx)],
receiverId: deserializedTx.delegateAction.senderId
});
}

Other examples could be looking into the actions and seeing if there is deposit or gas and limiting them, gating by particular smart contract methods or even args.

You can decode the args using:

JSON.parse(Buffer.from(args_base64 || "", "base64").toString())
Was this page helpful?