mirror of
https://github.com/luxfi/explore.git
synced 2026-10-30 16:57:53 +00:00
Zoo's home rendered no P-Chain panel but still sent the validators, chain list and height reads behind the Lux panels, because the hooks ran in the page body. The metrics strip and the chain-health grid are now components the Lux explorer alone renders, and the reads live inside them. The stats page's chain count, shown only on Lux, reads the chain list the same way. NetworkOverview.spec.tsx asserts a brand host sends no P-Chain request and the Lux host sends all three; putting the reads back in the page body fails it. Claude-Session: https://claude.ai/code/session_011Ge9UnfkEK37Lapx5nfPQK
108 lines
3.1 KiB
TypeScript
108 lines
3.1 KiB
TypeScript
// Network-level stats: validators, chains, stake, uptime.
|
|
// Displayed above the standard Explorer chain stats on the stats page.
|
|
|
|
import { Skeleton } from '@luxfi/ui/skeleton';
|
|
import React from 'react';
|
|
|
|
import { getPChain, isPrimaryNetworkExplorer } from 'configs/app/chainRegistry';
|
|
import { useBlockchains, useCurrentValidators } from 'lib/api/pchain';
|
|
|
|
// ── Constants ──
|
|
|
|
const PRIMARY_NETWORK_ID = '11111111111111111111111111111111LpoYY';
|
|
const PRIMARY_CHAIN_COUNT = 14;
|
|
// nLUX is NANO-LUX: 10^9 nLUX = 1 LUX. This read 6, inflating every LUX figure
|
|
// by 1000 — the validators page showed 2.5T LUX staked against a ~2T supply.
|
|
const LUX_DECIMALS = 9;
|
|
|
|
// ── Helpers ──
|
|
|
|
function formatStake(nanoLux: bigint): string {
|
|
const lux = Number(nanoLux) / Math.pow(10, LUX_DECIMALS);
|
|
if (lux >= 1_000_000_000) return `${ (lux / 1_000_000_000).toFixed(1) }B`;
|
|
if (lux >= 1_000_000) return `${ (lux / 1_000_000).toFixed(1) }M`;
|
|
if (lux >= 1_000) return `${ (lux / 1_000).toFixed(1) }K`;
|
|
return lux.toFixed(0);
|
|
}
|
|
|
|
// ── Stat card ──
|
|
|
|
interface StatCardProps {
|
|
readonly label: string;
|
|
readonly value: string;
|
|
readonly isLoading: boolean;
|
|
}
|
|
|
|
const StatCard = ({ label, value, isLoading }: StatCardProps) => (
|
|
<div
|
|
|
|
>
|
|
<span className="text-[var(--color-text-secondary)]">
|
|
{ label }
|
|
</span>
|
|
<Skeleton loading={ isLoading }>
|
|
<span className="text-[var(--color-text-primary)]">
|
|
{ value }
|
|
</span>
|
|
</Skeleton>
|
|
</div>
|
|
);
|
|
|
|
// ── Chain count ──
|
|
|
|
// The Lux primary network's chain list, which a brand explorer does not show,
|
|
// so only the Lux explorer renders this and reads the list.
|
|
const ChainCount = () => {
|
|
const { blockchains, isLoading } = useBlockchains();
|
|
|
|
const l1Count = React.useMemo(
|
|
() => blockchains.filter((c) => c.netID !== PRIMARY_NETWORK_ID).length,
|
|
[ blockchains ],
|
|
);
|
|
|
|
return (
|
|
<StatCard
|
|
label="Total Chains"
|
|
value={ String(PRIMARY_CHAIN_COUNT + l1Count) }
|
|
isLoading={ isLoading }
|
|
/>
|
|
);
|
|
};
|
|
|
|
// ── Main component ──
|
|
|
|
const NetworkStats = () => {
|
|
const { stats, isLoading, isKnown: hasValidatorData } = useCurrentValidators();
|
|
const pChain = getPChain();
|
|
|
|
return (
|
|
<div>
|
|
{ /* Named for whose P-Chain it is: on an L2 brand these are Lux's figures. */ }
|
|
<span className="text-[var(--color-text-secondary)]">
|
|
{ pChain?.name }
|
|
</span>
|
|
<div
|
|
|
|
>
|
|
{ isPrimaryNetworkExplorer() && <ChainCount/> }
|
|
<StatCard
|
|
label="Validators"
|
|
value={ hasValidatorData ? String(stats.validatorCount) : '\u2014' }
|
|
isLoading={ isLoading }
|
|
/>
|
|
<StatCard
|
|
label="Total Stake"
|
|
value={ hasValidatorData ? `${ formatStake(stats.totalStake) } ${ pChain?.symbol }` : '\u2014' }
|
|
isLoading={ isLoading }
|
|
/>
|
|
{ /*
|
|
Connected / Avg Uptime intentionally omitted: they are the public API node's
|
|
view of its peers, not a network fact. Only chain-verifiable metrics are shown.
|
|
*/ }
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default React.memo(NetworkStats);
|