Skip to content

Liquidity Request (Pending Withdrawal)

Что такое Liquidity Request

Когда пользователь хочет вывести токены из TVM в EVM сеть, обычно это происходит мгновенно. Но иногда вывод откладывается — создаётся Liquidity Request (LR).

Почему вывод может быть отложен?

  1. Превышены лимиты — сумма слишком большая для автоматического вывода
  2. Не хватает ликвидности — на контракте недостаточно токенов (только для Alien токенов — изначально выпущенных в EVM)

Что происходит дальше?

Пользователь не остаётся без денег — есть несколько способов завершить вывод:

СпособКто выполняетКогда подходит
ApproveАдминистраторКрупный вывод прошёл проверку
FillПровайдер ликвидностиLP хочет заработать bounty
Force WithdrawЛюбойLR одобрен, нужно просто забрать
CancelПользовательПередумал, хочет вернуть в TVM

Важно

Liquidity Request существует только на стороне EVM сети.

Когда создаётся Liquidity Request

Условия создания

Liquidity Request создаётся при вызове saveWithdrawNative() или saveWithdrawAlien(), когда одно из условий не выполнено:

Тип токенаУсловие мгновенного выводаЕсли не выполнено
Native (из TVM)Withdrawal limits пройденыСоздаётся LR с approveStatus = Required
Alien (из EVM)Withdrawal limits пройдены И достаточно ликвидностиСоздаётся LR

Для разных типов токенов

Native токены (изначально выпущены в TVM):

  • Проверяются только лимиты
  • Если лимиты превышены → LR со статусом Required (нужно одобрение)

Alien токены (изначально выпущены в EVM):

  • Проверяются лимиты И наличие ликвидности
  • Если не хватает ликвидности → LR со статусом NotRequired (можно сразу fill)
  • Если превышены лимиты → LR со статусом Required (нужно одобрение)

Схема создания Liquidity Request

┌─────────────────────────────────────────────────────────────────┐
│              saveWithdrawNative() / saveWithdrawAlien()         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. Декодировать event data                                     │
│  2. Проверить chainId и blacklist                               │
│  3. Рассчитать и вычесть fee                                    │
│  4. Проверить withdrawal limits                                 │
│       ↓                                                         │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Limits пройдены?                                        │    │
│  │  (Для Alien токенов: И достаточно ликвидности?)          │    │
│  └─────────────────┬───────────────────────┬───────────────┘    │
│                   YES                      NO                   │
│                    ↓                        ↓                   │
│           Мгновенный вывод         Создать Liquidity Request    │
│           IERC20.transfer()         → approveStatus = NotRequired│
│           или mint()                → emit PendingWithdrawalCreated│
│                                            ↓                    │
│                                    Limits нарушены?             │
│                                         YES → approveStatus = Required│
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Пример сценария

Пользователь выводит 100,000 USDT из TVM → EVM

1. Контракт проверяет: лимит на транзакцию = 50,000 USDT
2. 100,000 > 50,000 — лимит превышен
3. Создаётся LR с approveStatus = Required
4. Пользователь ждёт одобрения администратора
5. После Approve — получает свои 100,000 USDT

Структура данных

Что хранится в каждом LR

ПолеЧто означает
tokenКакой токен выводится
amountСколько (уже за вычетом комиссии)
bountyНаграда для LP за fill (устанавливает пользователь)
timestampКогда был создан запрос в TVM
approveStatusТекущий статус (см. ниже)
chainIdВ какую сеть выводить
callbackДанные для вызова после вывода

Статусы LR

СтатусЧто значитЧто можно делать
NotRequiredОдобрение не нужноFill, Force Withdraw, Cancel
RequiredЖдёт одобрения админаТолько ждать Approve/Reject
ApprovedОдобреноFill, Force Withdraw, Cancel
RejectedОтклоненоCancel (вернуть в TVM)

Как идентифицируется LR

Каждый LR имеет уникальный ID в рамках пользователя:

  • recipient — адрес получателя
  • id — порядковый номер LR у этого пользователя

Жизненный цикл

Путь Liquidity Request

                                    ┌──────────────┐
                                    │   Created    │
                                    │ (NotRequired │
                                    │  or Required)│
                                    └──────┬───────┘

              ┌────────────────────────────┼────────────────────────────┐
              │                            │                            │
              ▼                            ▼                            ▼
     ┌────────────────┐           ┌────────────────┐           ┌────────────────┐
     │    Approved    │           │    Rejected    │           │   Cancelled    │
     │  (by guardian) │           │  (by guardian) │           │ (by recipient) │
     └───────┬────────┘           └────────────────┘           └────────────────┘

     ┌───────┴───────┐
     │               │
     ▼               ▼
┌─────────┐    ┌──────────┐
│  Filled │    │ Withdrawn│
│ (by LP) │    │ (direct) │
└─────────┘    └──────────┘

Кто что может делать

ДействиеКто можетИз каких статусов
Approve/Rejectgovernance, withdrawGuardianRequired
FillЛюбой (обычно LP)NotRequired, Approved
Force WithdrawЛюбойNotRequired, Approved
CancelТолько владелец LRNotRequired, Approved
Изменить bountyТолько владелец LRЛюбой (пока amount > 0)

События для отслеживания

EventКогда эмититсяПараметры
PendingWithdrawalCreatedПри создании LRrecipient, id, token, amount, payloadId
PendingWithdrawalUpdateApproveStatusПри изменении approve statusrecipient, id, approveStatus
PendingWithdrawalFillПри fill через depositrecipient, id
PendingWithdrawalWithdrawПри выводе (approve + ликвидность)recipient, id, amount
PendingWithdrawalForceПри force withdrawrecipient, id
PendingWithdrawalCancelПри отменеrecipient, id, amount
PendingWithdrawalUpdateBountyПри изменении bountyrecipient, id, bounty

Управление: Approve и Reject

Кто принимает решение

Одобрить или отклонить LR могут только:

  • governance — главный администратор
  • withdrawGuardian — специальная роль для управления выводами

Как одобрить

solidity
setPendingWithdrawalApprove(pendingWithdrawalId, ApproveStatus.Approved)

Что происходит при Approve:

  1. Статус меняется на Approved
  2. Если на контракте есть ликвидность — токены сразу отправляются пользователю
  3. Если ликвидности нет — пользователь может ждать Fill или вызвать Force Withdraw позже

Как отклонить

solidity
setPendingWithdrawalApprove(pendingWithdrawalId, ApproveStatus.Rejected)

Что происходит при Reject:

  • Статус меняется на Rejected
  • Токены остаются заблокированными
  • Пользователь может вернуть их в TVM через Cancel

Массовое одобрение

Можно одобрить/отклонить несколько LR за одну транзакцию — это экономит газ.

Механизм Fill (LP)

Как работает Fill

Провайдер ликвидности (LP) может "заполнить" чужой LR:

  1. LP вносит свои токены
  2. Пользователь получает свои деньги (минус bounty)
  3. LP получает bounty + отправляет токены в TVM

Как это работает

LP вносит 1000 USDT через deposit()


Пользователь с LR получает 990 USDT (установил bounty = 10)


LP получает в TVM: 1000 + 10 - fee = ~1008 USDT

Процесс:

  1. LP вызывает deposit с токенами и списком LR
  2. Для каждого LR:
    • Проверяется что токен совпадает
    • Проверяется что достаточно средств
    • Получатель получает amount - bounty
    • LP накапливает bounty
  3. LP получает в TVM: depositAmount + totalBounty - fee

Bounty — вознаграждение для LP

Пользователь сам устанавливает bounty — это стимул для LP заполнить именно его LR:

  • Чем выше bounty, тем привлекательнее LR для LP
  • Bounty не может быть больше суммы вывода
  • Можно изменить bounty в любой момент (пока LR активен)

Как установить bounty:

solidity
setPendingWithdrawalBounty(id, bounty)

Cancel — отмена LR

Получатель может отменить Liquidity Request и вернуть токены в TVM.

solidity
cancelPendingWithdrawal(id, amount, tvmRecipient, ...)

Ограничения:

  • Только для Alien токенов (из EVM)
  • Только владелец LR
  • Статус должен быть NotRequired или Approved

Риски и Edge Cases

Риск/ОшибкаПричинаЗащита/Решение
Застрявший LRRejected статус, нет cancelRecipient может cancel и вернуть в TVM
Bounty > amountПопытка установить слишком большой bountyПроверка bounty <= amount
Fill с неправильным токеномLP указал LR другого токенаПроверка pendingWithdrawal.token == d.token
Fill уже заполненногоПопытка fill LR с amount = 0Проверка pendingWithdrawal.amount > 0
Двойной approveПопытка повторно одобритьПроверка approveStatus == Required

Коды ошибок

ОшибкаПричина
"Pending: amount is zero"LR уже заполнен или отменён
"Pending: native token"Bounty нельзя установить для Native токенов (из TVM)
"Pending: bounty too large"Bounty больше суммы вывода
"Pending: wrong current approve status"Нельзя одобрить — статус не Required
"Pending: wrong approve status"Указан неправильный новый статус
"Pending: wrong amount"Неверная сумма для cancel
"Pending: already filled"LR уже заполнен
"Pending: wrong token"Токен не совпадает
"Pending: deposit insufficient"Недостаточно средств для fill

ChainConnect Bridge Documentation