Skip to content

Zero-to-Hero Guide: Composable Multichain Orchestration

This guide will walk you through building a complete multichain orchestration flow using Biconomy's AbstractJS SDK. You'll learn both the fundamental concepts and practical implementation details to create complex cross-chain transactions that users can execute with a single signature.

What is Multichain Orchestration?

Multichain orchestration is a powerful pattern that allows users to perform complex sequences of actions across multiple blockchains with just a single signature. Traditional blockchain interactions require users to sign separate transactions for each step in a process (approve, transfer, bridge, swap, etc.), often across different chains with different gas tokens.

Biconomy's orchestration stack solves this by:

  • Combining multiple transactions into a single user operation
  • Handling cross-chain coordination automatically
  • Processing transactions in the correct sequence
  • Adapting to runtime conditions and token amounts

Overview of Our Example

In this guide, we'll build a DeFi workflow that demonstrates these capabilities:

  1. Transfers USDC from a user's EOA (Externally Owned Account) on Base chain
  2. Bridges the USDC to Optimism chain
  3. Swaps USDC to USDT on Optimism using Uniswap
  4. Deposits USDT into Aave to receive aUSDT (interest-bearing tokens)
  5. Returns the aUSDT tokens to the user's original EOA

All of this happens with just one signature from the user, eliminating the need for multiple transactions, approvals, and chain-switching.

Prerequisites

Before you begin, make sure you have:

  • Development Environment: Node.js installed on your machine
  • Blockchain Knowledge: Basic understanding of EVM blockchains, tokens, and DeFi concepts
  • Test Wallet: A private key for testing on testnets (never use production keys in code)
  • Test Tokens: Some test USDC on Base testnet (you can get these from testnet faucets)

Setup

First, let's install the necessary dependencies:

npm install @biconomy/abstractjs viem @rhinestone/module-sdk@0.2.7

Next, create a new file called orchestration.js and import the required dependencies:

import {
  AavePoolAbi,
  createMeeClient,
  getFusionQuote,
  mcAaveV3Pool,
  mcUniswapSwapRouter,
  mcUSDC,
  mcUSDT,
  runtimeERC20BalanceOf,
  toFeeToken,
  toMultichainNexusAccount,
  UniswapSwapRouterAbi,
  greaterThanOrEqualTo
} from "@biconomy/abstractjs";
 
import {
  encodeFunctionData,
  erc20Abi,
  http,
  parseUnits
} from "viem";
 
import { privateKeyToAccount } from "viem/accounts";
import { base, optimism } from "viem/chains";

Core Concepts

Before we start coding, let's understand some key concepts that power Biconomy's orchestration system:

Multichain Nexus Account

A Multichain Nexus Account is a specialized smart contract account that:

  • Works across multiple blockchains with a single identity
  • Orchestrates transaction sequences in the correct order
  • Acts as a central coordination point for cross-chain operations
  • Handles token transfers and protocol interactions on behalf of the user

In our architecture, the Nexus Account receives funds from the user's EOA, performs all the necessary operations across chains, and returns the final assets to the user.


Instructions

Instructions are the building blocks of an orchestration sequence. Each instruction defines:

  • Which blockchain it should execute on
  • What actions to perform (token approvals, swaps, bridge calls, etc.)
  • What parameters to use for each action
  • Any dependencies or conditions that must be met before execution

Instructions can be simple (like a token approval) or complex (like multiple contract interactions in sequence).


Triggers

A trigger is the user-signed transaction that initiates the entire orchestration sequence. In Biconomy's system, this is typically:

  • A token transfer from the user's EOA to the Nexus Account, or
  • A token approval that allows the Nexus Account to transfer tokens from the user's EOA

The trigger includes cryptographic proof of the user's consent to the entire orchestration sequence, not just the initial transaction.


Modular Execution Environment (MEE)

The MEE is Biconomy's infrastructure that:

  • Monitors for trigger transactions on-chain
  • Executes instructions in the correct sequence
  • Handles cross-chain coordination
  • Manages gas payments and transaction confirmation

Step by Step Instructions

Initialize Accounts and Client

Let's set up the EOA account and create a multichain Nexus account to handle the orchestration:

// Generate an account from private key (use environment variables in production)
const eoa = privateKeyToAccount(
  "0x3e478f4e7c4be62e616d4b347de5431a4a6774899db4a72702b58e0cb92a1e00" // Replace with your test key
);
 
// Log the address (useful for loading test tokens)
console.log(`EOA Address: ${eoa.address}`);
 
// Create a multichain Nexus account
// This creates a smart contract account that can operate across multiple chains
const oNexus = await toMultichainNexusAccount({
  signer: eoa,                  // The EOA that controls this Nexus account
  chains: [base, optimism],     // The chains this account will operate on
  transports: [http(), http()]  // Network providers for each chain
});
 
// Log the orchestrator account address
console.log("Orchestrator Account address:", oNexus.addressOn(base.id));
 
// Initialize the MEE client
// This gives us access to Biconomy's Modular Execution Environment
const meeClient = await createMeeClient({
  account: oNexus,
});

Define the Amount and Trigger

Now we'll define how much USDC we'll use and set up the trigger transaction:

// Amount of USDC to use (adjust as needed)
// parseUnits converts 2.5 to the proper decimal representation (USDC uses 6 decimals)
const amountConsumed = parseUnits("2.5", 6); // Equals 2,500,000 in raw units
 
// Define the trigger transaction
// This is the transaction the user will actually sign - a transfer of USDC from their EOA 
// to the Nexus account. This single transaction will kick off the entire orchestration sequence.
const transferToNexusTrigger = {
  tokenAddress: mcUSDC.addressOn(base.id), // The USDC token address on Base chain
  amount: amountConsumed,                  // How much USDC to transfer
  chainId: base.id,                        // Which chain this trigger executes on
};

Prepare the Bridge Transaction

We'll use the Across bridge to move USDC from Base to Optimism:

// First, let's prepare the bridge transaction details
const callDepositAcrossSpokePool = await oNexus.buildComposable({
  type: 'default',
  data: {
    // Encode a call to Across Spoke Pool
  }
})
 
// Approve the bridge contract to spend USDC
const approveUsdcSpendToAcross = await oNexus.buildComposable({
    type: 'approve',
    data: {
      tokenAddress: mcUSDC.addressOn(base.id),
      amount: amountConsumed,
      chainId: base.id,
      spender: callDepositAcrossSpokePool.to
    }
})

Define Execution Constraints

A key challenge in cross-chain operations is that you can't know exactly how many tokens will arrive after a bridge or swap due to fees, slippage, and price movements. Execution constraints solve this problem:

// Allow for up to 20% slippage from the bridge
// This means the next steps will only proceed if at least 80% of the original amount arrives
const executionConstraints = [
  greaterThanOrEqualTo((amountConsumed * 80n) / 100n),
];

Define Swap Instructions

Now, let's prepare instructions to swap USDC to USDT on Optimism using Uniswap. This involves two steps: approving Uniswap to spend our USDC, then executing the swap.

// Approve Uniswap to spend the USDC that arrives on Optimism
const approveUniswapToSpendUSDC = await oNexus.buildComposable({
  type: "approve",                                // This is a token approval instruction
  data: {
    chainId: optimism.id,                        // Execute on Optimism chain
    tokenAddress: mcUSDC.addressOn(optimism.id), // The token to approve
    spender: mcUniswapSwapRouter.addressOn(optimism.id), // Who can spend the token
    
    // How much to approve - this is where runtime values come in!
    amount: runtimeERC20BalanceOf({
      targetAddress: oNexus.addressOn(optimism.id, true),
      tokenAddress: mcUSDC.addressOn(optimism.id),
      constraints: executionConstraints,
    }),
  },
});
 
// Swap USDC to USDT on Uniswap
const swapUSDCtoUSDTOnUniswap = await oNexus.buildComposable({
  type: "default",                             // For complex protocol interactions
  data: {
    chainId: optimism.id,                         // Execute on Optimism chain
    abi: UniswapSwapRouterAbi,                    // The contract ABI for function encoding
    to: mcUniswapSwapRouter.addressOn(optimism.id), // Uniswap router address
    functionName: "exactInputSingle",             // The specific function to call
    args: [
      {
        tokenIn: mcUSDC.addressOn(optimism.id),   // From USDC
        
        // Use exactly how much USDC we have at runtime
        amountIn: runtimeERC20BalanceOf({
          tokenAddress: mcUSDC.addressOn(optimism.id),
          targetAddress: oNexus.addressOn(optimism.id, true),
          constraints: executionConstraints,
        }),
        
        tokenOut: mcUSDT.addressOn(optimism.id),  // To USDT
        recipient: oNexus.addressOn(optimism.id, true), // Recipient of USDT
        amountOutMinimum: 0n,                     // In production, set appropriate slippage
        fee: 100,                                  // 0.01% pool fee
        sqrtPriceLimitX96: 0n,                    // No price limit
      },
    ],
  },
});

Step 6: Define Aave Deposit Instructions

Next, let's prepare instructions to deposit USDT into Aave:

// Approve Aave to spend the USDT received from the swap
const approveAAVEtoSpendUSDT = await oNexus.buildComposable({
  type: "approve",
  data: {
    chainId: optimism.id,
    tokenAddress: mcUSDT.addressOn(optimism.id),
    spender: mcAaveV3Pool.addressOn(optimism.id),
    amount: runtimeERC20BalanceOf({
      targetAddress: oNexus.addressOn(optimism.id, true),
      tokenAddress: mcUSDT.addressOn(optimism.id),
      constraints: executionConstraints,
    }),
  },
});
 
// Supply USDT to Aave
const supplyUsdtToAAVE = await oNexus.buildComposable({
  type: "default",
  data: {
    abi: AavePoolAbi,
    to: mcAaveV3Pool.addressOn(optimism.id),
    chainId: optimism.id,
    functionName: "supply",
    args: [
      mcUSDT.addressOn(optimism.id),
      runtimeERC20BalanceOf({
        targetAddress: oNexus.addressOn(optimism.id, true),
        tokenAddress: mcUSDT.addressOn(optimism.id),
        constraints: executionConstraints,
      }),
      oNexus.addressOn(optimism.id, true),
      0,
    ],
  },
});

Return aUSDT to the User

Finally, let's return the aUSDT tokens to the user's EOA:

// Define the aUSDT token address on Optimism
const aUSDTOptimismAddress = "0x6ab707Aca953eDAeFBc4fD23bA73294241490620";
 
// Transfer aUSDT back to the user's EOA
const moveAusdtBackToEOA = await oNexus.buildComposable({
  type: "transfer",
  data: {
    amount: runtimeERC20BalanceOf({
      tokenAddress: aUSDTOptimismAddress,
      targetAddress: oNexus.addressOn(optimism.id, true),
      constraints: [greaterThanOrEqualTo(parseUnits("0.3", 1))],
    }),
    chainId: optimism.id,
    recipient: eoa.address,
    tokenAddress: aUSDTOptimismAddress,
  },
});

Get a Quote and Execute

With all our instructions defined, we need to get a quote for execution costs and then execute the transaction sequence:

// Get a quote for executing the entire sequence
const quote = await meeClient.getFusionQuote({
  // The trigger transaction that the user will sign
  trigger: transferToNexusTrigger,
  
  // Specify which token to use for paying execution fees
  // This allows users to pay for all gas fees using USDC instead of ETH/native tokens
  feeToken: toFeeToken({
    chainId: base.id,
    mcToken: mcUSDC,
  }),
  
  // The sequence of instructions to execute
  // These will be executed in order, with dependencies and constraints respected
  instructions: [
    approveUsdcSpendToAcross,
    callDepositAcrossSpokePool,
    approveUniswapToSpendUSDC,
    swapUSDCtoUSDTOnUniswap,
    approveAAVEtoSpendUSDT,
    supplyUsdtToAAVE,
    moveAusdtBackToEOA,
  ],
});
 
console.log(`Got Quote: ${quote.quote.hash}`);
console.log(`Execution cost: ${quote.quote.paymentInfo.tokenValue}`);
 
// Execute the Fusion quote
// This generates the trigger transaction for the user to sign
const { hash } = await meeClient.executeFusionQuote({
  fusionQuote: quote,
});
 
console.log(`Started execution: ${hash}`);
 
// Wait for the entire transaction sequence to complete
// This might take a few minutes since it involves cross-chain operations
const receipt = await meeClient.waitForSupertransactionReceipt({ hash });
console.log(`Successful execution: ${receipt}`);

Complete Code Example

Here's the complete code for our orchestration flow:

import {
  AavePoolAbi,
  createMeeClient,
  getFusionQuote,
  mcAaveV3Pool,
  mcUniswapSwapRouter,
  mcUSDC,
  mcUSDT,
  runtimeERC20BalanceOf,
  toFeeToken,
  toMultichainNexusAccount,
  UniswapSwapRouterAbi,
  greaterThanOrEqualTo
} from "@biconomy/abstractjs";
 
import {
  encodeFunctionData,
  erc20Abi,
  http,
  parseUnits
} from "viem";
 
import { privateKeyToAccount } from "viem/accounts";
import { base, optimism } from "viem/chains";
 
const main = async () => {
  // Generate an account from private key (use environment variables in production)
  const eoa = privateKeyToAccount(
    "0x3e478f4e7c4be62e616d4b347de5431a4a6774899db4a72702b58e0cb92a1e00" // Replace with your test key
  );
 
  console.log(`EOA Address: ${eoa.address}`);
 
  // Create a multichain Nexus account
  const oNexus = await toMultichainNexusAccount({
    signer: eoa,
    chains: [base, optimism],
    transports: [http(), http()]
  });
 
  console.log("Orchestrator Account address:", oNexus.addressOn(base.id));
 
  // Initialize the MEE client
  const meeClient = await createMeeClient({
    account: oNexus,
  });
 
  // Amount of USDC to use
  const amountConsumed = parseUnits("2.5", 6);
 
  // Define the trigger transaction
  const transferToNexusTrigger = {
    tokenAddress: mcUSDC.addressOn(base.id),
    amount: amountConsumed,
    chainId: base.id,
  };
 
  // [Bridge configuration code goes here]
  
  // Define execution constraints
  const executionConstraints = [
    greaterThanOrEqualTo((amountConsumed * 80n) / 100n),
  ];
 
  // [Instructions code goes here]
 
  // Get a quote for executing the entire sequence
  const quote = await meeClient.getFusionQuote({
    trigger: transferToNexusTrigger,
    feeToken: toFeeToken({
      chainId: base.id,
      mcToken: mcUSDC,
    }),
    instructions: [
      bridgeUsdcFromBaseToOptimism,
      approveUniswapToSpendUSDC,
      swapUSDCtoUSDTOnUniswap,
      approveAAVEtoSpendUSDT,
      supplyUsdtToAAVE,
      moveAusdtBackToEOA,
    ],
  });
 
  console.log(`Got Quote: ${quote.quote.hash}`);
  console.log(`Execution cost: ${quote.quote.paymentInfo.tokenValue}`);
 
  // Execute the quote
  const { hash } = await meeClient.executeFusionQuote({
    fusionQuote: quote,
  });
 
  console.log(`Started execution: ${hash}`);
 
  // Wait for the transaction to complete
  const receipt = await meeClient.waitForSupertransactionReceipt({ hash });
  console.log(`Successful execution: ${receipt}`);
};
 
main().catch(console.error);

How It All Works Together

Let's summarize the complete flow of our orchestration:

  1. User Experience: The user signs just one transaction from their EOA wallet - a simple USDC transfer
  2. Behind The Scenes:
    • The USDC is transferred to the Nexus account on Base
    • The Nexus account bridges the USDC to Optimism
    • Once funds arrive on Optimism, they're approved and swapped to USDT
    • The USDT is then approved and deposited into Aave
    • The resulting aUSDT tokens are sent back to the user's original EOA

From the user's perspective, this entire multi-step, cross-chain process appears as a single, seamless operation.

Architecture Overview

The system components work together as follows:

  1. User's EOA: The externally owned account that initiates the transaction
  2. Nexus Account: The smart contract account that orchestrates all operations
  3. MEE Nodes: Biconomy's infrastructure that monitors and executes instructions
  4. Runtime Values: Dynamic values calculated at execution time
  5. Execution Constraints: Rules that determine when operations can proceed
  6. Fusion Execution: The mechanism that ties everything into a single user signature

This architecture enables powerful, user-friendly experiences that abstract away the complexities of blockchain interactions.

What's Next?

You've just built a complex multichain orchestration flow that allows users to:

  1. Start with USDC on Base
  2. End with an interest-bearing aUSDT position on Optimism
  3. Complete the entire operation with a single signature

Next, you can:

  • Explore Additional Patterns:

    • Intent-based swaps using 1inch or CoW Protocol
    • Dollar-cost averaging across multiple chains
    • Zapping into LP positions across chains
    • Debt management across lending protocols
  • Customize User Experiences:

    • Add front-end interfaces to your orchestration flows
    • Implement dynamic routing based on gas costs or exchange rates
    • Create protocol-specific optimizations

Troubleshooting Guide

Insufficient Funds

  • Symptom: Transaction reverts or fails to execute
  • Cause: Not enough tokens for operations or fees
  • Solution: Ensure your account has sufficient token balances and some native tokens for gas

Bridge Delays

  • Symptom: Orchestration seems stuck after the bridge step
  • Cause: Cross-chain bridges can take from 5-30 minutes depending on the provider
  • Solution: Be patient and monitor bridge status on the destination chain

Slippage Errors

  • Symptom: Transactions after bridging fail to execute
  • Cause: Constraints too strict for actual received amounts
  • Solution: Adjust your executionConstraints to allow for more slippage if needed

Invalid Function Calls

  • Symptom: Contract calls revert with error messages
  • Cause: Incorrect ABI, function parameters, or token addresses
  • Solution: Double-check all contract ABIs and verify token addresses on each chain