Deposits, Transfers and Withdrawals

MethodChainsAssets creditedBest for
Universal Deposit Address (UDA) – recommendedEthereum, Arbitrum, Base, Optimism, Polygon, BNB, HyperEVM, Monad, Robinhood, Solana, Tron, BitcoinUSDC or ETH, perps or spotWallets & apps: one address per user, any chain
Direct Ethereum depositEthereum mainnetAny listed assetUsers already on Ethereum; contracts
CCTP intent address (legacy)Arbitrum, Base, Avalanche, ArcUSDCExisting integrations

The first credited deposit creates the account. See Create accounts programmatically.

Deposit via Ethereum Mainnet

To deposit via Ethereum mainnet, you can use the deposit method (0x8a857083) directly. You'll need to specify the following parameters:

  • deposit: the amount of Ether you're depositing. That is optional and only needed when interacting with the contract directly, Lighter's Ethereum gateway page does not require this. You can leave this at zero if you're depositing other kinds of assets.
  • _to: the L1 address you want to credit the deposit to
  • _assetIndex: the asset you want to deposit. You can grab the correct asset id from the assetDetails endpoint
  • _routeType: whether you want to deposit the asset to your perps (0), or spot account (1). Only USDC can be deposited to your perps account
  • _amount: the amount you're depositing. it should be in line with the ERC20's decimals. E.g. 6 decimals for USDC, 18 decimals for Ether etc. 1 USDC, or equivalent, minimum.

If you're depositing assets different from ETH (e.g. USDC), make sure to approve spending for Lighter's smart contract (0x3B4D794a66304F130a4Db8F2551B0070dfCf5ca7) for that ERC20. There is a minimum of 1 USDC, and equivalent for other ERC20s, per deposit.

Example with viem, where wallet is a WalletClient connected to the user's wallet on Ethereum mainnet:

import { parseUnits, erc20Abi } from "viem";

const LIGHTER = "0x3B4D794a66304F130a4Db8F2551B0070dfCf5ca7";
const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const amount = parseUnits("100", 6);

await wallet.writeContract({ address: USDC, abi: erc20Abi, functionName: "approve", args: [LIGHTER, amount] });
await wallet.writeContract({
  address: LIGHTER,
  abi: lighterAbi, // fetch from Etherscan (proxy implementation)
  functionName: "deposit",
  args: [userAddress, usdcAssetIndex, 0 /* perps */, amount],
});

Deposit via Fun.xyz

Generate a Universal Deposit Address (UDA) address for each client, for any supported chain.

  • Base URL: https://bridge.lighter.xyz
  • Authentication: every request needs to include x-api-key in its header. Builders can contact us through Discord tickets via #support to request one.

For new users, depositing will automatically create an account on Lighter and assign the wallet an account_index.

Supported Assets

ChainSupported AssetsChain ID
EthereumUSDC, USDT, cbBTC, DAI, ETH, MATIC, POL, USDe, wETH1
RobinhoodUSDG4663
ArbitrumUSDC, USDT, DAI, ETH, USDe, wETH42161
SolanaUSDC, USDT, SOL, TRUMP, USDeN/A
BNB Smart ChainUSDC, USDT, BNB, BUSD, BTCB, DAI, ETH, USDe, wBNB56
BaseUSDC, USDT, cbBTC, DAI, ETH, USDe, wETH8453
PolygonUSDC, USDT, DAI, MATIC, POL, USDC.e, wETH137
HyperEVMUSDC, USDT, HYPE, uBTC, USDe999
OptimismUSDC, USDT, DAI, ETH, wETH10
MonadUSDC, MON143
TronUSDTN/A
BitcoinBTCN/A

Deposit Minimums

SourceNew accountExisting account
Ethereum mainnet$5$5
EVM-compatible chains$5$3
Solana$5$3
Tron$5$5
Bitcoin$10$10

Note that depositing slightly larger amounts than the minimums advertised above is suggested, as bridge and swap operations may incur slippage and result in a lower amount.

Create a Universal Deposit Address (UDA)

  • POST request to https://bridge.lighter.xyz/v1/uda
  • Content-Type: application/json
FieldTypeNotes
walletAddressstringThe client's EVM wallet, where funds will be credited
marketstring"perps" or "spot"
assetstring"USDC" or "ETH". This is what the deposit asset gets converted to, when credited on Lighter
accountIndexnumberOptional. It can be used to target a sub-account; if you omit it, the deposit is credited to the master account index.

JSON Body example

{
  "walletAddress": "0x1111111111111111111111111111111111111111",
  "market": "perps",
  "asset": "USDC"
}

Example Response (200)

{
  "depositAddr": "0x2222222222222222222222222222222222222222",
  "solanaAddr": "So1anaDepositAddressExample11111111111111111",
  "tronAddr": "TTronDepositAddressExample1234567890",
  "btcAddrSegwit": "bc1qexampledepositaddress0000000000000000",
  "blocked": false,
  "resolved": {
    "toChainId": "3586256", // lighter's chain id
    "toTokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    "actionType": "LIGHTER_PERPS",
    "recipientAddr": "1234", // client's account index, or l1 if the account is new
    "userId": "0x1111111111111111111111111111111111111111"
  }
}

Deposit addresses are used to route and credit the amount to the client's address (returned as userId and recipientAddr). Show the address for the chain the user picks, and check blocked before displaying it. Call the bridge API server-side only: never expose the x-api-key in a client.

Generate a Quote (optional)

  • POST request https://bridge.lighter.xyz/v1/uda/quote
  • Content-Type: application/json
FieldTypeNotes
fromAmountBaseUnitstringAmount the user will send, in the source token's base units
fromChainIdstringSource Chain ID
fromTokenAddressstringSource Token Address
recipientAddressstringThe user's wallet (equal to walletAddress)
toChainIdstringDestination Chain ID (resolved.toChainId)
toTokenAddressstringDestination Token (resolved.toTokenAddress)

JSON Body Example

{
  "fromAmountBaseUnit": "5000000",
  "fromChainId": "42161",
  "fromTokenAddress": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
  "recipientAddress": "0x1111111111111111111111111111111111111111",
  "toChainId": "1",
  "toTokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}

Example Response (200)

{
  "estCheckoutTimeMs": 25000,
  "estFeeBreakdown": {
    "appFeeLabel": "Fun fee",
    "appFeePercent": 0,
    "appFeeUsd": 0,
    "fillCostPercent": 0,
    "fillCostUsd": 0,
    "gasUsd": 0.000465,
    "maxSlippage": 0,
    "minReceived": 4.99,
    "swapImpact": 0,
    "swapImpactUsd": 0,
    "totalImpact": 0,
    "totalImpactUsd": 0
  },
  "estInputUsd": 5.0,
  "estOutputUsd": 4.99,
  "estToTokenBaseUnit": "4990000",
  "quoteId": "0xeb9f1ac3..."
}

Use this to show the user an estimated amount received, fees, and arrival time before they send funds. Quotes are estimates only. The UDA generated above works without one.

Track Deposit Status

GET request to https://bridge.lighter.xyz/v1/uda/status/{walletAddress}

walletAddress is the client's wallet, the same one used to generate a UDA above, and returned as userId.

Example Response (200)

{
  "transactions": [
    {
      "fromChainId": "42161",
      "fromTokenAddress": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
      "fromAmountBaseUnit": "4999999",
      "toChainId": "3586256",
      "toTokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "txHash": "0xabc123...",
      "createdTimeMs": 1787068237169,
      "status": "COMPLETED",
      "depositAddr": "0x2222222222222222222222222222222222222222"
    }
  ]
}

Deposits appear with status: "PROCESSING" shortly after the user’s transaction confirms on the source chain, and moves to "COMPLETED" once the funds are credited on Lighter, typically within a few minutes.

Errors

Example error

{ "errorCode": "InvalidParameterError", "errorMsg": "market must be \"spot\" or \"perps\"" }
HTTPerrorCodeMeaning
400InvalidParameterErrorMalformed body, unsupported market/asset, bad address
403InsufficientPermissionErrorMissing/invalid API key, or wallet not generated by you
502DependencyFailureErrorUpstream temporarily unavailable, retry with backoff

Example (TypeScript)

const BASE = "https://bridge.lighter.xyz";
const KEY = process.env.LIGHTER_BUILDER_KEY!; // server-side only

async function createDepositAddress(walletAddress: string) {
  const res = await fetch(`${BASE}/v1/uda`, {
    method: "POST",
    headers: { "x-api-key": KEY, "Content-Type": "application/json" },
    body: JSON.stringify({ walletAddress, market: "perps", asset: "USDC" }),
  });
  if (!res.ok) throw new Error(`UDA mint failed: ${res.status} ${await res.text()}`);
  return res.json();
}

async function getDepositQuote(body: {
  fromAmountBaseUnit: string;
  fromChainId: string;
  fromTokenAddress: string;
  recipientAddress: string;
  toChainId: string;
  toTokenAddress: string;
}) {
  const res = await fetch(`${BASE}/v1/uda/quote`, {
    method: "POST",
    headers: { "x-api-key": KEY, "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(`quote failed: ${res.status} ${await res.text()}`);
  return res.json();
}

async function getDepositStatus(walletAddress: string) {
  const res = await fetch(`${BASE}/v1/uda/status/${walletAddress}`, {
    headers: { "x-api-key": KEY },
  });
  if (!res.ok) throw new Error(`status failed: ${res.status} ${await res.text()}`);
  return res.json();
}

[Legacy] Deposit USDC via other EVM-compatible chains

The following chains are supported via Circle's CCTP: Arbitrum, Base, Avalanche C-Chain, Arc. Minimum deposit is 5 USDC. You can either generate an intent address via the front-end, or via API:

curl -X POST https://mainnet.zklighter.elliot.ai/api/v1/createIntentAddress \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "chain_id=42161&from_addr=0xyourL1address&amount=0&is_external_deposit=true"

Available networks can be fetched, together with their Chain ID, via the deposit/networks endpoint.

Withdrawals: Secure and Fast

You can process both secure, fast withdrawals, and transfers via both SDKs, you can find linked examples with the Python SDK. The TypeScript examples below use the WASM signer and sendTx helper from Signing Transactions.

OperationDestinationSignatures neededAssetMinimumSpeed
Secure withdrawalThe account owner's own L1 address onlyAPI keyAny1 USDC equiv.Settles via Ethereum
Fast withdrawalAny Ethereum addressAPI key + L1USDC only4 USDCFast (liquidity pool)
TransferAnother Lighter accountAPI key (+ L1 if another owner)Any1 USDC equiv.Instant

Withdrawals and transfers have their own per-transaction-type limits; see Rate Limits.

Secure withdrawal

Funds can only go to the L1 address that owns the account, so the API key alone is enough.

const tx = check(lighter.SignWithdraw(
  assetIndex,   // from /api/v1/assetDetails
  0,            // routeType: 0 = perps, 1 = spot
  amount,       // integer in the asset's Lighter decimals (USDC: 6)
  0, await nextNonce(), API_KEY_INDEX, ACCOUNT_INDEX,
));
await sendTx(tx);

The withdrawal is finalized on Ethereum. Lighter usually claims it for the user; if not, the user can call withdrawPendingBalance on the contract (see Priority Transactions). If you prefer, you can also process secure withdrawals from the contract directly, using the withdraw (0xd20191bd) method.

Transfer between Lighter accounts

Use transfers to move funds between a user's master and sub-accounts, or to another user.

  1. Get the fee: transferFeeInfo (GET /api/v1/transferFeeInfo?account_index=…&to_account_index=…, authenticated). The fee is charged on top of the amount and must match exactly.
  2. Sign:
const tx = check(lighter.SignTransfer(
  toAccountIndex,
  assetIndex,
  0,               // fromRouteType: 0 perps, 1 spot
  0,               // toRouteType
  amount,          // integer, asset decimals
  fee.transfer_fee_usdc,
  "0x" + "00".repeat(32), // memo: 32 bytes
  0, await nextNonce(), API_KEY_INDEX, ACCOUNT_INDEX,
));
  1. If the destination belongs to a different owner (a different master account), have the owner's wallet sign tx.messageToSign and set txInfo.L1Sig (same pattern as ChangePubKey). Transfers between the same owner's accounts need no wallet signature, even though messageToSign is still returned. See When an L1 signature is required.
  2. Send with sendTx.

Fast withdrawal (USDC)

A fast withdrawal is a transfer to Lighter's fast-withdraw pool with the destination address encoded in the memo:

  1. fastwithdraw/info (GET /api/v1/fastwithdraw/info?account_index=…, authenticated) → to_account_index, withdraw_limit.
  2. transferFeeInfo for that to_account_index.
  3. Build memo = destination address (20 bytes) + 12 zero bytes.
  4. SignTransfer(...) to the pool account, collect the L1 signature.
  5. fastwithdraw (POST /api/v1/fastwithdraw) with form fields tx_info and to_address.

Reference implementation: withdraw_fast.py.

History

deposit_history, withdraw_history and transfer_history return past movements. See Historical Data.



Did this page help you?