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 On-Chain Voting for Treasury Proposals

A step-by-step technical guide for DAO developers to create enforceable, on-chain voting for treasury spending, covering proposal types, quorum thresholds, and multi-sig execution.
Chainscore © 2026
introduction
TUTORIAL

Setting Up On-Chain Voting for Treasury Proposals

A practical guide to implementing a secure, transparent voting system for managing a DAO's treasury using smart contracts.

On-chain treasury governance moves decision-making from informal discussions to a verifiable, automated process. At its core, it uses smart contracts to create proposals, manage voting periods, tally votes, and execute approved actions directly on the blockchain. This ensures transparency and immutability, as every proposal and vote is permanently recorded on-chain. Popular frameworks like OpenZeppelin Governor provide modular, audited contracts that form the foundation for most DAO voting systems. The primary components are the governance token (used for voting power), the Governor contract (manages proposals), and a Timelock contract (delays execution for safety).

The first step is defining your governance parameters, which are critical for security and efficiency. Key settings include the voting delay (time between proposal submission and voting start), voting period (duration of the active vote, typically 3-7 days), proposal threshold (minimum token balance needed to submit a proposal), and quorum (minimum percentage of total supply that must vote for a result to be valid). For a treasury with significant assets, a longer voting period and a high quorum (e.g., 4-10%) are common to prevent low-participation attacks. These values are set in the Governor contract's constructor and should be carefully considered based on your token distribution and community size.

To implement a basic system, you can deploy a contract using OpenZeppelin's Governor. The following Solidity snippet shows a minimal setup using the GovernorCountingSimple module for vote counting and a timelock for execution control.

solidity
import "@openzeppelin/contracts/governance/Governor.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";

contract TreasuryGovernor is Governor, GovernorCountingSimple, GovernorTimelockControl {
    constructor(IVotes _token, TimelockController _timelock)
        Governor("TreasuryGovernor")
        GovernorTimelockControl(_timelock)
    {}
    // Override required functions: votingDelay, votingPeriod, quorum, etc.
    function votingDelay() public pure override returns (uint256) { return 1 days; }
    function votingPeriod() public pure override returns (uint256) { return 5 days; }
    function quorum(uint256 blockNumber) public pure override returns (uint256) {
        // Return a fixed quorum of 4% of total token supply
        return 4 * 10 ** 16; // 4% in basis points (1e18 = 100%)
    }
}

Proposals can execute arbitrary calls, making treasury management powerful but risky. A typical proposal to transfer funds might encode a call to the treasury's execute function. The proposal lifecycle is managed by the Governor contract: 1) A user with sufficient tokens propose() with a list of target addresses, values, and calldata. 2) After the voting delay, token holders castVote(). 3) If the vote succeeds and meets quorum, anyone can queue() the proposal into the Timelock. 4) After the timelock delay, the proposal can be execute(). The TimelockController is essential here, as it enforces a mandatory waiting period between a proposal's approval and its execution, giving the community a final window to react to malicious actions.

Security considerations are paramount. Always use a TimelockController for treasury actions; a 24-72 hour delay is standard. Ensure your governance token uses a snapshot mechanism (like OpenZeppelin's ERC20Votes) to prevent voting power manipulation during the voting period. Be wary of proposal spam—set a meaningful proposal threshold. For critical upgrades, consider a multisig guardian that can veto proposals in extreme cases, a pattern used by protocols like Uniswap. Thoroughly test all governance logic on a testnet (like Sepolia) and conduct audits before mainnet deployment. Tools like Tally and Boardroom provide user-friendly interfaces for communities to interact with these contracts.

prerequisites
ON-CHAIN VOTING

Prerequisites and Setup

A practical guide to the tools and accounts required to create and vote on treasury proposals using on-chain governance.

Before interacting with on-chain governance, you need a wallet and testnet tokens. For Ethereum-based DAOs, a wallet like MetaMask is standard. You must connect this wallet to the DAO's governance interface, such as Tally or the project's custom dApp. Crucially, you will need the DAO's native governance token (e.g., $UNI for Uniswap, $ENS for Ethereum Name Service) to create proposals and vote. On mainnet, these tokens have real value and voting power. For testing, you'll use testnet tokens obtained from a faucet on networks like Goerli or Sepolia.

The second prerequisite is understanding the proposal lifecycle and smart contract addresses. Every DAO's governance is powered by a suite of contracts: typically a Token contract, a Governor contract (e.g., using OpenZeppelin's Governor standard), and a Treasury (e.g., a Safe multisig or custom contract). You need the address of the Governor contract to interact with the system. The lifecycle involves: 1) Proposal Submission, where a transaction calldata is queued; 2) a Voting Period, where token holders cast votes; and 3) Execution, where approved proposals are executed against the treasury.

For developers, setting up a local environment is essential for testing proposal logic. You'll need Node.js, a package manager like npm or yarn, and a development framework such as Hardhat or Foundry. Clone the DAO's governance repository (if open-source) to access the contract ABIs and understand the proposal format. Use the following Hardhat command to run a local fork of mainnet and simulate voting: npx hardhat node --fork https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY. This allows you to test proposals without spending gas or risking real funds.

Finally, ensure you understand the voting strategy and quorum requirements. Voting can be token-weighted (1 token = 1 vote) or use delegation (like in Compound's Governor Bravo). The quorum is the minimum number of votes required for a proposal to be valid. These parameters are defined in the Governor contract. Before submitting a mainnet proposal, always simulate the entire process on a testnet or local fork to verify the transaction calldata executes correctly and to estimate the gas costs involved in submission and execution.

key-concepts-text
CORE GOVERNANCE CONCEPTS

Setting Up On-Chain Voting for Treasury Proposals

A technical guide to implementing a secure and transparent on-chain voting system for managing a DAO treasury.

On-chain voting provides a transparent, immutable, and enforceable mechanism for DAOs to manage their treasuries. Unlike off-chain signaling, on-chain votes execute transactions directly, such as transferring funds or upgrading contracts, based on the outcome. This requires a smart contract system that handles proposal creation, voting, vote tallying, and execution. Popular frameworks for building these systems include OpenZeppelin Governor, Compound's Governor Bravo, and Aragon OSx. The core components are the voting token (which determines voting power), the timelock controller (which queues successful proposals), and the executor (which carries out the approved actions.

The first step is to define your governance parameters, which are critical for security and participation. Key parameters include: votingDelay (time between proposal submission and voting start), votingPeriod (duration of the active vote), proposalThreshold (minimum token balance to submit a proposal), and quorum (minimum participation required for a vote to be valid). For a treasury, you might set a higher quorum (e.g., 4% of total supply) and a longer voting period (e.g., 5-7 days) to ensure broad consensus for financial decisions. These values are set in the governor contract's constructor and can be upgraded via governance itself.

Here is a basic example of deploying an OpenZeppelin Governor contract for an ERC-20 token, GOV, with a 1-day voting delay and a 1-week voting period. The TimelockController adds a security delay before execution.

solidity
// SPDX-License-Identifier: MIT
import "@openzeppelin/contracts/governance/Governor.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";

contract TreasuryGovernor is Governor, GovernorSettings, GovernorTimelockControl {
    constructor(IVotes _token, TimelockController _timelock)
        Governor("TreasuryGovernor")
        GovernorSettings(1 /* 1 block delay */, 50400 /* 1 week in blocks */, 1000e18 /* 1000 token threshold */)
        GovernorTimelockControl(_timelock)
    {}
    // ... required virtual function implementations
}

Creating a treasury proposal involves encoding the target transaction. For example, to propose sending 100 ETH to a grant recipient, you would encode a call to the Timelock contract. Using the Governor's propose function, you submit the target (the timelock), value (100 ETH), and calldata (the encoded transfer function). Once the voting delay passes, token holders can cast votes using castVote. Votes are typically weighted by token balance at the block when voting starts, a mechanism known as snapshotting, which prevents last-minute token borrowing to manipulate votes. The Governor contract tallies votes and, if quorum and majority are met, the proposal state becomes Queued.

After a successful vote, the proposal enters the timelock period—a mandatory delay (e.g., 2 days) before execution. This is a critical security feature that allows token holders to react if a malicious proposal passes. During this window, they can exit vulnerable positions or prepare to fork the DAO. Once the timelock expires, anyone can call the execute function on the Governor to run the proposal's transaction, transferring the funds from the treasury. All these steps are recorded on-chain, providing a complete audit trail. For production use, consider integrating with Tally or Boardroom for user-friendly voting interfaces and real-time analytics.

Common pitfalls include setting parameters that are too restrictive (stalling governance) or too permissive (enabling attacks). Always audit governor and timelock contracts, and use a multisig as the initial executor for the timelock as a fallback. For complex treasury actions involving multiple tokens or DeFi interactions, consider using a Zodiac Module or SafeSnap to bundle off-chain verification with on-chain execution. The goal is a system that balances security, decentralization, and practical efficiency for managing the DAO's most valuable asset: its treasury.

governance-tools
ON-CHAIN VOTING

Essential Governance Tools

Tools and frameworks for implementing secure, transparent, and efficient on-chain voting for DAO treasury proposals.

06

Governance Security Best Practices

Critical considerations for securing your on-chain voting system to prevent exploits and ensure voter confidence.

  • Use a timelock (minimum 24-72 hours) on the executor to allow review of malicious proposals.
  • Set a quorum threshold (e.g., 4-20% of supply) to prevent low-participation attacks.
  • Implement vote delegation to reduce voter apathy and consolidate expertise.
  • Audit all custom voting logic and use battle-tested standards like Governor.
step-proposal-contract
FOUNDATION

Step 1: Design the Proposal Smart Contract

The smart contract defines the rules, lifecycle, and data structure for all treasury proposals. This is the immutable core of your on-chain governance system.

The proposal contract is the primary state machine for your governance process. It must define the core data structure for a proposal, typically including fields like id, proposer, description, amount, recipient, startBlock, endBlock, forVotes, againstVotes, and executed. This contract is responsible for managing the entire proposal lifecycle: creation, voting, and execution. It enforces rules such as proposal duration, quorum requirements, and vote tallying logic.

A critical design decision is choosing between a simple yes/no vote and a more complex system like quadratic voting or conviction voting. For most treasury proposals, a majority vote with a quorum is sufficient. The contract must also implement access control, ensuring only authorized addresses (often a separate Governor contract or TimelockController) can execute a successful proposal. This separation of powers prevents a single contract from having unilateral control over funds.

Here is a simplified example of a proposal struct and state-changing functions in Solidity:

solidity
struct Proposal {
    uint256 id;
    address proposer;
    uint256 amount;
    address payable recipient;
    uint256 startBlock;
    uint256 endBlock;
    uint256 forVotes;
    uint256 againstVotes;
    bool executed;
}

function propose(string memory description, uint256 amount, address payable recipient) external {
    // Check proposer requirements (e.g., token balance)
    // Create and store a new Proposal
}

function castVote(uint256 proposalId, bool support) external {
    // Ensure voting is active (block.number between start/end)
    // Record the voter's choice, weighting by token balance
}

Integrating with a token contract for vote weighting is essential. Instead of one-address-one-vote, votes are typically weighted by the voter's token balance (e.g., ERC-20 or ERC-721) at a specific snapshot block. The proposal contract must reference an external token contract and call its balanceOfAt(address voter, uint256 snapshotBlock) function. This snapshot mechanism prevents users from borrowing tokens to manipulate votes.

Finally, the contract must define a secure execution path. After a vote passes, the execute function should transfer treasury funds to the designated recipient. This function should be protected by modifiers that check: the voting period has ended, the proposal achieved a quorum and majority, and it hasn't been executed already. For maximum security, execution is often delegated to a Timelock contract, which introduces a mandatory delay, giving the community time to react if a malicious proposal slips through.

Before deployment, thoroughly test all state transitions and edge cases using a framework like Foundry or Hardhat. Key tests include: preventing double voting, rejecting execution before the vote ends, correctly calculating weighted votes, and enforcing quorum. The design of this contract dictates the security and efficiency of your entire governance process, so invest time in getting it right.

step-configure-governor
GOVERNANCE SETUP

Step 2: Configure Voting Parameters

Define the core rules that will govern your DAO's treasury proposals, including voting duration, quorum, and approval thresholds.

Voting parameters are the constitutional rules of your DAO, encoded directly into your governance smart contracts. These settings determine how proposals pass or fail and are critical for security and efficiency. Key parameters include the voting delay (time between proposal submission and voting start), voting period (duration of the active vote), proposal threshold (minimum token power needed to submit a proposal), quorum (minimum participation required for a valid vote), and approval threshold (percentage of For votes needed to pass). Setting these requires balancing inclusivity with protection against spam and malicious proposals.

For a typical OpenZeppelin Governor-based contract, these parameters are set during contract deployment. Below is an example initialization for a DAO using a 7-day voting period, a 4% quorum, and a simple majority (50% + 1 vote) approval threshold. The votingDelay is set to 1 block, and the proposalThreshold is set to 10,000 tokens.

solidity
// Example: Initializing Governor contract with voting parameters
constructor(IVotes _token)
    Governor("MyDAOGoverner")
    GovernorVotes(_token)
    GovernorVotesQuorumFraction(4) // 4% quorum
{}

function votingDelay() public pure override returns (uint256) {
    return 1; // 1 block
}

function votingPeriod() public pure override returns (uint256) {
    return 50400; // ~7 days in blocks (assuming 12s block time)
}

function proposalThreshold() public pure override returns (uint256) {
    return 10_000 * 1e18; // 10,000 tokens required to propose
}

Choosing the right values is a strategic decision. A short voting period (e.g., 3 days) enables rapid execution but reduces deliberation time. A high quorum (e.g., 20%) ensures broad consensus but can lead to voter apathy causing proposals to fail from lack of participation. A low proposal threshold encourages more community submissions but increases governance overhead. Analyze successful DAOs like Uniswap (4% quorum, 2-day voting delay) or Compound (2% quorum, 2-day delay) for reference, but tailor parameters to your token distribution and community engagement levels.

After deployment, changing these parameters typically requires a governance proposal itself, making initial configuration critically important. Use a testnet to simulate proposal lifecycles with your chosen settings. Tools like Tally or Boardroom provide interfaces to visualize these parameters and their impact. Document your final choices transparently for your community, as they define the fundamental power dynamics and operational tempo of your DAO's treasury management.

step-integrate-executor
ON-CHAIN EXECUTION

Step 3: Integrate with a Treasury Executor

This step connects your on-chain voting mechanism to a secure contract that autonomously executes approved treasury transactions.

A treasury executor is a smart contract that holds the authority to move funds from your DAO's treasury, but only after receiving a valid, on-chain approval signal. This separation of voting and execution is a critical security pattern. Popular frameworks like OpenZeppelin's Governor use an Executor contract (often called a TimelockController) for this purpose. The executor enforces a mandatory delay between proposal approval and execution, providing a final safety window for the community to review and potentially cancel malicious transactions.

Integration involves configuring your voting contract to point to the executor's address. For a Governor-based setup, this is set during deployment via the TimelockController address parameter. Once linked, successful proposals do not execute directly; instead, they are queued in the executor. After the timelock delay elapses, any account can call the execute function to trigger the approved actions. This process ensures execution is permissionless and verifiable, while the delay acts as a circuit breaker.

The executor contract manages a queue of pending operations, each with a unique operationId (a hash of the target addresses, values, calldata, and salt). To execute a proposal, you must call the executor with the correct operation ID. In practice, front-end interfaces like Tally or Boardroom automate this queue and execute process, providing a user-friendly dashboard to track proposal status from creation through execution.

Key configuration parameters for the executor include the timelock delay (e.g., 24-72 hours for major treasuries) and the list of proposers (your voting contract) and executors (often set to address(0) to allow any address). It's crucial to verify these settings on-chain after deployment using a block explorer. The executor should hold zero treasury funds initially; its power derives solely from its role as the approved spender in the DAO's main asset vaults (like a Gnosis Safe).

For advanced use cases, you can implement multi-signature requirements within the executor itself or use specialized modules like Zodiac's Reality Module to bridge on-chain votes with off-chain execution via a Gnosis Safe. Always test the full proposal lifecycle—from creation to queue to execution—on a testnet like Sepolia or Goerli before deploying to mainnet to ensure the integration handles asset transfers and contract calls correctly.

step-frontend-snapshot
IMPLEMENTING ON-CHAIN GOVERNANCE

Step 4: Set Up the Voting Interface (Snapshot/Tally)

Deploy a frontend interface for token holders to create, view, and vote on treasury proposals, using platforms like Snapshot or Tally.

With your treasury contract deployed and a token like OpenZeppelin's Governor in place, you need a user-friendly interface for governance. The two primary solutions are Snapshot for gasless off-chain signaling and Tally for direct on-chain execution. Snapshot uses signed messages and IPFS for proposal storage, making it ideal for community sentiment checks without transaction fees. Tally connects directly to your Governor contract, enabling token holders to cast votes that are recorded on-chain, which is required to execute passed proposals. Your choice depends on whether you need binding on-chain votes or lightweight community polling.

To integrate Snapshot, you first create a space on snapshot.org. You must configure the space with your governance token's contract address and the specific blockchain network. The voting strategy is defined using a plugin; for a standard ERC-20 token, you would use the erc20-balance-of strategy. This strategy calculates voting power based on the user's token balance at a specific block number (a snapshot). You can then set admins, add proposal validation rules, and customize the interface's branding to match your DAO.

For on-chain voting with Tally, you connect your wallet to tally.xyz and import your deployed Governor contract address. Tally will automatically index the contract's ABI to surface proposal creation forms, active votes, and voting history. Key configuration involves verifying the contract on a block explorer like Etherscan and ensuring Tally recognizes your token for vote weighting. Tally also provides delegate dashboards, allowing users to easily delegate their voting power to others, a core feature of contracts like Governor.

Here is a basic example of the data needed to create a proposal via a Governor contract's propose function, which an interface like Tally would abstract for users:

solidity
// Target contracts, values, and calldata for the proposal's actions
address[] memory targets = new address[](1);
targets[0] = treasuryAddress;
uint256[] memory values = new uint256[](1);
values[0] = 0;
bytes[] memory calldatas = new bytes[](1);
calldatas[0] = abi.encodeWithSignature("transfer(address,uint256)", recipient, amount);
// Description hash (often IPFS CID)
string memory description = "Proposal #1: Send 1000 ETH to grant recipient";
bytes32 descriptionHash = keccak256(abi.encodePacked(description));
// Submit the proposal
uint256 proposalId = governor.propose(targets, values, calldatas, description);

After setup, you must promote the voting interface to your community. This involves adding the Snapshot space URL or Tally DAO page to your project's documentation, website, and social channels. For a seamless experience, consider embedding a voting widget directly on your project's site. Governance participation hinges on clear communication: establish channels for proposal discussion (like a forum), announce voting periods, and provide guides on how to connect wallets and delegate votes. The interface is the bridge between your smart contract infrastructure and your token-holding community.

DAO FRAMEWORKS

Governance Parameter Comparison

Key configuration differences between popular governance frameworks for treasury proposals.

ParameterOpenZeppelin GovernorCompound GovernorAave Governance V2

Voting Delay

1 block

~2 days

~1 day

Voting Period

3 days

3 days

3 days

Proposal Threshold

1 token

25,000 COMP

80,000 AAVE

Quorum Required

4% of supply

400,000 COMP

Varies by proposal

Timelock Execution

Proposal Cancellation

Proposer only

Guardian only

Guardian only

Gas-Optimized Execution

Vote Delegation

ON-CHAIN VOTING

Common Implementation Issues and Fixes

Troubleshooting guide for developers implementing on-chain voting for DAO treasury proposals. Covers common pitfalls, gas optimization, and security considerations.

This error typically occurs when the proposal's snapshot block is incorrectly set or the voting delay has not elapsed. The snapshot block determines voter eligibility and token balances for the vote.

Common causes and fixes:

  • Incorrect Snapshot Block: Ensure the snapshot block number is finalized (not a future block). Use block.number - 1 when creating the proposal.
  • Voting Delay Not Met: Most governors (like OpenZeppelin's) have a votingDelay. The vote cannot be cast until proposalSnapshot() + votingDelay blocks have passed. Check your governor's state with state(proposalId).
  • Proposal Not Active: Verify the proposal is in the Active state (state == 1) before allowing votes.

Example check in a script:

solidity
uint256 state = governor.state(proposalId);
require(state == 1, "Governor: vote not currently active");
ON-CHAIN VOTING

Frequently Asked Questions

Common technical questions and troubleshooting steps for developers implementing on-chain treasury governance.

The voting period and timelock are distinct security and execution phases in a governance lifecycle.

Voting Period: This is the active window (e.g., 3-7 days) during which token holders can cast votes on a proposal. No actions can be executed during this time. The period length is a core governance parameter set in the voting contract (like OpenZeppelin's Governor).

Timelock: After a proposal succeeds, it typically enters a timelock delay (e.g., 24-48 hours) before the encoded transactions can be executed. This is a critical security feature, often managed by a separate TimelockController contract. It gives the community a final window to react if a malicious proposal passes, allowing for emergency actions like canceling the queued transaction.

In summary: Voting decides if something happens; the timelock controls when it happens, adding a safety buffer.