LiFi + Biconomy MEE Integration Guide
๐ Cross-Chain USDC Supply to AAVE with Gasless Fusion Orchestration
This guide demonstrates how to use LiFi Protocol together with Biconomy's Modular Execution Environment (MEE) to perform a cross-chain supply of USDC into AAVE, completely gasless using Fusion Orchestration.
๐ฏ Overview
The integration enables:
- ๐ Cross-chain bridging from Optimism to Base using LiFi Protocol
- ๐ฆ Automated AAVE supply on the destination chain
- โฝ Gasless execution through Fusion Orchestration
- โ๏ธ Single signature UX for the entire flow
- โก Optimized routing with FASTEST or CHEAPEST options
๐๏ธ Architecture
Key Components
- ๐ LiFi Protocol: Smart routing aggregator for cross-chain swaps and bridges
- ๐ง Biconomy MEE: Orchestrates the entire transaction flow
- ๐ญ Fusion Mode: Enables gasless execution with external wallets
- ๐ค Companion Account: Temporary smart account for orchestration
Flow Diagram
User EOA (Optimism) ๐ฐ
โ [Sign Trigger] โ๏ธ
Companion Account ๐ค
โ [Bridge via LiFi] ๐
Base Network โ๏ธ
โ [Supply to AAVE] ๐ฆ
aUSDC โ User EOA (Base) ๐
๐ Implementation Guide
1. Setup and Dependencies
First, create the LiFi Quote Service file. Copy and paste this entire code block into a new file called lifi-quote-service.ts
:
// lifi-quote-service.ts
import type { Address, Hex } from 'viem';
/**
* -------------------------------------------------------------
* LI.FI Quote Service โ typed with viem
* -------------------------------------------------------------
*/
export interface TokenInfo {
address: Address;
symbol: string;
decimals: number;
chainId: number;
name: string;
coinKey: string;
priceUSD?: string;
logoURI: string;
}
export interface GasCost {
type: string;
price: string;
estimate: string;
limit: string;
amount: string;
amountUSD: string;
token: TokenInfo;
}
export interface ProtocolStep {
name: string;
part: number;
fromTokenAddress: Address;
toTokenAddress: Address;
}
export interface EstimateData {
fromToken: TokenInfo;
toToken: TokenInfo;
toTokenAmount: string;
fromTokenAmount: string;
protocols: ProtocolStep[][][];
estimatedGas: number;
}
export interface FeeCost {
name: string;
description: string;
percentage: string;
token: TokenInfo;
amount: string;
amountUSD: string;
included: boolean;
}
export interface Action {
fromChainId: number;
toChainId: number;
fromToken: TokenInfo;
toToken: TokenInfo;
fromAmount: string;
slippage: number;
fromAddress: Address;
toAddress: Address;
}
export interface Estimate {
fromAmount: string;
toAmount: string;
toAmountMin: string;
approvalAddress: Address;
feeCosts: FeeCost[];
gasCosts: GasCost[];
data: EstimateData;
}
export interface ToolDetails {
key: string;
logoURI: string;
name: string;
}
export interface TransactionRequest {
from: Address;
to: Address;
chainId: number;
data: Hex;
value: Hex;
gasPrice: Hex;
gasLimit: Hex;
}
export interface Step {
id: string;
type: string;
tool: string;
toolDetails: ToolDetails;
action: Action;
estimate: Estimate;
}
export interface QuoteRequest {
fromChain: string;
toChain: string;
fromToken: Address;
toToken: Address;
fromAddress: Address;
toAddress?: Address;
fromAmount: string;
order?: 'FASTEST' | 'CHEAPEST';
slippage?: number;
integrator?: string;
fee?: number;
referrer?: string;
}
export interface QuoteResponse {
id: string;
type: string;
tool: string;
toolDetails: ToolDetails;
action: Action;
estimate: Estimate;
transactionRequest: TransactionRequest;
includedSteps: Step[];
integrator?: string;
referrer?: string;
execution?: unknown;
}
const BASE_URL = 'https://li.quest/v1/quote';
function buildQuery(params: QuoteRequest): string {
const qs = new URLSearchParams();
qs.set('fromChain', params.fromChain);
qs.set('toChain', params.toChain);
qs.set('fromToken', params.fromToken);
qs.set('toToken', params.toToken);
qs.set('fromAddress', params.fromAddress);
qs.set('toAddress', params.toAddress ?? params.fromAddress);
qs.set('fromAmount', params.fromAmount);
if (params.order) qs.set('order', params.order);
qs.set('slippage', (params.slippage ?? 0.005).toString());
if (params.integrator) qs.set('integrator', params.integrator);
if (params.fee !== undefined) qs.set('fee', params.fee.toString());
if (params.referrer) qs.set('referrer', params.referrer);
return qs.toString();
}
export async function getLifiQuote(params: QuoteRequest): Promise<QuoteResponse> {
const query = buildQuery(params);
const resp = await fetch(`${BASE_URL}?${query}`, {
method: 'GET',
headers: {
// Optional: Add your LiFi API key if you have one
// 'x-lifi-api-key': process.env.LIFI_API_KEY,
},
});
if (!resp.ok) {
const errorText = await resp.text();
throw new Error(`LI.FI quote failed: ${errorText}`);
}
return await resp.json() as QuoteResponse;
}
Now, import the dependencies in your main file:
import {
createMeeClient,
toMultichainNexusAccount,
createChainAddressMap,
runtimeERC20BalanceOf,
greaterThanOrEqualTo,
type Trigger
} from "@biconomy/abstractjs"
import { getLifiQuote } from "./lifi-quote-service" // ๐ Import the service you just created
import { base, optimism } from "viem/chains"
import { privateKeyToAccount } from "viem/accounts"
import { http, parseAbi, parseUnits } from "viem"
2. ๐ Configure Token and Protocol Addresses
// ๐ต USDC addresses on different chains
const usdcAddresses = createChainAddressMap([
[base.id, '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'],
[optimism.id, '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85']
])
// ๐ฆ AAVE Pool addresses
const aavePoolAddresses = createChainAddressMap([
[base.id, '0xA238Dd80C259a72e81d7e4664a9801593F98d1c5'],
[optimism.id, '0x794a61358D6845594F94dc1DB02A252b5b4814aD']
])
// ๐ aUSDC (AAVE interest-bearing USDC)
const aUSDCAddresses = createChainAddressMap([
[base.id, '0x4e65fE4DbA92790696d040ac24Aa414708F5c0AB'],
[optimism.id, '0x625E7708f30cA75bfd92586e17077590C60eb4cD']
])
3. ๐ Initialize MEE Client and Orchestrator
// Create orchestrator account
const eoa = privateKeyToAccount(PRIVATE_KEY)
const orchestrator = await toMultichainNexusAccount({
chains: [optimism, base],
transports: [http(), http('https://base.llamarpc.com')],
signer: eoa
})
// Initialize MEE client
const meeClient = await createMeeClient({
account: orchestrator,
apiKey: 'your_mee_api_key'
})
4. ๐ Get LiFi Bridge Quote
โ ๏ธ Important: Make sure you've created the lifi-quote-service.ts
file from Step 1 before proceeding!
LiFi provides smart routing across multiple bridges and DEXs:
const { transactionRequest } = await getLifiQuote({
fromAddress: orchestrator.addressOn(optimism.id, true),
toAddress: orchestrator.addressOn(base.id, true),
fromAmount: inputAmount.toString(),
fromChain: optimism.id.toString(),
fromToken: usdcAddresses[optimism.id],
toChain: base.id.toString(),
toToken: usdcAddresses[base.id],
order: 'FASTEST' // or 'CHEAPEST' for cost optimization
})
5. ๐ ๏ธ Build Orchestration Instructions
Step 1: Define Trigger ๐ฌ
The trigger initiates the orchestration by pulling tokens from the EOA:
const trigger: Trigger = {
chainId: optimism.id,
tokenAddress: usdcAddresses[optimism.id],
amount: inputAmount,
}
Step 2: Approve LiFi โ
const approveLiFi = await orchestrator.buildComposable({
type: 'approve',
data: {
amount: inputAmount,
chainId: optimism.id,
spender: transactionRequest.to,
tokenAddress: usdcAddresses[optimism.id]
}
})
Step 3: Execute Bridge Transaction ๐
const callLiFiInstruction = await orchestrator.buildComposable({
type: 'rawCalldata',
data: {
to: transactionRequest.to,
calldata: transactionRequest.data,
chainId: transactionRequest.chainId,
gasLimit: BigInt(transactionRequest.gasLimit),
value: BigInt(transactionRequest.value)
}
})
Step 4: Approve AAVE (with runtime balance) ๐
Using runtime balance ensures we approve exactly what arrived:
const approveAAVEInstruction = await orchestrator.buildComposable({
type: 'approve',
data: {
amount: runtimeERC20BalanceOf({
targetAddress: orchestrator.addressOn(base.id),
tokenAddress: usdcAddresses[base.id],
constraints: [greaterThanOrEqualTo(1n)]
}),
chainId: base.id,
spender: aavePoolAddresses[base.id],
tokenAddress: usdcAddresses[base.id]
}
})
Step 5: Supply to AAVE ๐ฆ
const aaveSupplyAbi = parseAbi([
'function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)'
])
const callAAVEInstruction = await orchestrator.buildComposable({
type: 'default',
data: {
abi: aaveSupplyAbi,
chainId: base.id,
functionName: 'supply',
to: aavePoolAddresses[base.id],
args: [
usdcAddresses[base.id],
runtimeERC20BalanceOf({
targetAddress: orchestrator.addressOn(base.id),
tokenAddress: usdcAddresses[base.id],
constraints: [greaterThanOrEqualTo(1n)]
}),
orchestrator.addressOn(base.id),
0
]
}
})
Step 6: Withdraw aUSDC to EOA ๐
const withdrawInstruction = await orchestrator.buildComposable({
type: 'withdrawal',
data: {
amount: runtimeERC20BalanceOf({
targetAddress: orchestrator.addressOn(base.id),
tokenAddress: aUSDCAddresses[base.id],
constraints: [greaterThanOrEqualTo(1n)]
}),
chainId: base.id,
tokenAddress: aUSDCAddresses[base.id],
}
})
6. ๐ฏ Execute Fusion Orchestration
const fusionQuote = await meeClient.getFusionQuote({
trigger,
instructions: [
approveLiFi,
callLiFiInstruction,
approveAAVEInstruction,
callAAVEInstruction,
withdrawInstruction
],
cleanUps: [{
chainId: base.id,
recipientAddress: eoa.address,
tokenAddress: usdcAddresses[base.id]
}],
feeToken: {
address: usdcAddresses[optimism.id],
chainId: optimism.id
},
lowerBoundTimestamp: nowInSeconds,
upperBoundTimestamp: nowInSeconds + 60
})
const { hash } = await meeClient.executeFusionQuote({ fusionQuote })
console.log(getMeeScanLink(hash))
๐ง Key Concepts
๐ฏ LiFi Smart Routing
LiFi aggregates multiple bridges and DEXs to find the best route:
- โก FASTEST: Optimizes for speed
- ๐ฐ CHEAPEST: Optimizes for lowest cost
- ๐ Multi-hop: Can route through multiple chains if needed
๐ Runtime Balance Constraints
The runtimeERC20BalanceOf
function ensures instructions use the exact amount that arrives:
- โ Handles bridge slippage automatically
- โ Ensures proper sequencing
- โ Avoids failed transactions
๐ Constraints and Execution Order
Instructions execute only when their constraints are met:
- ๐ฆ AAVE approval waits for bridged funds
- ๐ธ AAVE supply waits for approval
- ๐ค Withdrawal waits for aUSDC
๐ก๏ธ Cleanup Mechanism
If any step fails, cleanup instructions ensure funds are returned:
cleanUps: [{
chainId: base.id,
recipientAddress: eoa.address,
tokenAddress: usdcAddresses[base.id]
}]
โฝ Gas Payment
The orchestration is gasless because:
- ๐ฐ Gas paid using bridged USDC
- ๐ง MEE handles gas abstraction
- โ๏ธ Users only sign once
๐ญ LiFi Quote Service
Quote Parameters
interface QuoteRequest {
fromChain: string; // Source chain ID
toChain: string; // Destination chain ID
fromToken: Address; // Source token address
toToken: Address; // Destination token address
fromAddress: Address; // Sender address
toAddress?: Address; // Receiver (optional)
fromAmount: string; // Amount to bridge
order?: 'FASTEST' | 'CHEAPEST'; // Routing preference
slippage?: number; // Max slippage (default: 0.5%)
integrator?: string; // Your app name
fee?: number; // Revenue share fee
referrer?: string; // Referrer address
}
Response Structure
interface QuoteResponse {
id: string; // Quote ID
transactionRequest: { // Ready-to-send transaction
to: Address;
data: Hex;
value: Hex;
gasLimit: Hex;
};
estimate: { // Cost breakdown
fromAmount: string;
toAmount: string;
toAmountMin: string;
gasCosts: GasCost[];
feeCosts: FeeCost[];
};
toolDetails: { // Bridge/DEX used
name: string;
logoURI: string;
};
}
๐ก Best Practices
- ๐ Always use runtime balances for cross-chain operations
- ๐ก๏ธ Include cleanup instructions for failure scenarios
- โฐ Set reasonable time bounds (60 seconds recommended)
- ๐งช Test on testnets first before mainnet deployment
- ๐ Monitor transactions using MEE Scan
- ๐ฏ Choose appropriate routing:
- Use
FASTEST
for time-sensitive operations - Use
CHEAPEST
for cost optimization
- Use
- ๐ Secure your API keys in environment variables
๐จ Error Handling
try {
const { transactionRequest } = await getLifiQuote(params)
// ... orchestration logic
} catch (error) {
if (error.message.includes('LI.FI quote failed')) {
console.error('โ Bridge route not available')
// Try alternative routes or notify user
}
console.error('๐จ Orchestration failed:', error)
}
๐ Advanced Features
๐จ Custom Slippage
const quote = await getLifiQuote({
// ... other params
slippage: 0.01, // 1% slippage tolerance
})
๐ฐ Revenue Sharing
Earn fees by setting integrator params:
const quote = await getLifiQuote({
// ... other params
integrator: 'YourAppName',
fee: 0.003, // 0.3% fee
referrer: '0xYourAddress'
})
๐ Multi-Chain Cleanups
cleanUps: [
{
chainId: optimism.id,
recipientAddress: eoa.address,
tokenAddress: usdcOptimism
},
{
chainId: base.id,
recipientAddress: eoa.address,
tokenAddress: usdcBase
}
]
๐ Supported Chains & Bridges
LiFi aggregates 30+ bridges across 20+ chains including:
- ๐ Bridges: Stargate, Across, Hop, Connext, etc.
- ๐ DEXs: Uniswap, SushiSwap, Curve, etc.
- โ๏ธ Chains: Ethereum, Arbitrum, Optimism, Base, Polygon, etc.
๐ Monitoring & Analytics
Track your orchestrations:
const meeScanUrl = getMeeScanLink(hash)
console.log(`๐ Track transaction: ${meeScanUrl}`)
๐ง Optional: Using LiFi API Key
If you have high volume or need priority access, you can get a LiFi API key:
- Visit LiFi Developer Portal
- Sign up and get your API key
- Update the
lifi-quote-service.ts
file:
// In lifi-quote-service.ts, update the headers:
headers: {
'x-lifi-api-key': process.env.LIFI_API_KEY!, // Add your API key
}
- Set your environment variable:
LIFI_API_KEY=your_api_key_here
๐ Conclusion
This integration combines LiFi's powerful routing engine with Biconomy MEE's orchestration capabilities to deliver:
- ๐ Access to the best cross-chain routes
- โฝ Completely gasless experience
- โ๏ธ Single signature for complex flows
- ๐ก๏ธ Built-in failure protection
The result is a seamless DeFi experience that abstracts away all the complexity of cross-chain operations.