Step 1 — The lure

Drainers don't usually pop up out of nowhere. They arrive through a familiar door: a Twitter / X post pretending to be an NFT mint announcement, a Discord DM offering free tokens to "verify your wallet," a Google ad placed against a search for a well-known dApp, a copy-pasted Notion document spreading among a group of friends. The URL on the page is one or two characters off from a real one — metamash.io, uni-swap.org, opensaa.io.

A single Levenshtein-distance lookalike domain catches ~70% of the drainers UTXO Guard has tested against. That's the TYPOSQUAT signal — fired before you've even clicked connect.

Step 2 — Connect

You click "Connect wallet." Your wallet (MetaMask, Rabby, Phantom) shows a permission popup. You approve. Nothing alarming happens yet. The page now has read-only access to your address. The attacker uses that read access to do something interesting: they scan your wallet's holdings — every ERC-20, every NFT collection — and compute which assets are worth stealing. Drainers don't waste time on $5 of meme tokens. They target the wallet's biggest, most liquid positions.

Step 3 — The ask

Now the wallet popup re-appears with a request. Depending on the drainer family it'll be one of these three shapes:

3a — The unlimited approve

Classic and still the most common. The contract asks you to sign an ERC-20 approve(spender, amount) where amount is 2^256 - 1 — the largest value a 256-bit integer can hold.

Function: approve(address spender, uint256 amount)
  spender: 0xDEADbeef…  (attacker contract)
  amount:  ffffffffffffffffffffffffffffffff
           ffffffffffffffffffffffffffffffff   ← unlimited

If you sign this for a token, the attacker contract can then call transferFrom on that token at any time, forever, until you revoke. They wait a few minutes (you're probably still on the page reading) and drain that token entirely.

This is the APPROVE_UNLIMITED signal. UTXO Guard decodes the call data on the fly, sees that the amount is ≥ 2160, and surfaces a critical banner. The wallet popup still appears — Guard doesn't block — but you've already been warned in plain language before clicking confirm.

3b — The NFT-collection setApprovalForAll

For NFT holders, the equivalent attack is setApprovalForAll(operator, true). One signature grants the operator the right to transfer every NFT you own in that collection. The most common cover story: "verify you own this NFT to claim your airdrop." There is no airdrop.

UTXO Guard's NFT_APPROVE_ALL signal is hard-coded to critical. Genuine dApps use setApprovalForAll too (marketplaces need it), so the warning won't tell you not to sign — but it will make sure you understand what you're authorising.

3c — The off-chain Permit signature

The most elegant and the most dangerous of the three: off-chain Permit signatures (EIP-2612 Permit, EIP-712 Permit2, and Uniswap's PermitSingle). Instead of an on-chain approval, the dApp asks you to sign a piece of structured data. No gas is spent. Nothing shows up on Etherscan. To the average user it looks like "just signing a message."

But the signed data is an approval. It says: "Token X authorises spender Y to move value of my balance before deadline Z, on chain ID N." A drainer that holds your signed Permit can use it to drain you up to value with no further interaction from you. If value is set to the max uint256, it's identical in power to the on-chain unlimited approval — minus the visibility.

UTXO Guard's PERMIT_UNLIMITED signal is the one we're proudest of catching. The off-chain Permit-drainer family was specifically designed to be invisible to wallet history. Guard decodes the EIP-712 payload locally and flags any value above the unlimited threshold.

Step 4 — The drain

Once you've signed, the attacker doesn't necessarily move immediately. They may queue your wallet alongside dozens or hundreds of victims, then drain in a coordinated batch at a time of day when their flashbot bundle is most likely to succeed. By the time you check the page again, the wallet is empty.

What Guard does

  • Decodes the request before signing. Calldata, typed-data payload, signature type — all parsed in the page using the local SafeSign kernel.
  • Scores it locally. 0-100 based on weighted signals. Score is shown alongside the plain-language list of what's risky.
  • Banners high-risk requests. Critical and high requests trigger an in-page slide-down banner so you see it before you click the wallet popup.
  • Never modifies the request. Guard is observe-only. The decision is always yours, in your wallet. We just make sure you actually understand what you're confirming.

What you can do without Guard

  • Verify the domain character-by-character before connecting. Yes, one letter matters.
  • When in doubt, paste the URL into a separate tab and check it doesn't redirect somewhere you don't expect.
  • Treat off-chain signatures with the same suspicion as on-chain transactions.
  • Use a separate burner wallet for new dApps. Keep your main wallet for known counter-parties only.
  • Revoke old approvals quarterly via revoke.cash or your wallet's built-in revoker.
The best mitigation remains user-side. Guard's role is to surface what's happening; the right call is still yours.