> ## Documentation Index
> Fetch the complete documentation index at: https://docs.li.fi/llms.txt
> Use this file to discover all available pages before exploring further.

# Cross-Chain Composer

> Deposit into any protocol on any EVM chain, starting from wherever your assets are. LI.FI handles the bridge, swaps, and final deposit in one flow.

Composer supports cross-chain deposits across EVM chains. A user on Ethereum can deposit into a Morpho vault on Base, or deposit USDC from Arbitrum into any supported protocol on Base. LI.FI handles bridge selection, intermediate swaps, and the final deposit automatically.

From a developer's perspective, the integration is identical to same-chain: just set `fromChain` and `toChain` to different values.

<Note>
  Cross-chain Composer works across EVM chains today. Non-EVM chains (Solana,
  etc.) are not yet supported.
</Note>

***

## How It Works

Cross-chain Composer combines two LI.FI capabilities:

1. **Bridge routing:** LI.FI selects the optimal bridge to move assets from the source chain to the destination chain.
2. **Composer execution:** On the destination chain, Composer deposits into the target protocol atomically.

From the user's perspective, this is a single flow with one signature on the source chain.

***

## Cross-Chain vs. Same-Chain

|                 | Same-chain              | Cross-chain                                     |
| --------------- | ----------------------- | ----------------------------------------------- |
| Transactions    | 1                       | 2 (source chain + destination chain)            |
| Atomicity       | Fully atomic            | Per-chain atomic; eventually consistent overall |
| Status tracking | Not needed              | Poll `/status` until complete                   |
| Integration     | `fromChain === toChain` | `fromChain !== toChain`                         |

***

## Example: ETH (Ethereum) → Morpho Vault (Base)

Deposit ETH from Ethereum into a Spark-curated Morpho USDC vault on Base. LI.FI bridges ETH, swaps to USDC on Base, and deposits into the vault.

<CodeGroup>
  ```bash curl theme={"system"}
  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&\
  slippage=0.01'
  ```

  ```ts TypeScript theme={"system"}
  import axios from "axios";

  const { data: quote } = await axios.get("https://li.quest/v1/quote", {
    params: {
      fromChain: 1, // Ethereum
      toChain: 8453, // Base
      fromToken: "0x0000000000000000000000000000000000000000", // ETH (native)
      toToken: "0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A", // Morpho vault on Base
      fromAddress: "0xYOUR_WALLET_ADDRESS",
      toAddress: "0xYOUR_WALLET_ADDRESS",
      fromAmount: "100000000000000000", // 0.1 ETH
      slippage: 0.01, // 1% for cross-chain
    },
  });

  // No approval needed for native ETH; submit directly
  const tx = await signer.sendTransaction(quote.transactionRequest);
  await tx.wait();
  console.log("Source chain tx confirmed:", tx.hash);

  // Poll status until the cross-chain transfer completes
  let status;
  do {
    const { data } = await axios.get("https://li.quest/v1/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.status);
  // User now holds Morpho vault tokens on Base
  ```
</CodeGroup>

***

## Example: USDC (Arbitrum) → Morpho Vault (Base)

Bridge USDC from Arbitrum and deposit into the Spark-curated Morpho vault on Base in a single flow.

<CodeGroup>
  ```bash curl theme={"system"}
  curl -X GET 'https://li.quest/v1/quote?fromChain=42161&toChain=8453&fromToken=0xaf88d065e77c8cC2239327C5EDb3A432268e5831&toToken=0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A&fromAddress=0xYOUR_WALLET_ADDRESS&toAddress=0xYOUR_WALLET_ADDRESS&fromAmount=10000000'
  ```

  ```ts TypeScript theme={"system"}
  const { data: quote } = await axios.get("https://li.quest/v1/quote", {
    params: {
      fromChain: 42161, // Arbitrum
      toChain: 8453, // Base
      fromToken: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // USDC on Arbitrum
      toToken: "0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A", // Morpho vault on Base
      fromAddress: "0xYOUR_WALLET_ADDRESS",
      toAddress: "0xYOUR_WALLET_ADDRESS",
      fromAmount: "1000000000", // 1000 USDC (6 decimals)
      slippage: 0.01,
    },
  });

  // Approve USDC, send tx, and poll status (same pattern as above)
  ```
</CodeGroup>

**What may happen under the hood:**

1. Bridge USDC from Arbitrum to Base
2. Deposit USDC into Morpho vault on Base
3. User receives vault tokens

The exact steps depend on the route returned by the API. The routing engine may choose a different bridge or insert intermediate swaps if that produces a better outcome.

### Swap + Bridge + Deposit

User has a different token on a different chain.

**Example:** ETH on Ethereum → Aave USDC lending on Optimism

<CodeGroup>
  ```bash curl theme={"system"}
  curl -X GET 'https://li.quest/v1/quote?fromChain=1&toChain=10&fromToken=0x0000000000000000000000000000000000000000&toToken=AAVE_AUSDC_TOKEN_ADDRESS_ON_OPTIMISM&fromAddress=0xYOUR_WALLET_ADDRESS&toAddress=0xYOUR_WALLET_ADDRESS&fromAmount=100000000000000000'
  ```

  ```ts TypeScript theme={"system"}
  const quote = await getQuote({
    fromChain: 1, // Ethereum
    toChain: 10, // Optimism
    fromToken: "0x0000000000000000000000000000000000000000", // ETH (native)
    toToken: "AAVE_AUSDC_TOKEN_ADDRESS_ON_OPTIMISM", // Aave aUSDC on Optimism
    fromAmount: "100000000000000000", // 0.1 ETH
    fromAddress: "0xYOUR_WALLET_ADDRESS",
  });
  ```
</CodeGroup>

**What may happen under the hood:**

1. Swap ETH → USDC on Ethereum, then bridge USDC to Optimism, or bridge ETH directly and swap on the destination chain
2. Deposit USDC into Aave on Optimism
3. User receives aUSDC tokens

The routing engine decides the optimal sequence. You will see the chosen path in the route response.

### Bridge + Swap + Stake

User wants to stake on a different chain.

**Example:** USDC on Arbitrum → wstETH (Lido) on Ethereum

<CodeGroup>
  ```ts TypeScript theme={"system"}
  const quote = await getQuote({
    fromChain: 42161,                                             // Arbitrum
    toChain: 1,                                                   // Ethereum
    fromToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',     // USDC on Arbitrum
    toToken: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0',       // wstETH on Ethereum
    fromAmount: '1000000000',                                     // 1000 USDC
    fromAddress: '0xYOUR_WALLET_ADDRESS',
  });
  ```
</CodeGroup>

**What may happen under the hood:**

1. Bridge USDC from Arbitrum to Ethereum
2. Swap USDC → ETH on Ethereum
3. Stake ETH via Lido, wrap to wstETH
4. User receives wstETH

## The routing engine may choose a different sequence (e.g., swap on Arbitrum first, then bridge ETH) if it finds a better path.

## Status Tracking

Cross-chain transfers require status polling. After the source chain transaction confirms, poll `GET /v1/status` until the transfer reaches `DONE` or `FAILED`.

```ts TypeScript theme={"system"}
const pollStatus = async (
  txHash: string,
  fromChain: number,
  toChain: number,
) => {
  let status;
  do {
    const { data } = await axios.get("https://li.quest/v1/status", {
      params: { txHash, fromChain, toChain },
    });
    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");

  return status;
};
```

For the full status and substatus reference, see [Transaction Status Tracking](/introduction/user-flows-and-examples/status-tracking).

***

## Partial Failure Handling

Cross-chain flows are eventually consistent. In rare cases, the bridge step succeeds but the destination deposit fails. When this happens:

* The user receives the bridged tokens on the destination chain rather than vault tokens
* Source chain tokens are not at risk
* Gas fees are consumed

Build your UI to handle the `FAILED` status and prompt users to check their destination chain balance if a cross-chain transfer does not complete.

***

## Next Steps

<CardGroup cols={2}>
  <Card title="API Integration" icon="code" href="/composer/lifi-api/guides/api-integration">
    Full API integration guide with same-chain and cross-chain examples
  </Card>

  <Card title="SDK Integration" icon="cube" href="/composer/lifi-api/guides/sdk-integration">
    Managed cross-chain execution with the LI.FI TypeScript SDK
  </Card>

  <Card title="Deposit Recipes" icon="book" href="/composer/lifi-api/recipes/vault-deposits">
    Copy-paste recipes for common protocols and chains
  </Card>

  <Card title="Supported Protocols" icon="list" href="/composer/protocols-and-chains">
    Protocols available for cross-chain deposits
  </Card>
</CardGroup>
