Разработка системы автоматических периодических выплат в крипто
Блокчейн по природе своей — push-система. Никто не может снять средства с вашего кошелька без подписи транзакции. Рекуррентные платежи в крипто — это не «подписка, которая снимает деньги каждый месяц», а архитектурная проблема: как организовать автоматические выплаты без постоянного присутствия пользователя, не жертвуя безопасностью.
Модели реализации
Есть три принципиально разных подхода, и каждый решает разную задачу.
Pull-модель с аппрувом. Получатель (или протокол) может снять средства сам, но только в рамках одобренного allowance. Это стандартная ERC-20 схема: пользователь один раз делает approve(spender, amount), дальше spender вызывает transferFrom по расписанию.
Проблема — неограниченный approve. Пользователь апрувает type(uint256).max, и если контракт скомпрометирован, все средства уязвимы. Правильно: approve на конкретную сумму + allowance сбрасывается после каждой выплаты.
Escrow с расписанием. Пользователь вносит средства в контракт-хранилище, контракт выплачивает по расписанию. Контроль у пользователя сохраняется через функцию паузы/отмены. Это более безопасная архитектура — средства заблокированы, но пользователь знает точно, сколько и когда уйдёт.
Потоковая оплата (streaming). Протоколы Superfluid и Sablier реализуют непрерывный поток токенов: средства текут посекундно, получатель может забрать накопленное в любой момент. Особенно хорошо для зарплат, вестинга, арендных платежей.
Архитектура контракта
Для кастомной системы периодических выплат строим вокруг нескольких ключевых элементов:
struct PaymentSchedule {
address payer;
address payee;
address token; // address(0) для ETH
uint256 amount; // сумма за период
uint256 period; // в секундах
uint256 nextPaymentAt; // timestamp следующей выплаты
uint256 maxPayments; // 0 = бесконечно
uint256 completedPayments;
bool active;
}
mapping(bytes32 => PaymentSchedule) public schedules;
Ключевые функции:
-
createSchedule()— пользователь создаёт подписку, первый платёж опционально сразу -
processPayment(bytes32 scheduleId)— выполняет очередную выплату (вызывается keeper-ом) -
cancelSchedule()— пользователь отменяет подписку -
pauseSchedule()/resumeSchedule()— временная приостановка
Защита от double-spending. nextPaymentAt обновляется перед переводом средств (Check-Effects-Interactions). Добавляем paymentNonce — уникальный счётчик для каждой выплаты, защита от replay в мультисиг сценариях.
Автоматизация выплат: кто вызывает контракт
Контракт сам себя не вызовет. Нужен внешний триггер.
Chainlink Automation (бывший Keepers). Децентрализованная сеть нод, которая мониторит условие и вызывает контракт:
import "@chainlink/contracts/src/v0.8/automation/AutomationCompatible.sol";
contract RecurringPayments is AutomationCompatibleInterface {
function checkUpkeep(bytes calldata)
external view override
returns (bool upkeepNeeded, bytes memory performData)
{
bytes32[] memory dueSchedules = getDueSchedules(); // schedules с nextPaymentAt <= block.timestamp
upkeepNeeded = dueSchedules.length > 0;
performData = abi.encode(dueSchedules);
}
function performUpkeep(bytes calldata performData) external override {
bytes32[] memory scheduleIds = abi.decode(performData, (bytes32[]));
for (uint i = 0; i < scheduleIds.length; i++) {
_processPayment(scheduleIds[i]);
}
}
}
Chainlink Automation — надёжный выбор для mainnet. Стоимость — оплата LINK за каждый upkeep вызов плюс газ. Регистрация upkeep занимает несколько минут через веб-интерфейс или программно.
Gelato Network. Альтернатива Chainlink Automation с более гибкими условиями триггера. Поддерживает time-based и event-based триггеры. Можно платить в ETH вместо нативного токена.
Собственный backend keeper. Для B2B решений или когда нужна полная кастомизация: backend мониторит контракт, вызывает processPayment. Централизованный, но проще в отладке. Для enterprise-клиентов часто предпочтительнее.
Управление лимитами и безопасность
Лимит суммы за период. Пользователь устанавливает максимальный размер разовой выплаты при создании подписки. Попытка keeper-а вызвать выплату с суммой выше лимита — revert.
Временное окно. Выплата считается просроченной, если не выполнена в течение graceWindow после nextPaymentAt. Если keeper не вызвал функцию в окне — выплата пропускается (или накапливается, зависит от бизнес-логики).
Пауза при недостатке средств. Если на escrow счёте недостаточно токенов — вместо revert контракт эмитирует событие InsufficientFunds и деактивирует расписание. Keeper читает событие и отправляет уведомление пользователю (через backend + email/push).
Нативная валюта vs токены
ETH-выплаты проще в реализации, но сложнее в управлении: пользователь должен держать ETH в контракте. Токены (ERC-20) удобнее для stablecoin-платежей (USDC, DAI) — пользователь апрувает контракту тратить токены со своего кошелька, сам держит их у себя.
Для rекуррентных B2C платежей (подписки) — рекомендуем USDC на Polygon или Arbitrum. Низкий газ, стабильная стоимость, широкая поддержка.
| Критерий | ETH/MATIC native | ERC-20 (USDC) |
|---|---|---|
| Сложность | Проще | Чуть сложнее |
| Хранение средств | В контракте (escrow) | У пользователя (approve) |
| Предсказуемость суммы | Зависит от курса | Стабильная (stablecoin) |
| UX для пользователя | Хуже (нужно пополнять контракт) | Лучше |
Процесс разработки
Проектирование (1-2 дня). Определяем модель (pull/escrow/streaming), выбираем keeper, рисуем state machine для расписания (active → paused → cancelled → completed).
Разработка контракта (4-6 дней). Пишем на Solidity 0.8+, OpenZeppelin ReentrancyGuard, Pausable. Тесты через Foundry — fuzzing граничных условий по времени особенно важен.
Интеграция keeper (1-2 дня). Регистрация в Chainlink Automation или деплой Gelato task.
Backend и уведомления (2-3 дня). Мониторинг событий контракта через ethers.js или viem, отправка уведомлений пользователям.
Общий срок — 1-2 недели в зависимости от сложности и количества интеграций.







