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

# Crosschain Routing

> Execute complex DeFi operations across multiple blockchains using bridge callbacks and post-bridge execution

export const date_0 = "2026-07-15"

Crosschain routing coordinates DeFi operations across multiple blockchains. The source transaction and destination callback are separate stages: each transaction can be atomic on its own chain, but destination failure cannot revert a finalized source-chain bridge.

Both `route` and `bundle` API operate in crosschain mode.
For custom bundles, use the `bridge` action to facilitate cross-chain token transfers using one of four supported bridge protocols: **CCIP**, **CCTP** (USDC-only), **Relay** or **Stargate**.

For example: bridge assets to chains where protocols exist, execute operations such as minting, then optionally bridge results back to your origin chain and deposit them in a yield-bearing position.

```mermaid theme={null}
flowchart LR
    subgraph berachain ["🐻 Berachain"]
        USDC_BERA((USDC)) --> A1(( ))
    end

    A1 -.-> |balance<br/>check| USDC_ETH((USDC))

    subgraph ethereum ["🌐 Ethereum"]

        USDC_ETH((USDC)) -->|reservoir<br/>deposit| rUSD_ETH((rUSD))
    end

    rUSD_ETH -.-> |balance<br/>check|rUSD_BERA((rUSD))

    subgraph berachain ["🐻 Berachain"]
        rUSD_BERA((rUSD)) -->|euler-v2<br/>deposit| erUSD((erUSD))
    end
```

## Quick Start

<Tabs>
  <Tab title="Crosschain Stablecoin Minting (Stargate)">
    <Tip>
      Use the [GET `/layerzero/pool`](/api-reference/integration/layerzero-pool) API to find the correct pool address for Stargate, or `GET /api/v1/ccip/router?chainId={chainId}` for CCIP.

      Always start the post-bridging `callback` with a `balance` action.
    </Tip>

    ```ts theme={null}

      {
        protocol: "stargate",
        action: "bridge",
        args: {
          primaryAddress: rusdEthToBeraPools[0].pool,
          destinationChainId: ETHEREUM_ID,
          ...
          callback: [
            {
              protocol: "enso",
              action: "balance",
              args: { token: USDC_ETHEREUM },
            },
            // Mint e-rUSD using the delivered USDC on Ethereum
            {
              protocol: "reservoir",
              action: "deposit",
              args: {
                ...,
                amountIn: { useOutputOfCallAt: 0 },
              },
            },
            // Bridge newly minted e-rUSD back to Berachain
            {
              protocol: "stargate",
              action: "bridge",
              args: {
                destinationChainId: BERACHAIN_ID,
                ...
                // Callback executes on Berachain after e-rUSD arrives
                callback: [
                  {
                    protocol: "enso",
                    action: "balance",
                    args: { token: RUSD_BERACHAIN },
                  },
                  // Deposit delivered e-rUSD into Euler on Berachain
                  {
                    protocol: "euler-v2",
                    action: "deposit",
                    args: {
                      ...,
                      amountIn: { useOutputOfCallAt: 0 },
                    },
                  },
                ],
              },
            },
          ],
        },
      },
    ```
  </Tab>

  <Tab title="Crosschain Swap">
    [**Try this route →**](https://happypath.enso.build/?chainId=1\&destinationChainId=8453\&fromAddress=0xd8da6bf26964af9d7eed9e03e53415d37aa96045\&routingStrategy=delegate\&tokenIn=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\&tokenOut=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\&amountIn=1000000000)

    To simply bridge an asset, use the `route` API with automatic route optimization from input and output token/chain pairs.

    <CodeGroup dropdown>
      ```typescript SDK theme={null}
      // Basic USDC transfer: Ethereum → Base
      const route = await ensoClient.getRouteData({
        fromAddress: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
        chainId: 1, // Ethereum
        destinationChainId: 8453, // Base
        tokenIn: ["0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"], // USDC Ethereum
        tokenOut: ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"], // USDC Base
        amountIn: ["1000000000"], // 1,000 USDC
        slippage: "300", // 3%
        routingStrategy: "delegate"
      });

      await wallet.sendTransaction(route.tx);

      ```

      ```bash cURL theme={null}
      curl --request POST \
        --url https://api.enso.build/api/v1/shortcuts/route \
        --header 'Content-Type: application/json' \
        --header "Authorization: Bearer $ENSO_API_KEY" \
        --data '{
          "chainId": 1,
          "destinationChainId": 8453,
          "fromAddress": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
          "routingStrategy": "delegate",
          "tokenIn": ["0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"],
          "tokenOut": ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],
          "amountIn": ["1000000000"],
          "slippage": "300"
        }' | jq
      ```
    </CodeGroup>
  </Tab>
</Tabs>

## Core Concepts

<CardGroup cols={2}>
  <Card title="Bridge Protocols" icon="bridge" href="#bridge-protocols">
    Enso supports four bridge protocols: **CCIP** (Chainlink), **CCTP** (Circle, USDC-only), **Relay**, and **Stargate** (LayerZero). Each has different characteristics for callback limits, native token support, and fee handling.
  </Card>

  <Card title="Protocol Discovery" icon="arrow-right-arrow-left">
    Use protocol-specific APIs to discover bridge addresses:
    `GET /api/v1/ccip/router?chainId={chainId}` for CCIP,
    [`/cctp/bridge/tokenmessengerv2`](/pages/build/get-started/bridge-status) for
    CCTP, [`/layerzero/pool`](/api-reference/integration/layerzero-pool) for
    Stargate.
  </Card>

  <Card title="Native Drop" icon="gas-pump">
    Parent bridge calls calculate gas fees required for all child bridge
    operations and include these costs in the initial transaction fee.
  </Card>

  <Card title="Post-Bridge Execution" icon="play">
    Callback actions execute atomically within their destination transaction after
    asynchronous bridge delivery. Monitor source and destination status separately.
  </Card>
</CardGroup>

## Bridge Protocols

Enso supports four bridge protocols, each with different characteristics:

| Feature                   | CCIP                                 | CCTP                                        | Relay                                              | Stargate           |
| ------------------------- | ------------------------------------ | ------------------------------------------- | -------------------------------------------------- | ------------------ |
| **Provider**              | Chainlink                            | Circle                                      | Relay                                              | LayerZero          |
| **Token Coverage**        | Whitelisted ERC20                    | USDC only                                   | ERC20 + native                                     | OFT-enabled tokens |
| **Callback Data Limit**   | 30KB                                 | HyperCore deposit only                      | Unlimited                                          | \~9.5KB            |
| **Native Token Bridging** | No                                   | No (USDC only)                              | Yes                                                | Yes                |
| **Callback Gas Limit**    | 200k–3M                              | N/A                                         | Dynamic                                            | 300k base          |
| **Fee Token**             | Native                               | USDC (from minted amount)                   | Token itself                                       | Native             |
| **Finalization**          | Waits for finality (varies by chain) | Fast (where available) or wait for finality | Fast                                               | Fast               |
| **Discovery API**         | `/ccip/router`                       | `/cctp/bridge/tokenmessengerv2`             | Not needed (token address is the `primaryAddress`) | `/layerzero/pool`  |

### When to Use Each Bridge

<AccordionGroup>
  <Accordion title="CCIP (Chainlink)" icon="link">
    **Best for**: High-security transfers requiring source chain finalization guarantees.

    * Higher callback data limit (30KB) allows complex post-bridge operations
    * Waits for source chain finality before executing callbacks, ensuring transaction irreversibility
    * Does not support native token bridging (use wrapped tokens)

    <Warning>
      **Finalization timing**: CCIP waits for source chain finalization before
      executing callbacks. Timing varies significantly by chain (sub-second to
      hours). See [Chainlink's Finality By
      Blockchain](https://docs.chain.link/ccip/ccip-execution-latency#finality-by-blockchain)
      for current times. Consider this for time-sensitive swaps with slippage
      protection on slower chains.
    </Warning>

    ```typescript theme={null}
    {
      protocol: "ccip",
      action: "bridge",
      args: {
        primaryAddress: "0x...", // CCIP Router address from /ccip/router API
        destinationChainId: 8453,
        tokenIn: "0x...",
        amountIn: "1000000000000000000",
        receiver: "0x...",
        callback: [...]
      }
    }
    ```
  </Accordion>

  <Accordion title="CCTP (Circle)" icon="coins">
    **Best for**: Native USDC transfers across chains where the recipient shouldn't need destination gas.

    * USDC only — bridges burn-and-mint, not wrapped
    * Supports only the HyperCore deposit callback shape; use CCIP/Stargate/Relay for other post-bridge actions
    * Auto-relayed by Circle's Forwarding Service in both fast and standard modes
    * Fees deducted from minted amount: protocol fee (Fast only, \~Iris `minimumFee` \* 1.20) + forwarding fee (`low`/`med`/`high`, always)
    * `protocolArgs.transferType` defaults to `fast` where supported, otherwise silently falls back to `standard`

    ```typescript theme={null}
    {
      protocol: "cctp",
      action: "bridge",
      args: {
        primaryAddress: "0x...", // TokenMessengerV2 from /cctp/bridge/tokenmessengerv2
        destinationChainId: 10,
        tokenIn: "0x...", // Source-chain USDC
        amountIn: "5000000", // 5 USDC (6 decimals)
        receiver: "0x...",
        protocolArgs: {
          transferType: "fast", // optional: "fast" | "standard"
          forwardFee: "med",    // optional: "low" | "med" | "high"
        },
      }
    }
    ```

    See the [CCTP Bridge use case](/pages/use-cases/bridges/cctp-bridge) for full details on fees, fallbacks, and the manual claim flow. Per-chain Fast/Standard support comes from Circle's [supported blockchains matrix](https://developers.circle.com/cctp/concepts/supported-chains-and-domains#supported-blockchains).
  </Accordion>

  <Accordion title="Relay" icon="bolt">
    **Best for**: Flexible bridging with dynamic amounts and native token support.

    * Uses placeholder-based design for dynamic amount resolution
    * No callback data limit (amounts resolved at execution time)
    * Supports both ERC20 and native token bridging
    * API-based fee calculation

    ```typescript theme={null}
    {
      protocol: "relay",
      action: "bridge",
      args: {
        primaryAddress: "0x...", // Token address
        destinationChainId: 42161,
        tokenIn: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", // Native supported
        amountIn: { useOutputOfCallAt: 0 },
        receiver: "0x...",
        callback: [...]
      }
    }
    ```
  </Accordion>

  <Accordion title="Stargate (LayerZero)" icon="layer-group">
    **Best for**: Native token bridging and LayerZero ecosystem integration.

    * Full native token support
    * Tight callback data limit (\~9.5KB) - keep callbacks concise
    * Uses LayerZero messaging for cross-chain communication

    ```typescript theme={null}
    {
      protocol: "stargate",
      action: "bridge",
      args: {
        primaryAddress: "0x...", // OFT pool from /layerzero/pool API
        destinationChainId: 80094,
        tokenIn: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", // Native supported
        amountIn: "1000000000000000000",
        receiver: "0x...",
        callback: [...]
      }
    }
    ```
  </Accordion>
</AccordionGroup>

## When to use Route vs Bundle API?

Use **Route API** for:

* [simple crosschain swaps](#1-simple-cross-chain-swap)
* [crosschain zap deposits](#2-crosschain-vault-zap)

**Limitations**: The route API automatically selects the optimal bridge protocol - bridge protocol cannot be explicitly selected. Cannot handle custom post-bridge logic or multi-step protocol interactions. See [Bridge Transaction Status](/pages/build/get-started/bridge-status) to track delivery after submission.

Use **Bundle API** for:

* [crosschain position minting](#3-cross-chain-position-minting)
* [crosschain yield strategies](#4-crosschain-yield-strategy)
* [CCIP bridging with callbacks](#5-ccip-bridge-with-callback)
* [mixed bridge protocols](#6-mixed-bridge-protocols)
* [Relay bridging with callbacks](#7-relay-bridge-with-callback)
* [CCTP USDC bridging](#8-cctp-bridge)

**Advantages**: Allows explicit bridge protocol selection (CCIP, Relay, Stargate, or CCTP).

**Limitations**: Single callback sequence with up to 10 chained actions. Callback data limits vary by protocol (see [Bridge Protocols](#bridge-protocols)).

## Examples

### 1. Simple Cross-Chain Swap

Use Route API for basic cross-chain operations with automatic pathfinding.

```mermaid theme={null}
flowchart LR
    subgraph ethereum ["🌐 Ethereum"]
        ETH((ETH)) --> A1(( ))
    end

    A1 -.->|stargate<br/>bridge| B1

    subgraph base ["🔵 Base"]
        B1(( )) -->|balance<br/>check| USDC((USDC))
    end
```

<CodeGroup dropdown>
  ```typescript SDK theme={null}
  // ETH on Ethereum → USDC on Base
  const route = await ensoClient.getRouteData({
    fromAddress: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
    chainId: 1, // Ethereum
    destinationChainId: 8453, // Base
    tokenIn: ["0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"], // ETH
    tokenOut: ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"], // USDC Base
    amountIn: ["1000000000000000000"], // 1 ETH
    slippage: "300", // 3%
    routingStrategy: "delegate"
  });
  ```

  ```bash cURL theme={null}
  curl --request POST \
    --url https://api.enso.build/api/v1/shortcuts/route \
    --header 'Content-Type: application/json' \
    --header "Authorization: Bearer $ENSO_API_KEY" \
    --data '{
      "chainId": 1,
      "destinationChainId": 8453,
      "fromAddress": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
      "routingStrategy": "delegate",
      "tokenIn": ["0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"],
      "tokenOut": ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"],
      "amountIn": ["1000000000000000000"],
      "slippage": "300"
    }' | jq
  ```
</CodeGroup>

### 2. Crosschain Vault Zap

In this example, we'll bridge ETH from Ethereum to zap it to a Ether.fi `weETH` vault on Base.

[**Try this route →**](https://happypath.enso.build/?tokenIn=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48\&outChainId=8453\&chainId=1\&tokenOut=0x04c0599ae5a44757c0af6f9ec3b93da8976c150a)

```mermaid theme={null}
flowchart LR
    subgraph ethereum ["🌐 Ethereum"]
        ETH((ETH)) --> A1(( ))
    end

    A1 -.->|stargate<br/>bridge| B1

    subgraph base ["🔵 Base"]
        B1(( )) -->|balance<br/>check| ETH_B((ETH))
        ETH_B --> |ether.fi<br/>deposit|WEETH((weETH))
    end
```

```typescript theme={null}
const route = await ensoClient.getRouteData({
  fromAddress: "0x...",
  chainId: 1, // Ethereum
  destinationChainId: 8453, // Base
  tokenIn: ["0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"], // USDC Ethereum
  tokenOut: ["0x04C0599Ae5A44757c0af6F9eC3b93da8976c150A"], // weETH vault on Base
  amountIn: ["1000000000"], // 1,000 USDC
  slippage: "500", // 5% (complex operation)
  routingStrategy: "delegate",
});

// Bridge + vault entry in one transaction
await wallet.sendTransaction(route.tx);
```

### 3. Cross-Chain Position Minting

Crosschain routing enables you to mint positions on different chains and bridge them back, while signing only once.

In this example, we'll bridge USDC from Berachain to Ethereum, mint e-rUSD using Reservoir protocol,
then bridge rUSD back to Berachain.

**About Reservoir**: Reservoir is a stablecoin protocol that mints rUSD (a USD-pegged stablecoin) by accepting USDC as collateral on Ethereum mainnet.

```mermaid theme={null}
flowchart LR
    subgraph berachain ["🐻 Berachain"]
        USDC_BERA((USDC)) --> A1(( ))
    end

    A1 -.-> B1

    subgraph ethereum ["🌐 Ethereum"]
        B1(( )) -->|balance<br/>check| USDC_ETH((USDC))
        USDC_ETH -->|reservoir<br/>deposit| A2(( )) --> rUSD_ETH((rUSD))
    end

    rUSD_ETH -.-> C1

    subgraph berachain ["🐻 Berachain"]
        C1(( )) -->|balance<br/>check| rUSD_BERA((rUSD))
    end
```

<Note>
  Use the [GET `layerzero/pool`](/api-reference/integration/layerzero-pool) API
  or `client.getLayerZeroPool()` from the SDK to find the correct pool address
  and use it as the `primaryAddress` for `stargate.bridge` operation.
</Note>

**What's happening**: This workflow demonstrates a complete round-trip bridge operation - taking USDC from Berachain, minting a stablecoin on Ethereum where the protocol exists, then bringing the newly minted e-rUSD back to the origin chain.

**Understanding callback execution**

After bridge delivery, the encoded callback executes in a destination-chain transaction:

* **Destination atomicity**: Callback actions succeed or revert together on the destination chain. A callback failure cannot revert a finalized source transaction. Refund and recovery behavior is bridge-provider specific; monitor [Bridge Status](/pages/build/get-started/bridge-status).
* **Gas management**: The source request includes the destination execution fees calculated for the selected bridge route.
* **Output chaining**: Callback references are local to the callback array. Start supported bridge callbacks with `enso:balance`, then reference that delivered balance with `{ useOutputOfCallAt: 0 }`.

```typescript mintOnBeraFromMainnet.ts highlight={21, 27, 48, 55, 79} lines theme={null}
// Chain IDs
const BERACHAIN_ID = 80094;
const ETHEREUM_ID = 1;

// Common addresses
const WALLET_ADDRESS = "0x93621DCA56fE26Cdee86e4F6B18E116e9758Ff11"; // User wallet

// Token addresses
const USDC_BERACHAIN = "0x549943e04f40284185054145c6E4e9568C1D3241";
const USDC_ETHEREUM = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const RUSD_ETHEREUM = "0x09D4214C03D01F49544C0448DBE3A27f768F2b34";

// Protocol addresses
const RESERVOIR_MINTING_CONTRACT = "0x4809010926aec940b550D34a46A52739f996D75D";

const client = new EnsoClient({
  apiKey: process.env.ENSO_API_KEY || "your-api-key-here",
});

const usdcBeraToEthPools = await client.getLayerZeroPool({
  chainId: BERACHAIN_ID,
  token: USDC_BERACHAIN,
  destinationChainId: ETHEREUM_ID + "",
});

const rusdEthToBeraPools = await client.getLayerZeroPool({
  chainId: ETHEREUM_ID,
  token: RUSD_ETHEREUM,
  destinationChainId: BERACHAIN_ID + "",
});

if (!(usdcBeraToEthPools.length && usdcBeraToEthPools.length)) {
  throw new Error("Required pools not available");
}

const bundle = await client.getBundleData(
  {
    chainId: BERACHAIN_ID,
    fromAddress: WALLET_ADDRESS,
    spender: WALLET_ADDRESS,
    routingStrategy: "router",
    refundReceiver: WALLET_ADDRESS,
  },
  [
    {
      protocol: "stargate",
      action: "bridge",
      args: {
        primaryAddress: usdcBeraToEthPools[0].pool,
        destinationChainId: ETHEREUM_ID,
        tokenIn: USDC_BERACHAIN,
        amountIn: parseUnits("1000", 6).toString(), // 1000 USDC
        receiver: WALLET_ADDRESS,
        callback: [
          // Step 1: Check USDC balance on Ethereum after bridge
          {
            protocol: "enso",
            action: "balance",
            args: {
              token: USDC_ETHEREUM,
            },
          },
          // Step 2: Mint e-rUSD using bridged USDC
          {
            protocol: "reservoir",
            action: "deposit",
            args: {
              primaryAddress: RESERVOIR_MINTING_CONTRACT,
              tokenIn: USDC_ETHEREUM,
              tokenOut: RUSD_ETHEREUM,
              amountIn: { useOutputOfCallAt: 0 }, // Use USDC from balance check
              receiver: WALLET_ADDRESS,
            },
          },
          // Step 3: Bridge newly minted e-rUSD back to Berachain
          {
            protocol: "stargate",
            action: "bridge",
            args: {
              primaryAddress: rusdEthToBeraPools[0].pool,
              destinationChainId: BERACHAIN_ID,
              tokenIn: RUSD_ETHEREUM,
              amountIn: { useOutputOfCallAt: 1 }, // Use e-rUSD from minting
              receiver: WALLET_ADDRESS,
            },
          },
        ],
      },
    },
  ],
);
```

### 4. Crosschain Yield Strategy

Nested callbacks enable multi-hop workflows, allowing operations that span multiple chains where different protocols exist.

In this example, we'll do an Euler deposit of rUSD tokens minted on a Berachain by using Ethereum Mainnet assets.

The user starts with USDC on Berachain and ends with yield-generating vault shares with a single signature.

```mermaid theme={null}
flowchart LR
    subgraph berachain ["🐻 Berachain"]
        USDC_BERA((USDC)) --> A1(( ))
    end

    A1 -.-> |balance<br/>check| USDC_ETH((USDC))

    subgraph ethereum ["🌐 Ethereum"]

        USDC_ETH((USDC)) -->|reservoir<br/>deposit| rUSD_ETH((rUSD))
    end

    rUSD_ETH -.-> |balance<br/>check|rUSD_BERA((rUSD))

    subgraph berachain ["🐻 Berachain"]
        rUSD_BERA((rUSD)) -->|euler-v2<br/>deposit| erUSD((erUSD))
    end
```

```typescript mintOnBeraDepositOnMainnet.ts highlight={50, 57, 81, 89, 23, 29} lines theme={null}
const BERACHAIN_ID = 80094;
const ETHEREUM_ID = 1;

// Common addresses
const WALLET_ADDRESS = "0x93621DCA56fE26Cdee86e4F6B18E116e9758Ff11"; // User wallet

// Token addresses
const USDC_BERACHAIN = "0x549943e04f40284185054145c6E4e9568C1D3241";
const USDC_ETHEREUM = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const RUSD_ETHEREUM = "0x09D4214C03D01F49544C0448DBE3A27f768F2b34";
const RUSD_BERACHAIN = "0x09D4214C03D01F49544C0448DBE3A27f768F2b34";

// Protocol addresses
const RESERVOIR_MINTING_CONTRACT = "0x4809010926aec940b550D34a46A52739f996D75D";
const EULER_VAULT_E_RUSD_BERACHAIN =
  "0x109D6D1799f62216B4a7b0c6e245844AbD4DD281"; // Euler vault for e-rUSD on Berachain (need actual address)

const client = new EnsoClient({
  apiKey: process.env.ENSO_API_KEY!,
});

const usdcBeraToEthPools = await client.getLayerZeroPool({
  chainId: BERACHAIN_ID,
  token: USDC_BERACHAIN,
  destinationChainId: ETHEREUM_ID + "",
});

const rusdEthToBeraPools = await client.getLayerZeroPool({
  chainId: ETHEREUM_ID,
  token: RUSD_ETHEREUM,
  destinationChainId: BERACHAIN_ID + "",
});

if (!(usdcBeraToEthPools.length && usdcBeraToEthPools.length)) {
  throw new Error("Required pools not available");
}

const bundle = await client.getBundleData(
  {
    chainId: BERACHAIN_ID,
    fromAddress: WALLET_ADDRESS,
    spender: WALLET_ADDRESS,
    routingStrategy: "router",
    refundReceiver: WALLET_ADDRESS,
  },
  [
    {
      protocol: "stargate",
      action: "bridge",
      args: {
        primaryAddress: rusdEthToBeraPools[0].pool,
        destinationChainId: ETHEREUM_ID,
        tokenIn: USDC_BERACHAIN,
        amountIn: parseUnits("1000", 6).toString(), // 1000 USDC
        receiver: WALLET_ADDRESS,
        callback: [
          // Step 1: Check USDC balance on Ethereum after bridge
          {
            protocol: "enso",
            action: "balance",
            args: {
              token: USDC_ETHEREUM,
            },
          },
          // Step 2: Mint e-rUSD using bridged USDC on Ethereum
          {
            protocol: "reservoir",
            action: "deposit",
            args: {
              primaryAddress: RESERVOIR_MINTING_CONTRACT,
              tokenIn: USDC_ETHEREUM,
              tokenOut: RUSD_ETHEREUM,
              amountIn: { useOutputOfCallAt: 0 }, // Use USDC from balance check
              receiver: WALLET_ADDRESS,
            },
          },
          // Step 3: Bridge newly minted e-rUSD back to Berachain
          {
            protocol: "stargate",
            action: "bridge",
            args: {
              primaryAddress: rusdEthToBeraPools[0].pool,
              destinationChainId: BERACHAIN_ID,
              tokenIn: RUSD_ETHEREUM,
              amountIn: { useOutputOfCallAt: 1 }, // Use e-rUSD from minting
              receiver: WALLET_ADDRESS,
              // Callback executes on Berachain after e-rUSD arrives
              callback: [
                // Step 4: Check e-rUSD balance on Berachain
                {
                  protocol: "enso",
                  action: "balance",
                  args: {
                    token: RUSD_BERACHAIN,
                  },
                },
                // Step 5: Deposit e-rUSD into Euler vault on Berachain
                {
                  protocol: "euler-v2",
                  action: "deposit",
                  args: {
                    primaryAddress: EULER_VAULT_E_RUSD_BERACHAIN,
                    tokenIn: RUSD_BERACHAIN,
                    tokenOut: EULER_VAULT_E_RUSD_BERACHAIN, // ERC4626 vault token
                    amountIn: { useOutputOfCallAt: 0 }, // Use e-rUSD from balance check
                    receiver: WALLET_ADDRESS,
                  },
                },
              ],
            },
          },
        ],
      },
    },
  ],
);
```

### 5. CCIP Bridge with Callback

Use CCIP for ERC20 token bridging with larger callback payloads. This example bridges SolvBTC from BNB Chain to Base, then swaps it to native ETH.

```mermaid theme={null}
flowchart LR
    subgraph bnb ["🟡 BNB Chain"]
        SOLVBTC_BNB((SolvBTC)) --> A1(( ))
    end

    A1 -.->|ccip<br/>bridge| B1

    subgraph base ["🔵 Base"]
        B1(( )) -->|balance<br/>check| SOLVBTC_BASE((SolvBTC))
        SOLVBTC_BASE -->|enso<br/>route| ETH((ETH))
    end
```

<Note>
  Use `GET /api/v1/ccip/router?chainId={chainId}` to get the CCIP Router address for the `primaryAddress` parameter.
</Note>

```typescript ccipBridgeWithCallback.ts theme={null}
const BNB_CHAIN_ID = 56;
const BASE_CHAIN_ID = 8453;

const WALLET_ADDRESS = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045";

// Token addresses
const SOLVBTC_BNB = "0x4aae823a6a0b376De6A78e74eCC5b079d38cBCf7";
const SOLVBTC_BASE = "0x3B86Ad95859b6AB773f55f8d94B4b9d443EE931f";

const client = new EnsoClient({
  apiKey: process.env.ENSO_API_KEY!,
});

// Get CCIP Router address for BNB Chain
const ccipRouter = await client.getCcipRouter({
  chainId: BNB_CHAIN_ID,
});

const bundle = await client.getBundleData(
  {
    chainId: BNB_CHAIN_ID,
    fromAddress: WALLET_ADDRESS,
    routingStrategy: "router",
  },
  [
    {
      protocol: "ccip",
      action: "bridge",
      args: {
        primaryAddress: ccipRouter.router,
        destinationChainId: BASE_CHAIN_ID,
        tokenIn: SOLVBTC_BNB,
        amountIn: "100000000000000", // 0.0001 SolvBTC
        receiver: WALLET_ADDRESS,
        callback: [
          // Step 1: Check SolvBTC balance on Base after bridge
          {
            protocol: "enso",
            action: "balance",
            args: {
              token: SOLVBTC_BASE,
            },
          },
          // Step 2: Swap SolvBTC to native ETH on Base
          {
            protocol: "enso",
            action: "route",
            args: {
              slippage: "100", // 1%
              tokenIn: SOLVBTC_BASE,
              tokenOut: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
              amountIn: { useOutputOfCallAt: 0 },
            },
          },
        ],
      },
    },
  ],
);
```

### 6. Mixed Bridge Protocols

Combine different bridge protocols in a single workflow. This example uses Stargate for the outbound bridge and CCIP for the return bridge, useful when different protocols support different token pairs.

```mermaid theme={null}
flowchart LR
    subgraph plasma ["🟣 Plasma"]
        USDT_PLASMA((USDT)) --> A1(( ))
    end

    A1 -.->|stargate<br/>bridge| B1

    subgraph ethereum ["🌐 Ethereum"]
        B1(( )) -->|balance| USDT_ETH((USDT))
        USDT_ETH -->|route| syrupUSDT((syrupUSDT))
    end

    syrupUSDT -.->|ccip<br/>bridge| C1

    subgraph plasma ["🟣 Plasma"]
        C1(( )) -->|aave-v3<br/>deposit| asUSDT((asUSDT))
    end
```

```typescript mixedBridgeProtocols.ts theme={null}
const PLASMA_CHAIN_ID = 9745;
const ETHEREUM_ID = 1;

const WALLET_ADDRESS = "0x...";

// Token addresses
const USDT_PLASMA = "0x...";
const USDT_ETHEREUM = "0xdac17f958d2ee523a2206206994597c13d831ec7";
const SYRUP_USDT_ETHEREUM = "0x...";
const SYRUP_USDT_PLASMA = "0x...";

const client = new EnsoClient({ apiKey: process.env.ENSO_API_KEY! });

// Get pool addresses for both bridges
const stargatePool = await client.getLayerZeroPool({
  chainId: PLASMA_CHAIN_ID,
  token: USDT_PLASMA,
  destinationChainId: ETHEREUM_ID + "",
});

const ccipRouter = await client.getCcipRouter({
  chainId: ETHEREUM_ID,
});

const bundle = await client.getBundleData(
  {
    chainId: PLASMA_CHAIN_ID,
    fromAddress: WALLET_ADDRESS,
    routingStrategy: "router",
  },
  [
    {
      protocol: "stargate",
      action: "bridge",
      args: {
        primaryAddress: stargatePool[0].pool,
        destinationChainId: ETHEREUM_ID,
        tokenIn: USDT_PLASMA,
        amountIn: "1000000000", // 1000 USDT
        receiver: WALLET_ADDRESS,
        callback: [
          // Step 1: Check USDT balance on Ethereum
          {
            protocol: "enso",
            action: "balance",
            args: { token: USDT_ETHEREUM },
          },
          // Step 2: Swap USDT to syrupUSDT on Ethereum
          {
            protocol: "enso",
            action: "route",
            args: {
              tokenIn: USDT_ETHEREUM,
              tokenOut: SYRUP_USDT_ETHEREUM,
              amountIn: { useOutputOfCallAt: 0 },
            },
          },
          // Step 3: Bridge syrupUSDT back to Plasma via CCIP
          {
            protocol: "ccip",
            action: "bridge",
            args: {
              primaryAddress: ccipRouter.router,
              destinationChainId: PLASMA_CHAIN_ID,
              tokenIn: SYRUP_USDT_ETHEREUM,
              amountIn: { useOutputOfCallAt: 1 },
              receiver: WALLET_ADDRESS,
              // Callback on Plasma after CCIP bridge
              callback: [
                {
                  protocol: "enso",
                  action: "balance",
                  args: { token: SYRUP_USDT_PLASMA },
                },
                {
                  protocol: "aave-v3",
                  action: "deposit",
                  args: {
                    primaryAddress: "0x...", // AAVE V3 Plasma pool
                    tokenIn: SYRUP_USDT_PLASMA,
                    amountIn: { useOutputOfCallAt: 0 },
                    receiver: WALLET_ADDRESS,
                  },
                },
              ],
            },
          },
        ],
      },
    },
  ],
);
```

### 7. Relay Bridge with Callback

Use Relay for bridging with dynamic amount resolution. This example bridges ETH from Ethereum to Arbitrum and wraps it to WETH.

```mermaid theme={null}
flowchart LR
    subgraph ethereum ["🌐 Ethereum"]
        USDC((USDC)) -->|route| ETH_ETH((ETH))
        ETH_ETH --> A1(( ))
    end

    A1 -.->|relay<br/>bridge| B1

    subgraph arbitrum ["🔷 Arbitrum"]
        B1(( )) -->|balance| ETH_ARB((ETH))
        ETH_ARB -->|wrap| WETH((WETH))
    end
```

```typescript relayBridge.ts theme={null}
const ETHEREUM_ID = 1;
const ARBITRUM_ID = 42161;

const WALLET_ADDRESS = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045";
const USDC_ETHEREUM = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
const WETH_ARBITRUM = "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1";
const NATIVE_TOKEN = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";

const client = new EnsoClient({ apiKey: process.env.ENSO_API_KEY! });

const bundle = await client.getBundleData(
  {
    chainId: ETHEREUM_ID,
    fromAddress: WALLET_ADDRESS,
    routingStrategy: "router",
  },
  [
    // Step 1: Swap USDC to ETH on Ethereum
    {
      protocol: "enso",
      action: "route",
      args: {
        tokenIn: USDC_ETHEREUM,
        tokenOut: NATIVE_TOKEN,
        amountIn: "10000000", // 10 USDC
      },
    },
    // Step 2: Bridge ETH to Arbitrum via Relay
    {
      protocol: "relay",
      action: "bridge",
      args: {
        primaryAddress: "0xa5f565650890fba1824ee0f21ebbbf660a179934",
        destinationChainId: ARBITRUM_ID,
        tokenIn: NATIVE_TOKEN, // Relay supports native tokens
        amountIn: { useOutputOfCallAt: 0 },
        receiver: WALLET_ADDRESS,
        callback: [
          // Step 3: Check ETH balance on Arbitrum
          {
            protocol: "enso",
            action: "balance",
            args: { token: NATIVE_TOKEN },
          },
          // Step 4: Wrap ETH to WETH on Arbitrum
          {
            protocol: "wrapped-native",
            action: "deposit",
            args: {
              primaryAddress: WETH_ARBITRUM,
              tokenIn: NATIVE_TOKEN,
              tokenOut: WETH_ARBITRUM,
              amountIn: { useOutputOfCallAt: 0 },
            },
          },
        ],
      },
    },
  ],
);
```

### 8. CCTP Bridge

Bridge native USDC across chains with Circle's CCTP — burn on the source, mint on the destination. Both fast and standard transfers are auto-relayed by Circle's Forwarding Service, so the receiver never needs destination gas.

```mermaid theme={null}
flowchart LR
    subgraph ethereum ["🌐 Ethereum"]
        USDC_ETH((USDC)) --> A1(( ))
    end

    A1 -.->|cctp<br/>burn + forward| B1

    subgraph arbitrum ["🔷 Arbitrum"]
        B1(( )) -->|mint| USDC_ARB((USDC))
    end
```

<Tip>
  Use the [GET
  `/api/v1/cctp/bridge/tokenmessengerv2`](/pages/build/get-started/bridge-status)
  API to fetch the `TokenMessengerV2` address for the source chain.
</Tip>

```typescript cctpBridge.ts theme={null}
import { EnsoClient } from "@ensofinance/sdk";

const ETHEREUM_ID = 1;
const ARBITRUM_ID = 42161;
const WALLET_ADDRESS = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045";
const USDC_ETHEREUM = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";

const client = new EnsoClient({ apiKey: process.env.ENSO_API_KEY! });

// Fetch TokenMessengerV2 for the source chain
const { address: cctpTokenMessenger } = await fetch(
  `https://api.enso.finance/api/v1/cctp/bridge/tokenmessengerv2?chainId=${ETHEREUM_ID}`,
  { headers: { Authorization: `Bearer ${process.env.ENSO_API_KEY}` } },
).then((r) => r.json());

const bundle = await client.getBundleData(
  {
    chainId: ETHEREUM_ID,
    fromAddress: WALLET_ADDRESS,
    receiver: WALLET_ADDRESS,
    routingStrategy: "router",
  },
  [
    {
      protocol: "cctp",
      action: "bridge",
      args: {
        primaryAddress: cctpTokenMessenger,
        destinationChainId: ARBITRUM_ID,
        tokenIn: USDC_ETHEREUM,
        amountIn: "1000000000", // 1,000 USDC (6 decimals)
        receiver: WALLET_ADDRESS,
      },
    },
  ],
);

await wallet.sendTransaction(bundle.tx);
```

See [CCTP Bridge](/pages/use-cases/bridges/cctp-bridge) for the full fee model, fast vs standard trade-offs, and the manual claim/recovery flow.

## Reference

### Bridge Action Parameters

All bridge protocols share a common set of parameters:

| Parameter            | Description                                                                  | Required |
| -------------------- | ---------------------------------------------------------------------------- | -------- |
| `protocol`           | Bridge protocol: `"ccip"`, `"cctp"`, `"relay"`, or `"stargate"`              | Yes      |
| `action`             | Always `"bridge"`                                                            | Yes      |
| `primaryAddress`     | Protocol-specific address (see below)                                        | Yes      |
| `destinationChainId` | Target blockchain network ID                                                 | Yes      |
| `tokenIn`            | Token address to bridge from source chain                                    | Yes      |
| `amountIn`           | Amount to bridge (with full decimals) or reference to previous action output | Yes      |
| `receiver`           | Address to receive bridged tokens on destination chain                       | Yes      |
| `callback`           | Array of actions to execute on destination chain after bridging              | No       |

`refundReceiver` is not a generic bridge-action argument. Route-level dust/refund fields and recovery behavior depend on the selected endpoint and bridge provider.

### Protocol-Specific Parameters

<Tabs>
  <Tab title="CCIP">
    **`primaryAddress`**: CCIP Router address from `GET /api/v1/ccip/router?chainId={chainId}`

    **Callback limits**:

    * Data limit: \~30KB
    * Gas limit: 200k (default) to 3M (max)

    **Notes**:

    * ERC20 tokens only (no native token bridging)
    * Fee paid in native token

    ```typescript theme={null}
    // Get CCIP Router for the source chain
    const ccipRouter = await client.getCcipRouter({
      chainId: sourceChainId,
    });
    // Use: primaryAddress: ccipRouter.router
    ```
  </Tab>

  <Tab title="CCTP">
    **`primaryAddress`**: `TokenMessengerV2` for the source chain from [`/cctp/bridge/tokenmessengerv2`](/pages/build/get-started/bridge-status)

    **Callback limits**:

    * Only `[enso:balance, hypercore-spot:deposit]` is supported for USDC to HyperCore via chain `999`; other CCTP callbacks are rejected

    **Notes**:

    * USDC only — `tokenIn` must be the source-chain USDC
    * Auto-relayed by Circle's Forwarding Service on both fast and standard modes
    * Fees deducted from minted USDC: protocol fee (Fast only, dynamic) + forwarding fee (`low`/`med`/`high`, always)
    * Optional args: `protocolArgs.transferType` (`"fast"` | `"standard"`, default `fast` where supported else falls back to `standard`), `protocolArgs.forwardFee` (`"low"` | `"med"` | `"high"`, default `high`)

    ```typescript theme={null}
    // Get TokenMessengerV2 for the source chain
    const { address: cctpTokenMessenger } = await fetch(
      `https://api.enso.finance/api/v1/cctp/bridge/tokenmessengerv2?chainId=${sourceChainId}`,
      { headers: { Authorization: `Bearer ${process.env.ENSO_API_KEY}` } },
    ).then((r) => r.json());
    // Use: primaryAddress: cctpTokenMessenger
    ```
  </Tab>

  <Tab title="Relay">
    **`primaryAddress`**: Token address being bridged

    **Callback limits**:

    * Data limit: Unlimited (uses placeholder-based design)
    * Gas limit: Dynamic (calculated at execution)

    **Notes**:

    * Supports native token bridging (`0xeeee...eeee`)
    * Fee taken from bridged token amount
    * API-based quote system

    ```typescript theme={null}
    // For Relay, primaryAddress is the token itself
    // Use: primaryAddress: tokenAddress
    ```
  </Tab>

  <Tab title="Stargate">
    **`primaryAddress`**: OFT pool address from [`/layerzero/pool`](/api-reference/integration/layerzero-pool) API

    **Callback limits**:

    * Data limit: \~9.5KB (keep callbacks concise)
    * Gas limit: 300k base

    **Notes**:

    * Supports native token bridging (`0xeeee...eeee`)
    * Fee paid in native token
    * Uses LayerZero messaging

    ```typescript theme={null}
    // Get Stargate pool
    const pools = await client.getLayerZeroPool({
      chainId: sourceChainId,
      token: tokenAddress,
      destinationChainId: targetChainId + "",
    });
    // Use: primaryAddress: pools[0].pool
    ```
  </Tab>
</Tabs>

### Callback Requirements

<Warning>
  **Critical**: All callback sequences must begin with a balance check action to
  verify the bridged token amount on the destination chain.
</Warning>

1. **First action must be the `balance` action**:

```json theme={null}
{
  "protocol": "enso",
  "action": "balance",
  "args": { "token": "bridged_token_address" }
}
```

2. **Reference previous outputs**: Use `useOutputOfCallAt` to chain actions together

```json theme={null}
{ "useOutputOfCallAt": 0 }
```

Or with specific output index:

```json theme={null}
{ "useOutputOfCallAt": 0, "index": 1 }
```

3. **Nested callbacks**: Bridge actions within callbacks enable multi-hop workflows across multiple chains

4. **Callback data limits**: Keep callbacks concise for Stargate (\~9.5KB limit). CCIP supports larger payloads (\~30KB). Relay has no practical limit. CCTP supports only the HyperCore deposit callback shape.

### Protocol Discovery APIs

| Protocol | API Endpoint                                                                  | Returns                              |
| -------- | ----------------------------------------------------------------------------- | ------------------------------------ |
| CCIP     | `GET /api/v1/ccip/router?chainId={chainId}`                                   | CCIP Router address                  |
| CCTP     | [`GET /cctp/bridge/tokenmessengerv2`](/pages/build/get-started/bridge-status) | `TokenMessengerV2` address per chain |
| Relay    | Not needed — use the bridged token address as `primaryAddress`                | —                                    |
| Stargate | [`GET /layerzero/pool`](/api-reference/integration/layerzero-pool)            | OFT pool address                     |

<div className="text-right text-xs gray-200 font-semibold w-full" style={{marginTop: '0'}}>
  <p style={{
        color: "#b2b2b2"  
    }}>Updated {date_0}</p>
</div>

```
```
