An attacker chained six MAYAChain bugs so a false theft alert paid an uncapped 49.45M CACAO subsidy into a pool holding 0.11 LINK, then took 99.93% of it and withdrew 48.87M CACAO.
On August 18, 2026 at approximately 17:30 UTC, an attacker chained six bugs in MAYAChain's trade account and outbound handling to extract $1,356,218 to external chains from Maya Protocol, a THORChain fork that settles cross-chain swaps between Bitcoin, Ethereum, Arbitrum and its native CACAO. A single MsgDeposit transaction carrying 23 messages at block 17977941 overwrote the protocol's own observed-transaction voter, which made the outbound matcher conclude that a legitimate LINK transfer had been stolen. The resulting slash subsidy was never capped against pool depth, so it booked 49.45 million CACAO into an ARB.LINK pool holding roughly 0.11 LINK. That inflated balance was committed to state before the transfer meant to fund it, the transfer failed because the Reserve held only about 168,000 CACAO, and the error was logged without a rollback. Thirty blocks later the attacker added 100 CACAO to the inflated pool, took 99.93% ownership, and withdrew 48.87 million CACAO from the Asgard module. CACAO fell 88.7% from $0.115 to $0.013, and the team halted the network.
The exploit targeted no pricing formula and no signature check. It targeted the bookkeeping MAYAChain uses to decide whether it has been robbed, and then collected the compensation the protocol paid itself. Six defects were required in sequence, and no single one of them was sufficient.
The entry point was voter clobbering in handler_deposit.go. Every message inside a batched MsgDeposit constructs a fresh ObservedTxVoter keyed on the same native transaction ID and writes it over the previous one:
// handler_deposit.go:283-287
txIn := ObservedTx{Tx: tx}
txInVoter := NewObservedTxVoter(txIn.Tx.ID, []ObservedTx{txIn})
txInVoter.Height = ctx.BlockHeight()
txInVoter.FinalisedHeight = ctx.BlockHeight()
txInVoter.Tx = txIn
h.mgr.Keeper().SetObservedTxInVoter(ctx, txInVoter) // clobbers previousThe attacker's transaction packed 20 trade-:ARB~ETH withdrawals, two trade-:ARB~LINK withdrawals, and a final DONATE:ARB.LINK of one base unit. The DONATE message arrived last, so its voter overwrote every preceding trade-withdrawal voter, resetting OutboundHeight to 0 and marking the transaction done.
That reset broke the outbound matcher in handler_common_outbound.go. With OutboundHeight at zero it falls back to FinalisedHeight, then walks forward in signingTransPeriod strides rather than checking each block:
// handler_common_outbound.go:87-92
outHeight := voter.OutboundHeight // 0 (clobbered by DONATE)
if outHeight == 0 {
outHeight = voter.FinalisedHeight // 17977941
}
for height := outHeight; height <= ctx.BlockHeight(); height += signingTransPeriod {
// loops 17977941, +300, ... never checks 17977942
// where the LINK outbounds actually landed
}The LINK outbounds landed at height 17977942, one block past the starting point and 299 blocks short of the next stride. The matcher never looked there, declared the outbounds missing, and triggered slash-for-theft against a transfer that had in fact settled correctly. Every step after this is the protocol responding correctly to a false conclusion.
The slash path then computed a subsidy without bounding it by the pool it was compensating:
// helpers.go:224 (subsidizePoolWithSlashBondV92)
runeValue := pool.AssetValueInRune(coin.Amount) // UNCAPPED by pool.BalanceAsset
// coin.Amount = 97,280,371 (from LINK withdrawal)
// pool has 0.11 LINK, so the ratio explodes
// 97,280,371 x 508B / 100 = 4.9x10^17 = 49.45M CACAO
f.subsidiseRune = f.subsidiseRune.Add(runeValue)A sibling path in manager_slasher_current.go:916 does cap the value. This one does not, and because the ARB.LINK pool held roughly 0.11 LINK, converting the notionally stolen amount at the pool ratio produced 49.45 million CACAO of compensation for a pool worth almost nothing.
Two ordering defects then made that number permanent. The pool write is committed before the protocol attempts to move the CACAO backing it:
// helpers.go:248-261 (SetPool before SendFromModule)
pool.BalanceCacao = pool.BalanceCacao.Add(f.subsidiseRune)
pool.BalanceAsset = common.SafeSub(pool.BalanceAsset, f.stolenAsset)
if err = mgr.Keeper().SetPool(ctx, pool); err != nil { // STATE COMMITTED
...
}
// Then tries to fund it:
if err = mgr.Keeper().SendFromModuleToModule(ctx, ReserveName, AsgardName, ...); err != nil {
ctx.Logger().Error(...) // FAILS (reserve 168k < 49.45M) but pool stays inflated
return err // error returned, and the caller swallows it
}The Reserve held about 168,000 CACAO against a 49.45 million CACAO obligation, so the funding transfer could never succeed. The error it returned was then discarded by the caller:
// handler_observed_txout.go:282-291
_, err = handler(ctx, m)
if err != nil {
ctx.Logger().Error("handler failed:", "error", err)
slashObservedOutbound("failed_outbound")
voter.SetDone() // marks done
h.mgr.Keeper().SetObservedTxOutVoter(ctx, voter)
continue // NO ROLLBACK, inflated pool persists
}Execution continued in the same Cosmos context with no rollback, leaving a pool balance that nothing had ever funded. A state change that should have been atomic with its funding step was not.
Collecting the balance required no further cleverness. At block 17977971 the attacker added 100 CACAO and a minimal amount of asset to the ARB.LINK pool, whose BalanceAsset was near zero. With one side of the pool effectively empty, calculatePoolUnits treated the deposit as though it were seeding a new pool and issued roughly one trillion units against 731 million existing units, giving the attacker 99.93% ownership. An immediate 9900 basis point withdrawal paid out 48.87 million CACAO from the Asgard module, drawn from the CACAO reserves backing every other pool on the network.
Extraction ran from block 17977998 to 17978008, converting CACAO to Bitcoin across roughly ten blocks. The Bitcoin was consolidated at:
bc1q0hsgwunccczelq05ucpmfz268eyy5jr2y5l646
That address received 20.8274 BTC worth $1,343,367, and Maya identified the same address in its own announcement, describing it as the attacker's address "if intentional". The exploit itself ran to completion inside a single block, and the first Bitcoin left roughly a minute later, which is the window any response would have had to act within. Machine-readable detection is built for exactly that interval, and Defimon streams confirmed exploit alerts over a WebSocket feed that risk systems can subscribe to rather than waiting on a human to notice a halt.
Maya Protocol is a fork of THORChain, and the architecture inherits the property that makes cross-chain swap networks hard to secure: the protocol custodies real assets on several chains and relies on its own internal accounting to decide what happened on each of them. When that accounting is wrong the loss is not confined to one contract, which is the structural weakness that also makes bridges DeFi's weakest link. What is unusual here is that no key was compromised and no external chain was misread. The records were corrupted by a transaction shape the protocol's own accounting did not anticipate.
Confirmed extraction to external chains totalled $1,356,218, almost all of it the 20.8274 BTC sent to the address above. The remainder went to Arbitrum as 6.0275 ARB.ETH worth $11,452 and 99.92 ARB.LINK worth about $1,400 across three destinations. The attacker also still held 8,874,269.11 CACAO worth roughly $287,651 at $0.032, plus small trade account balances, putting total attacker value at approximately $1,647,620.
CACAO was stable at $0.1151 through block 17975000 and $0.1150 at block 17977763, the last block before the exploit. It reached $0.0478 by block 17978000 with Bitcoin extraction underway, bottomed at $0.0130 at block 17978094 for an 88.7% decline, and recovered to $0.0304 by block 17978500 as extraction slowed. Because CACAO sits on one side of every pool on the network, that collapse repriced the entire pool set rather than only the pool that was drained.
Attribution deserves care here, because a chain-wide price collapse pulls in unrelated activity that is easy to mistake for the attack. Only outbounds confirmed from the attacker's own transactions are counted above. Two known arbitrageur addresses, active since roughly block 14,000,000, traded heavily during the crash, and the pool depth changes they caused are opportunistic rebalancing rather than part of the exploit. Maya separately noted that a large share of value was absorbed by arbitrageurs and by pool fees from the extreme slippage the attacker's exit created, and said it would approach those parties about a path forward.
The team halted network operations globally, pausing swaps while the defects were identified and patched. Halting was the only containment available, because the inflated pool balance was already committed to state and the attacker's exit route ran through the protocol's own swap paths.
Co-founder Aaluxx Myth announced the incident publicly within roughly an hour, stating that the protocol had "likely been exploited", that the global halt had contained the damage, and that the team would remedy the bug path before resuming swapping. The announcement appealed directly to the attacker to accept a bug bounty and return the funds.
The team also outlined how it intends to make liquidity providers whole. Rather than treating the shortfall as a permanent loss to the pools, Maya said it would work to recover the $1.4 million through investments in Aztec Chain and other means, and that donating 20 BTC back to the pool through those means would return CACAO to its pre-exploit price of $0.115 and make all parties whole. That plan depends on assets outside the protocol rather than on recovering the stolen Bitcoin, and no timeline was given.
Remediation is more involved than capping one calculation. Only one of the six defects is arithmetic. The rest are state management: a transaction ID treated as unique when a batch can reuse it, an outbound matcher that strides past the block it needs to check, a state write ordered before the transfer meant to fund it, an error path that logs a failure and continues, and pool math that treats a near-empty pool as a new one. Capping the subsidy at helpers.go:224 would have reduced the size of this payout without removing the conditions that produced it.
On August 18, 2026, an attacker chained six bugs in MAYAChain to extract $1,356,218 to external chains. A single MsgDeposit transaction carrying 23 messages overwrote the protocol's observed-transaction voter, which made the outbound matcher misclassify a legitimate LINK transfer as theft and pay an uncapped slash subsidy of 49.45 million CACAO into an ARB.LINK pool holding about 0.11 LINK. The attacker added 100 CACAO to that pool, took 99.93% ownership, and withdrew 48.87 million CACAO. The team halted the network in response.
Confirmed extraction to external chains was $1,356,218, almost all of it 20.8274 BTC worth $1,343,367, with the remainder in ARB.ETH and ARB.LINK. Counting the 8.87 million CACAO the attacker still held on-chain, total attacker value was approximately $1,647,620. The damage to the network was wider than the theft, because CACAO fell 88.7% and sits on one side of every Maya pool.
Six defects in sequence. Each message in a batched MsgDeposit overwrote the voter for the same transaction ID, so a trailing DONATE message reset OutboundHeight to zero. The outbound matcher then walked forward in signingTransPeriod strides and never checked the block where the LINK outbounds actually landed, declaring them missing. The resulting slash subsidy was not capped against pool depth, the inflated pool state was committed before the transfer meant to fund it, that transfer failed against an insufficient Reserve, and the error was logged without a rollback.
Yes. The team halted network operations globally and paused swaps while the defects were identified and patched. Halting was the only containment available, because the inflated pool balance was already committed to state and the attacker's exit route ran through the protocol's own swap paths.
The team has said it intends to make all parties whole, but it has not happened yet and no timeline was given. Maya said it would work to recover the $1.4 million through investments in Aztec Chain and other means, and that donating 20 BTC back to the pool would return CACAO to its pre-exploit price of $0.115. That plan depends on assets outside the protocol rather than on the attacker returning the stolen Bitcoin, which the team has separately appealed for through a bug bounty.
Defimon detects exploits on major chains the moment they execute and streams them to you: human-readable alerts in Telegram, or structured JSON over WebSocket for your own systems.
// signals $50/mo · websocket $200/mo · channel free
@DefimonAlerts