Developing Mobile Applications for DAO (Voting, Proposals)
A DAO application is an interface to a governance smart contract. Token holders create proposals, vote, delegate voting power, and monitor execution. Unlike NFT or DeFi, UX here is read-heavy: most users vote rather than create proposals. The proposal list screen is the primary entry point.
Standards: OpenZeppelin Governor vs. Snapshot
Two fundamentally different approaches:
On-chain (Governor): everything happens on blockchain. OpenZeppelin Governor is the de-facto standard. Voting costs gas, but results are cryptographically verifiable. GovernorBravo, GovernorAlpha — forks used by Compound, Uniswap, Aave.
Off-chain (Snapshot): EIP-712 signatures without transactions. Voting is free, stored in Snapshot network. Results don't execute automatically — requires multisig or executor.
Most DAOs combine both: Snapshot for voting (no gas), Governor for executing passed proposals. The app must support both.
Proposal List: Data and States
A proposal transitions through several states:
| State | Governor | Description |
|---|---|---|
| Pending | 0 | Voting not started yet |
| Active | 1 | Voting is open |
| Canceled | 2 | Canceled by author |
| Defeated | 3 | Failed to reach quorum or majority |
| Succeeded | 4 | Passed, awaiting queuing |
| Queued | 5 | In TimeLock, awaiting execution |
| Expired | 6 | Execution deadline passed |
| Executed | 7 | Executed |
Fetch data via IGovernor.state(proposalId). Get proposal list via The Graph or Tally API. Direct contract calls are inefficient: no native getProposals() method.
// iOS — fetching proposals via Tally GraphQL API
struct TallyProposalsQuery: Codable {
// GraphQL query for fetching DAO proposals
static let query = """
query Proposals($governorId: ID!, $first: Int!) {
proposals(
governorId: $governorId,
pagination: { first: $first },
sort: { sortBy: id, isDescending: true }
) {
id
title
description
status
voteStats { support votes percent }
start { ... on Block { timestamp } }
end { ... on Block { timestamp } }
}
}
"""
}
Tally, Boardroom, Messari Governance — aggregators with APIs for most major DAOs. For custom DAOs — The Graph with OpenZeppelin Governor subgraph.
Proposal Screen
Screen structure:
- Title and description (Markdown → rendered text)
- Status and timeframes (start, end, timer)
- Real-time results: For / Against / Abstain with percentages and progress bars
- Quorum: collected X of Y votes (progress toward threshold)
- Voting buttons (only in Active status)
- Event history: who voted and when
// Android — displaying voting results
data class ProposalVoteStats(
val forVotes: BigDecimal,
val againstVotes: BigDecimal,
val abstainVotes: BigDecimal
) {
val totalVotes get() = forVotes + againstVotes + abstainVotes
val forPercent get() = if (totalVotes > BigDecimal.ZERO)
(forVotes / totalVotes * BigDecimal(100)).toInt() else 0
val quorumReached get() = totalVotes >= quorumThreshold
}
Voting: On-chain and Off-chain (Snapshot)
On-chain: governor.castVote(proposalId, support) — three support values: 0 (Against), 1 (For), 2 (Abstain). With reason: castVoteWithReason(proposalId, support, reason). Transaction costs gas.
Snapshot off-chain: EIP-712 message signature, sent to Snapshot API:
// iOS — voting via Snapshot API (no transaction)
struct SnapshotVotePayload: Codable {
let version: String // "0.1.3"
let timestamp: Int
let space: String // DAO space ID
let type: String // "single-choice", "approval", "quadratic"
let payload: VotePayload
struct VotePayload: Codable {
let proposal: String // IPFS hash of proposal
let choice: Int // 1 = For, 2 = Against
let metadata: String // "{}"
}
}
Sign via WalletConnect or built-in wallet. Free — Snapshot's key advantage.
Vote Delegation
Many tokens support delegation (ERC20Votes): holders can transfer voting power to another address without giving up tokens.
token.delegate(delegateeAddress) — single transaction. Until called — tokens don't participate in Governor voting, even if on balance.
UX: on first DAO app launch, check if tokens are delegated. If not — banner "Activate voting rights: delegate tokens (to yourself or other)".
// iOS — checking delegation status
func getDelegatee(for address: EthereumAddress) async throws -> EthereumAddress {
return try await governanceToken.delegates(account: address)
}
// If result == .zero or == address — not yet delegated
Push Notifications for Active Participants
- New proposal created
- Voting closes soon (24 hours)
- Proposal passed / rejected
- Proposal queued for execution
- Quorum reached
Let users configure which notifications to receive — don't spam all events.
Proposal Creation
Creating a Governor proposal is complex: must specify targets (contract addresses), values (ETH), calldatas (encoded function calls), and description. Interface should either simplify via templates or provide a call constructor for technical users.
Check minimum token threshold (proposalThreshold) upfront and show if user has enough tokens.
Timeline
| Component | Timeline |
|---|---|
| Proposals list + statuses | 1 week |
| Proposal screen with real-time votes | 1 week |
| On-chain voting (Governor) | 3 days |
| Snapshot voting | 3 days |
| Vote delegation | 2 days |
| Push notifications | 3 days |
| Proposal creation | 1 week |
MVP (list + voting + delegation): 3–4 weeks. Full app with proposal creation, history, analytics: 8–12 weeks.







