← BACK TO PORTFOLIO

/WRITING · SECURITY

What the $6.3B Binance hack teaches engineers about signing systems

On 29 October 2026 attackers drained roughly 6.3 billion dollars from Binance hot wallets, including a single Ethereum transaction carrying 50 EIP-7702 delegations. Here is what is known, what is not, and the engineering lessons that apply far beyond crypto.

November 2, 20266 MIN READ
Cover illustration: approximately 6.3 billion dollars taken from Binance hot wallets on 29 October 2026, broken down by chain — Ethereum about 3.8 billion, Bitcoin about 1.5 billion, Tron, XRP Ledger, Solana and others.

On the afternoon of 29 October 2026, US time, attackers began moving funds out of Binance's hot wallets. By the time the exchange revoked their access a little after midnight UTC, roughly 6.3 billion dollars had left across ten blockchains. It is the largest cryptocurrency theft on record, about four times the Bybit hack of February 2025, and the FBI has attributed it to North Korea's TraderTraitor group.

I am not a crypto-native engineer, and this post is not a trading take. I build full-stack products, including admin tools, dashboards and systems that move money. What happened at Binance is a case study in a design problem every one of those systems has: a component that can perform an irreversible action, and the question of what it should refuse to do. The facts below are as reported up to 2 November 2026; Binance has not yet published a root cause, and I will flag where I am relying on third-party analysis.

The problem: what actually happened

The most detailed public reconstruction comes from TRM Labs' on-chain analysis. Preparation started on 28 October, when an operator wallet received small withdrawals from Tornado Cash. The next day, the operator deployed attack code, tested it on two wallets, rehearsed on a third, and about four hours later installed the same code against a Binance hot wallet.

At 20:20 UTC, a single Ethereum transaction carried 50 EIP-7702 authorizations signed by the Binance hot wallet. EIP-7702 lets an ordinary account delegate its behaviour to smart-contract code. Each authorization pointed the hot wallet at an attacker-controlled sweeper contract, and the 50 signatures covered 50 consecutive nonces so that one would be valid whenever the transaction landed. Three minutes and 24 seconds later, the wallet was empty: 167,464 ETH and 63 tokens, including 1.615 billion USDT. Because the wallet now ran the attacker's code, new deposits kept being swept out for hours afterwards.

On the other nine chains there was no delegation trick, just ordinary signed transfers. That is why TRM's assessment is that the attacker likely gained the ability to make Binance's hot-wallet signing system sign arbitrary transactions across chains. Binance itself has said the breach may have involved a compromised DevOps engineer's laptop and that it later found additional points of breach.

Timeline of the 29 October 2026 Binance hack in UTC, from operator funding and 50 EIP-7702 authorizations to deposit sweeps, freezes, revoked access and resumed withdrawals.
Fig. 01 — The attack window, in UTC. Sources: TRM Labs, Wikipedia's incident summary, CNBC and Bloomberg reporting.

The response was fast by industry standards and still slow by the standards of the attack. Stablecoin issuers began freezing addresses within the hour, and roughly 2.7 to 3 billion dollars was eventually frozen depending on whose estimate you use. Binance suspended deposits and withdrawals, pledged to cover all losses, and resumed withdrawals on 15 chains on 1 November. But the delegated wallet was sweeping incoming funds until 23:56 UTC, more than three and a half hours after the first drain.

The insight: the signer trusted its caller

Compare this with Bybit in February 2025. There, attackers compromised a developer machine at the wallet provider Safe and injected JavaScript into its web interface. The signers' screens showed a routine transfer from cold to warm storage while they actually approved a delegatecall that handed over control. The failure was at the human-facing interface: people approved what they were shown, and what they were shown was false.

Binance's incident, on current evidence, broke one layer deeper. If the attacker could make the signing system sign arbitrary payloads, no human screen was involved at all. The two incidents look different, but they share a root shape: the component holding signing authority did whatever its caller asked, and nothing independent asked whether the request made sense.

Two signing pipelines: at Bybit in 2025 the human-facing interface was compromised; at Binance in 2026 the signer was, per TRM. A missing policy gate is common to both.
Fig. 02 — Different break points, same missing control: an independent check on what is being signed.

Implementation walkthrough: controls you can actually build

Most of us will never operate an exchange hot wallet. But plenty of us build systems that can issue refunds, send payouts, rotate credentials or approve agent tool calls. The same three controls apply.

Control 1: a policy gate that understands the payload

A policy gate sits between whatever requests a signature and whatever produces it. It evaluates the decoded request, not just the requester's identity. For an exchange hot wallet, an EIP-7702 delegation is never routine, and neither is a batch of fifty authorizations for consecutive nonces. Both should be impossible to sign without escalation. The sketch below is illustrative, not Binance's architecture, and it is deliberately boring: deny by default, explain every decision.

TE TAIWO EMMANUEL / CODE NOTE
TS
export type SignRequest =
  | { kind: "transfer"; chain: string; asset: string; amount: bigint; to: string; nonce: number }
  | { kind: "delegation"; chainId: number; delegate: string; nonce: number } // e.g. an EIP-7702 authorization
  | { kind: "raw"; chain: string; payload: string };

export type PolicyContext = {
  circuitOpen: boolean; // kill switch, controlled by a separate team and system
  allowedDelegates: ReadonlySet<string>; // for a hot wallet this is usually empty
  outflowLastHour: (chain: string, asset: string) => bigint;
  hourlyCeiling: (chain: string, asset: string) => bigint;
  maxBatchSize: number;
};

export type Decision = { verdict: "allow" | "deny" | "escalate"; reasons: string[] };

export function evaluate(batch: SignRequest[], ctx: PolicyContext): Decision {
  const reasons: string[] = [];
  if (ctx.circuitOpen) return { verdict: "deny", reasons: ["circuit breaker is open"] };
  if (batch.length > ctx.maxBatchSize) reasons.push(`batch of ${batch.length} exceeds ${ctx.maxBatchSize}`);

  const nonces = batch.map((request) => ("nonce" in request ? request.nonce : -1)).filter((n) => n >= 0);
  if (new Set(nonces).size !== nonces.length || nonces.length > 3) reasons.push("multiple signatures across nonces in one batch");

  for (const request of batch) {
    if (request.kind === "raw") return { verdict: "deny", reasons: ["raw payloads are never signed"] };
    if (request.kind === "delegation" && !ctx.allowedDelegates.has(request.delegate.toLowerCase())) {
      return { verdict: "deny", reasons: [`delegation to unapproved code ${request.delegate}`] };
    }
    if (request.kind === "transfer") {
      const projected = ctx.outflowLastHour(request.chain, request.asset) + request.amount;
      if (projected > ctx.hourlyCeiling(request.chain, request.asset)) {
        reasons.push(`hourly outflow ceiling exceeded for ${request.asset} on ${request.chain}`);
      }
    }
  }
  return reasons.length ? { verdict: "escalate", reasons } : { verdict: "allow", reasons: [] };
}

Escalate means a quorum of humans, through a separate interface, on separate devices. That is the lesson from Bybit: the review screen itself must be trustworthy, which I cover in the companion post on clear signing in React.

Control 2: velocity limits and anomaly signals

Hot wallets exist because withdrawals need to be fast. Fast does not have to mean unbounded. Per-asset, per-chain hourly ceilings turn a total loss into a bounded one, and they produce exactly the signal an operations dashboard should surface first. The real-time dashboard pattern for that is the subject of another post in this series.

Control 3: a kill switch that someone can find under pressure

The sweeping of new deposits continued for hours. Every system that moves value should have a circuit breaker that is pre-built, pre-authorised, tested, and reachable from an operations console in seconds. On the frontend this is unglamorous work: a status query that every money-moving screen respects, and a mutation guarded by its own confirmation flow.

TE TAIWO EMMANUEL / CODE NOTE
TSX
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";

type CircuitStatus = { open: boolean; openedBy: string | null; openedAt: string | null; scope: string[] };
const circuitKey = ["ops", "circuit"] as const;

export function CircuitBreakerPanel() {
  const queryClient = useQueryClient();
  const status = useQuery({
    queryKey: circuitKey,
    queryFn: () => fetch("/api/ops/circuit").then((r) => r.json() as Promise<CircuitStatus>),
    refetchInterval: 5_000, // operators need the truth, not a cached guess
  });
  const trip = useMutation({
    mutationFn: (scope: string[]) =>
      fetch("/api/ops/circuit/open", { method: "POST", body: JSON.stringify({ scope }) }).then((r) => {
        if (!r.ok) throw new Error("Circuit could not be opened");
      }),
    onSettled: () => queryClient.invalidateQueries({ queryKey: circuitKey }),
  });

  if (status.isPending) return <p>Checking circuit status…</p>;
  if (status.isError) return <p role="alert">Circuit status unknown. Treat withdrawals as unsafe.</p>;
  return status.data.open ? (
    <p role="status">Withdrawals halted by {status.data.openedBy} at {status.data.openedAt}</p>
  ) : (
    <button onClick={() => trip.mutate(["hot-wallets"])} disabled={trip.isPending}>
      Halt hot-wallet withdrawals
    </button>
  );
}

Takeaway

  • Signing authority without an independent policy check is a single point of failure, whether the caller is a person, a script or an AI agent.
  • Decode before you decide. A policy that only sees "sign this blob" cannot tell a payout from a delegation to an attacker's contract.
  • Bound the blast radius with velocity limits, and make the kill switch a first-class, rehearsed feature.
  • Assume developer endpoints are part of your attack surface. Binance's first explanation was a compromised engineer laptop; Bybit's started on a developer machine too.

The details of this incident will keep changing as investigators publish more. The design lesson will not: the most dangerous line of code in any system is the one that does exactly what it is told.

Sources: TRM Labs, "North Korea steals USD 6.36 billion from Binance in the largest hack in history"; Wikipedia, "2026 Binance hack" (as of 2 November 2026); CNBC, 30 and 31 October 2026; Bloomberg, 29 October 2026; NCC Group, "In-depth technical analysis of the Bybit hack".

/OCCASIONAL_NOTES

Useful notes, when there’s something worth sharing.

A low-volume digest on product systems, interfaces, and technical work. Your email stays in Taiwo’s private portfolio list—no third-party tracking.