On October 29, 2026, attackers drained about $6.36 billion from Binance's hot wallets across 10 blockchains, most of it in about 20 minutes (TRM Labs). It is the largest crypto theft on record, roughly 4x the $1.5B Bybit hack of February 2025. On October 31 the FBI attributed it to North Korea's TraderTraitor (part of the Lazarus Group).
I made a video about the hack, and for one part I wanted to check the money myself instead of trusting screenshots. Everything on Ethereum is public, so you can do the same with nothing but Python's standard library and a free public RPC node.
What we want to know
For each wallet Etherscan labelled during the hack:
- How much ETH does it hold right now?
- How much USDT does it hold?
- Has Tether frozen it (blacklisted the address)?
- Has Circle blocked it for USDC?
- Is it a plain wallet, a contract, or an EIP-7702 delegated account? (Binance co-CEO Richard Teng said the attackers abused an EIP-7702 delegation on the hot wallet.)
The script
"""Follow the Binance hack money on Ethereum, straight from a public node (no API key)."""
import json, urllib.request
RPC = "https://ethereum-rpc.publicnode.com"
USDT, USDC = "0xdAC17F958D2ee523a2206206994597C13D831ec7", "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
WALLETS = [ # Etherscan labels, Oct 29-30 2026
("Binance 14 (hot wallet)", "0x28C6c06298d514Db089934071355E5743bf21d60"),
("Attacker control wallet", "0x7C8601d4645b93cea45F71e118A729332e6B5CA0"),
("Exploiter 2 (collector)", "0x5227f603C1eE34CE3C06eFe6477003BDfCCB2f8c"),
("Exploiter 8", "0x8532FA14ACAf483729C449b8458aedDb900543D4"),
("Exploiter 11", "0x897C80d1a9729E94460e2Bd93178bD5215158DF3"),
("Exploiter 24 (sweeper)", "0x63e842B054Bd624a465454D533C33F573e9c6fAe"),
]
def rpc(method, params):
req = urllib.request.Request(RPC, json.dumps({"jsonrpc": "2.0", "id": 1, "method": method, "params": params}).encode(),
{"content-type": "application/json", "user-agent": "track.py"})
return json.load(urllib.request.urlopen(req, timeout=30))["result"]
def call(to, sig, addr):
return int(rpc("eth_call", [{"to": to, "data": sig + addr[2:].lower().rjust(64, "0")}, "latest"]), 16)
block = int(rpc("eth_blockNumber", []), 16)
print(f"block {block:,} · {RPC}")
for name, a in WALLETS:
eth = int(rpc("eth_getBalance", [a, "latest"]), 16) / 1e18
usdt = call(USDT, "0x70a08231", a) / 1e6
frozen = call(USDT, "0xe47d6060", a) # Tether isBlackListed(address)
ublock = call(USDC, "0xfe575a87", a) # Circle isBlacklisted(address)
code = rpc("eth_getCode", [a, "latest"])
kind = "contract" if len(code) > 2 and not code.startswith("0xef0100") else ("7702 delegated -> 0x" + code[8:] if code.startswith("0xef0100") else "wallet")
flags = (" USDT FROZEN" if frozen else "") + (" USDC BLOCKED" if ublock else "")
print(f"{name:<25} {a[:8]}…{a[-4:]} {eth:>12,.2f} ETH {usdt:>16,.2f} USDT {kind}{flags}")
How it works
There are only four JSON-RPC methods involved:
-
eth_blockNumber: which block we are reading at. -
eth_getBalance: native ETH balance. -
eth_call: read-only call into a token contract. Thedatafield is a 4-byte function selector plus the address left-padded to 32 bytes:-
0x70a08231=balanceOf(address)(ERC-20, works for USDT) -
0xe47d6060=isBlackListed(address)on Tether's USDT contract -
0xfe575a87=isBlacklisted(address)on Circle's USDC contract
-
-
eth_getCode: empty (0x) means a normal wallet. Code that starts with0xef0100is an EIP-7702 delegation designator: the account is an EOA that has delegated its execution to the contract address that follows. Anything else is a real contract.
USDT has 6 decimals, ETH has 18, hence the / 1e6 and / 1e18.
What it printed (Nov 2, 2026, 13:27 UTC)
block 26,327,392 · https://ethereum-rpc.publicnode.com
Binance 14 (hot wallet) 0x28C6c0…1d60 0.80 ETH 1,352,017.94 USDT wallet USDT FROZEN USDC BLOCKED
Attacker control wallet 0x7C8601…5CA0 1.18 ETH 0.00 USDT wallet
Exploiter 2 (collector) 0x5227f6…2f8c 0.00 ETH 1,175,464,712.88 USDT wallet USDT FROZEN USDC BLOCKED
Exploiter 8 0x8532FA…43D4 197,460.01 ETH 0.00 USDT wallet
Exploiter 11 0x897C80…8DF3 78,415.25 ETH 0.00 USDT wallet
Exploiter 24 (sweeper) 0x63e842…6fAe 0.00 ETH 0.00 USDT contract
What the numbers say
- Exploiter 2 still holds 1,175,464,712.88 USDT, and it's useless to the attackers. Tether blacklisted the address, so the USDT can't be moved. Tether froze more than 1.567B USDT in total and Circle more than 800M USDC. About $3B of the stolen funds were frozen overall.
- The ETH can't be frozen. Exploiter 8 still holds 197,460 ETH and Exploiter 11 holds 78,415 ETH. ETH has no issuer and no blacklist function. The attackers swapped a lot of the stolen stablecoins into ETH before the freezes landed, and the FBI said the stolen assets were converted mainly into Ethereum.
-
Binance's own hot wallet is blacklisted too. Tether and Circle both flagged Binance 14 during the incident, which is why it shows
USDT FROZEN USDC BLOCKED. - None of these accounts showed a 7702 delegation when I read them. Binance revoked the attackers' permissions at 00:10 UTC on October 30, so a delegation that existed during the attack would no longer be visible at
latest.
The balances were identical to my first run on November 1 at 15:17 UTC, so the big ETH piles haven't moved yet.
The takeaway for developers
-
Stablecoins are centrally controllable.
isBlackListedis a plain public view function. If your app accepts USDT/USDC, check it before crediting a deposit. - EIP-7702 changes what "a wallet" means. An address with no code yesterday can run arbitrary contract logic today. If you build signing or approval flows, treat a 7702 authorization like handing over the keys, and route it through the same approvals as any other privileged change. Teng said the hackers' delegation "did not pass through our normal approval controls".
- You don't need an API key to audit public claims. A free RPC endpoint and the standard library let you check Etherscan labels and news numbers yourself in a few seconds.
Full video
I explain the whole story (the DevOps engineer's laptop, the EIP-7702 delegation, the Lazarus link, the freeze race, and whether funds on Binance are safe), and I run this script on camera:
This post was written with AI assistance and checked against the script's real output and the sources linked in the video description.
Top comments (0)