Axelar’s core protocol was not affected. The impact is confined to Secret-wrapped versions of Axelar assets on the Secret↔Axelar channel-69/channel-61 IBC connection; no other chains, assets, IBC channels, or escrow accounts were affected, and no action is needed.
Incident Summary
On June 10, 2026, an attacker exploited an ‘infinite mint’ bug in a vulnerable IBC-enabled smart contract on Secret Network (secret-4) to mint unbacked, Secret-wrapped versions of Axelar-wrapped assets (saTokens), then redeemed them back over the legitimate IBC channel to drain the real Axelar-wrapped assets held in escrow on Axelar. The smart contract, a modified CW20-ICS20 implementation deployed on Secret, did not verify the source channel of inbound IBC packets before minting, so deposits forged over an attacker-controlled channel minted genuine saTokens with no assets backing them. Approximately $4.67M worth of tokens were drained.
Background
The Inter-Blockchain Communication protocol (IBC) lets independent Cosmos chains exchange messages and tokens. Instead of a trusted intermediary, IBC secures each channel with light clients. For each counterparty chain, both chains run an on-chain light client of the other, tracking the counterparty’s validator set and block headers. This setup allows the light client to confirm whether some event happened on the other side, without needing to trust any external validators. To deliver an IBC packet, a relayer submits the packet together with a proof that this packet was committed on the source chain. The destination chain verifies that proof against its light client’s stored consensus state before accepting it. IBC relaying is therefore permissionless and trust-minimized, by only needing to trust the counterparty’s IBC implementation and chain validators.
Through IBC channels, any token can be transferred. When a token is sent to a chain from which it doesn’t originate, it is escrowed (locked) on the source chain and a wrapped voucher is minted on the destination chain. When that voucher is later sent back over the same channel, it is burnt on the destination side and the original token is unlocked on the source side. This accounting is per-channel: Each channel has its own escrow account, and a voucher carries a denomination path tied to the channel it came through, so it can only be redeemed over that same channel. This isolation is what keeps channels from interfering with each other. This isolation and correct handling of denomination paths must be enforced by all IBC implementations.
Opening IBC connections is permissionless. Anyone can stand up a new chain A, create a light client of a target chain B, open a connection and a channel, and begin transferring assets they hold. This is by design and harmless if IBC is implemented correctly: Tokens bridged in over a new channel receive a fresh, distinct denomination and have their own escrow, so they are not fungible with assets that arrive over other channels.
The Secret Network (chain id secret-4) connects to Axelar mainnet (chain id axelar-dojo-1) over the IBC channel axelar-dojo-1 transfer/channel-69 ↔ secret-4 wasm.secret1yxjmepvyl2c25vnt53cr2dpn8amknwausxee83/channel-61, allowing both plain token transfers as well as token transfers carrying GMP messages to pass between these two networks. On the Secret Network side of this IBC connection, a smart contract handles receiving IBC messages and mints “Secret Axelar Wrapped Tokens” (saTokens such as saUSDC, saWBTC, and saDAI), wrapping the bridged Axelar assets in Secret’s privacy-preserving SNIP-20 token standard, so that the resulting balances are encrypted on-chain.
Visualization of the Token Flow
Detailed Root-Cause Analysis
The root cause was a modified CW20-ICS20 IBC-enabled smart contract (github.com/scrtlabs/ics20-for-axelar, with Code ID 2446, deployed at secret1yxjmepvyl2c25vnt53cr2dpn8amknwausxee83): It minted vouchers without verifying the source of the inbound transfer. The deployed contract is a fork of Secret’s SNIP-20 ICS-20 implementation (github.com/scrtlabs/snip20-ics20), modified for the Axelar↔Secret IBC connection (github.com/scrtlabs/ics20-for-axelar).
The defect lived in do_ibc_packet_receive, where the two checks that bind a voucher to the channel it arrived on were commented out:
parse_voucher_denom(&msg.denom, &packet.src)(ibc.rs#L231), which would have validated the denom’s<port>/<channel>trace against the packet’s actual source.reduce_channel_balance(...)(ibc.rs#L234), which would have capped any release at the amount that channel had genuinely escrowed.
What remained rejected denoms containing a / and minted any bare denom present on the allow-list. Because that allow-list was keyed by denom name rather than by channel, a uusdt arriving on the attacker’s channel-227 was indistinguishable from one arriving on Axelar’s legitimate channel-69: Both minted the same saUSDT, but only the latter had anything escrowed behind it. Because opening an IBC channel is permissionless, the attacker was free to connect a chain under their own control, running a single validator, and self-relay forged deposits into it.
The attack followed directly from this: The attacker spun up a fake chain, opened an IBC channel to Secret, and sent over bare denoms (carrying no source-channel path prefix) whose names matched the allow-list. The contract minted genuine saTokens with nothing backing them. Each forged deposit was sized to the saToken’s entire circulating supply, so redeeming the minted balances back over the Axelar channel released exactly the backing held in the IBC escrow on the Axelar side.
The vulnerability was not introduced by a recent change. It was present in the codebase from the repo’s initial commit on January 15, 2023, and deployed on Secret mainnet on March 30, 2023 (with Code ID 872), uploaded by secret1mryld3gd05c7gtm36hfq64emdv54djz7rcmfva, with codehash 2976a2577999168b89021ecb2e09c121737696f71c4342f9a922ce8654e98662, and instantiated the same day with contract address secret1yxjmepvyl2c25vnt53cr2dpn8amknwausxee83.
On March 5, 2026 (Secret block 24186627, MsgMigrateContract A049548A...ACADB8B5), the migration admin secret1lrnpnp6ltfxwuhjeaz97htnajh096q7y72rp5d migrated the contract to Code ID 2446, uploaded by secret1f2jrcqsx7glyta39c6tum2lhk5kh2a0ty6r9ms, codehash ba26d9bcba2901300a53343b7aa9e71095afa149ae18ee1772a64d1a7e5add2f. This migration changed the deployed bytecode (the add-migration-message branch added a memo field and the migrate message itself) but carried the same missing source verification forward. The June 10 exploit struck this migrated code, Code ID 2446 (HEAD b206182, “fix reproducible build”, January 27, 2026).
Impact
Approximately $4.67M worth of tokens were minted without backing, across saUSDT, saUSDC, saDAI, saWETH, saWBTC, saWBNB, and sawstETH, and then redeemed back over the legitimate channel to Axelar. The drained balance represented the real assets that users had bridged into Secret over this connection. With the Axelar IBC escrow account for channel-69 now effectively empty, those assets can no longer be bridged out of Secret. The impact is confined to the seven saTokens on this connection. No other Axelar chains, assets, IBC channels, or escrow accounts were affected.
Incident Response & Remediation
We detected the incident through our firewalling functionality, which prevented contamination to other chains. The Axelar Emergency Committee disabled the Secret↔Axelar connection. Squid disabled Secret on its frontend. The Secret team was notified to halt and migrate the affected bridge contract.
Exploit Timeline
All times are UTC.
June 10, 2026
- 06:50-18:54: The attacker account
secret154pdez8zazqh26wyuyu70nqraal3hkvf2f03vmrehearses the setup, creates 4 light clients and channels, moves small amounts of native funds, and makes preparatory calls.- CreateClient
07-tendermint-241: A6326EAE…AC5996C5 - ConnOpenInit
connection-238: 73FF0C24…FFE9DF9C - UpdateClient: 6267FDB7…FDAB5A85
- ConnOpenAck: E0F807E4…1C4E463A
- ChannelOpenInit
channel-224: BB412D82…4A572510 - ChannelOpenInit
channel-225: CED2A347…48A0C82C - ChannelOpenAck: 945BFA28…0F563987
- Native transfer of 180 SCRT from Secret via Osmosis to Axelar, potentially testing the exit route: 8EA1152D…200F9033
- CreateClient
- 19:01: Attacker creates the live client
07-tendermint-244for the fake chainibc-dev-allowlist-denom-1. This is represented by the red “Attacker chain” in the above visualization. - 19:02-19:05: Attacker opens
connection-241andchannel-227to the bridge contract on Secretsecret1yxjmepvyl2c25vnt53cr2dpn8amknwausxee83. This connection is represented by the red line between the attacker chain and Secret in the visualization.- ConnOpenInit: 20C707CD…B284AFA9
- ConnOpenAck: 76503664…EF472B76
- ChannelOpenInit
channel-227: D404B1BF…14D4970E - ChannelOpenAck: C0D8B4B4…944293DD
- 19:14-19:20: Attacker self-relays 7 forged deposits from a fake sender address
cosmos1lquy33kxj7ywgzwvc662sqv4y0hyns4a8na5x2onibc-dev-allowlist-denom-1overchannel-227to the bridge contract that mints the current total locked values for 7 saTokens (saUSDT, saUSDC, saDAI, saWETH, saWBTC, saWBNB, sawstETH). This is step 1 in the visualization.- 1,121,270 USDT (≈$1,121k) in E17184D4…C2B1BFA1
- 638,198 USDC (≈$638k) in DC4529F3…4039BB42
- 893 WETH (≈$1,537k) in 4A0CC868…4C9283A0
- 16 WBTC (≈$1,065k) in AE894827…3C4BC351
- 174,536 DAI (≈$174k) in 46AEC0D5…3ADB2DFB
- 170 WBNB (≈$102k) in 872D4FFC…85ED1313
- 17 wstETH (≈$38k) in 44F1987A…A797640E
- The attacker then unwraps the stolen funds as outlined in the next section.
Fund Flow of Stolen Assets
Once the saTokens had been redeemed, the attacker unescrowed the real tokens from the IBC escrow account on Axelar, moved everything to Ethereum, converted it all to ETH, and split it across multiple wallets, eventually depositing the funds into exchanges. It happened in six steps, all on June 10, 2026:
- Withdraw the stolen assets from Secret to Axelar via IBC.
- Bridge the tokens off Axelar and over to Ethereum.
- Consolidate tokens in a single Ethereum wallet.
- Sell every token for ETH.
- Break the ETH into ≈30 smaller transfers to fresh wallets.
- Deposit to exchanges.
The attacker’s destination wallet on Ethereum is 0x6c2eAB82bA2897A6E99FB6Af018020dA15123976. The attacker’s Axelar address is axelar1hzra9z4zn8q0w8f3dj2wnw0xgetu8dfdhl6ad8.
Step 1: Withdraw the stolen assets from Secret to Axelar
Timeline: 19:33-19:36
The attacker withdraws the newly minted tokens over channel-61 to Axelar axelar1hzra9z4zn8q0w8f3dj2wnw0xgetu8dfdhl6ad8, and Axelar’s channel-69 escrow account unlocks the equivalent amount of locked tokens. This is step 2 in the visualization.
- 1,121,270 USDT in E17184D4…C2B1BFA1
- 638,198 USDC in 842CCB75…AB5B3A18
- 893 WETH in 45C2E285…7E857B64
- 16 WBTC in D0BF6C27…F702C3C2
- 174,536 DAI in DD2993CC…0CCF46FE
- 170 WBNB in 22ABA9D6…4ABBBFCF
- 17 wstETH in 86B7F032…CDF7D05F
Step 2: Off Axelar, on the way to Ethereum
Timeline: 19:38-19:56
The attacker moved the tokens off Axelar to Osmosis through 18 IBC transfers, sent in 3 batches between 19:38 and 19:56 UTC. Each transfer carried a packet-forwarding memo (the Skip/PFM convention), instructing each chain along the route to immediately forward the tokens to the next hop rather than hold them, so the whole multi-hop path executed automatically without manual relaying. Batches 1 and 2 each contained one transfer for each of the 7 stolen assets; batch 3 contained 4 retries (WETH, USDT, DAI, wstETH). WBNB, WBTC, and USDC were not retried in batch 3, leaving the residual amounts that appear in the leftover paragraph below.
| # | Time (UTC) | Asset | Amount | Axelar Transaction Hash |
|---|---|---|---|---|
| 1 | 19:38:42 | WETH | 223.367 | C6F4F590…4F9EB0FB |
| 2 | 19:43:07 | USDT | 280,317 | AB74CDDE…487EF291 |
| 3 | 19:43:24 | WBTC | 4.134 | D0EF0311…2FF13AFF |
| 4 | 19:43:42 | USDC | 159,549 | BE8F0F13…89E7BF36 |
| 5 | 19:44:00 | DAI | 43,634 | B0DF7FE2…37DE588C |
| 6 | 19:44:17 | WBNB | 42.691 | 7EA8439F…9D9AE008 |
| 7 | 19:44:36 | wstETH | 4.414 | 8B384A98…49D18BED |
| 8 | 19:49:43 | WETH | 335.051 | 4D92E886…1CD0E49C |
| 9 | 19:49:59 | USDT | 420,476 | 86767670…A54EC5CA |
| 10 | 19:50:16 | WBTC | 6.201 | D34C6714…613BC80E |
| 11 | 19:50:36 | USDC | 239,324 | 7E56A115…853EC383 |
| 12 | 19:50:53 | DAI | 65,451 | 8FB1F586…1A17C41C |
| 13 | 19:51:10 | WBNB | 64.037 | 53382B68…26868229 |
| 14 | 19:51:26 | wstETH | 6.621 | 998EEDA7…68FB80FC |
| 15 | 19:55:31 | WETH | 335.051 | 5F3B02D6…4C7370C0 |
| 16 | 19:55:46 | USDT | 420,476 | 56CA93E0…001188E3 |
| 17 | 19:56:02 | DAI | 65,451 | E65C67D6…D224D8BC |
| 18 | 19:56:19 | wstETH | 6.621 | 55BA01EF…A4B7C6E7 |
From Osmosis, the funds reached Ethereum via two routes:
- Non-USDC tokens: Axelar → Osmosis → back to Axelar → Ethereum. The round-trip through Osmosis is consistent with an automated cross-chain router (the transfers carry Skip-style forwarding memos): Osmosis provides the swap liquidity and Axelar is used to transfer the tokens to Ethereum.
- USDC: Swapped on Osmosis, sent over IBC to Noble, and bridged to Ethereum through Circle’s native USDC bridge (CCTP).
Step 3: Funds land in one Ethereum wallet
Timeline: ≈20:00
Every bridged transfer converged on a single Ethereum wallet, 0x6c2eAb82Ba2897A6e99fB6aF018020da15123976. The tokens routed through Axelar were bridged via Axelar’s GMP Gateway, while new USDC was minted via CCTP. The amounts that arrived are listed below:
| Asset | Amount Arrived | Bridge | Transaction Hash |
|---|---|---|---|
| WETH | 893.47 (223.367 + 335.050 + 335.050) | Axelar GMP | 0x3513509c...98ca8e05, 0xc5cff113...004d0d5e, 0xe40f58d2...2cc7133e |
| USDT | 1,121,267 (280,316.74 + 420,475.34 + 420,475.32) | Axelar GMP | 0x5abb7ce7...1e899025, 0xae231983...1b0f6225, 0x20d9cfdc...1cd298bb |
| DAI | 174,533.8 (43,633.36 + 65,450.19 + 65,450.22) | Axelar GMP | 0xde42dd55...59d73343, 0x2cbd4a70...f34ae2e6, 0x78028bf1...98d0396d |
| WBTC | 10.335 (4.134 + 6.201) | Axelar GMP | 0xe49353d3...efdf8967, 0x5b111208...9606bf0b |
| wstETH | 17.654 (4.414 + 6.620 + 6.620) | Axelar GMP | 0xbc3bde34...e05661da, 0xf05277c5...179c7593, 0x08db0a3c...97a2ee3f |
| USDC | 398,873 (239,323.54 via ITS + 159,549.57 via CCTP) | Axelar GMP + CCTP | Axelar GMP: 0xd0fc7d56...ee8b2317; CCTP mint: 0x7039eba2...cd582cff |
Not all of the assets were bridged out of Axelar. Roughly 6.2 WBTC, 239k USDC, 64 WBNB, and 248 AXL were left in the attacker’s Axelar wallet. These stranded amounts match the leftover funds we identified in the escrow during the investigation.
Step 4: Swap funds to ETH
Timeline: 20:04-20:15
From the destination wallet the attacker swapped each token for WETH on CoW Protocol, then unwrapped the WETH into native ETH:
| Token Sold | Amount | Transaction Hash |
|---|---|---|
| USDT | 1,121,267.40 | 0x388b57dc...f3512d5d |
| USDC | 398,873.11 | 0xdf2b389b...51e46f22 |
| DAI | 174,533.78 | 0x16b309fb...486de1d1 |
| WBTC | 10.335 | 0xfdc4a8a2...b82a8c37 |
| wstETH | 17.654 | 0xd3ac3c2e...418ccdfd |
The swaps returned about 1,456 WETH (689.22 + 392.04 + 245.23 + 107.24 + 21.83). Added to the 893 WETH that had bridged in as WETH already, and unwrapped to native ETH (for example 0x3984f61c...b801d84b and 0xa3b05a11...72c236ff), this left roughly 2,350 ETH (≈$4M) sitting in this Ethereum wallet.
Step 5: Split ETH across many wallets
Timeline: 20:51 the same day to 09:19 the next
The attacker then split the ≈2,350 ETH into about 30 transfers of 50-139 ETH each, sending them to fresh wallets. A few of the transfers:
| Time (UTC) | Amount | To | Transaction Hash |
|---|---|---|---|
| 2026-06-11 09:19 | 139 ETH | 0x737cF06B...B364EBC8 |
0xc311d3f1...b685ec84 |
| 2026-06-11 09:10 | 100 ETH | 0xD3ef2D45...b0F5d807 |
0xcdb8c7f0...724058ad |
| 2026-06-10 22:33 | 110 ETH | 0xf039D57e...C0967882 |
0x4cf74260...72528a05 |
| 2026-06-10 22:32 | 100 ETH | 0xd73a2182...7d201290 |
0x8803e97a...17e217e4 |
| 2026-06-10 20:51 | 50 ETH | 0xf4F50ed6...2e1b5897 |
0xa206fa2d...cb702183 |
About 25 more followed in the 50-100 ETH range over the same window.
Step 6: Deposit ETH to exchanges
The attacker moved ETH into KuCoin (1,199 ETH), ChangeNow (1,050 ETH), and HitBTC (100 ETH):
From those 30 wallets the funds moved on to three exchanges, usually in a single hop, sometimes through one or two intermediate wallets first.
KuCoin (1,199 ETH across 15 deposits to 0x45300136...d992b785):
| Amount | From | Deposit Transaction Hash |
|---|---|---|
| 50 ETH | 0x1eafd8c5...533f19c8 |
0xd0fb3e4a...92963ae5 |
| 50 ETH | 0xf32a06aa...66f1b886 |
0x011b764c...cf108ae7 |
| 70 ETH | 0xc3e2f603...47fa848b |
0xb7caefae...85b4f4fb |
| 60 ETH | 0x517e0c32...86804c28 |
0x482c1d2e...d771c64e |
| 50 ETH | 0xd49b8443...edebad94 |
0xcc396950...6932cf8e |
| 80 ETH | 0xe5db1200...dfa3abdd |
0xa1990282...99420e93 |
| 100 ETH | 0xf7bbb4fb...d43bc3b9 |
0x5013bc93...7dcd7a45 |
| 90 ETH | 0xb6156c50...8de95a70 |
0xf195881d...e99afc73 |
| 100 ETH | 0x0187f9b4...b060f725 |
0xa813149e...1e5576e8 |
| 100 ETH | 0xd73a2182...7d201290 |
0x12f54f02...0e4acc3d |
| 110 ETH | 0xf039d57e...c0967882 |
0x9255c0c9...c010afee |
| 50 ETH | 0xe5130ade...a6d37b5e |
0xdc35dad7...f08cf62a |
| 50 ETH | 0xb04331ae...e4eb7c11 |
0xdc35dad7...f08cf62a |
| 100 ETH | 0xd3ef2d45...b0f5d807 |
0xadda1323...713f6519 |
| 139 ETH | 0x737cf06b...b364ebc8 |
0xc66dd132...4c07961a |
ChangeNow (1,050 ETH across 13 deposits to 0xEbA88149...DE94cB1):
| Amount | From | Deposit Transaction Hash |
|---|---|---|
| 50 ETH | 0xf4f50ed6...2e1b5897 |
0x3e75cf1e...735a249d |
| 100 ETH | 0x67e25c71...5be0b46c |
0xccaa3656...5759319a |
| 80 ETH | 0xdacf61aa...3687d36c |
0xd9fbc4fa...67df9519 |
| 60 ETH | 0xc12fd863...7ddc5b0e |
0xc51a6a46...6cacfbb1 |
| 80 ETH | 0x4de57c31...f80cb13b |
0x550c558c...0fcd7400 |
| 90 ETH | 0x40b42df7...b29b53cb |
0xe2134546...440b4c40 |
| 60 ETH | 0xd8df7fd5...5fc0bc52 |
0xebd687f7...daecc0bb |
| 80 ETH | 0x2c08e99e...54a4c56c |
0x962ece0f...8bb82f46 |
| 90 ETH | 0x67f0eb70...b2889ce0 |
0x93542413...a3025df4 |
| 90 ETH | 0x8364b1bf...51825aa3 |
0x3615606d...4dad1098 |
| 90 ETH | 0x5d88861e...2a2f2c7a |
0x86dbce29...ec261df6 |
| 90 ETH | 0x74a4a5c0...ab4a4219 |
0x920690b1...861802ee |
| 90 ETH | 0x528beb3c...0c868a88 |
0x20f37c2a...1d8c303e |
HitBTC (100 ETH across 2 deposits to 0x80787af1...d7Ee1A):
| Amount | From | Deposit Transaction Hash |
|---|---|---|
| 50 ETH | 0xc466504f...feaccfa5 |
0x4a2319a8...246db50d |
| 50 ETH | 0x8ec5569b...1861f056 |
0x0be415c8...b4a3ff8c |
Discovery Timeline
June 17, 2026
- 15:39: Team identified a failed cross-chain transaction on Axelar: 6B6A8951…CD75CF39. This IBC transfer failed on Axelar with error:
unable to unescrow tokens, this may be caused by a malicious counterparty module or a bug: please open an issue on counterparty module: spendable balance 803200wbtc-satoshi is smaller than 1000000wbtc-satoshi: insufficient funds, indicating that more tokens are trying to be bridged out of Secret than have been bridged into Secret. - 16:00: Team discovered that the IBC escrow account on Axelar for the
secret-snipconnection had been drained. - 16:48: Team discovered that nearly all funds had been withdrawn from Secret to Axelar in 7 single IBC transactions on June 10, 2026, indicating an attack. Further analysis traced the funds, showing that most had been moved out of Axelar to Ethereum via Osmosis, where they had in part been deposited in KuCoin.
- 18:28: We first notified the Secret team about the incident in a shared Telegram channel.
- 18:30: The Secret team responded.
- 18:40: We notified Squid to disable Secret from their frontend.
- 18:52: Analysis uncovered the outline of the attack via the attacker-controlled, connected chain, which was passed on to the Secret team.
- 19:21: The Secret team began investigating on their end as well.
- 20:00: Squid confirmed Secret was removed from their frontend.
- 20:45: Axelar Emergency Committee paused the Secret↔Axelar connections.
Root Cause and Contributing Factors
Root cause: The bridge minted saTokens against a denom’s name without verifying the source channel of the inbound packet. The allow-list decided which denoms were mintable but never checked whether a transfer actually arrived over the channel holding the backing, so a forged deposit over an attacker-controlled channel minted genuine, unbacked saTokens. A mint must be authorized by the packet’s source channel, not the denom name alone.
Contributing factors:
- An unaudited fork. The deployed contract was a fork of github.com/scrtlabs/snip20-ics20 with its core security checks (
parse_voucher_denom,reduce_channel_balance) commented out. The fork changed the contract’s trust model, so the original audit no longer applied, yet it shipped to mainnet without re-audit. Forks that alter a contract’s trust model must be re-audited against the new invariants. - Permissionless channel opening. Opening an IBC channel into the minting contract required no permission, letting the attacker connect a chain under their own control and self-relay forged packets. A minting contract in this position cannot trust its counterparties and must enforce every invariant itself, or restrict who may open a channel. This was a necessary enabling condition for the attack.
Appendix A: Addresses and Identifiers
- Vulnerable contract:
secret1yxjmepvyl2c25vnt53cr2dpn8amknwausxee83; Code ID:2446; Source code GitHub repo: github.com/scrtlabs/ics20-for-axelar/tree/b2061822376a772da0e55bbf9436797b9d0356b7; Contract bytecode hash (data_hash):ba26d9bcba2901300a53343b7aa9e71095afa149ae18ee1772a64d1a7e5add2f; Instantiated on 2023-03-30, 13:00 UTC, block8152995, labelics20-1680181216721 - Original CW20-ICS20 contract deployer (Code ID
872):secret1mryld3gd05c7gtm36hfq64emdv54djz7rcmfva - Migrated CW20-ICS20 contract deployer (Code ID
2446):secret1f2jrcqsx7glyta39c6tum2lhk5kh2a0ty6r9ms; migration admin:secret1lrnpnp6ltfxwuhjeaz97htnajh096q7y72rp5d - Attacker wallet on Secret:
secret154pdez8zazqh26wyuyu70nqraal3hkvf2f03vm - Attacker wallet on Axelar:
axelar1hzra9z4zn8q0w8f3dj2wnw0xgetu8dfdhl6ad8 - Attacker wallet on Osmosis:
osmo1hzra9z4zn8q0w8f3dj2wnw0xgetu8dfdm2l9s5(same key as the Axelar address) - Attacker main wallet on Ethereum:
0x6c2eAB82bA2897A6E99FB6Af018020dA15123976 - Forged IBC message sender on fake chain:
cosmos1lquy33kxj7ywgzwvc662sqv4y0hyns4a8na5x2 - Fake chain ID:
ibc-dev-allowlist-denom-1 - Light clients:
07-tendermint-241&07-tendermint-244(+4 rehearsal clients) - Rogue channels on Secret:
channel-224,channel-225,channel-227 - Withdrawal channel (Secret to Axelar):
channel-61; Axelar escrow channel:channel-69
Appendix B: Reproducing the On-Chain Contracts (Code IDs 872 & 2446)
Both contracts were compiled on amd64 (x86_64) and optimized with wasm-opt -Oz. The Secret Network codehash is the sha256 of the uncompressed optimized .wasm.
Toolchain Summary
| Code 872 (2023 – Mar 2026) | Code 2446 (Mar 2026 – Present) | |
|---|---|---|
| Repo | scrtlabs/ics20-for-axelar |
scrtlabs/ics20-for-axelar |
| Branch / commit | master / 0546744 |
add-migration-message / b206182 |
| Rust | 1.64.0 (x86_64) | 1.86.0 (x86_64) |
wasm-opt -Oz (binaryen) |
~v91 (v90–97 all match) | v116 |
| Build | local make build |
Docker make build-reproducible (optimizer 1.0.13) |
Expected codehashes:
- Code 872:
2976a2577999168b89021ecb2e09c121737696f71c4342f9a922ce8654e98662 - Code 2446:
ba26d9bcba2901300a53343b7aa9e71095afa149ae18ee1772a64d1a7e5add2f
Building Code 2446
git clone https://github.com/scrtlabs/ics20-for-axelar
cd ics20-for-axelar && git checkout add-migration-message # commit b206182
cd contracts/cw20-ics20
make build-reproducible
Building Code 872
Code 872 was a local build, so its absolute paths are baked into the binary. For a byte-exact hash you must replicate them (/mnt/d/scrtlabs/ics20-for-axelar and CARGO_HOME=/home/tovi/.cargo).
rustup toolchain install 1.64.0-x86_64-unknown-linux-gnu --force-non-host
rustup target add --toolchain 1.64.0-x86_64-unknown-linux-gnu wasm32-unknown-unknown
# 1. lay out the source at the exact path the 2023 build used
sudo mkdir -p /mnt/d/scrtlabs/ics20-for-axelar && sudo chown -R "$(id -u)" /mnt/d/scrtlabs/ics20-for-axelar
git -C /path/to/ics20-for-axelar archive 0546744 | tar -x -C /mnt/d/scrtlabs/ics20-for-axelar
cd /mnt/d/scrtlabs/ics20-for-axelar
# 2. nest the cw-plus packages UNDER the contract, fix the contract's path deps
cp -a packages contracts/cw20-ics20/packages
sed -i 's#path = "\.\./\.\./packages/#path = "packages/#g' contracts/cw20-ics20/Cargo.toml
# 3. build standalone with x86_64 Rust 1.64.0 and the dev's CARGO_HOME
cd contracts/cw20-ics20
export CARGO_HOME=/home/tovi/.cargo
sudo mkdir -p /home/tovi/.cargo && sudo chown -R "$(id -u)" /home/tovi/.cargo
RUSTFLAGS='-C link-arg=-s' cargo +1.64.0-x86_64-unknown-linux-gnu build \
--release --target wasm32-unknown-unknown --locked
# 4. optimize with an old binaryen
wasm-opt -Oz target/wasm32-unknown-unknown/release/cw20_ics20.wasm -o out_872.wasm # binaryen v91 (v90-97 work)
sha256sum out_872.wasm
# => 2976a2577999168b89021ecb2e09c121737696f71c4342f9a922ce8654e98662
