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

Setting Up a Governance Token Delegation System

This guide provides a technical walkthrough for implementing a secure delegation system for ERC-20 governance tokens. It covers contract architecture, delegation mechanics, and security considerations.
Chainscore © 2026
introduction
DEVELOPER GUIDE

Setting Up a Governance Token Delegation System

A technical guide to implementing on-chain token delegation for DAOs and governance protocols, covering smart contract design, security considerations, and integration patterns.

Token delegation is a core mechanism for scaling decentralized governance, allowing token holders to delegate their voting power to trusted representatives or experts. This system is fundamental to protocols like Compound's COMP, Uniswap's UNI, and Aave's AAVE, where it helps mitigate voter apathy and concentrates expertise. At its core, a delegation contract maintains a mapping of delegators to delegates, transferring voting weight without transferring token ownership. This separation of economic interest from governance rights is crucial for maintaining security and participation in large-scale DAOs.

The most common implementation uses an ERC-20Votes or ERC-5805 compatible token, which standardizes delegation logic. Below is a simplified example of a delegation mapping in a Solidity smart contract:

solidity
mapping(address => address) public delegates;
mapping(address => uint256) public checkpoints; // Snapshots of voting power

function delegate(address delegatee) public {
    address currentDelegate = delegates[msg.sender];
    uint256 senderBalance = balanceOf(msg.sender];
    delegates[msg.sender] = delegatee;
    _moveDelegates(currentDelegate, delegatee, senderBalance);
}

This function updates the delegate for the caller and transfers the historical voting power checkpoint from the old delegate to the new one, a critical step to prevent double-counting votes.

When designing your system, key security considerations include preventing flash loan voting attacks by using timestamp-based checkpoints, implementing a delegation delay (like a 1-block cooldown) to thwart last-minute manipulation, and ensuring proper access controls so only the token owner can set their delegate. For gas efficiency, many systems use a checkpointing model, where voting power is recorded at specific block numbers rather than recalculated on every transaction, as seen in OpenZeppelin's ERC20Votes library.

Integrating delegation into your governance framework requires connecting the token contract to a governor contract, such as OpenZeppelin Governor. The governor will call the token's getVotes(address account, uint256 blockNumber) function to determine voting power at the proposal snapshot block. It's essential to audit the entire flow, from delegation to vote casting, to ensure the delegate's votes are correctly tallied and cannot be altered retroactively.

Best practices for user experience include providing clear front-end interfaces for delegating, displaying delegate profiles and voting history, and supporting partial delegation (splitting votes among multiple delegates) for advanced users. Monitoring tools like Tally or Boardroom offer dashboards to track delegation metrics and delegate performance, which are vital for an informed electorate. Always test your implementation thoroughly on a testnet, simulating various delegation and voting scenarios before mainnet deployment.

prerequisites
PREREQUISITES AND SETUP

Setting Up a Governance Token Delegation System

A technical guide to the essential tools, accounts, and smart contract knowledge required to build a token delegation system for on-chain governance.

Before writing any code, you must establish a secure development environment and understand the core components. You will need a Node.js environment (v18+ recommended) and a package manager like npm or yarn. Essential tools include Hardhat or Foundry for smart contract development and testing, and a wallet such as MetaMask for managing testnet accounts. You must also obtain test ETH or the native token for your chosen blockchain (e.g., Sepolia, Goerli, or a local node) to deploy contracts and pay gas fees. A basic understanding of Solidity (v0.8.x) and the ERC-20 token standard is mandatory.

The foundation of any delegation system is a governance token. You can use an existing standard like OpenZeppelin's ERC20Votes extension, which provides built-in vote tracking and delegation logic compliant with ERC-5805 and ERC-6372. This extension maintains a historical checkpoint of token balances, enabling gas-efficient vote delegation and preventing double-voting. Your first setup step is to initialize a Hardhat project (npx hardhat init) and install the OpenZeppelin Contracts library: npm install @openzeppelin/contracts. You will then import and extend the ERC20Votes contract for your token.

Delegation requires a secure mechanism for users to assign their voting power. In ERC20Votes, the delegate function allows a token holder to delegate to themselves or another address. The delegated voting power is calculated from token balances at the block number of the last checkpoint, not the current balance. To test this, you will write and run scripts using Hardhat's ethers.js integration. A critical setup task is to fund your deployer wallet with testnet ETH and verify you can compile and deploy your token contract. Always test delegation on a local network first using npx hardhat node before proceeding to a public testnet.

For a complete system, you need a governance contract to receive and count delegated votes. This is typically a Governor contract, also available from OpenZeppelin (e.g., GovernorCompatibilityBravo). This contract interacts with your ERC20Votes token to read a voter's delegated power at a specific block. Your setup must include configuring the Governor's parameters: voting delay, voting period, proposal threshold, and quorum. These values are set in blocks (e.g., a 1-day voting period is ~6570 blocks on Ethereum mainnet). You will deploy the Governor contract, setting your token's address as the voting token.

Finally, integrate a front-end to make the system usable. You will need a library like wagmi or ethers.js to connect to user wallets and interact with your contracts. The front-end must call the token contract's delegate function and the Governor contract's propose, castVote, and queue/execute functions. For testing, use Alchemy or Infura to connect to public testnets. Ensure your application handles chain switching and displays proper error messages for failed transactions. The complete setup creates a functional pipeline: Token Deployment → Governor Configuration → Front-End Integration → User Delegation → Proposal Lifecycle.

core-architecture
CORE CONTRACT ARCHITECTURE

Setting Up a Governance Token Delegation System

A step-by-step guide to implementing a secure and efficient on-chain delegation system for ERC-20 governance tokens.

A governance token delegation system allows token holders to delegate their voting power to other addresses without transferring token ownership. This is a core mechanism for scalable on-chain governance, enabling participation from users who lack the time or expertise to vote on every proposal. The system is typically built using two primary smart contracts: a standard ERC-20 token (like OpenZeppelin's ERC20Votes) and a custom Governor contract. The token contract manages the delegation logic and vote-weight snapshots, while the Governor contract reads these snapshots to tally votes. This separation of concerns is critical for security and upgradability.

The foundation is an ERC-20 token with checkpointing capabilities. Use OpenZeppelin's ERC20Votes contract, which extends ERC20 with the Votes interface. It automatically creates a historical record (a checkpoint) of each account's voting power whenever tokens are transferred, minted, or burned. Crucially, it also tracks delegation. Users call delegate(address delegatee) on the token contract to assign their voting power. The delegated votes are not tied to a specific proposal; the delegatee receives the delegator's full voting weight for all future proposals until delegation is changed.

Here is a basic implementation of a delegatable governance token:

solidity
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";

contract GovernanceToken is ERC20, ERC20Votes {
    constructor() ERC20("GovToken", "GT") ERC20Permit("GovToken") {}

    // The following overrides are required by Solidity.
    function _afterTokenTransfer(address from, address to, uint256 amount)
        internal
        override(ERC20, ERC20Votes)
    {
        super._afterTokenTransfer(from, to, amount);
    }

    function _mint(address to, uint256 amount)
        internal
        override(ERC20, ERC20Votes)
    {
        super._mint(to, amount);
    }

    function _burn(address account, uint256 amount)
        internal
        override(ERC20, ERC20Votes)
    {
        super._burn(account, amount);
    }
}

The Governor contract, such as OpenZeppelin's Governor suite, interacts with this token. When a proposal is created, the Governor records the current block number as the proposal's snapshot block. To determine a voter's power, it calls token.getPastVotes(account, snapshotBlock). This returns the voting weight the account had at that specific block, based on the token's checkpoint history and delegation state. This snapshot mechanism prevents users from acquiring tokens after a proposal is created to manipulate the vote. The Governor contract handles the proposal lifecycle: creation, voting, queuing, and execution.

Key architectural considerations include gas efficiency and security. Checkpointing every transfer increases gas costs; this is a necessary trade-off for accurate historical lookups. Use ERC20Votes's delegateBySig function to allow users to delegate via a signed message, saving gas. For security, ensure the Governor contract is the only entity that can mint new tokens (if applicable) to prevent inflation attacks on voting power. Thoroughly test the integration, especially the snapshot logic, using frameworks like Foundry or Hardhat to simulate proposal creation and voting across multiple blocks.

Successful deployment involves verifying the contracts on a block explorer like Etherscan and creating a front-end interface. Users need a clear UI to delegate their votes, view active delegates, and understand their voting power. The system's parameters—such as proposal threshold, voting delay, and voting period—should be carefully set in the Governor constructor. For live examples, study the source code of established systems like Uniswap Governance or Compound's Governor Bravo.

implementing-delegation-logic
GOVERNANCE

Implementing Delegation Logic

A technical guide to building a secure and efficient on-chain delegation system for governance tokens.

Governance token delegation is a core mechanism for scaling decentralized decision-making. It allows token holders (delegators) to assign their voting power to other addresses (delegates) who vote on their behalf. This system is critical for achieving effective participation in DAOs and on-chain protocols like Compound and Uniswap, where active voter turnout is often low. Implementing delegation requires managing a mapping of delegate relationships and correctly tallying votes based on delegated balances, not just direct token ownership.

The foundation of any delegation system is a data structure that tracks delegation relationships. A typical Solidity implementation uses a nested mapping: mapping(address => mapping(address => uint256)) public delegations;. The first key is the delegator, the second is the delegate, and the value is the delegated amount. To vote, the contract must calculate a user's voting power, which is the sum of their own tokens plus all tokens delegated to them. This requires iterating through all potential delegators, which can be gas-intensive.

A more gas-efficient pattern, used by OpenZeppelin's Votes library, employs a checkpointing system. Instead of storing live delegation amounts, the contract records historical snapshots (checkpoints) of a delegate's voting power at specific block numbers. When a delegation changes, it writes a new checkpoint for the delegate. To find voting power at a past block (e.g., for a proposal), the contract performs a binary search on the delegate's checkpoint history. This optimizes gas costs for voters and delegates over time.

Security is paramount. Your contract must prevent double-spending of voting power. A user cannot delegate the same tokens to multiple delegates simultaneously. The logic should ensure that when a user updates their delegation, the previous delegate's voting power is decremented before the new delegate's power is incremented. Furthermore, consider implementing a delegation delay or lock-up period, as seen in systems like Optimism's Governance, to prevent last-minute delegation changes that could manipulate vote outcomes.

Here is a simplified core function for delegating tokens, excluding checkpoint logic for clarity:

solidity
function delegate(address to, uint256 amount) external {
    address from = msg.sender;
    require(balanceOf(from) >= amount, "Insufficient balance");
    // Decrease old delegate's power (if any)
    uint256 currentDelegation = delegations[from][currentDelegate[from]];
    if (currentDelegation > 0) {
        _decreaseVotingPower(currentDelegate[from], currentDelegation);
        delegations[from][currentDelegate[from]] = 0;
    }
    // Increase new delegate's power
    _increaseVotingPower(to, amount);
    delegations[from][to] = amount;
    currentDelegate[from] = to;
    emit Delegated(from, to, amount);
}

For production systems, integrate established libraries like OpenZeppelin's Votes (ERC-5805) or Governor (ERC-6372) standards. These provide audited, gas-optimized implementations for vote tracking, delegation, and snapshotting. Always test delegation logic extensively with scenarios including transferring tokens after delegation, self-delegation, and delegation to the zero address (which typically revokes delegation). Properly implemented, delegation empowers token-based governance to be both permissionless and practically functional.

ARCHITECTURE

Delegation Model Comparison

Key differences between on-chain, off-chain, and hybrid delegation implementations for governance tokens.

FeatureOn-Chain DelegationOff-Chain Delegation (Snapshot)Hybrid Model

Voting Power Source

Direct token balance in wallet

Token balance snapshot at proposal creation

On-chain lockup for off-chain voting

Gas Costs for Delegation

$10-50 (one-time)

$0

$10-50 (one-time lockup)

Voting Transaction Cost

$5-20 per vote

$0

$0 for vote, gas for lock/unlock

Real-Time Voting Power Updates

Resistance to Sybil Attacks

High (costly to split tokens)

Low (free to split addresses)

Medium (costly to split locked positions)

Integration Complexity

High (custom smart contracts)

Low (uses Snapshot API)

Medium (contracts + off-chain indexer)

Delegation Revocation Time

Immediate (1 transaction)

Immediate (UI action)

Delayed (unlock period, e.g., 7 days)

Example Protocols

Compound, Uniswap

Yearn, Aave (on Snapshot)

Curve (ve-token model)

handling-undelegation
GOVERNANCE TOKENS

Handling Undelegation and Cool-Down Periods

A guide to implementing secure and user-friendly undelegation mechanisms with cool-down periods for on-chain governance systems.

In a governance token delegation system, undelegation is the process by which a token holder revokes the voting power they have delegated to another address. A cool-down period (or timelock) is a mandatory waiting period between initiating an undelegate transaction and the delegation being fully revoked. This period serves critical functions: it prevents malicious actors from rapidly acquiring and delegating large amounts of tokens to swing a live vote, and it gives the community time to react to suspicious delegation changes. Protocols like Compound and Uniswap implement cool-downs, typically ranging from 1 to 7 days, to protect governance integrity.

Implementing this requires tracking two key states per delegator: a pendingUndelegate timestamp and a delegate address. When a user calls initiateUndelegate(), you set the pending timestamp to block.timestamp + COOLDOWN_PERIOD and keep the current delegate active. The user's voting power remains with the old delegate until the cool-down expires. A separate completeUndelegate() function, callable only after the timestamp has passed, finally sets the delegate address to zero or the delegator themselves. This two-step pattern is a common security practice, as seen in OpenZeppelin's governance templates.

Here is a simplified Solidity example of the core logic:

solidity
contract DelegationWithCooldown {
    uint256 public constant COOLDOWN_PERIOD = 5 days;
    mapping(address => address) public delegate;
    mapping(address => uint256) public pendingUndelegateTimestamp;

    function initiateUndelegate() external {
        require(delegate[msg.sender] != address(0), "No active delegation");
        pendingUndelegateTimestamp[msg.sender] = block.timestamp + COOLDOWN_PERIOD;
        emit UndelegationInitiated(msg.sender, pendingUndelegateTimestamp[msg.sender]);
    }

    function completeUndelegate() external {
        require(block.timestamp >= pendingUndelegateTimestamp[msg.sender], "Cooldown active");
        delete delegate[msg.sender];
        delete pendingUndelegateTimestamp[msg.sender];
    }
}

The initiateUndelegate function locks in a future timestamp, while completeUndelegate executes the state change after the wait.

When integrating this with a snapshot-based governance system, your getVotes function must account for the cool-down. During the waiting period, votes should still be allocated to the original delegate. Only after completeUndelegate is called should the voting power return to zero or the holder. This ensures vote counts remain consistent for the duration of any active proposal. Failing to handle this correctly can lead to vote manipulation, where a user could undelegate to reduce a delegate's voting power on a proposal they oppose, then redelegate after the vote.

Key design considerations include: setting an appropriate COOLDOWN_PERIOD (long enough to deter gaming but not so long it frustrates users), allowing users to cancel a pending undelegation, and clearly exposing these states and timestamps in the frontend. Tools like The Graph can index these events to provide users with clear dashboards showing their delegation status and cool-down timer. Always include events like UndelegationInitiated and UndelegationCompleted for off-chain tracking and transparency.

security-considerations
SECURITY MODELS AND ATTACK PREVENTION

Setting Up a Governance Token Delegation System

A secure delegation system is critical for decentralized governance. This guide covers the core security models and implementation steps to prevent common attacks.

Governance token delegation allows token holders to transfer their voting power to a representative, or delegate, without transferring the underlying asset. This mechanism is fundamental to protocols like Compound and Uniswap, enabling participation while maintaining asset custody. The core security model relies on a non-custodial, permissionless smart contract that records delegation mappings. A critical design choice is whether to implement snapshot-based voting (gas-free, off-chain) or on-chain voting (transaction-based, binding). Each model has distinct security implications, primarily around vote manipulation and delegation revocation.

The primary attack vectors in delegation systems include vote buying, delegation front-running, and governance capture. To mitigate these, implement time-locks on delegation changes—a 2-3 day delay prevents last-minute manipulation before a proposal snapshot. Use a checkpoint system (like OpenZeppelin's ERC20Votes) to record delegation balances at specific block numbers, making historical state manipulation prohibitively expensive. Always validate that delegated votes cannot exceed the delegator's token balance at the proposal's snapshot block. Failing to anchor votes to a historical snapshot is a common flaw that allows double-spending of voting power.

For on-chain implementations, use the ERC-5805 (Votes) and ERC-6372 (Clock) standards for a consistent interface. Below is a basic delegation setup using OpenZeppelin's contracts:

solidity
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
contract GovernanceToken is ERC20Votes {
    constructor() ERC20("Token", "TKN") ERC20Permit("Token") {}
    // Delegation is handled by inherited `delegate` function
}

This contract automatically manages delegation history and checkpoints. The delegate function updates the delegatee for the caller, and getVotes retrieves the voting power at a past block number.

To prevent governance attacks, integrate a timelock controller (like OpenZeppelin's TimelockController) for executing passed proposals. This adds a mandatory delay between proposal approval and execution, allowing token holders to exit the system if a malicious proposal passes. Furthermore, consider implementing delegation limits—a cap on the percentage of total supply a single delegate can represent—to reduce centralization risk. Audit all governance logic for reentrancy and ensure proposal state transitions (e.g., from Active to Executed) are atomic and irreversible.

For off-chain/snapshot voting, security shifts to the integrity of the snapshot data. Use a decentralized oracle or a merkle root posted on-chain to prove the snapshot's state. The Snapshot.org platform uses this model, where delegation is signed off-chain via EIP-712 typed signatures. The security risk here is a malicious snapshot curator. Mitigate this by using a multi-sig or a DAO to approve snapshot data. Always verify the signature's deadline and nonce to prevent replay attacks across different proposals.

Regularly test your system with scenarios like a delegate accumulating >51% of voting power, a rapid delegation change during a voting period, and a malicious proposal that tries to drain the treasury. Use tools like Slither or Foundry's fuzzing to automate vulnerability detection. Remember, a secure delegation system is not set-and-forget; it requires ongoing monitoring of delegate concentration and proposal patterns to prevent slow-moving governance capture.

building-interface
GOVERNANCE

Building a Delegate Discovery Interface

A practical guide to implementing a frontend interface for users to discover and delegate their governance tokens, covering smart contract integration, data indexing, and UI patterns.

A delegate discovery interface is a critical component for any decentralized governance system. It allows token holders to find and delegate their voting power to representatives who will vote on their behalf. This system is fundamental to protocols like Compound, Uniswap, and ENS. The core technical challenge involves querying on-chain delegation data, presenting it in a user-friendly way, and enabling secure delegation transactions. A well-designed interface increases voter participation by lowering the technical barrier to delegation.

The foundation of any delegation system is its smart contract. Most governance tokens, following the ERC-20Votes or ERC-20VotesComp standard, include functions for delegation. Key contract methods you'll need to integrate are delegate(address delegatee), getVotes(address account), and delegates(address delegator). For delegate discovery, you must index historical events like DelegateChanged and DelegateVotesChanged. Using a subgraph with The Graph or an indexer like Covalent is essential for efficiently querying a list of all delegates, their current voting power, and their proposal voting history.

When designing the UI, focus on clear information hierarchy. The main view should list potential delegates, typically sorted by voting power or number of delegators. Each delegate card should display their on-chain address (with an ENS name if available), total voting power, self-delegated amount, and a brief description pulled from a registry. Implement robust filtering: users should filter by delegates who voted on specific proposals, by minimum voting power, or by those who have published a delegation statement. A search function for addresses or ENS names is also crucial.

To fetch and display this data, your frontend will make several types of calls. First, use your indexer to get the list of delegates and their stats. Second, use ethers.js or viem to read on-chain state for real-time checks, like a user's current delegate. Third, to enable delegation, you must prompt a wallet transaction calling the delegate function. Always display the delegate's address in the transaction confirmation. A good practice is to show the user their new voting power breakdown immediately after a successful transaction by refetching their getVotes.

Beyond basic functionality, consider advanced features to improve engagement. Integrate with Snapshot or similar off-chain voting platforms to show a delegate's voting history and rationale. Calculate and display delegate "voting power concentration" metrics to highlight decentralization. For security, implement clear warnings when delegating to a contract address that may not be able to vote. Finally, ensure the design is mobile-responsive, as many users interact with dApps via mobile wallets. The goal is to make the complex process of political choice in DAOs as intuitive as possible.

GOVERNANCE TOKENS

Frequently Asked Questions

Common technical questions and solutions for developers implementing on-chain delegation systems for governance tokens.

These are distinct concepts with different smart contract implementations. Token delegation typically refers to transferring voting power by transferring the token itself, often using the ERC-20 transfer or transferFrom function to a delegate address. The delegate then holds the tokens and their associated rights.

Vote delegation (e.g., as used by Compound's Governor Bravo or OpenZeppelin's Governor) uses a separate delegation mechanism. Token holders call a delegate(address delegatee) function on the governance token contract (often an ERC-20Votes or ERC-721Votes extension). This records the delegatee's address without transferring the underlying tokens. The delegatee's voting power is calculated from the sum of all tokens delegated to them at a past block (using a checkpointing system), while the original holder retains custody of their assets.

conclusion
IMPLEMENTATION SUMMARY

Conclusion and Next Steps

This guide has walked through the core components of building a governance token delegation system. The next steps involve operational security, community activation, and exploring advanced features.

You have now implemented the foundational elements of a token delegation system: a DelegationRegistry contract for managing delegate relationships, a Governor contract that respects these delegations for voting power, and a frontend interface for user interaction. The critical security considerations—preventing double-voting, ensuring vote weight is calculated at the correct block, and using a pull-over-push pattern for delegation—are essential for a robust system. Always conduct thorough audits, such as those from firms like OpenZeppelin or Trail of Bits, before deploying to mainnet.

To activate your governance system, focus on clear documentation and community education. Create guides explaining how to delegate votes using your platform, perhaps mirroring successful models like Uniswap or Compound. Use tools like Snapshot for off-chain signaling to gauge sentiment before on-chain proposals. Encourage early participation by delegating a portion of the treasury's tokens to known, active community members or establishing a grants program for delegates who contribute analysis and reporting.

For further development, consider implementing advanced delegation features. These could include time-locked delegations (delegating votes for a fixed period), contract-level delegations (allowing smart contracts like DAO tooling to act as delegates), or liquid delegation protocols like those explored by Element Finance. Monitoring delegate performance with dashboards using The Graph for on-chain data can help token holders make informed decisions. The goal is to evolve from a simple transfer of voting power to a sophisticated ecosystem of informed, accountable representation.

How to Build a Governance Token Delegation System | ChainScore Guides