Use mode: 'eoa' for users with standard Ethereum wallets like MetaMask, Rabby, Trust Wallet, or any WalletConnect-compatible wallet.
How EOA Mode Works
EOA mode uses Fusion execution—a system that lets regular wallets access Biconomy’s gas abstraction without needing a smart account.
User specifies funding tokens
You tell the API which tokens the user will spend
API returns permit or approval payload
Depending on token support, user either signs a gasless permit or sends an approval tx
User signs
One signature per funding token
MEE executes
Biconomy handles the rest
Basic Request
const quote = await fetch('https://api.biconomy.io/v1/quote', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'YOUR_API_KEY'
},
body: JSON.stringify({
mode: 'eoa',
ownerAddress: '0xYourUserAddress',
fundingTokens: [{
tokenAddress: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', // USDC on Base
chainId: 8453,
amount: '100000000' // 100 USDC (6 decimals)
}],
feeToken: {
address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
chainId: 8453
},
composeFlows: [{
type: '/instructions/intent-simple',
data: {
srcChainId: 8453,
dstChainId: 10,
srcToken: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
dstToken: '0x94b008aa00579c1307b0ef2c499ad98a8ce58e58',
amount: '100000000',
slippage: 0.01
}
}]
})
}).then(r => r.json());
Required Parameters
| Parameter | Type | Description |
|---|
mode | 'eoa' | Must be 'eoa' |
ownerAddress | string | User’s wallet address |
fundingTokens | array | Tokens user will spend (required for EOA) |
composeFlows | array | Operations to execute |
Understanding fundingTokens
This is the key difference from other modes. You must specify exactly which tokens the user is spending:
fundingTokens: [
{
tokenAddress: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', // Token contract
chainId: 8453, // Chain the token is on
amount: '100000000' // Amount in wei
}
]
The amount should cover both the operation AND any fees. If you’re paying fees in the same token, include enough for both.
Multiple Funding Tokens
If your operation involves multiple input tokens, list them all:
fundingTokens: [
{
tokenAddress: USDC_BASE,
chainId: 8453,
amount: '50000000' // 50 USDC
},
{
tokenAddress: USDT_BASE,
chainId: 8453,
amount: '50000000' // 50 USDT
}
]
Each funding token requires a separate signature. 2 tokens = 2 signatures.
Fee Token Rules
When using EOA mode, the feeToken must match one of your fundingTokens:
// ✅ Correct - feeToken matches a fundingToken
{
fundingTokens: [{ tokenAddress: USDC, chainId: 8453, amount: '100000000' }],
feeToken: { address: USDC, chainId: 8453 }
}
// ❌ Wrong - feeToken doesn't match any fundingToken
{
fundingTokens: [{ tokenAddress: USDC, chainId: 8453, amount: '100000000' }],
feeToken: { address: USDT, chainId: 8453 } // Error!
}
Quote Response
The API returns different quoteType values based on token capabilities:
{
"quoteType": "permit", // or "onchain"
"payloadToSign": [...],
"fee": { "amount": "50000", "token": "0x...", "chainId": 8453 }
}
| quoteType | Meaning | User Experience |
|---|
permit | Token supports EIP-2612 | Gasless—user just signs a message |
onchain | Token doesn’t support permit | User must send approval transaction first |
Signing the Payload
permit (Gasless)
onchain (Approval Tx)
Most modern tokens (USDC, DAI) support permit—user signs typed data:const payload = quote.payloadToSign[0];
const signature = await walletClient.signTypedData({
...payload.signablePayload,
account
});
For older tokens, user must send an approval transaction:const payload = quote.payloadToSign[0];
// Send the approval
const txHash = await walletClient.sendTransaction({
to: payload.to,
data: payload.data,
value: BigInt(payload.value || '0')
});
// Wait for confirmation (important!)
await publicClient.waitForTransactionReceipt({
hash: txHash,
confirmations: 5
});
// Use txHash as the "signature"
const signature = txHash;
Complete Example
import { createWalletClient, createPublicClient, http, parseUnits } from 'viem';
import { base } from 'viem/chains';
const USDC_BASE = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913';
const USDT_OP = '0x94b008aa00579c1307b0ef2c499ad98a8ce58e58';
async function swapWithEOA(userAddress, walletClient, publicClient) {
// 1. Get quote
const quote = await fetch('https://api.biconomy.io/v1/quote', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
mode: 'eoa',
ownerAddress: userAddress,
fundingTokens: [{
tokenAddress: USDC_BASE,
chainId: 8453,
amount: parseUnits('100', 6).toString()
}],
feeToken: {
address: USDC_BASE,
chainId: 8453
},
composeFlows: [{
type: '/instructions/intent-simple',
data: {
srcChainId: 8453,
dstChainId: 10,
srcToken: USDC_BASE,
dstToken: USDT_OP,
amount: parseUnits('100', 6).toString(),
slippage: 0.01
}
}]
})
}).then(r => r.json());
// 2. Sign based on quoteType
const payload = quote.payloadToSign[0];
let signature;
if (quote.quoteType === 'permit') {
signature = await walletClient.signTypedData({
...payload.signablePayload,
account: walletClient.account
});
} else {
// onchain - send approval tx
const txHash = await walletClient.sendTransaction({
to: payload.to,
data: payload.data
});
await publicClient.waitForTransactionReceipt({ hash: txHash });
signature = txHash;
}
// 3. Execute
const result = await fetch('https://api.biconomy.io/v1/execute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...quote,
payloadToSign: [{ ...payload, signature }]
})
}).then(r => r.json());
return result.supertxHash;
}
Important: Withdraw Funds
In EOA mode, swapped tokens land in a temporary Nexus account. Add a withdrawal instruction to send them back to the user’s EOA:
composeFlows: [
// Swap
{
type: '/instructions/intent-simple',
data: { /* swap params */ }
},
// Withdraw to EOA
{
type: '/instructions/build',
data: {
chainId: 10, // Destination chain
to: USDT_OP, // Token contract
functionSignature: 'function transfer(address to, uint256 value)',
args: [
userAddress, // Send to user's EOA
{
type: 'runtimeErc20Balance',
tokenAddress: USDT_OP,
constraints: { gte: '1' }
}
]
}
}
]
Without the withdrawal instruction, tokens stay in the Nexus account and user won’t see them in their wallet!
Next Steps