Skip to content

Liquidity Request (Pending Withdrawal)

What is a Liquidity Request

When a user wants to withdraw tokens from TVM to an EVM network, it usually happens instantly. But sometimes the withdrawal is delayed — a Liquidity Request (LR) is created.

Why might a withdrawal be delayed?

  1. Limits exceeded — the amount is too large for automatic withdrawal
  2. Insufficient liquidity — the contract doesn't have enough tokens (only for Alien tokens — originally issued in EVM)

What happens next?

The user doesn't lose their funds — there are several ways to complete the withdrawal:

MethodWho executesWhen it's suitable
ApproveAdministratorLarge withdrawal passed verification
FillLiquidity providerLP wants to earn bounty
Force WithdrawAnyoneLR is approved, just need to claim
CancelUserChanged mind, wants to return to TVM

Important

Liquidity Request exists only on the EVM network side.

When a Liquidity Request is Created

Creation Conditions

A Liquidity Request is created when calling saveWithdrawNative() or saveWithdrawAlien(), when one of the conditions is not met:

Token TypeInstant withdrawal conditionIf not met
Native (from TVM)Withdrawal limits passedLR created with approveStatus = Required
Alien (from EVM)Withdrawal limits passed AND sufficient liquidityLR created

For Different Token Types

Native tokens (originally issued in TVM):

  • Only limits are checked
  • If limits exceeded → LR with status Required (approval needed)

Alien tokens (originally issued in EVM):

  • Limits AND liquidity availability are checked
  • If insufficient liquidity → LR with status NotRequired (can fill immediately)
  • If limits exceeded → LR with status Required (approval needed)

Liquidity Request Creation Flow

┌─────────────────────────────────────────────────────────────────┐
│              saveWithdrawNative() / saveWithdrawAlien()         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. Decode event data                                           │
│  2. Check chainId and blacklist                                 │
│  3. Calculate and deduct fee                                    │
│  4. Check withdrawal limits                                     │
│       ↓                                                         │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Limits passed?                                          │    │
│  │  (For Alien tokens: AND sufficient liquidity?)           │    │
│  └─────────────────┬───────────────────────┬───────────────┘    │
│                   YES                      NO                   │
│                    ↓                        ↓                   │
│           Instant withdrawal        Create Liquidity Request    │
│           IERC20.transfer()         → approveStatus = NotRequired│
│           or mint()                 → emit PendingWithdrawalCreated│
│                                            ↓                    │
│                                    Limits violated?             │
│                                         YES → approveStatus = Required│
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Example Scenario

User withdraws 100,000 USDT from TVM → EVM

1. Contract checks: transaction limit = 50,000 USDT
2. 100,000 > 50,000 — limit exceeded
3. LR created with approveStatus = Required
4. User waits for administrator approval
5. After Approve — receives their 100,000 USDT

Data Structure

What is Stored in Each LR

FieldWhat it means
tokenWhich token is being withdrawn
amountHow much (already after fee deduction)
bountyReward for LP for fill (set by user)
timestampWhen the request was created in TVM
approveStatusCurrent status (see below)
chainIdWhich network to withdraw to
callbackData for callback after withdrawal

LR Statuses

StatusWhat it meansWhat can be done
NotRequiredApproval not neededFill, Force Withdraw, Cancel
RequiredWaiting for admin approvalOnly wait for Approve/Reject
ApprovedApprovedFill, Force Withdraw, Cancel
RejectedRejectedCancel (return to TVM)

How LR is Identified

Each LR has a unique ID within a user:

  • recipient — recipient address
  • id — sequential LR number for this user

Lifecycle

Liquidity Request Path

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

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

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

Who Can Do What

ActionWho canFrom which statuses
Approve/Rejectgovernance, withdrawGuardianRequired
FillAnyone (usually LP)NotRequired, Approved
Force WithdrawAnyoneNotRequired, Approved
CancelOnly LR ownerNotRequired, Approved
Change bountyOnly LR ownerAny (while amount > 0)

Events for Tracking

EventWhen emittedParameters
PendingWithdrawalCreatedWhen LR is createdrecipient, id, token, amount, payloadId
PendingWithdrawalUpdateApproveStatusWhen approve status changesrecipient, id, approveStatus
PendingWithdrawalFillWhen filled via depositrecipient, id
PendingWithdrawalWithdrawWhen withdrawn (approve + liquidity)recipient, id, amount
PendingWithdrawalForceWhen force withdrawrecipient, id
PendingWithdrawalCancelWhen cancelledrecipient, id, amount
PendingWithdrawalUpdateBountyWhen bounty changesrecipient, id, bounty

Management: Approve and Reject

Who Makes the Decision

Only these roles can approve or reject LR:

  • governance — main administrator
  • withdrawGuardian — special role for managing withdrawals

How to Approve

solidity
setPendingWithdrawalApprove(pendingWithdrawalId, ApproveStatus.Approved)

What happens on Approve:

  1. Status changes to Approved
  2. If contract has liquidity — tokens are immediately sent to user
  3. If no liquidity — user can wait for Fill or call Force Withdraw later

How to Reject

solidity
setPendingWithdrawalApprove(pendingWithdrawalId, ApproveStatus.Rejected)

What happens on Reject:

  • Status changes to Rejected
  • Tokens remain locked
  • User can return them to TVM via Cancel

Batch Approval

Multiple LRs can be approved/rejected in one transaction — this saves gas.

Fill Mechanism (LP)

How Fill Works

A liquidity provider (LP) can "fill" someone else's LR:

  1. LP deposits their tokens
  2. User receives their funds (minus bounty)
  3. LP receives bounty + sends tokens to TVM

How It Works

LP deposits 1000 USDT via deposit()


User with LR receives 990 USDT (set bounty = 10)


LP receives in TVM: 1000 + 10 - fee = ~1008 USDT

Process:

  1. LP calls deposit with tokens and list of LRs
  2. For each LR:
    • Check that token matches
    • Check that sufficient funds
    • Recipient receives amount - bounty
    • LP accumulates bounty
  3. LP receives in TVM: depositAmount + totalBounty - fee

Bounty — Reward for LP

User sets bounty themselves — this is an incentive for LP to fill their specific LR:

  • Higher bounty makes LR more attractive to LPs
  • Bounty cannot be greater than withdrawal amount
  • Can change bounty at any time (while LR is active)

How to set bounty:

solidity
setPendingWithdrawalBounty(id, bounty)

Cancel — LR Cancellation

Recipient can cancel a Liquidity Request and return tokens to TVM.

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

Restrictions:

  • Only for Alien tokens (from EVM)
  • Only LR owner
  • Status must be NotRequired or Approved

Risks and Edge Cases

Risk/ErrorCauseProtection/Solution
Stuck LRRejected status, no cancelRecipient can cancel and return to TVM
Bounty > amountAttempt to set too large bountyCheck bounty <= amount
Fill with wrong tokenLP specified LR of different tokenCheck pendingWithdrawal.token == d.token
Fill already filledAttempt to fill LR with amount = 0Check pendingWithdrawal.amount > 0
Double approveAttempt to approve againCheck approveStatus == Required

Error Codes

ErrorCause
"Pending: amount is zero"LR already filled or cancelled
"Pending: native token"Bounty cannot be set for Native tokens (from TVM)
"Pending: bounty too large"Bounty greater than withdrawal amount
"Pending: wrong current approve status"Cannot approve — status is not Required
"Pending: wrong approve status"Wrong new status specified
"Pending: wrong amount"Incorrect amount for cancel
"Pending: already filled"LR already filled
"Pending: wrong token"Token doesn't match
"Pending: deposit insufficient"Insufficient funds for fill

ChainConnect Bridge Documentation