Introduction to the Composability Stack

The Biconomy composability stack enables developers to write fully composable sequences of function calls across multiple chains and have them executed with a single user signature.

One of the defining features of the Biconomy stack is the support for application-layer single-chain and cross-chain composability! What does this mean? How does it help developers build more secure apps in 1/10th the time? Let's dig in!

Composability

In blockchains, composability implies the ability for one smart contract to leverage the functions exposed another smart contract - sometimes called "Money Legos". In real terms, it means that any developer can build a contract which interacts with all major protocols and takes full advantage of all the liquidity and assets on the blockchain they're developing on.

For example - when building an app, the developer can use USDT/USDC for their global on-ramp/off-ramp integrations, use lending protocols like AAVE, Morpho or Compound to get access to their highly liquid lending pools or access high performance trading platforms such as Hypeliquid - all without building any of the infrastructure or bootstrapping any of the liquidity themselves.

Composability is the true superpower of blockchains - it means that any developer can access world-class financial infrastructure which, until now, was only available to major TradFi/FinTech companies.

The Problem(s) With Composability

While composability worked nicely during the dawn of DeFi and composable blockchains - when developers were primarily executing transactions only on Ethereum - in the current state of blockchains - where we have hundreds and soon thousands of active blockchains, composability hasn't kept up. The core issue of composability in a multi-chain environment is the inability for a smart contract on one chain to know the state of a smart contract on another chain or to interact with it.

The Smart Contract Solution

Luckily, there is a solution. Actually, there are many solutions - all working to solve the composability challenge in a rollup-centric environment. Many protocols have come to market which enable developers to interact with other contracts on "remote" chains. They can be broadly split into three categories:

  • General Message Passing
    These are protocols such as LayerZero, Axelar, Chainlink CCIP, Hyperlane, Concero - which enable developers to send a message from one contract on one chain to another contract on another chain. The contract on the "destination" chain then receives the message and continues the execution.
    Many products can be built on top of cross-chain messaging protocols, with the most notable categories being bridges and multichain tokens. Relevant examples of bridges built with this stack include Stargate, Squid, Satellite... Notable multichain token standards include the LayerZero OFT, the xERC20 standard, the SuperERC20 standard, Chainlink CCT standard and Axler ITS.
  • Intent Solvers
    Intent solvers take a different approach. Instead of sending a message, the developer will state - in a contract call on the "source" chain - what assets they're willing to provide and what end state they wish to get on the "destination" chain. For example, a developer might offer USDC on Optimism to get WETH on Arbitrum! Notable protocols in this category include Across and Relay for cross chain swaps and more general solver platforms, such as Anoma or Khalani.
  • ZK Provers
    Another way to enable cross-chain communication is to "prove" via Zero-Knowledge proofs that something happened on a remote chain. For example, a prover could prove that a "send ERC20 token from xyz to zyx" has been emitted on OP Mainnet, to a contract on Arbitrum! In this category - Polymer serves as a notable example.

Remaining Challenges

While the mentioned protocols and systems have made the lives of developers building in a multichain environment much simpler, significant challenges remain. Notably:

  • The need for developers to write custom smart contracts to send and receive cross-chain messages.
    • In order for developers to use protocols such as LayerZero or CCIP, they must write "sender" and "receiver" contracts for cross-chain messages. Smart contract work, by default, requires audits.
    • Building smart contracts on multiple chains and "tying" them together, is still somewhat complex - with developers needing to maintain complex multi-chain deployment scripts and keep cross-chain contracts in a single code repository.
  • The inability to combine multiple solvers or cross-chain providers into a single flow.
    • While solvers enable fast execution of cross-chain actions, developers need to choose their own solvers for every action. If they'd like to use multiple solvers in a single execution flow, that can become extremely cumbersome.
  • The inability to orchestrate complex multi-chain action sequences due to issues with slippage and changing blockchain runtime conditions.
    • In DeFi, in general it's impossible to know what the "exact" output of a transaction will be - swaps incur slippage, bridges take fees and runtime conditions change all the time. However, when the user is signing the "callData" of the transaction they're posting onchain - all parameters have to be known.

      This makes it impossible to encode transaction sequences which control for changing runtime conditions, without requiring the user to sign every step of the way separately.

Asynchronous Composable Orchestration - The Solution to Cross-Chain Composability

The mentioned challenges motivated us at Biconomy to approach the question of application-layer cross-chain composability much more seriously. Rather then re-inventing the wheel - we've decided to built on top of all of the protocols mentioned before.

The result of this effort has been our developer and user friendly execution stack - the Modular Execution Environment. Powered by our recently introduced Supertransaction data structure - it enables developers to encode fully composable, multi-transaction, multi-chain sequences - which are approved and executed with a single user signature!

The Composable Supertransaction

In order to achieve this, we've set four requirements:

  • Single Signature, Multichain Execution
    The developer must be able to encode a sequence of function calls across multiple chains, into a single data structure. This sequence must be uniquely represented by a single hash and executed by a single signature from the user. We call this structure a Supertransaction.
  • True Application-Layer Composability
    All of the function calls within the Supertransaction must be fully composable. Every function call must be able to "pull" the exact state of the blockchain during runtime and "inject" the current state into any of the parameters of the function call. (e.g. a call to supply function on a lending protocol injecting the exact balance of USDC during runtime into the amount parameter of the supply function).
  • Multi-transaction Context
    All the function calls within the Supertransaction must share the same context. For example, one call can set an onchain variable to "completed" which would then trigger the execution of the next function call. One other example might be a function call reverting and setting its state to "failed", which would then trigger a cleanup function call to revert state changes which the Supertransaction did on other chains.
  • Fully trustless and onchain
    All validation must be done 100% onchain. No relayer, bundler or Node is allowed to inject any data into the parameters or ever take custody of the user funds. The stack must inherit all of the security of the underlying chain on which the function calls are being executed.
The transaction which is dynamically injecting the balance of WETH for every instruction of the Supertransaction. This controls for the slippages and fees incurred by the DEX and the Bridge/Solver!

The Biconomy Composability Stack

The Biconomy Composability stack enables developers to effortlessly encode single-chain or multi-chain sequences which can dynamically inject runtime data into their function parameters. Let's break it down.

Imagine you have WETH on Optimism and you see an amazing yield opportunity for USDC/USDT LP tokens on Arbitrum. The best pathway to getting those tokens is the following:

  1. Swap WETH to USDC on Optimism
  2. Bridge USDC you got from Optimism to Arbitrum
  3. Swap half of USDC on Arbitrum to USDT
  4. Take the USDT and USDC you got and supply them to the LP Pool
  5. Take the LP tokens and put them into the yield optimizer protocol

These are five function calls, across two chains - four of which have unknown outputs since:

  1. You can't know exactly how much you'll get from the WETH/USDC swap since runtime conditions might change from signing to execution.
  2. You can't know exactly how much a bridge will give you on the destination chain
  3. Another swap, same as 1)
  4. Since you don't know the inputs of how much USDC and USDT you got, you can't know the exact amount of LP tokens you'll get
  5. Last operation so it doesn't matter how much it outputs, but even here you can't know exactly how much yieldLp(UsdcUsdt) tokens you'll get.

Since slippage, fees and runtime changes are transitive - this means that if any precision is lost in the first steps - it's carried over to every next step. The Biconomy Composability stack solves for that.

Creating a Composable Workflow

Creating a composable workflow is done by encoding a function call to the encodeComposable function on an ERC-7579 compatible Smart Account. This entire process can be either done manually through the SDK or, much more easily, by leveraging the Biconomy AbstractJS SDK.

💡
While the Biconomy Composability Stack requires Smart Accounts, you can take advantage of the full functionality offered by it even with EOAs - through leveraging either EIP-7702 enabled wallets or (as a fallback) using our backwards-compatible Fusion Execution model.

Encode composable instructions

💡
The Composability helpers for AbstractJS are still in active development and will be finalized by the production network launch (April 2025). The code below might change.
// Create account
const myAccount = await toMultichainNexusAccount({ ... })

// Create amounts
const amountForSwap = getBalance(mcUSDC, optimism)
const slippageMax = calculateSlippage(amountForSwap, {
  maxPercentSlippage: 3
})

// Encode approve + swap instructions
const approveAndSwap: Instruction[] = [ 
  // Helper function to encode erc20Approve
  encodeApproveErc20({
    mcToken: mcUSDC,
    chain: optimism,
    target: mcUniswapSwapRouter.addressOn(optimism.id),
    amount: amountForSwap
  }),
  mcUniswapSwapRouter.on(optimism.id).exactInputSingle({
      args: [{
        amountIn: amountForSwap,
        amountOutMinimum: amountForSwap - slippageMax,
        fee: 0,
        recipient: myAccount.getAddress(),
        sqrtPriceLimitX96: 0n,
        tokenIn: mcUSDC.addressOn(optimism.id),
        tokenOut: mcWeth.addressOn(optimism.id)
      }]
    })
]

// encodeIntent is a helper which will search all the best 
// routes to move a token from one chain to antoher. It'll
// then encode all the required instructions for this call.
const approveAndBridge: Instruction[] = encodeIntent({
  // Don't start execution until the last instruction in
  // approveAndSwap has finished
  waitFor: approveAndSwap

  // Parameters for intent
  sourceChain: optimism,
  destinationChain: arbitrum,
  mcToken: mcWeth,
  account: myAccount,
  
  // amount is not passed an exact value! Instead, we have passed
  // a runtimeBalanceOf - which will encode a call to the
  // executeComposable function and dynamically inject the balance
  // of WETH during runtime
  amount: runtimeBalanceOf({
    token: mcWeth,
    account: myAccount,
  }),
}

// This will supply to AAVE on the destination chain. Since the 
// composability stack is powered by Biconomy asynchronous orchestration
// the nodes will wait for the bridge to complete, before proceeding with
// this action.
const approveAndSupply: Instruction[] = [
  encodeApproveErc20({
    mcToken: mcUSDC,
    chain: arbitrum,
    target: mcAaveV3Pool.addressOn(arbitrum.id),
    amount: runtimeBalanceOf({
      token: mcWeth,
      account: myAccount,
    })
  }),
  mcAaveV3Pool.on(arbitrum.id).supply({
    args: [
      mcUSDC.addressOn(arbitrum.id), // Token to Supply
      runtimeBalanceOf({ // Dynamic composable amount of WETH
        token: mcWeth,
        account: myAccount
      }),
      zeroAddress, // on behalf of, zeroAddress for msg.sender
      0 // referralCode - we can leave empty
    ]
  })
]

// Supertransaction which composably and asynchronously
// orchestrates a sequence of actions across multiple chains.
const sequence: Supertransaction = {
  instructions: [
    ...approveAndSwap,
    ...approveAndBridge,
    ...approveAndSupply
  ]
}

Execute Composable Actions

After encoding all of the actions, the Supertransaction is sent to a Modular Execution Environment (such as Biconomy Network) to be executed. The MEE will start orchestrating the composable flow. You can either display this to your users on the frontend (and AbstractJS comes with built-in helper functions just for that) or you can send them to MEEScan to track the execution through our Explorer.

The Modular Execution Environment explorer showing the execution progress of a Supertransaction!

Conclusion

Application-layer composability enables developers to effortlessly execute and compose complex sequences of actions across multiple blockchains. This drastically reduces the time-to-market for builders looking to build single-chain or multi-chain apps which touch multiple contracts as one.