Implementing Snapshot Integration for DAO Voting in Mobile Applications
Snapshot is an off-chain voting system where users sign an EIP-712 message with their wallet, and the result is stored in a decentralized IPFS/Snapshot Hub network. Voting costs nothing — no gas. Vote weight is determined by token balance on the blockchain at the proposal's snapshot block.
Integrating Snapshot into a mobile app is GraphQL API for reading data and REST API for submitting votes with signatures.
Reading Data: Snapshot GraphQL API
// Android — fetching proposals from Space via GraphQL
suspend fun getProposals(spaceId: String, first: Int = 20): List<SnapshotProposal> {
val query = """
{
proposals(
first: $first,
skip: 0,
where: { space: "$spaceId", state: "active" },
orderBy: "created",
orderDirection: desc
) {
id
title
body
choices
start
end
snapshot
state
scores
scores_total
votes
quorum
author
}
}
""".trimIndent()
return snapshotApi.query(query).data?.proposals ?: emptyList()
}
Endpoint: https://hub.snapshot.org/graphql. Space ID is DAO's unique Snapshot identifier (e.g., uniswap.eth, aave.eth).
Voting: EIP-712 Signature
A vote is a signed message with typed data:
// iOS — forming and signing vote for Snapshot
func createVoteMessage(
proposalId: String,
choice: Int,
spaceId: String,
snapshotBlock: String
) -> TypedData {
return TypedData(
domain: TypedDataDomain(
name: "snapshot",
version: "0.1.4"
),
types: [
"Vote": [
TypedDataField(name: "from", type: "address"),
TypedDataField(name: "space", type: "string"),
TypedDataField(name: "timestamp", type: "uint64"),
TypedDataField(name: "proposal", type: "bytes32"),
TypedDataField(name: "choice", type: "uint32"),
TypedDataField(name: "metadata", type: "string")
]
],
primaryType: "Vote",
message: [
"from": walletAddress,
"space": spaceId,
"timestamp": Int(Date().timeIntervalSince1970),
"proposal": proposalId,
"choice": choice,
"metadata": "{}"
]
)
}
After obtaining the signature from user — send to Snapshot API:
// iOS — submitting signed vote to Snapshot Hub
func submitVote(vote: VotePayload, sig: String) async throws {
let body = SnapshotVoteRequest(
address: walletAddress,
msg: jsonEncode(vote),
sig: sig
)
try await snapshotClient.post("/api/msg", body: body)
}
POST https://hub.snapshot.org/api/msg — endpoint for publishing. Response contains vote id (IPFS hash).
Voting Strategies
Snapshot supports custom strategies for determining vote weight:
-
erc20-balance-of— standard token balance -
erc20-votes— delegated weight viagetVotes() -
delegation— including incoming delegations -
quadratic— square root of balance - Can combine multiple strategies
Strategies are read from Space config via GET https://hub.snapshot.org/api/spaces/{spaceId}. Show users which strategy is used and their voting power.
Calculating Vote Weight Before Voting
// Android — fetching voting power via Snapshot Score API
suspend fun getVotingPower(
voter: String,
spaceId: String,
proposal: SnapshotProposal
): BigDecimal {
val response = snapshotScoreApi.getScores(
space = spaceId,
strategies = proposal.strategies,
network = proposal.network,
addresses = listOf(voter),
snapshot = proposal.snapshot.toLong()
)
return response.result.scores.firstOrNull()?.get(voter) ?: BigDecimal.ZERO
}
Endpoint: https://score.snapshot.org/api/scores. Display voting power directly in voting form: "Your power: 1,250.4 UNI".
Snapshot Voting Types
| Type | Description |
|---|---|
single-choice |
One option |
approval |
Multiple options |
ranked-choice |
Ranking (IRV) |
quadratic |
Quadratic voting |
weighted |
Distribute power across options |
Each type requires its own UI form. weighted — sliders with percentages, sum = 100%. ranked-choice — drag-and-drop or numbered list.
Timeline: 3–5 days: GraphQL queries for proposal list and details, voting form with EIP-712 signature via WalletConnect, displaying voting power, supporting basic voting types (single-choice, approval).







