All incidents

AirdropGrapesToken ApeCoin Claim via NFTX BAYC Vault

Share
Mar 17, 2022 11:45 UTCAttackLoss: 60,564 APEManually checked2 exploit txWindow: 27m 41s
Estimated Impact
60,564 APE
Label
Attack
Exploit Tx
2
Addresses
2
Attack Window
27m 41s
Mar 17, 2022 11:45 UTC → Mar 17, 2022 12:13 UTC

Exploit Transactions

TX 1Ethereum
0x8a61927598ce26f296ba1be155301418e2c01fe8f5585f9b46369e1ec5c408b0
Mar 17, 2022 11:45 UTCExplorer
TX 2Ethereum
0xeb8c3bebed11e2e4fcd30cbfc2fb3c55c4ca166003c7f7d319e78eaab9747098
Mar 17, 2022 12:13 UTCExplorer

Victim Addresses

0x025c6da5bd0e6a5dd1350fda9e3b6a614b205a1fEthereum
0xea47b64e1bfccb773a0420247c0aa0a3c1d2e5c5Ethereum

Loss Breakdown

60,564APE

Similar Incidents

Root Cause Analysis

AirdropGrapesToken ApeCoin Claim via NFTX BAYC Vault

1. Incident Overview TL;DR

An adversary-controlled helper stack on Ethereum mainnet — EOA 0x6703741e913a30d6604481472b6d81f3da45e6e8 together with helper contracts 0x3ebd3d86f810b141f9b2e9b15961fc66364b54f3 and 0x7797a99a2e91646abdc9dc30e838a149ccb3013b — exploited the AirdropGrapesToken ApeCoin airdrop contract 0x025c6da5bd0e6a5dd1350fda9e3b6a614b205a1f.

Within a single transaction 0xeb8c3bebed11e2e4fcd30cbfc2fb3c55c4ca166003c7f7d319e78eaab9747098 in block 14403949, the adversary temporarily accumulated on-chain ownership of six BAYC NFTs (IDs 1060, 7594, 8214, 9915, 8167, 4755), five of which belonged economically to depositors in the NFTX BAYC vault 0xea47b64e1bfccb773a0420247c0aa0a3c1d2e5c5. While holding those NFTs, the helper contract 0x7797a9… called AirdropGrapesToken.claimTokens(), causing the contract to treat 0x7797a9… as the owner of those BAYCs and transfer 60,564 ApeCoin to the adversary cluster. The NFTs were then returned to the vault, leaving vault depositors unable to claim their ApeCoin allocation.

At a high level, the root cause is that AirdropGrapesToken.claimTokens() grants claim rights based solely on the live ERC721 owner at claim time (via ownerOf/tokenOfOwnerByIndex), with no binding to a snapshot owner or underlying economic owner behind vault wrappers. Any unprivileged actor who can temporarily take custody of eligible BAYC/MAYC/BAKC tokens can therefore permanently consume the associated ApeCoin allocations.

2. Key Background

AirdropGrapesToken (0x025c6d…) is a generic airdrop contract deployed on Ethereum mainnet to distribute ApeCoin (0x4d224452801aced8b2f0aebe155379bb5d594381) to holders of BAYC, MAYC, and BAKC NFTs. The constructor binds:

  • grapesToken to ApeCoin (ERC20) 0x4d2244…,
  • alpha to BAYC (ERC721Enumerable) 0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d,
  • beta to MAYC 0x60e4d786628fea6478f785a6d7e704777c86a7c6,
  • gamma to BAKC 0xba30e5f9bb24caa003e9f2f0497ad287fdf95623.

The contract holds a fixed ApeCoin pool and defines per-token distribution amounts for each collection. Eligibility is enforced at the token ID level via mappings alphaClaimed, betaClaimed, and gammaClaimed.

The key claim function is:

// Seed: verified source for 0x025c6d… (AirdropGrapesToken.sol)
function claimTokens() external whenNotPaused {
    require(block.timestamp >= claimStartTime && block.timestamp < claimStartTime + claimDuration, "Claimable period is finished");
    require((beta.balanceOf(msg.sender) > 0 || alpha.balanceOf(msg.sender) > 0), "Nothing to claim");

    uint256 tokensToClaim;
    uint256 gammaToBeClaim;

    (tokensToClaim, gammaToBeClaim) = getClaimableTokenAmountAndGammaToClaim(msg.sender);

    for (uint256 i; i < alpha.balanceOf(msg.sender); ++i) {
        uint256 tokenId = alpha.tokenOfOwnerByIndex(msg.sender, i);
        if (!alphaClaimed[tokenId]) {
            alphaClaimed[tokenId] = true;
            emit AlphaClaimed(tokenId, msg.sender, block.timestamp);
        }
    }

    // Analogous loops for beta and gamma, then:
    grapesToken.safeTransfer(msg.sender, tokensToClaim);
    totalClaimed += tokensToClaim;
    emit AirDrop(msg.sender, tokensToClaim, block.timestamp);
}

This implementation determines both eligibility and the payout recipient purely from the current ERC721Enumerable view of msg.sender’s holdings at claim time. There is no reference to:

  • the owner at a prior snapshot block,
  • the address that bore the economic risk of the NFTs (e.g., NFTX vault depositors), or
  • any off-chain allowlist.

The NFTX BAYC vault 0xea47b64e1bfccb773a0420247c0aa0a3c1d2e5c5 is a separate protocol that holds BAYC NFTs on-chain and issues fungible ERC20 vault tokens representing fractional ownership. At blocks 14403948 and 14403949, the vault owns BAYC token IDs {7594, 8214, 9915, 8167, 4755} while depositors hold the corresponding vault ERC20 shares:

// BAYC owner snapshots at blocks 14403948 and 14403949
{
  "token_id": 7594,
  "owner_at": {
    "14403948": { "result": "0x000000000000000000000000ea47b64e1bfccb773a0420247c0aa0a3c1d2e5c5" },
    "14403949": { "result": "0x000000000000000000000000ea47b64e1bfccb773a0420247c0aa0a3c1d2e5c5" }
  }
}

Thus, at the time of the exploit, the canonical on-chain owner for five of the six BAYCs is the NFTX vault contract, not the vault depositors.

3. Vulnerability Analysis & Root Cause Summary

Vulnerability class. The incident arises from a mismatch between the intended economic entitlement model (ApeCoin allocations belong to BAYC/MAYC/BAKC holders at the beginning of the claim period, including those using vault wrappers) and the actual on-chain entitlement check, which uses the current ERC721 owner at claim time. This is a “live ownership over snapshot/economic ownership” vulnerability in an airdrop contract.

Root cause summary. AirdropGrapesToken.claimTokens() computes tokensToClaim and marks token IDs as claimed exclusively by:

  • enumerating the current tokens owned by msg.sender via tokenOfOwnerByIndex(msg.sender, i), and
  • checking whether each token ID has not yet been marked as claimed.

There is no binding between the ApeCoin allocation for a given BAYC/MAYC/BAKC token ID and the account that held that token at the beginning of the claim period or that bears its economic risk. Any address that can route those NFTs through itself during the claim window is treated as the legitimate claimer and permanently consumes the associated allocation.

Invariant and breakpoint.

  • Intended invariant. For each BAYC/MAYC/BAKC token ID, only the account that is intended to hold the economic rights to that NFT at the start of the claim period should be able to claim its associated ApeCoin allocation; third parties must not be able to permanently seize another party’s allocation merely by taking temporary custody of the NFT.
  • Breakpoint. In claimTokens(), the contract bases eligibility and payout solely on live alpha.balanceOf(msg.sender) / alpha.tokenOfOwnerByIndex(msg.sender, i) (and analogs for beta/gamma) against the current ERC721 state, marking alphaClaimed[tokenId] = true and transferring ApeCoin to msg.sender. This logic allows any address that temporarily holds eligible NFTs at claim time to consume their allocations, regardless of underlying vault or wrapper relationships.

Because all relevant contract code and on-chain traces are public, this behavior constitutes an Anyone-Can-Take (ACT) opportunity for unprivileged adversaries who can engineer temporary control of eligible NFTs.

4. Detailed Root Cause Analysis

This section links the vulnerability to the concrete on-chain execution that produced the loss.

4.1. Pre-state and ownership configuration (σ_B)

At block 14403948 (immediately before the profit transaction’s block), the relevant state is:

  • BAYC ownership snapshots show:
    • Token ID 1060 owned by adversary EOA 0x6703741e913a30d6604481472b6d81f3da45e6e8.
    • Token IDs 7594, 8214, 9915, 8167, 4755 owned by NFTX BAYC vault 0xea47b64e1bfccb773a0420247c0aa0a3c1d2e5c5.
  • AirdropGrapesToken 0x025c6d… holds a large ApeCoin balance with per-token allocations preconfigured for BAYC/MAYC/BAKC and maintains alphaClaimed, betaClaimed, gammaClaimed mappings to prevent double-claims.
  • Helper contracts 0x3ebd3d… and 0x7797a9… are not yet deployed.

Snapshot evidence:

// Owner snapshots for BAYC token IDs 1060 and 7594 at 14403948 and 14403949
{
  "token_id": 1060,
  "owner_at": {
    "14403948": { "result": "0x0000000000000000000000006703741e913a30d6604481472b6d81f3da45e6e8" },
    "14403949": { "result": "0x000000000000000000000000ea47b64e1bfccb773a0420247c0aa0a3c1d2e5c5" }
  }
}

Normal and internal txlists for the NFTX vault and helpers show that the vault’s BAYC holdings were established prior to the incident by third-party deposits and that 0x3ebd3d… and 0x7797a9… are single-use contracts created and used only in the profit transaction.

4.2. Airdrop claim logic and its implications

Combining the AirdropGrapesToken source and its deployment metadata:

  • claimTokens() calls getClaimableTokenAmountAndGammaToClaim(msg.sender) to compute tokensToClaim by iterating over all token IDs currently held by _account and counting those not yet marked as claimed.
  • For each unclaimed BAYC (alpha) token owned by msg.sender, the contract:
    • Marks alphaClaimed[tokenId] = true,
    • Emits AlphaClaimed(tokenId, msg.sender, timestamp).
  • After processing BAYC, MAYC, and BAKC tokens similarly, the contract transfers tokensToClaim ApeCoin to msg.sender.

There is no parameterization of claimTokens() by token IDs or owner at a particular block; the on-chain logic solely trusts the dynamic ERC721 Enumerable views. Therefore, if an attacker can:

  1. Temporarily cause multiple eligible BAYCs to be owned by an adversary-controlled contract A during the claim period, and
  2. Call claimTokens() as A,

then the contract will:

  • Treat A as the legitimate claimant for all those BAYCs,
  • Permanently mark those token IDs as claimed, and
  • Transfer the associated ApeCoin allocations to A (and ultimately back to the adversary EOA).

This is the exact pattern realized in tx 0xeb8c3b…7098.

4.3. Helper/orchestrator design

The adversary deploys an orchestrator contract at 0x7797a99a2e91646abdc9dc30e838a149ccb3013b. The decompiled code shows:

// Seed: Heimdall decompile for 0x7797a9… (excerpt)
contract DecompiledContract {
    address public apex;      // NFTX BAYC vault token
    address public sushiswap; // DEX router
    address public apecoin;   // ApeCoin token
    address public weth;      // WETH
    address public claims;    // AirdropGrapesToken
    address public bayc;      // BAYC

    function Unresolved_b61d27f6(address arg0, uint256 arg1, uint256 arg2) public payable {
        // ...
        require(address(msg.sender) == 0x6703741e913a30d6604481472b6d81f3da45e6e8);
        // forwards value to arg0, used for routing ETH/APE back to the EOA
    }

    function apein() public payable {
        // interacts with apex (NFTX vault token), sushiswap, apecoin, weth
        // routes proceeds to 0x6703…e6e8
    }
}

This structure establishes that:

  • 0x7797a9… is not a generic third-party protocol; it is a bespoke orchestrator controlled by EOA 0x6703…e6e8.
  • The orchestrator coordinates NFTX vault interactions, ApeCoin transfers, and DEX swaps, with value flows terminating at the EOA.

4.4. Concrete exploit sequence

The exploit consists of two EOA-initiated transactions:

  1. Priming transaction 0x8a61927598ce26f296ba1be155301418e2c01fe8f5585f9b46369e1ec5c408b0 (block 14403831):

    • From: EOA 0x6703…e6e8.
    • Calls BAYC.setApprovalForAll(0x3ebd3d86f810b141f9b2e9b15961fc66364b54f3, true).
    • Effect: Grants helper contract 0x3ebd3d… permission to move the EOA’s BAYC NFTs, including token ID 1060.
  2. Attack/profit transaction 0xeb8c3bebed11e2e4fcd30cbfc2fb3c55c4ca166003c7f7d319e78eaab9747098 (block 14403949):

    • From: EOA 0x6703…e6e8 (nonce 3, value 0).
    • Actions within the single transaction (from traces and receipts):
      • Deploys helper contract 0x3ebd3d….
      • Uses 0x3ebd3d… plus the prior approval to move BAYC token 1060 from the EOA into orchestrator 0x7797a9….
      • Deploys orchestrator 0x7797a9….
      • Interacts with NFTX vault 0xea47… to withdraw (or otherwise route) BAYC IDs 7594, 8214, 9915, 8167, 4755 such that 0x7797a9… temporarily becomes their owner.
      • Calls AirdropGrapesToken.claimTokens() from 0x7797a9…, causing the airdrop contract to:
        • Enumerate the six BAYC token IDs owned by 0x7797a9… via alpha.tokenOfOwnerByIndex(0x7797a9…, i),
        • Mark those IDs as claimed,
        • Transfer 60,564 ApeCoin from 0x025c6d… to 0x7797a9….
      • Forwards the ApeCoin and additional ETH proceeds to the EOA via orchestrator functions and DEX swaps.
      • Returns the five NFTX-origin BAYCs to the NFTX vault so that on-chain holdings at 14403949 again show the vault as owner of those five tokens, and BAYC 1060 also ends in the vault.

The net effect is that NFTX vault depositors lose the right to claim ApeCoin via 0x025c6d… for their BAYC token IDs, while the adversary cluster captures that ApeCoin and additional ETH.

5. Adversary Flow Analysis

This section details the adversary-related cluster and the on-chain flow realizing the ACT opportunity.

5.1. Adversary-related cluster

Evidence from traces, txlists, and decompiled code supports the following adversary cluster:

  • EOA: 0x6703741e913a30d6604481472b6d81f3da45e6e8
    • Initiates both the priming tx and the profit tx.
    • Receives ApeCoin and ETH proceeds at the end of the exploit.
  • Helper contract 1: 0x3ebd3d86f810b141f9b2e9b15961fc66364b54f3
    • Deployed by the EOA in the profit tx.
    • Uses setApprovalForAll from the prior priming tx to move BAYC 1060.
    • No additional activity outside the exploit transaction.
  • Orchestrator contract: 0x7797a99a2e91646abdc9dc30e838a149ccb3013b
    • Deployed and used only within the profit tx.
    • Decompile reveals routing of ApeCoin and ETH to 0x6703…e6e8 and an explicit require(msg.sender == 0x6703…e6e8) gate in a key function.

No evidence indicates that NFTX vault 0xea47…, DEX router, or other external contracts are controlled by the adversary; they act as permissionless liquidity and wrapper infrastructure.

5.2. ACT opportunity and inclusion feasibility

The entire strategy fits the ACT model:

  • Pre-state: Publicly observable state at block 14403948, including BAYC ownership and AirdropGrapesToken’s balances and configuration.
  • Strategy:
    1. Acquire BAYC exposure (here via prior deposits and NFTX interactions).
    2. Ensure the ability to move BAYC NFTs (EOA obtains and grants approvals).
    3. Use helper contracts to temporarily route BAYC NFTs into an adversary-controlled address.
    4. Call AirdropGrapesToken.claimTokens() from that address.
    5. Return NFTs and realize ApeCoin/ETH profit.
  • Inclusion feasibility: Both txs (0x8a61…408b0 and 0xeb8c3b…7098) are standard EOA transactions with normal gas usage, no privileged roles, and only calls to publicly deployed contracts with public ABIs or decompiled code. Any unprivileged searcher with comparable on-chain state and code access could construct equivalent calldata.
  • Deterministic success predicate: The profit condition is purely on-chain: the adversary cluster’s ApeCoin balance increases by 60,564 APE, and there is no ApeCoin outflow from the cluster in the same tx.

5.3. Profit computation

The balance_diff.json artifact for tx 0xeb8c3b…7098 shows:

// Seed: balance_diff.json for tx 0xeb8c3b…7098 (APE deltas)
{
  "erc20_balance_deltas": [
    {
      "token": "0x4d224452801aced8b2f0aebe155379bb5d594381",
      "holder": "0x025c6da5bd0e6a5dd1350fda9e3b6a614b205a1f",
      "before": "150000000000000000000000000",
      "after":  "149939436000000000000000000",
      "delta":  "-60564000000000000000000"
    },
    {
      "token": "0x4d224452801aced8b2f0aebe155379bb5d594381",
      "holder": "0x6703741e913a30d6604481472b6d81f3da45e6e8",
      "before": "0",
      "after":  "60564000000000000000000",
      "delta":  "60564000000000000000000"
    }
  ]
}
  • ApeCoin (token 0x4d2244…) decreases by 60,564 APE at the airdrop contract and increases by the same amount at the adversary EOA.
  • No other ApeCoin holders change balance in this transaction.

Native ETH/WETH deltas also show a net ETH gain for the EOA mediated via WETH and DEX interactions, but the ACT profit predicate can be stated purely in ApeCoin units:

  • Reference asset: ApeCoin (APE), 18 decimals.
  • Adversary cluster initial ApeCoin balance: 0.
  • Adversary cluster final ApeCoin balance: 60,564 APE.
  • Net APE profit: 60,564 APE.

Gas and ancillary costs are paid in ETH and do not diminish the ApeCoin-denominated profit predicate.

6. Impact & Losses

Direct on-chain loss.

  • AirdropGrapesToken 0x025c6d… transfers 60,564 ApeCoin from its reserves to the adversary EOA 0x6703…e6e8 in tx 0xeb8c3b…7098.
  • This ApeCoin corresponds to the per-token allocation for the six BAYC NFTs {1060, 7594, 8214, 9915, 8167, 4755} as encoded in AirdropGrapesToken’s distribution parameters.

Economic impact to stakeholders.

  • Five of the six BAYC NFTs (7594, 8214, 9915, 8167, 4755) are owned by the NFTX vault 0xea47… both before and after the tx, as shown by BAYC owner snapshots.
  • Those NFTs are economically owned by NFTX vault depositors who hold vault ERC20 shares; they never control 0x7797a9… or initiate the claim.
  • After the exploit, AirdropGrapesToken’s mappings mark all six BAYC token IDs as claimed. The vault depositors are therefore permanently unable to claim ApeCoin for their BAYCs via 0x025c6d…, and the value is captured by the adversary cluster.

The precise USD-denominated loss depends on external market prices for ApeCoin and BAYC at the time, but on-chain data deterministically records a transfer of 60,564 APE from the airdrop contract to the adversary cluster and the irreversible consumption of the corresponding claim rights for the affected BAYC token IDs.

7. References

  • Seed transaction and core artifacts

    • Ethereum mainnet tx 0xeb8c3bebed11e2e4fcd30cbfc2fb3c55c4ca166003c7f7d319e78eaab9747098 (attacker-profit).
    • artifacts/root_cause/seed/1/0xeb8c3bebed11e2e4fcd30cbfc2fb3c55c4ca166003c7f7d319e78eaab9747098/metadata.json.
    • artifacts/root_cause/seed/1/0xeb8c3bebed11e2e4fcd30cbfc2fb3c55c4ca166003c7f7d319e78eaab9747098/balance_diff.json.
    • artifacts/root_cause/seed/1/0xeb8c3bebed11e2e4fcd30cbfc2fb3c55c4ca166003c7f7d319e78eaab9747098/trace.cast.log.
  • AirdropGrapesToken contract

    • Contract: 0x025c6da5bd0e6a5dd1350fda9e3b6a614b205a1f (AirdropGrapesToken).
    • Verified source and deployment history: artifacts/root_cause/data_collector/iter_2/contract/1/0x025c6da5bd0e6a5dd1350fda9e3b6a614b205a1f/.
  • NFTX BAYC vault and BAYC ownership

    • NFTX vault: 0xea47b64e1bfccb773a0420247c0aa0a3c1d2e5c5 (NFTX BAYC vault).
    • Logs and tx history: artifacts/root_cause/data_collector/iter_3/contract/1/0xea47b64e1bfccb773a0420247c0aa0a3c1d2e5c5/.
    • BAYC owner snapshots for token IDs {1060, 7594, 8214, 9915, 8167, 4755} at blocks 14403948 and 14403949: artifacts/root_cause/data_collector/iter_3/other/bayc_owner_snapshots/owners_14403948_14403949.json.
  • Helper and orchestrator contracts

    • Helper: 0x3ebd3d86f810b141f9b2e9b15961fc66364b54f3 (created in the attacker-profit tx).
    • Orchestrator: 0x7797a99a2e91646abdc9dc30e838a149ccb3013b.
    • Decompile and ABI: artifacts/root_cause/data_collector/iter_2/contract/1/0x7797a99a2e91646abdc9dc30e838a149ccb3013b/decompile/.
    • Normal and internal txlists for helpers across analysis iterations, confirming single-use attack behavior.
  • Adversary EOA history

    • EOA: 0x6703741e913a30d6604481472b6d81f3da45e6e8.
    • Normal and internal txlists: artifacts/root_cause/data_collector/iter_1/address/1/0x6703741e913a30d6604481472b6d81f3da45e6e8/.
    • Priming tx 0x8a61927598ce26f296ba1be155301418e2c01fe8f5585f9b46369e1ec5c408b0 calling BAYC.setApprovalForAll(0x3ebd3d…, true).