Relay API Reference

The Relay API is split into 2 modules:

  1. Relay Client

    • Execute create, read, and update operations across all relayers within an account

    • Authenticates with bearer token generated using Team API Key/Secret (available when Team API Key is created)

  2. Relay Signer

    • Execute send, sign, and other operations using a specific relayer

    • Authenticates with bearer token generated using Relayer API Key/Secret (available when relayer is created)

For more information on authentication, refer to the authentication section.

You can use the @openzeppelin/defender-relay-client npm package for simplifying interactions with the Relay API.
It is not recommended to use the @openzeppelin/defender-relay-client npm package in a browser environment as sensitive keys would be exposed publicly.

Relay Client Module Reference

The Relay Client Module exposes the following endpoints:

  • list: list all relayers associated with the account

  • create: create a new relayer

  • retrieve: retrieve data for a single relayer

  • update: update an existing relayer

List Relayers

To list existing relayers, you can call the list function on the client, which returns a ListRelayerResponse object:

await client.list();

The relayers/summary endpoint is used to retrieve a list of existing relayers via a GET request.

curl \
  -X GET \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H "X-Api-Key: $KEY" \
  -H "Authorization: Bearer $TOKEN" \
    "https://defender-api.openzeppelin.com/relayer/relayers/summary"

An example response:

[
    {
      relayerId: '201832e2-c612-4283-97c5-31849242c1fe',
      name: 'MyRelayer',
      address: '0x623e258760220127d4856fa24ff126a230560749',
      network: 'rinkeby',
      createdAt: '2022-04-05T16:57:37.154Z',
      paused: false,
      policies: {},
      minBalance: '100000000000000000',
      pendingTxCost: '0'
    }
]

List Relayer Keys

To list keys associated with an existing relayer, you can call the listKeys function with a relayerId on the client, which returns an array of RelayerKey objects:

await client.listKeys('58b3d255-e357-4b0d-aa16-e86f745e63b9');

The /relayers/${relayerId}/keys endpoint is used to retrieve a list of relayer keys via a GET request.

curl \
  -X GET \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H "X-Api-Key: $KEY" \
  -H "Authorization: Bearer $TOKEN" \
    "https://defender-api.openzeppelin.com/relayer/relayers/58b3d255-e357-4b0d-aa16-e86f745e63b9/keys"

An example response:

[
    {
      apiKey: '5Kk12pMA6LjPSppaRAsfAJRAjYeFJzr6',
      createdAt: '2022-04-26T19:49:10.292Z',
      relayerId: '58b3d255-e357-4b0d-aa16-e86f745e63b9',
      keyId: '898a952b-415b-4d4b-882b-3958f11pbeea'
  }
]

Create Relayers

To create a new relayer, you need to provide the parameters as described directly below in interface CreateRelayerRequest.

interface CreateRelayerRequest {
  name: string;
  useAddressFromRelayerId?: string;
  network: Network;
  minBalance: string;
  policies?: UpdateRelayerPoliciesRequest;
}

interface UpdateRelayerPoliciesRequest {
  gasPriceCap?: string;
  whitelistReceivers?: string[];
  EIP1559Pricing?: boolean;
  privateTransactions?: boolean;
}

type Network =
  | 'mainnet'
  | 'sepolia'
  | 'holesky'
  | 'xdai'
  | 'sokol'
  | 'fuse'
  | 'bsc'
  | 'bsctest'
  | 'fantom'
  | 'fantomtest'
  | 'moonbase'
  | 'moonriver'
  | 'moonbeam'
  | 'matic'
  | 'mumbai'
  | 'avalanche'
  | 'fuji'
  | 'arbitrum'
  | 'arbitrum-nova'
  | 'arbitrum-goerli'
  | 'arbitrum-sepolia'
  | 'optimism'
  | 'optimism-goerli'
  | 'optimis-sepolia'
  | 'celo'
  | 'alfajores'
  | 'harmony-s0'
  | 'harmony-test-s0'
  | 'aurora'
  | 'auroratest'
  | 'hedera'
  | 'hederatest'
  | 'zksync'
  | 'zksync-goerli'
  | 'base'
  | 'base-goerli'
  | 'base-sepolia'
  | 'linea-goerli'
  | 'linea'
  | 'mantle'
  | 'mantle-testnet'
  | 'scroll'
  | 'scroll-sepolia'
  | 'meld'
  | 'meld-kanazawa';

Using the @openzeppelin/defender-relay-client package:

const requestParameters = {
  name: 'MyNewRelayer',
  network: 'rinkeby',
  minBalance: BigInt(1e17).toString(),
  policies: {
    whitelistReceivers: ['0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B'],
  },
};

await client.create(requestParameters);

You can also clone an existing relayer to a different network by referencing an existing relayerId in the useAddressFromRelayerId parameter:

const requestParameters = {
  name: 'MyClonedRelayer',
  useAddressFromRelayerId: '201832e2-c612-4283-97c5-31849242c1fe',
  network: 'mainnet',
  minBalance: BigInt(1e17).toString()
};

await client.create(requestParameters);

Making a POST request to the /relayers endpoint:

curl \
  -X POST \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H "X-Api-Key: $KEY" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{...}' \
    "https://defender-api.openzeppelin.com/relayer/relayers"

Relayer Policies

The following relayer policies may be optionally included in both create and update calls:

  • gasPriceCap: Maximum gasPrice or maxFeePerGas in wei

  • whitelistReceivers: An array of all valid addresses with which this relayer can interact; the relayer can send transactions to any address if this policy is undefined.

  • EIP1559Pricing: A boolean indicating the default pricing. If true, transactions will be priced as EIP1559, otherwise, as legacy transactions.

  • privateTransactions: A boolean indicating the default transaction mempool visibility. If true, transactions will be sent via private mempool.

EIP1559Pricing flag is only available for relayers on EIP1559 networks
Private transactions are only enabled for mainnet by using the Flashbots Protect RPC. So, the same key considerations might apply while sending private transactions through Defender (such as uncle bandit risk)

Create Relayer Key

A relayer created via the API will not have any associated relayer keys by default. To create one, you can call the createKey method on the client, which returns a RelayerKey (including the secretKey - available on creation only):

await client.createKey('58b3d255-e357-4b0d-aa16-e86f745e63b9');

Making a POST request to the /relayers/${relayerId}/keys endpoint:

curl \
  -X POST \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H "X-Api-Key: $KEY" \
  -H "Authorization: Bearer $TOKEN" \
    "https://defender-api.openzeppelin.com/relayer/relayers/58b3d255-e357-4b0d-aa16-e86f745e63b9/keys"

An example response:

[
    {
      apiKey: '5Kk12pMA6LjPSppaRAsfAJRAjYeFJzr6',
      createdAt: '2022-04-26T19:49:10.292Z',
      relayerId: '58b3d255-e357-4b0d-aa16-e86f745e63b9',
      keyId: '898a952b-415b-4d4b-882b-3958f11pbeea',
      secretKey: '1pDxUoWYtrAK4xMsKmOITpd1PQofWDFRRTNp2jshDhZ1XhYxDCfAvQ5vyKprFhXQ'
  }
]

Update Relayers

To update a relayer, you can call the update function on the client. This will require the relayer ID and a UpdateRelayerRequest object as parameters:

interface UpdateRelayerRequest {
  name?: string;
  minBalance?: string;
  policies?: UpdateRelayerPoliciesRequest;
}

Using the @openzeppelin/defender-relay-client package:

await client.update('201832e2-c612-4283-97c5-31849242c1fe', { name: 'My Updated Name' });

Making a PUT request to the /relayers endpoint:

curl \
  -X PUT \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H "X-Api-Key: $KEY" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{...}' \
    "https://defender-api.openzeppelin.com/relayer/relayers"
If making the request directly (not with @openzeppelin/defender-relay-client), any policy updates will need to be made via requests to the endpoint directly below with a body type of UpdateRelayerPoliciesRequest.
curl \
  -X PUT \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H "X-Api-Key: $KEY" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{...}' \
    "https://defender-api.openzeppelin.com/relayer/relayers/{relayerId}"

Delete Relayer Key

To delete a key associated with a relayer, you can call the deleteKey method on the client, which returns a message indicating deletion status:

await client.deleteKey('58b3d255-e357-4b0d-aa16-e86f745e63b9', 'j3bru93-k32l-3p1s-pp56-u43f675e92p1');
The second argument to deleteKey is the keyId, not the apiKey. One way to differentiate the two is the keyId contains hyphens. This can be fetched via the list operation described above and is also available in the response on key creation.

Making a DELETE request to the /relayers/${relayerId}/keys/${keyId} endpoint:

curl \
  -X DELETE \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H "X-Api-Key: $KEY" \
  -H "Authorization: Bearer $TOKEN" \
    "https://defender-api.openzeppelin.com/relayer/relayers/58b3d255-e357-4b0d-aa16-e86f745e63b9/keys/j3bru93-k32l-3p1s-pp56-u43f675e92p1"

An example response:

{
  message: 'j3bru93-k32l-3p1s-pp56-u43f675e92p1 deleted'
}

Deletion of a relayer (not just a key) is only available via the Defender console.

Relayer Signer Module Reference

The Relayer Signer Module exposes the following endpoints:

  • txs: send transactions to the Ethereum blockchain and query their status

  • sign: sign arbitrary data with relayer’s private key

  • relayer: retrieve information from the relayer

  • jsonrpc: make json-rpc calls to the Ethereum network

Transactions Endpoint

The /txs endpoint is used for both sending transactions and for retrieving transaction information given a transaction identifier.

Send Transaction

To send a transaction to the Ethereum blockchain submit a POST request with the desired payload. The payload format is as follows:

type Address = string;
type BigUInt = string | number;
type Hex = string;
type Speed = 'safeLow' | 'average' | 'fast' | 'fastest';

interface SendBaseTransactionRequest {
  to?: Address;
  value?: BigUInt;
  data?: Hex;
  gasLimit: BigUInt;
  validUntil?: string;
  isPrivate?: boolean;
}

interface SendSpeedTransactionRequest extends SendBaseTransactionRequest {
  speed: Speed;
}

interface SendLegacyTransactionRequest extends SendBaseTransactionRequest {
  gasPrice: BigUInt;
}

interface SendEIP1559TransactionRequest extends SendBaseTransactionRequest {
  maxFeePerGas: BigUInt;
  maxPriorityFeePerGas: BigUInt;
}

type RelayerTransactionPayload =
  | SendBaseTransactionRequest
  | SendSpeedTransactionRequest
  | SendLegacyTransactionRequest
  | SendEIP1559TransactionRequest;

An example of the request:

DATA='{ "to": "0x179810822f56b0e79469189741a3fa5f2f9a7631", "value": "1", "speed": "fast", "gasLimit": "21000" }'

curl \
  -X POST \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H "X-Api-Key: $KEY" \
  -H "Authorization: Bearer $TOKEN" \
  -d "$DATA" \
    "https://api.defender.openzeppelin.com/txs"

You would receive a response in the following format:

type Address = string;
type BigUInt = string | number;
type Hex = string;
type Speed = 'safeLow' | 'average' | 'fast' | 'fastest';
type Status = 'pending' | 'sent' | 'submitted' | 'inmempool' | 'mined' | 'confirmed' | 'failed';

interface RelayerTransactionBase {
  transactionId: string;
  hash: string;
  to: Address;
  from: Address;
  value?: string;
  data?: string;
  speed: Speed;
  gasLimit: number;
  nonce: number;
  status: Status;
  chainId: number;
  validUntil: string;
  createdAt: string;
  sentAt?: string;
  pricedAt?: string;
  isPrivate?: boolean;
}

interface RelayerLegacyTransaction extends RelayerTransactionBase {
  gasPrice: number;
}

interface RelayerEIP1559Transaction extends RelayerTransactionBase {
  maxPriorityFeePerGas: number;
  maxFeePerGas: number;
}

export type TransactionResponse = RelayerLegacyTransaction | RelayerEIP1559Transaction;

Note the extra transactionId field, which is an internal Defender identifier for the transaction, which is used for querying and replacing.

Transaction Status

Every transaction response will have a status assigned depending on the last time the backend has seen it. Each time Defender monitors a transaction, the status might be updated.

The available status and their corresponding description are the following:

Status pending, sent and submitted are subject to be merged in the future. Avoid to rely on them since backwards compatibility is not assured.
type Status =
  /**
   * Temporary pre-sent status.
   * The transaction has been received but not yet signed.
   */
  | 'pending'
  /**
   * Sent to queue.
   * The transaction is already signed, enqueued and waiting to be broadcasted to the network.
   */
  | 'sent'
  /**
   * Transaction broadcasted.
   * The transaction has been broadcasted to the network providers.
   */
  | 'submitted'
  /**
   * Seen on mempool.
   * It's confirmed that the transaction is in the mempool of at least one of the nodes of the network.
   */
  | 'inmempool'
  /**
   * Transaction mined.
   * A mined block has included the transaction.
   */
  | 'mined'
  /**
   * Enough confirmations passed.
   * The transaction has been kept in a mined block after N new mined blocks, where N depends on the chain.
   */
  | 'confirmed'
  /**
   * Terminal failure.
   * The transaction couldn't get processed for unexpected reasons.
   */
  | 'failed';

Replace Transaction

To replace a pending transaction make a PUT request to the txs endpoint with the Defender transactionId or nonce, not with the transaction hash.

An example of the request:

curl \
  -X PUT \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H "X-Api-Key: $KEY" \
  -H "Authorization: Bearer $TOKEN" \
  -d "$DATA" \
    "https://api.defender.openzeppelin.com/txs/$IDorNONCE"

The request payload and the response are the same as that of sending a transaction.

Query Transaction

To retrieve a transaction status and data make a GET request to the txs endpoint with the Defender transactionId, not with the transaction hash.

An example of the request:

curl \
  -X GET \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H "X-Api-Key: $KEY" \
  -H "Authorization: Bearer $TOKEN" \
    "https://api.defender.openzeppelin.com/txs/$ID"

An example of the response:

{
   "chainId":4,
   "hash":"0xcef95469a9f02757f0968ec8c11449ae5e7486073075381dcd62bacec9e5d627",
   "transactionId":"affba150-e563-441e-ae49-04bd6050979a",
   "value":"0x1",
   "maxFeePerGas":1000000000,
   "maxPriorityFeePerGas":150000000,
   "gasLimit":21000,
   "to":"0x179810822f56b0e79469189741a3fa5f2f9a7631",
   "from":"0xbce0b5b71668e42d908e387b68dba91789c932b8",
   "data":"0x",
   "nonce":160,
   "status":"mined",
   "speed":"fast"
   "validUntil": "2022-10-27T03:22:09.566Z",
   "sentAt": "2022-10-26T19:22:10.067Z",
   "createdAt": "2022-10-26T19:22:10.067Z",
   "isPrivate": false
}

List Transactions

To retrieve a list of recent transactions sent from your relayer, make a GET request to the txs. You can optionally set since, limit, and status (mined, pending, or failed) as query parameters.

An example of the request:

curl \
  -X GET \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H "X-Api-Key: $KEY" \
  -H "Authorization: Bearer $TOKEN" \
    "https://api.defender.openzeppelin.com/txs?status=pending&limit=5"

An example of the response:

[{
   "chainId":4,
   "hash":"0xcef95469a9f02757f0968ec8c11449ae5e7486073075381dcd62bacec9e5d627",
   "transactionId":"affba150-e563-441e-ae49-04bd6050979a",
   "value":"0x1",
   "maxFeePerGas":1000000000,
   "maxPriorityFeePerGas":150000000,
   "gasLimit":21000,
   "to":"0x179810822f56b0e79469189741a3fa5f2f9a7631",
   "from":"0xbce0b5b71668e42d908e387b68dba91789c932b8",
   "data":"0x",
   "nonce":160,
   "status":"mined",
   "speed":"fast"
   "validUntil": "2022-10-27T03:22:09.566Z",
   "sentAt": "2022-10-26T19:22:10.067Z",
   "createdAt": "2022-10-26T19:22:10.067Z",
   "isPrivate": false
}]

Sign Endpoint

To sign arbitrary messages according to the EIP-191 Standard (prefixed by \x19Ethereum Signed Message:\n) with your Relay private key make a POST request to /sign with a payload containing the hex string to sign. The payload format is:

interface SignMessagePayload {
  message: Hex;
}

An example of the request:

DATA='{ "message": "0x0123456789abcdef" }'

curl \
  -X POST \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H "X-Api-Key: $KEY" \
  -H "Authorization: Bearer $TOKEN" \
  -d "$DATA" \
    "https://api.defender.openzeppelin.com/sign"

You would receive a response in the following format:

interface SignedMessagePayload {
  sig: Hex;
  r: Hex;
  s: Hex;
  v: number;
}

An example of the response:

{
   "r":"0x819b2645a0b73494724dac355e6ecfc983d94597b533d34fe3ecd0277046a1eb",
   "s":"0x3b73c695b47dd275d17246d86bbfe35f112a7bdb5bf4a5a1a8e22fe37dfd005a",
   "v":44,
   "sig":"0x819b2645a0b73494724dac355e6ecfc983d94597b533d34fe3ecd0277046a1eb3b73c695b47dd275d17246d86bbfe35f112a7bdb5bf4a5a1a8e22fe37dfd005a2c"
}

Sign Typed Data Endpoint

To sign typed data according to the [EIP-712 Specification](https://eips.ethereum.org/EIPS/eip-712) with your Relay private key make a POST request to /sign with a payload containing the domainSeparator and the hashStruct(message). Both should be 32-bytes long as they’re hashes theirselves. The payload format is:

interface SignTypedDataPayload {
  domainSeparator: Hex;
  hashStructMessage: Hex;
}

An example of the request:

DATA='{ "domainSeparator": "0x0123456789abcdef...", "hashStructMessage": "0x0123456789abcdef..." }'

curl \
  -X POST \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H "X-Api-Key: $KEY" \
  -H "Authorization: Bearer $TOKEN" \
  -d "$DATA" \
    "https://api.defender.openzeppelin.com/sign-typed-data"

You would receive a response in the following format:

interface SignedMessagePayload {
  sig: Hex;
  r: Hex;
  s: Hex;
  v: number;
}

An example of the response:

{
   "r":"0x819b2645a0b73494724dac355e6ecfc983d94597b533d34fe3ecd0277046a1eb",
   "s":"0x3b73c695b47dd275d17246d86bbfe35f112a7bdb5bf4a5a1a8e22fe37dfd005a",
   "v":44,
   "sig":"0x819b2645a0b73494724dac355e6ecfc983d94597b533d34fe3ecd0277046a1eb3b73c695b47dd275d17246d86bbfe35f112a7bdb5bf4a5a1a8e22fe37dfd005a2c"
}

Relayer Endpoint

To retrieve a relayer’s data with the Relay API make a GET request to the /relayer endpoint.

An example of the request:

curl \
  -X GET \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H "X-Api-Key: $KEY" \
  -H "Authorization: Bearer $TOKEN" \
    "https://api.defender.openzeppelin.com/relayer"

You would receive a response in the following format:

interface RelayerModel {
  relayerId: string;
  name: string;
  address: string;
  network: string;
  paused: boolean;
  createdAt: string;
  pendingTxCost: string;
}

An example of the response:

{
   "relayerId":"d5484fb1-df83-4659-9903-16d57d41f188",
   "name":"Rinkeby",
   "address":"0x71764d6450c2b710fc3e4ee5b7a038d1e7e4fc29",
   "network":"rinkeby",
   "createdAt":"2020-11-02T18:00:00.212Z",
   "paused":false,
   "pendingTxCost":"0"
}

JSON RPC Endpoint

To make a JSON RPC call to the network of your Relay, make a POST request to the /relayer/jsonrpc endpoint with the method name and parameters. Note that event filter methods and websocket subscriptions are not supported.

An example of the request:

DATA='{ "jsonrpc":"2.0","method":"eth_getBalance","params":["0x407d73d8a49eeb85d32cf465507dd71d507100c1","latest"],"id":1 }'

curl \
  -X POST \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H "X-Api-Key: $KEY" \
  -H "Authorization: Bearer $TOKEN" \
  -d "$DATA" \
    "https://api.defender.openzeppelin.com/relayer/jsonrpc"

An example of the response:

{
  "id": 1,
  "jsonrpc": "2.0",
  "result": "0x0234c8a3397aab58"
}