Web3 & Smart Contracts · 2 min read

Decoding EVM Transaction Calldata

Calldata is the binary payload that travels with every contract transaction on Ethereum and EVM-compatible chains. The first 4 bytes are a function selector. Everything after is ABI-encoded arguments. If you can read calldata, you can answer the question every wallet user should ask before signing: what is this transaction actually going to do.

Try it now

ABI Decoder

Runs in your browser, nothing leaves your device.

Every external function call begins with a 4-byte selector, the first 4 bytes of keccak256 of the function's canonical signature. For example, transfer(address,uint256) hashes to 0xa9059cbb. When you see calldata starting with 0xa9059cbb, you are looking at an ERC-20 transfer. Selectors are not unique by design, two unrelated functions can collide, but in practice common selectors are well known.

After the selector, arguments are packed in 32-byte slots. Static types (address, uint256, bool) take exactly one slot. Dynamic types (bytes, string, arrays) use a head/tail layout where the head holds an offset into the tail section. A transfer(address,uint256) call always has 68 bytes of calldata: 4 for the selector, 32 for the recipient, 32 for the amount.

Match the selector against a database to identify the function. Confirm the destination address in the transaction matches the contract you expected. Decode the parameters and check that recipients and amounts make sense. If the UI says you are swapping 1 ETH for USDC but the calldata calls approve() with max uint256, the UI is lying.

ERC-20 transfer

Input
0xa9059cbb000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa960450000000000000000000000000000000000000000000000000de0b6b3a7640000
Result
transfer(address to=0xd8dA...6045, uint256 amount=1000000000000000000) — 1.0 token (assuming 18 decimals)

ERC-20 unlimited approval

Input
0x095ea7b3000000000000000000000000<spender>ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
Result
approve(address spender=<spender>, uint256 amount=2^256-1) — grants the spender permission to drain every token of this type from your wallet

Security context

Wallet UIs simulate transactions, but simulations can be fooled by contracts that branch on msg.sender or block number. Reading the raw calldata is the only ground truth you have before signing. If your wallet shows you a friendly description but cannot decode the calldata yourself, treat the transaction as unverified.

Dynamic types like bytes and arrays add length prefixes and padding. A function taking a single bytes argument might produce 100+ bytes of calldata even for a small input. The ABI specification requires every argument slot to be a multiple of 32 bytes.

Yes. Selectors are 4 bytes (32 bits), so collisions exist. Attackers occasionally craft function names that collide with well-known selectors to confuse decoders. Always verify against the contract's actual ABI when stakes are high.

Related techniques

Comparing (/4):

Tool Comparison

Feature
Type
Pricing
Platforms
Description