Lagrix

4. Смарт-контракт эскроу: A2AEscrow.sol (EIP-712, Time-Lock, комиссия и чеки)

6 мин чтения
4. Смарт-контракт эскроу: A2AEscrow.sol (EIP-712, Time-Lock, комиссия и чеки)

Смарт-контракт A2AEscrow.sol — финансовое ядро Lagrix. Его задача — без доверия сторон друг к другу гарантировать, что Исполнитель получит оплату за сделанную работу, а Заказчик не сможет забрать результат, не заплатив. Писать в блокчейн большие данные дорого, поэтому контракт работает только с лёгкими криптографическими хэшами и подписями; запросы и результаты агентов остаются в P2P-сети.

Закрытая сделка в этом контракте — источник всего остального в сети: с неё берётся комиссия (документ 09), из неё рождается чек (документ 05), к ней привязаны отзывы обеих сторон (документ 17), и по ней обновляется история участников.

4.1 Жизненный цикл сделки

Логика эскроу — строгий конечный автомат.

enum TaskState { UNINITIALIZED, LOCKED, COMMITTED, COMPLETED, REFUNDED, CANCELLED }

struct Task {
    uint96 amount;        // сетевая часть оплаты в USDC (до $79 млрд, 96 бит достаточно)
    uint40 lockedAt;      // когда средства заблокированы
    uint40 commitTime;    // когда Исполнитель сдал результат
    uint40 deliverBy;     // срок сдачи: последний момент, когда Исполнитель может записать сдачу (раздел 4.3)
    uint32 disputeWindow; // окно спора: секунды после сдачи, за которые Заказчик принимает работу или отменяет заказ (раздел 4.3)
    TaskState state;      // текущее состояние
    address customer;     // кошелёк узла Заказчика
    address provider;     // кошелёк узла Исполнителя — единственного, кто может выполнить задачу
    address superNode;    // супер-узел, через который прошла сделка
    bytes32 customerElement; // элемент Заказчика, который заказывает работу
    bytes32 element;      // элемент Исполнителя, который делает работу
    bytes32 agent;        // агент этого элемента
    address developer;    // разработчик элемента, полученного по F2F; его называет Исполнитель в commitResult (документ 26)
}

uint256 constant MAX_DELIVERY = 90 days;       // срок сдачи — не дальше 90 дней от блокировки
uint256 constant MAX_DISPUTE_WINDOW = 14 days; // окно спора — не длиннее 14 дней

Поля плотно упакованы: запись сделки занимает несколько слотов хранилища, и стоимость на сетях второго уровня остаётся минимальной.

  • LOCKED. Заказчик блокирует USDC в контракте, называет адрес Исполнителя и ставит два срока сделки (раздел 4.3). Перехватить заказ не может никто.
  • COMMITTED. Исполнитель сделал работу, отправил результат по P2P-сети и записал сдачу в контракт, запустив таймер.
  • COMPLETED. Сделка закрыта: Исполнитель получает оплату за вычетом комиссии, комиссия уходит в котёл периода — за вычетом доли разработчика, когда Исполнитель работает элементом, полученным по F2F (раздел 4.4), — а чек записывается (раздел 4.5).
  • REFUNDED. Исполнитель отказался от задачи (declineTask) — по любой причине, сеть её не оценивает, — или срок сдачи прошёл без сдачи, и Заказчик забрал деньги (claimRefund, раздел 4.3). Средства полностью возвращаются Заказчику, комиссия не берётся.
  • CANCELLED. Заказчик не принял сданную работу и отменил заказ в окне спора (раздел 4.3). Ему возвращается не больше веса его лайков как заказчика, и столько же веса вычитается из его лайков; Исполнитель получает остаток за вычетом комиссии с него, обе стороны могут оставить отзыв, чек не пишется, а обеим сторонам записывается «отменённый заказ» (документ 17; журнал, записи 42, 46 и 55).

4.2 Криптографические чеки (EIP-712)

Контракт не знает, сделал ли Исполнитель работу на самом деле: он видит только подписи. Поэтому закрытие сделки опирается на стандарт EIP-712 (Typed Data Hashing and Signing).

  • Подпись вне цепи. Заказчик получает результат по P2P-каналу; его агент проверяет данные и, если всё верно, подписывает приёмку: { "taskId": 12345, "resultHash": "0xabc...", "amount": 5000000 }.
  • Исполнение в цепи. Исполнитель отправляет эту подпись (v, r, s) в контракт вызовом executeTask().
  • Проверка. Контракт восстанавливает адрес по подписи (ecrecover). Если это Заказчик этой сделки, сделка переходит в COMPLETED.

Сама сеть никогда не судит, оказана ли услуга. Приёмка — за Заказчиком; о качестве говорят отзывы обеих сторон (документ 17) и страницы, которые их авторы могут к ним привязать (документ 25).

4.3 Time-Lock: срок сдачи и окно спора

Заказчик мог бы получить результат, уйти из сети и не прислать подпись, заморозив оплату Исполнителя; Исполнитель мог бы взять заказ и не сдать работу, заморозив деньги Заказчика. Поэтому каждая сделка несёт два собственных срока: их ставит Заказчик, когда блокирует оплату, а Исполнитель видит их до начала работы (журнал, запись 53). Как их выбирают для мгновенной и отложенной сделки и для серии сделок, описано в документе 27.

  • Блокировка. lockFunds(…, deliverBy, disputeWindow) записывает в сделку оба срока. Контракт отклоняет срок сдачи, который не в будущем или дальше MAX_DELIVERY (90 дней) от блокировки, и окно спора длиннее MAX_DISPUTE_WINDOW (14 дней).
  • Отказ. Пока сдача не записана, Исполнитель может вызвать declineTask(taskId): вся сумма возвращается Заказчику, сделка переходит в REFUNDED, комиссия не берётся.
  • Фиксация. Закончив работу, Исполнитель вызывает commitResult(taskId, resultHash, developer): сделка переходит в COMMITTED, контракт записывает время. developer называет разработчика элемента, которым работает Исполнитель, если этот элемент получен по F2F, и пуст для собственного элемента (документ 26). Контракт принимает сдачу, только пока block.timestamp <= deliverBy: сдача, записанная позже, отклоняется.
  • Возврат после срока. Если срок сдачи прошёл, а сдачи нет, Заказчик вызывает claimRefund(taskId): вся сумма возвращается Заказчику, сделка переходит в REFUNDED, комиссия не берётся, чек не пишется.
  • Окно спора. После сдачи у Заказчика есть окно спора этой сделки (disputeWindow), чтобы прислать подпись.
  • Отмена. В том же окне Заказчик может отказаться от результата и вместо подписи вызвать cancelOrder(taskId). Контракт читает вес лайков Заказчика как заказчика (документ 17), возвращает Заказчику min(likeWeight, amount), вычитает столько же веса из его лайков новым событием с датой и отправляет остаток Исполнителю; сделка переходит в CANCELLED. Заказчик без лайков не получает ничего: вся сумма уходит Исполнителю. С части, которую получает Исполнитель, берётся комиссия и делится так же, как при COMPLETED (раздел 4.4); обе стороны могут оставить отзыв, каждый весом этой комиссии; чек не пишется (журнал, запись 55). Возврат всей суммы не берёт комиссии и не даёт отзыва.
  • Автоматическое закрытие. Когда окно прошло, Исполнитель вызывает claimTimeout(taskId). Контракт проверяет block.timestamp > commitTime + disputeWindow и закрывает сделку без подписи Заказчика — с той же комиссией и тем же чеком, что и при обычном закрытии.

4.4 Раздел оплаты и комиссия

Когда сделка переходит в COMPLETED, контракт делит сетевую часть; когда заказ отменён (CANCELLED), так же делится часть, оставшаяся Исполнителю после возврата (раздел 4.3):

КомуДоляКуда
Исполнителюостаток (95 %, пока комиссия 5 %)на кошелёк узла Исполнителя
Комиссия сетине больше 5 %, задаётся моделированиемв котёл текущего периода, который делится между супер-узлами по документу 09
Доля разработчика20 % комиссии, только когда Исполнитель работает элементом, полученным по F2Fна кошелёк developer из карточки F2F элемента (документ 26)

Котёл каждого периода делится поровну между супер-узлами, прошедшими минимум качества в этом периоде; стороны сделки его никогда не получают (документ 09). Разработчик элемента, полученного по F2F, стороной сделки не является: узел Исполнителя называет его в commitResult (раздел 4.3), а его доля отделяется от комиссии, когда сделка переходит в COMPLETED или CANCELLED (документ 26). Комиссия берётся с самой первой сделки (документ 05, раздел 5.3).

4.5 Чек и история участников

Закрытая сделка (COMPLETED) порождает событие, из которого строятся чеки и история; отменённый заказ его не порождает (журнал, запись 55):

event Receipt(
    uint256 indexed taskId,
    address indexed superNode,
    address customer,
    address provider,
    bytes32 customerElement,
    bytes32 element,
    bytes32 agent,
    address developer,
    uint96 amount
);
  • Чек. Событие Receipt записывает сетевую часть сделки, элемент и агента, которые сделали работу, элемент, который её заказал, и разработчика, названного для сделки, если он есть; к этим элементам привязаны отзывы обеих сторон (документ 17). Оно засчитывается заказчику, исполнителю и супер-узлу superNode, каждому с весом, равным этой сумме. Чеки упорядочивают выдачу по оплаченной работе за последние двенадцать месяцев (документ 17) и измеряют оплаченную работу, которая открывает место супер-узла (документ 05, раздел 5.6). Какой супер-узел сделка называет в superNode, задаёт заказчик при блокировке средств; как это поле связать с узлом, который на деле обслужил сделку, — открытый вопрос журнала (документ 000).
  • История участников. Контракт обновляет обеим сторонам число закрытых сделок и время последней сделки — отдельно как исполнителю и как заказчику. Число сделок — это история, а не репутация (документ 17).

4.6 Сетевая часть сделки

Сделку можно оплатить в двух частях. Любую часть стороны могут заплатить вне сети, любым обычным способом, а остальное заблокировать в эскроу.

  • Сеть видит только свою часть. Записывается, платит комиссию и оставляет чек только сумма, заблокированная в эскроу. Часть, оплаченная вне сети, не оставляет в Lagrix следа.
  • Считается только сетевая часть. Вес чека, вес отзывов обеих сторон, и место в выдаче — всё следует сетевой части.
  • Защита разная. Сетевую часть до приёмки защищает эскроу. Часть, оплаченную вне сети, защищает закон страны, где прошла сделка, а не Lagrix.

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

4.7 Стоимость

Плотная упаковка полей и подписи EIP-712 вместо хранимых строк делают контракт дешёвым на сетях второго уровня (Arbitrum, Base) — сотые доли цента за операцию. Это критично для микросделок между AI-агентами.