After the Binance hack: find poisoned entries in a wallet history before anyone copies from them
On 29 October Binance reported a security incident and paused deposits and withdrawals. The company says about $6.3 billion was taken from its hot wallets, that it will cover all losses, and that user funds are safe (Al
On 29 October Binance reported a security incident and paused deposits and withdrawals. The company says about $6.3 billion was taken from its hot wallets, that it will cover all losses, and that user funds are safe (Al Jazeera, TechCrunch).
This post is not about the hack itself. It is about what usually comes next. When withdrawals reopen, a lot of people will do the sensible thing and move funds to wallets they control. Many will send a small test first, then the full amount. That is exactly the traffic address poisoning bots watch.
Why this moment matters
The biggest known poisoning loss started the same way. In December 2025 a trader withdrew almost 50 million USDT from Binance to a personal wallet, sent a 50 USDT test that arrived, and 26 minutes later sent the rest to a look-alike address that had appeared in their history (The Block).
It is not a rare trick. Researchers at Carnegie Mellon's CyLab measured two years of Ethereum and BNB Chain transfers and counted about 270 million poisoning attempts against 17 million victims, with at least $83.8 million in confirmed losses. Only about 1 attempt in 10,000 works, and organised groups still earn 10 to 20 times what they spend (CyLab, USENIX Security 2025). A rush of fresh, high value withdrawals is the best day of the year for a numbers game.
We covered the basic attack and a recipient check last week. This time we look at the other side: the history itself. If you build a wallet, a portfolio tracker or an internal treasury tool, you can find poisoned entries before a user ever copies from them.
Find poisoned entries in a history
The attacker needs one thing: an entry in your history from an address that looks like someone you really paid. So the check is:
- Collect the addresses you sent real value to. Those are the ones worth imitating.
- Look at every zero value or dust entry.
- Flag it if the other side shares the start and end of a real counterparty but is not the same address.
// Flag history entries that imitate an address you really paid.
const norm = a => a.trim().toLowerCase().replace(/^0x/, "");
export function findPoisonedEntries(history, me, { edge = 4, dust = 0.01 } = {}) {
const self = norm(me);
// Addresses you sent real value to are the ones worth imitating.
const paid = new Set(
history.filter(t => norm(t.from) === self && t.value > dust).map(t => norm(t.to))
);
const flagged = [];
for (const t of history) {
const other = norm(norm(t.from) === self ? t.to : t.from);
if (paid.has(other) || t.value > dust) continue;
for (const real of paid) {
if (other.slice(0, edge) === real.slice(0, edge) &&
other.slice(-edge) === real.slice(-edge)) {
flagged.push({ hash: t.hash, lookalike: "0x" + other, imitates: "0x" + real });
}
}
}
return flagged;
}
history is whatever your indexer or explorer API gives you, mapped to { hash, from, to, value }. It checks both directions on purpose: some poisoning transfers come from the look-alike, others are zero value token transfers that appear to come from you.
A quick test with example addresses:
const me = "0x1111111111111111111111111111111111111111";
const real = "0xd967f8a2c41b9e07d3f5a6c2b18e4d9035bd16f9";
const fake = "0xd9671c9e03fb7a26d84e0c95f1a3b72e6abd16f9";
findPoisonedEntries([
{ hash: "0xa1", from: me, to: real, value: 50 }, // test send
{ hash: "0xa2", from: fake, to: me, value: 0 }, // poison
{ hash: "0xa4", from: me, to: real, value: 1000 }, // real payment
], me);
// → [{ hash: "0xa2", lookalike: "0xd9671c9e...bd16f9", imitates: "0xd967f8a2...bd16f9" }]
What to do with a flagged entry: hide it from the default history view, or keep it with a clear label ("look-alike of an address you paid") and disable the copy button on it. Either is better than a clean row one click away from the real one.
Show the middle, not the ends
The second fix is in the UI. Shortened addresses like 0xd967...16f9 show exactly the parts an attacker copies. Before a user signs, show the part they cannot copy cheaply:
function showMiddle(addr, edge = 4) {
const a = addr.replace(/^0x/, "");
const middle = a.slice(edge, -edge).match(/.{1,4}/g).join(" ");
return `0x${a.slice(0, edge)} [ ${middle} ] ${a.slice(-edge)}`;
}
showMiddle(real); // 0xd967 [ f8a2 c41b 9e07 d3f5 a6c2 b18e 4d90 35bd ] 16f9
showMiddle(fake); // 0xd967 [ 1c9e 03fb 7a26 d84e 0c95 f1a3 b72e 6abd ] 16f9
Grouped in fours, the difference is obvious in a second. Shortened, the two are identical.
A checklist for the withdrawal rush
If you run anything that moves funds this week, or you help friends do it:
- Take the destination address from the wallet itself (the receive screen), never from transaction history.
- After the test transfer, copy the address again from that same source. Do not reuse the entry that now sits in history.
- Use the exchange withdrawal whitelist or your wallet address book, and add each address once, after checking it in full.
- Treat any surprise zero value or tiny incoming transfer as a warning sign, not as noise.
- Check the full address on a block explorer before signing a large transfer.
Binance has also warned users to expect scams around the incident. Fake "support" and "compensation" messages tend to arrive in the same week as poisoning, so a short rule helps: any refund will come through the exchange's own app and site, never through a DM or a link.
We keep a plain language guide for non-developers, good for sharing with friends who are moving funds right now: how to prevent address poisoning.
Question for you: does your wallet or tool hide zero value transfers today, or show them? And if it shows them, would you rather label look-alikes or hide them completely?
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.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.