Biconomy MEE — Simplified Onboarding via Smart Accounts

Use Biconomy’s Modular Execution Environment (MEE) to enable cross-chain onboarding with a single user signature

View as Markdown

Biconomy’s Modular Execution Environment (MEE) lets you build seamless cross-chain experiences on top of Transak’s fiat on-ramp — with no smart wallet setup required for users.

EVM Account Types

Before diving into MEE, it helps to understand the two foundational account types on Ethereum:

Externally Owned Accounts (EOAs)

Controlled by a private key (e.g. MetaMask). Can send transactions and interact with contracts, but cannot execute complex logic or batch operations.

Contract Accounts (CAs)

Smart contract code deployed on-chain. Can execute complex logic and interact with other contracts, but require deployment and cannot initiate transactions on their own.

EIP-4337 Smart Accounts

Combine the best of both worlds — smart contract functionality within a single wallet. Enable account abstraction across DeFi, GameFi, DAOs, and more.

Account Abstraction overview

MEE: Beyond ERC-4337

Biconomy’s MEE extends standard ERC-4337 to enable true cross-chain composability — with no extra setup for users.

True Composability

Dynamic execution where each step can reference outputs from previous steps.

Cross-Chain Orchestration

A single signature authorizes complex flows spanning multiple chains simultaneously.

Universal Gas Abstraction

Pay for gas on any chain using tokens from any other supported chain.

EOA Wallet Support

Works with standard wallets like MetaMask — no smart wallet deployment required.

How MEE Works: Fusion Execution

MEE uses a Fusion execution model with four core components:

Orchestrator

A Companion Smart Account that represents the user’s wallet. Invisible to users — handles batching, permissions, and fee payments.

MEE Client

Collects instructions, bundles them, and coordinates execution across chains.

Instructions

Transaction objects created by dApps. Built using composable patterns that allow dynamic, multi-step execution.

Fee Token

Specifies which token to use for gas, allowing users to pay fees with any supported token on any chain.

1

Authorization Phase

The user signs a single quote authorizing their Companion account to pull tokens and execute instructions.

2

Execution Phase

The Companion executes the batched instructions, using the pulled tokens to pay for gas and perform the requested actions.

Why Integrate MEE with Transak?

Easy User Onboarding

Works with existing EOA wallets — no smart wallet setup required for your users.

Fiat On-Ramp

Let users buy crypto directly within your dApp via Transak’s on-ramp.

Single Signature UX

One signature covers complex multi-step and multi-chain operations.

Gasless Transactions

Users pay transaction fees with any supported token — no native gas token required.

Chain Agnostic

Natively orchestrates operations across chains with a single authorization.

Custom Transaction Bundling

Batch multiple actions across chains in one transaction (e.g. Approve + Deposit).

Getting Started

You can start testing MEE without any API key. Add your Biconomy API key only when you

Biconomy API Key (Production only)

1

Go to the Biconomy Dashboard

Visit dashboard.biconomy.io and create an account or log in.

2

Create a new project

Set up a new project and copy your API key for production use.

Project Setup

1

Create your project

bun create vite my-mee-app --template react-ts
cd my-mee-app
2

Install dependencies

bun add viem wagmi @biconomy/abstractjs @tanstack/react-query
3

Configure the Wagmi provider

Create src/wagmi.ts:

import { baseSepolia } from 'wagmi/chains';
import { createConfig, http } from 'wagmi';
export const config = createConfig({
chains: [baseSepolia],
transports: {
[baseSepolia.id]: http()
}
});
4

Wrap your app in providers

Edit src/main.tsx:

import ReactDOM from 'react-dom/client';
import { WagmiProvider } from 'wagmi';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { config } from './wagmi';
import App from './App';
const queryClient = new QueryClient();
ReactDOM.createRoot(document.getElementById('root')!).render(
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</WagmiProvider>
);
5

Start the development server

bun dev

You should see the Vite starter page — setup is complete.

Implementing MEE in App.tsx

1

Add import statements

Open src/App.tsx and add the following imports:

import { useState } from 'react';
import {
createWalletClient,
custom,
erc20Abi,
http,
type Hex,
formatUnits
} from 'viem';
import { baseSepolia } from 'viem/chains';
import {
createMeeClient,
toMultichainNexusAccount,
getMeeScanLink,
getMEEVersion,
MEEVersion,
type MeeClient,
type MultichainSmartAccount
} from '@biconomy/abstractjs';
import { useReadContract } from 'wagmi';
2

Connect wallet and initialize MEE

Create a connectAndInit function that sets up the Orchestrator and MEE client:

const usdcAddress = '0x036CbD53842c5426634e7929541eC2318f3dCF7e';
const [account, setAccount] = useState<string | null>(null);
const [walletClient, setWalletClient] = useState<WalletClient | null>(null);
const [meeClient, setMeeClient] = useState<MeeClient | null>(null);
const [orchestrator, setOrchestrator] = useState<MultichainSmartAccount | null>(null);
const connectAndInit = async () => {
if (typeof window.ethereum === 'undefined') {
alert('MetaMask not detected');
return;
}
const wallet = createWalletClient({
chain: baseSepolia,
transport: custom(window.ethereum)
});
setWalletClient(wallet);
const [address] = await wallet.requestAddresses();
setAccount(address);
const multiAccount = await toMultichainNexusAccount({
chainConfigurations: [
{
chain: baseSepolia,
transport: http(),
version: getMEEVersion(MEEVersion.V2_1_0)
}
],
signer: createWalletClient({
account: address,
transport: custom(window.ethereum)
})
});
setOrchestrator(multiAccount);
const mee = await createMeeClient({ account: multiAccount });
setMeeClient(mee);
};
FunctionPurpose
toMultichainNexusAccountCreates the Orchestrator (Companion Smart Account)
chainConfigurationsArray of chains to support — add more for cross-chain ops
getMEEVersionRetrieves the correct MEE version for compatibility
createMeeClientCreates the MEE client that coordinates execution
3

Build and execute transactions

Create an executeTransfers function that builds composable instructions and executes them with a single signature:

const executeTransfers = async () => {
if (!orchestrator || !meeClient || !account) {
alert('Account not initialized');
return;
}
try {
await walletClient?.switchChain({ id: baseSepolia.id });
const recipients = [
'0x322Af0da66D00be980C7aa006377FCaaEee3BDFD',
'0x1234567890123456789012345678901234567890'
];
// Build composable instructions for each recipient
const transfers = await Promise.all(
recipients.map((recipient) =>
orchestrator.buildComposable({
type: 'default',
data: {
abi: erc20Abi,
chainId: baseSepolia.id,
to: usdcAddress,
functionName: 'transfer',
args: [recipient as Hex, 1_000_000n] // 1 USDC (6 decimals)
}
})
)
);
const totalAmount = BigInt(transfers.length) * 1_000_000n;
// Get a Fusion quote with fee token specification
const fusionQuote = await meeClient.getFusionQuote({
instructions: transfers,
trigger: {
chainId: baseSepolia.id,
tokenAddress: usdcAddress,
amount: totalAmount
},
feeToken: {
address: usdcAddress,
chainId: baseSepolia.id
}
});
// Execute with a single user signature
const { hash } = await meeClient.executeFusionQuote({ fusionQuote });
// Wait for on-chain confirmation
const transactionDetail = await meeClient.waitForSupertransactionReceipt({ hash });
console.log('Transaction completed!');
console.log(getMeeScanLink(hash));
} catch (error) {
console.log(error);
}
};

Complete Code Sample

The full App.tsx below includes wallet connection, balance display, dynamic recipient management, live status updates, and a MEE Scan link.

import { useState } from 'react';
import {
createWalletClient,
custom,
erc20Abi,
http,
type WalletClient,
type Hex,
formatUnits
} from 'viem';
import { baseSepolia } from 'viem/chains';
import {
createMeeClient,
toMultichainNexusAccount,
getMeeScanLink,
getMEEVersion,
MEEVersion,
type MeeClient,
type MultichainSmartAccount
} from '@biconomy/abstractjs';
import { useReadContract } from 'wagmi';
export default function App() {
const [account, setAccount] = useState<string | null>(null);
const [walletClient, setWalletClient] = useState<WalletClient | null>(null);
const [meeClient, setMeeClient] = useState<MeeClient | null>(null);
const [orchestrator, setOrchestrator] = useState<MultichainSmartAccount | null>(null);
const [status, setStatus] = useState<string | null>(null);
const [meeScanLink, setMeeScanLink] = useState<string | null>(null);
const [recipients, setRecipients] = useState<string[]>(['']);
const usdcAddress = '0x036CbD53842c5426634e7929541eC2318f3dCF7e';
const { data: balance } = useReadContract({
abi: erc20Abi,
address: usdcAddress,
chainId: baseSepolia.id,
functionName: 'balanceOf',
args: account ? [account as Hex] : undefined,
query: { enabled: !!account }
});
const connectAndInit = async () => {
if (typeof (window as any).ethereum === 'undefined') {
alert('MetaMask not detected');
return;
}
const wallet = createWalletClient({
chain: baseSepolia,
transport: custom((window as any).ethereum)
});
setWalletClient(wallet);
const [address] = await wallet.requestAddresses();
setAccount(address);
const multiAccount = await toMultichainNexusAccount({
chainConfigurations: [
{
chain: baseSepolia,
transport: http(),
version: getMEEVersion(MEEVersion.V2_1_0)
}
],
signer: createWalletClient({
account: address,
transport: custom((window as any).ethereum)
})
});
setOrchestrator(multiAccount);
const mee = await createMeeClient({ account: multiAccount });
setMeeClient(mee);
};
const executeTransfers = async () => {
if (!orchestrator || !meeClient || !account) {
alert('Account not initialized');
return;
}
try {
setStatus('Encoding instructions…');
await walletClient?.switchChain({ id: baseSepolia.id });
const transfers = await Promise.all(
recipients
.filter((r) => r)
.map((recipient) =>
orchestrator.buildComposable({
type: 'default',
data: {
abi: erc20Abi,
chainId: baseSepolia.id,
to: usdcAddress,
functionName: 'transfer',
args: [recipient as Hex, 1_000_000n]
}
})
)
);
const totalAmount = BigInt(transfers.length) * 1_000_000n;
setStatus('Requesting quote…');
const fusionQuote = await meeClient.getFusionQuote({
instructions: transfers,
trigger: {
chainId: baseSepolia.id,
tokenAddress: usdcAddress,
amount: totalAmount
},
feeToken: {
address: usdcAddress,
chainId: baseSepolia.id
}
});
setStatus('Please sign the transaction…');
const { hash } = await meeClient.executeFusionQuote({ fusionQuote });
const link = getMeeScanLink(hash);
setMeeScanLink(link);
setStatus('Waiting for completion…');
await meeClient.waitForSupertransactionReceipt({ hash });
setStatus('Transaction completed!');
} catch (err: any) {
console.error(err);
setStatus(`Error: ${err.message ?? err}`);
}
};
return (
<main style={{ padding: 40, fontFamily: 'sans-serif' }}>
<h1>Biconomy MEE Quickstart (Base Sepolia)</h1>
<button onClick={connectAndInit} disabled={!!account}>
{account ? 'Connected' : 'Connect Wallet'}
</button>
{account && (
<div style={{ marginTop: 20 }}>
<p><strong>Address:</strong> {account}</p>
<p>USDC Balance: {balance ? `${formatUnits(balance, 6)} USDC` : '–'}</p>
<h3>Recipients</h3>
{recipients.map((recipient, idx) => (
<input
key={idx}
type="text"
value={recipient}
onChange={(e) => {
const updated = [...recipients];
updated[idx] = e.target.value;
setRecipients(updated);
}}
placeholder="0x..."
style={{ display: 'block', margin: '8px 0', padding: '6px', width: '100%' }}
/>
))}
<button onClick={() => setRecipients([...recipients, ''])}>
Add Recipient
</button>
</div>
)}
{meeClient && (
<>
<p style={{ marginTop: 20 }}>
<strong>MEE client ready</strong> – you can now orchestrate multichain transactions!
</p>
<button onClick={executeTransfers}>
Send 1 USDC to each recipient
</button>
</>
)}
{status && <p style={{ marginTop: 20 }}>{status}</p>}
{meeScanLink && (
<p style={{ marginTop: 10 }}>
<a href={meeScanLink} target="_blank" rel="noopener noreferrer">
View on MEE Scan
</a>
</p>
)}
</main>
);
}

For the complete Biconomy MEE documentation, visit docs.biconomy.io.