Skip to main content
Extracts the function selector (first 4 bytes) from calldata. The selector identifies which function to invoke in a smart contract.

Signature

function getSelector(calldata: CallDataType): [4]u8

Parameters

  • calldata - CallData instance to extract selector from

Returns

[4]u8 - 4-byte array containing function selector

Examples

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

const calldata = CallData("0xa9059cbb...");
const selector = CallData.getSelector(calldata);

console.log(selector);
// [0xa9, 0x05, 0x9c, 0xbb]

Function Selectors

Function selector is first 4 bytes of keccak256(signature):
import { Keccak256 } from '@tevm/voltaire';

// Compute selector manually
const signature = "transfer(address,uint256)";
const hash = Keccak256.hashString(signature);
const selector = hash.slice(0, 4);

console.log(selector);
// [0xa9, 0x05, 0x9c, 0xbb]

// Same as extracting from calldata
const calldata = abi.transfer.encode(recipient, amount);
console.log(CallData.getSelector(calldata));
// [0xa9, 0x05, 0x9c, 0xbb] (identical)

Common Selectors

const ERC20_SELECTORS = {
  TRANSFER: [0xa9, 0x05, 0x9c, 0xbb],      // transfer(address,uint256)
  APPROVE: [0x09, 0x5e, 0xa7, 0xb3],       // approve(address,uint256)
  TRANSFER_FROM: [0x23, 0xb8, 0x72, 0xdd], // transferFrom(address,address,uint256)
  BALANCE_OF: [0x70, 0xa0, 0x82, 0x31],    // balanceOf(address)
  ALLOWANCE: [0xdd, 0x62, 0xed, 0x3e],     // allowance(address,address)
} as const;

function matchERC20Function(calldata: CallDataType): string {
  const selector = CallData.getSelector(calldata);

  if (selectorEquals(selector, ERC20_SELECTORS.TRANSFER)) {
    return "transfer";
  } else if (selectorEquals(selector, ERC20_SELECTORS.APPROVE)) {
    return "approve";
  }
  // ...
}

Use Cases

Function Routing

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

function routeCall(calldata: CallDataType) {
  const selector = CallData.getSelector(calldata);
  const selectorHex = Hex.fromBytes(selector);

  switch (selectorHex) {
    case "0xa9059cbb":
      return handleTransfer(calldata);
    case "0x095ea7b3":
      return handleApprove(calldata);
    case "0x23b872dd":
      return handleTransferFrom(calldata);
    default:
      throw new Error(`Unknown function: ${selectorHex}`);
  }
}

Selector Matching

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

function isTransfer(calldata: CallDataType): boolean {
  const selector = CallData.getSelector(calldata);
  const TRANSFER_SELECTOR = [0xa9, 0x05, 0x9c, 0xbb];

  return selector.every((byte, i) => byte === TRANSFER_SELECTOR[i]);
}

// Or use hasSelector helper
const isTransfer = CallData.hasSelector(calldata, "0xa9059cbb");

Transaction Filtering

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

interface Transaction {
  to: string;
  data: string;
}

function filterERC20Transfers(txs: Transaction[]): Transaction[] {
  const TRANSFER_SELECTOR = [0xa9, 0x05, 0x9c, 0xbb];

  return txs.filter(tx => {
    try {
      const calldata = CallData(tx.data);
      const selector = CallData.getSelector(calldata);
      return selector.every((byte, i) => byte === TRANSFER_SELECTOR[i]);
    } catch {
      return false;
    }
  });
}

Contract Interface Detection

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

const ERC20_SELECTORS = new Set([
  "0xa9059cbb", // transfer
  "0x095ea7b3", // approve
  "0x23b872dd", // transferFrom
  "0x70a08231", // balanceOf
]);

function looksLikeERC20(calldata: CallDataType): boolean {
  const selector = CallData.getSelector(calldata);
  const hex = Hex.fromBytes(selector);
  return ERC20_SELECTORS.has(hex);
}

Performance

Selector extraction is extremely fast (just array slice):
// Benchmark: 10M iterations
const calldata = CallData("0xa9059cbb...");

console.time("getSelector");
for (let i = 0; i < 10_000_000; i++) {
  CallData.getSelector(calldata);
}
console.timeEnd("getSelector");

// Pure JS: ~120ms
// WASM: ~65ms (1.8x faster)
// Effectively zero-cost operation

Selector Collisions

Different functions can have same selector (rare but possible):
// Example collision (contrived)
const sig1 = "transfer(address,uint256)";
const sig2 = "transferFrom(address,address)"; // Hypothetical collision

// Both hash to same first 4 bytes (extremely rare)
// Probability: 1 in 4,294,967,296 (2^32)
Real collisions are virtually impossible but:
  • Always validate full function signature when security-critical
  • Use ABI decoding to verify parameter types
  • Don’t rely solely on selector for authentication

Zero-Copy Access

Direct byte access without allocation:
import { CallData } from '@tevm/voltaire';

const calldata = CallData.fromBytes(
  new Uint8Array([0xa9, 0x05, 0x9c, 0xbb, ...])
);

// Zero-copy: Returns view of first 4 bytes
const selector = CallData.getSelector(calldata);

// Mutations visible (don't mutate!)
selector[0] = 0xff;
console.log(calldata[0]); // 0xff (affected!)
For immutability, copy selector:
const selector = CallData.getSelector(calldata).slice(); // Copy