Deposits, Transfers and Withdrawals
| Method | Chains | Assets credited | Best for |
|---|---|---|---|
| Universal Deposit Address (UDA) – recommended | Ethereum, Arbitrum, Base, Optimism, Polygon, BNB, HyperEVM, Monad, Robinhood, Solana, Tron, Bitcoin | USDC or ETH, perps or spot | Wallets & apps: one address per user, any chain |
| Direct Ethereum deposit | Ethereum mainnet | Any listed asset | Users already on Ethereum; contracts |
| CCTP intent address (legacy) | Arbitrum, Base, Avalanche, Arc | USDC | Existing 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-keyin 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
| Chain | Supported Assets | Chain ID |
|---|---|---|
| Ethereum | USDC, USDT, cbBTC, DAI, ETH, MATIC, POL, USDe, wETH | 1 |
| Robinhood | USDG | 4663 |
| Arbitrum | USDC, USDT, DAI, ETH, USDe, wETH | 42161 |
| Solana | USDC, USDT, SOL, TRUMP, USDe | N/A |
| BNB Smart Chain | USDC, USDT, BNB, BUSD, BTCB, DAI, ETH, USDe, wBNB | 56 |
| Base | USDC, USDT, cbBTC, DAI, ETH, USDe, wETH | 8453 |
| Polygon | USDC, USDT, DAI, MATIC, POL, USDC.e, wETH | 137 |
| HyperEVM | USDC, USDT, HYPE, uBTC, USDe | 999 |
| Optimism | USDC, USDT, DAI, ETH, wETH | 10 |
| Monad | USDC, MON | 143 |
| Tron | USDT | N/A |
| Bitcoin | BTC | N/A |
Deposit Minimums
| Source | New account | Existing 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)
POSTrequest tohttps://bridge.lighter.xyz/v1/udaContent-Type:application/json
| Field | Type | Notes |
|---|---|---|
walletAddress | string | The client's EVM wallet, where funds will be credited |
market | string | "perps" or "spot" |
asset | string | "USDC" or "ETH". This is what the deposit asset gets converted to, when credited on Lighter |
accountIndex | number | Optional. 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)
POSTrequesthttps://bridge.lighter.xyz/v1/uda/quoteContent-Type:application/json
| Field | Type | Notes |
|---|---|---|
fromAmountBaseUnit | string | Amount the user will send, in the source token's base units |
fromChainId | string | Source Chain ID |
fromTokenAddress | string | Source Token Address |
recipientAddress | string | The user's wallet (equal to walletAddress) |
toChainId | string | Destination Chain ID (resolved.toChainId) |
toTokenAddress | string | Destination 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\"" }| HTTP | errorCode | Meaning |
|---|---|---|
400 | InvalidParameterError | Malformed body, unsupported market/asset, bad address |
403 | InsufficientPermissionError | Missing/invalid API key, or wallet not generated by you |
502 | DependencyFailureError | Upstream 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.
| Operation | Destination | Signatures needed | Asset | Minimum | Speed |
|---|---|---|---|---|---|
| Secure withdrawal | The account owner's own L1 address only | API key | Any | 1 USDC equiv. | Settles via Ethereum |
| Fast withdrawal | Any Ethereum address | API key + L1 | USDC only | 4 USDC | Fast (liquidity pool) |
| Transfer | Another Lighter account | API key (+ L1 if another owner) | Any | 1 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.
- 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. - 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,
));- If the destination belongs to a different owner (a different master account), have the owner's wallet sign
tx.messageToSignand settxInfo.L1Sig(same pattern as ChangePubKey). Transfers between the same owner's accounts need no wallet signature, even thoughmessageToSignis still returned. See When an L1 signature is required. - 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:
- fastwithdraw/info (
GET /api/v1/fastwithdraw/info?account_index=…, authenticated) →to_account_index,withdraw_limit. - transferFeeInfo for that
to_account_index. - Build
memo= destination address (20 bytes) + 12 zero bytes. SignTransfer(...)to the pool account, collect the L1 signature.- fastwithdraw (
POST /api/v1/fastwithdraw) with form fieldstx_infoandto_address.
Reference implementation: withdraw_fast.py.
History
deposit_history, withdraw_history and transfer_history return past movements. See Historical Data.
Updated 12 days ago
