How Function Selectors Work
The function selector is the first 4 bytes of the keccak256 hash of the canonical function signature:Copy
Ask AI
import { Keccak256 } from 'tevm'
// biome-ignore lint/suspicious/noShadowRestrictedNames: Function is the ABI namespace
import * as Function from 'tevm/Abi/function'
// Get canonical signature
const signature = Function.getSignature(transferFn)
// "transfer(address,uint256)"
// Hash with keccak256
const hash = Keccak256.hash(signature)
// "0xa9059cbb2ab09eb13591db4583a4daa828198961f519e24c10cbf0c16b83d8f6"
// Take first 4 bytes
const selector = hash.slice(0, 10) // "0x" + 8 hex chars = 4 bytes
// "0xa9059cbb"
Usage Examples
Basic Usage
Copy
Ask AI
import { Function } from 'tevm'
const balanceOfFn = new Function({
type: "function",
name: "balanceOf",
inputs: [{ type: "address", name: "owner" }],
outputs: [{ type: "uint256" }]
})
const selector = balanceOfFn.getSelector()
console.log(selector) // "0x70a08231"
Matching Selectors
Copy
Ask AI
import { Function } from 'tevm'
const fn1 = new Function({
type: "function",
name: "transfer",
inputs: [
{ type: "address", name: "to" },
{ type: "uint256", name: "amount" }
]
})
const fn2 = new Function({
type: "function",
name: "transfer",
inputs: [
{ type: "address", name: "recipient" },
{ type: "uint256", name: "value" }
]
})
// Same signature = same selector (parameter names don't matter)
console.log(fn1.getSelector() === fn2.getSelector()) // true
Detecting Function from Calldata
Copy
Ask AI
import { Function } from 'tevm'
const transferFn = new Function({
type: "function",
name: "transfer",
inputs: [
{ type: "address", name: "to" },
{ type: "uint256", name: "amount" }
]
})
const approveFn = new Function({
type: "function",
name: "approve",
inputs: [
{ type: "address", name: "spender" },
{ type: "uint256", name: "amount" }
]
})
// Extract selector from calldata
const calldata = "0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc9e7595f51e3e00000000000000000000000000000000000000000000000000000000000003e8"
const calldataSelector = calldata.slice(0, 10) // "0xa9059cbb"
// Check which function was called
if (calldataSelector === transferFn.getSelector()) {
console.log("Transfer function called")
}
Building Function Registry
Copy
Ask AI
import { Function } from 'tevm'
const functions = [
new Function({
type: "function",
name: "transfer",
inputs: [
{ type: "address", name: "to" },
{ type: "uint256", name: "amount" }
]
}),
new Function({
type: "function",
name: "approve",
inputs: [
{ type: "address", name: "spender" },
{ type: "uint256", name: "amount" }
]
}),
new Function({
type: "function",
name: "balanceOf",
inputs: [{ type: "address", name: "owner" }]
})
]
// Create selector → function mapping
const registry = new Map(
functions.map(fn => [fn.getSelector(), fn])
)
// Look up function by selector
const selector = "0xa9059cbb"
const fn = registry.get(selector)
console.log(fn?.name) // "transfer"
Error Handling
Copy
Ask AI
import { Function } from 'tevm'
try {
const fn = new Function({
type: "function",
name: "", // Invalid: empty name
inputs: []
})
fn.getSelector()
} catch (error) {
console.error("Invalid function definition")
}
Selector Collisions
While rare, 4-byte selectors can theoretically collide. The EVM uses only the selector to route calls, so two functions with different names but identical selectors would conflict:Copy
Ask AI
// These would have different selectors (collision unlikely but possible)
const fn1 = new Function({
type: "function",
name: "transferFrom",
inputs: [
{ type: "address", name: "from" },
{ type: "address", name: "to" },
{ type: "uint256", name: "amount" }
]
})
const fn2 = new Function({
type: "function",
name: "safeTransferFrom",
inputs: [
{ type: "address", name: "from" },
{ type: "address", name: "to" },
{ type: "uint256", name: "amount" }
]
})
console.log(fn1.getSelector()) // "0x23b872dd"
console.log(fn2.getSelector()) // "0x42842e0e" (different)
See Also
- getSignature - Get canonical function signature string
- encodeParams - Encode function calldata
- decodeParams - Decode function parameters

