On the evening of December 31, 2023 (UTC), an attacker emptied the Orbit Bridge vault on Ethereum: about $81.5M in DAI, WBTC, ETH, USDC and USDT12. Orbit confirmed the incident the same night13.
The vault did not empty in one transaction. The large withdrawals took 17 minutes, and the attacker's first steps came half an hour before them. This post asks when the chain first showed something was wrong, how much was still in the vault at that moment, and what the team could have done.
How the Orbit Bridge vault works
Orbit Bridge, run by the Korean company Ozys, moves tokens between Orbit Chain and other networks. On Ethereum all bridged tokens sit in one contract, the vault 0x1Bf6…489a2.
- Deposit. A user sends tokens to the vault on Ethereum and receives wrapped tokens on Orbit Chain.
- Withdrawal. The user burns the wrapped tokens on Orbit Chain. Bridge validators see the burn and each signs a withdrawal message.
- Release. Anyone can then call
withdrawon the vault with those signatures. The vault checks that enough of its owners signed, marks the message as used so it cannot be replayed, and sends the tokens.
The vault is a multisig. Before the attack it had 10 owners and required 7 signatures: we read getOwners() and required() at block 18907899, one block before the attacker's first transaction4. Ten keys and a 70% threshold sound safe. They are safe only while no single party can produce seven of them.
The vault cannot tell a real burn on Orbit Chain from an invented one. It sees only signatures. If seven valid signatures exist, the tokens leave.
The attack, step by step
All times UTC, block times from Ethereum. We read every transaction below on-chain ourselves4. Every withdrawal called the vault's withdraw with at least seven signatures from its owners, and the vault accepted all of them.
20:40:35 — a test with 0.012 WBTC
From 0xdc54…c522, the attacker withdrew 0.0120879 WBTC to 0x9ca5…4049 (0x9e51…b589). Half an hour later the same address received 230 WBTC. A few hundred dollars is a test: the attacker checked that the signatures work.
20:52:47 — 30 ETH
The main attacker address 0x9263…6AfF withdrew 30.095 ETH to 0x27E2…5710 (0x958a…5f16). Native ETH leaves the vault as an internal transfer, with no Transfer event. According to Rekt, Orbit names 20:52 as the time of the first unauthorized access2.
21:07:59 to 21:25:35 — the vault empties
| Time | What left the vault | Transaction |
|---|---|---|
| 21:07:59 | 10,000,000 DAI | 0xafdc…07ca |
| 21:10:59 | 230.88 WBTC (~$9.8M) | 0xe0ba…71f9 |
| 21:16:59 | 9,500 ETH (~$21.5M) | 0x639d…ae0a |
| 21:25:11 | 10,000,000 USDC | 0x64a6…3f0e |
| 21:25:35 | 30,000,000 USDT | 0xd8ca…0ac8 |
Dollar amounts are from Orbit and Rekt at the time of the incident12. The WBTC went to the address from the test, the rest each to a new address.
This is where an amount threshold works. $10M of DAI out of a bridge vault in one transaction is not ordinary user traffic.
The same rule on WBTC, USDC and USDT, and a "Total address balance in $ below threshold" rule on the vault with a floor around $75M, would have fired at about the same moment, from the DAI and the WBTC. Another alert at the same moment adds no time. It confirms this is not a mistake.
22:21:35 — the vault is switched off
At 22:21:35 address 0x4C35…4d05 called changeActivate(false) on the vault (0xedfc…13f4). From this block the vault refused withdrawals. A withdraw for 300,000 USDC at 22:26:23 reverted (0x0958…705b)4.
It was 74 minutes after the DAI withdrawal and 56 minutes after the last one. The vault was already almost empty.
After the vault — where the money went
The attacker started moving funds within half an hour: USDC from 21:50, USDT from 22:13, WBTC from 22:444. According to Rekt, the stablecoins and WBTC were swapped to ETH, and the attacker's addresses were first funded through Tornado Cash2. In June 2024 the attacker sent 8,671 ETH, about $32M, to Tornado Cash6.
We found no report that the USDT or USDC issuers froze these addresses.
What Orbit said about the cause
Ozys stated that the contracts had no bug7. In January 2024 it sued its former chief information security officer. It claims he weakened the firewall policy in November 2023 and left the company in early December, three weeks before the attack7. How the attacker got seven valid signatures has not been explained in public. We do not guess.
What no rule would have changed
How to set up these rules in SmartAlert
- Add each token the vault holds. For Orbit that is DAI, WBTC, USDC and USDT.
- On each, create a transfer above an amount in $ rule with
from= your vault. Pick the threshold from your traffic: above the largest normal user withdrawal, well below the vault total. The amount is priced at the block of the transfer, so the threshold holds when prices move. Pro and above. - Check the threshold before you turn it on. The $ rule reads prices only from the moment it is created, so it cannot be checked on past transfers. Pick the threshold with a large transfer rule in tokens: it shows on past data how often it would have fired. If it fires every day, raise it.
- Cover native ETH. ETH does not emit
Transfer. Add the vault in "Addresses" and create a "Total address balance in $ below threshold" rule with a floor a little under the normal total. - Send these alerts to people who can switch off the vault. Telegram is free. The holder of that key should get them directly, not through a shared channel someone reads in the morning.
See all alert recipes.
Moving off OpenZeppelin Defender? See how each Defender monitor maps to a SmartAlert rule and the Defender comparison.
Sources
- 1.Orbit Chain, Official statement regarding Orbit Bridge exploit: amounts and the stolen tokens. ↩
- 2.Rekt, Orbit Bridge — Rekt: timeline, 20:52 as the first unauthorized access, swaps to ETH, attacker funded via Tornado Cash. ↩
- 3.Orbit Chain on X, incident announcement. ↩
- 4.Checked by SmartAlert on-chain:
required()= 7 andgetOwners()= 10 addresses on the vault at block 18907899 (archiveeth_call); every transaction in this post, its block time, sender, recipient and amount, through the Blockscout API; the reverted withdrawal at 22:26:23. ↩ - 5.SmartAlert, alert delay on Ethereum: 12 confirmations (about 144 s), head polling up to 12 s, storage up to 15 s (P95), rule check every 60 s on Pro. ↩
- 6.Forklog, Orbit hacker funnels $32 million into Tornado Cash. ↩
- 7.The Block, report on Ozys and its former security chief: no contract bug, firewall change in November 2023, departure in December, lawsuit. ↩
- 8.CertiK, Bybit incident technical analysis. ↩
- 9.KiloEx, Security incident root cause analysis. ↩