```
#### Run the relay sidecar
```bash [bash]
docker run -v $(pwd)/config.yaml:/config.yaml \
symbioticfi/relay:latest \
--config /config.yaml
```
:::
:::info
Docker Hub: [https://hub.docker.com/r/symbioticfi/relay](https://hub.docker.com/r/symbioticfi/relay)
:::
## API
The relay exposes both gRPC and HTTP/JSON REST APIs for interacting with the network:
### gRPC API
- [**API Documentation**](https://github.com/symbioticfi/relay/tree/dev/docs/api/v1/doc.md)
- [**Proto Definitions**](https://github.com/symbioticfi/relay/blob/dev/api/proto/v1/api.proto)
- [**Go Client**](https://github.com/symbioticfi/relay/tree/dev/api/client/v1/)
- [**Client Examples**](https://github.com/symbioticfi/relay/tree/dev/api/client/examples/)
### HTTP/JSON REST API Gateway
The relay includes an optional HTTP/JSON REST API gateway that translates HTTP requests to gRPC:
- [**Swagger File**](https://github.com/symbioticfi/relay/tree/dev/docs/api/v1/api.swagger.json)
- **Endpoints**: All gRPC methods accessible via RESTful HTTP at `/api/v1/*`
Enable via configuration:
```yaml [config.yaml]
api:
http-gateway: true
```
Or via command-line flag:
```bash [bash]
./relay_sidecar --api.http-gateway=true
```
### Client Libraries
| Language | Repository |
| ---------- | -------------------------------------------------------------------- |
| Go | [relay](https://github.com/symbioticfi/relay/tree/dev/api/client/v1) |
| TypeScript | [relay-client-ts](https://github.com/symbioticfi/relay-client-ts) |
| Rust | [relay-client-rs](https://github.com/symbioticfi/relay-client-rs) |
:::note
Use the HTTP gateway for language-agnostic access if needed.
:::
### Snippets
Check out multiple simple snippets how to use the clients mentioned above:
:::code-group
```go [myApp.go]
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)
}
```
```go [myApp.ts]
import { createClient } from "@connectrpc/connect";
import { SymbioticAPIService } from "@symbioticfi/relay-client-ts";
import {
GetLastAllCommittedRequestSchema,
SignMessageRequestSchema,
ListenProofsRequestSchema,
} from "@symbioticfi/relay-client-ts";
import { create } from "@bufbuild/protobuf";
async function main() {
const relayClient = createClient(SymbioticAPIService, transport);
const getLastAllCommittedResponse = await relayClient.getLastAllCommitted(create(GetLastAllCommittedRequestSchema));
const suggestedEpoch = getLastAllCommittedResponse.suggestedEpochInfo.lastCommittedEpoch;
const signMessageRequest = create(SignMessageRequestSchema, {
keyTag: 15, // default key tag - BN254
message: encode(taskId),
requiredEpoch: suggestedEpoch,
});
const signMessageResponse = await relayClient.signMessage(signMessageRequest);
const listenProofsRequest = create(ListenProofsRequestSchema, { startEpoch: suggestedEpoch });
const proofsStream = await relayClient.listenProofs(listenProofsRequest);
let aggregationProof;
for await (const proofResponse of proofsStream) {
if (proofResponse.requestId === signMessageResponse.requestId) {
aggregationProof = proofResponse.aggregationProof?.proof;
break;
}
}
await appContract.completeTask(taskId, signMessageResponse.epoch, aggregationProof);
}
```
```go [my_app.rs]
use symbiotic_relay_client::generated::api::proto::v1::{
GetLastAllCommittedRequest, SignMessageRequest, ListenProofsRequest,
symbiotic_api_service_client::SymbioticApiServiceClient,
};
#[tokio::main]
async fn main() -> Result<(), Box> {
let mut relay_client = SymbioticApiServiceClient::new(channel);
let epoch_infos_response = relay_client.get_last_all_committed(tonic::Request::new(GetLastAllCommittedRequest {})).await?;
let epoch_infos_data = epoch_infos_response.into_inner();
let mut suggested_epoch = epoch_infos_data.suggested_epoch_info.last_committed_epoch;
let sign_request = tonic::Request::new(SignMessageRequest {
key_tag: 15, // default key tag - BN254
message: encode(task_id).into(),
required_epoch: suggested_epoch,
});
let sign_response = relay_client.sign_message(sign_request).await?;
let sign_data = sign_response.into_inner();
let listen_proofs_request = tonic::Request::new(ListenProofsRequest { start_epoch: suggested_epoch });
let proofs_stream = relay_client.listen_proofs(listen_proofs_request).await?.into_inner();
let mut aggregation_proof = None;
while let Some(proof_response) = proofs_stream.next().await {
match proof_response {
Ok(proof_data) => {
if proof_data.request_id == sign_data.request_id {
if let Some(proof) = proof_data.aggregation_proof {
aggregation_proof = Some(proof.proof);
break;
}
}
}
Err(e) => { break; }
}
}
appContract.complete_task(task_id, sign_data.epoch, aggregation_proof.unwrap()).await?;
}
```
:::
## Integration Examples
For a complete end-to-end examples application using the relay, see:
| Repository | Description |
| ------------------------------------------------------------------------- | ------------------------------------------------------------- |
| [Symbiotic Super Sum](https://github.com/symbioticfi/symbiotic-super-sum) | A simple task-based network |
| [Cosmos Relay SDK](https://github.com/symbioticfi/cosmos-relay-sdk) | An application built using the Cosmos SDK and Symbiotic Relay |
## Next Steps
}
href="/v1/integrate/networks/helpful-core-contracts-endpoints"
/>
---
## /v1/integrate/networks/relay-onchain
---
description: "The Symbiotic Relay system implements a complete signature aggregation workflow from validator set derivation through on-chain commitment of the ValSetHeader..."
---
import { Card1 } from "../../../components/Card1";
# Relay On-Chain
The Symbiotic Relay system implements a complete signature aggregation workflow from validator set derivation through on-chain commitment of the ValSetHeader data structure. This enables provable attestation checks on any chain for arbitrary data signed by the validator set quorum.
The system provides a modular smart contract framework that lets you manage validator sets dynamically, handle cryptographic keys, aggregate signatures, and commit cross-chain state.
## Architecture
Symbiotic provides a set of predefined smart contracts representing the following modules:
- [`VotingPowerProvider`](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/voting-power/) - provides basic data about operators, vaults, and their voting power, enabling various onboarding schemes
- [`KeyRegistry`](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/key-registry/) - verifies and manages operators' keys; currently, these key types are supported:
- [`BlsBn254`](https://github.com/symbioticfi/relay-contracts/blob/main/src/libraries/keys/KeyBlsBn254.sol) ([signature verification](https://github.com/symbioticfi/relay-contracts/blob/main/src/libraries/sigs/SigBlsBn254.sol))
- [`EcdsaSecp256k1`](https://github.com/symbioticfi/relay-contracts/blob/main/src/libraries/keys/KeyEcdsaSecp256k1.sol) ([signature verification](https://github.com/symbioticfi/relay-contracts/blob/main/src/libraries/sigs/SigEcdsaSecp256k1.sol))
- [`ValSetDriver`](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/valset-driver/) - used by the off-chain part of Symbiotic Relay for validator set derivation and maintenance
- [`Settlement`](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/settlement/) - requires committing a compressed validator set (header) each epoch, but allows verifying signatures made by the validator set; currently supports the following verification mechanics:
- [`SimpleVerifier`](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/settlement/sig-verifiers/SigVerifierBlsBn254Simple.sol) - requires the whole validator set as an input during verification, but in a compressed and efficient way, making it the best choice for up to 125 validators
- [`ZKVerifier`](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/settlement/sig-verifiers/SigVerifierBlsBn254ZK.sol) - uses ZK verification made with [gnark](https://github.com/Consensys/gnark), allowing larger validator sets with an almost constant verification gas cost
:::info
For Symbiotic Core's `NetworkMiddlewareService` contract, set the `VotingPowerProvider` contract as the middleware.
:::
### Permissions
Relay contracts have three ready-to-use permission models:
- [`OzOwnable`](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/common/permissions/OzOwnable.sol) - allows setting an owner address that can perform permissioned actions
- Based on [OpenZeppelin's `Ownable` contract](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol)
- [`OzAccessControl`](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/common/permissions/OzAccessControl.sol) - enables role-based permissions for each action
- Based on [OpenZeppelin's `AccessControl` contract](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/AccessControl.sol)
- Roles can be assigned to function selectors using `_setSelectorRole(bytes4 selector, bytes32 role)`
- [`OzAccessManaged`](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/common/permissions/OzAccessManaged.sol) - controls which callers can access specific functions
- Based on [OpenZeppelin's `AccessManaged` contract](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/manager/AccessManager.sol)
To use these permission models, inherit one of the above contracts and add the `checkPermission` modifier to functions that require access control.
:::info
Only one permission model can be used at a time.
:::
#### Examples
##### ValSetDriver with OzOwnable
```solidity [MyValSetDriver.sol]
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
import {ValSetDriver} from "../src/modules/valset-driver/ValSetDriver.sol";
import {OzOwnable} from "../src/modules/common/permissions/OzOwnable.sol";
import {IEpochManager} from "../src/interfaces/modules/valset-driver/IEpochManager.sol";
import {IValSetDriver} from "../src/interfaces/modules/valset-driver/IValSetDriver.sol";
contract MyValSetDriver is ValSetDriver, OzOwnable {
function initialize(
ValSetDriverInitParams memory valSetDriverInitParams,
address owner
) public virtual initializer {
__ValSetDriver_init(valSetDriverInitParams);
__OzOwnable_init(ozOwnableInitParams);
}
}
```
##### Settlement with OzAccessControl
```solidity [MySettlement.sol]
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
import {Settlement} from "../src/modules/settlement/Settlement.sol";
import {OzAccessControl} from "../src/modules/common/permissions/OzAccessControl.sol";
import {ISettlement} from "../src/interfaces/modules/settlement/ISettlement.sol";
contract MySettlement is Settlement, OzAccessControl {
bytes32 public constant SET_SIG_VERIFIER_ROLE = keccak256("SET_SIG_VERIFIER_ROLE");
bytes32 public constant SET_GENESIS_ROLE = keccak256("SET_GENESIS_ROLE");
function initialize(
SettlementInitParams memory settlementInitParams,
address defaultAdmin
) public virtual initializer {
__Settlement_init(settlementInitParams);
_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);
_setSelectorRole(ISettlement.setSigVerifier.selector, SET_SIG_VERIFIER_ROLE);
_setSelectorRole(ISettlement.setGenesis.selector, SET_GENESIS_ROLE);
}
}
```
### VotingPowerProvider Extensions
Multiple voting power extensions can be combined to achieve different properties of the VotingPowerProvider:
- [`OperatorsWhitelist`](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/voting-power/extensions/OperatorsWhitelist.sol) - only whitelisted operators can register
- [`OperatorsBlacklist`](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/voting-power/extensions/OperatorsBlacklist.sol) - blacklisted operators are unregistered and are forbidden to return back
- [`OperatorsJail`](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/voting-power/extensions/OperatorsJail.sol) - operators can be jailed for some amount of time and register back after that
- [`SharedVaults`](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/voting-power/extensions/SharedVaults.sol) - shared (with other networks) vaults (like the ones with NetworkRestakeDelegator) can be added
- [`OperatorVaults`](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/voting-power/extensions/OperatorVaults.sol) - vaults that are attached to a single operator can be added
- [`MultiToken`](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/voting-power/extensions/MultiToken.sol) - possible to add new supported tokens on the go
- [`OpNetVaultAutoDeploy`](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/voting-power/extensions/OpNetVaultAutoDeploy.sol) - enable auto-creation of the configured by you vault on each operator registration
- Ready bindings are also available for [slashing](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/voting-power/extensions/BaseSlashing.sol) and [rewards](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/voting-power/extensions/BaseRewards.sol)
#### Examples
##### Single-Operator Vaults Added by Owner
```solidity [MyVotingPowerProvider.sol]
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
import {VotingPowerProvider} from "../src/modules/voting-power/VotingPowerProvider.sol";
import {OzOwnable} from "../src/modules/common/permissions/OzOwnable.sol";
import {EqualStakeVPCalc} from "../src/modules/voting-power/common/voting-power-calc/EqualStakeVPCalc.sol";
import {OperatorVaults} from "../src/modules/voting-power/extensions/OperatorVaults.sol";
contract MyVotingPowerProvider is VotingPowerProvider, OzOwnable, EqualStakeVPCalc, OperatorVaults {
constructor(address operatorRegistry, address vaultFactory) VotingPowerProvider(operatorRegistry, vaultFactory) {}
function initialize(
VotingPowerProviderInitParams memory votingPowerProviderInitParams,
OzOwnableInitParams memory ozOwnableInitParams
) public virtual initializer {
__VotingPowerProvider_init(votingPowerProviderInitParams);
__OzOwnable_init(ozOwnableInitParams);
}
}
```
##### Shared Vaults Whitelisted under AccessControl
```solidity [MyVotingPowerProvider.sol]
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
import {VotingPowerProvider} from "../src/modules/voting-power/VotingPowerProvider.sol";
import {OzAccessControl} from "../src/modules/common/permissions/OzAccessControl.sol";
import {EqualStakeVPCalc} from "../src/modules/voting-power/common/voting-power-calc/EqualStakeVPCalc.sol";
import {SharedVaults} from "../src/modules/voting-power/extensions/SharedVaults.sol";
import {OperatorsWhitelist} from "../src/modules/voting-power/extensions/OperatorsWhitelist.sol";
import {ISharedVaults} from "../src/interfaces/modules/voting-power/extensions/ISharedVaults.sol";
contract MyVotingPowerProvider is VotingPowerProvider, OzAccessControl, EqualStakeVPCalc, SharedVaults, OperatorsWhitelist {
bytes32 public constant REGISTER_SHARED_VAULT = keccak256("REGISTER_SHARED_VAULT");
bytes32 public constant UNREGISTER_SHARED_VAULT = keccak256("UNREGISTER_SHARED_VAULT");
bytes32 public constant SET_WHITELIST_STATUS = keccak256("SET_WHITELIST_STATUS");
bytes32 public constant WHITELIST_OPERATOR = keccak256("WHITELIST_OPERATOR");
bytes32 public constant UNWHITELIST_OPERATOR = keccak256("UNWHITELIST_OPERATOR");
constructor(address operatorRegistry, address vaultFactory) VotingPowerProvider(operatorRegistry, vaultFactory) {}
function initialize(
VotingPowerProviderInitParams memory votingPowerProviderInitParams,
address defaultAdmin
) public virtual initializer {
__VotingPowerProvider_init(votingPowerProviderInitParams);
_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);
_setSelectorRole(ISharedVaults.registerSharedVault.selector, REGISTER_SHARED_VAULT);
_setSelectorRole(ISharedVaults.unregisterSharedVault.selector, UNREGISTER_SHARED_VAULT);
_setSelectorRole(ISharedVaults.setWhitelistStatus.selector, SET_WHITELIST_STATUS);
_setSelectorRole(ISharedVaults.whitelistOperator.selector, WHITELIST_OPERATOR);
_setSelectorRole(ISharedVaults.unwhitelistOperator.selector, UNWHITELIST_OPERATOR);
}
function _registerOperatorImpl(
address operator
) internal virtual override(VotingPowerProvider, OperatorsWhitelist) {
super._registerOperatorImpl(operator);
}
}
```
### VotingPowerProvider Power Calculators
`VotingPowerProvider` always inherits a virtual [VotingPowerCalculators](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/voting-power/common/voting-power-calc/) contracts that has to be implemented in the resulting contract.
Symbiotic provides several stake-to-votingPower conversion mechanisms you can separately or combine:
- [EqualStakeVPCalc](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/voting-power/common/voting-power-calc/EqualStakeVPCalc.sol) - voting power is equal to stake
- [NormalizedTokenDecimalsVPCalc](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/voting-power/common/voting-power-calc/NormalizedTokenDecimalsVPCalc.sol) - all tokens' decimals are normalized to 18
- [PricedTokensChainlinkVPCalc](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/voting-power/common/voting-power-calc/PricedTokensChainlinkVPCalc.sol) - voting power is calculated using Chainlink price feeds
- [WeightedTokensVPCalc](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/voting-power/common/voting-power-calc/WeightedTokensVPCalc.sol) - voting power is affected by configured weights for tokens
- [WeightedVaultsVPCalc](https://github.com/symbioticfi/relay-contracts/blob/main/src/modules/voting-power/common/voting-power-calc/WeightedVaultsVPCalc.sol) - voting power is affected by configured weights for vaults
#### Examples
##### Chainlink-Priced Stake with Token-/Vault-Specific Weights
```solidity [MyVotingPowerProvider.sol]
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
import {VotingPowerProvider} from "../src/modules/voting-power/VotingPowerProvider.sol";
import {PricedTokensChainlinkVPCalc} from
"../src/modules/voting-power/common/voting-power-calc/PricedTokensChainlinkVPCalc.sol";
import {OzOwnable} from "../src/modules/common/permissions/OzOwnable.sol";
import {WeightedTokensVPCalc} from "../src/modules/voting-power/common/voting-power-calc/WeightedTokensVPCalc.sol";
import {WeightedVaultsVPCalc} from "../src/modules/voting-power/common/voting-power-calc/WeightedVaultsVPCalc.sol";
import {VotingPowerCalcManager} from "../src/modules/voting-power/base/VotingPowerCalcManager.sol";
contract MyVotingPowerProvider is VotingPowerProvider, OzOwnable, PricedTokensChainlinkVPCalc, WeightedTokensVPCalc, WeightedVaultsVPCalc {
constructor(address operatorRegistry, address vaultFactory) VotingPowerProvider(operatorRegistry, vaultFactory) {}
function initialize(
VotingPowerProviderInitParams memory votingPowerProviderInitParams,
OzOwnableInitParams memory ozOwnableInitParams
) public virtual initializer {
__VotingPowerProvider_init(votingPowerProviderInitParams);
__OzOwnable_init(ozOwnableInitParams);
}
function stakeToVotingPowerAt(
address vault,
uint256 stake,
bytes memory extraData,
uint48 timestamp
)
public
view
override(VotingPowerCalcManager, PricedTokensChainlinkVPCalc, WeightedTokensVPCalc, WeightedVaultsVPCalc)
returns (uint256)
{
return super.stakeToVotingPowerAt(vault, stake, extraData, timestamp);
}
function stakeToVotingPower(
address vault,
uint256 stake,
bytes memory extraData
)
public
view
override(VotingPowerCalcManager, PricedTokensChainlinkVPCalc, WeightedTokensVPCalc, WeightedVaultsVPCalc)
returns (uint256)
{
return super.stakeToVotingPower(vault, stake, extraData);
}
}
```
:::note
If too many extensions/additions are implemented, the contract may exceed the maximum bytecode size. In this case, try adding `via-ir` flag with low number of optimizer runs for Solidity compiler.
:::
## Deployment
The deployment tooling is in the [`script/`](https://github.com/symbioticfi/relay-contracts/tree/main/script) folder. It consists of [`RelayDeploy.sol`](https://github.com/symbioticfi/relay-contracts/tree/main/script/RelayDeploy.sol) Foundry script template and [`relay-deploy.sh`](https://github.com/symbioticfi/relay-contracts/tree/main/script/relay-deploy.sh) bash script (Relay smart contracts use external libraries, so it's not currently possible to use solely Foundry script for multi-chain deployment).
- [`RelayDeploy.sol`](https://github.com/symbioticfi/relay-contracts/tree/main/script/RelayDeploy.sol) - abstract base that wires common Symbiotic Core helpers and exposes four deployment hooks: KeyRegistry, VotingPowerProvider, Settlement, and ValSetDriver
- [`relay-deploy.sh`](https://github.com/symbioticfi/relay-contracts/tree/main/script/relay-deploy.sh) - orchestrates per-contract multi-chain deployments (uses Python to parse `toml` file)
The script deploys Relay modules under [OpenZeppelin's TransparentUpgradeableProxy](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) using [CreateX](https://github.com/pcaversaccio/createx), providing better control for production deployments and simpler approaches for development.
:::info
**Prerequisites.** Clone with submodules and build first — the script compiles the whole project, including the nested Symbiotic Core / forge-std submodules:
```bash [bash]
git clone --recurse-submodules https://github.com/symbioticfi/relay-contracts.git
cd relay-contracts
git submodule update --init --recursive # ensure nested submodules are present
forge build
```
A shallow clone without submodules fails with `Source "…/forge-std/src/Test.sol" not found`.
:::
::::steps
### Configure on-chain deployment
Implement your `MyRelayDeploy.sol` ([see example](https://github.com/symbioticfi/relay-contracts/tree/main/script/examples/MyRelayDeploy.sol)):
- Include the deployment configuration of your Relay modules
- Implement all virtual functions of `RelayDeploy.sol`
- In the constructor, input the path of the `toml` file
- Use additional helpers such as `getCore()`, `getKeyRegistry()`, `getVotingPowerProvider()`, etc. (see full list in [`RelayDeploy.sol`](https://github.com/symbioticfi/relay-contracts/tree/main/script/RelayDeploy.sol))
- See [Deployment Parameters](#deployment-parameters) below for what each `MyRelayDeploy.sol` constant controls
### Choose multi-chain setup
Implement your `my-relay-deploy.toml` ([see example](https://github.com/symbioticfi/relay-contracts/tree/main/script/examples/my-relay-deploy.toml)). Two kinds of entries: one block per chain with its RPC URL, and one sentinel block mapping each module to the chain(s) it deploys to.
```toml [my-relay-deploy.toml]
# One block per chain you deploy to — the table name is the chain ID.
[11155111] # e.g. Sepolia
endpoint_url = "https://sepolia.example/rpc" # RPC URL for that chain
[11155420] # e.g. OP Sepolia
endpoint_url = "https://op-sepolia.example/rpc"
# Deployment map. Keep the literal [1234567890] table name and the empty endpoint_url.
[1234567890]
endpoint_url = ""
keyRegistry = 11155111 # chain to deploy KeyRegistry on
votingPowerProvider = [11155111] # one or more chains
settlement = [11155111, 11155420] # Settlement is commonly multi-chain
valSetDriver = 11155111
```
- Use a chain ID + `endpoint_url` block for every chain referenced in the map.
- **Keep the `[1234567890]` block and its empty `endpoint_url` — it is a required placeholder, not a real chain.**
- Modules deploy in this order regardless: 1. KeyRegistry 2. VotingPowerProvider 3. Settlement 4. ValSetDriver. After the run, deployed addresses are written back into this file under `[.address]`.
### Run the deployment
Execute the deployment script, e.g.:
```bash [bash]
# sign with a hardware wallet (--ledger) or an EOA (--private-key )
./script/relay-deploy.sh ./script/examples/MyRelayDeploy.sol ./script/examples/my-relay-deploy.toml --broadcast --ledger
```
:::note
Basic form is `./script/relay-deploy.sh ` — anything after the two paths is forwarded to every `forge script` call (e.g. `--private-key`, `--ledger`, `-vvvv`). The signer pays gas on every chain in the map.
:::
At the end, your `toml` file will contain the addresses of the deployed Relay modules.
:::note
**Local chains:** Symbiotic Core isn't deployed on a fresh devnet, so the script bootstraps it automatically on first run (on Ethereum/Holešky/Sepolia/Hoodi it's already deployed and this is skipped). If you hit `FailedDeployment()` / `CreateCollision`, your chain or the `broadcast/` + `cache/` folders carry stale state from a previous attempt — restart the chain from a clean genesis and remove `broadcast/MyRelayDeploy.sol` and `cache/MyRelayDeploy.sol`, then re-run.
:::
::::
### Deployment Parameters
The example `MyRelayDeploy.sol` is a **template, not a ready-to-run network** — it deploys with `OWNER = NETWORK_ADDRESS = address(1)` and a minimal `VotingPowerProvider`. Before a real deployment you must, at minimum, set `OWNER` and `NETWORK_ADDRESS` to real values (a [registered network](/v1/integrate/networks/network-contract)) and pick a `VotingPowerProvider` composition that lets operators actually carry stake — e.g. add `OpNetVaultAutoDeploy` so each operator gets a vault on registration, or wire shared/operator vaults explicitly (see [Relay On-Chain extensions](#votingpowerprovider-extensions) and [Manage Allocations](/v1/integrate/curators/manage-allocations)).
The `initialize(...)` arguments are declared as constants at the top of the example
`MyRelayDeploy.sol`. Review these before any non-demo deployment:
| Constant | Controls | Note |
| -------------------------------------------------------------------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OWNER` | Admin / `DEFAULT_ADMIN_ROLE` on the deployed modules | Defaults to `address(1)` in the example — **set it to your multisig/governance**. |
| `NETWORK_ADDRESS` | The network entity (from `NetworkRegistry`) bound to every module | Defaults to `address(1)` — **must be your registered network**. For `OpNetVaultAutoDeploy` it must be a contract implementing `ISetMaxNetworkLimitHook`. |
| `SUBNETWORK_ID` | Subnetwork identifier under your network | Usually `0`. |
| `QUORUM_THRESHOLD` | Signing threshold per key tag | **Scaled by `1e18` (so `1e18` = 100%).** The example's `6667` is a placeholder — a realistic value is e.g. `666666666666666667` (≈ 66.67%). |
| `REQUIRED_HEADER_KEY_TAG` | Key tag used to sign validator-set headers | Must be a **BLS BN254** tag (the only aggregatable type), e.g. `15`. |
| `VERIFICATION_TYPE` | Settlement verifier selector | Must match the verifier you wire into `Settlement`. Confirm after deploy with `getVerificationType()`. |
| `EPOCH_DURATION` / `COMMITTER_SLOT_DURATION` | Epoch length and committer slot, in seconds | — |
| `NUM_AGGREGATORS` / `NUM_COMMITTERS` | Off-chain roles scheduled per epoch | — |
| `MAX_VOTING_POWER` / `MIN_INCLUSION_VOTING_POWER` / `MAX_VALIDATORS_COUNT` | Validator-set admission filters | `0` disables the cap where applicable. |
| `*_SALT` | CreateX salt per module | Determines the deterministic proxy address; reuse the same salt to get identical addresses across chains. |
**Where the addresses come from:**
- **Symbiotic Core** addresses the modules depend on (e.g. `operatorRegistry`, `vaultFactory` for the `VotingPowerProvider` constructor) are resolved automatically by the `getCore()` helper on supported chains — Ethereum, Holešky, Sepolia, and Hoodi. The canonical list is on the [Addresses](/v1/get-started/resources/addresses#core) page.
- `NETWORK_ADDRESS` is your own network entity — register it in `NetworkRegistry` first (see [Network Contract](/v1/integrate/networks/network-contract)).
- **Your deployed relay modules** (`KeyRegistry`, `VotingPowerProvider`, `Settlement`, `ValSetDriver`) are written back into your `my-relay-deploy.toml` once the script finishes. Hand the `ValSetDriver` address + its chain ID to operators — that is all they need for [Relay Utils](/v1/integrate/networks/relay-utils) and the [sidecar](/v1/integrate/networks/relay-offchain).
### After deploying: wire and go live
With the modules deployed, finish bringing the network online — **in this order**:
1. **Set your middleware** — from the network, call `NetworkMiddlewareService.setMiddleware(votingPowerProvider)` (the `SetMiddleware` action in the [network repo](/v1/integrate/networks/network-contract#manage-your-network)). Required: until it's set, Core doesn't associate stake with your network and the relay derives an empty validator set.
2. **Wire vault limits** — the network calls `setMaxNetworkLimit`, the curator sets `setNetworkLimit` / operator shares (automatic for `OpNetVaultAutoDeploy`). See [Manage Allocations](/v1/integrate/curators/manage-allocations).
3. **Onboard your initial operators** — they register in Core, opt into the network, register in your `VotingPowerProvider` with their keys, opt into a vault, and stake. Point them at [Add an operator](/v1/integrate/operators).
4. **Commit genesis** — `utils network generate-genesis --commit` derives the first validator set from current on-chain voting power and commits the header to every Settlement chain. See [Relay Utils → Commit the genesis validator set](/v1/integrate/networks/relay-utils#commit-the-genesis-validator-set).
5. **Go live** — operators run [`relay_sidecar`](/v1/integrate/networks/relay-offchain); each epoch the set is derived, signatures aggregated, and committers post the header on-chain.
:::warning
Genesis reads the voting power that exists **at that moment** — make sure at least one operator is registered, keyed, and staked **before** you commit, or the genesis set is empty.
:::
## Integrate
Symbiotic Relay provides comprehensive tooling that works independently, so you can focus on your stake-backed application logic.
### Verify Message
Your application contract can verify any message using a validator set at any point in time via:
```solidity [MyApp.sol]
import {ISettlement} from "@symbioticfi/relay-contracts/src/interfaces/modules/settlement/ISettlement.sol";
function verifyMessage(bytes calldata message, uint48 epoch, bytes calldata proof) public returns (bool) {
return ISettlement(SETTLEMENT).verifyQuorumSigAt(
abi.encode(keccak256(message)),
15, // default key tag - BN254
(uint248(1e18) * 2) / 3 + 1, // default quorum threshold - 2/3 + 1
proof,
epoch,
new bytes(0)
);
}
```
:::note
You need to have a `Settlement` deployed on the verification chain first.
:::
### Use Validator Set Data
Your application contract can use the validator set at any point in time using [SSZ](https://ethereum.org/developers/docs/data-structures-and-encoding/ssz/) proof verification, for example:
```solidity [MyApp.sol]
import {ValSetVerifier} from "@symbioticfi/relay-contracts/src/libraries/utils/ValSetVerifier.sol";
import {Math} from "openzeppelin-contracts/contracts/utils/math/Math.sol";
function verifyOperatorVotingPower(
ValSetVerifier.SszProof calldata validatorRootProof,
uint256 validatorRootLocalIndex,
bytes32 validatorSetRoot,
ValSetVerifier.SszProof calldata operatorProof,
address operator,
ValSetVerifier.SszProof calldata votingPowerProof,
uint256 votingPower
) public returns (bool) {
return operatorProof.leaf == bytes32(uint256(uint160(operator)) << 96)
&& ValSetVerifier.verifyOperator(
validatorRootProof, validatorRootLocalIndex, validatorSetRoot, operatorProof
) && votingPowerProof.leaf == bytes32(votingPower << (256 - (Math.log2(votingPower) / 8 + 1) * 8))
&& ValSetVerifier.verifyVotingPower(
validatorRootProof, validatorRootLocalIndex, validatorSetRoot, votingPowerProof
);
}
```
:::note
You need to have a `Settlement` deployed on the verification chain first.
:::
## Next Steps
}
href="/v1/integrate/networks/relay-offchain"
/>
---
## /v1/integrate/networks/relay-utils
---
description: "relay_utils is the relay's key, operator, and network CLI — generate and register validator keys, commit the genesis validator set, and inspect operator status."
---
import { Card1 } from "../../../components/Card1";
# 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](/v1/integrate/networks/relay-onchain)
and running the [Relay sidecar](/v1/integrate/networks/relay-offchain):
- **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.
:::info
`relay_utils` ships in the same image as the sidecar. Run it from the published Docker image:
```bash [bash]
docker run --rm -v $(pwd):/data --entrypoint relay_utils symbioticfi/relay:latest --help
```
or build it from [`symbioticfi/relay`](https://github.com/symbioticfi/relay) (`make build-relay-utils`).
Examples below are written as `utils …` for brevity.
:::
:::note
`relay_utils` is **not** the same tool as the [`symb` CLI](/v1/integrate/builders-researchers/cli).
`symb` reads and writes Symbiotic **Core** state (registries, opt-ins, vault allocations);
`relay_utils` handles the relay-specific layer (validator keys, `KeyRegistry`, genesis). Operators
typically use both.
:::
## What you need
Every command below uses the same handful of values. Here is exactly what each one is and where to
get it:
| Value | What it is | Where to get it |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `` | 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`](/v1/integrate/networks/relay-onchain#deployment).) |
| `` | Chain ID where `ValSetDriver` is deployed. | From the network operator, alongside the driver address. |
| `` | Chain ID where the network's `VotingPowerProvider` lives (often the same as the driver chain). | From the network operator, or read it: `cast call "getVotingPowerProviders()((uint64,address)[])" --rpc-url `. |
| `` (and `,,…`) | 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](/v1/get-started/resources/addresses). |
| `` (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 "getRequiredKeyTags()(uint8[])" --rpc-url ` (the header-signing tag is `getRequiredHeaderKeyTag()`). |
| `` | 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. |
:::note
You never pass the `VotingPowerProvider`, `KeyRegistry`, or `Settlement` addresses to `relay_utils`
directly — it resolves them from the `ValSetDriver` for you. The driver address + chain ID and an
RPC are enough.
:::
## 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 tag** → [Generate and manage keys](#generate-and-manage-keys).
2. **Register each key** in `KeyRegistry` → [Register a key](#register-a-key-in-keyregistry).
3. **Register your operator** in the `VotingPowerProvider` → [Register as an operator](#register-as-an-operator). On auto-deploy networks this also creates your vault.
4. **Opt into your vault and deposit stake** — Core [opt-ins](/v1/integrate/operators/opt-ins) + the curator's [allocation limits](/v1/integrate/curators/manage-allocations) — so your voting power is active.
5. **Run the sidecar** → [Relay Off-Chain](/v1/integrate/networks/relay-offchain), then confirm with [Check operator status](#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 [bash]
# generate a fresh EVM key for the voting-power-provider chain
utils keys add --generate --evm --chain-id --password
# or import an existing private key
utils keys add --evm --chain-id --private-key 0x... --password
```
Generate one **relay** signing key per key tag the network requires (ask the network operator which
tags are required):
```bash [bash]
utils keys add --relay --key-tag --generate --password
```
:::note
**Key tags.** A key tag is one byte: `(keyType << 4) | keyId`. Key types: `0` = BLS BN254 (the only
type that supports signature aggregation), `1` = ECDSA secp256k1, `2` = BLS12-381. So tag `15`
(`0x0F`) is BLS BN254 / id 15, and tag `16` (`0x10`) is ECDSA secp256k1 / id 0.
:::
Inspect and clean up:
```bash [bash]
utils keys list --password
utils keys remove --evm --chain-id --password
```
## 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 [bash]
utils operator register-key \
--driver.address \
--driver.chainid \
--voting-provider-chain-id \
--chains \
--key-tag \
--secret-keys : \
--password
```
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 [bash]
cast call "getKeysProvider()((uint64,address))" --rpc-url
```
The returned `(chainId, address)` is the chain and address of the network's `KeyRegistry`.
:::warning
`register-key` operates on a **single chain** at a time, and reads `ValSetDriver` at the chain's
`finalized` block. On a freshly-started local devnet `finalized` is block `0`, so the call reverts
with `no contract code at given address` — advance it once (e.g. `cast rpc anvil_mine 100`) before
registering. Public testnets and mainnet are unaffected.
:::
## 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 [bash]
utils operator register-operator \
--driver.address \
--driver.chainid \
--voting-provider-chain-id \
--chains \
--secret-keys :
```
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 [bash]
cast call "getVotingPowerProviders()((uint64,address)[])" --rpc-url
```
:::info
Prerequisite: the operator must already be registered in Symbiotic Core's `OperatorRegistry` and
opted into the network — see [Register Operator](/v1/integrate/operators/register-operator) and
[Opt-Ins](/v1/integrate/operators/opt-ins).
:::
:::note
If the network's `VotingPowerProvider` uses the `OpNetVaultAutoDeploy` extension, this call also
**auto-creates a vault** dedicated to your operator. Read its address from the transaction logs, or
look it up afterwards:
```bash [bash]
cast call "getOperatorVaults(address)(address[])" --rpc-url
```
Opt into that vault and deposit stake into it. On networks without auto-deploy, opt into an existing
curator-managed vault instead.
:::
Verify the registration:
```bash [bash]
cast call "isOperatorRegistered(address)(bool)" --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.
:::warning
Registering (and even depositing) is not enough on its own — your stake only counts as **voting
power** once the vault's delegator limits are set. See
[Manage Allocations](/v1/integrate/curators/manage-allocations) for who sets `setNetworkLimit` /
`setOperatorNetworkShares`.
:::
## 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.
:::warning
Genesis derives the set from the voting power that exists **right now**. Make sure your initial
operators are registered in the `VotingPowerProvider`, have their keys in `KeyRegistry`, and are
backed by stake (with delegator limits set) **before** committing — otherwise the genesis validator
set is empty. The `--secret-keys` are the committer keys that sign the genesis transactions on each
chain.
:::
```bash [bash]
utils network generate-genesis \
--driver.address \
--driver.chainid \
--chains ,,... \
--secret-keys :,... \
--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 [bash]
utils network info \
--driver.address \
--driver.chainid \
--chains ,,...
```
## Check operator status
Confirm your operator is registered, included, and carries voting power:
```bash [bash]
utils operator info \
--driver.address \
--driver.chainid \
--voting-provider-chain-id \
--chains
```
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](/v1/integrate/curators/manage-allocations).
## Next Steps
}
href="/v1/integrate/networks/relay-offchain"
/>
}
href="/v1/integrate/networks/relay-onchain"
/>
---
## /v1/integrate/networks/rewards
---
description: "Networks use Rewards system to compensate Stakers, Operators, and Curators for providing security and network operation."
---
import { Card1 } from "../../../components/Card1";
# Distribute Rewards
Networks use Rewards system to compensate Stakers, Operators, and Curators for providing security and network operation.
This guide highlights practical steps and contract touchpoints to distribute and claim rewards.
To reproduce this flow, follow the [Quickstart: Network with Staking](/v1/integrate/networks/minimal-network) guide first.
***
The rewards implementation is available in the [Symbiotic Rewards Repository](https://github.com/symbioticfi/rewards-v2). There are 3 smart contracts:
- **Rewards**: Main contract for distributions and claims.
- **FeeRegistry**: Registry for operator and curator fees.
- **CuratorRegistry**: Registry mapping vaults to curators.
## Vault Snapshot Rewards

Vault Snapshot Rewards implement **fully on-chain**, snapshot-based distributions using historical vault fee split and state at specific timestamps.
:::info
The distribution and claim operations must be handled separately for each vault.
:::
Actions ([Contract](https://github.com/symbioticfi/rewards-v2/blob/main/src/contracts/VaultSnapshotRewards.sol) | [Interface](https://github.com/symbioticfi/rewards-v2/blob/main/src/interfaces/IVaultSnapshotRewards.sol)):
- **Distribution entrypoint**: `distributeVaultSnapshotRewards()` - used by network to distribute rewards.
- **Claim entrypoints** for stakers, operators and curators:
- `claimVaultSnapshotRewards()`
- `claimOperatorFees()`
- `claimCuratorFees()`
:::info
Both network and its middleware can distribute Vault Snapshot rewards.
:::
### Reward Workflow
:::info
Reward distribution is a regular routine. Unlike DeFi farms that distribute rewards continuously, networks distribute rewards periodically (for example, weekly).
:::
Rewards contract addresses are listed on the [Addresses](/v1/get-started/resources/addresses#rewards) page.
:::steps
#### Distribute Rewards
For `msg.sender`, use the Network Middleware account (see [Quickstart: Network with Staking](/v1/integrate/networks/minimal-network)).
- Increase token allowance ([Sample Tx](https://hoodi.etherscan.io/tx/0x51b48ffe641da09b8975a3bc670b77b7dd1b62c1d6028770cc569bcbe4404987))
- Distribute rewards by calling `Rewards` via `distributeVaultSnapshotRewards()` ([Sample Tx](https://hoodi.etherscan.io/tx/0xca0ce507a5e93230c848cf5a4b261714847500fef5203b27dbc49dcbe78b8d1a))
#### Verify on Symbiotic dApp
Open the [Vault Page](https://app.hoodi.symbiotic.fi/vault/0xb29BAD40B00587dE1145C7862F96746f043b9daD). Find Vault Reward data and claimed rewards in the corresponding section.
#### Staker Flow
- [Acquire wstETH](https://stake-hoodi.testnet.fi/wrap) ([Sample Tx](https://hoodi.etherscan.io/tx/0xe799b1611a19cc4ad9c8bc1d61cd328c1f54218c78a5b18f9b559c8791d5b876))
- Deposit to Vault (via UI) ([Tx 1](https://hoodi.etherscan.io/tx/0xe622a7bfd0c17e76f92da77a8ce378aebf5e32443e2fb4f78363871efab51531), [Tx 2](https://hoodi.etherscan.io/tx/0x0ebe2fedef0443d38ee7e933600b3f16139d87d31dbed627cd9fe314effddb33))
- Claim Rewards via `claimVaultSnapshotRewards` ([Sample Tx](https://hoodi.etherscan.io/tx/0x0f9d5d14772999320b88b45ad40f6764054a56993386f0d0d709ff7a2fcd7c8e))
#### Curator and Operator flows
- The `vault.owner()` registers the curator in `CuratorRegistry`
- Curator sets curator and operator fees in `FeeRegistry`.
- Curator and operator claim via `Rewards.sol`
For more details, see [Curator: Manage & Claim Fees](/v1/integrate/curators/registry-and-fees#claim-process) and [Operator: Claim Rewards](/v1/integrate/operators/claim-rewards).
:::
### Production considerations
This guide uses testnet and simplified EOAs for ease of replication. In production, plan the security-aware setup accordingly.
- [Implement Middleware](/v1/integrate/networks/network-contract), do not use EOA or multisig
- Expect fees to be deducted from the amount distributed. To perform fee-related calculations, use [FeeRegistry and Rewards methods](/v1/integrate/curators/registry-and-fees#fee-related-calculations)
## Next Steps
}
href="/v1/integrate/networks/slashing"
/>
}
href="/v1/integrate/networks/relay-onchain"
/>
---
## /v1/integrate/networks/slashing
---
description: "Integrate Symbiotic slashing flows, resolvers, veto slashers, burners, and operator accountability checks."
---
import { Card1 } from "../../../components/Card1";
# Slashing
[Read Learn first](/v1/learn/core-concepts/slashing)
Slashing is a penalty mechanism that deters operators from breaking their commitments. Violations include failing to complete tasks properly or accurately. Slashing typically burns or redistributes the operator's staked funds.
## **Slasher Module**
Each Symbiotic Vault has an immutably set `Slasher` module implementation. There are three possible implementation choices:
1. **No Slasher** - no slashing can occur within the Vault
2. **Instant Slasher (`TYPE = 0`)** - allows networks to immediately slash funds in a FIFO order
3. **Veto Slasher (`TYPE = 1`)** - supports a veto process over a pre-defined veto period, where designated **Resolvers** can cancel the slashing request
Each Slasher module contains a `slashableStake(subnetwork, operator, captureTimestamp, hints)` function that returns the amount of collateral still slashable for the given `captureTimestamp` at the current moment.
:::warning
The provided slashable collateral amount may be inaccurate in practice when the vault uses **restaking** (multiple networks may slash the same amount of collateral).
:::
:::info
When executing a slashing request, funds are transferred to an immutably set `Vault.burner()`.
:::
### Slashing Guarantees
Each Symbiotic Vault has an epoch duration (obtained via `Vault.epochDuration()`), which determines the withdrawal delay and provides a period during which slashing guarantees are held.
The following inequality must hold:
executeSlashTimestamp - captureTimestamp \<= epochDuration
### Slasher (Type 0)
The slasher instantly executes slashing requests when received and validated.
```solidity [NetworkSlasher.sol]
import {IVault} from "@symbioticfi/core/src/interfaces/vault/IVault.sol";
import {ISlasher} from "@symbioticfi/core/src/interfaces/slasher/ISlasher.sol";
import {Subnetwork} from "@symbioticfi/core/src/contracts/libraries/Subnetwork.sol";
address slasher = IVault(vault).slasher();
bytes32 subnetwork = Subnetwork.subnetwork(NETWORK, IDENTIFIER);
ISlasher(slasher).slash(
subnetwork,
operator,
amount,
captureTimestamp,
hints
)
```
Parameters:
- `subnetwork` - full identifier of the subnetwork (address of the network concatenated with the uint96 identifier)
- `operator` - address of the operator
- `amount` - amount of the collateral to slash
- `captureTimestamp` - time point when the stake was captured
- `hints` - hints for checkpoints' indexes
:::info
Call this function from `NetworkMiddlewareService.middleware(network)`.
:::
### VetoSlasher (Type 1)
The flow consists of three stages:
1. Request Slashing
2. Veto Slashing
3. Execute Slashing (if not vetoed)
Let’s assume the veto duration period is set to **5 days** and the epoch duration is set to **7 days**.
::::steps
#### Day 1 - Request Slashing
```solidity [NetworkSlasher.sol]
import {IVault} from "@symbioticfi/core/src/interfaces/vault/IVault.sol";
import {IVetoSlasher} from "@symbioticfi/core/src/interfaces/slasher/IVetoSlasher.sol";
import {Subnetwork} from "@symbioticfi/core/src/contracts/libraries/Subnetwork.sol";
address slasher = IVault(vault).slasher();
bytes32 subnetwork = Subnetwork.subnetwork(NETWORK, IDENTIFIER);
uint256 slashIndex = IVetoSlasher(slasher).requestSlash(
subnetwork,
operator,
amount,
captureTimestamp,
hints
)
```
This call succeeds only if the following inequality holds:
requestSlashTimestamp + vetoDuration - captureTimestamp \<= epochDuration
:::info
`NetworkMiddlewareService.middleware(network)` should call this function.
:::
#### Days 1 to 5 - Veto Slashing
```solidity [Resolver]
IVetoSlasher(slasher).vetoSlash(slashIndex, hints)
```
:::info
Call this function from `VetoSlasher.resolver(subnetwork, hint)`.
:::
#### Days 6 to 7 - Execute Slashing
If the slashing request wasn't vetoed:
```solidity [NetworkSlasher.sol]
IVetoSlasher(slasher).executeSlash(
slashIndex,
hints
)
```
:::info
`NetworkMiddlewareService.middleware(network)` should call this function.
:::
::::
## Resolvers
If a vault uses a VetoSlasher, there is a veto phase (duration set during vault deployment) when the resolver can veto the request.
Networks can set the resolver via `IVetoSlasher(slasher).setResolver(identifier, resolver, hints)`.
:::note
The first time you set the resolver, it applies immediately. Otherwise, the update applies after `VetoSlasher.resolverSetEpochsDelay()` epochs.
:::
## Next Steps
}
href="/v1/integrate/networks/relay-onchain"
/>
---
## /v1/integrate/networks/submit-metadata
---
description: "The Symbiotic UI displays TVL, allocations, and relationships between curators, vaults, operators, and networks. To make your entity visible on the UI, submit..."
---
import { Card1 } from "../../../components/Card1";
# Submit Metadata
The [Symbiotic UI](https://app.symbiotic.fi/deposit) displays TVL, allocations, and relationships between curators, vaults, operators, and networks. To make your entity visible on the UI, submit its metadata to the corresponding repository.
After you submit metadata, the Symbiotic team reviews and merges it. Once merged, your data appears on the UI.
## Add a New Entity Template
### Choose a Repository
| Chain | URL |
| ------- | -------------------------------------------------------------------------------------------------- |
| Mainnet | [https://github.com/symbioticfi/metadata-mainnet](https://github.com/symbioticfi/metadata-mainnet) |
| Hoodi | [https://github.com/symbioticfi/metadata-hoodi](https://github.com/symbioticfi/metadata-hoodi) |
### Repository Structure
The repository is organized as follows:
```
repository/
├── vaults/
│ ├── 0x/
│ │ ├── info.json
│ │ └── logo.png (optional)
├── networks/
├── operators/
├── tokens/
```
Each entity is identified by its Ethereum address (`0x...`), and its data is stored in a folder named after the address. Inside this folder, include a file `info.json` containing metadata, and optionally, an icon file `logo.png`.
***
### Steps to Add a New Entity
**Note: After your PR is submitted, email your PR link to [verify@symbiotic.fi](mailto\:verify@symbiotic.fi) from your official business email (domain must match that of your entity website) to allow us to confirm your identity ahead of merging your PR.**
1. **Determine the entity type**:
- Decide whether the entity belongs to `vaults`, `networks`, `operators`, `tokens` or `points`.
- If the entity is a `vault`, ensure its collateral token entity is registered in the `tokens` folder before adding the vault metadata. If not, add the token first.
2. **Register the entity in the registry**:
- Before adding metadata for vaults, networks, or operators, ensure that they are registered in their respective registries. You can find the current registry contract addresses in the [Addresses page](/v1/get-started/resources/addresses). Unregistered entities will not be accepted.
3. **Create a new folder**:
- Navigate to the appropriate directory for the entity type.
- Create a folder named after the Ethereum address (e.g., `0x1234567890abcdef1234567890abcdef12345678`).
4. **Add the `info.json` file**:
- Include metadata in the specified format (see below).
5. **(Optional) Add an icon file**:
- If available, include a `logo.png` file with the entity’s logo.
The Symbiotic team reviews your PR after automated checks pass. If approved, it will be merged into the repository.
***
### File Format: `info.json`
The `info.json` file must follow this structure:
#### Required Fields
- `name` (string): The name of the entity.
- `description` (string): A brief description of the entity.
- `tags` (array of strings): Tags categorizing the entity.
- `links` (array of objects): External links related to the entity.
#### Fields for Points
- `type` (string): The type of the point (e.g., "network").
- `decimals` (number): The number of decimal places for the point.
#### Supported `links` Types
Each link should include:
- `type`: The type of the link. Supported values are:
- `website`: The official website of the entity.
- `explorer`: A blockchain explorer link for the entity's Ethereum address or contract.
- `docs`: Documentation related to the entity.
- `example`: Example use cases or tutorials.
- `externalLink`: A link to be shown below the entity's name.
- `name`: A user-friendly name for the link.
- `url`: The URL of the resource.
### Icon File: `logo.png` (Optional)
If you want to include an icon for the entity, follow these guidelines:
- **File Name**: `logo.png`
- **Dimensions**: 256x256 pixels
- **Format**: PNG
Place the `logo.png` file in the same folder as the `info.json` file.
***
### Validation
Before submitting your PR, ensure the following:
1. The Ethereum address is valid:
- It must start with `0x` and be exactly 42 characters long.
2. The `info.json` file is valid:
- Use a JSON validator, such as [https://jsonlint.com/](https://jsonlint.com/).
3. The `logo.png` file (if included) meets the size requirement of **256x256 pixels**.
***
### Submitting the Pull Request
Once your files are added to the repository, create a Pull Request with the following details:
1. **Entity Type**: Specify the type (vault, network, operator, token).
2. **Ethereum Address**: Provide the address of the entity.
3. **Description**: Summarize the entity’s purpose and data.
#### Example PR Description
```
Added new token entity: 0x1234567890abcdef1234567890abcdef12345678
- **Name**: USDT
- **Description**: USDT is a stablecoin pegged to the US Dollar, widely used for trading and liquidity in cryptocurrency markets.
- **Tags**: stablecoin, usdt
- **Links**:
- [Website](https://tether.to/)
- [Etherscan](https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7)
- [Tether Documentation](https://docs.tether.to/)
- **CMC ID**: 825
- **Permit Name**: USDT Permit Token
- **Permit Version**: 1
- **Icon**: Included (256x256 px)
```
***
### Review and Approval
Your PR will be reviewed to ensure:
- The `info.json` file has all required fields and valid data.
- The `logo.png` file (if included) meets the requirements.
- The metadata is accurate and well-structured.
- The submitter of the PR is from the entity in question (verified via an email with your PR link to [verify@symbiotic.fi](mailto\:verify@symbiotic.fi) from your official business email)
After approval, your changes will be merged into the repository.
## Add a Network
:::steps
##### Create a new folder in the `/networks` directory
##### Create a new json file in the folder with the following structure:
```json [info.json]
{
"name": "My Network",
"description": "My Network is a network that allows you to stake your tokens and earn rewards.",
"tags": ["network", "staking"],
"links": [{ "type": "website", "name": "Website", "url": "https://mynetwork.com" }]
}
```
##### Save a logo of the Network to `logo.png` of 256x256 pixels size
:::
## Add Points
:::steps
##### Create a new folder in the `/points` directory
##### Create a new json file in the folder with the following structure:
```json [info.json]
{
"name": "My Points",
"description": "My Points is a points system that allows you to earn rewards.",
"tags": ["points", "staking"],
"links": [{ "type": "website", "name": "Website", "url": "https://mypoints.com" }]
}
```
##### Save a logo of the Points to `logo.png` of 256x256 pixels size
:::
## Add a Pre-deposit Vault
See the [vault metadata submission guide](/v1/integrate/curators/submit-metadata) for details.
## Next Steps
}
href="/v1/integrate/networks/pre-deposit"
/>
}
href="/v1/integrate/networks/rewards"
/>