TAC Bridge Drain 2026: anatomy of a $2.5M+ TON attack
On-chain forensics of the TAC bridge drain that started 11 May 2026: handler 0x0E50D313, no validator-signature check on TON, attacker addresses
- Author
- Denis Kim · research lead · security desk
- Published
Contents16sections
- TL;DR
- Timeline
- What the TAC bridge is
- Anatomy of the drain handler 0x0E50D313
- What controls validators_dict
- Attack chain end-to-end
- Synchronisation with the rotator
- Root causes
- (C) Insider rug — most likely (~60%)
- (A) Rotator key compromise (~25%)
- (B) Logic bug in the rotator (~15%)
- Where admin1 is and why it is silent
- What this means for TON users
- Lessons for other TON bridges
- Disclosure path
- Open work
On 11 May 2026 at 09:14 UTC a drain attack began on the TAC bridge admin contract on TON. At the time of this writeup (14 May) the drain is still active: over $2.5M in wrapped jettons (USDT, BLUM, tsTON and more) plus 384k freshly-minted TAC have already left the bridge. No official TAC channel has published a warning or a postmortem. This article is the full technical breakdown: what all 10 handlers of the bridge admin contract do, why the attack works without any TAC L2 validator private keys, and which exact addresses are involved. Built from TASM decompilation, c4 state recovery and narrow-window transaction traces — every step is independently verifiable on-chain.
TL;DR
- The TAC bridge on the TON side is made of a bridge admin contract (2,399 TASM lines), admin1 (874 lines), and a rotator (793 lines with two CHKSIGNU).
- The bridge admin contains zero on-chain CHKSIGN/CHKSIGNU/CHKSIGNS instructions, confirmed by grepping all 2,399 lines of disassembly.
- The drain handler
0x0E50D313accepts a Merkle proof whose “virtual root” must be present in theGLOBAL 5dictionary (validators_dict). If it is — the bridge releases wrapped jettons to the destination encoded in the payload. That is the legitimate, by-design mechanism. - The only source of writes to validators_dict is the rotator. Handler
0x23B05641(EPOCH_ROTATE) accepts the root verbatim, with no on-chain verification of TAC L2 validator signatures. - Whoever controls the rotator can inject any root and drain any wrapped jetton. This is an architectural vulnerability, not a code bug.
- The attacker is synchronised with the rotator: the “root injected -> drain claims” window is 2-7 seconds. It is an automated pipeline.
- The trigger wallet was a legitimate bridge user since July 2025. It shares a custom code template with another 48k-TON wallet — a strong insider signal.
Timeline
- 2025-07-15 — trigger wallet
0:7d628ee4…is created and funded with 100 TON from FixedFloat (no-KYC CEX). For 10 months it uses the bridge for normal cross-chain swaps. This is anti-attribution: by the time of the drain the wallet looks like a regular user. - 2026-05-10 — the final jetton recipient V5R1
0:4f1e15f2…is funded with 4 TON, again via FixedFloat. The two wallets have different funding sources — compartmentalisation. - 2026-05-11 ~09:14 UTC — first drain transaction. The rotator injects a root, 2-7 seconds later a proxy contract claims it via
0x0E50D313, and the bridge releases wrapped jettons to V5R1. - From 09:14 UTC onward — the rotator keeps injecting new roots every ~36 seconds. The drain wallet claims drain-useful roots within their 90-second TTL.
- 2026-05-12 12:14 UTC — c4 snapshot of the bridge: only 2 roots live in validators_dict (older ones evicted), cumulative outflow already exceeds $2.5M in wrapped jettons.
- 2026-05-14 (today) — still no official TAC response. admin1 handler
0x7EE5A6D0(WIPE_VALIDATORS) has not been called, no code upgrade has been shipped. The drain continues.
What the TAC bridge is
TAC is an EVM-compatible L2 associated with TON. The bridge between TON and TAC runs a classical lock-and-mint flow:
- A user sends a TON asset (e.g. USDT jetton) into the bridge admin contract.
- The bridge locks or burns the asset and emits an event.
- TAC L2 validators sign the event.
- A corresponding wrapped asset is minted on the TAC chain.
The reverse path is the inverse: the user burns the wrapped asset on TAC, validators sign, and the TON side releases the original jetton to the user’s address. The reverse branch (TAC -> TON) is what the current attack exploits.
The bridge admin contract on TON is 0:20a5698ef27054ae67ce3a57481264642c4c15f8821f540949b29858b309ee2f. It controls the master contracts of all wrapped jettons (wUSDT, wWETH, wcbBTC, native TAC and more). Any jetton release or TAC mint can only originate from this admin contract.
Anatomy of the drain handler 0x0E50D313
All 10 handlers of the bridge admin are decoded. The drain release sits at op-code 0x0E50D313 (decimal 240177939) and looks like this. Pseudocode is condensed but matches lines 513-574 of the disassembly:
IFJMPREF {
; AUTH 1: sender must be a proxy whose state_init
; derives from the message body
NEWC ... STDICT STDICT STU 1 ENDC
HASHCU
CTOS SDEQ
THROWIFNOT 71
; AUTH 2: virtual_root_hash from the Merkle proof
; must be a key in validators_dict (GLOBAL 5)
XCTOS DROP
PUSHINT 8 SDSKIPFIRST
LDU 256
GETGLOB 5 PUSHPOW2 8 DICTUGET
NULLSWAPIFNOT NIP
THROWIFNOT 201
; PAYLOAD: dict of jetton amounts, destination addr, TON grams
DUP CTOS LDDICT SKIPDICT LDMSGADDR LDGRAMS DROP
; ... emit 0xD7B9C06E (mint TAC) + 0x7817B330 (release jetton)
}
Two authorisation gates:
- AUTH 1 (proxy-self-hash). The sender must match the hash of a
state_initreconstructed from the message body. So the attacker must send the message from an address whosestate_initis present in the same message. Easy: deploy a fresh proxy contract on the fly in the same transaction. - AUTH 2 (root in validators_dict). Extract
virtual_root_hashfrom the Merkle proof; the root must exist inGLOBAL 5.
No validator signature checks, no quorum, no chain of custody back to TAC L2 anywhere in the drain handler. If the root is in the dictionary — the release fires.
What controls validators_dict
Handler 0x23B05641 (EPOCH_ROTATE), lines 1213-1291:
IFJMPREF {
GETGLOB 3 ; rotator address
SDEQ THROWIFNOT 72 ; sender == GLOBAL 3
NOW GETGLOB 9 GEQ
THROWIFNOT 202 ; rotate only after deadline
XCHG_0I s3
LDU 256 ; new root_hash from body
LDU 48 ; new deadline
... GLOBAL 7/8/9 updates ...
; Evict roots whose deadline <= NOW - GLOBAL 4 * GLOBAL 10
WHILE { DICTUMIN ... DICTUDEL }
; Insert (root_hash -> 48-bit deadline)
NEWC GETGLOB 9 STU 48 ENDC CTOS
GETGLOB 5 PUXC s4 s-1 PUSHPOW2 8 DICTUSET
SETGLOB 5
}
This is the entire trust boundary. Whatever the rotator submits gets accepted as a “validator-certified” record on the TON side. No CHKSIGN inside the bridge admin for verifying any cross-chain signature exists at all.
The c4 of the bridge also carries a notable shape:
GLOBAL 10 = 30— epoch duration in seconds.GLOBAL 4 = 3— TTL multiplier. A root is valid for3 * 30 = 90seconds.- In the live snapshot, validators_dict only holds 2 roots. Older ones are evicted on every rotation.
This explains why the drain is hard to spot in a fresh sample of the bridge: a used root only lives ~90 seconds and is immediately removed. In the 12 May snapshot the dictionary only held the two freshest roots — the drain-used roots are no longer present in the current state and can only be recovered from transaction history.
Attack chain end-to-end
Worked example: drain transaction fd239f3189f89f63d271f80a35b93f6ba2ecb092ed1e8eccdcd54f963daa7431 at 2026-05-11 02:27:28 UTC. 14 internal transactions.
| Step | src -> dst | op | Role |
|---|---|---|---|
| 1 | ext -> Highload 0:7d628ee… | 0x59b6416b | external signed entry |
| 2 | Highload -> self | 0xae42e5a4 | wallet wrapper |
| 3 | Highload -> PROXY 0:e31a1fb4… | 0x3b6616c6 | deploy proxy (state_init in body) + invoke |
| 4 | PROXY -> bridge admin | 0x0E50D313 | drain trigger |
| 5 | bridge -> wUSDT master | 0x7817b330 | release request |
| 6 | bridge -> TAC jetton master | 0xd7b9c06e | mint TAC to attacker |
| 7 | wUSDT master -> wUSDT wallet | 0x0f8a7ea5 | internal burn (release) |
| 8 | TAC master -> new TAC wallet | 0x178d4519 | internal transfer (mint dest) |
| 9 | bridge -> Highload | 0xd53276db | excess refund |
| 10 | USDT chain -> final wallet 0:2ced3a3b… | 0x178d4519 | 800k USD₮ lands at V5R1 |
| 11-14 | notifications + excess | — | refunds back to Highload + V5R1 |
The proxy contract 0:e31a1fb4… is an exotic cell ref to a library with hash E46FA11560C837D5226A87E1BEC85BB89BF960C0077C8E4354A747242CBA1B05. That library was published by the TAC team and stored in the bridge admin itself (ref 1 in c4). So the proxy mechanism is by design — the attacker is not shipping their own shellcode, they are using the same template legitimate users use.
Synchronisation with the rotator
A narrow-window scan of bridge admin transaction history at 02:25-02:30 UTC on 11 May shows perfect synchronisation:
| Time (UTC) | Source | op | Contains target root 21259ae9… |
|---|---|---|---|
| 02:26:11 | rotator | 0x23B05641 | first injection (different root) |
| 02:26:18 (+7s) | proxy | 0x0E50D313 | drain on previous root |
| 02:26:45 | rotator | 0x23B05641 | new injection |
| 02:26:47 (+2s) | proxy | 0x0E50D313 | drain |
| 02:27:21 | rotator | 0x23B05641 | root 21259ae9… injected (offset 25) |
| 02:27:28 (+7s) | proxy | 0x0E50D313 | drain claims the same root |
The window between injection and claim is 2-7 seconds. No human submits transactions with this latency. It is an automated pipeline: something listens to the rotator in real time and fires the drain immediately.
Root causes
There is no logic bug in the TON contract. The trust chain admin1 -> rotator -> validators_dict -> drain runs exactly as coded. That means the vulnerability is architectural: a single entity (rotator) can insert an arbitrary Merkle root into the dictionary without any on-chain validator signature verification on the TON side.
Three possible root causes:
(C) Insider rug — most likely (~60%)
- The drain wallet was a legitimate bridge user for 10 months. Triggering the drain requires deep knowledge of the Merkle proof payload format and the proxy library mechanism.
- The wallet uses custom code template
11acad79…, also seen in another 48k-TON wallet (~$120k). That is an internal distribution pattern — a validator programme, partner programme, or team-issued wallet. - The absence of CHKSIGN in TVM on the TON side is a deliberate architectural decision by the TAC team. The rotator is treated as a trusted oracle by design. Misuse of a trusted role is exactly the failure mode this architecture exposes.
- The rotator runs in fully automated mode, emitting new roots every ~36 seconds for hours on end. If this were a panic reaction to a compromise, behaviour would be different: rotator stopped, WIPE_VALIDATORS attempted, patch shipped.
(A) Rotator key compromise (~25%)
- The rotator code contains two CHKSIGNU instructions and a
signers_dictin c4. By shape it is an N-of-M multisig (threshold check viaGTINT 3 THROWIF_SHORT 44suggests 2-of-3 or 3-of-N). - If N signers’ keys leaked (phishing, supply chain, infrastructure compromise), an external attacker could submit correctly signed messages to the rotator, which would honestly forward them to the bridge admin.
(B) Logic bug in the rotator (~15%)
- 793 TASM lines of rotator code with two CHKSIGNU and complex
PUXC2/SDATASIZE/dict-of-pendinglogic. Replay attacks, signature malleability, dict collisions are theoretically possible. - A line-by-line audit of the rotator has not yet been done — that is open work.
Distinguishing (A) / (B) / (C) requires decoding the rotator’s signers_dict and comparing signer public keys against known TAC team wallets — especially wallets using the 11acad79… template.
Where admin1 is and why it is silent
admin1 (0:c1af58471b74ea5da7cbc7020ae0831bfa0b96e47ccb491943153bee796f2bc3) holds emergency powers, any of which would stop the drain:
0x7EE5A6D0(WIPE_VALIDATORS) — clearsGLOBAL 5(validators_dict) and resetsGLOBAL 14. The drain handler will stop finding roots, AUTH 2 will always throw exception 201.0x20FAEC53(UPGRADE_CODE) —SETCODE BLESS POPCTR c3. Full contract code replacement, no timelock. A single patch can add real validator signature verification.0x5CEC6BE0(SET_ROTATOR) — reassign rotator to any address. If admin1 has a backup multisig with real verification, the pipeline can be redirected to it.0x581879BC->0x6A4FBE34(two-step admin rotation) — transfer admin1 to another address.
All four powers are timelock-free. The decision can be enacted in a single transaction. The fact that admin1 has not intervened three days into the drain is itself an argument in favour of the insider scenario.
What this means for TON users
The direct risk applies to anyone holding positions in wrapped TAC jettons: wUSDT, wWETH, wcbBTC, native TAC, BLUM on TAC, tsTON and others. Until admin1 acts:
- Pull liquidity out of every wrapped TAC jetton now, if it is still there.
- Do not make new deposits in either direction. Any funds that end up under bridge admin control can be drained within seconds of the next epoch.
- Do not buy wrapped TAC jettons on DEX. They are “backed” by a bridge contract that is being drained in real time.
- Audit your connected dApps via “Connected apps” in Tonkeeper / MyTonWallet. If any active sessions are with frontends that route through the bridge — revoke them.
The indirect risk is liquidity pools on STON.fi / DeDust paired with wrapped TAC jettons. If LP providers exit en masse, TAC bridge tokens collapse to zero on DEX, dragging pairs against TON and USDT with them. That scenario is also worth pricing in.
Lessons for other TON bridges
The TAC vulnerability is not a quirk, it is a pattern. Any DIY bridge on TON with a “trusted oracle” / “trusted rotator” and no on-chain cross-chain signature verification has the same attack surface. If you use or build a bridge, here is the checklist:
- Grep the disassembly for CHKSIGN/CHKSIGNU/CHKSIGNS. If the bridge admin contains zero signature-check instructions — that is a red flag. Trust shifts entirely to an off-chain process you cannot see.
- How many signers actually sign a release? If the answer is “one rotator address” — that is not a bridge, that is a custodian with extra steps.
- Does admin have UPGRADE_CODE with no timelock? If yes — that is a rug button. Even if the team is honest, a leaked admin key drains any amount in a single transaction.
- Are there validator-wipe handlers in the code? WIPE_VALIDATORS / SET_ROTATOR with no timelock are a crisis backdoor, and equally an insider-rug backdoor.
- Is the validator rotation mechanism transparent? If epochs are short (tens of seconds) and roots evict within minutes — externally tracking abuse is effectively impossible. That is a plus to the attacker’s anonymity.
Disclosure path
The TAC bug bounty channel is info@tac.build (cc tech@tac.build). Before sending technical details, verify channel authenticity: PGP signature from admin1’s wallet, DKIM-validated email headers. The scene is heated and attackers may try to extract PoC details under the guise of “official” representatives.
Mainnet repro is impossible without rotator signing keys — TVM reverts any 0x23B05641 from any other sender at compute (THROWIFNOT 72). Testnet repro is the standard route: clone the bridge admin, assign yourself as admin1 and rotator, demonstrate the same drain. That is sufficient for a PoC with no mainnet risk.
Open work
Open tasks on the forensics side:
- Decode the rotator’s
signers_dictin c4GLOBAL 4, match signer pubkeys against known TAC team addresses and wallets using the11acad79…template. A direct match closes the insider-rug case. - Line-by-line audit of the 793 TASM lines of the rotator for signature bugs: replay, malleability, deduplication, off-by-one in threshold check.
- Disassemble admin1 (874 lines). The absence of CHKSIGN in admin1 means authorisation goes through a different path — find which one.
- Disassemble the proxy library at hash
0xE46FA11560…via a direct query to the masterchain library_dict. - Track outflow from V5R1 (
0:4f1e15f2…) — where 1.117M+ USD₮ ends up, through which mixers, and into which CEX.
The TAC bridge attack is an example of how an architectural vulnerability can incubate for months while an insider wallet waits for the right moment. The current state — drain still active, no official response. If you hold funds tied to the TAC bridge, exit now.
Frequently asked
What is the TAC bridge and why does it exist?
How much has been drained and is the attack ongoing?
What is the attacker's address and can I verify it?
Is this a bug or intentional?
Is it safe to use the bridge right now?
Are other TON bridges affected?
Why has the drain not been stopped yet?
Can the attack be reproduced for a bug bounty?
Related
- SecurityMar 20, 2026
Drainer sites in TON: how they work and how not to fall
Technical breakdown of drainer campaigns in the TON ecosystem in 2025-2026 — from Drainer-as-a-Service to specific TON Connect tricks
- SecurityMar 15, 2026
Top 10 TON scams on Telegram and how to defend yourself
What schemes attackers run on Telegram against TON users in 2025-2026, real loss figures and step-by-step defence rules for retail.
- AnalyticsApr 7, 2026
How to read TON on-chain metrics: an analyst's guide
Which TON metrics matter, how they differ from Ethereum's, and how to interpret them correctly. Data sources, traps, methodology — with references.
- SecurityMar 24, 2026
Anatomy of phishing: how to spot a fake TON wallet site
Step-by-step breakdown of how attackers clone Tonkeeper and MyTonWallet sites, the markers that give away a fake
- BasicsJan 6, 2026
Reading TON transactions in TonScan: 2026 guide
Walking through TonScan and Tonviewer step by step: how to find a transaction by hash, what every field means, how to read messages, jetton transfers