Platform › Rewards › Battlepass

Rewards

The battlepass

The season pass and the daily THD pot: points, premium and where its payment goes, and claiming.

Version
1.5
Updated
2026-10-23
Source
THD contract sources · THD — Full Plan v1.43

Status#

The battlepass and the platform shop are the platform’s two THD sinks: the only places where a user spends THD and the supply falls. Everything else in the reward layer pays THD out. The battlepass is also the largest destination of the Spoils user pool — 5,000 bps of it at construction. See Spoils.

A season is a calendar quarter#

A season is a fixed calendar quarter beginning 1 January, 1 April, 1 July and 1 October, UTC. There is no season-duration constant and nothing an owner can move: the index is year × 4 + (quarter − 1), derived from block.timestamp by the same civil-date routine ProtocolFeeSplitter uses.

Constant or viewValue
MONTHS_PER_SEASON3
seasonDays(index)90, 91 or 92 — measured, not assumed. A fixed 90 would over-charge every buyer in a 92-day quarter by two days’ worth.
BACK_CLAIM_WINDOW30 days — 2,592,000 seconds, after seasonEnd
claimDeadline(index)seasonEnd(index) + 30 days

The two points boards: Spending and Earnings#

Two daily boards — both live before TGE, both paying from TGE
BoardRanks onYour standing
SpendingNet bonding-curve buying per launch plus prediction-market stakes, in any curve or market currencySpoils
EarningsThe fee-split income paid to creators and founding-crew members, KOLs and referrers, in any curve currencySpoils
code
// lib/points/formula.ts — dayPoints, once per board
a board's day points = the board's measure       // Spending, or Earnings
                       × (checked in that day ? 1 : 0)
                       × streak multiplier       // 1 + 0.5 × min(1, (days ÷ 20)^0.7)
                       × premium multiplier      // 1.25 while a pass is held, else 1
The inputs, each at event-time ETH value
InputWhat counts
SpendingNet bonding-curve buying (below) and prediction-market stakes in full
EarningsThe creator’s trade-fee share, the creator’s half of a KOL window that closed unallocated, founding-crew credits (and the creator’s remainder of a crew sync), KOL allocations and referrer credits — in ETH or in the launch’s ERC-20
Not earningsTrading profit, LP income, prediction winnings, settler THD and wheel prizes. Markets pay no fee split to a user
UnpricedAn amount with no posted price in force at its event is left out and counted, never taken at face value
Check-inA day with no check-in scores zero on both boards, whatever moved
Streak× 1 to × 1.5, flat from day 20, covered days included
Premium× 1.25 while a pass or a trial is held
Rank, season score and pot
RuleAs built
Daily rankPoints, descending. Equal points share a rank — 1, 2, 2, 4 — and the season points and pot share that go with it. Time decides nothing; tied rows are listed by address
Season scoreΣ max(0, 101 − rank) per board. Every shared rank of 100 or better scores, so ties at the cut can put more than 100 wallets in the money
Pot between the boardsSplit daily by fee value: the fees the day’s counted spending paid, against the fee income paid to the day’s earners. A board with nobody on it that day passes its share to the other
Pot inside a boardPro rata by points
Frozen daysEach closed UTC day is frozen once, with every event it read, and served at /api/v1/points/{spending|earnings}
The pot split, worked from the shared fee parameters
FigureValue
Curve volume, buys and sells10 ETH
Trade fee, 200 bps — Spending’s fee value0.2 ETH
Creator 3,000 bps + KOLs 4,000 bps + referrer 1,000 bps of the 3,000 bps ledger leg — Earnings’ fee value0.06 + 0.08 + 0.006 = 0.146 ETH
Split57.8% Spending · 42.2% Earnings
Curve spending — every trade counts, both legs
TradeCounts as spending
Buy 1 ETH+1
Sell for 1 ETH+1
Buy 1 ETH, sell for 1 ETH+2
Redeem from a curve that did not graduate0
Prediction stake 1 ETH+1
code
// lib/points/curve-volume.ts
spending = Σ ETH paid on curve buys + Σ ETH received on curve sells + Σ prediction stakes
// at event-time ETH value; graduated-pool trades never count

Premium, and where the payment goes#

Premium — the season pass
PropertyValue
CurrencyBefore TGE: ETH or any currency CurrencyRegistry lists, at the pass’s dollar price. After TGE: THD by default (burned, 1×), any other listed currency at 2×. Currency payments go to the marketing treasury and never meter.
Price$4.99 a month, always pro-rated; one purchase covers the rest of the season (owner ruling 15-10-2026). The monthly rate is PricingRules’ PREMIUM_PASS price in THD, read by the monthlyPrice of quotePremium and settable by the owner Safe; a change reaches future purchases only. The opening figure is $4.99 converted once at deploy through the TGE price (targetEthWei ÷ targetThd on the liquidity manager) and the posted ETH/USD — about 1,486 THD at ETH ≈ $2,686.
Calibration keymoney.premiumMonthlyPriceUsd — $4.99 on production, scaled on dev and staging together with the TGE ETH target, so the THD figure is the same everywhere.
ScopeOne pass per account per season; it ends when the season does, 00:00 UTC on 1 January, April, July or October. Days premium you already hold (a trial) covers are credited, and a season already covered to its end is refused (PremiumAlreadyCovered). Contracts deployed before 15-10-2026 sell one calendar month instead. buyPremiumAtMost buys for msg.sender and nobody else — gifting is refused by design.
Currency pricePricingRules PREMIUM_PASS for native ETH in USD ($4.99), converted at the posted price; a listed ERC-20 inherits it at its own posted feed. Sold by PremiumDesk.buyPremiumAtMost(currency, maxPrice), recorded on Battlepass.
What it grantsA 1.25× points multiplier, applied off chain, and the premium discount in the shop. Nothing else.

The price is pro-rated by day within each month of the season, over the days not already covered, and each month’s term rounds down, in the buyer’s favour:

code
// PremiumSeason.quote (Battlepass.quotePremium, PremiumDesk.quotePremium)
payFrom = today + daysCredited          // daysCredited: whole days your current premium covers
price   = sum over the season's 3 months m of
          monthlyPrice * payableDays(m) * (10_000 - discountBps)
          / (daysIn(m) * 10_000)
// payableDays(m) = days of m from payFrom to the season's end; a full season = 3 x the rate

Waiting therefore does not save money, it buys less. The one approved milestone discount is 5,000 bps — 50% off the season’s pass, at day 30, proved against a second Merkle root published per season, spent once a season, and reserving nothing.

Paid in THD, premium is deflation, not revenue. Paid in ETH or another listed currency it goes to the marketing treasury, to fund user acquisition. Neither ever reaches totalProtocolFeesReceived and neither unlocks THD. Trading contributes fees, which the meter counts; buying premium contributes burn, which the meter never sees. The two are not interchangeable and must never be added together. See THD and the fee meter.

Claiming, and the 25% late penalty#

Rewards are committed as one cumulative Merkle root per season, republishable as often as daily. Per-account progress is a running total in claimedOf[seasonIndex][account], and a claim pays the delta — which is what makes the root republishable without a per-claim bitmap. The leaf is keccak256(abi.encode(seasonIndex, account, cumulative)).

What a claim pays, by when it is made
WhenPaidBurned
Before the season endsThe whole deltaNothing
Within the 30-day back-claim window75%25% — BACK_CLAIM_BURN_BPS = 2500
After the deadlineNothing — claim revertsThe whole residue — burnExpired(season), callable by anyone
Every battlepass claim, since 28-09-2026
RuleValueRefusal
THD claim gatePassed once: the minimum earned or spent on the platform, and X connectedClaimGateNotPassed(account, claimGate)
VestNone—
Unclaimed after the back-claimBurned — totalSupply() fallsSeasonWindowClosed(season, closesAt, now)

totalThdBurned() is the three burn counters combined — totalPremiumBurned, totalLateClaimBurned and totalExpiredBurned. A root may never allocate more than the contract holds: publishRewards checks the new outstanding against available() plus the season’s existing outstanding, so an over-allocation reverts at publication rather than at the last claimant.

The battlepass keeper publishes both roots, bounded by the pot. A reward root becomes claimable 12 hours after it is queued (ROOT_ACTIVATION_DELAY), and the owner or guardian Safe may cancel it before then with vetoPendingRewards.

THD spent in the platform shop is the other burn. Shops, their prices and where every shop receipt goes: The shop.

Three things that must never be added#

Three design rules, not implementation notes:

NeverWhy
A reward that grants points directlyPoints already drive the daily share, the season standing and the band contribution. A reward denominated in points pays for the same behaviour twice. A tier jump is the same thing under another name, and was removed once already.
A bigger season-standing share for premiumZero-sum — it moves shares without growing the pot — and it stacks with the 1.25× multiplier into a compounding advantage.
Premium-only milestonesThe 3/7/14/30 ladder is the retention funnel for new users. Gating it behind payment removes the mechanic from the only cohort it exists for.

The 1.25× multiplier is the single permitted exception to activity-weighted rewards, and it is permitted because the payment is burned and it is capped: its value to the buyer decays to zero as adoption rises, at which point the whole cohort has burned THD for nothing but the burn. It is not a precedent for a second lever.