Skip to main content
Meta-transactions (NEP-366) allow a user to sign a transaction off-chain, which is then submitted and paid for by a separate “Relayer” account.

How it Works

  1. User (Client): Constructs a transaction and calls .delegate(). This returns a typed object (for UI/validation) and a serialized string (for transport).
  2. Transport: The user sends the serialized string (payload) to your backend.
  3. Relayer (Server): Your server decodes the payload back into an object, validates it, wraps it, and submits it.

1. Client Side: Signing the Action

On the frontend, use the .delegate() method to sign the queued actions without submitting a transaction or paying gas. It may make RPC calls to look up the nonce and expiration block height, or prompt a connected wallet to sign. For local signing, set the delegate nonce with .delegate({ nonce: n }). For V2 delegates, use .delegateV2({ nonce: n }), adding nonceIndex when signing with a gas-key slot. The builder’s .nonce(n) method is reserved for the outer transaction: combining it with delegate() or delegateV2() throws before RPC calls or signing, even if a delegate nonce option is also provided. Wallets choose their own delegate nonce. A relayer can still use .nonce(n).signedDelegateAction(signedDelegateAction).send() (or signedDelegateActionV2) to choose its outer transaction nonce independently of the user’s signed delegate nonce. It returns two properties:
  • signedDelegateAction: The raw JS object. Useful for debugging or showing details in your UI.
  • payload: A Base64-encoded string. This is what you send to the server.

2. Server Side: The Relayer

The server receives the Base64 string (payload). It must decode this string back into a typed object to inspect it before submitting. Use near-kit’s decodeSignedDelegateAction helper for this.

Example: Express.js Route

Security Checklist

Running a relayer makes you a target.
  1. Whitelist Receivers: Always check userAction.delegateAction.receiverId.
  2. Whitelist Methods: Inspect userAction.delegateAction.actions to ensure users are only calling allowed methods (e.g., “move” vs “withdraw”).
  3. Rate Limiting: Rate limit your API endpoint to prevent draining your relayer’s funds.