Skip to main content
This guide walks you through integrating Composer directly via the LI.FI REST API. This approach gives you full control over the request/response flow and is ideal for backend services, custom frontends, or any environment where you want to manage transactions yourself.
Already using the LI.FI SDK or Widget? Composer works automatically. See the SDK guide or Widget guide instead.

Overview

The integration flow:
  1. Request a quoteGET /v1/quote or POST /v1/advanced/routes
  2. Set token allowance — approve the LI.FI contract to spend your tokens
  3. Send the transaction — submit the transactionRequest from the quote response
  4. Track status — poll GET /v1/status for cross-chain transfers

Request a Composer Quote

The simplest way to get a Composer transaction. Returns a single best route with transaction data included.
curl -X GET 'https://li.quest/v1/quote?\
fromChain=8453&\
toChain=8453&\
fromToken=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913&\
toToken=0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A&\
fromAddress=0xYOUR_WALLET_ADDRESS&\
toAddress=0xYOUR_WALLET_ADDRESS&\
fromAmount=1000000'

Using POST /advanced/routes

Returns multiple route options. Useful when you want to present choices to the user or need more control over route selection.
const getRoutes = async (params: {
  fromChainId: number;
  toChainId: number;
  fromTokenAddress: string;
  toTokenAddress: string;
  fromAmount: string;
  fromAddress: string;
}) => {
  const result = await axios.post('https://li.quest/v1/advanced/routes', {
    ...params,
    toAddress: params.fromAddress,
  });
  return result.data;
};

const routesResponse = await getRoutes({
  fromChainId: 8453,
  toChainId: 8453,
  fromTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
  toTokenAddress: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A',
  fromAmount: '1000000',
  fromAddress: '0xYOUR_WALLET_ADDRESS',
});

// Select the best route
const route = routesResponse.routes[0];
When using /advanced/routes, transaction data is not included in the response. You must call POST /v1/advanced/stepTransaction to get the transactionRequest for each step. With /quote, transaction data is included directly.
For a detailed comparison, see Difference between Quote and Route.

Set Token Allowance

Before executing, the LI.FI contract needs approval to spend your tokens. The approval address is in the quote response at estimate.approvalAddress.
Skip this step if fromToken is a native token (e.g., ETH). Native tokens don’t require approval.
import { ethers } from 'ethers';

const ERC20_ABI = [
  'function approve(address spender, uint256 amount) returns (bool)',
  'function allowance(address owner, address spender) view returns (uint256)',
];

const ensureAllowance = async (
  signer: ethers.Signer,
  tokenAddress: string,
  approvalAddress: string,
  amount: string
) => {
  if (tokenAddress === ethers.ZeroAddress) return; // Native token

  const erc20 = new ethers.Contract(tokenAddress, ERC20_ABI, signer);
  const owner = await signer.getAddress();
  const currentAllowance = await erc20.allowance(owner, approvalAddress);

  if (currentAllowance < BigInt(amount)) {
    const tx = await erc20.approve(approvalAddress, amount);
    await tx.wait();
  }
};

await ensureAllowance(
  signer,
  quote.action.fromToken.address,
  quote.estimate.approvalAddress,
  quote.action.fromAmount
);

Send the Transaction

Submit the transactionRequest from the quote response. This is a standard EVM transaction.
const tx = await signer.sendTransaction(quote.transactionRequest);
console.log('Transaction hash:', tx.hash);

const receipt = await tx.wait();
console.log('Confirmed in block:', receipt.blockNumber);

Track Status

For same-chain Composer transactions, the operation is complete once the transaction is confirmed. For cross-chain Composer flows, poll the /status endpoint until the transfer completes:
const getStatus = async (txHash: string, fromChain: number, toChain: number) => {
  const result = await axios.get('https://li.quest/v1/status', {
    params: { txHash, fromChain, toChain },
  });
  return result.data;
};

const pollStatus = async (txHash: string, fromChain: number, toChain: number) => {
  let status;
  do {
    status = await getStatus(txHash, fromChain, toChain);
    console.log(`Status: ${status.status} (${status.substatus || ''})`);

    if (status.status !== 'DONE' && status.status !== 'FAILED') {
      await new Promise((r) => setTimeout(r, 5000));
    }
  } while (status.status !== 'DONE' && status.status !== 'FAILED');

  return status;
};

// Only needed for cross-chain
if (quote.action.fromChainId !== quote.action.toChainId) {
  const finalStatus = await pollStatus(
    tx.hash,
    quote.action.fromChainId,
    quote.action.toChainId
  );
  console.log('Final:', finalStatus.status);
}
For the full status reference including substatus values, see Transaction Status Tracking.

Error Handling

Common errors when working with Composer:
ErrorCauseResolution
No routes foundVault token not supported or insufficient liquidityVerify the vault token address is correct and the protocol is supported
Simulation failedThe Composer execution would fail onchainCheck token balances, allowances, and that the vault is accepting deposits
Insufficient allowanceToken approval not set or too lowCall approve() with the correct approvalAddress and amount
For the full error reference, see Error Codes.

Next Steps