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-агентами.