DAO Voting Aggregator with Multi-Network Support

A large token holder participating in Uniswap, Compound, Aave, and other DAOs faces different Governor interfaces, proposal logic, and deadlines daily. Missing a deadline is common. Our aggregator provides a unified dashboard, monitoring, delegation, and automatic execution of votes. Our team has 5+

Blockchain Development Services

Frequently Asked Questions

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1441
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1301
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    998
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1267
  • image_logo-advance_0.webp
    B2B Advance company logo design
    713
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    1003

A large token holder participating in Uniswap, Compound, Aave, and other DAOs faces different Governor interfaces, proposal logic, and deadlines daily. Missing a deadline is common. Our aggregator provides a unified dashboard, monitoring, delegation, and automatic execution of votes. Our team has 5+ years of experience and has delivered over 20 smart contracts. Contact us for an architecture consultation tailored to your stack.

Architecture system

The aggregator consists of three layers: Indexing layer — indexes events from Governor contracts via adapters (each protocol has its own specifics — Governor Bravo vs OZ Governor differ). State layer — a uniform data model: proposals normalized into {id, protocol, status, deadline, description, calldata}. Action layer — on-chain or off-chain mechanism for executing votes.

How do protocol adapters work?

Each Governor-compatible contract has a slightly different ABI. OZ Governor v4/v5 differs from Compound Governor Bravo, which differs from Compound Governor Alpha. For each we write an adapter implementing a unified interface:

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]) }; } } class CompoundBravoAdapter implements GovernorAdapter { async castVote(proposalId: bigint, support: number) { return { to: this.contract.target, data: this.contract.interface.encodeFunctionData('castVote', [proposalId, support]) }; } } 

During development, check ready-made adapters in libraries like wagmi/viem or The Graph subgraphs.

On-chain component: batch vote

The aggregator contract allows voting in multiple DAOs with one transaction using the Multicall pattern. The contract code is minimal and auditable.

contract GovernanceAggregator { struct VoteInstruction { address governor; uint256 proposalId; uint8 support; bytes reason; } mapping(address => mapping(address => bool)) public authorizedDelegates; modifier onlyAuthorized(address voter) { require( msg.sender == voter || authorizedDelegates[voter][msg.sender], "Not authorized" ); _; } 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); } } } } 

An important nuance: each Governor checks msg.sender as the voter. The aggregator votes from its own address — this works only if voting power is delegated to the aggregator address. An alternative approach is using a smart wallet with DELEGATECALL, but that adds security complexity.

Delegation and proportional voting

For ERC-20Votes tokens, the user delegates voting power to the aggregator address. The problem: if 60% want For and 40% Against, the aggregator should vote proportionally. The solution is fractional voting: the aggregator votes with its weight proportionally. Compound Governor Bravo supports castVoteWithWeightBySig, and OZ Governor v5 added GovernorCountingFractional.

Automatic voting rules

A key feature is automatic execution of votes based on predefined rules. Example: always vote Against for treasury proposals > $1M. An off-chain service analyzes new proposals, extracts parameters from calldata, and applies rules. If conditions are not met, a default vote is used.

interface VotingRule { protocol: string; proposalType: string; conditions: Condition[]; defaultVote: 0 | 1 | 2; requireConfirmation: boolean; } 

Parsing proposal calldata

We need to extract meaning from calldata. For example, a Compound proposal to change collateral factor:

const KNOWN_SIGNATURES = { '0x3c3e4f7a': { name: 'setCollateralFactor', protocol: 'compound' }, '0x5b85a600': { name: '_setReserveFactor', protocol: 'compound' }, }; function parseProposalCalldata(calldata: string): ProposalAction { const selector = calldata.slice(0, 10); const known = KNOWN_SIGNATURES[selector]; if (!known) return { type: 'unknown', selector }; const iface = new ethers.Interface([`function ${known.name}(...)`]); const decoded = iface.decodeFunctionData(known.name, calldata); return { type: known.name, protocol: known.protocol, params: decoded }; } 

This is labor-intensive work for each protocol.

Notifications and deadlines

Notification system: poll active proposals every N minutes or subscribe via WebSocket (eth_subscribe logs). Notifications via email / Telegram / webhook on proposal creation, approaching deadline (24 hours before), and execution. Deadline calculation: deadlineBlock * avgBlockTime + referenceTimestamp, accuracy ±5 minutes.

Development stack

Backend: Node.js + TypeScript + viem + BullMQ + PostgreSQL. Indexing: The Graph subgraphs + custom listener. Frontend: React + wagmi + Radix UI. Smart contract: Solidity 0.8.x + Foundry.

What's included in the work

Component Description
Protocol analysis Identify supported DAOs and their Governor contracts
Adapter development Create normalizers for each protocol
Aggregator smart contract Implement batch vote with try/catch
Backend for indexing and rules Monitoring, parsing, automatic voting
Frontend dashboard View proposals and manage rules
Notifications Telegram/Email alerts
Security audit Check contract and backend
Documentation API, rules, architecture

Comparison with manual voting

The aggregator reduces monitoring time by 3x. Instead of 5–10 minutes to check all active proposals, it takes 1 minute on the dashboard. Automatic rules eliminate human error. Contact us for a project estimate based on the number of protocols.

Why our approach is more reliable?

Every contract is checked for reentrancy, overflow, and uses formal verification with Echidna (fuzzing). Our team has 5+ years in blockchain development. The aggregator does not store user funds; all transactions are signed by an authorized delegate.

Indicative timelines

Feature Timeline
Basic indexer (3–5 protocols) 2–3 weeks
On-chain batch vote contract 1 week
Rules and automation 2–3 weeks
Frontend dashboard 2–3 weeks
Notifications 1 week

MVP with manual batch voting takes 4–5 weeks. Full aggregator takes 2–3 months. Contact us — we'll calculate cost and timelines for your specific protocols.