Quickstart
Get started with the Value Transfer API in minutes. This guide walks you through the complete transfer flow.
Prerequisites
- An API key from LayerZero
- A funded wallet on the source chain
Base URL
https://transfer.layerzero-api.com/v1
Transfer Execution Flow
Check chains and tokens
Before requesting a quote, verify your source and destination chains are supported and discover available tokens.
List supported chains
- cURL
- TypeScript
- Python
curl -X GET "https://transfer.layerzero-api.com/v1/chains"
const response = await fetch('https://transfer.layerzero-api.com/v1/chains');
const {chains} = await response.json();
console.log(chains);
import requests
response = requests.get("https://transfer.layerzero-api.com/v1/chains")
chains = response.json()["chains"]
print(chains)
Response
| Field | Description |
|---|---|
chainKey | Unique chain identifier (e.g., ethereum, base) |
chainType | Blockchain type (EVM or SOLANA) |
chainId | Native chain ID (e.g., 1 for Ethereum, 8453 for Base) |
See the Chains API reference for complete endpoint documentation.
List supported tokens
Query tokens with optional filters. Without parameters, the API returns all tokens across all supported chains.
- cURL
- TypeScript
- Python
# Get all tokens (full catalog)
curl -X GET "https://transfer.layerzero-api.com/v1/tokens"
# Get valid destinations for ETH from Base
curl -X GET "https://transfer.layerzero-api.com/v1/tokens?transferrableFromChainKey=&transferrableFromTokenAddress=&pagination%5BnextToken%5D="
// Get tokens transferrable from Base ETH
const response = await fetch(
'https://transfer.layerzero-api.com/v1/tokens?' +
new URLSearchParams({
transferrableFromChainKey: 'base',
transferrableFromTokenAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
}),
);
const {tokens} = await response.json();
console.log(tokens);
import requests
# Get tokens transferrable from Base ETH
response = requests.get(
"https://transfer.layerzero-api.com/v1/tokens",
params={
"transferrableFromChainKey": "base",
"transferrableFromTokenAddress": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
)
tokens = response.json()["tokens"]
print(tokens)
Query parameters
| Parameter | Type | Description |
|---|---|---|
transferrableFromChainKey | string | Source chain key (e.g., base, ethereum). Must be combined with transferrableFromTokenAddress to filter results. |
transferrableFromTokenAddress | string | Source token address. Must be combined with transferrableFromChainKey to filter results. |
To get all destination tokens you can transfer to, provide both the source chain and token address. See Tokens for the full endpoint reference.
Response
| Field | Description |
|---|---|
isSupported | Whether the token is available for transfers |
chainKey | Chain identifier (e.g., ethereum, base) |
address | Token contract address |
decimals | Token decimal places |
symbol | Token symbol (e.g., ETH, USDC) |
name | Full token name |
price.usd | Current price in USD |
Get a quote
Request a quote for your cross-chain transfer. The API returns available routes with fees, estimated duration, and the steps needed to execute.
The quote includes userSteps with transaction or signature data to execute. See Quotes for the full request/response schema.
- cURL
- TypeScript
- Python
curl -X POST "https://transfer.layerzero-api.com/v1/quotes" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"srcChainKey": "base",
"dstChainKey": "optimism",
"srcTokenAddress": "<0xTOKEN_ADDRESS>",
"dstTokenAddress": "<0xTOKEN_ADDRESS>",
"srcWalletAddress": "<0xYOUR_WALLET>",
"dstWalletAddress": "<0xYOUR_WALLET>",
"amount": "<AMOUNT_IN_LOCAL_DECIMALS>",
"options": {
"amountType": "EXACT_SRC_AMOUNT",
"feeTolerance": { "type": "PERCENT", "amount": 2 }
}
}'
const response = await fetch('https://transfer.layerzero-api.com/v1/quotes', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
srcChainKey: 'base',
dstChainKey: 'optimism',
srcTokenAddress: '<0xTOKEN_ADDRESS>',
dstTokenAddress: '<0xTOKEN_ADDRESS>',
srcWalletAddress: '<0xYOUR_WALLET>',
dstWalletAddress: '<0xYOUR_WALLET>',
amount: '<AMOUNT_IN_LOCAL_DECIMALS>', // 0.0001 ETH in wei
options: {
amountType: 'EXACT_SRC_AMOUNT',
feeTolerance: {type: 'PERCENT', amount: 2},
},
}),
});
const {quotes} = await response.json();
const quote = quotes[0];
console.log('Quote ID:', quote.id);
console.log('Fee:', quote.feeUsd, 'USD');
import requests
response = requests.post(
"https://transfer.layerzero-api.com/v1/quotes",
headers={
"x-api-key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"srcChainKey": "base",
"dstChainKey": "optimism",
"srcTokenAddress": "<0xTOKEN_ADDRESS>",
"dstTokenAddress": "<0xTOKEN_ADDRESS>",
"srcWalletAddress": "<0xYOUR_WALLET>",
"dstWalletAddress": "<0xYOUR_WALLET>",
"amount": "<AMOUNT_IN_LOCAL_DECIMALS>", # 0.0001 ETH in wei
"options": {
"amountType": "EXACT_SRC_AMOUNT",
"feeTolerance": {"type": "PERCENT", "amount": 2},
},
},
)
quote = response.json()["quotes"][0]
print(f"Quote ID: {quote['id']}")
print(f"Fee: {quote.get('feeUsd', 'N/A')} USD")
Build user steps (Solana only)
For EVM transfers, the quote response already includes userSteps with transaction data - skip to Step 4.
For Solana transfers, you must call /build-user-steps to get the encoded transaction data. Solana transactions have short blockhash validity (~60 seconds), so this step generates fresh transaction data.
| Chain type | Where to get userSteps |
|---|---|
| EVM | Directly from the quote response |
| Solana | Must call /build-user-steps first |
See Build User Steps for complete documentation.
- cURL
- TypeScript
- Python
curl -X POST "https://transfer.layerzero-api.com/v1/build-user-steps" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"quoteId": "QUOTE_ID"}'
const response = await fetch('https://transfer.layerzero-api.com/v1/build-user-steps', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({quoteId: 'QUOTE_ID'}),
});
const {body} = await response.json();
const userSteps = body.userSteps;
console.log('User steps:', userSteps);
import requests
response = requests.post(
"https://transfer.layerzero-api.com/v1/build-user-steps",
headers={
"x-api-key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
json={"quoteId": "QUOTE_ID"},
)
user_steps = response.json()["body"]["userSteps"]
print("User steps:", user_steps)
Execute the transfer
Process each user step in order. For transactions, sign and submit to the blockchain. For signatures, sign and submit to the API.
Executing requires a wallet to sign and broadcast the transaction. This step cannot be done with cURL alone - you need a signing library (viem, web3.py, @solana/web3.js) and an RPC connection.
Where to get userSteps:
- EVM: Use
quote.userStepsdirectly from the quote response - Solana: Use
body.userStepsfrom the/build-user-stepsresponse
Execute a transaction step
Use the encoded transaction data from the user step:
- TypeScript (viem)
- Python (web3.py)
import {createWalletClient, http} from 'viem';
import {privateKeyToAccount} from 'viem/accounts';
import {base} from 'viem/chains';
const account = privateKeyToAccount('0xYOUR_PRIVATE_KEY');
const wallet = createWalletClient({
account,
chain: base,
transport: http(),
});
// From userSteps[0].transaction.encoded
const tx = userStep.transaction.encoded;
const hash = await wallet.sendTransaction({
to: tx.to,
data: tx.data,
value: BigInt(tx.value ?? 0),
});
console.log('Transaction hash:', hash);
from web3 import Web3
w3 = Web3(Web3.HTTPProvider("https://mainnet.base.org"))
account = w3.eth.account.from_key("YOUR_PRIVATE_KEY")
# From userSteps[0].transaction.encoded
tx = user_step["transaction"]["encoded"]
signed = account.sign_transaction(
{
"to": tx["to"],
"data": tx["data"],
"value": int(tx.get("value", 0)),
"gas": 200000,
"gasPrice": w3.eth.gas_price,
"nonce": w3.eth.get_transaction_count(account.address),
"chainId": tx["chainId"],
}
)
tx_hash = w3.eth.send_raw_transaction(signed.rawTransaction)
print(f"Transaction hash: {tx_hash.hex()}")
Submit a signature step
For intent-based routes, sign the EIP-712 data and submit to the API.
See Submit Signature for EIP-712 typed data handling and BigInt conversion requirements.
- TypeScript (viem)
- Python
// Sign the typed data from userStep.signature.typedData
const typed = userStep.signature.typedData;
const signature = await wallet.signTypedData({
domain: typed.domain,
types: typed.types,
primaryType: typed.primaryType,
message: typed.message,
});
// Submit to API
await fetch('https://transfer.layerzero-api.com/v1/submit-signature', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
quoteId: quote.id,
signatures: [signature],
}),
});
from eth_account.messages import encode_typed_data
# Sign the typed data from userStep["signature"]["typedData"]
typed = user_step["signature"]["typedData"]
signable = encode_typed_data(full_message=typed)
signed = account.sign_message(signable)
# Submit to API
requests.post(
"https://transfer.layerzero-api.com/v1/submit-signature",
headers={
"x-api-key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"quoteId": quote["id"],
"signatures": [signed.signature.hex()],
},
)
Track the transfer
After executing all user steps, poll the status endpoint to monitor completion.
Poll every 4 seconds until status is SUCCEEDED or FAILED. See Status for response details and error handling.
- cURL
- TypeScript
- Python
curl -X GET "https://transfer.layerzero-api.com/v1/status/QUOTE_ID?txHash=0xYOUR_TX_HASH" \
-H "x-api-key: YOUR_API_KEY"
async function pollStatus(quoteId: string, txHash?: string): Promise<string> {
const query = txHash ? `?txHash=${txHash}` : '';
while (true) {
const response = await fetch(
`https://transfer.layerzero-api.com/v1/status/${encodeURIComponent(quoteId)}${query}`,
{headers: {'x-api-key': 'YOUR_API_KEY'}},
);
const {status, explorerUrl} = await response.json();
console.log('Status:', status);
if (status === 'SUCCEEDED' || status === 'FAILED') {
console.log('Explorer:', explorerUrl);
return status;
}
await new Promise((resolve) => setTimeout(resolve, 4000));
}
}
import time
from urllib.parse import quote as url_quote
import requests
def poll_status(quote_id: str, tx_hash: str | None = None) -> str:
query = f"?txHash={tx_hash}" if tx_hash else ""
while True:
response = requests.get(
f"https://transfer.layerzero-api.com/v1/status/{url_quote(quote_id, safe='')}{query}",
headers={"x-api-key": "YOUR_API_KEY"},
)
data = response.json()
print(f"Status: {data['status']}")
if data["status"] in ("SUCCEEDED", "FAILED"):
print(f"Explorer: {data.get('explorerUrl')}")
return data["status"]
time.sleep(4)
Status values
| Status | Description |
|---|---|
PENDING | Transfer initiated, waiting for confirmation |
PROCESSING | Transfer in progress across chains |
SUCCEEDED | Transfer completed successfully |
FAILED | Transfer failed |
UNKNOWN | Status cannot be determined |
Next steps
- EVM Example: Complete TypeScript example with viem
- Solana Example: Complete TypeScript example for Solana transfers
- API Reference: Explore all endpoints with interactive testing