For the complete documentation index, see llms.txt. This page is also available as Markdown.

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

Constant
Value
What it points to

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

Type
CURVE
FX
FRAX
RESUPPLY
CONVEX

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 1e17 units 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 should sync() 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 must processExpiredLocks() / 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_weight on the platform); gauges validated against the platform's GAUGE_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 + overtime has 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, and GaugeProposer (the latter used for both Curve and F(x) gauge routes) entry points are permissionless subject to their external/epoch checks; only GenericDaoProposer is operator-gated.

  • Executors act only after the required on-chain checks:

    • CurveVoteExecutor / ResupplyVoteExecutor enforce a quorum (quorumBps = 1500, i.e. 15% minimum participation) — quorum is enforced at the executor, not at the DaoVotePlatform.

    • CurveGaugeExecutor / FxGaugeExecutor submit 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 the Delegation contract 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 call vote(...) on the relevant Dao/GaugeVotePlatform with the account as _account. Votes from unregistered addresses are rejected unless they equal _account.

  • Reading results: getYes/getNo/voteTotals or gaugeTotal per proposal; listen to vote events for real-time updates.

  • Proposing: createProposal on 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, and ResupplyDaoProposer), 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

Component
Address

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?