Multi-chain
Arbitrary call

SwapNet's $13.4M Loss and How Olympix Would Have Prevented It

Loss
Loss
Exploit date
January 25, 2026

Date: January 25, 2026

Chain: Base, BNB Chain, Arbitrum

Loss: about $13.43M across 18 users, $13.34M of it from one wallet

Class: arbitrary external call that spent users' standing token approvals

At 19:23:25 UTC on January 25, 2026, 13,342,433 USDC landed in a single wallet on Base. The wallet belonged to a Matcha Meta user who, at some point earlier, had switched off Matcha's default One-Time Approval setting and given SwapNet's router an unlimited allowance to move their USDC. Twelve blocks later, 24 seconds by the block clock, a transaction from an address that had never touched that wallet pulled the same 13,342,433 USDC out through SwapNet's router and sent it to the attacker.

Over the next 45 minutes the attacker went looking for every other wallet that had approved SwapNet directly, and found 17 more on three chains before SwapNet paused its contracts. Matcha's post-mortem puts the total at $13.43M taken from 18 users. The router let anyone choose the address it called and the data it sent, so a swap request could be turned into a transferFrom on any token a user had approved, and the router, holding those approvals, carried it out.

A Router Built to Go Anywhere

SwapNet is a DEX aggregator. Given a trade, it searches many onchain liquidity sources, AMMs as well as private market makers, and stitches together the route that pays best. BlockSec's analysis notes one feature that set it apart: users could also point a swap at a custom router or pool of their own choosing. That flexibility is useful for traders who know where liquidity sits, and it means the router has to make calls to addresses it hasn't seen before.

Matcha Meta sits one layer above. It's a meta aggregator that compares quotes from several aggregators, SwapNet among them, and routes each trade to the best one. By default Matcha sends every swap through 0x's AllowanceHolder contract, which takes an approval for one trade and spends it immediately. Matcha's post-mortem says this default covers about 90% of its transactions. Users could switch it off and approve an aggregator's contracts directly, and they could choose an infinite approval instead of an exact one. Those users are the ones who lost money.

An approval is standing permission. When a wallet approves a contract for an unlimited amount of a token, the token contract will honor any transferFrom that contract makes against that wallet, today or a year from now, until the owner revokes it. A swap router that holds approvals from thousands of wallets is therefore sitting on permission to move all of their approved balances, and the only thing that limits what it does with that permission is its own code.

A Swap Step That Called a Token Contract

Each step in a SwapNet route makes a low-level call to the venue that's meant to fill it. According to BlockSec and Verichains, function 0x87395540, which handles the core routing logic, read the target of that call from the caller's input and never checked what kind of address it was. The design assumed the target would be a DEX router or a pool. Nothing in the contract enforced that assumption, so a caller could name a token contract instead, such as USDC, and the router would call it with whatever calldata the caller supplied.

Calldata is the encoded instruction a call carries: which function to run and with which arguments. When the target is a token contract and the calldata encodes transferFrom, the token sees a request coming from SwapNet's router, checks that the router has an allowance from the named wallet, and moves the funds. The token did exactly what an approval tells it to do. The router was the one that should have refused, because the person asking it to spend that allowance wasn't the wallet that granted it.

Verichains lists the two controls the router lacked: an allowlist of valid DEX router and pool addresses, with every other target rejected, and a restriction on which function selectors an external call may invoke. Either one would have stopped a swap step from becoming a call to a token's transferFrom.

The lesson in this case is plain. A contract that holds other people's approvals and also makes calls built from user input has to treat the call target and the calldata as hostile, because the approvals give any call it makes the power of a signature from every one of those users.

Twenty-Four Seconds on Base

SwapNet's contracts are closed source and unverified on block explorers. BlockSec notes that, decompiled, they expand into thousands of lines of code, and its researchers reconstructed the flaw from bytecode. Without verified source there's no vulnerable code to quote here, so the walk-through below uses the on-chain record and the published analyses.

At 19:23:25 UTC in Base block 41289829, an account abstraction transaction moved 13,342,433.169229 USDC from a smart account into wallet 0xba15...78eD, along with about 80 MORPHO. Matcha's post-mortem names that transaction as the point where the exposure occurred, and the wallet's existing approval to SwapNet's router was what made the balance reachable. Matcha says it's likely the attacker had been waiting for a balance this large to become exposed.

At 19:23:49 UTC in block 41289841, wallet 0x6cAa...833e sent a contract-creation transaction. The contract it deployed, 0xcCE2...225b, did all of its work in its constructor, the code that runs once when a contract is created. It called SwapNet's router at 0x6160...757e, a proxy that forwards execution to an implementation at 0xdC39...6D7D. The router, following the route it was handed, made its low-level call to the USDC contract, and the USDC contract moved 13,342,433.169249 USDC from 0xba15...78eD straight to the attacker's wallet. The deployed contract never held the funds, and the victim never signed anything in either block.

BlockSec describes the same pattern in its walk-through of that transaction: an internal variable set to the USDC address so the intended routing logic was skipped, then a low-level call carrying attacker-written calldata that invoked USDC's transferFrom and drained the approved balance.

Forty-Five Minutes Across Three Chains

After the first drain, according to Matcha, the attacker began emptying as many exposed wallets as possible. SwapNet paused its Base contracts about 45 minutes after the initial exploit and paused the other chains shortly after. In that window, 17 more users lost funds across three chains. BlockSec lists the affected chains as Base, BNB Chain and Arbitrum. Matcha originally counted 20 affected users and revised the figure to 18 on January 29.

The headline number moved too. The Block reported that CertiK first estimated about $13.3M, while PeckShield put early losses at up to $16.8M. Matcha says the larger figure folded in a separate and unrelated $3.4M incident at Aperture Finance, which was exploited the same day through a similar arbitrary-call flaw in its own closed-source contracts. BlockSec analyzed the two together and estimated SwapNet's share at about $13.41M. Matcha's confirmed total is $13.43M, with one user accounting for about $13.34M of it.

Matcha and 0x both said their own contracts weren't involved. Matcha's post-mortem states that the AllowanceHolder and Settler contracts remained secure, and users who kept One-Time Approval on were never exposed, because their approvals were consumed in the same transaction as each trade and left nothing behind for an attacker to spend.

Following the USDC

USDC is a freezable stablecoin, and that made the hours after the drain matter. According to Forklog's report, PeckShield tracked the attacker swapping about 10.5M USDC for about 3,655 ETH on Base and then moving funds to Ethereum. ZachXBT said about 3M USDC was still sitting in the attacker's address roughly 10 hours after the exploit, and criticized Circle for not freezing it. Crypto.news reported that the balance stayed unfrozen for hours and didn't report any freeze.

The address kept trading. Blockscout shows it selling USDC for WETH through Uniswap's Universal Router on January 27, in lots of 50,000 and 200,000 USDC between 18:44 and 19:04 UTC, about 47 hours after the drain. As of October 23, 2026, Blockscout shows the address holding a fraction of a cent in USDC on Base.

Matcha removed SwapNet from its platform until further notice and removed the option to turn off One-Time Approval, so every trade now routes through AllowanceHolder. Its post-mortem says the team is working with security firms and industry partners to trace the funds, credits Blockaid with helping on detection, response, impact analysis and attribution, and doesn't report any recovery. Matcha also told users who had approved SwapNet directly to revoke those approvals on every chain, listing two router addresses, 0x6160...757e and 0x6160...8BB5. Revoke.cash carries the same warning and adds that unused exact-amount approvals should be revoked as well.

Why a Flexible Router Kept Its Flaw

Two design choices met in this exploit, and each one made sense on its own. SwapNet let users route through custom routers and pools, so its swap step had to accept call targets it didn't know in advance. Matcha let experienced users skip One-Time Approval and approve aggregators directly, so some wallets left large, standing allowances on SwapNet's router. Matcha's post-mortem is candid about the second choice and says it will no longer let customizability outweigh security.

The router's code was also closed. BlockSec points out that both SwapNet and Aperture shipped unverified contracts, so outside researchers could only study them by decompiling bytecode, and it argues that closed-source systems need especially strict constraints on external calls for that reason. Whatever review the contracts received, a flaw of this kind sits exactly where a flexible feature meets a large pool of standing approvals, and that interaction is easy to miss when the routing logic and the approval model are reviewed as separate concerns.

How Olympix Catches a Router That Calls Whatever It's Told

Olympix reads a contract the way an attacker does, as a set of states, transitions and entry points, and it works on the code a team actually ships, so a router whose source never reached a block explorer gets the same treatment as an open one. The state that matters in SwapNet's router is simple to name: the allowances thousands of wallets have granted it. Every entry point that ends in an external call made by the router is a path that can spend those allowances, and the engine maps all of them.

A contract's state space looks infinite, but the paths that can touch a given invariant are few. The engine narrows from every path, to the feasible ones, to those that reach the router's approvals, using signals to decide which to attack first. Olympix's static analysis surfaces the signal here directly: a low-level call whose target address and calldata both come from the caller's input, with no allowlist on the target and no restriction on the selector, inside a contract that holds token approvals from users. Olympix has flagged this class before in other codebases, and the pattern is precise enough that it stands out from the thousands of ordinary calls a router makes.

The invariant the router depends on fits in one sentence: the router may move a wallet's tokens only when that wallet is the one trading, and only into the trade it asked for. BugPoCer takes that rule and tries to break it. Running Counterexample-Guided Invariant Refutation, it works outward from the router as a whole, executes the flagged path with the call target and calldata left free, and searches for a run where a third party's token balance falls while that third party sends nothing. The run it finds is a caller with no approvals of its own submitting a route whose venue is a token contract, and the router calling that token with a transferFrom against another user's allowance.

That run comes back as a counterexample: the sequence of calls and the state change at each step, the minimal conditions that trigger it (a victim wallet with a standing approval to the router and a nonzero balance), and a generated Forge test that replays it against the router. That counterexample is the January 25 drain, produced in development instead of on Base. Because Olympix runs in development, at every merge and before release, it would have surfaced at the merge that added custom router and pool support, which is the change that turned the swap step's call target into user input. From then on, every later change to the routing logic would have been tested against the same rule.

Approvals Turn Every Call Into a Signature

SwapNet's router held standing permission over millions of dollars in user funds, and one swap step let a stranger decide how that permission was used. Any contract that both holds approvals and makes calls assembled from user input needs its call targets and selectors pinned down in code, and needs a test that proves a third party's balance can't move without that party taking part. To see how Olympix maps the external calls in your own contracts and hands back a runnable counterexample for any path that spends someone else's approval, book a technical walkthrough.

On-Chain References

Protocol: SwapNet DEX aggregator (via Matcha Meta)

Chain: Base (first and largest drain); BNB Chain and Arbitrum also affected

Date: January 25, 2026, 19:23:49 UTC (first exploit tx)

Loss: $13.43M across 18 users (Matcha); about $13.41M (BlockSec)

Attacker EOA: 0x6cAa...833e

Attack contract: 0xcCE2...225b

SwapNet router (proxy, unverified): 0x6160...757e

SwapNet router implementation: 0xdC39...6D7D

Second SwapNet router (per Matcha): 0x6160...8BB5

Largest victim wallet: 0xba15...78eD

Exposure tx (USDC moves into the victim wallet, 19:23:25 UTC): 0xdf81...a011

Exploit tx (13,342,433 USDC drained): 0xc15d...dd57

Block: 41289841

USDC (Base): 0x8335...2913

Sources

Matcha Meta: SwapNet Incident Post Mortem

BlockSec: $17M Closed-Source Smart Contract Exploit, Arbitrary Call in SwapNet and Aperture

Verichains: SwapNet Exploit Analysis

The Block: Matcha Meta Users Hit in $13.4 Million SwapNet Contract Exploit

Forklog: ZachXBT Criticizes Circle's Inaction Following SwapNet Hack

Revoke.cash: SwapNet Hack

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.