The FHEVM Relayer bridges FHEVM host chains (e.g. Ethereum) and the Zama Gateway. It handles public decryption, user decryption, input proof verification, and FHE key material distribution. Anyone can self-host a relayer for permissionless access to the FHEVM network.
Running your own relayer gives you permissionless, independent access to the FHEVM network without depending on third-party infrastructure.
- Rust toolchain + Cargo -- install via rustup
- Docker + Docker Compose v2 -- for local PostgreSQL
- Foundry (
cast) -- for wallet operations during onboarding (install) - A funded wallet with ETH + $ZAMA tokens
- ETH on the Gateway chain for gas -- Bridge from Arbitrum One to Gateway via the Zama bridge
- $ZAMA tokens on the Gateway chain -- Buy $ZAMA then bridge from Ethereum L1 to the Gateway chain via the Zama Bridge
- An Ethereum L1 RPC endpoint
All commands run from relayer/.
make db-startStarts a local PostgreSQL instance via Docker Compose (port 5433).
make db-migratemake preflight-mainnetThis interactive onboarding wizard:
- Creates
config/local.mainnet.yamlfrom the example template if missing - Prompts for your wallet private key
- Derives your wallet address
- Checks ETH balance
- Checks $ZAMA balance
- Checks that your wallet has approved the ProtocolPayment contract to spend $ZAMA (offers to grant max allowance via
make approve-payment-mainnetif missing)
make run-mainnetStarts the relayer with cargo run using the mainnet config. Requires the local PostgreSQL to be running.
make healthChecks /liveness, /healthz, /version, and /metrics endpoints.
Testnet follows the same flow with different tokens:
- ETH: Bridge from Arbitrum Sepolia via the Testnet bridge
- $ZAMA: Ask the relayer team (not self-service on Testnet)
make db-start
make db-migrate
make preflight-testnet
make run-testnet
make healthThe config file stores the private key at gateway.tx_engine.private_key. Best practices:
- Never commit
config/local.mainnet.yamlorconfig/local.testnet.yamlto version control (they are already in.gitignore) - Environment variable override: Set
APP_GATEWAY__TX_ENGINE__PRIVATE_KEY=0x...to avoid storing the key in the config file at all - Use a dedicated wallet for relayer operations
Key operator-tunable fields in the config file:
| Field | Description | Default |
|---|---|---|
http.endpoint |
API listen address | 0.0.0.0:3000 |
log.format |
Log format (compact, pretty, json) |
pretty |
gateway.tx_engine.tx_throttlers.*.per_seconds |
TX throttle rate per operation type | 20 |
storage.app_pool.max_connections |
Max database connections (app pool) | 10 |
storage.cron.timeout_cron_interval |
How often the timeout worker runs | 60s |
storage.cron.public_decrypt_timeout |
Timeout for public decryption requests | 30m |
storage.cron.user_decrypt_timeout |
Timeout for user decryption requests | 30m |
storage.cron.input_proof_timeout |
Timeout for input proof requests | 30m |
storage.cron.expiry_enabled |
Enable automatic data cleanup | false |
storage.cron.public_decrypt_expiry |
Retention for public decrypt records | 365d |
storage.cron.user_decrypt_expiry |
Retention for user decrypt records | 7d |
storage.cron.input_proof_expiry |
Retention for input proof records | 7d |
http.retry_after.max_seconds |
Max Retry-After header value | 300 |
http.enable_admin_endpoint |
Enable runtime config via /admin/config |
false |
Configuration is hierarchical: YAML file -> environment variables (APP_ prefix, __ nesting) -> CLI args.
- Prometheus metrics at
:9898(GET /metrics) - Application health at
:3000(/liveness,/healthz) - See
src/metrics/docs_and_dashboards/for Grafana dashboard queries and metric descriptions
See the Troubleshooting section in the main README.