Web3 & Smart Contracts · 2 min read

Detecting Token Approval Phishing in Calldata

Most stolen Web3 funds are not stolen by hackers breaking cryptography. They are stolen by users signing approval transactions that grant a contract permission to move tokens, then the contract moves them. The pattern is always the same: a fake mint, a fake claim, a fake airdrop, anything that gets a wallet to approve an unfamiliar spender. If you can read calldata, you can refuse to sign.

Try it now

ABI Decoder

Runs in your browser, nothing leaves your device.

0x095ea7b3 is approve(address,uint256) for ERC-20. 0xa22cb465 is setApprovalForAll(address,bool) for ERC-721 and ERC-1155. 0xd505accf is permit(...) which lets a third party submit an approval on your behalf using your signature. Any transaction whose calldata starts with one of these selectors is granting someone else access to your assets, not moving anything yet.

An approve() amount of 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff (32 bytes of f) is uint256 max, often called infinite approval. Many legitimate dApps use this for gas efficiency, but it means the spender can drain that token at any time, forever, until you revoke. setApprovalForAll(spender, true) is the NFT equivalent and gives access to every NFT in that collection.

The spender address is the second 32-byte slot in the calldata, padded to the left with zeros. If the spender does not match the dApp you think you are using, stop. Search the address on Etherscan. Check whether the contract is verified, when it was deployed, and how many transactions it has. A freshly deployed unverified contract asking for setApprovalForAll on your high-value NFT collection is a drainer.

Suspicious unlimited USDT approval

Input
0x095ea7b3000000000000000000000000abcd...1234ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
Result
approve(spender=0xabcd...1234, amount=uint256_max) on USDT contract. If 0xabcd...1234 is not a router you recognize, refusing to sign costs nothing.

Wallet drainer NFT pattern

Input
0xa22cb465000000000000000000000000<drainer>0000000000000000000000000000000000000000000000000000000000000001
Result
setApprovalForAll(operator=<drainer>, approved=true). One signature gives the operator the ability to transfer every NFT in the collection out of your wallet.

Security context

An approval is not a transfer. Funds do not leave your wallet when you sign. They leave when the spender exercises the approval, which can happen seconds or months later. This is why drainer attacks often look harmless at the moment of signing. Use revoke.cash or your wallet's approval manager to prune approvals you no longer need.

It is dangerous only if the spender contract is malicious or gets exploited. Many top DEXes request unlimited approvals to save users gas on repeated swaps. The risk is concentration: if the spender is ever compromised, every wallet with an unlimited approval is drained at once.

permit() lets a token holder grant an approval by signing an off-chain message instead of submitting a transaction. The signed message can later be submitted by anyone. Phishing sites trick users into signing permit messages because there is no on-chain transaction popup, just a wallet signature request that looks like a regular login.

Related techniques

Comparing (/4):

Tool Comparison

Feature
Type
Pricing
Platforms
Description