Orchestration with Smart Accounts
Executing orchestration flows with smart accounts is the most powerful way of leveraging the orchestration flows. This means that the user funds are already in the Nexus multichain smart account before starting orchestration.
Key Benefits of Smart Account Based Orchestration
-
Chain Abstraction / Unified Balances
Since the orchestration validator has access to an instance of a smart account across multiple chains, it's able to trigger multiple chains at once to bridge one or more tokens to a desired destination chain.
-
Multi-Chain Gas
When using smart account based orchestration, users can pay for execution with native tokens or ERC20 tokens across any supported chain.
Key Drawbacks of Smart Account Based Orchestration
-
Depositing Assets
The users must deposit the assets into the Smart Account before using it. For users who come from EOA wallets, this creates additional onboarding friction.
-
Fragmenting Liquidity
Unlike EOAs, Smart Accounts are not portable and users can have multiple Smart Account addresses across multiple apps. This means that the user balances get fragmented easily as they interact with more apps.
Guide
In order to run multi-chain orchestration, you need to execute the following steps:
Initialize the multichain (or single chain) smart account
Create an instance of the MultichainSmartAccount
object in your
code. You can do this by calling the toMultichainNexusAccount
function.
const oNexus = await toMultichainNexusAccount({
signer: eoa,
chains: [optimism, base],
transports: [http(), http()]
});
Create a connection to the Modular Execution Environment (MEE)
Initialize an MEE Client
which will create the connection to an
MEE Node. The default connection is to the Biconomy Network.
const meeClient = await createMeeClient({ account: oNexus });
Encode the orchestration instructions
The instructions are an array of EVM [to, value, data]
tuples. For this
example, we'll just encode empty calls to 0x0000...
const optimismCall = await oNexus.build({
type: "default",
data: {
chainId: optimism.id,
calls: [{ to: zeroAddress, value: 0n, data: "0x" }],
},
});
const baseCall = await oNexus.build({
type: "default",
data: {
chainId: base.id,
calls: [{ to: zeroAddress, value: 0n, data: "0x" }],
},
});
Choose how you want to pay for the orchestration fee
One of the key features of the Biconomy stack is the ability to pay for the execution of all instructions across all chains with a single token on a single chain.
Simply put the token address (or 0x0000...
) for native token and the
chainId.
const feeToken = {
address: zeroAddress,
chainId: optimism.id
}
Get the execution quote
Call the getQuote
function which will send your instructions to the
modular execution environment. The reponse will contain two crucial
pieces of info - quoted fee for the orchestration and the hash of
all the instructions.
const quote = await meeClient.getQuote({
feeToken: { address: zeroAddress, chainId: optimism.id },
instructions: [optimismCall, baseCall],
})
Accept the quoted execution fee and execute
If you're okay with the quoted execution cost, you can call executeQuote
which will get the user to sign the execution and start executing the
flow:
const { hash } = await meeClient.executeQuote({
quote
})
Code Example
import {
createMeeClient,
toMultichainNexusAccount
} from "@biconomy/abstractjs";
import { http, zeroAddress } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base, optimism } from "viem/chains";
const main = async () => {
// Setup EOA and Nexus account
const eoa = privateKeyToAccount("0x3e478f4e7c4be62e616d4b347de5431a4a6774899db4a72702b58e0cb92a1e00");
const oNexus = await toMultichainNexusAccount({
signer: eoa,
chains: [optimism, base],
transports: [http(), http()]
});
const meeClient = await createMeeClient({ account: oNexus });
// Create empty calls on each chain
const optimismCall = await oNexus.build({
type: "default",
data: {
chainId: optimism.id,
calls: [{ to: zeroAddress, value: 0n, data: "0x" }],
},
});
const baseCall = await oNexus.build({
type: "default",
data: {
chainId: base.id,
calls: [{ to: zeroAddress, value: 0n, data: "0x" }],
},
});
// Execute both calls (pays fees with ETH on Optimism)
const { hash } = await meeClient.execute({
feeToken: { address: zeroAddress, chainId: optimism.id },
instructions: [optimismCall, baseCall],
});
// Wait for completion
const receipt = await meeClient.waitForSupertransactionReceipt({ hash });
console.log(`Completed: ${hash}`);
};
main().catch(console.error);