Разработка системы условного платежа (conditional payment) на блокчейне
Условный платёж — это escrow на стероидах. Деньги заблокированы в контракте и освобождаются только при выполнении заранее определённых условий. Звучит просто, но дьявол в деталях: кто верифицирует выполнение условия, что происходит при споре, как контракт знает о событиях off-chain.
Именно последний вопрос делает эту задачу нетривиальной. Блокчейн-контракт изолирован — он не может сам проверить, что «задача выполнена», «товар доставлен» или «KPI достигнут». Ему нужен оракул.
Архитектура: кто подтверждает условие
Выбор механизма подтверждения определяет всю архитектуру системы.
Арбитр (trusted third party)
Классический escrow: покупатель, продавец, арбитр. Arbirator — address с правом вызвать release() или refund(). Простейшая реализация, подходит для большинства B2B случаев.
Проблема: арбитр централизован. Если это адрес одного человека — это single point of failure и доверия. Решение: арбитр — мультисиг или DAO.
Оракул (Chainlink или кастомный)
Для условий, которые можно получить on-chain: цена актива, результат события, on-chain данные из другого протокола. Chainlink AnyAPI позволяет запросить любые HTTP-данные и доставить их в контракт.
// Запрос данных через Chainlink Functions
function requestVerification(bytes32 jobId, string calldata apiUrl) external {
Chainlink.Request memory req = buildChainlinkRequest(
jobId, address(this), this.fulfill.selector
);
req.add("get", apiUrl);
req.add("path", "result.completed");
sendChainlinkRequest(req, fee);
}
function fulfill(bytes32 requestId, bool completed) external recordChainlinkFulfillment(requestId) {
if (completed) {
_releaseFunds();
}
}
Для простых числовых условий (price threshold) — Chainlink Price Feeds без дополнительной интеграции.
Подпись получателя (cryptographic proof)
Условие подтверждается цифровой подписью доверенной стороны. Например, платёжная система подписывает подтверждение транзакции, а контракт верифицирует подпись через ecrecover. Это работает без on-chain оракула — подпись содержит всю необходимую информацию.
function releaseWithSignature(
uint256 paymentId,
bytes memory signature
) external {
bytes32 hash = keccak256(abi.encodePacked(paymentId, address(this)));
bytes32 ethHash = hash.toEthSignedMessageHash();
address signer = ethHash.recover(signature);
require(signer == trustedVerifier, "Invalid signature");
_release(paymentId);
}
Многосторонняя верификация
Комбинация: 2-of-3 между покупателем, продавцом и арбитром. Любые два из трёх могут освободить средства. Это снижает риск сговора одной стороны с арбитром.
Базовая структура контракта
struct Payment {
address payer;
address payee;
uint256 amount;
address token; // address(0) для ETH
uint256 deadline; // timestamp истечения
PaymentState state; // Pending, Released, Refunded, Disputed
bytes32 conditionHash; // хэш условия (off-chain документ)
}
enum PaymentState { Pending, Released, Refunded, Disputed }
Deadline критичен. Если условие не выполнено к deadline, плательщик должен иметь возможность вернуть средства. Без deadline средства могут застрять навсегда.
Типичные кейсы и их специфика
Freelance-платёж. Условие: подтверждение заказчика. Арбитр: DAO или мультисиг. Срок: 14-30 дней. Особенность: нужен механизм оспаривания с хранением доказательств (IPFS-хэши в контракте).
DeFi-опцион. Условие: достижение ценового уровня. Оракул: Chainlink Price Feed. Срок: expiry опциона. Особенность: oracle manipulation risk — flash loan может кратковременно изменить цену. Решение: TWAP (time-weighted average price) вместо spot price.
Вендор-платёж с KPI. Условие: on-chain данные (TVL, объём транзакций). Оракул: The Graph + кастомный оракул. Срок: квартальный. Особенность: данные с The Graph не push, а pull — контракт запрашивает их через Chainlink.
Игровые достижения. Условие: on-chain событие в игровом контракте. Верификация: прямой вызов от игрового контракта (самый простой случай — всё on-chain).
Безопасность: что может пойти не так
Reentrancy при release. _release() переводит ETH или токены. Если recipient — контракт, он может вызвать _release() повторно. Защита: ReentrancyGuard от OpenZeppelin + checks-effects-interactions паттерн (сначала меняем state, потом переводим).
Oracle manipulation. Flash loan атакующий за один блок манипулирует ценой на DEX → оракул читает изменённую цену → условие выполнено → средства освобождены. Для ценовых условий — только Chainlink с TWAP, не DEX spot price.
Front-running на fulfill. MEV-бот видит транзакцию fulfillment в mempool и вставляет свою транзакцию перед ней. Для escrow это обычно не критично (получатель определён заранее), но в аукционных схемах нужна commit-reveal.
Истечение срока при валидном условии. Условие выполнено, но транзакция confirmaion застряла, и deadline прошёл. Нужен разумный grace period или off-chain мониторинг с алертами.
Сроки
Базовый escrow-контракт с ETH/ERC-20, арбитром и deadline — 2 дня с тестами. С интеграцией Chainlink Price Feed — +1 день. С Chainlink Functions для произвольных API — +2 дня. Мультисторонний спор-механизм с IPFS для доказательств — +2 дня. Полная система с frontend — 3-5 рабочих дней сверху.







