Epic: #1229 · Spec: §5.3, §8 Phase 6, R9, R21 · Type: OPERATOR · Estimate: 1 day · Merged: was T6.2 + T6.3 + T6.4 (RE1 round-9 consolidation)
Scope
Single settlement-day operator session. Branches on T6.1 (#1262) tier outcome:
- Normal branch: deploy MerkleClaim → unlock + fund pool → flip frontend env + announce
- Sub-Bronze branch: skip Merkle deploy → unlock + burn full 200K → announce sub-Bronze
Internal 🚦 PAUSE checkpoints between Steps preserve verification safety.
NORMAL BRANCH (FDV ≥ Bronze, T6.1 reported normal)
Step 1 — Deploy MerkleClaim contract
Pre-flight
🚨 Compute claimDeadline at deploy time (RE1 round-7)
Do NOT derive from CAMPAIGN_END. The 30-day claim window opens at Step 3 announce, not at campaign end.
Primary path (recommended): explicit intended claim-open timestamp
INTENDED_CLAIM_OPEN_TS=1755266400 # operator-confirmed unix ts for planned Step 3 announce
CLAIM_DEADLINE=$((INTENDED_CLAIM_OPEN_TS + ${CLAIM_WINDOW_DAYS} * 86400))
Acceptable shortcut (same-session ops only): if Steps 1-3 execute back-to-back within minutes:
CLAIM_DEADLINE=$(($(date +%s) + ${CLAIM_WINDOW_DAYS} * 86400))
CLAIM_WINDOW_DAYS from T2.1 config (default 30, confirmed at T6.1).
🚦 Delay safety rule (RE1 r28)
If Step 1 deploy happens but Steps 2 (fund) or 3 (announce) are materially delayed beyond INTENDED_CLAIM_OPEN_TS BEFORE funding (e.g., bonding-curve depth issue per R17 forces hours-long pause), the on-chain claimDeadline was computed assuming the announce moment — so the real claim window shrinks. Two acceptable responses:
(A) Redeploy with fresh deadline (preferred if delay > 6 hours): re-run Step 1 with a new INTENDED_CLAIM_OPEN_TS matching the new planned announce time. Operator gas cost (~$5-20 on Base) acceptable trade-off for full claim window. Old contract is abandoned (no funds transferred yet).
(B) Explicit shortened-window approval: operator comment in this issue stating "claim window shortened from N days to M days, accepted for [reason]". Funding proceeds with the original deadline. NOT recommended if M < CLAIM_WINDOW_DAYS / 2.
Document choice in this issue before funding (Step 2).
Deploy
forge create MerkleClaim \
--constructor-args $PLOT_TOKEN $MERKLE_ROOT $CLAIM_DEADLINE \
--rpc-url $BASE_RPC \
--private-key $DEPLOYER_KEY
Verify on Basescan
- Contract name reads as
MerkleClaim (not PLOTAirdrop)
- Constructor args match (token, root, deadline)
owner() returns deployer wallet
claimDeadline() matches computed value
- Source code verified
Record (Step 1)
- Deployed address →
NEXT_PUBLIC_MERKLE_CLAIM_ADDRESS
- Owner address → for T6.5 sweep authorization
claimDeadline value + human-readable date
- Which path used (explicit-ts or same-session shortcut)
- Tx hash
🚦 PAUSE — verify all Step 1 items before Step 2
Step 2 — Unlock 200K + transfer released_pool + burn rest
Milestone reference (per §4.3)
| Tier |
FDV |
Released |
Burned |
| 🥉 Bronze |
$100K |
10% (20K) |
90% (180K) |
| 🥈 Silver |
$1M |
30% (60K) |
70% (140K) |
| 🥇 Gold |
$5M |
50% (100K) |
50% (100K) |
| 💎 Diamond |
$10M |
100% (200K) |
0% |
Actions
- Call
unlock() on Locker (unlockTime ≤ now now)
- Transfer
released_pool PLOT to MerkleClaim address (from Step 1)
- Bronze 20K / Silver 60K / Gold 100K (NOT 60K) / Diamond 200K
- Transfer
(200K − released_pool) PLOT to dead address (0x000000000000000000000000000000000000dEaD)
- Record both tx hashes
Verify
- MerkleClaim contract balance equals
released_pool (via Basescan)
- Dead address received
200K − released_pool
- Operator wallet returns to pre-lock state (minus gas)
🚦 PAUSE — verify all Step 2 items before Step 3
Step 3 — Set env vars + announce settlement
Set production env vars
NEXT_PUBLIC_MERKLE_CLAIM_ADDRESS = Step 1 address
NEXT_PUBLIC_BURN_TX = Step 2 burn tx hash
Flips frontend /airdrop from Mining → Settlement state.
Test (production)
- 1 eligible test wallet successfully fetches Merkle proof + sees claim UI
- 1 non-eligible test wallet sees "you weren't eligible" message
Announce
- X post (@plotlinkxyz): settlement complete, tier reached, claim window 30 days, deadline date
- Farcaster post (@plotlink)
- Include: final FDV TWAP / milestone tier / released amount / burned amount / eligible recipient count / claim deadline date / link to
/airdrop
SUB-BRONZE BRANCH (FDV < $100K, T6.1 reported sub-Bronze)
Do NOT skip this ticket. The 200K burn execution lives here, not in T6.1 (#1262 is finalize/review only, not on-chain execution).
Sub-Bronze Step 1 — Skip Merkle deploy
No MerkleClaim contract deployment. No claims will be possible.
Sub-Bronze Step 2 — Unlock + burn full 200K
- Call
unlock() on Locker (unlockTime ≤ now now)
- Transfer full 200,000 PLOT to dead address (
0x000000000000000000000000000000000000dEaD)
- Record single burn tx hash
Verify
- 200,000 PLOT in dead address
- Operator wallet returns to pre-lock state (minus gas)
- Locker shows withdrawn
🚦 PAUSE — verify burn before Step 3 announce
Sub-Bronze Step 3 — Announce
- Do NOT set
NEXT_PUBLIC_MERKLE_CLAIM_ADDRESS (no contract to point at)
- Set
NEXT_PUBLIC_AIRDROP_FINAL_BURN_TX = Sub-Bronze Step 2 burn tx hash (RE1 r28: explicit settlement signal — without this, T3.1 cannot tell sub-Bronze from "campaign still in Mining" because both have MERKLE_CLAIM_ADDRESS=null)
- T3.1 reads this env: if set + non-empty, render Settlement (sub-Bronze final-burn variant) instead of Mining. T3.6 ClaimCard reads it too — shows the "watch for Season 2" message + burn tx deep link instead of claim button.
- X + Farcaster announcement: "Settlement complete. Sub-Bronze outcome — full 200K PLOT burned. Watch for Season 2." Link to burn tx on Basescan.
- Skip T6.5 entirely — no MerkleClaim contract exists, nothing to sweep
ZERO-RECIPIENT BRANCH (FDV ≥ Bronze BUT proof_count = 0, RE1 r29)
Triggered when T6.1 (#1262) reported zero-recipient (milestone tier hit but no wallet has positive weighted_spend — e.g., everyone activated but nobody bought storyline tokens).
Same on-chain mechanics as Sub-Bronze (skip Merkle deploy, burn full 200K), but distinct user-facing messaging since the milestone WAS reached.
Zero-Recipient Step 1 — Skip Merkle deploy
Same as Sub-Bronze. No MerkleClaim contract; no proofs to distribute.
Zero-Recipient Step 2 — Unlock + burn full 200K
Same actions as Sub-Bronze Step 2:
- Call
unlock() on Locker
- Transfer full 200,000 PLOT to dead address (
0x000000000000000000000000000000000000dEaD)
- Record single burn tx hash
🚦 PAUSE — verify burn before Step 3.
Zero-Recipient Step 3 — Announce
- Do NOT set
NEXT_PUBLIC_MERKLE_CLAIM_ADDRESS (no contract)
- Set
NEXT_PUBLIC_AIRDROP_FINAL_BURN_TX = Zero-Recipient Step 2 burn tx hash (same settlement signal as Sub-Bronze — T3.1 renders Settlement)
- Set
NEXT_PUBLIC_AIRDROP_FINAL_STATE=zero_recipient (RE1 r29 — optional, distinguishes copy from sub-Bronze if T3.6 wants distinct messaging). If T3.6 keeps unified "watch for Season 2" copy, this env is not required.
- X + Farcaster announcement (distinct from sub-Bronze copy):
- "Settlement complete. [Tier] reached ($X.X FDV), but no eligible recipients had storyline-token spend > 0 — full 200K PLOT burned. Watch for Season 2."
- Link burn tx on Basescan
- Skip T6.5 entirely — no contract to sweep
Why distinct branch (not just sub-Bronze)
- Marketing transparency: sub-Bronze = "community failed to push FDV"; zero-recipient = "community pushed FDV but didn't engage with storyline trading". Different operator lessons.
- Audit clarity: post-mortem can distinguish two failure modes
- Future iteration: if zero-recipient happens, consider tweaking MIN_REFERRAL_THRESHOLD or activation gating in v6
Overall acceptance (updated for 3 branches)
Dependencies
T2.7 (#1267 — contract code), T6.1 (#1262 — Merkle root + tier outcome + CLAIM_WINDOW_DAYS confirmation)
History
Originally three tickets: #1263 (deploy) + #1264 (fund/burn) + #1265 (env/announce). Merged at RE1 round-9. Sub-Bronze branch initially routed burn to #1262 by mistake (caught by RE1 round-10) — burn execution now correctly lives here in the Sub-Bronze branch. #1264 + #1265 closed with reference to this issue.
Epic: #1229 · Spec: §5.3, §8 Phase 6, R9, R21 · Type: OPERATOR · Estimate: 1 day · Merged: was T6.2 + T6.3 + T6.4 (RE1 round-9 consolidation)
Scope
Single settlement-day operator session. Branches on T6.1 (#1262) tier outcome:
Internal 🚦 PAUSE checkpoints between Steps preserve verification safety.
NORMAL BRANCH (FDV ≥ Bronze, T6.1 reported normal)
Step 1 — Deploy MerkleClaim contract
Pre-flight
🚨 Compute claimDeadline at deploy time (RE1 round-7)
Do NOT derive from
CAMPAIGN_END. The 30-day claim window opens at Step 3 announce, not at campaign end.Primary path (recommended): explicit intended claim-open timestamp
Acceptable shortcut (same-session ops only): if Steps 1-3 execute back-to-back within minutes:
CLAIM_DEADLINE=$(($(date +%s) + ${CLAIM_WINDOW_DAYS} * 86400))CLAIM_WINDOW_DAYSfrom T2.1 config (default 30, confirmed at T6.1).🚦 Delay safety rule (RE1 r28)
If Step 1 deploy happens but Steps 2 (fund) or 3 (announce) are materially delayed beyond
INTENDED_CLAIM_OPEN_TSBEFORE funding (e.g., bonding-curve depth issue per R17 forces hours-long pause), the on-chainclaimDeadlinewas computed assuming the announce moment — so the real claim window shrinks. Two acceptable responses:(A) Redeploy with fresh deadline (preferred if delay > 6 hours): re-run Step 1 with a new
INTENDED_CLAIM_OPEN_TSmatching the new planned announce time. Operator gas cost (~$5-20 on Base) acceptable trade-off for full claim window. Old contract is abandoned (no funds transferred yet).(B) Explicit shortened-window approval: operator comment in this issue stating "claim window shortened from N days to M days, accepted for [reason]". Funding proceeds with the original deadline. NOT recommended if M < CLAIM_WINDOW_DAYS / 2.
Document choice in this issue before funding (Step 2).
Deploy
Verify on Basescan
MerkleClaim(notPLOTAirdrop)owner()returns deployer walletclaimDeadline()matches computed valueRecord (Step 1)
NEXT_PUBLIC_MERKLE_CLAIM_ADDRESSclaimDeadlinevalue + human-readable date🚦 PAUSE — verify all Step 1 items before Step 2
Step 2 — Unlock 200K + transfer released_pool + burn rest
Milestone reference (per §4.3)
Actions
unlock()on Locker (unlockTime ≤ nownow)released_poolPLOT to MerkleClaim address (from Step 1)(200K − released_pool)PLOT to dead address (0x000000000000000000000000000000000000dEaD)Verify
released_pool(via Basescan)200K − released_pool🚦 PAUSE — verify all Step 2 items before Step 3
Step 3 — Set env vars + announce settlement
Set production env vars
NEXT_PUBLIC_MERKLE_CLAIM_ADDRESS= Step 1 addressNEXT_PUBLIC_BURN_TX= Step 2 burn tx hashFlips frontend
/airdropfrom Mining → Settlement state.Test (production)
Announce
/airdropSUB-BRONZE BRANCH (FDV < $100K, T6.1 reported sub-Bronze)
Do NOT skip this ticket. The 200K burn execution lives here, not in T6.1 (#1262 is finalize/review only, not on-chain execution).
Sub-Bronze Step 1 — Skip Merkle deploy
No
MerkleClaimcontract deployment. No claims will be possible.Sub-Bronze Step 2 — Unlock + burn full 200K
unlock()on Locker (unlockTime ≤ nownow)0x000000000000000000000000000000000000dEaD)Verify
🚦 PAUSE — verify burn before Step 3 announce
Sub-Bronze Step 3 — Announce
NEXT_PUBLIC_MERKLE_CLAIM_ADDRESS(no contract to point at)NEXT_PUBLIC_AIRDROP_FINAL_BURN_TX= Sub-Bronze Step 2 burn tx hash (RE1 r28: explicit settlement signal — without this, T3.1 cannot tell sub-Bronze from "campaign still in Mining" because both haveMERKLE_CLAIM_ADDRESS=null)ZERO-RECIPIENT BRANCH (FDV ≥ Bronze BUT
proof_count = 0, RE1 r29)Triggered when T6.1 (#1262) reported zero-recipient (milestone tier hit but no wallet has positive weighted_spend — e.g., everyone activated but nobody bought storyline tokens).
Same on-chain mechanics as Sub-Bronze (skip Merkle deploy, burn full 200K), but distinct user-facing messaging since the milestone WAS reached.
Zero-Recipient Step 1 — Skip Merkle deploy
Same as Sub-Bronze. No MerkleClaim contract; no proofs to distribute.
Zero-Recipient Step 2 — Unlock + burn full 200K
Same actions as Sub-Bronze Step 2:
unlock()on Locker0x000000000000000000000000000000000000dEaD)🚦 PAUSE — verify burn before Step 3.
Zero-Recipient Step 3 — Announce
NEXT_PUBLIC_MERKLE_CLAIM_ADDRESS(no contract)NEXT_PUBLIC_AIRDROP_FINAL_BURN_TX= Zero-Recipient Step 2 burn tx hash (same settlement signal as Sub-Bronze — T3.1 renders Settlement)NEXT_PUBLIC_AIRDROP_FINAL_STATE=zero_recipient(RE1 r29 — optional, distinguishes copy from sub-Bronze if T3.6 wants distinct messaging). If T3.6 keeps unified "watch for Season 2" copy, this env is not required.Why distinct branch (not just sub-Bronze)
Overall acceptance (updated for 3 branches)
MerkleClaim; deadline computed correctly; unlock+fund+burn verified; env vars set; frontend flipped; announcements posted; all 3 🚦 PAUSE checkpoints respectedNEXT_PUBLIC_AIRDROP_FINAL_BURN_TXset; announcement posted; T6.5 marked skipped in [T6.5] At claimDeadline: sweep unclaimed to dead address #1266NEXT_PUBLIC_AIRDROP_FINAL_BURN_TXset; distinct copy in announcement (milestone-reached-but-no-spend, not sub-Bronze failure); T6.5 marked skipped in [T6.5] At claimDeadline: sweep unclaimed to dead address #1266Dependencies
T2.7 (#1267 — contract code), T6.1 (#1262 — Merkle root + tier outcome + CLAIM_WINDOW_DAYS confirmation)
History
Originally three tickets: #1263 (deploy) + #1264 (fund/burn) + #1265 (env/announce). Merged at RE1 round-9. Sub-Bronze branch initially routed burn to #1262 by mistake (caught by RE1 round-10) — burn execution now correctly lives here in the Sub-Bronze branch. #1264 + #1265 closed with reference to this issue.