This is a lower bound: only assets with reliable historical USD prices are counted, so the actual loss may be higher.
0x346f65ac333eb6d69886f5614aaf569a561a53a8d93db4384bd7c0bec15ae9f60xdb103fd28ca4b18115f5ce908baaeed7e0f1f101BSC0x003b724f9e1fa7350a7723bb8313acbdbe7188cbBSC0x390d9078cb06f3cd4a17a39693b489446724a093BSCOn BNB Chain block 29554344, transaction 0x346f65ac333eb6d69886f5614aaf569a561a53a8d93db4384bd7c0bec15ae9f6 exploited the public MultiSender helper at 0xdb103fd28ca4b18115f5ce908baaeed7e0f1f101. The caller satisfied MultiSender's allowance gate from its own address, set _from to an unrelated holder 0x003b724f9e1fa7350a7723bb8313acbdbe7188cb that had previously approved the helper, pulled 99999999999940000 raw MyAi units, sold the stolen MyAi through the public MyAi/WBNB Pancake pair 0x390d9078cb06f3cd4a17a39693b489446724a093, and ended with a net profit of 10.579374286685023016 BNB after gas.
The root cause is an authorization-subject mismatch in MultiSender's token path. The helper checks allowance(msg.sender, address(this)) but debits _from inside transferFrom, so any unprivileged actor can approve the helper from their own account and drain a third-party holder that has already approved MultiSender and still has enough balance. This is an ACT opportunity because the helper is public, the BNB fees are fixed, the vulnerable logic is on-chain and verified, and the liquidation path uses public PancakeSwap liquidity.
MultiSender is a public batching helper that charges BNB fees and then either forwards token transfers or native-BNB transfers. On the relevant pre-state, validator RPC queries at block 29554343 confirmed:
platformFees() = 10000000000000000 wei.devFees() = 2000000000000000 wei.0x40d1e011669c0dc7dc7c7fb93e623d6a661df5ee has 9 decimals.0x003b724f9e1fa7350a7723bb8313acbdbe7188cb held 169984260220000000 MyAi and had granted the same allowance to MultiSender.(3187138689840675 MyAi, 11119734748534322792 WBNB, 1688002569).Those conditions matter because they are the full exploit prerequisites: a public helper, a third-party approval to that helper, and liquid public exit liquidity for the stolen token.
The vulnerability is a direct authorization bug in MultiSender's token-transfer helper, not a MEV-only pricing edge or an attacker-specific integration trick. MultiSender is supposed to move tokens on behalf of an approved sender, but the implementation validates the caller's allowance and then debits an arbitrary _from address. That means the subject being authorized and the subject being debited are different addresses.
Because the two addresses are independent inputs, an attacker can do three public actions in one transaction: approve MultiSender from the attacker's own address, point _from at any holder that has already approved MultiSender, and route the transferred tokens to an attacker-controlled recipient list. Once the tokens are in the attacker's possession, liquidation through PancakeSwap is ordinary permissionless trading. The invariant that should hold is simple: a helper must only debit the same address whose authorization it just validated. The code-level breakpoint is MultiSender's internal tokenTransfer function.
The verified MultiSender source on BscScan shows the vulnerable logic directly:
function tokenTransfer(
address _from,
address[] memory _address,
uint256[] memory _amounts,
address token,
uint256 totalAmount
) internal {
require(
IERC20(token).allowance(msg.sender, address(this)) >= totalAmount,
"allowance is not sufficient"
);
IERC20(token).transferFrom(_from, address(this), totalAmount);
uint256 tokenBalance = IERC20(token).balanceOf(address(this));
for (uint256 i = 0; i < _address.length; ++i) {
IERC20(token).transfer(
_address[i],
_amounts[i].mul(tokenBalance).div(totalAmount)
);
}
}
The bug is the mismatch between msg.sender and _from. If _from is not the caller, the function still debits _from as long as the caller has approved the helper and _from has also approved the helper at some earlier time.
The observed transaction uses exactly that path. The collected seed trace shows the attacker-controlled contract paying the helper fee, calling batchTokenTransfer, satisfying the allowance check from its own address, and then pulling MyAi from the unrelated approved holder:
MultiSender::batchTokenTransfer{value: 12000000000000000}(
0x003B724f9e1fa7350A7723BB8313ACBDbE7188CB,
[... attacker-controlled recipients ...],
[...],
0x40d1E011669c0dc7Dc7c7Fb93E623d6A661Df5Ee,
99999999999940000,
true
)
CoinToken::allowance(0x0D3aaFb9..., 0xDb103fd2...) -> max_uint
CoinToken::transferFrom(0x003B724f..., 0xDb103fd2..., 99999999999940000)
emit Approval(owner: 0x003B724f..., spender: 0xDb103fd2..., value: 69984260220060000)
That trace excerpt aligns with independent state queries around the incident. At block 29554343, the approved holder had both 169984260220000000 MyAi and 169984260220000000 allowance to MultiSender. At block 29554344, both values dropped to 69984260220060000. The drop exactly matches the unauthorized transferFrom path. No privileged signer, off-chain secret, or attacker-owned helper contract is needed to make that state transition happen.
After the unauthorized pull, the trace shows MultiSender forwarding the resulting MyAi balance to the attacker-controlled contract, which then liquidates the stolen MyAi into WBNB on PancakeSwap and unwraps the WBNB into native BNB. The balance-diff artifact records the profit consequence:
{
"address": "0xc47fcc9263b026033a94574ec432514c639a2d12",
"before_wei": "1843719942000000000",
"after_wei": "12423094228685023016",
"delta_wei": "10579374286685023016"
}
This establishes the full success predicate reported in root_cause.json: the exploit both violates the authorization invariant and realizes positive attacker profit in BNB terms.
The adversary flow is a single transaction with three deterministic stages:
0xc47fcc9263b026033a94574ec432514c639a2d12 sends tx 0x346f65ac333eb6d69886f5614aaf569a561a53a8d93db4384bd7c0bec15ae9f6 to attacker-controlled contract 0x0d3aafb9ade835456b2595509ac1f58922e465b3 with 1.3 BNB of value. That contract approves MultiSender and PancakeRouter from its own address.MultiSender.batchTokenTransfer(address,address[],uint256[],address,uint256,bool) with _from=0x003b724f9e1fa7350a7723bb8313acbdbe7188cb, token=MyAi, totalAmount=99999999999940000, and isToken=true. MultiSender accepts the caller-side approval, then debits the approved holder and forwards the resulting MyAi to attacker-controlled recipients.10773405253685023016 raw WBNB units, unwraps them, and pays out 12061405253685023016 wei to the attacker EOA. The attacker EOA retains 10579374286685023016 wei net after gas.The observed exploit used a bespoke attacker contract to bundle those calls, but the strategy does not depend on that specific contract. Any unprivileged actor can reproduce the same sequence with a fresh EOA or locally deployed helper contract because the exploit uses only public approvals, public helper functions, and public AMM liquidity.
The incident caused both direct token theft from the approved holder and reserve depletion in the public exit market:
0x003b724f9e1fa7350a7723bb8313acbdbe7188cb lost 99999999999940000 raw MyAi units.0x390d9078cb06f3cd4a17a39693b489446724a093 lost 10773405253685023016 raw WBNB units through the liquidation path.10579374286685023016 wei after paying 182030967000000000 wei in gas.The loss summary in smallest on-chain units is therefore:
[
{
"token_symbol": "MyAi",
"amount": "99999999999940000",
"decimal": 9
},
{
"token_symbol": "WBNB",
"amount": "10773405253685023016",
"decimal": 18
}
]
0x346f65ac333eb6d69886f5614aaf569a561a53a8d93db4384bd7c0bec15ae9f6 on BscScan.0xdb103fd28ca4b18115f5ce908baaeed7e0f1f101 on BscScan.batchTokenTransfer, transferFrom, Pancake swaps, WBNB withdrawal, and payout.29554343 and 29554344 for MultiSender fees, MyAi decimals, approved-holder balances and allowances, and Pancake pair reserves.