Подключение Snapshot для голосования DAO в мобильном приложении

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.

Разработка и поддержка любых видов мобильных приложений:

Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

Это лишь некоторые из типы мобильных приложений, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента.

Услуги, которые мы предлагаем
Показано 1 из 1Все 1734 услуг
Подключение Snapshot для голосования DAO в мобильном приложении
Средний
~3-5 дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    860
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    746
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1163
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    1035
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    970
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    564

Газовые сборы при 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

  1. Настройка Space: создайте Space в Snapshot и настройте стратегии (например, erc20-balance-of). Укажите параметры голосования (тип, длительность, кворум).
  2. Получение данных: настройте GraphQL-запросы для загрузки предложений и голосов. Используйте библиотеку Apollo или встроенный клиент.
  3. Расчёт веса голоса: получите вес через Snapshot Score API (https://score.snapshot.org/api/scores) до голосования. Покажите пользователю его голосующую силу.
  4. Подпись голоса: сформируйте типизированные данные в соответствии с EIP-712. Подпишите через WalletConnect или встроенный кошелёк.
  5. Отправка голоса: отправьте подписанное сообщение на POST /api/msg. Обработайте ошибки (повторные попытки, таймауты).
  6. Обновление интерфейса: после успешного голосования обновите 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 или numbered list.

Чек-лист интеграции 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 и текущей мобильной архитектуры. Получите консультацию — мы бесплатно оценим ваш проект и подберем оптимальное решение. Свяжитесь с нами, чтобы обсудить детали интеграции.

Платежи в мобильных приложениях: In-App Purchase, StoreKit 2, Google Billing, Stripe, RevenueCat

В каждом нашем проекте по монетизации приложения мы балансируем между политиками App Store и Google Play, требованиями PCI DSS и логикой верификации покупок на бэкенде. Неправильно сделанная система платежей — это не просто баг, это финансовые потери и возможный бан приложения. За 7 лет мы разобрали более 50 кейсов интеграции платёжных SDK — от простой Stripe-формы до распределённого биллинга с собственными серверными вебхуками.

In-App Purchase: две платформы, два разных API

Если приложение продаёт цифровой контент или подписки — Apple и Google требуют использовать их платёжные системы. Обойти это нельзя: нарушение правил 3.1.1 App Store или Google Play Developer Policy приводит к удалению приложения. Физические товары и сервисы, оказываемые офлайн, — другая история.

StoreKit 2 (iOS 15+)

StoreKit 2 — полная переработка исходного StoreKit с async/await API. Product.products(for:), product.purchase(), Transaction.currentEntitlements — читаемо и предсказуемо по сравнению с очередью транзакций через SKPaymentTransactionObserver.

Самое важное изменение: транзакции в StoreKit 2 подписаны JWS (JSON Web Signature) и верифицируются локально без серверного roundtrip. Transaction.verificationResult возвращает .verified(Transaction) или .unverified(Transaction, VerificationError). Это не значит, что сервер не нужен — он нужен для хранения статуса подписки, но локальная верификация убирает задержку при старте.

StoreKit.AppTransaction — верификация самого факта загрузки приложения из App Store. Нужна для приложений с платным скачиванием или бессрочными покупками, не подписками.

Сложное место в StoreKit 2 — обработка renewalState для подписок: .subscribed, .expired, .inBillingRetryPeriod, .inGracePeriod, .revoked. Состояние inGracePeriod означает, что Apple пытается возобновить оплату (до 16 дней) — в это время нужно продолжать давать доступ. Не обработаешь — потеряешь лояльных пользователей, у которых временно не прошла карта. По опыту, около 5% подписок попадают в billing retry, и автоматическое восстановление доступа возвращает до 80% из них.

Google Play Billing Library (v6+)

Google Billing — сложнее StoreKit по количеству сценариев. BillingClient с PurchasesUpdatedListener, queryProductDetailsAsync, launchBillingFlow, queryPurchasesAsync — обязательно вызывать при каждом старте приложения, не полагаться на PurchasesUpdatedListener как единственный источник правды.

Подтверждение покупки: acknowledgePurchase() для non-consumables и подписок, consumePurchase() для consumables. Если не вызвать acknowledge в течение 3 дней — Google автоматически сделает возврат. Это гарантированная потеря денег, если забыть про acknowledge на бэкенде после верификации.

ProductDetails с SubscriptionOfferDetails — в Billing v5+ структура оферт усложнилась: один продукт может иметь несколько basePlanId и offerId (пробный период, скидка для новых пользователей, retention-оффер). BillingFlowParams.SubscriptionUpdateParams для апгрейда/даунгрейда подписки с prorationMode.

Почему серверная верификация обязательна?

Никогда не доверяйте только клиентскому коду при разблокировке платного контента. Клиентская верификация обходится модификацией приложения.

Для IAP минимальная схема: приложение получает receiptData (iOS) или purchaseToken (Android), отправляет на бэкенд, бэкенд верифицирует через Apple App Store Server API / Google Play Developer API, сохраняет статус в БД, отдаёт ответ клиенту. RevenueCat делает это за вас — но если у вас кастомный бэкенд, нужно реализовать самостоятельно.

Webhook-и важнее, чем кажется. Пользователь может отменить подписку через настройки телефона, не через приложение — приложение не получит об этом событие в реальном времени. Только webhook от Apple/Google (или RevenueCat) позволяет своевременно обновить статус. Мы используем проверку подписи входящих запросов через Apple's signedPayload и Google's DeveloperNotification.

Как RevenueCat упрощает интеграцию?

Поддерживать StoreKit 2 и Google Billing одновременно, с учётом промо-кодов, оферт, восстановления покупок и серверной верификации — это несколько месяцев разработки. RevenueCat закрывает большую часть этого слоя.

RevenueCat — не просто SDK для платежей. Это:

  • Единый API для iOS и Android (и Stripe для веба)
  • Серверная верификация и хранение статусов подписок
  • Webhooks на события (покупка, возобновление, отмена, billing issue)
  • Аналитика по когортам, MRR, churn
  • A/B тестирование оферт через Experiments

Purchases.configure(withAPIKey:) при старте, Purchases.shared.getCustomerInfo() для получения текущих entitlements — минимальный интегрируемый слой. Purchases.shared.purchase(package:) вместо прямого вызова StoreKit/Billing.

В документации RevenueCat сказано: «RevenueCat handles receipt validation on the server side, reducing client-side complexity and preventing fraudulent purchases.»

Ограничения RevenueCat: платный (бесплатно до $2.5k MRR, дальше процент от дохода), не подходит для очень сложных flow с несколькими storefront-ами или кастомными bundle-ами. Однако для типового SaaS-приложения экономия на собственной разработке составляет $15–30 тыс. — интеграция окупается за 2 месяца.

Stripe в мобильных приложениях

Stripe — для оплаты физических товаров, услуг, B2B-платежей где IAP не требуется политикой платформы.

Stripe iOS SDK и Android SDKPaymentSheet для готового UI оплаты, PaymentSheetFlowController для кастомного UI с сохранёнными картами. Payment Intent создаётся на сервере, client secret передаётся в приложение — карточные данные никогда не проходят через ваш сервер, только через Stripe.

Apple Pay и Google Pay через Stripe: PKPaymentRequest (iOS) и GooglePayLauncher (Android) уже интегрированы в Stripe SDK. Конверсия у Apple Pay даёт в 1.3–2 раза выше результат, чем формы с ручным вводом карты — это цифры, которые мы подтвердили на десятке проектов.

Сохранённые карты через SetupIntent + Customer API — пользователь платит в один тап при повторном визите. Compliance: PCI DSS SAQ A — самый лёгкий уровень compliance, потому что Stripe Tokenization убирает нужду хранить карточные данные на своей стороне. Согласно PCI DSS, передача токенов освобождает от необходимости сертификации уровня 1.

3DS2 (Strong Customer Authentication) — обязателен для платежей в ЕС по PSD2. Stripe обрабатывает автоматически через PaymentIntent.confirmPayment, но нужно корректно обработать .requiresAction статус и вернуть пользователя в нужный экран после аутентификации.

Что входит в работу (deliverables)

Документация/Артефакт Содержание
Архитектурная схема биллинга Диаграмма потоков: клиент → SDK → сервер → store/webhook
Интеграция SDK Подключение и конфигурация StoreKit 2, Google Billing, RevenueCat или Stripe
Серверная верификация Реализация эндпоинтов и обработка webhook (Apple/Google/RevenueCat)
Тестовый стенд Sandbox Apple, License Testers Google, Stripe Test Mode
Документация по запуску Описание ключей, provisioning profiles, TestFlight
Обучение команды Сессия по поддержке платёжного модуля

Процесс и сроки

Начинаем с прояснения бизнес-модели: подписки, разовые покупки, consumables, freemium. От этого зависит архитектура. Тестирование IAP требует Sandbox-аккаунтов (Apple) и License Testers (Google) — это отдельная настройка окружения.

Sandbox Apple ведёт себя не так, как production: подписки возобновляются каждые 5 минут вместо месяца, inGracePeriod работает по-другому. Обязательно тестировать сценарии: истечение триала, отмена, billing retry, refund.

Сценарий Инструмент Время реализации
Подписки iOS + Android StoreKit 2 + Google Billing + RevenueCat 2–3 недели
Подписки с кастомным бэкендом StoreKit 2 + Google Billing + собственный webhook 4–6 недель
Оплата картой (физические товары) Stripe PaymentSheet 1–2 недели
Apple Pay / Google Pay Stripe или нативные SDK + 3–5 дней
Полный платёжный стек Всё вышеперечисленное 6–10 недель
Развернуть типичные ошибки при интеграции
  • Забыли вызвать acknowledgePurchase() на Android — деньги возвращаются через 3 дня.
  • Не обработали inGracePeriod — лояльные пользователи блокируются без доступа.
  • Положились только на Pushtokens при восстановлении подписок — пропускаете state обновления.
  • Использовали production-ключи в TestFlight — срабатывают реальные списания.

Стоимость рассчитывается индивидуально исходя из набора инструментов и сложности серверной логики. В среднем мы укладываемся в бюджет $5k–15k на типовую интеграцию, но экономия от предотвращения ошибок и churn окупает эти вложения за 2–3 месяца.

Получите консультацию по вашему проекту — свяжитесь с нами. Мы поможем выбрать оптимальную архитектуру платежей, которая пройдёт ревью сторов и не сломается при пиковых нагрузках.