On-Chain Voting
Convex has deployed a dedicated on-chain voting platform that lets vlCVX holders and delegates participate in governance across multiple external protocols: Curve, f(x), Frax, and Resupply. Convex aggregates sentiment from vlCVX holders/delegates on-chain. Where executors are registered, the platform then executes consolidated votes on the destination protocol's governance contracts (Curve DAO, Curve gauge, f(x) gauge, Resupply DAO); for protocols without a registered executor, votes finalize at the DaoVotePlatform level. The same infrastructure is the foundation for moving Convex's own administrative control toward token-weighted on-chain governance.
GitHub: https://github.com/convex-eth/voting
VotingRegistry
The routed platform components are discoverable through the VotingRegistry (mainnet 0x6C3a56ea7c2DA4ee2876DbDa437173B28f001c34) via a two-key lookup:
function getAddress(string platform, uint8 type) external view returns (address)Keys are case-sensitive (e.g. "CURVE", "FX", "FRAX", "RESUPPLY", "CONVEX"). The registry itself and the legacy VoteDelegateExtension have no registry route.
Type constants
VOTE_DAO
0
DaoVotePlatform for the platform
VOTE_GAUGE
1
GaugeVotePlatform for the platform
GAUGE_REGISTRY
2
Gauge registry contract (Curve and F(x) only)
DAO_EXECUTOR
3
DAO vote executor (Curve, Resupply only)
GAUGE_EXECUTOR
4
Gauge vote executor (Curve, F(x) only)
DAO_PROPOSER
5
DAO proposal creator
GAUGE_PROPOSER
6
Gauge proposal creator (Curve, F(x) only)
DAO_METADATA
7
Proposal metadata contract (FX, FRAX, CONVEX live)
OTHER_TYPE
8
reserved/generic
Additional shared registrations: "DELEGATION" + VOTE_DAO (0) → Delegation (DAO), "DELEGATION" + VOTE_GAUGE (1) → Delegation (Gauge), "SURROGATE" + VOTE_DAO/VOTE_GAUGE → SurrogateRegistry, "ConvexCore" + VOTE_DAO → ConvexCore, "OWNER" + VOTE_DAO → ConvexCore (alias), "HELPER" + VOTE_GAUGE → GaugeVoteHelper.
What each platform registers
VOTE_DAO (0)
DaoVotePlatform
DaoVotePlatform
DaoVotePlatform
DaoVotePlatform
DaoVotePlatform
VOTE_GAUGE (1)
GaugeVotePlatform
GaugeVotePlatform
—
—
—
GAUGE_REGISTRY (2)
CurveGaugeRegistry
FxGaugeRegistry
—
—
—
DAO_EXECUTOR (3)
CurveVoteExecutor
—
—
ResupplyVoteExecutor
—
GAUGE_EXECUTOR (4)
CurveGaugeExecutor
FxGaugeExecutor
—
—
—
DAO_PROPOSER (5)
CurveDaoProposer
GenericDaoProposer
GenericDaoProposer
ResupplyDaoProposer
GenericDaoProposer
GAUGE_PROPOSER (6)
GaugeProposer
GaugeProposer
—
—
—
Note: f(x) Protocol, Frax and Convex Finance have no DAO executor registration — votes on those platforms finalize at the DaoVotePlatform level and are not executed into an external contract.
Voting weight model
Voting weight is derived directly from vlCVX lock state — no off-chain merkle proofs.
baseWeight=vlCVX.balanceAtEpochOf(proposalEpoch, user)— the user's raw vlCVX balance at the proposal's reference epoch.Add/subtract delegated weight via
adjustedWeight(a signed value — it can go negative when a direct delegator reclaims weight from a delegate).Effective voting weight =
baseWeight + adjustedWeight.Delegation weights are stored truncated to
1e17units in the Delegation contract, so sub-0.1-CVX precision is lost.
Proposals are anchored to a vlCVX epoch. Gauge proposals record epoch = vlCVX.epochCount() - 2 (current epoch); DAO proposals may walk backward to an epoch beginning at/before _startTime. The epoch reference is fixed per proposal, but each account's balance is read lazily when that account votes or re-votes, not copied at proposal creation.
Delegation
The Delegation.sol contract lets a vlCVX holder delegate their weight to one address (depth 1 — chains are not followed):
setDelegate(_delegate)— changes take effect from the next epoch;address(0)removes delegation.sync(_user)/syncAtEpoch(_user, _epoch)— propagate a user's changed vlCVX balance into the delegate's accumulated weight. Because vlCVX balances change over time, delegators and delegates shouldsync()periodically.balanceOf(_delegate)/balanceAtEpochOf(_epoch, _delegate)— a delegate's total accumulated voting weight.Expired locks: if a delegator's vlCVX locks have expired,
sync()reverts — the user mustprocessExpiredLocks()/ relock first.
Delegation powers both DAO and gauge voting (two separate Delegation instances registered under "DELEGATION"). On the voting platform, lossless-style weight checks: a first vote syncs if cached delegation weight differs from the Delegation contract; re-votes use deltas.
Surrogates
SurrogateRegistry.sol lets an account register a surrogate address that may vote on its behalf:
setSurrogate(_surrogate)— caller registers another address as its surrogate.isSurrogate(_surrogate, _account)— check helper.Voting contracts accept votes from an account or its registered surrogate.
Priority rule: a direct vote from the account itself overrides a surrogate vote and permanently blocks later surrogate updates for that proposal. There is no signature/EIP-1271 vote path on-chain — the caller must be the account or its registered surrogate.
DaoVotePlatform (yes/no voting)
createProposal(startTime, endTime, voteType, proposalId) (operator-only). Duration must be 1–6 days. Concurrent DAO proposals are supported — a new proposal does not wait for the previous one's finalization (that restriction is a gauge-voting rule).
vote(proposalId, account, yesWeight, noWeight)— weights in basis points, must sum to 10000 (e.g.(10000,0)= yes,(6000,4000)= 60/40 split).voteType∈ {0 = Ownership, 1 = Parameter} — metadata for off-chain policy, and used by the Curve vote executor to select ownership vs parameter voting.getYes/getNo/voteTotals,getVote,isFinalized,forceEndProposal.Proposal IDs are external references (snapshot hash, forum ID) recorded on-chain.
A user can re-vote/change their vote while the proposal is active; weight changes propagate to totals on each vote.
GaugeVotePlatform (gauge weight voting)
createProposal(startTime, endTime) (operator-only). Same epoch anchoring and 1–6 day duration. Voters allocate their weight across multiple gauges:
vote(account, gauges[], weights[])— weights are parts per million of 1,000,000, must be nonzero, and must sum exactly to 1,000,000 for every vote (max_weighton the platform); gauges validated against the platform'sGAUGE_REGISTRY(Curve or f(x)).gaugeTotal(proposalId, gauge)/getGaugeCount/getGaugeEntry— read totals per gauge.forceEndProposal(),isFinalized,setOvertimeAccount(account, active)— marks equalizer accounts that may vote/re-vote during the 10-minute overtime after a gauge proposal's normal end.A new gauge proposal cannot begin until the previous one's
endTime + overtimehas passed.
Executors, proposers & metadata
Proposers create/mirror the internal proposal before voting and prepare the external reference; they do not draft the final execution payload.
CurveDaoProposer,ResupplyDaoProposer, andGaugeProposer(the latter used for both Curve and F(x) gauge routes) entry points are permissionless subject to their external/epoch checks; onlyGenericDaoProposeris operator-gated.Executors act only after the required on-chain checks:
CurveVoteExecutor/ResupplyVoteExecutorenforce a quorum (quorumBps = 1500, i.e. 15% minimum participation) — quorum is enforced at the executor, not at theDaoVotePlatform.CurveGaugeExecutor/FxGaugeExecutorsubmit gauge weight votes to the respective gauge controllers.
ProposalMetadata contracts store operator-written title/description/reference URL keyed by internal proposal ID. Executors do not read metadata and there is no enforced payload-hash linkage — treat metadata as documentation, not as verified execution evidence.
ConvexCore (registry owner / governance alias)
ConvexCore.sol (mainnet 0xCC07e8BA6bc8aeb18C4AE110C3Da9c7Dce4A3e74) is the registry owner ("OWNER" registry key). It is a one-of-N operator dispatcher: any of its operators can execute(target, data) arbitrary calls and manage the operator set (setOperator). It is not currently token-governed — verify operatorCount()/operatorList on-chain before assuming its trust model. Its role is to be the point of control that governance-aligned operators act through on the way to fuller on-chain ownership.
Integrating with the voting platform
Reading voting power:
vlCVX.balanceAtEpochOf(epoch, account)for raw weight; use theDelegationcontract for aggregated/delegated weight; ask the user to register a surrogate if your service submits votes on their behalf.Submitting votes as a service: register as the user's surrogate via
SurrogateRegistry, then callvote(...)on the relevant Dao/GaugeVotePlatform with the account as_account. Votes from unregistered addresses are rejected unless they equal_account.Reading results:
getYes/getNo/voteTotalsorgaugeTotalper proposal; listen to vote events for real-time updates.Proposing:
createProposalon the vote platforms is operator-only — external integrations can not create proposals there. The permissionless entry points are the proposer contracts (e.g.CurveDaoProposer,GaugeProposer— used for both Curve and F(x) gauge routes, andResupplyDaoProposer), which mirror external proposals into the platform subject to their own epoch/external checks and do not require operator coordination. Only the generic DAO proposer routes are operator-gated.
Deployed addresses
VotingRegistry
0x6C3a56ea7c2DA4ee2876DbDa437173B28f001c34
ConvexCore
0xCC07e8BA6bc8aeb18C4AE110C3Da9c7Dce4A3e74
SurrogateRegistry
0x8E4828a8C69A837F95Caa0D5e18fa09Ded12F73f
DaoDelegation
0x22697721EC1C14e42305e91BF02672932E8a7B09
GaugeDelegation
0xb8270eef1319173dE9f5033FED442F638ff1607d
Curve DAO voting
0xE645F3d7b04BFFD67F5Ad4457c08E0C8FE1ddB89
Curve gauge voting
0x64D9B5AC386B70af9EDCD20A58cE9262D2EAC278
F(x) DAO voting
0x51e08c14991387C2F5ad2E374eBc9B144FFC1E13
F(x) gauge voting
0xDcEa673B021f1f431E7D0Ec70a63bF8DcB6d44E6
Frax DAO voting
0xC4df4415120C1305b7dd998a881e809fA6695f03
Resupply DAO voting
0xb463Cf769577aa062Cb72f5e1f8276eDF28914f3
Convex DAO voting
0x26674330B9696E052D70b4F971E46425761daCc2
VoteDelegateExtension (legacy)
0x5349ffba494aC3c888ffa16fD438F44B8c67fB07
The canonical addresses and upgrade/replacement records live in the voting repo's deployment/ directory (mainnet.json, plus per-change safe transaction files). Verify live registrations with VotingRegistry.getAddress(...) before publishing.
Last updated
Was this helpful?