Metrics reference
THD and the fee meter
The protocol fee meter, the unlock it drives, the eleven buckets, and why an unlock percentage is meaningless without its denominator.
protocolFeeMeterWei — the meter#
protocolFeeMeterWei| unit | wei |
|---|---|
| derivation | ProtocolFeeSplitter.totalProtocolFeesReceived() |
| source | contracts/src/launchpad/ProtocolFeeSplitter.sol |
| freshness | Live read. Monotonic — two write sites, both +=, no setter, no delete. |
| coverage | FULL, but incomplete as a revenue figure. See below. |
| id | Read | Why it must be visible |
|---|---|---|
protocolFeeLifetimeWei | lifetimeFeesReceived() | THE revenue figure. Every wei from every armed payer, ever. No ceiling throttles it and finalise() does not freeze it. |
protocolFeeRefusedByCeilingWei | totalRefusedByCeiling() | Arrived, paid out in full, refused by the count for good. Monotonic. Replaced protocolFeePendingWei on 21-09-2026. |
protocolFeeDailyWei | dailyFeesWei(day), keyed timestamp / EPOCH_SECONDS | Armed-payer receipts per UTC day. Drives the Spoils daily release, so it keeps recording after finalise(). |
protocolFeeUncountedWei | totalUncounted() | Wei from unregistered senders, or received after finalise(). Never counted, by design. |
protocolFeeCountedRemainingToday | countedRemainingThisEpoch() | How much of today’s ceiling is left. |
protocolFeeFinalised | finalised() | One-way; every later receipt routes uncounted. |
Both figures are monotonic and neither has a downward path — no setter, no delete, and no write-down function since discardPending(uint256,string) was removed on 21-09-2026. A published revenue figure that fell would be a bug in the publisher, never a fact about the chain.
Each receipt is one FeeRouted(from, counted, amount, toLiquidity, toTreasury, meterAfter, label), and meterAfter is the meter at that log — which makes the meter’s whole history re-derivable without a single archive state read. That matters on a chain whose public RPC keeps only 6,555 blocks of state — about thirty minutes on testnet 46630 and about eleven on mainnet 4663.
The unlock the meter drives#
// THDEmissionControl.totalUnlocked()
effectiveFees = min(feeSplitter.totalProtocolFeesReceived(), FEES_TO_FULL_EMISSION);
unlocked = effectiveFees × 1_000_000_000e18 / FEES_TO_FULL_EMISSION;
thdUnlockBps = unlocked × 10_000 / 1_000_000_000e18;| id | Read |
|---|---|
thdUnlockedBaseUnits | totalUnlocked() |
thdRemainingToUnlock | remainingToUnlock() |
thdFeesToCompletion | feesToCompletion() — ceiling division |
thdEmissionComplete | isComplete() — totalUnlocked() >= TOTAL_SUPPLY |
thdEmissionCompletedAt | emissionCompletedAt() — 0 until stampCompletion() |
thdOutstandingBaseUnits | totalOutstanding() — accrued and unclaimed across the eleven buckets |
There is no per-unlock event. The unlock is a pure function of the meter, so the event that records it is ProtocolFeeSplitter.FeeRouted.meterAfter.
thdSupply — total and circulating#
THD mints its entire supply to the emission contract at genesis and has no mint function, so:
totalSupply = THD.totalSupply() // 1e27 minus every burn
circulating = totalSupply
− THD.balanceOf(emission) // never claimed by a bucket
− Σ THDMerkleDistributor.unclaimedBalance()
− Σ (THDVestedClaim.totalAllocation() − released)
− Σ THDImmediateClaim.claimable()The bucket destinations come from the addresses file’s thdBuckets[]. There is no totalEmitted on the emission contract: total claimed is Σ claimedOf(bucket) over the eleven, or Σ Claimed.amount from logs.
The eleven buckets#
BUCKET_COUNT is 11 on every deployed THDEmissionControl, mainnet included. See the eleven allocations for what each is for and how it pays.
| Bucket | bps | THD |
|---|---|---|
| Traders | 2200 | 220,000,000 |
| Creators | 1500 | 150,000,000 |
| KOLs / Referrers | 1700 | 170,000,000 |
| Liquidity | 1200 | 120,000,000 |
| LP Providers | 1000 | 100,000,000 |
| Spoils | 1100 | 110,000,000 |
| BD / Marketing | 400 | 40,000,000 |
| Ecosystem Grants | 300 | 30,000,000 |
| Insurance Backstop | 300 | 30,000,000 |
| Team | 200 | 20,000,000 |
| Settlers | 100 | 10,000,000 |
A shareBps read on chain that disagrees with the addresses file means the file describes a different deployment, not drift. A bucket whose destination reads as the zero address is a recorded fact — some destinations are deliberately left unset until the Safe that will hold them exists, and those buckets refuse to pay until then.
The THD/ETH pool#
| id | Read |
|---|---|
thdPoolFeesToTreasuryWei | ThdLiquidityManager.totalEthToRouter |
thdPoolFeesFromPoolWei | ThdLiquidityManager.totalEthFromPool |
thdRoutedByPoolBaseUnits | ThdLiquidityManager.totalThdToRouter — sent, not yet destroyed. The burn count is ThdFeeRouter.totalThdBurned, beside the burn. |
Every collection is one FeesRouted(caller, ethToRouter, thdToRouter, router). Both legs go in full to the immutable ThdFeeRouter, which holds them until anybody flushes: the ETH on to thdPoolTreasury, the THD burned or recycled to ThdLpRewards according to the monthly holder vote. The ETH is not a protocol fee, it never passes the splitter, and it must never appear in feesProtocolWei.
The contract publishes an identity every balance figure must satisfy:
address(this).balance == totalEthReceived + totalEthFromPool − totalEthToPool − totalEthToRouterMetering the pool’s fees would let THD trading unlock THD, which is why they are excluded structurally rather than by policy. See fees, by leg.