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

# Cross-chain swaps with LI.FI

> Learn how to integrate LI.FI's cross-chain bridge aggregator with Dynamic's MPC embedded wallets for seamless multi-chain token swaps

## What We're Building

A React (Next.js) app that connects Dynamic's MPC wallets to LI.FI's cross-chain bridge aggregator, allowing users to:

* Swap tokens across different blockchain networks
* Find optimal routes with best rates and fees
* Execute cross-chain transactions seamlessly
* Monitor transaction progress across chains

If you want to take a quick look at the final code, check out the [GitHub repository](https://github.com/dynamic-labs/examples/tree/main/examples/nextjs-bridge-swaps-lifi).

<div style={{ position: "relative", paddingBottom: "56.25%", height: 0 }}>
  <iframe src="https://jumpshare.com/embed/3RvO5LRtvUvW7obkiB38" frameBorder="0" allowFullScreen style={{ position: "absolute", top: 0, left: 0, width: "100%", height: "100%" }} title="Aave V3 Integration Demo" />
</div>

## Building the Application

### Project setup

Follow the [React Quickstart](/react/reference/quickstart) and use the **Custom setup** path: **Ethereum (EVM)** with **Wagmi** and **viem**. This recipe’s example uses **Next.js**—scaffold with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app) and apply the same packages and provider wiring as in the quickstart, or mirror the layout in the [GitHub repository](https://github.com/dynamic-labs/examples/tree/main/examples/nextjs-bridge-swaps-lifi) linked above.

<Info>
  **Dashboard:** Under **Chains & Networks**, enable **every EVM network** your LI.FI routes use (not only Ethereum mainnet). Under **Sign-in Methods** and **Wallets**, enable what your flow needs (including **Embedded wallets**). Under **Security** → **Allowed Origins**, add the origin where the app runs (for example `http://localhost:3000`).
</Info>

### Install LI.FI Dependencies

Add the LI.FI SDK:

<CodeGroup>
  ```bash npm  theme={"system"}
  npm install @lifi/sdk @lifi/wallet-management 
  ```

  ```bash yarn theme={"system"}
  yarn add @lifi/sdk @lifi/wallet-management
  ```

  ```bash pnpm theme={"system"}
  pnpm add @lifi/sdk @lifi/wallet-management
  ```

  ```bash bun theme={"system"}
  bun add @lifi/sdk @lifi/wallet-management
  ```
</CodeGroup>

This installs the LI.FI SDK for cross-chain routing and execution capabilities.

### Configure Environment

Create a `.env.local` file with your Dynamic environment ID:

```env .env.local theme={"system"}
NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID=your-environment-id-here
NEXT_PUBLIC_LIFI_API_KEY=your-lifi-api-key-here
```

Set your Dynamic environment ID and LI.FI API key for the integration.

#### Get LI.FI API Key with Improved Rate Limits

To get an API key with improved rate limits, you need to create an integration on the [LI.FI Partner Portal](https://portal.li.fi/integrations).

### Enable Transaction Simulation (Optional)

1. Go to your Dynamic dashboard
2. Navigate to **Developer Settings** → **Embedded Wallets** → **Dynamic**
3. Enable **"Show Confirmation UI"** and **"Transaction Simulation"** toggles

This enables transaction previews showing asset transfers, fees, gas costs, and contract addresses before execution.

<Frame>
  <img src="https://mintcdn.com/dynamic-docs-testing/UE-XnPYRwgMqTMGV/images/dashboard/dashboard-enable-transaction-simulation.png?fit=max&auto=format&n=UE-XnPYRwgMqTMGV&q=85&s=c2846250532555c22c82ae987e96ea6a" alt="" width="1892" height="668" data-path="images/dashboard/dashboard-enable-transaction-simulation.png" />
</Frame>

When enabled, users will see a comprehensive transaction preview like this when swapping tokens:

<Frame>
  <img className="h-80" src="https://mintcdn.com/dynamic-docs-testing/L6Baf9nHRMN5_MjI/images/widget/widget-transaction-simulation.png?fit=max&auto=format&n=L6Baf9nHRMN5_MjI&q=85&s=28de3ba6804410e0cbea6bd99b943ee0" width="764" height="1154" data-path="images/widget/widget-transaction-simulation.png" />
</Frame>

**Note:** These features are optional. You can disable the toggles if you prefer a more streamlined experience without transaction previews and confirmation UI.

### Create LI.FI Client

Create `src/lib/lifi.ts` to configure the LI.FI SDK:

```typescript src/lib/lifi.ts theme={"system"}
import { ChainType, EVM, createConfig, getChains } from "@lifi/sdk";
import { getWalletClient, switchChain } from "@wagmi/core";
import type { Config } from "wagmi";

export const initializeLiFiConfig = (wagmiConfig: Config) => {
  return createConfig({
    integrator: "Dynamic",
    providers: [
      EVM({
        getWalletClient: () => {
          const client = getWalletClient(wagmiConfig);
          return client;
        },
        switchChain: async (chainId) => {
          try {
            const chain = await switchChain(wagmiConfig, { chainId });
            const client = getWalletClient(wagmiConfig, { chainId: chain.id });
            return client;
          } catch (error) {
            throw error;
          }
        },
      }),
    ],
    apiKey: process.env.NEXT_PUBLIC_LIFI_API_KEY,
  });
};

export const loadLiFiChains = async () => {
  try {
    const chains = await getChains({
      chainTypes: [ChainType.EVM],
    });
    return chains;
  } catch {
    return [];
  }
};
```

Here, we've created a `loadLiFiChains` function that fetches EVM chains from LI.FI. The other function `initializeLiFiConfig` is used to initialize the LI.FI SDK with wagmi, whose state is managed by Dynamic.

### Create LI.FI Provider

Create `src/lib/lifi-provider.tsx` to wrap the LI.FI configuration:

```typescript src/lib/lifi-provider.tsx theme={"system"}
"use client";

import { config as lifiConfig } from "@lifi/sdk";
import { useSyncWagmiConfig } from "@lifi/wallet-management";
import { useQuery } from "@tanstack/react-query";
import { type FC, type PropsWithChildren, useEffect, useState } from "react";
import type { Config, CreateConnectorFn } from "wagmi";
import { initializeLiFiConfig, loadLiFiChains } from "./lifi";
import { useDynamicContext } from "@dynamic-labs/sdk-react-core";

interface LiFiProviderProps extends PropsWithChildren {
  wagmiConfig: Config;
  connectors: CreateConnectorFn[];
}

export const LiFiProvider: FC<LiFiProviderProps> = ({
  children,
  wagmiConfig,
  connectors,
}) => {
  const { sdkHasLoaded } = useDynamicContext();
  const [isInitialized, setIsInitialized] = useState(false);

  const {
    data: chains,
    error: chainsError,
    isLoading: chainsLoading,
  } = useQuery({
    queryKey: ["lifi-chains"] as const,
    queryFn: async () => {
      const chains = await loadLiFiChains();

      if (chains.length > 0) {
        lifiConfig.setChains(chains);
      }
      return chains;
    },
    staleTime: 5 * 60 * 1000,
    gcTime: 10 * 60 * 1000,
    retry: 3,
    retryDelay: 1000,
    enabled: sdkHasLoaded,
  });

  useEffect(() => {
    if (sdkHasLoaded && !isInitialized) {
      try {
        initializeLiFiConfig(wagmiConfig);
        setIsInitialized(true);
      } catch {
        setIsInitialized(false);
      }
    }
  }, [sdkHasLoaded, wagmiConfig, isInitialized]);

  useSyncWagmiConfig(wagmiConfig, connectors, chains);

  if (chainsLoading || !sdkHasLoaded || !isInitialized) {
    return (
      <div
        style={{
          display: "flex",
          justifyContent: "center",
          alignItems: "center",
          height: "100px",
          fontSize: "14px",
          opacity: 0.7,
        }}
      >
        {!sdkHasLoaded
          ? "Loading Dynamic SDK..."
          : chainsLoading
          ? "Loading LI.FI chains..."
          : "Initializing LI.FI..."}
      </div>
    );
  }

  if (chainsError) {
    return (
      <div
        style={{
          display: "flex",
          justifyContent: "center",
          alignItems: "center",
          height: "100px",
          fontSize: "14px",
          color: "#ef4444",
        }}
      >
        Failed to load LI.FI chains. Please refresh the page.
      </div>
    );
  }

  return <>{children}</>;
};
```

Here, we have created a `LiFiProvider` component that can get the chains from LI.FI and initialize the LI.FI SDK with wagmi. We also call the `useSyncWagmiConfig` function to sync the wagmi config with the LI.FI SDK.

### Configure Providers

Update `src/lib/providers.tsx` to include the LI.FI configuration:

```typescript src/lib/providers.tsx highlight={3,10,14,27,29} theme={"system"}
"use client";

import { LiFiProvider } from "@/lib/lifi-provider";
import { config } from "@/lib/wagmi";
import { EthereumWalletConnectors } from "@dynamic-labs/ethereum";
import { DynamicContextProvider } from "@dynamic-labs/sdk-react-core";
import { DynamicWagmiConnector } from "@dynamic-labs/wagmi-connector";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { WagmiProvider } from "wagmi";
import type { CreateConnectorFn } from "wagmi";

export default function Providers({ children }: { children: React.ReactNode }) {
  const queryClient = new QueryClient();
  const connectors: CreateConnectorFn[] = [];

  return (
    <DynamicContextProvider
      theme="auto"
      settings={{
        environmentId: process.env.NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID!,
        walletConnectors: [EthereumWalletConnectors],
      }}
    >
      <WagmiProvider config={config}>
        <QueryClientProvider client={queryClient}>
          <DynamicWagmiConnector>
            <LiFiProvider wagmiConfig={config} connectors={connectors}>
              {children}
            </LiFiProvider>
          </DynamicWagmiConnector>
        </QueryClientProvider>
      </WagmiProvider>
    </DynamicContextProvider>
  );
}
```

This sets up Dynamic wallet providers with LI.FI integration and React Query for state management.

### Create Multi-Chain Swap Component

Create `src/components/MultiChainSwap.tsx` for the main swap interface:

```typescript src/components/MultiChainSwap.tsx theme={"system"}
import { useDynamicContext } from "@dynamic-labs/sdk-react-core";
import {
  executeRoute,
  getChains,
  getRoutes,
  getTokens,
  type Route,
  type Token,
} from "@lifi/sdk";
import { useEffect, useState } from "react";
import { formatUnits, parseUnits } from "viem";

interface SimpleChain {
  id: number;
  name: string;
}

interface SwapState {
  fromChain: SimpleChain | null;
  toChain: SimpleChain | null;
  fromToken: Token | null;
  toToken: Token | null;
  amount: string;
  routes: Route[];
  selectedRoute: Route | null;
  isLoading: boolean;
  error: string | null;
  txHash: string | null;
}

export default function MultiChainSwap() {
  const { primaryWallet, sdkHasLoaded } = useDynamicContext();
  const isConnected = !!primaryWallet;
  const address = primaryWallet?.address;
  const isReady = sdkHasLoaded && isConnected && !!address;

  const [swapState, setSwapState] = useState<SwapState>({
    fromChain: null,
    toChain: null,
    fromToken: null,
    toToken: null,
    amount: "0.001",
    routes: [],
    selectedRoute: null,
    isLoading: false,
    error: null,
    txHash: null,
  });

  const [chains, setChains] = useState<SimpleChain[]>([]);
  const [fromTokens, setFromTokens] = useState<Token[]>([]);
  const [toTokens, setToTokens] = useState<Token[]>([]);

  useEffect(() => {
    if (!isReady) return;

    const fetchChains = async () => {
      try {
        const availableChains = await getChains();
        const simpleChains: SimpleChain[] = availableChains.map((chain) => ({
          id: chain.id,
          name: chain.name,
        }));
        setChains(simpleChains);

        if (simpleChains.length >= 2) {
          setSwapState((prev) => ({
            ...prev,
            fromChain: simpleChains[0],
            toChain: simpleChains[1],
          }));
        }
      } catch {
        setSwapState((prev) => ({
          ...prev,
          error: "Failed to fetch available chains",
        }));
      }
    };

    fetchChains();
  }, [isReady]);

  useEffect(() => {
    if (!swapState.fromChain || !swapState.toChain || !isReady) return;

    const fetchTokens = async () => {
      try {
        const fromChainId = swapState.fromChain?.id;
        const toChainId = swapState.toChain?.id;

        if (!fromChainId || !toChainId) return;

        const [fromTokensResponse, toTokensResponse] = await Promise.all([
          getTokens({ chains: [fromChainId] }),
          getTokens({ chains: [toChainId] }),
        ]);

        const fromTokensList = fromTokensResponse.tokens[fromChainId] || [];
        const toTokensList = toTokensResponse.tokens[toChainId] || [];

        setFromTokens(fromTokensList);
        setToTokens(toTokensList);

        if (fromTokensList.length > 0) {
          setSwapState((prev) => ({ ...prev, fromToken: fromTokensList[0] }));
        }
        if (toTokensList.length > 0) {
          setSwapState((prev) => ({ ...prev, toToken: toTokensList[0] }));
        }
      } catch {
        setSwapState((prev) => ({
          ...prev,
          error: "Failed to fetch available tokens",
        }));
      }
    };

    fetchTokens();
  }, [swapState.fromChain, swapState.toChain, isReady]);

  const getRoutesForSwap = async () => {
    if (
      !isReady ||
      !swapState.fromChain ||
      !swapState.toChain ||
      !swapState.fromToken ||
      !swapState.toToken
    ) {
      throw new Error("Not ready");
    }

    try {
      const amountInWei = parseUnits(
        swapState.amount,
        swapState.fromToken.decimals
      );

      const routes = await getRoutes({
        fromChainId: swapState.fromChain.id,
        toChainId: swapState.toChain.id,
        fromTokenAddress: swapState.fromToken.address,
        toTokenAddress: swapState.toToken.address,
        fromAmount: amountInWei.toString(),
        fromAddress: address!,
        toAddress: address!,
        options: {
          order: "CHEAPEST",
          maxPriceImpact: 0.3,
          slippage: 0.005,
          fee: 0.01, // 1% fee
        },
      });

      return routes;
    } catch (error) {
      throw error;
    }
  };

  const handleGetRoutes = async () => {
    if (!isFormValid) {
      setSwapState((prev) => ({
        ...prev,
        error: "Please fill in all required fields and connect wallet",
      }));
      return;
    }

    setSwapState((prev) => ({
      ...prev,
      isLoading: true,
      error: null,
      routes: [],
      selectedRoute: null,
    }));

    try {
      const routesResult = await getRoutesForSwap();
      const availableRoutes = routesResult.routes || [];

      setSwapState((prev) => ({
        ...prev,
        routes: availableRoutes,
        selectedRoute: availableRoutes[0] || null,
        isLoading: false,
        error: availableRoutes.length === 0 ? "No routes found" : null,
      }));
    } catch (error) {
      setSwapState((prev) => ({
        ...prev,
        error: error instanceof Error ? error.message : "Failed to get routes",
        isLoading: false,
      }));
    }
  };

  const handleExecuteSwap = async () => {
    if (!swapState.selectedRoute || !isConnected) {
      setSwapState((prev) => ({
        ...prev,
        error: "No route selected or wallet not connected",
      }));
      return;
    }

    setSwapState((prev) => ({
      ...prev,
      isLoading: true,
      error: null,
      txHash: null,
    }));

    try {
      const result = await executeRoute(swapState.selectedRoute, {
        updateRouteHook: (updatedRoute) => {
          console.log("Route updated:", updatedRoute);
        },
        updateTransactionRequestHook: async (txRequest) => {
          return txRequest;
        },
        acceptExchangeRateUpdateHook: async (params) => {
          const accepted = window.confirm(
            `Exchange rate has changed!\nOld amount: ${formatUnits(
              BigInt(params.oldToAmount),
              params.toToken.decimals
            )} ${params.toToken.symbol}\nNew amount: ${formatUnits(
              BigInt(params.newToAmount),
              params.toToken.decimals
            )} ${params.toToken.symbol}\n\nDo you want to continue?`
          );
          return accepted;
        },
        switchChainHook: async (chainId) => {
          try {
            if (primaryWallet?.connector.supportsNetworkSwitching()) {
              await primaryWallet.switchNetwork(chainId);
            }
            return undefined;
          } catch (error) {
            throw error;
          }
        },
        executeInBackground: false,
        disableMessageSigning: false,
      });

      setSwapState((prev) => ({
        ...prev,
        isLoading: false,
        txHash: "Transaction executed successfully",
      }));
    } catch (error) {
      setSwapState((prev) => ({
        ...prev,
        error:
          error instanceof Error ? error.message : "Failed to execute swap",
        isLoading: false,
      }));
    }
  };

  const isFormValid = !!(
    swapState.fromChain &&
    swapState.toChain &&
    swapState.fromToken &&
    swapState.toToken &&
    swapState.amount &&
    isConnected
  );

  if (!isReady) {
    return (
      <div className="min-h-screen bg-gradient-to-br from-gray-50 to-blue-50 p-6">
        <div className="max-w-6xl mx-auto">
          <div className="text-center py-20">
            <div className="w-16 h-16 bg-blue-500 rounded-full flex items-center justify-center mx-auto mb-4">
              <div className="w-8 h-8 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
            </div>
            <p className="text-xl text-gray-600">
              Loading wallet connection...
            </p>
          </div>
        </div>
      </div>
    );
  }

  if (!isConnected) {
    return (
      <div className="min-h-screen bg-gradient-to-br from-gray-50 to-blue-50 p-6">
        <div className="max-w-6xl mx-auto">
          <div className="text-center py-20">
            <h2 className="text-2xl font-bold text-gray-900 mb-4">
              Connect Your Wallet
            </h2>
            <p className="text-gray-600 mb-8 max-w-md mx-auto">
              Please connect your wallet to use the multi-chain swap feature.
            </p>
          </div>
        </div>
      </div>
    );
  }

  return (
    <div className="max-w-4xl mx-auto p-6 space-y-6">
      <h1 className="text-3xl font-bold text-center mb-8">Cross-Chain Swap</h1>

      <div className="bg-white p-6 rounded-lg shadow-md space-y-4">
        <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
          <div>
            <label className="block text-sm font-medium text-gray-700 mb-2">
              From Chain
            </label>
            <select
              value={swapState.fromChain?.id || ""}
              onChange={(e) => {
                const chain = chains.find(
                  (c) => c.id === Number(e.target.value)
                );
                setSwapState((prev) => ({
                  ...prev,
                  fromChain: chain || null,
                  fromToken: null,
                }));
              }}
              className="w-full p-2 border border-gray-300 rounded"
            >
              <option value="">Select chain</option>
              {chains.map((chain) => (
                <option key={chain.id} value={chain.id}>
                  {chain.name}
                </option>
              ))}
            </select>
          </div>

          <div>
            <label className="block text-sm font-medium text-gray-700 mb-2">
              To Chain
            </label>
            <select
              value={swapState.toChain?.id || ""}
              onChange={(e) => {
                const chain = chains.find(
                  (c) => c.id === Number(e.target.value)
                );
                setSwapState((prev) => ({
                  ...prev,
                  toChain: chain || null,
                  toToken: null,
                }));
              }}
              className="w-full p-2 border border-gray-300 rounded"
            >
              <option value="">Select chain</option>
              {chains.map((chain) => (
                <option key={chain.id} value={chain.id}>
                  {chain.name}
                </option>
              ))}
            </select>
          </div>
        </div>

        <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
          <div>
            <label className="block text-sm font-medium text-gray-700 mb-2">
              From Token
            </label>
            <select
              value={swapState.fromToken?.address || ""}
              onChange={(e) => {
                const token = fromTokens.find(
                  (t) => t.address === e.target.value
                );
                setSwapState((prev) => ({ ...prev, fromToken: token || null }));
              }}
              className="w-full p-2 border border-gray-300 rounded"
            >
              <option value="">Select token</option>
              {fromTokens.map((token) => (
                <option key={token.address} value={token.address}>
                  {token.symbol} - {token.name}
                </option>
              ))}
            </select>
          </div>

          <div>
            <label className="block text-sm font-medium text-gray-700 mb-2">
              To Token
            </label>
            <select
              value={swapState.toToken?.address || ""}
              onChange={(e) => {
                const token = toTokens.find(
                  (t) => t.address === e.target.value
                );
                setSwapState((prev) => ({ ...prev, toToken: token || null }));
              }}
              className="w-full p-2 border border-gray-300 rounded"
            >
              <option value="">Select token</option>
              {toTokens.map((token) => (
                <option key={token.address} value={token.address}>
                  {token.symbol} - {token.name}
                </option>
              ))}
            </select>
          </div>
        </div>

        <div>
          <label className="block text-sm font-medium text-gray-700 mb-2">
            Amount
          </label>
          <input
            type="number"
            value={swapState.amount}
            onChange={(e) =>
              setSwapState((prev) => ({ ...prev, amount: e.target.value }))
            }
            placeholder="Enter amount"
            className="w-full p-2 border border-gray-300 rounded"
          />
        </div>

        <div className="flex gap-4">
          <button
            onClick={handleGetRoutes}
            disabled={!isFormValid || swapState.isLoading}
            className="flex-1 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 text-white py-2 px-4 rounded"
          >
            {swapState.isLoading ? "Loading..." : "Find Routes"}
          </button>

          {swapState.routes.length > 0 && (
            <button
              onClick={handleExecuteSwap}
              disabled={!swapState.selectedRoute || swapState.isLoading}
              className="flex-1 bg-green-600 hover:bg-green-700 disabled:bg-gray-400 text-white py-2 px-4 rounded"
            >
              Execute Swap
            </button>
          )}
        </div>
      </div>

      {swapState.error && (
        <div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
          {swapState.error}
        </div>
      )}

      {swapState.txHash && (
        <div className="bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded">
          {swapState.txHash}
        </div>
      )}

      {swapState.routes.length > 0 && (
        <div className="bg-white p-6 rounded-lg shadow-md">
          <h3 className="text-lg font-semibold mb-4">Available Routes</h3>
          <div className="space-y-3">
            {swapState.routes.map((route, index) => (
              <div
                key={index}
                className={`p-3 border rounded cursor-pointer ${
                  swapState.selectedRoute === route
                    ? "border-blue-500 bg-blue-50"
                    : "border-gray-200 hover:border-gray-300"
                }`}
                onClick={() =>
                  setSwapState((prev) => ({ ...prev, selectedRoute: route }))
                }
              >
                <div className="flex justify-between items-center">
                  <span className="font-medium">
                    Route {index + 1} - {route.steps.length} steps
                  </span>
                  <span className="text-sm text-gray-600">
                    {route.toAmount
                      ? `${formatUnits(
                          BigInt(route.toAmount),
                          swapState.toToken?.decimals || 18
                        )} ${swapState.toToken?.symbol}`
                      : "Calculating..."}
                  </span>
                </div>
                <div className="text-sm text-gray-500 mt-1">
                  Estimated time: {route.estimatedTime}s
                </div>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}
```

We've created several UI components that you can check out in the [GitHub repository](https://github.com/dynamic-labs/examples/tree/main/examples/nextjs-bridge-swaps-lifi). All the functionality is in the `MultiChainSwap` component. Let's break it down and see how it
works.

## How It Works

### Data Flow

1. **Chains**: Gets available networks from LI.FI when the component loads
2. **Tokens**: Fetches tokens for selected chains (updates when chains change)
3. **Routes**: Finds swap paths when user selects tokens and enters amount
4. **Execution**: Runs the swap using the selected route

### Key Parameters You Can Customize

**Swap Options** (in `getRoutes`):

```typescript theme={"system"}
options: {
  order: "CHEAPEST",        // "CHEAPEST", "FASTEST", "RECOMMENDED"
  maxPriceImpact: 0.3,      // Maximum price impact (30%)
  slippage: 0.005,          // Slippage tolerance (0.5%)
  fee: 0.01                 // Fee percentage (1%)
}
```

**Execution Hooks** (in `executeRoute`):

* `slippage`: How much price can change before failing
* `maxPriceImpact`: Maximum acceptable price impact
* `fee`: Your platform fee percentage

We use `useDynamicContext()` for wallet management, `switchNetwork()` for chain switching, manage state updates through a single state object, and provide comprehensive error handling throughout the swap flow.

### Collecting Fees

We can collect fees from the user by adding a fee to the `getRoutes` function.

```typescript highlight={13} theme={"system"}
const routes = await getRoutes({
  fromChainId: swapState.fromChain.id,
  toChainId: swapState.toChain.id,
  fromTokenAddress: swapState.fromToken.address,
  toTokenAddress: swapState.toToken.address,
  fromAmount: amountInWei.toString(),
  fromAddress: address!,
  toAddress: address!,
  options: {
    order: "CHEAPEST",
    maxPriceImpact: 0.3,
    slippage: 0.005,
    fee: 0.01, // 1% fee
  },
});
```

You can set up wallet addresses where you want to collect fees in the [portal dashboard](https://portal.li.fi/integrations).

<img src="https://mintcdn.com/dynamic-docs-testing/duI3BEML43E_Xi7b/images/lifi/lifi-fees-wallet.png?fit=max&auto=format&n=duI3BEML43E_Xi7b&q=85&s=12736518fee71b0a9080a5b483ca48c9" alt="Configure wallets for fees on LI.FI" width="2380" height="1120" data-path="images/lifi/lifi-fees-wallet.png" />

### Run the Application

Start the development server:

<CodeGroup>
  ```bash npm  theme={"system"}
  npm run dev 
  ```

  ```bash yarn theme={"system"}
  yarn dev
  ```

  ```bash pnpm theme={"system"}
  pnpm dev
  ```
</CodeGroup>

This starts the development server at `http://localhost:3000`.

### Configure CORS

Add your local development URL to the CORS origins in your Dynamic dashboard under **Developer Settings** → **CORS Origins**.

## Conclusion

This integration demonstrates Dynamic's MPC wallets connecting to LI.FI's cross-chain bridge aggregator for seamless multi-chain token swaps. The implementation provides comprehensive route management, advanced state handling, and modular architecture for production-ready cross-chain DeFi applications.

### Additional Resources

* [Dynamic React SDK](/react/reference/quickstart)
* [LI.FI Documentation](https://docs.li.fi/)
* [Cross chain swaps with Mayan](/recipes/integrations/swaps/mayan)
