Разработка контрактов эскроу

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка контрактов эскроу
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1258
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1170
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    873
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1092
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    563
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    830

Разработка контрактов эскроу

Эскроу-контракт выглядит просто: положить деньги, выполнить условие, забрать деньги. На практике это один из наиболее аудируемых типов контрактов, потому что любая ошибка в логике условий или правах вывода ведёт к прямой потере средств. Не к неправильному отображению баланса — к потере ETH или токенов.

Две главные точки отказа

Недостаточно жёсткие условия разблокировки

Самый частый баг в эскроу-контрактах — неполная проверка условий перед release(). Пример: маркетплейс NFT с escrow для P2P-сделок. Покупатель депонирует ETH, продавец должен передать NFT. Контракт проверяет ownerOf(tokenId) == address(this) перед release — то есть что NFT находится на контракте. Но не проверяет, что это именно тот NFT, который был заявлен в момент deposit().

Атака: продавец депонирует дешёвый NFT из той же коллекции (или вовсе другой контракт с совпадающим tokenId), контракт видит NFT на своём адресе и отдаёт ETH. Потеря для покупателя — разница между заявленным и реальным NFT.

Правильная реализация хранит в маппинге не просто флаг депозита, а полный слепок сделки: адрес NFT-контракта, tokenId, ожидаемый продавец, сумму, дедлайн.

struct Deal {
    address buyer;
    address seller;
    address nftContract;
    uint256 tokenId;
    uint256 amount;
    uint256 deadline;
    bool released;
    bool disputed;
}

Проблема арбитража и dispute-механизма

Простой двусторонний эскроу (покупатель и продавец соглашаются на release) имеет очевидную проблему: если стороны не согласны, средства заморожены навсегда. Либо нужен третий арбитр, либо таймаут с логикой возврата.

Арбитр — отдельная точка централизации и риска. Если арбитр — EOA, это single point of failure (потеря ключа). Если арбитр — контракт, нужна governance. Если арбитр — multisig (Gnosis Safe), это приемлемый компромисс для большинства случаев.

Важный момент: арбитр не должен иметь возможность принудительно вывести средства на произвольный адрес. Права арбитра должны быть ограничены: либо approve release к покупателю, либо approve возврат к продавцу. Не более.

Как мы строим эскроу-контракт

Базовая структура

Три состояния сделки: PENDING (средства задепонированы, условие не выполнено), COMPLETED (release выполнен), CANCELLED (возврат). Переходы между состояниями — строго через функции с проверками. Никаких прямых обращений к state извне.

Checks-effects-interactions везде без исключений. Это особенно важно для ETH-эскроу: обновляем state (marked as released) до отправки ETH, не после.

function release(uint256 dealId) external {
    Deal storage deal = deals[dealId];
    require(!deal.released, "Already released");
    require(msg.sender == deal.buyer || msg.sender == arbiter, "Unauthorized");
    
    deal.released = true; // Effects first
    
    // Interactions last
    (bool success, ) = deal.seller.call{value: deal.amount}("");
    require(success, "Transfer failed");
    
    emit Released(dealId, deal.seller, deal.amount);
}

Работа с ERC-20 токенами

ETH-эскроу проще: ETH нельзя отозвать после deposit с помощью approve(). С ERC-20 — другая история. Если контракт держит токены через transferFrom() в момент deposit — он физически владеет ими, и approve-отзыв после депозита ничего не меняет. Это правильный паттерн.

Неправильный паттерн: контракт только записывает allowance и делает transferFrom() в момент release. Между deposit и release покупатель может отозвать approve(), и release() завершится revert. Продавец выполнил условие, а средства не получил.

Для fee-on-transfer токенов (USDT на некоторых чейнах) считаем реально полученную сумму: balanceBefore - balanceAfter, не доверяем параметру amount в transferFrom().

Таймауты и дедлайны

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

Дедлайн проверяем через block.timestamp. Известный грабль: block.timestamp может быть сдвинут майнером на ±15 секунд на Ethereum. Для дедлайнов в днях это несущественно, для дедлайнов в секундах — нет.

Reentrancy в эскроу

ETH-эскроу особенно уязвим к reentrancy через receive(). Используем ReentrancyGuard из OpenZeppelin на функциях release() и refund(). Альтернатива — pull-паттерн: не отправляем ETH напрямую, а записываем в маппинг withdrawable[seller] += amount, продавец сам вызывает withdraw(). Это полностью устраняет reentrancy в release().

Подход Reentrancy риск UX
Push (прямая отправка) Есть, нужен ReentrancyGuard Автоматически
Pull (withdrawable маппинг) Отсутствует Требует отдельной транзакции
Pull + permit Отсутствует Gasless через подпись

Апгрейдность и многоцелевой эскроу

Для маркетплейсов с большим объёмом сделок имеет смысл фабричный паттерн: один EscrowFactory, который деплоит минимальные прокси (EIP-1167) под каждую сделку. Это изолирует средства разных сделок в отдельных контрактах и упрощает аудит.

Апгрейдность через Transparent Proxy или UUPS добавляет риск: администратор может изменить логику release() уже после депозита. Для честного эскроу апгрейдность должна быть ограничена или исключена. Если апгрейдность нужна для исправления багов — используем timelock (минимум 48 часов) и multisig.

Сроки

Базовый ETH/ERC-20 эскроу с арбитром и дедлайном: 2-3 рабочих дня включая тесты. NFT-эскроу с dispute-механизмом и фабричным паттерном: 4-6 рабочих дней. Стоимость рассчитывается индивидуально после уточнения требований.