--- ## /v1/integrate/networks/helpful-core-contracts-endpoints --- description: "This page lists useful functions for networks across Symbiotic Core contracts." --- # Helpful Core Contracts' Endpoints This page lists useful functions for networks across Symbiotic Core contracts. | Function | Use-case | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | | [`NetworkRegistry.registerNetwork()`](https://github.com/symbioticfi/core/blob/7cb06639c5cd656d1d212dafa2c270b5fde39306/src/interfaces/INetworkRegistry.sol#L12) | Register network | | [`Vault.isInitialized() → bool`](https://github.com/symbioticfi/core/blob/7cb06639c5cd656d1d212dafa2c270b5fde39306/src/interfaces/vault/IVault.sol#L165) | Check if the vault has set delegator and slasher before on-boarding as a valid Vault | | [`Vault.delegator() → address`](https://github.com/symbioticfi/core/blob/7cb06639c5cd656d1d212dafa2c270b5fde39306/src/interfaces/vault/IVaultStorage.sol#L60) | Get the Vault's delegator | | [`Vault.slasher() → address`](https://github.com/symbioticfi/core/blob/7cb06639c5cd656d1d212dafa2c270b5fde39306/src/interfaces/vault/IVaultStorage.sol#L72) | Get the Vault's slasher | | [`BaseDelegator.TYPE() → uint64`](https://github.com/symbioticfi/core/blob/7cb06639c5cd656d1d212dafa2c270b5fde39306/src/interfaces/common/IEntity.sol#L17) | Get the delegator's type (0 - NetworkRestake, 1 - FullRestake, etc.) | | [`BaseDelegator.setMaxNetworkLimit(uint96 identifier, uint256 amount)`](https://github.com/symbioticfi/core/blob/7cb06639c5cd656d1d212dafa2c270b5fde39306/src/interfaces/delegator/IBaseDelegator.sol#L158) | Set an amount of collateral you are ready to accept from the Vault (maximum network limit) | | [`BaseDelegator.maxNetworkLimit(bytes32 subnetwork) → uint256`](https://github.com/symbioticfi/core/blob/7cb06639c5cd656d1d212dafa2c270b5fde39306/src/interfaces/delegator/IBaseDelegator.sol#L125) | Check the maximum network limit | | [`BaseDelegator.stakeAt(bytes32 subnetwork, address operator, uint48 timestamp, bytes hints) → uint256`](https://github.com/symbioticfi/core/blob/7cb06639c5cd656d1d212dafa2c270b5fde39306/src/interfaces/delegator/IBaseDelegator.sol#L137) | Get the operator's stake at the given timestamp | | [`BaseSlasher.TYPE() → uint64`](https://github.com/symbioticfi/core/blob/7cb06639c5cd656d1d212dafa2c270b5fde39306/src/interfaces/common/IEntity.sol#L17) | Get the slasher's type (0 - Slasher, 1 - VetoSlasher) | | [`BaseSlasher.slashableStake() → uint256`](https://github.com/symbioticfi/core/blob/7cb06639c5cd656d1d212dafa2c270b5fde39306/src/interfaces/slasher/IBaseSlasher.sol#L113) | Get the operator's still slashable stake captured at the given timestamp | | [`Slasher.slash() → uint256`](https://github.com/symbioticfi/core/blob/7cb06639c5cd656d1d212dafa2c270b5fde39306/src/interfaces/slasher/ISlasher.sol#L55) | Slash the operator | | [`VetoSlasher.requestSlash(bytes32 subnetwork, address operator, uint256 amount, uint48 captureTimestamp, bytes hints) -> uint256`](https://github.com/symbioticfi/core/blob/7cb06639c5cd656d1d212dafa2c270b5fde39306/src/interfaces/slasher/IVetoSlasher.sol#L213) | Request slashing of the operator | | [`VetoSlasher.executeSlash(uint256 slashIndex, bytes hints) -> uint256`](https://github.com/symbioticfi/core/blob/7cb06639c5cd656d1d212dafa2c270b5fde39306/src/interfaces/slasher/IVetoSlasher.sol#L228) | Execute slashing of the operator | | [`VetoSlasher.setResolver(uint96 identifier, address resolver, bytes hints)`](https://github.com/symbioticfi/core/blob/7cb06639c5cd656d1d212dafa2c270b5fde39306/src/interfaces/slasher/IVetoSlasher.sol#L245) | Set a resolver | | [`VetoSlasher.resolver(bytes32 subnetwork, bytes hint)`](https://github.com/symbioticfi/core/blob/7cb06639c5cd656d1d212dafa2c270b5fde39306/src/interfaces/slasher/IVetoSlasher.sol#L201) | Check the resolver | --- ## /v1/integrate/networks --- description: "This guide walks you through integrating your network with Symbiotic. Follow these steps to register your network, set up security, and start receiving stake..." --- import { Card1 } from "../../../components/Card1"; # Get Started This guide walks you through integrating your network with Symbiotic. Follow these steps to register your network, set up security, and start receiving stake allocations. :::steps ## Network Contract Register your network's management address in the Symbiotic system.
} href="/v1/integrate/networks/minimal-network" /> } href="/v1/integrate/networks/network-contract" />
## Submit Metadata
} href="/v1/integrate/networks/submit-metadata" />
## Set Up Pre-deposit Vault (Optional) If you need a self-curated vault, deploy and configure it.
} href="/v1/integrate/curators/deploy-vault" /> } href="/v1/integrate/curators/submit-metadata" />
## Integrate Rewards Rewards are essential for healthy protocol economics. Learn how to integrate Symbiotic rewards.
} href="/v1/integrate/networks/rewards" />
## Integrate Slashing Slashing protects your protocol by penalizing misbehavior. Learn how slashing works in Symbiotic.
} href="/v1/integrate/networks/slashing" />
## Implement On-Chain Components Build your on-chain integration using Symbiotic's Relay framework.
} href="/v1/integrate/networks/relay-onchain" /> } href="/v1/integrate/networks/network-contract#manage-your-network" />
## Implement Off-Chain Components Build your off-chain application using Relay Sidecar.
} href="/v1/integrate/networks/relay-offchain" />
## Bring the Network Online Onboard your operators, then commit the genesis validator set. **Order matters:** operators must be registered, keyed, and staked before genesis, or the genesis set is empty.
} href="/v1/integrate/operators" /> } href="/v1/integrate/networks/relay-utils" />
::: --- ## /v1/integrate/networks/minimal-network --- description: "This guide sets up a minimal Hoodi testnet environment so you can validate production flows on testnet." --- # Quickstart: Network with Staking This guide sets up a minimal Hoodi testnet environment so you can validate production flows on testnet. :::info Although this walkthrough uses externally owned accounts (EOAs) for Network and Middleware, the sequence of on-chain actions outside of the Network and Middleware is the same as if those accounts were production smart contracts. ::: ## Prerequisites Prepare 6 EOAs for simulation: - **Network**: network identity account - **Middleware**: set by the network to handle reward distribution and vault opt-ins - **Vault Curator**: sets up the vault and allocates shares - **Operator**: receives delegated stake - **Two Test Stakers**: deposit collateral ::::steps ### Register Network & Set Middleware For both transactions, originator (`msg.sender`) is the network address. 1. Register your network in `NetworkRegistry` via `registerNetwork()`. ([Sample Tx](https://hoodi.etherscan.io/tx/0x2da545af56094c2dbd7e49dfb838915b84e889ea341dae812483ee7c6840c196)) 2. Set the middleware address in `NetworkMiddlewareService` via `setMiddleware(middleware)`. ([Sample Tx](https://hoodi.etherscan.io/tx/0xf57a8e2fe75a9453620c0b2903165a34be9bc20749dc36b966f169e7f8f963ac)) ### Create Vault & Register Operator 1. [Create a vault](https://app.hoodi.symbiotic.fi/create) using `wstETH` as collateral. - Sample Vault address: `0xb29BAD40B00587dE1145C7862F96746f043b9daD` ([Sample Tx](https://hoodi.etherscan.io/tx/0x94ad7d76fc41e75528b1d2bbc0496c5664e59900c07f5e8bfa81505cca88d45b)) 2. Register operator in `OperatorRegistry` via `registerOperator()` ([Sample Tx](https://hoodi.etherscan.io/tx/0xc6ff4fabf604325e2357963004f4f96dceefec6e874edbec75677d827fbc507b)) 3. Opt operator into Network using `OperatorNetworkOptInService` via `optIn()` ([Sample Tx](https://hoodi.etherscan.io/tx/0xa116a2e7e7386019147b2952ad59fb169d2043f49088098dfc82c2422ecdd83d)) 4. Opt operator into Vault using `OperatorVaultOptInService` via `optIn()` ([Sample Tx](https://hoodi.etherscan.io/tx/0x81daac1756eb1ca3e5a35f10a356ff7487e0ce1b60fdbf91c05ac11dbfd42eea)) 5. Allocate shares in Delegator via `setOperatorNetworkShares()` ([Sample Tx](https://hoodi.etherscan.io/tx/0xe2ceee91cebadc1c1abf11fbad9b40d32604f00695282cc082a6796bc889376b)) 6. (Optional) Submit PRs with metadata (network, vault, operator) to the [metadata-hoodi](https://github.com/symbioticfi/metadata-hoodi/) repo. Examples: [13](https://github.com/symbioticfi/metadata-hoodi/pull/13), [14](https://github.com/symbioticfi/metadata-hoodi/pull/14), [15](https://github.com/symbioticfi/metadata-hoodi/pull/15) :::info If you complete step 6, entities and their interactions will be reflected on [app.hoodi.symbiotic.fi](https://app.hoodi.symbiotic.fi). ::: ### Vault Opt-In (Network) 1. Opt-in to vault by calling Delegator via `setMaxNetworkLimit` ([Sample Tx](https://hoodi.etherscan.io/tx/0x99f6a276c558184a6a5f325a25c1432f10d391d7e26207ffa8aaf1dd9a791efa)) 2. From emitted event, extract `subnetwork ID` Using the **Vault Admin** address: accept opt-in by calling Delegator via `setNetworkLimit` ([Sample Tx](https://hoodi.etherscan.io/tx/0x3918e61998aff1dc637f15ac3fe9024d6da9f0ef299a40f0b49c12eb623ad249)) :::: When finished, 2 test stakers should be able to deposit to the wstETH vault. --- ## /v1/integrate/networks/network-contract --- description: "Register your network using the NetworkRegistry smart contract. The registered address serves as your management address for interacting with Symbiotic vaults..." --- import { Card1 } from "../../../components/Card1"; # Network Contract Register your network using the `NetworkRegistry` smart contract. The registered address serves as your management address for interacting with Symbiotic vaults to accept stake allocations. We provide a [network repository](https://github.com/symbioticfi/network) that simplifies registration and makes management more secure. It includes the `Network.sol` contract and tooling to manage it. The `Network.sol` contract extends [OpenZeppelin](https://www.openzeppelin.com/)'s [`TimelockController.sol`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/governance/TimelockController.sol) with additional functionality to define delays for `(exact target | exact selector)` or `(any target | exact selector)` pairs. ## Prerequisites :::steps #### Clone the repository ```bash [bash] git clone --recurse-submodules https://github.com/symbioticfi/network.git cd network ``` #### Install dependencies ```bash [bash] npm install ``` ::: ## Deploy Network and Register The deployment scripts support two scenarios: 1. Deploy a network only (opt into vaults later) 2. Deploy a network with vault opt-ins :::info The `Network.sol` contract automatically registers itself on deployment. ::: ### Pure Network Deployment :::steps ##### Open [`DeployNetwork.s.sol`](https://github.com/symbioticfi/network/blob/main/script/DeployNetwork.s.sol) and configure these settings: ```solidity [DeployNetwork.s.sol] // Name of the Network string NAME = "My Network"; // Default minimum delay (will be applied for any action that doesn't have a specific delay yet) uint256 DEFAULT_MIN_DELAY = 3 days; // Cold actions delay (a delay that will be applied for major actions like upgradeProxy and setMiddleware) uint256 COLD_ACTIONS_DELAY = 14 days; // Hot actions delay (a delay that will be applied for minor actions like setMaxNetworkLimit and setResolver) uint256 HOT_ACTIONS_DELAY = 0; // Admin address (will become executor, proposer, and default admin by default) address ADMIN = 0x0000000000000000000000000000000000000000; // Optional // Metadata URI of the Network string METADATA_URI = ""; // Salt for deterministic deployment bytes11 SALT = "SymNetwork"; ``` ##### Edit the configuration fields, then run: ```bash [bash] forge script script/DeployNetwork.s.sol --rpc-url --private-key --etherscan-api-key --broadcast --verify ``` ::: ### Network Deployment With Opt-ins :::steps ##### Open [`DeployNetworkForVaults.s.sol`](https://github.com/symbioticfi/network/blob/main/script/DeployNetworkForVaults.s.sol) and configure these settings: ```solidity [DeployNetworkForVaults.s.sol] // Name of the Network string NAME = "My Network"; // Default minimum delay (will be applied for any action that doesn't have a specific delay yet) uint256 DEFAULT_MIN_DELAY = 3 days; // Cold actions delay (a delay that will be applied for major actions like upgradeProxy and setMiddleware) uint256 COLD_ACTIONS_DELAY = 14 days; // Hot actions delay (a delay that will be applied for minor actions like setMaxNetworkLimit and setResolver) uint256 HOT_ACTIONS_DELAY = 0; // Admin address (will become executor, proposer, and default admin by default) address ADMIN = 0x0000000000000000000000000000000000000000; // Vault address to opt-in to (multiple vaults can be set) address[] VAULTS = [0x0000000000000000000000000000000000000000]; // Maximum amount of delegation that network is ready to receive (multiple vaults can be set) uint256[] MAX_NETWORK_LIMITS = [0]; // Resolver address (optional, is applied only if VetoSlasher is used) (multiple vaults can be set) address[] RESOLVERS = [0x0000000000000000000000000000000000000000]; // Optional // Subnetwork Identifier (multiple subnetworks can be used, e.g., to have different resolvers for the same network) uint96 SUBNETWORK_ID = 0; // Metadata URI of the Network string METADATA_URI = ""; // Salt for deterministic deployment bytes11 SALT = "SymNetwork"; ``` ##### Edit the configuration fields, then run: ```bash [bash] forge script script/DeployNetworkForVaults.s.sol --rpc-url --private-key --etherscan-api-key --broadcast --verify ``` ::: ## Manage Your Network Use these five predefined action scripts: - [SetMaxNetworkLimit](https://github.com/symbioticfi/network/blob/main/script/actions/SetMaxNetworkLimit.s.sol) - set a new maximum network limit for the vault - [SetResolver](https://github.com/symbioticfi/network/blob/main/script/actions/SetResolver.s.sol) - set the resolver for the vault (only if the vault uses VetoSlasher) - [SetMiddleware](https://github.com/symbioticfi/network/blob/main/script/actions/SetMiddleware.s.sol) - set the middleware - [UpgradeProxy](https://github.com/symbioticfi/network/blob/main/script/actions/UpgradeProxy.s.sol) - upgrade the proxy (network itself) - [ArbitraryCall](https://github.com/symbioticfi/network/blob/main/script/actions/ArbitraryCall.s.sol) - make a call to any contract with any data All actions follow a similar pattern. Here's how to use [SetMaxNetworkLimit](https://github.com/symbioticfi/network/blob/main/script/actions/SetMaxNetworkLimit.s.sol) as an example: :::steps ##### Open [SetMaxNetworkLimit.s.sol](https://github.com/symbioticfi/network/blob/main/script/actions/SetMaxNetworkLimit.s.sol) and configure these settings: ```solidity [SetMaxNetworkLimit.s.sol] // Address of the Network address NETWORK = 0x0000000000000000000000000000000000000000; // Address of the Vault address VAULT = 0x0000000000000000000000000000000000000000; // Maximum amount of delegation that network is ready to receive uint256 MAX_NETWORK_LIMIT = 0; // Delay for the action to be executed uint256 DELAY = 0; // Optional // Subnetwork Identifier (multiple subnetworks can be used, e.g., to have different max network limits for the same network) uint96 SUBNETWORK_IDENTIFIER = 0; // Salt for TimelockController operations bytes32 SALT = "SetMaxNetworkLimit"; ``` ##### Edit the configuration fields, then choose an operation: - `runS()` - schedule an action - `runE()` - execute an action - `runSE()` - schedule and execute an action (only if the delay is zero) ##### Run the operation: - To execute the script with an EOA: ```bash [bash] forge script script/actions/SetMaxNetworkLimit.s.sol:SetMaxNetworkLimit --sig "runS()" --rpc-url --private-key --broadcast ``` - To get transaction calldata for a [Safe](https://app.safe.global/) multisig: ```bash [bash] forge script script/actions/SetMaxNetworkLimit.s.sol:SetMaxNetworkLimit --sig "runS()" --rpc-url --sender --unlocked ``` The logs will show a `callData` field like this: ```bash [Expected output] callData:0x01d5062a00000000000000000000000025ed2ee6e295880326bdeca245ee4d8b72c8f103000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000005365744d61784e6574776f726b4c696d697400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004423f752d500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002b6700000000000000000000000000000000000000000000000000000000 ``` In [Safe](https://app.safe.global/) Transaction Builder: 1. Enable "Custom data" 2. Enter the **Network's address** as the target address 3. Use the `callData` (e.g., `0x01d5062a0000000000000000000000...`) received earlier as the `Data (Hex encoded)` ::: ## Update Delays Every network action is protected by a delay (from zero to infinity). Use these update delay scripts for the actions above, plus additional ones: - [SetMaxNetworkLimitUpdateDelay](https://github.com/symbioticfi/network/blob/main/script/update-delay/SetMaxNetworkLimitUpdateDelay.s.sol) - [SetResolverUpdateDelay](https://github.com/symbioticfi/network/blob/main/script/update-delay/SetResolverUpdateDelay.s.sol) - [SetMiddlewareUpdateDelay](https://github.com/symbioticfi/network/blob/main/script/update-delay/SetMiddlewareUpdateDelay.s.sol) - [UpgradeProxyUpdateDelay](https://github.com/symbioticfi/network/blob/main/script/update-delay/UpgradeProxyUpdateDelay.s.sol) - [HotActionsUpdateDelay](https://github.com/symbioticfi/network/blob/main/script/update-delay/HotActionsUpdateDelay.s.sol) - update a delay for [SetMaxNetworkLimitUpdateDelay](https://github.com/symbioticfi/network/blob/main/script/update-delay/SetMaxNetworkLimitUpdateDelay.s.sol) and [SetResolverUpdateDelay](https://github.com/symbioticfi/network/blob/main/script/update-delay/SetResolverUpdateDelay.s.sol) - [ColdActionsUpdateDelay](https://github.com/symbioticfi/network/blob/main/script/update-delay/ColdActionsUpdateDelay.s.sol) - update a delay for [SetMiddlewareUpdateDelay](https://github.com/symbioticfi/network/blob/main/script/update-delay/SetMiddlewareUpdateDelay.s.sol) and [UpgradeProxyUpdateDelay](https://github.com/symbioticfi/network/blob/main/script/update-delay/UpgradeProxyUpdateDelay.s.sol) - [DefaultUpdateDelay](https://github.com/symbioticfi/network/blob/main/script/update-delay/DefaultUpdateDelay.s.sol) - update a delay for unconstrained actions - [ArbitraryUpdateDelay](https://github.com/symbioticfi/network/blob/main/script/update-delay/ArbitraryCallUpdateDelay.s.sol) - update a delay for an arbitrary call: - set a delay for the exact target address and the exact selector - set a delay for any target address and the exact selector (by setting target address to `0x0000000000000000000000000000000000000000`) For usage examples, see [Manage Your Network](/v1/integrate/networks/network-contract#manage-your-network). *** ## Dashboard :::warning Work-in-progress, use with caution. ::: The `Network.sol` contract inherits [OpenZeppelin](https://www.openzeppelin.com/)'s `TimelockController`, which inherits `AccessControl`. You can't determine the full state (operation statuses or role holders) using only RPC calls. Use the Network Dashboard to: - Get delays for all operations - Get holders of any role - Get scheduled/executed operations - Schedule/execute arbitrary actions :::info Network Dashboard UI: [https://network-sage-rho.vercel.app/](https://network-sage-rho.vercel.app/) ::: *** ## Next Steps
} href="/v1/integrate/networks/submit-metadata" />
--- ## /v1/integrate/networks/pre-deposit --- description: "You may want to curate or deploy vaults yourself in these cases:" --- import { Card1 } from "../../../components/Card1"; # Deploy Pre-deposit Vault You may want to curate or deploy vaults yourself in these cases: - A DAO-controlled vault with your network's native asset as collateral - Your network needs a specific vault configuration for onboarding See the [vault deployment guide](/v1/integrate/curators/deploy-vault) for details. ## Next Steps
} href="/v1/integrate/networks/rewards" />
--- ## /v1/integrate/networks/relay-offchain --- description: ":::warning The code is a work in progress and not production ready yet. Breaking changes may occur in code updates, and backward compatibility is not..." --- import { Card1 } from "../../../components/Card1"; # Relay Off-Chain :::warning The code is a work in progress and not production ready yet. Breaking changes may occur in code updates, and backward compatibility is not guaranteed. Use with caution. ::: 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](https://github.com/symbioticfi/relay/blob/dev/DEVELOPMENT.md). ## Configure :::info For a complete reference of all configuration options and command-line flags, see the [relay\_sidecar CLI documentation](https://github.com/symbioticfi/relay/tree/dev/docs/cli/relay/relay_sidecar.md). ::: The values you fill in come from the network you're joining: | Field | What to put | Where it comes from | | ------------------------------------ | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `driver.address` + `driver.chain-id` | The network's `ValSetDriver` and its chain | The network operator (see [Relay Utils → What you need](/v1/integrate/networks/relay-utils#what-you-need)). | | `evm.chains` | One RPC URL per chain the network uses | A node/provider for each chain in the driver's `getVotingPowerProviders()` ∪ `getSettlements()` ∪ driver chain. | | `secret-keys` | Your relay signing key(s) + an EVM key + a p2p key | Your keys — relay keys are the ones you registered in `KeyRegistry`; the relay key tag must match the network's `RequiredHeaderKeyTag`. | | `p2p.bootnodes` | Multiaddr(s) of the network's bootnodes | The network operator. | Create a `config.yaml` file with the following structure: ```yaml [config.yaml] # 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 policy ``` ### Configure via Command-Line Flags You can override config file values with command-line flags: ```bash ./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: ```bash 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.yaml ``` ### Configuration Priority Configuration is loaded in the following order (highest priority first): 1. Command-line flags 2. Environment variables (with `SYMB_` prefix) 3. Configuration file (specified by `--config`) ### External Voting-Power Providers A network can source voting power from an **off-chain** gRPC service instead of an EVM `VotingPowerProvider` contract. Routing is decided by the provider's chain ID in the network config: any `VotingPowerProvider` entry whose `chainId` falls in the reserved range `4_000_000_000..4_100_000_000` is treated as external, and the sidecar queries the matching gRPC endpoint you supply: ```bash ./relay_sidecar \ --config config.yaml \ --external-voting-power-provider id=,url= ``` Optional settings can be appended: `secure=true`, `ca-cert-file=`, `server-name=`, `timeout=`, `headers=`. `provider_id` is the first 10 bytes (20 hex chars) of the on-chain `CrossChainAddress.Address`. :::note Operators of a network that uses an external provider skip on-chain stake setup (registering in the EVM `VotingPowerProvider`, vault opt-in, deposit) for that provider — voting power comes from the external service. On-chain providers in the same network still follow the normal flow. ::: ### Example For reference, see how configurations are generated in the E2E setup: ```bash # See the template in e2e/scripts/sidecar-start.sh (lines 11-27) cat e2e/scripts/sidecar-start.sh ``` ## Download and Run Pre-built Docker images are available from Docker Hub: :::steps #### Pull the image The latest image: ```bash [bash] docker pull symbioticfi/relay:latest ``` Or a specific version: ```bash [bash] docker pull symbioticfi/relay: ``` #### 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 ![Network rewards distribution diagram](/v1/img/rewards-fee-split.png) 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" />