Skip to main content
Decodes calldata into a structured form with parsed selector, signature, and ABI-decoded parameters.

Signature

function decode(
  calldata: CallDataType,
  abi: Abi
): CallDataDecoded

Parameters

  • calldata - CallData instance to decode
  • abi - ABI specification with function definitions

Returns

CallDataDecoded - Structured object with:
  • selector: 4-byte function identifier
  • signature: Human-readable function signature (optional)
  • parameters: Array of decoded ABI values

Examples

import { CallData, Abi } from '@tevm/voltaire';

const abi = Abi([{
  name: "transfer",
  type: "function",
  inputs: [
    { name: "to", type: "address" },
    { name: "amount", type: "uint256" }
  ]
}]);

const calldata = CallData("0xa9059cbb...");
const decoded = CallData.decode(calldata, abi);

console.log(decoded.selector); // [0xa9, 0x05, 0x9c, 0xbb]
console.log(decoded.signature); // "transfer(address,uint256)"
console.log(decoded.parameters[0]); // Address("0x...")
console.log(decoded.parameters[1]); // Uint256(...)

CallDataDecoded Structure

export type CallDataDecoded = {
  selector: [4]u8;        // 4-byte function selector
  signature: ?[]const u8; // Optional function signature
  parameters: []AbiValue; // Decoded parameters
};

Fields

selector: First 4 bytes identifying the function
  • Computed from keccak256(signature)
  • Always present even without ABI
signature: Human-readable function name and types
  • Example: "transfer(address,uint256)"
  • Useful for debugging and logging
  • Optional (null if ABI unavailable)
parameters: Typed parameter values
  • Array of decoded ABI values (Address, Uint256, etc.)
  • Preserves type information
  • Empty array if no parameters

Validation

Validates calldata against ABI:
import { CallData, Abi } from '@tevm/voltaire';

const abi = Abi([{
  name: "transfer",
  type: "function",
  inputs: [
    { name: "to", type: "address" },
    { name: "amount", type: "uint256" }
  ]
}]);

// Wrong selector
try {
  const calldata = CallData("0x12345678..."); // Unknown function
  CallData.decode(calldata, abi);
} catch (error) {
  console.error("Function not found in ABI");
}

// Invalid encoding
try {
  const calldata = CallData("0xa9059cbb1234"); // Truncated
  CallData.decode(calldata, abi);
} catch (error) {
  console.error("Invalid parameter encoding");
}

Use Cases

Transaction Analysis

import { CallData, Abi } from '@tevm/voltaire';

const ERC20_ABI = Abi([
  { name: "transfer", type: "function", inputs: [/*...*/] },
  { name: "approve", type: "function", inputs: [/*...*/] },
]);

function analyzeTransaction(tx: Transaction) {
  try {
    const decoded = CallData.decode(tx.data, ERC20_ABI);

    console.log("Function:", decoded.signature);
    console.log("Parameters:");
    decoded.parameters.forEach((param, i) => {
      console.log(`  [${i}]:`, param);
    });

    return decoded;
  } catch {
    console.log("Unknown or non-ERC20 transaction");
    return null;
  }
}

Event Indexing

import { CallData, Abi } from '@tevm/voltaire';

interface IndexedCall {
  function: string;
  params: Record<string, unknown>;
  timestamp: number;
}

async function indexCallData(
  calldata: CallDataType,
  abi: Abi
): Promise<IndexedCall> {
  const decoded = CallData.decode(calldata, abi);

  // Map parameters to names
  const functionDef = abi.find(decoded.signature);
  const params: Record<string, unknown> = {};

  functionDef.inputs.forEach((input, i) => {
    params[input.name] = decoded.parameters[i];
  });

  return {
    function: decoded.signature!,
    params,
    timestamp: Date.now(),
  };
}

Smart Contract Testing

import { CallData, Abi } from '@tevm/voltaire';
import { describe, it, expect } from 'vitest';

describe('Contract decoding', () => {
  const abi = Abi([/*...*/]);

  it('decodes transfer correctly', () => {
    const calldata = abi.transfer.encode(recipient, amount);
    const decoded = CallData.decode(calldata, abi);

    expect(decoded.signature).toBe('transfer(address,uint256)');
    expect(decoded.parameters[0]).toEqual(recipient);
    expect(decoded.parameters[1]).toEqual(amount);
  });
});

Performance

Decoding optimized in WASM:
// Benchmark: 1M iterations
const calldata = abi.transfer.encode(
  Address("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"),
  TokenBalance.fromUnits("1", 18)
);

console.time("decode");
for (let i = 0; i < 1_000_000; i++) {
  CallData.decode(calldata, abi);
}
console.timeEnd("decode");

// Pure JS: ~920ms
// WASM (ReleaseSmall): ~380ms (2.4x faster)
// WASM (ReleaseFast): ~210ms (4.4x faster)

Error Handling

Decode can fail for multiple reasons:
const abi = Abi([
  { name: "transfer", type: "function", inputs: [/*...*/] }
]);

// Selector not in ABI
const calldata = CallData("0x095ea7b3..."); // approve()

try {
  CallData.decode(calldata, abi);
} catch (error) {
  console.error("Function not found in ABI");
  // Fallback: Extract selector only
  const selector = CallData.getSelector(calldata);
}

Memory Management (Zig)

In Zig, decoded parameters own allocated memory:
const std = @import("std");
const CallData = @import("primitives").CallData;

pub fn processCallData(
    allocator: std.mem.Allocator,
    calldata: CallData,
) !void {
    var decoded = try calldata.decode(allocator);
    defer decoded.deinit(); // Free allocated memory

    // Use decoded parameters
    for (decoded.parameters) |param| {
        switch (param) {
            .address => |addr| try handleAddress(addr),
            .uint256 => |val| try handleUint256(val),
            else => {},
        }
    }
}