```
#### 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="/integrate/networks/helpful-core-contracts-endpoints"
/>
---
## /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.
::::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))
### 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)):
- Include RPC URLs needed for deployment and specify which modules to deploy on which chains
- **Do not replace \[1234567890] placeholder with endpoint\_url = ""**
- Contracts are deployed in this order: 1. KeyRegistry 2. VotingPowerProvider 3. Settlement 4. ValSetDriver
### Run the deployment
Execute the deployment script, e.g.:
```bash [bash]
./script/relay-deploy.sh ./script/examples/MyRelayDeploy.sol ./script/examples/my-relay-deploy.toml --broadcast --ledger
```
:::note
Basic form is `./script/relay-deploy.sh `
:::
At the end, your `toml` file will contain the addresses of the deployed Relay modules.
::::
## 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="/integrate/networks/relay-offchain"
/>
---
## /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](/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](/get-started/resources/addresses#rewards) page.
:::steps
#### Distribute Rewards
For `msg.sender`, use the Network Middleware account (see [Quickstart: Network with Staking](/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](/integrate/curators/registry-and-fees#claim-process) and [Operator: Claim Rewards](/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](/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](/integrate/curators/registry-and-fees#fee-related-calculations)
## Next Steps
}
href="/integrate/networks/slashing"
/>
}
href="/integrate/networks/relay-onchain"
/>
---
## /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](/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="/integrate/networks/relay-onchain"
/>
---
## /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](/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](/integrate/curators/submit-metadata) for details.
## Next Steps
}
href="/integrate/networks/pre-deposit"
/>
}
href="/integrate/networks/rewards"
/>