Orchestration with Native EOA Accounts
Biconomy orchestration for EOA accounts is done by leveraging an passthrough smart account comined with a trigger transaction from the EOA.
The flow is as follows:
Approve Spend
The trigger transaction approves the "passthrough" orchestrator smart
account to spend funds from the EOA. The trigger transaction can be any
EVM function call, but is usually a call to approve
function on an ERC-20
token or an NFT transfer from the EOA to the orchestrator account.
Orchestrate
The orchestration starts once the "trigger" transaction has been included onchain. This is when the relayer nodes will start to execute desired function calls, bridge/solver triggers, etc... on the orchestrator smart account.
This is where most of the execution happens, as the orchestration step can include multiple function calls across multiple chains and multiple transactions.
Return to EOA
As the last step of the orchestration, it's common to encode function calls which return all of the assets that were the result of the orchestration back to the EOA.
This ensures that the user never has to access the smart account manually, nor do they even need to know about the orchestrator account address.
Trigger Types
To trigger the native EOA account to start the orchestration, we use one of two separate types of triggers:
Onchain Transaction Trigger
The onchain transaction trigger takes the hash of the orchestration instructions
and appends it after the useful callData
of the transaction.
-
The key benefit of the onchain transaction trigger is that it's general purpose and the trigger transaction can be any EVM function call.
-
The key drawback of the onchain transaction trigger is that the user has to have native gas tokens on their EOA to pay for the first "trigger" transaction.
ERC20Permit Trigger
The ERC20Permit trigger uses the ERC-2612
signature to approve the orchestrator smart account to spend some tokens. It
packs the hash of the orchestration instructions in the DateTime
field of the ERC20Permit
signature.
-
The key benefit of the ERC20Permit approach is that it's gasless. This means that the user can encode a full multichain orchestration sequence, without having any native gas tokens.
-
The key drawback of the ERC20Permit approach is that it only works with tokens which implement the
ERC-2612
standard. While this standard is common, it's by no means ubiquitous.
Selecting the Trigger Type
The AbstractJS
SDK will automatically check whether the token you're using
as the trigger supports ERC-2612
or no. If it supports it, it will use the
ERC20Permit trigger, otherwise it'll use the onchain transaction trigger.
Quick Code Reference
Here's a condensed code example showing the essential steps for implementing EOA-based orchestration:
import { createMeeClient, toMultichainNexusAccount, runtimeERC20BalanceOf, toFeeToken } from "@biconomy/abstractjs";
import { privateKeyToAccount } from "viem/accounts";
import { base, optimism } from "viem/chains";
import { http, parseUnits } from "viem";
// 1. Initialize accounts and client
const eoa = privateKeyToAccount("0x...your_private_key...");
const oNexus = await toMultichainNexusAccount({
signer: eoa,
chains: [base, optimism],
transports: [http(), http()]
});
const meeClient = await createMeeClient({ account: oNexus });
// 2. Define trigger - this is what the user signs
const trigger = {
tokenAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
amount: parseUnits("10", 6),
chainId: base.id
};
// 3. Build instructions - what happens after the trigger
const instructions = [
// Bridge from Base to Optimism
await oNexus.buildComposable({
type: "default",
data: // Encode call to bridge here!
}),
// Use funds on destination chain with runtime values
await oNexus.buildComposable({
type: "default",
data: {
chainId: optimism.id,
abi: someProtocolAbi,
to: "0xProtocolAddress",
functionName: "deposit",
args: [
// Use whatever amount arrived after bridging
runtimeERC20BalanceOf({
tokenAddress: "0xTokenOnOptimism",
targetAddress: oNexus.addressOn(optimism.id),
constraints: [/* minimum amount constraints */]
})
]
}
}),
// Return results to user's EOA
await oNexus.buildComposable({
type: "transfer",
data: {
chainId: optimism.id,
tokenAddress: "0xResultTokenAddress",
recipient: eoa.address,
amount: runtimeERC20BalanceOf({
tokenAddress: "0xResultTokenAddress",
targetAddress: oNexus.addressOn(optimism.id)
})
}
})
];
// 4. Get quote and execute
const quote = await meeClient.getFusionQuote({
trigger: trigger,
feeToken: toFeeToken({
chainId: base.id,
tokenAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" // USDC
}),
instructions: instructions
});
// 5. Execute - this creates the transaction for user to sign
const { hash } = await meeClient.executeFusionQuote({ fusionQuote: quote });
// 6. Wait for orchestration to complete
const receipt = await meeClient.waitForSupertransactionReceipt({ hash });
Key Patterns to Remember
- Trigger Transaction: Usually an approval or transfer from EOA to Nexus
- Runtime Values: Use
runtimeERC20BalanceOf()
to handle dynamic amounts - Return Assets: Always include a final step to return assets to the user's EOA
- Constraints: Add constraints when bridging or swapping to handle slippage
- Chain IDs: Specify the correct chainId for each instruction
For complete implementations, refer to our examples on GitHub.