Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
LABS
Guides

How to Implement a Vote Escrow (veToken) Model

A step-by-step technical guide to designing and deploying a vote-escrow token system, covering contract architecture, lock management, and gauge integration with Solidity examples.
Chainscore © 2026
introduction
DEVELOPER GUIDE

How to Implement a Vote Escrow (veToken) Model

A technical guide to building a vote-escrow tokenomics system from scratch, covering core contracts, lock mechanics, and governance integration.

A vote escrow (veToken) model is a tokenomics mechanism that aligns long-term incentives by allowing users to lock their governance tokens in exchange for voting power and protocol rewards. Pioneered by Curve Finance with its veCRV system, this model has become a standard for decentralized autonomous organizations (DAOs) seeking to reduce sell pressure and encourage committed participation. The core principle is simple: lock tokens for a chosen duration (e.g., 1 week to 4 years) to receive non-transferable veTokens, with voting power weighted by both the amount locked and the lock time.

Implementing a veToken system requires several key smart contract components. The central contract is the VotingEscrow contract, which manages user locks, minting/burning of veTokens, and the calculation of voting power. A user's voting power decays linearly over time until their lock expires, a mechanism known as time-weighted voting power. You'll also need a RewardsDistributor contract to allocate incentives (like protocol fees or token emissions) to veToken holders proportionally to their voting power. These contracts must integrate with your core protocol's governance and reward systems.

Here is a simplified Solidity snippet showing the core structure for creating a lock, adapted from the ve(3,3) model used by protocols like Solidly:

solidity
function createLock(uint256 _value, uint256 _unlockTime) external {
    require(_value > 0, "Must lock non-zero amount");
    require(_unlockTime > block.timestamp, "Unlock time must be in the future");
    require(_unlockTime <= block.timestamp + MAX_TIME, "Lock duration too long");

    _depositFor(msg.sender, _value, _unlockTime, LockedBalance(0,0), DepositType.CREATE_LOCK);
}

function _depositFor(...) internal {
    // Update the user's locked balance struct
    locked[msg.sender] = LockedBalance(amount, unlockTime);
    // Calculate and mint the new veToken balance (voting power)
    _checkpoint(msg.sender);
    // Transfer the underlying tokens into the escrow contract
    IERC20(token).safeTransferFrom(msg.sender, address(this), _value);
}

The _checkpoint function is critical, as it updates the user's decaying voting power based on the new lock parameters.

When designing your system, you must decide on critical parameters: the maximum lock duration (commonly 4 years), the minimum lock duration (often 1 week), and the decay slope for voting power. A steeper decay incentivizes longer locks. You also need a mechanism for users to increase their lock amount or duration and to withdraw tokens only after the lock expires. Security is paramount; the escrow contract must be thoroughly audited, as it will custody a significant portion of the protocol's native token supply. Consider using battle-tested implementations like the veToken Suite from Curve as a reference or base.

Integrating the veToken model with your protocol's governance is the final step. Voting power from the VotingEscrow contract should be used to weigh votes on Snapshot off-chain proposals or on-chain governor contracts (like OpenZeppelin's Governor). Furthermore, protocol fees or inflationary token emissions can be directed to the RewardsDistributor, which automatically allocates them to veToken holders. This creates a powerful flywheel: users lock tokens for more voting power and rewards, which in turn reduces circulating supply and stabilizes the token's economics, fostering long-term protocol alignment.

prerequisites
VE TOKEN IMPLEMENTATION

Prerequisites and Required Knowledge

Before building a vote-escrow (veToken) system, you need a solid foundation in smart contract development, tokenomics, and governance mechanics.

A vote-escrow (veToken) model is a tokenomic mechanism that locks a governance token (e.g., CRV, BAL) to grant voting power and protocol rewards. The core principle is simple: the longer a user locks their tokens, the more governance weight and incentives they receive. This aligns long-term user and protocol interests, creating a powerful flywheel for liquidity and governance participation. Understanding this fundamental trade-off—liquidity for influence—is the first prerequisite.

You must be proficient in smart contract development using Solidity and a framework like Hardhat or Foundry. Key concepts include: - ERC-20 token standards - Time-locked contracts and vesting schedules - Secure upgrade patterns (e.g., Transparent Proxy) - Gas optimization for state updates. Familiarity with decentralized governance frameworks like OpenZeppelin Governor or Compound's Governor Bravo is also essential, as the veToken will integrate with these systems to execute on-chain votes.

A successful implementation requires designing the locking mechanics. You'll need to decide on: - Minimum and maximum lock durations (e.g., 1 week to 4 years) - The decay function for voting power over time (often linear) - The formula for reward boosts (e.g., a multiplier based on lock time). Reference implementations like Curve Finance's veCRV or Balancer's veBAL are invaluable for studying these patterns in production.

Finally, you'll need a testing and deployment strategy. Write comprehensive tests for edge cases: early lock extensions, partial withdrawals after expiry, and correct voting power calculations across time. Plan for contract upgradeability from the start, as parameters like max lock time may need adjustment. Deploy to a testnet (like Sepolia) and use a block explorer to verify all state transitions before a mainnet launch. This groundwork ensures your veToken system is secure, efficient, and ready to govern.

core-architecture
CORE CONTRACT ARCHITECTURE

How to Implement a Vote Escrow (veToken) Model

A technical guide to building a veToken system for governance and reward distribution, based on the model popularized by Curve Finance.

The vote escrow (veToken) model is a core DeFi primitive that aligns long-term incentives by locking governance tokens to grant voting power and boosted rewards. The canonical implementation is Curve's veCRV. The system's architecture typically involves three main contracts: a staking/locking contract, a voting contract, and a rewards distributor. The core principle is that a user's voting weight and reward multiplier are proportional to the amount of tokens locked and the duration of the lock, up to a maximum (e.g., 4 years). This creates a time-based commitment, discouraging short-term speculation.

The central contract is the Vote Escrow contract itself. Its primary functions are create_lock, increase_amount, increase_unlock_time, and withdraw. When a user calls create_lock, they deposit their base token (e.g., CRV) and specify a lock duration. The contract mints a non-transferable veToken NFT (ERC-721) representing the locked position. The voting power for this NFT is calculated as locked_amount * (lock_time / max_lock_time). This power decays linearly over time until the unlock date, after which it reaches zero and the underlying tokens can be withdrawn.

To integrate this with a protocol's gauge system for reward distribution, you need a Gauge Controller. This contract manages the relative weights of different liquidity pools (gauges) for reward emission. The veToken holder calls vote_for_gauge_weights on the controller, allocating their decaying voting power to their preferred gauges. The controller calculates each gauge's weight based on the total votes it receives, which then determines the proportion of weekly reward emissions it receives from a central Minter or Rewards Distributor contract.

A key feature for user incentives is reward boosting. When a user provides liquidity in a gauge, their share of the gauge's rewards is boosted based on their veToken balance relative to the total liquidity. The formula, often implemented in gauge contracts, is: boost = min(0.4, 0.4 * (user_ve_balance / (total_liquidity * 0.4))). This means a user with sufficient veTokens can earn up to a 2.5x multiplier on their base liquidity provider rewards, creating a strong flywheel for protocol loyalty.

Here is a simplified Solidity snippet for the core locking logic:

solidity
function create_lock(uint256 _value, uint256 _unlock_time) external {
    require(_value > 0, "No zero value");
    require(_unlock_time <= block.timestamp + MAX_TIME, "Lock time too long");
    require(_unlock_time > block.timestamp, "Lock time in past");

    _deposit_for(msg.sender, _value, _unlock_time, LockedBalance({amount: _value, end: _unlock_time}), DepositType.CREATE_LOCK);

    // Mint an NFT representing the lock position
    _mint(msg.sender, tokenId++);
}

function _deposit_for(...) internal {
    // Update user's locked balance and total supply
    // Calculate new voting power = amount * (unlock_time - now) / MAX_TIME
}

When implementing a veToken system, critical security considerations include ensuring the voting power decay math is correct and cannot be manipulated, making the veNFT truly non-transferable (overriding ERC-721 transferFrom), and carefully auditing the integration between the escrow, gauge controller, and reward distributor to prevent inflation exploits. Successful deployments like Curve, Balancer, and Frax Finance demonstrate its effectiveness for building sticky, governance-aligned liquidity.

key-concepts
VOTE ESCROW (VETOKEN) MODEL

Key Concepts and Definitions

The vote-escrow (veToken) model aligns long-term incentives by locking governance tokens for voting power and protocol rewards. This guide covers its core mechanics and implementation.

01

Core Mechanism: Locking for Power

The veToken model converts a base governance token (e.g., CRV, BAL) into a non-transferable vote-escrowed token (e.g., veCRV).

  • Lock Duration: Users lock tokens for a set period (e.g., 1-4 years).
  • Voting Power: Power is proportional to the amount locked multiplied by the lock time. It decays linearly to zero at unlock.
  • Key Property: veTokens are non-transferable and non-tradable, tying power directly to committed users.
02

Fee Distribution & Reward Boosts

Protocols use veToken holdings to distribute fees and boost user rewards.

  • Fee Sharing: A portion of protocol revenue (e.g., trading fees) is distributed to veToken holders, often in ETH or stablecoins.
  • Yield Boosts: Liquidity providers who hold veTokens receive multipliers on their LP rewards (e.g., up to 2.5x on Curve).
  • Implementation: Requires a fee distributor contract and a mechanism to calculate and claim rewards based on a user's veToken balance.
03

Gauge Weight Voting

veToken holders vote to direct emission incentives to specific liquidity pools via gauge weights.

  • Gauges: Smart contracts that measure liquidity and distribute token emissions.
  • Weekly Epochs: Votes are typically cast weekly. Emission rewards are distributed proportionally to the votes each pool receives.
  • Bribes: Third-party protocols can offer bribes (extra tokens) to veToken holders to vote for their pool, creating a secondary market for governance influence.
04

Technical Implementation Overview

Building a veToken system requires several core smart contracts.

  • VotingEscrow Contract: Manages token locks, mint/burn of veNFTs, and calculates voting power. Inspired by Curve's vyper implementation.
  • Gauge Controller: Records user votes for different gauges and calculates weight distributions.
  • Minter & Reward Distributor: Handles the minting of protocol tokens for emissions and the distribution of fees to veToken holders.
  • Key Libraries: Use established audits and templates from protocols like Curve, Balancer, and Solidly.
05

veNFTs and ERC-721 Representation

Modern implementations represent a user's lock position as a veNFT (ERC-721 non-fungible token).

  • Position Flexibility: Each NFT contains metadata like lock amount, unlock time, and voting power. Users can have multiple locks.
  • Composability: veNFTs can be used as collateral in other DeFi protocols, though their non-transferable nature limits this.
  • Standardization: Adopting the ERC-721 standard improves interoperability with wallets and marketplaces for visualization.
06

Security Considerations & Audits

The locking mechanism and reward distribution are critical attack surfaces.

  • Time Manipulation: Ensure lock expiration and power decay are resistant to timestamp manipulation.
  • Reward Math: Fee and inflation distribution formulas must be gas-efficient and prevent rounding errors.
  • Governance Attacks: Design vote delegation and quorum rules to prevent low-cost takeover.
  • Audit Sources: Reference audits from firms like Trail of Bits, OpenZeppelin, and Quantstamp for established implementations like Curve.
implementing-voting-escrow
CORE CONCEPT

Step 1: Implementing the Voting Escrow Contract

The vote-escrow (veToken) model is a governance mechanism that ties voting power to the duration of locked tokens. This guide walks through implementing the core smart contract logic.

The foundational contract for a veToken system is the VotingEscrow. Its primary function is to accept a user's governance tokens (e.g., ERC20 tokens like XYZ) and lock them for a user-defined period. In return, the user receives a non-transferable veToken (e.g., veXYZ) that represents their voting power. The key innovation is that voting power decays linearly over time, incentivizing long-term alignment. A user locking 1000 tokens for 4 years receives maximum power initially, while locking for 1 year grants only a fraction.

The contract must manage a ledger of user locks, each storing the locked amount, the unlock time, and a calculated voting power. The core state variables include a mapping from user address to a LockedBalance struct. Critical functions include create_lock(uint256 _value, uint256 _unlock_time) to deposit tokens and initiate a lock, and increase_amount(uint256 _value) or increase_unlock_time(uint256 _unlock_time) to modify an existing position. Withdrawals are only permitted after the lock expires via a withdraw() function.

Calculating voting power is central to the design. The formula is typically: voting_power = locked_amount * (unlock_time - current_time) / max_lock_duration. This linear decay must be computed on-chain for governance snapshots. The contract often implements the ERC20 interface for the veToken itself (with a balanceOf that returns the decaying power) and the ERC721 interface to represent each lock as a non-fungible position, a pattern popularized by Curve Finance's veCRV.

Security considerations are paramount. The contract must prevent manipulation of voting power snapshots, ensure accurate time calculations using block timestamps, and guard against reentrancy during deposits or withdrawals. Common practice is to use a maximum lock duration (e.g., 4 years) and to round unlock times to weekly increments to reduce gas costs and simplify calculations. All state changes should emit events like Deposit and Withdraw for off-chain indexing.

Here is a simplified code snippet for the core lock creation logic in Solidity, assuming an underlying ERC20 token _token:

solidity
function create_lock(uint256 _value, uint256 _unlock_time) external nonReentrant {
    require(_value > 0, "Must lock non-zero amount");
    require(_unlock_time > block.timestamp, "Unlock time must be in future");
    require(_unlock_time <= block.timestamp + MAX_TIME, "Lock duration too long");
    require(userLocks[msg.sender].amount == 0, "Existing lock found");

    _token.safeTransferFrom(msg.sender, address(this), _value);
    userLocks[msg.sender] = LockedBalance(_value, _unlock_time);
    _update_voting_power(msg.sender);
    emit Deposit(msg.sender, _value, _unlock_time);
}

After deploying the VotingEscrow contract, the next step is integrating it with a governance system (like OpenZeppelin Governor) and gauge systems for directing emissions. The veTokens minted by this contract become the source of truth for voting weight. Developers should thoroughly audit the time-dependent math and consider using established libraries like Solidity's SafeCast for arithmetic operations to prevent overflows, given the potential value of locked assets.

implementing-gauge-controller
IMPLEMENTING VOTE ESCROW

Step 2: Building the Gauge Controller

The gauge controller is the core governance module that manages vote-locked token weights and allocates emissions to liquidity gauges.

The gauge controller is a smart contract that translates governance power from vote-escrowed tokens (like veCRV or veBAL) into specific weight allocations for liquidity pools. Users lock their governance tokens for a set duration to receive non-transferable veTokens, which grant them voting power. This power is then delegated to the gauge controller contract to vote on the distribution of protocol incentives (e.g., token emissions) across designated liquidity gauges. The controller calculates a continuous weight for each gauge based on the aggregated votes, ensuring a smooth, weekly adjustment of rewards.

A standard implementation involves several key state variables and functions. The contract must track: the total voting supply, a list of approved gauge types and addresses, and a history of user votes. The core function is vote_for_gauge_weights(address _gauge_addr, uint256 _user_weight), which allows a veToken holder to allocate their voting power. The controller sums all votes for a gauge at the end of an epoch (often one week) and calculates its weight as a fraction of the total votes cast. This weight directly determines the share of weekly emissions the corresponding gauge receives.

To prevent gaming and ensure fairness, the design incorporates a vote decay mechanism. A user's voting power diminishes linearly over time, mirroring the decay of their veToken balance as the lock period expires. The controller must recalculate weights for each gauge in every epoch to reflect this decay and any new votes. Furthermore, implementations often include a change_gauge_weight(address _gauge, uint256 _weight) function, reserved for governance multisigs, to manually adjust weights in emergencies or for new gauge onboarding before community voting occurs.

Here is a simplified code snippet illustrating the core vote recording logic in Solidity:

solidity
function vote(address gauge, uint256 weight) external {
    uint256 userPower = IVotingEscrow(ve).balanceOf(msg.sender);
    require(userPower > 0, "No voting power");
    require(weight <= 10000, "Weight too high"); // Weight in basis points

    // Clear previous vote power from this gauge
    uint256 oldWeight = _userVotes[msg.sender][gauge];
    _gaugeWeights[gauge] -= oldWeight;
    _totalWeight -= oldWeight;

    // Apply new vote
    uint256 votePower = (userPower * weight) / 10000;
    _userVotes[msg.sender][gauge] = votePower;
    _gaugeWeights[gauge] += votePower;
    _totalWeight += votePower;

    emit Voted(msg.sender, gauge, votePower);
}

This function updates the gauge's accumulated weight based on the user's current veToken balance and their chosen weight allocation.

Integrating the gauge controller requires it to be permissionlessly callable by the emissions distributor (often called the Minter). At the start of a new rewards epoch, the distributor queries the controller for the current weight of each gauge. It then mints the period's token emissions and allocates them proportionally. For example, if Gauge A has a controller weight of 30% and the weekly emission is 100,000 tokens, then 30,000 tokens are scheduled for distribution to liquidity providers in that pool. This creates a direct feedback loop where governance participants steer liquidity to the most valued pools.

integrating-rewards-gauges
VE TOKEN MODEL

Step 3: Integrating Rewards and Liquidity Gauges

This section details how to connect your vote-escrowed tokens to a reward distribution system, enabling protocol governance and liquidity incentives.

After implementing the core veToken contract for locking, the next step is to distribute rewards to token holders. This is managed by a rewards distributor contract. Its primary function is to accept reward tokens (like protocol fees or emissions) and allocate them proportionally to all veToken holders based on their voting power and lock duration. A common pattern, used by protocols like Curve Finance, is for the distributor to calculate a continuous reward rate per veToken (often called reward_per_token) and allow users to claim accrued rewards on-demand.

To incentivize liquidity in specific pools, protocols use liquidity gauges. A gauge is a smart contract assigned to a single liquidity pool (e.g., a Uniswap V3 position or a Curve pool). veToken holders vote to allocate their voting power to their preferred gauges. The total weekly emissions are then distributed to gauges in proportion to their share of the total votes. This creates a direct link between governance and liquidity provisioning, allowing the community to steer incentives toward the most valuable pools.

Implementing a gauge involves creating a contract that tracks a user's deposited LP tokens and calculates their share of the gauge's rewards. A basic gauge interface includes functions like deposit(uint256 amount), withdraw(uint256 amount), and getReward(). The gauge must be permissioned by the protocol's governance to receive emissions from the central distributor. Security is critical; gauge code should be audited, as it holds user funds. Many projects fork and adapt the well-tested gauge contracts from Curve or Balancer.

The final integration connects all components. The sequence is: 1) Users lock tokens to get veNFTs, 2) They vote with their ve balance on gauges, 3) The rewards distributor sends weekly emissions to gauges based on votes, 4) Users providing liquidity to a gauge deposit LP tokens and earn rewards. This model, often called vote-escrow with gauge voting, aligns long-term token holders with protocol health by making them the arbiters of liquidity incentives.

ARCHITECTURE

veToken Implementation Comparison: Curve vs. Balancer

A technical comparison of the two dominant veToken implementations, highlighting key design choices and trade-offs.

Feature / MetricCurve Finance (veCRV)Balancer (veBAL)

Core Locking Contract

VotingEscrow (Solidity)

VotingEscrow (Solidity, forked)

Native Token Locked

CRV

BAL + WETH BPT (80/20 pool)

Maximum Lock Duration

4 years

1 year

Boost Mechanism

vote_weight = balance * (time_remaining / max_time)

Uses gauge system; veBAL weight influences gauge votes

Gauge Voting System

Weekly votes on gauge weight distributions

Weekly votes on gauge weight distributions

Fee Distribution to Lockers

50% of trading fees (USDT, DAI, USDC)

100% of protocol fees (swap & yield)

Admin Control / Upgradability

DAO-controlled (CRV holders)

DAO-controlled (BAL holders), time-lock

Major Fork / Derivative

Inspires ve(3,3), veNFT models

Less forked; unique BPT lock requirement

VOTE ESCROW (VETOKEN) MODEL

Common Implementation Pitfalls and Security Considerations

Implementing a veToken model introduces complex smart contract logic and economic incentives. This guide addresses frequent developer questions and critical security risks to avoid.

This is a common issue stemming from incorrect vote-locked token accounting. The balance should be a function of locked.amount * (lock_end - current_time) / MAX_TIME. A failure to update typically occurs when the contract doesn't properly recalculate the slope and bias for the user's lock in the checkpointing system.

To fix:

  • Ensure your _checkpoint_user function is called on every state-changing interaction (lock, increase_amount, increase_unlock_time).
  • The checkpoint must update the global slope_changes mapping for the old and new unlock times to correctly adjust future voting power decay.
  • Always verify that the user's locked.amount and locked.end are updated before running the checkpoint logic. Forgetting this order will cause the math to use stale values.

Example from ve(3,3) implementations:

solidity
function increase_unlock_time(uint _tokenId, uint _lock_duration) external {
    LockedBalance memory _locked = locked[_tokenId];
    uint unlock_time = ((block.timestamp + _lock_duration) / WEEK) * WEEK; // Locktime is rounded to weeks
    require(_locked.end > block.timestamp, "Lock expired");
    require(unlock_time > _locked.end, "Can only increase lock duration");
    require(unlock_time <= block.timestamp + MAX_TIME, "Voting lock can be 4 years max");

    _deposit_for(_tokenId, 0, unlock_time, _locked, DepositType.INCREASE_UNLOCK_TIME);
}
// _deposit_for must handle the checkpointing.
VE TOKEN IMPLEMENTATION

Frequently Asked Questions (FAQ)

Common technical questions and solutions for developers building or integrating vote-escrow token models.

A vote-escrow (ve) model is a governance and incentive system where users lock their native protocol tokens (e.g., CRV, BAL) to receive non-transferable, non-tradable veTokens. The amount of veTokens received is proportional to the lock amount and lock duration, typically using a linear time-weighted formula: veTokens = locked_amount * (lock_time_in_weeks / max_lock_time_in_weeks). This veToken balance grants:

  • Voting rights on governance proposals and liquidity gauge weight distributions.
  • Boosted rewards (e.g., higher yield) in associated liquidity pools.
  • A share of protocol fees or other economic benefits. The model creates long-term alignment by making governance power illiquid and time-bound.
conclusion-next-steps
IMPLEMENTATION SUMMARY

Conclusion and Next Steps

This guide has covered the core mechanics and security considerations for building a vote escrow (veToken) model. Here's a summary of key takeaways and resources for further development.

Implementing a veToken model successfully requires a focus on three core components: the locking mechanism, the voting power calculation, and the reward distribution logic. Your smart contract must accurately track user lock durations, calculate decaying voting power (often using a linear or time-weighted formula), and integrate seamlessly with your protocol's governance and gauge systems. Security is paramount; thorough testing of edge cases around early withdrawals, expired locks, and delegate voting is non-negotiable.

For practical next steps, begin by studying established implementations. The Curve Finance veCRV contract (originally by Solidly) is the canonical reference. Review its code on GitHub and consider using battle-tested libraries like OpenZeppelin's for safe math and access control. Develop a comprehensive test suite using Foundry or Hardhat that simulates multi-year lock periods, delegation scenarios, and malicious attacks. Tools like Tenderly or OpenZeppelin Defender can help you monitor and automate contract interactions post-deployment.

Beyond the base mechanics, consider the broader ecosystem integration. Your veToken's utility must be compelling—common patterns include directing liquidity mining rewards via gauge votes, earning a share of protocol fees, or accessing exclusive pools. Analyze the tokenomics carefully to avoid excessive inflation or voter apathy. Finally, engage with your community early; transparent documentation and clear communication about locking rewards and governance power are critical for adoption. Start with a testnet deployment, gather feedback, and iterate before a mainnet launch.