Calculated from recorded token losses using historical USD prices at the incident time.
0xa618933a0e0ffd0b9f4f0835cc94e523d0941032821692c01aa96cd6f80fc3fd0x7f12d13b34f5f4f0a9449c16bcd42f0da47af200Base0x24605e0bb933f6ec96e6bbbcea0be8cc880f6e6fBase0x67ab0e84c7f9e399a67037f94a08e5c664dc1c66BaseIn Base block 14952783, an unprivileged attacker used a single transaction, 0xa618933a0e0ffd0b9f4f0835cc94e523d0941032821692c01aa96cd6f80fc3fd, to drain WETH liquidity from the public NORMIE/WETH markets. The attack used public flash liquidity and normal AMM entrypoints to mark an attacker-controlled helper as a hidden premarket_user, mint unbacked NORMIE into the token contract during pair-to-helper transfers, and repeatedly trigger swapAndLiquify so the token contract sold those phantom tokens for ETH. The resulting price collapse let the attacker repay the flash-borrowed NORMIE cheaply and keep the remaining ETH as profit.
The root cause is a token-side accounting bug in NORMIE. _get_premarket_user marks any recipient as a premarket_user when the transfer amount equals the public team-wallet balance. Once that flag is set, _transfer credits address(this) with the full transfer amount on market-pair transfers to that recipient without debiting any other balance. That breaks token conservation, manufactures sellable contract inventory, and makes the drain permissionlessly reachable.
NORMIE is an ERC-20 token on Base with custom transfer logic around a Uniswap V2 pair and a swapAndLiquify routine. swapAndLiquify sells tokens held by the token contract into the NORMIE/WETH V2 pair and forwards the resulting ETH to the hard-coded team wallet and dev wallet. In the exploit state, both teamWalletAddress and pointed to .
devWalletAddress0xd8056B0F8AA2126a8DB6f0B3109Fe9127617bEb2The crucial hidden state is the private premarket_user mapping. NORMIE updates that flag through _get_premarket_user(recipient, amount), which compares the transfer amount to balanceOf(teamWalletAddress). Because the team-wallet balance is public on-chain state, an attacker can deliberately transfer that exact amount to an attacker-controlled helper and deterministically set the flag.
The exploit relied only on public venues:
0x7F12d13B34F5F4f0a9449c16Bcd42f0da47AF2000x24605E0bb933f6EC96E6bBbCEa0be8cC880F6E6f0x67ab0E84C7f9e399a67037F94a08e5C664DC1C660xd8056B0F8AA2126a8DB6f0B3109Fe9127617bEb2Immediately before the exploit block, the team wallet held exactly 5000000000000000 NORMIE and _minimumTokensBeforeSwap was also 5000000000000000. Those two public values made the marking step and the forced swapAndLiquify threshold easy to target.
The vulnerability is an accounting failure in NORMIE's transfer path. A market-pair transfer should conserve balances except for explicit fees, but NORMIE adds the full transferred amount to the token contract balance when the recipient is a flagged premarket_user, before the sender is debited and without any paired debit elsewhere. That is equivalent to minting unbacked tokens into address(this).
The flag that unlocks this branch is also permissionlessly reachable. _get_premarket_user does not authenticate the recipient or require privileged setup; it simply checks whether the transfer amount matches the team wallet's current balance. Since that balance was 5e15 NORMIE in the exploit pre-state, the attacker used a public V2 flash swap to deliver exactly that amount to an attacker helper and set the flag.
Once the helper was marked, repeated pair-to-helper transfers produced repeated phantom credits to address(this). Because swapAndLiquify sells the token contract's inventory into the V2 pair and forwards the resulting ETH to the hard-coded wallet, the attacker could repeatedly convert those phantom tokens into real WETH from the pair. The exploit is therefore an ACT attack: every dependency was public, every step was permissionless, and the profit predicate is visible in the post-transaction balance diffs.
The relevant NORMIE source shows both the reachability bug and the accounting breakpoint:
function _get_premarket_user(address _address, uint256 amount) internal {
premarket_user[_address] = !premarket_user[_address]
? (amount == balanceOf(teamWalletAddress))
: premarket_user[_address];
}
if (
isMarketPair[sender] &&
!isExcludedFromFee[recipient] &&
premarket_user[recipient]
) {
_balances[address(this)] = _balances[address(this)].add(amount);
}
This logic violates the basic invariant for token transfers: the sender must lose the transferred amount, the recipient must gain the post-fee amount, and the token contract balance must increase only through explicit fee logic. Instead, the flagged branch adds amount to the token contract balance before normal debit/credit processing, so each qualifying pair-to-helper transfer creates additional contract-held NORMIE with no balancing subtraction.
The exploit trace confirms that the attacker reached this path exactly as the source suggests. The helper 0x5ff34545ea227BCA3826B11d6C1599095b39d9b8 first received 5000000000000000 NORMIE from the V2 pair, which matches the team-wallet balance:
UniswapV2Pair::swap(..., 5000000000000000, 0x5ff3..., ...)
NORMIE::transfer(0x5ff3..., 5000000000000000)
emit Transfer(from: UniswapV2Pair, to: 0x5ff3..., value: 5000000000000000)
After that marking step, the same helper took a V3 flash loan of 11333141501283594 NORMIE and sold 9066513201026875 NORMIE into the V2 pair for 65976594658865290645 wei of WETH-equivalent ETH output:
0x67ab...::flash(0x5ff3..., 0, 11333141501283594, ...)
UniswapV2Router02::swapExactTokensForETHSupportingFeeOnTransferTokens(
9066513201026875, 0, [NORMIE, WETH], 0x5ff3..., ...
)
emit Swap(... amount0Out: 65976594658865290645 ...)
The helper then recycled its remaining NORMIE through repeated transfer plus skim loops. Each skim caused another pair-to-helper transfer, which re-entered the premarket_user branch and increased the token contract's phantom balance. The trace shows the repeated UniswapV2Pair::skim(0x5ff3...) calls and the resulting marketingGetBnb events, proving that swapAndLiquify repeatedly sold the token contract's fabricated inventory:
UniswapV2Pair::skim(0x5ff3...)
emit marketingGetBnb(amount: 18684952337505736072)
...
UniswapV2Pair::skim(0x5ff3...)
emit marketingGetBnb(amount: 12728078543120874018)
Those ETH flows went to the hard-coded team wallet because swapAndLiquify forwards amountBNBMarketing to teamWalletAddress and amountBNBTeam to devWalletAddress, which were the same address in the exploit state. The transaction balance diff shows that 0xd8056b0f8aa2126a8db6f0b3109fe9127617beb2 gained 100051402193853346184 wei during the attack.
After forcing the price down, the attacker spent only 0.5 ETH to buy back enough NORMIE to close the flash position and repaid 11446472916296430 NORMIE to the V3 pool. The same trace shows the repayment and the final ETH transfer from the helper back to the attacker-controlled orchestrator:
NORMIE::transfer(0x67ab0E84C7f9e399a67037F94a08e5C664DC1C66, 11446472916296430)
0xEF0BA56DA26B4DDFEf0959c1D0Fc7a73A908beFC::fallback{value: 65476594658865290645}()
The balance diff closes the loop. The attacker EOA 0xf7f3a556ac21d081f6dba961b6a84e52e37a717d increased from 0.241502284849935602 ETH to 77.756005625539074265 ETH, a net gain of 77.514503340689138663 ETH after gas, while the V2 pair lost 162.532301798926267335 WETH and the V3 pool lost 12 WETH-equivalent liquidity. This is deterministic evidence that the accounting bug directly enabled a profitable, permissionless drain.
The attacker cluster consisted of:
0xf7f3a556ac21d081f6dba961b6a84e52e37a717d, which signed the exploit transaction.0xef0ba56da26b4ddfef0959c1d0fc7a73a908befc, which received the transaction call and intermediate ETH proceeds.0x5ff34545ea227bca3826b11d6c1599095b39d9b8, which performed the flash-loan and skim loop mechanics.The exploit unfolded in one transaction:
0xef0b... in tx 0xa618933a0e0ffd0b9f4f0835cc94e523d0941032821692c01aa96cd6f80fc3fd.5e15 NORMIE, received that amount, and repaid 5.02e15 NORMIE. That single equality transfer marked the helper as a premarket_user.11333141501283594 NORMIE from the public V3 pool and immediately sold 80% of it into the V2 pair, pulling out roughly 65.976594658865290645 ETH.skim. Every skim created another pair-to-helper transfer, which minted fresh NORMIE into address(this) on the token side and eventually triggered swapAndLiquify to dump those tokens for ETH.0.5 ETH, repaid the V3 flash loan plus fee, and forwarded the remaining ETH back through 0xef0b... to the attacker EOA.The related post-incident transaction 0x587f14b7ffb30b5013ab0db02e9bc94183817ef34c24a9595f33277e752f81eb appears in the artifact set as follow-up metadata, but the exploit realization itself is fully contained in the single adversary-crafted transaction above.
The measured asset loss from the exploit path is:
174.532301798926267335 WETH-equivalent native asset removed across the touched Base liquidity venues.162.532301798926267335 WETH removed from the NORMIE/WETH V2 pair specifically.12 WETH removed from the NORMIE/WETH V3 pool during the flash path.The attacker EOA realized a net gain of 77.514503340689138663 ETH after gas. Separately, the hard-coded team/dev wallet received 100.051402193853346184 ETH from the forced swapAndLiquify executions because the victim contract routed the proceeds to that address. The direct victimized market component was the NORMIE/WETH public liquidity, which absorbed the token dumps backed by phantom inventory.
0x7F12d13B34F5F4f0a9449c16Bcd42f0da47AF200, especially _get_premarket_user, _transfer, swapAndLiquify, and takeFee.0xa618933a0e0ffd0b9f4f0835cc94e523d0941032821692c01aa96cd6f80fc3fd on Base, including full trace and balance diff artifacts.0xf7f3a556ac21d081f6dba961b6a84e52e37a717d and target 0xef0ba56da26b4ddfef0959c1d0fc7a73a908befc.0x587f14b7ffb30b5013ab0db02e9bc94183817ef34c24a9595f33277e752f81eb.