Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.li.fi/llms.txt

Use this file to discover all available pages before exploring further.

This quickstart walks you through a complete cross-chain USDC transfer (Base → Arbitrum) using the standard escrow flow, the recommended path for most integrations. By the end, you’ll have requested a quote, approved tokens, opened an order on-chain, and tracked it to settlement. If you need gasless off-chain order submission or are building on top of resource locks, see the Compact Orders guide instead. The entire flow uses TypeScript with ethers.js since the escrow flow requires on-chain transactions. You’ll need a provider connected to Base and a signer (wallet) with USDC.
No API key is required. All integrator endpoints are open with no rate limits. See Authentication for details.

Prerequisites

The escrow flow involves on-chain transactions (approving tokens and calling the escrow contract to lock funds). We use ethers.js to interact with the Base network, but any EVM-compatible library (viem, web3.js, etc.) works the same way.
npm install ethers
TypeScript
import { ethers } from 'ethers';

const provider = new ethers.JsonRpcProvider('https://mainnet.base.org');
const signer = new ethers.Wallet('YOUR_PRIVATE_KEY', provider);
The code below uses contract addresses from the System Architecture reference. These are the same across all supported chains.

Escrow Flow

The standard escrow integration is a 4-step process: request a quote, approve tokens, open the order on-chain, and track it to settlement.
The Intents API uses interoperable addresses (EIP-7930). See Request a Quote for encoding details.
1

Request a quote

Call POST /quote/request with the user’s input and desired output. This example sends 10 USDC from Base to Arbitrum.
TypeScript
const userAddress = await signer.getAddress();
const userAddressRaw = userAddress.slice(2); // Remove 0x prefix

const response = await fetch('https://order.li.fi/quote/request', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    user: `0x0001000002210514${userAddressRaw}`,
    intent: {
      intentType: 'oif-swap',
      inputs: [{
        user: `0x0001000002210514${userAddressRaw}`,
        asset: '0x0001000002210514833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',  // USDC on Base
        amount: '10000000',  // 10 USDC (6 decimals)
      }],
      outputs: [{
        receiver: `0x0001000002A4B114${userAddressRaw}`,
        asset: '0x0001000002A4B114af88d065e77c8cC2239327C5EDb3A432268e5831',  // USDC on Arbitrum
        amount: null,
      }],
      swapType: 'exact-input',
    },
    supportedTypes: ['oif-escrow-v0'],
  }),
});

const { quotes } = await response.json();
const bestQuote = quotes[0];
console.log('Output amount:', bestQuote.preview.outputs[0].amount);
The interoperable address prefix encodes the chain. 0x00010000022105 is Base (8453) and 0x0001000002A4B1 is Arbitrum (42161). See Interoperable Address Encoding for details.The response contains an array of quotes sorted by best price. The best quote is at index 0. Use preview.outputs[0].amount from the best quote when constructing your order in the next steps.
2

Approve tokens

Before opening the escrow, the InputSettlerEscrow contract needs permission to transfer your tokens. Approve USDC on Base.
TypeScript
const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const INPUT_SETTLER_ESCROW = '0x000025c3226C00B2Cdc200005a1600509f4e00C0';

const erc20 = new ethers.Contract(USDC_BASE, [
  'function approve(address spender, uint256 amount) returns (bool)',
  'function allowance(address owner, address spender) view returns (uint256)',
], signer);

const currentAllowance = await erc20.allowance(userAddress, INPUT_SETTLER_ESCROW);
if (currentAllowance < BigInt('10000000')) {
  const tx = await erc20.approve(INPUT_SETTLER_ESCROW, '10000000');
  await tx.wait();
  console.log('Approval confirmed');
}
You can also use Permit2 for gasless approvals. The escrow supports registration via Permit2 signatures. See Input Settlement for details.
3

Construct and open the order on-chain

Build a StandardOrder from the quote response and call open() on the InputSettlerEscrow contract. This locks your tokens and broadcasts the intent to solvers.
TypeScript
const POLYMER_ORACLE = '0x0000003E06000007A224AeE90052fA6bb46d43C9';
const OUTPUT_SETTLER = '0x0000000000eC36B683C2E6AC89e9A75989C22a2e';
const USDC_ARBITRUM = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831';

const outputAmount = bestQuote.preview.outputs[0].amount;

const order = {
  user: userAddress,
  nonce: BigInt(Date.now()),
  originChainId: BigInt(8453),
  expires: Math.floor(Date.now() / 1000) + 3600,        // 1 hour
  fillDeadline: Math.floor(Date.now() / 1000) + 1800,   // 30 minutes
  inputOracle: POLYMER_ORACLE,
  inputs: [
    [BigInt(USDC_BASE), BigInt('10000000')]              // [tokenId (address as uint256), amount]
  ],
  outputs: [{
    oracle: ethers.zeroPadValue(POLYMER_ORACLE, 32),
    settler: ethers.zeroPadValue(OUTPUT_SETTLER, 32),
    chainId: BigInt(42161),                              // Arbitrum
    token: ethers.zeroPadValue(USDC_ARBITRUM, 32),
    amount: BigInt(outputAmount),
    recipient: ethers.zeroPadValue(userAddress, 32),
    call: '0x',                                          // No callback
    context: '0x',                                       // Limit order (no auction)
  }],
};

const escrow = new ethers.Contract(INPUT_SETTLER_ESCROW, [
  'function open(bytes calldata order) external',
], signer);

const encodedOrder = ethers.AbiCoder.defaultAbiCoder().encode(
  [
    'tuple(address user, uint256 nonce, uint256 originChainId, uint32 expires, uint32 fillDeadline, address inputOracle, uint256[2][] inputs, tuple(bytes32 oracle, bytes32 settler, uint256 chainId, bytes32 token, uint256 amount, bytes32 recipient, bytes call, bytes context)[] outputs)',
  ],
  [order]
);

const tx = await escrow.open(encodedOrder);
const receipt = await tx.wait();
console.log('Order opened! Tx:', receipt.hash);

const orderId = receipt.logs[0]?.topics[1];
console.log('Order ID:', orderId);
The open() call transfers your tokens into escrow and emits an Open event. Solvers and the order server detect this event automatically. No separate submission step is needed.
Set fillDeadline well before expires. The solver must deliver before fillDeadline; expires is the final deadline after which you can claim a refund if the order wasn’t filled.
4

Track the order

Poll GET /orders/status until the order reaches a terminal state. Use the onChainOrderId from the Open event.
TypeScript
const trackOrder = async (onChainOrderId: string) => {
  let status: string;
  do {
    const res = await fetch(
      `https://order.li.fi/orders/status?onChainOrderId=${onChainOrderId}`
    );
    const data = await res.json();
    status = data.meta.orderStatus;
    console.log(`Status: ${status}`);

    if (status !== 'Settled' && status !== 'Expired') {
      await new Promise(r => setTimeout(r, 3000));
    }
  } while (status !== 'Settled' && status !== 'Expired');

  return status;
};

await trackOrder(orderId);
StatusMeaning
OpenOrder registered on-chain, tokens locked in escrow
SignedOrder signed and available for solver pickup
DeliveredSolver has delivered assets on the destination chain
SettledProof verified, locked funds released to solver. Complete.
Once Settled, the user has received USDC on Arbitrum and the solver has been paid from escrow.

What Just Happened

  1. Requested a quote. The order server returned pricing from its solver network based on your input/output pair.
  2. Approved tokens. The escrow contract was authorized to transfer your USDC.
  3. Opened the order. Tokens were locked in escrow and the intent was broadcast to solvers via the Open event.
  4. Solver delivered. A solver fulfilled the order by delivering USDC on Arbitrum.
  5. Settlement completed. The oracle verified delivery and the escrow released the locked funds to the solver.

Next Steps

API Overview

Full endpoint reference, base URLs, and authentication

Request a Quote

Exact-input, exact-output, and exclusive quote details

Compact Orders

Off-chain gasless order submission via The Compact

Track Order Status

On-chain events and order server status polling