DEV Community

Cover image for Address poisoning: the scam that never hacks you (plus a 15 line check for your own app)
cryptostrapon
cryptostrapon

Posted on AI-assisted

Address poisoning: the scam that never hacks you (plus a 15 line check for your own app)

In December 2025 a trader withdrew almost 50 million USDT from Binance to move it to their own wallet. They did the careful thing first: a 50 USDT test transfer to the right address. It arrived.

Twenty six minutes later the full 49,999,950 USDT went to a different address. That address matched the real one in its first five and last four characters. The attacker swapped the funds to DAI, then ETH, and most of it went into Tornado Cash (The Block).

Nothing was hacked. No key leaked, no contract was approved. And on 28 October Scam Sniffer flagged another user who lost about $50,000 the same way. The attack is called address poisoning, and it works because of how our interfaces show addresses.

How it works

  1. A bot watches the chain for transfers, including small test sends.
  2. It generates a vanity address whose start and end match the address you just paid. This is cheap: matching a few characters at each end takes minutes on a single GPU.
  3. It sends you a zero value or dust transfer from that look-alike. Now it sits in your history, right next to the real entry.
  4. You come back to finish the payment, open your history, copy what looks like the same address, and send.

The test transfer did not protect the trader. It was the signal the bot needed: it told the attacker which address to imitate.

Why the UI makes it easy

Wallets and explorers shorten addresses to 0x1234...abcd. People check the start, maybe the end, and stop. So "the start and end match" proves nothing. The only part an attacker cannot cheaply copy is the middle, and that is the part we hide.

A check you can add in 15 lines

If you build a wallet, a payment form, a treasury tool or an internal payout script, you can warn before the user signs:

// Warn when a recipient looks like a known address but is not the same one.
function normalize(addr) {
  return addr.trim().toLowerCase().replace(/^0x/, "");
}

export function lookalikeOf(recipient, knownAddresses, edge = 4) {
  const r = normalize(recipient);
  if (knownAddresses.some(k => normalize(k) === r)) return null; // exact match: fine
  for (const known of knownAddresses) {
    const k = normalize(known);
    const sameStart = r.slice(0, edge) === k.slice(0, edge);
    const sameEnd = r.slice(-edge) === k.slice(-edge);
    if (sameStart && sameEnd) return known;         // looks the same, is not
  }
  return null;
}
Enter fullscreen mode Exit fullscreen mode

Use it with the addresses the user really trusts: their address book, saved withdrawal addresses, and addresses they sent real value to before.

const twin = lookalikeOf(recipient, addressBook);
if (twin) {
  showWarning(
    `This address looks like ${twin} from your contacts, but it is a different address. ` +
    `Copy the recipient from the original source, not from history.`
  );
}
Enter fullscreen mode Exit fullscreen mode

Three cheap upgrades make it much stronger:

  • Show the middle. Before signing, display the full address and highlight the characters between the first and last four. That is where the difference is.
  • Distrust dust senders. If the recipient only ever appeared in history through a zero value or tiny incoming transfer, say so. Real counterparties rarely introduce themselves that way.
  • Hide or mark zero value transfers in the history view, so poisoned entries are not one click away.

The check is deliberately simple. It does not decide who is honest. It only catches the one moment the attack depends on: copying something that looks familiar.

What users can do today

  • Never copy a recipient from transaction history. Take it from the original source: the invoice, the exchange deposit page, the person.
  • Save trusted addresses once, after checking them in full, and send only from the address book or a withdrawal whitelist.
  • After a test transfer, copy the address again from the same original source, not from the history that now shows two similar entries.
  • Compare the middle of the address, not just the ends.

We keep a short practical guide on this: how to check an address before you send.


Question for you: if you build wallets or payment tools, do you hide zero value transfers in history, or show them with a warning? I am curious which one users actually understand.

If this was useful, a star on crypto-scam-glossary, our open glossary of scam patterns, helps other developers find it.

Written with AI assistance and fact checked by the CryptoStrapon team.

Top comments (0)