# PMINTHolderAirdrop Review Notes ## Gas Review - Merkle proofs keep claims O(log n), so the contract does not store the holder list on-chain. - `pFLY`, `pMINT`, and `snapshotBlock` are immutable, reducing repeated storage reads. - Claim proofs use calldata and a single `hasClaimed` mapping write per wallet. - The contract enforces one claim per address, a global `1,050,000 pFLY` allocation cap, a per-wallet cap, a minimum claim amount, and a minimum pMINT snapshot balance before token transfer. - Merkle leaves include `account`, `snapshotPmintBalance`, and `amount`, so a proof binds the claim to a specific pMINT holder balance at the published snapshot. - `stakeAndClaim()` uses exact temporary approval to the staking contract, then resets allowance to zero after the stake call. - Revert strings are used to match the repo's current testing style. If final mainnet bytecode needs further squeezing, custom errors are the next obvious gas reduction. ## Security Review Comments - The Merkle snapshot is intentionally off-chain. The published snapshot package must include the pMINT snapshot block, excluded addresses, claim JSON, Merkle root, and generation settings. - On PulseChain mainnet, constructor deployment rejects any pMINT token other than `0xFf640cBd35A618Df1348D861B5e47f7eaB05b422`. - Required exclusions are handled in `scripts/generatePMINTHolderAirdropSnapshot.js`: dead wallet, LP pair contracts, treasury wallets, team wallets, staking contracts, and any extra excluded addresses. - Unknown multi-wallet ownership cannot be proven on-chain. The snapshot tool therefore supports `PMINT_SYBIL_CLUSTER_FILE` or the shared `SYBIL_CLUSTER_FILE`, which groups known linked wallets, computes `sqrt(totalClusterPmintBalance)`, applies the cap per cluster, then splits that cluster allocation across its wallets by pMINT balance. - Fairness still depends on snapshot timing and review quality. Use an unannounced snapshot block, meaningful `MIN_PMINT_BALANCE`, a conservative `MAX_CLAIM_PER_WALLET`, and a public exclusion/cluster list before publishing the root. - The Merkle root can only be changed before the first claim, which avoids changing eligibility after users begin claiming. - The owner can extend the claim deadline after claims start, but cannot shorten it. - `recoverNonPflyToken()` cannot recover pFLY, so the airdrop allocation cannot be pulled by generic owner recovery. - `recoverExcessPfly()` can only recover pFLY above `AIRDROP_ALLOCATION - totalClaimed`, which protects the remaining claim reserve while allowing accidental overfunds to be returned. - Expired unclaimed pFLY is split 50/50: half to the burn address and half to the staking reserve. This can only run once after the claim deadline. - `stakeAndClaim()` requires a staking contract with `stakeFor(address,uint256,uint16)`. The local `PMINTFlywheelStaking` now includes this so the position belongs to the claimant, not the airdrop contract. - Supported stake locks are exactly 180, 555, and 5,555 days. - This is an internal scoped review and test pass, not a formal third-party audit. Before mainnet promotion, review the final snapshot file, excluded wallets, owner address, staking reserve, burn address, and funded pFLY transaction.