Calculated from recorded token losses using historical USD prices at the incident time.
0x97201900198d0054a2f7a914f5625591feb6a18e7fc6bb4f0c964b967a6c15f60x4622aff8e521a444c9301da0efd05f6b482221b8Ethereum0x07d7685becb1a72a1cf614b4067419334c9f1b4dEthereumOn Ethereum mainnet block 19029290, transaction 0x97201900198d0054a2f7a914f5625591feb6a18e7fc6bb4f0c964b967a6c15f6 exploited BasketDAO's BMIZapper at 0x4622aff8e521a444c9301da0efd05f6b482221b8 to steal 114146247097 raw USDC units from victim 0x07d7685becb1a72a1cf614b4067419334c9f1b4d. The attacker submitted the transaction from EOA 0x63136677355840f26c0695dd6de5c9e4f514f8e8, routed execution through helper contract 0xae5919160a646f5d80d89f7aae35a2ca74738440, and converted the stolen USDC into ETH through Uniswap V2 in the same transaction.
The root cause is a direct arbitrary-call bug in BMIZapper. zapToBMI accepts attacker-controlled _from, _aggregator, and _aggregatorData, and for bare-token inputs routes into _primitiveToBMI, which executes (bool success, ) = _aggregator.call(_aggregatorData) under BMIZapper's own spender identity. Because the victim had already granted BMIZapper a large USDC allowance, the attacker used _aggregator = USDC and calldata for USDC.transferFrom(victim, helper, victimBalance), turning BMIZapper into an allowance-spending gadget.
BMIZapper is a public helper contract intended to convert supported assets into BMI basket exposure. Its entrypoint zapToBMI accepts a source asset, route parameters, weightings, and an external aggregator target with calldata. That design means BMIZapper can hold or exercise token allowances while also invoking external contracts during conversion.
USDC follows the standard ERC-20 approval model: once a holder grants a spender allowance, that spender can call transferFrom(holder, recipient, amount) without additional approval at execution time. The seed trace shows that victim 0x07d7685becb1a72a1cf614b4067419334c9f1b4d had already approved BMIZapper for more than its full USDC balance before the exploit ran.
The exploit only works because BMIZapper combines those two facts. First, it permits a bare-token path with _amount = 0, so the attacker does not need to supply meaningful input capital. Second, the same path reaches an unrestricted external call controlled by user input, allowing the attacker to make BMIZapper call USDC as though BMIZapper itself intended to spend the victim's approval.
This is an ATTACK-category protocol bug, not a pure MEV opportunity. The vulnerable behavior is an unchecked arbitrary external call in an asset-handling path. BMIZapper's intended invariant is that a BMI mint should only consume assets intentionally supplied by the caller for the requested conversion path. Instead, the implementation allows a caller to repurpose BMIZapper's pre-existing spender rights over third-party approvals.
The critical code path is visible in the verified BMIZapper source:
function zapToBMI(...) public returns (uint256) {
...
IERC20(_from).safeTransferFrom(msg.sender, address(this), _amount);
if (_isBare(_from)) {
_primitiveToBMI(_from, _amount, _bmiConstituents, _bmiConstituentsWeightings, _aggregator, _aggregatorData);
}
...
}
function _primitiveToBMI(...) internal {
if (_token != DAI && _token != USDC && _token != USDT) {
IERC20(_token).safeApprove(_aggregator, 0);
IERC20(_token).safeApprove(_aggregator, _amount);
(bool success, ) = _aggregator.call(_aggregatorData);
require(success, "!swap");
_token = USDC;
}
...
}
The attacker selected _from = BUSD and _amount = 0, which let safeTransferFrom(msg.sender, address(this), 0) succeed without funding the zapper. Because BUSD is a supported bare token and is not DAI, USDC, or USDT, execution entered _primitiveToBMI and reached the attacker-controlled _aggregator.call(_aggregatorData). That is the code-level breakpoint where BMIZapper's authorization boundary fails.
The pre-state at block 19029289 already satisfied the exploit predicate. The victim held 114146247097 raw USDC units and had granted BMIZapper an allowance of 115792089237316195423570985008687907853269984665640564039457584007912948854512, which exceeded the full victim balance. Those conditions are visible in the seed trace immediately before the exploit path:
0xA0b86991...::allowance(victim, BMIZapper)
0xA0b86991...::balanceOf(victim)
The attacker helper then invoked BMIZapper with a zero-amount BUSD route and malicious aggregator calldata:
0x4622aFF8...::zapToBMI(
0x4Fabb145..., // BUSD
0,
0x0000000000000000000000000000000000000000,
0,
0,
[],
[1000000000000000000],
0xA0b86991..., // USDC as "aggregator"
0x23b872dd...1a93a581b9, // transferFrom(victim, helper, 114146247097)
true
)
Inside that call, the trace shows the exact unauthorized spend:
0xA0b86991...::transferFrom(
0x07d7685bECB1a72a1Cf614b4067419334C9f1b4d,
0xae5919160A646f5D80d89F7aaE35A2CA74738440,
114146247097
)
That transfer is decisive evidence that BMIZapper executed the attacker-supplied USDC calldata using BMIZapper's own spender rights. The victim did not interact in this transaction; their prior allowance alone was enough. The exploit therefore does not depend on private keys, privileged roles, or attacker-owned protocol contracts. It is an ACT opportunity available to any unprivileged party who can observe victim allowances and balances on-chain.
After the drain, the helper no longer needed BMIZapper. It approved Uniswap V2 Router 02 and monetized the stolen USDC:
UniswapV2Router02::swapExactTokensForETH(
114146247097,
0,
[USDC, WETH],
0xae5919160A646f5D80d89F7aaE35A2CA74738440,
...
)
The collector balance diff confirms the state transition that matters: the victim's USDC balance moved from 114146247097 to 0, and the adversary cluster realized 44776142887355667842 wei of net ETH profit after gas across the two profit recipients.
The attack is a single-transaction sequence with three stages.
First, the attacker EOA 0x63136677355840f26c0695dd6de5c9e4f514f8e8 called helper contract 0xae5919160a646f5d80d89f7aae35a2ca74738440. The helper queried the victim's USDC allowance to BMIZapper and the victim's USDC balance, which let it compute the maximum drainable amount entirely from public state.
Second, the helper invoked BMIZapper.zapToBMI with BUSD as the nominal input token, zero input amount, USDC as the aggregator target, and calldata encoding USDC.transferFrom(victim, helper, victimBalance). The zero-amount BUSD transfer avoided any real funding requirement while still entering the vulnerable bare-token branch. BMIZapper then executed the raw external call and spent the victim's allowance.
Third, the helper approved Uniswap V2 Router 02, swapped the stolen USDC for ETH through the canonical USDC/WETH pool, and distributed the proceeds. The trace and balance diff show 42536491431166760520 wei net to the submitting EOA and 2239651456188907322 wei to 0x7e2a2fa2a064f693f0a55c5639476d913ff12d05, matching total cluster profit of 44776142887355667842 wei net after gas.
The direct victim loss was 114146247097 raw USDC units, which equals 114146.247097 USDC at 6 decimals. The immediate victim was address 0x07d7685becb1a72a1cf614b4067419334c9f1b4d, while the protocol component that enabled the theft was BMIZapper at 0x4622aff8e521a444c9301da0efd05f6b482221b8.
The attacker monetized the stolen funds into ETH in the same transaction, proving realized rather than latent impact. No speculative assumptions are needed: the victim balance diff, unauthorized transferFrom, swap trace, and native-balance deltas all agree on the loss and profit amounts.
0x97201900198d0054a2f7a914f5625591feb6a18e7fc6bb4f0c964b967a6c15f60x4622aff8e521a444c9301da0efd05f6b482221b80x07d7685becb1a72a1cf614b4067419334c9f1b4d0xae5919160a646f5d80d89f7aae35a2ca747384400x7a250d5630b4cf539739df2c5dacb4c659f2488d/workspace/session/artifacts/collector/seed/1/0x97201900198d0054a2f7a914f5625591feb6a18e7fc6bb4f0c964b967a6c15f6/trace.cast.log/workspace/session/artifacts/collector/seed/1/0x97201900198d0054a2f7a914f5625591feb6a18e7fc6bb4f0c964b967a6c15f6/balance_diff.jsonhttps://etherscan.io/address/0x4622aff8e521a444c9301da0efd05f6b482221b8#code