Skip to content

Biconomy MEE SDK Implementation Guide

Introduction

The Biconomy MEE SDK revolutionizes how we think about cross-chain operations. Imagine being able to gather funds scattered across multiple blockchains and use them in a single operation, all with just one signature. This is now possible through ERC7579-compliant smart accounts and the MEE orchestration service.

For example, you might have 300 USDC on Optimism, 400 on Polygon, and 300 on Base, but want to supply 1000 USDC to Aave on Arbitrum. Traditionally, this would require multiple manual bridge operations, waiting for confirmations, and then interacting with Aave. With the MEE SDK, this becomes a single atomic operation. The MEE service handles all the complexity - it bridges funds from multiple sources simultaneously, waits for all confirmations, and only then executes your Aave supply transaction.

This orchestration opens up powerful new possibilities. You could automatically move lending positions to the highest-yielding chain by unwinding a position on one chain and rebuilding it on another. Or distribute tokens from a single source to multiple destinations across different chains. Perhaps you need to gather liquidity from multiple chains to perform a large DEX trade - the MEE SDK makes this seamless. The magic happens through the MEE service's intelligent orchestration. When you initiate a transaction, the service:

  • Computes the optimal route for moving your funds
  • Executes all necessary bridges simultaneously
  • Monitors the arrival of funds across chains
  • Automatically proceeds with your intended operation once all prerequisites are met

And because all of this happens through ERC7579-compliant smart accounts, you maintain full security and control while gaining unprecedented cross-chain capabilities. Let's dive into how you can implement this in your own applications.

Initial Setup & Core Concepts

Understanding Account Types

Before diving into implementation, it's crucial to understand the two types of accounts you'll be working with:

  1. EOA (Externally Owned Account):

    • This is your traditional private key-controlled account
    • Used ONLY for signing transactions
    • ⚠️ DO NOT store your funds here
  2. Smart Contract Account (SCA):

    • This is where all your funds MUST be stored
    • All operations happen through this account
    • Generated automatically for each chain

🚨 CRITICAL WARNING: Your funds must be on the Smart Contract Account (SCA), not your EOA. Many users make the mistake of sending funds to their EOA - this will not work with the MEE SDK.

Installation

First, install the required packages:

npm install @biconomy/experimental-mee viem

Implementation Steps

1. Initialize Core Services

First, set up the MEE service which will handle all your cross-chain operations:

const meeService = createMeeService({
    meeNodeUrl: "https://mee-node.biconomy.io",
});
 
// Create a signer from your private key
const signer = privateKeyToAccount(
    "your-private-key-here"  // ⚠️ Never commit this to source control!
);

2. Set Up Multi-Chain Account

Create your Smart Contract Account infrastructure across multiple chains:

const mcNexus = await toMultichainNexusAccount({
    chains: [polygon, optimism, base],  // Specify which chains you'll operate on
    signer: signer,
});
 
// Get your SCA addresses for each chain
const optimismAddress = mcNexus.deploymentOn(optimism.id).address;
const baseAddress = mcNexus.deploymentOn(base.id).address;
const polygonAddress = mcNexus.deploymentOn(polygon.id).address;
 
console.log('Your SCA addresses:');
console.log('Optimism:', optimismAddress);
console.log('Base:', baseAddress);
console.log('Polygon:', polygonAddress);
 
// ⚠️ Remember: Send your funds to these addresses, not your EOA!

3. Initialize Multi-Chain Contracts

Set up your token and protocol contract interfaces across chains:

// Set up USDC across chains
const xUSDC = getMultichainContract({
    abi: erc20Abi,
    deployments: [
        ["0x0b2c639c533813f4aa9d7837caf62653d097ff85", optimism.id],
        ["0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", base.id],
    ],
});
 
// Set up AAVE lending pool across chains
const xAAVE = getMultichainContract({
    abi: parseAbi([
        "function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)",
    ]),
    deployments: [
        ["0x794a61358D6845594F94dc1DB02A252b5b4814aD", optimism.id],
        ["0xA238Dd80C259a72e81d7e4664a9801593F98d1c5", base.id],
    ],
});

4. Define Your Operation Parameters

Set up the parameters for your operations:

// Define the amount you want to supply (1 USDC in this example)
const supplyAmount = parseUnits("1", 6);  // USDC has 6 decimals
 
// zeroAddress is used when you want to supply funds for yourself
// If you want to supply for another address, replace zeroAddress with that address
const zeroAddress = "0x0000000000000000000000000000000000000000";

5. Build and Execute the Supertransaction

Now, build your transaction using the fluent builder pattern:

const quote = await supertransaction()
    // Inject your multi-chain account
    .injectAccount(mcNexus)
    
    // Specify which token to use for gas payments and on which chain
    .payGasWith("USDC", { on: optimism.id })
    
    // Add your transaction instructions
    .addInstructions(
        // First, ensure the required USDC balance is available on Base
        // requireErc20Balance is a powerful function that ensures you have
        // the required token balance on the specified chain. If the balance
        // isn't available, it'll automatically find optimal bridging routes
        // from multiple chains to make sure the required amount is available.
        // If the amount isn't available across any chains, it'll throw an error.
        await requireErc20Balance({
            amount: supplyAmount,  // Amount needed on the target chain
            chain: base,          // Target chain where balance is required
            token: xUSDC,        // Token contract interface
        }),
        
        // Then, supply to AAVE on Base
        xAAVE.on(base.id).supply({
            args: [
                xUSDC.addressOn(base.id),    // token to supply
                supplyAmount - parseUnits("0.2", 6),  // amount minus some buffer for fees
                zeroAddress,  // beneficiary (zero = supply to yourself)
                0,  // referral code
            ],
            gasLimit: 100_000n,
        })
    )
    // Get quote from MEE service
    .getQuote(meeService);
 
// Execute the transaction
const result = await meeService.execute(
    await signMeeQuote({
        executionMode: 'direct-to-mee',
        quote,
        signer
    })
);
 
console.log("Supertransaction hash:", result.hash);

6. Monitor Your Transaction

Track your transaction progress at:

https://meescan.biconomy.io/details/{your-transaction-hash}

Replace {your-transaction-hash} with result.hash from the previous step.

Understanding requireErc20Balance

The requireErc20Balance function is the MEE SDK's power tool for cross-chain token management. Its most remarkable feature is the ability to bridge tokens from multiple source chains to your target chain with just a single signature.

Consider this example:

await requireErc20Balance({
    amount: parseUnits("1000", 6),  // Need 1000 USDC
    chain: base,                     // On Base chain
    token: xUSDC,                    // USDC token contract
})

When you execute this, the function:

  1. Checks if you already have 1000 USDC on Base
  2. If not, it scans your other chains (like Optimism and Polygon)
  3. Automatically calculates the best way to move your tokens

The real magic happens when your tokens are spread across chains. For instance, if you have:

  • 400 USDC on Optimism
  • 600 USDC on Polygon
  • Need 1000 USDC on Base

With a single signature, requireErc20Balance will orchestrate both bridges simultaneously:

  • Bridge 400 USDC from Optimism → Base
  • Bridge 600 USDC from Polygon → Base

The function handles all the complexity of approvals, bridge interactions, and confirmations behind the scenes. You don't need to worry about manually coordinating multiple bridge transactions or managing complex cross-chain workflows. Just tell it how much you need and where you need it - the function handles everything else.

Let's break down what's happening in this transaction:

  1. The SDK first checks if you have enough USDC on Base chain using requireErc20Balance
  2. If the balance isn't sufficient, it will automatically:
    • Check balances across all configured chains
    • Calculate optimal bridging routes from multiple source chains
    • Orchestrate complex multi-hop bridges if necessary (e.g., Chain A → Chain B → Base)
    • Execute necessary approvals and transfers
    • Wait for bridge confirmations
  3. If the required amount isn't available across ANY chains:
    • The function will throw an error
    • No transaction will be executed
    • You'll need to ensure sufficient funds exist somewhere in your SCA network
  4. Once the balance is confirmed, it executes the AAVE supply transaction
  5. All gas fees are paid in USDC on Optimism (as specified in payGasWith)

Best Practices and Warnings

Pre-execution Checklist

Before executing any transaction:

  1. Balance Location:

    • ✅ Funds in SCA addresses
    • ❌ NOT in EOA address
  2. Gas Token:

    • Ensure you have enough USDC on the specified gas chain (Optimism in this example)
    • The SDK will use this to pay for all cross-chain operations
  3. Buffer Amounts:

    • Always leave some buffer in your amounts for fees
    • Note how we subtract parseUnits("0.2", 6) from the supply amount

Missing features

Right now, there is no way for contracts across chains to share context on e.g.

  • How much was bridged
  • Output of operation on source chains

This will all be solved in a big upcoming release - the Composability Stack!

Considerations

  1. Address Verification:

    • Always verify SCA addresses before sending funds
    • Use the mcNexus.deploymentOn(chainId).address method
    • Double-check all contract addresses
  2. Transaction Monitoring:

    • Always monitor complex transactions on MEEscan
    • Set up proper error handling
    • Implement timeout mechanisms for long-running operations

Understanding Bridge Plugins

Behind the scenes, the MEE SDK uses a system of bridge plugins to handle cross-chain token transfers. Each plugin encapsulates the logic for interacting with a specific bridge protocol. When you use requireErc20Balance, the SDK automatically selects and uses the appropriate bridge plugin based on the best available route.

Here's an example of how a bridge plugin works, using the Across Protocol implementation:

import { Address, parseAbi } from "abitype";
import { encodeFunctionData, erc20Abi } from "viem";
import { AbstractCall, buildMeeUserOp } from "../workflow";
import { BridgingPlugin, BridgingPluginResult, BridgingUserOpParams } from "../utils/syntax/bridging-builder";
 
export const AcrossPlugin: BridgingPlugin = {
  encodeBridgeUserOp: async (params: BridgingUserOpParams): Promise<BridgingPluginResult> => {
    const {
      bridgingAmount,
      fromChain,
      multichainAccount,
      toChain,
      tokenMapping,
    } = params;
 
    // Get token addresses and user accounts on both chains
    const inputToken = tokenMapping.on(fromChain.id);
    const outputToken = tokenMapping.on(toChain.id);
    const depositor = multichainAccount.deploymentOn(fromChain.id).address;
    const recipient = multichainAccount.deploymentOn(toChain.id).address;
 
    // Get bridge fees from Across API
    const suggestedFees = await acrossGetSuggestedFees({
      amount: bridgingAmount,
      destinationChainId: toChain.id,
      inputToken,
      outputToken,
      originChainId: fromChain.id,
    });
 
    // Calculate output amount after fees
    const outputAmount = BigInt(bridgingAmount) - BigInt(suggestedFees.totalRelayFee.total);
 
    // Create approval transaction for bridge
    const approveCall: AbstractCall = {
      to: inputToken,
      gasLimit: 100000n,
      data: encodeFunctionData({
        abi: erc20Abi,
        functionName: "approve",
        args: [suggestedFees.spokePoolAddress, bridgingAmount],
      }),
    };
 
    // Create bridge deposit transaction
    const depositCall: AbstractCall = {
      to: suggestedFees.spokePoolAddress,
      gasLimit: 150000n,
      data: encodeFunctionData({
        abi: depositV3abi,
        args: [
          depositor,
          recipient,
          inputToken,
          outputToken,
          bridgingAmount,
          outputAmount,
          BigInt(toChain.id),
          suggestedFees.exclusiveRelayer,
          parseInt(suggestedFees.timestamp),
          fillDeadline,
          parseInt(suggestedFees.exclusivityDeadline),
          "0x", // message
        ],
      }),
    };
 
    // Build and return the user operation
    const userOp = buildMeeUserOp({
      calls: [approveCall, depositCall],
      chainId: fromChain.id,
    });
 
    return {
      userOp: userOp,
      receivedAtDestination: outputAmount,
      bridgingDurationExpectedMs: undefined,
    };
  },
};

This plugin demonstrates how the SDK:

  1. Takes parameters like amount, source/destination chains, and token addresses
  2. Handles bridge-specific requirements (like fetching fees from Across's API)
  3. Creates the necessary approval and deposit transactions
  4. Returns a structured user operation that the SDK can execute

The beauty is that you never need to interact with these plugins directly - the SDK automatically chooses and uses the optimal bridge based on your requirements.