We do not have a reliable USD price for the recorded assets yet.
0x12fe79f1de8aed0ba947cec4dce5d33368d649903cb45a5d3e915cc459e751fc0x11a4a5733237082a6c08772927ce0a2b5f8a86b6Ethereum0x8f1cece048cade6b8a05dfa2f90ee4025f4f2662EthereumOn Ethereum mainnet block 19835925, an unprivileged attacker exploited Galaxy Fox's claim contract 0x11a4a5733237082a6c08772927ce0a2b5f8a86b6 by replacing the active Merkle root that governed token claims. The attacker then submitted an empty-proof claim for an attacker-controlled allocation, extracted 1335339824388750000000000000 GFOX, and sold the tokens through the public GFOX/WETH Uniswap V2 market. The exploit transaction was 0x12fe79f1de8aed0ba947cec4dce5d33368d649903cb45a5d3e915cc459e751fc, and the attacker cluster realized 108739124855422458081 wei-equivalent of net profit after gas. The root cause is a broken authorization boundary: the deployed GfoxClaim runtime leaves setMerkleRoot(bytes32) permissionless even though claim() fully trusts merkleRoot() as the authorization source for token distribution.
GfoxClaim is the Galaxy Fox distribution contract at 0x11a4a5733237082a6c08772927ce0a2b5f8a86b6. The collected runtime analysis shows that it exposes owner(), claimStart(), merkleRoot(), claimedAmount(address), setMerkleRoot(bytes32), and , alongside other administrative functions such as and .
claim(address,uint256,bytes32[])setClaimStart(uint256)transferOwnership(address)The claim system is a Merkle-based token distribution. A claimant supplies (account, amount, proof), and claim() accepts the claim when the supplied proof matches the current merkleRoot(). Because the exploit root is keccak256(abi.encodePacked(account, amount)), an attacker can make an empty proof valid by installing a one-leaf tree where the root equals the leaf hash.
At the pre-state after block 19835924, the contract state was:
owner() = 0x4e6647a2bda8dfe75316a72e73586ecd24d0e700claimStart() = 1713491400merkleRoot() = 0xa1f89bcc4981b41883f84abcef1e8b44bc9d000f59abc86870892fda105d0abaGfoxClaim held 1780453099185000000000000000 GFOX before the exploit transactionThese values are confirmed by the on-chain contract analysis and seed artifacts for the exploit transaction.
The vulnerability is an access-control failure in the deployed GfoxClaim runtime. The contract treats merkleRoot as privileged state because every valid claim derives authority from that root, but setMerkleRoot(bytes32) does not enforce the owner check that guards other administrative entrypoints. As a result, any arbitrary caller can redefine the active claim set.
The violated invariant is straightforward: only the claim administrator should be able to define or rotate the Merkle root that authorizes GFOX claims. The code-level breakpoint is the runtime dispatch path for selector 0x7cb64759, corresponding to setMerkleRoot(bytes32), which writes storage slot 3 without executing the owner gate used by functions such as transferOwnership(address) and setClaimStart(uint256).
Once the attacker can set merkleRoot freely, claim(address,uint256,bytes32[]) no longer enforces an administrator-defined allocation list. The attacker chooses both the root and the leaf contents, installs a one-leaf tree for an attacker-controlled address, and then claims tokens using an empty proof. The remainder of the exploit is a routine liquidation of stolen tokens through public liquidity.
The exploit relies on the public pre-state at block 19835924, where claims were active and GfoxClaim still held a large GFOX balance. The collected runtime analysis states that setMerkleRoot(bytes32) is exposed in the deployed bytecode and that its dispatch path does not execute the owner check used by other administrative functions.
The exploit trace confirms the unauthorized state transition:
0x11A4a5733237082a6C08772927CE0a2B5f8A86B6::setMerkleRoot(
0xe8236ea3d247bd0c85596d7bb707f40ca374a6fa58d66ece1d80cdc2ac7ecbaf
)
@ storage slot 3:
0xa1f89bcc4981b41883f84abcef1e8b44bc9d000f59abc86870892fda105d0aba
-> 0xe8236ea3d247bd0c85596d7bb707f40ca374a6fa58d66ece1d80cdc2ac7ecbaf
This call came from helper contract 0x47c4b3144de2c87a458d510c0c0911d1903d1686, not from the owner. The same analysis computes that the new root equals:
keccak256(
abi.encodePacked(
0x47c4b3144de2c87a458d510c0c0911d1903d1686,
1780453099185000000000000000
)
)
That value defines a one-leaf Merkle tree. With the attacker-controlled root active, the helper immediately called:
0x11A4a5733237082a6C08772927CE0a2B5f8A86B6::claim(
0x47c4b3144de2C87a458D510C0c0911d1903d1686,
1780453099185000000000000000,
[]
)
The claim succeeded and updated claimedAmount(helper) to 1335339824388750000000000000, transferring that amount of GFOX out of GfoxClaim. The token balance diff for the victim contract shows the exact depletion:
{
"token": "0x8f1cece048cade6b8a05dfa2f90ee4025f4f2662",
"holder": "0x11a4a5733237082a6c08772927ce0a2b5f8a86b6",
"before": "1780453099185000000000000000",
"after": "445113274796250000000000000",
"delta": "-1335339824388750000000000000"
}
From that point, the authorization model is already broken. The subsequent swap is not a separate vulnerability; it is the monetization step that converts the unauthorized claim into liquid value.
The attack is a single-transaction ACT sequence executed in tx 0x12fe79f1de8aed0ba947cec4dce5d33368d649903cb45a5d3e915cc459e751fc.
0xfce19f8f823759b5867ef9a5055a376f20c5e454 submits a transaction that deploys helper contract 0x47c4b3144de2c87a458d510c0c0911d1903d1686.GfoxClaim::setMerkleRoot(...) with the attacker-chosen one-leaf root and successfully overwrites the live root despite not being the owner.GfoxClaim::claim(helper, 1780453099185000000000000000, []). Because the root now authorizes exactly that leaf, the empty proof is accepted and claimedAmount(helper) is updated to 1335339824388750000000000000.0x7a250d5630b4cf539739df2c5dacb4c659f2488d and swaps the claimed GFOX through the public GFOX/WETH market.0xfce19f8f823759b5867ef9a5055a376f20c5e454 and recipient 0x0899529af5a64125d000bc36f503de012463f094.Representative trace lines from the exploit transaction are:
0x11A4...86B6::setMerkleRoot(0xe8236e...ecbaf)
0x11A4...86B6::claim(0x47c4...1686, 1780453099185000000000000000, [])
0x7a250d...488D::swapExactTokensForTokensSupportingFeeOnTransferTokens(
1335339824388750000000000000,
0,
[GFOX, WETH],
0x47c4...1686,
1715335235
)
This is ACT because every step uses public entrypoints and public liquidity. No private key compromise, privileged role, or non-public dependency is required.
The direct loss to GfoxClaim was unauthorized distribution of 1335339824388750000000000000 GFOX. The attacker then sold the extracted tokens into the public market and realized 108739124855422458081 wei-equivalent of net profit after gas.
The measurable token loss is:
GFOX: 1335339824388750000000000000 raw units (18 decimals)The incident affected the Galaxy Fox claim contract and the intended recipients of the token distribution, because the claim reserve was depleted by an attacker-chosen allocation rather than an administrator-approved claim set.
0x12fe79f1de8aed0ba947cec4dce5d33368d649903cb45a5d3e915cc459e751fcGfoxClaim at 0x11a4a5733237082a6c08772927ce0a2b5f8a86b6GalaxyFox at 0x8f1cece048cade6b8a05dfa2f90ee4025f4f26620x92ee0df7f6b0674cabc9bfc64873786fa7be82d0setMerkleRoot, claim, and router swap