Relay Off-Chain
The Symbiotic Relay operates as a distributed middleware layer that facilitates:
- Validator Set Management: Derives and maintains validator sets across different epochs based on on-chain state
- Signature Aggregation: Collects individual validator signatures and aggregates them using BLS signatures or zero-knowledge proofs
- Cross-Chain Coordination: Manages validator sets across multiple EVM-compatible blockchains
Architecture
The relay consists of several key components:
- P2P Layer: Uses libp2p with GossipSub for decentralized communication
- Signer Nodes: Sign messages using BLS/ECDSA keys
- Aggregator Nodes: Collect and aggregate signatures with configurable policies
- Committer Nodes: Submit aggregated proofs to settlement chains
- API Server: Exposes gRPC API for external clients
For detailed architecture information, see here.
Configure
Create a config.yaml file with the following structure:
# Logging
log:
level: "debug" # Options: debug, info, warn, error
mode: "pretty" # Options: json, text, pretty
# Storage
storage-dir: ".data" # Directory for persistent data
circuits-dir: "" # Path to ZK circuits (optional, empty disables ZK proofs)
# API Server
api:
listen: ":8080" # API server address
verbose-logging: false # Enable verbose API logging
# Metrics (optional)
metrics:
listen: ":9090" # Metrics endpoint address
pprof: false # Enable pprof debug endpoints
# Driver Contract
driver:
chain-id: 31337 # Chain ID where driver contract is deployed
address: "0x..." # Driver contract address
# Secret Keys
secret-keys:
- namespace: "symb" # Namespace for the key
key-type: 0 # 0=BLS-BN254, 1=ECDSA
key-id: 15 # Key identifier
secret: "0x..." # Private key hex
- namespace: "evm"
key-type: 1
key-id: 31337
secret: "0x..."
- namespace: "p2p"
key-type: 1
key-id: 1
secret: "0x..."
# Alternatively, use keystore
# keystore:
# path: "/path/to/keystore.json"
# password: "your-password"
# Signal Configuration, used for internal messages and event queues
signal:
worker-count: 10 # Number of signal workers
buffer-size: 20 # Signal buffer size
# Cache Configuration, used for in memorylookups for db queries
cache:
network-config-size: 10 # Network config cache size
validator-set-size: 10 # Validator set cache size
# Sync Configuration, sync signatures and proofs over p2p to recover missing information
sync:
enabled: true # Enable P2P sync
period: 5s # Sync period
timeout: 1m # Sync timeout
epochs: 5 # Number of epochs to sync
# Key Cache, used for fast public key lookups
key-cache:
size: 100 # Key cache size
enabled: true # Enable key caching
# P2P Configuration
p2p:
listen: "/ip4/0.0.0.0/tcp/8880" # P2P listen address
bootnodes: # List of bootstrap nodes (optional)
- /dns4/node1/tcp/8880/p2p/...
dht-mode: "server" # Options: auto, server, client, disabled, default: server (ideally should not change)
mdns: true # Enable mDNS local discovery (useful for local networks)
# EVM Configuration
evm:
chains: # List of settlement chain RPC endpoints
- "http://localhost:8545"
- "http://localhost:8546"
max-calls: 30 # Max calls in multicall batches
# Aggregation Policy
aggregation-policy-max-unsigners: 50 # Max unsigners for low-cost policyConfigure via Command-Line Flags
You can override config file values with command-line flags:
./relay_sidecar \
--config config.yaml \
--log.level debug \
--storage-dir /var/lib/relay \
--api.listen ":8080" \
--p2p.listen "/ip4/0.0.0.0/tcp/8880" \
--driver.chain-id 1 \
--driver.address "0x..." \
--secret-keys "symb/0/15/0x...,evm/1/31337/0x..." \
--evm.chains "http://localhost:8545"Configure via Environment Variables
Environment variables use the SYMB_ prefix with underscores instead of dashes and dots:
export SYMB_LOG_LEVEL=debug
export SYMB_LOG_MODE=pretty
export SYMB_STORAGE_DIR=/var/lib/relay
export SYMB_API_LISTEN=":8080"
export SYMB_P2P_LISTEN="/ip4/0.0.0.0/tcp/8880"
export SYMB_DRIVER_CHAIN_ID=1
export SYMB_DRIVER_ADDRESS="0x..."
./relay_sidecar --config config.yamlConfiguration Priority
Configuration is loaded in the following order (highest priority first):
- Command-line flags
- Environment variables (with
SYMB_prefix) - Configuration file (specified by
--config)
Example
For reference, see how configurations are generated in the E2E setup:
# See the template in e2e/scripts/sidecar-start.sh (lines 11-27)
cat e2e/scripts/sidecar-start.shDownload and Run
Pre-built Docker images are available from Docker Hub:
Pull the image
The latest image:
docker pull symbioticfi/relay:latestOr a specific version:
docker pull symbioticfi/relay:<tag>Run the relay sidecar
docker run -v $(pwd)/config.yaml:/config.yaml \
symbioticfi/relay:latest \
--config /config.yamlAPI
The relay exposes both gRPC and HTTP/JSON REST APIs for interacting with the network:
gRPC API
HTTP/JSON REST API Gateway
The relay includes an optional HTTP/JSON REST API gateway that translates HTTP requests to gRPC:
- Swagger File
- Endpoints: All gRPC methods accessible via RESTful HTTP at
/api/v1/*
Enable via configuration:
api:
http-gateway: trueOr via command-line flag:
./relay_sidecar --api.http-gateway=trueClient Libraries
| Language | Repository |
|---|---|
| Go | relay |
| TypeScript | relay-client-ts |
| Rust | relay-client-rs |
Snippets
Check out multiple simple snippets how to use the clients mentioned above:
import (
relay "github.com/symbioticfi/relay/api/client/v1"
)
func main() {
relayClient = relay.NewSymbioticClient(conn)
epochInfos, _ := relayClient.GetLastAllCommitted(ctx, &relay.GetLastAllCommittedRequest{})
suggestedEpoch := epochInfos.SuggestedEpochInfo.GetLastCommittedEpoch()
signMessageResponse, _ := relayClient.SignMessage(ctx, &relay.SignMessageRequest{
KeyTag: 15, // default key tag - BN254
Message: encode(taskId),
RequiredEpoch: &suggestedEpoch,
})
listenProofsRequest := &relay.ListenProofsRequest{
StartEpoch: suggestedEpoch,
}
proofsStream, _ := relayClient.ListenProofs(ctx, listenProofsRequest)
aggregationProof = []byte{}
while proofResponse, _ := proofsStream.Recv() {
if proofResponse.GetRequestId() == signMessageResponse.GetRequestId() {
aggregationProof = proofResponse.GetAggregationProof().GetProof()
break
}
}
appContract.CompleteTask(taskId, signMessageResponse.Epoch, aggregationProof)
}Integration Examples
For a complete end-to-end examples application using the relay, see:
| Repository | Description |
|---|---|
| Symbiotic Super Sum | A simple task-based network |
| Cosmos Relay SDK | An application built using the Cosmos SDK and Symbiotic Relay |
