Skip to main content
Checks whether calldata matches a specific function selector. Convenient helper for routing and filtering transactions.

Signature

function hasSelector(
  calldata: CallDataType,
  selector: string | Uint8Array | [4]u8
): boolean

Parameters

  • calldata - CallData instance to check
  • selector - Expected selector to match against:
    • string - Hex string (e.g., "0xa9059cbb")
    • Uint8Array - Byte array
    • [4]u8 - 4-byte tuple

Returns

boolean - true if selector matches, false otherwise

Examples

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

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

// Check against hex string
const isTransfer = CallData.hasSelector(calldata, "0xa9059cbb");
console.log(isTransfer); // true

// Check against different selector
const isApprove = CallData.hasSelector(calldata, "0x095ea7b3");
console.log(isApprove); // false

Selector Formats

Accepts multiple input formats:
// With 0x prefix
CallData.hasSelector(calldata, "0xa9059cbb");

// Without 0x prefix
CallData.hasSelector(calldata, "a9059cbb");

// Both work identically

Use Cases

Transaction Filtering

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

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

// Filter transactions by function
function filterTransfers(txs: Transaction[]): Transaction[] {
  return txs.filter(tx => {
    try {
      const calldata = CallData(tx.data);
      return CallData.hasSelector(calldata, "0xa9059cbb");
    } catch {
      return false; // Invalid calldata
    }
  });
}

Multi-Selector Matching

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

const ERC20_WRITE_SELECTORS = [
  "0xa9059cbb", // transfer
  "0x095ea7b3", // approve
  "0x23b872dd", // transferFrom
];

function isERC20WriteCall(calldata: CallDataType): boolean {
  return ERC20_WRITE_SELECTORS.some(selector =>
    CallData.hasSelector(calldata, selector)
  );
}

Access Control

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

const ALLOWED_FUNCTIONS = [
  "0xa9059cbb", // transfer
  "0x095ea7b3", // approve
];

function validateCall(calldata: CallDataType): void {
  const isAllowed = ALLOWED_FUNCTIONS.some(selector =>
    CallData.hasSelector(calldata, selector)
  );

  if (!isAllowed) {
    throw new Error("Function not allowed");
  }
}

Event Handler Selection

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

class TransactionMonitor {
  private handlers = new Map<string, (data: CallDataType) => void>();

  register(selector: string, handler: (data: CallDataType) => void) {
    this.handlers.set(selector, handler);
  }

  async process(tx: Transaction) {
    const calldata = CallData(tx.data);

    for (const [selector, handler] of this.handlers) {
      if (CallData.hasSelector(calldata, selector)) {
        await handler(calldata);
        return;
      }
    }

    console.log("No handler for selector");
  }
}

// Usage
const monitor = new TransactionMonitor();
monitor.register("0xa9059cbb", handleTransfer);
monitor.register("0x095ea7b3", handleApprove);

Performance

Constant-time comparison (4-byte check):
// Benchmark: 10M iterations
const calldata = CallData("0xa9059cbb...");

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

// Pure JS: ~180ms
// WASM: ~95ms (1.9x faster)
// Extremely fast (just 4-byte comparison)

Comparison with Manual Check

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

// Concise and clear
if (CallData.hasSelector(calldata, "0xa9059cbb")) {
  handleTransfer(calldata);
}
Advantages:
  • Clear intent
  • Handles multiple selector formats
  • Optimized implementation
  • Type-safe
Use hasSelector for cleaner, faster code.

Constant-Time Comparison

Implementation uses constant-time comparison to prevent timing attacks:
// Pseudocode (actual implementation)
function hasSelector(calldata: CallDataType, expected: [4]u8): boolean {
  const actual = getSelector(calldata);

  // Constant-time: Always checks all 4 bytes
  let result = 0;
  for (let i = 0; i < 4; i++) {
    result |= actual[i] ^ expected[i];
  }

  return result === 0;
}
Early returns would leak timing information about which byte differs.

Type Safety

Enforces CallData type at compile time:
import { CallData, type CallDataType } from '@tevm/voltaire';

// Type error: Uint8Array not CallDataType
const bytes = new Uint8Array([0xa9, 0x05, 0x9c, 0xbb]);
CallData.hasSelector(bytes, "0xa9059cbb"); // ❌

// Correct: Use CallData constructor
const calldata: CallDataType = CallData.fromBytes(bytes);
CallData.hasSelector(calldata, "0xa9059cbb"); // ✅