Skip to main content

Overview

The Escrow contract is the liquidity hub of the zkp2p protocol. It holds depositor funds, manages deposit configurations, and coordinates with the Orchestrator to lock/unlock liquidity as intents progress through their lifecycle.
Each deposit can support multiple payment methods (Venmo, PayPal, Wise, etc.), each with its own verification requirements and supported currencies.

Key Responsibilities

  • Deposit Management: Create and manage deposits with flexible intent ranges
  • Liquidity Locking: Lock funds when intents are signaled, release when fulfilled or cancelled
  • Payment Method Configuration: Support multiple payment services with independent verification data
  • Intent Expiry: Track and reclaim liquidity from expired intents
  • Dust Collection: Automatically close small deposits and sweep remainder to protocol

Core Data Structures

Deposit Struct

Every deposit created on the Escrow is represented by this struct (from contracts/interfaces/IEscrow.sol:24):
Delegate: An optional address that can manage deposit parameters (conversion rates, intent ranges, payment methods) on behalf of the depositor.

Intent Struct

Each intent locked against a deposit is tracked with this struct (from contracts/interfaces/IEscrow.sol:12):

Payment Method Data

Deposits can support multiple payment methods. Each has its own configuration (from contracts/interfaces/IEscrow.sol:44):
The payeeDetails field stores a hash of the depositor’s payment account ID (e.g., Venmo username hash) to preserve privacy on-chain.

Deposit Lifecycle

Creating a Deposit

Depositors create liquidity pools by calling createDeposit() with comprehensive configuration (from contracts/Escrow.sol:139):
CreateDepositParams includes:
  • token: ERC20 token address
  • amount: Initial deposit amount
  • intentAmountRange: Min/max amounts per intent
  • paymentMethods: Array of supported payment method hashes
  • paymentMethodData: Verification data for each payment method
  • currencies: Supported currencies and min conversion rates for each method
  • delegate: Optional management delegate
  • intentGuardian: Optional address that can extend intent expiry
  • retainOnEmpty: Whether to keep deposit config when balance reaches zero

Example: Multi-Currency Deposit

A depositor might configure:
  • Venmo: USD only, min rate 1.0
  • Wise: USD, EUR, GBP with different conversion rates
  • PayPal: USD, EUR
Each payment method has independent payeeDetails (the depositor’s account ID for that service).

Liquidity Management

Adding Funds

Anyone can add funds to an existing deposit (from contracts/Escrow.sol:187):
Funds are added to remainingDeposits, making them immediately available for new intents.

Removing Funds

Only the depositor can remove funds (from contracts/Escrow.sol:213):
If remainingDeposits is insufficient, the function automatically prunes expired intents to reclaim liquidity before attempting removal.

Intent Amount Range

The intentAmountRange (min/max) controls the size of intents accepted:
Depositors can update this range via setIntentRange() (from contracts/Escrow.sol:352).

Intent Lifecycle on Escrow

Locking Funds

When an intent is signaled on the Orchestrator, it calls lockFunds() (from contracts/Escrow.sol:557):
The function:
  1. Validates deposit state (acceptingIntents == true)
  2. Checks amount is within intentAmountRange
  3. Prunes expired intents if needed to free liquidity
  4. Moves amount from remainingDeposits to outstandingIntentAmount
  5. Stores the intent with expiry time (block.timestamp + intentExpirationPeriod)
  6. Emits FundsLocked event

Unlocking Funds (Cancel)

When an intent is cancelled, the Orchestrator calls unlockFunds() (from contracts/Escrow.sol:620):
This returns the locked amount to remainingDeposits.

Unlocking & Transferring Funds (Fulfill)

When an intent is fulfilled, the Orchestrator calls unlockAndTransferFunds() (from contracts/Escrow.sol:651):
Partial Fulfillment: The _transferAmount can be less than the original intent amount. The difference is returned to remainingDeposits.

Intent Expiration

Intents have a configurable expiration period (default 5 days max per MAX_TOTAL_INTENT_EXPIRATION_PERIOD at line 42).

Automatic Expiry Pruning

Expired intents are automatically pruned when:
  • lockFunds() needs more liquidity
  • removeFunds() is called
  • withdrawDeposit() is called
  • Anyone calls pruneExpiredIntents() (from contracts/Escrow.sol:533)

Extending Expiry

The intentGuardian (if set) can extend intent expiry via extendIntentExpiry() (from contracts/Escrow.sol:702):
This is useful when off-chain payment is delayed but legitimate.

Delegate System

Depositors can assign a delegate to manage deposit parameters without transferring ownership. Delegate can:
  • Update conversion rates (setCurrencyMinRate)
  • Update intent ranges (setIntentRange)
  • Add/remove payment methods and currencies
  • Toggle accepting intents state
  • Set retention behavior
Delegate cannot:
  • Withdraw funds (only depositor)
  • Change the delegate itself (only depositor)

Dust Collection

When a deposit’s remainingDeposits falls below dustThreshold (max 1 USDC, see line 41) and has no outstanding intents:
  • Deposit is automatically closed
  • Remaining balance is swept to dustRecipient
  • All payment method and currency data is deleted
Set retainOnEmpty = true to prevent auto-closure and keep the deposit configuration for future reuse.

Payment Method & Currency Management

Adding Payment Methods

Depositors can add new payment methods after creation (from contracts/Escrow.sol:381):

Toggling Payment Methods

Payment methods can be deactivated without deletion (from contracts/Escrow.sol:404):

Managing Currencies

Each payment method can support multiple currencies with independent min conversion rates:
Functions:
  • addCurrencies(): Add new currencies to a payment method (line 430)
  • setCurrencyMinRate(): Update min conversion rate (line 324)
  • deactivateCurrency(): Set conversion rate to 0 (line 458)

State Variables

Access Control

Owner (Governance)

  • Set orchestrator address
  • Update payment verifier registry
  • Configure dust parameters
  • Set max intents per deposit
  • Set intent expiration period
  • Pause/unpause deposit operations

Depositor

  • Create deposits
  • Withdraw deposits
  • Remove funds
  • Set delegate
  • Remove delegate

Depositor OR Delegate

  • Update conversion rates
  • Update intent ranges
  • Add/remove payment methods
  • Add/remove currencies
  • Toggle accepting intents state
  • Set retention behavior

Orchestrator Only

  • Lock funds
  • Unlock funds
  • Unlock and transfer funds

Intent Guardian Only

  • Extend intent expiry

Key Events

Security Features

Reentrancy Protection

All state-changing functions use nonReentrant modifier from OpenZeppelin

Pausability

Owner can pause deposit creation/modification while leaving withdrawals active

Intent Limits

maxIntentsPerDeposit prevents gas DOS attacks on withdrawal

Expiry Enforcement

Intents automatically expire after intentExpirationPeriod