Skip to main content
Every Composer deposit, whether it targets a vault, staking protocol, or lending market, uses the same API call. Set toToken to the protocol’s token address and LI.FI handles swaps, bridging, and the final deposit. Each recipe below is end-to-end: request a quote, approve tokens, sign the transaction, and confirm the deposit.
All examples use GET /quote. The same toToken addresses work with POST /advanced/routes and the LI.FI SDK. See API Integration or SDK Integration for full integration guides.

Same-Chain: USDC → Morpho Vault (Base)

Deposit 1 USDC into a Spark-curated Morpho USDC vault on Base.
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'
import { ethers } from "ethers";
import axios from "axios";

const API_URL = "https://li.quest/v1";

// 1. Get a Composer quote
const { data: quote } = await axios.get(`${API_URL}/quote`, {
  params: {
    fromChain: 8453,
    toChain: 8453,
    fromToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
    toToken: "0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A", // Morpho vault token (sparkUSDC)
    fromAddress: await signer.getAddress(),
    toAddress: await signer.getAddress(),
    fromAmount: "1000000", // 1 USDC (6 decimals)
    slippage: 0.005,
  },
});

console.log("Tool:", quote.tool); // "composer"
console.log("Estimated output:", quote.estimate.toAmount);
console.log("Approval target:", quote.estimate.approvalAddress);

// 2. Approve the LI.FI Diamond to spend your tokens
const ERC20_ABI = [
  "function approve(address spender, uint256 amount) returns (bool)",
  "function allowance(address owner, address spender) view returns (uint256)",
];

const erc20 = new ethers.Contract(
  quote.action.fromToken.address,
  ERC20_ABI,
  signer,
);
const allowance = await erc20.allowance(
  await signer.getAddress(),
  quote.estimate.approvalAddress,
);

if (allowance < BigInt(quote.action.fromAmount)) {
  const approveTx = await erc20.approve(
    quote.estimate.approvalAddress,
    quote.action.fromAmount,
  );
  await approveTx.wait();
  console.log("Approval confirmed.");
}

// 3. Sign and send the transaction
const tx = await signer.sendTransaction(quote.transactionRequest);
console.log("Tx hash:", tx.hash);

const receipt = await tx.wait();
console.log("Confirmed in block:", receipt.blockNumber);
To target a different protocol, swap the toToken for any address listed in the Supported Protocols reference. Everything else stays the same.

Same-Chain: USDC → Aave (Ethereum)

Deposit 1000 USDC into Aave V3 on Ethereum, receiving aUSDC in return.
curl -X GET 'https://li.quest/v1/quote?\
fromChain=1&\
toChain=1&\
fromToken=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&\
toToken=0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c&\
fromAddress=0xYOUR_WALLET_ADDRESS&\
toAddress=0xYOUR_WALLET_ADDRESS&\
fromAmount=1000000000&\
slippage=0.005'
const { data: quote } = await axios.get(`${API_URL}/quote`, {
  params: {
    fromChain: 1, // Ethereum
    toChain: 1, // Ethereum
    fromToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
    toToken: "0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c", // aEthUSDC (Aave V3)
    fromAddress: await signer.getAddress(),
    toAddress: await signer.getAddress(),
    fromAmount: "1000000000", // 1000 USDC (6 decimals)
    slippage: 0.005,
  },
});

// Approve + send (same pattern as the Morpho example above)
const erc20 = new ethers.Contract(
  quote.action.fromToken.address,
  ERC20_ABI,
  signer,
);
const allowance = await erc20.allowance(
  await signer.getAddress(),
  quote.estimate.approvalAddress,
);
if (allowance < BigInt(quote.action.fromAmount)) {
  const approveTx = await erc20.approve(
    quote.estimate.approvalAddress,
    quote.action.fromAmount,
  );
  await approveTx.wait();
}

const tx = await signer.sendTransaction(quote.transactionRequest);
const receipt = await tx.wait();
console.log("aEthUSDC received. Tx:", receipt.hash);

Same-Chain: USDe → sUSDe on Ethena (Ethereum)

Deposit USDe into Ethena’s staked USDe (sUSDe) for yield.
curl -X GET 'https://li.quest/v1/quote?\
fromChain=1&\
toChain=1&\
fromToken=0x4c9EDD5852cd905f086C759E8383e09bff1E68B3&\
toToken=0x9D39A5DE30e57443BfF2A8307A4256c8797A3497&\
fromAddress=0xYOUR_WALLET_ADDRESS&\
toAddress=0xYOUR_WALLET_ADDRESS&\
fromAmount=1000000000000000000000&\
slippage=0.005'
const { data: quote } = await axios.get(`${API_URL}/quote`, {
  params: {
    fromChain: 1,
    toChain: 1,
    fromToken: "0x4c9EDD5852cd905f086C759E8383e09bff1E68B3", // USDe
    toToken: "0x9D39A5DE30e57443BfF2A8307A4256c8797A3497", // sUSDe (Ethena)
    fromAddress: await signer.getAddress(),
    toAddress: await signer.getAddress(),
    fromAmount: "1000000000000000000000", // 1000 USDe (18 decimals)
    slippage: 0.005,
  },
});

// Approve + send (same pattern as the Morpho example above)

Same-Chain: USDC → Euler eUSDC-2 (Ethereum)

Deposit USDC into Euler V2’s USDC lending vault on Ethereum, receiving eUSDC-2 in return.
curl -X GET 'https://li.quest/v1/quote?\
fromChain=1&\
toChain=1&\
fromToken=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&\
toToken=0x797DD80692c3b2dAdabCe8e30C07fDE5307D48a9&\
fromAddress=0xYOUR_WALLET_ADDRESS&\
toAddress=0xYOUR_WALLET_ADDRESS&\
fromAmount=1000000000&\
slippage=0.005'
const { data: quote } = await axios.get(`${API_URL}/quote`, {
  params: {
    fromChain: 1, // Ethereum
    toChain: 1, // Ethereum
    fromToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
    toToken: "0x797DD80692c3b2dAdabCe8e30C07fDE5307D48a9", // eUSDC-2 (Euler V2)
    fromAddress: await signer.getAddress(),
    toAddress: await signer.getAddress(),
    fromAmount: "1000000000", // 1000 USDC (6 decimals)
    slippage: 0.005,
  },
});

// Approve + send (same pattern as the Morpho example above)

Same-Chain: USDC → syrupUSDC on Maple (Ethereum)

Deposit USDC into Maple Finance’s Syrup USDC pool on Ethereum, receiving syrupUSDC.
curl -X GET 'https://li.quest/v1/quote?\
fromChain=1&\
toChain=1&\
fromToken=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&\
toToken=0x80ac24aA929eaF5013f6436cdA2a7ba190f5Cc0b&\
fromAddress=0xYOUR_WALLET_ADDRESS&\
toAddress=0xYOUR_WALLET_ADDRESS&\
fromAmount=1000000000&\
slippage=0.005'
const { data: quote } = await axios.get(`${API_URL}/quote`, {
  params: {
    fromChain: 1, // Ethereum
    toChain: 1, // Ethereum
    fromToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
    toToken: "0x80ac24aA929eaF5013f6436cdA2a7ba190f5Cc0b", // syrupUSDC (Maple)
    fromAddress: await signer.getAddress(),
    toAddress: await signer.getAddress(),
    fromAmount: "1000000000", // 1000 USDC (6 decimals)
    slippage: 0.005,
  },
});

// Approve + send (same pattern as the Morpho example above)

Cross-Chain: ETH (Ethereum) → Morpho Vault (Base)

Deposit ETH from Ethereum into the Morpho vault on Base. LI.FI handles the bridge, intermediate swaps, and deposit.
curl -X GET 'https://li.quest/v1/quote?fromChain=1&toChain=8453&fromToken=0x0000000000000000000000000000000000000000&toToken=0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A&fromAddress=0xYOUR_WALLET_ADDRESS&toAddress=0xYOUR_WALLET_ADDRESS&fromAmount=100000000000000000'
// 1. Get cross-chain quote
const { data: quote } = await axios.get(`${API_URL}/quote`, {
  params: {
    fromChain: 1, // Ethereum
    toChain: 8453, // Base
    fromToken: "0x0000000000000000000000000000000000000000", // ETH
    toToken: "0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A", // Morpho vault on Base
    fromAddress: await signer.getAddress(),
    toAddress: await signer.getAddress(),
    fromAmount: "100000000000000000", // 0.1 ETH
    slippage: 0.01, // 1% for cross-chain
  },
});

// 2. No approval needed for native ETH

// 3. Send the source chain transaction
const tx = await signer.sendTransaction(quote.transactionRequest);
await tx.wait();
console.log("Source tx confirmed:", tx.hash);

// 4. Poll status until the cross-chain transfer completes
let status;
do {
  const { data } = await axios.get(`${API_URL}/status`, {
    params: {
      txHash: tx.hash,
      fromChain: quote.action.fromChainId,
      toChain: quote.action.toChainId,
    },
  });
  status = data;
  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");

console.log("Final:", status.status);
Cross-chain transfers require status polling via GET /status. See Cross-Chain Composer Patterns for the full execution flow and partial failure handling.

Cross-Chain: USDC (Ethereum) → Morpho Vault (Base)

Bridge USDC from Ethereum and deposit into the Spark-curated Morpho vault on Base. LI.FI handles the bridge and final deposit in one flow.
curl -X GET 'https://li.quest/v1/quote?fromChain=1&toChain=8453&fromToken=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&toToken=0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A&fromAddress=0xYOUR_WALLET_ADDRESS&toAddress=0xYOUR_WALLET_ADDRESS&fromAmount=1000000000'
const { data: quote } = await axios.get(`${API_URL}/quote`, {
  params: {
    fromChain: 1, // Ethereum
    toChain: 8453, // Base
    fromToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
    toToken: "0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A", // Morpho vault on Base
    fromAddress: await signer.getAddress(),
    toAddress: await signer.getAddress(),
    fromAmount: "1000000000", // 1000 USDC (6 decimals)
    slippage: 0.01,
  },
});

// Approve USDC on Ethereum
const erc20 = new ethers.Contract(
  quote.action.fromToken.address,
  ERC20_ABI,
  signer,
);
const allowance = await erc20.allowance(
  await signer.getAddress(),
  quote.estimate.approvalAddress,
);
if (allowance < BigInt(quote.action.fromAmount)) {
  const approveTx = await erc20.approve(
    quote.estimate.approvalAddress,
    quote.action.fromAmount,
  );
  await approveTx.wait();
}

// Send source chain transaction
const tx = await signer.sendTransaction(quote.transactionRequest);
await tx.wait();
console.log("Source tx confirmed:", tx.hash);

// Poll until cross-chain transfer completes
let status;
do {
  const { data } = await axios.get(`${API_URL}/status`, {
    params: { txHash: tx.hash, fromChain: 1, toChain: 8453 },
  });
  status = data;
  if (status.status !== "DONE" && status.status !== "FAILED") {
    await new Promise((r) => setTimeout(r, 5000));
  }
} while (status.status !== "DONE" && status.status !== "FAILED");

console.log("Final status:", status.status);

Felix Vanilla Vaults

Felix Vanilla vaults are supported for deposit and withdraw.

Same-Chain: Deposit into Felix Vanilla Vault

curl -X GET 'https://li.quest/v1/quote?fromChain=CHAIN_ID&toChain=CHAIN_ID&fromToken=SOURCE_TOKEN_ADDRESS&toToken=FELIX_VAULT_TOKEN_ADDRESS&fromAddress=0xYOUR_WALLET_ADDRESS&toAddress=0xYOUR_WALLET_ADDRESS&fromAmount=AMOUNT_IN_SMALLEST_UNIT'
const quote = await axios.get("https://li.quest/v1/quote", {
  params: {
    fromChain: CHAIN_ID,
    toChain: CHAIN_ID,
    fromToken: "SOURCE_TOKEN_ADDRESS",
    toToken: "FELIX_VAULT_TOKEN_ADDRESS", // Felix Vanilla vault token
    fromAddress: "0xYOUR_WALLET_ADDRESS",
    toAddress: "0xYOUR_WALLET_ADDRESS",
    fromAmount: "AMOUNT_IN_SMALLEST_UNIT",
  },
});

Neverland Vaults

Neverland vaults are supported for deposit and withdraw.

Same-Chain: Deposit into Neverland Vault

curl -X GET 'https://li.quest/v1/quote?fromChain=CHAIN_ID&toChain=CHAIN_ID&fromToken=SOURCE_TOKEN_ADDRESS&toToken=NEVERLAND_VAULT_TOKEN_ADDRESS&fromAddress=0xYOUR_WALLET_ADDRESS&toAddress=0xYOUR_WALLET_ADDRESS&fromAmount=AMOUNT_IN_SMALLEST_UNIT'
const quote = await axios.get("https://li.quest/v1/quote", {
  params: {
    fromChain: CHAIN_ID,
    toChain: CHAIN_ID,
    fromToken: "SOURCE_TOKEN_ADDRESS",
    toToken: "NEVERLAND_VAULT_TOKEN_ADDRESS",
    fromAddress: "0xYOUR_WALLET_ADDRESS",
    toAddress: "0xYOUR_WALLET_ADDRESS",
    fromAmount: "AMOUNT_IN_SMALLEST_UNIT",
  },
});

Depositing on Behalf of Another Address

Some protocols (such as Aave with onBehalfOf) support depositing on behalf of a different wallet. To use this pattern, set toAddress to the recipient’s address:
TypeScript
const { data: quote } = await axios.get(`${API_URL}/quote`, {
  params: {
    fromChain: 8453,
    toChain: 8453,
    fromToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    toToken: "0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A",
    fromAddress: "0xSENDER_ADDRESS",
    toAddress: "0xRECIPIENT_ADDRESS", // Vault tokens go to this address
    fromAmount: "1000000",
    slippage: 0.005,
  },
});
Not all protocols support deposit-on-behalf. If the target protocol does not support it, the API will return an error or ignore the toAddress distinction. Test with your target protocol before relying on this pattern.

Pendle Yield Tokens

Pendle enables yield tokenisation. Composer supports deposit and withdraw.

Same-Chain: Deposit into Pendle

curl -X GET 'https://li.quest/v1/quote?fromChain=CHAIN_ID&toChain=CHAIN_ID&fromToken=SOURCE_TOKEN_ADDRESS&toToken=PENDLE_TOKEN_ADDRESS&fromAddress=0xYOUR_WALLET_ADDRESS&toAddress=0xYOUR_WALLET_ADDRESS&fromAmount=AMOUNT_IN_SMALLEST_UNIT'
const quote = await axios.get("https://li.quest/v1/quote", {
  params: {
    fromChain: CHAIN_ID,
    toChain: CHAIN_ID,
    fromToken: "SOURCE_TOKEN_ADDRESS",
    toToken: "PENDLE_TOKEN_ADDRESS", // Pendle yield token
    fromAddress: "0xYOUR_WALLET_ADDRESS",
    toAddress: "0xYOUR_WALLET_ADDRESS",
    fromAmount: "AMOUNT_IN_SMALLEST_UNIT",
  },
});

Deposit-Only Protocols

The following protocols support deposit only (no withdraw via Composer): For these protocols, use the same GET /quote pattern with the protocol’s vault/staking token as toToken.

General Pattern

Every vault deposit recipe follows the same pattern:
GET /quote
  fromChain  = source chain ID
  toChain    = destination chain ID (same or different)
  fromToken  = token you're starting with
  toToken    = VAULT TOKEN ADDRESS (this triggers Composer)
  fromAmount = amount in smallest unit
  fromAddress = your wallet
  toAddress   = your wallet (receives vault tokens)
The only Composer-specific detail is that toToken must be a vault token address from a supported protocol. Everything else is identical to a standard LI.FI swap or bridge request.

Next Steps

Withdrawals

Withdraw from protocol positions via Composer

Cross-Chain Patterns

Bridge + deposit patterns, status polling, and partial failure handling

Supported Protocols

Full protocol list with example token addresses

API Integration

Step-by-step REST API integration walkthrough