All incidents

LUXERA Approval Drain via Multicall3

Share
Aug 20, 2025 14:29 UTCAttackLoss: 27,900,000 XERA, 41.03 WBNBPending manual check1 exploit txWindow: Atomic
Estimated Impact
27,900,000 XERA, 41.03 WBNB
Label
Attack
Exploit Tx
1
Addresses
2
Attack Window
Atomic
Aug 20, 2025 14:29 UTC → Aug 20, 2025 14:29 UTC

Exploit Transactions

TX 1BSC
0xed6fd61c1eb2858a1594616ddebaa414ad3b732dcdb26ac7833b46803c5c18db
Aug 20, 2025 14:29 UTCExplorer

Victim Addresses

0x9a619ae8995a220e8f3a1df7478a5c8d2affc542BSC
0x231075e4aa60d28681a2d6d4989f8f739bac15a0BSC

Loss Breakdown

27,900,000XERA
41.03WBNB

Similar Incidents

Root Cause Analysis

LUXERA Approval Drain via Multicall3

1. Incident Overview TL;DR

At BNB Chain block 58269337, the LUXERA owner wallet 0x9a619ae8995a220e8f3a1df7478a5c8d2affc542 approved the public Multicall3 contract 0xca11bde05977b3631167028862be2a173976ca11 for unlimited XERA in transaction 0x8bcc020c663d39855890c159aa2a2e18eebd6d00fffbe87113f690bedde0a78a. Two blocks later, transaction 0xed6fd61c1eb2858a1594616ddebaa414ad3b732dcdb26ac7833b46803c5c18db used that approval to transfer 27,900,000 XERA from the victim wallet into the XERA/WBNB pair 0x231075e4aa60d28681a2d6d4989f8f739bac15a0, swap the drained tokens for 41.034748173552867045 WBNB, unwrap the WBNB, and leave 20.517394885196268493 BNB net profit in the observed adversary cluster after gas.

The root cause is a spend-authority mistake, not a hidden key compromise. LUXERA follows the standard OpenZeppelin ERC20 allowance model, and Multicall3 is a public arbitrary-call executor. Once the victim approved Multicall3 as spender, every external caller gained a path to exercise that allowance through Multicall3 and transfer the victim's XERA without any further privilege.

2. Key Background

LUXERA (0x93e99ae6692b07a36e7693f4ae684c266633b67d) is an ERC20 token whose Token.sol constructor mints supply to the owner wallet and exposes tradingEnabled() and pairV2() state used by the incident context. The collected ERC20.sol artifact is OpenZeppelin-style ERC20 code: transferFrom(from, to, value) spends allowance(from, msg.sender) through _spendAllowance and then moves balances with _transfer.

Multicall3 is not a privileged wallet helper. It is a general-purpose batching contract that executes user-supplied call descriptors against arbitrary targets. Approving Multicall3 as an ERC20 spender therefore delegates spend authority to any caller that can make Multicall3 execute transferFrom.

The realization venue was the XERA/WBNB pair 0x231075e4aa60d28681a2d6d4989f8f739bac15a0. Before the drain, the pool reserves were 952613793756048742258591 XERA and 42440048919282251928 WBNB, and tradingEnabled() on LUXERA was still false. The exploit did not need local mocks or privileged pool controls; it only needed the published approval, the victim's balance, and the live pair reserves.

3. Vulnerability Analysis & Root Cause Summary

This was an ATTACK-category ACT opportunity created by approving a public arbitrary-call contract as ERC20 spender. The safety invariant is simple: only a spender intentionally trusted by the token owner should be able to exercise the owner's allowance. That invariant was broken when the victim wallet approved Multicall3 for type(uint256).max.

The critical code-level breakpoint is LUXERA's ERC20 allowance path. In the collected source, transferFrom sets spender = _msgSender() and then calls _spendAllowance(from, spender, value). In the exploit trace, the direct caller into XERA is Multicall3, so the spender checked by LUXERA is 0xca11..., not the external EOA. Because the victim had already approved that contract, any unprivileged EOA could submit calldata to Multicall3 that invoked transferFrom(victim, pair, amount) and spend the victim's tokens.

The exploit did not rely on hidden state, leaked keys, or a privileged protocol role. After the approval transaction became public chain state, the remaining path was fully permissionless: call Multicall3, drain the victim's XERA into the pair, compute the Uniswap V2 output from public reserves, and swap out WBNB.

4. Detailed Root Cause Analysis

The victim-side mechanism is visible in the collected LUXERA ERC20 source:

function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
    address spender = _msgSender();
    _spendAllowance(from, spender, value);
    _transfer(from, to, value);
    return true;
}

function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
    uint256 currentAllowance = allowance(owner, spender);
    if (currentAllowance != type(uint256).max) {
        if (currentAllowance < value) {
            revert ERC20InsufficientAllowance(spender, currentAllowance, value);
        }
        unchecked {
            _approve(owner, spender, currentAllowance - value, false);
        }
    }
}

In transaction 0x8bcc020c663d39855890c159aa2a2e18eebd6d00fffbe87113f690bedde0a78a, the victim wallet sent an approve call directly to the XERA token with calldata:

0x095ea7b3
000000000000000000000000ca11bde05977b3631167028862be2a173976ca11
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

That call set allowance(victim, 0xca11...) = type(uint256).max and emitted the matching Approval event in the transaction receipt. From that moment, the ACT pre-state existed: the victim still held 27900000000000000000000000 XERA, the spender was a public batching contract, and the pair had enough WBNB liquidity to monetize a drain.

The exploit transaction 0xed6fd61c1eb2858a1594616ddebaa414ad3b732dcdb26ac7833b46803c5c18db then exercised the allowance. The trace shows the helper calling Multicall3 and Multicall3 calling XERA:

SM Address: 0xca11bde05977b3631167028862be2a173976ca11, caller:0x90be00229fe8000000009e007743a485d400c3b7
SM Address: 0x93e99ae6692b07a36e7693f4ae684c266633b67d, caller:0xca11bde05977b3631167028862be2a173976ca11, input_size:100
0xcA11...::aggregate3([(0x93E99..., false, 0x23b872dd...9a619a...231075...17140e07d688b2a5800000)])

The selector 0x23b872dd is transferFrom(address,address,uint256), and the encoded arguments are the victim wallet, the XERA/WBNB pair, and 27900000000000000000000000. Because msg.sender inside XERA was Multicall3, the approval was valid and the transfer succeeded.

The balance diff confirms the exact state change:

{
  "holder": "0x9a619ae8995a220e8f3a1df7478a5c8d2affc542",
  "before": "27900000000000000000000000",
  "after": "0",
  "delta": "-27900000000000000000000000"
}
{
  "holder": "0x231075e4aa60d28681a2d6d4989f8f739bac15a0",
  "before": "952613793756048742258591",
  "after": "28852613793756048742258591",
  "delta": "27900000000000000000000000"
}

Once the pair received the drained XERA, the same transaction swapped it for WBNB according to the live reserves. The trace emitted:

emit Swap(param0: 0x90bE00229fE8000000009e007743A485d400C3B7, param1: 27900000000000000000000000, param4: 41034748173552867045)
emit Withdrawal(src: 0x90bE00229fE8000000009e007743A485d400C3B7, wad: 41034748173552867045)

That is the full end-to-end root cause. A public spender approval converted a standard ERC20 allowance into permissionless spend authority for any caller of Multicall3, and the drained tokens were immediately liquidated through the existing XERA/WBNB pool.

5. Adversary Flow Analysis

The observed adversary cluster was:

  • EOA 0x00b700b9da0053009cb84400ed1e8fe251002af3, which submitted the exploit transaction.
  • Helper contract 0x90be00229fe8000000009e007743a485d400c3b7, which executed the Multicall3 call and retained the realized BNB.

The attacker flow had three stages.

First, the victim published the approval in transaction 0x8bcc020c663d39855890c159aa2a2e18eebd6d00fffbe87113f690bedde0a78a at block 58269337. That transaction was observable public state and therefore created an ACT opportunity for any searcher watching approvals to public spenders.

Second, the adversary exercised the approval in transaction 0xed6fd61c1eb2858a1594616ddebaa414ad3b732dcdb26ac7833b46803c5c18db at block 58269339. The helper contract called Multicall3 with encoded XERA transferFrom calldata targeting the victim wallet and the pair. The victim's full XERA balance moved into the pair in a single step.

Third, the adversary realized profit by calling swap on the pair, receiving 41034748173552867045 WBNB, and unwrapping that WBNB to BNB. The native balance diff shows the helper contract increasing from 22877966441957319 wei to 20540323455696495159 wei, a delta of 20517445489254537840 wei. The submitting EOA paid 50604058269347 wei in gas, leaving net cluster profit of 20517394885196268493 wei, or 20.517394885196268493 BNB.

6. Impact & Losses

The incident impacted two parties.

The victim owner wallet lost its full XERA balance:

  • 27,900,000 XERA, encoded on-chain as 27900000000000000000000000 with 18 decimals.

The XERA/WBNB pool lost the counter-asset paid out during liquidation:

  • 41.034748173552867045 WBNB, encoded on-chain as 41034748173552867045 with 18 decimals.

Operationally, the drain also left the pool almost entirely stripped of WBNB while LUXERA trading remained disabled. The realized adversary profit was BNB-denominated because the WBNB was unwrapped inside the same exploit transaction.

7. References

  • Approval transaction: 0x8bcc020c663d39855890c159aa2a2e18eebd6d00fffbe87113f690bedde0a78a
  • Exploit transaction: 0xed6fd61c1eb2858a1594616ddebaa414ad3b732dcdb26ac7833b46803c5c18db
  • Victim wallet: 0x9a619ae8995a220e8f3a1df7478a5c8d2affc542
  • Public spender: 0xca11bde05977b3631167028862be2a173976ca11
  • LUXERA token: 0x93e99ae6692b07a36e7693f4ae684c266633b67d
  • XERA/WBNB pair: 0x231075e4aa60d28681a2d6d4989f8f739bac15a0
  • Collected exploit metadata: /workspace/session/artifacts/collector/seed/56/0xed6fd61c1eb2858a1594616ddebaa414ad3b732dcdb26ac7833b46803c5c18db/metadata.json
  • Collected exploit trace: /workspace/session/artifacts/collector/seed/56/0xed6fd61c1eb2858a1594616ddebaa414ad3b732dcdb26ac7833b46803c5c18db/trace.cast.log
  • Collected exploit balance diff: /workspace/session/artifacts/collector/seed/56/0xed6fd61c1eb2858a1594616ddebaa414ad3b732dcdb26ac7833b46803c5c18db/balance_diff.json
  • Collected LUXERA source: /workspace/session/artifacts/collector/seed/56/0x93e99ae6692b07a36e7693f4ae684c266633b67d/src/Token.sol
  • Collected ERC20 source: /workspace/session/artifacts/collector/seed/56/0x93e99ae6692b07a36e7693f4ae684c266633b67d/src/ERC20.sol