Skip to main content
Encode view function calls and decode return values to read contract state.
import { Abi } from '@tevm/voltaire/Abi';
import { Hex } from '@tevm/voltaire/Hex';
import { Address } from '@tevm/voltaire/Address';

// ERC-20 view functions
const erc20Abi = Abi([
  {
    type: "function",
    name: "balanceOf",
    stateMutability: "view",
    inputs: [{ type: "address", name: "account" }],
    outputs: [{ type: "uint256", name: "" }]
  },
  {
    type: "function",
    name: "name",
    stateMutability: "view",
    inputs: [],
    outputs: [{ type: "string", name: "" }]
  },
  {
    type: "function",
    name: "symbol",
    stateMutability: "view",
    inputs: [],
    outputs: [{ type: "string", name: "" }]
  },
  {
    type: "function",
    name: "decimals",
    stateMutability: "view",
    inputs: [],
    outputs: [{ type: "uint8", name: "" }]
  }
]);

// Encode balanceOf(address) call
const account = "0x742d35cc6634c0532925a3b844bc9e7595f251e3";
const calldata = erc20Abi.encode("balanceOf", [account]);

// Make eth_call (pseudo-code for RPC)
// const result = await provider.call({
//   to: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
//   data: Hex.fromBytes(calldata)
// });

// Decode the return value
const returnData = Hex.toBytes(
  "0x00000000000000000000000000000000000000000000000000000000000f4240"
);
const [balance] = erc20Abi.decode("balanceOf", returnData);
// balance = 1000000n (1 USDC with 6 decimals)

Read Token Metadata

// Encode name() call (no arguments)
const nameCalldata = erc20Abi.encode("name", []);

// Decode string return value
const nameReturnData = Hex.toBytes(
  "0x0000000000000000000000000000000000000000000000000000000000000020" +
  "0000000000000000000000000000000000000000000000000000000000000008" +
  "5553442054657468657200000000000000000000000000000000000000000000"
);
const [name] = erc20Abi.decode("name", nameReturnData);
// name = "USD Tether"

// Encode decimals() call
const decimalsCalldata = erc20Abi.encode("decimals", []);

// Decode uint8 return value
const decimalsReturnData = Hex.toBytes(
  "0x0000000000000000000000000000000000000000000000000000000000000006"
);
const [decimals] = erc20Abi.decode("decimals", decimalsReturnData);
// decimals = 6n

Low-Level Encoding

Use Abi.Function for more control:
// Get function from ABI
const balanceOfFn = erc20Abi.getFunction("balanceOf");

// Get function selector (first 4 bytes of keccak256 hash)
const selector = Abi.Function.getSelector(balanceOfFn);
// 0x70a08231 = keccak256("balanceOf(address)")[0:4]

// Encode just the parameters (without selector)
const params = Abi.Function.encodeParams(balanceOfFn, [account]);

// Decode return value
const result = Abi.Function.decodeResult(balanceOfFn, returnData);
// [1000000n]

Batch Multiple Calls

Encode multiple view calls for multicall:
const calls = [
  { fn: "name", args: [] },
  { fn: "symbol", args: [] },
  { fn: "decimals", args: [] },
  { fn: "balanceOf", args: [account] }
];

const encodedCalls = calls.map(({ fn, args }) => ({
  target: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
  callData: Hex.fromBytes(erc20Abi.encode(fn, args))
}));

// Use with Multicall3 contract for batched reads
This is a fully executable example. View the complete source with test assertions at examples/contracts/read-contract.ts.