LayerZero Value Transfer API
Unified REST API for moving assets across 170 blockchains. One endpoint consolidates multiple bridge/swap protocols — you get a quote, sign the returned transactions, and poll for completion. Gas is paid on the source chain only.
Base URL: https://transfer.layerzero-api.com/v1
Auth (Starchild): the key is injected by sc-proxy — you do NOT set
LAYERZERO_API_KEY. transfer.layerzero-api.com is a proxied domain, so route
transfer calls through core.http_client and the platform adds the real key. The
x-api-key header value can be anything (or omitted); the proxy overrides it.
from core.http_client import proxied_post, proxied_get
r = proxied_post(
"https://transfer.layerzero-api.com/v1/quotes",
json=body,
headers={"x-api-key": "proxy", "SC-CALLER-ID": f"chat:{thread_id}"},
timeout=40,
)
Discovery endpoints (/chains, /tokens, /metadata) need no auth and work with
plain curl too. A direct curl to a transfer endpoint bypasses the proxy and
returns {"error": "Unauthorized"} — use proxied_* for anything that needs the key.
Route types
| Route | Protocol |
|---|---|
OFT |
Standard omnichain token transfer |
STARGATE_V2_TAXI |
Instant Stargate transfer |
STARGATE_V2_BUS |
Batched (cheaper, slower) Stargate transfer |
CCTP |
Circle native USDC |
AORI |
Intent-based swap (uses EIP-712 signatures) |
The API picks the optimal route; you don't normally need to choose.
Workflow
1. GET /chains, /tokens → discover routes (optional)
2. POST /quotes → get quote.id, feeUsd, userSteps
3. Execute userSteps → sign & send each tx (or EIP-712 signature)
4. GET /status/{quoteId} → poll every ~4s until terminal
1. Discovery
curl -s https://transfer.layerzero-api.com/v1/chains
# → { "chains": [{ "name": "Base", "chainKey": "base", "chainType": "EVM",
# "chainId": 8453, "nativeCurrency": { "symbol": "ETH", "decimals": 18,
# "address": "0xEeee...EEeE" } }, ...] }
# Note: results are under the "chains" key (not a bare array). chainType is
# uppercase: EVM, SOLANA, APTOS, SUI, TON, TRON, STARKNET, IOTAMOVE.
# Tokens receivable from a given source token:
curl -s "https://transfer.layerzero-api.com/v1/tokens?transferrableFromChainKey=base&transferrableFromTokenAddress=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
# → { "tokens": [{ chainKey, address, decimals, symbol, name, price.usd }, ...],
# "pagination": { "nextToken"? } } (results under the "tokens" key)
- Native token address is
0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE. - List endpoints paginate: pass
pagination[nextToken]and keep fetching until the response has nonextToken. GET /metadatareturns contract deployment addresses per chain.
2. Get a quote
from core.http_client import proxied_post
r = proxied_post(
"https://transfer.layerzero-api.com/v1/quotes",
json={
"srcChainKey": "base",
"dstChainKey": "optimism",
"srcTokenAddress": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
"dstTokenAddress": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
"srcWalletAddress": "0xYOUR_WALLET",
"dstWalletAddress": "0xYOUR_WALLET",
"amount": "1000000000000000",
"options": {
"amountType": "EXACT_SRC_AMOUNT",
"feeTolerance": {"type": "PERCENT", "amount": 2},
},
},
headers={"x-api-key": "proxy", "SC-CALLER-ID": f"chat:{thread_id}"},
timeout=40,
)
quote = r.json()["quotes"][0] # id, feeUsd, dstAmount, userSteps
Response shape: {"error": null, "quotes": [{ "id", "routeSteps", "fees", "feeUsd", "feePercent", "srcAmount", "dstAmount", "dstAmountMin", "userSteps" }]}.
quotes is an array (the API may return several routes); take quotes[0] unless
you want to compare feeUsd.
amountis a string in the token's local decimals (wei for ETH).- Response:
quote.id,quote.feeUsd,quote.userSteps(ordered transactions to execute). Show the userfeeUsdand expected output before executing.
3. Execute userSteps
EVM: loop through userSteps in order; each contains ready-to-send transaction
calldata. Sign with the agent wallet, send, and wait for confirmation before the next
step. ERC-20 transfers typically yield two steps: approve then the bridge tx.
Solana: transaction blockhashes expire in ~60s, so first regenerate fresh data:
r = proxied_post(
"https://transfer.layerzero-api.com/v1/build-user-steps",
json={"quoteId": "QUOTE_ID"},
headers={"x-api-key": "proxy", "SC-CALLER-ID": f"chat:{thread_id}"},
)
Signature steps (intent routes like AORI): sign the EIP-712 payload in
userStep.signature.typedData, then submit:
r = proxied_post(
"https://transfer.layerzero-api.com/v1/submit-signature",
json={"quoteId": "QUOTE_ID", "signatures": ["0xSIGNATURE"]},
headers={"x-api-key": "proxy", "SC-CALLER-ID": f"chat:{thread_id}"},
)
4. Poll status
from core.http_client import proxied_get
r = proxied_get(
"https://transfer.layerzero-api.com/v1/status/QUOTE_ID",
params={"txHash": "0xSRC_TX_HASH"},
headers={"x-api-key": "proxy", "SC-CALLER-ID": f"chat:{thread_id}"},
)
# → { "status": "PROCESSING", "explorerUrl": "..." }
Poll every ~4 seconds. Terminal states: SUCCEEDED, FAILED, UNKNOWN.
Non-terminal: PENDING, PROCESSING. Share explorerUrl with the user.
Safety rules
- ⚠️ Never approve the LZMulticall (Wrapper) contract as a token spender. The API-generated calldata already uses the TransferDelegate contract for approvals — execute steps as returned, don't hand-craft approvals.
- Quotes expire — get a fresh quote if execution is delayed; don't reuse old ones.
- Always confirm with the user before signing/sending real-value transactions:
state source/destination chains, token, amount, and
feeUsd. - Verify the destination address matches the user's intent before quoting.
- On
FAILEDorUNKNOWN, report theexplorerUrland do not retry blindly.