Впровадження інтеграції Snapshot для голосування DAO в мобільних додатках
Snapshot — це off-chain система голосування, де користувач підписує повідомлення EIP-712 своїм гаманцем, а результат зберігається в децентралізованій мережі IPFS/Snapshot Hub. Голосування нічого не коштує — немає газу. Вага голосу визначається балансом токенів у блокчейні на моменту снапшоту пропозиції.
Інтеграція Snapshot у мобільний додаток — це GraphQL API для читання даних та REST API для відправки голосів з підписом.
Читання даних: Snapshot GraphQL API
// Android — отримання списку пропозицій Space через 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 — унікальний ідентифікатор DAO у Snapshot (наприклад, uniswap.eth, aave.eth).
Голосування: Підпис EIP-712
Голос — це підписане повідомлення з типізованими даними:
// iOS — формування та підпис голосу для 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": "{}"
]
)
}
Після отримання підпису від користувача — відправте в Snapshot API:
// iOS — відправка підписаного голосу в 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 для публікації. У відповіді — id голосу (IPFS хеш).
Стратегії голосування
Snapshot підтримує кастомні стратегії визначення ваги голосу:
-
erc20-balance-of— стандартний баланс токена -
erc20-votes— делегована вага черезgetVotes() -
delegation— з урахуванням вхідних делегацій -
quadratic— квадратний корінь від балансу - Можна комбінувати кілька стратегій
Стратегії читаються з конфігурації Space через GET https://hub.snapshot.org/api/spaces/{spaceId}. Показуйте користувачам, яка стратегія використовується та їх голосуюча сила.
Розрахунок ваги голосу перед голосуванням
// Android — отримання голосуючої сили через 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. Показуйте голосуючу силу прямо у формі голосування: «Ваша сила: 1250,4 UNI».
Типи голосування Snapshot
| Тип | Описання |
|---|---|
single-choice |
Один варіант |
approval |
Кілька варіантів |
ranked-choice |
Ранжування (IRV) |
quadratic |
Квадратичне голосування |
weighted |
Розподіл сили по варіантам |
Кожен тип потребує своєї UI форми. weighted — слайдери з відсотками, сума = 100%. ranked-choice — drag-and-drop або нумерований список.
Часова шкала: 3–5 днів: GraphQL запити для списку пропозицій та деталей, форма голосування з EIP-712 підписом через WalletConnect, відображення голосуючої сили, підтримка базових типів голосування (single-choice, approval).







