Parent: #877
Depends on: #878, #890
Scope
Script to finalize the campaign: compute TWAP, determine milestone, generate Merkle tree for distribution.
Built and tested during Phase 0 test run (#892), reused at actual campaign end (month 6).
Logic
1. Freeze & Calculate TWAP
- Query
pl_daily_prices for the last 7 days before campaign end
- Compute TWAP:
AVG(mcap_usd) over 7 days
- Determine milestone:
- mcap >= $70M → Gold (100%)
- mcap >= $10M → Silver (30%)
- mcap >= $1M → Bronze (10%)
- mcap < $1M → None (0%, full burn)
2. Compute Distribution
- Query
pl_points: SUM(points) GROUP BY address
- Compute each user's share:
user_points / total_points
- Calculate PLOT amount:
share × pool_amount × milestone_pct
- Output:
{ address: string, amount: bigint }[]
3. Generate Merkle Tree
- Use
@openzeppelin/merkle-tree to build tree from (address, amount) pairs
- Output: root hash + per-user proofs
- Store proofs in DB or JSON file for claim API
4. Deploy MerkleClaim Contract
contract PLOTAirdrop {
IERC20 public immutable PLOT;
bytes32 public immutable merkleRoot;
mapping(address => bool) public claimed;
function claim(uint256 amount, bytes32[] calldata proof) external {
require(!claimed[msg.sender], "Already claimed");
bytes32 leaf = keccak256(abi.encodePacked(msg.sender, amount));
require(MerkleProof.verify(proof, merkleRoot, leaf), "Invalid proof");
claimed[msg.sender] = true;
PLOT.transfer(msg.sender, amount);
}
}
Standard OpenZeppelin MerkleProof-based contract.
5. Transfer & Burn
- P7 calls
unlock() on Mint Club Locker to retrieve PLOT
- Transfer earned portion to MerkleClaim contract
- Send burn portion to
0x000000000000000000000000000000000000dEaD
Files
scripts/airdrop-finalize.ts
contracts/MerkleClaim.sol
Acceptance Criteria
Parent: #877
Depends on: #878, #890
Scope
Script to finalize the campaign: compute TWAP, determine milestone, generate Merkle tree for distribution.
Built and tested during Phase 0 test run (#892), reused at actual campaign end (month 6).
Logic
1. Freeze & Calculate TWAP
pl_daily_pricesfor the last 7 days before campaign endAVG(mcap_usd)over 7 days2. Compute Distribution
pl_points:SUM(points) GROUP BY addressuser_points / total_pointsshare × pool_amount × milestone_pct{ address: string, amount: bigint }[]3. Generate Merkle Tree
@openzeppelin/merkle-treeto build tree from (address, amount) pairs4. Deploy MerkleClaim Contract
Standard OpenZeppelin MerkleProof-based contract.
5. Transfer & Burn
unlock()on Mint Club Locker to retrieve PLOT0x000000000000000000000000000000000000dEaDFiles
scripts/airdrop-finalize.tscontracts/MerkleClaim.solAcceptance Criteria
pl_daily_pricespool_amount × milestone_pct