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

# Relay

> Bridge and swap tokens across chains using Crossmint wallets and Relay

This guide explains how to bridge and swap tokens across chains using the <a href="https://docs.relay.link/" target="_blank">Relay</a> API with Crossmint wallets. Relay is an intent-based cross-chain protocol that aggregates bridges and DEXs to execute transfers across 20+ EVM chains. Both human users and AI agents can use this integration to move assets between blockchains programmatically.

## Prerequisites

To use Relay with Crossmint wallets, you need:

* A Crossmint [wallet](/wallets/guides/create-wallet) on any supported EVM chain
* A **production API Key** with the scope: `wallets:transactions.create` (create in the <a href="https://www.crossmint.com/console" target="_blank">Production Console</a>)

This guide uses mainnet for all examples because testnet bridging support is limited. Start with small test amounts before moving larger sums.

To follow along using Base as the origin chain, you also need:

* ETH on Base mainnet for gas fees (not required if [gas sponsorship](/wallets/guides/gas-sponsorship) is enabled)
* For bridging: ETH on Base
* For swapping: USDC on Base

A Relay API key is optional. Without one, requests are subject to default rate limits. To obtain a key, visit the <a href="https://docs.relay.link/references/api/api-keys#how-to-request-an-api-key" target="_blank">Relay API Keys page</a>.

## Bridge Tokens

Bridging moves the same token from one chain to another. This example bridges native ETH from Base to Arbitrum.

<Note>
  Both the Relay API and Crossmint wallets must support the source and destination chains. Most major EVM chains are supported, but for less common chains, verify support on the <a href="https://docs.relay.link/resources/supported-chains" target="_blank">Relay supported chains page</a> and the <a href="/wallets/guides/create-wallet" target="_blank">Crossmint supported chains list</a> before proceeding.
</Note>

High level steps:

1. Request a bridge quote from the Relay `/quote/v2` endpoint
2. Execute the bridge transaction using the quote calldata
3. Poll the Relay `/intents/status/v3` endpoint until the bridge completes

<Warning>
  Cross-chain bridges primarily operate on mainnet. The wallet must hold sufficient tokens on the source chain and ETH for gas fees. Testnet support varies by route; see the <a href="https://docs.relay.link/resources/supported-chains" target="_blank">Relay supported chains page</a> for availability.
</Warning>

<Tabs>
  <Tab title="React">
    ```typescript theme={null}
    import { useWallet, EVMWallet } from "@crossmint/client-sdk-react-ui";
    import { parseEther } from "viem";

    const RELAY_API = "https://api.relay.link";
    const ORIGIN_CHAIN_ID = 8453; // Base
    const DESTINATION_CHAIN_ID = 42161; // Arbitrum
    const ETH_TOKEN_ADDRESS = "0x0000000000000000000000000000000000000000";

    export function BridgeComponent() {
        const { wallet } = useWallet();

        async function bridge(amount: string) {
            if (!wallet) return;
            const evmWallet = EVMWallet.from(wallet);

            // 1. Get a bridge quote from Relay
            const quoteRes = await fetch(`${RELAY_API}/quote/v2`, {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({
                    user: wallet.address,
                    originChainId: ORIGIN_CHAIN_ID,
                    destinationChainId: DESTINATION_CHAIN_ID,
                    originCurrency: ETH_TOKEN_ADDRESS,
                    destinationCurrency: ETH_TOKEN_ADDRESS,
                    amount: parseEther(amount).toString(),
                    tradeType: "EXACT_INPUT",
                }),
            });
            if (!quoteRes.ok) {
                throw new Error(`Quote request failed: ${quoteRes.status}`);
            }
            const quote = await quoteRes.json();

            // 2. Execute the bridge transaction
            // For native ETH bridges, Relay returns a single deposit step
            const txData = quote.steps[0].items[0].data;
            await evmWallet.sendTransaction({
                to: txData.to,
                data: txData.data,
                value: txData.value ? BigInt(txData.value) : 0n,
            });

            // 3. Poll Relay for bridge status
            const requestId = quote.steps[0].requestId;
            let status = "pending";
            while (status === "pending") {
                await new Promise((r) => setTimeout(r, 5000));
                const statusRes = await fetch(
                    `${RELAY_API}/intents/status/v3?requestId=${requestId}`
                );
                const s = await statusRes.json();
                status = s.status;
            }
            if (status !== "success") {
                throw new Error(`Bridge failed with status: ${status}`);
            }
        }

        return (
            <button onClick={() => bridge("0.001")}>
                Bridge ETH to Arbitrum
            </button>
        );
    }
    ```

    See the [React SDK reference](/sdk-reference/wallets/react/hooks#wallet-methods) for more details.
  </Tab>

  <Tab title="React Native">
    ```typescript theme={null}
    import { useWallet, EVMWallet } from "@crossmint/client-sdk-react-native-ui";
    import { View, Button } from "react-native";
    import { parseEther } from "viem";

    const RELAY_API = "https://api.relay.link";
    const ORIGIN_CHAIN_ID = 8453; // Base
    const DESTINATION_CHAIN_ID = 42161; // Arbitrum
    const ETH_TOKEN_ADDRESS = "0x0000000000000000000000000000000000000000";

    export function BridgeComponent() {
        const { wallet } = useWallet();

        async function bridge(amount: string) {
            if (!wallet) return;
            const evmWallet = EVMWallet.from(wallet);

            // 1. Get a bridge quote from Relay
            const quoteRes = await fetch(`${RELAY_API}/quote/v2`, {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({
                    user: wallet.address,
                    originChainId: ORIGIN_CHAIN_ID,
                    destinationChainId: DESTINATION_CHAIN_ID,
                    originCurrency: ETH_TOKEN_ADDRESS,
                    destinationCurrency: ETH_TOKEN_ADDRESS,
                    amount: parseEther(amount).toString(),
                    tradeType: "EXACT_INPUT",
                }),
            });
            if (!quoteRes.ok) {
                throw new Error(`Quote request failed: ${quoteRes.status}`);
            }
            const quote = await quoteRes.json();

            // 2. Execute the bridge transaction
            // For native ETH bridges, Relay returns a single deposit step
            const txData = quote.steps[0].items[0].data;
            await evmWallet.sendTransaction({
                to: txData.to,
                data: txData.data,
                value: txData.value ? BigInt(txData.value) : 0n,
            });

            // 3. Poll Relay for bridge status
            const requestId = quote.steps[0].requestId;
            let status = "pending";
            while (status === "pending") {
                await new Promise((r) => setTimeout(r, 5000));
                const statusRes = await fetch(
                    `${RELAY_API}/intents/status/v3?requestId=${requestId}`
                );
                const s = await statusRes.json();
                status = s.status;
            }
            if (status !== "success") {
                throw new Error(`Bridge failed with status: ${status}`);
            }
        }

        return (
            <View>
                <Button
                    title="Bridge ETH to Arbitrum"
                    onPress={() => bridge("0.001")}
                />
            </View>
        );
    }
    ```

    See the [React Native SDK reference](/sdk-reference/wallets/react-native/hooks#wallet-methods) for more details.
  </Tab>

  <Tab title="Node.js">
    ```typescript theme={null}
    import { CrossmintWallets, createCrossmint, EVMWallet } from "@crossmint/wallets-sdk";
    import { parseEther } from "viem";

    const crossmint = createCrossmint({
        apiKey: "YOUR_SERVER_API_KEY",
    });
    const crossmintWallets = CrossmintWallets.from(crossmint);

    const wallet = await crossmintWallets.getWallet(
        "email:user@example.com",
        { chain: "base" }
    );
    if (!wallet) throw new Error("Wallet not found");
    await wallet.useSigner({ type: "email", email: "user@example.com" });
    const evmWallet = EVMWallet.from(wallet);

    const RELAY_API = "https://api.relay.link";
    const ORIGIN_CHAIN_ID = 8453; // Base
    const DESTINATION_CHAIN_ID = 42161; // Arbitrum
    const ETH_TOKEN_ADDRESS = "0x0000000000000000000000000000000000000000";
    const amount = "0.001";

    // 1. Get a bridge quote from Relay
    const quoteRes = await fetch(`${RELAY_API}/quote/v2`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
            user: wallet.address,
            originChainId: ORIGIN_CHAIN_ID,
            destinationChainId: DESTINATION_CHAIN_ID,
            originCurrency: ETH_TOKEN_ADDRESS,
            destinationCurrency: ETH_TOKEN_ADDRESS,
            amount: parseEther(amount).toString(),
            tradeType: "EXACT_INPUT",
        }),
    });
    if (!quoteRes.ok) {
        throw new Error(`Quote request failed: ${quoteRes.status}`);
    }
    const quote = await quoteRes.json();

    // 2. Execute the bridge transaction
    // For native ETH bridges, Relay returns a single deposit step
    const txData = quote.steps[0].items[0].data;
    await evmWallet.sendTransaction({
        to: txData.to,
        data: txData.data,
        value: txData.value ? BigInt(txData.value) : 0n,
    });

    // 3. Poll Relay for bridge status
    const requestId = quote.steps[0].requestId;
    let status = "pending";
    while (status === "pending") {
        await new Promise((r) => setTimeout(r, 5000));
        const statusRes = await fetch(
            `${RELAY_API}/intents/status/v3?requestId=${requestId}`
        );
        const s = await statusRes.json();
        status = s.status;
    }
    if (status !== "success") {
        throw new Error(`Bridge failed with status: ${status}`);
    }

    console.log("Bridge complete!");
    ```
  </Tab>

  <Tab title="REST">
    Transactions must be approved by one of the wallet's [signers](/wallets/concepts/signers).
    The SDK handles this automatically, but with the REST API you must [approve the transaction](/api-reference/wallets/approve-transaction) to complete it.

    <Steps>
      <Step title="Get a bridge quote from Relay">
        Call the <a href="https://docs.relay.link/references/api/get-quote-v2" target="_blank">Relay API</a> `/quote/v2` endpoint with the origin and destination chain, token addresses, and amount.

        <CodeGroup>
          ```js Node.js theme={null}
          const response = await fetch(
              "https://api.relay.link/quote/v2",
              {
                  method: "POST",
                  headers: {
                      "Content-Type": "application/json",
                  },
                  body: JSON.stringify({
                      user: "YOUR_WALLET_ADDRESS",
                      originChainId: 8453,
                      destinationChainId: 42161,
                      originCurrency:
                          "0x0000000000000000000000000000000000000000",
                      destinationCurrency:
                          "0x0000000000000000000000000000000000000000",
                      amount: "1000000000000000",
                      tradeType: "EXACT_INPUT",
                  }),
              }
          );
          const quote = await response.json();
          ```

          ```bash cURL theme={null}
          curl --request POST \
              --url 'https://api.relay.link/quote/v2' \
              --header 'Content-Type: application/json' \
              --data '{
                  "user": "YOUR_WALLET_ADDRESS",
                  "originChainId": 8453,
                  "destinationChainId": 42161,
                  "originCurrency": "0x0000000000000000000000000000000000000000",
                  "destinationCurrency": "0x0000000000000000000000000000000000000000",
                  "amount": "1000000000000000",
                  "tradeType": "EXACT_INPUT"
              }'
          ```

          ```python Python theme={null}
          import requests

          response = requests.post(
              "https://api.relay.link/quote/v2",
              json={
                  "user": "YOUR_WALLET_ADDRESS",
                  "originChainId": 8453,
                  "destinationChainId": 42161,
                  "originCurrency": "0x0000000000000000000000000000000000000000",
                  "destinationCurrency": "0x0000000000000000000000000000000000000000",
                  "amount": "1000000000000000",
                  "tradeType": "EXACT_INPUT",
              },
          )
          quote = response.json()
          ```
        </CodeGroup>

        The response includes `steps[]` with transaction calldata and a `requestId` for tracking.
      </Step>

      <Step title="Execute the bridge transaction">
        Use the transaction data from `quote.steps[0].items[0].data` to send the bridge transaction via the Crossmint REST API.

        Follow the steps in the [Send a transaction](/wallets/guides/send-transaction-evm#rest) guide to submit the transaction via the Crossmint REST API.
      </Step>

      <Step title="Poll for bridge status">
        Call the Relay `/intents/status/v3` endpoint every five seconds until the bridge completes.

        <CodeGroup>
          ```js Node.js theme={null}
          const statusRes = await fetch(
              `https://api.relay.link/intents/status/v3?requestId=YOUR_REQUEST_ID`
          );
          const status = await statusRes.json();
          // status.status is "pending", "success", or "failure"
          ```

          ```bash cURL theme={null}
          curl --request GET \
              --url 'https://api.relay.link/intents/status/v3?requestId=YOUR_REQUEST_ID'
          ```

          ```python Python theme={null}
          import requests

          response = requests.get(
              "https://api.relay.link/intents/status/v3",
              params={
                  "requestId": "YOUR_REQUEST_ID",
              },
          )
          status = response.json()
          # status["status"] is "pending", "success", or "failure"
          ```
        </CodeGroup>

        The bridge is complete when `status` is `success`. If `failure`, check the transaction on the block explorer for details.
      </Step>
    </Steps>
  </Tab>
</Tabs>

## Swap Tokens

Swapping exchanges one token for another across chains or within the same chain. This example swaps USDC on Base for USDC on Arbitrum. The same pattern works for any token pair that Relay supports.

For ERC-20 swaps, Relay may return multiple steps — an **approval** step followed by a **deposit** step. The code below iterates through all steps to handle both cases.

High level steps:

1. Request a swap quote from the Relay `/quote/v2` endpoint
2. Execute all steps from the quote (approval + deposit)
3. Poll the Relay `/intents/status/v3` endpoint until the swap completes

<Tabs>
  <Tab title="React">
    ```typescript theme={null}
    import { useWallet, EVMWallet } from "@crossmint/client-sdk-react-ui";
    import { parseUnits } from "viem";

    const RELAY_API = "https://api.relay.link";
    const ORIGIN_CHAIN_ID = 8453; // Base
    const DESTINATION_CHAIN_ID = 42161; // Arbitrum
    const BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
    const ARB_USDC = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831";
    const USDC_DECIMALS = 6;

    export function SwapComponent() {
        const { wallet } = useWallet();

        async function swap(amount: string) {
            if (!wallet) return;
            const evmWallet = EVMWallet.from(wallet);

            // 1. Get a swap quote from Relay
            const quoteRes = await fetch(`${RELAY_API}/quote/v2`, {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({
                    user: wallet.address,
                    originChainId: ORIGIN_CHAIN_ID,
                    destinationChainId: DESTINATION_CHAIN_ID,
                    originCurrency: BASE_USDC,
                    destinationCurrency: ARB_USDC,
                    amount: parseUnits(amount, USDC_DECIMALS).toString(),
                    tradeType: "EXACT_INPUT",
                }),
            });
            if (!quoteRes.ok) {
                throw new Error(`Quote request failed: ${quoteRes.status}`);
            }
            const quote = await quoteRes.json();

            // 2. Execute all steps (approval + deposit)
            for (const step of quote.steps) {
                for (const item of step.items) {
                    if (item.status === "incomplete") {
                        await evmWallet.sendTransaction({
                            to: item.data.to,
                            data: item.data.data,
                            value: item.data.value
                                ? BigInt(item.data.value)
                                : 0n,
                        });
                    }
                }
            }

            // 3. Poll Relay for swap status
            const depositStep = quote.steps.find((s: { id: string }) => s.id === "deposit");
            if (!depositStep) throw new Error("No deposit step found in quote response");
            const requestId = depositStep.requestId;
            let status = "pending";
            while (status === "pending") {
                await new Promise((r) => setTimeout(r, 5000));
                const statusRes = await fetch(
                    `${RELAY_API}/intents/status/v3?requestId=${requestId}`
                );
                const s = await statusRes.json();
                status = s.status;
            }
            if (status !== "success") {
                throw new Error(`Swap failed with status: ${status}`);
            }
        }

        return (
            <button onClick={() => swap("5")}>
                Swap USDC to Arbitrum
            </button>
        );
    }
    ```

    See the [React SDK reference](/sdk-reference/wallets/react/hooks#wallet-methods) for more details.
  </Tab>

  <Tab title="React Native">
    ```typescript theme={null}
    import { useWallet, EVMWallet } from "@crossmint/client-sdk-react-native-ui";
    import { View, Button } from "react-native";
    import { parseUnits } from "viem";

    const RELAY_API = "https://api.relay.link";
    const ORIGIN_CHAIN_ID = 8453; // Base
    const DESTINATION_CHAIN_ID = 42161; // Arbitrum
    const BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
    const ARB_USDC = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831";
    const USDC_DECIMALS = 6;

    export function SwapComponent() {
        const { wallet } = useWallet();

        async function swap(amount: string) {
            if (!wallet) return;
            const evmWallet = EVMWallet.from(wallet);

            // 1. Get a swap quote from Relay
            const quoteRes = await fetch(`${RELAY_API}/quote/v2`, {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({
                    user: wallet.address,
                    originChainId: ORIGIN_CHAIN_ID,
                    destinationChainId: DESTINATION_CHAIN_ID,
                    originCurrency: BASE_USDC,
                    destinationCurrency: ARB_USDC,
                    amount: parseUnits(amount, USDC_DECIMALS).toString(),
                    tradeType: "EXACT_INPUT",
                }),
            });
            if (!quoteRes.ok) {
                throw new Error(`Quote request failed: ${quoteRes.status}`);
            }
            const quote = await quoteRes.json();

            // 2. Execute all steps (approval + deposit)
            for (const step of quote.steps) {
                for (const item of step.items) {
                    if (item.status === "incomplete") {
                        await evmWallet.sendTransaction({
                            to: item.data.to,
                            data: item.data.data,
                            value: item.data.value
                                ? BigInt(item.data.value)
                                : 0n,
                        });
                    }
                }
            }

            // 3. Poll Relay for swap status
            const depositStep = quote.steps.find((s: { id: string }) => s.id === "deposit");
            if (!depositStep) throw new Error("No deposit step found in quote response");
            const requestId = depositStep.requestId;
            let status = "pending";
            while (status === "pending") {
                await new Promise((r) => setTimeout(r, 5000));
                const statusRes = await fetch(
                    `${RELAY_API}/intents/status/v3?requestId=${requestId}`
                );
                const s = await statusRes.json();
                status = s.status;
            }
            if (status !== "success") {
                throw new Error(`Swap failed with status: ${status}`);
            }
        }

        return (
            <View>
                <Button
                    title="Swap USDC to Arbitrum"
                    onPress={() => swap("5")}
                />
            </View>
        );
    }
    ```

    See the [React Native SDK reference](/sdk-reference/wallets/react-native/hooks#wallet-methods) for more details.
  </Tab>

  <Tab title="Node.js">
    ```typescript theme={null}
    import { CrossmintWallets, createCrossmint, EVMWallet } from "@crossmint/wallets-sdk";
    import { parseUnits } from "viem";

    const crossmint = createCrossmint({
        apiKey: "YOUR_SERVER_API_KEY",
    });
    const crossmintWallets = CrossmintWallets.from(crossmint);

    const wallet = await crossmintWallets.getWallet(
        "email:user@example.com",
        { chain: "base" }
    );
    if (!wallet) throw new Error("Wallet not found");
    await wallet.useSigner({ type: "email", email: "user@example.com" });
    const evmWallet = EVMWallet.from(wallet);

    const RELAY_API = "https://api.relay.link";
    const ORIGIN_CHAIN_ID = 8453; // Base
    const DESTINATION_CHAIN_ID = 42161; // Arbitrum
    const BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
    const ARB_USDC = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831";
    const USDC_DECIMALS = 6;
    const amount = "5";

    // 1. Get a swap quote from Relay
    const quoteRes = await fetch(`${RELAY_API}/quote/v2`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
            user: wallet.address,
            originChainId: ORIGIN_CHAIN_ID,
            destinationChainId: DESTINATION_CHAIN_ID,
            originCurrency: BASE_USDC,
            destinationCurrency: ARB_USDC,
            amount: parseUnits(amount, USDC_DECIMALS).toString(),
            tradeType: "EXACT_INPUT",
        }),
    });
    if (!quoteRes.ok) {
        throw new Error(`Quote request failed: ${quoteRes.status}`);
    }
    const quote = await quoteRes.json();

    // 2. Execute all steps (approval + deposit)
    for (const step of quote.steps) {
        for (const item of step.items) {
            if (item.status === "incomplete") {
                await evmWallet.sendTransaction({
                    to: item.data.to,
                    data: item.data.data,
                    value: item.data.value ? BigInt(item.data.value) : 0n,
                });
            }
        }
    }

    // 3. Poll Relay for swap status
    const depositStep = quote.steps.find((s: { id: string }) => s.id === "deposit");
    if (!depositStep) throw new Error("No deposit step found in quote response");
    const requestId = depositStep.requestId;
    let status = "pending";
    while (status === "pending") {
        await new Promise((r) => setTimeout(r, 5000));
        const statusRes = await fetch(
            `${RELAY_API}/intents/status/v3?requestId=${requestId}`
        );
        const s = await statusRes.json();
        status = s.status;
    }
    if (status !== "success") {
        throw new Error(`Swap failed with status: ${status}`);
    }

    console.log("Swap complete!");
    ```
  </Tab>

  <Tab title="REST">
    Transactions must be approved by one of the wallet's [signers](/wallets/concepts/signers).
    The SDK handles this automatically, but with the REST API you must [approve the transaction](/api-reference/wallets/approve-transaction) to complete it.

    <Steps>
      <Step title="Get a swap quote from Relay">
        Call the <a href="https://docs.relay.link/references/api/get-quote-v2" target="_blank">Relay API</a> `/quote/v2` endpoint with the origin and destination tokens and amount.

        <CodeGroup>
          ```js Node.js theme={null}
          const response = await fetch(
              "https://api.relay.link/quote/v2",
              {
                  method: "POST",
                  headers: {
                      "Content-Type": "application/json",
                  },
                  body: JSON.stringify({
                      user: "YOUR_WALLET_ADDRESS",
                      originChainId: 8453,
                      destinationChainId: 42161,
                      originCurrency:
                          "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
                      destinationCurrency:
                          "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
                      amount: "5000000",
                      tradeType: "EXACT_INPUT",
                  }),
              }
          );
          const quote = await response.json();
          ```

          ```bash cURL theme={null}
          curl --request POST \
              --url 'https://api.relay.link/quote/v2' \
              --header 'Content-Type: application/json' \
              --data '{
                  "user": "YOUR_WALLET_ADDRESS",
                  "originChainId": 8453,
                  "destinationChainId": 42161,
                  "originCurrency": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
                  "destinationCurrency": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
                  "amount": "5000000",
                  "tradeType": "EXACT_INPUT"
              }'
          ```

          ```python Python theme={null}
          import requests

          response = requests.post(
              "https://api.relay.link/quote/v2",
              json={
                  "user": "YOUR_WALLET_ADDRESS",
                  "originChainId": 8453,
                  "destinationChainId": 42161,
                  "originCurrency": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
                  "destinationCurrency": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
                  "amount": "5000000",
                  "tradeType": "EXACT_INPUT",
              },
          )
          quote = response.json()
          ```
        </CodeGroup>

        The response includes `steps[]` — for ERC-20 swaps this may include an approval step followed by a deposit step.
      </Step>

      <Step title="Execute all steps from the quote">
        Iterate through each step in `quote.steps` and each item in `step.items`. For items with `status: "incomplete"`, send a transaction using the `item.data` fields (`to`, `data`, `value`).

        1. **Approval** (if present) — grants the Relay contract permission to spend the source token
        2. **Deposit** — executes the swap

        Follow the steps in the [Send a transaction](/wallets/guides/send-transaction-evm#rest) guide to submit each transaction via the Crossmint REST API.
      </Step>

      <Step title="Poll for swap status">
        Call the Relay `/intents/status/v3` endpoint every five seconds until the swap completes.

        <CodeGroup>
          ```js Node.js theme={null}
          const statusRes = await fetch(
              `https://api.relay.link/intents/status/v3?requestId=YOUR_REQUEST_ID`
          );
          const status = await statusRes.json();
          // status.status is "pending", "success", or "failure"
          ```

          ```bash cURL theme={null}
          curl --request GET \
              --url 'https://api.relay.link/intents/status/v3?requestId=YOUR_REQUEST_ID'
          ```

          ```python Python theme={null}
          import requests

          response = requests.get(
              "https://api.relay.link/intents/status/v3",
              params={
                  "requestId": "YOUR_REQUEST_ID",
              },
          )
          status = response.json()
          # status["status"] is "pending", "success", or "failure"
          ```
        </CodeGroup>

        The swap is complete when `status` is `success`. If `failure`, check the transaction on the block explorer for details.
      </Step>
    </Steps>
  </Tab>
</Tabs>

## Customizing the Route

You can bridge or swap any token pair that Relay supports by changing the chain IDs and token addresses:

```typescript theme={null}
const ORIGIN_CHAIN_ID = 1; // Ethereum
const DESTINATION_CHAIN_ID = 10; // Optimism
const ORIGIN_CURRENCY =
    "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; // USDC on Ethereum
const DESTINATION_CURRENCY =
    "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85"; // USDC on Optimism
```

See the full list of supported chains in the <a href="https://docs.relay.link/resources/supported-chains" target="_blank">Relay documentation</a>.

## Testing the Integration

To test the integration end-to-end:

1. Create a Crossmint wallet on Base using the <a href="https://www.crossmint.com/console" target="_blank">Crossmint Console</a>
2. Fund the wallet with a small amount of ETH on Base for gas (and USDC if testing the swap)
3. Run the bridge or swap code with a small amount (for example, 0.001 ETH for bridging or 1 USDC for swapping)
4. Verify the transaction completes by checking the status polling returns `success`
5. Confirm the destination wallet balance updated on the destination chain using the [Check Balances](/wallets/guides/check-balances) guide

<Note>
  Relay also supports testnets (Base Sepolia, Sepolia, etc.), but testnet availability may vary. Check the <a href="https://docs.relay.link/resources/supported-chains" target="_blank">Relay supported chains page</a> for the latest testnet status.
</Note>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Transaction Fails with Insufficient Balance">
    Verify the wallet holds enough tokens on the source chain and ETH for gas fees.
    Use the [Check Balances](/wallets/guides/check-balances) guide to confirm token balances before bridging or swapping.
  </Accordion>

  <Accordion title="Quote Request Returns an Error">
    Ensure the `user` address is a valid wallet address and the `amount` is in the smallest unit (wei for ETH, or the token's smallest denomination — for example, 6 decimals for USDC).
    Verify the origin and destination chain IDs and token addresses are correct.
    Check the <a href="https://docs.relay.link/resources/supported-chains" target="_blank">Relay supported chains</a> to confirm the route is available.
  </Accordion>

  <Accordion title="ERC-20 Swap Requires Multiple Transactions">
    For ERC-20 tokens, Relay may return an approval step before the deposit step. Make sure to iterate through all `steps` and all `items` within each step. Only execute items where `status` is `"incomplete"`.
  </Accordion>

  <Accordion title="Bridge or Swap Status Stays Pending for a Long Time">
    Cross-chain operations can take several minutes depending on the route and chain congestion.
    If the status does not change after 10 minutes, check the source transaction on a block explorer to verify it was included.
  </Accordion>

  <Accordion title="Deposits Not Detected by Relay (Smart Contract Wallets)">
    If Relay is not detecting deposits from a Crossmint smart wallet, try adding `useReceiver: true` to the quote request body. This routes the payment through a receiver contract that emits events, allowing the Relay solver to detect deposits from smart contract wallets.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Check Balances" icon="wallet" href="/wallets/guides/check-balances">
    Verify updated token balances after bridging or swapping on any chain
  </Card>

  <Card title="Bridge with LI.FI" icon="bridge" href="/wallets/guides/wallet-extensions/bridge-lifi">
    Compare with the LI.FI bridging integration for alternative routes
  </Card>
</CardGroup>
