Skip to content
LogoLogo

Relay Utils

relay_utils is the command-line tool that drives the operational steps of a Symbiotic Relay network — the work that sits between deploying the Relay contracts and running the Relay sidecar:

  • Network operators use it to commit the genesis validator set that bootstraps the network.
  • Validators/operators use it to generate their signing keys, register them in KeyRegistry, and check whether they made it into the validator set.

What you need

Every command below uses the same handful of values. Here is exactly what each one is and where to get it:

ValueWhat it isWhere to get it
<ValSetDriver address>The network's on-chain config contract — the single source of truth the CLI reads everything else from.The network operator gives it to you. (After deployment it is written into the network's my-relay-deploy.toml.)
<driver_chain_id>Chain ID where ValSetDriver is deployed.From the network operator, alongside the driver address.
<vpp_chain_id>Chain ID where the network's VotingPowerProvider lives (often the same as the driver chain).From the network operator, or read it: cast call <ValSetDriver> "getVotingPowerProviders()((uint64,address)[])" --rpc-url <rpc>.
<rpc_url> (and <rpc1>,<rpc2>,…)A JSON-RPC endpoint for each chain you transact on (driver, VPP, settlement chains).Your own node or a provider (e.g. Alchemy). Supported chains and Symbiotic Core addresses: Addresses.
<tag> (key tags)Which validator keys the network requires — you generate and register one per tag.Ask the network operator, or read on-chain: cast call <ValSetDriver> "getRequiredKeyTags()(uint8[])" --rpc-url <rpc> (the header-signing tag is getRequiredHeaderKeyTag()).
<operator_private_key>Your operator EOA key — signs the on-chain transactions and pays gas.Your own key. Fund the address with native gas on every chain you transact on.

Operator path

Joining an existing network, in order:

  1. Find the required key tags — they live in the driver's config (getRequiredKeyTags); you generate one relay key per tagGenerate and manage keys.
  2. Register each key in KeyRegistryRegister a key.
  3. Register your operator in the VotingPowerProviderRegister as an operator. On auto-deploy networks this also creates your vault.
  4. Opt into your vault and deposit stake — Core opt-ins + the curator's allocation limits — so your voting power is active.
  5. Run the sidecarRelay Off-Chain, then confirm with Check operator status.

Generate and manage keys

Keys live in a local keystore (--path, default ./keystore.jks, unlocked with --password).

Generate an EVM key for paying gas on a chain, or import an existing one:

bash
# generate a fresh EVM key for the voting-power-provider chain
utils keys add --generate --evm --chain-id <vpp_chain_id> --password <pwd>
 
# or import an existing private key
utils keys add --evm --chain-id <vpp_chain_id> --private-key 0x... --password <pwd>

Generate one relay signing key per key tag the network requires (ask the network operator which tags are required):

bash
utils keys add --relay --key-tag <tag> --generate --password <pwd>

Inspect and clean up:

bash
utils keys list --password <pwd>
utils keys remove --evm --chain-id <vpp_chain_id> --password <pwd>

Register a key in KeyRegistry

Each relay key must be registered on-chain so the network can attribute signatures to your operator. Run once per required key tag:

bash
utils operator register-key \
  --driver.address <ValSetDriver address> \
  --driver.chainid <driver_chain_id> \
  --voting-provider-chain-id <vpp_chain_id> \
  --chains <rpc_url> \
  --key-tag <tag> \
  --secret-keys <vpp_chain_id>:<operator_private_key> \
  --password <pwd>

This builds the proof of key ownership and calls KeyRegistry.setKey(...). You don't pass the KeyRegistry address — relay_utils resolves it from the ValSetDriver. To see it directly (e.g. to inspect the contract on a block explorer), read it off the driver:

bash
cast call <ValSetDriver address> "getKeysProvider()((uint64,address))" --rpc-url <rpc_url>

The returned (chainId, address) is the chain and address of the network's KeyRegistry.

Register as an operator

Register your operator in the network's VotingPowerProvider. This adds you to the network's operator set; once you also hold the required keys and stake, you become eligible for the derived validator set. It calls VotingPowerProvider.registerOperator():

bash
utils operator register-operator \
  --driver.address <ValSetDriver address> \
  --driver.chainid <driver_chain_id> \
  --voting-provider-chain-id <vpp_chain_id> \
  --chains <rpc_url> \
  --secret-keys <vpp_chain_id>:<operator_private_key>

As with key registration, the VotingPowerProvider address is resolved from the ValSetDriver — you don't pass it. To see it directly (e.g. to register through a Safe, or to look up your vault below):

bash
cast call <ValSetDriver address> "getVotingPowerProviders()((uint64,address)[])" --rpc-url <rpc_url>

Verify the registration:

bash
cast call <VotingPowerProvider address> "isOperatorRegistered(address)(bool)" <operator_address> --rpc-url <rpc_url>

To let a third party submit the transaction (gasless), generate an EIP-712 signature with utils operator register-operator-with-signature and hand the artifact to the submitter.

Commit the genesis validator set

A network operator runs this once to bootstrap the network: it derives the first validator set and commits the genesis header to every settlement chain. Until genesis is committed, sidecars have no starting snapshot to agree on.

bash
utils network generate-genesis \
  --driver.address <ValSetDriver address> \
  --driver.chainid <driver_chain_id> \
  --chains <rpc1>,<rpc2>,... \
  --secret-keys <chain_id>:<key>,... \
  --commit

Omit --commit for a dry run — it prints the derived header without sending transactions, which is the fastest way to sanity-check your ValSetDriver configuration. Inspect the live network with:

bash
utils network info \
  --driver.address <ValSetDriver address> \
  --driver.chainid <driver_chain_id> \
  --chains <rpc1>,<rpc2>,...

Check operator status

Confirm your operator is registered, included, and carries voting power:

bash
utils operator info \
  --driver.address <ValSetDriver address> \
  --driver.chainid <driver_chain_id> \
  --voting-provider-chain-id <vpp_chain_id> \
  --chains <rpc_url>

If you registered keys and deposited stake but still show zero voting power, the missing piece is almost always a vault delegator limit — see Manage Allocations.

Next Steps

Relay Off-Chain

Run the relay sidecar so your operator starts signing and aggregating.

Relay On-Chain

Deploy the Relay contracts your network and operators reference here.