This instruction type enables contract interactions using pre-encoded calldata. Provide raw hex-encoded function calls for maximum control over the exact calldata being executed.
How It Works
The build-raw flow:
Accepts pre-encoded calldata - Raw hex strings representing complete function calls
Validates calldata format - Ensures proper hex encoding
Generates instructions - Creates executable MEE instructions
Enables composition - Combines with other flow types seamlessly
When to Use BuildRaw
✅ Use BuildRaw When
You have pre-generated calldata from another system
You need exact control over the calldata format
You’re migrating from a system that already encodes calldata
You want to avoid ABI parsing overhead client-side
❌ Use /instructions/build Instead
You need runtime balance injection
You want the API to handle encoding
You prefer working with function signatures
You want more readable code
Key Differences from Build
Feature /instructions/build/instructions/build-rawInput Function signature + args Pre-encoded calldata Runtime Balance ✅ Supported ❌ NOT Supported Client Encoding API encodes Client encodes Use Case Most common operations Advanced/custom calldata
Critical Limitation : BuildRaw does NOT support runtime balance injection. The calldata must be fully encoded client-side with concrete values. If you need runtime balance (e.g., transferring all remaining tokens), use /instructions/build instead.
Parameters
When using /instructions/build-raw in your composeFlows array:
Parameter Type Required Description datastring Yes Pre-encoded calldata as hex string (e.g., “0xa9059cbb…”) tostring Yes Target contract address (checksummed) chainIdnumber Yes Chain ID for execution valuestring No Native token value in wei (default: “0”) gasLimitstring No Gas limit override
Complete Workflow Examples
Simple ERC20 Transfer
WETH Deposit with Value
Combining with /build
Transfer tokens using pre-encoded calldata import { createWalletClient , http , parseUnits , encodeFunctionData , parseAbi } from 'viem' ;
import { privateKeyToAccount } from 'viem/accounts' ;
import { base } from 'viem/chains' ;
const account = privateKeyToAccount ( '0x...' );
const walletClient = createWalletClient ({
account ,
chain: base ,
transport: http ()
});
// Encode the calldata client-side
const transferAbi = parseAbi ([ 'function transfer(address to, uint256 value)' ]);
const transferCalldata = encodeFunctionData ({
abi: transferAbi ,
functionName: 'transfer' ,
args: [
'0x742d35cc6639cb8d4b5d1c5d7b8b5e2e7c0c7a8a' , // Recipient
parseUnits ( '100' , 6 ) // Amount (must be concrete)
],
});
// Build quote request using build-raw
const quoteRequest = {
mode: 'smart-account' ,
ownerAddress: account . address ,
composeFlows: [
{
type: '/instructions/build-raw' ,
data: {
to: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913' , // USDC on Base
data: transferCalldata , // Pre-encoded calldata
chainId: 8453
}
}
]
};
// Get quote
const quote = await fetch ( 'https://api.biconomy.io/v1/quote' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ( quoteRequest )
}). then ( r => r . json ());
// Then sign and POST to /v1/execute
Deposit ETH to WETH contract import { encodeFunctionData , parseAbi , parseUnits } from 'viem' ;
// Encode WETH deposit
const wethAbi = parseAbi ([ 'function deposit()' ]);
const depositCalldata = encodeFunctionData ({
abi: wethAbi ,
functionName: 'deposit' ,
});
const quoteRequest = {
mode: 'smart-account' ,
ownerAddress: account . address ,
composeFlows: [
{
type: '/instructions/build-raw' ,
data: {
to: '0x4200000000000000000000000000000000000006' , // WETH on Base
data: depositCalldata ,
chainId: 8453 ,
value: parseUnits ( '1' , 18 ). toString (), // Send 1 ETH
gasLimit: '100000'
}
}
]
};
// Then quote → sign → execute
Use build-raw for static calls, /build for dynamic amounts import { encodeFunctionData , parseAbi , parseUnits } from 'viem' ;
// Encode approve calldata with concrete amount
const approveAbi = parseAbi ([ 'function approve(address spender, uint256 amount)' ]);
const approveCalldata = encodeFunctionData ({
abi: approveAbi ,
functionName: 'approve' ,
args: [
'0x...spenderAddress' ,
parseUnits ( '100' , 6 ) // Concrete approval amount
],
});
const quoteRequest = {
mode: 'eoa' ,
ownerAddress: account . address ,
fundingTokens: [{
tokenAddress: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913' ,
chainId: 8453 ,
amount: parseUnits ( '100' , 6 ). toString ()
}],
composeFlows: [
// Use build-raw for approval with fixed amount
{
type: '/instructions/build-raw' ,
data: {
to: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913' ,
data: approveCalldata ,
chainId: 8453
}
},
// Use /build for withdrawal with runtime balance
{
type: '/instructions/build' ,
data: {
functionSignature: 'function transfer(address to, uint256 value)' ,
args: [
account . address ,
{
type: 'runtimeErc20Balance' ,
tokenAddress: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913' ,
constraints: { gte: '1' }
}
],
to: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913' ,
chainId: 8453
}
}
]
};
// Then quote → sign → execute
BuildRaw vs Build Decision Guide
// ❌ CANNOT use buildRaw for runtime balance
{
type : '/instructions/build-raw' , // Wrong! Runtime balance not supported
data : {
to : tokenAddress ,
data : encodeFunctionData ({
abi: [ ... ],
functionName: 'transfer' ,
args: [ recipient , { type: 'runtimeErc20Balance' , ... }] // Error!
}),
chainId : 8453
}
}
// ✅ Use /build for runtime balance
{
type : '/instructions/build' ,
data : {
functionSignature : 'function transfer(address to, uint256 value)' ,
args : [
recipient ,
{
type: 'runtimeErc20Balance' ,
tokenAddress: tokenAddress ,
constraints: { gte: '1' }
}
],
to : tokenAddress ,
chainId : 8453
}
}
// ✅ Use buildRaw when you have concrete calldata
{
type : '/instructions/build-raw' ,
data : {
to : tokenAddress ,
data : '0xa9059cbb...' , // Pre-encoded transfer calldata
chainId : 8453
}
}
Best Practices
Validate Encoded Calldata
Always validate your encoded calldata before sending: // ✅ Good - validate calldata format
if ( ! calldata . startsWith ( '0x' ) || calldata . length < 10 ) {
throw new Error ( 'Invalid calldata format' );
}
// ✅ Good - decode to verify
import { decodeFunctionData , parseAbi } from 'viem' ;
try {
const decoded = decodeFunctionData ({
abi: transferAbi ,
data: calldata
});
console . log ( 'Decoded args:' , decoded . args );
} catch ( error ) {
throw new Error ( 'Invalid calldata encoding' );
}
Set Appropriate Gas Limits
Override gas limits for complex operations: // Simple operations
gasLimit : '50000'
// DeFi protocol interactions
gasLimit : '350000'
// Complex multi-step operations
gasLimit : '500000'
Know When to Use Build Instead
Use /instructions/build when you need:
Runtime balance injection for dynamic amounts
Better code readability with function signatures
API-side encoding to reduce client complexity
Use /instructions/build-raw when you need:
Pre-encoded calldata from external systems
Exact control over calldata format
Migration from existing systems with encoded data
Test with Small Amounts First
Always test your encoded calldata with small amounts: // ❌ Bad - testing with large amount
args : [ recipient , parseUnits ( '10000' , 6 )]
// ✅ Good - test with small amount first
args : [ recipient , parseUnits ( '1' , 6 )]
Common Use Cases
Pre-generated Transaction Data
// When you already have encoded calldata from another source
const existingCalldata = '0xa9059cbb000000000000000000000000742d35cc6639cb8d4b5d1c5d7b8b5e2e7c0c7a8a0000000000000000000000000000000000000000000000000000000000989680' ;
{
type : '/instructions/build-raw' ,
data : {
to : '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913' ,
data : existingCalldata ,
chainId : 8453
}
}
Multicall Operations
import { encodeFunctionData , parseAbi } from 'viem' ;
// Encode multicall
const multicallAbi = parseAbi ([ 'function multicall(bytes[] calldata data) returns (bytes[] memory)' ]);
const multicallData = encodeFunctionData ({
abi: multicallAbi ,
functionName: 'multicall' ,
args: [[
'0x...' , // First call
'0x...' , // Second call
'0x...' // Third call
]]
});
{
type : '/instructions/build-raw' ,
data : {
to : '0x...multicallContract' ,
data : multicallData ,
chainId : 8453 ,
gasLimit : '500000'
}
}
Troubleshooting
Common causes:
Incorrect calldata encoding
Wrong function parameters or order
Insufficient balance for operation
Missing approvals for token operations
Gas limit too low
Cannot use runtime balance
BuildRaw does NOT support runtime balance injection. If you need dynamic amounts: // ❌ Won't work with build-raw
{
type : 'runtimeErc20Balance' ,
tokenAddress : '0x...'
}
// ✅ Use /instructions/build instead
{
type : '/instructions/build' ,
data : {
functionSignature : 'function transfer(address to, uint256 value)' ,
args : [ recipient , { type: 'runtimeErc20Balance' , ... }],
to : tokenAddress ,
chainId : 8453
}
}
Calldata encoding mismatch
Verify your encoding matches the contract ABI: // Decode your calldata to verify
import { decodeFunctionData } from 'viem' ;
const decoded = decodeFunctionData ({
abi: yourAbi ,
data: yourCalldata
});
console . log ( 'Function name:' , decoded . functionName );
console . log ( 'Arguments:' , decoded . args );