Most wallet drains don't break any cryptography. They get you to sign something you didn't read. Here's what the common prompts actually do.
transfer(to, amount)
Moves tokens out of your wallet right now. The risk is limited to that amount, and you can see it happen.
approve(spender, amount)
No tokens move now. You're allowing spender to pull up to amount later, as many times as it wants, until the allowance is used up or revoked. "Unlimited" approvals use the max uint256 value and never expire on their own.
permit (EIP-2612)
Same effect as approve, but it's an off-chain typed-data signature instead of a transaction. No gas, and nothing shows up in your transaction history when you sign it. Whoever holds that signature can submit it before the deadline. That's why phishing pages love it: "it's just a signature" sounds harmless.
Permit2
You approve the Permit2 contract once, then grant individual apps allowances by signature, usually with an amount and an expiry. Safer by design, but the same rule applies: read the spender, amount and expiry.
setApprovalForAll (NFTs)
Gives an operator control over every item you hold in that collection, not just one.
Decode before you trust the UI
If you're building or debugging, decode the calldata yourself instead of trusting a label:
import { Interface, MaxUint256 } from "ethers";
const erc20 = new Interface([
"function transfer(address to, uint256 amount)",
"function approve(address spender, uint256 amount)",
]);
const tx = erc20.parseTransaction({ data: calldata });
console.log(tx.name, tx.args);
if (tx.name === "approve" && tx.args[1] === MaxUint256) {
console.warn("Unlimited approval to", tx.args[0]);
}
Five checks before you confirm
- Which function: transfer, approve, permit, or something else?
- Who is the spender or operator, and do you recognize that contract?
- Is the amount unlimited?
- Is there a deadline or expiry?
- Is it the chain you expected?
If the prompt says "approve" or "permit" when you expected a simple swap or deposit, stop and find out why.
What's the most confusing signing prompt you've run into? Drop it below.
Top comments (1)
The five checks are right, and the thing worth saying about them is that they are a user-vigilance control. That is the design payments spent twenty years failing with.
Card payments had the same shape. A card number is an unbounded standing authority: whoever holds it can charge what they like, repeatedly, until somebody revokes it. The fix was never better-informed cardholders. It was making the unbounded option harder to reach than the bounded one, and moving the loss to whoever was best placed to prevent it.
The bank-rails equivalent of approve is a variable recurring payment mandate, and the notable part is which fields are mandatory. A maximum per payment, a maximum per period, and a valid-to date. Not optional, no defaults, and no equivalent of uint256 max. An unlimited open-ended mandate is not a risky choice a user is allowed to make. It is not expressible.
Which is why Permit2's amount-plus-expiry is the interesting entry in your list and the rest are history. Expiry is the field doing the work, because it fails toward revoked. An allowance with no expiry fails toward still valid, and five years later nobody remembers granting it.
The other rule worth stealing is dynamic linking. Under the EU and UK strong-authentication rules the authentication code has to be bound to the amount and the payee shown to the user, and a change to either invalidates it. That is your decode-the-calldata advice, except it is the verifier's job rather than the signer's. A human reading hex is a workaround for the wallet not refusing a mismatch.