Clawditor
← all research
post-mortemhigh

NEAR Intents: Credit-on-Attempt Deposit Bug Enables $3.87M Phantom Balance Drain

Clawditor Research·Published Oct 16, 2026·Incident Oct 1, 2026
NEAR Intents

A BNB Chain vault contract in the NEAR Intents bridge incremented user balances on deposit attempt but never decremented them on revert, letting an attacker accumulate $3.87M in phantom USDT balances via 23 failed transactions before draining the vault; all funds were subsequently returned.

Root Cause

NEAR Intents' BNB Chain vault contract tracked user balances but contained a credit-on-attempt, no-debit-on-failure accounting bug: it incremented a user's internal balance when a deposit was initiated, but did not decrement that balance when the transaction failed and tokens were returned to the sender.

Each failed deposit left an inflated "phantom" credit that could be withdrawn against real vault funds.

// Simplified vulnerable pattern
function deposit(uint256 amount) external {
    // Attempt the transfer — may revert internally
    try token.transferFrom(msg.sender, address(this), amount) {
        balances[msg.sender] += amount;  // success path
    } catch {
        // BUG: tokens returned to sender but no balance update here
        // In the actual bug, balance was incremented BEFORE the try,
        // or the catch path did not subtract — exact form unconfirmed
        balances[msg.sender] += amount;  // phantom credit survives
    }
}

function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount);
    balances[msg.sender] -= amount;
    token.transfer(msg.sender, amount);  // real funds drained
}

Note: The postmortem describes the semantic effect; the precise Solidity pattern is reconstructed from the published description.

The October 16 postmortem also revealed a monitoring gap: withdrawals continued for nearly 40 hours before the protocol detected the drain, and Omni (the cross-chain messaging layer) was initially — incorrectly — blamed.

Attack Steps

StepActionPhantom Balance
FundingAttacker wallet loaded with only 10 USDT$10 seed
1–23Submit deposit; trigger revert condition; tokens returned+X USDT phantom per attempt
24Call withdraw() with accumulated phantom balanceDrain $3.87M USDT

The attacker executed 23 failed deposits between September 29 and October 1 before draining.

Impact

  • Initial Drain: ~$3.87M USDT (BNB Chain vault)
  • Scope: BNB Chain USDT vault only; NEAR Protocol, NEAR token, and other chains unaffected
  • Date: October 1, 2026
  • Recovery: Attacker returned all funds within 24–36 hours, ahead of a 48-hour return deadline
  • Net Loss: $0 (full recovery)
  • Monitoring Gap: ~40 hours between exploit start and detection
  • Fix: Contract patched within ~1 hour of detection; automatic balance-mismatch pausing added

Lessons for Auditors

  1. Balance updates must be atomic with token transfers. Always apply the Checks-Effects-Interactions (CEI) pattern: update state after confirming the transfer succeeded, or revert the entire call on failure. Never increment a balance in a catch block or before confirming receipt.

  2. Inspect all try/catch blocks that wrap asset movements. A catch {} that silently swallows a failed transfer while keeping a balance credit is a critical accounting bug. Grep for try.*transferFrom, try.*transfer, and similar patterns; verify the catch reverts or correctly reverses any preceding state change.

  3. Cross-chain vaults need real-time invariant monitoring. Alert when sum(balances[]) > token.balanceOf(vault). A 40-hour detection gap is far too long for a live financial contract.

  4. Low-cost exploits require low-seed capital. 10 USDT was sufficient to steal $3.87M. Protocols should fuzz-test deposit/withdraw accounting with revert scenarios at minimal balances.

attack patterns
erc20bridgesdefi-staking
sources