Documentation Index
Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
Use this file to discover all available pages before exploring further.
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
calldata.hasSelector(
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
Basic Usage
Function Routing
Byte Array Selector
Class API
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
import { CallData } from '@tevm/voltaire';
function routeERC20Call(calldata: CallDataType) {
if (CallData.hasSelector(calldata, "0xa9059cbb")) {
return handleTransfer(calldata);
} else if (CallData.hasSelector(calldata, "0x095ea7b3")) {
return handleApprove(calldata);
} else if (CallData.hasSelector(calldata, "0x23b872dd")) {
return handleTransferFrom(calldata);
} else {
throw new Error("Unknown function");
}
}
import { CallData } from '@tevm/voltaire';
const TRANSFER_SELECTOR = new Uint8Array([0xa9, 0x05, 0x9c, 0xbb]);
const isTransfer = CallData.hasSelector(calldata, TRANSFER_SELECTOR);
console.log(isTransfer);
import { CallData } from '@tevm/voltaire';
const calldata = CallData("0xa9059cbb...");
const isTransfer = calldata.hasSelector("0xa9059cbb"); // Method on instance
Accepts multiple input formats:
Hex String
Uint8Array
Tuple
// With 0x prefix
CallData.hasSelector(calldata, "0xa9059cbb");
// Without 0x prefix
CallData.hasSelector(calldata, "a9059cbb");
// Both work identically
const selector = new Uint8Array([0xa9, 0x05, 0x9c, 0xbb]);
CallData.hasSelector(calldata, selector);
const selector: [4]u8 = [0xa9, 0x05, 0x9c, 0xbb];
CallData.hasSelector(calldata, selector);
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);
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
Using hasSelector
Manual Comparison
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
import { CallData, Hex } from '@tevm/voltaire';
// Verbose
const selector = CallData.getSelector(calldata);
const selectorHex = Hex.fromBytes(selector);
if (selectorHex === "0xa9059cbb") {
handleTransfer(calldata);
}
Disadvantages:
- More verbose
- Manual hex conversion
- Easy to make mistakes
- Slower (string comparison)
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"); // ✅