Files
explore/ui/stats/lux/FeeSplitPanel.tsx
zeekay 5d1038aa06 the fee panel appears when there is something in it
While the split is off every figure in this panel is a zero, and it spent the
top of the explorer's landing page explaining why: a status tag reading "Not
active", two zero counters, a third card for the coinbase, and a line promising
a chart later. A reader arriving to look up a block met a paragraph about a
feature that is not running.

It renders when the split is active and not otherwise, which also deletes the
inactive/scheduled/unknown copy, the fallback styling and the empty-chart note —
the states existed only to describe an absence.
2026-08-10 21:18:00 -07:00

97 lines
3.9 KiB
TypeScript

// Fee burn and staking rewards, both read from ONE balance.
//
// creditTxFee credits floor(fee/2) to the fee reward vault and destroys the
// rest, so the vault's balance is simultaneously the rewards counter and the
// burn counter — they differ by at most one wei per transaction. That is why
// this panel needs no indexer, no event log and no receipts: one
// eth_getBalance is the whole data set.
//
// It renders only while the split is running. Before that every figure here is
// a zero, and a panel of zeros explaining why it is empty costs a reader the
// top of the page and tells them nothing they came for.
import { Heading } from '@luxfi/ui/heading';
import React from 'react';
import { useFeeSplit, burnedWei, toCoinSeries, FEE_REWARD_VAULT } from 'lib/api/cchain';
import { currencyUnits } from 'lib/units';
import { ChartWidget } from 'toolkit/components/charts/ChartWidget';
import { useChartsConfig } from 'ui/shared/chart/config';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import StatsWidget from 'ui/shared/stats/StatsWidget';
import NativeCoinValue from 'ui/shared/value/NativeCoinValue';
const ACCURACY = 4;
const FeeSplitPanel = () => {
const { reading, series, isLoading, isError } = useFeeSplit();
const chartsConfig = useChartsConfig();
const charts = React.useMemo(() => [ {
id: 'fee-vault',
name: 'Fee reward vault',
items: toCoinSeries(series ?? []),
charts: chartsConfig,
units: currencyUnits.ether,
} ], [ series, chartsConfig ]);
if (isError || reading?.status !== 'active') {
return null;
}
return (
<section className="mt-6 sm:mt-8">
<Heading level="3" className="text-sm mb-2">Fee burn &amp; staking rewards</Heading>
{ /*
sm/lg, never md: this repo redefines sm=415px and lg=1000px in
nextjs/global.css, and the generated `md` rules land AFTER `lg`, so
`md:grid-cols-2 lg:grid-cols-3` measured 682px 682px at 1440px — two
columns, with the third card orphaned on its own row.
*/ }
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<StatsWidget
label={ `${ currencyUnits.ether } burned` }
icon="flame"
value={ <NativeCoinValue amount={ burnedWei(reading) } accuracy={ ACCURACY } loading={ isLoading }/> }
isLoading={ isLoading }
hint={
`Supply destroyed by the fee split: the half of every fee that is credited to no account at all, ` +
`so it leaves the total supply. Equal to the vault balance to within one wei per transaction, and ` +
`never rounded up.`
}
/>
<StatsWidget
label="Staking rewards accrued"
icon="lightning"
value={ <NativeCoinValue amount={ reading.vaultWei } accuracy={ ACCURACY } loading={ isLoading }/> }
isLoading={ isLoading }
hint={
`The other half of every fee, parked in the protocol-owned fee reward vault for the P-Chain ` +
`staking-reward pool. Still in circulation — moved, never minted.`
}
/>
</div>
<div className="flex flex-wrap items-center gap-x-2 gap-y-1 mt-3 text-2xs text-[var(--color-text-secondary)]">
<span>Fee reward vault</span>
<AddressEntity address={{ hash: FEE_REWARD_VAULT }} noIcon noCopy truncation="constant" className="text-2xs"/>
<span>·</span>
<span>read at block #{ reading.blockNumber.toLocaleString() }</span>
</div>
<ChartWidget
className="h-[240px] mt-4"
title="Fee reward vault"
description="Vault balance, sampled once per block while this page is open. Nodes prune state, so no history can be backfilled."
charts={ charts }
isLoading={ isLoading }
isError={ false }
emptyText="Not enough samples yet — the vault is read once per new block while this page is open."
/>
</section>
);
};
export default React.memo(FeeSplitPanel);