Cross-Chain USDC Supply to AAVE with Gasless Fusion Orchestration

This guide demonstrates how to use Across Protocol together with Biconomy’s Modular Execution Environment (MEE) to perform a cross-chain supply of USDC into AAVE, completely gasless using Fusion Orchestration. With Biconomy, you can leverage runtime value injection for Across as well. Bridge with no dust left!

Overview

The integration showcases:
  • Cross-chain bridging from Optimism to Base using Across Protocol
  • Automated AAVE supply on the destination chain
  • Gasless execution through Fusion Orchestration
  • Single signature UX for the entire flow
  • Ultra-fast bridging with Across’s optimistic design

Architecture

Key Components

  1. Across Protocol: Ultra-fast optimistic bridge with capital-efficient design
  2. Biconomy MEE: Orchestrates the entire transaction flow
  3. Fusion Mode: Enables gasless execution with external wallets
  4. Companion Account: Temporary smart account for orchestration

Flow Diagram

User EOA (Optimism)
    ↓ [Sign Trigger]
Companion Account
    ↓ [Bridge via Across]
Base Network
    ↓ [Supply to AAVE]
aUSDC → User EOA (Base)

Implementation Guide

1. Setup and Dependencies

First, make sure you are using the latest abstractjs version. Update it via your favourite package manager.

2. Configure Token and Protocol Addresses

// 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({
  chainConfigurations: [
    {
      chain: optimism,
      transport: http(),
      version: getMEEVersion(MEEVersion.V2_1_0)
    },
    {
      chain: base,
      transport: http(),
      version: getMEEVersion(MEEVersion.V2_1_0)
    }
  ],
  signer: eoa
})

// Initialize MEE client
const meeClient = await createMeeClient({
  account: orchestrator,
  apiKey: 'your_mee_api_key'
})

4. Build Orchestration Instructions

Step 1: Define Trigger

The trigger initiates the orchestration by pulling tokens from the EOA. This given trigger will send inputAmount + required orchestration fees from EOA to the orchestrator account.
const trigger: Trigger = {
  chainId: optimism.id,
  tokenAddress: usdcAddresses[optimism.id],
  amount: inputAmount,
}

Step 2: Build a composable Across instruction

It will bridge the full USDC balance of the orchestrator account.
// see comment below the code
const benchmarkInputAmount = inputAmount

const callAcrossInstruction = await orchestrator.buildComposable({
        type: "acrossIntent",
        data: {
          depositor: orchestrator.addressOn(optimism.id)!,
          recipient: orchestrator.addressOn(base.id)!,
          inputToken: mcUSDC.addressOn(optimism.id),
          outputToken: mcUSDC.addressOn(base.id),
          inputAmountRuntimeParams: {
            targetAddress: orchestrator.addressOn(optimism.id)!,
            tokenAddress: mcUSDC.addressOn(optimism.id),
            constraints: []
          },
          approximateExpectedInputAmount: benchmarkInputAmount,
          originChainId: optimism.id,
          destinationChainId: base.id,
          message: "0x",
          relayerAddress: zeroAddress
        }
      })
  • approximateExpectedInputAmount this the amount that is going to be used as a benchmark to calculate the input/output bridge ratio. It is recommended that this amount is close to the runtime balance that is expected to be fetched using the params above.
    • For example, if you swap 1 eth to usdc prior to bridging usdc, and the eth/usdc rate at the timeof bridging is 5000, you may end up receiving from 4999 to 5001 usdc. Composable helper will allow you to bridge the exact amount, leaving no dust. In this case you should use $5000 as the approximateExpectedInputAmount .
  • inputAmountRuntimeParams this is the replacement of the inputAmount. Instead specifying the amount itself, one sets the params that define how to obtain the amount at the runtime. Those params include the
    • targetAddress - the address of the account which erc20 balance is going to be used as the input amount
    • tokenAddress - the erc20 token. balance of this token is used as the input amount
    • constraints - the array of arithmetical constraints to make sure the fetched amount will be satisfying certain conditions. More about constraints.

Step 3: 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 4: 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 5: 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: [
    callAcrossInstruction,
    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

Across Optimistic Design

Across uses an optimistic architecture that:
  • Lightning fast: Relayers front capital for instant fills
  • Capital efficient: Uses a single liquidity pool across all chains
  • Secure: UMA’s optimistic oracle validates all transfers

Runtime Balance Constraints

The runtimeERC20BalanceOf function ensures instructions use the exact amount that arrives:
  • Handles bridge fees automatically
  • Ensures proper sequencing
  • Avoids failed transactions

Constraints and Execution Order

Instructions execute only when their constraints are met:
  1. AAVE approval waits for bridged funds
  2. AAVE supply waits for approval
  3. 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

Across Quote Service

Quote Parameters

interface SuggestedFeesParameters {
  inputToken: Address           // Source token address
  outputToken: Address         // Destination token address
  originChainId: number       // Source chain ID
  destinationChainId: number  // Destination chain ID
  inputAmountRuntimeParams: object // Runtime injection parameters, explained above
  approximateExpectedInputAmount: bigint // The benchmark number that is used to calculate expected outcome
  
  // Optional parameters
  depositor?: Address        // Override depositor address
  recipient?: Address        // Override recipient address
  message?: Hex             // Cross-chain message
  referrer?: Address        // For referral tracking
}

Best Practices

  1. Always use runtime balances for cross-chain operations
  2. Include cleanup instructions for failure scenarios
  3. Set reasonable time bounds (60 seconds recommended)
  4. Test on testnets first before mainnet deployment
  5. Monitor transactions using MEE Scan
  6. Check deposit limits before bridging large amounts
  7. Secure your API keys in environment variables
  8. Validate fee amounts to ensure they’re reasonable

Advanced Features

Cross-Chain Messaging

Include messages with your bridge:
const callAcrossInstruction = await orchestrator.buildComposable({
  // ... other params
  message: '0x1234...', // Encoded message
})

Referral Tracking

Track referrals for analytics:
const callAcrossInstruction = await orchestrator.buildComposable({
  // ... other params
  referrer: '0xYourReferrerAddress',
})

Exclusive Relayers

Specify exclusive relayers for priority fills:
const callAcrossInstruction = await orchestrator.buildComposable({
  // .. other params
  relayerAddress: '0xYourRelayerAddress'

Multi-Chain Cleanups

cleanUps: [
  { 
    chainId: optimism.id, 
    recipientAddress: eoa.address, 
    tokenAddress: usdcOptimism 
  },
  { 
    chainId: base.id, 
    recipientAddress: eoa.address, 
    tokenAddress: usdcBase 
  }
]

Fee Analysis

// Format fees for display
import { formatAcrossFeePercentage } from './across-quote-service'

console.log(`
  Total Fee: ${formatAcrossFeePercentage(fees.totalRelayFee.pct)}%
  Gas Fee: ${formatAcrossFeePercentage(fees.relayerGasFee.pct)}%
  LP Fee: ${formatAcrossFeePercentage(fees.lpFee.pct)}%
`)

Monitoring & Analytics

Track your orchestrations:
const meeScanUrl = getMeeScanLink(hash)
console.log(`Track transaction: ${meeScanUrl}`)
Monitor Across fills:
// Check fill status on Across Explorer
const acrossExplorerUrl = `https://app.across.to/transactions?deposit=${transactionHash}`
console.log(`Track on Across: ${acrossExplorerUrl}`)

Supported Chains & Features

Across supports ultra-fast bridging across major chains:
  • Chains: Ethereum, Arbitrum, Optimism, Base, Polygon, and more
  • Features: Optimistic filling, single liquidity pool, UMA oracle validation
  • Speed: Usually completes in under 2 minutes
  • Capital Efficiency: Best rates due to unified liquidity

Conclusion

This integration combines Across’s ultra-fast optimistic bridging with Biconomy MEE’s orchestration capabilities to deliver:
  • Lightning-fast cross-chain transfers (under 2 minutes)
  • Completely gasless experience
  • Single signature for complex flows
  • Built-in failure protection
  • Capital-efficient pricing
The result is a seamless DeFi experience that abstracts away all the complexity of cross-chain operations while providing the fastest possible bridging.

Resources