For educational purposes only; not investment, legal, or security advice. Decoding and simulation reduce ambiguity but do not guarantee authorization, execution, asset safety, or finality.
Direct answer
Calldata is the immutable byte string supplied as input to a top-level Ethereum transaction or an internal message call. A conventional Solidity function call starts with a 4-byte selector followed by ABI-encoded arguments, but calldata is not self-describing: the same bytes can mean different things for different runtime code, proxy implementations or schemas. Fallback functions, raw assembly and non-Solidity protocols need not follow the conventional function ABI at all.
A wallet should therefore show more than a candidate function name. Safe review binds the bytes to chainId, a block, from, to, native value, runtime codeHash, the active implementation and a trusted ABI; strictly decodes every nested call; distinguishes on-chain calldata from EIP-712 signatures; simulates in an explicit state; and reconciles the actual receipt and state changes after inclusion.
Completing this review does not prove an asset, transaction, or system is safe.
How it works
- Pin the signing envelope and observation point:
chainId, block number and hash,from,to, nativevalue, input bytes, nonce and fee fields. Preserve the wallet or RPC source; a decoded payload from another chain or block is not the same claim. - Classify the object before decoding. A transaction, EIP-712 typed-data request, ERC-2612 permit, ERC-4337 UserOperation and raw personal-sign message use different domains and schemas; do not force all of them through the transaction ABI.
- Resolve the target at the pinned block. Read runtime bytecode and
codeHash; identify a proxy, beacon or implementation where applicable; record implementation and admin slots; and obtain an ABI that matches that exact code version. A selector registry provides candidates, not authority. - Decode strictly. The selector is the first
4 bytesof Keccak-256 over the canonical function signature, excluding return types. Static values occupy32-bytewords; dynamic heads contain offsets from the arguments block after the selector. Reject truncated data, out-of-bounds offsets, impossible lengths, invalid padding and unexplained trailing bytes. - Recursively expand multicalls, nested calldata and delegated execution. For every child, list target, native value, selector, arguments, call type and any
allowFailureflag. Withdelegatecall, implementation code runs in the caller’s address, balance and storage context while preservingmsg.senderandmsg.value. - Build separate authority and value ledgers, then simulate. Record recipients, spenders, NFT operators, raw token units, decimals, deadlines, slippage bounds and native value. Simulate with the exact block, sender and value, but treat the result as a conditional snapshot because state, prices, time, code and transaction ordering can change.
- Confirm every material field before signing. After inclusion, inspect receipt status, logs, traces where available and balance, allowance and operator-state deltas; distinguish caught child failures from top-level success; account for gas even on a revert; wait for the required finality; and stop rather than blindly re-signing an unexplained failure.
Worked examples
- Static ERC-20 transfer.
transfer(address,uint256)commonly uses selector0xa9059cbb. One selector plus two ABI words is4 + 2 * 32 = 68 bytes. A raw amount of1,500,000for a token independently verified to use6 decimalsdisplays as1.5 tokens. Decimals are external contract metadata, not encoded in those arguments, and the selector alone does not identify the contract or function uniquely. - Dynamic bytes offset. For
f(address,bytes)with a3-bytepayload, the two-word head occupies64 bytes. The dynamic offset is0x40, measured from the start of the arguments block and excluding the selector. Its tail contains one32-bytelength word and one32-bytepadded data word, so total calldata is4 + 64 + 32 + 32 = 132 bytes. Treating the offset as absolute from byte zero lands four bytes late. - Batch value is implementation-specific. An outer call carries
1.00 ETH; three decoded children explicitly request0.20 ETH,0.30 ETHand0.10 ETH, totaling0.60 ETH. The remaining0.40 ETHmight be refunded, retained, forwarded or cause a revert depending on the batch code. If the third child fails withallowFailure=true, earlier children may remain committed; an atomic implementation may instead revert everything. - A permit is not the relayer’s calldata at signing time. An owner with
1,000 USDCsigns an ERC-2612 permit for300 USDCat nonce41. The signature alone changes neither balance nor allowance. After a relayer successfully submits it, nonce becomes42and allowance becomes300; after the spender uses180, balance is820and remaining allowance is120. Disconnecting the site does not revoke the permission.
Risks
- Decoding against the wrong chain, fork, block tag or transaction envelope.
- Signing for a spoofed domain, target address or recipient.
- Treating a
4-byteselector as unique despite possible collisions. - Using a guessed, stale or incorrectly verified ABI.
- Trusting a verified source label without matching current runtime
codeHash. - Missing an implementation, beacon or admin upgrade between review and execution.
- Ignoring a proxy function whose selector clashes with the implementation.
- Forgetting that
delegatecallwrites in the caller’s storage context. - Accepting malformed dynamic offsets, lengths, padding or trailing bytes.
- Failing to expand a nested batch that hides targets, values or permissions.
- Assuming atomicity when the implementation catches or permits child failures.
- Ignoring top-level native
valuebecause token arguments look harmless. - Applying wrong decimals or assuming fee-on-transfer and rebasing tokens are standard ERC-20s.
- Granting unlimited ERC-20 allowance or mishandling the allowance-update race.
- Overlooking the collection-wide scope of NFT
setApprovalForAll. - Mistaking EIP-712 typed data or an ERC-2612 permit for transaction calldata.
- Missing nonce, deadline, verifying-contract, chain-domain or replay boundaries.
- Treating simulation as stable despite oracle, timestamp, pending-state, MEV or code changes.
- Treating receipt status, logs or provider traces as a complete economic state proof.
- Blindly re-signing through a compromised UI or ignoring inclusion, reorg and finality risk.
Common misconceptions
- A function selector uniquely identifies what the contract will execute.
- A verified front-end summary is identical to the bytes and current implementation being signed.
- A transaction with
value=0cannot move tokens, NFTs or delegated assets. - Successful simulation or a successful receipt proves safety and the intended economic outcome.
- Disconnecting a dapp revokes approvals, permits and NFT operator permissions.
Related topics
Sources
- Contract ABI Specification - Solidity Documentation (accessed: 2026-08-12)
- Introduction to Smart Contracts - Solidity Documentation (accessed: 2026-08-12)
- Transactions - Ethereum.org (accessed: 2026-08-12)
- ERC-20: Token Standard - Ethereum Improvement Proposals (accessed: 2026-08-12)
- EIP-712: Typed structured data hashing and signing - Ethereum Improvement Proposals (accessed: 2026-08-12)
- ERC-2612: Permit Extension for EIP-20 Signed Approvals - Ethereum Improvement Proposals (accessed: 2026-08-12)
- ERC-721: Non-Fungible Token Standard - Ethereum Improvement Proposals (accessed: 2026-08-12)
- ERC-1967: Proxy Storage Slots - Ethereum Improvement Proposals (accessed: 2026-08-12)