Skip to main content
T TON Adoption
Security FORENSICS · 2026

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
· research lead · security desk
Published
11 min read

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 0x0E50D313 accepts a Merkle proof whose “virtual root” must be present in the GLOBAL 5 dictionary (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:

  1. AUTH 1 (proxy-self-hash). The sender must match the hash of a state_init reconstructed from the message body. So the attacker must send the message from an address whose state_init is present in the same message. Easy: deploy a fresh proxy contract on the fly in the same transaction.
  2. AUTH 2 (root in validators_dict). Extract virtual_root_hash from the Merkle proof; the root must exist in GLOBAL 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 for 3 * 30 = 90 seconds.
  • 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.

Stepsrc -> dstopRole
1ext -> Highload 0:7d628ee…0x59b6416bexternal signed entry
2Highload -> self0xae42e5a4wallet wrapper
3Highload -> PROXY 0:e31a1fb4…0x3b6616c6deploy proxy (state_init in body) + invoke
4PROXY -> bridge admin0x0E50D313drain trigger
5bridge -> wUSDT master0x7817b330release request
6bridge -> TAC jetton master0xd7b9c06emint TAC to attacker
7wUSDT master -> wUSDT wallet0x0f8a7ea5internal burn (release)
8TAC master -> new TAC wallet0x178d4519internal transfer (mint dest)
9bridge -> Highload0xd53276dbexcess refund
10USDT chain -> final wallet 0:2ced3a3b…0x178d4519800k USD₮ lands at V5R1
11-14notifications + excessrefunds 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)SourceopContains target root 21259ae9…
02:26:11rotator0x23B05641first injection (different root)
02:26:18 (+7s)proxy0x0E50D313drain on previous root
02:26:45rotator0x23B05641new injection
02:26:47 (+2s)proxy0x0E50D313drain
02:27:21rotator0x23B05641root 21259ae9… injected (offset 25)
02:27:28 (+7s)proxy0x0E50D313drain 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_dict in c4. By shape it is an N-of-M multisig (threshold check via GTINT 3 THROWIF_SHORT 44 suggests 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-pending logic. 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) — clears GLOBAL 5 (validators_dict) and resets GLOBAL 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:

  1. Pull liquidity out of every wrapped TAC jetton now, if it is still there.
  2. 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.
  3. Do not buy wrapped TAC jettons on DEX. They are “backed” by a bridge contract that is being drained in real time.
  4. 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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:

  1. Decode the rotator’s signers_dict in c4 GLOBAL 4, match signer pubkeys against known TAC team addresses and wallets using the 11acad79… template. A direct match closes the insider-rug case.
  2. Line-by-line audit of the 793 TASM lines of the rotator for signature bugs: replay, malleability, deduplication, off-by-one in threshold check.
  3. Disassemble admin1 (874 lines). The absence of CHKSIGN in admin1 means authorisation goes through a different path — find which one.
  4. Disassemble the proxy library at hash 0xE46FA11560… via a direct query to the masterchain library_dict.
  5. 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

A cross-chain bridge between TON and EVM networks. It moves assets (USDT, WETH, cbBTC and more) between TON and the TAC L2 chain. On the TON side it is run by an admin contract that mints wrapped jettons on deposit and releases them on withdrawal.
As of the analysis snapshot (12 May 2026) — over $2.5M in wrapped jettons (USDT, BLUM, tsTON and others) plus ~384k freshly-minted TAC. The drain is still active at time of writing: the rotator injects new Merkle roots every ~36 seconds, the attacker claims them within 2-7 seconds.
Drain trigger wallet 0:7d628ee463b865b1507a69e8bc6644a9888d8e3c2fe491b879280a14aca5b4e1 (UQB9Yo7kY7hlsVB6aei8ZkSpiI2OPC_kkbh5KAoUrKW04ZxW). Final jetton recipient — V5R1 0:4f1e15f21bfbdbb0012707a61c70d86169573a2378b138273bbeb89a3b3a3800. Both visible on any TON explorer (tonviewer, tonscan).
No logic bug exists in the TON contract — the entire trust chain (admin1 -> rotator -> validators_dict -> drain) runs exactly as coded. The vulnerability is architectural: there is not a single CHKSIGN instruction anywhere in the bridge admin TVM code. The source of truth is whatever the rotator submits. The attacker either controls the rotator or is part of the TAC team itself (insider scenario).
Absolutely not. Until admin1 calls WIPE_VALIDATORS (handler 0x7EE5A6D0) or ships a code upgrade with actual TAC L2 signature verification, any funds that end up under bridge admin control can be drained. Pull liquidity out of every wrapped TAC jetton before any patch lands.
Not directly — this is a vulnerability of TAC's specific architecture, where the rotator works as a trusted oracle without on-chain signature verification. But the pattern (a bridge with a trusted rotator and no CHKSIGN in TVM) recurs across DIY TON bridges. If you have funds in wrapped jettons of non-standard bridges, grep their disassembly for CHKSIGN/CHKSIGNU.
Only admin1 can stop it (address 0:c1af58471b74ea5da7cbc7020ae0831bfa0b96e47ccb491943153bee796f2bc3). It has WIPE_VALIDATORS (0x7EE5A6D0) and UPGRADE_CODE (0x20FAEC53), both timelock-free. The fact that admin1 has not intervened in three days is alarming on its own and supports the insider-rug theory.
On mainnet — no. TVM reverts any 0x23B05641 from a non-rotator sender at the compute phase (THROWIFNOT 72). On testnet — yes. Clone the bridge admin, assign yourself as admin1+rotator, demonstrate the same drain. That is a legitimate PoC without touching mainnet funds.

Related