An on-chain proposal system is a smart contract framework that formalizes decision-making for decentralized protocols. Unlike informal off-chain signaling, these systems encode governance logic directly on the blockchain, enabling actions like treasury spending, parameter adjustments, or contract upgrades to be proposed, voted on, and executed autonomously. Major protocols like Compound Governor Bravo, Uniswap, and Arbitrum's DAO implement their own variants, but all share a common lifecycle: proposal submission, a voting period, a timelock delay, and final execution.
Setting Up On-Chain Proposal Submission and Lifecycle
Introduction to On-Chain Proposal Systems
On-chain governance allows token holders to directly control protocol evolution through transparent, executable proposals. This guide covers the core lifecycle from submission to execution.
The proposal lifecycle begins with submission. A proposer, who must typically hold a minimum threshold of governance tokens (e.g., 0.25% of supply), calls a function like propose() on the governance contract. This function requires specifying a list of target addresses, values (in ETH), and calldata for the desired actions. For example, a proposal to update a Uniswap fee parameter would target the PoolFactory contract with calldata encoding the setFeeProtocol function call. The proposal is then stored on-chain with a unique ID.
The Voting Process
Once submitted, the proposal enters a voting delay (for discussion) followed by an active voting period, usually lasting 3-7 days. Token holders cast votes weighted by their balance, often with options like For, Against, and Abstain. Voting power can be delegated, and many systems support vote delegation to experts. The vote is decided by a quorum (a minimum percentage of total supply that must vote) and a vote threshold (e.g., a majority For). Smart contracts like OpenZeppelin's Governor provide modular, audited implementations of this logic.
After a successful vote, proposals do not execute immediately. They enter a timelock period, a critical security feature. This delay, often 2-3 days, gives the community time to react if a malicious proposal somehow passes. The timelock contract holds the proposal's encoded actions. Once the delay elapses, any address can call execute() to trigger the actions. This final step changes the state of the target contracts, completing the governance cycle. The entire process is transparent and verifiable on a block explorer like Etherscan.
Setting up a proposal requires interacting with the governance contract. Using ethers.js and a Governor contract address, a developer would prepare the transaction data. Here's a simplified code snippet for proposal submission:
javascriptconst targets = [contractAddress]; const values = [0]; const calldatas = [contractInterface.encodeFunctionData('updateParameter', [newValue])]; const description = "# Proposal to Update Fee Parameter\nThis proposal sets the fee to 0.05%."; const descriptionHash = ethers.utils.id(description); await governorContract.propose(targets, values, calldatas, description);
The description is hashed on-chain, while the full text is typically hosted on IPFS or a forum.
Effective governance requires more than technical setup. Best practices include thorough off-chain discussion on forums like Commonwealth or Discourse before submitting, ensuring clear specification of calldata, and understanding the gas costs for execution. Security audits for both the governance contract and the target contracts are essential. By mastering this lifecycle, developers and DAO contributors can actively participate in shaping the decentralized protocols they use.
Prerequisites and Tech Stack
This guide outlines the essential tools and knowledge required to build and manage on-chain governance proposals, from initial setup to final execution.
To interact with on-chain governance systems, you need a foundational understanding of blockchain concepts and the specific protocol you're targeting. This includes familiarity with smart contracts, gas fees, and the governance token that confers voting rights (e.g., UNI for Uniswap, COMP for Compound). You must also understand the proposal lifecycle stages: submission, voting, timelock, and execution. Before writing any code, review the target protocol's official governance documentation, such as the Compound Governance documentation or Uniswap Governance docs, to understand its specific contract addresses, proposal thresholds, and voting periods.
Your development environment requires Node.js (v18 or later) and a package manager like npm or Yarn. The core technical stack involves a blockchain interaction library and a wallet connection. For Ethereum and EVM-compatible chains, ethers.js v6 or viem are the standard libraries for constructing and sending transactions. You will use a wallet provider such as MetaMask for signing, which can be integrated via libraries like wagmi. For non-EVM chains (e.g., Solana, Cosmos), you'll need their respective SDKs, like @solana/web3.js or @cosmjs/stargate. A basic project setup includes initializing a new Node.js project and installing these dependencies.
A critical prerequisite is access to a governance token balance with sufficient voting power to meet the proposal submission threshold. For testing, you can use tokens on a testnet (e.g., Goerli, Sepolia). You'll need testnet ETH for gas and test governance tokens, which are often obtainable through faucets. Configure your wallet to connect to the correct network and ensure your script can read the state of the governance contracts, such as the proposal id counter or the current quorumVotes, using the provider instance. Always verify contract ABIs from official sources to ensure your transactions interact with the correct functions.
The final setup step is writing the proposal logic itself. This typically involves encoding the calldata for the actions you want the governance system to execute. For example, a proposal to change a protocol parameter might call setParameter(uint256 newValue) on a specific contract. Use your library's interface utilities (like ethers.Interface) to encode this function call. Your submission transaction will send this encoded data to the governance contract's propose function. Thorough testing on a forked mainnet or testnet using tools like Hardhat or Foundry is essential before submitting any live proposal to avoid costly errors.
On-Chain Proposal Submission and Lifecycle
A technical guide to implementing a complete on-chain governance system, from proposal creation to execution.
On-chain governance systems are defined by a proposal lifecycle—a series of sequential states managed by smart contracts. A typical lifecycle begins with a Pending state, where a proposal is created but not yet open for voting. It then moves to Active for a defined voting period, followed by a Succeeded or Defeated state based on voter turnout and quorum. Finally, a successful proposal enters a Queued state for a timelock delay before being Executed. This deterministic flow, enforced by code, eliminates ambiguity and ensures every proposal follows the same rules. The core contract must track this state machine and enforce transitions.
Proposal submission is the entry point. A user calls a function like propose(address[] targets, uint256[] values, bytes[] calldatas, string description) on the governance contract. This function validates the proposer (often checking they hold a minimum token balance), hashes the proposal details to create a unique proposalId, and initializes the proposal struct with a startBlock. Critical security checks here include ensuring identical proposals cannot be re-submitted and that the proposer cannot front-run execution by setting the targets to malicious contracts. The OpenZeppelin Governor contract is a widely audited reference implementation for this pattern.
The voting mechanism is activated after the proposal becomes Active. Voters cast their votes using castVote(uint256 proposalId, uint8 support), where support is typically 0 (Against), 1 (For), or 2 (Abstain). Voting power is usually snapshot at the proposal's creation block to prevent manipulation. The contract tallies votes, and once the voting period ends, it evaluates conditions like quorum (minimum voting power required) and a simple majority to determine the outcome. Advanced systems may use quadratic voting or delegation. The result is calculated on-chain, and the proposal state updates to Succeeded or Defeated.
A critical security feature for high-value protocols is the timelock. When a proposal succeeds, it is not executed immediately. Instead, it is queued in a TimelockController contract (like OpenZeppelin's) with a mandatory delay (e.g., 48 hours). This gives the community a final window to review the executed calldata and, if a critical flaw is discovered, to potentially exit the system or prepare a counter-proposal. After the delay, anyone can call execute to run the proposal's transactions. The timelock contract becomes the executor of the governance contract, adding a crucial buffer between decision and action.
To implement this, you would deploy and wire together several contracts. A typical stack includes: a token contract for voting power (e.g., an ERC20Votes or ERC721Votes), a Governor contract (e.g., GovernorCountingSimple), and a TimelockController. The Governor is configured with voting parameters (votingDelay, votingPeriod, quorum). The Timelock is set as the Governor's executor via _setTimelock. Finally, the protocol's core contracts (like a Treasury) are transferred to be owned by the Timelock address. This architecture ensures all privileged actions flow through the transparent governance process.
Defining Proposal Types
Understanding the different types of on-chain proposals is the first step to building effective governance. Each type has a specific purpose, technical structure, and lifecycle.
On-Chain Proposal Submission Parameters
Comparison of common parameter sets for initial proposal submission across major governance frameworks.
| Parameter | Conservative (Security-First) | Balanced (Default) | Permissive (Community-First) |
|---|---|---|---|
Minimum Deposit | 50,000 GOV | 10,000 GOV | 1,000 GOV |
Deposit Denom | Native GOV token | Native GOV token | Native GOV token or LP token |
Deposit Period | 14 days | 7 days | 3 days |
Quorum Required | 40% of staked supply | 20% of staked supply | 10% of staked supply |
Threshold for Passing | 66.67% Yes votes | 50% Yes votes (simple majority) | 50% Yes votes (simple majority) |
Veto Threshold | 33.33% NoWithVeto votes | 33.33% NoWithVeto votes | 20% NoWithVeto votes |
Whitelisted Proposal Types | TextParameterChange | TextParameterChangeSpendCommunityPool | TextParameterChangeSpendCommunityPoolSoftwareUpgrade |
Min Initial Deposit % | 20% of min deposit | 10% of min deposit | 1% of min deposit |
Implementing the Proposal Lifecycle
A technical guide to building a complete on-chain proposal system, from smart contract architecture to user interface.
The core of any DAO is its proposal lifecycle, which governs how decisions are made and executed. A robust on-chain system requires several key smart contracts: a proposal factory for creating proposals, a voting token (like an ERC-20 or ERC-721) to determine voting power, a voting module to handle the voting logic, and an executor contract to carry out passed proposals. This modular design, often following standards like OpenZeppelin's Governor, separates concerns and allows for upgrades to individual components. The lifecycle typically follows a state machine: Pending -> Active -> Succeeded/Defeated -> Queued -> Executed.
Setting up proposal submission begins with the factory contract. It defines the proposal structure, which must include a target address, calldata for the action, and a description hash. Security is paramount; the factory should validate that proposals target whitelisted contracts or have zero-value calls unless explicitly allowed. Here's a simplified example of a proposal creation function:
solidityfunction propose( address[] memory targets, uint256[] memory values, bytes[] memory calldatas, string memory description ) public returns (uint256 proposalId) { require(targets.length == values.length && targets.length == calldatas.length, "Length mismatch"); proposalId = _hashProposal(targets, values, calldatas, keccak256(bytes(description))); proposals[proposalId] = Proposal({ proposer: msg.sender, voteStart: block.number + votingDelay, voteEnd: block.number + votingDelay + votingPeriod, executed: false, canceled: false }); emit ProposalCreated(proposalId, msg.sender, targets, values, calldatas, description); }
The voting module is where governance tokens confer power. The most common model is token-weighted voting, where one token equals one vote. More advanced systems include time-weighted voting (like ve-token models), quadratic voting to reduce whale dominance, or conviction voting for continuous signaling. The contract must implement a getVotes function that returns a user's voting power at a specific block number, enabling snapshot-based voting to prevent last-minute manipulation. After the voting period ends, the proposal state is determined by comparing for and against votes against a quorum and a pass threshold (e.g., >50%).
Once a proposal succeeds, it moves to the timelock and execution phase. A timelock contract is a critical security component; it imposes a mandatory delay between a proposal's passing and its execution. This gives token holders a final window to react if a malicious proposal slips through. The executor function validates the proposal state, checks the timelock delay has expired, and then uses a low-level call to execute the stored calldata on the target contract. Failed executions should revert the entire transaction to maintain state consistency. All state changes and external calls must be carefully audited, as this function has the highest privilege level in the system.
For developers, integrating this backend with a frontend is the final step. The UI must fetch active proposals from the blockchain, display voting power, and facilitate transaction signing. Key libraries include wagmi and viem for React integration, and The Graph for efficiently indexing and querying proposal data. Best practices involve providing clear voter information: - The exact code the proposal will execute - The current vote tally and quorum status - The time remaining in each phase - The voter's own voting power and previous votes. Transparency at this layer is essential for legitimate participation and trust in the governance system.
Timelock and Security Features
Secure on-chain governance requires structured proposal lifecycles and execution safeguards. This guide covers the tools and patterns for implementing robust, time-delayed governance.
The Proposal Lifecycle: From Creation to Execution
A secure on-chain proposal follows a defined path with multiple security checkpoints.
- Propose: A holder with sufficient tokens submits a call data payload (target, value, calldata).
- Vote: After the
votingDelay, token holders cast votes weighted by their balance. - Queue: If the vote succeeds, the proposal is sent to the Timelock contract, starting the delay timer.
- Execute: After the delay, anyone can trigger the execution of the queued calls.
- Cancel: A permissioned guardian (or the proposer during voting) can cancel malicious proposals. Each step is enforced on-chain, ensuring transparency and eliminating centralized points of failure.
Security Considerations and Best Practices
Implementing governance requires careful design to avoid common pitfalls.
- Timelock Duration: Balance security and agility. A 2-7 day delay is common for major protocols, allowing for community review without paralyzing operations.
- Guardian Role: Assign a Timelock Guardian (often a multi-sig) with exclusive cancel rights as an emergency brake.
- Proposal Threshold: Set high enough to prevent spam but low enough for accessibility.
- Calldata Review: The delay period is for reviewing the exact calldata that will execute. Use tools like Tenderly to simulate execution before the vote concludes.
- Upgradability: Ensure your Timelock and Governor contracts are not upgradeable without going through the governance process itself.
Real-World Examples: Compound and Uniswap
Study established implementations to inform your design.
- Compound Governance: Uses a GovernorBravo contract with a 2-day timelock. Proposals require 25,000 COMP to submit and a 3-day voting period. The timelock holds all protocol assets.
- Uniswap Governance: Features a 7-day timelock delay. Proposals need 10,000,000 UNI delegated (as of v3) to submit. It uses a multi-sig as a guardian for the timelock.
- Key Takeaway: Both systems separate the voting mechanism from the asset custody (Timelock). The timelock address is the most privileged contract in the system, often controlling the upgradeability of all other core contracts.
Code Walkthrough: Governor Contract
This guide explains the core mechanics of submitting and managing a proposal through a Governor smart contract, using OpenZeppelin's Governor framework as a reference.
An on-chain governance proposal is a structured transaction that bundles executable code with metadata. The lifecycle is managed by a Governor contract, which defines the rules for proposal creation, voting, and execution. The core function for submission is propose. This function takes arrays of target addresses, values (in wei), and calldata payloads for the actions to be executed, along with a human-readable description. The contract validates the proposer's voting power (typically requiring it to meet a proposalThreshold) before creating a new ProposalCore struct and emitting an event.
Upon successful submission, the proposal enters a pending state, moving to active after a delay defined by votingDelay. During the active period, which lasts for votingPeriod, token holders cast their votes. Voting power is usually snapshot at the start of the voting block. The Governor contract delegates vote counting to a IVotes token contract (like ERC20Votes or ERC721Votes) to determine the proposal's outcome based on a quorum and the chosen voting module (e.g., simple majority).
After the voting period ends, the proposal state becomes succeeded if it passed the quorum and vote threshold, or defeated otherwise. A successful proposal must then be queued before execution, which involves a mandatory timelock delay for security. The queue function interacts with a timelock controller (like OpenZeppelin's TimelockController), scheduling the proposal's actions for future execution. Finally, after the timelock delay expires, the execute function can be called to run the proposal's calldata on the target contracts, changing the protocol's state.
Frequently Asked Questions
Common questions and solutions for developers working with on-chain proposal submission and lifecycle management.
A standard on-chain governance proposal follows a multi-stage lifecycle designed to ensure deliberation and security. The typical flow is:
- Submission & Deposit: A proposer submits the proposal (e.g., a
Proposalstruct) and locks a security deposit, often in the native governance token. - Voting Delay: A mandatory waiting period begins, allowing voters to review the proposal details before voting opens.
- Voting Period: Token holders cast votes, usually weighted by their token balance or delegated voting power. Common voting options are For, Against, and Abstain.
- Timelock & Execution: If the proposal passes the required quorum and majority threshold, it is queued in a Timelock contract. After a mandatory delay (e.g., 48-72 hours), the proposal's encoded transactions can be executed by anyone.
Protocols like Compound and Uniswap use variations of this model, with key parameters (delay, period, quorum) configurable via governance itself.
Resources and Further Reading
Technical resources for implementing on-chain proposal submission, voting, execution, and governance lifecycle management across Ethereum and EVM-compatible networks.
Conclusion and Next Steps
You have now configured a complete on-chain governance system for proposal submission and lifecycle management.
This guide walked through the core components of an on-chain proposal system: creating a Governor contract with configurable voting parameters, writing a TimelockController for secure execution delays, and deploying a custom ERC20Votes token for snapshot-based voting power. You learned how to use OpenZeppelin's Governor contracts as a secure foundation, which handles the standard proposal states—Pending, Active, Canceled, Defeated, Succeeded, Queued, Expired, and Executed. The integration of a timelock is a critical security best practice, introducing a mandatory delay between a proposal's approval and its execution to allow for community review.
To extend this system, consider implementing more advanced features. Proposal thresholds can be added to require a minimum token stake for submission. For gas-efficient voting, explore vote delegation and signature-based voting via EIP-712, which allows users to sign votes off-chain. You can also integrate with Tally or Boardroom for a user-friendly frontend that displays proposal history and voting analytics. Always audit upgrade paths for your governance contracts, especially if using transparent or UUPS proxy patterns, to prevent unauthorized changes to the governance rules themselves.
The next step is to test your system end-to-end in a forked mainnet environment using tools like Hardhat or Foundry. Simulate a full proposal lifecycle: a user creates a proposal to call a function on a mock treasury contract, voters cast their votes using delegated tokens, the proposal succeeds, waits in the timelock, and is finally executed. Monitor gas costs at each stage and consider implementing gas refunds for proposal creators to encourage participation. For production, a comprehensive security audit is non-negotiable. Finally, document the process clearly for your community, specifying proposal guidelines, voting periods, and the role of multisig guardians during the initial bootstrapping phase.