Tally Integration (Governance)
Tally is frontend and infrastructure for on-chain governance. A protocol creates a DAO with Governor contract; Tally automatically indexes proposals, votes, and delegation — and displays everything in a convenient interface. Without Tally the team would need to build this UI from scratch or use Snapshot (which is off-chain).
Integrating with Tally requires no special SDK — just deploy a compatible Governor contract. But "compatible" means several specific things.
Compatible Governor Contracts
Tally works with two contract families:
OpenZeppelin Governor (recommended). Base Governor.sol contract with modular extensions: GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl. This is the de facto standard for new DAOs.
Compound Bravo Governor (legacy). Original Governor from Compound, used in Uniswap, Compound, many others. Tally supports it, but new projects should use OZ.
Minimal configuration for new DAO:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/governance/Governor.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";
contract MyDAOGovernor is
Governor,
GovernorSettings,
GovernorCountingSimple,
GovernorVotes,
GovernorVotesQuorumFraction,
GovernorTimelockControl
{
constructor(
IVotes _token,
TimelockController _timelock
)
Governor("MyDAO Governor")
GovernorSettings(
7200, // votingDelay: 1 day in blocks (Ethereum ~12s/block)
50400, // votingPeriod: 7 days
100e18 // proposalThreshold: 100 tokens to create proposal
)
GovernorVotes(_token)
GovernorVotesQuorumFraction(4) // 4% of total supply for quorum
GovernorTimelockControl(_timelock)
{}
// Mandatory overrides to resolve conflicts between extensions
function votingDelay() public view override(Governor, GovernorSettings) returns (uint256) {
return super.votingDelay();
}
// ... other overrides
}
Voting Token
Important nuance: standard ERC-20 doesn't work with Governor. Need ERC20Votes — an extension storing historical balances (checkpoints) for vote counting at proposal creation moment.
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
contract GovernanceToken is ERC20, ERC20Permit, ERC20Votes {
constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {}
// ERC20Votes requires override _afterTokenTransfer
function _afterTokenTransfer(address from, address to, uint256 amount)
internal override(ERC20, ERC20Votes)
{
super._afterTokenTransfer(from, to, amount);
}
}
Checkpoint mechanism is critical: without it you can buy tokens, vote, sell — all in one block. With checkpoints votes are counted at proposal creation block number.
Delegation: holder must delegate votes (even to themselves) for them to count. Without delegate() voting power is zero. Tally displays this and reminds users to delegate.
Tally Registration
Tally automatically indexes Governor contracts deployed on supported networks (Ethereum, Arbitrum, Optimism, Polygon, Base, and others). For DAO to appear in Tally:
- Go to tally.xyz → "Add DAO"
- Enter Governor contract address and network
- Tally verifies ABI and starts indexing
Optionally add: DAO logo, description, social links — editable in Tally admin panel.
TimelockController: Protection Between Voting and Execution
Timelock is mandatory. After proposal passes, changes don't apply immediately — there's a waiting period (usually 2-7 days) during which the community can notice potentially malicious proposals and take action.
TimelockController timelock = new TimelockController(
2 days, // minDelay
proposers, // only Governor can propose
executors, // address(0) = anyone can execute after delay
admin // admin for initial setup, then renounce
);
// Critical: transfer ownership of all managed contracts to Timelock
managedContract.transferOwnership(address(timelock));
After deploy: Governor is added as proposer; after testing admin role is renounced. Contract is fully under DAO control.
What Tally Shows and How It Affects UX
Tally displays:
- List of all proposals with current status and voting results
- Delegation: who votes are delegated to, change history
- Delegate profiles with voting history
- Proposal details: description, on-chain actions (which functions are called)
- Quorum progress bar in real-time
Best practice: include full reasoning in proposal description (markdown supported), links to forum discussions. Tally renders this beautifully. Proposals with empty descriptions lose community trust.
Typical Integration Issues
Wrong quorum parameters: 4% of total supply sounds reasonable, but if 60% of tokens are with team and undelegated — actual circulating supply is smaller; quorum effectively unreachable. Count quorum from circulating supply, not total.
Too short votingDelay: if votingDelay = 0, proposal creator can buy tokens, create proposal and vote in one block. Minimum 1-2 days.
No proposal threshold: anyone can spam proposals. Set reasonable threshold — e.g., 0.1-1% of total supply.
Governor doesn't own managed contracts: common mistake on first deploy. Ensure Timelock is owner of all contracts DAO manages.
Tally integration takes 1-3 weeks including deploy, parameter setup, testnet testing, and DAO registration.







