Multi-DAO Governance Aggregator Development
Large token holders participating in multiple DAOs simultaneously face operational chaos: Uniswap Governor, Compound Governor, Aave AIP, ENS Governor — each has different interface, proposal logic, and deadlines. Missing voting deadline is common. A governance aggregator solves this: single dashboard for monitoring, ability to delegate, and auto-voting rules.
System Architecture
Three Aggregation Layers
Indexing layer — indexes events from all tracked Governor contracts. Each protocol has specifics (Governor Bravo vs OZ Governor differ); needs adapters.
State layer — unified data model for proposals from different sources. Normalization: despite different contracts, all proposals share structure {id, protocol, status, deadline, description, calldata}.
Action layer — on-chain or off-chain mechanism for executing votes. Most complex component.
Protocol Adapters
interface GovernorAdapter {
getProposals(fromBlock: number): Promise<Proposal[]>;
getProposalState(proposalId: bigint): Promise<ProposalState>;
castVote(proposalId: bigint, support: number): Promise<TransactionRequest>;
getVotingPower(voter: string, blockNumber: number): Promise<bigint>;
}
class OZGovernorAdapter implements GovernorAdapter {
constructor(private contract: Contract) {}
async getProposals(fromBlock: number) {
const filter = this.contract.filters.ProposalCreated();
const events = await this.contract.queryFilter(filter, fromBlock);
return events.map(e => this.normalizeProposal(e));
}
async castVote(proposalId: bigint, support: number) {
return {
to: this.contract.target,
data: this.contract.interface.encodeFunctionData('castVote', [proposalId, support])
};
}
}
On-chain Component: Batch Voting
Aggregator contract allows voting in multiple DAOs with one transaction:
contract GovernanceAggregator {
struct VoteInstruction {
address governor; // Governor contract address
uint256 proposalId;
uint8 support; // 0=Against, 1=For, 2=Abstain
bytes reason; // optional reasoning
}
mapping(address => mapping(address => bool)) public authorizedDelegates;
function batchVote(
address voter,
VoteInstruction[] calldata instructions
) external onlyAuthorized(voter) {
for (uint i = 0; i < instructions.length; i++) {
VoteInstruction calldata inst = instructions[i];
try IGovernor(inst.governor).castVoteWithReason(
inst.proposalId,
inst.support,
string(inst.reason)
) {
emit VoteCast(voter, inst.governor, inst.proposalId, inst.support);
} catch Error(string memory reason) {
emit VoteFailed(voter, inst.governor, inst.proposalId, reason);
}
}
}
}
Auto-voting Rules
Key feature — automatic vote execution by rules:
interface VotingRule {
protocol: string; // "uniswap" | "compound" | "*"
proposalType: string; // "parameter-change" | "treasury" | "*"
conditions: Condition[];
defaultVote: 0 | 1 | 2; // Against/For/Abstain
requireConfirmation: boolean;
}
// Example: always vote Against treasury proposals > $1M
const rule: VotingRule = {
protocol: "*",
proposalType: "treasury",
conditions: [{
field: "requestedAmount",
operator: ">",
value: 1_000_000_000000 // $1M in USDC (6 decimals)
}],
defaultVote: 0, // Against
requireConfirmation: false
};
Notifications and Deadlines
Without deadline notifications, aggregator loses half its value:
Monitoring: poll active proposals every N minutes or subscribe via WebSocket.
Notifications: email / Telegram / webhook on: new proposal, deadline approaching (24h), proposal passed/failed.
Deadline calculation: Governor stores proposalDeadline as block number. Convert to timestamp: deadlineBlock * avgBlockTime + referenceTimestamp. Accuracy ±5 minutes sufficient.
Development Stack
Backend: Node.js + TypeScript + viem + BullMQ (task queues) + PostgreSQL (proposal state storage).
Indexing: The Graph subgraphs for major protocols + own listeners for others.
Frontend: React + wagmi + Radix UI. Key screens: active proposals dashboard, rule management, vote history.
Smart contract: Solidity 0.8.x + Foundry. Aggregator contract minimal; logic off-chain.
Timeline
| Function | Timeline |
|---|---|
| Indexer (3–5 protocols) | 2–3 weeks |
| Batch vote contract | 1 week |
| Rules + automation | 2–3 weeks |
| Frontend dashboard | 2–3 weeks |
| Notifications | 1 week |
MVP with manual batch voting and basic dashboard — 4–5 weeks. Full aggregator with rules — 2–3 months.







