Разработка подписочной модели оплаты в крипто
Традиционные подписки работают просто: карта привязана, банк списывает каждый месяц. В крипто нет механизма pull-платежей — никто не может снять токены с вашего кошелька без вашей подписи. Это принципиальное архитектурное отличие, которое делает крипто-подписки нетривиальной задачей. Каждое решение — компромисс между UX и децентрализацией.
Три архитектуры и их реальные компромиссы
Prepaid с on-chain балансом
Пользователь вносит депозит в контракт подписки. Контракт списывает плату периодически — либо по требованию сервиса (chargeFee(address user)), либо при каждом обращении пользователя к защищённой функции.
Это самый простой вариант. Проблема: пользователь должен помнить о пополнении баланса. Баланс заканчивается — подписка прерывается без предупреждения. Для сервисов с автопополнением нужен off-chain триггер (Chainlink Automation или Gelato) для своевременного уведомления.
Pull-платежи через EIP-2612 (permit) + off-chain trigger
Пользователь подписывает permit — разрешение на списание до N токенов со своего кошелька без отдельной on-chain транзакции. Permit хранится off-chain. Каждый расчётный период backend отправляет транзакцию transferFrom, используя сохранённый permit.
Это максимально близко к традиционным pull-платежам. Компромисс: backend должен быть доверенным и не злоупотреблять правами списания. Для полностью децентрализованной системы — не подходит. Для B2B SaaS с доверенным оператором — отличный вариант.
EIP-2612 permit имеет deadline — разрешение истекает. Нужно заранее предупреждать пользователя о необходимости обновить разрешение. Бесконечный permit (deadline = type(uint256).max) — снижает UX-трение, но увеличивает риски при компрометации ключа.
Account Abstraction + Session Keys
Наиболее user-friendly подход. Смарт-кошелёк пользователя (EIP-4337) выдаёт session key с правом списывать до X токенов в месяц на адрес подписочного контракта. Session key живёт на серверной стороне сервиса. Каждый расчётный период — UserOperation через bundler, без участия пользователя.
Это требует, что у пользователя смарт-кошелёк, а не EOA. Сейчас — ограничение. Через 2-3 года, по мере роста AA-адаптации — вероятно, стандарт.
Структура подписочного контракта
Минимально необходимые компоненты:
struct Subscription {
address subscriber;
uint256 planId;
uint256 paidUntil; // timestamp
uint256 depositBalance; // предоплаченный баланс
bool active;
}
Ключевые функции:
-
subscribe(uint256 planId)— оформление подписки с начальным депозитом -
renewSubscription(address subscriber)— продление (вызывается оператором или Chainlink Automation) -
cancelSubscription()— отмена с возвратом остатка депозита -
isSubscribed(address user) returns (bool)— проверка статуса (view, без газа) -
withdraw()— вывод накопленных платежей владельцем
Управление доступом
Защищённый ресурс проверяет статус подписки через ISubscription.isSubscribed(msg.sender). Если ресурс — on-chain контракт, проверка происходит в modifier. Если ресурс — off-chain (API, контент), backend проверяет isSubscribed через eth_call перед ответом на запрос.
Второй вариант имеет точку централизации: backend может игнорировать on-chain статус. Это не проблема для бизнес-сервисов, но важно понимать при позиционировании «децентрализованного» продукта.
Ценообразование планов и оракулы
Если подписка номинирована в USD, а оплата в ETH или волатильных токенах — нужен ценовой оракул. Chainlink Price Feed — стандартное решение: контракт получает актуальный курс и вычисляет количество токенов, эквивалентное N USD.
Риск: оракул может манипулироваться через flash loan в том же блоке. Для подписочных контрактов это менее критично, чем для AMM (атакующий может получить несправедливую цену на одну транзакцию), но TWAP (Time-Weighted Average Price) через Uniswap V3 — более устойчивый вариант для sensitive-систем.
Стабильные токены (USDC, USDT, DAI) снимают проблему курса — подписка в USDC номинирована в USD без оракулов. Это предпочтительный вариант для большинства SaaS-продуктов.
Автоматизация продлений
Chainlink Automation (бывший Keepers) позволяет контракту «самообслуживаться»: определяем checkUpkeep() — логику проверки, нужно ли продление — и performUpkeep() — само продление. Chainlink node вызывает performUpkeep() автоматически, когда checkUpkeep() возвращает true.
Альтернатива — Gelato Network, более гибкий в настройке условий. Для простых time-based триггеров — Chainlink Automation проще и дешевле.
Процесс и сроки
Базовый подписочный контракт (prepaid модель, один план, USDC) — 3-4 дня разработки + 1 день тестов. Мультиплановая система с EIP-2612 permit, оракулом цены и Chainlink Automation — 2-3 недели. Полная интеграция с AA-кошельками и session keys — от 4 недель.
Стоимость зависит от выбранной архитектуры и требований к децентрализации.







