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:
-
EOA (Externally Owned Account):
- This is your traditional private key-controlled account
- Used ONLY for signing transactions
- ⚠️ DO NOT store your funds here
-
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:
- Checks if you already have 1000 USDC on Base
- If not, it scans your other chains (like Optimism and Polygon)
- 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:
- The SDK first checks if you have enough USDC on Base chain using
requireErc20Balance
- 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
- 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
- Once the balance is confirmed, it executes the AAVE supply transaction
- All gas fees are paid in USDC on Optimism (as specified in
payGasWith
)
Best Practices and Warnings
Pre-execution Checklist
Before executing any transaction:
-
Balance Location:
- ✅ Funds in SCA addresses
- ❌ NOT in EOA address
-
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
-
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
-
Address Verification:
- Always verify SCA addresses before sending funds
- Use the
mcNexus.deploymentOn(chainId).address
method - Double-check all contract addresses
-
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:
- Takes parameters like amount, source/destination chains, and token addresses
- Handles bridge-specific requirements (like fetching fees from Across's API)
- Creates the necessary approval and deposit transactions
- 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.