> ## 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.

# Quickstart

> Execute your first Composer transaction in under 5 minutes. Deposit USDC into a Morpho vault on Base with a single API call.

This quickstart walks you through a same-chain Composer transaction, depositing USDC into a Morpho vault on Base. By the end, you'll understand the full flow of requesting a quote, approving tokens, executing the transaction, and tracking status.

Composer uses standard LI.FI endpoints. Set `toToken` to a supported protocol token address and the routing engine returns a Composer route automatically.

<Tip>
  This quickstart covers a same-chain deposit. Composer also supports
  cross-chain deposits across EVM chains. See [Cross-Chain Composer
  Patterns](/composer/lifi-api/guides/cross-chain-compose) for those flows.
</Tip>

## Prerequisites

* A wallet address with USDC on Base (even a small amount like 1 USDC works)
* Node.js 18+ (for the TypeScript examples) or `curl`

***

<Steps>
  <Step title="Get a Composer quote">
    Request a quote with `toToken` set to the vault token address:

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

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

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

      const getQuote = async (
        fromChain: number,
        toChain: number,
        fromToken: string,
        toToken: string,
        fromAmount: string,
        fromAddress: string,
      ) => {
        const result = await axios.get(`${API_URL}/quote`, {
          params: {
            fromChain,
            toChain,
            fromToken,
            toToken,
            fromAmount,
            fromAddress,
            toAddress: fromAddress,
          },
        });
        return result.data;
      };

      const quote = await getQuote(
        8453, // Base
        8453, // Base (same-chain)
        "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
        "0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A", // Morpho vault token
        "1000000", // 1 USDC (6 decimals)
        "0xYOUR_WALLET_ADDRESS",
      );

      console.log(quote);
      ```
    </CodeGroup>

    **Key parameters:**

    | Parameter    | Value           | Description                |
    | ------------ | --------------- | -------------------------- |
    | `fromChain`  | `8453`          | Base chain ID              |
    | `toChain`    | `8453`          | Base (same-chain deposit)  |
    | `fromToken`  | `0x8335...2913` | USDC on Base               |
    | `toToken`    | `0x7BfA...34A`  | Morpho vault token address |
    | `fromAmount` | `1000000`       | 1 USDC (6 decimals)        |

    <Tip>
      The `toToken` is always the **vault token address** of the target protocol. You can find vault token addresses on the protocol's own app or documentation.
    </Tip>

    The response includes `transactionRequest`, a ready-to-sign EVM transaction, along with estimated output amounts and the tools used. The `tool` field will be `"composer"` for Composer routes.

    <Tip>
      Quotes reflect current market conditions. If the user reviews a quote for more than 30 seconds, re-fetch before signing to get up-to-date pricing and simulation results.
    </Tip>
  </Step>

  <Step title="Set token allowance">
    Before executing, ensure the LI.FI Diamond contract is approved to spend your tokens. The approval address is returned in the quote response at `quote.estimate.approvalAddress`. This points to the [LI.FI Diamond](https://etherscan.io/address/0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE), an audited contract deployed on all supported chains.

    <Note>
      If you're sending a native token (e.g., ETH), skip this step. Native tokens don't require approval.
    </Note>

    <CodeGroup>
      ```ts TypeScript theme={"system"}
      import { erc20Abi, type Address } from "viem";
      import type { PublicClient, WalletClient } from "viem";

      const checkAndSetAllowance = async (
        publicClient: PublicClient,
        walletClient: WalletClient,
        tokenAddress: Address,
        approvalAddress: Address,
        amount: bigint,
      ) => {
        const [account] = await walletClient.getAddresses();
        const allowance = await publicClient.readContract({
          address: tokenAddress,
          abi: erc20Abi,
          functionName: "allowance",
          args: [account, approvalAddress],
        });

        if (allowance < amount) {
          const hash = await walletClient.writeContract({
            address: tokenAddress,
            abi: erc20Abi,
            functionName: "approve",
            args: [approvalAddress, amount],
            account,
            chain: walletClient.chain,
          });
          await publicClient.waitForTransactionReceipt({ hash });
          console.log("Approval set.");
        } else {
          console.log("Allowance already sufficient.");
        }
      };

      await checkAndSetAllowance(
        publicClient,
        walletClient,
        quote.action.fromToken.address as Address,
        quote.estimate.approvalAddress as Address,
        BigInt(quote.action.fromAmount),
      );
      ```
    </CodeGroup>

    <Warning>
      If an approval transaction was sent, re-fetch the quote before executing. The `transactionRequest` returned in the original quote contains gas estimates that can become stale by the time the approval confirms. Call `GET /v1/quote` again with the same parameters to get a fresh transaction.
    </Warning>
  </Step>

  <Step title="Execute the transaction">
    Send the transaction using the `transactionRequest` object from the quote response.

    <CodeGroup>
      ```ts TypeScript theme={"system"}
      import { type Address, type Hex } from "viem";

      const [account] = await walletClient.getAddresses();

      const hash = await walletClient.sendTransaction({
        account,
        to: quote.transactionRequest.to as Address,
        data: quote.transactionRequest.data as Hex,
        value: BigInt(quote.transactionRequest.value),
        gas: BigInt(quote.transactionRequest.gasLimit),
        gasPrice: BigInt(quote.transactionRequest.gasPrice),
        chain: walletClient.chain,
      });
      console.log("Transaction sent:", hash);

      const receipt = await publicClient.waitForTransactionReceipt({ hash });
      console.log("Transaction confirmed:", receipt.transactionHash);
      ```
    </CodeGroup>

    That's it. Composer handles the swap and deposit in a single atomic transaction.
  </Step>

  <Step title="Track the status">
    For same-chain transactions, the transaction is complete once confirmed. For cross-chain transfers, poll the `/status` endpoint until the transfer reaches `DONE` or `FAILED`:

    <CodeGroup>
      ```ts TypeScript theme={"system"}
      const getStatus = async (
        txHash: string,
        fromChain: number,
        toChain: number,
      ) => {
        const result = await axios.get(`${API_URL}/status`, {
          params: { txHash, fromChain, toChain },
        });
        return result.data;
      };

      // For cross-chain transfers, poll until complete
      if (quote.action.fromChainId !== quote.action.toChainId) {
        let status;
        do {
          status = await getStatus(
            hash,
            quote.action.fromChainId,
            quote.action.toChainId,
          );
          console.log("Status:", status.status, status.substatus);

          if (status.status !== "DONE" && status.status !== "FAILED") {
            await new Promise((resolve) => setTimeout(resolve, 5000)); // Wait 5s
          }
        } while (status.status !== "DONE" && status.status !== "FAILED");

        console.log("Final status:", status.status);
      }
      ```
    </CodeGroup>

    | Status      | Meaning                                   |
    | ----------- | ----------------------------------------- |
    | `NOT_FOUND` | Transaction submitted but not yet indexed |
    | `INVALID`   | Hash is not tied to the requested tool    |
    | `PENDING`   | Transaction is in progress                |
    | `DONE`      | Completed successfully                    |
    | `FAILED`    | Transaction failed                        |

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

***

## Full Working Example

Copy-paste this complete example to run your first Composer transaction:

<CodeGroup>
  ```ts TypeScript theme={"system"}
  import axios from 'axios';
  import {
    createPublicClient,
    createWalletClient,
    http,
    erc20Abi,
    type Address,
    type Hex,
  } from 'viem';
  import { privateKeyToAccount } from 'viem/accounts';
  import { base } from 'viem/chains';

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

  // --- Configuration ---
  const PRIVATE_KEY = 'YOUR_PRIVATE_KEY';
  const RPC_URL = 'https://mainnet.base.org';
  const FROM_CHAIN = 8453; // Base
  const FROM_TOKEN = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // USDC on Base
  const TO_TOKEN = '0x7BfA7C4f149E7415b73bdeDfe609237e29CBF34A'; // Morpho vault token
  const FROM_AMOUNT = '1000000'; // 1 USDC

  // --- Setup ---
  const account = privateKeyToAccount(PRIVATE_KEY as Hex);
  const publicClient = createPublicClient({ chain: base, transport: http(RPC_URL) });
  const walletClient = createWalletClient({ account, chain: base, transport: http(RPC_URL) });

  // --- Helpers ---
  const getQuote = async (fromAddress: string) => {
    const result = await axios.get(`${API_URL}/quote`, {
      params: {
        fromChain: FROM_CHAIN,
        toChain: FROM_CHAIN,
        fromToken: FROM_TOKEN,
        toToken: TO_TOKEN,
        fromAmount: FROM_AMOUNT,
        fromAddress,
        toAddress: fromAddress,
      },
    });
    return result.data;
  };

  const ensureAllowance = async (
    tokenAddress: Address,
    approvalAddress: Address,
    amount: bigint
  ) => {
    const allowance = await publicClient.readContract({
      address: tokenAddress,
      abi: erc20Abi,
      functionName: 'allowance',
      args: [account.address, approvalAddress],
    });

    if (allowance < amount) {
      console.log('Setting allowance...');
      const hash = await walletClient.writeContract({
        address: tokenAddress,
        abi: erc20Abi,
        functionName: 'approve',
        args: [approvalAddress, amount],
      });
      await publicClient.waitForTransactionReceipt({ hash });
      console.log('Allowance set.');
    }
  };

  // --- Main ---
  const run = async () => {
    console.log('Wallet:', account.address);

    // 1. Get quote
    console.log('Requesting Composer quote...');
    const quote = await getQuote(account.address);
    console.log('Quote received. Tool:', quote.tool);
    console.log('Estimated output:', quote.estimate.toAmount);

    // 2. Approve
    await ensureAllowance(
      quote.action.fromToken.address as Address,
      quote.estimate.approvalAddress as Address,
      BigInt(quote.action.fromAmount)
    );

    // 3. Execute
    console.log('Sending transaction...');
    const hash = await walletClient.sendTransaction({
      to: quote.transactionRequest.to as Address,
      data: quote.transactionRequest.data as Hex,
      value: BigInt(quote.transactionRequest.value),
      gas: BigInt(quote.transactionRequest.gasLimit),
      gasPrice: BigInt(quote.transactionRequest.gasPrice),
    });
    console.log('Tx hash:', hash);

    const receipt = await publicClient.waitForTransactionReceipt({ hash });
    console.log('Confirmed in block:', receipt.blockNumber);
    console.log('Done! USDC deposited into Morpho vault.');
  };

  run().catch(console.error);
  ```
</CodeGroup>

***

## What Just Happened?

Behind the scenes, Composer:

1. **Identified the optimal path.** LI.FI's routing engine determined the best way to convert USDC into the Morpho vault token.
2. **Activated Composer from `toToken`.** The vault token destination signaled a Composer-capable route.
3. **Compiled eDSL instructions.** The Composer compiler generated bytecode for the onchain VM.
4. **Simulated the execution.** The full path was simulated before returning the quote, ensuring it will succeed.
5. **Executed atomically.** Your single transaction swapped USDC and deposited into Morpho in one atomic operation.

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Cross-Chain Composer" icon="bridge" href="/composer/lifi-api/guides/cross-chain-compose">
    Bridge + deposit and swap + bridge + deposit across EVM chains
  </Card>

  <Card title="Withdrawals Guide" icon="clock" href="/composer/lifi-api/guides/withdrawals">
    Add the other half of the user journey with withdraw flows
  </Card>

  <Card title="SDK Integration" icon="cube" href="/composer/lifi-api/guides/sdk-integration">
    Use the LI.FI SDK for managed execution with hooks and events
  </Card>

  <Card title="Vault Deposit Recipes" icon="book" href="/composer/lifi-api/recipes/vault-deposits">
    Copy-paste recipes for Morpho, Aave, Euler, and more
  </Card>
</CardGroup>
