Secret Network's $4.67M Loss and How Olympix Would Have Prevented It
On June 17, 2026, a routine transfer of a little Wrapped Bitcoin from Secret Network back to Axelar failed. The escrow on the Axelar side was supposed to hold 1,000,000 satoshis' worth for the release and only held 803,200. Nobody had noticed anything wrong until then, because the money had been gone for a week.
Seven days earlier, an attacker had built a blockchain of their own, connected it to the bridge contract that wraps Axelar assets on Secret, and sent it seven deposits that never happened. The contract minted wrapped tokens for every one of them, and the attacker redeemed those tokens through the real Axelar channel for about $4.67 million in USDT, USDC, DAI, WETH, WBTC, WBNB and wstETH, according to the postmortem from Common Prefix, Axelar's lead steward.
Two Chains, One Escrow
Secret Network is a Cosmos chain built around privacy. Balances and transfers are encrypted by default, and its smart contracts run on CosmWasm, the Rust-based contract framework used across the Cosmos ecosystem. Assets from Ethereum and other chains reach Secret through Axelar, a cross-chain network that moves tokens over IBC, the Cosmos standard for chain-to-chain messages.
The connection works like an escrow and a receipt. When a user sends USDC from Axelar to Secret, Axelar locks the real USDC in an escrow tied to a specific IBC channel, and a contract on Secret issues a wrapped version, saUSDC, to the user. When the user sends saUSDC back, Secret burns it and Axelar releases the real USDC from that same escrow. The arrangement only stays solvent if every saToken on Secret corresponds to something actually locked on Axelar.
On Secret's side, that receipt-issuing job belonged to a contract called ics20-for-axelar, deployed at secret1yxjm...ee83. It's a fork of CW20-ICS20, the standard CosmWasm contract for moving tokens over IBC, and it was first instantiated on March 30, 2023, according to Common Prefix. A migration on March 5, 2026 moved it to a new code version, which is the one the attacker found.
The Checks That Were Commented Out
The upstream contract tracks tokens per channel. When a deposit packet arrives, it parses the token's denomination to confirm the packet came from the channel that token belongs to, and it checks that the channel has actually escrowed enough of that token to cover what's being released. Those two checks are what tie an incoming packet to real money on the other side.
The Secret fork changed the model from escrow to mint. Instead of releasing tokens it was holding, it mints saTokens through separate token contracts, and it decides what it can mint from an allow-list of supported denominations. According to Secret Network's incident report, the two functions that validated transfer sources were removed in the process.
The fork's public source code on GitHub (scrtlabs/ics20-for-axelar, at the commit Common Prefix identified) shows exactly where. Inside do_ibc_packet_receive, the function that handles every inbound deposit, both checks sit commented out:
A few lines later, the same function looks the denomination up and mints:
What's left is a function that accepts any packet whose denomination appears on the allow-list, from any channel, for any amount. The allow-list is keyed by token name, not by where the packet came from. The only other filter rejects denominations containing a slash, which keeps out tokens that originated on Secret but does nothing to stop a bare name like uusdc arriving from somewhere it shouldn't.
That gap only matters because anyone can open an IBC channel. Channel creation is permissionless by design, so the contract would happily talk to a chain nobody had ever heard of, and it would treat that chain's word on what had been deposited as final.
A Private Chain With One Validator
The attack took one day, according to Common Prefix's timeline. From 06:50 to 18:54 UTC on June 10, the attacker rehearsed, creating test light clients and channels and pushing a small 180 SCRT test transfer through Osmosis to Axelar.
The real run started at 19:01 UTC. The attacker created a light client for a chain they controlled, named ibc-dev-allowlist-denom-1, with a single validator. Between 19:02 and 19:05 they opened a connection and a channel, channel-227, from that chain straight to the bridge contract. Since they ran the only validator, they could put whatever they wanted in that chain's state and prove it to Secret.
From 19:14 to 19:20 UTC, they relayed seven forged deposit packets over channel-227 themselves. Each one claimed that a sender on the fake chain had deposited a supported token, and each one carried a bare denomination that matched the allow-list. Secret's report lists the mints as 1,121,270.05 saUSDT, 638,198.65 saUSDC, 174,536.48 saDAI, 893.47 saWETH, 16.54 saWBTC, 170.76 saWBNB and 17.66 sawstETH.
Mintscan shows one of them clearly. Transaction DC4529...BB42, at 19:15:07 UTC in Secret block 25,688,686, was signed by the attacker's Secret wallet, updated light client 07-tendermint-244 and delivered the packet that minted the saUSDC. The relayer memo reads hermes 1.13.2, the standard open-source IBC relayer, which the attacker was simply running against their own chain.
Cashing Out Through the Real Bridge
The forged tokens were real saTokens, indistinguishable from the ones backed by Axelar's escrow, so the attacker sent them home. Between 19:33 and 19:36 UTC, all seven were withdrawn to Axelar over channel-61, the legitimate Secret-to-Axelar channel, and Axelar's escrow on channel-69 released the matching real assets to a single wallet, axelar1hzra...6ad8.
From there the trail moved fast. Common Prefix counted 18 IBC transfers from Axelar between 19:38 and 19:56 UTC, routed through Osmosis and back out to Ethereum, with USDC swapped on Osmosis and sent through Noble and Circle's CCTP. By about 20:00 UTC, the attacker's Ethereum wallet 0x6c2e...3976 held roughly 893 WETH, 1.12 million USDT, 174,534 DAI, 10.3 WBTC, 17.65 wstETH and 398,873 USDC.
Between 20:04 and 20:15 UTC, those tokens were swapped into ETH on CoW Protocol, consolidating about 2,350 ETH. Over the next twelve hours, the ETH was split into about 30 transfers of 50 to 139 ETH each and pushed into deposit addresses at KuCoin, ChangeNow and HitBTC. A smaller leg of about 106 BNB went through BSC to ChangeNow.
Some of it never left. About 6.2 WBTC, 239,324 USDC, 64 WBNB and 248 AXL stayed stranded in the attacker's Axelar wallet, which Secret valued at about $770,000 when it published its report.
A Week of Silence
For seven days, nothing flagged. Secret's balances are encrypted by default, so the growing gap between saTokens in circulation and real assets in escrow wasn't visible on-chain the way it would be on a transparent chain. The discovery came from the failed WBTC transfer at 15:39 UTC on June 17, and within about an hour Axelar's team had found the drained escrow and traced it to the seven June 10 withdrawals.
The response moved quickly after that. Secret was notified at 18:28 UTC, the cross-chain router Squid removed Secret from its frontend, and at 20:45 UTC Axelar's Emergency Committee paused the Secret and Secret-SNIP connections. Secret disabled bridging on its own frontend, warned holders that saTokens may be impaired, and engaged exchanges and law enforcement with a full trace.
The two teams disagree on parts of the story. Axelar said neither Axelar nor IBC was compromised and that the exploited contract wasn't developed, deployed or maintained by Axelar. Secret said no monitoring or emergency pause on the Axelar side caught the drain, that Axelar didn't request an external audit for the integration, and that Axelar declined its request to freeze the remaining funds. As of November 10, 2026, the stranded assets hadn't been reported recovered.
Why a Commented-Out Line Lasted Three Years
Common Prefix lists three contributing factors: minting authorized by token name instead of source channel, a fork whose core checks were disabled without a re-audit of the new trust model, and permissionless channel opening that let an attacker connect a chain they controlled.
The first two are really one decision seen from two sides. Moving from escrow to mint changed what each check protected. reduce_channel_balance() made sense when the contract released tokens it held, because it capped releases at what a channel had deposited. In a mint model there's no balance to reduce, so dropping it looked natural. But that function was also the only thing stopping an unknown channel from claiming deposits it never made, and the allow-list that replaced it didn't carry that job over.
It's the kind of change that reads as cleanup. Two lines commented out during a refactor, a new allow-list doing what looks like the same filtering, and a contract that works perfectly for every legitimate transfer. Every packet that came through the real Axelar channel was valid, so normal use would never reveal that packets from anywhere else were valid too.
How Olympix Would Have Caught the Open Mint
Olympix reads a contract as states, transitions and entry points, and it takes in the surrounding context to infer what the code is supposed to guarantee. For ics20-for-axelar, the state that matters is the supply of each saToken against the assets Axelar holds in escrow for it. The transition is the mint in do_ibc_packet_receive. The entry point is every inbound IBC packet, from every channel the contract will accept.
The invariant falls straight out of that structure. Every saToken minted must be backed by a matching deposit on the authorized Axelar channel, so a packet from any other channel must never mint. The upstream code enforced exactly that, and the fork's own comments still describe how a remote token's denomination is supposed to look, which is the kind of context the engine uses to infer what the code should check.
The signal here is plain. A minting path whose authorization depends only on a value the caller supplies, the denomination, and never on the packet's source, is a missing source check on the most sensitive operation the contract has. Signals like that let the engine narrow from every path into the contract to the feasible ones, and from there to the few that touch the backing invariant.
From there the search is short. The engine looks for a sequence of state changes that breaks the invariant, and it would have found one in a few steps: open a channel from a counterparty the contract has never seen, deliver a packet with an allow-listed bare denomination, and observe saTokens minted with nothing escrowed against them. Olympix would have returned that sequence as a counterexample, with the minimal conditions that trigger it and a generated test that reproduces it, at the merge where the two checks were commented out.
That counterexample points at a small fix. Restore the source check so a packet is accepted only from the authorized Axelar channel, or key the allow-list by channel and denomination together, so the same token name arriving from anywhere else is rejected. Either would have made the June 10 packets fail on arrival.
Olympix runs at every merge and before release, and a migration is a release. The March 2026 code version that went to production would have been checked against the same invariant before it ever held a dollar.
Mint Only What Something Else Is Holding
A bridge contract that mints is making a promise that something real sits behind every token. That promise has to be enforced where the minting happens, against where the message came from, not just what it says. To see how Olympix maps every path into your mint and escrow logic and returns a working counterexample for each one that breaks, book a technical walkthrough.
On-Chain References
Forged saUSDC mint (Jun 10, 2026, 19:15:07 UTC): DC4529...BB42
Vulnerable ics20-for-axelar contract: secret1yxjm...ee83
Attacker Secret wallet: secret154pd...03vm
Attacker Axelar wallet: axelar1hzra...6ad8
Attacker Ethereum wallet: 0x6c2e...3976
More post-mortems
See every post-mortem →See what Olympix finds on your own code
Send us a repo that's already been audited, and we'll run Olympix at the same commit and show you what comes back.




