Skip to main content
This instruction type enables direct contract interaction by specifying exact function calls. Provide ABI signatures and arguments to call any EVM contract as part of your supertransaction.

How It Works

The build flow:
  1. Accepts function signatures - ABI-formatted function definitions
  2. Processes arguments - Supports static values and runtime balance injection
  3. Generates instructions - Creates executable MEE instructions
  4. Enables composition - Combines with other flow types seamlessly

Parameters

When using /instructions/build in your composeFlows array:
ParameterTypeRequiredDescription
functionSignaturestringYesABI function signature (e.g., function transfer(address to, uint256 amount))
argsarrayYesFunction arguments array
tostringYesTarget contract address (checksummed)
chainIdnumberYesChain ID for execution
valuestringNoNative token value in wei (default: “0”)
gasLimitstringNoGas limit override

Runtime Balance Injection

The build flow type supports runtimeErc20Balance for dynamic amount calculations:
{
  type: 'runtimeErc20Balance',
  tokenAddress: '0x...',
  constraints: { gte: '1' }  // Optional: minimum balance required
}
Runtime balance is currently only supported for /instructions/build flow type. Support for other types coming in future updates.

Complete Workflow Examples

  • Token Transfer
  • With Runtime Balance
  • DeFi Operations
  • Native ETH Withdrawal
Simple ERC20 transfer
import { createWalletClient, http, parseUnits } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { base } from 'viem/chains';

const account = privateKeyToAccount('0x...');
const walletClient = createWalletClient({
  account,
  chain: base,
  transport: http()
});

// Step 1: Build quote request
const quoteRequest = {
  mode: 'smart-account',
  ownerAddress: account.address,
  composeFlows: [
    {
      type: '/instructions/build',
      data: {
        functionSignature: 'function transfer(address to, uint256 value)',
        args: [
          '0x742d35cc6639cb8d4b5d1c5d7b8b5e2e7c0c7a8a', // recipient
          parseUnits('10', 6).toString() // 10 USDC
        ],
        to: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', // USDC on Base
        chainId: 8453
      }
    }
  ]
};

// Step 2: 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

Argument Types

The args array supports various data types:

Static Values

args: [
  '0x742d35cc6639cb8d4b5d1c5d7b8b5e2e7c0c7a8a',  // address
  '1000000',                                         // uint256 as string
  true,                                              // boolean
  ['0x...', '0x...']                                // address[] array
]

Runtime Balance (Dynamic)

args: [
  recipient,
  {
    type: 'runtimeErc20Balance',
    tokenAddress: '0x...',
    constraints: { gte: '1' } // Optional minimum
  }
]

Complex Types

// Struct arguments
args: [
  {
    token: '0x...',
    amount: '1000000',
    recipient: '0x...'
  }
]

// Nested arrays
args: [
  [
    ['0x...', '100'],
    ['0x...', '200']
  ]
]

Best Practices

Always use runtime balance when withdrawing after swaps (for EOA mode):
{
  type: '/instructions/build',
  data: {
    functionSignature: 'function transfer(address to, uint256 value)',
    args: [
      ownerAddress,
      {
        type: 'runtimeErc20Balance',
        tokenAddress: resultToken,
        constraints: { gte: '1' }
      }
    ],
    to: resultToken,
    chainId: dstChainId
  }
}
Override gas limits for complex operations:
// Simple transfer
gasLimit: '50000'

// DeFi protocol interactions
gasLimit: '350000'

// Complex multi-step operations
gasLimit: '500000'
Same-chain build instructions are automatically batched:
composeFlows: [
  {
    type: '/instructions/build',
    // batch: true (default)
    data: { chainId: 8453, ... }
  },
  {
    type: '/instructions/build',
    // Automatically batched with above
    data: { chainId: 8453, ... }
  }
]
Ensure function signatures match the ABI exactly:
// ✅ Correct
functionSignature: 'function transfer(address to, uint256 value)'

// ❌ Wrong (missing 'function' keyword)
functionSignature: 'transfer(address to, uint256 value)'

// ❌ Wrong (incorrect parameter names - doesn't matter but be consistent)
functionSignature: 'function transfer(address recipient, uint256 amt)'
When withdrawing or transferring native tokens (ETH, MATIC, BNB, etc.), always use the ETH Forwarder contract:
import { zeroAddress } from 'viem';

// ❌ Bad - Cannot use ERC20 transfer for native tokens
{
  type: '/instructions/build',
  data: {
    functionSignature: 'function transfer(address to, uint256 value)',
    args: [ownerAddress, parseUnits('0.1', 18).toString()],
    to: zeroAddress,  // Native ETH - transfer() won't work
    chainId: 8453
  }
}

// ✅ Good - Use ETH forwarder for native tokens
{
  type: '/instructions/build',
  data: {
    functionSignature: 'function forward(address recipient)',
    args: [ownerAddress],
    to: '0x000000Afe527A978Ecb761008Af475cfF04132a1',  // ETH forwarder
    chainId: 8453,
    value: parseUnits('0.1', 18).toString(),  // Amount to forward
    gasLimit: '300000'
  }
}

Common Use Cases

Withdraw from DeFi Protocol

{
  type: '/instructions/build',
  data: {
    functionSignature: 'function withdraw(address asset, uint256 amount, address to)',
    args: [
      '0x...tokenAddress',
      parseUnits('100', 18).toString(),
      account.address
    ],
    to: '0x...aavePoolAddress',
    chainId: 8453,
    gasLimit: '300000'
  }
}

NFT Transfer

{
  type: '/instructions/build',
  data: {
    functionSignature: 'function safeTransferFrom(address from, address to, uint256 tokenId)',
    args: [
      account.address,
      '0x...recipientAddress',
      '42' // NFT token ID
    ],
    to: '0x...nftContractAddress',
    chainId: 8453
  }
}

Custom DEX Swap

{
  type: '/instructions/build',
  data: {
    functionSignature: 'function swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address[] path, address to, uint256 deadline)',
    args: [
      parseUnits('100', 6).toString(),
      parseUnits('95', 6).toString(),
      ['0x...tokenA', '0x...tokenB'],
      account.address,
      Math.floor(Date.now() / 1000) + 3600 // 1 hour deadline
    ],
    to: '0x...routerAddress',
    chainId: 8453,
    gasLimit: '200000'
  }
}

Troubleshooting

Ensure your signature exactly matches the contract ABI:
  • Include the function keyword
  • Use correct parameter types (e.g., uint256, not uint)
  • Match parameter names (though names don’t affect encoding)
Common causes:
  • Insufficient balance for operation
  • Missing token approvals (add approve instruction first)
  • Gas limit too low (increase gasLimit)
  • Contract-specific requirements not met
Runtime balance is only supported for /instructions/build:
  • Cannot use in /instructions/intent-simple
  • Cannot use in /instructions/intent
  • Must be in the args array, not other parameters