Skip to main content
This page provides complete, runnable code samples for integrating LI.FI into your AI agent or application.

Node.js (API Only)

A complete example using only HTTP requests - no SDK required.
// 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.
// 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.
# 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:
# 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)

# No dependencies required (uses native fetch)
node lifi-agent.js

Node.js (SDK)

npm install @lifi/sdk viem
node lifi-sdk-agent.js

Python

pip install requests
python lifi_agent.py

Error Handling Patterns

Both examples include these error handling patterns:
  1. Exponential backoff for rate limits (429)
  2. Retry logic for transient failures
  3. Status polling with timeout
  4. Graceful error messages for debugging
For more details on error handling, see Error Playbooks.