Platform › Rewards › The fee meter and unlock

Rewards

The fee meter and unlock

What counts toward the meter, the daily ceiling, how a counted fee becomes unlocked THD, and how to re-derive the rate.

Version
1.0
Updated
2026-10-14
Source
THD contract sources · Launchpad contract sources · THD — Full Plan v3.45 · Calibration — shared ratios

THD unlocks against protocol fees, and only against fees ProtocolFeeSplitter counts. The count is one monotonic number, totalProtocolFeesReceived, with no setter. THDEmissionControl reads it live; nothing is pushed to it.

Status#

On Robinhood Chain mainnet
ContractStatus
ProtocolFeeSplitterDeployed
THDEmissionControl — reads that splitter as an immutableDeployed
FeeConverterDeployed
THD’s poolNot seededTHD has not launched

What counts#

A receipt moves the meter only when its sender is a registered and armed payer. Two keys are needed to add one, and neither can do it alone.

Payer states
SenderSet byReceipt
Unregistered—Paid wholly to the treasury. Counted nowhere; recorded in totalUncounted.
Registered, disarmedOwner Safe, setPayer — always leaves the payer disarmedRefused (PayerIsDisabled). The fee waits upstream in the payer.
Registered, armedGuardian Safe, enablePayerCounted, up to the day’s ceiling, then split.
Which money is a protocol fee
Test, in orderRule
Who chooses the amount?If whoever pays can size it at will, it never meters, whatever its destination.
Which treasury is it destined for?The protocol treasury makes it a protocol fee. Any other treasury TreasuryRegistry lists is paid directly and never meters.
Which contract pays it?Exactly one registered, armed payer per revenue line.
What does that payer accept, and from whom?A payer the public can pay through is never armed: it would let anyone buy THD unlock.
Revenue that meters
RevenueCounted as
Curve and graduated-pool trade fees, protocol share, and the launch taxETH as received; an ERC-20 as the ETH FeeConverter realised
A lapsed KOL window’s platform halfThe remainder after the launch’s referrer is paid their rate, through ReferralLedger
Launch-creation fee, protocol legETH as received; a raise currency as the ETH it realised
Market-creation fee and surge, protocol legs; market settlement cutETH as received; an ERC-20 as the ETH it realised
Internal coup feeETH as received
Settlement-currency listing feeThe ETH it realised
An ERC-20 fee: FeeConverter
StepRule
ReceiptreceiveFee accepts only from a registered fee source and credits the measured balance change to attributedOf[token]. A token sent any other way is never converted.
Saleconvert is permissionless. It sells along a listed route to native ETH and refuses any sale that realises less than the posted-price value less maxDeviationBps (bounded on chain to 10–1,000 bps). A posted price older than MAX_PRICE_AGE, one hour, defers the sale.
PaymentThe ETH goes to ProtocolFeeSplitter as an ordinary payer. The converter refuses to convert (SplitterWouldNotCountPayment) if the splitter would not count it.
Conversion switched offThe token goes to the protocol treasury unconverted and uncounted.

ProtocolFeeSplitter itself holds no ERC-20 logic: only native ETH reaches it.

What never meters#

Revenue that never reaches the meter
RevenuePaid toWhy it does not meter
Promoted slots, featured placements, shop-opening feesMarketing treasury, directSold visibility, not a protocol fee
Sponsored-drop fee, both legsMarketing treasury, directThe sponsor sizes the drop
Premium bought in ETH or a listed currencyMarketing treasury, directA product, and the buyer decides to buy
Premium bought in THDBurnedTHD never unlocks THD
THD/ETH pool LP fees, ETH legTHD pool treasury, directEarned by trading THD
Creator milestone sales, platform 25%Protocol treasury, directThe creator sets the price and pack size
MarketSeeder’s recovered opening stakesProtocol treasury, directStrangers’ stakes decide the size
Auto-payout feeManagement treasury, directSized by the user’s own earnings
A launch fee paid in THD90% burned, 10% to the Fortune WheelTHD never unlocks THD
ETH sent to the splitter by anyone elseProtocol treasuryNot a registered payer

The daily ceiling#

The epoch ceiling
ItemValue
maxCountedPerEpochConstructor immutable, from money.protocolFeeMaxCountedPerEpochWei — 2 ETH on production
EPOCH_SECONDS1 day; epoch = timestamp / 86,400, a fixed UTC day
Above the day’s headroomPaid out in full; the excess is never counted and is added to totalRefusedByCeiling, with CeilingRefusedCount emitted
Carry to a later dayNone — no call recovers a refused count
ReadscountedThisEpochNow(), countedRemainingThisEpoch(), currentEpochEndsAt()
Three counters on the splitter
CounterThrottled by the ceiling?Stops at finalise?Drives
totalProtocolFeesReceivedYesYesThe THD unlock
dailyFeesWei(day)NoNoThe Spoils daily release
lifetimeFeesReceivedNoNoThe band cap

Where a counted receipt goes#

Every armed receipt is paid out in full, whatever the ceiling counted. The split applies to the whole receipt; donations are never split.

code
// ProtocolFeeSplitter.receive(), armed payer
toLiquidity = amount * share / 10_000          // while a manager is set, until its target is met, never after finalise
toTreasury  = amount - toLiquidity
toSeason    = (part of toTreasury above this month's opex threshold) * seasonShareBps / 10_000
toTreasury -= toSeason
The legs
LegRuleProduction
LiquidityliquidityShareBps (or the payer’s own share) of the receipt to ThdLiquidityManager, capped at its remaining targetEthWei. Cap on every share: MAX_LIQUIDITY_SHARE_BPS = 5,000. A share may not be lowered while the manager is unseeded.5,000 bps · target 75 ETH
SeasonseasonShareBps of the treasury leg above the month’s opexThresholdWei, measured on treasuryIntakeInMonth per UTC calendar month and split at the exact wei. Paid to seasonPool; inert while it is unset. Cap: MAX_SEASON_SHARE_BPS = 5,000. A change applies from the next month.2,500 bps above 25 ETH a month
TreasuryThe rest, to treasury, re-checked against TreasuryRegistry on every payment (TreasuryNoLongerListed).—

From counted fees to unlocked THD#

code
// THDEmissionControl.totalUnlocked()
effective = min( feeSplitter.totalProtocolFeesReceived(), FEES_TO_FULL_EMISSION );
unlocked  = effective * TOTAL_SUPPLY / FEES_TO_FULL_EMISSION;      // TOTAL_SUPPLY = 1,000,000,000 THD
Parameters
ParameterSourceProduction
TOTAL_SUPPLYConstant1,000,000,000 THD
FEES_TO_FULL_EMISSIONConstructor immutable, from money.feesToFullEmissionWei. No setter.150 ETH of counted fees
Shortest possible emissionFEES_TO_FULL_EMISSION / maxCountedPerEpoch75 days
Reads on THDEmissionControl
FunctionReturns
totalUnlocked()THD unlocked so far
remainingToUnlock()TOTAL_SUPPLY − totalUnlocked()
feesToCompletion()Counted wei still needed, rounded up
isComplete()totalUnlocked() >= TOTAL_SUPPLY
claimableOf(bucket)A bucket’s accrued share less what claim(bucket) already paid its beneficiary

Each unlock is split across the eleven allocations at their fixed shares — see the eleven allocations. claim(bucket) is permissionless and pays only the bucket’s beneficiary.

Re-deriving the rate#

The THD-per-ETH rate of the unlock is derived from two live reads on THDEmissionControl, never transcribed:

code
rate (THD per counted ETH) = TOTAL_SUPPLY() / FEES_TO_FULL_EMISSION()

cast call <THDEmissionControl> "TOTAL_SUPPLY()(uint256)"          --rpc-url <rpc>
cast call <THDEmissionControl> "FEES_TO_FULL_EMISSION()(uint256)" --rpc-url <rpc>

The quotient is a repeating decimal, so any written figure is rounded. A screen may round it for display; the exact value is always these two reads. FEES_TO_FULL_EMISSION is per deployment, so a testnet rate is a rate against a different denominator. The address is on contract addresses; the meter’s published metrics are on THD and the fee meter.