Skip to main content
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:
  1. Accepts pre-encoded calldata - Raw hex strings representing complete function calls
  2. Validates calldata format - Ensures proper hex encoding
  3. Generates instructions - Creates executable MEE instructions
  4. 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-raw
InputFunction signature + argsPre-encoded calldata
Runtime Balance✅ SupportedNOT Supported
Client EncodingAPI encodesClient encodes
Use CaseMost common operationsAdvanced/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:
ParameterTypeRequiredDescription
datastringYesPre-encoded calldata as hex string (e.g., “0xa9059cbb…”)
tostringYesTarget contract address (checksummed)
chainIdnumberYesChain ID for execution
valuestringNoNative token value in wei (default: “0”)
gasLimitstringNoGas 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

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

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');
}
Override gas limits for complex operations:
// Simple operations
gasLimit: '50000'

// DeFi protocol interactions
gasLimit: '350000'

// Complex multi-step operations
gasLimit: '500000'
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
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

Ensure your calldata:
  • Starts with 0x
  • Is a valid hex string
  • Has even length (excluding 0x)
  • Matches the expected function selector
Common causes:
  • Incorrect calldata encoding
  • Wrong function parameters or order
  • Insufficient balance for operation
  • Missing approvals for token operations
  • Gas limit too low
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
  }
}
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);
I