Binance Hack in 2026: How $6.3 Billion Left in 20 Minutes Without a Leaked Key
Hello HaWkers, on October 29, 2026, Binance lost about $6.3 billion in what is already being treated as the largest cryptocurrency theft in history. The previous record was $1.5 billion, taken from Bybit in February 2025. TRM Labs calculates that nearly all of it left in about 20 minutes, across ten different blockchains. And the most uncomfortable detail for anyone who writes code: Binance says it found no evidence of a leaked private key.
If the key did not leak, how did the money get out? That question matters even if you have never touched crypto, because the likely answer applies to any system that moves money, approves refunds, or deploys to production. In this article you will see what is confirmed, what is still a hypothesis, how EIP-7702 works, and three controls in code that you can adapt for your backend.
What Is Confirmed and What Is Still a Hypothesis
Before any lesson, a caveat: the investigation is still open, and Binance has promised to publish the technical details only after it concludes. Much of what is circulating is reconstruction by on-chain analytics companies, not an official report. Keeping those two things apart prevents wrong conclusions.
| Claim | Who says it | Status |
|---|---|---|
| About $6.3 billion stolen | Binance | Estimate; may change |
| $6.36 billion across ten blockchains | TRM Labs | Independent estimate |
| About $5.9 billion at the time of the theft | Chainalysis | Revised estimate |
| DevOps engineer's laptop compromised | Binance | Initial hypothesis; "additional points of breach" came later |
| No evidence of a leaked private key | Binance | Company statement, disputed by Mark Karpeles, formerly of Mt. Gox |
| Carried out by the North Korean group TraderTraitor | FBI, TRM Labs, Chainalysis | FBI attribution on October 31 |
| The signing system was tricked into signing | TRM Labs | Analyst assessment |
The gap between the figures comes down to method: the price used, the moment of the calculation, and what gets counted. The title that sparked the discussion on Hacker News mentioned $5.6 billion, and the Arkham dashboard showed more than $5.7 billion in the attacker's wallets at one point.
It is also worth recording what Binance says about customers: the attack allegedly hit the company's own hot wallets, meaning connected wallets used to process withdrawals, and the loss comes out of its own funds. The company set aside about $6.34 billion, including the $1 billion SAFU fund, and said on October 31 that it had already replenished 90% of the assets.
The Attack Timeline
TRM Labs' reconstruction shows a rehearsed operation, not a crime of opportunity. The operator's wallet received three small withdrawals from Tornado Cash starting on October 28, at least 36 hours before the theft. In the early hours of the 29th, the attack code was published and tested on two wallets, with a final rehearsal on a third. About four hours later, the same code was used against Binance.
| Time (UTC) | What happened |
|---|---|
| 20:05 | The drain begins on the XRP Ledger; five wallets emptied down to the minimum balance |
| 20:20 | Bitcoin hot wallet sends 18,270 BTC to the attacker |
| 20:24 | Main transaction on Ethereum, according to the public timeline of the case |
| Around 21:01 | Tether starts freezing wallets; Circle follows three minutes later |
| 00:10 (on the 30th) | Binance removes the attacker's permissions on the hot wallet |
On the Tron network, 612.7 million USDT left in two minutes. On Ethereum, a single sweep took 167,464 ETH and 1.615 billion USDT.
Note the gap between the first transaction and the removal of access: about four hours. During that period the attacker kept cleaning out the wallet as new money came in. In an incident, the time to revoke usually costs more than the time to detect.
The freezes saved part of the value. Tether and Circle froze more than $2.3 billion in stablecoins, and estimates of the total frozen range from $2.7 billion to $3 billion. Bitcoin has no such button: there is no issuer capable of freezing the $1.5 billion in BTC.
What Is EIP-7702 and Why It Showed Up Here
EIP-7702 arrived on Ethereum with the Pectra upgrade in May 2025. It lets a regular account, controlled by a private key, delegate its behavior to the code of a smart contract. The idea is good: it provides smart wallet features, such as transaction batching and fee payment by third parties, without changing addresses.
In practice, the account signs an authorization with three fields: chain_id, contract address, and nonce. This authorization goes in a transaction of type 0x04, and the network writes to the account's address the prefix 0xef0100 followed by the contract address. From then on, any call to that account executes the contract's code. And it persists: the delegation stays active until another authorization replaces or clears it.
According to TRM Labs, this is where the attacker acted on Ethereum. One transaction carried 50 pre-signed authorizations, all pointing the hot wallet to a sweeper contract that only the attacker could call. Why 50? Because the authorization's nonce must equal the account's current nonce, and a busy exchange wallet advances that number all the time. With 50 consecutive values signed, at least one would be valid at execution time.
After the delegation, the attacker no longer needed a new signature from Binance. The wallet became a sweeping machine, used six more times in about 3.5 hours.
EIP-7702 has no known bug involved in this case. Richard Teng, co-CEO of Binance, said the delegation bypassed normal approval controls, but the most likely failure lies in who could request signatures, not in the standard. The specification already warns that a malicious delegate can take "near complete control" of the account and that wallets should not let applications request this kind of signature.
You can check whether an address is delegated with a regular RPC call:
// Checks whether an Ethereum address has an active EIP-7702 delegation
// Requires Node.js 18+ (native fetch)
const RPC_URL = 'https://ethereum-rpc.publicnode.com';
const DELEGATION_PREFIX = '0xef0100';
async function getDelegation(address) {
const response = await fetch(RPC_URL, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'eth_getCode',
params: [address, 'latest'],
}),
});
const { result, error } = await response.json();
if (error) throw new Error(error.message);
const code = result.toLowerCase();
// A delegated account has exactly 23 bytes: prefix (3) + address (20)
const isDelegated = code.startsWith(DELEGATION_PREFIX) && code.length === 48;
return isDelegated ? `0x${code.slice(DELEGATION_PREFIX.length)}` : null;
}
// Run this in a loop over your operational wallets:
// an unexpected return value here should open an incident right away
const delegate = await getDelegation(
'0x0000000000000000000000000000000000000000'
);
console.log(delegate ?? 'No active delegation');The Real Lesson: Signing Is Not Authorizing
If Binance is right about the keys, the attacker did not steal the vault: they convinced the vault to open. TRM Labs notes that the other nine networks, with different key types and signature schemes, released funds through ordinary signed transfers. Its conclusion is that the attacker probably gained the ability to make the signing system sign whatever they wanted. Bybit fell along a similar path, and the pattern goes back a long way: compromise someone with privileged access and reach the system that moves money.
Swap "hot wallet" for "payment service", "refund API", or "deploy pipeline". The problem is the same: an internal service that trusts any well-formed request coming from inside the network. Automatic trust is also what makes the everyday install dangerous, as the GemStuffer case on RubyGems showed: whatever arrives through a known channel gets in unchecked.
Analyst Kok Tin Gan, of NyxLab, described the case as appearing to be a signature that generated a full-access token with no expiration, while warning that he was working with incomplete public evidence. True or not in this case, it is the right picture of what not to have in production.
Three Controls to Apply in Your Backend
The list below draws on what TRM Labs and experts quoted in the press recommended after the attack. The code is a starting point, not a finished system.
Control 1: A Signing Policy Outside the Service That Makes the Request
The first control is a policy engine that receives the request and decides, running separately from the service that asked. If it lives in the same process, whoever breaks into the process turns off the policy too.
// Signing policy: denies by default and explains each refusal
type SignRequest = {
kind: 'transfer' | 'contract_call' | 'set_code';
chain: string;
to: string;
valueUsd: number;
requestedBy: string;
approvals: string[];
};
type Decision = { allowed: boolean; reason: string };
const LIMIT_WITHOUT_APPROVAL_USD = 10_000;
const REQUIRED_APPROVERS = 2;
function evaluate(request: SignRequest, allowlist: Set<string>): Decision {
// Delegation is never routine on an operational wallet
if (request.kind === 'set_code') {
return { allowed: false, reason: 'EIP-7702 delegation blocked' };
}
if (!allowlist.has(`${request.chain}:${request.to.toLowerCase()}`)) {
return { allowed: false, reason: 'destination not on the allowlist' };
}
if (request.valueUsd <= LIMIT_WITHOUT_APPROVAL_USD) {
return { allowed: true, reason: 'within the automatic limit' };
}
// The requester cannot approve their own request
const independent = new Set(
request.approvals.filter(person => person !== request.requestedBy)
);
return independent.size >= REQUIRED_APPROVERS
? { allowed: true, reason: 'enough independent approvals' }
: { allowed: false, reason: 'missing independent approvals' };
}The values above are examples; adjust them to your volume. TRM Labs recommends rejecting unexpected EIP-7702 authorizations and treating anomalous requests, such as 50 authorizations across a nonce range, as an indicator of compromise. That is why a refusal on set_code should trigger a critical alert, not just a log line. Jason Soroko, of Sectigo, points out something else: multiple approvals offer little protection if a single intruder controls all of them. Each approver needs to be on a separate identity and device.
Control 2: A Short-Lived Credential Tied to One Request
Long-lived credentials are what turn a compromised laptop into billions lost. An approval token should last a few minutes, work a single time, and be valid only for that exact request. This example uses the jose library:
// Short-lived, single-use approval token bound to the request content
import { createHash, randomUUID } from 'node:crypto';
import { SignJWT, jwtVerify } from 'jose';
const secret = new TextEncoder().encode(process.env.APPROVAL_SECRET);
const usedTokens = new Set<string>(); // In production, use Redis with a TTL
const hashRequest = (request: object) =>
createHash('sha256').update(JSON.stringify(request)).digest('hex');
export async function issueApproval(request: object, approver: string) {
return new SignJWT({ requestHash: hashRequest(request) })
.setProtectedHeader({ alg: 'HS256' })
.setSubject(approver)
.setJti(randomUUID())
.setIssuedAt()
.setExpirationTime('2m') // No tokens without expiration
.sign(secret);
}
export async function consumeApproval(token: string, request: object) {
const { payload } = await jwtVerify(token, secret, {
algorithms: ['HS256'],
});
// A stolen token is useless for another destination or another amount
if (payload.requestHash !== hashRequest(request)) {
throw new Error('Approval does not match this request');
}
if (!payload.jti || usedTokens.has(payload.jti)) {
throw new Error('Approval already used');
}
usedTokens.add(payload.jti);
return payload.sub;
}Two notes if you want to take this seriously. First, serialize the request canonically: JSON.stringify depends on key order, so sort the keys before generating the hash. Second, a shared HS256 secret lets anyone who holds it issue tokens; in a high-value system, prefer asymmetric signing with the private key in an HSM or KMS.
Control 3: A Volume Circuit Breaker
A policy looks at one request at a time. The theft happened in the sum of many. A circuit breaker measures the total that left within a time window and stops everything when it exceeds normal levels. If the attacker needs 20 minutes, stopping at minute two changes the size of the loss.
// Circuit breaker: pauses outflows when the window's volume strays from normal
class WithdrawalBreaker {
private events: { at: number; usd: number }[] = [];
private tripped = false;
constructor(
private readonly windowMs: number,
private readonly maxUsd: number,
private readonly onTrip: (totalUsd: number) => void
) {}
// Call before signing; returns false if the outflow should stop
allow(usd: number, now = Date.now()): boolean {
if (this.tripped) return false;
this.events = this.events.filter(e => now - e.at < this.windowMs);
const total = this.events.reduce((sum, e) => sum + e.usd, 0) + usd;
if (total > this.maxUsd) {
this.tripped = true;
this.onTrip(total);
return false;
}
this.events.push({ at: now, usd });
return true;
}
// Reset is manual on purpose: a person turns it back on
reset() {
this.tripped = false;
this.events = [];
}
}
// Example: up to $2 million in 10 minutes
const breaker = new WithdrawalBreaker(10 * 60 * 1000, 2_000_000, total => {
console.error(`Outflows paused: US$ ${total} in the window`);
});Keep this state in shared storage: with multiple replicas, an in-memory counter becomes several independent breakers. And run the component out of reach of the service that signs, for the same reason as the previous control.
What to Do If You Do Not Work With Crypto
Most of these measures apply to any backend. If you need to prioritize, start here:
- Treat DevOps machines as privileged infrastructure. The laptop of someone with production access deserves the monitoring and protection of a critical server.
- Grant temporary access. Production access is granted on approval and expires on its own, including for service accounts and AI agents.
- Use phishing-resistant MFA and short sessions. In the Bybit case, the attacker reused an AWS session token that had already passed MFA.
- Practice revocation. Have a tested path to cut off credentials and delegations in minutes, and know who triggers it.
- Know your response partners before the incident. Tether froze 971.1 million USDT 37 minutes after the main sweep; that only works with an open channel.
- Keep patches up to date. Initial intrusion is often mundane, as the race to protect servers against PHP2Shell showed.
If you keep cryptocurrency on an exchange, the practical advice is simple: distrust any message asking you to "verify" your account or "recover" funds because of the hack. Scammers love big news. Check the status only in the official app or website.
What to Watch in the Coming Days
Three things can still change how this case reads.
Withdrawals. The reopening is happening in stages: Bitcoin came back first, and on November 1 USDT, USDC, ETH, and BNB were added on the Ethereum and BNB Smart Chain networks. Other networks are still awaiting release.
Proof of reserves. Binance promised a full report, but sources differ between November 2 and 3. It should cover ten tokens, including BTC, ETH, USDT, BNB, SOL, and USDC. Proof of reserves without liabilities tells only half the story, so it is worth looking at the methodology.
Remaining money. TRM Labs calculates that the attacker still controls more than 420,000 ETH and 18,270 BTC. The FBI expects more laundering, and Chainalysis observed an automated pattern with thousands of transactions circulating between addresses. This part usually takes months or years.
The market reacted quickly and then calmed down: Bitcoin touched $76,368 and Ether $2,178. According to Al Jazeera, the sector was already under pressure from higher US interest rates and lower ETF demand.
Outlook: Security Is Changing Places
This case shifts the center of the conversation. For years, the question was "where is the key stored?". Cold wallets, HSMs, and multisig answered it well. The next question is harder: "who can ask the key to sign, and what checks that request?".
When the Binance report comes out, some conclusions in this text may change, especially about the initial entry and about the keys. What is unlikely to change is the principle: any permanent credential and any service that blindly trusts internal calls is a bigger risk than it looks, until the day someone finds it.
Open the most sensitive system you maintain today and look for the token without expiration. If it exists, that is your next ticket.
Let's go! 🦅
📚 Want to Keep Up With What Is Coming?
This article covered the Binance hack and what it teaches about systems that move money, but the ecosystem changes every week and not everything becomes an article here.
On X I share what I am testing, behind the scenes of my projects, and news that shows up before it becomes a post.
Follow Me There
💡 Daily content about development, career, and the tools I actually use

