Delegated voting is a core governance mechanism where token holders can delegate their voting power to representatives, or delegates, who vote on their behalf. This system, used by protocols like Compound and Uniswap, increases participation by allowing less active users to contribute without constant engagement. At its heart, it requires a smart contract that tracks delegation relationships and tallies votes based on delegated balances, not just direct token ownership. The key data structures are a mapping from voter to delegate and a record of each delegate's voting weight, which updates whenever tokens are transferred or delegation changes.
Setting Up Delegated Voting for Token Holders
Setting Up Delegated Voting for Token Holders
A technical walkthrough for developers to implement a basic delegated voting system for ERC-20 governance tokens, covering smart contract structure, delegation logic, and vote casting.
The foundational contract must extend an ERC-20 token standard, typically OpenZeppelin's implementation, and override key functions to handle delegation. The critical function is _afterTokenTransfer, which is called after any transfer or transferFrom. Here, you must update the voting weights for both the sender's and recipient's delegates. For example, if Alice delegates to Bob and then transfers tokens to Charlie (who delegates to himself), the contract must decrement Bob's voting weight and increment Charlie's.
solidityfunction _afterTokenTransfer(address from, address to, uint256 amount) internal virtual override { super._afterTokenTransfer(from, to, amount); _moveDelegates(delegates[from], delegates[to], amount); }
A separate Voting contract then uses these snapshotted delegate weights to execute proposals. To prevent manipulation, voting power is often calculated from a historical snapshot, like using ERC-20 Snapshots or a specific block number recorded at proposal creation. When a delegate casts a vote, the contract checks their voting power at that snapshot block. The vote tally logic is straightforward: proposalVotes[proposalId][support] += delegateVotingPower. This ensures votes reflect the delegated stake at a fixed point in time, not the current fluctuating balance.
For frontend integration, you need to query both the token contract for delegation data and the voting contract for proposal state. Using ethers.js or viem, you would call tokenContract.getVotes(delegateAddress, blockNumber) to get voting power and votingContract.getProposalVotes(proposalId) for results. Best practices include implementing events like DelegateChanged and DelegateVotesChanged for efficient off-chain indexing and providing a clear interface for users to delegate and vote in a single transaction using a multicall pattern to improve UX and reduce gas costs.
Prerequisites and Required Knowledge
Before implementing a delegated voting system, you need a solid foundation in smart contract development, token standards, and governance models.
Delegated voting allows token holders to assign their voting power to a representative, or delegate, who votes on proposals on their behalf. This model, used by protocols like Uniswap and Compound, improves participation by reducing voter apathy and enabling experts to guide decisions. To build this system, you must understand the core components: a governance token (typically an ERC-20 or ERC-1155), a proposal lifecycle, and a delegation registry that tracks voting power transfers. Familiarity with existing implementations, such as OpenZeppelin's Governor contracts, provides a crucial reference point.
Your technical stack must include proficiency in Solidity for writing secure smart contracts and a testing framework like Hardhat or Foundry. You'll need to interact with the system using a library such as ethers.js or web3.js. A deep understanding of the Ethereum Virtual Machine (EVM) is essential for optimizing gas costs and preventing security vulnerabilities like reentrancy or vote manipulation. Setting up a local development environment with a mainnet fork is recommended for accurate testing of governance interactions and delegation logic.
Key conceptual knowledge includes the difference between token-balance snapshots and real-time balances for voting power, a mechanism critical for preventing manipulation. You must also understand the trade-offs in proposal types: - Optimistic voting (votes are final) vs. challenge periods (votes can be contested) - Simple majority vs. quorum-based thresholds - Time-locked execution for passed proposals. Analyzing past governance attacks, such as those involving flash loaned tokens to sway votes, will inform your security design.
Finally, prepare your deployment strategy. You'll need testnet ETH (e.g., Sepolia ETH) for deploying and interacting with contracts. Decide if you will use an existing governance framework like OpenZeppelin Governor or build a custom solution. Document the delegation interface, including functions for delegate(address to), delegates(address account), and getVotes(address account, uint256 blockNumber). Thoroughly test all state changes and edge cases before considering a mainnet launch, as governance contracts are often upgradeable and involve significant community trust.
Setting Up Delegated Voting for Token Holders
A technical guide to implementing a gas-efficient, on-chain delegated voting system for ERC-20 governance tokens.
Delegated voting is a foundational governance pattern where token holders can delegate their voting power to a representative, or delegate, who votes on their behalf. This system, used by protocols like Compound and Uniswap, improves participation by reducing voter apathy and gas costs for small holders. The core smart contract mechanics involve tracking delegation relationships and calculating voting weights based on token balances at a specific block number, known as a snapshot. This prevents manipulation through token transfers during an active proposal.
The implementation requires extending a standard ERC20Votes token, as defined in OpenZeppelin's contracts (v4.9.0+). This extension automatically maintains a history of account balances for past block numbers. The key function is delegate(address delegatee), which allows any token holder to set or change their delegate. The contract stores these mappings and uses them to compute the getVotes(address account, uint256 blockNumber) for any historical block. This vote-getting logic is separate from the proposal and tallying contract, following a modular design.
Here is a minimal contract example for a delegatable governance token:
solidityimport "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; contract GovernanceToken is ERC20Votes { constructor() ERC20("GovToken", "GOV") ERC20Permit("GovToken") {} // The _afterTokenTransfer hook automatically adjusts delegation balances. function _afterTokenTransfer(address from, address to, uint256 amount) internal override(ERC20Votes) { super._afterTokenTransfer(from, to, amount); } }
Deploying this contract gives you a token with built-in delegate() and getVotes() functions. The ERC20Votes contract handles the complexity of tracking historical checkpoints internally.
To execute a vote, a separate Governor contract (e.g., OpenZeppelin's Governor) queries the token contract's getVotes function. The proposal state and voting logic are managed here. A critical security consideration is setting the voting delay and voting period. A voting delay allows time for delegates to react to a new proposal after the snapshot is taken. All major decisions—like setting quorum, proposal threshold, and timelock delays—should be governance-upgradable parameters, not hardcoded constants.
For production deployment, you must integrate a timelock executor (like TimelockController) to queue and execute successful proposals. This introduces a mandatory delay between a vote passing and its execution, providing a safety mechanism for token holders to exit if they disagree with a passed decision. The complete system flow is: 1) Token holder delegates, 2) Proposal is created (snapshot taken), 3) Delegates vote during the period, 4) Successful proposals are queued in the timelock, then 5) Executed after the delay. Auditing and testing with tools like Foundry or Hardhat is essential before mainnet deployment.
Key Concepts and Components
Delegated voting allows token holders to assign their voting power to representatives, balancing participation with efficiency. This section covers the core technical components required to implement it.
Voting Power Calculation
Voting power is typically derived from a user's token balance at a specific block height, often using a snapshot mechanism to prevent manipulation. Common patterns include:
- Linear voting: 1 token = 1 vote.
- Quadratic voting: Power scales with the square root of tokens held, reducing whale dominance.
- Time-weighted voting: Power increases based on token lock-up duration (ve-token models). Smart contracts must reference a canonical token balance source, such as an ERC-20 snapshot or a staking contract.
Delegate Registry & Management
A core smart contract maintains the mapping between token holders and their chosen delegates. Key functions include:
delegate(address delegatee): Allows a user to delegate all voting power.delegateBySig(address delegatee, uint nonce, uint expiry, uint8 v, bytes32 r, bytes32 s): Permits delegation via signed message (EIP-712).- Delegation chains are not typically supported; power flows directly from delegator to delegate. The registry must emit events for indexers to track delegation changes off-chain.
Proposal Lifecycle & State Machine
Proposals follow a defined state transition to ensure orderly governance. A standard lifecycle includes:
- Pending: Created, awaiting activation.
- Active: Voting is open for a set period (e.g., 3-7 days).
- Succeeded/Defeated: Determined by quorum and majority thresholds.
- Queued & Executed: For proposals that require on-chain execution via
timelockcontracts. The contract must enforce quorum (minimum participation) and vote differential (margin of victory) requirements.
Vote Casting & Tallying
Delegates cast votes on behalf of themselves and their delegators. The voting contract must:
- Aggregate voting power from the delegate registry at the proposal's snapshot block.
- Support vote types: For, Against, Abstain.
- Prevent double voting and ensure votes are immutable once cast. Tallying is done on-chain, with results stored in the proposal struct. Gas costs for delegates can be high, leading to the use of snapshot strategies for off-chain signaling with on-chain execution.
Delegation Implementation Comparison: Compound vs. Uniswap
A technical comparison of how two major DeFi protocols implement delegated voting for token holders.
| Governance Feature | Compound v2 (Governor Bravo) | Uniswap v3 (Governor Bravo Fork) |
|---|---|---|
Delegation Model | Explicit, on-chain | Explicit, on-chain |
Vote Weight Source | cToken balances (COMP) | UNI token balance |
Delegation Gas Cost | ~45,000 gas | ~45,000 gas |
Self-Delegation Required to Propose | ||
Votes Delegated By Default | ||
Delegation Flexibility | Any address, including self | Any address, including self |
Voting Delay (Blocks) | ~1 block (13 sec) | ~1 block (13 sec) |
Voting Period | ~3 days (19,200 blocks) | ~7 days (44,800 blocks) |
Setting Up Delegated Voting for Token Holders
This guide provides a technical walkthrough for implementing a delegated voting system, enabling token holders to delegate their voting power to representatives.
Delegated voting is a core governance mechanism in many DAOs and DeFi protocols, allowing token holders who lack the time or expertise to participate directly to delegate their voting power to trusted community members. This system is implemented using smart contracts that track delegation relationships and calculate voting weight. The primary components are a token contract (often ERC-20Votes or ERC-5805) and a separate governor contract that uses the delegation state to tally votes. Setting this up requires careful planning of the delegation logic, snapshot mechanism, and security considerations to prevent manipulation.
The first step is to ensure your token contract supports delegation. If you are deploying a new token, use the OpenZeppelin ERC20Votes contract, which extends ERC-20 with snapshot and delegation capabilities. For existing tokens, you may need to upgrade to a compatible standard. The key function is delegate(address delegatee), which allows a caller to delegate their votes to another address. The contract maintains a history of checkpoints for each account's voting power, which is crucial for creating immutable snapshots at the start of a voting period. This prevents users from changing delegations to affect ongoing proposals.
Next, you need a governor contract to manage proposals and voting. The OpenZeppelin Governor suite provides a modular system for this. You will deploy a contract that inherits from Governor and GovernorVotes, which is initialized with your token's address. The GovernorVotes module uses the token's checkpoint system to read the voting power of each address at the block number when a proposal is created. When a user votes, their power is calculated based on their own balance plus any votes delegated to them. The voting logic must also handle vote delegation chains, where A delegates to B, and B delegates to C, ensuring C receives the combined voting weight.
Critical implementation details include setting the voting delay and period. A voting delay (e.g., 1 block) prevents immediate voting after proposal creation, allowing delegates time to review. The voting period (e.g., 3 days) defines how long the vote remains open. You must also decide on a quorum requirement, often implemented using the GovernorVotesQuorumFraction module, which sets a quorum as a percentage of the total token supply at the time of the proposal snapshot. All these parameters are set in the constructor of your custom governor contract. Thorough testing with tools like Hardhat or Foundry is essential to simulate delegation scenarios and vote tallying.
Finally, you need to build a front-end interface to make the system usable. This involves integrating with libraries like wagmi or ethers.js to call the delegate and castVote functions. The UI should display current delegations, active proposals, and delegate profiles. For transparency, consider using the Tally or Boardroom governance portals, which can read standard Governor contracts. Remember to verify and publish your contracts on block explorers like Etherscan, and consider a timelock controller for executing successful proposals, which adds a security delay. Proper documentation and community education are key to ensuring high participation in your delegated voting system.
Code Examples and Snippets
Using OpenZeppelin's Governor
For a standard ERC-20 token, the fastest way to implement delegated voting is using OpenZeppelin's Governor contracts. This pattern uses a token-weighted, vote-escrowed system.
Core Workflow:
- Token holders call
delegate()on the token contract to assign voting power. - A proposal is created via the Governor contract.
- Delegates vote during the voting period.
- Votes are tallied, and if quorum and majority are met, the proposal can be executed.
Key Contract Setup:
solidityimport "@openzeppelin/contracts/governance/Governor.sol"; import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol"; import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol"; contract MyGovernor is Governor, GovernorVotes, GovernorVotesQuorumFraction { constructor(IVotes _token) Governor("MyGovernor") GovernorVotes(_token) GovernorVotesQuorumFraction(4) // 4% quorum {} // ... voting delay, period, and proposal threshold functions }
The GovernorVotes extension automatically reads voting power from the token's getPastVotes function, which tracks historical balances for delegation.
Frontend and Interface Considerations
A secure and intuitive frontend is critical for user adoption. This section covers the key tools and frameworks for building interfaces that enable token holders to delegate their voting power effectively.
Security & User Experience (UX)
Prevent errors and malicious interactions. Implement clear warnings for irreversible actions and validate all user inputs on-chain.
- Delegate Verification: Use EIP-712 typed data signing for delegation to prevent phishing.
- Error Handling: Catch and explain common errors (e.g., insufficient gas, wrong network).
- State Management: Use React Query or SWR to cache on-chain data and prevent UI flicker.
- Gas Estimation: Provide accurate estimates for delegation transactions, which can be more complex than simple transfers.
Accessibility & Mobile Responsiveness
Ensure all token holders can participate. A significant portion of users access dApps via mobile wallets. Key considerations:
- Mobile-First Design: Test interfaces with in-app browsers (MetaMask Mobile, Rainbow).
- Touch Targets: Make buttons and links large enough for touch input.
- Readable Data: Present complex delegation data in simple tables or cards.
- Color Contrast: Meet WCAG guidelines for users with visual impairments.
Poor accessibility directly reduces voter turnout.
Security Considerations and Risks
Delegated voting empowers token holders to participate in governance without managing proposals directly, but introduces unique security risks that must be mitigated.
Delegated voting systems, like those used by Compound and Uniswap, allow token holders to delegate their voting power to a representative or "delegate." This is essential for scaling decentralized governance, as it reduces voter apathy and consolidates expertise. However, it creates a principal-agent problem where the delegate's incentives may not align with the delegator's. Key risks include vote selling, where delegates trade their influence for payment, and lazy voting, where delegates fail to participate, effectively disenfranchising their constituents. Smart contract vulnerabilities in the delegation logic itself are another critical attack vector.
The security of the delegation mechanism is paramount. A common vulnerability is insufficient access control on the delegate function, which could allow any address to delegate tokens they do not own. The function should include a modifier to check msg.sender is the delegator. Furthermore, the contract must correctly handle state updates for both the old and new delegate to prevent vote dilution or corruption. Always use established, audited patterns from libraries like OpenZeppelin's Votes or ERC20Votes standards, which have been battle-tested across major protocols.
Delegators must perform due diligence on their chosen delegates. This involves analyzing their voting history on platforms like Tally or Boardroom, their public statements, and potential conflicts of interest. A delegate with a large, concentrated voting share becomes a single point of failure and a target for bribery or coercion. Protocols can mitigate this by implementing vote delegation caps or encouraging the use of delegate registries that require transparency reports. Smart contracts can also enforce a cooling-off period before a new delegate's votes are active, preventing last-minute, malicious delegation swings.
For developers, implementing secure delegation requires careful state management. When a user transfers tokens, the voting power associated with those tokens must correctly transfer to the recipient's delegate, not remain with the sender's. The _afterTokenTransfer hook is crucial here. Additionally, consider implementing snapshotting mechanisms (e.g., using ERC20Snapshot) to prevent manipulation by transferring tokens right before a proposal snapshot is taken. Failing to update checkpoints for voting power correctly can lead to incorrect vote tallies and governance attacks.
Beyond technical risks, economic and game-theoretic risks exist. A delegate could engage in vote extraction by threatening to vote against a delegator's interests unless paid. Futarchy-based prediction markets or conviction voting models are alternative designs that can reduce reliance on trusted delegates. Ultimately, a secure delegated voting system combines robust smart contract architecture, transparent delegate ecosystems, and clear economic incentives aligned with the protocol's long-term health. Regular security audits and bug bounty programs are non-negotiable for live governance contracts.
Frequently Asked Questions
Common technical questions and troubleshooting for implementing on-chain delegated voting systems for token holders.
Delegated voting is a governance model where token holders can delegate their voting power to a representative, or delegate, who votes on proposals on their behalf. On-chain, this is typically implemented using smart contracts that manage delegation records and tally votes.
Key components:
- A voting token (ERC-20, ERC-721) that confers voting power.
- A delegation registry mapping token holder addresses to delegate addresses.
- A voting contract that checks the registry to calculate a delegate's total voting power (their own tokens plus all tokens delegated to them) when a vote is cast.
This system, used by protocols like Compound and Uniswap, increases participation by allowing less active holders to contribute their voting weight to trusted experts.
Resources and Further Reading
Technical documentation and tooling references for implementing delegated voting systems in token-based governance. These resources cover onchain and offchain delegation patterns, vote aggregation, and security considerations.
Conclusion and Next Steps
You have successfully configured a delegated voting system, empowering token holders to participate in governance without managing proposals directly.
Your setup now includes a VotingToken for staking, a VotingEscrow contract to lock tokens for voting power, and a DelegationRegistry to manage delegate relationships. The core workflow is functional: users lock tokens to receive veTokens, delegate their voting power to a trusted address, and that delegate can cast votes on their behalf in a governance contract like OpenZeppelin's Governor. This structure separates the concerns of capital commitment from active governance participation, a pattern used by protocols like Curve Finance and Frax Finance.
For production deployment, several critical steps remain. First, conduct a comprehensive security audit of all contracts, especially the custom DelegationRegistry. Consider using established libraries like OpenZeppelin's Governor for the voting mechanism to reduce risk. Second, you must integrate a front-end interface. This should allow users to connect their wallet (e.g., via WalletConnect), view their veToken balance, select or change a delegate, and see active proposals. Frameworks like wagmi and viem are excellent for building this.
Finally, consider advanced features to enhance your system. Implement a time-weighted voting power decay, where a user's voting power decreases linearly as their lock expires, encouraging long-term alignment. Add functionality for delegate compensation, allowing delegates to claim a small percentage of rewards for their service. You should also establish clear, off-chain communication channels (e.g., a forum like Commonwealth) for delegates to present their platforms and for token holders to discuss proposals before on-chain voting begins.