Node.js (API Only)
A complete example using only HTTP requests - no SDK required.Copy
Ask AI
// lifi-agent.js
// Run with: node lifi-agent.js
const LIFI_API = 'https://li.quest/v1';
// Optional: Set API key for higher rate limits
const API_KEY = process.env.LIFI_API_KEY || '';
const headers = {
'Content-Type': 'application/json',
...(API_KEY && { 'x-lifi-api-key': API_KEY })
};
// Helper: Sleep for exponential backoff
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// Helper: Fetch with retry and backoff
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, { ...options, headers });
if (response.status === 429) {
// Rate limited - exponential backoff
const waitTime = Math.pow(2, attempt) * 1000;
console.log(`Rate limited. Waiting ${waitTime}ms...`);
await sleep(waitTime);
continue;
}
if (!response.ok) {
const error = await response.json();
throw new Error(`API Error: ${error.message || response.statusText}`);
}
return await response.json();
} catch (error) {
if (attempt === maxRetries - 1) throw error;
await sleep(1000);
}
}
}
// Step 1: Get supported chains
async function getChains(chainTypes = 'EVM') {
const url = `${LIFI_API}/chains?chainTypes=${chainTypes}`;
const data = await fetchWithRetry(url);
return data.chains;
}
// Step 2: Get tokens for chains
async function getTokens(chainIds) {
const url = `${LIFI_API}/tokens?chains=${chainIds.join(',')}`;
const data = await fetchWithRetry(url);
return data.tokens;
}
// Step 3: Get a quote
async function getQuote(params) {
const {
fromChain,
toChain,
fromToken,
toToken,
fromAmount,
fromAddress,
slippage = 0.005
} = params;
const url = new URL(`${LIFI_API}/quote`);
url.searchParams.set('fromChain', fromChain);
url.searchParams.set('toChain', toChain);
url.searchParams.set('fromToken', fromToken);
url.searchParams.set('toToken', toToken);
url.searchParams.set('fromAmount', fromAmount);
url.searchParams.set('fromAddress', fromAddress);
url.searchParams.set('slippage', slippage);
return await fetchWithRetry(url.toString());
}
// Step 4: Check transfer status
async function getStatus(txHash, bridge, fromChain, toChain) {
const url = `${LIFI_API}/status?txHash=${txHash}&bridge=${bridge}&fromChain=${fromChain}&toChain=${toChain}`;
return await fetchWithRetry(url);
}
// Step 5: Poll status until complete
async function pollStatus(txHash, bridge, fromChain, toChain, maxAttempts = 60) {
console.log('Polling transfer status...');
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const status = await getStatus(txHash, bridge, fromChain, toChain);
console.log(`Status: ${status.status} (${status.substatus || 'no substatus'})`);
if (status.status === 'DONE') {
if (status.substatus === 'COMPLETED') {
console.log('Transfer completed successfully!');
return { success: true, status };
}
if (status.substatus === 'PARTIAL') {
console.log('Transfer completed with different token');
return { success: true, partial: true, status };
}
if (status.substatus === 'REFUNDED') {
console.log('Transfer failed - tokens refunded');
return { success: false, refunded: true, status };
}
}
if (status.status === 'FAILED') {
console.log('Transfer failed');
return { success: false, status };
}
// Wait 10 seconds before next poll
await sleep(10000);
}
throw new Error('Status polling timeout');
}
// Main execution flow
async function executeTransfer(params) {
console.log('Getting quote...');
const quote = await getQuote(params);
console.log(`Quote received: ${quote.tool}`);
console.log(`Expected output: ${quote.estimate.toAmount}`);
console.log(`Minimum output: ${quote.estimate.toAmountMin}`);
// Here you would:
// 1. Check if approval is needed (for ERC20 tokens)
// 2. Send approval transaction if needed
// 3. Send the main transaction using quote.transactionRequest
// 4. Get the txHash from the transaction receipt
console.log('\nTransaction request ready:');
console.log(JSON.stringify(quote.transactionRequest, null, 2));
return quote;
}
// Example usage
async function main() {
try {
// Get available chains
const chains = await getChains();
console.log(`Found ${chains.length} chains`);
// Get quote for 10 USDC from Ethereum to Arbitrum
const quote = await executeTransfer({
fromChain: 1,
toChain: 42161,
fromToken: 'USDC',
toToken: 'USDC',
fromAmount: '10000000', // 10 USDC (6 decimals)
fromAddress: '0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0' // Replace with actual address
});
// After executing the transaction, poll for status:
// const result = await pollStatus(txHash, quote.tool, 1, 42161);
} catch (error) {
console.error('Error:', error.message);
}
}
main();
Node.js (with SDK)
Using the LI.FI SDK for full execution support.Copy
Ask AI
// lifi-sdk-agent.js
// Install: npm install @lifi/sdk viem
// Run with: node lifi-sdk-agent.js
import { createConfig, getQuote, executeRoute, convertQuoteToRoute } from '@lifi/sdk';
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { mainnet, arbitrum } from 'viem/chains';
// Configuration
const PRIVATE_KEY = process.env.PRIVATE_KEY; // Never hardcode!
const INTEGRATOR_NAME = 'your-agent-name';
// Initialize SDK
createConfig({
integrator: INTEGRATOR_NAME,
// Optional: API key for higher rate limits
// apiKey: process.env.LIFI_API_KEY,
});
// Create wallet client
const account = privateKeyToAccount(PRIVATE_KEY);
const walletClient = createWalletClient({
account,
chain: mainnet,
transport: http(),
});
// Get a quote
async function fetchQuote(params) {
const quote = await getQuote({
fromChain: params.fromChain,
toChain: params.toChain,
fromToken: params.fromToken,
toToken: params.toToken,
fromAmount: params.fromAmount,
fromAddress: account.address,
});
return quote;
}
// Execute the transfer
async function executeTransfer(quote) {
console.log('Executing transfer...');
// Convert quote to route format for execution
const route = convertQuoteToRoute(quote);
const result = await executeRoute(route, {
// Update hook called on each status change
updateRouteHook: (updatedRoute) => {
console.log('Route updated:', updatedRoute.id);
const step = updatedRoute.steps[0];
if (step?.execution) {
console.log(`Execution status: ${step.execution.status}`);
}
},
});
return result;
}
// Main flow
async function main() {
try {
console.log(`Wallet address: ${account.address}`);
// Get quote
const quote = await fetchQuote({
fromChain: 1, // Ethereum
toChain: 42161, // Arbitrum
fromToken: 'USDC',
toToken: 'USDC',
fromAmount: '10000000', // 10 USDC
});
console.log('Quote received:');
console.log(`- Tool: ${quote.tool}`);
console.log(`- Expected: ${quote.estimate.toAmount}`);
console.log(`- Minimum: ${quote.estimate.toAmountMin}`);
// Execute (uncomment to run actual transfer)
// const result = await executeTransfer(quote);
// console.log('Transfer complete:', result);
} catch (error) {
console.error('Error:', error.message);
}
}
main();
Python (API Only)
Complete Python example using only HTTP requests.Copy
Ask AI
# lifi_agent.py
# Install: pip install requests
# Run with: python lifi_agent.py
import os
import time
import requests
from typing import Optional, Dict, Any, List
LIFI_API = "https://li.quest/v1"
API_KEY = os.environ.get("LIFI_API_KEY", "")
def get_headers() -> Dict[str, str]:
headers = {"Content-Type": "application/json"}
if API_KEY:
headers["x-lifi-api-key"] = API_KEY
return headers
def fetch_with_retry(url: str, max_retries: int = 3) -> Dict[str, Any]:
"""Fetch with exponential backoff for rate limiting."""
for attempt in range(max_retries):
try:
response = requests.get(url, headers=get_headers())
if response.status_code == 429:
wait_time = (2 ** attempt)
print(f"Rate limited. Waiting {wait_time}s...")
time.sleep(wait_time)
continue
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
if attempt == max_retries - 1:
raise
time.sleep(1)
raise Exception("Max retries exceeded")
def get_chains(chain_types: str = "EVM") -> List[Dict]:
"""Get supported chains."""
url = f"{LIFI_API}/chains?chainTypes={chain_types}"
data = fetch_with_retry(url)
return data["chains"]
def get_tokens(chain_ids: List[int]) -> Dict[str, List[Dict]]:
"""Get tokens for specified chains."""
chains_param = ",".join(str(c) for c in chain_ids)
url = f"{LIFI_API}/tokens?chains={chains_param}"
data = fetch_with_retry(url)
return data["tokens"]
def get_quote(
from_chain: int,
to_chain: int,
from_token: str,
to_token: str,
from_amount: str,
from_address: str,
slippage: float = 0.005
) -> Dict[str, Any]:
"""Get a quote for token transfer."""
params = {
"fromChain": from_chain,
"toChain": to_chain,
"fromToken": from_token,
"toToken": to_token,
"fromAmount": from_amount,
"fromAddress": from_address,
"slippage": slippage
}
url = f"{LIFI_API}/quote?" + "&".join(f"{k}={v}" for k, v in params.items())
return fetch_with_retry(url)
def get_status(
tx_hash: str,
bridge: str,
from_chain: int,
to_chain: int
) -> Dict[str, Any]:
"""Check transfer status."""
url = f"{LIFI_API}/status?txHash={tx_hash}&bridge={bridge}&fromChain={from_chain}&toChain={to_chain}"
return fetch_with_retry(url)
def poll_status(
tx_hash: str,
bridge: str,
from_chain: int,
to_chain: int,
max_attempts: int = 60,
poll_interval: int = 10
) -> Dict[str, Any]:
"""Poll status until transfer completes."""
print("Polling transfer status...")
for attempt in range(max_attempts):
status = get_status(tx_hash, bridge, from_chain, to_chain)
status_str = status.get("status", "UNKNOWN")
substatus_str = status.get("substatus", "")
print(f"Status: {status_str} ({substatus_str})")
if status_str == "DONE":
if substatus_str == "COMPLETED":
print("Transfer completed successfully!")
return {"success": True, "status": status}
elif substatus_str == "PARTIAL":
print("Transfer completed with different token")
return {"success": True, "partial": True, "status": status}
elif substatus_str == "REFUNDED":
print("Transfer failed - tokens refunded")
return {"success": False, "refunded": True, "status": status}
if status_str == "FAILED":
print("Transfer failed")
return {"success": False, "status": status}
time.sleep(poll_interval)
raise Exception("Status polling timeout")
def execute_transfer(
from_chain: int,
to_chain: int,
from_token: str,
to_token: str,
from_amount: str,
from_address: str
) -> Dict[str, Any]:
"""Get quote and prepare for execution."""
print("Getting quote...")
quote = get_quote(
from_chain=from_chain,
to_chain=to_chain,
from_token=from_token,
to_token=to_token,
from_amount=from_amount,
from_address=from_address
)
print(f"Quote received: {quote['tool']}")
print(f"Expected output: {quote['estimate']['toAmount']}")
print(f"Minimum output: {quote['estimate']['toAmountMin']}")
# Transaction request is ready for signing
print("\nTransaction request:")
tx_request = quote["transactionRequest"]
print(f" To: {tx_request['to']}")
print(f" Value: {tx_request['value']}")
print(f" Gas Limit: {tx_request.get('gasLimit', 'auto')}")
return quote
def main():
try:
# Get available chains
chains = get_chains()
print(f"Found {len(chains)} chains")
# Get quote for 10 USDC from Ethereum to Arbitrum
quote = execute_transfer(
from_chain=1,
to_chain=42161,
from_token="USDC",
to_token="USDC",
from_amount="10000000", # 10 USDC (6 decimals)
from_address="0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0" # Replace
)
# After executing the transaction, poll for status:
# result = poll_status(tx_hash, quote["tool"], 1, 42161)
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
main()
Environment Variables
Both examples support these environment variables:Copy
Ask AI
# Optional: API key for higher rate limits
export LIFI_API_KEY="your-api-key"
# For SDK example: Private key for signing (never commit!)
export PRIVATE_KEY="0x..."
Quick Reference
Node.js (API)
Copy
Ask AI
# No dependencies required (uses native fetch)
node lifi-agent.js
Node.js (SDK)
Copy
Ask AI
npm install @lifi/sdk viem
node lifi-sdk-agent.js
Python
Copy
Ask AI
pip install requests
python lifi_agent.py
Error Handling Patterns
Both examples include these error handling patterns:- Exponential backoff for rate limits (429)
- Retry logic for transient failures
- Status polling with timeout
- Graceful error messages for debugging

