OpenZeppelin Relayer

Overview

This software is in alpha stage. Use in production environments at your own risk.

OpenZeppelin Relayer is a service that provides infrastructure to relay transactions to the EVM & Non-EVM networks. It is designed to be used as a backend for dApps that need to interact with these networks.

Features

  • Multi-Chain Support: Interact with multiple blockchain networks, including Solana and EVM-based chains.

  • Transaction Relaying: Submit transactions to supported blockchain networks efficiently.

  • Transaction Signing: Securely sign transactions using configurable key management.

  • Transaction Fee Estimation: Estimate transaction fees for better cost management.

  • Solana Gasless Transactions: Support for gasless transactions on Solana, enabling users to interact without transaction fees.

  • Transaction Nonce Management: Handle nonce management to ensure transaction order.

  • Transaction Status Monitoring: Track the status of submitted transactions.

  • SDK Integration: Easily interact with the relayer through our companion JavaScript/TypeScript SDK.

  • Extensible Architecture: Easily add support for new blockchain networks.

  • Configurable Network Policies: Define and enforce network-specific policies for transaction processing.

  • Metrics and Observability: Monitor application performance using Prometheus and Grafana.

  • Docker Support: Deploy the relayer using Docker for both development and production environments.

Supported Networks

  • Solana

  • EVM (Basic support for Ethereum and other EVM-compatible chains):

    • Ethereum (Mainnet, Sepolia, Holesky)

    • Optimism (Mainnet, Sepolia)

    • Worldchain (Sepolia)

For information about our development plans and upcoming features, see Project Roadmap.

To get started immediately, see Quickstart.

Technical Overview

%%{init: { 'theme': 'base', 'themeVariables': { 'background': '#ffffff', 'mainBkg': '#ffffff', 'primaryBorderColor': '#cccccc' } }}%% flowchart TB subgraph "Clients" client[API/SDK] end subgraph "OpenZeppelin Relayer" subgraph "API Layer" api[API Routes & Controllers] middleware[Middleware] end subgraph "Domain Layer" domain[Domain Logic] relayer[Relayer Services] policies[Policy Enforcement] end subgraph "Infrastructure" repositories[Repositories] jobs[Job Queue System] signer[Signer Services] provider[Network Providers] end subgraph "Services Layer" transaction[Transaction Services] vault[Vault Services] webhook[Webhook Notifications] monitoring[Monitoring & Metrics] end subgraph "Configuration" config_files[Config Files] env_vars[Environment Variables] end end subgraph "External Systems" blockchain[Blockchain Networks] redis[Redis] vault_ext[HashiCorp Vault] metrics[Prometheus/Grafana] notification[Notification Services] end %% Client connections client -- "HTTP Requests" --> api %% API Layer connections api -- "Processes requests" --> middleware middleware -- "Validates & routes" --> domain %% Domain Layer connections domain -- "Uses" --> relayer domain -- "Enforces" --> policies relayer -- "Processes" --> transaction %% Services Layer connections transaction -- "Signs with" --> signer transaction -- "Connects via" --> provider transaction -- "Queues jobs" --> jobs webhook -- "Notifies" --> notification monitoring -- "Collects" --> metrics signer -- "May use" --> vault %% Infrastructure connections repositories -- "Stores data" --> redis jobs -- "Processes async" --> redis vault -- "Secrets management" --> vault_ext provider -- "Interacts with" --> blockchain %% Configuration connections config_files -- "Configures" --> domain env_vars -- "Configures" --> domain %% Styling classDef apiClass fill:#f9f,stroke:#333,stroke-width:2px classDef domainClass fill:#bbf,stroke:#333,stroke-width:2px classDef infraClass fill:#bfb,stroke:#333,stroke-width:2px classDef serviceClass fill:#fbf,stroke:#333,stroke-width:2px classDef configClass fill:#fbb,stroke:#333,stroke-width:2px classDef externalClass fill:#ddd,stroke:#333,stroke-width:1px class api,middleware apiClass class domain,relayer,policies domainClass class repositories,jobs,signer,provider infraClass class transaction,vault,webhook,monitoring serviceClass class config_files,env_vars configClass class blockchain,redis,vault_ext,metrics,notification externalClass

Project Structure

The project follows a standard Rust project layout:

openzeppelin-relayer/
├── src/
│   ├── api/              # Route and controllers logic
│   ├── bootstrap/        # Service initialization logic
│   ├── config/           # Configuration logic
│   ├── constants/        # Constant values used in the system
│   ├── domain/           # Domain logic
│   ├── jobs/             # Asynchronous processing logic (queueing)
│   ├── logging/          # Logs File rotation logic
│   ├── metrics/          # Metrics logic
│   ├── models/           # Data structures and types
│   ├── repositories/     # Configuration storage
│   ├── services/         # Services logic
│   └── utils/            # Helper functions
│
├── config/               # Configuration files
├── tests/                # Integration tests
├── docs/                 # Documentation
├── scripts/              # Utility scripts
├── examples/             # Configuration examples
├── helpers/              # Rust helper scripts
└── ... other root files (Cargo.toml, README.md, etc.)

For detailed information about each directory and its contents, see Project Structure Details.

Getting Started

Prerequisites

  • Rust 2021 edition

  • Docker (optional, for containerized deployment)

Ready-to-Use Example Configurations

For quick setup with various configurations, check the examples directory in our GitHub repository:

  • basic-example: Simple setup with Redis

  • basic-example-logging: Configuration with file-based logging

  • basic-example-metrics: Setup with Prometheus and Grafana metrics

  • vault-secret-signer: Using HashiCorp Vault for key management

  • vault-transit-signer: Using Vault Transit for secure signing

Each example includes a README with step-by-step instructions and Docker Compose configuration.

Install Locally

  1. Clone the repository:

    git clone https://github.com/openzeppelin/openzeppelin-relayer
    cd openzeppelin-relayer
  2. Verify you have sodium libs installed. If not, follow these instructions:

  3. Install dependencies:

    cargo build

Running the Relayer

Option 1: Run Locally

cargo run
Before executing the command, ensure that the .env and config.json files are configured as detailed in the Configuration References section.

Option 2: Run with Docker

The Relayer can be run as either a development or production container using the corresponding Dockerfile (Dockerfile.development or Dockerfile.production).

Step 1: Configure Environment

  • Edit .env at the root of the repository to adjust environment variables

  • The appropriate .env file will be included during image build

Step 2: Build the Image

You can build using Docker Compose (v2).

# Default build
docker compose build

# Or, for a leaner image (and using Dockerfile.production)
DOCKERFILE=Dockerfile.production docker compose build

Step 3: Run the Container

Use Docker Compose to run the container:

docker compose up -d

For production runs, you can use:

DOCKERFILE=Dockerfile.production docker compose up -d

Configuration References

Most configuration files should live under ./config, including the signer configurations, under ./config/keys. Please ensure appropriate access permissions on all configuration files (for ./config/keys/*, we recommend 0500.

The configuration system consists of two main components:

  1. config.json: Contains relayer definitions, signer configurations, and network policies

  2. .env file: Contains environment variables like API keys and connection strings

Both files must be properly configured before starting the application. Changes to either file require restarting the container to take effect.

For quick setup examples with pre-configured files, see the examples directory in our GitHub repository.

Environment configuration (.env)

This defines some base configurations for the Relayer application:

Copy the example environment file and update values according to your needs

cp .env.example .env

This table lists the environment variables and their default values.

Environment Variable Default Value Accepted Values Description

RUST_LOG

info

info, debug, warn, error, trace

Log level.

CONFIG_DIR

./config

<any relative file path where config.json is located>

Relative path of directory where config files reside

CONFIG_FILE_NAME

config.json

<any file name>

File Name of the configuration file.

RATE_LIMIT_RPS

100

<any value>

Rate limit for the API in requests per second.

RATE_LIMIT_BURST_SIZE

300

<any value>

Rate limit burst size.

API_KEY

``

string,

API key to use for authentication to the relayer server. Minimum length 32 characters.

WEBHOOK_SIGNING_KEY

``

string

Signing key to use for webhook notifications. Minimum length 32 characters.

LOG_MODE

stdout

stdout, file

Write logs either to console or to file.

LOG_DATA_DIR

./logs

<any file path>

Directory to persist log files on host.

LOG_MAX_SIZE (in bytes)

1073741824

<any value in bytes>

Size after which logs needs to be rolled.

METRICS_ENABLED

false

bool

Enable metrics server for external tools to scrape metrics.

METRICS_PORT

8081

<any tcp port (preferably choose non-privileged ports i.e. (1024-65535))>

Port to use for metrics server.

REDIS_URL

redis://localhost:6379

<redis connection string>

Redis connection URL for the relayer.

REDIS_CONNECTION_TIMEOUT_MS

10000

<timeout in milliseconds>

Connection timeout for Redis in milliseconds.

ENABLE_SWAGGER

false

true, false

Enable or disable Swagger UI for API documentation.

KEYSTORE_PASSPHRASE

``

<keystore passphrase>

Passphrase for the keystore file used for signing transactions.

Environment configuration example

.env file config example:

RUST_LOG=DEBUG
CONFIG_DIR=./config
CONFIG_FILE_NAME=config.json
WEBHOOK_SIGNING_KEY=e1d42480-6f74-4d0b-85f4-b7f0bb690fae
API_KEY=5eefd216-0e44-4ca7-b421-2925f90d30d5
RATE_LIMIT_RPS=100
RATE_LIMIT_BURST_SIZE=300
METRICS_ENABLED=true
METRICS_PORT=8081
REDIS_URL=redis://localhost:6379
REDIS_CONNECTION_TIMEOUT_MS=10000
ENABLE_SWAGGER=false
KEYSTORE_PASSPHRASE=your_keystore_passphrase

Main configuration file (config.json)

This file can exist in any directory, but the default location is ./config/config.json.

Copy the example config file and update values according to your needs

cp config/config.example.json config/config.json

There are 3 important sections in this file:

  • Signers: Defines transaction signing methods.

  • Notifications: Sets up status alerts

  • Relayers: Configures networks, notifications channels, policies & singers.

1. Signers

  • signers array, which must contain, at least, one valid signer configuration:

Example:

"signers": [
  {
    "id": "my_id",
    "type": "local",
    "config": {
      "path": "config/keys/local-signer.json",
      "passphrase": {
        "type": "env",
        "value": "KEYSTORE_PASSPHRASE"
      }
    }
  }
]

Supported signer types:

  • test: temporary key only for testing

  • local: keystore file signer

  • vault: vault secret signer

  • vault_cloud: hosted vault secret signer

  • vault_transit: vault transit signer

Available configuration fields

Field Type Description

id

String

Unique id for the signer

type

String

Type of signer (see supported signer types)

config

Map

signer type related config

local type config:

Field Type Description

path

String

path to the signer json file. Should be under the ./config directory

passphrase.type

String

Type of passphrase (env or plain)

passphrase.value

String

Passphrase value, env variable name, …​

vault type config:

Field Type Description

address

String

Specifies the Vault API endpoint.

role_id.type

String

Type of value source (env or plain)

role_id.value

String

The Vault AppRole role identifier value, or the environment variable name where the AppRole role identifier is stored.

secret_id.type

String

Type of value source (env or plain)

secret_id.value

String

The Vault AppRole role secret value, or the environment variable name where the AppRole secret value is stored.

key_name

String

The name of the cryptographic key within Vault’s Secret engine that is used for signing operations.

mount_point

String

The mount point for the Secrets engine in Vault. Defaults to secret if not explicitly specified. Optional.

vault_cloud type config:

Field Type Description

client_id

String

The client identifier used to authenticate with Vault Cloud.

client_secret.type

String

Type of value source (env or plain)

client_secret.value

String

The Vault secret value, or the environment variable name where the secret value is stored.

org_id

String

The organization ID for your Vault Cloud account.

project_id

String

The project ID that uniquely identifies your Vault Cloud project.

app_name

String

The name of the application integrating with Vault Cloud.

key_name

String

The name of the cryptographic key used for signing or encryption operations in Vault Cloud.

vault_transit type config:

Field Type Description

address

String

Specifies the Vault API endpoint.

role_id.type

String

Type of value source (env or plain)

role_id.value

String

The Vault AppRole role identifier value, or the environment variable name where the AppRole role identifier is stored.

secret_id.type

String

Type of value source (env or plain)

secret_id.value

String

The Vault AppRole role secret value, or the environment variable name where the AppRole secret value is stored.

key_name

String

The name of the cryptographic key within Vault’s Transit engine that is used for signing operations.

mount_point

String

The mount point for the Transit secrets engine in Vault. Defaults to transit if not explicitly specified. Optional.

namespace

String

The Vault namespace for API calls. This is used only in Vault Enterprise environments. Optional.

pubkey

String

Public key of the cryptographic key within Vault’s Transit engine that is used for signing operations

2. Notifications

  • notifications array, which should contain, at least, one valid configuration:

"notifications": [
  {
    "id": "notification-test",
    "type": "webhook",
    "url": "https://webhook.site/f95cf78d-742d-4b21-88b7-d683e6fd147b",
    "signing_key": {
      "type": "env",
      "value": "WEBHOOK_SIGNING_KEY"
    }
  }
]

Available configuration fields

Field Type Description

id

String

Unique id for the notification

type

String

Type of notification (only webhook available, for now)

url

String

Notification URL

signing_key.type

String

Type of key used in signing the notification (env or plain)

signing_key.value

String

Signing key value, env variable name, …​

3. Relayers

  • relayers array, containing at least one valid relayer configuration:

"relayers": [
  {
    "id": "solana-testnet",
    "name": "Solana Testnet",
    "paused": false,
    "notification_id": "notification-test",
    "signer_id": "local-signer",
    "network_type": "solana",
    "network": "testnet",
    "policies": {
      "allowed_programs": [
          "11111111111111111111111111111111",
          "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
          "BPFLoaderUpgradeab1e11111111111111111111111"
        ]
    }
  },
]

Available configuration fields

Field Type Description

id

String

Unique id for the relayer

name

String

Human readable name for the relayer

paused

Boolean

Whether or not the relayer is paused (true, false)

notification_id

String

ID of a configured notification object

signer_id

String

ID of a configured signer

network_type

String

Type of network the relayer will connect to (evm, solana)

network

String

Network the relayer will connect to. Please refer to Supported networks.

custom_rpc_urls

list

Optional custom RPC URLs for the network. If provided, this will be used instead of the public RPC URLs. This is useful for using your own RPC node or a paid service provider. The first url of the list is going to be used as the default

policies

list

Overrides default policies. Please refer to the Policies table

Supported networks

Network type Network Description

solana

mainnet-beta

Solana’s mainnet

solana

devnet

Solana’s devnet

solana

testnet

Solana’s testnet

evm

mainnet

Ethereum mainnet

evm

sepolia

Ethereum testnet

evm

holesky

Ethereum testnet

Policies

Network type Policy Type Description

solana, evm

min_balance

unsigned 128

Minimum balance (in lamports or wei) required for the relayer to operate. Optional.

solana

fee_payment_strategy

enum(user,relayer)

Specifies who pays the fee. "user" (default) means the sender pays; "relayer" means the relayer pays. For "user", RPC methods add an instruction to transfer SPL tokens (calculated from the current SOL price plus a configurable margin) from the user to the relayer, ensuring fees are sustainably covered in tokens rather than SOL.

solana

fee_margin_percentage

f32

Additional margin percentage added to estimated transaction fees to account for price fluctuations. For example, a value of 10 will add 10% to estimated fees. Optional.

solana

max_allowed_fee_lamports

unsigned 64

Maximum allowed fee (in lamports) for a transaction. Optional.

solana

allowed_tokens

Vector<AllowedToken>

List of allowed tokens. Only these tokens are supported if provided. Optional.

solana

allowed_programs

Vector<String>

List of allowed programs by their identifiers. Only these programs are supported if provided.

solana

allowed_accounts

Vector<String>

List of allowed accounts by their public keys. The relayer will only operate with these accounts if provided.

solana

disallowed_accounts

Vector<String>

List of disallowed accounts by their public keys. These accounts will be explicitly blocked.

solana

max_tx_data_size

unsigned 16

Maximum transaction size. Optional.

solana

max_signatures

unsigned 8

Maximum supported signatures. Optional.

evm

gas_price_cap

unsigned 128

Specify a maximum gas price for every transaction sent with the Relayer. When enabled, any transaction exceeding the cap will have its gasPrice or maxFeePerGas overwritten. (Optional)

evm

whitelist_receivers

Vector<String>

A list of authorized contracts for each transaction sent using the Relayer. Transactions will be rejected if the destination address is not on the list. (Optional)

Config file full example

Full config/config.json example with evm and solana relayers definitions using keystore signer:

{
  "relayers": [
    {
      "id": "sepolia-example",
      "name": "Sepolia Example",
      "network": "sepolia",
      "paused": false,
      "notification_id": "notification-example",
      "signer_id": "local-signer",
      "network_type": "evm",
      "custom_rpc_urls": ["https://your-private-rpc-url.example.com"],
      "policies": {
        "gas_price_cap": 30000000000000,
        "eip1559_pricing": true
      }
    },
    {
      "id": "solana-example",
      "name": "Solana Example",
      "network": "devnet",
      "paused": false,
      "notification_id": "notification-example",
      "signer_id": "local-signer",
      "network_type": "solana",
      "custom_rpc_urls": ["https://your-private-solana-rpc.example.com"],
      "policies": {
        "fee_payment_strategy": "user",
        "min_balance": 0,
        "allowed_tokens": [
          {
            "mint": "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr",
            "max_allowed_fee": 100000000
          },
          {
            "mint": "So11111111111111111111111111111111111111112"
          }
        ]
      }
    }
  ],
  "notifications": [
    {
      "id": "notification-example",
      "type": "webhook",
      "url": "https://webhook.site/1384d4d9-21b1-40a0-bcd1-d3f3b66be955",
      "signing_key": {
        "type": "env",
        "value": "WEBHOOK_SIGNING_KEY"
      }
    }
  ],
  "signers": [
    {
      "id": "local-signer",
      "type": "local",
      "config": {
        "path": "config/keys/local-signer.json",
        "passphrase": {
          "type": "env",
          "value": "KEYSTORE_PASSPHRASE"
        }
      }
    }
  ]
}
```

RPC URL Configuration

The relayer supports two ways to configure RPC URLs:

  1. Public RPC URLs: These are the default RPC endpoints provided by the network. They are automatically selected based on the network configuration.

  2. Custom RPC URL: You can specify a custom RPC URL using the custom_rpc_urls field in the relayer configuration. This is useful when you want to:

    • Use your own RPC node

    • Use a paid service provider for better reliability and performance

    • Override the default public RPC URLs

    • Access custom network endpoints

When both are available, the relayer will: 1. First attempt to use the custom_rpc_urls if configured 2. Fall back to the public RPC URLs if no custom URL is configured or if it’s not accessible

When using custom RPC URLs:

  • Ensure the URL is secure (HTTPS) when accessing over public networks

  • Keep your API keys and authentication tokens secure

  • Test the RPC endpoint’s reliability and performance before deploying to production

Important Considerations

This software is in alpha stage. Use in production environments at your own risk.

Deployment Considerations

The OpenZeppelin Relayer is designed to function as a backend service and is not meant to be directly exposed to the public internet. To protect the service from unauthorized access, deploy it behind your own secure backend infrastructure—such as a reverse proxy or firewall—and restrict access to trusted internal components only. Direct exposure can increase the risk of exploitation and security breaches.

Support

For support or inquiries, contact us on Telegram.

License

This project is licensed under the GNU Affero General Public License v3.0 - see the LICENSE file for details.

Security

For security concerns, please refer to our Security Policy.