Files
zeekay 0ac8927c3d The P-Chain is read through typed GETs, from the P-Chain that secures the chain
luxd serves no P-Chain JSON-RPC, so every platform.* read the explorer sent
answered 404 and the validators, chains and heights widgets were empty on Lux
and Zoo. Each read is now GET /v1/chain/P/ops/{validators,blockchains,height},
coded against the node's OpenAPI 3.1 document, through GET
/v1/node/p-chain?op=<op>. The proxy's fixed table allows those three ops and one
NodeID parameter; a POST there answers 405.

ChainEntry.pChain is where the P-Chain that secures a chain is read: Lux reads
its own; Zoo, an L2 carried by Lux's validator set, reads Lux's and never its
own node's; Hanzo, on its own committee until hanzod joins as an L2, has none.
P-Chain UI names whose it is ("Lux primary network validators securing Zoo
Network") and prices stake in LUX. The cross-network validator sum is gone: it
counted Lux's validators again under Zoo.

The top bar shows a link only where its page has content: Chains, Bridge and
DEX on the Lux C-Chain host (their pages now 404 elsewhere), Validators where a
P-Chain secures the chain, Stats where there is a P-Chain panel or a stats
service. A P-Chain at genesis renders height #0 rather than nothing. The chain
page's uptime is no longer multiplied by 100.

Claude-Session: https://claude.ai/code/session_011Ge9UnfkEK37Lapx5nfPQK
2026-09-23 20:04:11 -07:00

139 lines
4.1 KiB
TypeScript

import { Input } from '@luxfi/ui/input';
import { Skeleton } from '@luxfi/ui/skeleton';
import React from 'react';
import { getPChain } from 'configs/app/chainRegistry';
import type { PChainValidator } from 'lib/api/pchain';
import { formatStake, formatUptime, truncateNodeId } from './utils';
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------
interface ValidatorsListProps {
readonly validators: ReadonlyArray<PChainValidator>;
readonly isLoading: boolean;
}
const ValidatorsList = ({ validators, isLoading }: ValidatorsListProps) => {
const [ search, setSearch ] = React.useState('');
const symbol = getPChain()?.symbol;
const handleSearchChange = React.useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value);
},
[],
);
// Sort by stake desc, then filter by search
const filtered = React.useMemo(() => {
const sorted = [ ...validators ].sort((a, b) => {
const aStake = BigInt(a.weight);
const bStake = BigInt(b.weight);
if (bStake > aStake) return 1;
if (bStake < aStake) return -1;
return 0;
});
if (!search.trim()) {
return sorted;
}
const term = search.trim().toLowerCase();
return sorted.filter((v) => v.nodeID.toLowerCase().includes(term));
}, [ validators, search ]);
return (
<div className="flex flex-col gap-4 text-[var(--color-text-primary)]">
{ /* Search */ }
<div>
<Input
placeholder="Search by Node ID..."
value={ search }
onChange={ handleSearchChange }
size="md"
/>
</div>
{ /* Table */ }
<div
className="overflow-hidden"
>
{ /* Header */ }
<div className="flex gap-4 py-2 border-b border-[var(--color-border-divider)]">
<div className="font-semibold flex-[3] min-w-0">
Node ID
</div>
<div className="font-semibold flex-[2] text-right">
Stake Amount
</div>
<div className="font-semibold flex-1 text-right">
Delegation Fee
</div>
<div className="font-semibold flex-1 text-right">
Delegators
</div>
<div className="font-semibold flex-1 text-center">
Connected
</div>
<div className="font-semibold flex-1 text-right">
Uptime
</div>
</div>
{ /* Loading */ }
{ isLoading && (
<div>
<Skeleton loading/>
<Skeleton loading/>
<Skeleton loading/>
<Skeleton loading/>
</div>
) }
{ /* Empty state */ }
{ !isLoading && filtered.length === 0 && (
<div>
{ search.trim() ? 'No validators match your search' : 'No validators found' }
</div>
) }
{ /* Rows */ }
{ !isLoading && filtered.map((v) => (
<div className="flex gap-4 py-2 border-b border-[var(--color-border-divider)]" key={ v.nodeID }>
<div
title={ v.nodeID }
className="flex-[3] min-w-0 overflow-hidden text-ellipsis"
>
{ truncateNodeId(v.nodeID) }
</div>
<div className="flex-[2] text-right">
{ formatStake(v.weight) } { symbol }
</div>
<div className="flex-1 text-right">
{ v.delegationFee }%
</div>
<div className="flex-1 text-right">
{ v.delegatorCount ?? 0 }
</div>
<div className="flex-1 flex justify-center">
<div/>
</div>
<div className="flex-1 text-right">
{ formatUptime(v.uptime) }
</div>
</div>
)) }
</div>
</div>
);
};
export default React.memo(ValidatorsList);