Skip to main content
The LI.FI SDK handles the full Composer lifecycle (allowance checks, chain switching, transaction submission, and status tracking) so you can focus on your application logic. This guide shows how to use Composer through the SDK.
Prerequisites: You should have the LI.FI SDK installed and configured. If not, see Installing the SDK and Configure SDK.

Quick Example

Deposit USDC into a Morpho vault on Base using the SDK:
import { createConfig, getQuote, convertQuoteToRoute, executeRoute } from '@lifi/sdk';

// 1. Configure the SDK (once, at app startup)
createConfig({
  integrator: 'YourAppName',
});

// 2. Get a Composer quote
const quote = await getQuote({
  fromChain: 8453,                                              // Base
  toChain: 8453,                                                // Base
  fromToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',     // USDC on Base
  toToken: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A',       // Morpho vault token
  fromAmount: '1000000',                                        // 1 USDC
  fromAddress: '0xYOUR_WALLET_ADDRESS',
});

// 3. Convert quote to route and execute - SDK handles allowance, submission, and tracking
const route = convertQuoteToRoute(quote);
const executedRoute = await executeRoute(route, {
  updateRouteHook(updatedRoute) {
    console.log('Route updated:', updatedRoute);
  },
});

console.log('Done!', executedRoute);
That’s it. The SDK internally manages:
  • Allowance checks and approvals
  • Transaction data retrieval (if using /advanced/routes)
  • Transaction submission
  • Status tracking and polling
  • Chain switching (for cross-chain flows)

Step-by-Step Guide

1. Configure the SDK

Set up the SDK once at application startup. You must configure EVM providers for the chains you want to use.
import { createConfig } from '@lifi/sdk';
import { createWalletClient, http } from 'viem';
import { base } from 'viem/chains';

createConfig({
  integrator: 'YourAppName',
  providers: [
    // Configure your EVM provider - see SDK docs for full setup
  ],
});
For full provider configuration, see Configure SDK Providers.

2. Request a Composer Quote

Use getQuote for a single best route (includes transaction data) or getRoutes for multiple options.

Using getQuote

import { getQuote } from '@lifi/sdk';

const quote = await getQuote({
  fromChain: 8453,
  toChain: 8453,
  fromToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
  toToken: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A',
  fromAmount: '1000000',
  fromAddress: '0xYOUR_WALLET_ADDRESS',
});

Using getRoutes

import { getRoutes } from '@lifi/sdk';

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

const route = result.routes[0]; // Select the best route
The toToken / toTokenAddress is always the vault token address of the target protocol. This is what triggers Composer. Find vault token addresses on the protocol’s own app or documentation.

3. Execute the Route

The executeRoute function handles the entire execution lifecycle:
import { executeRoute } from '@lifi/sdk';

const executedRoute = await executeRoute(route, {
  // Called whenever the route object is updated during execution
  updateRouteHook(updatedRoute) {
    const step = updatedRoute.steps[0];
    const process = step?.execution?.process;
    const lastProcess = process?.[process.length - 1];

    console.log(`Step: ${step?.tool}`);
    console.log(`Status: ${lastProcess?.status}`);
    console.log(`Tx: ${lastProcess?.txHash || 'pending'}`);
  },
});

4. Monitor Execution

The updateRouteHook callback fires on every state change. Use it to update your UI:
const executedRoute = await executeRoute(route, {
  updateRouteHook(updatedRoute) {
    for (const step of updatedRoute.steps) {
      if (!step.execution) continue;

      for (const process of step.execution.process) {
        switch (process.status) {
          case 'STARTED':
            console.log(`${process.type} started`);
            break;
          case 'PENDING':
            console.log(`${process.type} pending - tx: ${process.txHash}`);
            break;
          case 'DONE':
            console.log(`${process.type} complete`);
            break;
          case 'FAILED':
            console.error(`${process.type} failed: ${process.error?.message}`);
            break;
        }
      }
    }
  },
});

Cross-Chain Composer via SDK

Cross-chain Composer works identically. Just use different fromChain and toChain values:
const crossChainQuote = await getQuote({
  fromChain: 1,                                                 // Ethereum
  toChain: 8453,                                                // Base
  fromToken: '0x0000000000000000000000000000000000000000',       // ETH (native)
  toToken: '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A',       // Morpho vault on Base
  fromAmount: '100000000000000000',                             // 0.1 ETH
  fromAddress: '0xYOUR_WALLET_ADDRESS',
});

const route = convertQuoteToRoute(crossChainQuote);
const executedRoute = await executeRoute(route, {
  updateRouteHook(updatedRoute) {
    console.log('Route updated:', updatedRoute.steps.map(s => s.tool));
  },
});
The SDK automatically handles:
  • Bridge selection and execution
  • Waiting for bridge completion
  • Chain switching to the destination chain
  • Executing the Composer deposit on the destination chain
  • Status tracking throughout

Execution Options

All execution options are optional but can be useful for advanced use cases:
OptionTypeDescription
updateRouteHook(route) => voidCalled on every route state change. Use for UI updates.
updateTransactionRequestHook(txRequest) => Promise<tx>Modify transaction requests before submission (e.g., custom gas).
acceptExchangeRateUpdateHook(params) => Promise<boolean>Called if the exchange rate changes during execution. Return true to accept.
infiniteApprovalbooleanIf true, approves max uint256 instead of the exact amount.
executeInBackgroundbooleanIf true, execution continues even if the user navigates away.
For the full execution options reference, see Execute Routes/Quotes.

Error Handling

The SDK throws errors that you can catch and handle:
try {
  const executedRoute = await executeRoute(route, {
    updateRouteHook(updatedRoute) {
      // Track progress
    },
  });
} catch (error) {
  console.error('Execution failed:', error.message);

  // Check the route's step execution for detailed failure info
  for (const step of route.steps) {
    if (step.execution) {
      for (const process of step.execution.process) {
        if (process.status === 'FAILED') {
          console.error(`Step ${step.tool} failed:`, process.error?.message);
        }
      }
    }
  }
}

Next Steps