*** title: Biconomy MEE — Simplified Onboarding via Smart Accounts slug: guides/biconomy subtitle: >- Use Biconomy's Modular Execution Environment (MEE) to enable cross-chain onboarding with a single user signature --------------------------------------- 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.
The user signs a single quote authorizing their Companion account to pull tokens and execute instructions. 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) Visit [dashboard.biconomy.io](https://dashboard.biconomy.io/) and create an account or log in. Set up a new project and copy your API key for production use. ## Project Setup ```shell bun create vite my-mee-app --template react-ts cd my-mee-app ``` ```shell bun add viem wagmi @biconomy/abstractjs @tanstack/react-query ``` Create `src/wagmi.ts`: ```typescript import { baseSepolia } from 'wagmi/chains'; import { createConfig, http } from 'wagmi'; export const config = createConfig({ chains: [baseSepolia], transports: { [baseSepolia.id]: http() } }); ``` Edit `src/main.tsx`: ```typescript 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( ); ``` ```shell bun dev ``` You should see the Vite starter page — setup is complete. ## Implementing MEE in `App.tsx` Open `src/App.tsx` and add the following imports: ```typescript 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'; ``` Create a `connectAndInit` function that sets up the Orchestrator and MEE client: ```typescript const usdcAddress = '0x036CbD53842c5426634e7929541eC2318f3dCF7e'; const [account, setAccount] = useState(null); const [walletClient, setWalletClient] = useState(null); const [meeClient, setMeeClient] = useState(null); const [orchestrator, setOrchestrator] = useState(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); }; ``` | Function | Purpose | | -------------------------- | --------------------------------------------------------- | | `toMultichainNexusAccount` | Creates the Orchestrator (Companion Smart Account) | | `chainConfigurations` | Array of chains to support — add more for cross-chain ops | | `getMEEVersion` | Retrieves the correct MEE version for compatibility | | `createMeeClient` | Creates the MEE client that coordinates execution | Create an `executeTransfers` function that builds composable instructions and executes them with a single signature: ```typescript 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.
```typescript 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(null); const [walletClient, setWalletClient] = useState(null); const [meeClient, setMeeClient] = useState(null); const [orchestrator, setOrchestrator] = useState(null); const [status, setStatus] = useState(null); const [meeScanLink, setMeeScanLink] = useState(null); const [recipients, setRecipients] = useState(['']); 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 (

Biconomy MEE Quickstart (Base Sepolia)

{account && (

Address: {account}

USDC Balance: {balance ? `${formatUnits(balance, 6)} USDC` : '–'}

Recipients

{recipients.map((recipient, idx) => ( { const updated = [...recipients]; updated[idx] = e.target.value; setRecipients(updated); }} placeholder="0x..." style={{ display: 'block', margin: '8px 0', padding: '6px', width: '100%' }} /> ))}
)} {meeClient && ( <>

MEE client ready – you can now orchestrate multichain transactions!

)} {status &&

{status}

} {meeScanLink && (

View on MEE Scan

)}
); } ```
```typescript import { baseSepolia } from 'wagmi/chains'; import { createConfig, http } from 'wagmi'; export const config = createConfig({ chains: [baseSepolia], transports: { [baseSepolia.id]: http() } }); ``` ```typescript 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( ); ```
For the complete Biconomy MEE documentation, visit [docs.biconomy.io](https://docs.biconomy.io).