Advanced Bundling: Custom Function Calls

When a standard enso:route isn’t enough for protocols with unique function signatures (e.g., requiring a referral code as an argument), you can use the /bundle endpoint. By chaining actions like enso:route and enso:call, you can combine optimal routing with custom contract calls

Use Case: Swap and Deposit with a Custom Referral Code

Let’s explore a scenario where a user wants to swap USDT for STAKED_HYPE, and then deposit it for LHYPE, through a contract whose deposit function requires a custom communityCode argument.
When using the delegate routing strategy with Smart Wallets, receiver: SENDER (or the spender address) is used for route actions. This is because the Smart Wallet executes the bundle directly through delegate calls, and the wallet itself holds the tokens throughout the execution.When using the router routing strategy with EOAs (Externally Owned Accounts), receiver: ENSO_ROUTER is required for route actions. This is because the Enso Router contract needs to hold the intermediate tokens between actions The router executes the sequence of actions and needs custody of tokens to pass them between steps

EOA Example (routingStrategy: 'router')

When the transaction is from an EOA, the Enso Router contract executes the sequence. This requires explicit approvals for the router.

Bundle Design

In pseudo-code, the bundle looks as follows:
0: erc20.approve(USDT, ENSO_ROUTER)
1: enso.route(in: USDT, out: STAKED_HYPE, receiver: ENSO_ROUTER)
2: erc20.approve(STAKED_HYPE, HYPE_DEPOSITOR, amount: outputOfCallAt(1))
3: enso.call(
    HYPE_DEPOSITOR.deposit(STAKED_HYPE, outputOf(1), 0, SENDER, "0x1234")
  )
  • Action 0: erc20:approve. The user approves the Enso Router to spend their input USDT, which is a required first step to grant the router permission for the swap.
  • Action 1: enso:route. The router swaps USDT for STAKED_HYPE. The receiver is critically set to the ENSO_ROUTER itself so it can hold the tokens for the next steps.
  • Action 2: erc20:approve. The Enso Router approves the final HYPE_DEPOSITOR contract, using { useOutputOfCallAt: 1 } to dynamically set the approval amount to the exact output from the previous swap.
  • Action 3: enso:call. The router calls the final deposit function, passing the dynamic swap amount (again with { useOutputOfCallAt: 1 }), the user’s address as the recipient, and the required static communityCode.
routeCustomCallEOA.ts
import { EnsoClient } from "@ensofinance/sdk";

const client = new EnsoClient({
  apiKey: "YOUR_API_KEY", // Replace with your Enso API key
});

// Define asset and contract addresses
const USDT = "0xb8ce59fc3717ada4c02eadf9682a9e934f625ebb";
const STAKED_HYPE = "0xffaa4a3d97fe9107cef8a3f48c069f577ff76cc1";
const HYPE_DEPOSITOR = "0x6e358dd1204c3fb1D24e569DF0899f48faBE5337";

const SENDER = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045"; // The user's EOA address
const ENSO_ROUTER = "0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf"; // Enso's Router contract

const bundle = await client.getBundleData(
  {
    chainId: 999,
    fromAddress: SENDER,
    routingStrategy: "router", // Using the Enso Router
    receiver: SENDER,
  },
  [
    {
      protocol: "erc20",
      action: "approve",
      args: {
        token: USDT,
        spender: ENSO_ROUTER,
        amount: "100000000",
      },
    },
    {
      protocol: "enso",
      action: "route",
      args: {
        tokenIn: USDT,
        tokenOut: STAKED_HYPE,
        amountIn: "100000000",
        receiver: ENSO_ROUTER, // The router must receive the intermediate tokens
      },
    },
    {
      protocol: "erc20",
      action: "approve",
      args: {
        token: STAKED_HYPE,
        spender: HYPE_DEPOSITOR,
        amount: { useOutputOfCallAt: 1 },
      },
    },
    {
      protocol: "enso",
      action: "call",
      args: {
        address: HYPE_DEPOSITOR,
        method: "deposit",
        abi: "function deposit(address depositAsset, uint256 depositAmount, uint256 minimumMint, address to, bytes communityCode) external returns (uint256 shares)",
        args: [STAKED_HYPE, { useOutputOfCallAt: 1 }, 0, SENDER, "0x1234"],
      },
    },
  ]
);
console.log(JSON.stringify(bundle, null, 2));

Smart Wallet Example (routingStrategy: 'delegate')

With a Smart Wallet, the wallet executes the bundle directly, simplifying the approval flow into fewer steps.

Bundle Design

In pseudo-code, we need to do the following:
0: enso.route(in: USDT, out: STAKED_HYPE, receiver: SENDER) |
1: erc20.approve(STAKED_HYPE, HYPE_DEPOSITOR, amount: outputOfCallAt(0)) |
2: enso.call(
  HYPE_DEPOSITOR.deposit(STAKED_HYPE, outputOf(0), 0, SENDER, "0x1234")
)
  • Action 0: enso:route. The Smart Wallet swaps USDT for STAKED_HYPE and receives the output tokens directly.
  • Action 1: erc20:approve. The Smart Wallet approves the final HYPE_DEPOSITOR contract, using { useOutputOfCallAt: 0 } to dynamically approve the exact amount received from the swap.
  • Action 2: enso:call. The Smart Wallet calls the custom deposit function, passing the dynamic swap amount (again using { useOutputOfCallAt: 0 }), the user’s address as the recipient, and the required static communityCode.
routeCustomCallSmartWallet.ts
import { EnsoClient } from "@ensofinance/sdk";

const client = new EnsoClient({
  apiKey: "YOUR_API_KEY", // Replace with your Enso API key
});

// Define asset and contract addresses
const USDT = "0xb8ce59fc3717ada4c02eadf9682a9e934f625ebb";
const STAKED_HYPE = "0xffaa4a3d97fe9107cef8a3f48c069f577ff76cc1";
const HYPE_DEPOSITOR = "0x6e358dd1204c3fb1D24e569DF0899f48faBE5337";

const SENDER = "0xd8da6bf26964af9d7eed9e03e53415d37aa96045";

const bundle = await client.getBundleData(
  {
    chainId: 999,
    fromAddress: SENDER,
    routingStrategy: "delegate",
    receiver: SENDER,
  },
  [
    {
      protocol: "enso",
      action: "route",
      args: {
        tokenIn: USDT,
        tokenOut: STAKED_HYPE,
        amountIn: "100000000",
        receiver: SENDER,
      },
    },
    {
      protocol: "erc20",
      action: "approve",
      args: {
        token: STAKED_HYPE,
        spender: HYPE_DEPOSITOR,
        amount: { useOutputOfCallAt: 0 },
      },
    },
    {
      protocol: "enso",
      action: "call",
      args: {
        address: HYPE_DEPOSITOR,
        method: "deposit",
        abi: "function deposit(address depositAsset, uint256 depositAmount, uint256 minimumMint, address to, bytes communityCode) external returns (uint256 shares)",
        args: [STAKED_HYPE, { useOutputOfCallAt: 0 }, 0, SENDER, "0x1234"],
      },
    },
  ]
);
console.log(JSON.stringify(bundle, null, 2));