Methodology
DISCLAIMER // NFA // DYOR
This analysis is based on observations of the contract behavior. We are not smart contract security experts. This document aims to explain what the contract appears to do based on the code. It should not be considered a comprehensive security audit or financial advice. Always verify critical information independently and consult with blockchain security professionals for important decisions.
⊙ generated by robots | curated by humans
| METADATA | |
|---|---|
| Contract Address | 0x090D4613473dEE047c3f2706764f49e0821D256e (etherscan) |
| Network | Ethereum Mainnet |
| Analysis Date | 2026-03-19 |
Overview
This analysis was performed on a verified Solidity 0.6.11 contract (162 lines) implementing the Merkle Distributor pattern for the Uniswap UNI token airdrop. Because the source code is verified and the contract is minimal, the analysis was straightforward compared to unverified bytecode analyses.
The approach followed our standard four-phase methodology:
-
Phase 0 — Obtain the Contract: Fetched verified source code, ABI, and bytecode from Etherscan. Decoded constructor arguments to identify the token address (UNI) and Merkle root. Saved all artifacts to the local working directory.
-
Phase 1 — Discovery & Understanding: Identified the contract as a standalone, non-upgradeable implementation of the
IMerkleDistributorinterface. Cataloged all 5 functions (4 external, 1 private). Mapped the storage layout — one mapping with two immutable variables in bytecode. -
Phase 2 — Deep Dive Analysis: Analyzed each function's purpose, access control, preconditions, state changes, events, and failure cases. The
claim()function is the only state-changing function. Verified the checks-effects-interactions pattern and bitmap packing logic. -
Phase 3 — Risk & Trust Analysis: Assessed centralization (none), trust assumptions (Merkle root correctness, off-chain data availability), economic risks (locked unclaimed tokens), and external dependencies (UNI token contract).
Thought Process
%%{init: {'theme': 'base'}}%%
mindmap
root((MerkleDistributor<br/>Analysis))
Phase 0: Obtain
Etherscan API
Verified source code
Constructor arguments
Creation transaction
On-chain queries
Runtime bytecode
Token balance
Sample claim statuses
Transaction history
31,515+ total transactions
Claims still active in 2026
Phase 1: Discovery
Contract type
Standalone
Not upgradeable
No proxy pattern
Architecture
IMerkleDistributor interface
MerkleProof library
IERC20 dependency
Function catalog
claim — sole state changer
isClaimed — bitmap reader
merkleRoot — immutable getter
token — immutable getter
_setClaimed — private helper
Storage layout
Packed bitmap mapping
Immutables in bytecode
Phase 2: Deep Dive
claim function
Proof verification
Bitmap update before transfer
Checks-effects-interactions
Three revert conditions
Bitmap mechanics
256 claims per slot
Bitwise OR for setting
Bitwise AND for checking
Immutable pattern
Zero storage reads
Embedded in bytecode
Phase 3: Risk Assessment
No centralization
No owner
No admin functions
No upgrade mechanism
Trust assumptions
Merkle root correctness
Off-chain data availability
UNI token behavior
Economic observation
12.57M UNI unclaimed
No recovery mechanism
Locked indefinitely
Verification Guide
The following tools and resources were used to verify on-chain data during this analysis.
External Resources
| RESOURCE | PURPOSE |
|---|---|
| Etherscan Contract Page | Verified source code, ABI, constructor arguments, transaction history |
| Etherscan API v2 | Programmatic access to contract metadata, creation details, and transaction lists |
| Uniswap merkle-distributor GitHub | Original source repository and Merkle tree generation code |
Commandline Tools
Tip
Commands below use cast from the Foundry Toolkit. To run the commands below, you must set the RPC URL environment variable:
Verify Immutable Values
The token and merkleRoot are immutable variables embedded in the bytecode.
# VERIFY TOKEN ADDRESS
cast call 0x090D4613473dEE047c3f2706764f49e0821D256e "token()(address)"
# VERIFY MERKLE ROOT
cast call 0x090D4613473dEE047c3f2706764f49e0821D256e "merkleRoot()(bytes32)"
Check Claim Status
Query whether a specific index has been claimed.
# CHECK IF INDEX 0 IS CLAIMED (RETURNS false — UNCLAIMED)
cast call 0x090D4613473dEE047c3f2706764f49e0821D256e "isClaimed(uint256)(bool)" 0
# CHECK IF INDEX 1 IS CLAIMED (RETURNS true — CLAIMED)
cast call 0x090D4613473dEE047c3f2706764f49e0821D256e "isClaimed(uint256)(bool)" 1
# CHECK IF INDEX 100 IS CLAIMED
cast call 0x090D4613473dEE047c3f2706764f49e0821D256e "isClaimed(uint256)(bool)" 100
Verify Remaining Token Balance
Check how many UNI tokens remain unclaimed in the distributor contract.
# CHECK UNI BALANCE OF DISTRIBUTOR CONTRACT
cast call 0x1f9840a85d5af5bf1d1762f925bdaddc4201f984 "balanceOf(address)(uint256)" 0x090D4613473dEE047c3f2706764f49e0821D256e
# CONFIRM TOKEN NAME
cast call 0x1f9840a85d5af5bf1d1762f925bdaddc4201f984 "name()(string)"
Read Raw Bitmap Storage
Directly inspect the packed bitmap storage slots.
# COMPUTE STORAGE SLOT FOR claimedBitMap[0] (INDICES 0-255)
cast keccak "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
# READ THE BITMAP VALUE AT THAT SLOT
cast storage 0x090D4613473dEE047c3f2706764f49e0821D256e 0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5
Fetch Runtime Bytecode
Token Cost Breakdown
| PHASE | DESCRIPTION | TOKENS |
|---|---|---|
| Phase 0 | Obtain the Contract | 8 tok |
| Phase 1 | Discovery & Understanding | 8 tok |
| Phase 2 | Deep Dive Analysis | 10 tok |
| Phase 3 | Risk & Trust Analysis | 7 tok |
| Phase 4 | Documentation Generation | 12 tok |
| TOTAL | Complete Contract Analysis | 45 tok |
Note: Token costs are estimates based on typical conversation lengths and complexity. Actual consumption may vary by ±10-15% depending on API responses, iterative refinement, and verification steps.