IBC v2 Relayer

May 5, 2026 · View on GitHub

IBC v2 Relayer is a relaying service for the IBC v2 Protocol. The relayer supports interoperating between a Cosmos-based chain and major EVM networks.

Relaying Sequence

Relaying Sequence

Supported Features

  • Compatible with all major EVM chains (Ethereum, Base, Optimism, Arbitrum, Polygon, and more)
  • Request-driven design for configurable, on-demand relaying
  • Transaction failure retry support
    • Re-orgs
    • Out-of-gas
    • Inadequate gas price
    • Tx network propagation fails to reach leader
    • Invalid by the network, but valid by the submitting node
  • Transaction Tracking API
  • Remote signing support
  • Configurable packet delivery latency via batching
  • Ability to blacklist addresses (ex: OFAC)
  • Transaction cost tracking

Getting Started

Run the commands in this document from the relayer repo root (apps/relayer in this monorepo) unless noted otherwise.

Prerequisites

  • Go 1.24+
  • Docker and Docker Compose
  • A running proof API (attestor) service
  • RPC endpoints for the chains you want to relay between

Local Development

  1. Start Postgres and run migrations:
docker-compose up -d
  1. Create a local config file (see Configuration Reference below).

  2. Create a local keys file (see Local Signing below).

  3. Run the relayer:

make relayer-local

The relayer will start:

  • gRPC API server on the address configured in relayer_api.address
  • Prometheus metrics server on the configured address
  • Relay dispatcher polling for new transfers

CLI Flags

FlagDefaultDescription
--config./config/local/config.ymlPath to relayer config file
--ibcv2-relayingtrueEnable/disable the relay dispatcher

Running Tests

make test

Database Migrations

Database migrations must be run before starting the relayer. The relayer expects the database schema to already exist.

Running Migrations

Local Development:

docker-compose up -d

This starts PostgreSQL and runs migrations automatically.

Using the migrate CLI:

# Install: https://github.com/golang-migrate/migrate
migrate -path ./db/migrations -database "postgres://relayer:relayer@localhost:42500/relayer?sslmode=disable" up

Using the relayer migrations container:

docker run --rm --network host <registry>/relayer-migrate:<version> \
  -database "postgres://relayer:relayer@localhost:42500/relayer?sslmode=disable" \
  up

Using the generic migrate Docker image with local files:

docker run --rm -v $(pwd)/db/migrations:/migrations --network host migrate/migrate \
  -path /migrations \
  -database "postgres://relayer:relayer@localhost:42500/relayer?sslmode=disable" \
  up

Migration Files

Migration files are located in ./db/migrations/.

Design

Design

The relayer has three main components - the gRPC server which clients use to interact with the relayer, a Postgres db, and the core relayer. The gRPC server populates the db with packets, which the core relayer monitors and updates as it progresses in relaying those packets.

API Interface

The relayer serves a gRPC server which clients use to specify what packets to relay and track packet relaying progress.

service RelayerApiService {
    // Relay is used to specify a source tx hash for packets the relayer should relay.
    // The relayer will identify all packets created by the transaction and attempt to relay them all.
    rpc Relay(RelayRequest) returns (RelayResponse) {}

    // The status endpoint is used to track the progress of packet relaying.
    // It takes a transaction hash and returns the status of any relevant packets the relayer is aware of.
    // The transaction must first have been passed to the relay endpoint.
    rpc Status(StatusRequest) returns (StatusResponse) {}
}

message StatusRequest {
    string tx_hash = 1;
    string chain_id = 2;
}

enum TransferState {
    TRANSFER_STATE_UNKNOWN = 0;
    TRANSFER_STATE_PENDING = 1;
    TRANSFER_STATE_COMPLETE = 2;
    TRANSFER_STATE_FAILED = 3;
}

message TransactionInfo {
    string tx_hash = 1;
    string chain_id = 2;
}

message PacketStatus {
    TransferState state = 1;
    uint64 sequence_number = 2;
    string source_client_id = 3;
    TransactionInfo send_tx = 4;
    TransactionInfo recv_tx = 5;
    TransactionInfo ack_tx = 6;
    TransactionInfo timeout_tx = 7;
}

message StatusResponse {
    repeated PacketStatus packet_statuses = 1;
}

message RelayRequest {
    string tx_hash = 1;
    string chain_id = 2;
}

message RelayResponse {}

Observability

Alert setup guidance for customers lives in ./docs/alerts.md.

TypeNameDescription
MetricRelayer api request countPaginated by method and response code
MetricRelayer api request latencyPaginated by method
MetricTransfer countPaginated by source, destination chain, and transfer state
MetricRelayer gas balancePaginated by chain and gas token
MetricRelayer gas balance stateA gauge where each value represents a gas balance state. 0 = ok, 1 = warning, 2 = critical. The thresholds that define each state are defined in the relayer configuration. Paginated by chain
MetricExternal request countPaginated by endpoint, method and response code
MetricExternal request latencyPaginated by endpoint and method
MetricTransactions submitted counterPaginated by node response success status and chain
MetricTransaction retry counterPaginated by source and destination chain
MetricTransactions confirmed counterPaginated by execution success and chain
MetricTransaction gas cost counterPaginated by chain
MetricRelay latencyTime between send tx and ack/timeout tx. Paginated by source and destination chain
MetricDetected client update required counterPaginated by chain
MetricClient updated counterPaginated by chain
MetricExcessive relay latency counterIncremented anytime a transfer is pending for longer than some configured threshold. Paginated by source and destination chain
AlertExcessive relay latencyShould alert whenever the excessive relay latency counter increases
AlertExcessive gas usageShould alert whenever the gas cost counter increases faster than some threshold
AlertLow gas balanceShould alert whenever the relayer gas balance state metric is in the warning or critical state

Configuration Reference

The relayer is configured via a YAML file. The example below is a representative starting point, not an exhaustive schema reference.

Full Example

postgres:
  hostname: localhost
  port: "42500"
  database: relayer

metrics:
  prometheus_address: "0.0.0.0:8888"

relayer_api:
  address: "0.0.0.0:9000"

ibcv2_proof_api:
  grpc_address: "localhost:50051"
  grpc_tls_enabled: false

signing:
  # Local signing — set keys_path to use local key file
  keys_path: "./config/local/ibcv2keys.json"
  # Remote signing — set grpc_address to use remote signer (takes precedence over keys_path)
  # grpc_address: "localhost:50052"
  # cosmos_wallet_key: "cosmos-wallet-id"
  # evm_wallet_key: "evm-wallet-id"
  # svm_wallet_key: "svm-wallet-id"

coingecko:
  base_url: "https://pro-api.coingecko.com/api/v3"
  api_key: "your-api-key"
  requests_per_minute: 30
  cache_refresh_interval: 5m

chains:
  cosmoshub:
    chain_name: "cosmoshub"
    chain_id: "cosmoshub-4"
    type: "cosmos"
    environment: "mainnet"
    gas_token_symbol: "ATOM"
    gas_token_coingecko_id: "cosmos"
    gas_token_decimals: 6
    supported_bridges:
      - ibcv2
    cosmos:
      ibcv2_tx_fee_denom: "uatom"
      ibcv2_tx_fee_amount: 5000
      rpc: "https://cosmos-rpc.example.com"
      rpc_basic_auth_var: "COSMOS_RPC_AUTH"
      grpc: "cosmos-grpc.example.com:9090"
      grpc_tls_enabled: true
      address_prefix: "cosmos"
    ibcv2:
      counterparty_chains:
        "08-wasm-0": "1"   # client ID on cosmoshub → ethereum chain ID
      finality_offset: 10
      recv_batch_size: 50
      recv_batch_timeout: 10s
      recv_batch_concurrency: 3
      ack_batch_size: 50
      ack_batch_timeout: 10s
      ack_batch_concurrency: 3
      timeout_batch_size: 50
      timeout_batch_timeout: 10s
      timeout_batch_concurrency: 3
      should_relay_success_acks: true
      should_relay_error_acks: true
    signer_gas_alert_thresholds:
      ibcv2:
        warning_threshold: "5000000"    # in smallest denom units (uatom)
        critical_threshold: "1000000"

  ethereum:
    chain_name: "ethereum"
    chain_id: "1"
    type: "evm"
    environment: "mainnet"
    gas_token_symbol: "ETH"
    gas_token_coingecko_id: "ethereum"
    gas_token_decimals: 18
    supported_bridges:
      - ibcv2
    evm:
      rpc: "https://eth-mainnet.g.alchemy.com/v2/your-key"
      rpc_basic_auth_var: "ETH_RPC_AUTH"
      contracts:
        ics_26_router_address: "0x..."
        ics_20_transfer_address: "0x..."
      gas_fee_cap_multiplier: 1.5
      gas_tip_cap_multiplier: 1.2
    ibcv2:
      counterparty_chains:
        "tendermint-0": "cosmoshub-4"   # client ID on ethereum → cosmoshub chain ID
      recv_batch_size: 100
      recv_batch_timeout: 10s
      recv_batch_concurrency: 3
      ack_batch_size: 100
      ack_batch_timeout: 10s
      ack_batch_concurrency: 3
      timeout_batch_size: 100
      timeout_batch_timeout: 10s
      timeout_batch_concurrency: 3
      should_relay_success_acks: true
      should_relay_error_acks: true
    signer_gas_alert_thresholds:
      ibcv2:
        warning_threshold: "1000000000000000000"   # 1 ETH
        critical_threshold: "500000000000000000"   # 0.5 ETH

Section Reference

postgres

FieldTypeDescription
hostnamestringPostgres host
portstringPostgres port
databasestringDatabase name

Database credentials are read from environment variables POSTGRES_USER and POSTGRES_PASSWORD (default: relayer/relayer).

metrics

FieldTypeDescription
prometheus_addressstringAddress to serve Prometheus metrics (e.g. 0.0.0.0:8888)

relayer_api

FieldTypeDescription
addressstringAddress for the gRPC API server to listen on (e.g. 0.0.0.0:9000)

ibcv2_proof_api

Connection to the proof api service that generates relay transactions.

FieldTypeDescription
grpc_addressstringgRPC address of the proof API
grpc_tls_enabledboolEnable TLS for the proof API connection

signing

Signing configuration. The mode is inferred from which fields are set:

  • If grpc_address is set → remote signing (ignores keys_path)
  • Else if keys_path is set → local signing from key file
  • Else → fatal error at startup
FieldTypeDescription
keys_pathstringPath to local signing keys JSON file
grpc_addressstringgRPC address of the remote signer service. If set, takes precedence over keys_path
cosmos_wallet_keystringWallet ID for Cosmos chain signing (remote signer only)
evm_wallet_keystringWallet ID for EVM chain signing (remote signer only)
svm_wallet_keystringWallet ID for Solana chain signing (remote signer only)

coingecko (optional)

Used for tracking transaction gas costs in USD. If omitted, gas cost tracking is disabled.

FieldTypeDescription
base_urlstringCoingecko API base URL
api_keystringAPI key
requests_per_minuteintRate limit
cache_refresh_intervaldurationHow often to refresh cached prices

chains.<chain_key>

Each entry under chains defines a chain the relayer can interact with.

FieldTypeDescription
chain_namestringHuman-readable chain name. Used primarily in metrics.
chain_idstringChain identifier (numeric for EVM, string for Cosmos)
typestringcosmos, evm, or svm
environmentstringmainnet or testnet
gas_token_symbolstringGas token ticker symbol
gas_token_coingecko_idstringCoingecko ID for gas cost tracking (optional)
gas_token_decimalsuint8Decimal places for the gas token
supported_bridges[]stringList of bridge types (currently only ibcv2)

chains.<chain_key>.cosmos

Required when type: cosmos.

FieldTypeDescription
gas_pricefloat64Gas price for fee estimation. Mutually exclusive with ibcv2_tx_fee_amount
ibcv2_tx_fee_denomstringFee denom for ibcv2 txs (required if ibcv2_tx_fee_amount is set)
ibcv2_tx_fee_amountuint64Fixed fee amount for ibcv2 txs. Mutually exclusive with gas_price
rpcstringTendermint RPC endpoint
rpc_basic_auth_varstringEnvironment variable name containing basic auth credentials for RPC
grpcstringgRPC endpoint
grpc_tls_enabledboolEnable TLS for gRPC
address_prefixstringBech32 address prefix (e.g. cosmos, osmo)

chains.<chain_key>.evm

Required when type: evm.

FieldTypeDescription
rpcstringEthereum JSON-RPC endpoint
rpc_basic_auth_varstringEnvironment variable name containing basic auth credentials for RPC
contracts.ics_26_router_addressstringICS26 Router contract address
contracts.ics_20_transfer_addressstringICS20 Transfer contract address
gas_fee_cap_multiplierfloat64Multiplier applied to the estimated gas fee cap (optional)
gas_tip_cap_multiplierfloat64Multiplier applied to the estimated gas tip cap (optional)

chains.<chain_key>.ibcv2

IBC v2 relay configuration for this chain. Defines which counterparty chains to relay for and batching behavior.

FieldTypeDescription
counterparty_chainsmap[string]stringMaps client IDs on this chain to their counterparty chain IDs. Only connections listed here will be relayed
finality_offsetuint64Number of blocks to wait after a tx before considering it finalized. If omitted, uses the chain's native finality (e.g. finalized block tag for EVM)
recv_batch_sizeintMax packets to accumulate before flushing a recv batch
recv_batch_timeoutdurationMax time to wait for recv packets to accumulate before flushing
recv_batch_concurrencyintMax concurrent recv batches being processed
ack_batch_sizeintMax packets to accumulate before flushing an ack batch
ack_batch_timeoutdurationMax time to wait for ack packets to accumulate before flushing
ack_batch_concurrencyintMax concurrent ack batches being processed
timeout_batch_sizeintMax packets to accumulate before flushing a timeout batch
timeout_batch_timeoutdurationMax time to wait for timeout packets to accumulate before flushing
timeout_batch_concurrencyintMax concurrent timeout batches being processed
should_relay_success_acksboolWhether to relay acknowledgements for successful packet deliveries
should_relay_error_acksboolWhether to relay acknowledgements for failed packet deliveries

chains.<chain_key>.signer_gas_alert_thresholds

FieldTypeDescription
ibcv2.warning_thresholdstringGas balance (in smallest denom) at which the metric reports warning state
ibcv2.critical_thresholdstringGas balance (in smallest denom) at which the metric reports critical state

Signing

The relayer supports two signing modes, configured via the signing block in the YAML config. The mode is inferred from which fields are populated:

  • If grpc_address is set → remote signing (ignores keys_path)
  • Else if keys_path is set → local signing from key file
  • Else → fatal error at startup

Local Signing

Set signing.keys_path to point to a JSON file containing private keys. The format is a map of chain IDs to key objects:

signing:
  keys_path: "./config/local/ibcv2keys.json"
{
  "1": {
    "private_key": "0xabc123..."
  },
  "cosmoshub-4": {
    "private_key": "abc123..."
  }
}

For EVM chains, the private key is a hex-encoded ECDSA private key. For Cosmos chains, it is a hex-encoded secp256k1 private key.

Remote Signing

For production deployments, the relayer can delegate signing to an external gRPC service. This keeps private keys isolated from the relayer process.

Configuration

signing:
  grpc_address: "signer.internal:50052"
  cosmos_wallet_key: "my-cosmos-wallet"
  evm_wallet_key: "my-evm-wallet"
  svm_wallet_key: "my-svm-wallet"

Authentication

The remote signer connection uses the SERVICE_ACCOUNT_TOKEN environment variable as a bearer token in gRPC metadata for authenticating requests to the signing service.

Signer Service Interface

The remote signer must implement the following gRPC service:

service SignerService {
    rpc GetChains(GetChainsRequest) returns (GetChainsResponse) {}
    rpc GetWallet(GetWalletRequest) returns (GetWalletResponse) {}
    rpc GetWallets(GetWalletsRequest) returns (GetWalletsResponse) {}
    rpc Sign(SignRequest) returns (SignResponse) {}
}

The Sign RPC accepts transaction payloads for EVM, Cosmos, and Solana chains and returns the appropriate signature format for each:

  • EVM: Accepts serialized tx bytes + chain ID, returns (r, s, v) signature components
  • Cosmos: Accepts sign doc bytes, returns a raw signature
  • Solana: Accepts a base64-encoded transaction, returns a raw signature

The full proto definition is at proto/signer/signerservice.proto.

Implementing a Compatible Signer Service

To implement your own signer service, refer to the proto file for message formats and signature requirements.

Methods used by the relayer:

  • GetWallet - called at startup to retrieve public keys (uses Cosmos, Ethereum, and Solana pubkey types)
  • Sign - called for every transaction

Methods not used by the relayer:

  • GetChains - can return an empty response
  • GetWallets - can return an empty response

Sign payload types used by the relayer:

  • EvmTransaction / EvmTransactionSignature
  • CosmosTransaction / CosmosTransactionSignature

Sign payload types not used by the relayer:

  • RawMessage / RawMessageSignature
  • SolanaTransaction / SolanaTransactionSignature

Authentication: The relayer sends the SERVICE_ACCOUNT_TOKEN environment variable as a bearer token in the authorization gRPC metadata header. Validation is optional.