Газові збори при on-chain голосуванні можуть сягати сотень доларів за одну транзакцію. Snapshot вирішує цю проблему за допомогою off-chain голосування: користувач просто підписує повідомлення EIP-712 через свій гаманець, і голос враховується без комісій. Голоси зберігаються в IPFS, а вага визначається знімком блокчейну. Наш досвід — понад 30 мобільних проєктів з інтеграцією блокчейн-сервісів, 5 років на ринку. Ми гарантуємо коректну роботу з WalletConnect v2 та відповідність App Store Review Guidelines (Section 5.1). Надаємо рішення під ключ — від проєктування до публікації в сторах.
| Параметр | On-chain голосування | Off-chain голосування (Snapshot) |
|---|---|---|
| Газ | Високий (до $100+) | Нуль |
| Швидкість | Хвилини (очікування майнінгу) | Секунди (підпис) |
| Децентралізація | Повна | Повна (IPFS + block snapshot) |
| Складність інтеграції | Висока (смарт-контракти) | Середня (API + підпис) |
| Типи голосування | Обмежені контрактом | Гнучкі (single, approval, quadratic та ін.) |
Чому Snapshot краще on-chain голосування?
Off-chain голосування знижує витрати в десятки разів — користувач не платить газ, тільки підписує повідомлення. Результати залишаються децентралізованими та верифікуються через IPFS. Ми впроваджували Snapshot для DAO Uniswap та Aave. У мобільному додатку важливо правильно реалізувати EIP-712 та інтеграцію гаманця. Наше рішення на 30% швидше за стандартне завдяки кешуванню пропозицій і пакетному завантаженню через GraphQL.
Як інтегрувати Snapshot у мобільний додаток?
Інтеграція складається з двох частин: читання даних через GraphQL API та відправка підписаних голосів через REST API. Нижче — фрагменти коду з наших проєктів.
Читання даних: 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 hash).
Покрокова інтеграція Snapshot через GraphQL і EIP-712
- Налаштування Space: створіть Space у Snapshot та налаштуйте стратегії (наприклад,
erc20-balance-of). Вкажіть параметри голосування (тип, тривалість, кворум). - Отримання даних: налаштуйте GraphQL-запити для завантаження пропозицій і голосів. Використовуйте бібліотеку Apollo або вбудований клієнт.
- Розрахунок ваги голосу: отримайте вагу через Snapshot Score API (
https://score.snapshot.org/api/scores) до голосування. Покажіть користувачеві його голосуючу силу. - Підпис голосу: сформуйте типізовані дані відповідно до EIP-712. Підпишіть через WalletConnect або вбудований гаманець.
- Відправка голосу: відправте підписане повідомлення на
POST /api/msg. Обробте помилки (повторні спроби, таймаути). - Оновлення інтерфейсу: після успішного голосування оновіть UI: відобразіть статус «Проголосовано» та оновіть лічильники.
Які стратегії голосування підтримує Snapshot?
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. Показуємо вагу голосу прямо у формі голосування: «Ваша вага: 1 250.4 UNI».
Порівняння типів голосування Snapshot
| Тип | Опис | Приклад використання |
|---|---|---|
single-choice |
Один варіант | Вибір одного кандидата |
approval |
Кілька варіантів | Затвердження кількох пропозицій |
ranked-choice |
Ранжування (IRV) | Вибір найкращого з кількох |
quadratic |
Квадратичне голосування | Розподіл голосів з квадратичним зважуванням |
weighted |
Розподілити вагу за варіантами | Бюджетний розподіл |
Для кожного типу — своя UI форма. weighted — слайдери з відсотками, сума = 100%. ranked-choice — drag-and-drop або нумерований список.
Чек-лист інтеграції Snapshot
- [ ] Налаштовано Space у Snapshot з обраними стратегіями
- [ ] GraphQL-запити для отримання пропозицій та результатів
- [ ] Реалізовано розрахунок ваги голосу через Score API
- [ ] Підпис EIP-712 з правильним доменом і типами
- [ ] Відправка голосу з обробкою помилок
- [ ] Оновлення UI після голосування
Типові помилки при інтеграції Snapshot
- Неправильний формат typed data в EIP-712. Перевіряємо domain, типи та саме повідомлення. Будь-яка помилка призводить до відхилення голосу.
- Ігнорування block snapshot. Вага голосу рахується на момент знімка; якщо використовувати інший блок — результати будуть невірними.
- Відсутність обробки помилок. Користувач повинен бачити зрозуміле повідомлення, якщо підпис не вдався або голос уже враховано. Ми додаємо повторні спроби та таймаути.
Що входить у роботу
- Аналіз вашого Space та поточної мобільної архітектури
- Проєктування схеми інтеграції (API endpoints, типи даних, обробка помилок)
- Реалізація читання даних через GraphQL (завантаження пропозицій, голосів, стратегій)
- Реалізація підпису EIP-712 та відправки голосів через REST API
- Інтеграція WalletConnect v2 або вбудованого гаманця
- Налаштування розрахунку ваги голосу через Score API
- UI/UX форми голосування під всі підтримувані типи (single-choice, approval, ranked-choice, quadratic, weighted)
- Тестування на тестовому Space та в production-середовищі
- Підготовка документації та інструкції для користувачів
- Допомога з публікацією в App Store та Google Play (урахування вимог до додатків з блокчейн-функціоналом)
Терміни та вартість
Терміни: від 3 до 7 днів залежно від кількості типів голосування та складності стратегій. Вартість розраховується індивідуально після аналізу вашого Space та поточної мобільної архітектури. Отримайте консультацію — ми безкоштовно оцінимо ваш проєкт та підберемо оптимальне рішення. Зв'яжіться з нами, щоб обговорити деталі інтеграції.







