DEV Community

ohmygod
ohmygod

Posted on

The Approval Paradox: How SwapNet's $13.4M Arbitrary Call Exploit Exposes DeFi's Infinite Allowance Time Bomb

TL;DR

On January 25, 2026, an attacker exploited an arbitrary call vulnerability in SwapNet's aggregator contracts to drain $13.4 million from a single user on Base. The exploit weaponized persistent ERC-20 token approvals — turning a convenience feature into a loaded gun. This article dissects the vulnerability, explains why infinite approvals remain DeFi's most underestimated attack surface, and provides a defense playbook for both developers and users.


The Attack: 45 Minutes to $13.4 Million

SwapNet operated as a DEX aggregator, routing trades through optimal liquidity paths. Its contracts needed to move tokens on behalf of users, which meant users had to approve SwapNet's contracts to spend their tokens.

The critical flaw was an arbitrary external call vulnerability — insufficient input validation in SwapNet's routing logic allowed an attacker to:

  1. Craft malicious call parameters that bypassed the intended routing logic
  2. Hijack the contract's low-level call() function to invoke arbitrary targets
  3. Call transferFrom() on ERC-20 contracts using the SwapNet contract as msg.sender
  4. Drain tokens from any user who had granted SwapNet a persistent allowance

The attacker walked away with approximately 10.5 million USDC, swapped it for ~3,655 ETH on Base, and began bridging to Ethereum mainnet. SwapNet paused contracts 45 minutes later — but the damage was done.

Why Only 20 Users Were Affected

Here's the twist: Matcha Meta (the DEX frontend using SwapNet) had a "One-Time Approval" feature enabled by default. This routed approvals through 0x's AllowanceHolder contract, which automatically revoked allowances after each trade.

The 20 victims had manually disabled this safety feature, granting direct, persistent (often infinite) allowances to SwapNet's contracts. One user alone lost $13.34 million.


Anatomy of an Arbitrary Call Vulnerability

Arbitrary call bugs are among the most dangerous smart contract vulnerabilities because they let an attacker impersonate the vulnerable contract. Here's the pattern:

// DANGEROUS: Arbitrary call with user-controlled target and data
function executeRoute(
    address target,
    bytes calldata data,
    uint256 value
) external {
    // Missing: validation that 'target' is a whitelisted DEX
    // Missing: validation that 'data' doesn't call transferFrom/approve
    (bool success, ) = target.call{value: value}(data);
    require(success, "Route execution failed");
}
Enter fullscreen mode Exit fullscreen mode

When a contract holds token approvals from users, an arbitrary call effectively gives the attacker proxy access to every approved token balance.

The Kill Chain

1. Identify contract with arbitrary call vulnerability
2. Find users who granted persistent allowances to that contract  
3. Craft calldata: target = USDC contract, data = transferFrom(victim, attacker, balance)
4. Execute through the vulnerable contract (which has the allowance)
5. Contract's address is msg.sender → transferFrom succeeds
6. Repeat for every approved user/token pair
Enter fullscreen mode Exit fullscreen mode

The Infinite Allowance Problem

The SwapNet exploit is a symptom of a deeper disease. Consider these numbers:

  • Aperture Finance lost $3.7M in the same week to the same vulnerability class
  • An estimated $2.7 billion in token approvals are outstanding to unaudited contracts on Ethereum alone
  • The average DeFi user has 47 active approvals across protocols they no longer use

Why Infinite Approvals Exist

Every ERC-20 interaction requires an approve() transaction before the actual operation. To save users gas fees and friction, protocols request type(uint256).max approval — effectively saying "you can spend all my tokens, forever."

// The standard "infinite approval" pattern
token.approve(spenderContract, type(uint256).max);
Enter fullscreen mode Exit fullscreen mode

This made sense when gas was $50 per transaction. It makes less sense when:

  • The approved contract has an unverified, closed-source implementation
  • The approval persists after you stop using the protocol
  • A single vulnerability in the approved contract compromises every approver

Historical Toll

Year Exploit Root Cause Loss
2023 Multichain Compromised keys + persistent approvals $126M
2024 Socket Gateway Arbitrary call + infinite approvals $3.3M
2025 1inch Resolver Arbitrary call + stale approvals $5M
2026 SwapNet Arbitrary call + disabled safety features $13.4M

The pattern is clear: persistent approvals transform any contract vulnerability into a user fund vulnerability.


Defense Playbook

For Smart Contract Developers

1. Whitelist call targets

mapping(address => bool) public whitelistedTargets;

function executeRoute(
    address target,
    bytes calldata data,
    uint256 value
) external {
    require(whitelistedTargets[target], "Target not whitelisted");
    // Additional: block calls to ERC-20 approve/transferFrom selectors
    bytes4 selector = bytes4(data[:4]);
    require(
        selector != IERC20.approve.selector &&
        selector != IERC20.transferFrom.selector &&
        selector != IERC20.transfer.selector,
        "Forbidden selector"
    );
    (bool success, ) = target.call{value: value}(data);
    require(success);
}
Enter fullscreen mode Exit fullscreen mode

2. Implement per-transaction allowances

Follow the Permit2 or AllowanceHolder pattern:

// User approves Permit2 once
// Each protocol interaction uses a signed permit with:
// - Specific amount
// - Specific spender  
// - Expiration timestamp
// - Single-use nonce
Enter fullscreen mode Exit fullscreen mode

3. Never hold persistent approvals in routing contracts

Aggregator contracts should use pull-based patterns where tokens are transferred to an intermediary, processed, and the intermediary is emptied in the same transaction.

For DeFi Users

1. Audit your approvals regularly

2. Prefer limited approvals

  • Approve only the exact amount needed for each transaction
  • Use wallets that default to exact-amount approvals (Rabby, for example)

3. Never disable safety features

  • If a frontend offers one-time approvals, keep them on
  • The gas savings from infinite approval are not worth the tail risk

4. Separate hot wallets

  • Never keep $13M in a wallet with active DeFi approvals
  • Use a dedicated trading wallet with limited funds
  • Transfer profits to a cold wallet with zero approvals

The Bigger Picture: ERC-20 Approvals Need a Rethink

The approval model was designed in 2015 for a simpler DeFi landscape. Today's composable, multi-protocol interactions create approval chains where:

  • User approves Aggregator A
  • Aggregator A approves Router B
  • Router B approves Pool C
  • A vulnerability anywhere in this chain can drain user funds

EIP-2612 (Permit) and Uniswap's Permit2 are steps in the right direction, enabling gasless, expiring, single-use approvals. But adoption is uneven — most protocols still default to type(uint256).max.

The SwapNet exploit should be a wake-up call: every infinite approval is a contingent liability. The question isn't whether the approved contract will be exploited — it's when.


Key Takeaways

  1. Arbitrary call vulnerabilities + persistent approvals = catastrophic loss — SwapNet proved this again
  2. One-time approvals saved the majority of users — Matcha Meta's default saved all but 20 users
  3. Audit your approvals today — if you've been in DeFi for more than a year, you probably have dozens of stale approvals
  4. Developers: whitelist targets AND block sensitive selectors — either alone is insufficient
  5. The $13M single-user loss was entirely preventable — wallet hygiene and approval management would have eliminated the risk

This article is part of the DeFi Security Deep Dives series. Follow for weekly analysis of real-world exploits and actionable security guidance.

Disclaimer: This analysis is for educational purposes only. The author has no affiliation with any mentioned protocols.

Top comments (0)