Voting Power
The VotingPowerProvider is the Relay contract that turns delegated Symbiotic stake into operator voting power inside a validator set. It sits between Symbiotic Core (vaults, operators, networks) and the Relay settlement layer, and exposes a clean interface for:
- which operators are in the validator set
- how much voting power each one has
- what that power was at a specific timestamp (for verifying old decisions)
This is what Relay uses to build validator sets and what Settlement uses to verify signatures.
Inputs
At a high level, VotingPowerProvider pulls three kinds of data:
- Stake and vault state from Symbiotic Core (how much collateral each operator has in which vaults / networks).
- Onboarding / filtering modules, e.g.
- OperatorsWhitelist / OperatorsBlacklist / OperatorsJail – which operators are even allowed in the set
- SharedVaults / OperatorVaults – which vaults are considered for this network
- MultiToken / OpNetVaultAutoDeploy – which tokens and “auto-created” vaults are in scope
- Voting power calculators, which define how raw stake → voting power.
The public view methods (like getOperatorVotingPower and getOperatorVotingPowerAt) are what off-chain tooling and Relay’s ValSetDriver actually call when they derive the active set and its weights.
From Delegated Stake to Voting Power
The core idea: delegated stake is the input, but the contract lets you pick the function that maps “stake” to “voting power”.
Some standard derivations:
- Equal – every opted-in operator gets the same power, regardless of stake. Useful for “one node, one vote” or PoA-style governance.
- Linear – power proportional to stake. This is the vanilla PoS model: double the effective stake, double the voting power.
- Capped / concave – diminishing returns at higher stake. You can cap per-operator power or use a concave function to stop one operator from dominating the set even if they bring a lot of collateral.
- Behavior-aware – adjust weights based on uptime, freshness, or custom performance metrics. In practice you’d compute a “score” off-chain and feed it in via weights or parameters that sit on top of the base calculators.
VotingPowerProvider achieves this through a plug-in set of VotingPowerCalculators. The repo ships with several that you can compose or chain:
EqualStakeVPCalcNormalizedTokenDecimalsVPCalc– normalize all tokens to 18 decimalsPricedTokensChainlinkVPCalc– convert different tokens to a common value using Chainlink price feedsWeightedTokensVPCalc– apply token-level weightsWeightedVaultsVPCalc– apply vault-level weights (e.g. “this vault counts 1.2×, that one 0.8×”)
Networks can pick a simple single calculator (e.g. “linear stake using normalized decimals”) or a pipeline (“normalize decimals → price everything in ETH → apply vault weights”).
Multi-Token Considerations
In practice, stake can come from multiple collaterals across several vaults:
- LST A, LRT B, native token, etc.
- different decimals and potentially different prices
VotingPowerProvider does not impose one global rule here; it just provides building blocks. The network defines:
- which tokens are accepted (via MultiToken module and supported-token registration)
- how each token is converted into a single comparable number (e.g. price feeds, fixed haircuts, or simple 1:1)
- whether some tokens or vaults get higher or lower weight in the final voting power
The end result is a single voting power number per operator that already bakes in all these decisions.
Quorum and Time Variation
Once voting power is defined, the network can reason about quorums and thresholds:
- decisions might require > 50% or ≥ 2/3 of total voting power
- some sub-protocols (e.g. light client, DA, bridge) can have their own minimum voting power requirements
Voting power is time-varying:
- stake moves in and out of vaults
- operators join, leave, or are jailed / unregistered
- token prices change if you use price-based calculators
- off-chain behavior metrics change over time
To keep things tractable, most networks update the validator set once per epoch and use that snapshot for all decisions in that period. Relay’s contracts are built around this:
ValSetDriverusesVotingPowerProviderto derive the validator set at a chosen genesis and at subsequent epochs.Settlementverifies signatures against exactly that compressed validator set header, so verifiers and networks agree on which power distribution applied.
This gives you a clean, epoch-by-epoch history of “who had how much power when”.
With and Without Relay
You can think of VotingPowerProvider as the Relay version of what a custom middleware might do.
- Without Relay Your own middleware reads vault accounting and Delegator state directly from Symbiotic Core, applies your stake → voting power rules off-chain, and uses that to drive your protocol. You still need to handle cross-chain verification and efficient proof formats yourself.
- With Relay
VotingPowerProvideris the on-chain oracle of voting power for the Relay network. Relay’s off-chain sidecar and ValSetDriver:- read operator/vault data from Symbiotic Core
- call
VotingPowerProviderto get per-operator voting power - compress that into a validator set header
- commit it into
Settlement, which is then used to verify aggregated signatures on any connected chain
In other words: VotingPowerProvider is where you define what “power” means for your network, and Relay takes that definition and turns it into a cheap, verifiable validator set you can reuse everywhere.
