# Agent Types & Examples Source: https://docs.biconomy.io/agents-automation/agent-types Ready-to-use configurations for common agent types Copy-paste configurations for the most common autonomous agent use cases. ## 🤖 AI Trading Agent An AI that executes trades based on market signals, sentiment, or predictions. ### Permissions Needed * Swap tokens on DEXes * Per-trade and max spending limits * Time-limited access ### Configuration ```typescript theme={null} const UNISWAP_ROUTER = "0x2626664c2603336E57B271c5C0b26F421741e481"; // Base const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; const now = Math.floor(Date.now() / 1000) const validAfter = now const validUntil = now + 7 * 24 * 60 * 60 // 7-day access window const timeframePolicy = mcNexus.buildActionPolicy({ type: "timeframe", validAfter, validUntil }); const universalPolicy = mcNexus.buildActionPolicy({ type: "universal", rules: [ // Recipient must be user's account (prevent draining) { condition: "equal", calldataOffset: calldataArgument(4), comparisonValue: userAccountAddress }, // Max 500 USDC per trade, 5000 Max limit { condition: "lessThanOrEqual", calldataOffset: calldataArgument(5), comparisonValue: parseUnits("500", 6), isLimited: true, usage: { limit: parseUnits("5000", 6), used: 0n } } ], }); const actions = mcNexus.buildSessionAction({ type: "custom", data: { chainIds: [8453], contractAddress: UNISWAP_ROUTER, functionSignature: toFunctionSelector("exactInputSingle((address,address,uint24,address,uint256,uint256,uint160))"), policies: [universalPolicy, timeframePolicy] } }); const quote = await meeClient.getSessionQuote({ mode: "PREPARE", enableSession: { redeemer: sessionSigner.address, actions, // Max gas spend maxPaymentAmount: parseUnits("50", 6), }, simulation: { simulate: true }, feeToken: { address: USDC, chainId: 8453 }, trigger: { tokenAddress: USDC, chainId: 8453, amount: parseUnits("2", 6), }, }); // Store the sessions details for later use. let sessionDetails: SessionDetail[] = []; if (quote) { const { hash } = await meeClient.executeSessionQuote(quote); await meeClient.waitForSupertransactionReceipt({ hash }); if (quote.sessionDetails) sessionDetails = quote.sessionDetails; } ``` ### Agent Logic Example ```typescript theme={null} async function executeTrade(signal: TradeSignal) { if (signal.confidence < 0.7) return; // Only high-confidence trades const instructions = await agentAccount.buildComposable({ type: "default", data: { chainId: base.id, to: UNISWAP_ROUTER, abi: UniswapAbi, functionName: "exactInputSingle", args: [{ tokenIn: signal.fromToken, tokenOut: signal.toToken, fee: 3000, recipient: userAccountAddress, amountIn: signal.amount, amountOutMinimum: signal.minOutput, sqrtPriceLimitX96: 0n }] } }); const quote = await agentClient.getSessionQuote({ mode: "USE", sessionDetails, simulation: { simulate: true }, feeToken: { address: USDC, chainId: 8453 }, instructions }); const { hash } = await agentClient.executeSessionQuote(quote); await agentClient.waitForSupertransactionReceipt({ hash }); } ``` *** ## 📈 Yield Optimization Agent Automatically moves funds to the highest-yielding opportunities. ### Permissions Needed * Deposit/withdraw from multiple protocols * Cross-chain movement (optional) * Trusted protocol access ### Configuration ```typescript theme={null} const PROTOCOLS = { base: { morpho: "0xA2Cac0023a4797b4729Db94783405189a4203AFc", aave: "0x...", }, optimism: { aave: "0x...", exactly: "0x...", } }; const now = Math.floor(Date.now() / 1000) const validAfter = now const validUntil = now + 30 * 24 * 60 * 60 // 30-day access const timeframePolicy = mcNexus.buildActionPolicy({ type: "timeframe", validAfter, // unix-timestamp when policy becomes active validUntil // unix-timestamp when policy expires }); // Morpho deposit const [actionOne] = mcNexus.buildSessionAction({ type: 'custom', data: { chainIds: [8453], contractAddress: PROTOCOLS.base.morpho, functionSignature: toFunctionSelector("deposit(uint256,address)"), policies: [timeframePolicy] } }) // Morpho withdraw const [actionTwo] = mcNexus.buildSessionAction({ type: 'custom', data: { chainIds: [8453], contractAddress: PROTOCOLS.base.morpho, functionSignature: toFunctionSelector("withdraw(uint256,address,address)"), policies: [timeframePolicy] } }) // Aave supply const [actionThree] = mcNexus.buildSessionAction({ type: 'custom', data: { chainIds: [10], contractAddress: PROTOCOLS.optimism.aave, functionSignature: toFunctionSelector("supply(address,uint256,address,uint16)"), policies: [timeframePolicy] } }) // Aave withdraw const [actionFour] = mcNexus.buildSessionAction({ type: 'custom', data: { chainIds: [10], contractAddress: PROTOCOLS.optimism.aave, functionSignature: toFunctionSelector("withdraw(address,uint256,address)"), policies: [timeframePolicy] } }) const quote = await meeClient.getSessionQuote({ mode: "PREPARE", enableSession: { redeemer: agentSigner.address, actions: [actionOne, actionTwo, actionThree, actionFour], // Generous gas for cross-chain maxPaymentAmount: parseUnits("100", 6), }, simulation: { simulate: true }, feeToken: { address: USDC, chainId: 8453 }, // Any additional Instructions instructions: [...] }); // Store the sessions details for later use. let sessionDetails: SessionDetail[] = []; if (quote) { const { hash } = await meeClient.executeSessionQuote(quote); await meeClient.waitForSupertransactionReceipt({ hash }); if (quote.sessionDetails) sessionDetails = quote.sessionDetails; } ``` *** ## 💳 Subscription Payment Agent Automates recurring payments to a fixed recipient. ### Permissions Needed * Transfer to specific address only * Fixed amount per payment * Monthly frequency ### Configuration ```typescript theme={null} // Chargeable on day one of the month const dayOneTimeframePolicy = mcNexus.buildActionPolicy({ type: "timeframe", validAfter: ..., // unix-timestamp Eg: 1-1-2026 00:00:00 in unix timestamp validUntil: ... // unix-timestamp Eg: 1-1-2026 23:59:59 in unix timestamp }); // Used only once per month const usagePolicy = mcNexus.buildActionPolicy({ type: "usageLimit", limit: 1n }); // Only specific recipient and specific amount const paymentPolicy = mcNexus.buildActionPolicy({ type: "universal", rules: [ // Only send to merchant { condition: "equal", calldataOffset: calldataArgument(1), comparisonValue: MERCHANT }, // Max 100 USDC per payment { condition: "lessThanOrEqual", calldataOffset: calldataArgument(2), comparisonValue: parseUnits("100", 6), } ], // Configure spending limit for native token valueLimitPerUse: 0n, }); const monthOnePaymentAction = mcNexus.buildSessionAction({ type: "transfer", data: { chainIds: [8453, 10], contractAddress: USDC, policies: [dayOneTimeframePolicy, usagePolicy, paymentPolicy] } }); // 10 similar actions for 10 months const monthTwelvePaymentAction = mcNexus.buildSessionAction({ type: "transfer", data: { chainIds: [8453, 10], contractAddress: USDC, policies: [dayOneTimeframePolicy, usagePolicy, paymentPolicy] } }); // 12 actions which can be executed only once per month on day one for a year. // Get a session quote and execute it to enable a permission for subscriptions ``` *** ## 🔄 Portfolio Rebalancing Agent Maintains target allocations by trading when drift exceeds threshold. ### Configuration ```typescript theme={null} // Chargeable in every quarter const quarterlyTimeframePolicy = mcNexus.buildActionPolicy({ type: "timeframe", validAfter: ..., // unix-timestamp Eg: 1-1-2026 in unix timestamp validUntil: ... // unix-timestamp Eg: 31-3-2026 in unix timestamp }); // Used only once per quarter const usagePolicy = mcNexus.buildActionPolicy({ type: "usageLimit", limit: 1n }); const quarterOneRebalanceAction = mcNexus.buildSessionAction({ type: "custom", data: { chainIds: [8453, 10], contractAddress: REBALANCER, functionSignature: '0x...', policies: [quarterlyTimeframePolicy, usagePolicy] } }); // 2 similar actions for 2 quarters const quarterFourRebalanceAction = mcNexus.buildSessionAction({ type: "custom", data: { chainIds: [8453, 10], contractAddress: REBALANCER, functionSignature: '0x...', policies: [quarterlyTimeframePolicy, usagePolicy] } }); // 4 actions which can be executed only once per quarter for a year. // Get a session quote and execute it to enable a permissions for rebalancer ``` *** ## 🎮 Gaming Agent Automates in-game actions like claiming rewards, crafting, or battles. ### Configuration ```typescript theme={null} const GAME_CONTRACT = "0x..."; const [claimAction] = mcNexus.buildSessionAction({ type: 'custom', data: { chainIds: [8453], contractAddress: GAME_CONTRACT, functionSignature: toFunctionSelector("claimRewards()"), policies: [ { type: "usageLimit", limit: 100n // Max 100 claims } ] } }) const [performAction] = mcNexus.buildSessionAction({ type: 'custom', data: { chainIds: [8453], contractAddress: GAME_CONTRACT, functionSignature: toFunctionSelector("performAction(uint256)"), policies: [ { type: "usageLimit", limit: 1000n // Max 1000 actions } ] } }) // Get a session quote and execute it to enable a permissions for game agent executions. ``` *** ## Quick Reference | Agent Type | Primary Policy | Key Limits | | ------------ | ------------------------------- | ----------------------------------------------- | | Trading | Universal Action + Time | Per-trade + Max caps | | Yield | Time | Quarterly executions + gas limits | | Subscription | Universal Action + Time + Usage | Monthly executions + Recipient + amount + usage | | Rebalancing | Time + Usage | Portfolio % + usage | | Gaming | Usage | Gameplay limits | # Building Your Agent Source: https://docs.biconomy.io/agents-automation/implementation Complete guide to implementing an autonomous agent This guide walks through building a production-ready agent from scratch. ## Architecture Overview ``` +------------------------------------------------------------------+ | YOUR APPLICATION | +------------------------------------------------------------------+ | Frontend (User Prepares And Enable Permission) | | - SCA deployment if not already | | - Smart session validator installation if not already | | - Funds the SCA if required | | - Enable session permissions | +------------------------------------------------------------------+ | Backend (Agent Executes) | | - Store agent private key securely | | - Store sessionDetails per user | | - Executes on behalf of user with enabled permissions | +------------------------------------------------------------------+ | BICONOMY MEE NETWORK | | - Executes transactions, enforces policies onchain | +------------------------------------------------------------------+ ``` ## Step 1: Setup ### Install Dependencies ```bash theme={null} npm install @biconomy/abstractjs viem ``` ### Initialize SDK ```typescript theme={null} import { createMeeClient, getMEEVersion, MEEVersion, SessionDetail, toMultichainNexusAccount } from "@biconomy/abstractjs"; import { Abi, Address, http, parseUnits, toFunctionSelector, encodeFunctionData } from "viem"; import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; import { arbitrum, base, optimism } from "viem/chains"; ``` ## Step 2: Generate Agent Key Your agent needs its own keypair. Store this securely in your backend. ```typescript theme={null} import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; // Generate once, store securely (e.g., AWS Secrets Manager, Vault) const agentPrivateKey = generatePrivateKey(); const agentSigner = privateKeyToAccount(agentPrivateKey); console.log("Agent address:", agentSigner.address); // This address is what users will enable permissions to ``` **Never expose the agent private key.** Treat it like a production database password. ## Step 3: User Onboarding (Frontend) When a user wants to activate your agent, they enable permissions. ### Create User's Account & Mee Client ```typescript theme={null} // User's signer (from their wallet - Privy, MetaMask, etc.) const userSigner = privateKeyToAccount(generatePrivateKey()); console.log("User address:", userSigner.address); // Create the user's multichain account const userAccount = await toMultichainNexusAccount({ signer: userSigner, chainConfigurations: [ { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) }, { chain: optimism, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) }, { chain: arbitrum, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) } ] }); // Create MEE client with session actions const meeClient = await createMeeClient({ account: userAccount }); ``` ### Prepare And Enable Permissions While user wants to enable permissions, they do the following operations * Deploying the Smart Contract Account (SCA) if it doesn't exist yet. * Installing the Smart Session Validator module if not already present. * Funding the SCA if additional funds are required. * Granting (enabling) session permissions for the agent to act on the user's behalf. All in one supertransaction. ```typescript theme={null} const MORPHO_VAULT: Address = "0x..."; const USDC: Address = "0x..."; // Define what your agent can do const [depositAction] = userAccount.buildSessionAction({ type: "custom", data: { chainIds: [base.id], contractAddress: MORPHO_VAULT, functionSignature: toFunctionSelector("deposit(uint256,address)"), // By default sudo policy will be applied } }); const [withdrawAction] = userAccount.buildSessionAction({ type: "custom", data: { chainIds: [base.id], contractAddress: MORPHO_VAULT, functionSignature: toFunctionSelector("withdraw(uint256,address,address)"), // By default sudo policy will be applied } }); const [transferAction] = userAccount.buildSessionAction({ type: "transfer", data: { chainIds: [base.id], contractAddress: USDC, recipientAddress: "0x...", amountLimitPerAction: parseUnits("5", 6), maxAmountLimit: parseUnits("100", 6), usageLimit: 100n, validAfter: Math.floor(Date.now() / 1000), validUntil: Math.floor(Date.now() / 1000) + 60 * 60 * 24 } }); const prepareAndEnableSessionQuote = await meeClient.getSessionQuote({ mode: "PREPARE", enableSession: { redeemer: agentSigner.address, // Allowed actions actions: [depositAction, withdrawAction, transferAction], // Max gas spend maxPaymentAmount: parseUnits("20", 6), // 20 USDC for gas }, simulation: { simulate: true }, feeToken: { address: USDC, chainId: base.id }, trigger: { tokenAddress: USDC, chainId: base.id, amount: parseUnits("10", 6), // $10 USDC funding }, }); // Store the sessions details for later use. let sessionDetails: SessionDetail[] = []; if (prepareAndEnableSessionQuote) { const { hash } = await meeClient.executeSessionQuote(prepareAndEnableSessionQuote); await meeClient.waitForSupertransactionReceipt({ hash }); if (prepareAndEnableSessionQuote.sessionDetails) sessionDetails = prepareAndEnableSessionQuote.sessionDetails; } // IMPORTANT: Send sessionDetails to your backend await sendToBackend(sessionDetails, userAccount.addressOn(base.id)); ``` ## Step 4: Store Session Data (Backend) Your backend needs to store session details for each user: ```typescript theme={null} // Example database schema interface UserSession { userId: string; accountAddress: string; // User's smart account address sessionDetails: SessionDetail[]; // Can be JSON stringified while storing createdAt: Date; expiresAt: Date; } // Store session async function storeSession(userId: string, accountAddress: string, sessionDetails: SessionDetail[]) { await db.userSessions.insert({ userId, accountAddress, sessionDetails: JSON.stringify(sessionDetails), createdAt: new Date(), expiresAt: new Date(sessionDetails.sessionValidUntil * 1000) }); } ``` ## Step 5: Agent Execution (Backend) Your agent can now act on behalf of users: ```typescript theme={null} async function executeAgentAction(userId: string, action: AgentAction) { // 1. Load user's session const session = await db.userSessions.findOne({ userId }); const sessionDetails: SessionDetail[] = JSON.parse(session.sessionDetails); // 2. Create agent's MEE client const agentAccount = await toMultichainNexusAccount({ signer: agentSigner, chainConfigurations: [ { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0), accountAddress: session.accountAddress // User's account address } ], }); const agentMeeClient = await createMeeClient({ account: agentAccount }); // 3. Build the instruction const depositData = encodeFunctionData({ abi: MORPHO_ABI, functionName: "deposit", args: [parseUnits("5", 6), session.accountAddress], }); const instructions = await agentAccount.build({ type: "default", data: [{ calls: [{ to: MORPHO_VAULT, data: depositData }], chainId: base.id, }] }); // 4. Get session quote const quote = await agentMeeClient.getSessionQuote({ mode: "USE", sessionDetails, simulation: { simulate: true }, feeToken: { address: USDC, chainId: base.id }, instructions }); // 5. Execute with permission const { hash } = await agentMeeClient.executeSessionQuote(quote); // 6. Wait for confirmation const { receipts } = await agentMeeClient.waitForSupertransactionReceipt({ hash }); return receipts; } ``` ## Step 6: Agent Logic Implement your agent's decision-making: ### Yield Optimization Agent ```typescript theme={null} async function runYieldOptimizer() { // Get all users with active sessions const users = await db.userSessions.find({ expiresAt: { $gt: new Date() } }); for (const user of users) { // Check current positions const positions = await getPositions(user.accountAddress); // Find best yield opportunity const bestOpportunity = await findBestYield(); // Rebalance if significantly better if (bestOpportunity.apy > positions.currentApy * 1.1) { // Withdraw from current await executeAgentAction(user.userId, { type: "withdraw", protocol: positions.currentProtocol, amount: positions.amount }); // Deposit to better opportunity await executeAgentAction(user.userId, { type: "deposit", protocol: bestOpportunity.protocol, amount: positions.amount }); } } } // Run every hour setInterval(runYieldOptimizer, 60 * 60 * 1000); ``` ### Trading Agent ```typescript theme={null} async function runTradingAgent() { const users = await getActiveUsers(); for (const user of users) { const signal = await analyzeMarket(user.preferences); if (signal.shouldTrade) { await executeAgentAction(user.userId, { type: "swap", tokenIn: signal.fromToken, tokenOut: signal.toToken, amount: signal.amount }); } } } ``` ## Sponsored Agent Execution If you want to pay gas for your users: ```typescript theme={null} await agentMeeClient.getSessionQuote({ mode: "USE", sessionDetails, simulation: { simulate: true }, sponsorship: true, // You pay gas instructions }); ``` When using `sponsorship: true`, don't set `feeToken` when enabling permissions. ## Monitoring & Observability ```typescript theme={null} // Log all agent actions async function executeWithLogging(userId: string, action: AgentAction) { const startTime = Date.now(); try { const receipts = await executeAgentAction(userId, action); await logAgentAction({ userId, action, status: "success", transactionHashes: receipts.map((receipt) => receipt.transactionHash), duration: Date.now() - startTime }); return result; } catch (error) { await logAgentAction({ userId, action, status: "failed", error: error.message, duration: Date.now() - startTime }); throw error; } } ``` ## Security Checklist Agent private key stored in secure secret management Session details stored encrypted in database Rate limiting on agent actions Monitoring for unusual activity Automatic session cleanup on expiry User notification system for important events # Agents & Automation Source: https://docs.biconomy.io/agents-automation/index Build autonomous onchain agents that act on behalf of users Build AI agents, trading bots, and automated systems that execute onchain transactions for users—securely, with user-defined limits, across any chain. ## The Agent Problem Today's onchain agents face a fundamental challenge: **they need to transact, but users don't trust them with private keys.** Traditional solutions are broken: | Approach | Problem | | ------------------ | ---------------------------------- | | Share private keys | Users won't do it (rightfully so) | | Custodial wallets | Regulatory nightmare, trust issues | | Approve max tokens | One exploit drains everything | | Sign every action | Defeats the purpose of automation | ## Smart Sessions: The Solution Smart Sessions let users grant **scoped, time-limited, revocable permissions** to your agent. The agent can act autonomously within those bounds—enforced by smart contracts, not trust. ``` User grants permission → Agent acts within limits → Smart contract enforces rules ``` ### What Users Control Agent can only call whitelisted addresses Specific function selectors, not general access Spending caps per action and total Automatic expiration of permissions ## Agent Types & Use Cases ### 🤖 AI Trading Agents Autonomous agents that execute trades based on market signals. ```typescript theme={null} // Agent can only swap on Uniswap // Max $500 per trade and Max $4000 limit // Expires in 7 days const functionSignature = toFunctionSelector( getAbiItem({ abi: UniswapRouterAbi, name: "exactInputSingle" }) ); const actions = mcNexus.buildSessionAction({ type: "custom", data: { chainIds: [8453, 10], contractAddress: UNISWAP_ROUTER, functionSignature, policies: [ { type: "timeframe", validAfter, // unix-timestamp when policy becomes active validUntil // unix-timestamp when policy expires }, { type: "universal", rules: [ { condition: "lessThanOrEqual", calldataOffset: calldataArgument(2), comparisonValue: parseUnits("500", 6), isLimited: true, usage: { limit: parseUnits("4000", 6) } } ], } ] } }); const permissions = { actions }; ``` **Best policies:** Universal Action (spending limits) + Timeframe *** ### 📈 Yield Optimization Agents Move funds to highest-yielding opportunities across protocols and chains. ```typescript theme={null} // Agent can deposit/withdraw from approved protocols // Morpho, Aave, Compound on Base, Optimism, Arbitrum // Max 10,000 USDC total exposure const [morphoAction] = mcNexus.buildSessionAction({ type: "custom", data: { chainIds: [8453], contractAddress: MORPHO_BASE, ... } }); const [aaveAction] = mcNexus.buildSessionAction({ type: "custom", data: { chainIds: [10], contractAddress: AAVE_OP, ... } }); const [compoundAction] = mcNexus.buildSessionAction({ type: "custom", data: { chainIds: [42161], contractAddress: COMPOUND_ARB, ... } }); const permissions = { actions: [morphoAction, aaveAction, compoundAction] }; ``` **Best policies:** Usage Limit + No other restrictive policies (for trusted protocols) *** ### 🔄 Portfolio Rebalancing Agents Maintain target allocations by automatically trading when drift exceeds threshold. ```typescript theme={null} // Agent can swap between ETH, BTC, stables // Only rebalance when >5% drift // Max 2% of portfolio per rebalance ``` **Best policies:** Universal Action (parameter rules for amounts) *** ### 💳 Subscription & Payments Agents Automate recurring payments, subscriptions, and payroll. ```typescript theme={null} // Agent can send up to 100 USDC // Only to merchant address 0x... // expires in 1 year and only 12 payments are allowed // Chargeable on day one of the month const dayOneTimeframePolicy = mcNexus.buildActionPolicy({ type: "timeframe", validAfter: ..., // unix-timestamp Eg: 1-1-2026 00:00:00 in unix timestamp validUntil: ... // unix-timestamp Eg: 1-1-2026 23:59:59 in unix timestamp }); // Used only once per month const usagePolicy = mcNexus.buildActionPolicy({ type: "usageLimit", limit: 1n }); // Only specific recipient and specific amount const paymentPolicy = mcNexus.buildActionPolicy({ type: "universal", rules: [ // Only send to merchant { condition: "equal", calldataOffset: calldataArgument(1), comparisonValue: MERCHANT }, // Max 100 USDC per payment { condition: "lessThanOrEqual", calldataOffset: calldataArgument(2), comparisonValue: parseUnits("100", 6), } ], // Configure spending limit for native token valueLimitPerUse: 0n, }); const monthOnePaymentAction = mcNexus.buildSessionAction({ type: "transfer", data: { chainIds: [8453, 10], contractAddress: USDC, policies: [dayOneTimeframePolicy, usagePolicy, paymentPolicy] } }); // 10 similar actions for 10 months const monthTwelvePaymentAction = mcNexus.buildSessionAction({ type: "transfer", data: { chainIds: [8453, 10], contractAddress: USDC, policies: [dayOneTimeframePolicy, usagePolicy, paymentPolicy] } }); // 12 actions which can be executed only once per month on day one for a year. const permissions = { actions: [monthOnePaymentAction, ..., monthTwelvePaymentAction] }; ``` **Best policies:** Universal Action + Usage Limit + Timeframe *** ### 🎮 Gaming & Social Agents Automate in-game actions, social interactions, or NFT operations. ```typescript theme={null} // Agent can mint, transfer, and interact with game contract // No value transfers allowed // 1000 actions per day ``` **Best policies:** Usage Limit + No other restrictive policy *** ### 🏦 Treasury Management Agents Institutional-grade automated treasury operations. ```typescript theme={null} // Agent can move funds between approved vaults // Requires 2-of-3 multisig for amounts over $100k // Full audit trail ``` **Best policies:** Universal Action + custom multisig integration ## Quick Implementation ```typescript theme={null} import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; const agentKey = generatePrivateKey(); const agentSigner = privateKeyToAccount(agentKey); // Store agentKey securely in your backend ``` ```typescript theme={null} const quote = await userClient.getSessionQuote({ mode: "PREPARE", enableSession: { redeemer: agentSigner.address, actions: [/* your agent's allowed actions */], maxPaymentAmount: parseUnits("10", 6), }, simulation: { simulate: true }, // Can be sponsored if needed sponsorship: true, feeToken: { address: USDC, chainId: 8453 }, trigger: { tokenAddress: USDC, chainId: 8453, amount: parseUnits("2", 6), }, }); if (quote) { const { hash } = await userClient.executeSessionQuote(quote); await userClient.waitForSupertransactionReceipt({ hash }); if (quote.sessionDetails) // Store sessionDetails - agent needs this } ``` ```typescript theme={null} // In your agent backend const quote = await agentClient.getSessionQuote({ mode: "USE", sessionDetails, simulation: { simulate: true }, feeToken: { address: USDC, chainId: 8453 }, instructions: [/* agent's action */] }); const { hash } = await agentClient.executeSessionQuote(quote); await agentClient.waitForSupertransactionReceipt({ hash }); ``` ## Why Biconomy? Single permission works across all chains. No per-chain grants. Users keep their existing wallets. No migration needed. Agents can pay gas in any token or have it sponsored. Smart contracts enforce limits. No trust required. ## Next Steps Pick the right security model for your agent Step-by-step code walkthrough # Session Policies Overview Source: https://docs.biconomy.io/agents-automation/policies/index Control what your agent can do with fine-grained policies Policies define the rules that govern what your agent can and cannot do. They're enforced on-chain by smart contracts—not by trust. ## Available Policies Full access to specific functions. Simple but powerful. Fine-grained parameter-level control with spending limits. Restrict when the agent can act. Cap the total number of actions. ## How Policies Work When your agent tries to execute an action: ``` Agent gets a session quote with instructions and submits it for execution ↓ MEE broadcasts the transaction to blockchain ↓ Smart contract checks: ✓ Is this contract/function allowed? ✓ Do parameters pass all policy rules? ✓ Is the session still valid (time)? ✓ Is usage limit not exceeded? ↓ All checks pass → Execute Any check fails → Revert ``` ## Combining Policies Real agents typically combine multiple policies for defense in depth: ```typescript theme={null} const functionSignature = toFunctionSelector( getAbiItem({ abi: UniswapRouterAbi, name: "exactInputSingle" }) ); const customActions = mcNexus.buildSessionAction({ type: "custom", data: { chainIds: [8453, 10], contractAddress: UNISWAP_ROUTER, functionSignature, policies: [ // Time limit { type: "timeframe", validAfter: now, validUntil: now + 7 * DAY, }, // Usage limit { type: "usageLimit", limit: 5n }, // Spending limits { type: "universal", rules: [ { condition: "equal", calldataOffset: calldataArgument(2), // 10 USDC per tx comparisonValue: parseUnits("10", 6) } ], } ] } }); ``` ## Policy Selection Guide | Your Agent Needs | Use This Policy | | ------------------------------- | ----------------------------------------- | | Full access to trusted protocol | Sudo | | Spending limits per action | Universal Action | | Total spending caps | Universal Action (with `isLimited: true`) | | Recipient whitelisting | Universal Action | | Time-limited access | Timeframe | | Max number of actions | Usage Limit | | Scheduled execution windows | Timeframe | ## Quick Decision Tree ``` Is the target contract fully trusted? ├─ Yes: Consider Timeframe + No other restrictive policy └─ No: Use Universal Action Does the agent handle user funds? ├─ Yes: Universal Action with spending limits └─ No: Sudo may be acceptable Need to limit total actions? ├─ Yes: Add Usage Limit └─ No: Timeframe may suffice ``` ## Security Layers Always think in layers: | Layer | Control | Example | | ------------- | ----------------------------- | ------------------------------ | | **Contract** | Which contracts can be called | Only Uniswap, Morpho | | **Function** | Which functions are allowed | Only `swap()`, not `approve()` | | **Parameter** | Rules on arguments | Max \$500 per trade | | **Time** | When agent can act | Next 7 days only | | **Usage** | How many times | Max 50 trades | | **Gas** | Max gas spend | \$20 USDC for gas | ## Next Steps Start here for simple agents Start here for DeFi agents # Sudo Policy Source: https://docs.biconomy.io/agents-automation/policies/sudo Grant full access to specific contract functions The Sudo Policy grants unrestricted access to specified contract functions. It's the simplest policy—and the most powerful. ## When to Use Sudo Target contract is audited and trusted (Uniswap, Aave, Morpho) No user funds at direct risk (game actions, governance votes) You need simplicity over granular control Combined with time limits or usage limits ## When NOT to Use Sudo Agent handles significant user funds without other limits Target contract has dangerous functions (e.g., `approve` for arbitrary spenders) You need spending caps or parameter validation Indefinite access without time bounds ## Basic Usage ```typescript theme={null} const actions = mcNexus.buildSessionAction({ type: 'transfer', data: { chainIds: [10], contractAddress: UNISWAP_ROUTER, policies: [{ type: "sudo" }] } }) ``` ## Agent Examples ### Yield Optimizer Agent Trust deposits/withdrawals on vetted protocols: ```typescript theme={null} const TRUSTED_PROTOCOLS = { morpho: "0xA2Cac0023a4797b4729Db94783405189a4203AFc", aave: "0x...", }; const now = Math.floor(Date.now() / 1000) const validAfter = now const validUntil = now + 3600 const timeframePolicy = mcNexus.buildActionPolicy({ type: "timeframe", validAfter, // unix-timestamp when policy becomes active validUntil // unix-timestamp when policy expires }); // Morpho deposit const [actionOne] = mcNexus.buildSessionAction({ type: 'custom', data: { chainIds: [10], contractAddress: TRUSTED_PROTOCOLS.morpho, functionSignature: toFunctionSelector("deposit(uint256,address)"), policies: [timeframePolicy] } }) // Morpho withdraw const [actionTwo] = mcNexus.buildSessionAction({ type: 'custom', data: { chainIds: [10], contractAddress: TRUSTED_PROTOCOLS.morpho, functionSignature: toFunctionSelector("withdraw(uint256,address,address)"), policies: [timeframePolicy] } }) // Aave supply const [actionThree] = mcNexus.buildSessionAction({ type: 'custom', data: { chainIds: [10], contractAddress: TRUSTED_PROTOCOLS.aave, functionSignature: toFunctionSelector("supply(address,uint256,address,uint16)"), policies: [timeframePolicy] } }) // Aave withdraw const [actionFour] = mcNexus.buildSessionAction({ type: 'custom', data: { chainIds: [10], contractAddress: TRUSTED_PROTOCOLS.aave, functionSignature: toFunctionSelector("withdraw(address,uint256,address)"), policies: [timeframePolicy] } }) const quote = await meeClient.getSessionQuote({ mode: "PREPARE", enableSession: { redeemer: agentSigner.address, actions: [actionOne, actionTwo, actionThree, actionFour], // Cap gas spend maxPaymentAmount: parseUnits("50", 6), }, simulation: { simulate: true }, feeToken: { address: USDC, chainId: 8453 }, // Fund SCA trigger: { tokenAddress: USDC, chainId: 8453, amount: parseUnits("2", 6), }, // Any additional Instructions instructions: [...] }); // Store the sessions details for later use. let sessionDetails: SessionDetail[] = []; if (quote) { const { hash } = await meeClient.executeSessionQuote(quote); await meeClient.waitForSupertransactionReceipt({ hash }); if (quote.sessionDetails) sessionDetails = quote.sessionDetails; } ``` ### Gaming Agent Full access to game contract with usage limits: ```typescript theme={null} const GAME_CONTRACT = "0x..."; const [claimAction] = mcNexus.buildSessionAction({ type: 'custom', data: { chainIds: [8453], contractAddress: GAME_CONTRACT, functionSignature: toFunctionSelector("claimRewards()"), policies: [ { type: "usageLimit", limit: 100n // Max 100 claims } ] } }) const [performAction] = mcNexus.buildSessionAction({ type: 'custom', data: { chainIds: [8453], contractAddress: GAME_CONTRACT, functionSignature: toFunctionSelector("performAction(uint256)"), policies: [ { type: "usageLimit", limit: 1000n // Max 1000 actions } ] } }) ``` ### Governance Agent Vote on proposals automatically: ```typescript theme={null} const GOVERNOR = "0x..."; const [castVoteAction] = mcNexus.buildSessionAction({ type: 'custom', data: { chainIds: [8453], contractAddress: GOVERNOR, functionSignature: toFunctionSelector("castVote(uint256,uint8)"), policies: [ // 1 year for governance { type: "timeframe", validAfter: Math.floor(Date.now() / 1000), validUntil: Math.floor(Date.now() / 1000) + 365 * 24 * 60 * 60 } ] } }) ``` ## Security Checklist Timeframe Policy configured Only specific functions allowed (not entire contract) Target contract is audited Usage limit considered for high-risk actions Gas spend capped (`maxPaymentAmount`) ## Common Mistakes **Granting Sudo to `approve()` function** This lets the agent approve arbitrary spenders. Never do this: ```typescript theme={null} // ❌ DANGEROUS functionSignature: toFunctionSelector("approve(address,uint256)") ``` **No time limit** Without timeframe policy, the permission never expires: ```typescript theme={null} // ❌ BAD - no time limit const actions = mcNexus.buildSessionAction({ type: 'transfer', data: { chainIds: [10], contractAddress: UNISWAP_ROUTER, policies: [{ type: "sudo" }] } }) // ✅ GOOD - expires in 30 days const now = Math.floor(Date.now() / 1000) const validAfter = now const validUntil = now + 30 * 24 * 60 * 60 const actions = mcNexus.buildSessionAction({ type: 'transfer', data: { chainIds: [10], contractAddress: UNISWAP_ROUTER, policies: [ { type: "sudo" }, { type: "timeframe", validAfter, validUntil } ] } }) ``` ## When to Upgrade to Universal Action Switch to Universal Action when you need: * Spending limits (per-action or total) * Recipient whitelisting * Parameter validation * Cumulative caps See [Universal Action Policy](/agents-automation/policies/universal-action) for details. # Timeframe Policy Source: https://docs.biconomy.io/agents-automation/policies/time-range Control when your agent can act The Timeframe Policy restricts when your agent can execute actions. Essential for scheduled automation, temporary access, and timebounded safety limits. ## When to Use Timeframe Subscription payments Trial periods for new users Scheduled automation (run only at specific times) Combining with Sudo for safety ## Basic Usage ```typescript theme={null} const now = Math.floor(Date.now() / 1000) const validAfter = now const validUntil = now + 3600 const timeframePolicy = mcNexus.buildActionPolicy({ type: "timeframe", validAfter, // unix-timestamp when policy becomes active validUntil // unix-timestamp when policy expires }); ``` ## Agent Examples ### Subscription Agent (Monthly Windows) Only allow payments once per month for a year ```typescript theme={null} // Chargeable on day one of the month const dayOneTimeframePolicy = mcNexus.buildActionPolicy({ type: "timeframe", validAfter: ..., // unix-timestamp Eg: 1-1-2026 00:00:00 in unix timestamp validUntil: ... // unix-timestamp Eg: 1-1-2026 23:59:59 in unix timestamp }); // Used only once per month const usagePolicy = mcNexus.buildActionPolicy({ type: "usageLimit", limit: 1n }); const monthOnePaymentAction = mcNexus.buildSessionAction({ type: "transfer", data: { chainIds: [8453, 10], contractAddress: USDC, policies: [dayOneTimeframePolicy, usagePolicy] } }); // 10 similar actions for 10 months const monthTwelvePaymentAction = mcNexus.buildSessionAction({ type: "transfer", data: { chainIds: [8453, 10], contractAddress: USDC, policies: [dayOneTimeframePolicy, usagePolicy] } }); // 12 actions which can be executed only once per month on day one for a year. // Get a session quote and execute it to enable a permission for subscriptions ``` ### Trial Agent (7-Day Trial) Limited access for evaluation: ```typescript theme={null} const SEVEN_DAYS = 7 * 24 * 60 * 60; const now = Math.floor(Date.now() / 1000) const validAfter = now const validUntil = now + SEVEN_DAYS // Only 7 days trail const timeframePolicy = mcNexus.buildActionPolicy({ type: "timeframe", validAfter, validUntil }); // Only 100 trades in trail mode const usagePolicy = mcNexus.buildActionPolicy({ type: "usageLimit", limit: 100n }); const monthOnePaymentAction = mcNexus.buildSessionAction({ type: "transfer", data: { chainIds: [8453, 10], contractAddress: USDC, policies: [timeframePolicy, usagePolicy] } }); ``` ### Yield Optimizer (Quarterly Rebalancing) Restrict to specific rebalancing windows: ```typescript theme={null} // Chargeable in every quarter const quarterlyTimeframePolicy = mcNexus.buildActionPolicy({ type: "timeframe", validAfter: ..., // unix-timestamp Eg: 1-1-2026 in unix timestamp validUntil: ... // unix-timestamp Eg: 31-3-2026 in unix timestamp }); // Used only once per quarter const usagePolicy = mcNexus.buildActionPolicy({ type: "usageLimit", limit: 1n }); const quarterOneRebalanceAction = mcNexus.buildSessionAction({ type: "custom", data: { chainIds: [8453, 10], contractAddress: REBALANCER, functionSignature: '0x...', policies: [quarterlyTimeframePolicy, usagePolicy] } }); // 2 similar actions for 2 quarters const quarterFourRebalanceAction = mcNexus.buildSessionAction({ type: "custom", data: { chainIds: [8453, 10], contractAddress: REBALANCER, functionSignature: '0x...', policies: [quarterlyTimeframePolicy, usagePolicy] } }); // 4 actions which can be executed only once per quarter for a year. // Get a session quote and execute it to enable a permissions for rebalancer ``` ## Delayed Start Start permissions in the future: ```typescript theme={null} const ONE_WEEK = 7 * 24 * 60 * 60; const now = Math.floor(Date.now() / 1000) const validAfter = now + ONE_WEEK; const validUntil = validAfter + ONE_WEEK; const delayedTimeframePolicy = mcNexus.buildActionPolicy({ type: "timeframe", validAfter, validUntil }); // Transfer allowed after a week of delay const delayedAction = mcNexus.buildSessionAction({ type: "transfer", data: { chainIds: [8453, 10], contractAddress: USDC, policies: [delayedTimeframePolicy] } }); ``` Use cases: * Scheduled launches * Vesting schedules * Delayed automation ## Combining with Other Policies Timeframe works best combined with other restrictions: ### Time + No other restrictive policies ```typescript theme={null} const timeframePolicy = mcNexus.buildActionPolicy({ type: "timeframe", validAfter, validUntil }); const actions = mcNexus.buildSessionAction({ type: "transfer", data: { chainIds: [8453, 10], contractAddress: USDC, policies: [timeframePolicy] } }); ``` ### Time + Usage Limit ```typescript theme={null} const timeframePolicy = mcNexus.buildActionPolicy({ type: "timeframe", validAfter, validUntil }); const usagePolicy = mcNexus.buildActionPolicy({ type: "usageLimit", limit: 5n }); const actions = mcNexus.buildSessionAction({ type: "transfer", data: { chainIds: [8453, 10], contractAddress: USDC, policies: [timeframePolicy, usagePolicy] } }); ``` ### Time + Universal Action ```typescript theme={null} const timeframePolicy = mcNexus.buildActionPolicy({ type: "timeframe", validAfter, validUntil }); const universalPolicy = mcNexus.buildActionPolicy({ type: "universal", rules: [ { condition: "equal", calldataOffset: calldataArgument(2), comparisonValue: parseUnits("10", 6) } ], }); const actions = mcNexus.buildSessionAction({ type: "transfer", data: { chainIds: [8453, 10], contractAddress: USDC, policies: [timeframePolicy, universalPolicy] } }); ``` ## Time Format All timestamps are **Unix timestamps in seconds** (not milliseconds): ```typescript theme={null} // ✅ Correct - seconds const validUntil = Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60; // ❌ Wrong - milliseconds const validUntil = Date.now() + 7 * 24 * 60 * 60 * 1000; ``` ## Helper Functions ```typescript theme={null} const DAY = 24 * 60 * 60; const WEEK = 7 * DAY; const MONTH = 30 * DAY; const YEAR = 365 * DAY; const now = () => Math.floor(Date.now() / 1000); const inDays = (days: number) => now() + days * DAY; const inWeeks = (weeks: number) => now() + weeks * WEEK; const inMonths = (months: number) => now() + months * MONTH; ``` # Universal Action Policy Source: https://docs.biconomy.io/agents-automation/policies/universal-action Fine-grained parameter-level control for DeFi agents The Universal Action Policy provides granular control over function parameters. Use it when your agent handles funds and you need spending limits, recipient whitelisting, or parameter validation. ## When to Use Universal Action Agent handles user funds Need per-action spending limits Need cumulative spending caps Want to restrict recipients Need parameter validation ## Core Concepts ### Parameter Rules Each rule validates one parameter in the function call and maximum of 16 rules can be added per policy. ```typescript theme={null} const universalPolicy = mcNexus.buildActionPolicy({ type: "universal", rules: [ { condition: "equal", // Comparison type calldataOffset: calldataArgument(1), // Argument position comparisonValue: 10n || "0x..", // Value to compare against isLimited: true, // Track cumulative usage usage: { limit: parseUnits("5000", 6), used: 0n } // Cumulative limit } ], // Configure spending limit for native token valueLimitPerUse: 1n }); ``` ### Conditions | Condition | Meaning | Use Case | | -------------------- | ------------------ | ----------------------- | | `equal` | Must equal ref | Whitelist addresses | | `lessThan` | Must be \< ref | Amount caps | | `lessThanOrEqual` | Must be ≤ ref | Amount caps (inclusive) | | `greaterThan` | Must be > ref | Minimum amounts | | `greaterThanOrEqual` | Must be ≥ ref | Minimum amounts | | `notEqual` | Must not equal ref | Blacklist | ## Trading Agent Example Limit per-trade and max spending limit per session: ```typescript theme={null} const UNISWAP_ROUTER = "0x2626664c2603336E57B271c5C0b26F421741e481"; const tradingPolicy = mcNexus.buildActionPolicy({ type: "universal", rules: [ // Rule 1: Recipient must be user's account { condition: "equal", calldataOffset: calldataArgument(4), comparisonValue: userAccountAddress, }, // Rule 2: Max $500 per trade, $5000 cumulative { condition: "lessThanOrEqual", calldataOffset: calldataArgument(5), comparisonValue: parseUnits("500", 6), isLimited: true, // Track cumulative usage: { limit: parseUnits("5000", 6), // $5000 total limit used: 0n } } ], valueLimitPerUse: 0n, // No ETH value }); const now = Math.floor(Date.now() / 1000) const validAfter = now const validUntil = now + 7 * 24 * 60 * 60 const timeframePolicy = mcNexus.buildActionPolicy({ type: "timeframe", validAfter, // unix-timestamp when policy becomes active validUntil // unix-timestamp when policy expires }); const UNISWAP_ROUTER = "0x2626664c2603336E57B271c5C0b26F421741e481"; const customActions = mcNexus.buildSessionAction({ type: "custom", data: { chainIds: [8453, 10], contractAddress: UNISWAP_ROUTER, functionSignature: '0x...', policies: [tradingPolicy, timeframePolicy] } }); // Finally get the session quote and execute the quote to enable permissions ``` ## Payment Agent Example Fixed recipient and amount: ```typescript theme={null} const MERCHANT = "0x1234..."; const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; const paymentPolicy = mcNexus.buildActionPolicy({ type: "universal", rules: [ // Only send to merchant { condition: "equal", calldataOffset: calldataArgument(1), comparisonValue: MERCHANT }, // Max 100 USDC per payment { condition: "lessThanOrEqual", calldataOffset: calldataArgument(2), comparisonValue: parseUnits("100", 6), } ], // Configure spending limit for native token valueLimitPerUse: 0n, }); const usagePolicy = mcNexus.buildActionPolicy({ type: "usageLimit", limit: 12n, // 12 payments max }); const now = Math.floor(Date.now() / 1000) const validAfter = now const validUntil = now + 365 * 24 * 60 * 60 const timeframePolicy = mcNexus.buildActionPolicy({ type: "timeframe", validAfter, // unix-timestamp when policy becomes active validUntil // unix-timestamp when policy expires }); // Finally use these policies in the action and enable permission for agents. const policies = [paymentPolicy, usageLimit, timeframePolicy]; ``` ## Rebalancing Agent Example Limit rebalance size to % of portfolio: ```typescript theme={null} const rebalancePolicy = mcNexus.buildActionPolicy({ type: "universal", rules: [ // Max 10% of portfolio per rebalance ($10,000 cap) { condition: "lessThanOrEqual", calldataOffset: calldataArgument(4), comparisonValue: parseUnits("10000", 6), // $10k isLimited: true, usage: { limit: parseUnits("50000", 6), // $50k total per period used: 0n } } ], valueLimitPerUse: 0n, }); ``` ## Per-Action vs Cumulative Limits ### Per-Action Only Each individual action must be ≤ limit, but no cumulative tracking: ```typescript theme={null} const universalPolicy = mcNexus.buildActionPolicy({ type: "universal", rules: [ { condition: "lessThanOrEqual", calldataOffset: calldataArgument(2), comparisonValue: parseUnits("500", 6), // Max $500 per trade // No cumulative tracking } ] }); ``` ### Cumulative Tracking Track total spent across all actions: ```typescript theme={null} const universalPolicy = mcNexus.buildActionPolicy({ type: "universal", rules: [ { condition: "lessThanOrEqual", calldataOffset: calldataArgument(2), comparisonValue: parseUnits("500", 6), // Max $500 per trade isLimited: true, // Track cumulative usage: { limit: parseUnits("5000", 6), // $5000 total used: 0n } } ] }); ``` ## Multiple Rules Example Validate multiple parameters: ```typescript theme={null} // For transfer(address recipient, uint256 amount) const universalPolicyOne = mcNexus.buildActionPolicy({ type: "universal", rules: [ // Rule 1: Recipient in whitelist (address A) { condition: "equal", calldataOffset: calldataArgument(1), comparisonValue: ALLOWED_RECIPIENT_A }, // Rule 2: Amount limit { condition: "lessThanOrEqual", calldataOffset: calldataArgument(2), comparisonValue: parseUnits("1000", 6), isLimited: true, usage: { limit: parseUnits("10000", 6), used: 0n } } ] }); const universalPolicyTwo = mcNexus.buildActionPolicy({ type: "universal", rules: [ // Rule 1: recipient B (rules are OR'd for same offset) { condition: "equal", calldataOffset: calldataArgument(1), comparisonValue: ALLOWED_RECIPIENT_B }, // Rule 3: Amount limit { condition: "lessThanOrEqual", calldataOffset: calldataArgument(2), comparisonValue: parseUnits("1000", 6), isLimited: true, usage: { limit: parseUnits("10000", 6), used: 0n } } ] }); // This allows the recipient to be either A or B const policies = [universalPolicyOne, universalPolicyTwo]; ``` ## Finding Parameter Positions: For a function like `swap(address tokenIn, address tokenOut, uint256 amount)`: ``` calldataArgument(1): tokenIn (address) calldataArgument(2): tokenOut (address) calldataArgument(3): amount (uint256) ``` For structs, calldataArguments are more complex. Check the ABI encoding. ## Common Patterns ### Whitelist Single Recipient ```typescript theme={null} const universalPolicy = mcNexus.buildActionPolicy({ type: "universal", rules: [ { condition: "equal", calldataOffset: calldataArgument(1), comparisonValue: ALLOWED_ADDRESS }, ] }); ``` ### Cap Per Transaction Amount ```typescript theme={null} const universalPolicy = mcNexus.buildActionPolicy({ type: "universal", rules: [ { condition: "lessThanOrEqual", calldataOffset: calldataArgument(2), comparisonValue: parseUnits("500", 6) }, ] }); ``` ### Total Max Spending Limit ```typescript theme={null} const universalPolicy = mcNexus.buildActionPolicy({ type: "universal", rules: [ { condition: "lessThanOrEqual", calldataOffset: calldataArgument(2), comparisonValue: parseUnits("500", 6), // Per-tx max isLimited: true, usage: { limit: parseUnits("5000", 6), // Cumulative max used: 0n } }, ] }); ``` ### Minimum Amount ```typescript theme={null} const universalPolicy = mcNexus.buildActionPolicy({ type: "universal", rules: [ { condition: "greaterThanOrEqual", calldataOffset: calldataArgument(2), comparisonValue: parseUnits("10", 6), // At least $10 }, ] }); ``` ## Debugging Tips 1. **Check offsets**: Use `console.log(encodeFunctionData(...))` to see actual calldata 2. **Match decimals**: USDC = 6, ETH = 18, etc. 3. **Test on testnet**: Verify rules work before mainnet # Usage Limit Policy Source: https://docs.biconomy.io/agents-automation/policies/usage-limit Cap the total number of actions your agent can perform The Usage Limit Policy restricts how many times your agent can execute a specific action. Simple but effective rate limiting. ## When to Use Usage Limit Subscription agents (12 payments per year) Trial periods (limited actions) Preventing runaway agents Metered access models Safety cap for any automation ## Basic Usage ```typescript theme={null} const usagePolicy = mcNexus.buildActionPolicy({ type: "usageLimit", limit: 5n }); // 5 transfers per session const actions = mcNexus.buildSessionAction({ type: "transfer", data: { chainIds: [8453, 10], contractAddress: USDC, policies: [usagePolicy] } }); ``` ## Agent Examples ### Subscription Agent (12/Year) ```typescript theme={null} // Chargeable on day one of the month const dayOneTimeframePolicy = mcNexus.buildActionPolicy({ type: "timeframe", validAfter: ..., // unix-timestamp Eg: 1-1-2026 00:00:00 in unix timestamp validUntil: ... // unix-timestamp Eg: 1-1-2026 23:59:59 in unix timestamp }); // Used only once per month const usagePolicy = mcNexus.buildActionPolicy({ type: "usageLimit", limit: 1n }); const monthOnePaymentAction = mcNexus.buildSessionAction({ type: "transfer", data: { chainIds: [8453, 10], contractAddress: USDC, policies: [dayOneTimeframePolicy, usagePolicy] } }); // 10 similar actions for 10 months const monthTwelvePaymentAction = mcNexus.buildSessionAction({ type: "transfer", data: { chainIds: [8453, 10], contractAddress: USDC, policies: [dayOneTimeframePolicy, usagePolicy] } }); // 12 actions which can be executed only once per month on day one for a year. // Get a session quote and execute it to enable a permission for subscriptions ``` ### Trial Agent (10 Actions) ```typescript theme={null} const SEVEN_DAYS = 7 * 24 * 60 * 60; const now = Math.floor(Date.now() / 1000) const validAfter = now const validUntil = now + SEVEN_DAYS // Only 7 days trail const timeframePolicy = mcNexus.buildActionPolicy({ type: "timeframe", validAfter, validUntil }); // Only 100 trades in trail mode const usagePolicy = mcNexus.buildActionPolicy({ type: "usageLimit", limit: 100n }); const monthOnePaymentAction = mcNexus.buildSessionAction({ type: "transfer", data: { chainIds: [8453, 10], contractAddress: USDC, policies: [timeframePolicy, usagePolicy] } }); ``` ### Rebalancing Agent (Quarterly) ```typescript theme={null} // Chargeable in every quarter const quarterlyTimeframePolicy = mcNexus.buildActionPolicy({ type: "timeframe", validAfter: ..., // unix-timestamp Eg: 1-1-2026 in unix timestamp validUntil: ... // unix-timestamp Eg: 31-3-2026 in unix timestamp }); // Used only once per quarter const usagePolicy = mcNexus.buildActionPolicy({ type: "usageLimit", limit: 1n }); const quarterOneRebalanceAction = mcNexus.buildSessionAction({ type: "custom", data: { chainIds: [8453, 10], contractAddress: REBALANCER, functionSignature: '0x...', policies: [quarterlyTimeframePolicy, usagePolicy] } }); // 2 similar actions for 2 quarters const quarterFourRebalanceAction = mcNexus.buildSessionAction({ type: "custom", data: { chainIds: [8453, 10], contractAddress: REBALANCER, functionSignature: '0x...', policies: [quarterlyTimeframePolicy, usagePolicy] } }); // 4 actions which can be executed only once per quarter for a year. // Get a session quote and execute it to enable a permissions for rebalancer ``` ### Gaming Agent (Action Budget) ```typescript theme={null} // 100 attacks per session const attackUsagePolicy = mcNexus.buildActionPolicy({ type: "usageLimit", limit: 100n }); const [attackAction] = mcNexus.buildSessionAction({ type: "custom", data: { chainIds: [8453], contractAddress: GAME, functionSignature: toFunctionSelector("attack(uint256)"), policies: [attackUsagePolicy] } }); // 50 heals per session const healUsagePolicy = mcNexus.buildActionPolicy({ type: "usageLimit", limit: 50n }); const [healAction] = mcNexus.buildSessionAction({ type: "custom", data: { chainIds: [8453], contractAddress: GAME, functionSignature: toFunctionSelector("heal()"), policies: [healUsagePolicy] } }); // 5 claim rewards per session const claimRewardsUsagePolicy = mcNexus.buildActionPolicy({ type: "usageLimit", limit: 5n }); const [claimAction] = mcNexus.buildSessionAction({ type: "custom", data: { chainIds: [8453], contractAddress: GAME, functionSignature: toFunctionSelector("claimRewards()"), policies: [claimRewardsUsagePolicy] } }); ``` ### Metered Access Sell action packs: ```typescript theme={null} const USAGE_TIERS = { starter: 1000n, pro: 10_000n, }; const starterUsagePolicy = mcNexus.buildActionPolicy({ type: "usageLimit", limit: USAGE_TIERS["starter"] }); const proUsagePolicy = mcNexus.buildActionPolicy({ type: "usageLimit", limit: USAGE_TIERS["pro"] }); ``` ## Combining with Other Policies ### Usage + Time (Most Common) ```typescript theme={null} const timeframePolicy = mcNexus.buildActionPolicy({ type: "timeframe", validAfter, validUntil }); const usagePolicy = mcNexus.buildActionPolicy({ type: "usageLimit", limit: 5n }); const actions = mcNexus.buildSessionAction({ type: "transfer", data: { chainIds: [8453, 10], contractAddress: USDC, policies: [timeframePolicy, usagePolicy] } }); ``` ### Usage + Universal Action ```typescript theme={null} const universalPolicy = mcNexus.buildActionPolicy({ type: "universal", rules: [ { condition: "equal", calldataOffset: calldataArgument(2), comparisonValue: parseUnits("10", 6) } ], // Configure spending limit for native token valueLimitPerUse: 1n }); const usagePolicy = mcNexus.buildActionPolicy({ type: "usageLimit", limit: 5n }); const actions = mcNexus.buildSessionAction({ type: "transfer", data: { chainIds: [8453, 10], contractAddress: USDC, policies: [universalPolicy, usagePolicy] } }); ``` Usage renewal: When usage limits are reached, the onchain actions will be reverted. Need to enable a fresh session for the user. ## Limits * `usageLimit` is a `bigint` * Maximum value: `2^256 - 1` (effectively unlimited) * Counter decrements atomically per successful execution * Failed executions don't count against limit # Build CCIP token bridge instructions Source: https://docs.biconomy.io/api-reference/instructions/build-ccip-token-bridge-instructions supertransaction-api/openapi.yaml post /v1/instructions/build-ccip Build CCIP token bridge instructions for your supertransaction # Build Hyperliquid deposit instructions Source: https://docs.biconomy.io/api-reference/instructions/build-hyperliquid-deposit-instructions supertransaction-api/openapi.yaml post /v1/instructions/hyperliquid/deposit Build instructions for depositing USDC from Arbitrum to Hyperliquid # Get orchestrator addresses Source: https://docs.biconomy.io/api-reference/mee/get-orchestrator-addresses supertransaction-api/openapi.yaml post /v1/mee/orchestrator Get orchestrator addresses for the specified owner on requested chains # Compose instructions and generate a quote Source: https://docs.biconomy.io/api-reference/root/compose-instructions-and-generate-a-quote supertransaction-api/openapi.yaml post /v1/quote Compose instructions and retrieve a MEE quote in a single request. # Submit the supertransaction for execution Source: https://docs.biconomy.io/api-reference/root/submit-the-supertransaction-for-execution supertransaction-api/openapi.yaml post /v1/execute Submit the signed supertransaction quote for the execution where multichain composable and async execution happens via MEE nodes # Contracts & Audits Source: https://docs.biconomy.io/contracts-and-audits/index Verified smart contract addresses and security audit reports for MEE protocol Always verify contract addresses from official sources before interacting with them ## Contract Suite Biconomy is versioning our smart contracts by groups. One "Suite" version contains many different versioned contracts within itself. The contracts being versioned are: * Nexus Account * Modular Execution Environment (MEE) K1 Validator (ERC-7579 Module) * Nexus Boostrap & Factory * Composability & Fusion Utils (Composable Storage, ETH Forwarder) ## Versions **What's New:** * New fusion mode `safe-sa` which allows using Safe accounts as master accounts instead of EOAs **Attention!** This version utilizes unaudited cotracts. ### Nexus & MEE Validator | Contract Name | Contract Address | | --------------------------- | -------------------------------------------- | | Nexus Implementation v1.3.1 | `0x54F220e4f0DEAb58Be26153df5a674668B9d7Fb2` | | MEE K1 Validator v1.2.0 | `0x1Cdae7dcc3f32551865EfE3d77AC2b88Ee2905B4` | | Nexus Bootstrap v1.3.0 | `0xCa8f48912A3a33fE694c318a1d097AD394CFAB76` | | Nexus Account Factory | `0x5836Bdb35913c7CBA6ef40675354445121449917` | **What's New:** * New composability version - enables native token runtime injection * Sign EIP-712 Data Struct instead of blind Stx hash for the `smart-account` mode * New ERC-7702 Nexus accounts can be initialized via relayers * Gas optimizations ### Nexus & MEE Validator | Contract Name | Contract Address | | --------------------------- | -------------------------------------------- | | Nexus Implementation v1.3.1 | `0x0000000020fe2F30453074aD916eDeB653eC7E9D` | | MEE K1 Validator v1.1.0 | `0x0000000002d3cC5642A748B6783F32C032616E03` | | Nexus Bootstrap v1.3.0 | `0x000000007BfEdA33ac982cb38eAaEf5D7bCC954c` | | Nexus Account Factory | `0x000000002c9A405a196f2dc766F2476B731693c3` | ### Composability & Utility | Contract Name | Contract Address | | --------------------------- | -------------------------------------------- | | Composable Execution Module | `0x00000000f61636C0CA71d21a004318502283aB2d` | | Composable Storage | `0x0000000078994c6ef6A4596BE53A728b255352c2` | | ETH Forwarder | `0x000000C48Cdf2b46bEc062483dBD27046dfE3b8d` | **What's New:** K1 MEE module introduced - enables ERC-7702-delegated EOAs to own Nexus accounts ### Nexus & MEE Validator | Contract Name | Contract Address | | --------------------------- | -------------------------------------------- | | Nexus Implementation v1.2.0 | `0x00000000383e8cBe298514674Ea60Ee1d1de50ac` | | MEE K1 Validator v1.0.3 | `0x0000000031ef4155C978d48a8A7d4EDba03b04fE` | | Nexus Bootstrap v1.2.1 | `0x0000003eDf18913c01cBc482C978bBD3D6E8ffA3` | | Nexus Account Factory | `0x0000006648ED9B2B842552BE63Af870bC74af837` | ### Composability & Utility | Contract Name | Contract Address | | ------------------ | -------------------------------------------- | | Composable Storage | `0x0000000671eb337E12fe5dB0e788F32e1D71B183` | | ETH Forwarder | `0x000000Afe527A978Ecb761008Af475cfF04132a1` | Major release with Nexus 1.2.0, ERC-7702 support, and native composability ### Nexus & MEE | Contract Name | Contract Address | | --------------------------- | -------------------------------------------- | | Nexus Implementation v1.2.0 | `0x000000004F43C49e93C970E84001853a70923B03` | | MEE K1 Validator v1.0.1 | `0x00000000d12897DDAdC2044614A9677B191A2d95` | | Nexus Bootstrap v1.2.1 | `0x00000000D3254452a909E4eeD47455Af7E27C289` | | Nexus Account Factory | `0x000000001D1D5004a02bAfAb9de2D6CE5b7B13de` | | MEE EntryPoint & Paymaster | `0xE854C84cD68fC434cB3B0042c29235D452cAD977` | ### Composability & Utility | Contract Name | Contract Address | | ------------------ | -------------------------------------------- | | Composable Storage | `0x0000000671eb337E12fe5dB0e788F32e1D71B183` | | ETH Forwarder | `0x000000Afe527A978Ecb761008Af475cfF04132a1` | Compiled for **EVM Paris** (No PUSH0, no MCOPY, no TSTORE) ### Nexus & MEE | Contract Name | Contract Address | | --------------------------- | -------------------------------------------- | | Nexus Implementation v1.0.2 | `0x000000001964d23C59962Fc7A912872EE8fB3b6A` | | MEE K1 Validator v1.0.3 | `0x00000000E894100bEcFc7c934Ab7aC8FBA08A44c` | | Nexus Bootstrap v1.0.0 | `0x000000c4781Be3349F81d341027fd7A4EdFa4Dd2` | | Nexus Account Factory | `0x0000000C8B6b3329cEa5d15C9d8C15F1f254ec3C` | ### Composability & Utility | Contract Name | Contract Address | | -------------------- | -------------------------------------------- | | Composability Module | `0x000000Fc19Cf049b445dd5CC0D590f6f93075f42` | | Composable Storage | `0x000000d2520640d0993B58112b929e71C9747300` | | ETH Forwarder | `0x000000001f1c68bD5bF69aa1cCc1d429700D41Da` | Added Nexus Account Factory at `0x000000903887EA36EBe051038287f49fD4A07733` Initial release - Requires manual installation of MEE K1 validator and Composability module [GitHub Release →](https://github.com/bcnmy/nexus/releases/tag/v1.0.2) ### Nexus & MEE | Contract Name | Contract Address | | --------------------------- | -------------------------------------------- | | Nexus Implementation v1.0.2 | `0x000000aC74357BFEa72BBD0781833631F732cf19` | | MEE K1 Validator v1.0.1 | `0x00000000d12897DDAdC2044614A9677B191A2d95` | | Nexus Bootstrap v1.0.0 | `0x879fa30248eeb693dcCE3eA94a743622170a3658` | | Nexus Account Factory | `0x000000c3A93d2c5E02Cb053AC675665b1c4217F9` | | MEE EntryPoint & Paymaster | `0xE854C84cD68fC434cB3B0042c29235D452cAD977` | ### Composability & Utility | Contract Name | Contract Address | | -------------------- | -------------------------------------------- | | Composability Module | `0x00000004430bB055dB66eBef6Fe5Ee1DA9668B10` | | Composable Storage | `0x0000000671eb337E12fe5dB0e788F32e1D71B183` | | ETH Forwarder | `0x000000Afe527A978Ecb761008Af475cfF04132a1` | ### Nexus Audits Competition Audit * [Core audit](https://github.com/bcnmy/nexus/blob/main/audits/report-cantinacode-biconomy-0708-updated.pdf) * [ERC-7739](https://github.com/bcnmy/nexus/blob/main/audits/report-cantinacode-biconomy-erc7739-addon-final.pdf) Full Review 2025 Review ### MEE Stack Audits **Audited by Zenith** * MEE EntryPoint * Node Paymaster * MEEK1 Validator **Multiple Auditors** * [Zenith Report](https://github.com/bcnmy/composability/blob/main/audits/2025-03-Composability_Zenith-Audit-Report.pdf) * [Pashov Report](https://github.com/bcnmy/composability/blob/main/audits/2025-03-Composability-Pashov-Review.pdf) *** ## Legacy Contracts These contracts are deprecated. Use the latest version for new deployments. | Contract Name | Contract Address | | -------------------- | -------------------------------------------- | | Nexus Implementation | `0x000000008761E87F023f65c49DC9cb1C7EdFEaaf` | | K1 Validator | `0x0000002D6DB27c52E3C11c1Cf24072004AC75cBa` | | K1 Validator Factory | `0x00000024115AA990F0bAE0B6b0D5B8F68b684cd6` | | Account Factory | `0x000000226cada0d8b36034F5D5c06855F59F6F3A` | | Bootstrap | `0x000000F5b753Fdd20C5CA2D7c1210b3Ab1EA5903` | | EntryPoint V7 | `0x0000000071727de22e5e9d8baf0edac6f37da032` | ### Base & Optimism | Contract Name | Contract Address | | --------------------- | -------------------------------------------- | | Sponsorship Paymaster | `0x0000006087310897e0BFfcb3f0Ed3704f7146852` | | Token Paymaster | `0x00000000301515A5410e0d768aF4f53c416edf19` | ### Other Chains | Contract Name | Contract Address | | --------------------- | -------------------------------------------- | | Sponsorship Paymaster | `0x00000072a5F551D6E80b2f6ad4fB256A27841Bbc` | | Token Paymaster | `0x00000000301515A5410e0d768aF4f53c416edf19` | # Supported chains Source: https://docs.biconomy.io/contracts-and-audits/supported-chains The list of chains that have Biconomy stack deployed with supported versions ## 🚀 MEE Contracts and versions The Modular Execution Environment (MEE) is Biconomy's Smart Account and Execution infrastructure (which is compliant with and built on ERC 4337) that enables gasless transactions and modular execution flows. Below is a comprehensive list of supported chains and their corresponding MEE versions. On the Biconomy Dashboard, set up a project on the Supertransaction tab, get an API key and you will have full access to all chains with just one API key. For detailed information about contract addresses and audit reports for each version, please check our [contracts and audits page](https://docs.biconomy.io/contracts-and-audits). | Network | MEE Versions | | ------------------ | --------------------------------- | | Ethereum Mainnet | 2.2.1, 2.1.0, 2.0.0, 1.1.0, 1.0.0 | | Base Mainnet | 2.2.1, 2.1.0, 2.0.0, 1.1.0, 1.0.0 | | Polygon Mainnet | 2.2.1, 2.1.0, 2.0.0, 1.1.0, 1.0.0 | | Arbitrum Mainnet | 2.2.1, 2.1.0, 2.0.0, 1.1.0, 1.0.0 | | OP Mainnet | 2.2.1, 2.1.0, 2.0.0, 1.1.0, 1.0.0 | | BSC Mainnet | 2.2.1, 2.1.0, 2.0.0, 1.1.0, 1.0.0 | | Sonic Mainnet | 2.2.1, 2.1.0, 2.0.0, 1.1.0, 1.0.0 | | Scroll Mainnet | 2.2.1, 2.1.0, 2.0.0, 1.1.0, 1.0.0 | | Gnosis Mainnet | 2.2.1, 2.1.0, 2.0.0, 1.1.0, 1.0.0 | | Avalanche Mainnet | 2.2.1, 2.1.0, 2.0.0, 1.1.0 | | Apechain | 2.2.1, 2.1.0, 2.0.0, 1.1.0 | | Hyper EVM | 2.2.1, 2.1.0, 2.0.0, 1.1.0 | | Sei Mainnet | 2.2.1, 2.1.0, 2.0.0, 1.1.0 | | Unichain Mainnet | 2.2.1, 2.1.0, 1.1.0 | | Katana Mainnet | 2.2.1, 2.1.0, 1.1.0 | | Lisk Mainnet | 2.2.1, 2.1.0, 1.1.0 | | Worldchain Mainnet | 2.2.1, 2.1.0, 1.1.0 | | Monad Mainnet | 2.2.1, 2.1.0 | | Plasma Mainnet | 2.2.1, 2.1.0 | | Network | MEE Versions | | ----------------------- | --------------------------------- | | Ethereum Sepolia | 2.2.1, 2.1.0, 2.0.0, 1.1.0, 1.0.0 | | Base Sepolia | 2.2.1, 2.1.0, 2.0.0, 1.1.0, 1.0.0 | | Polygon Amoy Testnet | 2.2.1, 2.1.0, 2.0.0, 1.1.0, 1.0.0 | | Arbitrum Sepolia | 2.2.1, 2.1.0, 2.0.0, 1.1.0, 1.0.0 | | OP Sepolia Testnet | 2.2.1, 2.1.0, 2.0.0, 1.1.0, 1.0.0 | | BSC Testnet | 2.2.1, 2.1.0, 2.0.0, 1.1.0, 1.0.0 | | Scroll Sepolia Testnet | 2.2.1, 2.1.0, 2.0.0, 1.1.0, 1.0.0 | | Gnosis Chiado Testnet | 2.2.1, 2.1.0, 2.0.0, 1.1.0, 1.0.0 | | Avalanche Fuji Testnet | 2.2.1, 2.1.0, 2.0.0, 1.1.0 | | Apechain Curtis Testnet | 2.2.1, 2.1.0, 2.0.0, 1.1.0 | | Core DAO Testnet | 2.2.1, 2.1.0, 2.0.0, 1.1.0 | | Sei Testnet | 2.2.1, 2.1.0, 2.0.0, 1.1.0 | | Neura Testnet | 2.2.1, 2.1.0, 1.1.0 | | Unichain Testnet | 2.2.1, 2.1.0, 1.1.0 | | Worldchain Testnet | 2.2.1, 2.1.0, 1.1.0 | | Sonic Testnet | 2.2.1, 2.1.0 | | Monad Testnet | 2.2.1, 2.1.0 | | Plasma Testnet | 2.2.1, 2.1.0 | | Arc Testnet | 2.2.1, 2.1.0 | | Sophon Zk Testnet | 2.2.1, 2.1.0 | | Fluent Testnet | 2.1.0, 1.1.0 | | Chiliz Spicy Testnet | 2.0.0 Pre-cancun | *** ## 🔧 ERC-4337 Infra Support We are not supporting new chains with this infrastructure. Any new chains we support will utilise our newer and advanced MEE stack, and thus the newer chain support are added to the above MEE Contracts and Services list. If you are a new developer, or starting a new project to find the chains we support, do not reference this list. Why did we move to MEE in favour of ERC 4337? You can learn more [here](https://docs.biconomy.io/new/learn-about-biconomy/mee-vs-4337). All features supported: Bundler, Sponsorship PM, Token PM, Contracts, and Nexus v1.0.2 | Network | Bundler | Sponsorship PM | Token PM | Contracts | Nexus | | --------------------------- | :-----: | :------------: | :------: | :-------: | :---: | | Ethereum Mainnet | ✅ | ✅ | ✅ | ✅ | ✅ | | Ethereum Sepolia | ✅ | ✅ | ✅ | ✅ | ✅ | | Base Mainnet | ✅ | ✅ | ✅ | ✅ | ✅ | | Base Sepolia | ✅ | ✅ | ✅ | ✅ | ✅ | | OP Mainnet | ✅ | ✅ | ✅ | ✅ | ✅ | | OP Sepolia Testnet | ✅ | ✅ | ✅ | ✅ | ✅ | | Scroll Mainnet | ✅ | ✅ | ✅ | ✅ | ✅ | | Scroll Sepolia Testnet | ✅ | ✅ | ✅ | ✅ | ✅ | | Arbitrum Mainnet | ✅ | ✅ | ✅ | ✅ | ✅ | | Arbitrum Sepolia Testnet | ✅ | ✅ | ✅ | ✅ | ✅ | | Binance Smart Chain Mainnet | ✅ | ✅ | ✅ | ✅ | ✅ | | Binance Smart Chain Testnet | ✅ | ✅ | ✅ | ✅ | ✅ | | Polygon Mainnet | ✅ | ✅ | ✅ | ✅ | ✅ | | Gnosis Mainnet | ✅ | ✅ | ✅ | ✅ | ✅ | | Sonic Mainnet | ✅ | ✅ | ✅ | ✅ | ✅ | | Blast Mainnet | ✅ | ✅ | ✅ | ✅ | ✅ | Bundler + Sponsorship PM supported (Token PM not available) | Network | Bundler | Sponsorship PM | Token PM | Contracts | Nexus | | --------------------- | :-----: | :------------: | :------: | :-------: | :---: | | Polygon Amoy Testnet | ✅ | ✅ | ❌ | ✅ | ✅ | | Gnosis Chiado Testnet | ✅ | ✅ | ❌ | ✅ | ✅ | | Sonic Blaze Testnet | ✅ | ✅ | ❌ | ✅ | ✅ | | Blast Sepolia | ✅ | ✅ | ❌ | ✅ | ✅ | | Monad Testnet | ✅ | ✅ | ❌ | ✅ | ✅ | | Soneium Mainnet | ✅ | ✅ | ❌ | ✅ | ✅ | | Soneium Testnet | ✅ | ✅ | ❌ | ✅ | ✅ | | Network | Bundler | Sponsorship PM | Token PM | Contracts | Nexus | | ------------ | :-----: | :------------: | :------: | :-------: | :---: | | IOTA Mainnet | ⏳ | ⏳ | ⏳ | ✅ | ✅ | ### Sponsorship Paymaster Contracts | Chains | Contract Address | | ------------ | ---------------------------------------------------------------------------------------------------------------------------- | | Base & OP | [`0x0000006087310897e0BFfcb3f0Ed3704f7146852`](https://contractscan.xyz/contract/0x0000006087310897e0BFfcb3f0Ed3704f7146852) | | Other chains | [`0x00000072a5F551D6E80b2f6ad4fB256A27841Bbc`](https://contractscan.xyz/contract/0x00000072a5F551D6E80b2f6ad4fB256A27841Bbc) | *** | Network | Testnet | Mainnet | | ------------------ | :-----: | :-----: | | Ethereum | ✅ | ✅ | | Polygon | ✅ | ✅ | | BSC | ✅ | ✅ | | Polygon zkEVM | ✅ | ✅ | | Arbitrum One | ✅ | ✅ | | Arbitrum Nova | ✅ | ✅ | | Optimism (Sepolia) | ✅ | ✅ | | Avalanche | ✅ | ✅ | | Base | ✅ | ✅ | | Linea | ✅ | ✅ | | Chiliz | ✅ | ✅ | | Astar | ✅ | ✅ | | opBNB | ✅ | ✅ | | Manta | ✅ | ✅ | | Core | ✅ | ✅ | | Combo | ✅ | ✅ | | Mantle (Sepolia) | ✅ | ✅ | | Blast | ✅ | ✅ | | Zeroone | ✅ | ✅ | | Scroll | ✅ | ✅ | | Zetachain | ✅ | ✅ | | Gnosis | ✅ | ✅ | | X Layer | ✅ | ✅ | | Tangle | ✅ | ✅ | | Taiko | | ✅ | | Morph | ✅ | ✅ | | Sei (Devnet) | ✅ | ✅ | | Boba | ✅ | ✅ | | 5irechain | ✅ | ✅ | | Metal L2 | ✅ | ✅ | | Lisk | ✅ | ✅ | # Automate DeFi Trades with Agentic Signers Source: https://docs.biconomy.io/faq/automate-defi-trades Learn how to automate DeFi trading with agentic signers. Build automated trading bots, DCA strategies, and AI-powered agents using session keys and smart accounts # Automate DeFi Trades with Agentic Signers An **agentic signer** is an autonomous program that holds a session key and can execute blockchain transactions on behalf of a user. Unlike traditional bots that require the user's private key, agentic signers operate with: * **Limited scope**: Can only perform specific, pre-approved actions * **Time bounds**: Authorization expires automatically * **Spending limits**: Cannot exceed defined value thresholds * **Revocability**: User can cancel at any time ``` Traditional Bot: Agentic Signer: Full private key access Limited session key Can do anything Scoped permissions only Trust the bot operator Trustless constraints Hard to revoke Instant revocation ``` | Strategy | Description | Example | | ------------------------------- | ------------------------------------- | --------------------------------------- | | **DCA (Dollar-Cost Averaging)** | Buy fixed amounts at intervals | Buy \$100 ETH every week | | **Stop-Loss** | Sell when price drops below threshold | Sell if ETH \< \$2000 | | **Take-Profit** | Sell when price exceeds target | Sell 50% if ETH > \$5000 | | **Rebalancing** | Maintain target portfolio ratios | Keep 60% ETH, 40% stables | | **Yield Harvesting** | Auto-compound farming rewards | Claim & restake daily | | **Arbitrage** | Exploit price differences | Cross-DEX arbitrage | | **Grid Trading** | Buy/sell at price intervals | Buy every $50 drop, sell every $50 rise | **Step 1: Create a smart account with MEE** ```typescript theme={null} import { createMeeClient, toMultichainNexusAccount } from "@biconomy/abstractjs"; const account = await toMultichainNexusAccount({ signer: ownerSigner, chains: [base, arbitrum] }); const meeClient = await createMeeClient({ account }); ``` Biconomy's MEE (Modular Execution Environment) provides all the functionality of traditional ERC-4337 bundlers and paymasters, with additional cross-chain orchestration capabilities. **Step 2: Define trading permissions** ```typescript theme={null} const tradingPolicy = { permissions: [ { type: "contract-call", target: UNISWAP_ROUTER, functions: ["exactInputSingle", "exactOutputSingle"], constraints: { // Max $1000 per trade maxValuePerTx: parseEther("0.5"), // Max $5000 per day dailyLimit: parseEther("2.5"), // Only approved token pairs allowedTokens: [WETH, USDC, WBTC] } } ], validUntil: Date.now() + 30 * 24 * 60 * 60 * 1000 // 30 days }; ``` **Step 3: Create session for the bot** ```typescript theme={null} const session = await smartAccount.createSession({ policy: tradingPolicy, sessionSigner: botSignerAddress }); console.log("Session ID:", session.id); // Share session with your trading bot ``` **Step 4: Run the trading bot** ```typescript theme={null} // In your bot service const bot = new TradingBot({ sessionKey: session.key, accountAddress: smartAccount.address, strategies: [ { type: "dca", token: "ETH", amount: 100, interval: "weekly" }, { type: "stop-loss", token: "ETH", trigger: 2000 } ] }); bot.start(); ``` ```typescript theme={null} import { createMeeClient, toMultichainNexusAccount, createSession } from "@biconomy/abstractjs"; import { encodeFunctionData, parseUnits } from "viem"; class DCABot { constructor(config) { this.sessionKey = config.sessionKey; this.account = config.account; this.token = config.token; this.amount = config.amount; // in USD this.interval = config.interval; // in ms } async executeBuy() { const swapData = encodeFunctionData({ abi: uniswapRouterAbi, functionName: "exactInputSingle", args: [{ tokenIn: USDC_ADDRESS, tokenOut: this.token, fee: 3000, recipient: this.account, amountIn: parseUnits(this.amount.toString(), 6), amountOutMinimum: 0n, sqrtPriceLimitX96: 0n }] }); const { hash } = await this.executeWithSession({ to: UNISWAP_ROUTER, data: swapData }); console.log(`DCA executed: ${hash}`); return hash; } start() { // Execute immediately, then on interval this.executeBuy(); setInterval(() => this.executeBuy(), this.interval); } async executeWithSession(tx) { // Use session key to sign and submit return await sendTransactionWithSession({ sessionKey: this.sessionKey, accountAddress: this.account, ...tx }); } } // Usage const dcaBot = new DCABot({ sessionKey: session.key, account: smartAccount.address, token: WETH_ADDRESS, amount: 100, // $100 USDC interval: 7 * 24 * 60 * 60 * 1000 // Weekly }); dcaBot.start(); ``` ```typescript theme={null} class PriceMonitorBot { constructor(config) { this.sessionKey = config.sessionKey; this.account = config.account; this.positions = config.positions; this.priceFeeds = config.priceFeeds; } async checkPrices() { for (const position of this.positions) { const currentPrice = await this.getPrice(position.token); // Check stop-loss if (position.stopLoss && currentPrice <= position.stopLoss) { console.log(`Stop-loss triggered for ${position.token}`); await this.executeSell(position, "stop-loss"); } // Check take-profit if (position.takeProfit && currentPrice >= position.takeProfit) { console.log(`Take-profit triggered for ${position.token}`); await this.executeSell(position, "take-profit"); } } } async executeSell(position, reason) { const balance = await this.getBalance(position.token); const sellAmount = position.sellPercentage ? (balance * BigInt(position.sellPercentage)) / 100n : balance; const swapData = encodeFunctionData({ abi: uniswapRouterAbi, functionName: "exactInputSingle", args: [{ tokenIn: position.token, tokenOut: USDC_ADDRESS, fee: 3000, recipient: this.account, amountIn: sellAmount, amountOutMinimum: 0n, sqrtPriceLimitX96: 0n }] }); const { hash } = await this.executeWithSession({ to: UNISWAP_ROUTER, data: swapData }); console.log(`${reason} executed: ${hash}`); // Remove or update position this.updatePosition(position, reason); } start() { // Check prices every minute setInterval(() => this.checkPrices(), 60 * 1000); } } // Usage const monitorBot = new PriceMonitorBot({ sessionKey: session.key, account: smartAccount.address, positions: [ { token: WETH_ADDRESS, stopLoss: 2000, // Sell if ETH drops to $2000 takeProfit: 5000, // Sell if ETH rises to $5000 sellPercentage: 50 // Sell 50% of position } ], priceFeeds: CHAINLINK_PRICE_FEEDS }); monitorBot.start(); ``` ```typescript theme={null} import { createSession } from "@biconomy/abstractjs"; import { OpenAI } from "openai"; class AITradingAgent { constructor(config) { this.sessionKey = config.sessionKey; this.account = config.account; this.ai = new OpenAI({ apiKey: config.openaiKey }); this.portfolio = config.portfolio; } async analyzeMarket() { // Gather market data const prices = await this.getPrices(this.portfolio.tokens); const trends = await this.getTechnicalIndicators(this.portfolio.tokens); const sentiment = await this.getSentiment(this.portfolio.tokens); // Ask AI for trading decision const response = await this.ai.chat.completions.create({ model: "gpt-4", messages: [ { role: "system", content: `You are a DeFi trading agent. Analyze market data and suggest trades. Portfolio: ${JSON.stringify(this.portfolio)} Risk tolerance: ${this.portfolio.riskLevel} Max trade size: $${this.portfolio.maxTradeSize}` }, { role: "user", content: `Current market data: Prices: ${JSON.stringify(prices)} Technical indicators: ${JSON.stringify(trends)} Sentiment: ${JSON.stringify(sentiment)} Should we make any trades? Respond with JSON: { "action": "buy"|"sell"|"hold", "token": "...", "amount": ..., "reason": "..." }` } ] }); return JSON.parse(response.choices[0].message.content); } async executeTrade(decision) { if (decision.action === "hold") { console.log("AI decision: Hold positions"); return; } const tradeData = this.buildTradeCalldata(decision); // Validate against session constraints if (!await this.validateTrade(decision)) { console.log("Trade exceeds session limits, skipping"); return; } const { hash } = await this.executeWithSession(tradeData); console.log(`AI trade executed: ${decision.action} ${decision.token}, tx: ${hash}`); // Log decision for review await this.logDecision(decision, hash); } async run() { const decision = await this.analyzeMarket(); await this.executeTrade(decision); } start(intervalMs = 60 * 60 * 1000) { // Hourly by default this.run(); // Run immediately setInterval(() => this.run(), intervalMs); } } // Usage const aiAgent = new AITradingAgent({ sessionKey: session.key, account: smartAccount.address, openaiKey: process.env.OPENAI_API_KEY, portfolio: { tokens: [WETH, WBTC, USDC], riskLevel: "moderate", maxTradeSize: 500 } }); aiAgent.start(); ``` ```typescript theme={null} class YieldHarvester { constructor(config) { this.sessionKey = config.sessionKey; this.account = config.account; this.farms = config.farms; } async harvestAndCompound() { for (const farm of this.farms) { try { // Check pending rewards const pending = await this.getPendingRewards(farm); if (pending < farm.minHarvestAmount) continue; // Batch: Claim → Swap → Redeposit const transactions = [ // 1. Claim rewards { to: farm.address, data: encodeFunctionData({ abi: farm.abi, functionName: "claim" }) }, // 2. Swap rewards to deposit token { to: UNISWAP_ROUTER, data: encodeSwap(farm.rewardToken, farm.depositToken, pending) }, // 3. Redeposit { to: farm.address, data: encodeFunctionData({ abi: farm.abi, functionName: "deposit", args: [runtime.outputOf(1)] // Use swap output }) } ]; const { hash } = await this.executeWithSession({ transactions }); console.log(`Harvested & compounded ${farm.name}: ${hash}`); } catch (error) { console.error(`Failed to harvest ${farm.name}:`, error); } } } start() { // Harvest every 12 hours setInterval(() => this.harvestAndCompound(), 12 * 60 * 60 * 1000); } } // Usage const harvester = new YieldHarvester({ sessionKey: session.key, account: smartAccount.address, farms: [ { name: "Aave USDC", address: AAVE_POOL, rewardToken: AAVE_TOKEN, depositToken: USDC, minHarvestAmount: parseUnits("10", 18) // 10 AAVE } ] }); harvester.start(); ``` **1. Use restrictive session policies:** ```typescript theme={null} const securePolicy = { permissions: [ { type: "contract-call", target: UNISWAP_ROUTER, // Only specific functions functions: ["exactInputSingle"], constraints: { // Strict value limits maxValuePerTx: parseEther("0.1"), dailyLimit: parseEther("1.0"), totalLimit: parseEther("10.0"), // Only approved tokens allowedTokens: [WETH, USDC], // Rate limiting minTimeBetweenTxs: 60 * 1000 // 1 minute } } ], // Short validity period validUntil: Date.now() + 7 * 24 * 60 * 60 * 1000 // 7 days }; ``` **2. Monitor bot activity:** ```typescript theme={null} // Set up alerts const monitor = new BotMonitor({ sessionId: session.id, alerts: [ { type: "daily-spend-exceeded", threshold: 0.5 }, // ETH { type: "unusual-activity", sensitivity: "high" }, { type: "consecutive-failures", count: 3 } ], notificationChannels: ["email", "telegram"] }); ``` **3. Implement kill switches:** ```typescript theme={null} // Automatic session revocation on anomaly if (await detectAnomalousActivity(botActivity)) { await smartAccount.revokeSession(session.id); await notifyOwner("Session revoked due to unusual activity"); } ``` **4. Use separate hot wallets:** ```typescript theme={null} // Only keep trading capital in smart account // Keep long-term holdings in separate cold storage const tradingAccount = await createSmartAccount({...}); await fundTradingAccount(tradingAccount, TRADING_CAPITAL); // Never give bot access to main holdings ``` | Practice | Description | | ------------------------ | ---------------------------------------- | | **Start small** | Test with minimal capital before scaling | | **Set strict limits** | Use daily/total spending caps | | **Monitor continuously** | Set up alerts and dashboards | | **Use testnets first** | Validate strategies on testnets | | **Log everything** | Keep detailed records for debugging | | **Have kill switches** | Ability to stop bot immediately | | **Diversify strategies** | Don't put all funds in one strategy | | **Regular reviews** | Check bot performance weekly | | **Update session keys** | Rotate session keys regularly | | **Audit your code** | Have trading logic reviewed | *** ## Get started with automation Learn automation fundamentals Configure security policies # Batched Transactions on EVM: Execute Multiple Operations Source: https://docs.biconomy.io/faq/batched-transactions-evm Learn how to batch multiple EVM transactions into one. Reduce gas costs, improve UX, and execute atomic multi-step operations with smart contract wallets # Batched Transactions on EVM: Execute Multiple Operations **Batched transactions** combine multiple blockchain operations into a single transaction. Instead of submitting separate transactions (each with its own gas cost and confirmation), you execute everything atomically in one go. ``` Without Batching: TX 1: Approve USDC → Wait → Confirm → Pay gas TX 2: Deposit to Aave → Wait → Confirm → Pay gas TX 3: Claim rewards → Wait → Confirm → Pay gas Total: 3 signatures, 3 gas payments, 3 confirmations With Batching: TX 1: [Approve + Deposit + Claim] → Single signature → Single gas payment Total: 1 signature, 1 gas payment, atomic execution ``` Batching is enabled by smart contract wallets (account abstraction). | Benefit | Description | | ------------------------ | -------------------------------------------- | | **Lower gas costs** | One base transaction fee instead of multiple | | **Better UX** | Users sign once instead of multiple times | | **Atomic execution** | All operations succeed or all fail together | | **Faster completion** | No waiting between steps | | **Reduced failure risk** | No partial state from incomplete sequences | **Gas savings example:** | Scenario | Without Batching | With Batching | Savings | | ------------------------- | ---------------- | ------------- | ------- | | Approve + Swap | \~140k gas | \~95k gas | 32% | | 3 token transfers | \~120k gas | \~75k gas | 37% | | Approve + Deposit + Claim | \~200k gas | \~130k gas | 35% | **Using AbstractJS SDK with MEE:** ```typescript theme={null} import { createMeeClient, toMultichainNexusAccount } from "@biconomy/abstractjs"; import { encodeFunctionData } from "viem"; const account = await toMultichainNexusAccount({ signer: userSigner, chains: [base] }); const meeClient = await createMeeClient({ account }); // Batch multiple operations with MEE const quote = await meeClient.getQuote({ instructions: [ { calls: [ { to: usdcAddress, data: encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [aavePool, amount] }) }, { to: aavePool, data: encodeFunctionData({ abi: aaveAbi, functionName: "deposit", args: [usdcAddress, amount, userAddress, 0] }) }, { to: rewardsController, data: encodeFunctionData({ abi: rewardsAbi, functionName: "claimAllRewards", args: [assets, userAddress] }) } ] } ] }); const { hash } = await meeClient.executeQuote({ quote }); console.log("All operations completed in one tx:", hash); ``` MEE (Modular Execution Environment) provides all the batching capabilities of ERC-4337 bundlers and paymasters, plus cross-chain orchestration. Use MEE instead of configuring bundlers and paymasters separately. You can batch any combination of EVM operations: **Common batching patterns:** | Pattern | Operations | | ------------------ | ------------------------------------- | | **Token + Action** | Approve → Swap/Deposit/Stake | | **Multi-send** | Multiple token transfers in one tx | | **DeFi combo** | Claim → Swap → Deposit | | **NFT batch** | Approve → List multiple NFTs | | **Gaming** | Equip + Upgrade + Consume items | | **DAO** | Delegate → Vote on multiple proposals | **Example: Multi-token transfer** ```typescript theme={null} const transfers = recipients.map(({ address, amount }) => ({ to: tokenAddress, data: encodeFunctionData({ abi: erc20Abi, functionName: "transfer", args: [address, amount] }) })); // Send to 100 recipients in ONE transaction await smartAccount.sendTransactions(transfers); ``` Atomic execution means **all operations succeed or all fail**. There's no partial state. **Why this matters:** ``` Scenario: Approve USDC → Swap USDC for ETH Non-atomic (separate txs): 1. Approve succeeds ✅ 2. Swap fails (price changed) ❌ Result: Approval is stuck, USDC still approved to router 😰 Atomic (batched): 1. [Approve + Swap] fails ❌ Result: Nothing happened, state unchanged 😌 ``` **Biconomy ensures atomicity:** ```typescript theme={null} try { const { hash } = await smartAccount.sendTransactions([ { /* operation 1 */ }, { /* operation 2 */ }, { /* operation 3 */ } ]); // All succeeded } catch (error) { // ALL operations reverted, no partial state console.log("Batch failed atomically"); } ``` Yes! Use runtime values to reference outputs from earlier operations: ```typescript theme={null} import { runtime } from "@biconomy/abstractjs"; const instructions = [ // Step 1: Swap ETH for USDC (output unknown until execution) { type: "intent", intent: "swap", params: { from: "ETH", to: "USDC", amount: "1.0" } }, // Step 2: Use EXACT output from step 1 { to: aavePool, data: encodeDeposit( usdcAddress, runtime.outputOf(0), // Dynamic: uses actual swap output userAddress ) } ]; ``` The `runtime.outputOf(0)` is resolved at execution time with the actual value from the first operation. Batch size is limited by: 1. **Block gas limit**: Total gas must fit in a block 2. **Bundler limits**: Typically 5-10M gas per UserOperation 3. **Practical limits**: More operations = higher failure risk **Guidelines:** | Chain | Recommended Max Operations | Max Gas | | -------- | -------------------------- | -------- | | Ethereum | 10-20 | 3-5M gas | | Arbitrum | 20-50 | 10M gas | | Base | 20-50 | 10M gas | | Polygon | 20-50 | 8M gas | **Best practices:** ```typescript theme={null} // Split large batches const BATCH_SIZE = 20; async function batchTransfer(transfers) { const results = []; for (let i = 0; i < transfers.length; i += BATCH_SIZE) { const batch = transfers.slice(i, i + BATCH_SIZE); const result = await smartAccount.sendTransactions(batch); results.push(result); } return results; } ``` ```typescript theme={null} // Estimate gas for batch const gasEstimate = await smartAccount.estimateGas({ transactions: [ { to: contract1, data: data1 }, { to: contract2, data: data2 }, { to: contract3, data: data3 } ] }); console.log("Total gas estimate:", gasEstimate); // Compare with individual estimates const individual1 = await publicClient.estimateGas({ to: contract1, data: data1 }); const individual2 = await publicClient.estimateGas({ to: contract2, data: data2 }); const individual3 = await publicClient.estimateGas({ to: contract3, data: data3 }); const totalIndividual = individual1 + individual2 + individual3; const savings = totalIndividual - gasEstimate; console.log(`Gas savings from batching: ${savings} (${(savings/totalIndividual*100).toFixed(1)}%)`); ``` **Default behavior: Atomic revert** ```typescript theme={null} try { await smartAccount.sendTransactions([op1, op2, op3]); } catch (error) { // Entire batch reverted // Identify which operation failed from error message console.log("Batch failed:", error.message); } ``` **For non-critical operations, use conditional execution:** ```typescript theme={null} const { conditions } = require("@biconomy/abstractjs"); const transactions = [ // Critical: Always execute { to: contract1, data: data1 }, // Optional: Only if balance exists { to: contract2, data: data2, condition: conditions.hasBalance(tokenAddress, minAmount) }, // Optional: Only if previous succeeded { to: contract3, data: data3, condition: conditions.previousSucceeded() } ]; ``` **Pre-validate operations:** ```typescript theme={null} async function validateBatch(transactions) { const simulations = await Promise.all( transactions.map(tx => publicClient.simulateContract({ address: tx.to, data: tx.data }).catch(e => ({ error: e })) ) ); const failures = simulations.filter(s => s.error); if (failures.length > 0) { console.log("Operations that would fail:", failures); return false; } return true; } ``` Yes! Cross-chain batching is possible with Biconomy's orchestration: ```typescript theme={null} const crossChainBatch = { instructions: [ // Chain 1: Ethereum { chainId: 1, to: usdcEthereum, data: encodeApprove(bridge, amount) }, // Bridge operation { type: "intent", intent: "bridge", params: { token: "USDC", from: 1, to: 8453, amount } }, // Chain 2: Base { chainId: 8453, to: aaveBase, data: encodeDeposit(usdcBase, runtime.bridgeOutput, user) } ] }; // One signature covers all chains const result = await meeClient.execute(crossChainBatch); ``` See [Multi-Chain Execution](/faq/multi-chain-execution) for details. **Example 1: DeFi Yield Optimization** ```typescript theme={null} // Claim rewards → Swap to stablecoin → Redeposit await smartAccount.sendTransactions([ // Claim pending rewards { to: farm, data: encodeClaim() }, // Approve swap { to: rewardToken, data: encodeApprove(router, maxUint256) }, // Swap rewards to USDC { to: router, data: encodeSwap(rewardToken, usdc, rewardAmount) }, // Deposit USDC back into farm { to: farm, data: encodeDeposit(usdc, usdcAmount) } ]); ``` **Example 2: NFT Marketplace Listing** ```typescript theme={null} // Approve collection → List multiple NFTs const listings = tokenIds.map(id => ({ to: marketplace, data: encodeList(collection, id, price) })); await smartAccount.sendTransactions([ { to: collection, data: encodeSetApprovalForAll(marketplace, true) }, ...listings ]); ``` **Example 3: Payroll Distribution** ```typescript theme={null} // Approve total → Transfer to all employees const totalAmount = employees.reduce((sum, e) => sum + e.amount, 0n); const transfers = employees.map(({ address, amount }) => ({ to: paymentToken, data: encodeTransfer(address, amount) })); await smartAccount.sendTransactions([ { to: paymentToken, data: encodeApprove(batchContract, totalAmount) }, ...transfers ]); ``` *** ## Learn more Batch across multiple chains Coordinate complex workflows # How to Build a Gasless Web3 App Source: https://docs.biconomy.io/faq/build-gasless-web3-app Step-by-step guide to building a gasless Web3 application. Learn how to implement gas sponsorship, paymasters, and frictionless onboarding for your dApp # How to Build a Gasless Web3 App Building a gasless Web3 app requires: | Component | Purpose | Biconomy Solution | | ------------------------- | ----------------------------------------------------------------------------- | ----------------------------------- | | **Smart Account** | Account abstraction enabled wallet | Nexus Smart Account | | **Execution Environment** | Handles transaction orchestration, gas sponsorship, and cross-chain execution | MEE (Modular Execution Environment) | | **SDK** | Developer integration | AbstractJS SDK | Biconomy's MEE provides all the functionality of traditional ERC-4337 bundlers and paymasters in a unified, easier-to-use package. Instead of configuring separate bundler and paymaster services, MEE handles everything automatically. You'll also need: * A Biconomy dashboard account for API keys * A sponsorship policy (who/what to sponsor) * Frontend integration (React, Vue, vanilla JS, etc.) **Step 1: Install dependencies** ```bash theme={null} npm install @biconomy/abstractjs viem ``` **Step 2: Get API keys from Biconomy Dashboard** 1. Go to [dashboard.biconomy.io](https://dashboard.biconomy.io) 2. Create a new project 3. Get your MEE API key 4. Configure sponsorship policies **Step 3: Initialize the SDK with MEE** ```typescript theme={null} import { createMeeClient, toMultichainNexusAccount } from "@biconomy/abstractjs"; import { privateKeyToAccount } from "viem/accounts"; import { base } from "viem/chains"; // Create multichain account const account = await toMultichainNexusAccount({ signer: privateKeyToAccount("0x..."), // User's signer chains: [base] }); // Create MEE client - handles all bundler and paymaster functionality const meeClient = await createMeeClient({ account }); console.log("Smart Account Address:", account.address); ``` MEE (Modular Execution Environment) replaces the need to configure separate bundlers and paymasters. It provides all ERC-4337 functionality plus cross-chain capabilities in a single, unified interface. Once configured, sending gasless transactions is simple with MEE: ```typescript theme={null} // The user pays NOTHING - gas is sponsored through MEE const quote = await meeClient.getQuote({ instructions: [ { calls: [{ to: "0xRecipientAddress", value: 0n, data: "0x" // Or encoded function call }] } ], feeToken: { address: "sponsored" } // Gas sponsored }); const { hash } = await meeClient.executeQuote({ quote }); console.log("Transaction hash:", hash); ``` **For contract interactions:** ```typescript theme={null} import { encodeFunctionData } from "viem"; // Encode your contract call const data = encodeFunctionData({ abi: yourContractABI, functionName: "mint", args: [userAddress, tokenId] }); // Send gasless transaction via MEE const quote = await meeClient.getQuote({ instructions: [ { calls: [{ to: contractAddress, value: 0n, data }] } ], feeToken: { address: "sponsored" } }); const { hash } = await meeClient.executeQuote({ quote }); ``` MEE automatically handles gas sponsorship based on your dashboard configuration. **With Privy (Recommended for embedded wallets):** ```typescript theme={null} import { usePrivy, useWallets } from "@privy-io/react-auth"; import { createMeeClient, toMultichainNexusAccount } from "@biconomy/abstractjs"; import { base } from "viem/chains"; function App() { const { authenticated } = usePrivy(); const { wallets } = useWallets(); async function initMeeClient() { const wallet = wallets[0]; const provider = await wallet.getEthereumProvider(); const account = await toMultichainNexusAccount({ signer: provider, chains: [base] }); const meeClient = await createMeeClient({ account }); return meeClient; } } ``` **With RainbowKit/wagmi:** ```typescript theme={null} import { useWalletClient } from "wagmi"; import { createMeeClient, toMultichainNexusAccount } from "@biconomy/abstractjs"; import { base } from "viem/chains"; function App() { const { data: walletClient } = useWalletClient(); async function initMeeClient() { if (!walletClient) return; const account = await toMultichainNexusAccount({ signer: walletClient, chains: [base] }); const meeClient = await createMeeClient({ account }); return meeClient; } } ``` Sponsorship policies control who and what gets sponsored. Configure in the Biconomy Dashboard: **Dashboard Configuration:** 1. Navigate to MEE sponsorship settings 2. Set spending limits (per transaction, total) MEE handles gas sponsorship (traditionally done by ERC-4337 paymasters) as part of its unified execution environment. You configure policies in the dashboard, and MEE applies them automatically. **Policy Examples:** *Sponsor all transactions (development):* ```json theme={null} { "mode": "SPONSORED", "policy": { "type": "open" } } ``` *Sponsor with spending limits:* ```json theme={null} { "mode": "SPONSORED", "policy": { "type": "limited", "maxGasPerTransaction": "0.001", "maxTotalSpend": "10.0" } } ``` For first-time users, the smart account needs to be deployed. MEE handles this automatically: ```typescript theme={null} // First transaction deploys the account + executes the action // All in one gasless transaction via MEE! const quote = await meeClient.getQuote({ instructions: [ { calls: [{ to: contractAddress, data: mintCalldata }] } ], feeToken: { address: "sponsored" } }); const { hash } = await meeClient.executeQuote({ quote }); // User experience: // 1. User clicks "Mint NFT" // 2. Signs one message (no gas needed) // 3. Account deployed + NFT minted // 4. Done! ✅ ``` **Best practices for onboarding:** ```typescript theme={null} async function onboardUser(email: string) { // 1. Create wallet with embedded solution (Privy, Dynamic, etc.) const wallet = await createEmbeddedWallet(email); // 2. Initialize MEE client const account = await toMultichainNexusAccount({ signer: wallet, chains: [base] }); const meeClient = await createMeeClient({ account }); // 3. Pre-compute address (no deployment yet) const address = account.address; // 4. First gasless transaction deploys account // Schedule this for user's first meaningful action return { wallet, meeClient, address }; } ``` Batch operations save gas and improve UX with MEE: ```typescript theme={null} // Execute multiple operations in ONE gasless transaction via MEE const quote = await meeClient.getQuote({ instructions: [ { calls: [ { to: tokenAddress, data: encodeApprove(spender, amount) }, { to: vaultAddress, data: encodeDeposit(amount) }, { to: rewardsAddress, data: encodeClaim() } ] } ], feeToken: { address: "sponsored" } }); const { hash } = await meeClient.executeQuote({ quote }); // User signs once, all three actions execute atomically ``` **Real-world example - NFT mint with approval:** ```typescript theme={null} const quote = await meeClient.getQuote({ instructions: [ { calls: [ // 1. Approve payment token { to: usdcAddress, data: encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [nftContract, mintPrice] }) }, // 2. Mint NFT { to: nftContract, data: encodeFunctionData({ abi: nftAbi, functionName: "mint", args: [quantity] }) } ] } ], feeToken: { address: "sponsored" } }); // One click, one signature, gasless! await meeClient.executeQuote({ quote }); ``` Even for gasless transactions, showing users what's happening builds trust: ```typescript theme={null} // Get quote with cost estimate via MEE const quote = await meeClient.getQuote({ instructions: [ { calls: [{ to: contractAddress, data: calldata }] } ], feeToken: { address: "sponsored" } }); // Quote includes cost breakdown console.log("Estimated cost:", quote.paymentInfo); // Display to user console.log(`Gas sponsored: $${quote.paymentInfo.usdCost.toFixed(2)}`); // "Gas sponsored: $0.45" ``` **UI Example:** ```tsx theme={null} function TransactionButton({ onSubmit }) { const [sponsoredAmount, setSponsoredAmount] = useState(null); return ( ); } ``` Common errors and how to handle them: ```typescript theme={null} try { const quote = await meeClient.getQuote({ instructions: [{ calls: [{ to: contractAddress, data: calldata }] }], feeToken: { address: "sponsored" } }); const { hash } = await meeClient.executeQuote({ quote }); } catch (error) { if (error.message.includes("sponsorship denied")) { // User exceeded sponsorship limits showMessage("Daily free transactions exceeded. Please try again tomorrow."); } else if (error.message.includes("insufficient balance")) { // Contract requires token balance showMessage("Insufficient token balance for this action."); } else if (error.message.includes("user rejected")) { // User cancelled signature showMessage("Transaction cancelled."); } else { // Generic error showMessage("Transaction failed. Please try again."); console.error(error); } } ``` **Implement retry logic:** ```typescript theme={null} async function sendWithRetry(instructions, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { const quote = await meeClient.getQuote({ instructions, feeToken: { address: "sponsored" } }); return await meeClient.executeQuote({ quote }); } catch (error) { if (i === maxRetries - 1) throw error; await new Promise(r => setTimeout(r, 1000 * (i + 1))); } } } ``` Here's a complete gasless NFT minting app using MEE: ```typescript theme={null} // lib/biconomy.ts import { createMeeClient, toMultichainNexusAccount } from "@biconomy/abstractjs"; import { base } from "viem/chains"; export async function createGaslessMeeClient(signer: any) { const account = await toMultichainNexusAccount({ signer, chains: [base] }); return createMeeClient({ account }); } ``` ```tsx theme={null} // components/MintButton.tsx import { useState } from "react"; import { createGaslessMeeClient } from "@/lib/biconomy"; import { encodeFunctionData } from "viem"; import { nftAbi, NFT_ADDRESS } from "@/lib/contracts"; export function MintButton({ signer }) { const [loading, setLoading] = useState(false); const [txHash, setTxHash] = useState(null); async function handleMint() { setLoading(true); try { // Initialize MEE client const meeClient = await createGaslessMeeClient(signer); // Encode mint function const data = encodeFunctionData({ abi: nftAbi, functionName: "mint", args: [1] // Mint 1 NFT }); // Get quote and execute gasless transaction via MEE const quote = await meeClient.getQuote({ instructions: [ { calls: [{ to: NFT_ADDRESS, data, value: 0n }] } ], feeToken: { address: "sponsored" } }); const { hash } = await meeClient.executeQuote({ quote }); setTxHash(hash); } catch (error) { console.error("Mint failed:", error); alert("Minting failed. Please try again."); } finally { setLoading(false); } } return (
{txHash && (

Success! View transaction

)}
); } ``` MEE provides all the functionality of traditional ERC-4337 bundlers and paymasters in a single, unified interface. No need to configure separate bundler URLs or paymaster URLs.
*** ## Continue building Deep dive into paymaster configuration Combine multiple operations efficiently # DeFi Intents: Intent-Based Execution Explained Source: https://docs.biconomy.io/faq/defi-intents Learn about DeFi intents: intent-based transaction execution, how solvers work, and why intents are revolutionizing decentralized finance user experience # DeFi Intents: Intent-Based Execution Explained **DeFi intents** are a new paradigm where users express *what* they want to achieve rather than *how* to achieve it. Instead of manually constructing transactions, users declare their desired outcome: | Traditional Approach | Intent-Based Approach | | ------------------------------------------------------------------------------------------- | ------------------------------------------ | | "Call Uniswap router at 0x... with exactInputSingle, set path to WETH→USDC, amount 1e18..." | "Swap 1 ETH for USDC with best price" | | User must know contracts, routes, parameters | User states goal, system handles execution | The intent infrastructure finds the optimal path, handles approvals, and executes the transaction—all from a simple declaration. Intent-based systems have several components: ``` ┌──────────────────────────────────────────────────────────┐ │ User Intent │ │ "Swap 1 ETH → USDC, best price" │ └──────────────────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────┐ │ Intent Solver │ │ • Queries multiple DEXs (Uniswap, Curve, 1inch...) │ │ • Finds optimal route and price │ │ • Handles token approvals │ │ • Manages slippage protection │ └──────────────────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────┐ │ Execution Layer │ │ • Bundles operations efficiently │ │ • Submits atomic transaction │ │ • Handles gas payment │ └──────────────────────────────────────────────────────────┘ ``` 1. **User submits intent**: Declares desired outcome 2. **Solver processes**: Finds optimal execution path 3. **Quote generated**: User sees exact outcome before signing 4. **Atomic execution**: All operations execute together Biconomy's intent system supports a wide range of DeFi operations: | Intent Type | Description | Example | | ------------ | ------------------------------ | ---------------------------------------- | | **Swap** | Exchange one token for another | "Swap 100 USDC for ETH" | | **Bridge** | Move assets across chains | "Bridge 1 ETH from Ethereum to Base" | | **Deposit** | Add liquidity or stake | "Deposit 1000 USDC into Aave" | | **Withdraw** | Remove liquidity or unstake | "Withdraw all from Compound" | | **Borrow** | Take a loan | "Borrow 500 USDC against ETH collateral" | | **Repay** | Pay back loans | "Repay 50% of my AAVE debt" | ```typescript theme={null} // Intent examples with Biconomy const swapIntent = { type: "intent", intent: "swap", params: { from: { token: "ETH", amount: "1.0" }, to: { token: "USDC" }, slippage: 0.5 // 0.5% max slippage } }; const bridgeIntent = { type: "intent", intent: "bridge", params: { token: "USDC", amount: "1000", fromChain: 1, toChain: 8453 } }; ``` **Solvers** are specialized services that fulfill user intents by finding optimal execution paths. They compete to provide: * **Best prices**: Aggregate across DEXs, find arbitrage opportunities * **Lowest gas**: Optimize calldata and execution path * **Fastest execution**: Route through liquid pools * **MEV protection**: Shield users from sandwich attacks ``` User Intent: "Swap 10 ETH → USDC" │ ┌───────────┼───────────┐ ▼ ▼ ▼ Solver A Solver B Solver C via Uniswap via Curve via 1inch $31,250 $31,300 $31,275 │ │ │ └───────────┼───────────┘ ▼ Best: Solver B User gets $31,300 ``` Biconomy integrates with multiple solver networks to ensure users always get competitive execution. | Aspect | Regular Transactions | Intent-Based | | -------------------- | -------------------------- | ---------------------------- | | **Specification** | Exact calldata required | Outcome-based declaration | | **Route finding** | User's responsibility | Solver handles | | **Optimization** | Manual | Automatic | | **Composability** | Complex to chain | Native chaining support | | **UX** | Technical knowledge needed | Simple, declarative | | **Failure handling** | User manages | System optimizes for success | **Example comparison:** Regular transaction: ```typescript theme={null} // User must know exact contract, function, parameters const tx = { to: "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", data: "0x5ae401dc...", // Encoded swap params value: parseEther("1.0") }; ``` Intent-based: ```typescript theme={null} // Just declare what you want const intent = { intent: "swap", from: "ETH", to: "USDC", amount: "1.0" }; ``` Yes! Cross-chain intents are a core capability: ```typescript theme={null} const crossChainIntent = { instructions: [ // Intent 1: Swap on source chain { chainId: 1, type: "intent", intent: "swap", params: { from: "ETH", to: "USDC", amount: "2.0" } }, // Intent 2: Bridge to destination { type: "intent", intent: "bridge", params: { token: "USDC", fromChain: 1, toChain: 8453 } }, // Intent 3: Deposit on destination { chainId: 8453, type: "intent", intent: "deposit", params: { protocol: "aave", token: "USDC" } } ] }; ``` The system: 1. Finds optimal swap route on Ethereum 2. Selects best bridge (cost vs. speed) 3. Deposits on Base 4. All with **one signature** When you submit an intent, you receive a **quote** before execution: ```typescript theme={null} const quote = await biconomy.getQuote({ intent: "swap", from: { token: "ETH", amount: "1.0" }, to: { token: "USDC" } }); console.log(quote); // { // inputAmount: "1.0 ETH", // outputAmount: "3125.50 USDC", // minOutputAmount: "3109.87 USDC", // With 0.5% slippage // route: "Uniswap V3 ETH→USDC", // gasCost: "$2.50", // priceImpact: "0.02%", // validUntil: 1699900000 // } ``` The quote is: * **Guaranteed**: Execute within validity period for quoted price * **Slippage protected**: Minimum output amount enforced * **Transparent**: See route, fees, and price impact Intents can be composed into complex workflows where each step depends on previous outputs: ```typescript theme={null} import { runtime } from "@biconomy/abstractjs"; const composedIntents = [ // Step 1: Swap ETH to USDC { type: "intent", intent: "swap", params: { from: "ETH", to: "USDC", amount: "1.0" } }, // Step 2: Use EXACT output from step 1 { type: "intent", intent: "deposit", params: { protocol: "aave", token: "USDC", amount: runtime.outputOf(0) // Dynamic reference } }, // Step 3: Borrow against the deposit { type: "intent", intent: "borrow", params: { protocol: "aave", token: "DAI", amount: runtime.multiply(runtime.outputOf(0), 0.5) // 50% of deposited } } ]; ``` No leftover tokens, no estimation errors—execution is perfectly composed. Yes, intent-based execution provides MEV protection through several mechanisms: | Protection | How It Works | | ---------------------- | ---------------------------------- | | **Private submission** | Intents don't enter public mempool | | **Solver competition** | Solvers compete to give best price | | **Slippage bounds** | Guaranteed minimum output | | **Atomic execution** | No intermediate states to exploit | | **Trusted execution** | Bundlers submit on user's behalf | Unlike regular DEX swaps that can be sandwiched, intent-based swaps are protected from front-running and MEV extraction. **Using AbstractJS SDK:** ```typescript theme={null} import { createMeeClient, buildIntent } from "@biconomy/abstractjs"; const client = await createMeeClient({ account: smartAccount }); // Build intent-based instructions const instructions = [ buildIntent({ type: "swap", from: { token: "ETH", chainId: 1, amount: "1.0" }, to: { token: "USDC" } }), buildIntent({ type: "bridge", token: "USDC", fromChain: 1, toChain: 8453 }) ]; // Get quote and execute const quote = await client.getQuote({ instructions }); const result = await client.executeQuote(quote); ``` **Using Supertransaction API:** ```bash theme={null} curl -X POST "https://api.biconomy.io/v1/intent" \ -H "x-api-key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "intent": "swap", "params": { "from": { "token": "ETH", "amount": "1.0" }, "to": { "token": "USDC" }, "slippage": 0.5 }, "sender": "0x..." }' ``` *** ## Explore intent-based execution Coordinate complex multi-step operations Execute across chains with one signature # ERC-4337 Paymaster: Gas Sponsorship Explained Source: https://docs.biconomy.io/faq/erc-4337-paymaster Learn how ERC-4337 paymasters work for gas sponsorship. Understand paymaster types, configuration, and how to implement gasless transactions in your dApp # ERC-4337 Paymaster: Gas Sponsorship Explained Biconomy provides all paymaster functionality through the **MEE (Modular Execution Environment)** stack. While this page explains how ERC-4337 paymasters work conceptually, you should use MEE to implement gas sponsorship in your applications—it provides paymaster capabilities plus cross-chain orchestration in a unified interface. A **Paymaster** is a smart contract in the ERC-4337 account abstraction standard that pays gas fees on behalf of users. Instead of users needing ETH for gas, the paymaster covers the cost. ``` Traditional Transaction: User → pays gas in ETH → Transaction Executed With Paymaster: User → signs UserOperation → Paymaster pays gas → Transaction Executed ``` Paymasters enable: * **Gasless transactions**: Users pay nothing * **Gas abstraction**: Users pay in ERC-20 tokens (USDC, DAI, etc.) * **Sponsored transactions**: Apps cover user gas costs The paymaster integrates with the ERC-4337 flow: ``` ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ User │───▶│ Bundler │───▶│ EntryPoint │───▶│ Paymaster │ │ (signs │ │ (submits │ │ (validates │ │ (pays gas, │ │ UserOp) │ │ UserOp) │ │ UserOp) │ │ verifies) │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ ▼ ┌─────────────┐ │ Account │ │ (executes │ │ action) │ └─────────────┘ ``` **Key steps:** 1. User creates and signs a UserOperation 2. UserOperation includes `paymasterAndData` field 3. Bundler submits to EntryPoint 4. EntryPoint calls `validatePaymasterUserOp` on Paymaster 5. Paymaster validates and agrees to pay 6. Transaction executes 7. Paymaster's deposit is debited for gas | Paymaster Type | How It Works | Use Case | | ------------------------ | ------------------------------------------ | ---------------------------------------- | | **Verifying Paymaster** | Off-chain signature authorizes sponsorship | App-controlled sponsorship with policies | | **Token Paymaster** | Accepts ERC-20 tokens as gas payment | Let users pay gas in stablecoins | | **Deposit Paymaster** | Users pre-deposit funds for gas | Enterprise/institutional use | | **Sponsoring Paymaster** | Unconditionally sponsors gas | Development, promotions | **Biconomy's MEE** provides all these paymaster capabilities: * Flexible sponsorship policies * Token payment options (pay gas in USDC, DAI, etc.) * Spending limits (per transaction, total) * Plus cross-chain gas abstraction (pay on any chain with tokens from any chain) Biconomy provides paymaster functionality through MEE (Modular Execution Environment): **Step 1: Get your MEE API key from Dashboard** **Step 2: Initialize MEE client** ```typescript theme={null} import { createMeeClient, toMultichainNexusAccount } from "@biconomy/abstractjs"; import { base } from "viem/chains"; const account = await toMultichainNexusAccount({ signer: userSigner, chains: [base] }); const meeClient = await createMeeClient({ account }); ``` **Step 3: Send sponsored transaction** ```typescript theme={null} // Get quote with sponsored gas const quote = await meeClient.getQuote({ instructions: [ { calls: [{ to: contractAddress, data: encodedCall }] } ], feeToken: { address: "sponsored" } // Gas sponsored via MEE }); // Execute gasless transaction const { hash } = await meeClient.executeQuote({ quote }); ``` MEE handles all gas sponsorship automatically based on your dashboard configuration. Configure policies in the Biconomy Dashboard or via API: **Per-Transaction Limit:** ```json theme={null} { "policy": { "transactionLimit": { "maxSpendUsd": 5 } } } ``` **Total Spending Limit:** ```json theme={null} { "policy": { "globalLimit": { "maxSpendUsd": 1000 } } } ``` Spending limits help you control costs and prevent abuse: **Per-Transaction Limits:** * Set maximum gas cost per individual transaction * Prevents unexpectedly expensive operations from draining your balance **Total Limits:** * Set an overall spending cap * MEE automatically stops sponsoring once the limit is reached Configure both limits in the Biconomy Dashboard for optimal cost control. MEE supports paying gas in ERC-20 tokens (gas abstraction): ```typescript theme={null} import { createMeeClient, toMultichainNexusAccount } from "@biconomy/abstractjs"; import { base } from "viem/chains"; const account = await toMultichainNexusAccount({ signer: userSigner, chains: [base] }); const meeClient = await createMeeClient({ account }); // Get quote with token payment const quote = await meeClient.getQuote({ instructions: [ { calls: [{ to: contractAddress, data: calldata }] } ], // User pays gas in USDC instead of ETH feeToken: { address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC chainId: base.id } }); const { hash } = await meeClient.executeQuote({ quote }); ``` **MEE supports cross-chain gas payment** - pay for gas on any chain using tokens from any other supported chain! **Supported gas payment tokens** vary by chain. Common options: * USDC * USDT * DAI * WETH Check [supported gas tokens](/new/getting-started/supported-gas-tokens) for the full list. Biconomy Dashboard provides: * Real-time gas spending * Contract interaction breakdown * Spending trends over time **API Access:** ```typescript theme={null} // Get usage statistics const stats = await fetch( "https://api.biconomy.io/v1/mee/stats", { headers: { "x-api-key": API_KEY } } ).then(r => r.json()); console.log(stats); // { // totalTransactions: 15420, // totalGasSponsored: "1.234", // ETH // uniqueUsers: 3250, // topContracts: [...], // dailyTrend: [...] // } ``` **Set up alerts:** ```typescript theme={null} // Configure spending alerts in dashboard const alertConfig = { dailySpendThreshold: "$50", unusualActivityThreshold: "200%", lowBalanceThreshold: "0.01 ETH", notificationChannels: ["email", "slack"] }; ``` **1. Set per-transaction limits:** ```json theme={null} { "policy": { "spendingCaps": { "perTransaction": "$5" } } } ``` **2. Set total spending caps:** ```json theme={null} { "policy": { "spendingCaps": { "total": "$1000" } } } ``` **3. Monitor for abuse:** * Watch for unusual patterns * Set up anomaly alerts * Review spending regularly in the dashboard Common failure scenarios and handling: | Scenario | Cause | Solution | | ---------------------------------- | ------------------------------ | --------------------------- | | "Sponsorship validation failed" | Policy rejected | Check spending limits | | "Insufficient sponsorship balance" | Low balance | Top up deposit in dashboard | | "Spending limit exceeded" | Transaction or total limit hit | Adjust limits in dashboard | | "Quote expired" | Quote too old | Get fresh quote | **Implement fallback logic:** ```typescript theme={null} async function sendWithFallback(instructions) { try { // Try sponsored first const quote = await meeClient.getQuote({ instructions, feeToken: { address: "sponsored" } }); return await meeClient.executeQuote({ quote }); } catch (error) { if (error.message.includes("sponsorship")) { // Fall back to user-paid gas (in tokens) console.log("Sponsorship unavailable, user pays gas in USDC"); const quote = await meeClient.getQuote({ instructions, feeToken: { address: USDC_ADDRESS, chainId: base.id } }); return await meeClient.executeQuote({ quote }); } throw error; } } ``` *** ## Related resources Complete integration guide How gasless transactions work # Gasless Transactions on EVM: How They Work Source: https://docs.biconomy.io/faq/gasless-transactions-evm Learn how gasless transactions work on EVM chains. Understand meta-transactions, paymasters, and gas sponsorship for building frictionless Web3 applications # Gasless Transactions on EVM: How They Work Biconomy provides gasless transaction capabilities through **MEE (Modular Execution Environment)**. MEE implements all the functionality of ERC-4337 bundlers and paymasters, plus cross-chain orchestration, in a unified developer experience. Gasless transactions allow users to interact with blockchain applications **without holding native tokens** (like ETH) to pay for gas fees. Instead, a third party—typically the application developer or a paymaster service—covers the gas costs. From the user's perspective, the transaction appears "free," dramatically improving onboarding and user experience. Gasless transactions work through a mechanism called **meta-transactions**: 1. **User signs a message** describing their intended action (not a transaction) 2. **Relayer receives** the signed message 3. **Relayer submits** an actual transaction to the blockchain, paying gas 4. **Smart contract verifies** the user's signature and executes the action 5. **Paymaster reimburses** the relayer (in ERC-4337 model) ``` User Intent → Signed Message → Relayer → Blockchain ↓ Paymaster pays gas ``` With ERC-4337, this flow is standardized through UserOperations and Paymasters. A **Paymaster** is a smart contract that sponsors gas fees for UserOperations. There are different types: | Paymaster Type | Description | Use Case | | ----------------------- | ---------------------------------------- | -------------------------------------- | | **Verifying Paymaster** | Sponsors based on off-chain verification | App-specific sponsorship with policies | | **Token Paymaster** | Accepts ERC-20 tokens for gas | Let users pay gas in USDC, DAI, etc. | | **Deposit Paymaster** | Uses pre-deposited funds | Enterprise gas sponsorship | Biconomy's **MEE** provides all paymaster functionality with additional capabilities like cross-chain gas abstraction—pay for gas on any chain using tokens from any other supported chain. **For Users:** * ✅ No need to buy/hold ETH before using an app * ✅ Familiar Web2-like experience * ✅ Lower barrier to entry for new users * ✅ Can pay gas in stablecoins if preferred **For Developers:** * ✅ 10x higher conversion rates on onboarding * ✅ Eliminate "buy ETH" support tickets * ✅ Control user acquisition costs (sponsor strategically) * ✅ Competitive advantage over apps requiring gas **For the Ecosystem:** * ✅ Mass adoption becomes feasible * ✅ Mobile-first experiences possible * ✅ Web3 accessible to non-crypto natives Biconomy provides gasless transactions through MEE (Modular Execution Environment): **Option 1: AbstractJS SDK (Recommended)** ```typescript theme={null} import { createMeeClient, toMultichainNexusAccount } from "@biconomy/abstractjs"; import { base } from "viem/chains"; const account = await toMultichainNexusAccount({ signer: userSigner, chains: [base] }); const meeClient = await createMeeClient({ account }); // Get quote with sponsored gas const quote = await meeClient.getQuote({ instructions: [ { calls: [{ to: contractAddress, data: encodedFunctionCall }] } ], feeToken: { address: "sponsored" } // Gasless! }); // Execute - user pays nothing const { hash } = await meeClient.executeQuote({ quote }); ``` **Option 2: Supertransaction API** ```bash theme={null} curl -X POST "https://api.biconomy.io/v1/quote" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "sender": "0x...", "instructions": [...], "feeToken": { "address": "sponsored", "chainId": 1 } }' ``` MEE handles all the complexity of ERC-4337 (bundlers, paymasters, UserOperations) plus cross-chain orchestration behind the scenes. Yes! MEE supports flexible sponsorship policies configured in the Biconomy Dashboard: * **Spending limits**: Max gas per transaction and total spending cap Configure these policies in your Biconomy Dashboard, and MEE enforces them automatically when processing transactions. | Term | Meaning | | ------------------- | ----------------------------------------------------------- | | **Gasless** | User pays nothing; sponsor covers 100% of gas | | **Gas Abstraction** | User pays, but in a token of their choice (USDC, DAI, etc.) | Both are enabled by the same infrastructure (paymasters), just with different configurations. Gas abstraction is useful when you want users to pay but remove the friction of needing the native token. ```typescript theme={null} // Gasless (sponsored) feeToken: { address: "sponsored" } // Gas abstraction (pay in USDC) feeToken: { address: "0xA0b86991c...", // USDC chainId: 1 } ``` Yes, gasless transactions maintain the same security guarantees as regular transactions: * **User signature required**: Transactions still need the user's cryptographic signature * **Non-custodial**: Sponsors never have access to user funds * **On-chain verification**: Smart contracts verify signatures before execution * **No replay attacks**: Nonces prevent transaction replay The only difference is *who pays* for gas, not *who controls* the transaction. Gasless transactions via ERC-4337 are supported on all major EVM chains: * Ethereum Mainnet * Arbitrum One & Nova * Optimism * Base * Polygon PoS & zkEVM * BNB Chain * Avalanche C-Chain * And 50+ more chains See our [supported chains](/contracts-and-audits/supported-chains) for the complete list. *** ## Start building gasless apps Step-by-step implementation guide Configure gas sponsorship policies # FAQ Source: https://docs.biconomy.io/faq/index Frequently asked questions about account abstraction, gasless transactions, onchain orchestration, and Web3 automation # Frequently Asked Questions Find answers to common questions about blockchain infrastructure, account abstraction, and building modern Web3 applications. ## Concepts Learn how account abstraction transforms the Web3 user experience Understand how gasless transactions work on EVM chains Discover how to coordinate complex multi-step blockchain operations Explore automated transaction execution and scheduling Learn about intent-based execution for DeFi operations ## How-To Guides Step-by-step guide to building gasless applications Understanding paymasters and gas sponsorship Execute multiple operations in a single transaction Execute transactions across multiple chains with one signature Build automated trading strategies with agentic signers # Execute Multi-Chain Transactions with One Signature Source: https://docs.biconomy.io/faq/multi-chain-execution Learn how to execute transactions across multiple blockchains with a single signature. Understand cross-chain orchestration, bridging, and unified multi-chain DeFi operations # Execute Multi-Chain Transactions with One Signature **Multi-chain execution** allows you to perform operations across multiple blockchains in a single, coordinated flow. Instead of manually switching networks, bridging assets, and executing separate transactions, everything happens with **one signature**. ``` Traditional Multi-Chain: 1. Sign tx on Ethereum → Wait for confirmation 2. Bridge to Arbitrum → Wait 10+ minutes 3. Switch to Arbitrum → Sign tx 4. Execute on Arbitrum → Wait for confirmation Total: 4 signatures, multiple waits, manual coordination With Biconomy Multi-Chain: 1. Sign once → Automatic execution across all chains Total: 1 signature, automated coordination ``` Biconomy's Multi-chain Execution Environment (MEE) orchestrates cross-chain operations: ``` ┌──────────────────────────────────────────────────────────────┐ │ Supertransaction │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ Instruction │ │ Instruction │ │ Instruction │ │ │ │ Chain A │──▶│ Bridge │──▶│ Chain B │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ └──────────────────────────────────────────────────────────────┘ │ Single Signature │ ┌───────────────┼───────────────┐ ▼ ▼ ▼ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Execute │ │ Monitor │ │ Execute │ │ Chain A │ │ Bridge │ │ Chain B │ └─────────────┘ └─────────────┘ └─────────────┘ ``` **The process:** 1. **Build**: Define operations across chains 2. **Quote**: Get execution cost and timing estimates 3. **Sign**: User signs once for entire flow 4. **Execute**: Biconomy handles coordination 5. **Track**: Monitor progress across all chains Biconomy supports 70+ EVM chains including: | Category | Chains | | ---------------------- | ---------------------------------------- | | **L1s** | Ethereum, BNB Chain, Avalanche, Polygon | | **Optimistic Rollups** | Arbitrum, Optimism, Base, Mantle | | **ZK Rollups** | zkSync Era, Polygon zkEVM, Scroll, Linea | | **Alt L1s** | Fantom, Gnosis, Celo | See [supported chains](/contracts-and-audits/supported-chains) for the complete list. **Cross-chain routes** are supported between any combination of these chains via integrated bridge providers. **Using AbstractJS SDK:** ```typescript theme={null} import { createMeeClient, runtime } from "@biconomy/abstractjs"; const client = await createMeeClient({ account: smartAccount }); // Define multi-chain operation const quote = await client.getQuote({ instructions: [ // Step 1: Swap on Ethereum { chainId: 1, type: "intent", intent: "swap", params: { from: { token: "ETH", amount: "1.0" }, to: { token: "USDC" } } }, // Step 2: Bridge to Base { type: "intent", intent: "bridge", params: { token: "USDC", amount: runtime.outputOf(0), // Use swap output fromChain: 1, toChain: 8453 } }, // Step 3: Deposit on Base { chainId: 8453, to: aavePoolBase, data: encodeDeposit(runtime.outputOf(1)) // Use bridge output } ], feeToken: { address: "USDC", chainId: 1 } }); // Execute with single signature const result = await client.executeQuote(quote); console.log("Multi-chain tx:", result.hash); ``` **Using Supertransaction API:** ```bash theme={null} curl -X POST "https://api.biconomy.io/v1/quote" \ -H "x-api-key: $API_KEY" \ -d '{ "sender": "0x...", "instructions": [ {"chainId": 1, "type": "intent", "intent": "swap", ...}, {"type": "intent", "intent": "bridge", ...}, {"chainId": 8453, "to": "0x...", "data": "0x..."} ] }' ``` Biconomy aggregates multiple bridge providers to find optimal routes: | Provider | Strengths | | ------------ | -------------------------------- | | **Across** | Fast finality, competitive rates | | **Stargate** | Deep liquidity, many chains | | **Connext** | Trustless, EVM-focused | | **Hop** | L2-optimized, fast | | **Synapse** | Wide chain support | **Route selection criteria:** * **Speed**: How quickly funds arrive * **Cost**: Bridge fees + gas on destination * **Reliability**: Historical success rate * **Liquidity**: Available for transaction size ```typescript theme={null} // Get quote with route details const quote = await client.getQuote({ instructions: [ { type: "intent", intent: "bridge", params: {...} } ] }); console.log(quote.bridgeRoute); // { // provider: "Across", // estimatedTime: "2-5 minutes", // fee: "0.05%", // outputAmount: "999.50 USDC" // } ``` Multi-chain execution requires gas on each destination chain. Biconomy handles this automatically: **Option 1: Pay from source chain (Recommended)** ```typescript theme={null} const quote = await client.getQuote({ instructions: [...], // Pay all gas from USDC on Ethereum feeToken: { address: USDC_ADDRESS, chainId: 1 // Ethereum } }); // Biconomy converts and allocates gas for all chains ``` **Option 2: Sponsored gas** ```typescript theme={null} const quote = await client.getQuote({ instructions: [...], feeToken: { address: "sponsored" } }); // Your Paymaster covers gas on all chains ``` **Option 3: Pre-funded on each chain** ```typescript theme={null} // Ensure smart account has gas tokens on each chain // Biconomy uses local balance for each chain's gas ``` Multi-chain transactions have multiple stages. Track progress with: ```typescript theme={null} const result = await client.executeQuote(quote); // Poll for status const status = await client.getExecutionStatus(result.hash); console.log(status); // { // overall: "in_progress", // steps: [ // { chain: 1, action: "swap", status: "completed", txHash: "0x..." }, // { chain: "bridge", action: "bridge", status: "in_progress", // estimatedCompletion: "2024-01-15T10:30:00Z" }, // { chain: 8453, action: "deposit", status: "pending" } // ] // } // Wait for completion const finalResult = await client.waitForExecution(result.hash); console.log("All chains completed:", finalResult); ``` **Webhook notifications:** ```typescript theme={null} // Configure webhook for status updates await client.setWebhook({ url: "https://your-app.com/api/tx-status", events: ["step_completed", "execution_completed", "execution_failed"] }); ``` Biconomy implements safeguards for cross-chain failures: **Before bridging:** * Transaction reverts atomically * No funds leave source chain * User can retry immediately **After bridging (during destination execution):** * Funds arrive on destination chain safely * Destination execution is retried automatically * If retries fail, funds remain in user's destination account ```typescript theme={null} const result = await client.executeQuote(quote); const status = await client.getExecutionStatus(result.hash); if (status.overall === "partial_failure") { console.log("Failed step:", status.failedStep); console.log("Funds location:", status.fundsLocation); // Option 1: Retry failed step await client.retryStep(result.hash, status.failedStep.index); // Option 2: Manual recovery // Funds are in your account on destination chain } ``` Execution time depends on the chains and bridge used: | Route | Typical Time | | --------------------------------------- | ---------------------------------------- | | **Same L2 (e.g., Base → Base)** | 2-5 seconds | | **L2 → L2 (e.g., Arbitrum → Base)** | 1-5 minutes | | **L1 → L2 (e.g., Ethereum → Base)** | 2-10 minutes | | **L2 → L1 (e.g., Arbitrum → Ethereum)** | 7 days (native) / 5-30 min (fast bridge) | **Speed vs. cost tradeoffs:** ```typescript theme={null} // Prioritize speed const fastQuote = await client.getQuote({ instructions: [...], preferences: { priority: "speed" } }); // Prioritize cost const cheapQuote = await client.getQuote({ instructions: [...], preferences: { priority: "cost" } }); ``` **Example 1: Cross-Chain Yield Farming** ```typescript theme={null} // Move funds from Ethereum to higher-yield farm on Base const instructions = [ // Withdraw from Aave on Ethereum { chainId: 1, to: aaveEth, data: encodeWithdraw(usdc, maxAmount) }, // Bridge to Base { type: "intent", intent: "bridge", params: { token: "USDC", fromChain: 1, toChain: 8453 } }, // Deposit to higher-yield protocol on Base { chainId: 8453, to: yieldProtocol, data: encodeDeposit(runtime.outputOf(1)) } ]; ``` **Example 2: Cross-Chain NFT Purchase** ```typescript theme={null} // Use ETH on Arbitrum to buy NFT listed on Base const instructions = [ // Swap ETH to USDC on Arbitrum { chainId: 42161, type: "intent", intent: "swap", params: { from: "ETH", to: "USDC", amount: "0.5" } }, // Bridge USDC to Base { type: "intent", intent: "bridge", params: { token: "USDC", fromChain: 42161, toChain: 8453 } }, // Buy NFT on Base { chainId: 8453, to: nftMarketplace, data: encodeBuy(nftAddress, tokenId), value: runtime.outputOf(1) } ]; ``` **Example 3: Multi-Chain Portfolio Rebalancing** ```typescript theme={null} // Consolidate assets from multiple chains const instructions = [ // Withdraw from Polygon { chainId: 137, to: protocolA, data: encodeWithdraw() }, { type: "intent", intent: "bridge", params: { fromChain: 137, toChain: 1 } }, // Withdraw from Arbitrum { chainId: 42161, to: protocolB, data: encodeWithdraw() }, { type: "intent", intent: "bridge", params: { fromChain: 42161, toChain: 1 } }, // Consolidated deposit on Ethereum { chainId: 1, to: mainVault, data: encodeDeposit(totalAmount) } ]; ``` *** ## Continue learning Deep dive into orchestration Combine operations efficiently # Onchain Orchestration Explained Source: https://docs.biconomy.io/faq/onchain-orchestration Learn about onchain orchestration: coordinate complex multi-step, multi-chain blockchain operations with atomic execution and composable transaction batching # Onchain Orchestration Explained **Onchain orchestration** is the coordination of multiple blockchain operations across one or more chains into a unified, atomic execution flow. Instead of manually executing a series of transactions, orchestration handles: * **Sequencing**: Execute operations in the correct order * **Dependencies**: Ensure step B uses the output of step A * **Atomicity**: All-or-nothing execution (no partial failures) * **Cross-chain coordination**: Bridge assets and execute on destination chains Think of it as a conductor coordinating an orchestra—each instrument (transaction) plays its part at the right time to create a cohesive result. Modern DeFi operations often require multiple steps: **Example: Deposit USDC into a yield vault on another chain** 1. Approve USDC spending 2. Swap USDC → bridgeable asset 3. Bridge to destination chain 4. Swap back to destination USDC 5. Approve vault contract 6. Deposit into vault Without orchestration, users must: * ❌ Execute 6+ separate transactions * ❌ Wait for each to confirm * ❌ Handle failures manually * ❌ Pay gas 6 times * ❌ Risk price slippage between steps With orchestration: * ✅ One signature, one transaction * ✅ Atomic execution * ✅ Automatic dependency handling * ✅ Optimal gas efficiency Biconomy's **Supertransaction** system orchestrates complex workflows: ``` ┌─────────────────────────────────────────────────────────┐ │ Supertransaction │ ├─────────────────────────────────────────────────────────┤ │ Instruction 1: Swap ETH → USDC (Chain A) │ │ ↓ │ │ Instruction 2: Bridge USDC (Chain A → Chain B) │ │ ↓ │ │ Instruction 3: Deposit USDC to Vault (Chain B) │ └─────────────────────────────────────────────────────────┘ ↓ Single Signature ↓ Atomic Execution ``` The orchestration layer: 1. **Builds** optimal execution paths 2. **Quotes** total costs across all operations 3. **Executes** with guaranteed ordering 4. **Tracks** progress across chains A **Supertransaction** is Biconomy's abstraction for orchestrated blockchain operations. It combines: * **Instructions**: Individual operations (swaps, bridges, contract calls) * **Runtime values**: Dynamic values computed during execution * **Conditions**: Logic to control execution flow * **Fee configuration**: How gas is paid (sponsored, token, etc.) ```typescript theme={null} const supertransaction = { instructions: [ { type: "intent", intent: "swap", params: { from: "ETH", to: "USDC", amount: "1.0" } }, { type: "intent", intent: "bridge", params: { token: "USDC", fromChain: 1, toChain: 8453, amount: runtime.outputOf(0) // Uses output from first instruction } } ], feeToken: { address: "sponsored" } }; ``` Biconomy supports orchestrating any EVM operation: | Category | Operations | | ---------- | ------------------------------------------------------------ | | **DeFi** | Swaps, bridges, lending, borrowing, staking, LP provisioning | | **NFTs** | Minting, transfers, marketplace interactions | | **Gaming** | In-game purchases, asset transfers, crafting | | **DAOs** | Voting, delegation, treasury management | | **Custom** | Any smart contract interaction | Operations can be: * **Intent-based**: "Swap 1 ETH to USDC" (solver finds best route) * **Explicit**: Specific contract call with exact parameters * **Conditional**: Execute only if certain conditions are met Cross-chain orchestration coordinates operations across multiple blockchains: 1. **Source chain execution**: Execute initial operations 2. **Asset bridging**: Move tokens to destination chain 3. **Destination execution**: Complete operations on target chain Biconomy integrates with multiple bridge providers to find optimal routes: ```typescript theme={null} const crossChainFlow = { instructions: [ // Step 1: Swap on Ethereum { chainId: 1, type: "intent", intent: "swap", params: { from: "ETH", to: "USDC", amount: "1.0" } }, // Step 2: Bridge to Base { type: "intent", intent: "bridge", params: { fromChain: 1, toChain: 8453, token: "USDC" } }, // Step 3: Deposit on Base { chainId: 8453, type: "call", to: VAULT_ADDRESS, data: depositCalldata } ] }; ``` The orchestrator handles: * Bridge selection and routing * Waiting for bridge confirmation * Gas on destination chain * Failure recovery **Runtime values** allow later instructions to reference outputs from earlier ones. This enables truly composable workflows: ```typescript theme={null} import { runtime } from "@biconomy/abstractjs"; const instructions = [ // Instruction 0: Swap - outputs USDC amount { type: "intent", intent: "swap", params: {...} }, // Instruction 1: Use the EXACT output from swap { type: "call", to: vaultContract, value: 0n, data: encodeDeposit(runtime.outputOf(0)) // Dynamic! } ]; ``` Without runtime values, you'd have to estimate outputs, leading to failed transactions or leftover tokens. Orchestration provides different failure modes: | Mode | Behavior | Use Case | | --------------- | ---------------------------------------- | ------------------------------ | | **Atomic** | All operations succeed or all revert | Critical multi-step operations | | **Partial** | Continue after failures, collect results | Non-critical batch operations | | **Conditional** | Skip steps based on conditions | Complex branching logic | ```typescript theme={null} // Atomic execution (default) const result = await execute(supertransaction); // With condition - only execute if balance > 0 { type: "call", condition: { type: "balance", token: USDC_ADDRESS, operator: "gt", value: 0n }, ... } ``` | Aspect | Aggregation | Orchestration | | --------------- | ------------------------------ | ----------------------------------- | | **Scope** | Finding best single route | Coordinating multiple operations | | **Example** | DEX aggregator finds best swap | Bridge + swap + deposit in one flow | | **Complexity** | Single operation optimization | Multi-step workflow management | | **Cross-chain** | Usually single chain | Native multi-chain support | Orchestration *uses* aggregation for individual steps but adds coordination, dependencies, and cross-chain capabilities. **Using AbstractJS SDK:** ```typescript theme={null} import { createMeeClient, runtime } from "@biconomy/abstractjs"; const client = await createMeeClient({ account: smartAccount }); // Build orchestrated transaction const quote = await client.getQuote({ instructions: [ mcUSDC.on(base.id).read.balanceOf(account.address), // More instructions... ], feeToken: mcUSDC.on(optimism.id) }); // Execute with single signature const result = await client.executeQuote(quote); ``` **Using Supertransaction API:** ```bash theme={null} # 1. Get quote for orchestrated operations curl -X POST "https://api.biconomy.io/v1/quote" \ -H "x-api-key: $API_KEY" \ -d '{"instructions": [...], "feeToken": {...}}' # 2. Execute with signature curl -X POST "https://api.biconomy.io/v1/execute" \ -H "x-api-key: $API_KEY" \ -d '{"quote": {...}, "signature": "0x..."}' ``` *** ## Learn more about orchestration Execute across chains with one signature Combine multiple operations efficiently # Web3 Automation: Automated Blockchain Transactions Source: https://docs.biconomy.io/faq/web3-automation Learn about Web3 automation: automated blockchain transactions, scheduled execution, trigger-based operations, and building autonomous onchain agents # Web3 Automation: Automated Blockchain Transactions **Web3 automation** enables blockchain transactions to execute automatically without manual user intervention. This includes: * **Scheduled transactions**: Execute at specific times or intervals * **Trigger-based execution**: React to onchain or offchain events * **Conditional operations**: Execute when certain conditions are met * **Autonomous agents**: AI or rule-based systems managing onchain activities Automation transforms blockchain from a manual, click-to-execute system into a programmable, always-on infrastructure. | Category | Use Cases | | ------------------ | ---------------------------------------------------------------------------------------------- | | **DeFi** | Auto-compounding yields, rebalancing portfolios, stop-loss orders, recurring investments (DCA) | | **Trading** | Limit orders, arbitrage bots, MEV strategies, market making | | **Treasury** | Scheduled payments, payroll, vesting distributions | | **Gaming** | Auto-claiming rewards, resource harvesting, scheduled actions | | **Infrastructure** | Oracle updates, keeper operations, protocol maintenance | | **Personal** | Subscription payments, recurring transfers, automated savings | Automated transactions require three components: 1. **Authorization**: User grants permission for specific actions 2. **Trigger**: Condition or schedule that initiates execution 3. **Executor**: Service that submits the transaction when triggered ``` ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ User │────▶│ Session │────▶│ Executor │ │ (one-time │ │ Key │ │ (always │ │ setup) │ │ (limited │ │ online) │ └──────────────┘ │ scope) │ └──────────────┘ └──────────────┘ │ ▼ ┌──────────────┐ │ Blockchain │ └──────────────┘ ``` With session keys, users can authorize specific automated actions without exposing their main private key. **Session keys** are temporary, scoped signing keys that enable secure automation: * **Time-limited**: Valid only for a specific duration * **Action-scoped**: Can only perform specific operations * **Revocable**: User can cancel authorization anytime * **Non-custodial**: User retains full control of main wallet ```typescript theme={null} import { createSessionKey } from "@biconomy/abstractjs"; // Create a session key for automated swaps const session = await createSessionKey({ account: smartAccount, permissions: [ { type: "contract-call", target: UNISWAP_ROUTER, functions: ["exactInputSingle"], constraints: { maxValue: parseEther("1.0"), // Max 1 ETH per transaction dailyLimit: parseEther("10.0") // Max 10 ETH per day } } ], expiry: Date.now() + 7 * 24 * 60 * 60 * 1000 // 7 days }); ``` The executor uses this session key to submit transactions within the defined scope. **Agentic signers** are autonomous programs that hold session keys and execute transactions based on predefined logic or AI decisions. Types include: | Type | Description | Example | | ------------------ | ---------------------------- | ------------------------------------------------------ | | **Rule-based** | Executes on fixed conditions | Stop-loss bot: sell if price drops 10% | | **Schedule-based** | Executes at intervals | DCA bot: buy \$100 ETH every week | | **AI-powered** | Uses ML for decisions | Trading agent: analyze market and optimize entries | | **Event-driven** | Reacts to onchain events | Arbitrage bot: execute when price discrepancy detected | ```typescript theme={null} // Example: Agentic signer for DCA strategy const dcaAgent = new AgentSigner({ sessionKey: session, strategy: { type: "schedule", interval: "weekly", action: { type: "swap", from: "USDC", to: "ETH", amount: "100" } } }); dcaAgent.start(); // Runs autonomously ``` Setting up automated trading with Biconomy involves: **Step 1: Create a smart account with MEE** ```typescript theme={null} import { createMeeClient, toMultichainNexusAccount } from "@biconomy/abstractjs"; const account = await toMultichainNexusAccount({ signer: ownerSigner, chains: [base, arbitrum] }); const meeClient = await createMeeClient({ account }); ``` MEE (Modular Execution Environment) provides all ERC-4337 bundler and paymaster functionality needed for automation, plus cross-chain capabilities. **Step 2: Define automation policy** ```typescript theme={null} const policy = { permissions: [{ type: "defi", protocols: ["uniswap-v3", "aave"], actions: ["swap", "deposit", "withdraw"], constraints: { maxSlippage: 0.5, // 0.5% dailyVolumeLimit: parseEther("100") } }], validUntil: Date.now() + 30 * 24 * 60 * 60 * 1000 // 30 days }; ``` **Step 3: Grant session to executor** ```typescript theme={null} const session = await account.createSession({ policy, executor: AUTOMATION_SERVICE_ADDRESS }); ``` **Step 4: Configure automation logic** ```typescript theme={null} await automationService.configure({ sessionId: session.id, triggers: [ { type: "price", token: "ETH", condition: "lt", value: 2000 }, { type: "schedule", cron: "0 0 * * 1" } // Every Monday ], actions: [...] }); ``` Biconomy's automation infrastructure includes multiple security layers: | Protection | Description | | ---------------------- | -------------------------------------------------------- | | **Scoped permissions** | Session keys can only perform explicitly allowed actions | | **Spending limits** | Per-transaction, daily, and total limits on value | | **Time bounds** | Sessions automatically expire after set duration | | **Revocation** | Users can cancel sessions instantly | | **Allowlists** | Restrict to specific contracts and functions | | **Rate limits** | Prevent excessive transaction frequency | ```typescript theme={null} // Example: Highly restricted session const restrictedSession = { permissions: [{ target: SPECIFIC_CONTRACT, functions: ["deposit"], constraints: { maxValuePerTx: parseEther("0.1"), maxTxPerDay: 5, maxTotalValue: parseEther("1.0") } }], validUntil: Date.now() + 24 * 60 * 60 * 1000, // 24 hours only requireConfirmationAbove: parseEther("0.5") // Require user signature above 0.5 ETH }; ``` Yes! Biconomy's automation supports cross-chain workflows: ```typescript theme={null} const crossChainAutomation = { trigger: { type: "price", chain: 1, // Monitor on Ethereum token: "ETH", condition: "gt", value: 4000 }, action: { type: "orchestrated", instructions: [ { chain: 1, action: "swap", from: "ETH", to: "USDC" }, { action: "bridge", from: 1, to: 8453 }, // Bridge to Base { chain: 8453, action: "deposit", protocol: "aave" } ] } }; ``` The automation service handles: * Cross-chain message passing * Bridge monitoring * Gas on all chains (can be sponsored) * Failure recovery | Aspect | Traditional Keepers | Smart Account Automation | | ----------------- | ------------------------------ | ------------------------------ | | **Control** | Protocol-controlled | User-controlled | | **Scope** | Protocol-specific tasks | Any user-defined action | | **Authorization** | Relies on protocol permissions | Session keys with user consent | | **Flexibility** | Fixed functionality | Fully programmable | | **Cost** | Usually subsidized by protocol | User or sponsor pays | Biconomy's automation lets *users* set up their own "keepers" for personal workflows. Building an AI agent with Biconomy MEE: ```typescript theme={null} import { createMeeClient, toMultichainNexusAccount, createSession } from "@biconomy/abstractjs"; import { TradingAI } from "./your-ai-model"; // 1. Setup smart account with MEE const account = await toMultichainNexusAccount({ signer: ownerSigner, chains: [base] }); const meeClient = await createMeeClient({ account }); // 2. Create scoped session for AI const session = await createSession({ account, permissions: [{ type: "trading", maxPositionSize: parseEther("10"), allowedPairs: ["ETH/USDC", "BTC/USDC"], maxSlippage: 1 // 1% }] }); // 3. Initialize AI with session const agent = new TradingAI({ sessionKey: session.key, meeClient, async onDecision(action) { // AI decides to trade - execute via MEE if (action.type === "buy") { const quote = await meeClient.getQuote({ instructions: [buildSwapInstruction(action)], feeToken: { address: "sponsored" } }); return meeClient.executeQuote({ quote, sessionKey: session.key }); } } }); // 4. Run agent agent.start({ dataFeeds: ["price", "volume", "sentiment"], decisionInterval: 60 * 1000 // Every minute }); ``` *** ## Start automating Build automated trading strategies Configure automation permissions # What is Account Abstraction? Source: https://docs.biconomy.io/faq/what-is-account-abstraction Account abstraction explained: Learn how ERC-4337 and smart contract wallets transform Web3 user experience with gasless transactions, social recovery, and programmable accounts # What is Account Abstraction? Account abstraction is a blockchain upgrade that transforms how users interact with Web3. Instead of using basic externally owned accounts (EOAs) controlled only by private keys, users can have **smart contract wallets** with programmable logic, enabling features like gasless transactions, social recovery, and multi-signature security. Think of it as upgrading from a basic key-and-lock to a smart home security system—same purpose, infinitely more capabilities. | Feature | Traditional EOA | Account Abstraction | | ------------------------ | ------------------------------ | ------------------------------------------ | | **Gas Payment** | Must pay in native token (ETH) | Can pay in any token or have gas sponsored | | **Transaction Batching** | One transaction at a time | Multiple operations in single transaction | | **Key Recovery** | Lost key = lost funds | Social recovery, multi-sig options | | **Signing** | Single signature required | Flexible validation logic | | **Automation** | Not possible | Session keys enable automated transactions | With account abstraction, your wallet becomes a programmable smart contract that can implement custom security policies, spending limits, and automation rules. **ERC-4337** is the Ethereum standard that enables account abstraction without requiring changes to the core protocol. It introduces: * **UserOperations**: A new transaction type that encapsulates user intent * **Bundlers**: Services that bundle multiple UserOperations into single transactions * **Paymasters**: Contracts that can sponsor gas fees on behalf of users * **EntryPoint**: A singleton contract that validates and executes UserOperations This architecture allows any Ethereum-compatible chain to support account abstraction immediately, without hard forks. Biconomy's **MEE (Modular Execution Environment)** provides all ERC-4337 functionality (bundlers, paymasters, UserOperations) in a unified interface, plus advanced features like cross-chain orchestration. You don't need to configure separate bundlers and paymasters—MEE handles everything. **For End Users:** * **No gas tokens needed**: Use apps without holding ETH for gas * **Better security**: Multi-factor authentication, spending limits, social recovery * **Simpler onboarding**: Create wallets with email/social login * **Batch transactions**: Approve and swap in one click **For Developers:** * **Higher conversion**: Remove the "buy ETH for gas" friction * **Better UX**: Abstract away blockchain complexity * **Flexible authentication**: Support passkeys, social logins, hardware keys * **Sponsored transactions**: Cover gas costs for your users A smart contract wallet is a wallet where the account itself is a smart contract, not just an externally owned account (EOA). This enables: * **Custom validation logic**: Define who can sign transactions and under what conditions * **Modular functionality**: Add or remove features like session keys, spending limits * **Programmable security**: Implement time-locks, multi-sig requirements, recovery mechanisms * **Upgradability**: Update wallet logic without changing your address Biconomy's Nexus smart account is a modular smart contract wallet that supports ERC-4337 and ERC-7702. Biconomy provides account abstraction through MEE (Modular Execution Environment): 1. **Nexus Smart Accounts**: Modular ERC-4337 compatible smart contract wallets 2. **MEE**: Unified execution environment providing all bundler and paymaster functionality plus cross-chain orchestration 3. **AbstractJS SDK**: Developer-friendly TypeScript SDK for integration ```typescript theme={null} import { createMeeClient, toMultichainNexusAccount } from "@biconomy/abstractjs"; import { base } from "viem/chains"; // Create a smart account with MEE const account = await toMultichainNexusAccount({ signer: yourSigner, chains: [base] }); const meeClient = await createMeeClient({ account }); // Send gasless transaction via MEE const quote = await meeClient.getQuote({ instructions: [ { calls: [{ to: "0x...", value: 0n, data: "0x..." }] } ], feeToken: { address: "sponsored" } }); const { hash } = await meeClient.executeQuote({ quote }); ``` MEE replaces the need for separate bundler and paymaster configuration. It provides all ERC-4337 functionality plus cross-chain capabilities in a single, unified interface. **ERC-7702** is a newer standard that allows existing EOA wallets to temporarily delegate to smart contract code. This means: * **No migration needed**: Your existing EOA can gain smart account features * **Temporary upgrades**: Delegate to smart contract logic for specific transactions * **Full compatibility**: Works with existing EOA addresses and balances Biconomy supports both ERC-4337 (native smart accounts) and ERC-7702 (EOA delegation) for maximum flexibility. Yes, when implemented correctly. Security considerations include: * **Audited contracts**: Biconomy's smart contracts are audited by leading security firms * **Non-custodial**: Users retain full control of their keys and assets * **Programmable security**: Add extra layers like spending limits, time-locks, multi-sig * **Battle-tested**: ERC-4337 has been live on mainnet since 2023 with billions in TVL The additional programmability actually enables *better* security than traditional EOAs through features like social recovery and fraud monitoring. *** ## Ready to implement account abstraction? Set up AbstractJS SDK in minutes Learn how gasless transactions work # Gasless Applications Source: https://docs.biconomy.io/gasless-apps/index Remove gas friction and 10x your conversion rates Gas fees are the #1 reason users abandon transactions. They don't have ETH or don't understand gas. Gasless apps eliminate this entirely. **MEE Replaces Bundlers and Paymasters**: Traditional ERC-4337 implementations require configuring separate bundlers (for transaction submission) and paymasters (for gas sponsorship). Biconomy's MEE (Modular Execution Environment) supersedes both, providing unified gas sponsorship, transaction bundling, and cross-chain orchestration in a single, simpler interface. ## The Problem Users abandon when asked for gas Users have USDC but need ETH Unexpected fees kill trust Gas estimation confuses users ## The Solution Biconomy offers three approaches to eliminate gas friction: | Approach | Who Pays | Best For | | ------------------- | --------------------- | ---------------------------------------- | | **Sponsored** | You (developer) | Onboarding, promotions, premium features | | **Pay in Tokens** | User (in ERC-20) | Users with stablecoins, no native tokens | | **Cross-Chain Gas** | User (from any chain) | Users with funds on different chains | ## Who Should Go Gasless? ### Consumer Apps Users are crypto-novice Onboarding friction kills growth Small transaction values (gas > value feels wrong) **Examples:** Games, social apps, NFT mints, loyalty programs ### DeFi Applications Multi-step transactions (approve + swap + stake) Cross-chain operations Users already hold ERC-20 tokens **Examples:** DEX aggregators, yield optimizers, portfolio managers ### Enterprise & B2B End users shouldn't think about blockchain Predictable costs (you control spending) White-label solutions **Examples:** Payment processors, supply chain, tokenized assets ## Choose Your Approach **You pay for everything.** Users never see or think about gas. Best for: * New user onboarding * Free trials and promotions * Premium/paid features * High-value conversions ```typescript theme={null} const quote = await meeClient.getQuote({ sponsorship: true, instructions: [...] }); ``` **User pays, but in any token.** No ETH needed. Best for: * DeFi users with stablecoins * Trading applications * Users who refuse to hold native tokens ```typescript theme={null} const quote = await meeClient.getQuote({ instructions: [...], feeToken: { address: USDC, chainId: 8453 } }); ``` **Pay from any chain.** Execute on Arbitrum, pay from Base. Best for: * Multi-chain apps * Users with fragmented balances * Bridging workflows ```typescript theme={null} // Execute on Arbitrum, pay gas from Base const quote = await meeClient.getQuote({ instructions: [{ chainId: 42161, // Arbitrum calls: [...] }], feeToken: { address: USDC_BASE, chainId: 8453 // Pay from Base } }); ``` ## How It Works ``` User Action ↓ Your App calls Biconomy MEE API/SDK ↓ Quote returned with fee (if not sponsored) ↓ User signs message (not transaction) ↓ MEE handles everything: - Bundles transactions (replaces bundlers) - Sponsors or deducts gas (replaces paymasters) - Orchestrates cross-chain if needed ↓ Transaction confirmed ``` Users sign messages, not transactions. This means no wallet pop-ups asking about gas, no "speed up" or "cancel" options—just a clean signature request. **No More Bundler/Paymaster Configuration**: Unlike traditional ERC-4337 setups where you configure bundler URLs and paymaster URLs separately, MEE provides all this functionality through a single API key and unified interface. ## Supported Tokens Any ERC-20 with liquidity can be used for gas: | Category | Examples | | --------------- | --------------------------- | | **Stablecoins** | USDC, USDT, DAI, FRAX, LUSD | | **Wrapped** | WETH, WBTC, wstETH | | **Yield** | aUSDC, stETH, sDAI | | **LP** | Uniswap, Curve positions | | **Governance** | UNI, AAVE, ARB, OP | ## Supported Chains Gasless transactions work across all Biconomy-supported chains: ## Next Steps Pay gas for your users Let users pay in ERC-20 Examples by app type Manage payments and billing # Pay Gas in Tokens Source: https://docs.biconomy.io/gasless-apps/pay-in-tokens Let users pay gas fees in any ERC-20 token Users can pay gas in USDC, USDT, or any token with liquidity—no native ETH required. Perfect for DeFi users who hold stablecoins but not native tokens. **MEE Supersedes Token Paymasters**: Traditional ERC-4337 token paymasters handle gas-in-tokens on a single chain. Biconomy's MEE replaces this with universal gas abstraction—pay for gas on **any chain** using tokens from **any other supported chain**. One unified system instead of chain-by-chain paymaster configuration. ## When to Use Users have stablecoins but no ETH DeFi applications where users already hold tokens Cross-chain apps (pay from any chain) You don't want to sponsor gas ## How It Works ``` User has 100 USDC, 0 ETH ↓ User initiates transaction ↓ Biconomy quotes: "This costs 0.50 USDC" ↓ User signs approval ↓ Biconomy: - Deducts 0.50 USDC from user - Pays native gas on their behalf - Executes transaction ↓ User has 99.50 USDC, transaction complete ``` ## Implementation ```typescript theme={null} import { createMeeClient, toMultichainNexusAccount } from "@biconomy/abstractjs"; const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // 1. Setup const account = await toMultichainNexusAccount({ signer: userWallet, chainConfigurations: [ { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) } ] }); const meeClient = await createMeeClient({ account }); // 2. Build instruction const swapInstruction = await account.buildComposable({ type: "default", data: { chainId: base.id, to: UNISWAP_ROUTER, abi: UniswapAbi, functionName: "exactInputSingle", args: [swapParams] } }); // 3. Get quote with fee token const quote = await meeClient.getQuote({ instructions: [swapInstruction], feeToken: { address: USDC, chainId: base.id } }); // Show fee to user console.log('Fee:', quote.paymentInfo.tokenAmount, 'USDC'); // 4. Execute const { hash } = await meeClient.executeQuote({ quote }); const receipt = await meeClient.waitForSupertransactionReceipt({ hash }); ``` ## Cross-Chain Gas Payment Pay for transactions on one chain using tokens from another: ```typescript theme={null} // Execute on Arbitrum, pay gas from Base USDC const quote = await meeClient.getQuote({ instructions: [{ chainId: 42161, // Arbitrum calls: [/* Arbitrum transaction */] }], feeToken: { address: USDC_BASE, chainId: 8453 // Pay from Base } }); ``` Cross-chain gas requires Smart Account or EIP-7702 mode. Fusion (EOA) mode only supports same-chain gas payment. ## Supported Tokens ### Stablecoins | Token | Chains | | ----- | ------------------------------------- | | USDC | All | | USDT | All | | DAI | Ethereum, Optimism, Arbitrum, Polygon | | FRAX | Ethereum, Arbitrum, Optimism | ### Major Tokens | Token | Chains | | ------------ | ------------------------------------- | | WETH | All | | WBTC | Ethereum, Arbitrum, Optimism, Polygon | | stETH/wstETH | Ethereum, Arbitrum, Optimism | ### Yield-Bearing | Token | Chains | | ----- | ------------------------------------- | | aUSDC | Ethereum, Arbitrum, Optimism, Polygon | | sDAI | Ethereum | ### And 10,000+ More Any token with DEX liquidity can be used. Biconomy's solver network finds the best route to convert to native gas. ## By Wallet Type Most flexible—any token from any chain: ```typescript theme={null} const quote = await meeClient.getQuote({ instructions: [instruction], feeToken: { address: USDC, chainId: base.id } }); ``` Same flexibility, requires delegation: ```typescript theme={null} const quote = await meeClient.getQuote({ instructions: [instruction], delegate: true, authorization, feeToken: { address: USDC, chainId: base.id } }); ``` Fee token must match trigger token, same chain: ```typescript theme={null} const quote = await meeClient.getFusionQuote({ trigger: { chainId: base.id, tokenAddress: USDC, amount: inputAmount }, instructions: [instruction], feeToken: { address: USDC, chainId: base.id } // Must match trigger }); ``` ## Showing Fees to Users Always show the fee before asking for signature: ```typescript theme={null} const quote = await meeClient.getQuote({ instructions: [...], feeToken: { address: USDC, chainId: base.id } }); // Display to user const feeDisplay = ` Transaction Fee: ${formatUnits(quote.paymentInfo.tokenAmount, 6)} USDC You will receive: ${formatUnits(outputAmount, 18)} WETH `; // Confirm before executing if (await userConfirms(feeDisplay)) { await meeClient.executeQuote({ quote }); } ``` ## Multiple Fee Tokens Let users choose their preferred token: ```typescript theme={null} // Get quotes for different tokens const [usdcQuote, usdtQuote, daiQuote] = await Promise.all([ meeClient.getQuote({ instructions, feeToken: { address: USDC, chainId } }), meeClient.getQuote({ instructions, feeToken: { address: USDT, chainId } }), meeClient.getQuote({ instructions, feeToken: { address: DAI, chainId } }), ]); // Show options to user const options = [ { token: 'USDC', fee: usdcQuote.paymentInfo.tokenAmount }, { token: 'USDT', fee: usdtQuote.paymentInfo.tokenAmount }, { token: 'DAI', fee: daiQuote.paymentInfo.tokenAmount }, ]; ``` ## Next Steps Pay gas for your users instead See token payment examples # Payments & Billing Source: https://docs.biconomy.io/gasless-apps/payments How developers pay for gas sponsorship costs Biconomy uses a **post-paid billing model** for gas sponsorship. You sponsor transactions for your users, and Biconomy invoices you based on actual usage. ## How It Works 1. **You sponsor transactions** — Enable sponsorship in your API calls 2. **Biconomy tracks usage** — All sponsored gas is metered automatically 3. **You receive an invoice** — Pay based on what you used No prepaid gas tanks or bridging funds to multiple chains. One invoice covers all your sponsored transactions across all networks. ## Get Pricing Schedule a call to discuss pricing for your application # Gas Sponsorship Source: https://docs.biconomy.io/gasless-apps/sponsorship Pay gas for your users—they never see fees Sponsorship is the ultimate gasless experience. You pay, users don't see or think about gas. Perfect for onboarding, promotions, and premium features. **MEE Supersedes Paymasters**: In traditional ERC-4337 implementations, gas sponsorship requires configuring a separate paymaster service. Biconomy's MEE (Modular Execution Environment) has replaced paymasters—providing all sponsorship functionality plus cross-chain gas abstraction in a unified interface. One gas tank powers sponsorship across **all supported chains**. ## When to Sponsor Onboarding new users (first transaction free) Promotional campaigns Premium features for paying customers High-value actions where friction costs conversions Enterprise apps where users shouldn't see blockchain ## Choosing the Right Flow | Wallet Type | Flow | Sponsorship Ready? | Notes | | ---------------- | ----------------- | ------------------ | ------------------------------------------------- | | Embedded Wallets | EIP-7702 | Easy | Best UX, full smart account delegation | | External EOAs | Fusion | Conditional | Requires permit-supporting tokens or fallback gas | | Native SCAs | SCA Orchestration | Flexible | Best for apps controlling deployment | ## Setup ### 1. Get API Key with Sponsorship 1. Go to [dashboard.biconomy.io](https://dashboard.biconomy.io) 2. Create a project 3. Enable sponsorship 4. Copy your API key 5. Sponsorship is post-paid, Biconomy will invoice your for the costs at the end of the month ### 2. Implement ```typescript theme={null} import { createMeeClient, toMultichainNexusAccount, getMEEVersion, MEEVersion } from "@biconomy/abstractjs"; // 1. Setup with API key const account = await toMultichainNexusAccount({ signer: userWallet, chainConfigurations: [ { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) } ] }); const meeClient = await createMeeClient({ account, apiKey: YOUR_API_KEY // Sponsorship-enabled key }); // 2. Build instruction const mintInstruction = await account.buildComposable({ type: "default", data: { chainId: base.id, to: NFT_CONTRACT, abi: nftAbi, functionName: "mint", args: [userAddress, tokenId] } }); // 3. Get sponsored quote const quote = await meeClient.getQuote({ sponsorship: true, // Enable sponsorship instructions: [mintInstruction] }); // 4. Execute (user signs, you pay gas) const { hash } = await meeClient.executeQuote({ quote }); // 5. Wait for confirmation const receipt = await meeClient.waitForSupertransactionReceipt({ hash }); ``` For sponsorship on testnets (no API key, shared gas tank), see [Testnet Sponsorship](/gasless-apps/testnet-sponsorship). ## Sponsorship Controls **All sponsorship limits and configurations are managed exclusively through the [Biconomy Dashboard](https://dashboard.biconomy.io) — not in code.** Your code only needs `sponsorship: true` in the quote request. Everything else is configured in the dashboard. Go to [dashboard.biconomy.io](https://dashboard.biconomy.io) and navigate to your project's sponsorship settings to configure: * **Per-transaction limit** — set a maximum USD amount you're willing to sponsor per individual transaction * **Total spending cap** — set an overall budget ceiling for your sponsorship * **Allowed contracts** — restrict sponsorship to specific contract addresses * **Chain restrictions** — limit which chains are eligible for sponsored transactions ## Monitoring & Analytics Track sponsorship usage in the dashboard: * Total gas sponsored * Spend by contract * Remaining balance ## Best Practices ### Start with Limits Before going live, head to [dashboard.biconomy.io](https://dashboard.biconomy.io) and set reasonable per-transaction and total spending limits. Don't launch with unlimited sponsorship — always cap your exposure first. ### Monitor Spend Use the dashboard to track spending and set up alerts for: * Spend thresholds being approached * Unusual activity patterns * Low balance warnings ## EIP-7702 Sponsorship (Embedded Wallets) EIP-7702 lets you install smart account logic **directly on EOAs**, enabling orchestration and sponsorship with zero user effort. ```typescript theme={null} const quote = await meeClient.getQuote({ sponsorship: true, instructions: [/* your calls here */], delegate: true, authorization // Required for 7702 accounts }); ``` **Full Support**: Supports all ERC-20s, multichain execution, and full transaction abstraction. **Best For**: Embedded wallets (Privy, Dynamic, Turnkey, ...) ## Fusion Sponsorship (External Wallets) For MetaMask/Rabby users, use Fusion with sponsorship. Fusion enables orchestration from EOAs via a **trigger + Companion Account** pattern: ```typescript theme={null} const quote = await meeClient.getFusionQuote({ sponsorship: true, trigger: { amount: 1n, chainId: base.id, tokenAddress: USDC }, instructions: [/* your calls here */] }); const { hash } = await meeClient.executeFusionQuote({ fusionQuote: quote }); ``` Fusion sponsorship requires the trigger token to support ERC-2612 permit for fully gasless experience. Otherwise, user needs gas for one approval. **Best For**: External Wallets - MetaMask, Trust, Rabby, Coinbase Wallet, Uniswap Wallet, ... ## Hosted vs Self-Hosted Sponsorship * No infra required * Uses Biconomy-managed gas tanks * Set up via dashboard * Supports post-paid modes with enterprise contracts or paying via credit card ```typescript theme={null} const meeClient = await createMeeClient({ account: orchestrator, apiKey: "your_project_api_key" }); ``` * Run your own sponsorship backend * Fully programmable: accept or reject transactions based on any custom logic * Define your own gas tank and token ```typescript theme={null} sponsorshipOptions: { url: "https://your-custom-backend", gasTank: { address: "0x...", token: "0x...", chainId: 84532 } } ``` Ideal for wallets, infra providers, or apps needing custom user-based policies. ## Common Issues ### "Sponsorship not enabled" * Check API key has sponsorship enabled in dashboard ### "Transaction limit exceeded" * Transaction gas cost exceeds your per-transaction limit * Adjust limits in dashboard if needed ## Next Steps Alternative: let users pay in ERC-20 See sponsorship in action # Testnet Sponsorship Source: https://docs.biconomy.io/gasless-apps/testnet-sponsorship Gas sponsorship works out-of-the-box on testnets — no dashboard setup needed On testnets you can try sponsorship **without a dashboard API key**. Use the staging MEE environment and Biconomy's shared testnet gas tank. No dashboard configuration, no API key with sponsorship enabled, and no billing setup required. For general testnet setup (staging node URL, supported chains, limitations), see [Working with Testnets](/overview/abstractjs/working-with-testnets). ## Testnet setup (no API key) Use the SDK helpers `getDefaultMEENetworkUrl` and `getDefaultMEENetworkApiKey` with `true` to point to the staging environment where all supported testnets are available: ```typescript theme={null} import { createMeeClient, getDefaultMEENetworkUrl, getDefaultMEENetworkApiKey, getDefaultMeeGasTank, toMultichainNexusAccount, getMEEVersion, MEEVersion } from "@biconomy/abstractjs"; import { http } from "viem"; import { baseSepolia } from "viem/chains"; const account = await toMultichainNexusAccount({ signer, chainConfigurations: [ { chain: baseSepolia, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) } ] }); const meeClient = await createMeeClient({ account, url: getDefaultMEENetworkUrl(true), apiKey: getDefaultMEENetworkApiKey(true) }); ``` ## Execute sponsored supertransaction on testnet When executing, pass `sponsorship: true` and use the same staging URL and shared gas tank via `sponsorshipOptions`: ```typescript theme={null} const result = await meeClient.execute({ instructions: [dummyInstruction], sponsorship: true, sponsorshipOptions: { url: getDefaultMEENetworkUrl(true), gasTank: getDefaultMeeGasTank(true) }, }); ``` `getDefaultMeeGasTank(true)` returns the testnet sponsorship gas tank used by the staging environment. ## Active testnets (staging) The following testnet chains support sponsored transactions on staging: | Chain | Chain ID | | ---------------------- | -------- | | Arc Testnet | 5042002 | | Avalanche Fuji Testnet | 43113 | | Base Sepolia | 84532 | | Chiliz Spicy Testnet | 88882 | | Fluent Testnet | 20994 | | Mocaverse Testnet | 222888 | | OP Sepolia | 11155420 | | Plasma Testnet | 9746 | | Sei Testnet | 1328 | | Sepolia | 11155111 | | Sonic Testnet | 14601 | | World Chain Sepolia | 4801 | ## Quickstart repos You can run sponsorship on testnets with zero config using these quickstarts. Clone, install, and run—no API keys or private keys required: * **[Chiliz Spicy Testnet](https://github.com/bcnmy/chiliz-spicy-quickstart)** — `git clone https://github.com/bcnmy/chiliz-spicy-quickstart.git` then `bun i` and `bun run index.ts` This example uses Nexus smart account and MEE supertransaction sponsorship (gasless) on staging. ## Moving to production When you're ready for mainnet, sponsorship **does** require dashboard setup. See [Sponsor Gas](/gasless-apps/sponsorship#setup) for the full production configuration steps. # Gasless Use Cases Source: https://docs.biconomy.io/gasless-apps/use-cases Real examples of gasless apps by category See how different app types implement gasless transactions with both the Supertransaction API and AbstractJS SDK. All examples use Biconomy's MEE (Modular Execution Environment), which has replaced traditional ERC-4337 bundlers and paymasters with a unified interface for transaction submission, gas sponsorship, and cross-chain orchestration. ## NFT Marketplaces **Why gasless matters:** Buyers abandon at checkout when they see unexpected gas fees. ### Sponsored First Purchase ```typescript theme={null} const buyInstruction = await account.buildComposable({ type: "default", data: { chainId: base.id, to: MARKETPLACE, abi: marketAbi, functionName: "buy", args: [tokenId], value: priceInWei } }); const isFirstPurchase = await checkFirstPurchase(buyerAddress); const quote = await meeClient.getQuote({ sponsorship: isFirstPurchase, instructions: [buyInstruction], feeToken: isFirstPurchase ? undefined : { address: USDC, chainId: base.id } }); const { hash } = await meeClient.executeQuote({ quote }); ``` *** ## DeFi / Trading **Why gasless matters:** Traders have USDC, not ETH. Multi-step transactions (approve + swap) need batching. ### One-Click Swap ```typescript theme={null} // Approve + Swap in one transaction, pay in USDC const approve = await account.buildComposable({ type: "approve", data: { chainId: base.id, tokenAddress: USDC, spender: UNISWAP_ROUTER, amount: parseUnits("100", 6) } }); const swap = await account.buildComposable({ type: "default", data: { chainId: base.id, to: UNISWAP_ROUTER, abi: UniswapAbi, functionName: "exactInputSingle", args: [swapParams] } }); const quote = await meeClient.getQuote({ instructions: [approve, swap], feeToken: { address: USDC, chainId: base.id } }); // User signs once, both approve + swap execute atomically const { hash } = await meeClient.executeQuote({ quote }); ``` ### Cross-Chain DeFi Execute on Arbitrum, pay from Base: ```typescript theme={null} const arbitrumDeposit = await account.buildComposable({ type: "default", data: { chainId: 42161, // Arbitrum to: AAVE_ARBITRUM, abi: aaveAbi, functionName: "supply", args: [USDC_ARBITRUM, amount, userAddress, 0] } }); const quote = await meeClient.getQuote({ instructions: [arbitrumDeposit], feeToken: { address: USDC_BASE, chainId: 8453 // Pay from Base } }); ``` *** ## Subscriptions & Payments **Why gasless matters:** Recurring payments need to be frictionless. Users set and forget. ### Subscription Service ```typescript theme={null} // Monthly subscription paid in USDC const subscription = await account.buildComposable({ type: "transfer", data: { chainId: base.id, tokenAddress: USDC, recipient: MERCHANT_ADDRESS, amount: parseUnits("9.99", 6) } }); // Sponsor subscription payments (included in subscription cost) const quote = await meeClient.getQuote({ sponsorship: true, instructions: [subscription] }); ``` ### Payroll Distribution ```typescript theme={null} // Company distributes salaries, sponsors all gas const payments = employees.map(emp => ({ type: "transfer", data: { chainId: base.id, tokenAddress: USDC, recipient: emp.address, amount: emp.salary } })); const instructions = await Promise.all( payments.map(p => account.buildComposable(p)) ); const quote = await meeClient.getQuote({ sponsorship: true, // Company pays all gas instructions }); ``` *** ## Enterprise Applications **Why gasless matters:** End users shouldn't know they're using blockchain. ### Supply Chain Tracking ```typescript theme={null} // Manufacturer records shipment, company sponsors const recordShipment = await account.buildComposable({ type: "default", data: { chainId: base.id, to: SUPPLY_CHAIN_CONTRACT, abi: supplyChainAbi, functionName: "recordShipment", args: [shipmentId, "departed"] } }); const quote = await meeClient.getQuote({ sponsorship: true, // Company sponsors instructions: [recordShipment] }); const { hash } = await meeClient.executeQuote({ quote }); ``` ### Tokenized Asset Transfers ```typescript theme={null} // Real estate token transfer, seller pays gas in USDC const transferProperty = await account.buildComposable({ type: "default", data: { chainId: base.id, to: PROPERTY_TOKEN, abi: erc721Abi, functionName: "safeTransferFrom", args: [sellerAddress, buyerAddress, propertyTokenId] } }); const quote = await meeClient.getQuote({ instructions: [transferProperty], feeToken: { address: USDC, chainId: base.id } }); ``` *** ## Loyalty & Rewards **Why gasless matters:** Rewards should feel like rewards, not cost users gas. ### Redeem Points for NFT ```typescript theme={null} // User redeems loyalty points, brand sponsors const redeemReward = await account.buildComposable({ type: "default", data: { chainId: base.id, to: LOYALTY_CONTRACT, abi: loyaltyAbi, functionName: "redeemForNFT", args: [userAddress, rewardId] } }); const quote = await meeClient.getQuote({ sponsorship: true, // Brand pays instructions: [redeemReward] }); ``` *** ## Quick Reference | Use Case | Approach | Who Pays | | -------------------- | ------------ | ---------------- | | NFT (first purchase) | Sponsored | Marketplace | | NFT (repeat buyer) | Pay in token | User | | DeFi swaps | Pay in token | User | | Subscriptions | Sponsored | Service provider | | Enterprise | Sponsored | Company | | Loyalty rewards | Sponsored | Brand | # Composable Batch Calls Source: https://docs.biconomy.io/overview/abstractjs/batch-calls Execute multiple operations in one atomic transaction Batch multiple contract calls into a single transaction. Operations either all succeed or all fail—no partial execution. ## Why Batching? * **One signature** for complex flows * **Atomic execution**—no stuck states * **Lower gas costs** than separate transactions * **Composable**—output of one call feeds into the next ## Basic Batch ```typescript theme={null} const approveInstruction = await account.buildComposable({ type: "approve", data: { spender: UNISWAP_ROUTER, tokenAddress: USDC, chainId: base.id, amount: parseUnits("100", 6) } }); const swapInstruction = await account.buildComposable({ type: "default", data: { chainId: base.id, to: UNISWAP_ROUTER, abi: UniswapAbi, functionName: "exactInputSingle", args: [{ tokenIn: USDC, tokenOut: WETH, amountIn: parseUnits("100", 6), /* ... */ }] } }); // Execute both in one transaction const quote = await meeClient.getQuote({ instructions: [approveInstruction, swapInstruction], feeToken: { address: USDC, chainId: base.id } }); const { hash } = await meeClient.executeQuote({ quote }); ``` ## Composable Operations Use runtime injection when the next step depends on the previous output: ```typescript theme={null} import { runtimeERC20BalanceOf } from "@biconomy/abstractjs"; import { balanceNotZeroConstraint } from "../utils/balanceNotZero.util"; // Step 1: Swap USDC → WETH const swap = await account.buildComposable({ type: "default", data: { chainId: base.id, to: UNISWAP_ROUTER, abi: UniswapAbi, functionName: "exactInputSingle", args: [{ tokenIn: USDC, tokenOut: WETH, amountIn: parseUnits("100", 6), amountOutMinimum: 0n, // ... }] } }); // Step 2: Deposit ALL received WETH into Morpho const deposit = await account.buildComposable({ type: "default", data: { chainId: base.id, to: MORPHO_POOL, abi: MorphoAbi, functionName: "deposit", args: [ runtimeERC20BalanceOf({ tokenAddress: WETH, targetAddress: account.addressOn(base.id, true), constraints: [balanceNotZeroConstraint] }), account.addressOn(base.id, true) ] } }); const quote = await meeClient.getQuote({ instructions: [swap, deposit], feeToken: { address: USDC, chainId: base.id } }); ``` ## Fusion Mode (External Wallets) For MetaMask/Rabby users, use a trigger to fund the operation: ```typescript theme={null} const trigger = { chainId: base.id, tokenAddress: USDC, amount: parseUnits("100", 6) }; const quote = await meeClient.getFusionQuote({ trigger, instructions: [approveInstruction, swapInstruction, depositInstruction], feeToken: { address: USDC, chainId: base.id } }); const { hash } = await meeClient.executeFusionQuote({ fusionQuote: quote }); ``` ## Example: Swap → Stake in One Click ```typescript theme={null} import { runtimeERC20BalanceOf } from "@biconomy/abstractjs"; import { balanceNotZeroConstraint } from "../utils/balanceNotZero.util"; const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; const WETH = "0x4200000000000000000000000000000000000006"; const MORPHO_POOL = "0xA2Cac0023a4797b4729Db94783405189a4203AFc"; // 1. Approve Uniswap const approve = await account.buildComposable({ type: "approve", data: { spender: UNISWAP_ROUTER, tokenAddress: USDC, chainId: base.id, amount: inputAmount } }); // 2. Swap USDC → WETH const swap = await account.buildComposable({ type: "default", data: { chainId: base.id, to: UNISWAP_ROUTER, abi: UniswapAbi, functionName: "exactInputSingle", args: [{ tokenIn: USDC, amountIn: inputAmount, tokenOut: WETH, /* ... */ }] } }); // 3. Approve Morpho const approveMorpho = await account.buildComposable({ type: "approve", data: { spender: MORPHO_POOL, tokenAddress: WETH, chainId: base.id, amount: runtimeERC20BalanceOf({ tokenAddress: WETH, targetAddress: account.addressOn(base.id, true), constraints: [balanceNotZeroConstraint] }) } }); // 4. Deposit to Morpho const deposit = await account.buildComposable({ type: "default", data: { chainId: base.id, to: MORPHO_POOL, abi: [{ name: "deposit", inputs: [{ name: "assets", type: "uint256" }, { name: "receiver", type: "address" }], type: "function" }], functionName: "deposit", args: [ runtimeERC20BalanceOf({ tokenAddress: WETH, targetAddress: account.addressOn(base.id, true), constraints: [balanceNotZeroConstraint] }), account.addressOn(base.id, true) ] } }); // Execute all four operations with one signature const quote = await meeClient.getQuote({ instructions: [approve, swap, approveMorpho, deposit], feeToken: { address: USDC, chainId: base.id } }); const { hash } = await meeClient.executeQuote({ quote }); ``` ## Next Steps Execute flows across multiple chains # Cleanup Transactions Source: https://docs.biconomy.io/overview/abstractjs/cleanup Return leftover tokens after failed operations Cleanup transactions ensure users get their tokens back if something fails mid-execution—especially important for cross-chain flows. ## Why Cleanups Matter Single-chain flows are atomic: all succeed or all fail. Cross-chain flows aren't—a bridge might succeed but the destination swap fails, leaving tokens stranded. **Cleanups automatically return leftover tokens to the user.** ## Fusion Mode For external wallets (MetaMask, Rabby): ```typescript theme={null} const quote = await meeClient.getFusionQuote({ trigger, instructions: [bridge, swap, deposit], cleanUps: [ { chainId: base.id, tokenAddress: USDC, recipientAddress: userEOA } ], feeToken: { chainId: base.id, address: USDC } }); ``` No `dependsOn` needed—Fusion batches everything automatically. ## EIP-7702 / Smart Account Mode For embedded wallets or SCAs: ```typescript theme={null} const quote = await meeClient.getQuote({ instructions: [bridge, swap, deposit], cleanUps: [ { chainId: base.id, tokenAddress: USDC, recipientAddress: userEOA, dependsOn: [userOp(2)] // Wait for instruction 2 } ], feeToken: { chainId: base.id, address: USDC } }); ``` Use `dependsOn` when cleanup should wait for specific instructions. ## Multi-Token Cleanup Return multiple tokens: ```typescript theme={null} cleanUps: [ { chainId: base.id, tokenAddress: USDT, recipientAddress: userEOA, dependsOn: [userOp(1)] }, { chainId: base.id, tokenAddress: USDC, recipientAddress: userEOA, dependsOn: [userOp(2)] } ] ``` Each cleanup executes after its dependency resolves. ## How It Works 1. Cleanup instructions are added to the transaction 2. They wait until dependent operations complete 3. Transfer any remaining balance back to user 4. If balance is 0, cleanup harmlessly reverts Cleanups always run last and only transfer tokens that remain in the smart account. ## When to Use | Scenario | Use Cleanup? | | ---------------------------- | ------------ | | Cross-chain bridge + swap | ✅ Yes | | Multi-step DeFi flow | ✅ Yes | | Simple single-chain transfer | ❌ Not needed | | Atomic single-chain batch | ❌ Not needed | ## Tips * **Fusion**: Skip `dependsOn`—handled automatically * **EIP-7702/SCA**: Use `dependsOn` for non-sequential flows * Always set `recipientAddress` to the user's EOA * Cleanups prevent funds from being stuck in intermediate accounts # Composable Batching Source: https://docs.biconomy.io/overview/abstractjs/composable-batching Use dynamic values that resolve at execution time Runtime injection lets you use values that aren't known until execution—like token balances after a swap or bridge. ## The Problem Traditional batching requires hardcoded values: ```typescript theme={null} // ❌ This breaks if swap output differs from expected const depositAmount = parseUnits("0.05", 18); // Guessing WETH output ``` ## The Solution Use runtime functions to inject actual values at execution time: ```typescript theme={null} import { runtimeERC20BalanceOf, greaterThanOrEqualTo } from "@biconomy/abstractjs"; import { balanceNotZeroConstraint } from "../utils/balanceNotZero.util"; // ✅ Uses actual balance when executing const depositAmount = runtimeERC20BalanceOf({ tokenAddress: WETH, targetAddress: account.addressOn(base.id, true), constraints: [balanceNotZeroConstraint] }); ``` ## Available Functions | Function | Description | | --------------------------------- | ---------------------------------- | | `runtimeERC20BalanceOf` | ERC-20 token balance | | `runtimeNativeBalanceOf` | Native token balance (ETH, MATIC) | | `runtimeERC20AllowanceOf` | Token allowance between addresses | | `runtimeParamViaCustomStaticCall` | Any contract read (up to 32 bytes) | ## runtimeERC20BalanceOf Most common—inject token balance at execution: ```typescript theme={null} import { runtimeERC20BalanceOf } from "@biconomy/abstractjs"; const transferAll = await account.buildComposable({ type: "default", data: { chainId: base.id, to: USDC, abi: erc20Abi, functionName: "transfer", args: [ recipient, runtimeERC20BalanceOf({ tokenAddress: USDC, targetAddress: account.addressOn(base.id, true), constraints: [] }) ] } }); ``` ## runtimeNativeBalanceOf For native token operations: ```typescript theme={null} import { runtimeNativeBalanceOf } from "@biconomy/abstractjs"; const sendAllETH = await account.buildComposable({ type: "nativeTokenTransfer", data: { chainId: base.id, to: recipient, value: runtimeNativeBalanceOf({ targetAddress: account.addressOn(base.id, true) }) } }); ``` ## Constraints Constraints control **when** instructions execute and protect against bad values. ### Minimum Balance ```typescript theme={null} import { greaterThanOrEqualTo } from "@biconomy/abstractjs"; runtimeERC20BalanceOf({ tokenAddress: USDC, targetAddress: orchestrator, constraints: [greaterThanOrEqualTo(parseUnits("90", 6))] // Wait for 90+ USDC }) ``` ### Non-Zero Check ```typescript theme={null} import { balanceNotZeroConstraint } from "../utils/balanceNotZero.util"; runtimeERC20BalanceOf({ tokenAddress: WETH, targetAddress: orchestrator, constraints: [balanceNotZeroConstraint] // Wait for any WETH }) ``` ## Transaction Ordering Constraints determine execution sequence. MEE retries until satisfied: ```typescript theme={null} // Step 1: Bridge (executes immediately) const bridge = await account.buildComposable({ /* bridge config */ }); // Step 2: Swap (waits for bridge tokens) const swap = await account.buildComposable({ type: "default", data: { chainId: base.id, args: [ runtimeERC20BalanceOf({ tokenAddress: USDC_BASE, targetAddress: account.addressOn(base.id, true), constraints: [greaterThanOrEqualTo(minExpected)] // ← Waits here }) ] } }); ``` **Flow:** 1. Bridge instruction executes 2. Swap instruction simulated → fails (no tokens yet) 3. MEE waits and retries 4. Bridge completes, tokens arrive 5. Constraint satisfied → swap executes ## Example: Swap and Deposit All ```typescript theme={null} // Swap USDC → WETH const swap = await account.buildComposable({ type: "default", data: { chainId: base.id, to: UNISWAP_ROUTER, abi: UniswapAbi, functionName: "exactInputSingle", args: [{ tokenIn: USDC, tokenOut: WETH, amountIn: parseUnits("100", 6), /* ... */ }] } }); // Approve exactly what we received const approve = await account.buildComposable({ type: "approve", data: { chainId: base.id, spender: MORPHO_POOL, tokenAddress: WETH, amount: runtimeERC20BalanceOf({ tokenAddress: WETH, targetAddress: account.addressOn(base.id, true), constraints: [balanceNotZeroConstraint] }) } }); // Deposit all WETH const deposit = await account.buildComposable({ type: "default", data: { chainId: base.id, to: MORPHO_POOL, abi: MorphoAbi, functionName: "deposit", args: [ runtimeERC20BalanceOf({ tokenAddress: WETH, targetAddress: account.addressOn(base.id, true), constraints: [balanceNotZeroConstraint] }), account.addressOn(base.id, true) ] } }); const quote = await meeClient.getQuote({ instructions: [swap, approve, deposit], feeToken: { address: USDC, chainId: base.id } }); ``` ## Best Practices Swap outputs, bridge results, slippage Protect against unexpected states Transfer leftover tokens back to user Use `greaterThanOrEqualTo` with minimum ## Summary Runtime injection turns static transactions into **adaptive, state-aware flows** that handle: * Unknown swap outputs * Bridge timing * Slippage protection * Automatic sequencing No more guessing values or stuck transactions. # Conditional Execution Source: https://docs.biconomy.io/overview/abstractjs/conditional-execution Attach runtime conditions to transactions Transactions wait until conditions are met before executing—enabling limit orders, price triggers, and safe operations. ## Quick Example ```typescript theme={null} import { createCondition, ConditionType } from '@biconomy/abstractjs'; import { erc20Abi, parseUnits } from 'viem'; // Only execute if balance >= 100 USDC const minBalance = createCondition({ targetContract: USDC, functionAbi: erc20Abi, functionName: 'balanceOf', args: [userAddress], value: parseUnits('100', 6), type: ConditionType.GTE }); // Add condition to any instruction const instruction = await account.buildComposable({ type: 'transfer', data: { tokenAddress: USDC, recipient: recipientAddress, amount: transferAmount, chainId: base.id, conditions: [minBalance] // ← Waits until satisfied } }); ``` ## How It Works ``` Submit Transaction → Check Conditions → Pass? Execute : Wait (PENDING) ↓ MEE rechecks periodically ↓ Conditions pass → Execute ``` ## Condition Types | Type | Meaning | Use Case | | ----- | ------- | ------------------------------ | | `GTE` | ≥ value | Minimum balance | | `LTE` | ≤ value | Maximum price | | `EQ` | = value | Exact state (e.g., not paused) | ## Common Patterns ### Minimum Balance ```typescript theme={null} createCondition({ targetContract: USDC, functionAbi: erc20Abi, functionName: 'balanceOf', args: [userAddress], value: parseUnits('50', 6), type: ConditionType.GTE }) ``` ### Contract Not Paused ```typescript theme={null} createCondition({ targetContract: protocolAddress, functionAbi: pausableAbi, functionName: 'paused', args: [], value: 0n, // false type: ConditionType.EQ }) ``` ### Multiple Conditions (AND) All conditions must pass: ```typescript theme={null} const instruction = await account.buildComposable({ type: 'default', data: { to: lendingProtocol, abi: lendingAbi, functionName: 'borrow', args: [amount], chainId: base.id, conditions: [ // Must have collateral createCondition({ targetContract: collateralToken, functionAbi: erc20Abi, functionName: 'balanceOf', args: [userAddress], value: minCollateral, type: ConditionType.GTE }), // Must be healthy createCondition({ targetContract: lendingProtocol, functionAbi: lendingAbi, functionName: 'getHealthFactor', args: [userAddress], value: parseUnits('1.5', 18), type: ConditionType.GTE }) ] } }); ``` ## Waiting for Conditions Set a timeout for how long to wait: ```typescript theme={null} const quote = await meeClient.getFusionQuote({ trigger: { chainId: base.id, tokenAddress: USDC, amount }, instructions: [instruction], feeToken: { chainId: base.id, address: USDC }, upperBoundTimestamp: Math.floor(Date.now() / 1000) + 300 // Wait up to 5 min }); const { hash } = await meeClient.executeFusionQuote({ fusionQuote: quote }); // Transaction stays PENDING until condition met or timeout ``` ## Use Cases * **Limit orders**: Wait for price target * **Balance triggers**: Execute when funds arrive * **Safety checks**: Verify contract state before executing * **Slippage protection**: Only swap at acceptable prices ## Best Practices USDC = 6, WETH = 18 1-3 conditions optimal Always set `upperBoundTimestamp` For type-safe ABIs # Cross-Chain Orchestration Source: https://docs.biconomy.io/overview/abstractjs/cross-chain Execute multi-chain flows with a single signature Bridge, swap, and interact across chains in one transaction. User signs once—MEE handles timing, confirmations, and execution. ## How It Works 1. User signs a single message 2. MEE executes operations on the source chain 3. MEE waits for bridge completion 4. MEE executes operations on the destination chain 5. User receives final tokens ## Basic Cross-Chain Flow ```typescript theme={null} import { toMultichainNexusAccount, createMeeClient } from "@biconomy/abstractjs"; import { arbitrum, base } from "viem/chains"; // Account spans multiple chains const account = await toMultichainNexusAccount({ signer, chainConfigurations: [ { chain: arbitrum, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) }, { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) } ] }); const meeClient = await createMeeClient({ account }); // Instructions on different chains const bridgeInstruction = /* bridge from Arbitrum */; const swapInstruction = /* swap on Base */; const depositInstruction = /* deposit on Base */; const quote = await meeClient.getQuote({ instructions: [bridgeInstruction, swapInstruction, depositInstruction], feeToken: { address: USDC_ARBITRUM, chainId: arbitrum.id } }); const { hash } = await meeClient.executeQuote({ quote }); ``` ## Runtime Injection for Bridge Outputs Since bridge outputs aren't known upfront, use `runtimeERC20BalanceOf`: ```typescript theme={null} import { runtimeERC20BalanceOf, greaterThanOrEqualTo } from "@biconomy/abstractjs"; // Bridge USDC from Arbitrum to Base via Across SpokePool // See the Across integration guide for full setup: /new/integration-guides/bridges-and-solvers/integrate-across const bridge = await account.buildComposable({ type: "default", data: { chainId: arbitrum.id, to: ACROSS_SPOKE_POOL, abi: acrossSpokePoolAbi, functionName: "depositV3", args: [ account.addressOn(arbitrum.id, true), // depositor account.addressOn(base.id, true), // recipient USDC_ARBITRUM, // inputToken USDC_BASE, // outputToken parseUnits("100", 6), // inputAmount outputAmount, // outputAmount (from Across quote API) base.id, // destinationChainId zeroAddress, // exclusiveRelayer quoteTimestamp, // quoteTimestamp (from Across quote API) fillDeadline, // fillDeadline 0, // exclusivityDeadline "0x" // message ] } }); // Swap on Base—waits for bridge to complete const swap = await account.buildComposable({ type: "default", data: { chainId: base.id, to: UNISWAP_ROUTER, abi: UniswapAbi, functionName: "exactInputSingle", args: [{ tokenIn: USDC_BASE, amountIn: runtimeERC20BalanceOf({ tokenAddress: USDC_BASE, targetAddress: account.addressOn(base.id, true), constraints: [greaterThanOrEqualTo(parseUnits("80", 6))] // 20% slippage tolerance }), tokenOut: WETH_BASE, // ... }] } }); ``` **Automatic Sequencing**: MEE retries the swap instruction until the constraint is satisfied (bridge tokens have arrived). ## Fusion Mode for External Wallets For MetaMask/Rabby users: ```typescript theme={null} const trigger = { chainId: arbitrum.id, tokenAddress: USDC_ARBITRUM, amount: parseUnits("100", 6) }; const quote = await meeClient.getFusionQuote({ trigger, instructions: [ approveAcross, bridge, swapOnBase, depositOnBase, returnTokensToEOA ], feeToken: { address: USDC_ARBITRUM, chainId: arbitrum.id } }); const { hash } = await meeClient.executeFusionQuote({ fusionQuote: quote }); ``` ## Example: Bridge → Swap → Deposit Complete flow: USDC on Arbitrum → WETH yield on Base ```typescript theme={null} const inputAmount = parseUnits("100", 6); const minAfterSlippage = inputAmount * 80n / 100n; // 1. Approve Across to spend USDC const approveAcross = await account.buildComposable({ type: "approve", data: { spender: ACROSS_SPOKE_POOL, tokenAddress: USDC_ARBITRUM, chainId: arbitrum.id, amount: inputAmount } }); // 2. Bridge USDC: Arbitrum → Base (via Across SpokePool) const bridge = await account.buildComposable({ type: "default", data: { chainId: arbitrum.id, to: ACROSS_SPOKE_POOL, abi: acrossSpokePoolAbi, functionName: "depositV3", args: [ account.addressOn(arbitrum.id, true), // depositor account.addressOn(base.id, true), // recipient USDC_ARBITRUM, // inputToken USDC_BASE, // outputToken inputAmount, // inputAmount outputAmount, // outputAmount (from Across quote API) base.id, // destinationChainId zeroAddress, // exclusiveRelayer quoteTimestamp, // quoteTimestamp fillDeadline, // fillDeadline 0, // exclusivityDeadline "0x" // message ] } }); // 3. Swap USDC → WETH on Base (waits for bridge) const swap = await account.buildComposable({ type: "default", data: { chainId: base.id, to: UNISWAP_ROUTER, abi: UniswapAbi, functionName: "exactInputSingle", args: [{ tokenIn: USDC_BASE, amountIn: runtimeERC20BalanceOf({ tokenAddress: USDC_BASE, targetAddress: account.addressOn(base.id, true), constraints: [greaterThanOrEqualTo(minAfterSlippage)] }), tokenOut: WETH_BASE, amountOutMinimum: 0n, recipient: account.addressOn(base.id, true), fee: 500, sqrtPriceLimitX96: 0n }] } }); // 4. Deposit WETH into Morpho const deposit = await account.buildComposable({ type: "default", data: { chainId: base.id, to: MORPHO_WETH_POOL, abi: MorphoAbi, functionName: "deposit", args: [ runtimeERC20BalanceOf({ tokenAddress: WETH_BASE, targetAddress: account.addressOn(base.id, true) }), account.addressOn(base.id, true) ] } }); // Execute entire flow const quote = await meeClient.getQuote({ instructions: [approveAcross, bridge, swap, deposit], feeToken: { address: USDC_ARBITRUM, chainId: arbitrum.id } }); const { hash } = await meeClient.executeQuote({ quote }); ``` ## Key Benefits | Traditional | With MEE | | ------------------------- | ---------------------------------------------------------------------------------------- | | 4+ signatures | 1 signature | | Manual bridge waiting | Automatic | | ETH needed on both chains | Pay in one token | | Risk of stuck states | No stuck states—programmatic fund retrieval via [cleanups](/overview/abstractjs/cleanup) | **Important**: Cross-chain transactions are **not atomic**—only single-chain batches are fully atomic (all-or-nothing). For cross-chain flows, if a step fails after bridging, tokens won't be lost—use [cleanup transactions](/overview/abstractjs/cleanup) to programmatically return funds to the user's wallet. ## Next Steps Deep dive into dynamic parameters # Simulation and Gas Estimation Source: https://docs.biconomy.io/overview/abstractjs/estimating-gas Preview, test, and optimize your supertransactions before committing them on-chain Simulation enables you to preview and test your supertransactions in a forked mainnet environment before committing them on-chain. This crucial step helps you validate transaction logic, optimize gas costs, and prevent costly execution failures. ## What is Simulation? Simulation executes your supertransaction against a forked blockchain network to validate its behavior without broadcasting it on-chain. This allows you to: * **Validate transaction execution** - Test if your supertransaction will execute successfully across single or multiple chains * **Apply token overrides** - Test different token amounts and scenarios without using real assets * **Optimize gas limits** - Get precise gas estimates to set tight, optimal gas limits for your User Operations * **Catch failures early** - Identify and fix configuration errors before sending transactions on-chain **Best Practice**: Always simulate your supertransactions before sending them on-chain. This helps you set optimal gas limits, minimize transaction fees, and catch potential failures early in the development cycle. ## Why Simulation Matters ### Cost Optimization Simulations provide precise gas estimates, allowing you to set tight gas limits that minimize fees while ensuring successful execution. ### Error Prevention By testing transactions before submitting it on-chain, you can identify and fix issues such as: * Incorrect parameter configurations * Insufficient token allowances * Failed cross-chain routing * Logical errors in instruction sequencing ### Faster Development Identify and fix bugs before submitting your supertransaction for execution. This dramatically improves the debugging experience by eliminating long orchestration wait times to discover failures, while preventing costly failed on-chain transactions. ## Simulation Examples Simulation works for both single-chain and multi-chain supertransactions. Here are common use cases: ### Single-Chain Operations * **Swap → Lend → Borrow → Withdraw** - Perform multiple DeFi actions in a single atomic transaction * **Multi-step protocols** - Chain together various protocol interactions on the same chain ### Multi-Chain Operations * **Swap → Bridge → Swap** - Exchange tokens on one chain, bridge to another, then swap again * **Complex DeFi flows** - Execute sophisticated strategies that span multiple networks ## Simulation Overrides Overrides allow you to customize the blockchain state during simulation, enabling you to test your supertransactions under specific conditions. There are two types of overrides available: ### Token Overrides Token overrides let you set specific token balances for accounts during simulation. This is essential for testing multi-chain flows where tokens need to exist on destination chains. ```typescript theme={null} const overrides = { tokenOverrides: [ { tokenAddress: "0xUSDC", chainId: base.id, balance: parseUnits("1000", 6), accountAddress: orchestrator.addressOn(base.id, true), }, ], }; ``` **Important notes for token overrides:** * For native token overrides (ETH, MATIC, etc.), use the zero address (`0x0000000000000000000000000000000000000000`) as the token address * For funding transactions (triggers), token overrides are automatically added by the MEE node. If you explicitly configure additional token overrides for the same token, they will be summed with the trigger amount ### Custom Overrides Custom overrides provide advanced control over blockchain state by allowing you to modify any storage slot of a contract. This is useful for complex scenarios that go beyond simple token balance adjustments. ```typescript theme={null} const overrides = { customOverrides: [ { contractAddress: "0xUSDC", storageSlot: "0xbalance_storage_slot_user", chainId: base.id, value: parseUnits("1000", 6), }, ], }; ``` Custom overrides are a general-purpose mechanism for advanced use cases. Use them when you need to simulate specific contract states that aren't achievable through token overrides alone. ### Complete Override Example You can combine both token and custom overrides in a single simulation: ```typescript theme={null} const overrides = { tokenOverrides: [ { tokenAddress: "0xUSDC", chainId: base.id, balance: parseUnits("1000", 6), accountAddress: orchestrator.addressOn(base.id, true), }, ], customOverrides: [ { contractAddress: "0xUSDC", storageSlot: "0xbalance_storage_slot_user", chainId: base.id, value: parseUnits("1000", 6), }, ], }; ``` ## Running a Simulation Set up your supertransaction with all required parameters, including target chains, tokens, and instructions. Execute one of the methods that returns the quote to initiate the simulation and receive quote information along with simulation results. Analyze gas estimates, and any warnings or errors returned by the simulation. Make necessary changes based on simulation results, then re-simulate to verify corrections. ## Code Example ### Single-Chain Simulation Here's how to enable simulation for a single-chain supertransaction: ```typescript theme={null} import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; import { base } from "viem/chains"; import { toMultichainNexusAccount, getMEEVersion, MEEVersion, createMeeClient } from "@biconomy/abstractjs"; import { http } from "viem"; const eoaAccount = privateKeyToAccount(generatePrivateKey()); const orchestrator = await toMultichainNexusAccount({ chainConfigurations: [ { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) } ], signer: eoaAccount }); const meeClient = await createMeeClient({ account: orchestrator, }); const fusionQuote = await meeClient.getFusionQuote({ // Token overrides for triggers are automatically handled by the MEE node trigger: { tokenAddress: '0xyour-token-address', amount: 1n, chainId: base.id, }, // Enable simulation to validate and estimate gas simulation: { simulate: true, }, instructions: [...], feeToken: { address: '0xyour-token-address', chainId: base.id, }, }); ``` When `simulation.simulate` is set to `true`, the MEE node will execute your supertransaction against a forked blockchain network. This estimates accurate gas limits and attach it to your userOps and validates that your transaction will execute successfully before you commit it on-chain. ### Multi-Chain Simulation with Token Overrides For multi-chain supertransactions, you'll need to provide token overrides to simulate the expected token balances on destination chains: ```typescript theme={null} import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; import { base, optimism } from "viem/chains"; import { toMultichainNexusAccount, getMEEVersion, MEEVersion, createMeeClient, } from "@biconomy/abstractjs"; import { http, parseUnits, zeroAddress } from "viem"; const eoaAccount = privateKeyToAccount(generatePrivateKey()); const orchestrator = await toMultichainNexusAccount({ chainConfigurations: [ { chain: optimism, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0), }, { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0), }, ], signer: eoaAccount, }); const meeClient = await createMeeClient({ account: orchestrator, }); // Composable Across bridge operation to move your tokens from Optimism to Base // Use type: "default" to call the Across SpokePool directly const optimismToBaseAcrossCall = await orchestrator.buildComposable({ type: "default", data: { chainId: optimism.id, to: ACROSS_SPOKE_POOL, abi: acrossSpokePoolAbi, functionName: "depositV3", args: [ orchestrator.addressOn(optimism.id, true), // depositor orchestrator.addressOn(base.id, true), // recipient "0xUSDC", // inputToken "0xDAI", // outputToken runtimeERC20BalanceOf({ // inputAmount (runtime) targetAddress: orchestrator.addressOn(optimism.id, true), tokenAddress: "0xUSDC", constraints: [], }), outputAmount, // outputAmount (from Across quote API) base.id, // destinationChainId zeroAddress, // exclusiveRelayer quoteTimestamp, // quoteTimestamp fillDeadline, // fillDeadline 0, // exclusivityDeadline "0x" // message ] }, }); const fusionQuote = await meeClient.getFusionQuote({ // Token overrides for triggers are automatically handled by the MEE node trigger: { tokenAddress: "0xyour-token-address", amount: 1n, chainId: base.id, }, // For multi-chain transactions, provide token overrides to simulate // expected balances on destination chains simulation: { simulate: true, overrides: { tokenOverrides: [ { tokenAddress: "0xUSDC", chainId: base.id, // Simulate token balance on destination chain balance: parseUnits("1000", 6), accountAddress: orchestrator.addressOn(base.id, true), }, ], }, }, // Add custom instructions after bridging instructions: optimismToBaseAcrossCall, feeToken: { address: "0xyour-token-address", chainId: base.id, }, }); ``` **Token overrides** let you simulate how your supertransaction will behave with specific token balances on destination chains. This is crucial for multi-chain operations where tokens will arrive after bridging, allowing you to test the entire flow including post-bridge instructions. ## Speed vs Cost Optimization When building supertransactions, you face a fundamental tradeoff between execution speed and cost optimization. Choosing the right strategy depends on your application's priorities and user expectations. ### The Tradeoff **Latency:** \~250ms additional overhead **Benefits:** * Provides tight, precise gas limits that minimize user costs * Validates transaction execution before submission * Catches errors early in the development cycle * Eliminates guesswork in gas estimation **Best for:** * Production applications where cost optimization matters * Complex multi-chain flows that need validation * Scenarios where you're uncertain about gas requirements * User-facing applications where transparent pricing improves conversion **Latency:** No additional overhead **Benefits:** * Immediate execution without simulation delay * Full control over gas parameters * Optimal for high-frequency or time-sensitive operations **Trade-offs:** * Requires knowledge of exact gas requirements * Risk of setting limits too high (inflated quotes) or too low (transaction failures) * No pre-execution validation **Best for:** * High-speed execution requirements * Well-tested transaction patterns with known gas costs * Applications where 250ms matters significantly * Teams with strong gas optimization expertise ### Decision Matrix Choose your approach based on these criteria: | Priority | Recommended Approach | Reasoning | | ----------------------------- | -------------------- | ------------------------------------------- | | **Cost optimization** | Enable simulation | Tight gas limits = lower quotes = better UX | | **Execution speed** | Manual gas limits | Skip \~250ms simulation overhead | | **Development/Testing** | Enable simulation | Catch issues early, iterate faster | | **Production (first launch)** | Enable simulation | Validate behavior, optimize costs | | **Production (mature app)** | Manual gas limits | Known patterns, prioritize speed | | **Complex multi-chain flows** | Enable simulation | Validation is critical | **Critical:** Enabling simulation will override any manually configured gas limits with optimized values calculated from simulation results. You cannot use both approaches simultaneously. ### Making the Choice **Start with simulation** during development and early production. Once your transaction patterns are stable and well-understood, you can optionally switch to manual gas limits for the speed benefit—but only if the 250ms overhead is a genuine bottleneck for your use case. ## Setting Manual Gas Limits AbstractJS assigns **generous default gas limits** to every instruction so you can prototype quickly without tweaking `gasLimit` values. Once you move to production, those defaults become sub-optimal—quotes may look expensive and confuse users. ### Why You Should Care Understanding the difference between development and production gas limit strategies is crucial for user experience. | Stage | Default behaviour | Impact | | ----------------- | --------------------------------------------- | ---------------------------------------------- | | **PoC / testing** | High gas limits keep dev friction near-zero | Faster iterations | | **Production** | High limits over-inflate the **quoted price** | Users see a larger "max cost" and may drop off | ### Quote vs. Actual Cost The price returned by `meeClient.getQuote()` is a **maximum**. If you overshoot, MEE refunds all unspent gas to the user **on-chain**. Lowering `gasLimit` keeps the quote realistic and improves conversion, without risking "out of gas" as long as you size it sensibly. ### How to Set Manual Limits Add a `gasLimit` field (in wei) to each instruction's `data` object: ```typescript theme={null} const instruction = await orchestrator.buildComposable({ type: "default", data: { abi: erc20Abi, to: "0xUSDC_ADDRESS", chainId: optimism.id, functionName: "transfer", args: [ "0xRecipient", parseUnits("10", 6), ], gasLimit: 35_000n, // manual limit }, }); ``` ### Recommended Workflow Observe gas used in testnet with generous limits Set limit to \~20% above observed usage Deploy with explicit limits so quotes stay tight ### Key Takeaways * High defaults are fine for development, but tune gas limits before mainnet launch * Quotes show maximum cost; unused gas is always refunded * Tight gas limits result in accurate quotes that build user trust and reduce transaction abandonment * Simulation provides the tightest gas estimates but adds \~250ms latency * Manual gas limits offer faster execution when you know the requirements # Pay Gas in ERC-20 Tokens Source: https://docs.biconomy.io/overview/abstractjs/gas-tokens Let users pay transaction fees in any token Users can pay gas in USDC, USDT, or any ERC-20—no need for native tokens like ETH. ## Basic Usage Pass `feeToken` when getting a quote: ```typescript theme={null} const quote = await meeClient.getQuote({ instructions: [/* your calls */], feeToken: { address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base chainId: 8453 } }); const { hash } = await meeClient.executeQuote({ quote }); ``` ## Cross-Chain Gas Payment Pay for transactions on one chain using tokens from another: ```typescript theme={null} // Execute on Arbitrum, pay gas from Base USDC const quote = await meeClient.getQuote({ instructions: [{ chainId: 42161, // Arbitrum calls: [/* your calls */] }], feeToken: { address: USDC_BASE, chainId: 8453 // Pay from Base } }); ``` ## By Wallet Type Most flexible—any token from any chain: ```typescript theme={null} const quote = await meeClient.getQuote({ instructions: [instruction], feeToken: { address: USDC, chainId: base.id } }); ``` Same flexibility, requires `delegate: true`: ```typescript theme={null} const quote = await meeClient.getQuote({ instructions: [instruction], delegate: true, authorization, // From wallet feeToken: { address: USDC, chainId: base.id } }); ``` Fee token must match trigger token, same chain only: ```typescript theme={null} const trigger = { chainId: base.id, tokenAddress: USDC_BASE, amount: parseUnits("10", 6) }; const quote = await meeClient.getFusionQuote({ trigger, instructions: [instruction], feeToken: { address: USDC_BASE, chainId: base.id } }); ``` ## Supported Tokens Any ERC-20 with liquidity can be used for gas: * **Stablecoins**: USDC, USDT, DAI * **Wrapped tokens**: WETH, WBTC * **LP tokens**: Uniswap, Curve positions * **Yield tokens**: aUSDC, stETH * **Governance tokens**: UNI, AAVE, etc. ## Example: Swap with Gas in USDC ```typescript theme={null} import { parseUnits } from "viem"; const swapInstruction = await account.buildComposable({ type: "default", data: { chainId: base.id, to: UNISWAP_ROUTER, abi: UniswapAbi, functionName: "exactInputSingle", args: [{ tokenIn: USDC, tokenOut: WETH, amountIn: parseUnits("100", 6), /* ... */ }] } }); const quote = await meeClient.getQuote({ instructions: [swapInstruction], feeToken: { address: USDC, chainId: base.id } }); // User sees fee in USDC, not ETH console.log("Fee:", quote.paymentInfo.tokenAmount, "USDC"); const { hash } = await meeClient.executeQuote({ quote }); ``` ## Next Steps Cover gas costs entirely for your users # AbstractJS SDK Source: https://docs.biconomy.io/overview/abstractjs/index Type-safe SDK for account abstraction and cross-chain orchestration AbstractJS is Biconomy's TypeScript SDK for building gas-abstracted, cross-chain applications. It provides a viem-inspired API for managing smart accounts and orchestrating transactions. ## When to Use AbstractJS * You need fine-grained control over transactions * Building with TypeScript/JavaScript * Want to compose custom DeFi flows * Need runtime parameter injection * Rapid prototyping * Non-TypeScript environments * Pre-built DeFi integrations * Simpler use cases ## Core Concepts ### Multichain Nexus Account A smart account that exists across multiple chains with the same address. Supports gas abstraction, batching, and cross-chain orchestration. ```typescript theme={null} const account = await toMultichainNexusAccount({ signer: wallet, chainConfigurations: [ { chain: optimism, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) }, { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) }, { chain: arbitrum, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) } ] }); ``` ### MEE Client Connects to the Modular Execution Environment—the network that executes your transactions gaslessly across chains. ```typescript theme={null} const meeClient = await createMeeClient({ account }); ``` ### Instructions Building blocks for transactions. Can be simple calls or composable operations with runtime values. ```typescript theme={null} const instruction = await account.buildComposable({ type: "default", data: { chainId, to, abi, functionName, args } }); ``` ## What You Can Build | Feature | Description | | ------------------------ | ----------------------------------------------------- | | **Gasless Transactions** | Users pay in any ERC-20 token or you sponsor entirely | | **Batch Operations** | Multiple calls in one transaction | | **Cross-Chain Flows** | Bridge + swap + deposit with one signature | | **Runtime Injection** | Use actual balances at execution time | | **Smart Sessions** | Delegate permissions to agents/bots | ## Quick Start ```bash theme={null} npm install @biconomy/abstractjs viem ``` ```typescript theme={null} import { createMeeClient, toMultichainNexusAccount, getMEEVersion, MEEVersion } from "@biconomy/abstractjs"; import { http } from "viem"; import { optimism, base } from "viem/chains"; // 1. Create account const account = await toMultichainNexusAccount({ signer: wallet, chainConfigurations: [ { chain: optimism, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) }, { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) } ] }); // 2. Create client const meeClient = await createMeeClient({ account }); // 3. Build and execute const quote = await meeClient.getQuote({ instructions: [/* your calls */], feeToken: { address: USDC, chainId: base.id } }); const { hash } = await meeClient.executeQuote({ quote }); ``` ## Tutorials Install and configure AbstractJS Let users pay fees in ERC-20s Cover gas costs for users Composable single-chain batching Multi-chain orchestration Dynamic values at execution time # Setup AbstractJS Source: https://docs.biconomy.io/overview/abstractjs/setup Install and configure the AbstractJS SDK ## Installation ```bash theme={null} npm install @biconomy/abstractjs viem ``` ## Basic Setup ```typescript theme={null} import { createMeeClient, toMultichainNexusAccount, getMEEVersion, MEEVersion } from "@biconomy/abstractjs"; import { http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { base, optimism } from "viem/chains"; // 1. Create a signer (user's wallet) const signer = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); // 2. Create multichain account const account = await toMultichainNexusAccount({ signer, chainConfigurations: [ { chain: optimism, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) }, { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) } ] }); // 3. Create MEE client const meeClient = await createMeeClient({ account }); ``` **Lazy Deployment**: Accounts aren't deployed until the first transaction on each chain. Only addresses are calculated upfront. ## Configuration Options ### Account Parameters | Parameter | Description | Required | | -------------------------------------- | --------------------------------------- | -------- | | `signer` | User wallet (EOA) that owns the account | Yes | | `chainConfigurations` | Array of chain configs | Yes | | `chainConfigurations[].chain` | Viem chain object | Yes | | `chainConfigurations[].transport` | RPC transport | Yes | | `chainConfigurations[].version` | MEE version | Yes | | `chainConfigurations[].accountAddress` | Override for EIP-7702 | No | ### MEE Client Parameters | Parameter | Description | Required | | --------- | ----------------------- | -------- | | `account` | The multichain account | Yes | | `apiKey` | API key for sponsorship | No | ## EIP-7702 Mode For embedded wallets (Privy, Dynamic, Turnkey), use EIP-7702 by setting `accountAddress` to the EOA: ```typescript theme={null} const account = await toMultichainNexusAccount({ signer, chainConfigurations: [ { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0), accountAddress: signer.address // Use EOA directly } ] }); ``` This enables smart account features on the EOA itself via delegation. ## Get Account Addresses ```typescript theme={null} // Get address on a specific chain const baseAddress = account.addressOn(base.id); // Get address with strict mode (throws if chain is not defined in chainConfigurations) const strictAddress = account.addressOn(base.id, true); ``` Both calls return the **same address**. The `true` flag enables **strict mode**, which throws an error if the chain is not defined in your `chainConfigurations`. This helps catch configuration mistakes early. ## Next Steps Configure gas payment options Cover gas for your users # Sponsor Gas for Users Source: https://docs.biconomy.io/overview/abstractjs/sponsor-gas Cover transaction fees for your users Remove gas friction entirely—sponsor transactions so users never see or pay fees. ## Enable Sponsorship Set `sponsorship: true` when getting a quote: ```typescript theme={null} const quote = await meeClient.getQuote({ sponsorship: true, instructions: [/* your calls */] }); const { hash } = await meeClient.executeQuote({ quote }); ``` Requires an API key with sponsorship enabled. Get one at [dashboard.biconomy.io](https://dashboard.biconomy.io) ## By Wallet Type ```typescript theme={null} const quote = await meeClient.getQuote({ sponsorship: true, instructions: [instruction] }); ``` Best for embedded wallets (Privy, Dynamic): ```typescript theme={null} const quote = await meeClient.getQuote({ sponsorship: true, delegate: true, authorization, instructions: [instruction] }); ``` Works with MetaMask, Rabby, etc. Token must support `permit()` for fully gasless: ```typescript theme={null} const quote = await meeClient.getFusionQuote({ sponsorship: true, trigger: { amount: 1n, // Minimal amount chainId: base.id, tokenAddress: USDC_BASE }, instructions: [instruction] }); ``` If trigger token doesn't support permit, user needs gas for one approval tx. ## Hosted vs Self-Hosted Simplest setup—uses Biconomy's managed gas tanks: ```typescript theme={null} const meeClient = await createMeeClient({ account, apiKey: "your_project_api_key" }); const quote = await meeClient.getQuote({ sponsorship: true, instructions: [...] }); ``` * No infrastructure to manage * Pay via dashboard (credit card or crypto) * Works across all supported chains Full control with your own backend: ```typescript theme={null} const quote = await meeClient.getQuote({ sponsorship: true, sponsorshipOptions: { url: "https://your-backend.com/sponsor", gasTank: { address: "0xYourGasTank", token: "0xTokenAddress", chainId: 84532 } }, instructions: [...] }); ``` * Custom approval logic * User-based policies * Your own gas tank ## Example: Gasless NFT Mint ```typescript theme={null} const mintInstruction = await account.buildComposable({ type: "default", data: { chainId: base.id, to: NFT_CONTRACT, abi: nftAbi, functionName: "mint", args: [userAddress, tokenId] } }); const quote = await meeClient.getQuote({ sponsorship: true, instructions: [mintInstruction] }); // User signs, you pay gas const { hash } = await meeClient.executeQuote({ quote }); ``` ## When to Use | Scenario | Recommendation | | --------------------- | ---------------------------- | | Onboarding new users | ✅ Sponsor | | Premium/paid features | ✅ Sponsor | | High-value actions | ✅ Sponsor | | Frequent transactions | Consider user-pays | | Cost-sensitive apps | Consider user-pays in tokens | ## Next Steps Execute multiple operations atomically # Working with Testnets Source: https://docs.biconomy.io/overview/abstractjs/working-with-testnets Configure AbstractJS to work with testnet environments ## Staging MEE Node By default, AbstractJS connects to the production MEE node which operates on mainnets. To work with testnets during development, you need to target the **staging MEE node** instead. Use the `getDefaultMEENetworkUrl()` and `getDefaultMEENetworkApiKey()` helper functions with `true` to connect to the staging environment: ```typescript theme={null} import { createMeeClient, toMultichainNexusAccount, getDefaultMEENetworkUrl, getDefaultMEENetworkApiKey, getMEEVersion, MEEVersion } from "@biconomy/abstractjs"; import { http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { sepolia, baseSepolia } from "viem/chains"; // 1. Create a signer const signer = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); // 2. Create multichain account with testnet chains const account = await toMultichainNexusAccount({ signer, chainConfigurations: [ { chain: sepolia, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) }, { chain: baseSepolia, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) } ] }); // 3. Create MEE client targeting staging node const meeClient = await createMeeClient({ account, url: getDefaultMEENetworkUrl(true), // isStaging = true apiKey: getDefaultMEENetworkApiKey(true) }); ``` The `isStaging` parameter (set to `true`) tells the SDK to: * Connect to the staging MEE orchestrator URL * Use the staging API key for authentication **Production vs Staging**: When you're ready to deploy to mainnet, simply remove the `url` and `apiKey` parameters (or pass `false`) to use the production defaults. ## Important Considerations **Limited Multichain Support on Testnets** Testnets have significant limitations for cross-chain operations: * **Bridges**: Most bridge protocols don't support testnets or have limited testnet deployments * **Solvers**: Liquidity solvers typically don't operate on testnets * **DEX Liquidity**: Testnet DEXes often lack the liquidity needed for realistic testing This means multichain orchestration features (cross-chain swaps, bridging, etc.) may not work reliably in testnet environments. ### What Works Well on Testnets * Single-chain transactions and batching * Gas estimation and simulation * Smart account deployment and configuration * Session key setup and validation * Basic transaction flows ### What May Not Work on Testnets * Cross-chain asset transfers via bridges * Multi-chain swap routes * Complex DeFi operations requiring solver infrastructure ## Recommended Testing Approach For comprehensive testing of multichain features, consider: 1. **Use testnets for single-chain logic** — Test your transaction building, batching, and account setup 2. **Use mainnets with small amounts for multichain** — When testing cross-chain flows, use production with minimal funds 3. **Leverage simulation** — Use the quote and simulation endpoints to validate transactions before execution ```typescript theme={null} // Example: Get a quote to simulate without executing const quote = await meeClient.getQuote({ instructions: [...], feeToken: { address: "0x...", chainId: baseSepolia.id } }); // Inspect the quote before executing console.log("Estimated gas:", quote.paymentInfo); console.log("User operations:", quote.userOps); ``` ## Next Steps Learn how to estimate transaction costs Configure gas payment options # Introduction to Biconomy Source: https://docs.biconomy.io/overview/introduction The full-stack infrastructure for building seamless Web3 experiences across chains Biconomy provides the infrastructure to build Web3 applications with Web2-level user experience. Execute complex multi-chain workflows with a single signature, abstract away gas entirely, and enable automation through delegated permissions. Transactions processed Smart accounts deployed ## Core Components ### Nexus Smart Account [Nexus](/new/learn-about-biconomy/nexus) is Biconomy's ERC-7579 compliant smart account—the most gas-efficient on the market with **25% lower costs** than alternatives. Users pay fees in USDC, USDT, or any ERC-20 token—or you sponsor gas entirely Chain multiple operations where each step uses outputs from the previous one Install modules for recovery, spending limits, session keys, and custom logic Unified addresses across all supported EVM networks ### Orchestration The Biconomy stack enables advanced onchain orchestration capabilities. These include: * Executing multiple composable transactions in an atomic batch (Composable Batching) * Executing multiple function calls across multiple chains with a single user signature (Cross-Chain Orchestration) * Scheduling transactions or creating repeat transactions. * Setting up "Conditional Execution" - triggering transactions based on some on-chain condition (e.g. price oracles) The orchestration capabilities are enabled by the Biconomy **Modular Execution Environment** (MEE). [MEE](/new/learn-about-biconomy/mee-vs-4337) is the execution layer that goes beyond ERC-4337. While ERC-4337 introduced account abstraction, MEE enables true cross-chain composability. | Capability | ERC-4337 | MEE | | ---------------------------- | -------- | --- | | Batch transactions | ✅ | ✅ | | Dynamic composable execution | ❌ | ✅ | | Cross-chain orchestration | ❌ | ✅ | | Pay gas from any chain | ❌ | ✅ | | EOA wallet support | ❌ | ✅ | With MEE, your users sign once to execute operations across multiple chains. Bridge timing, gas management, and execution ordering are handled automatically. ## Choose Your Integration Path Biconomy offers two ways to integrate, depending on your needs: **Best for:** Rapid development, DeFi integrations, teams without blockchain expertise * REST API works with any language or framework * Pre-built integrations for hundreds of DeFi protocols * Automatic bridge and swap route optimization * No smart contract deployment or encoding required ```bash theme={null} # Quote → Sign → Execute curl -X POST https://api.biconomy.io/v1/quote \ -d '{"composeFlows": [...]}' ``` **Best for:** Custom logic, fine-grained control, TypeScript applications * Viem-inspired API with full type safety * Direct smart account management * Custom calldata encoding and gas limits * Integrates with existing viem/ethers workflows ```typescript theme={null} import { createMeeClient } from "@biconomy/abstractjs" const client = await createMeeClient({ account }) await client.execute({ instructions: [...] }) ``` Not sure which to choose? See the [detailed comparison](/new/learn-about-biconomy/choosing-stack). ## Smart Sessions for Automation [Smart Sessions](/new/smart-sessions/introduction) enable delegated execution—perfect for AI agents, bots, and automated strategies. Users grant scoped permissions to other signers with precise constraints. Specify which contracts, functions, and token limits the agent can access User signs once to grant the session—fully gasless Agent operates within boundaries without requiring additional user signatures **Example:** An AI agent that rebalances funds across DeFi protocols: * Limited to AAVE, Morpho, and Yearn contracts * Can only move USDC * Maximum spend of 10,000 USDC * Works across Optimism, Base, and Ethereum Smart Sessions work with both Smart Accounts and EOAs (via EIP-7702 or Fusion Execution). ## Next Steps Build cross-chain swap experiences One-click DeFi workflows Delegated execution for bots and AI Sponsor transactions for your users # Step 3: Execute Source: https://docs.biconomy.io/overview/supertransaction-api/execute Submit your signed transaction for execution With your signed payload ready, submit it to the `/v1/execute` endpoint. Biconomy's MEE network handles all the blockchain complexity. ## Execute Request The request body is simple: the entire quote response with your signed payloads: ```typescript theme={null} const result = await fetch('https://api.biconomy.io/v1/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...quote, // Spread the entire quote payloadToSign: signedPayloads // Replace with signed version }) }).then(r => r.json()); ``` ## Complete Example ```typescript theme={null} // 1. Get quote const quote = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: 'smart-account', ownerAddress: account.address, composeFlows: [{ type: '/instructions/intent-simple', data: { srcChainId: 8453, dstChainId: 10, srcToken: USDC_BASE, dstToken: USDT_OPTIMISM, amount: '100000000', slippage: 0.01 } }] }) }).then(r => r.json()); // 2. Sign const payload = quote.payloadToSign[0]; const signature = await walletClient.signTypedData({ ...payload.signablePayload, account }); // 3. Execute const result = await fetch('https://api.biconomy.io/v1/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...quote, payloadToSign: [{ ...payload, signature }] }) }).then(r => r.json()); // 4. Check result if (result.success) { console.log('Supertransaction hash:', result.supertxHash); console.log('Track at:', `https://meescan.biconomy.io/details/${result.supertxHash}`); } else { console.error('Execution failed:', result.error); } ``` ## Response ```json theme={null} { "success": true, "supertxHash": "0x9a72f87a93c55d8f88e3f8c2a7b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2", "error": null } ``` | Field | Description | | ------------- | ------------------------------ | | `success` | Whether execution was accepted | | `supertxHash` | Unique identifier for tracking | | `error` | Error message if failed | `success: true` means the transaction was **accepted** for execution, not that it's complete. Use the supertxHash to track actual completion. ## Tracking Execution ### MEE Explorer View real-time status at: ``` https://meescan.biconomy.io/details/{supertxHash} ``` ### Polling Status ```typescript theme={null} async function waitForCompletion(supertxHash, apiKey, timeout = 120000) { const startTime = Date.now(); while (Date.now() - startTime < timeout) { const status = await fetch( `https://network.biconomy.io/v1/explorer/${supertxHash}`, { headers: { 'Authorization': `Bearer ${apiKey}` } } ).then(r => r.json()); if (status.status === 'SUCCESS') { return { success: true, data: status }; } if (status.status === 'FAILED') { return { success: false, error: status.error }; } // Still pending, wait and retry await new Promise(r => setTimeout(r, 3000)); } throw new Error('Timeout waiting for execution'); } // Usage const completion = await waitForCompletion(result.supertxHash, API_KEY); if (completion.success) { console.log('Transaction complete!'); } ``` ### Using AbstractJS SDK If you're using the SDK, there's a built-in method: ```typescript theme={null} import { createMeeClient } from '@biconomy/abstractjs'; const meeClient = createMeeClient({...}); const receipt = await meeClient.waitForSupertransactionReceipt({ hash: result.supertxHash, timeout: 120000 }); console.log('Status:', receipt.status); ``` ## Error Handling ```typescript theme={null} const result = await fetch('/v1/execute', { method: 'POST', body: JSON.stringify({ ...quote, payloadToSign: signedPayloads }) }).then(async r => { if (!r.ok) { const error = await r.json(); throw new Error(error.message || 'Execution failed'); } return r.json(); }); if (!result.success) { // Handle specific errors if (result.error?.includes('insufficient balance')) { console.log('User needs more tokens'); } else if (result.error?.includes('signature')) { console.log('Signature invalid or expired'); } else { console.log('Execution failed:', result.error); } } ``` ## Common Issues * Make sure you're signing the exact payload from the quote * Check you're using the correct signing method for the `quoteType` * Quotes expire—get a fresh one if too much time has passed * Verify user has enough tokens for the operation * Remember fees are deducted from balance too * For EOA mode, check `fundingTokens` matches actual balance * Quotes are valid for \~30 seconds * Get a new quote if user takes too long to sign * Consider showing a countdown in your UI * `success: true` only means the tx was accepted * Check MEE Explorer for actual execution status * On-chain conditions may have changed (slippage, liquidity) ## Full Flow Summary ```typescript theme={null} // Complete implementation async function executeSupertransaction(composeFlows, account, walletClient) { // 1. Quote const quote = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY }, body: JSON.stringify({ mode: 'smart-account', ownerAddress: account.address, composeFlows }) }).then(r => r.json()); // 2. Sign const payload = quote.payloadToSign[0]; const signature = await walletClient.signTypedData({ ...payload.signablePayload, account }); // 3. Execute const result = await fetch('https://api.biconomy.io/v1/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...quote, payloadToSign: [{ ...payload, signature }] }) }).then(r => r.json()); if (!result.success) { throw new Error(result.error); } // 4. Wait for completion const completion = await waitForCompletion(result.supertxHash, API_KEY); return { hash: result.supertxHash, status: completion.success ? 'complete' : 'failed' }; } ``` ## Next Steps Build gasless trading experiences Multi-chain swap tutorials Complete endpoint documentation Lending, staking, and more # Step 1: Get a Quote Source: https://docs.biconomy.io/overview/supertransaction-api/get-quote Request an execution quote for your transaction The `/v1/quote` endpoint analyzes your transaction, finds optimal routes, calculates fees, and returns a payload for signing. The first step is choosing the right **mode** for your users. ## Choose Your Mode **MetaMask, Rabby, Trust** For standard wallets. Requires `fundingTokens`. **Nexus, ERC-4337** Simplest setup. Native gas abstraction. **Privy, Dynamic, Turnkey** For embedded wallets with 7702 support. ## Quick Comparison | Feature | EOA | Smart Account | EIP-7702 | | ---------------- | --------------- | ------------------- | ---------------- | | Wallet support | All wallets | Smart accounts only | Embedded wallets | | `fundingTokens` | Required | Not needed | Not needed | | Signatures | 1 per token | Always 1 | Always 1 | | Gas payment | Same-chain only | Any chain | Any chain | | First-time setup | None | Account deploy | Authorization | ## Basic Structure All quote requests share this structure: ```typescript theme={null} const quote = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': 'YOUR_API_KEY' }, body: JSON.stringify({ mode: 'smart-account', // or 'eoa', 'eoa-7702' ownerAddress: '0x...', composeFlows: [{ // Your operations type: '/instructions/intent-simple', data: { /* swap params */ } }], feeToken: { /* optional */ } // Omit for sponsored (gasless) }) }).then(r => r.json()); ``` ## Understanding composeFlows The `composeFlows` array defines what you want to do: **Token swaps** (same-chain or cross-chain): ```typescript theme={null} { type: '/instructions/intent-simple', data: { srcChainId: 8453, dstChainId: 10, srcToken: '0x833589...', dstToken: '0x94b008...', amount: '100000000', slippage: 0.01 } } ``` **Custom contract calls**: ```typescript theme={null} { type: '/instructions/build', data: { chainId: 8453, to: '0xContractAddress', functionSignature: 'function deposit(uint256 amount)', args: ['1000000000'] } } ``` **Complex multi-token operations**: ```typescript theme={null} { type: '/instructions/intent', data: { slippage: 0.01, inputPositions: [{ chainToken: {...}, amount: '...' }], targetPositions: [{ chainToken: {...}, weight: 1.0 }] } } ``` ## Response Structure ```json theme={null} { "ownerAddress": "0x...", "fee": { "amount": "50000", "token": "0x...", "chainId": 8453 }, "quoteType": "simple", "payloadToSign": [{ /* sign this */ }], "returnedData": [{ "outputAmount": "99500000", "minOutputAmount": "98505000", "route": { "summary": "lifi => across" } }] } ``` | Field | Description | | --------------- | --------------------------------------------- | | `fee` | Gas cost in the specified token | | `quoteType` | How to sign: `simple`, `permit`, or `onchain` | | `payloadToSign` | Data user needs to sign | | `returnedData` | Expected outputs and routing info | ## Validate Before Signing Always check the quote before asking user to sign: ```typescript theme={null} // Check fee is reasonable const feeUsd = Number(quote.fee.amount) / 1e6; if (feeUsd > 1.00) { throw new Error(`Fee too high: $${feeUsd}`); } // Check output meets minimum const minOutput = quote.returnedData[0]?.minOutputAmount; console.log(`User will receive at least ${minOutput}`); ``` ## Next Steps For MetaMask, Rabby, Trust users For Nexus smart account users For Privy, Dynamic, Turnkey users After getting your quote # Supertransaction API Source: https://docs.biconomy.io/overview/supertransaction-api/index Build and execute multi-chain workflows with simple REST API calls The Supertransaction API lets you build complex blockchain operations—swaps, bridges, DeFi deposits—without writing smart contracts. Users sign once, and Biconomy handles everything. ## The Pattern Every Supertransaction follows the same four steps: Define what you want to do using `composeFlows`—swap tokens, bridge chains, call contracts POST to `/v1/quote` → receive execution costs, routing info, and a payload to sign User signs once (the format depends on wallet type) POST to `/v1/execute` → Biconomy handles all the blockchain complexity ```typescript theme={null} // The complete flow in 20 lines const quote = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', body: JSON.stringify({ mode: 'smart-account', ownerAddress: userAddress, composeFlows: [{ type: '/instructions/intent-simple', data: { srcChainId: 8453, dstChainId: 10, srcToken: USDC, dstToken: USDT, amount: '100000000', slippage: 0.01 } }] }) }).then(r => r.json()); const signature = await wallet.signMessage(quote.payloadToSign[0]); const result = await fetch('https://api.biconomy.io/v1/execute', { method: 'POST', body: JSON.stringify({ ...quote, payloadToSign: [{ ...quote.payloadToSign[0], signature }] }) }).then(r => r.json()); ``` ## What You Can Build Same-chain or cross-chain swaps with automatic routing Deposit, withdraw, stake across any protocol Chain operations: swap → bridge → deposit in one tx Call any smart contract function ## Instruction Types The `composeFlows` array supports four instruction types: | Type | Use Case | Example | | ----------------------------- | --------------------------------- | --------------------- | | `/instructions/intent-simple` | Token swaps (same or cross-chain) | Swap USDC → ETH | | `/instructions/intent` | Complex multi-token operations | Rebalance portfolio | | `/instructions/build` | Custom contract calls | Deposit into Aave | | `/instructions/build-raw` | Pre-encoded calldata | Advanced integrations | Start with `intent-simple` for swaps—it handles routing, bridging, and DEX selection automatically. ## Account Modes Choose the mode that matches your users' wallets: **For:** Biconomy Nexus accounts, ERC-4337 smart accounts ```typescript theme={null} { mode: 'smart-account', ownerAddress: '0x...' } ``` * Native gas abstraction * Single signature always * Funds stay in smart account **For:** MetaMask, Rabby, Trust Wallet, any EOA ```typescript theme={null} { mode: 'eoa', ownerAddress: '0x...', fundingTokens: [{ tokenAddress: '0x...', chainId: 8453, amount: '1000000' }] } ``` * Works with any wallet * Requires `fundingTokens` parameter * Uses Fusion execution under the hood **For:** Embedded wallets (Privy, Dynamic, Turnkey) with EIP-7702 ```typescript theme={null} { mode: 'eoa-7702', ownerAddress: '0x...' } ``` * Best of both worlds * Single signature like smart accounts * No fundingTokens needed ## Gas Options Omit `feeToken`—gas is paid from your sponsorship account ```typescript theme={null} { mode: 'smart-account', ownerAddress: '0x...' } // No feeToken = sponsored ``` Set `feeToken` to deduct from user's balance ```typescript theme={null} { feeToken: { address: USDC, chainId: 8453 } } ``` ## API Reference | Endpoint | Purpose | | ------------------------- | ---------------------------------------- | | `POST /v1/quote` | Get execution quote and signable payload | | `POST /v1/execute` | Submit signed quote for execution | | `GET /v1/explorer/{hash}` | Track execution status | Base URL: `https://api.biconomy.io` ## Tutorials Build your first quote request Handle different signature types Submit and track your transaction # Get Quote: EIP-7702 Mode Source: https://docs.biconomy.io/overview/supertransaction-api/quote-7702 Request quotes for embedded wallets with 7702 support Use `mode: 'eoa-7702'` for embedded wallet users (Privy, Dynamic, Turnkey) that support EIP-7702 delegation. This gives you smart account capabilities without deploying a separate contract. ## How EIP-7702 Mode Works EIP-7702 lets an EOA temporarily delegate to smart contract code. This means: * **No separate smart account deployment** * **Single signature** like smart accounts * **No `fundingTokens`** required * **Native gas abstraction** User signs an authorization to delegate their EOA (once per chain) API returns a simple payload to sign One signature for any operation Transaction includes delegation + operations atomically ## Basic Request ```typescript theme={null} const quote = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': 'YOUR_API_KEY' }, body: JSON.stringify({ mode: 'eoa-7702', ownerAddress: '0xYourUserAddress', composeFlows: [{ type: '/instructions/intent-simple', data: { srcChainId: 8453, dstChainId: 10, srcToken: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', dstToken: '0x94b008aa00579c1307b0ef2c499ad98a8ce58e58', amount: '100000000', slippage: 0.01 } }] }) }).then(r => r.json()); ``` ## Required Parameters | Parameter | Type | Description | | -------------- | ------------ | --------------------- | | `mode` | `'eoa-7702'` | Must be `'eoa-7702'` | | `ownerAddress` | string | User's EOA address | | `composeFlows` | array | Operations to execute | No `fundingTokens` needed—7702 mode works like smart accounts but uses the EOA directly. ## Handling First-Time Authorization If the user hasn't delegated their EOA yet, the API returns a **412 error** with authorization data: ```typescript theme={null} const response = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', body: JSON.stringify({ mode: 'eoa-7702', ownerAddress, composeFlows: [...] }) }); // First time? API returns 412 if (response.status === 412) { const error = await response.json(); console.log('Authorization needed:', error.authorizations); // Handle authorization flow... } ``` ### 412 Response Structure ```json theme={null} { "error": "MISSING_AUTHORIZATION", "message": "EIP-7702 authorization required", "authorizations": [ { "chainId": 8453, "address": "0x00000069E0Fb590E092Dd0E36FF93ac28ff11a3a", "nonce": 38 } ] } ``` ### Complete Authorization Flow ```typescript theme={null} async function getQuoteWith7702(ownerAddress, composeFlows, walletClient) { // 1. Try without authorization let response = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: 'eoa-7702', ownerAddress, composeFlows }) }); // 2. Handle 412 - need authorization if (response.status === 412) { const error = await response.json(); // Sign each authorization const signedAuths = await Promise.all( error.authorizations.map(async (auth) => { const signed = await walletClient.signAuthorization({ contractAddress: auth.address, chainId: auth.chainId, nonce: auth.nonce }); return { ...signed, yParity: signed.yParity, v: signed.v?.toString() }; }) ); // 3. Retry with authorizations response = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: 'eoa-7702', ownerAddress, authorizations: signedAuths, composeFlows }) }); } return response.json(); } ``` Authorization only happens once per chain. After the first transaction, subsequent quotes won't return 412. ## Quote Response After authorization (or if already authorized), you get a normal quote: ```json theme={null} { "quoteType": "simple", "payloadToSign": [{ "signablePayload": { "message": { "raw": "0xed96a92aa94b8b1927fc7c52ca3b3fcd0d706147dfbeda34a62dc232f6993029" } } }], "fee": { "amount": "50000", "token": "0x...", "chainId": 8453 } } ``` ## Signing the Payload EIP-7702 mode uses simple message signing: ```typescript theme={null} const payload = quote.payloadToSign[0]; const signature = await walletClient.signMessage({ message: payload.signablePayload.message, account }); ``` ## Complete Example ```typescript theme={null} import { createWalletClient, http, parseUnits } from 'viem'; import { base } from 'viem/chains'; const USDC_BASE = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'; const USDT_OP = '0x94b008aa00579c1307b0ef2c499ad98a8ce58e58'; async function swapWith7702(ownerAddress, walletClient) { // 1. Get quote (handles 412 automatically) const quote = await getQuoteWith7702( ownerAddress, [{ type: '/instructions/intent-simple', data: { srcChainId: 8453, dstChainId: 10, srcToken: USDC_BASE, dstToken: USDT_OP, amount: parseUnits('100', 6).toString(), slippage: 0.01 } }], walletClient ); // 2. Sign the execution payload const payload = quote.payloadToSign[0]; const signature = await walletClient.signMessage({ message: payload.signablePayload.message, account: walletClient.account }); // 3. Execute const result = await fetch('https://api.biconomy.io/v1/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...quote, payloadToSign: [{ ...payload, signature }] }) }).then(r => r.json()); return result.supertxHash; } ``` ## Wallet Support EIP-7702 requires wallet support for `signAuthorization`. Currently supported by: Full support Full support Full support Standard browser wallets (MetaMask, Rabby) don't support EIP-7702 yet. Use `mode: 'eoa'` for those users. ## When to Use EIP-7702 Mode * Embedded wallets (Privy, Dynamic, Turnkey) * Backend systems with private key access * Apps wanting smart account UX without deployment * Single-signature flows * MetaMask/Rabby users (use `eoa` mode) * Wallets without 7702 support * Users who need persistent smart accounts ## Comparison with Other Modes | Feature | EOA | EIP-7702 | Smart Account | | ---------------- | ----------- | ------------- | -------------- | | Wallet support | All | Embedded only | All (with SA) | | Signatures | 1 per token | 1 always | 1 always | | fundingTokens | Required | Not needed | Not needed | | First-time setup | None | Authorization | Account deploy | | Cross-chain gas | Limited | Yes | Yes | ## Next Steps Signing details for all modes Submit and track # Get Quote: EOA Mode Source: https://docs.biconomy.io/overview/supertransaction-api/quote-eoa Request quotes for standard wallet users Use `mode: 'eoa'` for users with standard Ethereum wallets like MetaMask, Rabby, Trust Wallet, or any WalletConnect-compatible wallet. ## How EOA Mode Works EOA mode uses **Fusion execution**—a system that lets regular wallets access Biconomy's gas abstraction without needing a smart account. You tell the API which tokens the user will spend Depending on token support, user either signs a gasless permit or sends an approval tx One signature per funding token Biconomy handles the rest ## Basic Request ```typescript theme={null} const quote = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': 'YOUR_API_KEY' }, body: JSON.stringify({ mode: 'eoa', ownerAddress: '0xYourUserAddress', fundingTokens: [{ tokenAddress: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', // USDC on Base chainId: 8453, amount: '100000000' // 100 USDC (6 decimals) }], feeToken: { address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', chainId: 8453 }, composeFlows: [{ type: '/instructions/intent-simple', data: { srcChainId: 8453, dstChainId: 10, srcToken: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', dstToken: '0x94b008aa00579c1307b0ef2c499ad98a8ce58e58', amount: '100000000', slippage: 0.01 } }] }) }).then(r => r.json()); ``` ## Required Parameters | Parameter | Type | Description | | --------------- | ------- | ----------------------------------------- | | `mode` | `'eoa'` | Must be `'eoa'` | | `ownerAddress` | string | User's wallet address | | `fundingTokens` | array | Tokens user will spend (required for EOA) | | `composeFlows` | array | Operations to execute | ## Understanding fundingTokens **This is the key difference from other modes.** You must specify exactly which tokens the user is spending: ```typescript theme={null} fundingTokens: [ { tokenAddress: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', // Token contract chainId: 8453, // Chain the token is on amount: '100000000' // Amount in wei } ] ``` The `amount` should cover both the operation AND any fees. If you're paying fees in the same token, include enough for both. ### Multiple Funding Tokens If your operation involves multiple input tokens, list them all: ```typescript theme={null} fundingTokens: [ { tokenAddress: USDC_BASE, chainId: 8453, amount: '50000000' // 50 USDC }, { tokenAddress: USDT_BASE, chainId: 8453, amount: '50000000' // 50 USDT } ] ``` Each funding token requires a separate signature. 2 tokens = 2 signatures. ## Fee Token Rules When using EOA mode, the `feeToken` must match one of your `fundingTokens`: ```typescript theme={null} // ✅ Correct - feeToken matches a fundingToken { fundingTokens: [{ tokenAddress: USDC, chainId: 8453, amount: '100000000' }], feeToken: { address: USDC, chainId: 8453 } } // ❌ Wrong - feeToken doesn't match any fundingToken { fundingTokens: [{ tokenAddress: USDC, chainId: 8453, amount: '100000000' }], feeToken: { address: USDT, chainId: 8453 } // Error! } ``` ## Quote Response The API returns different `quoteType` values based on token capabilities: ```json theme={null} { "quoteType": "permit", // or "onchain" "payloadToSign": [...], "fee": { "amount": "50000", "token": "0x...", "chainId": 8453 } } ``` | quoteType | Meaning | User Experience | | --------- | ---------------------------- | ----------------------------------------- | | `permit` | Token supports EIP-2612 | Gasless—user just signs a message | | `onchain` | Token doesn't support permit | User must send approval transaction first | ## Signing the Payload Most modern tokens (USDC, DAI) support permit—user signs typed data: ```typescript theme={null} const payload = quote.payloadToSign[0]; const signature = await walletClient.signTypedData({ ...payload.signablePayload, account }); ``` For older tokens, user must send an approval transaction: ```typescript theme={null} const payload = quote.payloadToSign[0]; // Send the approval const txHash = await walletClient.sendTransaction({ to: payload.to, data: payload.data, value: BigInt(payload.value || '0') }); // Wait for confirmation (important!) await publicClient.waitForTransactionReceipt({ hash: txHash, confirmations: 5 }); // Use txHash as the "signature" const signature = txHash; ``` ## Complete Example ```typescript theme={null} import { createWalletClient, createPublicClient, http, parseUnits } from 'viem'; import { base } from 'viem/chains'; const USDC_BASE = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'; const USDT_OP = '0x94b008aa00579c1307b0ef2c499ad98a8ce58e58'; async function swapWithEOA(userAddress, walletClient, publicClient) { // 1. Get quote const quote = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: 'eoa', ownerAddress: userAddress, fundingTokens: [{ tokenAddress: USDC_BASE, chainId: 8453, amount: parseUnits('100', 6).toString() }], feeToken: { address: USDC_BASE, chainId: 8453 }, composeFlows: [{ type: '/instructions/intent-simple', data: { srcChainId: 8453, dstChainId: 10, srcToken: USDC_BASE, dstToken: USDT_OP, amount: parseUnits('100', 6).toString(), slippage: 0.01 } }] }) }).then(r => r.json()); // 2. Sign based on quoteType const payload = quote.payloadToSign[0]; let signature; if (quote.quoteType === 'permit') { signature = await walletClient.signTypedData({ ...payload.signablePayload, account: walletClient.account }); } else { // onchain - send approval tx const txHash = await walletClient.sendTransaction({ to: payload.to, data: payload.data }); await publicClient.waitForTransactionReceipt({ hash: txHash }); signature = txHash; } // 3. Execute const result = await fetch('https://api.biconomy.io/v1/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...quote, payloadToSign: [{ ...payload, signature }] }) }).then(r => r.json()); return result.supertxHash; } ``` ## Important: Withdraw Funds In EOA mode, swapped tokens land in a temporary Nexus account. **Add a withdrawal instruction** to send them back to the user's EOA: ```typescript theme={null} composeFlows: [ // Swap { type: '/instructions/intent-simple', data: { /* swap params */ } }, // Withdraw to EOA { type: '/instructions/build', data: { chainId: 10, // Destination chain to: USDT_OP, // Token contract functionSignature: 'function transfer(address to, uint256 value)', args: [ userAddress, // Send to user's EOA { type: 'runtimeErc20Balance', tokenAddress: USDT_OP, constraints: { gte: '1' } } ] } } ] ``` Without the withdrawal instruction, tokens stay in the Nexus account and user won't see them in their wallet! ## Next Steps Detailed signing guide Submit and track your transaction # Get Quote: Smart Account Mode Source: https://docs.biconomy.io/overview/supertransaction-api/quote-smart-account Request quotes for Nexus smart account users Use `mode: 'smart-account'` for users with Biconomy Nexus accounts or other ERC-4337 smart accounts. This is the simplest mode with native gas abstraction. ## How Smart Account Mode Works Smart accounts have built-in gas abstraction. Users always sign just once, regardless of how complex the operation is. Define operations in `composeFlows`—no `fundingTokens` needed API returns a single payload to sign Always exactly one signature Biconomy handles everything across all chains ## Basic Request ```typescript theme={null} const quote = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': 'YOUR_API_KEY' }, body: JSON.stringify({ mode: 'smart-account', ownerAddress: '0xYourUserAddress', composeFlows: [{ type: '/instructions/intent-simple', data: { srcChainId: 8453, dstChainId: 10, srcToken: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', dstToken: '0x94b008aa00579c1307b0ef2c499ad98a8ce58e58', amount: '100000000', slippage: 0.01 } }] }) }).then(r => r.json()); ``` That's it! No `fundingTokens` required—the smart account already holds the tokens. ## Required Parameters | Parameter | Type | Description | | -------------- | ----------------- | ----------------------------------- | | `mode` | `'smart-account'` | Must be `'smart-account'` | | `ownerAddress` | string | The EOA that owns the smart account | | `composeFlows` | array | Operations to execute | ## Gas Options **Omit `feeToken`** to use sponsorship—you pay for user's gas: ```typescript theme={null} { mode: 'smart-account', ownerAddress: '0x...', // No feeToken = sponsored composeFlows: [...] } ``` Requires an API key with sponsorship enabled. Get one at [dashboard.biconomy.io](https://dashboard.biconomy.io) **Include `feeToken`** to deduct gas from user's balance: ```typescript theme={null} { mode: 'smart-account', ownerAddress: '0x...', feeToken: { address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', // USDC chainId: 8453 }, composeFlows: [...] } ``` Fee is automatically deducted from the user's USDC balance. ## Quote Response Smart account mode always returns `quoteType: 'simple'`: ```json theme={null} { "quoteType": "simple", "payloadToSign": [{ "signablePayload": { "domain": { "name": "Nexus" }, "types": { /* EIP-712 types */ }, "primaryType": "SuperTx", "message": { /* transaction data */ } } }], "fee": { "amount": "50000", "token": "0x...", "chainId": 8453 }, "returnedData": [{ "outputAmount": "99500000", "minOutputAmount": "98505000" }] } ``` ## Signing the Payload Smart account mode uses EIP-712 typed data signing: ```typescript theme={null} const payload = quote.payloadToSign[0]; const signature = await walletClient.signTypedData({ ...payload.signablePayload, account }); ``` EIP-712 shows users a human-readable breakdown of what they're signing—much better UX than raw hex messages. ## Complete Example ```typescript theme={null} import { createWalletClient, http, parseUnits, formatUnits } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; import { base } from 'viem/chains'; const USDC_BASE = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'; const USDT_OP = '0x94b008aa00579c1307b0ef2c499ad98a8ce58e58'; async function swapWithSmartAccount(ownerAddress, walletClient) { // 1. Get quote (no fundingTokens needed!) const quote = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: 'smart-account', ownerAddress, feeToken: { address: USDC_BASE, chainId: 8453 }, composeFlows: [{ type: '/instructions/intent-simple', data: { srcChainId: 8453, dstChainId: 10, srcToken: USDC_BASE, dstToken: USDT_OP, amount: parseUnits('100', 6).toString(), slippage: 0.01 } }] }) }).then(r => r.json()); // 2. Show user what they'll get const output = quote.returnedData[0]; console.log('Expected output:', formatUnits(output.outputAmount, 6), 'USDT'); console.log('Minimum output:', formatUnits(output.minOutputAmount, 6), 'USDT'); console.log('Fee:', formatUnits(quote.fee.amount, 6), 'USDC'); // 3. Sign (always EIP-712 typed data) const payload = quote.payloadToSign[0]; const signature = await walletClient.signTypedData({ ...payload.signablePayload, account: walletClient.account }); // 4. Execute const result = await fetch('https://api.biconomy.io/v1/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...quote, payloadToSign: [{ ...payload, signature }] }) }).then(r => r.json()); console.log('Track at:', `https://meescan.biconomy.io/details/${result.supertxHash}`); return result.supertxHash; } ``` ## Cross-Chain Gas Payment Smart accounts can pay for gas on any chain using tokens from any other chain: ```typescript theme={null} { mode: 'smart-account', ownerAddress: '0x...', feeToken: { address: USDC_BASE, // Pay from Base chainId: 8453 }, composeFlows: [{ type: '/instructions/build', data: { chainId: 42161, // Execute on Arbitrum to: SOME_CONTRACT, functionSignature: 'function doSomething()', args: [] } }] } ``` User has USDC on Base, but the transaction executes on Arbitrum. Biconomy handles the cross-chain gas settlement. ## No Withdrawal Needed Unlike EOA mode, smart account funds stay in the smart account after operations—which is usually what you want. Users can: * Continue using funds for more operations * Withdraw manually when needed * Access via any dApp that supports smart accounts ## When to Use Smart Account Mode * Apps with embedded smart accounts * Users who've deployed Nexus accounts * Maximum gas abstraction flexibility * Cross-chain gas payment * Users with only MetaMask/Rabby * One-time users (account deployment overhead) * Apps that don't manage accounts ## Next Steps Signing details Submit and track # Step 2: Sign the Payload Source: https://docs.biconomy.io/overview/supertransaction-api/sign-payload Sign the quote payload to authorize execution Once you've received the payloads from the API, you need to sign them to approve execution. ## Signature Formats by Mode and Version MEE v2.2.1 introduces EIP-712 typed data signing for `smart-account` mode, providing better security and user experience through structured, human-readable signing requests. | Mode | Account Version | Signature Format | Signing Method | | --------------- | --------------------- | ------------------ | ---------------------------- | | `smart-account` | v2.2.1 | EIP-712 Typed Data | `eth_signTypedData_v4` | | `smart-account` | v2.1.0 (upgrade flow) | Personal Message | `eth_sign` / `personal_sign` | | `eoa` (Fusion) | Any | Permit / Onchain | Mode-specific | | `eoa-7702` | v2.1.0 | Personal Message | `eth_sign` / `personal_sign` | The API automatically returns the correct `payloadToSign` format based on your account version and mode. Your signing code should detect and handle both formats. ## Quick Start: Copy-Paste Utilities **For TypeScript users:** Copy the utilities below directly into your project.\ **Not using TypeScript?** Implement your own version following the same logic shown in the code. **Using a single execution mode?** The utility below handles all signature types, but each mode only uses a subset: | Mode | Signature Types Used | | --------------- | ------------------------------------------- | | `smart-account` | `simple` only (EIP-712 or personal message) | | `eoa-7702` | `simple` only (personal message) | | `eoa` (Fusion) | `permit` or `onchain` only | If your application uses only one mode, you can create a lighter implementation that handles just the signature types you'll encounter. ```typescript theme={null} import { Address, createWalletClient, Hex, http, OneOf, publicActions, SignTypedDataParameters, } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { base } from "viem/chains"; /** * Types for different signable payloads. */ // Payload for EIP-712 typed data signature (smart-account v2.2.1, permits) type SignableTypedDataPayload = Omit; // Payload for personal message signing (legacy v2.1.0, eoa-7702) type SignablePersonalMessagePayload = { message: { raw: Hex; }; }; // Union type for "simple" quote type payloads (can be either format) type SignableSimplePayload = SignableTypedDataPayload | SignablePersonalMessagePayload; // Payload for on-chain transaction signing type SignableOnChainPayload = | { data: Hex; to: Address; chainId: number; value: string; } | { data: Hex; to: Address; chainId: number; value?: string | undefined; }; /** * Type guard to detect EIP-712 typed data payload. * v2.2.1 smart accounts use typed data, v2.1.0 uses personal message. */ function isTypedDataPayload( payload: SignableSimplePayload ): payload is SignableTypedDataPayload { return 'domain' in payload && 'primaryType' in payload; } /** * Example EOA account and wallet client setup. * Replace the private key with a real one for production use. */ const eoaAccount = privateKeyToAccount("0x"); // ⚠️ Replace with your private key const walletClient = createWalletClient({ account: eoaAccount, chain: base, transport: http(), // ⚠️ Use private paid RPC for production }).extend(publicActions); /** * Signs a "simple" quote payload. Handles both formats: * - EIP-712 typed data (v2.2.1 smart accounts) * - Personal message (v2.1.0 legacy accounts, eoa-7702) * * @param signablePayload - The payload to sign (auto-detected format). * @returns The signature as a Hex string. * * @example * // EIP-712 typed data (v2.2.1 smart account) * const signature = await signSimpleQuoteSignablePayload({ * domain: { name: "Nexus" }, * types: { MeeUserOp: [...], SuperTx: [{ name: "meeUserOps", type: "MeeUserOp[]" }] }, * primaryType: "SuperTx", * message: { meeUserOps: [{ userOpHash: "0x...", lowerBoundTimestamp: 0, upperBoundTimestamp: 1765465749 }] } * }); * * @example * // Personal message (v2.1.0 or eoa-7702) * const signature = await signSimpleQuoteSignablePayload({ * message: { raw: "0x68656c6c6f" } * }); */ const signSimpleQuoteSignablePayload = async ( signablePayload: SignableSimplePayload ): Promise => { if (isTypedDataPayload(signablePayload)) { // EIP-712 typed data for v2.2.1 smart accounts return await walletClient.signTypedData({ ...signablePayload, account: eoaAccount, }); } else { // Personal message for v2.1.0 accounts or eoa-7702 mode return await eoaAccount.signMessage(signablePayload); } }; /** * Signs a permit quote payload (EIP-712 typed data for ERC20 permits). * * @param signablePayload - The EIP-712 payload to sign. * @returns The signature as a Hex string. * * @example * const signature = await signPermitQuoteSignablePayload({ * domain: { ... }, * types: { ... }, * primaryType: "Permit", * message: { ... } * }); */ const signPermitQuoteSignablePayload = async ( signablePayload: SignableTypedDataPayload ): Promise => { const signature = await walletClient.signTypedData({ ...signablePayload, account: eoaAccount, }); return signature; }; /** * Signs an on-chain quote payload (transaction). * * @param signablePayload - The transaction payload to send. * @returns The transaction hash as a Hex string. * * @example * const txHash = await signOnChainQuoteSignablePayload({ * to: "0x...", * data: "0x...", * value: "0", * chainId: 1 * }); */ const signOnChainQuoteSignablePayload = async ( signablePayload: SignableOnChainPayload ): Promise => { const hash = await walletClient.sendTransaction({ to: signablePayload.to, data: signablePayload.data, value: BigInt(signablePayload.value || "0") }); // Wait for 5 confirmations before returning the hash await walletClient.waitForTransactionReceipt({ hash, confirmations: 5 }); return hash; }; /** * Union type for all supported payloads to sign. */ type PayloadToSign = OneOf< | { signablePayload: SignableSimplePayload; metadata?: any; } | { signablePayload: SignableTypedDataPayload; metadata: { nonce: string; name: string; version: string; domainSeparator: string; owner: string; spender: string; amount: string; }; } | SignableOnChainPayload >; /** * Signs a quote payload based on its type. * Automatically handles both EIP-712 and personal message formats for "simple" type. * * @param quoteType - The type of quote from the API response. * @param payloadToSign - The payload to sign. * @returns The signature or transaction hash as a Hex string. * * @example * // Simple - EIP-712 (v2.2.1 smart account) * const sig = await signQuoteSignablePayload("simple", { * signablePayload: { domain: { name: "Nexus" }, types: {...}, primaryType: "SuperTx", message: { meeUserOps: [...] } } * }); * * @example * // Simple - Personal message (v2.1.0 or eoa-7702) * const sig = await signQuoteSignablePayload("simple", { * signablePayload: { message: { raw: "0x68656c6c6f" } } * }); * * @example * // Permit (EIP-712 for ERC20) * const sig = await signQuoteSignablePayload("permit", { * signablePayload: { domain: {...}, types: {...}, primaryType: "Permit", message: {...} }, * metadata: { ... } * }); * * @example * // On-chain transaction * const txHash = await signQuoteSignablePayload("onchain", { * to: "0x...", * data: "0x...", * value: "0", * chainId: 1 * }); */ const signQuoteSignablePayload = async ( quoteType: string, payloadToSign: PayloadToSign ): Promise => { let signature: Hex = "0x"; switch (quoteType) { case "simple": // Auto-detects EIP-712 vs personal message format signature = await signSimpleQuoteSignablePayload(payloadToSign.signablePayload || payloadToSign); break; case "permit": signature = await signPermitQuoteSignablePayload(payloadToSign.signablePayload); break; case "onchain": signature = await signOnChainQuoteSignablePayload(payloadToSign); break; default: throw new Error("Unsupported quote type, can't sign the payload"); } return signature; }; ``` ## How It Works: Three Signature Types * The API returns an array of payloads to sign. * In most cases this array has just one item. * Each item in the payload is one of three signature types (simple, permit, onchain) depending on your execution mode and token support ### Simple (Smart Account & EIP-7702) **What**: Signs a message for smart account or EIP-7702 execution\ **When**: `smart-account` mode or `eoa-7702` mode\ **Result**: Off-chain signature **By default, v2.2.1 uses EIP-712 typed data** for `smart-account` mode. Personal message signing only occurs in two cases: `eoa-7702` mode, or when upgrading legacy v2.1.0 smart accounts. #### EIP-712 Typed Data (Default for Smart Accounts) For v2.2.1 smart accounts (the default), the payload contains EIP-712 structured data: ```javascript theme={null} // Example payload from API (v2.2.1 smart account) { domain: { name: "Nexus" }, types: { MeeUserOp: [ { name: "userOpHash", type: "bytes32" }, { name: "lowerBoundTimestamp", type: "uint256" }, { name: "upperBoundTimestamp", type: "uint256" } ], SuperTx: [ { name: "meeUserOps", type: "MeeUserOp[]" } ] }, primaryType: "SuperTx", message: { meeUserOps: [ { userOpHash: "0xa6c23856046666c02f7a85f0b2e7dbc78beac25db5c1955ddc9eb78e0237fc9f", lowerBoundTimestamp: 0, upperBoundTimestamp: 1765465749 } ] } } ``` **How to sign EIP-712:** ```typescript theme={null} const signature = await walletClient.signTypedData({ ...payload, account }); ``` #### Personal Message (EIP-7702 & Legacy Upgrade Only) For `eoa-7702` mode and v2.1.0 accounts during upgrade, the payload contains a raw message: ```javascript theme={null} // Example payload from API (v2.1.0 or eoa-7702) { message: { raw: "0xed96a92aa94b8b1927fc7c52ca3b3fcd0d706147dfbeda34a62dc232f6993029" } } ``` **How to sign personal message:** ```typescript theme={null} const signature = await walletClient.signMessage({ ...payload, account }); ``` #### Detecting the Format Use a type guard to automatically detect and sign the correct format: ```typescript theme={null} // Type guard to detect EIP-712 typed data function isTypedDataSignature(payload: unknown): payload is TypedDataPayload { return typeof payload === 'object' && payload !== null && 'domain' in payload && 'primaryType' in payload; } // Sign based on detected format const signature = isTypedDataSignature(payload) ? await walletClient.signTypedData({ ...payload, account }) // EIP-712 for v2.2.1 : await walletClient.signMessage({ ...payload, account }); // Personal message for v2.1.0/7702 ``` ### Permit (EIP-712 Typed Data) **What**: Signs structured EIP-712 data for ERC20 Permit\ **When**: EOAs with tokens that support ERC20Permit\ **Result**: Off-chain signature\ **Special**: API embeds supertx hash in `deadline` field ```javascript theme={null} // Example payload from API { signablePayload: { domain: { name: "USD Coin", version: "2", chainId: 1, verifyingContract: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" }, types: { Permit: [/* ... */] }, primaryType: "Permit", message: { owner: "0x...", spender: "0x...", value: "1000000000", nonce: 0, deadline: "0xabcdef..." // ← Supertx hash here! } }, metadata: { nonce: "0", name: "USD Coin", version: "2", domainSeparator: "0x06c37168a7db5138defc7866392bb87a741f9b3d104deb5094588ce041cae335", owner: "0x742d35C9a91B1D5b5D24Dc30e8F0dF8E84b5d1c4", spender: "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", amount: "1000000000000000000000" } } ``` **How to sign:** ```typescript theme={null} const signature = await walletClient.signTypedData({ ...payload.signablePayload, account }); ``` ### Onchain (Transaction) **What**: Sends an actual blockchain transaction\ **When**: EOAs with tokens that don't support ERC20Permit\ **Result**: Transaction hash\ **Special**: API appends supertx hash to calldata ```javascript theme={null} // Example payload from API { to: "0x1111111254EEB25477B68fb85Ed929f73A960582", data: "0xa9059cbb...abcdef1234567890", // ← Supertx hash appended! value: "0", chainId: 1 } ``` **How to handle:** ```typescript theme={null} // Send approval transaction on-chain const txHash = await walletClient.sendTransaction({ to: payload.to, data: payload.data, value: payload.value || 0n }); // Wait for confirmation await publicClient.waitForTransactionReceipt({ hash: txHash }); // Use txHash as signature const signedPayload = [{ ...payload, signature: txHash }]; ``` ## The Magic: Supertx Hash Embedding For EOA mode, the API cleverly embeds the supertransaction hash into your signatures. This creates a cryptographic link between funding and execution, enabling single-signature cross-chain operations. **For Permit**: The supertx hash replaces the `deadline` field ```javascript theme={null} // Before: deadline = 1700000000 // After: deadline = "0xabcdef1234567890..." (supertx hash) ``` **For Onchain**: The supertx hash is appended to calldata ```javascript theme={null} // Before: 0xa9059cbb000000000000000000000000... // After: 0xa9059cbb000000000000000000000000...abcdef1234567890 ``` ## Usage by Execution Mode **Signature Requirements:** * One signature per funding token * Type depends on token support (permit or onchain) ```typescript theme={null} // 1. Get quote const quoteResponse = await fetch('/v1/quote', { method: 'POST', headers: { 'X-API-Key': 'YOUR_API_KEY' }, body: JSON.stringify({ mode: "eoa", ownerAddress: "0x...", fundingTokens: [{ tokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", chainId: 1, amount: "1000000000" }], instructions: [...] }) }); const { payloadToSign, quote, fee, quoteType, ownerAddress } = await quoteResponse.json(); // 2. Sign each payload (one per funding token) for (let i = 0; i < payloadToSign.length; i++) { const payload = payloadToSign[i]; // Use the utility function - it handles all types const signature = await signQuoteSignablePayload(quoteType, payload); // Add signature back to payload payloadToSign[i].signature = signature; } // 3. Execute with signed payloads await fetch('/v1/execute', { method: 'POST', body: JSON.stringify({ ownerAddress, fee, quoteType, quote, payloadToSign }) }); ``` **Signature Requirements:** * Authorization signature (if not already delegated) * One simple signature for execution ```typescript theme={null} // 1. Get quote (may need authorization first) let quoteResponse = await fetch('/v1/quote', { method: 'POST', headers: { 'X-API-Key': 'YOUR_API_KEY' }, body: JSON.stringify({ mode: "eoa-7702", ownerAddress: "0x...", instructions: [...] }) }); // 2. Handle authorization if needed if (quoteResponse.status === 412) { const error = await quoteResponse.json(); const auth = error.authorizations[0]; // Sign authorization (wallet-specific method) const authSig = await wallet.signAuthorization({ contractAddress: auth.address, chainId: auth.chainId, nonce: auth.nonce }); // Retry with authorization quoteResponse = await fetch('/v1/quote', { method: 'POST', body: JSON.stringify({ mode: "eoa-7702", ownerAddress: "0x...", authorizations: [authSig], instructions: [...] }) }); } const { payloadToSign, quote, fee, quoteType, ownerAddress } = await quoteResponse.json(); // 3. Sign execution (always simple type) const signature = await signQuoteSignablePayload(quoteType, payloadToSign[0]); payloadToSign[0].signature = signature; // 4. Execute await fetch('/v1/execute', { method: 'POST', body: JSON.stringify({ ownerAddress, fee, quoteType, quote, payloadToSign }) }); ``` **Signature Requirements:** * Always exactly one signature * **v2.2.1 accounts**: EIP-712 typed data (structured, human-readable) * **v2.1.0 accounts** (upgrade flow): Personal message v2.2.1 uses EIP-712 typed data which provides better UX - wallets display structured transaction details instead of opaque hex strings, helping users understand what they're signing. ```typescript theme={null} // Type guard to detect signature format function isTypedDataSignature(payload: unknown): payload is TypedDataPayload { return typeof payload === 'object' && payload !== null && 'domain' in payload && 'primaryType' in payload; } // 1. Get quote const quoteResponse = await fetch('/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: "smart-account", ownerAddress: "0x...", composeFlows: [...] }) }); const { payloadToSign, quote, fee, quoteType, ownerAddress } = await quoteResponse.json(); // 2. Sign - auto-detect format based on account version const payload = payloadToSign[0]; const signature = isTypedDataSignature(payload.signablePayload || payload) ? await walletClient.signTypedData({ ...(payload.signablePayload || payload), account }) : await walletClient.signMessage({ ...(payload.signablePayload || payload), account }); payloadToSign[0].signature = signature; // 3. Execute await fetch('/v1/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ownerAddress, fee, quoteType, quote, payloadToSign }) }); ``` ## Summary 1. **Copy the utilities** - They handle all signature types automatically 2. **Get a quote** - The API returns the appropriate payload type 3. **Sign with utilities** - `signQuoteSignablePayload()` routes to the right method 4. **Execute** - Send signed payloads back to complete the supertransaction The utilities abstract away the complexity while the API's clever hash embedding enables powerful single-signature cross-chain operations. ## Why EIP-712 Typed Data? MEE v2.2.1 uses EIP-712 typed data signing for `smart-account` mode for several benefits: Wallets display structured data instead of opaque hex strings. Users can see transaction details like amounts, recipients, and deadlines before signing. Users can verify transaction details in their wallet UI before signing, making it harder for malicious sites to trick users into signing harmful transactions. Signatures are cryptographically bound to specific contracts and chains, preventing signature replay attacks across different applications or networks. Structured types prevent signature malleability attacks and ensure data integrity throughout the signing process. ## Migration Notes When migrating from v2.1.0 to v2.2.1 or supporting both versions: 1. **Update signing logic** - Use the type guard to handle both payload formats 2. **Test with both formats** - Ensure backward compatibility with legacy accounts 3. **No API changes needed** - The API automatically returns the correct format based on account version 4. **Upgrade transactions use personal message** - When upgrading a v2.1.0 account, the upgrade quote uses personal message signing (not EIP-712) # Scheduled Execution Source: https://docs.biconomy.io/scheduled-execution/index Control when your supertransactions execute with time-bounded windows MEE supports **time-bounded execution windows**, enabling powerful UX patterns like scheduled actions, delayed bridging, market timing, and more. ## What is Scheduled Execution? With scheduled execution, you can define **when** a supertransaction is eligible to run using `lowerBoundTimestamp` and `upperBoundTimestamp`. Instead of executing immediately, you can defer execution, enforce expiration, or create precise windows of execution. ## Use Cases Trigger transactions after a set delay—e.g. execute a trade 5 minutes from now Keep retrying for a window, then discard if it still fails Execute a series of transactions at fixed intervals Set different time bounds per instruction across chains ## How It Works ``` Quote Created ↓ lowerBoundTimestamp reached → Execution becomes eligible ↓ MEE simulates and attempts execution ↓ upperBoundTimestamp reached → Quote expires if still failing ``` Timestamps are expected in **seconds**, not milliseconds. If both bounds are omitted, quotes execute immediately with a default 2-minute fallback. ## Next Steps Learn how to use global and instruction-level time bounds # Set Execution Time Bounds Source: https://docs.biconomy.io/scheduled-execution/set-execution-time-bounds Defer execution, enforce expiration, and create bounded execution windows for supertransactions MEE supports **time-bounded execution windows** using `lowerBoundTimestamp` and `upperBoundTimestamp`. These parameters let you defer execution or enforce expiration on quotes, enabling powerful UX like scheduled actions, delayed bridging, market timing, and more. ## Why Use Time Bounds? * **Scheduled execution** (e.g. trigger after a certain amount of time) * **Custom expiry** (e.g. keep trying to execute for five minutes, if simulation is error still - then discard) * **Sequential execution** (e.g. execute a transaction every five minutes) ## Setup ```ts theme={null} theme={null} import { createMeeClient, toMultichainNexusAccount, getMEEVersion, MEEVersion } from "@biconomy/abstractjs"; import { http, type Hex } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { base, optimism } from "viem/chains"; const eoa = privateKeyToAccount(Bun.env.PRIVATE_KEY as Hex); const orchestrator = await toMultichainNexusAccount({ chainConfigurations: [ { chain: optimism, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) }, { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) } ], signer: eoa }); const date = new Date(); const meeClient = await createMeeClient({ account: orchestrator }); const minutesToSeconds = (input: number) => input * 60; ``` ## Instant Execution (Default Expiry) Executes immediately and simulates until it passes or hits a default 3-minute timeout. ```ts theme={null} theme={null} const instantTxDefaultExpiryTime = await meeClient.getQuote({ instructions: [], feeToken: { address: '0xYourFeeTokenAddress', chainId: base.id }, }); ``` ## Instant Execution with Custom Expiry Runs immediately but expires 1 minute after quote creation if it still errors. ```ts theme={null} theme={null} const instantTxCustomExpiry = await meeClient.getQuote({ instructions: [], feeToken: { address: '0xYourFeeTokenAddress', chainId: base.id }, upperBoundTimestamp: Date.now() + minutesToSeconds(1) }); ``` ## Scheduled Execution Quote won't be eligible to run until 5 minutes from now. ```ts theme={null} theme={null} const scheduledTxQuote = await meeClient.getQuote({ instructions: [], feeToken: { address: '0xYourFeeTokenAddress', chainId: base.id }, lowerBoundTimestamp: Date.now() + minutesToSeconds(5) }); ``` ## Bounded Window Execution Will only attempt execution between 2 and 5 minutes from quote creation. ```ts theme={null} theme={null} const boundedTx = await meeClient.getQuote({ instructions: [], feeToken: { address: '0xYourFeeTokenAddress', chainId: base.id }, lowerBoundTimestamp: Date.now() + minutesToSeconds(2), upperBoundTimestamp: Date.now() + minutesToSeconds(5) }); ``` ## Instruction-Level Time Bounds In addition to setting global `lowerBoundTimestamp` and `upperBoundTimestamp` for the entire quote, MEE supports **instruction-level time bounds**. With this, you can define custom timing windows for each instruction, giving you granular control across multiple user operations and chains. ### Why Use Instruction-Level Time Bounds? * **Custom time bounds for each user operation:** Specify unique execution windows for every instruction. * **Different windows per chain:** If your supertransaction spans multiple chains, set different timing strategies for each. * **Flexible multi-instruction orchestration:** Chain together actions where some execute immediately, some wait, and some have stricter expiry. ### How Does It Work? Each instruction in your `instructions: []` array can include its own `lowerBoundTimestamp` and/or `upperBoundTimestamp`. These timestamps **override** the global bounds for that instruction's userOp. When batching instructions on the **same chain** and some of them have custom instruction-level time bounds, the **largest execution window** is used for that chain's batched user operation. This ensures that all instructions in the batch have the opportunity to execute. ### Example Suppose you want an ETH transfer on Optimism to run any time in the next 10 minutes, but a ETH transfer on Base should run only in the next 2 minutes. ```ts theme={null} theme={null} const quote = await meeClient.getQuote({ instructions: [ await mcNexus.build({ type: "nativeTokenTransfer", data: { to: eoaAccount.address, chainId: optimism.id, value: 1n, lowerBoundTimestamp: Math.floor(Date.now() / 1000), upperBoundTimestamp: Math.floor(Date.now() / 1000) + minutesToSeconds(10) } }), await mcNexus.build({ type: "nativeTokenTransfer", data: { to: eoaAccount.address, chainId: base.id, value: 1n, lowerBoundTimestamp: Math.floor(Date.now() / 1000), upperBoundTimestamp: Math.floor(Date.now() / 1000) + minutesToSeconds(2) } }) ], feeToken: { address: '0xYourFeeTokenAddress', chainId: base.id } }); ``` ### Benefits * Fine-tuned orchestration for complex flows. * Supports scenarios where actions on different chains require different timing guarantees. * Enables advanced scheduling, retry, and expiry strategies on a per-instruction basis. *** ## Developer Tips * Timestamps are expected in **seconds**, not milliseconds. * Quotes simulate continuously within the window. If execution fails for the full duration, they will revert. * If both `lowerBoundTimestamp` and `upperBoundTimestamp` are omitted, quotes execute immediately with a 2-minute fallback. * Use in combination with cleanup for robust, failure-tolerant flows. Time-bounded scheduling unlocks a whole new level of control over supertransactions—giving developers precision orchestration primitives without needing custom schedulers or off-chain cron jobs. # Account Functions Source: https://docs.biconomy.io/sdk-reference/account Create and manage multichain smart accounts ## toMultichainNexusAccount Creates a multichain Nexus smart account that can operate across multiple chains with the same address. ```typescript theme={null} const account = await toMultichainNexusAccount(options); ``` ### Parameters | Parameter | Type | Required | Description | | --------------------- | ------------------------- | -------- | ------------------------------------ | | `signer` | `Account \| WalletClient` | Yes | The EOA signer that owns the account | | `chainConfigurations` | `ChainConfiguration[]` | Yes | Array of chain configs | ### ChainConfiguration | Property | Type | Required | Description | | ---------------- | ------------------ | -------- | -------------------------- | | `chain` | `Chain` | Yes | Viem chain object | | `transport` | `Transport` | Yes | RPC transport | | `version` | `MeeVersionConfig` | Yes | MEE version config | | `accountAddress` | `Address` | No | Override for EIP-7702 mode | ### Returns `MultichainSmartAccount` ### Example ```typescript theme={null} import { toMultichainNexusAccount, getMEEVersion, MEEVersion } from "@biconomy/abstractjs"; import { http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { base, optimism } from "viem/chains"; const signer = privateKeyToAccount("0x..."); const account = await toMultichainNexusAccount({ signer, chainConfigurations: [ { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) }, { chain: optimism, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) } ] }); ``` ### EIP-7702 Mode For embedded wallets, set `accountAddress` to the EOA address: ```typescript theme={null} const account = await toMultichainNexusAccount({ signer, chainConfigurations: [ { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0), accountAddress: signer.address // Use EOA directly } ] }); ``` *** ## MultichainSmartAccount Methods ### addressOn Get the account address on a specific chain. ```typescript theme={null} const address = account.addressOn(chainId, strict?); ``` | Parameter | Type | Required | Description | | --------- | --------- | -------- | ------------------------------------------------------------------ | | `chainId` | `number` | Yes | Chain ID | | `strict` | `boolean` | No | Enable strict mode—throws if chain is not in `chainConfigurations` | Both calls return the **same address**. The `strict` flag is a safety check that throws an error if the requested chain was not defined in your `chainConfigurations`, helping catch configuration mistakes early. ### Example ```typescript theme={null} const baseAddress = account.addressOn(8453); const strictAddress = account.addressOn(8453, true); // Both return the same address // `true` throws if chain 8453 is not configured ``` *** ### buildComposable Build a composable instruction with support for runtime injection. ```typescript theme={null} const instruction = await account.buildComposable(options); ``` See [Instructions](/sdk-reference/instructions) for full documentation. *** ### build Build a simple instruction without composability features. ```typescript theme={null} const instruction = await account.build(options); ``` See [Instructions](/sdk-reference/instructions) for full documentation. *** ### buildActionPolicy Build a policy for Smart Session actions (sudo, universal, timeframe, usageLimit, spendingLimits). ```typescript theme={null} const policy = account.buildActionPolicy(params); ``` See [Smart Sessions – Policies](/sdk-reference/sessions#policies) for full documentation and examples. *** ### buildSessionAction Build session actions (transfer, transferFrom, approve, custom, or batch) to use with Smart Sessions. ```typescript theme={null} const sessionAction = account.buildSessionAction(params); ``` See [Smart Sessions – Actions](/sdk-reference/sessions#actions) for full documentation and examples. *** ### deployments Access individual chain deployments. ```typescript theme={null} for (const deployment of account.deployments) { const isDeployed = await deployment.isDeployed(); console.log(`Chain ${deployment.chain.id}: ${isDeployed}`); } ``` *** ## toNexusAccount Creates a single-chain Nexus account. Use for bundler-based flows without MEE. ```typescript theme={null} const account = await toNexusAccount(options); ``` ### Parameters | Parameter | Type | Required | Description | | -------------------- | -------------------- | -------- | ------------------- | | `signer` | `Account` | Yes | The EOA signer | | `chainConfiguration` | `ChainConfiguration` | Yes | Single chain config | ### Example ```typescript theme={null} import { toNexusAccount, getMEEVersion, MEEVersion } from "@biconomy/abstractjs"; const account = await toNexusAccount({ signer, chainConfiguration: { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) } }); ``` # MEE Client Functions Source: https://docs.biconomy.io/sdk-reference/client Connect to MEE and execute transactions ## createMeeClient Creates a client connected to the Modular Execution Environment for gasless, cross-chain execution. ```typescript theme={null} const meeClient = await createMeeClient(options); ``` ### Parameters | Parameter | Type | Required | Description | | ------------- | ------------------------ | -------- | ------------------------------------------------------------------------------------------ | | `account` | `MultichainSmartAccount` | Yes | The multichain account | | `apiKey` | `string` | No | API key for sponsorship | | `url` | `string` | No | Custom MEE node URL | | `isDebugMode` | `boolean` | No | When `true`, enables extra logging for HTTP calls, receipt polling, permit checks and etc. | ### Returns `MeeClient` ### Example ```typescript theme={null} import { createMeeClient, toMultichainNexusAccount } from "@biconomy/abstractjs"; const account = await toMultichainNexusAccount({ /* ... */ }); const meeClient = await createMeeClient({ account }); // With API key for sponsorship const meeClient = await createMeeClient({ account, apiKey: "your-api-key" }); ``` *** ## MeeClient Methods ### getQuote Get a quote for executing instructions. Returns cost estimates and payloads to sign. ```typescript theme={null} const quote = await meeClient.getQuote(options); ``` ### Parameters | Parameter | Type | Required | Description | | --------------------- | --------------- | -------- | ----------------------- | | `instructions` | `Instruction[]` | Yes | Array of instructions | | `feeToken` | `FeeTokenInfo` | Yes\* | Token to pay gas with | | `sponsorship` | `boolean` | No | Enable gas sponsorship | | `delegate` | `boolean` | No | Enable EIP-7702 mode | | `authorization` | `Authorization` | No | EIP-7702 authorization | | `lowerBoundTimestamp` | `number` | No | Earliest execution time | | `upperBoundTimestamp` | `number` | No | Latest execution time | | `cleanUps` | `CleanUp[]` | No | Cleanup instructions | \*Not required if `sponsorship: true` ### Returns ```typescript theme={null} type Quote = { hash: Hex; paymentInfo: { tokenAmount: string; tokenAddress: Address; chainId: number; }; userOps: UserOp[]; // ... signing payloads }; ``` ### Example ```typescript theme={null} const quote = await meeClient.getQuote({ instructions: [instruction1, instruction2], feeToken: { address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", chainId: 8453 } }); console.log("Fee:", quote.paymentInfo.tokenAmount); ``` *** ### getFusionQuote Get a quote for Fusion mode (external wallets like MetaMask). ```typescript theme={null} const fusionQuote = await meeClient.getFusionQuote(options); ``` ### Parameters | Parameter | Type | Required | Description | | --------------------- | ------------------- | -------- | ---------------------- | | `trigger` | `Trigger` | Yes | Token pull trigger | | `instructions` | `Instruction[]` | Yes | Array of instructions | | `feeToken` | `FeeTokenInfo` | Yes\* | Token to pay gas with | | `sponsorship` | `boolean` | No | Enable gas sponsorship | | `cleanUps` | `CleanUp[]` | No | Cleanup instructions | | `upperBoundTimestamp` | `number` | No | Latest execution time | | `simulation` | `SimulationOptions` | No | Simulation config | ### Example ```typescript theme={null} const fusionQuote = await meeClient.getFusionQuote({ trigger: { chainId: 8453, tokenAddress: USDC, amount: parseUnits("100", 6) }, instructions: [swap, deposit], feeToken: { address: USDC, chainId: 8453 } }); ``` *** ### executeQuote Execute a quote by signing and submitting it. ```typescript theme={null} const { hash } = await meeClient.executeQuote({ quote }); ``` ### Parameters | Parameter | Type | Required | Description | | --------- | ------- | -------- | --------------------- | | `quote` | `Quote` | Yes | Quote from `getQuote` | ### Returns ```typescript theme={null} { hash: Hex } ``` *** ### executeFusionQuote Execute a Fusion quote. ```typescript theme={null} const { hash } = await meeClient.executeFusionQuote({ fusionQuote }); ``` ### Parameters | Parameter | Type | Required | Description | | ------------- | ------------- | -------- | --------------------------- | | `fusionQuote` | `FusionQuote` | Yes | Quote from `getFusionQuote` | *** ## Session quote flow Use these methods for the Smart Sessions flow: prepare or use a permission by getting a session quote, then sign and execute. ### getSessionQuote Get a quote for preparing (deploy, fund, install module, enable session) or using a session. ```typescript theme={null} const quote = await meeClient.getSessionQuote(params); ``` ### Parameters | Parameter | Type | Required | Description | | --------------------- | -------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------- | | `mode` | `"PREPARE" \| "USE"` | Yes | `PREPARE` to enable a session; `USE` to run instructions with an existing session | | `enableSession` | `EnableSession` | No | Required when `mode === "PREPARE"`. Redeemer, actions (`SessionAction[]`), optional `batchActions`, `maxPaymentAmount` | | `sessionDetails` | `SessionDetail[]` | No | Required when `mode === "USE"`. From a previous prepare/enable flow | | `instructions` | `Instruction[]` | No | Instructions to execute (use case or prepare with extra setup) | | `trigger` | `Trigger` | No | Funding trigger (prepare with Fusion) | | `feeToken` | `FeeTokenInfo` | Yes\* | Token to pay gas with | | `sponsorship` | `boolean` | No | Enable gas sponsorship | | `delegate` | `boolean` | No | Enable EIP-7702 mode | | `authorization` | `Authorization` | No | EIP-7702 authorization | | `simulation` | `SimulationOptions` | No | Simulation config | | `lowerBoundTimestamp` | `number` | No | Earliest execution time | | `upperBoundTimestamp` | `number` | No | Latest execution time | | `cleanUps` | `CleanUp[]` | No | Cleanup instructions | \*Not required if `sponsorship: true` ### Returns `{ quoteType, quote, sessionDetails? }` or `undefined` when there is nothing to do (e.g. no enable, no instructions). See [Smart Sessions](/sdk-reference/sessions) for full parameter details and examples. *** ### signSessionQuote Sign the payload returned from `getSessionQuote`. Dispatches by `quoteType` to the appropriate signer (e.g. `signQuote` for simple, `signFusionQuote` for fusion). ```typescript theme={null} const signed = await meeClient.signSessionQuote(params); ``` ### Parameters | Parameter | Type | Required | Description | | --------- | ------------------------- | -------- | ------------------------------------------ | | `params` | `GetSessionQuoteResponse` | Yes | The object returned from `getSessionQuote` | Typically you use `executeSessionQuote`, which signs and executes in one step. *** ### executeSessionQuote Sign and execute a session quote. Handles both simple and fusion quote types and trigger execution. ```typescript theme={null} const { hash } = await meeClient.executeSessionQuote(params); ``` ### Parameters | Parameter | Type | Required | Description | | --------- | ------------------------- | -------- | ------------------------------------------ | | `params` | `GetSessionQuoteResponse` | Yes | The object returned from `getSessionQuote` | See [Smart Sessions](/sdk-reference/sessions) for full parameter details and examples. *** ### waitForSupertransactionReceipt Wait for a supertransaction to complete. ```typescript theme={null} const receipt = await meeClient.waitForSupertransactionReceipt(options); ``` ### Parameters | Parameter | Type | Required | Description | | --------------- | --------------------------- | -------- | ------------------------- | | `hash` | `Hex` | Yes | Supertransaction hash | | `mode` | `"default" \| "fast-block"` | No | Confirmation mode | | `confirmations` | `number` | No | Confirmations to wait for | ### Returns ```typescript theme={null} type Receipt = { transactionStatus: "PENDING" | "MINED_SUCCESS" | "MINED_FAILED" | "EXPIRED"; userOps: UserOpReceipt[]; explorerLinks: string[]; }; ``` ### Example ```typescript theme={null} const { hash } = await meeClient.executeQuote({ quote }); const receipt = await meeClient.waitForSupertransactionReceipt({ hash, mode: "default" }); if (receipt.transactionStatus === "MINED_SUCCESS") { console.log("Success!"); } ``` *** ### getSupertransactionReceipt Get current status without waiting. ```typescript theme={null} const receipt = await meeClient.getSupertransactionReceipt(options); ``` ### Parameters | Parameter | Type | Required | Description | | ----------------- | --------------------------- | -------- | -------------------------- | | `hash` | `Hex` | Yes | Supertransaction hash | | `waitForReceipts` | `boolean` | No | Wait for on-chain receipts | | `mode` | `"default" \| "fast-block"` | No | Confirmation mode | ### Example ```typescript theme={null} // Poll for status const receipt = await meeClient.getSupertransactionReceipt({ hash }); if (receipt.transactionStatus === "PENDING") { console.log("Still processing..."); } ``` *** ## Cleanup Instructions Return leftover tokens after execution. ```typescript theme={null} const quote = await meeClient.getQuote({ instructions: [...], feeToken: {...}, cleanUps: [ { chainId: 8453, tokenAddress: USDC, recipientAddress: userEOA, dependsOn: [userOp(2)] // Optional: wait for specific instruction } ] }); ``` ### CleanUp Parameters | Parameter | Type | Required | Description | | ------------------ | ------------- | -------- | --------------------------------------- | | `chainId` | `number` | Yes | Chain to cleanup on | | `tokenAddress` | `Address` | Yes | Token to return | | `recipientAddress` | `Address` | Yes | Where to send tokens | | `dependsOn` | `UserOpRef[]` | No | Instructions to wait for | | `amount` | `bigint` | No | Specific amount (default: full balance) | # Conditional Execution Source: https://docs.biconomy.io/sdk-reference/conditions Attach runtime conditions to transactions Conditions allow transactions to wait until specific requirements are met before executing. ## createCondition Create a condition that must be satisfied for execution. ```typescript theme={null} import { createCondition, ConditionType } from "@biconomy/abstractjs"; const condition = createCondition(options); ``` ### Parameters | Parameter | Type | Required | Description | | ---------------- | ------------------- | -------- | ----------------------------------------------------------------- | | `targetContract` | `Address` | Yes | Contract to call | | `functionAbi` | `Abi` | Yes | ABI containing the function | | `functionName` | `string` | Yes | View/pure function name | | `args` | `any[]` | Yes | Function arguments | | `value` | `bigint \| boolean` | Yes | Threshold or expected value; booleans are encoded via `toBytes32` | | `type` | `ConditionType` | Yes | Comparison type | | `description` | `string` | No | Human-readable description | ### Returns `Condition` - Can be passed to `buildComposable` ### Example ```typescript theme={null} import { createCondition, ConditionType } from "@biconomy/abstractjs"; import { erc20Abi, parseUnits } from "viem"; const minBalance = createCondition({ targetContract: USDC, functionAbi: erc20Abi, functionName: "balanceOf", args: [userAddress], value: parseUnits("100", 6), type: ConditionType.GTE, description: "User must have at least 100 USDC" }); ``` *** ## ConditionType ```typescript theme={null} enum ConditionType { GTE = "gte", // Greater than or equal (≥) LTE = "lte", // Less than or equal (≤) EQ = "eq" // Equal (=) } ``` ### Usage Examples ```typescript theme={null} // Balance must be at least 1000 tokens createCondition({ ... value: parseUnits("1000", 18), type: ConditionType.GTE }); // Price must not exceed maximum createCondition({ ... value: maxPrice, type: ConditionType.LTE }); // Contract must not be paused (paused = false = 0) createCondition({ ... value: 0n, type: ConditionType.EQ }); ``` *** ## Adding Conditions to Instructions ```typescript theme={null} const instruction = await account.buildComposable({ type: "transfer", data: { chainId: 8453, tokenAddress: USDC, recipient: "0x...", amount: parseUnits("50", 6), conditions: [minBalanceCondition] // Add conditions here } }); ``` *** ## Multiple Conditions (AND Logic) All conditions must pass for execution: ```typescript theme={null} const instruction = await account.buildComposable({ type: "default", data: { chainId: 8453, to: lendingProtocol, abi: lendingAbi, functionName: "borrow", args: [amount], conditions: [ // Condition 1: Sufficient collateral createCondition({ targetContract: collateralToken, functionAbi: erc20Abi, functionName: "balanceOf", args: [userAddress], value: minCollateral, type: ConditionType.GTE }), // Condition 2: Healthy position createCondition({ targetContract: lendingProtocol, functionAbi: lendingAbi, functionName: "getHealthFactor", args: [userAddress], value: parseUnits("1.5", 18), type: ConditionType.GTE }), // Condition 3: Protocol not paused createCondition({ targetContract: lendingProtocol, functionAbi: lendingAbi, functionName: "paused", args: [], value: 0n, type: ConditionType.EQ }) ] } }); ``` *** ## Waiting for Conditions Set a timeout for how long to wait: ```typescript theme={null} const quote = await meeClient.getFusionQuote({ trigger, instructions: [instruction], feeToken, upperBoundTimestamp: Math.floor(Date.now() / 1000) + 300 // Wait up to 5 min }); ``` ### Execution Flow 1. **Submit** - Transaction submitted with conditions 2. **PENDING** - MEE periodically checks conditions 3. **Satisfied** - All conditions pass → execute 4. **Timeout** - Conditions not met in time → fail *** ## Common Patterns ### Contract Not Paused ```typescript theme={null} const pausableAbi = [{ inputs: [], name: "paused", outputs: [{ name: "", type: "bool" }], stateMutability: "view", type: "function" }] as const; createCondition({ targetContract: protocol, functionAbi: pausableAbi, functionName: "paused", args: [], value: 0n, // false type: ConditionType.EQ }); ``` ### Minimum Balance ```typescript theme={null} createCondition({ targetContract: USDC, functionAbi: erc20Abi, functionName: "balanceOf", args: [userAddress], value: parseUnits("50", 6), type: ConditionType.GTE }); ``` ### Price Threshold ```typescript theme={null} createCondition({ targetContract: priceOracle, functionAbi: oracleAbi, functionName: "getPrice", args: [tokenAddress], value: targetPrice, type: ConditionType.GTE // Wait for price to reach target }); ``` *** ## Best Practices | Practice | Reason | | ----------------------- | -------------------------- | | Match decimal precision | USDC = 6, WETH = 18 | | Limit to 1-3 conditions | Each adds \~5-10k gas | | Use `as const` for ABIs | Enables type inference | | Set reasonable timeouts | Prevent indefinite waiting | | Add descriptions | Improves debugging | # AbstractJS SDK Reference Source: https://docs.biconomy.io/sdk-reference/index Complete API reference for the AbstractJS SDK The AbstractJS SDK provides a type-safe TypeScript interface for building gas-abstracted, cross-chain applications on the Biconomy stack. ## Installation ```bash theme={null} npm install @biconomy/abstractjs viem ``` ## Quick Reference `toMultichainNexusAccount`, `toNexusAccount` `createMeeClient`, `getQuote`, `executeQuote` `buildComposable`, `build` `runtimeERC20BalanceOf`, `runtimeNativeBalanceOf` `createCondition`, `ConditionType` `getSessionQuote`, `signSessionQuote`, `executeSessionQuote`; `buildSessionAction`, `buildActionPolicy` (Account) `getMeeScanLink`, `getMEEVersion` ## Core Imports ```typescript theme={null} import { // Account toMultichainNexusAccount, toNexusAccount, // Client createMeeClient, // Runtime runtimeERC20BalanceOf, runtimeNativeBalanceOf, runtimeERC20AllowanceOf, runtimeParamViaCustomStaticCall, // Constraints greaterThanOrEqualTo, lessThanOrEqualTo, equalTo, // Conditions createCondition, ConditionType, // Sessions toSmartSessionsModule, meeSessionActions, getSudoPolicy, getUniversalActionPolicy, // Utilities getMeeScanLink, getMEEVersion, MEEVersion, // Types type MeeClient, type MultichainSmartAccount, type Instruction, type FeeTokenInfo, type Trigger, } from "@biconomy/abstractjs"; ``` ## Version Constants ```typescript theme={null} enum MEEVersion { V2_1_0 = "2.1.0", V2_2_0 = "2.2.0" } // Get version config const version = getMEEVersion(MEEVersion.V2_1_0); ``` ## Type Definitions ### FeeTokenInfo ```typescript theme={null} type FeeTokenInfo = { address: Address; chainId: number; }; ``` ### Trigger ```typescript theme={null} type Trigger = { chainId: number; tokenAddress: Address; amount: bigint; }; ``` ### Instruction ```typescript theme={null} type Instruction = { chainId: number; calls: Call[]; // Optional time bounds lowerBoundTimestamp?: number; upperBoundTimestamp?: number; }; type Call = { to: Address; data?: Hex; value?: bigint; }; ``` # Building Instructions Source: https://docs.biconomy.io/sdk-reference/instructions Build transaction instructions for execution ## buildComposable Build a composable instruction with support for runtime injection, conditions, and automatic encoding. ```typescript theme={null} const instruction = await account.buildComposable(options); ``` ### Instruction Types | Type | Description | | --------------------- | --------------------------- | | `default` | Generic contract call | | `transfer` | ERC-20 token transfer | | `approve` | ERC-20 approval | | `nativeTokenTransfer` | Native token (ETH) transfer | *** ## Default Type Call any contract function. ```typescript theme={null} const instruction = await account.buildComposable({ type: "default", data: { chainId: number; to: Address; abi: Abi; functionName: string; args: any[]; value?: bigint; gasLimit?: bigint; conditions?: Condition[]; } }); ``` ### Parameters | Parameter | Type | Required | Description | | --------------------- | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `chainId` | `number` | Yes | Target chain | | `to` | `Address` | Yes | Contract address | | `abi` | `Abi` | Yes | Contract ABI | | `functionName` | `string` | Yes | Function to call | | `args` | `any[]` | Yes | Function arguments | | `value` | `bigint` | No | Native value to send | | `gasLimit` | `bigint` | No | Custom gas limit | | `conditions` | `Condition[]` | No | Execution conditions | | `simulationOverrides` | `Overrides` | No | Per-instruction simulation overrides (e.g. token balances); see [Utilities – Simulation](/sdk-reference/utilities#simulation-options) | ### Example ```typescript theme={null} const swap = await account.buildComposable({ type: "default", data: { chainId: 8453, to: UNISWAP_ROUTER, abi: UniswapAbi, functionName: "exactInputSingle", args: [{ tokenIn: USDC, tokenOut: WETH, amountIn: parseUnits("100", 6), amountOutMinimum: 0n, recipient: account.addressOn(8453, true), fee: 500, sqrtPriceLimitX96: 0n }] } }); ``` *** ## Transfer Type Simplified ERC-20 token transfer. ```typescript theme={null} const instruction = await account.buildComposable({ type: "transfer", data: { chainId: number; tokenAddress: Address; recipient: Address; amount: bigint | RuntimeValue; conditions?: Condition[]; } }); ``` ### Example ```typescript theme={null} const transfer = await account.buildComposable({ type: "transfer", data: { chainId: 8453, tokenAddress: USDC, recipient: "0x...", amount: parseUnits("50", 6) } }); ``` *** ## Approve Type Simplified ERC-20 approval. ```typescript theme={null} const instruction = await account.buildComposable({ type: "approve", data: { chainId: number; tokenAddress: Address; spender: Address; amount: bigint | RuntimeValue; } }); ``` ### Example ```typescript theme={null} const approve = await account.buildComposable({ type: "approve", data: { chainId: 8453, tokenAddress: USDC, spender: UNISWAP_ROUTER, amount: parseUnits("100", 6) } }); ``` *** ## Native Token Transfer Send ETH or native tokens. ```typescript theme={null} const instruction = await account.buildComposable({ type: "nativeTokenTransfer", data: { chainId: number; to: Address; value: bigint | RuntimeValue; } }); ``` ### Example ```typescript theme={null} const sendEth = await account.buildComposable({ type: "nativeTokenTransfer", data: { chainId: 8453, to: "0x...", value: parseEther("0.1") } }); ``` *** ## Bridge Operations To bridge tokens (e.g. via Across Protocol), use `type: "default"` and encode the bridge protocol's contract call directly. See the [Across integration guide](/new/integration-guides/bridges-and-solvers/integrate-across) for a complete example. ```typescript theme={null} const bridge = await account.buildComposable({ type: "default", data: { chainId: 42161, // Source chain to: ACROSS_SPOKE_POOL, // Bridge contract abi: acrossSpokePoolAbi, // Bridge ABI functionName: "depositV3", args: [ account.addressOn(42161, true), // depositor account.addressOn(8453, true), // recipient USDC_ARBITRUM, // inputToken USDC_BASE, // outputToken parseUnits("100", 6), // inputAmount outputAmount, // outputAmount (from bridge quote API) 8453, // destinationChainId zeroAddress, // exclusiveRelayer quoteTimestamp, // quoteTimestamp fillDeadline, // fillDeadline 0, // exclusivityDeadline "0x" // message ] } }); ``` Bridge operations use the same `type: "default"` as any other contract call. Get bridge parameters (output amount, timestamps, deadlines) from the bridge protocol's quote API before building the instruction. *** ## build (Simple) Build a simple instruction without composability. ```typescript theme={null} const instruction = await account.build({ type: "nativeTokenTransfer", data: { chainId: 8453, to: "0x...", value: 1n, lowerBoundTimestamp: Math.floor(Date.now() / 1000), upperBoundTimestamp: Math.floor(Date.now() / 1000) + 300 } }); ``` *** ## Time Bounds Add time constraints to instructions: ```typescript theme={null} const instruction = await account.buildComposable({ type: "default", data: { chainId: 8453, to: "0x...", abi: [...], functionName: "doSomething", args: [], lowerBoundTimestamp: Math.floor(Date.now() / 1000) + 60, // Start in 1 min upperBoundTimestamp: Math.floor(Date.now() / 1000) + 300 // Expire in 5 min } }); ``` | Parameter | Description | | --------------------- | -------------------------------------- | | `lowerBoundTimestamp` | Earliest execution time (unix seconds) | | `upperBoundTimestamp` | Latest execution time (unix seconds) | # Runtime Functions Source: https://docs.biconomy.io/sdk-reference/runtime Inject dynamic values at execution time Runtime functions allow you to use values that are resolved at execution time rather than when building the instruction. Essential for handling unknown swap outputs, bridge results, and composable flows. ## runtimeERC20BalanceOf Inject the ERC-20 token balance of an address at execution time. ```typescript theme={null} import { runtimeERC20BalanceOf } from "@biconomy/abstractjs"; const balance = runtimeERC20BalanceOf(options); ``` ### Parameters | Parameter | Type | Required | Description | | --------------- | -------------- | -------- | --------------------------- | | `tokenAddress` | `Address` | Yes | Token contract address | | `targetAddress` | `Address` | Yes | Address to check balance of | | `constraints` | `Constraint[]` | No | Execution constraints | ### Returns `RuntimeValue` - Can be used as argument in `buildComposable` ### Example ```typescript theme={null} import { runtimeERC20BalanceOf, greaterThanOrEqualTo } from "@biconomy/abstractjs"; // Transfer entire USDC balance const transfer = await account.buildComposable({ type: "transfer", data: { chainId: 8453, tokenAddress: USDC, recipient: userAddress, amount: runtimeERC20BalanceOf({ tokenAddress: USDC, targetAddress: account.addressOn(8453, true), constraints: [greaterThanOrEqualTo(1n)] }) } }); ``` *** ## runtimeNativeBalanceOf Inject the native token (ETH) balance of an address at execution time. ```typescript theme={null} import { runtimeNativeBalanceOf } from "@biconomy/abstractjs"; const balance = runtimeNativeBalanceOf(options); ``` ### Parameters | Parameter | Type | Required | Description | | --------------- | -------------- | -------- | --------------------------- | | `targetAddress` | `Address` | Yes | Address to check balance of | | `constraints` | `Constraint[]` | No | Execution constraints | ### Example ```typescript theme={null} import { runtimeNativeBalanceOf } from "@biconomy/abstractjs"; // Send all ETH const sendAll = await account.buildComposable({ type: "nativeTokenTransfer", data: { chainId: 8453, to: recipient, value: runtimeNativeBalanceOf({ targetAddress: account.addressOn(8453, true) }) } }); ``` *** ## runtimeERC20AllowanceOf Inject the ERC-20 allowance between two addresses at execution time. ```typescript theme={null} import { runtimeERC20AllowanceOf } from "@biconomy/abstractjs"; const allowance = runtimeERC20AllowanceOf(options); ``` ### Parameters | Parameter | Type | Required | Description | | -------------- | -------------- | -------- | ---------------------- | | `owner` | `Address` | Yes | Token owner address | | `spender` | `Address` | Yes | Spender address | | `tokenAddress` | `Address` | Yes | Token contract address | | `constraints` | `Constraint[]` | No | Execution constraints | ### Example ```typescript theme={null} const transferFrom = await account.buildComposable({ type: "default", data: { chainId: 8453, to: USDC, abi: erc20Abi, functionName: "transferFrom", args: [ owner, recipient, runtimeERC20AllowanceOf({ owner, spender: account.addressOn(8453, true), tokenAddress: USDC }) ] } }); ``` *** ## runtimeParamViaCustomStaticCall Inject any return value from a view function (up to 32 bytes). ```typescript theme={null} import { runtimeParamViaCustomStaticCall } from "@biconomy/abstractjs"; const value = runtimeParamViaCustomStaticCall(options); ``` ### Parameters | Parameter | Type | Required | Description | | ----------------------- | --------- | -------- | ----------------------- | | `targetContractAddress` | `Address` | Yes | Contract to call | | `functionAbi` | `Abi` | Yes | ABI containing function | | `functionName` | `string` | Yes | Function name | | `args` | `any[]` | Yes | Function arguments | ### Example ```typescript theme={null} const dynamicRecipient = runtimeParamViaCustomStaticCall({ targetContractAddress: registry, functionAbi: registryAbi, functionName: "getRecipient", args: [userId] }); const transfer = await account.buildComposable({ type: "nativeTokenTransfer", data: { chainId: 8453, to: dynamicRecipient, value: parseEther("0.1") } }); ``` *** ## Constraints Constraints control **when** instructions execute and protect against bad values. ### greaterThanOrEqualTo ```typescript theme={null} import { greaterThanOrEqualTo } from "@biconomy/abstractjs"; constraints: [greaterThanOrEqualTo(parseUnits("90", 6))] ``` Wait until value is ≥ threshold. ### lessThanOrEqualTo ```typescript theme={null} import { lessThanOrEqualTo } from "@biconomy/abstractjs"; constraints: [lessThanOrEqualTo(parseUnits("100", 6))] ``` Wait until value is ≤ threshold. ### equalTo ```typescript theme={null} import { equalTo } from "@biconomy/abstractjs"; constraints: [equalTo(expectedValue)] ``` Wait until value equals expected. *** ## Constraint Behavior Constraints determine execution order in multi-step flows: ```typescript theme={null} // Step 1: Swap (executes first) const swap = await account.buildComposable({ /* ... */ }); // Step 2: Deposit (waits for swap to complete) const deposit = await account.buildComposable({ type: "default", data: { args: [ runtimeERC20BalanceOf({ tokenAddress: WETH, targetAddress: account.addressOn(8453, true), constraints: [greaterThanOrEqualTo(minExpected)] // Waits here }) ] } }); ``` **Flow:** 1. MEE attempts to execute deposit 2. Constraint not satisfied (no WETH yet) 3. MEE waits and retries 4. Swap completes, WETH arrives 5. Constraint satisfied → deposit executes *** ## MEE Version Requirements | Function | Minimum Version | | --------------------------------- | --------------- | | `runtimeERC20BalanceOf` | MEE v1.0.0 | | `runtimeERC20AllowanceOf` | MEE v1.0.0 | | `runtimeNativeBalanceOf` | MEE v2.2.0 | | `runtimeParamViaCustomStaticCall` | MEE v2.2.0 | # Smart Sessions Source: https://docs.biconomy.io/sdk-reference/sessions Delegate permissions to session signers ## Smart Sessions Smart Sessions allow accounts to delegate specific permissions to other signers with fine-grained control over what actions they can perform. Enabling and using a permission with Smart Sessions is a two-step process involving setup and actual usage: ### 1. Enable Permission The initial setup phase involves several key operations to prepare your account for session-based permissions: * **Deploy the SCA** (if it does not exist yet). * **Fund the SCA** to ensure gas costs or funding for your DeFi needs. * **Bridge Funds, Unwind Positions or etc** as part of initial setup by passing extra instructions via the `instructions` array. * **Install the Smart Sessions Module** into the SCA, ensuring the account is capable of session management. * **Enable one or more permissions**: Define specific actions and their allowed signers. ### 2. Use Permission Once a permission is enabled, a valid session signer is now authorized to perform the allowed actions. * The session signer can initiate actions *within the allowed scope* (such as contract calls, swaps, mints, etc.). * Calls made by the session signer are validated against the permission rules applied in the previous step. ## Prepare and enable permissions Deploy accounts, fund accounts, install Smart Sessions module, and enable permissions. ```typescript theme={null} const quote = await meeClient.getSessionQuote({ mode: 'PREPARE', ...options }); ``` ### Parameters | Parameter | Type | Required | Description | | ------------------------------ | ------------------- | -------- | ------------------------------------------------ | | `mode` | `"PREPARE"` | Yes | Prepare or use a permission. | | `enableSession` | `EnableSession` | No | Options to enable a session. | | `smartSessionValidatorAddress` | `Address` | No | Address of the Smart Session validator to use. | | `instructions` | `Instruction[]` | No | Array of composable instructions to be executed. | | `feeToken` | `FeeTokenInfo` | Yes\* | Token used for gas payment. | | `sponsorship` | `boolean` | No | Enable or disable gas sponsorship. | | `delegate` | `boolean` | No | Enable EIP-7702 delegate mode. | | `authorization` | `Authorization` | No | EIP-7702 authorization configuration. | | `lowerBoundTimestamp` | `number` | No | Earliest allowed execution timestamp. | | `upperBoundTimestamp` | `number` | No | Latest allowed execution timestamp. | | `cleanUps` | `CleanUp[]` | No | List of cleanup instructions. | | `simulation` | `SimulationOptions` | No | Simulation config. | ### EnableSession | Property | Type | Required | Description | | ------------------ | ----------------- | -------- | ----------------------------------------------- | | `redeemer` | `Address` | Yes | Address of the session signer. | | `actions` | `SessionAction[]` | Yes | Allowed actions within this session. | | `batchActions` | `boolean` | No | Whether to allow batching of actions. | | `maxPaymentAmount` | `bigint` | No | Maximum payment amount allowed for the session. | **Payment policy and fee token:** When the session token (e.g. the token used in spending limits or transfer actions) is the same as `feeToken`, the SDK merges those policies with the payment policy—the effective spending limit is increased by `maxPaymentAmount` and no duplicate payment action is added. ### SessionAction | **Property** | **Type** | **Required** | **Description** | | ------------ | -------------- | ------------ | ---------------------------------------------------- | | `actions` | `ActionData[]` | Yes | Specific action configurations (see **ActionData**). | | `chainId` | `number` | Yes | Chain ID where the permission is valid. | ### ActionData | Property | Type | Required | Description | | ---------------------- | ---------- | -------- | ---------------------------------------------------------------- | | `actionTarget` | `Address` | Yes | Target contract address for this action. | | `actionTargetSelector` | `Hex` | Yes | Function selector (4-byte signature) for the contract method. | | `actionPolicies` | `Policy[]` | No | List of policies defining rules and restrictions on this action. | ### Example ```typescript theme={null} const quote = await meeClient.getSessionQuote({ mode: "PREPARE", enableSession: { redeemer: sessionSigner.address, actions, }, simulation: { simulate: true }, feeToken: { address: USDC, chainId: 8453 }, trigger: { tokenAddress: USDC, chainId: 8453, amount: parseUnits("2", 6), }, }); // Store the sessions details for later use. let sessionDetails: SessionDetail[] = []; if (quote) { const { hash } = await meeClient.executeSessionQuote(quote); await meeClient.waitForSupertransactionReceipt({ hash }); sessionDetails = quote.sessionDetails; } ``` *** ## Use Permission Execute instructions using granted permissions. ```typescript theme={null} const quote = await meeClient.getSessionQuote({ mode: 'USE', ...options }); ``` ### Parameters | Parameter | Type | Required | Description | | --------------------- | ------------------- | -------- | ------------------------------------------- | | `mode` | `"USE"` | Yes | Prepare or use a permission. | | `sessionDetails` | `SessionDetails` | Yes | Session information for enabled permissions | | `instructions` | `Instruction[]` | No | Array of instructions to be executed. | | `feeToken` | `FeeTokenInfo` | Yes\* | Token used for gas payment. | | `sponsorship` | `boolean` | No | Enable or disable gas sponsorship. | | `delegate` | `boolean` | No | Enable EIP-7702 delegate mode. | | `authorization` | `Authorization` | No | EIP-7702 authorization configuration. | | `lowerBoundTimestamp` | `number` | No | Earliest allowed execution timestamp. | | `upperBoundTimestamp` | `number` | No | Latest allowed execution timestamp. | | `simulation` | `SimulationOptions` | No | Simulation config. | ### Example ```typescript theme={null} const quote = await meeClient.getSessionQuote({ mode: "USE", sessionDetails, simulation: { simulate: true }, feeToken: { address: USDC, chainId: 8453 }, instructions: [ { chainId: 8453, calls: [{ to: CounterContract, data: "0x..." }] } ] }); const { hash } = await meeClient.executeSessionQuote(quote); await meeClient.waitForSupertransactionReceipt({ hash }); ``` *** ## Policies ### Sudo Policy Grants unlimited permissions for specified functions. ```typescript theme={null} const sudoPolicy = mcNexus.buildActionPolicy({ type: "sudo" }) ``` Use with caution, provides highest level of access. *** ### Universal Policy Fine-grained parameter-level control. ```typescript theme={null} const universalPolicy = mcNexus.buildActionPolicy({ type: "universal", rules: [ { condition: "equal", calldataOffset: calldataArgument(2), comparisonValue: parseUnits("10", 6), isLimited: false, usage: { limit: 0n, used: 0n } } ], // Configure spending limit for native token valueLimitPerUse: 1n }); ``` Use **`calldataArgument(n)`** to specify the calldata offset for the n-th (1-based) function argument—e.g. `calldataArgument(2)` is the second parameter. Import from `@biconomy/abstractjs`. *** ### Timeframe policy Add fine-grained expirations to the session actions. ```typescript theme={null} const now = Math.floor(Date.now() / 1000) const validAfter = now const validUntil = now + 3600 const timeframePolicy = mcNexus.buildActionPolicy({ type: "timeframe", validAfter, // unix-timestamp when policy becomes active validUntil // unix-timestamp when policy expires }); ``` *** ### Spending limit policy Add spending limits for the token spendings. ```typescript theme={null} const spendingLimitsPolicy = mcNexus.buildActionPolicy({ type: "spendingLimits", tokenLimits: [{ token: USDC, limit: parseUnits("10", 6) }] }); ``` *** ### Usage limit policy Add usage limits to control the session usage count. ```typescript theme={null} const usagePolicy = mcNexus.buildActionPolicy({ type: "usageLimit", limit: 5n }); ``` *** ## Actions ### Frequently Used Actions Build approve, transfer, and transferFrom actions for your session ```typescript theme={null} const actions = mcNexus.buildSessionAction({ type: "approve" | "transferFrom" | "transfer", data: { chainIds: [8453, 10], contractAddress: USDC, recipientAddress: "0x..." // Optional amountLimitPerAction: parseUnits("10", 6), // Optional maxAmountLimit: parseUnits("10", 6), // Optional usageLimit: 10n, // Optional validAfter: Math.floor(Date.now() / 1000), // Optional validUntil: Math.floor(Date.now() / 1000) + 60 * 60 * 24 // Optional // or // policies: [] // Define policies on your own. Defaults to sudo policy }, }); ``` By default, sudo policy will be configured. Configure policies on your own by defining policies *** ### Custom Actions Build custom actions for your session ```typescript theme={null} const functionSignature = toFunctionSelector( getAbiItem({ abi: CounterAbi, name: "incrementCount" }) ); const customActions = mcNexus.buildSessionAction({ type: "custom", data: { chainIds: [8453, 10], contractAddress: CounterContract, functionSignature, policies: [{ type: "sudo" }] // Optional } }); ``` *** ### Custom action batching By default, all actions are submitted as a single atomic batch when enabling session permissions. If you encounter issues with block gas limits on certain chains, you can break the actions into multiple smaller batches. ```typescript theme={null} const actionOne = mcNexus.buildSessionAction({...}); const actionTwo = mcNexus.buildSessionAction({...}); const batchedActions = mcNexus.buildSessionAction({ type: "batch", data: { actions: [...actionOne, ...actionTwo] } }); const actionThree = mcNexus.buildSessionAction({...}); // Actions can be partially batched const actions = [...batchedActions, ...actionThree]; ``` To enable custom batching, make sure to configure `batchActions: false` in enableSession while preparing the permissions. *** ## Check Permission Status ### meeSessionActions Extend the MEE client with session-related methods. ```typescript theme={null} import { meeSessionActions } from "@biconomy/abstractjs"; const sessionClient = meeClient.extend(meeSessionActions); ``` The following methods will be extended * `isPermissionEnabled` * `checkEnabledPermissions` *** ### isPermissionEnabled ```typescript theme={null} const isEnabled = await sessionClient.isPermissionEnabled({ permissionId: permission.permissionId, chainId: 8453 }); ``` ### checkEnabledPermissions ```typescript theme={null} const enabledMap = await sessionClient.checkEnabledPermissions(sessionDetails); // Returns: { [permissionId]: { [chainId]: boolean } } ``` *** ## Complete Flow ```typescript theme={null} // 1. User account setup const userSigner = privateKeyToAccount("0x..."); const mcNexus = await toMultichainNexusAccount(options): const meeClient = await createMeeClient(options); // 2. Session signer setup const sessionSigner = privateKeyToAccount("0x..."); const sessionMcNexus = await toMultichainNexusAccount({ chainConfigurations: [ { // ... other config // User McNexus address needs to be overriden here accountAddress: mcNexus.addressOn(chainId, true), }, { // ... other config // User McNexus address needs to be overriden here accountAddress: mcNexus.addressOn(chainId, true), }, ], signer: sessionSigner, }); const sessionMeeClient = await createMeeClient(options); // 3. Build actions for permissions const functionSignature = toFunctionSelector( getAbiItem({ abi: CounterAbi, name: "incrementCount" }) ); const actions = mcNexus.buildSessionAction({ type: "custom", data: { chainIds: [8453, 10], contractAddress: CounterContract, functionSignature } }); // 4. Prepare and enable sessions const quote = await meeClient.getSessionQuote({ mode: "PREPARE", enableSession: { redeemer: sessionSigner.address, actions, }, simulation: { simulate: true }, feeToken: { address: USDC, chainId: 8453 }, trigger: { tokenAddress: USDC, chainId: 8453, amount: parseUnits("2", 6), }, }); // Store the sessions details for later use. let sessionDetails: SessionDetail[] = []; if (quote) { const { hash } = await meeClient.executeSessionQuote(quote); await meeClient.waitForSupertransactionReceipt({ hash }); if (quote.sessionDetails) sessionDetails = quote.sessionDetails; } // 5. Execute transaction on behalf of user via session signer const quote = await sessionMeeClient.getSessionQuote({ mode: "USE", sessionDetails, simulation: { simulate: true }, feeToken: { address: USDC, chainId: 8453 }, instructions: [ { chainId: 8453, calls: [{ to: CounterContract, data: "0x..." }] } ] }); const { hash } = await sessionMeeClient.executeSessionQuote(quote); await sessionMeeClient.waitForSupertransactionReceipt({ hash }); ``` # Smart Sessions (Legacy) Source: https://docs.biconomy.io/sdk-reference/sessions-legacy Delegate permissions to session signers Smart Sessions allow accounts to delegate specific permissions to other signers with fine-grained control over what actions they can perform. A new Smart Sessions SDK flow is now available with improved features and recommended best practices. We encourage all developers to migrate to the new smart sessions flow. See the updated documentation here: Smart Sessions Reference ## toSmartSessionsModule Create a Smart Sessions validator module. ```typescript theme={null} import { toSmartSessionsModule } from "@biconomy/abstractjs"; const sessionsModule = toSmartSessionsModule(options); ``` ### Parameters | Parameter | Type | Required | Description | | --------- | --------- | -------- | ------------------ | | `signer` | `Account` | Yes | The session signer | ### Example ```typescript theme={null} import { toSmartSessionsModule } from "@biconomy/abstractjs"; import { privateKeyToAccount } from "viem/accounts"; const sessionSigner = privateKeyToAccount("0x..."); const sessionsModule = toSmartSessionsModule({ signer: sessionSigner }); ``` *** ## meeSessionActions Extend the MEE client with session-related methods. ```typescript theme={null} import { meeSessionActions } from "@biconomy/abstractjs"; const sessionClient = meeClient.extend(meeSessionActions); ``` ### Added Methods * `prepareForPermissions` * `grantPermissionTypedDataSign` * `grantPermissionPersonalSign` * `usePermission` * `isPermissionEnabled` * `checkEnabledPermissions` *** ## prepareForPermissions Deploy accounts and install Smart Sessions module. ```typescript theme={null} const payload = await sessionClient.prepareForPermissions(options); ``` ### Parameters | Parameter | Type | Required | Description | | ------------------------ | ---------------- | -------- | ----------------- | | `smartSessionsValidator` | `SessionsModule` | Yes | Sessions module | | `feeToken` | `FeeTokenInfo` | Yes | Gas payment token | | `trigger` | `Trigger` | No | Funding trigger | Legacy `prepareForPermissions` is documented with the minimal parameter set above. For extended options (e.g. `redeemer`, `actions`, `maxPaymentAmount`, or payloads that include `sessionDetails`), use the new flow: [getSessionQuote with `mode: 'PREPARE'`](/sdk-reference/sessions#prepare-and-enable-permissions). ### Example ```typescript theme={null} const payload = await sessionClient.prepareForPermissions({ smartSessionsValidator: sessionsModule, feeToken: { address: USDC, chainId: 8453 }, trigger: { tokenAddress: USDC, chainId: 8453, amount: parseUnits("10", 6) } }); if (payload) { await meeClient.waitForSupertransactionReceipt({ hash: payload.hash }); } ``` *** ## grantPermissionTypedDataSign Grant permissions using EIP-712 typed data signature. ```typescript theme={null} const sessionDetails = await sessionClient.grantPermissionTypedDataSign(options); ``` ### Parameters | Parameter | Type | Required | Description | | ------------------ | ---------------- | -------- | ---------------------- | | `redeemer` | `Address` | Yes | Session signer address | | `feeToken` | `FeeTokenInfo` | Yes | Gas payment token | | `actions` | `ActionConfig[]` | Yes | Allowed actions | | `maxPaymentAmount` | `bigint` | No | Max gas payment | ### ActionConfig | Property | Type | Description | | ---------------------- | ---------- | ------------------------------ | | `chainId` | `number` | Chain where permission applies | | `actionTarget` | `Address` | Contract address | | `actionTargetSelector` | `Hex` | Function selector | | `actionPolicies` | `Policy[]` | Policies to apply | ### Example ```typescript theme={null} import { toFunctionSelector, getAbiItem } from "viem"; const sessionDetails = await sessionClient.grantPermissionTypedDataSign({ redeemer: sessionSigner.address, feeToken: { address: USDC, chainId: 8453 }, actions: [ { chainId: 8453, actionTarget: CounterContract, actionTargetSelector: toFunctionSelector( getAbiItem({ abi: CounterAbi, name: "increment" }) ), actionPolicies: [getSudoPolicy()] } ], maxPaymentAmount: parseUnits("2", 6) }); ``` *** ## usePermission Execute instructions using granted permissions. ```typescript theme={null} const result = await sessionClient.usePermission(options); ``` ### Parameters | Parameter | Type | Required | Description | | ---------------------- | --------------------------- | -------- | ----------------------- | | `sessionDetails` | `SessionDetails` | Yes | From `grantPermission` | | `mode` | `"ENABLE_AND_USE" \| "USE"` | Yes | Execution mode | | `feeToken` | `FeeTokenInfo` | Yes | Gas payment token | | `instructions` | `Instruction[]` | Yes | Instructions to execute | | `sponsorship` | `boolean` | No | Enable sponsorship | | `verificationGasLimit` | `bigint` | No | Custom gas limit | When `sessionDetails` were obtained from **`prepareForPermissions`** (not from `grantPermissionTypedDataSign` / `grantPermissionPersonalSign`), use mode **`USE`** only. Using `ENABLE_AND_USE` in that case will throw. ### Example ```typescript theme={null} const result = await sessionClient.usePermission({ sessionDetails, mode: "ENABLE_AND_USE", feeToken: { address: USDC, chainId: 8453 }, instructions: [ { chainId: 8453, calls: [{ to: CounterContract, data: "0x..." }] } ] }); await meeClient.waitForSupertransactionReceipt({ hash: result.hash }); ``` *** ## Policies ### getSudoPolicy Grants unlimited permissions for specified functions. ```typescript theme={null} import { getSudoPolicy } from "@biconomy/abstractjs"; actionPolicies: [getSudoPolicy()] ``` Use with caution—provides highest level of access. ### getUniversalActionPolicy Fine-grained parameter-level control. ```typescript theme={null} import { getUniversalActionPolicy, ParamCondition } from "@biconomy/abstractjs"; const policy = getUniversalActionPolicy({ valueLimitPerUse: maxUint256, paramRules: { length: 2n, rules: [ { condition: ParamCondition.EQUAL, isLimited: false, offset: 0n, ref: pad(recipientAddress), usage: { limit: 0n, used: 0n } }, { condition: ParamCondition.LESS_THAN_OR_EQUAL, isLimited: true, offset: 32n, ref: pad(toHex(parseUnits("100", 6))), usage: { limit: parseUnits("1000", 6), used: 0n } } ] } }); ``` ### ParamCondition | Value | Description | | ----------------------- | ------------------ | | `EQUAL` | Exact match | | `GREATER_THAN` | Value > reference | | `LESS_THAN` | Value \< reference | | `GREATER_THAN_OR_EQUAL` | Value ≥ reference | | `LESS_THAN_OR_EQUAL` | Value ≤ reference | | `NOT_EQUAL` | Value ≠ reference | *** ## Check Permission Status ### isPermissionEnabled ```typescript theme={null} const isEnabled = await sessionClient.isPermissionEnabled({ permissionId: permission.permissionId, chainId: 8453 }); ``` ### checkEnabledPermissions ```typescript theme={null} const enabledMap = await sessionClient.checkEnabledPermissions(sessionDetails); // Returns: { [permissionId]: { [chainId]: boolean } } ``` *** ## Complete Flow ```typescript theme={null} // 1. Setup const sessionSigner = privateKeyToAccount("0x..."); const sessionsModule = toSmartSessionsModule({ signer: sessionSigner }); const sessionClient = meeClient.extend(meeSessionActions); // 2. Prepare (deploy + install module) await sessionClient.prepareForPermissions({ smartSessionsValidator: sessionsModule, feeToken, trigger }); // 3. Grant permissions const sessionDetails = await sessionClient.grantPermissionTypedDataSign({ redeemer: sessionSigner.address, feeToken, actions: [{ chainId: 8453, actionTarget: contract, actionTargetSelector, actionPolicies: [getSudoPolicy()] }] }); // 4. Use permissions (from session signer) const result = await dappSessionClient.usePermission({ sessionDetails, mode: "ENABLE_AND_USE", feeToken, instructions: [...] }); ``` # Utility Functions Source: https://docs.biconomy.io/sdk-reference/utilities Helper functions and constants ## getMeeScanLink Generate a link to MEE Scan explorer for a supertransaction. ```typescript theme={null} import { getMeeScanLink } from "@biconomy/abstractjs"; const link = getMeeScanLink(hash); ``` ### Parameters | Parameter | Type | Required | Description | | --------- | ----- | -------- | --------------------- | | `hash` | `Hex` | Yes | Supertransaction hash | ### Returns `string` - URL to MEE Scan ### Example ```typescript theme={null} const { hash } = await meeClient.executeQuote({ quote }); const link = getMeeScanLink(hash); console.log("View transaction:", link); // https://meescan.biconomy.io/tx/0x... ``` *** ## getMEEVersion Get MEE version configuration for account setup. ```typescript theme={null} import { getMEEVersion, MEEVersion } from "@biconomy/abstractjs"; const version = getMEEVersion(MEEVersion.V2_1_0); ``` ### MEEVersion Enum ```typescript theme={null} enum MEEVersion { V2_1_0 = "2.1.0", V2_2_0 = "2.2.0" } ``` ### Example ```typescript theme={null} const account = await toMultichainNexusAccount({ signer, chainConfigurations: [ { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) } ] }); ``` *** ## userOp Reference a specific user operation for dependencies. ```typescript theme={null} import { userOp } from "@biconomy/abstractjs"; const ref = userOp(2); // Reference to 3rd instruction (0-indexed) ``` ### Usage ```typescript theme={null} const quote = await meeClient.getQuote({ instructions: [instruction1, instruction2, instruction3], cleanUps: [ { chainId: 8453, tokenAddress: USDC, recipientAddress: userEOA, dependsOn: [userOp(2)] // Wait for instruction3 } ], feeToken }); ``` *** ## toFeeToken Helper to create FeeTokenInfo from multichain token. ```typescript theme={null} import { toFeeToken } from "@biconomy/abstractjs"; const feeToken = toFeeToken({ mcToken: mcUSDC, chainId: 8453 }); ``` *** ## Simulation Options Configure simulation for gas estimation. ```typescript theme={null} const quote = await meeClient.getFusionQuote({ trigger, instructions, feeToken, simulation: { simulate: true, overrides: { tokenOverrides: [ { tokenAddress: USDC, chainId: 8453, balance: parseUnits("1000", 6), accountAddress: account.addressOn(8453, true) } ] } } }); ``` ### SimulationOptions | Property | Type | Description | | ----------- | ----------- | ----------------- | | `simulate` | `boolean` | Enable simulation | | `overrides` | `Overrides` | State overrides | ### Overrides | Property | Type | Description | | ----------------- | ------------------------ | -------------------------------------------------------------- | | `tokenOverrides` | `TokenOverride[]` | Mock token balances | | `customOverrides` | `CustomOverride[]` | Custom storage slots | | `gasLimitBuffers` | `Record` | Optional per-chain gas limit buffer (chain ID → buffer amount) | *** ## Constants ### Default URLs ```typescript theme={null} import { DEFAULT_PATHFINDER_URL, DEFAULT_MEE_TESTNET_SPONSORSHIP_PAYMASTER_ACCOUNT, DEFAULT_MEE_TESTNET_SPONSORSHIP_TOKEN_ADDRESS, DEFAULT_MEE_TESTNET_SPONSORSHIP_CHAIN_ID } from "@biconomy/abstractjs"; ``` ### Nexus Singleton ```typescript theme={null} const NEXUS_V120_SINGLETON = "0x000000004F43C49e93C970E84001853a70923B03"; ``` *** ## Type Exports ```typescript theme={null} import type { MeeClient, MultichainSmartAccount, Instruction, FeeTokenInfo, Trigger, Quote, FusionQuote, Receipt, SessionDetails, Condition, RuntimeValue, CleanUp } from "@biconomy/abstractjs"; ``` *** ## Viem Re-exports AbstractJS re-exports commonly used viem utilities: ```typescript theme={null} import { parseUnits, parseEther, formatUnits, encodeFunctionData, decodeFunctionResult, toFunctionSelector, getAbiItem, pad, toHex } from "viem"; ``` *** ## Error Handling ```typescript theme={null} try { const { hash } = await meeClient.executeQuote({ quote }); const receipt = await meeClient.waitForSupertransactionReceipt({ hash }); if (receipt.transactionStatus === "MINED_SUCCESS") { console.log("Success!"); } else if (receipt.transactionStatus === "MINED_FAILED") { console.error("Transaction failed on-chain"); receipt.userOps.forEach(op => { console.log(`Chain ${op.chainId}: ${op.executionStatus}`); }); } } catch (error) { if (error.code === "INSUFFICIENT_FUNDS") { console.error("Not enough tokens for gas"); } else if (error.code === "SIMULATION_FAILED") { console.error("Transaction would revert"); } else { throw error; } } ``` # Check Account Status Source: https://docs.biconomy.io/supertransaction-api/endpoints/orchestrator Query Nexus deployment information across chains and MEE versions The `/v1/mee/orchestrator` endpoint queries deployment information for an owner address across multiple chains and MEE versions. Use this to check if users have legacy v2.1.0 deployments that need upgrading, or to retrieve their upgraded account addresses. ## Endpoint ``` POST https://api.biconomy.io/v1/mee/orchestrator ``` ## When to Use This endpoint is primarily useful if you have users with **legacy v2.1.0 deployments** and want to support an upgrade flow. New applications using only v2.2.1 can skip this endpoint entirely. Use this endpoint to: * Check if a user has existing Nexus deployments * Determine which accounts need upgrading from v2.1.0 to v2.2.1 * Retrieve `upgradedAddresses` to use with the `accountAddress` parameter in `/v1/quote` ## Request Structure ### Request Body | Parameter | Type | Required | Description | | ----------------- | --------- | -------- | ---------------------------------------------------------------------- | | `ownerAddress` | string | Yes | EOA wallet address to check | | `chains` | number\[] | No | Chain IDs to check. Defaults to all supported chains | | `addressVersions` | string\[] | No | Filter by version: `['2.1.0']`, `['2.2.1']`, or both. Defaults to both | ### Example Request ```bash theme={null} curl -X POST https://api.biconomy.io/v1/mee/orchestrator \ -H "Content-Type: application/json" \ -d '{ "ownerAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f7bD2B", "chains": [8453, 10] }' ``` ```typescript TypeScript theme={null} const orchestratorResponse = await fetch('https://api.biconomy.io/v1/mee/orchestrator', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ownerAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f7bD2B', chains: [8453, 10] // Optional: Base and Optimism }) }).then(r => r.json()); // Check if any deployments need upgrade const needsUpgrade = orchestratorResponse.deployments.some(d => d.isUpgradeNeeded); ``` ```python Python theme={null} import requests response = requests.post( 'https://api.biconomy.io/v1/mee/orchestrator', headers={'Content-Type': 'application/json'}, json={ 'ownerAddress': '0x742d35Cc6634C0532925a3b844Bc9e7595f7bD2B', 'chains': [8453, 10] } ) orchestrator_data = response.json() needs_upgrade = any(d['isUpgradeNeeded'] for d in orchestrator_data['deployments']) ``` ## Response Structure ### Success Response (200) ```json theme={null} { "ownerAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f7bD2B", "deployments": [ { "chainId": 8453, "chainName": "Base", "addressVersion": "2.1.0", "address": "0xLegacyAddr...", "isDeployed": true, "implementation": "0xOldImpl...", "nexusVersion": "2.1.0", "accountId": "biconomy.nexus.1.0.0", "isUpgradeNeeded": true }, { "chainId": 8453, "chainName": "Base", "addressVersion": "2.2.1", "address": "0xNewAddr...", "isDeployed": false, "implementation": null, "nexusVersion": null, "accountId": null, "isUpgradeNeeded": false } ], "upgradedAddresses": {} } ``` ### Response Fields | Field | Type | Description | | ------------------- | ------ | ----------------------------------------------------------- | | `ownerAddress` | string | The queried EOA address | | `deployments` | array | Array of deployment info for each chain/version combination | | `upgradedAddresses` | object | Chain ID to address mapping for upgraded v2.1.0 accounts | ### Deployment Object Fields | Field | Type | Description | | ----------------- | -------------- | ------------------------------------------------------------- | | `chainId` | number | Chain ID | | `chainName` | string | Human-readable chain name | | `addressVersion` | string | MEE version used to derive this address (`2.1.0` or `2.2.1`) | | `address` | string | Nexus smart account address | | `isDeployed` | boolean | Whether the account has code deployed | | `implementation` | string \| null | Current implementation contract address | | `nexusVersion` | string \| null | Current implementation version (`2.1.0`, `2.2.1`, or null) | | `accountId` | string \| null | Account identifier (e.g., `biconomy.nexus.1.2.0`) | | `isUpgradeNeeded` | boolean | True if deployed with v2.1.0 implementation and needs upgrade | ## Understanding the Response ### Key Concepts **Address Version** (`addressVersion`): The MEE version used to *derive* the address. Different versions derive different addresses for the same owner. **Nexus Version** (`nexusVersion`): The actual implementation version running at that address. An account derived with v2.1.0 can be *upgraded* to run v2.2.1 implementation. ``` addressVersion: "2.1.0" → Address derived using v2.1.0 formula nexusVersion: "2.2.1" → Currently running v2.2.1 implementation (upgraded) ``` The `isUpgradeNeeded` field is `true` when: * Account is deployed (`isDeployed: true`) * Running v2.1.0 implementation (`nexusVersion: "2.1.0"`) After upgrading, this becomes `false` even though `addressVersion` remains `2.1.0`. After upgrading a v2.1.0 account, its address appears in `upgradedAddresses`: ```json theme={null} "upgradedAddresses": { "8453": "0xLegacyAddr...", "10": "0xLegacyAddr..." } ``` Use these addresses with the `accountAddress` parameter in `/v1/quote` to execute transactions using the legacy address. ## Example Responses User has a v2.1.0 deployment that needs upgrading: ```json theme={null} { "ownerAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f7bD2B", "deployments": [ { "chainId": 8453, "chainName": "Base", "addressVersion": "2.1.0", "address": "0xLegacyAddr...", "isDeployed": true, "implementation": "0xOldImpl...", "nexusVersion": "2.1.0", "accountId": "biconomy.nexus.1.0.0", "isUpgradeNeeded": true } ], "upgradedAddresses": {} } ``` User's v2.1.0 account has been upgraded to v2.2.1 implementation: ```json theme={null} { "ownerAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f7bD2B", "deployments": [ { "chainId": 8453, "chainName": "Base", "addressVersion": "2.1.0", "address": "0xLegacyAddr...", "isDeployed": true, "implementation": "0xLatestImpl...", "nexusVersion": "2.2.1", "accountId": "biconomy.nexus.1.2.0", "isUpgradeNeeded": false } ], "upgradedAddresses": { "8453": "0xLegacyAddr..." } } ``` User has no legacy deployments - standard flow applies: ```json theme={null} { "ownerAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f7bD2B", "deployments": [ { "chainId": 8453, "chainName": "Base", "addressVersion": "2.2.1", "address": "0xNewAddr...", "isDeployed": false, "implementation": null, "nexusVersion": null, "accountId": null, "isUpgradeNeeded": false } ], "upgradedAddresses": {} } ``` ## Integration Pattern ```typescript theme={null} async function checkUserStatus(ownerAddress: string) { // 1. Query deployment status const nexusInfo = await fetch('https://api.biconomy.io/v1/mee/orchestrator', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ownerAddress }) }).then(r => r.json()); // 2. Check if upgrade is needed const deploymentsNeedingUpgrade = nexusInfo.deployments.filter(d => d.isUpgradeNeeded); if (deploymentsNeedingUpgrade.length > 0) { return { status: 'needs_upgrade', chainsToUpgrade: deploymentsNeedingUpgrade.map(d => d.chainId) }; } // 3. User is ready - return upgraded addresses for use in /quote return { status: 'ready', accountAddress: nexusInfo.upgradedAddresses }; } ``` ## Next Steps * If accounts need upgrading, use the [Upgrade endpoint](/supertransaction-api/endpoints/upgrade) to generate an upgrade quote * After upgrading, use the `accountAddress` parameter in [/v1/quote](/supertransaction-api/endpoints/quote) to transact with legacy addresses ## Frequently Asked Questions The orchestrator endpoint currently supports two versions: * **v2.1.0** - Legacy version (accounts that may need upgrading) * **v2.2.1** - Current version (all new deployments) Only these two versions can be queried via the `addressVersions` parameter. No. If you're building a new application without existing users on v2.1.0, you can skip this endpoint entirely. The standard `/v1/quote` → `/v1/execute` flow will automatically use v2.2.1. Yes. You can cache `upgradedAddresses` in memory or local storage to avoid repeated API calls. The upgrade status only changes when a user explicitly upgrades their account. The orchestrator endpoint supports all MEE-enabled chains. Common chain IDs: * **Base**: `8453` * **Optimism**: `10` * **Polygon**: `137` * **Arbitrum**: `42161` * **World Chain**: `480` For a complete list, see [Supported Chains](/contracts-and-audits/supported-chains). # Upgrade Legacy Account Source: https://docs.biconomy.io/supertransaction-api/endpoints/upgrade Generate an upgrade quote for accounts with v2.1.0 deployments The `/v1/mee/upgrade` endpoint generates an upgrade quote for Nexus accounts running v2.1.0 implementation. Execute the returned quote via `/v1/execute` to upgrade the account to v2.2.1 while preserving the original address. ## Endpoint ``` POST https://api.biconomy.io/v1/mee/upgrade ``` ## When to Use Use this endpoint only for users with **existing v2.1.0 deployments** that need upgrading. New users automatically get v2.2.1 accounts. **For legacy EOA (Fusion) mode users**, upgrade is optional. In EOA mode, Nexus acts as a pass-through only, so there are typically no funds remaining in the old Nexus account. You have two options: 1. **Rescue dust (optional)**: If users have small residual funds in their old Nexus and you want to help them recover these, build a separate rescue flow that uses address override to upgrade the old Nexus and sweep funds back to their EOA. After rescue, users should proceed with the default v2.2.1 account. 2. **Ignore dust**: Simply use the normal flow without any special handling—it will automatically default to the latest v2.2.1 Nexus. The upgrade flow is: 1. Check account status via [/v1/mee/orchestrator](/supertransaction-api/endpoints/orchestrator) to identify accounts needing upgrade 2. Generate an upgrade quote via this endpoint 3. Sign and execute the quote via [/v1/execute](/supertransaction-api/endpoints/execute) 4. Use the legacy address with `accountAddress` parameter in future [/v1/quote](/supertransaction-api/endpoints/quote) requests ## Request Structure ### Request Body | Parameter | Type | Required | Description | | -------------- | --------- | -------- | ------------------------------------------------------------------- | | `ownerAddress` | string | Yes | EOA wallet address | | `mode` | string | Yes | Execution mode: `eoa` or `smart-account` (not `eoa-7702`) | | `chainIds` | number\[] | Yes | Chain IDs where accounts should be upgraded | | `feeToken` | object | No | Token for gas payment. If not specified, uses sponsorship (gasless) | | `simulate` | boolean | No | Simulate before execution (default: `true`) | The `eoa-7702` mode is **not supported** for upgrades. Use `eoa` or `smart-account` mode instead. **Signature Format**: Upgrade transactions use **personal message signing** (`eth_sign`), not EIP-712 typed data. This is because the upgrade operates on v2.1.0 accounts which use the legacy signing format. After upgrading, future transactions will use EIP-712 typed data. ### Fee Token Object ```json theme={null} "feeToken": { "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "chainId": 8453 } ``` ### Example Request ```bash theme={null} curl -X POST https://api.biconomy.io/v1/mee/upgrade \ -H "Content-Type: application/json" \ -d '{ "ownerAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f7bD2B", "mode": "smart-account", "chainIds": [8453], "feeToken": { "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "chainId": 8453 } }' ``` ```typescript TypeScript theme={null} const upgradeQuote = await fetch('https://api.biconomy.io/v1/mee/upgrade', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ownerAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f7bD2B', mode: 'smart-account', chainIds: [8453], feeToken: { address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', // USDC on Base chainId: 8453 } }) }).then(r => r.json()); // Sign the upgrade quote const signature = await walletClient.signMessage({ message: upgradeQuote.payloadToSign[0].message, account }); // Execute the upgrade const result = await fetch('https://api.biconomy.io/v1/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...upgradeQuote, payloadToSign: [{ ...upgradeQuote.payloadToSign[0], signature }] }) }).then(r => r.json()); ``` ```python Python theme={null} import requests # Get upgrade quote upgrade_response = requests.post( 'https://api.biconomy.io/v1/mee/upgrade', headers={'Content-Type': 'application/json'}, json={ 'ownerAddress': '0x742d35Cc6634C0532925a3b844Bc9e7595f7bD2B', 'mode': 'smart-account', 'chainIds': [8453], 'feeToken': { 'address': '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', 'chainId': 8453 } } ) upgrade_quote = upgrade_response.json() # Sign and execute... ``` ## Response Structure ### Success Response (200) The response follows the standard quote format with an additional `upgradeDetails` field: ```json theme={null} { "ownerAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f7bD2B", "fee": { "amount": "150000", "token": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", "chainId": 8453 }, "quoteType": "simple", "quote": { "hash": "0x...", "node": "0x...", "commitment": "0x...", "paymentInfo": {...}, "userOps": [...] }, "payloadToSign": [ { "signablePayload": {...}, "metadata": {...} } ], "upgradeDetails": [ { "chainId": 8453, "addressVersion": "2.1.0", "address": "0xLegacyAddr...", "nexusVersion": "2.1.0", "targetVersion": "2.2.1" } ] } ``` ### Response Fields | Field | Type | Description | | ---------------- | ------ | ------------------------------------------------ | | `ownerAddress` | string | Owner wallet address | | `fee` | object | Execution fee details | | `quoteType` | string | Signature type (typically `simple` for upgrades) | | `quote` | object | Quote details for execution | | `payloadToSign` | array | Payloads requiring signatures | | `upgradeDetails` | array | Details about accounts being upgraded | ### Upgrade Details Object | Field | Type | Description | | ---------------- | ------ | -------------------------------------------------- | | `chainId` | number | Chain ID of the account being upgraded | | `addressVersion` | string | Always `2.1.0` (legacy address derivation version) | | `address` | string | The address being upgraded | | `nexusVersion` | string | Current implementation version (`2.1.0`) | | `targetVersion` | string | Target implementation version (`2.2.1`) | ## Error Responses ### 400 Bad Request No v2.1.0 deployments found that need upgrading on specified chains: ```json theme={null} { "code": "NO_UPGRADE_NEEDED", "message": "No accounts require upgrading on the specified chains" } ``` **Solution**: Check the account status via `/v1/mee/orchestrator` first to confirm upgrade is needed. Invalid or unsupported chain ID: ```json theme={null} { "code": "INVALID_CHAIN", "message": "Chain ID 999999 is not supported" } ``` **Solution**: Use a [supported chain ID](/contracts-and-audits/supported-chains). Unsupported execution mode for upgrades: ```json theme={null} { "code": "BAD_REQUEST", "message": "eoa-7702 mode is not supported for upgrades" } ``` **Solution**: Use `eoa` or `smart-account` mode instead. ## Complete Upgrade Flow Here's the complete flow for upgrading legacy accounts: ```typescript theme={null} async function upgradeLegacyAccount(ownerAddress: string, walletClient: WalletClient) { // Step 1: Check account status const nexusInfo = await fetch('https://api.biconomy.io/v1/mee/orchestrator', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ownerAddress }) }).then(r => r.json()); // Step 2: Identify chains needing upgrade const chainsToUpgrade = nexusInfo.deployments .filter(d => d.isUpgradeNeeded) .map(d => d.chainId); if (chainsToUpgrade.length === 0) { console.log('No upgrade needed'); return nexusInfo.upgradedAddresses; } // Step 3: Generate upgrade quote const upgradeQuote = await fetch('https://api.biconomy.io/v1/mee/upgrade', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ownerAddress, mode: 'smart-account', chainIds: chainsToUpgrade, feeToken: { address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', // USDC on Base chainId: 8453 } }) }).then(r => r.json()); // Step 4: Sign the upgrade quote const payload = upgradeQuote.payloadToSign[0]; const signature = await walletClient.signMessage({ message: payload.signablePayload.message, account: walletClient.account }); // Step 5: Execute the upgrade const result = await fetch('https://api.biconomy.io/v1/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...upgradeQuote, payloadToSign: [{ ...payload, signature }] }) }).then(r => r.json()); console.log('Upgrade submitted:', result.supertxHash); console.log('Track at:', `https://meescan.biconomy.io/details/${result.supertxHash}`); // Step 6: Fetch updated status to get upgraded addresses const updatedInfo = await fetch('https://api.biconomy.io/v1/mee/orchestrator', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ownerAddress }) }).then(r => r.json()); return updatedInfo.upgradedAddresses; } ``` ## After Upgrade Once the upgrade is complete: 1. **Store the upgraded addresses** - Save `upgradedAddresses` for future use, or query the [/v1/mee/orchestrator](/supertransaction-api/endpoints/orchestrator) endpoint anytime to retrieve this info 2. **Use `accountAddress` parameter** - Pass the legacy address in `/v1/quote` requests ```typescript theme={null} // After upgrade, use the legacy address in quote requests const quote = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ownerAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f7bD2B', mode: 'smart-account', composeFlows: [...], accountAddress: { '8453': '0xLegacyAddr...' // Use the upgraded legacy address } }) }).then(r => r.json()); ``` The `eoa-7702` mode is **not supported** for upgrades yet and will be supported in the future. Use `eoa` or `smart-account` mode instead. ## Frequently Asked Questions Yes. Pass multiple chain IDs in the `chainIds` array: ```json theme={null} { "chainIds": [8453, 10, 137] } ``` All upgrades will be batched into a single supertransaction. Currently, only upgrading from **v2.1.0** to **v2.2.1** is supported: * **v2.1.0** - Legacy version (source) * **v2.2.1** - Current version (target) No. Once upgraded to v2.2.1, accounts cannot be downgraded. However, this is generally not needed as v2.2.1 is backward compatible. ## Next Steps * Learn about [using upgraded accounts with accountAddress](/supertransaction-api/endpoints/quote#account-address-override) * Check [supported chains](/contracts-and-audits/supported-chains) * Explore [execution modes](/supertransaction-api/execution-modes/choose-execution-mode) # Cross-Chain Swaps Source: https://docs.biconomy.io/swaps-trading/cross-chain-swap Swap tokens across different blockchains with a single signature Move tokens between any supported chains in one transaction. Biconomy automatically finds the best route through bridges and DEXs—your users just sign once. ## The Problem It Solves Without Biconomy, a cross-chain swap requires: 1. Approve token on source chain *(signature 1)* 2. Swap to bridge-compatible token *(signature 2)* 3. Bridge to destination chain *(wait, signature 3)* 4. Swap to desired token *(signature 4)* 5. Hope nothing fails in between **With Biconomy:** One signature. Biconomy orchestrates everything. ## How It Works The `/instructions/intent-simple` endpoint handles cross-chain swaps automatically: ```typescript theme={null} { type: '/instructions/intent-simple', data: { srcChainId: 8453, // Source: Base dstChainId: 42161, // Destination: Arbitrum srcToken: USDC_BASE, // What user has dstToken: ETH_ARBITRUM, // What user wants amount: '100000000', // 100 USDC slippage: 0.01 // 1% slippage tolerance } } ``` Biconomy figures out the optimal path: * **Same-chain?** Routes through DEXs (Uniswap, Curve, etc.) * **Cross-chain?** Combines bridges (Across, LiFi) with DEX swaps * **Complex routes?** Handles multi-hop paths automatically ## Complete Example Swap 500 USDC on Base → ETH on Arbitrum: ```typescript theme={null} import { parseUnits, formatUnits } from 'viem'; const USDC_BASE = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'; const ETH_ARBITRUM = '0x0000000000000000000000000000000000000000'; // Native ETH // Step 1: Get quote const quote = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': 'YOUR_API_KEY' }, body: JSON.stringify({ mode: 'smart-account', ownerAddress: userAddress, feeToken: { address: USDC_BASE, chainId: 8453 }, composeFlows: [{ type: '/instructions/intent-simple', data: { srcChainId: 8453, dstChainId: 42161, srcToken: USDC_BASE, dstToken: ETH_ARBITRUM, amount: parseUnits('500', 6).toString(), slippage: 0.01 } }] }) }).then(r => r.json()); // Step 2: Review the route const swap = quote.returnedData[0]; console.log('Route:', swap.route.summary); console.log('Expected ETH:', formatUnits(swap.outputAmount, 18)); console.log('Min ETH:', formatUnits(swap.minOutputAmount, 18)); console.log('Est. time:', swap.route.estimatedTime, 'seconds'); console.log('Fee:', formatUnits(quote.fee.amount, 6), 'USDC'); // Step 3: Sign and execute const signature = await signPayload(quote); const result = await fetch('https://api.biconomy.io/v1/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...quote, payloadToSign: [{ ...quote.payloadToSign[0], signature }] }) }).then(r => r.json()); console.log('Supertransaction:', result.supertxHash); ``` ## Understanding Route Data The API returns detailed routing information: ```json theme={null} { "returnedData": [{ "outputAmount": "250000000000000000", "minOutputAmount": "247500000000000000", "route": { "summary": "lifi[uniswap] => across[SpokePoolV3]", "type": "DIRECT", "steps": [ { "type": "swap", "protocol": "lifi", "sources": ["uniswap"], "srcChainId": 8453, "dstChainId": 8453 }, { "type": "bridge", "protocol": "across", "srcChainId": 8453, "dstChainId": 42161 } ], "totalGasFeesUsd": 0.05, "totalBridgeFeesUsd": 0.12, "estimatedTime": 120 } }] } ``` | Field | Description | | ----------------- | ----------------------------------- | | `outputAmount` | Expected tokens to receive (in wei) | | `minOutputAmount` | Guaranteed minimum after slippage | | `route.summary` | Human-readable path description | | `route.steps` | Each hop in the route | | `estimatedTime` | Expected completion time (seconds) | ## Supported Chains | Chain | Chain ID | Status | | --------- | -------- | ------ | | Ethereum | 1 | ✅ | | Base | 8453 | ✅ | | Arbitrum | 42161 | ✅ | | Optimism | 10 | ✅ | | Polygon | 137 | ✅ | | BSC | 56 | ✅ | | Avalanche | 43114 | ✅ | See the [full supported chains list](/contracts-and-audits/supported-chains) for all 35+ networks. ## Provider Control Control which bridges and DEXs are used: ```typescript theme={null} { type: '/instructions/intent-simple', data: { srcChainId: 8453, dstChainId: 42161, srcToken: USDC, dstToken: ETH, amount: '100000000', slippage: 0.01, // Only use specific providers allowBridgeProviders: 'across', allowSwapProviders: 'lifi,uniswap', // Or exclude specific providers denyBridgeProviders: 'some-bridge', denySwapProviders: 'gluex' } } ``` ## Handling Slippage Cross-chain swaps have more price uncertainty. Set slippage appropriately: ```typescript theme={null} // Stablecoin to stablecoin (low volatility) slippage: 0.005 // 0.5% // Stable to volatile token slippage: 0.01 // 1% // Volatile to volatile, long bridge time slippage: 0.03 // 3% ``` Always check `minOutputAmount` before executing. If it's below your user's acceptable minimum, show a warning or reject the trade. ## Error Handling ```typescript theme={null} const quote = await fetch('https://api.biconomy.io/v1/quote', {...}) .then(r => r.json()); // Check for routing errors if (quote.error) { if (quote.error.includes('No route found')) { // Token pair not supported or liquidity too low console.log('Try different tokens or reduce amount'); } return; } // Validate output is acceptable const minOutput = BigInt(quote.returnedData[0].minOutputAmount); const userMinimum = parseUnits('0.24', 18); // User's minimum acceptable if (minOutput < userMinimum) { console.log('Output too low after slippage'); return; } ``` ## Next Steps Remove gas friction from swaps Full API tutorial with all options # Gasless Token Swaps Source: https://docs.biconomy.io/swaps-trading/gasless-swap Execute token swaps without paying gas fees Swap tokens without users needing ETH or native tokens for gas. Biconomy handles gas abstraction—users can pay fees in the token they're swapping, or you can sponsor gas entirely. ## How It Works User selects tokens and amount to swap Biconomy finds optimal route across DEXs and bridges Single signature approves the entire swap—no separate gas transaction Biconomy relayers execute and pay gas, deducting fee from swap or your sponsorship ## Quick Example Swap 100 USDC on Base to USDT on Optimism—completely gasless: ```typescript theme={null} // 1. Request a quote const quote = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: 'smart-account', ownerAddress: '0xYourAddress', // No feeToken = sponsored (gasless) composeFlows: [{ type: '/instructions/intent-simple', data: { srcChainId: 8453, // Base dstChainId: 10, // Optimism srcToken: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', // USDC dstToken: '0x94b008aa00579c1307b0ef2c499ad98a8ce58e58', // USDT amount: '100000000', // 100 USDC (6 decimals) slippage: 0.01 // 1% } }] }) }).then(r => r.json()); // 2. Sign the payload const signature = await wallet.signMessage({ message: quote.payloadToSign[0].signablePayload.message }); // 3. Execute const result = await fetch('https://api.biconomy.io/v1/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...quote, payloadToSign: [{ ...quote.payloadToSign[0], signature }] }) }).then(r => r.json()); console.log('Track at:', `https://meescan.biconomy.io/details/${result.supertxHash}`); ``` ## Two Ways to Go Gasless **Best for:** Onboarding flows, premium users, promotional campaigns Omit the `feeToken` parameter—gas is paid from your sponsorship account: ```typescript theme={null} { mode: 'smart-account', ownerAddress: userAddress, // No feeToken = use sponsorship composeFlows: [...] } ``` Requires an API key with sponsorship enabled. Get one at [dashboard.biconomy.io](https://dashboard.biconomy.io) **Best for:** Regular transactions where users cover costs Set `feeToken` to deduct gas from the token being swapped: ```typescript theme={null} { mode: 'smart-account', ownerAddress: userAddress, feeToken: { address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', // USDC chainId: 8453 }, composeFlows: [...] } ``` User still signs once—fee is automatically deducted from their USDC. ## Works with Any Wallet Use `mode: 'smart-account'` for Nexus or ERC-4337 accounts. Native gas abstraction. Use `mode: 'eoa'` for MetaMask, Rabby, etc. Requires `fundingTokens` parameter. ### EOA Example (MetaMask, Rabby, etc.) ```typescript theme={null} const quote = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: 'eoa', ownerAddress: userAddress, fundingTokens: [{ tokenAddress: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', chainId: 8453, amount: '100000000' }], feeToken: { address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', chainId: 8453 }, composeFlows: [{ type: '/instructions/intent-simple', data: { srcChainId: 8453, dstChainId: 10, srcToken: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', dstToken: '0x94b008aa00579c1307b0ef2c499ad98a8ce58e58', amount: '100000000', slippage: 0.01 } }] }) }).then(r => r.json()); ``` ## Understanding the Response The quote response tells you exactly what will happen: ```json theme={null} { "fee": { "amount": "50000", // Fee in token units "token": "0x833589...", // Fee token address "chainId": 8453 }, "returnedData": [{ "outputAmount": "99500000", // Expected output "minOutputAmount": "98505000", // After slippage "route": { "summary": "lifi[uniswap] => across", "estimatedTime": 120 } }] } ``` | Field | What It Means | | ----------------- | --------------------------------------- | | `fee.amount` | How much gas costs (in fee token units) | | `outputAmount` | Expected tokens user will receive | | `minOutputAmount` | Minimum after slippage protection | | `route.summary` | Which DEXs/bridges will be used | ## Next Steps Deep dive into multi-chain swap mechanics Learn the complete quote → sign → execute flow # Swaps & Trading Source: https://docs.biconomy.io/swaps-trading/index Build cross-chain swaps, limit orders, and advanced trading experiences Build trading experiences that rival centralized exchanges. Execute swaps across any chain, implement advanced order types, and route through deep liquidity—all without writing smart contracts. Automatically find the best prices across 200+ DEXs and aggregators Swap tokens across any supported chain with a single signature Enable limit orders, TWAPs, and conditional triggers ## Start Building Execute token swaps without users paying gas fees Swap tokens across different chains in a single transaction ## What You Can Build ### Cross-Chain Swap Interface Let users swap any token to any token across chains. Biconomy handles routing, bridging, and execution. ```typescript theme={null} // Swap USDC on Base → ETH on Arbitrum const quote = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', body: JSON.stringify({ mode: 'eoa', ownerAddress: userAddress, composeFlows: [{ type: '/instructions/intent-simple', data: { srcChainId: 8453, // Base dstChainId: 42161, // Arbitrum srcToken: USDC_BASE, dstToken: ETH_ARBITRUM, amount: '100000000', // 100 USDC slippage: 0.01 } }] }) }); ``` ### Limit Order System Implement conditional execution that triggers when price targets are met. Execute swaps only when tokens reach target prices Split large orders across time to minimize slippage ### Trading Bot Infrastructure Power high-frequency trading strategies with MEE's low-latency execution. * **Batch multiple swaps** into single transactions * **Arbitrage across chains** with single-signature execution * **React to on-chain events** with conditional triggers ## Key Features Biconomy aggregates liquidity from major DEXs and finds optimal routes: * Uniswap, SushiSwap, Curve, Balancer * 1inch, Paraswap, 0x aggregators * Cross-chain bridges (LiFi, Across, Relay) Routes are optimized for best price, lowest gas, and fastest execution. No matter how complex the trade: * Multi-hop swaps across DEXs * Bridge + swap combinations * Batch orders across chains Users sign once. MEE handles all execution. Trading without gas friction: * Pay fees in the token being swapped * Sponsor gas for your users entirely * Works with EOA wallets (no smart account required) ## Integration Options REST API for quick integration. Best for backends and non-TypeScript environments. TypeScript SDK with full type safety. Best for frontend apps and custom logic. Most trading applications start with the Supertransaction API for rapid development, then add AbstractJS for advanced features like custom routing logic. # Limit Orders Source: https://docs.biconomy.io/swaps-trading/limit-order Build limit orders that execute when price targets are met Create limit orders that automatically execute when tokens reach your target price. Biconomy's conditional execution monitors on-chain prices and triggers your swap only when conditions are satisfied. ## How Limit Orders Work Set the price target your order should wait for Create the swap that executes when condition is met Sign once—MEE monitors and executes when ready When price hits target, swap executes automatically ``` Submit Order → MEE Monitors Price → Target Hit? Execute Swap ↓ Keeps checking until timeout or execution ``` ## Quick Example Wait for WETH to drop below \$3,000 USDC, then buy: ```typescript theme={null} import { createMeeClient, createCondition, ConditionType, toMultiplier } from '@biconomy/abstractjs'; import { base } from 'viem/chains'; import { parseUnits } from 'viem'; // Token addresses on Base const USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; const WETH = '0x4200000000000000000000000000000000000006'; // Chainlink price feed for ETH/USD on Base const ETH_USD_FEED = '0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70'; // Chainlink Aggregator ABI (minimal) const aggregatorAbi = [ { name: 'latestAnswer', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'int256' }] } ] as const; // Create condition: Execute when ETH price <= $3000 const priceCondition = createCondition({ targetContract: ETH_USD_FEED, functionAbi: aggregatorAbi, functionName: 'latestAnswer', args: [], value: parseUnits('3000', 8), // Chainlink uses 8 decimals type: ConditionType.LTE // Less than or equal }); // Build the swap instruction with condition const limitOrder = await account.buildComposable({ type: 'intent-simple', data: { srcChainId: base.id, dstChainId: base.id, srcToken: USDC, dstToken: WETH, amount: parseUnits('1000', 6), // 1000 USDC slippage: 0.01, conditions: [priceCondition] // ← Only execute at target price } }); // Get quote and execute const quote = await meeClient.getFusionQuote({ trigger: { chainId: base.id, tokenAddress: USDC, amount: parseUnits('1000', 6) }, instructions: [limitOrder], feeToken: { chainId: base.id, address: USDC }, // Order expires in 24 hours upperBoundTimestamp: Math.floor(Date.now() / 1000) + 86400 }); const { hash } = await meeClient.executeFusionQuote({ fusionQuote: quote }); console.log('Limit order submitted:', hash); console.log('Track at:', `https://meescan.biconomy.io/details/${hash}`); ``` ## Condition Types for Trading | Type | Operator | Trading Use Case | | ----- | -------- | ------------------------------- | | `LTE` | ≤ | Buy when price drops to target | | `GTE` | ≥ | Sell when price rises to target | | `EQ` | = | Execute at exact price (rare) | ## Buy Limit Order (Price Drops) Wait for a token to become cheaper before buying: ```typescript theme={null} // Buy ETH when price drops to $2,800 or below const buyCondition = createCondition({ targetContract: ETH_USD_FEED, functionAbi: aggregatorAbi, functionName: 'latestAnswer', args: [], value: parseUnits('2800', 8), type: ConditionType.LTE // Execute when price <= $2800 }); ``` ## Sell Limit Order (Price Rises) Wait for a token to appreciate before selling: ```typescript theme={null} // Sell ETH when price rises to $4,000 or above const sellCondition = createCondition({ targetContract: ETH_USD_FEED, functionAbi: aggregatorAbi, functionName: 'latestAnswer', args: [], value: parseUnits('4000', 8), type: ConditionType.GTE // Execute when price >= $4000 }); const sellOrder = await account.buildComposable({ type: 'intent-simple', data: { srcChainId: base.id, dstChainId: base.id, srcToken: WETH, dstToken: USDC, amount: parseUnits('0.5', 18), // 0.5 ETH slippage: 0.01, conditions: [sellCondition] } }); ``` ## Using DEX Pool Prices For tokens without Chainlink feeds, read price directly from a DEX pool: ```typescript theme={null} // Uniswap V3 pool slot0 for price const poolAbi = [ { name: 'slot0', type: 'function', stateMutability: 'view', inputs: [], outputs: [ { name: 'sqrtPriceX96', type: 'uint160' }, { name: 'tick', type: 'int24' }, // ... other fields ] } ] as const; // Use tick as price proxy (higher tick = higher price for token1) const poolPriceCondition = createCondition({ targetContract: UNISWAP_POOL_ADDRESS, functionAbi: poolAbi, functionName: 'slot0', args: [], value: targetTick, type: ConditionType.LTE }); ``` ## Multiple Conditions (Safety Checks) Combine price target with safety conditions: ```typescript theme={null} const limitOrder = await account.buildComposable({ type: 'intent-simple', data: { srcChainId: base.id, dstChainId: base.id, srcToken: USDC, dstToken: WETH, amount: parseUnits('1000', 6), slippage: 0.01, conditions: [ // Price condition createCondition({ targetContract: ETH_USD_FEED, functionAbi: aggregatorAbi, functionName: 'latestAnswer', args: [], value: parseUnits('3000', 8), type: ConditionType.LTE }), // Safety: Ensure we have enough balance createCondition({ targetContract: USDC, functionAbi: erc20Abi, functionName: 'balanceOf', args: [userAddress], value: parseUnits('1000', 6), type: ConditionType.GTE }) ] } }); ``` When multiple conditions are specified, ALL must be satisfied before execution (AND logic). ## Setting Order Expiration Always set a timeout so orders don't stay pending forever: ```typescript theme={null} const quote = await meeClient.getFusionQuote({ trigger: { chainId: base.id, tokenAddress: USDC, amount }, instructions: [limitOrder], feeToken: { chainId: base.id, address: USDC }, // Order expiration options upperBoundTimestamp: Math.floor(Date.now() / 1000) + 86400 // 24 hours }); ``` | Duration | Seconds | Use Case | | -------- | ------- | ------------------- | | 1 hour | 3600 | Short-term scalping | | 24 hours | 86400 | Day trading | | 7 days | 604800 | Swing trading | ## Tracking Order Status Monitor your limit order until execution: ```typescript theme={null} const { hash } = await meeClient.executeFusionQuote({ fusionQuote: quote }); // Poll for status const checkStatus = async () => { const status = await fetch( `https://api.biconomy.io/v1/status/${hash}` ).then(r => r.json()); console.log('Status:', status.status); // PENDING = waiting for conditions // EXECUTING = conditions met, executing // COMPLETED = order filled // EXPIRED = timeout reached return status; }; // Check every 30 seconds const interval = setInterval(async () => { const status = await checkStatus(); if (status.status !== 'PENDING') { clearInterval(interval); } }, 30000); ``` ## Best Practices Chainlink feeds are most reliable. DEX pools can be manipulated. Don't leave orders pending indefinitely—always set expiration. Chainlink ETH/USD uses 8 decimals. Match your condition values. Price can move between condition check and execution. ## Common Price Feeds (Base) | Asset | Chainlink Feed Address | Decimals | | -------- | -------------------------------------------- | -------- | | ETH/USD | `0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70` | 8 | | BTC/USD | `0x64c911996D3c6aC71E9b8e0D8d8e81E3C6E0E5b3` | 8 | | USDC/USD | `0x7e860098F58bBFC8648a4311b374B1D669a2bc6B` | 8 | Find more Chainlink feeds at [data.chain.link](https://data.chain.link) ## Next Steps Execute limit orders without gas fees Create cross-chain limit orders # Speeding Up Quotes Source: https://docs.biconomy.io/swaps-trading/speeding-up-quotes Get faster quote responses by using fast-quote mode By default, the Biconomy API performs an exhaustive query across all underlying liquidity providers (DEXs, aggregators, bridges) to find the optimal route for your swap. While this ensures the best possible price, it takes longer to respond. For applications where speed is more important than finding the absolute best price, you can use **fast-quote mode** to get the first viable quote immediately. ## Using Fast-Quote Mode Add `routeSelectionMode: "fast-quote"` to the `data` field of your request: ```typescript theme={null} const quote = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: 'eoa', ownerAddress: userAddress, composeFlows: [{ type: '/instructions/intent-simple', data: { srcChainId: 8453, dstChainId: 42161, srcToken: USDC_BASE, dstToken: ETH_ARBITRUM, amount: '100000000', slippage: 0.01, routeSelectionMode: "fast-quote" // Return first viable quote } }] }) }); ``` ## Comparison | Mode | Behavior | Response Time | Price Optimization | | -------------- | -------------------------------------- | ------------- | ----------------------------------- | | **Default** | Queries all providers, compares routes | Slower | Best price guaranteed | | **fast-quote** | Returns first viable route | Faster | Good price, not necessarily optimal | ## When to Use Fast-Quote * Real-time price displays * Quick previews in UI * High-frequency quote refreshes * Time-sensitive applications * Large trade amounts * Final execution quotes * Price-sensitive users * Low-frequency requests ## Best Practice: Preview Then Execute A common pattern is to use fast-quote for UI previews, then fetch a full quote before execution: ```typescript theme={null} // Fast quote for immediate UI feedback const previewQuote = await getQuote({ ...params, data: { ...data, routeSelectionMode: "fast-quote" } }); // Show preview to user displayQuote(previewQuote); // When user clicks "Swap", get optimized quote for execution const executionQuote = await getQuote({ ...params // No routeSelectionMode = exhaustive search }); // Execute with best price await executeQuote(executionQuote); ``` Fast-quote mode typically reduces response times significantly, especially for cross-chain swaps where multiple bridge providers need to be queried. # Account Migration Guide Source: https://docs.biconomy.io/upgrade-migrate/index Complete guide to migrate between different versions of Biconomy smart accounts while preserving addresses and assets # Account Migration Guide > Complete guide to migrate between different versions of Biconomy smart accounts while preserving addresses and assets This section covers how to migrate between different versions of Biconomy smart accounts. AbstractJS provides migration paths that preserve your users' account addresses, balances, and transaction history while upgrading to newer implementations. ## Key Migration Concepts The most important concept in any smart account migration is **address preservation**. When you migrate a smart account: The account's address remains unchanged All assets and tokens stay with the account External contracts and services that interact with the account continue to work User experience remains seamless with no visible interruption AbstractJS supports the following migration paths: [**V2 to Nexus Migration**](/upgrade-migrate/v2-to-nexus): Migrate from Biconomy Smart Account v2 to Nexus accounts [**Nexus to Nexus Migration**](/upgrade-migrate/upgrade-mee-suite): Upgrade between different versions of Nexus accounts ## Why User Address Persistence Is Critical Smart account addresses are the cornerstone of user identity and asset ownership in blockchain applications. It's **absolutely essential** to maintain these addresses during and after migration for several key reasons: All user assets (tokens, NFTs, etc.) are associated with their account address. If this address changes or access is lost: Users lose access to all their funds and digital assets Recovery may be impossible, resulting in permanent loss **To avoid that, the address should be kept unchanged after the upgrade** Smart account addresses establish relationships with other contracts and services: Token allowances and approvals are tied to specific addresses DAO memberships, governance rights, and voting power Access control in permissioned systems ## Best Practices for Migration After migration, you **must always** use the `accountAddress` parameter when recreating the account instance: ```typescript theme={null} theme={null} // CORRECT: Specifying accountAddress preserves access to the existing account const nexusAccount = await toNexusAccount({ signer: eoaAccount, chainConfiguration: { chain: baseChain, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0), accountAddress: "0xUserExistingAccountAddress" // CRITICAL! } }); // INCORRECT: Without accountAddress, a new account is created // This will result in loss of access to the existing account and its funds! const newAccount = await toNexusAccount({ signer: eoaAccount, chainConfiguration: { chain: baseChain, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0) }, }); ``` Always store user account addresses in a persistent database or storage: Server-side database for backend applications Local storage or secure storage for frontend applications Consider multiple backup locations for critical applications Run the migration on testnets first Perform small test transactions after migration Verify all functionality works as expected with the migrated account If your application has end users: Communicate the migration timeline in advance Explain that the migration preserves all assets and functionality Provide clear instructions for any user-initiated steps Keep records of: The account version before migration The account version after migration Timestamps of when migrations occurred ## Framework Support AbstractJS provides comprehensive migration support with automatic handling of: Account implementation upgrades Initialization with the correct parameters Module installation and configuration Compatible validator setup ## Migration Links Choose the appropriate migration path based on your current account type: [Migrate from V2 to Nexus](/upgrade-migrate/v2-to-nexus) [Upgrade Nexus Accounts](/upgrade-migrate/upgrade-mee-suite) For additional support with migrations, please refer to the [AbstractJS GitHub repository](https://github.com/bcnmy/abstractjs) or join our [Discord community](https://discord.gg/biconomy). # MEE Versions Source: https://docs.biconomy.io/upgrade-migrate/mee-versions Complete guide to MEE versions and their capabilities # MEE Versions The **Modular Execution Environment (MEE)** supports multiple versions of its contract suite. Each version introduces new capabilities around **smart accounts**, **validator configuration**, and **composability**. This guide helps SDK developers understand and select the appropriate version for their use case. ## Versions Table The table below outlines all supported MEE versions and their core capabilities. Use this to determine the best version for your project based on EVM support, composability, and ERC-7702 compatibility. | Version | Nexus Version | ERC-7702 Support | Composability | Notes | | ------- | ------------- | ---------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `2.3.0` | 1.3.1 | ✅ Yes | ✅️ Native | **Experimental and unaudited** Adds new `safe-sa` fusion mode that enables Safw accounts with multichain orchestration features | | `2.2.1` | 1.3.1 | ✅ Yes | ✅️ Native | **Latest** Adds new [composability features](/new/getting-started/understanding-runtime-injection) and EIP-712 typed data sign for some stx modes | | `2.1.0` | 1.2.0 | ✅ Yes | ✅ Native | **SDK default** Allows delegated/upgraded EOAs to directly own smart accounts in Nexus 1.2.0. | | `2.0.0` | 1.2.0 | ✅ Yes | ✅ Native | Supports ERC-7702 flows but **does not** support delegated/upgraded EOAs as direct owners of smart accounts. | | `1.1.0` | 1.0.2 | ❌ No | ⚠️ Manual | Composability must be installed manually (handled by the SDK). Best for EVM Paris–only chains with limited opcode support. | | `1.0.0` | 1.0.2 | ❌ No | ⚠️ Manual | Same as `1.1.0`. Composability module installed via SDK. | ## Version Breakdown
!!

Experimental and unaudited

Unlocks cross-chain orchestration for Safe accounts

**Key Features:** * Safe accounts can now be used as master accounts in Fusion flow as well as EOAs. * Flow is the same as for EOA's, just instead of one master EOA signature, the proper multisig is required to initiate a SuperTxn * Safe accounts should be deployed at the same address on all the chains involved in the SuperTxn (source and destination chains) **Recommended for projects on early stages** looking to attract the large Safe multisig accounts userbase.

Latest with reachest features

Built on Nexus 1.3.1 with extended composability features

**Key Features:** * Adds native token runtime injection feature * Adds runtime injection via custom static call (inject ANY return value as an input param at the runtime) * Users signs EIP-712 Data Struct instead of blind Stx hash for the `smart-account` mode * Allows initializing new Nexus ERC-7702 account via relayer with an owner issued signature (gasless ERC-7702 delegation) **Recommended for projects** seeking the latest features for the [runtime injection](new/getting-started/understanding-runtime-injection) and wanting to provide transparent EIP-712 sig requests for users.

SDK default

Built on Nexus 1.2.0 with cutting-edge capabilities

**Key Features:** * Fully supports **ERC-7702**, including **delegated/upgraded EOAs** as direct owners of Smart Contract Accounts (SCAs) * Features **native composability** — no need for manual module installation * Built on Nexus `1.2.0` with pre-installed MEE K1 validator **Recommended for all new projects** seeking the simplest and most battle-tested developer experience.
2.0

Native Composability, No Delegated EOAs

ERC-7702 support with composability built-in

**Key Features:** * Supports **ERC-7702**, but **does not allow delegated or upgraded EOAs** to directly own smart accounts * Includes **native composability**, reducing setup complexity Suitable for applications that do not require EOAs as direct smart account owners but want full ERC-7702 support.
1.1

EVM Paris Compatibility with Manual Composability

For chains limited to Paris upgrade opcodes

**Key Features:** * Does **not support ERC-7702** * Requires **manual installation** of composability modules, though the SDK automates this during deployment Targeted for EVM chains limited to Paris upgrade opcodes (no PUSH0, MCOPY, or TSTORE). Good choice for compatibility-constrained environments.
1.0

Supports all EVM chains prior to the Cancun upgrade

Legacy compatibility for older EVM environments

**Key Features:** * No ERC-7702 support * Requires manual installation of composability modules, automatically handled by the SDK * Compatible with all EVM environments **below the Cancun upgrade** Suitable for legacy or specialized chains where native composability isn't available but can be enabled via modules.
## Recommendation For most development needs, **version >= `2.1.0` is strongly recommended** due to its full support for delegated EOAs and native composability. Version `2.2.1` is currently latest audited stable version with the reachest features. Earlier versions remain available for legacy or specialized chains but require additional setup steps handled by the SDK. ```typescript Example usage in SDK theme={null} theme={null} import { MEEVersion } from "@your-sdk/core"; const version = MEEVersion.V2_1_0; ``` ## How to Find Your Current Nexus Version Not sure which Nexus version your smart account is using? Follow these simple steps to find out: **Locate your existing Nexus smart account address** on the specific blockchain you're working with. **Open the block explorer** for that chain (e.g., Etherscan, Polygonscan) and paste the smart account address into the search bar. Navigate to the **Contracts** tab on the explorer page. If the contract is a **proxy** and the proxy contract isn't verified yet, you'll need to verify it first. (If "Read as proxy" option isn't available, this usually means verification is needed.) Once verified, go to the **Read as proxy** section and call the `getImplementation` function. This will return the address of the actual implementation contract your smart account is using. Compare this implementation contract address against the addresses listed on our [Contracts and Audits](/contracts-and-audits) page. This will help you identify exactly which MEE version you are using. If you need further assistance, feel free to join our [Discord community](https://discord.gg/biconomy) and ask for help! Keeping track of your Nexus version is key for smooth upgrades and compatibility checks. ## Not Sure Which Version to Choose? If you're still confused about which MEE version fits your needs, or if you're unsure about your current version for migration, don't worry! Join our [Discord community](https://discord.gg/biconomy) to ask questions and get real-time support from the team and other developers. We're here to help you choose the best path forward! # Upgrade MEE Suite Source: https://docs.biconomy.io/upgrade-migrate/upgrade-mee-suite Complete guide to migrate to the newer version of MEE stack # Upgrade to the newer MEE contracts > Complete guide to migrate to the newer version of MEE stack. This guide explains how to upgrade to the latest MEE contracts suite. This involves upgrading your Nexus smart accounts from older versions to the latest implementation. The migration process preserves your account's address, balance, and history while providing access to new features, security fixes, and performance improvements. ## Why Upgrade? Upgrading your Nexus smart account is recommended for several important reasons: The latest implementation includes critical security enhancements. Access to the newest MEE capabilities. Enhanced gas efficiency and transaction processing. Ensure you can leverage all the latest features of AbstractJS SDK and MEE infra. ## Migration Process Overview The migration consists of these key steps: Install the latest SDK version Use your existing account address Call `upgradeToAndCall()` via Supertransaction Test the account by executing a simple Supertransaction Adjust your application to use the latest features ## Important Notes for Migration **CRITICAL**: After migration, you **must store your account address** and use it with the `accountAddress` parameter in `toNexusAccount()` or `toMultichainNexusAccount()` for all future interactions. This is how you'll maintain access to your upgraded account and its balances. ## Step 1: Update AbstractJS Package First, update to the latest version of the AbstractJS SDK: ```bash theme={null} # npm npm update @biconomy/abstractjs # yarn yarn upgrade @biconomy/abstractjs # pnpm pnpm update @biconomy/abstractjs # bun bun update @biconomy/abstractjs ``` ## Step 2: Connect to Your Existing Account When connecting to an account created with an older SDK version, you'll need your existing account address: ```typescript theme={null} import { createMeeClient, getMEEVersion, MEEVersion, toMultichainNexusAccount, } from "@biconomy/abstractjs"; import { Hex, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { base } from "viem/chains"; const privateKey: Hex = "0xyour_private_key"; const eoaAccount = privateKeyToAccount(privateKey); // Initialize mcNexus with current version config const mcNexus = await toMultichainNexusAccount({ signer: eoaAccount, chainConfigurations: [ { // Your preferred chains chain: base, // Use a reliable RPC here to avoid RPC issues transport: http(), // Use the current MEE version here version: getMEEVersion(MEEVersion.V2_0_0), // This is completely optional if this is the first time your upgrading your Smart account. // If you have already upgraded from some X MEE version to Y MEE version ? This field is mandatory accountAddress: '0xyour_sca_address', }, ], }); // Create MEE client const meeClient = await createMeeClient({ account: mcNexus, }); ``` Note: After upgrading, use your existing accountAddress for all SDK operations. ## Step 3: Upgrade the Smart Account Call the `upgradeToAndCall()` method to upgrade your account to the latest implementation. In most cases, your account is going to use newer MEE K1 Validator, so you'll have to re-initialize it while upgrading the account. ```typescript theme={null} /** * Generate upgrade init data for Nexus account upgrade with new validator initialization */ export function getUpgradeInitData( ownerAddress: Address, bootstrapAddress: Address, ): `0x${string}` { // Step 1: Encode initNexusWithDefaultValidator call with owner address const bootstrapCall = encodeFunctionData({ abi: NexusBootstrapAbi, functionName: 'initNexusWithDefaultValidator', args: [ownerAddress], }); // Step 2: Package bootstrap address + call as tuple const bootstrapData = encodeAbiParameters( [ { name: 'bootstrap', type: 'address' }, { name: 'initData', type: 'bytes' }, ], [bootstrapAddress, bootstrapCall] ); // Step 3: Wrap in initializeAccount call const upgradeInitData = encodeFunctionData({ abi: parseAbi(['function initializeAccount(bytes initData)']), functionName: 'initializeAccount', args: [bootstrapData], }); return upgradeInitData; } // New MEE version config for account upgrades const newMEEVersionConfig = getMEEVersion(MEEVersion.V2_1_0); const newImplementationAddress = newMEEVersionConfig.implementationAddress; const newBootstrapAddress = newMEEVersionConfig.bootStrapAddress; const upgradeInitData = getUpgradeInitData( eoaAccount.address, newBootstrapAddress ); const upgradeAbi = parseAbi([ "function upgradeToAndCall(address newImplementation, bytes data)", ]); // Build composable instruction to upgrade your Nexus smart account const instructions = await mcNexus.buildComposable({ type: "default", data: { // Smart account address to: mcNexus.addressOn(base.id, true), functionName: "upgradeToAndCall", args: [newImplementationAddress, upgradeInitData], abi: upgradeAbi, chainId: base.id, }, }); const USDC_BASE: Address = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"; // fee token const triggerAmount = 1n; // This example follows a fusion mode for upgrading the Nexus smart account via Supertransaction // If you wanted to upgrade your account via smart account mode ? Please use getQuote and executeQuote flows const quote = await meeClient.getFusionQuote({ instructions, trigger: { tokenAddress: USDC_BASE, chainId: base.id, amount: triggerAmount, }, feeToken: { address: USDC_BASE, chainId: base.id, }, simulation: { simulate: true, }, }); // Executes the supertransaction const { hash: supertxHash } = await meeClient.executeFusionQuote({ fusionQuote: quote, }); // Wait for the supertransaction const receipt = await meeClient.waitForSupertransactionReceipt({ hash: supertxHash, }); ``` Sometimes, your account will keep using the same validator. For example, if you are upgrading from MEE 1.0.0 to MEE 2.0.0 In this case, you do not need to re-initialize the validator module. So just call `upgradeToAndCall()` without initData. ```typescript theme={null} const upgradeAbi = parseAbi([ "function upgradeToAndCall(address newImplementation, bytes data)", ]); // Build composable instruction to upgrade Nexus const instructions = await mcNexus.buildComposable({ type: "default", data: { // Smart account address to: mcNexus.addressOn(base.id, true), functionName: "upgradeToAndCall", args: [newImplementationAddress, '0x'], abi: upgradeAbi, chainId: base.id, }, }); const USDC_BASE: Address = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"; // fee token const triggerAmount = 1n; // This example follows a fusion mode for upgrading the Nexus smart account via Supertransaction // If you wanted to upgrade your account via smart account mode ? Please use getQuote and executeQuote flows const quote = await meeClient.getFusionQuote({ instructions, trigger: { tokenAddress: USDC_BASE, chainId: base.id, amount: triggerAmount, }, feeToken: { address: USDC_BASE, chainId: base.id, }, simulation: { simulate: true, }, }); // Executes the supertransaction const { hash: supertxHash } = await meeClient.executeFusionQuote({ fusionQuote: quote, }); // Wait for the supertransaction const receipt = await meeClient.waitForSupertransactionReceipt({ hash: supertxHash, }); ``` However, if you want, you may still provide initdata, which can make any additional configuration you may want at the upgrade time. For example, you may want to uninstall previously installed validators or install any additional validators you like, for example Smart Sessions. ## Step 4: Verify the Upgrade After upgrading, verify that your account works correctly by performing a test supertransaction: ```typescript theme={null} import { createMeeClient, getMEEVersion, MEEVersion, toMultichainNexusAccount, } from "@biconomy/abstractjs"; import { Address, Hex, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { base } from "viem/chains"; const privateKey: Hex = "0xyour_private_key"; const storedSmartAccountAddress: Address = "0xyour_sca_address"; const eoaAccount = privateKeyToAccount(privateKey); // Initialize mcNexus with new version config const mcNexus = await toMultichainNexusAccount({ signer: eoaAccount, chainConfigurations: [ { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0), // This is essential accountAddress: storedSmartAccountAddress, }, ], }); // Create MEE client const meeClient = await createMeeClient({ account: mcNexus, }); const USDC_BASE: Address = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"; // fee token const amount = 1n; const instructions = await mcNexus.buildComposable({ type: "transfer", data: { tokenAddress: USDC_BASE, chainId: base.id, amount, recipient: eoaAccount.address, }, }); // Get quotes const quote = await meeClient.getFusionQuote({ instructions, trigger: { tokenAddress: USDC_BASE, chainId: base.id, amount, }, feeToken: { address: USDC_BASE, chainId: base.id, }, }); // Executes the supertransaction const { hash: supertxHash } = await meeClient.executeFusionQuote({ fusionQuote: quote, }); // Wait for the supertransaction const receipt = await meeClient.waitForSupertransactionReceipt({ hash: supertxHash, }); ``` ## Step 5: Store Your Account Address After successful migration, you **MUST store your account address** for future use: ```typescript theme={null} // Get and store your account address const accountAddress = mcNexus.addressOn(base.id, true); console.log("IMPORTANT - Store this account address:", accountAddress); // In your application, store this address // (database, local storage, user profile, etc.) storeAccountAddress(userIdentifier, accountAddress); ``` ## Post-Migration: Future Account Access After migration, **ALL** future interactions with your account must use the `accountAddress` parameter: ### CORRECT WAY to access your account after migration ```typescript theme={null} // CORRECT WAY to access your account after migration const mcNexus = await toMultichainNexusAccount({ signer: eoaAccount, chainConfigurations: [ { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0), accountAddress: "YOUR_STORED_ACCOUNT_ADDRESS" // This is essential }, ], }); ``` ### INCORRECT WAY this will create a new account, not access your existing one ```typescript theme={null} // INCORRECT WAY - this will create a new account, not access your existing one const mcNexus = await toMultichainNexusAccount({ signer: eoaAccount, chainConfigurations: [ { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0), // Missing account address field }, ], }); ``` ## Example Code A minimal working migration script is available on GitHub: [Click here](https://github.com/bcnmy/mee-upgrade) ## Troubleshooting If you encounter issues during the migration: Make sure you're re-initializing or not re-initializing the validator module depending on the version you are migrating from and to. See two tabs on the Step 3. Ensure the account has sufficient funds for gas or use sponsorship mode for gasless upgrades Verify the account is properly deployed on-chain Check that your application is using the latest SDK version Make sure you're using the correct account address in the `accountAddress` parameter Verify the upgrade transaction completed successfully Ensure you're using the exact same address as your pre-migration account ## Next Steps After successfully migrating your Nexus account: **STORE YOUR ACCOUNT ADDRESS** in your application's persistent storage **Update your application code** to use the `accountAddress` parameter in all future interactions **Test thoroughly** with real transactions to ensure everything works as expected By following this migration guide and properly storing your account address, you've successfully upgraded your Nexus account to the latest implementation while preserving its address, balance, and history. # Smart Account V2 → Nexus Source: https://docs.biconomy.io/upgrade-migrate/v2-to-nexus Complete guide to migrate your BiconomyV2 smart accounts to the newer Nexus smart accounts # Smart Account V2 → Nexus > Complete guide to migrate your BiconomyV2 smart accounts to the newer Nexus smart accounts This guide explains how to migrate your BiconomySmartAccountV2 smart accounts to the newer Nexus smart accounts. The migration process preserves your account's address, balance, and history while upgrading to Nexus's enhanced architecture. ## Why Migrate? Migrating from V2 to Nexus smart accounts provides several benefits: Improved security model with modular validators More gas-efficient transaction processing Access to the newest account abstraction capabilities Ensure your smart account remains compatible with the latest AbstractJS SDK ## Migration Process Overview The migration follows these steps: Connect to your existing V2 smart account If not already deployed. Ensure the account is deployed on-chain Update implementation and initialize the Nexus account Test the migrated account with a supertransaction Use the latest SDK to interact with the migrated account ## Prerequisites Before starting the migration, ensure you have the following: Update to the latest version of the AbstractJS SDK: ```bash theme={null} theme={null} npm install @biconomy/abstractjs ``` Make sure you have your EOA's private key and the V2 account address ## Step 1: Connect to Your V2 Account First, set up the necessary connections to your V2 smart account: ```typescript theme={null} theme={null} import { createWalletClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { baseSepolia } from "viem/chains"; import { createSmartAccountClient as createV2Client, BiconomySmartAccountV2, PaymasterMode } from "@biconomy/account"; import { getMEEVersion, MEEVersion } from "@biconomy/abstractjs"; import dotenv from "dotenv"; dotenv.config(); // Use the version that suits your needs from the list // See MEE Versioning const version = MEEVersion.V2_1_0; const versionConfig = getMEEVersion(version) // Define configuration variables const config = { // Chain and network information chain: baseSepolia, // EOA credentials eoaPrivateKey: process.env.EOA_PRIVATE_KEY, // Replace with your private key eoaAddress: process.env.EOA_ADDRESS, // Replace with your EOA address // Biconomy infrastructure URLs v2BundlerUrl: process.env.V2_BUNDLER_URL, // Replace with your V2 bundler URL nexusBundlerUrl: process.env.NEXUS_BUNDLER_URL, // Replace with your Nexus bundler URL // API keys paymasterApiKey: process.env.PAYMASTER_API_KEY, // Replace with your Paymaster API key // Nexus contract addresses nexusImplementationAddress: versionConfig.implementationAddress, nexusBootstrapAddress: versionConfig.bootStrapAddress, }; // Connect to your EOA const eoaAccount = privateKeyToAccount(config.eoaPrivateKey as `0x${string}`); const client = createWalletClient({ account: eoaAccount, chain: config.chain, transport: http(), }); // Connect to your V2 smart account const V2Account = await createV2Client({ signer: client, biconomyPaymasterApiKey: config.paymasterApiKey, bundlerUrl: config.v2BundlerUrl!, }); // Get V2 account address const V2AccountAddress = await V2Account.getAccountAddress(); console.log("V2 Account Address:", V2AccountAddress); ``` ## Step 2: Check Deployment Status Check if your V2 account is already deployed, and deploy it if necessary: ```typescript theme={null} theme={null} // Check if account is deployed const isDeployed = await V2Account.isAccountDeployed(); if (!isDeployed) { console.log("Account not deployed, deploying now..."); // Deploy the V2 account const deploymentResponse = await V2Account.sendTransaction([ { to: V2AccountAddress, value: 0n, data: "0x", }, ]); const { transactionHash } = await deploymentResponse.waitForTxHash(); console.log("V2 account deployment transaction hash:", transactionHash); } else { console.log("Account already deployed, proceeding with migration"); } ``` ## Step 3: Migrate to Nexus Now perform the migration by updating the implementation to Nexus and initializing the Nexus account: ```typescript theme={null} theme={null} import { encodeFunctionData, encodeAbiParameters } from "viem"; async function migrateToNexus(V2Account: BiconomySmartAccountV2) { const V2AccountAddress = await V2Account.getAccountAddress(); // Step 1: Update implementation to Nexus console.log("Preparing update implementation to Nexus..."); const updateImplementationCalldata = encodeFunctionData({ abi: [ { name: "updateImplementation", type: "function", stateMutability: "nonpayable", inputs: [{ type: "address", name: "newImplementation" }], outputs: [] } ], functionName: "updateImplementation", args: [config.nexusImplementationAddress], }); const updateImplementationTransaction = { to: V2AccountAddress, data: updateImplementationCalldata, }; // Step 2: Initialize Nexus Account console.log("Preparing initialize Nexus account..."); const ownerAddress = config.eoaAddress; // Prepare initialization data for the validator const initData = encodeFunctionData({ abi: [ { name: "initNexusWithDefaultValidator", type: "function", stateMutability: "nonpayable", inputs: [ { type: "bytes", name: "data" } ], outputs: [] } ], functionName: "initNexusWithDefaultValidator", args: [ownerAddress as `0x${string}`] }); // Encode bootstrap data const initDataWithBootstrap = encodeAbiParameters( [ { name: "bootstrap", type: "address" }, { name: "initData", type: "bytes" }, ], [config.nexusBootstrapAddress, initData] ); // Create initializeAccount calldata const initializeNexusCalldata = encodeFunctionData({ abi: [ { name: "initializeAccount", type: "function", stateMutability: "nonpayable", inputs: [{ type: "bytes", name: "data" }], outputs: [] } ], functionName: "initializeAccount", args: [initDataWithBootstrap], }); const initializeNexusTransaction = { to: V2AccountAddress, data: initializeNexusCalldata, }; // Send both transactions in a batch console.log("Sending migration transaction..."); const migrateToNexusResponse = await V2Account.sendTransaction( [updateImplementationTransaction, initializeNexusTransaction], { paymasterServiceData: { mode: PaymasterMode.SPONSORED }, } ); const { transactionHash } = await migrateToNexusResponse.waitForTxHash(); console.log("Migration transaction hash:", transactionHash); console.log("Migration completed successfully"); return V2AccountAddress; // Return the address for the next step } ``` ## Step 4: Test Your Migrated Account After migration, verify that your account works correctly by creating a test supertransaction: ```typescript theme={null} theme={null} import { createMeeClient, getMEEVersion, MEEVersion, toMultichainNexusAccount, } from "@biconomy/abstractjs"; import { Address, Hex, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { base } from "viem/chains"; const privateKey: Hex = "0xyour_private_key"; const storedSmartAccountAddress: Address = "0xyour_sca_address"; const eoaAccount = privateKeyToAccount(privateKey); // Initialize mcNexus with new version config const mcNexus = await toMultichainNexusAccount({ signer: eoaAccount, chainConfigurations: [ { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0), // This is essential accountAddress: storedSmartAccountAddress, }, ], }); // Create MEE client const meeClient = await createMeeClient({ account: mcNexus, }); const USDC_BASE: Address = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"; // fee token const amount = 1n; const instructions = await mcNexus.buildComposable({ type: "transfer", data: { tokenAddress: USDC_BASE, chainId: base.id, amount, recipient: eoaAccount.address, }, }); // Get quotes const quote = await meeClient.getFusionQuote({ instructions, trigger: { tokenAddress: USDC_BASE, chainId: base.id, amount, }, feeToken: { address: USDC_BASE, chainId: base.id, }, }); // Executes the supertransaction const { hash: supertxHash } = await meeClient.executeFusionQuote({ fusionQuote: quote, }); // Wait for the supertransaction const receipt = await meeClient.waitForSupertransactionReceipt({ hash: supertxHash, }); ``` ## Step 5: Update Your Application Update your application to use the latest AbstractJs SDK with MEE stack for all future interactions: ```typescript theme={null} theme={null} // IMPORTANT: Always use the same address as your V2 account const migratedAccountAddress = "YOUR_V2_ACCOUNT_ADDRESS"; // Initialize mcNexus with new version config // Use this pattern for all future SDK interactions // Use the same version you used to get implementation address to upgrade to const mcNexus = await toMultichainNexusAccount({ signer: eoaAccount, chainConfigurations: [ { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0), // This is essential accountAddress: migratedAccountAddress, }, ], }); // Create MEE client const meeClient = await createMeeClient({ account: mcNexus, }); ``` ## Troubleshooting If you encounter issues during migration: Ensure your EOA has sufficient funds for gas Verify the V2 account is properly deployed Check that all environment variables are set correctly Make sure you're using the exact same address as your V2 account in the `accountAddress` parameter Verify the migration transaction completed successfully ## Next Steps After successfully migrating your V2 account to Nexus: **STORE YOUR ACCOUNT ADDRESS** in your application's persistent storage **Update your application code** to use the `accountAddress` parameter in all future interactions **Test thoroughly** with real transactions to ensure everything works as expected By following this migration guide and properly storing your account address, you've successfully upgraded your V2 account to a Nexus account while preserving its address, balance, and history. # External Wallets + AbstractJS SDK Source: https://docs.biconomy.io/wallet-integrations/external-wallets/abstractjs Enable MEE orchestration for MetaMask, Rabby, and Trust Wallet using the AbstractJS SDK External wallets like MetaMask, Rabby, and Trust Wallet don't allow apps to install smart account logic directly on your address. Biconomy solves this with **Fusion Mode**—a passthrough mechanism that enables the same powerful orchestration features through a temporary Companion Account. ## What We're Building By the end of this tutorial, your users will be able to: 1. Connect their existing MetaMask, Rabby, or Trust wallet 2. Execute orchestrated transactions (swaps, bridges, multicalls) 3. Pay gas with ERC-20 tokens instead of ETH 4. Receive all resulting tokens back to their original wallet **How it works**: When a user signs a "trigger" transaction, Biconomy's MEE temporarily moves funds to a Companion Account, executes all operations, and returns the results to the user's wallet—all in one seamless flow. *** ## Understanding Fusion Mode Before diving into code, let's understand how Fusion Mode differs from the EIP-7702 approach used with embedded wallets. ### Why Fusion Mode? External wallets are browser extensions that maintain their own security model. They don't permit apps to "upgrade" the wallet to a smart account using EIP-7702. Fusion Mode works around this limitation by: 1. Using a **trigger transaction** to authorize the orchestration 2. Routing execution through a **Companion Account** (fully owned by the user) 3. Returning all assets to the user's original wallet ### The Fusion Flow The user signs a transaction (often a permit or approve) that contains the hash of all orchestration instructions. This single signature authorizes the entire flow. Funds are pulled into a Companion Account that the user fully owns. This account is non-custodial and stateless—nothing remains after execution. All operations (swaps, bridges, multicalls) execute using the Companion Account. MEE nodes handle the complexity. Final assets are sent back to the user's original wallet address. The Companion Account is left clean. If any step fails, cleanup logic can revert intermediate steps and return funds safely. **Invisible to Users**: Your users never need to know about the Companion Account. From their perspective, they sign once and receive tokens in their wallet. ### Trigger Types Fusion Mode supports two trigger types, automatically selected based on your input token: | Trigger Type | How it Works | Gas Required | Token Support | | --------------- | --------------------------------------------------------------------------------------------------- | ------------ | -------------------- | | **ERC20Permit** | Uses ERC-2612 signature to permit spending. Orchestration hash is packed into the `deadline` field. | ❌ No | ERC-2612 tokens only | | **Onchain Tx** | User sends an `approve()` transaction that includes orchestration data. | ✅ Yes | All ERC-20 tokens | The SDK automatically detects whether your token supports ERC-2612 and chooses the appropriate trigger type. ### Fusion Constraints Before implementing, understand these limitations: * **One token per signature**: Fusion can only consume one input token per user action * **Same token for gas**: The trigger token must also pay for gas fees * **Same-chain gas only**: Unlike EIP-7702, you can't pay gas on Chain A for execution on Chain B * **Sponsorship available**: You can sponsor gas to bypass the gas requirement entirely *** ## Step 1: Install Dependencies ```bash theme={null} npm install @biconomy/abstractjs viem wagmi @tanstack/react-query ``` **What these packages do:** * `@biconomy/abstractjs` — Biconomy's SDK for MEE and Fusion Mode * `viem` — Ethereum library for building transactions * `wagmi` — React hooks for wallet connections * `@tanstack/react-query` — Required peer dependency for wagmi ## Step 2: Configure Wallet Connection Set up wagmi to support popular external wallets: ```tsx theme={null} import { WagmiProvider, createConfig, http } from "wagmi"; import { optimism, base, arbitrum } from "wagmi/chains"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { injected, walletConnect } from "wagmi/connectors"; const config = createConfig({ chains: [optimism, base, arbitrum], connectors: [ injected(), // MetaMask, Rabby, etc. walletConnect({ projectId: "YOUR_WC_PROJECT_ID" }), ], transports: { [optimism.id]: http(), [base.id]: http(), [arbitrum.id]: http(), }, }); const queryClient = new QueryClient(); function App({ children }) { return ( {children} ); } ``` ## Step 3: Connect the Wallet Use wagmi hooks to connect and access the user's wallet: ```tsx theme={null} import { useAccount, useConnect, useWalletClient } from "wagmi"; function WalletConnection() { const { address, isConnected } = useAccount(); const { connect, connectors } = useConnect(); const { data: walletClient } = useWalletClient(); if (!isConnected) { return (
{connectors.map((connector) => ( ))}
); } return

Connected: {address}

; } ``` ## Step 4: Create the Fusion Account For Fusion Mode, create a `toFusionAccount` instead of the standard Nexus account: ```tsx theme={null} import { mcUSDC, toFusionAccount, createMeeClient } from "@biconomy/abstractjs"; import { optimism, base } from "viem/chains"; import { http } from "viem"; async function setupFusion(walletClient) { // Create a Fusion account for the connected wallet const fusionAccount = await toFusionAccount({ // The user's EOA address from their external wallet address: walletClient.account.address, // The client for signing (from wagmi) client: walletClient, // Chains where Fusion will operate chains: [optimism, base], }); // Create the MEE client const meeClient = await createMeeClient({ account: fusionAccount, }); return { fusionAccount, meeClient }; } ``` ## Step 5: Build the Trigger The trigger defines which token the user will "spend" to initiate the orchestration: ```tsx theme={null} import { parseUnits } from "viem"; import { mcUSDC } from "@biconomy/abstractjs"; // Define the trigger: user will spend USDC on Optimism const trigger = { chainId: optimism.id, tokenAddress: mcUSDC.addressOn(optimism.id), amount: parseUnits("10", 6), // 10 USDC }; ``` The trigger token **must** be the same token used for gas payment in Fusion Mode. This is a key constraint. ## Step 6: Define Instructions Instructions describe what operations to execute. Here's an example that transfers USDC: ```tsx theme={null} import { encodeFunctionData, erc20Abi, parseUnits } from "viem"; // Example: Transfer some USDC to another address const instructions = [ { chainId: optimism.id, calls: [ { to: mcUSDC.addressOn(optimism.id), data: encodeFunctionData({ abi: erc20Abi, functionName: "transfer", args: ["0xRecipientAddress", parseUnits("5", 6)], }), }, ], }, ]; ``` ## Step 7: Get a Fusion Quote Request a quote that includes the trigger and instructions: ```tsx theme={null} const { quote, trigger: signableTrigger } = await meeClient.getFusionQuote({ trigger, instructions, feeToken: { address: trigger.tokenAddress, chainId: trigger.chainId, }, }); console.log("Estimated gas cost:", quote.paymentInfo.tokenWei); ``` ## Step 8: Execute the Fusion Transaction Execute the quote—the SDK handles trigger signing automatically: ```tsx theme={null} const { hash } = await meeClient.executeFusionQuote({ fusionQuote: quote }); console.log("Supertransaction submitted:", hash); // Wait for confirmation const receipt = await meeClient.waitForSupertransactionReceipt({ hash }); console.log("Completed:", receipt); ``` *** ## Complete Example Here's a full React component for Fusion Mode with MetaMask: ```tsx theme={null} import { useState } from "react"; import { useAccount, useWalletClient } from "wagmi"; import { parseUnits, encodeFunctionData, erc20Abi } from "viem"; import { optimism } from "viem/chains"; import { createMeeClient, toFusionAccount, mcUSDC } from "@biconomy/abstractjs"; const USDC_OPTIMISM = "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85"; export function FusionTransfer() { const { address } = useAccount(); const { data: walletClient } = useWalletClient(); const [status, setStatus] = useState(""); const [recipient, setRecipient] = useState(""); const [amount, setAmount] = useState(""); async function executeFusionTransaction() { if (!walletClient || !address) { setStatus("Please connect your wallet first"); return; } try { setStatus("Setting up Fusion account..."); // Create Fusion account for external wallet const fusionAccount = await toFusionAccount({ address, client: walletClient, chains: [optimism], }); // Create MEE client const meeClient = await createMeeClient({ account: fusionAccount, }); setStatus("Getting quote..."); // The amount to trigger with (includes buffer for gas) const triggerAmount = parseUnits(amount, 6); const transferAmount = parseUnits( (parseFloat(amount) * 0.95).toFixed(6), // 95% goes to recipient 6 ); // Define trigger const trigger = { chainId: optimism.id, tokenAddress: USDC_OPTIMISM, amount: triggerAmount, }; // Define what we want to do const instructions = [ { chainId: optimism.id, calls: [ { to: USDC_OPTIMISM, data: encodeFunctionData({ abi: erc20Abi, functionName: "transfer", args: [recipient, transferAmount], }), }, ], }, ]; // Get quote const { quote } = await meeClient.getFusionQuote({ trigger, instructions, feeToken: { address: USDC_OPTIMISM, chainId: optimism.id, }, }); setStatus("Please sign the transaction in your wallet..."); // Execute (user will be prompted to sign) const { hash } = await meeClient.executeFusionQuote({ fusionQuote: quote, }); setStatus(`Transaction submitted! Hash: ${hash}`); // Wait for confirmation const receipt = await meeClient.waitForSupertransactionReceipt({ hash }); setStatus(`✅ Success! Transferred to ${recipient}`); } catch (error) { setStatus(`Error: ${error.message}`); } } return (

Send USDC (Gasless with Fusion)

setRecipient(e.target.value)} /> setAmount(e.target.value)} />

{status}

Gas will be paid from your USDC. If your token supports ERC-2612, this will be completely gasless.
); } ``` *** ## Comparison: Fusion vs EIP-7702 | Feature | Fusion Mode | EIP-7702 | | --------------------- | ------------------------------------------ | -------------------------------------------- | | **Wallet Support** | All wallets (MetaMask, Rabby, Trust, etc.) | Embedded wallets only (Privy, Turnkey, etc.) | | **Gas Payment** | Same chain only | Cross-chain supported | | **Input Tokens** | One token per signature | Multiple tokens possible | | **User Experience** | Sign once (permit) or send approve tx | Sign authorization once | | **Companion Account** | Required (transparent) | Not needed | *** ## When to Use Fusion Mode * Users have MetaMask, Rabby, Trust, or similar * You need maximum wallet compatibility * Single-token input flows (swaps, deposits) * Same-chain gas payment is acceptable * You control the wallet (embedded wallets) * Cross-chain gas payment is needed * Multiple input tokens per operation * Direct smart account features required *** ## Key Takeaways 1. **Fusion enables any wallet** — MetaMask, Rabby, Trust, and all browser extensions work 2. **One signature, full orchestration** — Users sign once to authorize complex operations 3. **Transparent Companion Account** — Users never see it; assets always return to their wallet 4. **Automatic trigger detection** — SDK chooses gasless permit or onchain tx based on token ## Next Steps * [Pay gas with different tokens](/overview/abstractjs/gas-tokens) * [Execute cross-chain swaps](/swaps-trading/cross-chain-swap) * [Sponsor gas for your users](/overview/abstractjs/sponsor-gas) * [Use Supertransaction API instead](/wallet-integrations/external-wallets/api) # External Wallets + Supertransaction API Source: https://docs.biconomy.io/wallet-integrations/external-wallets/api Enable MEE orchestration for MetaMask, Rabby, and Trust Wallet using the REST API External wallets use `eoa` mode (Fusion) and do not support EIP-7702. This is the minimal Supertransaction API flow: request a quote, **sign the payload returned by the API**, then execute. Signing the payload is the critical step. The API returns `payloadToSign` and a `quoteType` (`permit` or `onchain`) that tells you how to sign. ## Minimal EOA (Fusion) flow ```typescript theme={null} import { createWalletClient, custom } from "viem"; const walletClient = createWalletClient({ account: userAddress, transport: custom(window.ethereum), }); const quoteResponse = await fetch("https://api.biconomy.io/v1/quote", { method: "POST", headers: { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ mode: "eoa", ownerAddress: userAddress, fundingTokens: [ { tokenAddress: "0xToken", chainId: 1, amount: "1000000", }, ], composeFlows: [...], }), }); const quote = await quoteResponse.json(); const payload = quote.payloadToSign[0]; let signature; if (quote.quoteType === "permit") { signature = await walletClient.signTypedData({ ...payload.signablePayload, account: userAddress, }); } else { const txHash = await walletClient.sendTransaction({ to: payload.to, data: payload.data, value: BigInt(payload.value || "0"), }); signature = txHash; } await fetch("https://api.biconomy.io/v1/execute", { method: "POST", headers: { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ ...quote, payloadToSign: [{ ...payload, signature }], }), }); ``` # Wallet Integrations Source: https://docs.biconomy.io/wallet-integrations/index Connect popular wallet providers to Biconomy for gasless, cross-chain transactions ## What You'll Learn These tutorials show you how to integrate Biconomy with different wallet types. Each wallet provider has two guides: * **AbstractJS SDK** — TypeScript library for frontend and Node.js apps * **Supertransaction API** — REST API for backend services and any language *** ## Embedded Wallets (EIP-7702) Embedded wallets live inside your application and support EIP-7702 for direct smart account upgrades. Email & social login wallets with React Secure key management for backend apps Session-based auth for server-side flows ## External Wallets (Fusion Mode) Browser extension wallets like MetaMask require Fusion Mode for MEE orchestration. Enable gasless orchestration for any browser extension wallet using Fusion Mode *** ## Understanding the Basics Before diving in, here are the key concepts you'll encounter: ### What is an Embedded Wallet? An embedded wallet is a wallet that lives inside your application rather than being a separate browser extension (like MetaMask). Users can create wallets through email, social login, or other auth methods—no seed phrases needed. ### What is an External Wallet? External wallets are browser extensions like MetaMask, Rabby, and Trust Wallet. They maintain their own security model and don't allow apps to directly upgrade them to smart accounts. ### What is EIP-7702? EIP-7702 is a new Ethereum standard that upgrades a regular wallet (EOA) into a smart account **without changing its address**. This means: * Users keep their existing address * They gain smart account features (gas abstraction, batched transactions) * No separate smart contract deployment needed EIP-7702 works with **embedded wallets** that allow authorization signing. External wallets like MetaMask use **Fusion Mode** instead. ### What is Fusion Mode? Fusion Mode enables MEE orchestration for wallets that don't support EIP-7702. It works by: * Using a **trigger transaction** (permit or approve) to authorize operations * Routing execution through a temporary **Companion Account** * Returning all assets to the user's original wallet This means any wallet—including MetaMask, Rabby, and Trust—can access gasless, orchestrated transactions. ### What is MEE? MEE (Modular Execution Environment) is Biconomy's infrastructure that handles: * **Gas Abstraction** — Pay gas fees with any ERC-20 token, or have someone sponsor them * **Cross-Chain Execution** — Execute transactions on multiple chains with one signature * **Transaction Batching** — Bundle multiple operations into a single transaction *** ## Which Approach Should I Use? | Feature | AbstractJS SDK | Supertransaction API | | ------------------ | ---------------------- | ------------------------------ | | **Setup** | TypeScript library | Simple REST calls | | **Best for** | Frontend apps, Node.js | Backend services, any language | | **Flexibility** | Rich composability | Standard HTTP requests | | **Learning curve** | Moderate | Low | Both approaches achieve the same result—choose based on your tech stack. *** ## Prerequisites All tutorials assume you have: * Node.js 18+ installed * Basic JavaScript/TypeScript knowledge * An API key from your chosen wallet provider *** ## Quick Links ### Embedded Wallets | Provider | AbstractJS SDK | Supertransaction API | | ----------- | ------------------------------------------------ | ----------------------------------------- | | **Privy** | [Guide](/wallet-integrations/privy/abstractjs) | [Guide](/wallet-integrations/privy/api) | | **Turnkey** | [Guide](/wallet-integrations/turnkey/abstractjs) | [Guide](/wallet-integrations/turnkey/api) | | **Para** | [Guide](/wallet-integrations/para/abstractjs) | [Guide](/wallet-integrations/para/api) | ### External Wallets | Provider | AbstractJS SDK | Supertransaction API | | ---------------------------- | --------------------------------------------------------- | -------------------------------------------------- | | **MetaMask / Rabby / Trust** | [Guide](/wallet-integrations/external-wallets/abstractjs) | [Guide](/wallet-integrations/external-wallets/api) | # Para + AbstractJS SDK Source: https://docs.biconomy.io/wallet-integrations/para/abstractjs Build server-side flows with Para session-based authentication and Biconomy using the AbstractJS SDK Para provides session-based authentication that's perfect for server-side transaction execution. This guide shows you how to combine Para with Biconomy's MEE using the AbstractJS SDK for gasless, cross-chain transactions. ## What We're Building By the end of this tutorial, you'll have a backend service that can: 1. Use Para sessions for secure authentication 2. Execute batched transactions without holding ETH 3. Handle operations across multiple chains **How it works**: Users authenticate with Para on the frontend and receive a session token. Your backend uses this token to sign transactions on the user's behalf, routing them through Biconomy's MEE for gas abstraction. *** ## Step 1: Install Dependencies ```bash theme={null} npm install @biconomy/abstractjs @getpara/server-sdk @getpara/viem-v2-integration viem express ``` **What these packages do:** * `@getpara/server-sdk` — Para's server SDK for session management * `@getpara/viem-v2-integration` — Adapter to use Para with viem * `@biconomy/abstractjs` — Biconomy's SDK for smart accounts and MEE * `viem` — Ethereum library for building transactions ## Step 2: Set Up Environment Variables Create a `.env` file: ```bash theme={null} # Para credentials PARA_API_KEY=your_para_api_key PARA_ENVIRONMENT=BETA # RPC endpoint RPC_URL=https://sepolia.arbitrum.io/rpc ``` Keep your API keys secure. Never commit them to version control. ## Step 3: Create the Request Handler Set up an Express handler that receives the user's session: ```typescript theme={null} import type { Request, Response } from "express"; import { Para as ParaServer, Environment } from "@getpara/server-sdk"; export async function handleTransaction(req: Request, res: Response) { // Get session from request body const session = req.body.session as string; if (!session) { return res.status(400).json({ error: "Missing session token" }); } // Initialize Para with the session const para = new ParaServer( process.env.PARA_ENVIRONMENT as Environment, process.env.PARA_API_KEY ); await para.importSession(session); // Continue with transaction... } ``` ## Step 4: Create Para Account Create a viem-compatible account from the Para session: ```typescript theme={null} import { createParaAccount } from "@getpara/viem-v2-integration"; import type { LocalAccount } from "viem"; // Create the Para account adapter const paraAccount: LocalAccount = createParaAccount(para); ``` **What's happening here?** Para already has the user's key material from their session. The adapter lets viem use Para for signing operations. ## Step 5: Set Up Custom Signing Para requires custom signing methods for EIP-7702: ```typescript theme={null} import { customSignMessage, customSignAuthorization } from "./signature-utils"; // Override the default signing methods paraAccount.signMessage = async ({ message }) => customSignMessage(para, message); paraAccount.signAuthorization = async (authorization) => customSignAuthorization(para, authorization); ``` Here's a basic implementation of the signing utilities: ```typescript theme={null} // signature-utils.ts import { Para as ParaServer } from "@getpara/server-sdk"; export async function customSignMessage( para: ParaServer, message: string | Uint8Array ): Promise<`0x${string}`> { const messageHex = typeof message === "string" ? message : Buffer.from(message).toString("hex"); return await para.signMessage(messageHex); } export async function customSignAuthorization( para: ParaServer, authorization: any ): Promise { return await para.signAuthorization(authorization); } ``` ## Step 6: Create Wallet Client Create a wallet client for signing transactions: ```typescript theme={null} import { createWalletClient, http } from "viem"; import { arbitrumSepolia } from "viem/chains"; const walletClient = createWalletClient({ account: paraAccount, chain: arbitrumSepolia, transport: http(process.env.RPC_URL), }); ``` ## Step 7: Sign EIP-7702 Authorization Sign the authorization that upgrades the EOA to a smart account: ```typescript theme={null} // Biconomy Nexus smart account implementation const NEXUS_IMPLEMENTATION = "0x000000004F43C49e93C970E84001853a70923B03"; const authorization = await walletClient.signAuthorization({ contractAddress: NEXUS_IMPLEMENTATION, chainId: 0, // 0 = works on any chain }); ``` **What is chainId: 0?** Setting chainId to 0 means the authorization works on any chain. This is useful for cross-chain applications where you want one authorization to enable transactions everywhere. ## Step 8: Create the Smart Account Create a Nexus account using the EOA address for EIP-7702 mode: ```typescript theme={null} import { toMultichainNexusAccount, getMEEVersion, MEEVersion } from "@biconomy/abstractjs"; const nexusAccount = await toMultichainNexusAccount({ chainConfigurations: [ { chain: arbitrumSepolia, transport: http(process.env.RPC_URL), version: getMEEVersion(MEEVersion.V2_1_0), // Use the EOA address for EIP-7702 mode accountAddress: paraAccount.address, }, ], signer: walletClient, }); ``` **Important**: Setting `accountAddress` to the EOA address is required for EIP-7702 mode. Without this, the SDK will try to compute a different smart account address. ## Step 9: Create MEE Client and Execute Create the MEE client and execute your transaction: ```typescript theme={null} import { createMeeClient } from "@biconomy/abstractjs"; import { encodeFunctionData } from "viem"; const meeClient = await createMeeClient({ account: nexusAccount, }); // Build your transaction calls const calls = [ { to: "0xYourContractAddress" as `0x${string}`, value: 0n, data: encodeFunctionData({ abi: yourContractAbi, functionName: "yourFunction", args: [arg1, arg2], }), }, ]; // Execute with EIP-7702 delegation const { hash } = await meeClient.execute({ authorization, delegate: true, // Optional: pay gas with ERC-20 // feeToken: { // address: "0xUSDC...", // chainId: arbitrumSepolia.id, // }, instructions: [ { chainId: arbitrumSepolia.id, calls, }, ], }); ``` ## Step 10: Wait for Confirmation ```typescript theme={null} const receipt = await meeClient.waitForSupertransactionReceipt({ hash, timeout: 30000, // 30 seconds }); res.status(200).json({ success: true, transactionHash: hash, receipt: { hash: receipt.hash, status: receipt.status, }, }); ``` *** ## Complete Example Here's a full Express.js handler: ```typescript theme={null} import type { Request, Response } from "express"; import { createMeeClient, toMultichainNexusAccount, getMEEVersion, MEEVersion } from "@biconomy/abstractjs"; import { Para as ParaServer, Environment } from "@getpara/server-sdk"; import { createParaAccount } from "@getpara/viem-v2-integration"; import { createWalletClient, http, encodeFunctionData } from "viem"; import { arbitrumSepolia } from "viem/chains"; const NEXUS_IMPLEMENTATION = "0x000000004F43C49e93C970E84001853a70923B03"; export async function executeTransaction( req: Request, res: Response ): Promise { try { const { session, calls } = req.body; if (!session) { res.status(400).json({ error: "Missing session" }); return; } // 1. Initialize Para const para = new ParaServer( Environment.BETA, process.env.PARA_API_KEY! ); await para.importSession(session); // 2. Create Para account const paraAccount = createParaAccount(para); // 3. Create wallet client const walletClient = createWalletClient({ account: paraAccount, chain: arbitrumSepolia, transport: http(process.env.RPC_URL), }); // 4. Sign authorization const authorization = await walletClient.signAuthorization({ contractAddress: NEXUS_IMPLEMENTATION, chainId: 0, }); // 5. Create Nexus account const nexusAccount = await toMultichainNexusAccount({ chainConfigurations: [ { chain: arbitrumSepolia, transport: http(process.env.RPC_URL), version: getMEEVersion(MEEVersion.V2_1_0), accountAddress: paraAccount.address, }, ], signer: walletClient, }); // 6. Create MEE client const meeClient = await createMeeClient({ account: nexusAccount }); // 7. Execute transaction const { hash } = await meeClient.execute({ authorization, delegate: true, instructions: [ { chainId: arbitrumSepolia.id, calls, }, ], }); // 8. Wait for confirmation const receipt = await meeClient.waitForSupertransactionReceipt({ hash, timeout: 30000, }); res.status(200).json({ success: true, address: paraAccount.address, transactionHash: hash, receipt: { hash: receipt.hash, status: receipt.status, }, }); } catch (error) { res.status(500).json({ error: error instanceof Error ? error.message : "Unknown error", }); } } ``` *** ## Gas Abstraction Options ### Pay with ERC-20 Token ```typescript theme={null} const { hash } = await meeClient.execute({ authorization, delegate: true, feeToken: { address: "0xUSDC_ADDRESS", chainId: arbitrumSepolia.id, }, instructions: [/* ... */], }); ``` ### Sponsor Gas Entirely Configure sponsorship in your Biconomy dashboard to cover all gas costs for users. *** ## Cross-Chain Execution Add multiple chains and execute across them: ```typescript theme={null} import { optimism, base } from "viem/chains"; const nexusAccount = await toMultichainNexusAccount({ chainConfigurations: [ { chain: arbitrumSepolia, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0), accountAddress: paraAccount.address, }, { chain: optimism, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0), accountAddress: paraAccount.address, }, ], signer: walletClient, }); const { hash } = await meeClient.execute({ authorization, delegate: true, instructions: [ { chainId: arbitrumSepolia.id, calls: [/* Arbitrum calls */] }, { chainId: optimism.id, calls: [/* Optimism calls */] }, ], }); ``` *** ## Key Takeaways 1. **Para provides sessions** — Users authenticate once, then your backend can sign on their behalf 2. **EIP-7702 adds smart features** — The user's address gains smart account capabilities 3. **MEE handles execution** — Gas abstraction and cross-chain routing are automatic 4. **Server-side control** — Your backend manages transaction flow securely ## Next Steps * [Pay gas with different tokens](/overview/abstractjs/gas-tokens) * [Execute cross-chain transactions](/overview/abstractjs/cross-chain) * [Sponsor gas for your users](/overview/abstractjs/sponsor-gas) * [Use Supertransaction API instead](/wallet-integrations/para/api) # Para + Supertransaction API Source: https://docs.biconomy.io/wallet-integrations/para/api Build server-side flows with Para session-based authentication and Biconomy using the REST API Para embedded wallets support EIP-7702. This is the minimal Supertransaction API flow: request a quote, sign the 7702 authorization if required, **sign the payload from the API response**, then execute. Signing the payload is the critical step. The API returns `payloadToSign`, and you must sign it with Para before calling `/v1/execute`. ## Minimal EIP-7702 flow ```typescript theme={null} import { Para as ParaServer, Environment } from "@getpara/server-sdk"; const para = new ParaServer(Environment.BETA, process.env.PARA_API_KEY); await para.importSession(session); const ownerAddress = await para.getAddress(); let quoteResponse = await fetch("https://api.biconomy.io/v1/quote", { method: "POST", headers: { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ mode: "eoa-7702", ownerAddress, composeFlows: [...], }), }); if (quoteResponse.status === 412) { const { authorizations } = await quoteResponse.json(); const signedAuths = await Promise.all( authorizations.map(async (authItem) => { const authorization = await para.signAuthorization({ contractAddress: authItem.address, chainId: authItem.chainId, nonce: authItem.nonce, }); return { ...authorization, yParity: authorization.yParity, v: authorization.v?.toString(), }; }) ); quoteResponse = await fetch("https://api.biconomy.io/v1/quote", { method: "POST", headers: { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ mode: "eoa-7702", ownerAddress, composeFlows: [...], authorizations: signedAuths, }), }); } const quote = await quoteResponse.json(); const payload = quote.payloadToSign[0]; const signablePayload = payload.signablePayload ?? payload; const signature = await para.signMessage(signablePayload.message.raw); await fetch("https://api.biconomy.io/v1/execute", { method: "POST", headers: { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ ...quote, payloadToSign: [{ ...payload, signature }], }), }); ``` # Privy + AbstractJS SDK Source: https://docs.biconomy.io/wallet-integrations/privy/abstractjs Add gasless transactions to your React app with Privy embedded wallets using the AbstractJS SDK Privy lets users create wallets through email or social login. This guide shows you how to connect Privy wallets to Biconomy using the AbstractJS SDK for gasless, cross-chain transactions. ## What We're Building By the end of this tutorial, your users will be able to: 1. Log in with email (no seed phrase) 2. Execute transactions without holding ETH for gas 3. Perform cross-chain operations with one signature **How it works**: When a user logs in with Privy, they get an embedded wallet. We upgrade this wallet to a smart account using EIP-7702, then route transactions through Biconomy's MEE to handle gas payments. *** ## Step 1: Install Dependencies ```bash theme={null} npm install @privy-io/react-auth @biconomy/abstractjs viem ``` **What these packages do:** * `@privy-io/react-auth` — Privy's React SDK for wallet creation and auth * `@biconomy/abstractjs` — Biconomy's SDK for smart accounts and MEE * `viem` — Ethereum library for building transactions ## Step 2: Configure Privy Provider Wrap your app with Privy's provider. The key settings are: ```tsx theme={null} import { PrivyProvider } from "@privy-io/react-auth"; function App({ children }) { return ( {children} ); } ``` ## Step 3: Access the Embedded Wallet After a user logs in, get their wallet from Privy: ```tsx theme={null} import { useWallets, useSignAuthorization } from "@privy-io/react-auth"; import { createWalletClient, custom } from "viem"; import { optimism } from "viem/chains"; function usePrivyWallet() { const { wallets } = useWallets(); const { signAuthorization } = useSignAuthorization(); // The embedded wallet is typically the first one const embeddedWallet = wallets?.[0]; return { embeddedWallet, signAuthorization }; } ``` ## Step 4: Create a Wallet Client The wallet client is how you'll sign transactions: ```tsx theme={null} async function getWalletClient(embeddedWallet) { // Switch to your target chain await embeddedWallet.switchChain(optimism.id); // Get the Ethereum provider from Privy const provider = await embeddedWallet.getEthereumProvider(); // Create a viem wallet client const walletClient = createWalletClient({ account: embeddedWallet.address, chain: optimism, transport: custom(provider), }); return walletClient; } ``` ## Step 5: Sign EIP-7702 Authorization This step upgrades the EOA to a smart account: ```tsx theme={null} // The Nexus smart account implementation address const NEXUS_IMPLEMENTATION = "0x000000004F43C49e93C970E84001853a70923B03"; // Sign the authorization to upgrade the wallet const authorization = await signAuthorization({ contractAddress: NEXUS_IMPLEMENTATION, chainId: 0, // 0 = works on any chain }); ``` **What just happened?** The user signed a message that says "I authorize my wallet to behave like this smart contract." Their address stays the same, but now it has smart account capabilities. ## Step 6: Create the Smart Account Now create a Nexus account that can work across multiple chains: ```tsx theme={null} import { toMultichainNexusAccount, getMEEVersion, MEEVersion } from "@biconomy/abstractjs"; import { optimism, base } from "viem/chains"; import { http } from "viem"; const nexusAccount = await toMultichainNexusAccount({ chainConfigurations: [ { chain: optimism, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0), // Use the original wallet address (EIP-7702 mode) accountAddress: embeddedWallet.address, }, { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0), accountAddress: embeddedWallet.address, }, ], signer: walletClient.account, }); ``` ## Step 7: Create the MEE Client The MEE client handles transaction execution: ```tsx theme={null} import { createMeeClient } from "@biconomy/abstractjs"; const meeClient = await createMeeClient({ account: nexusAccount }); ``` ## Step 8: Execute a Gasless Transaction Now you can execute transactions where gas is paid in any token: ```tsx theme={null} import { erc20Abi } from "viem"; const USDC_ADDRESS = "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85"; // USDC on Optimism const { hash } = await meeClient.execute({ // Required for EIP-7702 flow authorization, delegate: true, // Pay gas fees with USDC instead of ETH feeToken: { address: USDC_ADDRESS, chainId: optimism.id, }, // Your transaction instructions instructions: [ { chainId: optimism.id, calls: [ { to: "0xRecipientAddress", value: 0n, data: "0x", // or encoded function data }, ], }, ], }); // Wait for confirmation const receipt = await meeClient.waitForSupertransactionReceipt({ hash }); console.log("Transaction complete:", receipt.hash); ``` *** ## Complete Example Here's a full React component putting it all together: ```tsx theme={null} import { useState } from "react"; import { useWallets, useSignAuthorization } from "@privy-io/react-auth"; import { createWalletClient, custom, http, erc20Abi } from "viem"; import { optimism, base } from "viem/chains"; import { createMeeClient, toMultichainNexusAccount, getMEEVersion, MEEVersion, } from "@biconomy/abstractjs"; const NEXUS_IMPLEMENTATION = "0x000000004F43C49e93C970E84001853a70923B03"; const USDC_OPTIMISM = "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85"; export function GaslessTransfer() { const { wallets } = useWallets(); const { signAuthorization } = useSignAuthorization(); const [status, setStatus] = useState(""); async function sendGaslessTransaction() { try { setStatus("Setting up..."); const embeddedWallet = wallets?.[0]; if (!embeddedWallet) throw new Error("Please log in first"); // Create wallet client await embeddedWallet.switchChain(optimism.id); const provider = await embeddedWallet.getEthereumProvider(); const walletClient = createWalletClient({ account: embeddedWallet.address, chain: optimism, transport: custom(provider), }); setStatus("Signing authorization..."); // Sign EIP-7702 authorization const authorization = await signAuthorization({ contractAddress: NEXUS_IMPLEMENTATION, chainId: 0, }); setStatus("Creating smart account..."); // Create multichain account const nexusAccount = await toMultichainNexusAccount({ chainConfigurations: [ { chain: optimism, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0), accountAddress: embeddedWallet.address, }, ], signer: walletClient.account, }); // Create MEE client const meeClient = await createMeeClient({ account: nexusAccount }); setStatus("Executing transaction..."); // Execute gasless transaction const { hash } = await meeClient.execute({ authorization, delegate: true, feeToken: { address: USDC_OPTIMISM, chainId: optimism.id, }, instructions: [ { chainId: optimism.id, calls: [ { to: "0x0000000000000000000000000000000000000000", value: 0n, }, ], }, ], }); const receipt = await meeClient.waitForSupertransactionReceipt({ hash }); setStatus(`Success! Hash: ${receipt.hash}`); } catch (error) { setStatus(`Error: ${error.message}`); } } return (

{status}

); } ``` *** ## Key Takeaways 1. **Privy creates the wallet** — Users log in with email, Privy creates an embedded wallet 2. **EIP-7702 adds smart features** — The wallet gets upgraded without changing address 3. **MEE handles gas** — Transactions are executed with any ERC-20 token for gas 4. **Cross-chain ready** — Add more chains to `chainConfigurations` for multi-chain ops ## Next Steps * [Pay gas with different tokens](/overview/abstractjs/gas-tokens) * [Execute cross-chain transactions](/overview/abstractjs/cross-chain) * [Sponsor gas for your users](/overview/abstractjs/sponsor-gas) * [Use Supertransaction API instead](/wallet-integrations/privy/api) # Privy + Supertransaction API Source: https://docs.biconomy.io/wallet-integrations/privy/api Add gasless transactions to your app with Privy embedded wallets using the REST API Privy embedded wallets support EIP-7702. This is the minimal Supertransaction API flow: request a quote, sign the 7702 authorization if required, **sign the payload from the API response**, then execute. Signing the payload is the critical step. The API returns `payloadToSign`, and you must sign it with the user's wallet before calling `/v1/execute`. ## Minimal EIP-7702 flow ```tsx theme={null} import { useSignAuthorization, useWallets } from "@privy-io/react-auth"; const { signAuthorization } = useSignAuthorization(); const { wallets } = useWallets(); const embeddedWallet = wallets?.[0]; if (!embeddedWallet) throw new Error("No wallet found"); const ownerAddress = embeddedWallet.address; let quoteResponse = await fetch("https://api.biconomy.io/v1/quote", { method: "POST", headers: { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ mode: "eoa-7702", ownerAddress, composeFlows: [...], }), }); if (quoteResponse.status === 412) { const { authorizations } = await quoteResponse.json(); const signedAuths = await Promise.all( authorizations.map(async (authItem) => { const authorization = await signAuthorization({ contractAddress: authItem.address, chainId: authItem.chainId, nonce: authItem.nonce, }); return { ...authorization, yParity: authorization.yParity, v: authorization.v?.toString(), }; }) ); quoteResponse = await fetch("https://api.biconomy.io/v1/quote", { method: "POST", headers: { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ mode: "eoa-7702", ownerAddress, composeFlows: [...], authorizations: signedAuths, }), }); } const quote = await quoteResponse.json(); const payload = quote.payloadToSign[0]; const signablePayload = payload.signablePayload ?? payload; // eoa-7702 returns "simple" payloads (personal message) const signature = await embeddedWallet.signMessage({ message: signablePayload.message, }); await fetch("https://api.biconomy.io/v1/execute", { method: "POST", headers: { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ ...quote, payloadToSign: [{ ...payload, signature }], }), }); ``` # Turnkey + AbstractJS SDK Source: https://docs.biconomy.io/wallet-integrations/turnkey/abstractjs Build secure backend services with Turnkey key management and Biconomy using the AbstractJS SDK Turnkey provides institutional-grade key management for your applications. This guide shows you how to combine Turnkey's secure signing with Biconomy's MEE using the AbstractJS SDK for gasless, cross-chain transactions. ## What We're Building By the end of this tutorial, you'll have a backend service that can: 1. Securely manage private keys with Turnkey 2. Execute transactions without holding ETH for gas 3. Perform operations across multiple chains **How it works**: Turnkey stores and manages private keys in secure enclaves. When you need to sign a transaction, Turnkey signs it without ever exposing the private key. We then route the signed transaction through Biconomy's MEE for gas abstraction. *** ## Step 1: Install Dependencies ```bash theme={null} npm install @turnkey/sdk-server @turnkey/viem @biconomy/abstractjs viem dotenv ``` **What these packages do:** * `@turnkey/sdk-server` — Turnkey's server SDK for key management * `@turnkey/viem` — Adapter to use Turnkey with viem * `@biconomy/abstractjs` — Biconomy's SDK for smart accounts and MEE * `viem` — Ethereum library for building transactions ## Step 2: Set Up Environment Variables Create a `.env` file with your Turnkey credentials: ```bash theme={null} # Turnkey API endpoint BASE_URL=https://api.turnkey.com # Your API keys from Turnkey dashboard API_PRIVATE_KEY=your_api_private_key API_PUBLIC_KEY=your_api_public_key # Your organization ID ORGANIZATION_ID=your_organization_id # The private key ID to sign with SIGN_WITH=your_private_key_id ``` Never commit API keys to version control. Use environment variables or a secrets manager. ## Step 3: Initialize Turnkey Client Set up the Turnkey client that will handle all signing operations: ```typescript theme={null} import { Turnkey } from "@turnkey/sdk-server"; import * as dotenv from "dotenv"; dotenv.config(); const turnkeyClient = new Turnkey({ apiBaseUrl: process.env.BASE_URL, apiPrivateKey: process.env.API_PRIVATE_KEY, apiPublicKey: process.env.API_PUBLIC_KEY, defaultOrganizationId: process.env.ORGANIZATION_ID, }); ``` ## Step 4: Create a Wallet Client Create a viem wallet client using the Turnkey account: ```typescript theme={null} import { createWalletClient, http } from "viem"; import { createAccount } from "@turnkey/viem"; import { optimism } from "viem/chains"; // Create a Turnkey account adapter const account = await createAccount({ client: turnkeyClient.apiClient(), organizationId: process.env.ORGANIZATION_ID, signWith: process.env.SIGN_WITH, }); // Create the wallet client for signing const walletClient = createWalletClient({ account, chain: optimism, transport: http(), }); ``` **What's happening here?** The `createAccount` function creates an adapter that lets viem use Turnkey for signing. The actual private key never leaves Turnkey's secure infrastructure. ## Step 5: Sign EIP-7702 Authorization Sign the authorization that upgrades the EOA to a smart account: ```typescript theme={null} // Biconomy Nexus smart account implementation const NEXUS_IMPLEMENTATION = "0x000000004F43C49e93C970E84001853a70923B03"; // Sign authorization to delegate to Nexus const authorization = await walletClient.signAuthorization({ contractAddress: NEXUS_IMPLEMENTATION, account, }); ``` **What is this authorization?** It's a signed message saying "I authorize my wallet address to execute code from this smart contract." This enables smart account features while keeping the same address. ## Step 6: Create the Smart Account Create a Nexus account that works across multiple chains: ```typescript theme={null} import { toMultichainNexusAccount, getMEEVersion, MEEVersion } from "@biconomy/abstractjs"; import { optimism, base } from "viem/chains"; const nexusAccount = await toMultichainNexusAccount({ chainConfigurations: [ { chain: optimism, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0), // Use EOA address for EIP-7702 mode accountAddress: account.address, }, { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0), accountAddress: account.address, }, ], signer: walletClient, }); ``` ## Step 7: Create the MEE Client The MEE client handles transaction execution with gas abstraction: ```typescript theme={null} import { createMeeClient } from "@biconomy/abstractjs"; const meeClient = await createMeeClient({ account: nexusAccount, }); ``` ## Step 8: Execute a Gasless Transaction Now execute a transaction with gas paid in any ERC-20: ```typescript theme={null} const USDC_OPTIMISM = "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85"; const { hash } = await meeClient.execute({ // Required for EIP-7702 flow authorization, delegate: true, // Pay gas with USDC feeToken: { address: USDC_OPTIMISM, chainId: optimism.id, }, // Your transaction instructions instructions: [ { chainId: optimism.id, calls: [ { to: "0xRecipientAddress", value: 0n, }, ], }, ], }); console.log("Transaction submitted:", hash); ``` ## Step 9: Wait for Confirmation ```typescript theme={null} const receipt = await meeClient.waitForSupertransactionReceipt({ hash }); console.log("Transaction confirmed:", receipt.hash); ``` *** ## Complete Example Here's a full Node.js script: ```typescript theme={null} import { Turnkey } from "@turnkey/sdk-server"; import { createAccount } from "@turnkey/viem"; import { createWalletClient, http } from "viem"; import { optimism, base } from "viem/chains"; import { createMeeClient, toMultichainNexusAccount, getMEEVersion, MEEVersion, } from "@biconomy/abstractjs"; import * as dotenv from "dotenv"; dotenv.config(); const NEXUS_IMPLEMENTATION = "0x000000004F43C49e93C970E84001853a70923B03"; const USDC_OPTIMISM = "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85"; async function main() { // 1. Initialize Turnkey const turnkey = new Turnkey({ apiBaseUrl: process.env.BASE_URL!, apiPrivateKey: process.env.API_PRIVATE_KEY!, apiPublicKey: process.env.API_PUBLIC_KEY!, defaultOrganizationId: process.env.ORGANIZATION_ID!, }); // 2. Create Turnkey account adapter const account = await createAccount({ client: turnkey.apiClient(), organizationId: process.env.ORGANIZATION_ID!, signWith: process.env.SIGN_WITH!, }); console.log("Using address:", account.address); // 3. Create wallet client const walletClient = createWalletClient({ account, chain: optimism, transport: http(), }); // 4. Sign EIP-7702 authorization const authorization = await walletClient.signAuthorization({ contractAddress: NEXUS_IMPLEMENTATION, account, }); console.log("Authorization signed"); // 5. Create multichain Nexus account const nexusAccount = await toMultichainNexusAccount({ chainConfigurations: [ { chain: optimism, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0), accountAddress: account.address, }, { chain: base, transport: http(), version: getMEEVersion(MEEVersion.V2_1_0), accountAddress: account.address, }, ], signer: walletClient, }); // 6. Create MEE client const meeClient = await createMeeClient({ account: nexusAccount }); // 7. Execute gasless transaction const { hash } = await meeClient.execute({ authorization, delegate: true, feeToken: { address: USDC_OPTIMISM, chainId: optimism.id, }, instructions: [ { chainId: optimism.id, calls: [ { to: "0x0000000000000000000000000000000000000000", value: 0n, }, ], }, ], }); console.log("Transaction submitted:", hash); // 8. Wait for confirmation const receipt = await meeClient.waitForSupertransactionReceipt({ hash }); console.log("Transaction confirmed:", receipt.hash); } main().catch(console.error); ``` *** ## Cross-Chain Execution Execute on multiple chains with one signature: ```typescript theme={null} const { hash } = await meeClient.execute({ authorization, delegate: true, instructions: [ { chainId: optimism.id, calls: [{ to: "0x...", value: 0n }], }, { chainId: base.id, calls: [{ to: "0x...", value: 0n }], }, ], }); ``` The MEE infrastructure handles routing transactions to each chain and managing execution order. *** ## Key Takeaways 1. **Turnkey manages keys securely** — Private keys never leave Turnkey's secure infrastructure 2. **EIP-7702 adds smart features** — The wallet gets upgraded without deploying a new contract 3. **MEE handles gas** — Pay with any ERC-20 token or sponsor gas entirely 4. **Cross-chain ready** — Add chains to `chainConfigurations` and include them in instructions ## Next Steps * [Pay gas with different tokens](/overview/abstractjs/gas-tokens) * [Execute cross-chain transactions](/overview/abstractjs/cross-chain) * [Sponsor gas for your users](/overview/abstractjs/sponsor-gas) * [Use Supertransaction API instead](/wallet-integrations/turnkey/api) # Turnkey + Supertransaction API Source: https://docs.biconomy.io/wallet-integrations/turnkey/api Build secure backend services with Turnkey key management and Biconomy using the REST API Turnkey works well for backend EIP-7702 flows. This is the minimal Supertransaction API flow: request a quote, sign the 7702 authorization if required, **sign the payload from the API response**, then execute. Signing the payload is the critical step. The API returns `payloadToSign`, and you must sign it with Turnkey before calling `/v1/execute`. ## Minimal EIP-7702 flow ```typescript theme={null} import { Turnkey } from "@turnkey/sdk-server"; import { createAccount } from "@turnkey/viem"; import { createWalletClient, http } from "viem"; import { base } from "viem/chains"; const turnkey = new Turnkey({ apiBaseUrl: process.env.BASE_URL, // https://api.turnkey.com apiPrivateKey: process.env.API_PRIVATE_KEY, apiPublicKey: process.env.API_PUBLIC_KEY, defaultOrganizationId: process.env.ORGANIZATION_ID, }); const account = await createAccount({ client: turnkey.apiClient(), organizationId: process.env.ORGANIZATION_ID, signWith: process.env.SIGN_WITH, }); const walletClient = createWalletClient({ account, chain: base, transport: http(), }); const ownerAddress = account.address; let quoteResponse = await fetch("https://api.biconomy.io/v1/quote", { method: "POST", headers: { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ mode: "eoa-7702", ownerAddress, composeFlows: [...], }), }); if (quoteResponse.status === 412) { const { authorizations } = await quoteResponse.json(); const signedAuths = await Promise.all( authorizations.map(async (authItem) => { const authorization = await walletClient.signAuthorization({ account, ...authItem, }); return { ...authorization, yParity: authorization.yParity, v: authorization.v?.toString(), }; }) ); quoteResponse = await fetch("https://api.biconomy.io/v1/quote", { method: "POST", headers: { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ mode: "eoa-7702", ownerAddress, composeFlows: [...], authorizations: signedAuths, }), }); } const quote = await quoteResponse.json(); const payload = quote.payloadToSign[0]; const signablePayload = payload.signablePayload ?? payload; const signature = await walletClient.signMessage({ account, message: signablePayload.message, }); await fetch("https://api.biconomy.io/v1/execute", { method: "POST", headers: { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ ...quote, payloadToSign: [{ ...payload, signature }], }), }); ``` # DeFi Zaps Source: https://docs.biconomy.io/zaps/index Single-click supply and withdraw into any vault, lending market, or staking program Turn complex DeFi interactions into one-click experiences. Users supply or withdraw from any protocol on any chain—no matter what token they hold or which chain they're on. Direct integrations with major lending, staking, and yield protocols Users deposit with whatever they have—Biconomy handles conversion Deposit from any chain into protocols on any other chain ## What You Can Build ### One-Click Yield Deposits User has USDC on Base, wants to deposit into a vault on Ethereum. One signature: ```typescript theme={null} const quote = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: 'eoa', ownerAddress: userAddress, composeFlows: [{ type: '/instructions/intent-vault', data: { srcChainId: 8453, // Base srcToken: USDC_BASE, dstChainId: 1, // Ethereum dstVault: AAVE_VAULT_ETH, // Destination vault address amount: '10000000000', // 10,000 USDC slippage: 0.01 } }] }) }); ``` ### Protocol Aggregator Build interfaces that let users access the best yields across DeFi: AAVE, Compound, Morpho, Venus, Spark Yearn, Beefy, Convex, Pendle Lido, Rocket Pool, Frax, Coinbase Uniswap, Curve, Balancer, Aerodrome ### Position Migration Move user positions between protocols or chains without manual unwinding: ```typescript theme={null} // Migrate from one vault to another const quote = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: 'eoa', ownerAddress: userAddress, composeFlows: [{ type: '/instructions/intent-vault', data: { srcChainId: 137, srcVault: AAVE_VAULT_POLYGON, // Source vault to withdraw from dstChainId: 8453, dstVault: MORPHO_VAULT_BASE, // Destination vault to deposit into amount: '1000000000', slippage: 0.01 } }] }) }); ``` ## Key Features When source or destination tokens are vault tokens, the API performs direct deposit/withdraw operations instead of swapping—guaranteeing lossless execution with better quotes than swap-based approaches. Seamlessly move between vaults across different chains. Deposit from any chain into protocols on any other chain with automatic bridging. Access vaults that may not have liquidity on DEXs. Direct vault integration enables routes for a much wider range of vault tokens. ## Supported Protocols | Category | Protocols | | -------- | ---------------------------------------------- | | Lending | AAVE, Compound, Morpho, Venus, Spark, Radiant | | Vaults | Yearn, Beefy, Convex, Sommelier, Enzyme | | Staking | Lido, Rocket Pool, Frax, Stakewise | | DEX LP | Uniswap, Curve, Balancer, Velodrome, Aerodrome | | Perps | GMX, dYdX, Synthetix, Kwenta | Don't see your protocol? The `build` instruction type lets you call any smart contract function directly. ## Start Building Deposit any token into vaults with a single signature Withdraw from vaults and receive any token on any chain ## Integration Options REST API for quick integration. Best for backends and non-TypeScript environments. TypeScript SDK with full type safety. Best for frontend apps and custom logic. Most DeFi applications start with the Supertransaction API for rapid development, then add AbstractJS for advanced features like composing multiple vault operations. # Token to Vault Source: https://docs.biconomy.io/zaps/token-to-vault Deposit any token into a vault with a single signature Turn any token into a vault position with one click. Users don't need to hold the vault's underlying asset—Biconomy handles the swap and deposit atomically. ## The Problem It Solves Without Biconomy, depositing into a vault requires: 1. Swap your token to the vault's asset *(signature 1)* 2. Approve the vault contract *(signature 2)* 3. Deposit into the vault *(signature 3)* 4. If cross-chain: bridge first *(add 2 more signatures)* **With Biconomy:** One signature. Automatic routing to the best path. ## How It Works User chooses a vault to deposit into (AAVE, Morpho, Yearn, etc.) Biconomy calculates the best path: swap → bridge (if needed) → deposit Single signature approves the entire flow Direct vault deposit operations—no slippage on the final step ## Quick Example Deposit USDC on Base into an AAVE vault: ```typescript theme={null} const quote = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: 'eoa', ownerAddress: userAddress, composeFlows: [{ type: '/instructions/intent-vault', data: { srcChainId: 8453, // Base srcToken: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', // USDC dstChainId: 8453, // Same chain dstVault: '0x4e65fE4DbA92790696d040ac24Aa414708F5c0AB', // AAVE vault amount: '1000000000', // 1,000 USDC slippage: 0.01 // 1% } }] }) }).then(r => r.json()); // Sign the payload const signature = await wallet.signTypedData(quote.typedDataToSign); // Execute const result = await fetch('https://api.biconomy.io/v1/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ signedQuote: quote, signature }) }).then(r => r.json()); console.log('Track at:', `https://meescan.biconomy.io/details/${result.supertxHash}`); ``` ## Cross-Chain Deposit Deposit from one chain into a vault on another: ```typescript theme={null} const quote = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: 'eoa', ownerAddress: userAddress, composeFlows: [{ type: '/instructions/intent-vault', data: { srcChainId: 1, // Ethereum srcToken: USDC_ETH, // USDC on Ethereum dstChainId: 8453, // Base dstVault: MORPHO_VAULT, // Morpho vault on Base amount: '10000000000', // 10,000 USDC slippage: 0.01, allowBridgeProviders: 'across' // Prefer Across for speed } }] }) }).then(r => r.json()); ``` Biconomy automatically: * Bridges USDC from Ethereum to Base * Deposits directly into the Morpho vault * All in a single user signature ## Understanding the Response ```json theme={null} { "outputAmount": "990000000000000000", "minOutputAmount": "980100000000000000", "route": { "type": "token-to-vault", "summary": "lifi[sushiswap] => vaultsfyi[aave-v3]", "steps": [ { "type": "swap", "provider": "lifi", "tool": "sushiswap", "srcToken": { "symbol": "USDC" }, "dstToken": { "symbol": "WETH" } }, { "type": "vault-deposit", "provider": "vaultsfyi", "protocol": "aave-v3", "vaultName": "AAVE WETH" } ], "estimatedTime": 30 } } ``` | Field | Description | | ----------------- | ------------------------------------- | | `route.type` | Always `token-to-vault` for this flow | | `outputAmount` | Expected vault shares to receive | | `minOutputAmount` | Minimum after slippage protection | | `route.steps` | Each operation in sequence | ## Supported Protocols AAVE, Compound, Morpho, Venus, Spark, Radiant Yearn, Beefy, Convex, Sommelier, Enzyme Lido, Rocket Pool, Frax, Stakewise Uniswap, Curve, Balancer, Velodrome, Aerodrome ## Fee Structure | Scenario | Fee (BPS) | | -------------------------- | --------- | | Same-chain, no swap needed | 0 | | Same-chain with swap | 10 | | Cross-chain deposit | 15 | When the source token matches the vault's underlying asset, no swap is needed—resulting in zero fees for the vault operation. ## Next Steps Withdraw from vaults to any token Migrate positions between vaults # Vault to Token Source: https://docs.biconomy.io/zaps/vault-to-token Withdraw from any vault and receive any token on any chain Exit vault positions and receive any token you want—on any chain. Biconomy handles the redemption, swap, and bridge in a single transaction. ## The Problem It Solves Without Biconomy, exiting a vault position requires: 1. Approve vault withdrawal *(signature 1)* 2. Redeem shares for underlying *(signature 2)* 3. Swap to desired token *(signature 3)* 4. If cross-chain: bridge to destination *(more signatures, more waiting)* **With Biconomy:** One signature. Funds arrive as whatever token you want, wherever you want. ## How It Works User specifies vault shares to redeem and desired output token Biconomy finds optimal path: redeem → swap (if needed) → bridge (if needed) Single signature authorizes the entire withdrawal flow Direct vault redemption—no slippage on the withdrawal step ## Quick Example Withdraw from an AAVE vault on Base, receive USDC on Optimism: ```typescript theme={null} const quote = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: 'eoa', ownerAddress: userAddress, composeFlows: [{ type: '/instructions/intent-vault', data: { srcChainId: 8453, // Base srcVault: '0x4e65fE4DbA92790696d040ac24Aa414708F5c0AB', // AAVE vault dstChainId: 10, // Optimism dstToken: '0x94b008aa00579c1307b0ef2c499ad98a8ce58e58', // USDT amount: '1000000000000000000', // Vault shares to redeem slippage: 0.01 } }] }) }).then(r => r.json()); // Sign and execute const signature = await wallet.signTypedData(quote.typedDataToSign); const result = await fetch('https://api.biconomy.io/v1/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ signedQuote: quote, signature }) }).then(r => r.json()); console.log('Track at:', `https://meescan.biconomy.io/details/${result.supertxHash}`); ``` ## Same-Chain Withdrawal Withdraw and swap to a different token on the same chain: ```typescript theme={null} { type: '/instructions/intent-vault', data: { srcChainId: 8453, srcVault: YEARN_WETH_VAULT, // Withdraw from Yearn WETH vault dstChainId: 8453, // Same chain dstToken: USDC_BASE, // Receive USDC amount: '5000000000000000000', // 5 vault shares slippage: 0.01 } } ``` ## Cross-Chain Exit Exit a vault and receive tokens on a completely different chain: ```typescript theme={null} { type: '/instructions/intent-vault', data: { srcChainId: 137, // Polygon srcVault: AAVE_POLYGON_VAULT, // AAVE position on Polygon dstChainId: 1, // Ethereum mainnet dstToken: ETH_MAINNET, // Receive ETH amount: '10000000000000000000', slippage: 0.02, // Higher slippage for volatile pair allowBridgeProviders: 'across' // Control bridge selection } } ``` ## Understanding the Response ```json theme={null} { "outputAmount": "985000000", "minOutputAmount": "975150000", "route": { "type": "vault-to-token", "summary": "vaultsfyi[aave-v3] => across => lifi[uniswap]", "steps": [ { "type": "vault-redeem", "provider": "vaultsfyi", "protocol": "aave-v3", "vaultAddress": "0x...", "inputAmount": "1000000000000000000", "outputAmount": "1000000000" }, { "type": "bridge", "provider": "across", "srcChainId": 8453, "dstChainId": 10 }, { "type": "swap", "provider": "lifi", "tool": "uniswap" } ], "totalGasFeesUsd": 0.15, "totalBridgeFeesUsd": 0.25, "estimatedTime": 120 } } ``` | Field | Description | | ------------------- | --------------------------------------- | | `route.type` | Always `vault-to-token` for withdrawals | | `vault-redeem` step | Direct redemption from vault (lossless) | | `bridge` step | Cross-chain transfer if needed | | `swap` step | Token conversion if needed | ## Provider Control Control which DEXs and bridges are used for the post-withdrawal steps: ```typescript theme={null} { type: '/instructions/intent-vault', data: { srcChainId: 8453, srcVault: MORPHO_VAULT, dstChainId: 42161, dstToken: ARB_TOKEN, amount: '1000000000000000000', slippage: 0.01, // Route preferences allowSwapProviders: 'lifi,uniswap', allowBridgeProviders: 'across', denySwapProviders: 'gluex' } } ``` ## Fee Structure | Scenario | Fee (BPS) | | ---------------------------- | --------- | | Same-chain, underlying token | 0 | | Same-chain with swap | 10 | | Cross-chain withdrawal | 15 | Withdrawing to the vault's native underlying token on the same chain incurs zero protocol fees—only gas. ## Error Handling ```typescript theme={null} const quote = await fetch('https://api.biconomy.io/v1/quote', {...}) .then(r => r.json()); // Check for errors if (quote.error) { if (quote.error.includes('insufficient balance')) { console.log('Not enough vault shares'); } if (quote.error.includes('No route found')) { console.log('Try a different destination token'); } return; } // Validate output const minOutput = BigInt(quote.returnedData[0].minOutputAmount); if (minOutput < userMinimum) { console.log('Output too low—try reducing slippage or amount'); return; } ``` ## Next Steps Deposit any token into vaults Migrate positions between vaults # Vault to Vault Source: https://docs.biconomy.io/zaps/vault-to-vault Migrate DeFi positions between protocols and chains with one signature Move positions between vaults without manual unwinding. Migrate from AAVE to Morpho, Yearn to Beefy, or any combination—even across chains. One signature handles everything. ## The Problem It Solves Without Biconomy, migrating between vaults requires: 1. Approve withdrawal from source vault *(signature 1)* 2. Redeem shares *(signature 2)* 3. Bridge to destination chain *(wait, signature 3)* 4. Swap to destination vault's asset *(signature 4)* 5. Approve destination vault *(signature 5)* 6. Deposit into new vault *(signature 6)* **With Biconomy:** One signature. Atomic execution. No funds left in limbo. ## How It Works Choose source vault to exit and destination vault to enter Biconomy calculates: redeem → swap (if needed) → bridge (if needed) → deposit Single signature authorizes the entire migration All steps execute together—no partial states or stuck funds ## Quick Example Migrate from AAVE on Polygon to Morpho on Base: ```typescript theme={null} const quote = await fetch('https://api.biconomy.io/v1/quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: 'eoa', ownerAddress: userAddress, composeFlows: [{ type: '/instructions/intent-vault', data: { srcChainId: 137, // Polygon srcVault: '0xAavePolygonVault...', // AAVE vault to exit dstChainId: 8453, // Base dstVault: '0xMorphoBaseVault...', // Morpho vault to enter amount: '5000000000000000000', // Vault shares to migrate slippage: 0.01 } }] }) }).then(r => r.json()); // Sign and execute const signature = await wallet.signTypedData(quote.typedDataToSign); const result = await fetch('https://api.biconomy.io/v1/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ signedQuote: quote, signature }) }).then(r => r.json()); console.log('Track at:', `https://meescan.biconomy.io/details/${result.supertxHash}`); ``` ## Same-Chain Migration Move between vaults on the same chain—ideal for chasing better yields: ```typescript theme={null} { type: '/instructions/intent-vault', data: { srcChainId: 8453, srcVault: AAVE_USDC_VAULT, // Exit AAVE dstChainId: 8453, dstVault: MORPHO_USDC_VAULT, // Enter Morpho amount: '10000000000000000000', slippage: 0.005 // Lower slippage for same-asset migration } } ``` When both vaults use the same underlying asset on the same chain, the migration is nearly lossless—just gas fees. ## Cross-Chain Migration Move positions across chains to access better opportunities: ```typescript theme={null} { type: '/instructions/intent-vault', data: { srcChainId: 1, // Ethereum srcVault: YEARN_ETH_VAULT, // Yearn ETH vault on mainnet dstChainId: 42161, // Arbitrum dstVault: GMX_ETH_VAULT, // GMX vault on Arbitrum amount: '2000000000000000000', // 2 vault shares slippage: 0.02, allowBridgeProviders: 'across' // Fast bridging } } ``` ## Understanding the Response ```json theme={null} { "outputAmount": "4950000000000000000", "minOutputAmount": "4900500000000000000", "route": { "type": "vault-to-vault", "summary": "vaultsfyi[aave-v3] => across => lifi[uniswap] => vaultsfyi[morpho]", "steps": [ { "type": "vault-redeem", "provider": "vaultsfyi", "protocol": "aave-v3", "vaultAddress": "0x...", "chainId": 137, "inputAmount": "5000000000000000000", "outputAmount": "5000000000" }, { "type": "bridge", "provider": "across", "srcChainId": 137, "dstChainId": 8453 }, { "type": "swap", "provider": "lifi", "tool": "uniswap" }, { "type": "vault-deposit", "provider": "vaultsfyi", "protocol": "morpho", "vaultAddress": "0x...", "chainId": 8453, "inputAmount": "4960000000", "outputAmount": "4950000000000000000" } ], "totalGasFeesUsd": 0.35, "totalBridgeFeesUsd": 0.50, "estimatedTime": 180 } } ``` | Field | Description | | --------------- | ------------------------------------------- | | `route.type` | Always `vault-to-vault` for migrations | | `vault-redeem` | Exit from source vault (lossless) | | `bridge` | Cross-chain transfer (if different chains) | | `swap` | Asset conversion (if different underlyings) | | `vault-deposit` | Entry into destination vault (lossless) | ## Migration Strategies Move to higher-yielding vaults as rates change: ```typescript theme={null} // Monitor yields, migrate when profitable if (morphoYield > aaveYield + migrationCost) { // Execute migration } ``` Diversify across protocols to reduce smart contract risk: ```typescript theme={null} // Split position across multiple vaults const migrations = [ { dstVault: AAVE_VAULT, amount: position * 0.4 }, { dstVault: MORPHO_VAULT, amount: position * 0.3 }, { dstVault: COMPOUND_VAULT, amount: position * 0.3 } ]; ``` Move to chains with lower fees or better incentives: ```typescript theme={null} // Migrate from Ethereum to L2 for lower gas { srcChainId: 1, // Expensive mainnet dstChainId: 8453, // Cheap Base // ... } ``` ## Provider Control Customize the routing for your migration: ```typescript theme={null} { type: '/instructions/intent-vault', data: { srcChainId: 137, srcVault: SOURCE_VAULT, dstChainId: 8453, dstVault: DEST_VAULT, amount: '1000000000000000000', slippage: 0.01, // Fine-tune the route routeSelectionMode: 'cheap', // Optimize for cost over speed allowBridgeProviders: 'across', // Use Across for bridging allowSwapProviders: 'lifi', // Use LiFi for swaps } } ``` ## Fee Structure | Scenario | Fee (BPS) | | -------------------------------- | --------- | | Same-chain, same underlying | 0 | | Same-chain, different underlying | 10 | | Cross-chain migration | 15 | ## Best Practices Always check `minOutputAmount` before confirming a migration. Large positions may experience slippage on the swap steps. **For large positions:** * Consider splitting into multiple smaller migrations * Use `routeSelectionMode: 'cheap'` for best rates * Set appropriate slippage (1-2% for cross-chain, 0.5% for same-chain) **For time-sensitive migrations:** * Use `routeSelectionMode: 'fast-quote'` * Prefer `allowBridgeProviders: 'across'` for speed ## Supported Vault Types AAVE ↔ Compound ↔ Morpho ↔ Venus ↔ Spark Yearn ↔ Beefy ↔ Convex ↔ Sommelier Lido ↔ Rocket Pool ↔ Frax ↔ Stakewise Uniswap ↔ Curve ↔ Balancer ↔ Velodrome ## Next Steps Deposit any token into vaults Withdraw from vaults to any token