Files
explore/lib/api/pchain/useCurrentValidators.ts
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

126 lines
3.9 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// The P-Chain's current validators and their aggregate statistics:
// GET /v1/chain/P/ops/validators. The list carries each validator's
// delegatorCount and delegatorWeight; the delegator records themselves come
// only from a read naming that one validator, so those are fetched for the
// validators that have delegators, and for no others.
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import type {
GetCurrentValidatorsResponse,
PChainValidator,
ValidatorStats,
} from './types';
import { getPChain } from 'configs/app/chainRegistry';
import { read } from './read';
const VALIDATORS_STALE_TIME_MS = 60_000;
const VALIDATORS_QUERY_KEY = 'pchain:currentValidators' as const;
const ZERO = BigInt(0);
function computeValidatorStats(
validators: ReadonlyArray<PChainValidator>,
): ValidatorStats {
let totalStake = ZERO;
let totalDelegatedStake = ZERO;
let connectedCount = 0;
let delegatorCount = 0;
let uptimeSum = 0;
for (const v of validators) {
const delegated = BigInt(v.delegatorWeight ?? '0');
totalStake += BigInt(v.weight) + delegated;
totalDelegatedStake += delegated;
delegatorCount += Number(v.delegatorCount ?? '0');
// connected field may be absent in some node configurations;
// infer connectivity from uptime > 0 when missing
if (v.connected ?? (parseFloat(v.uptime) > 0)) {
connectedCount += 1;
}
uptimeSum += parseFloat(v.uptime);
}
// uptime values from the API are already in percentage (0–100); no scaling needed
const averageUptime = validators.length > 0 ?
uptimeSum / validators.length :
0;
return {
totalStake,
validatorCount: validators.length,
connectedCount,
delegatorCount,
totalDelegatedStake,
averageUptime,
};
}
export interface UseCurrentValidatorsResult {
readonly validators: ReadonlyArray<PChainValidator>;
readonly stats: ValidatorStats;
}
// The node lists delegators only when one validator is named (service.go:
// `numNodeIDs == 1`), so each validator with delegators is read on its own.
async function withDelegators(v: PChainValidator): Promise<PChainValidator> {
if (Number(v.delegatorCount ?? '0') === 0) {
return v;
}
const { validators } = await read<GetCurrentValidatorsResponse>('validators', { nodeIDs: v.nodeID });
return { ...v, delegators: validators[0]?.delegators ?? [] };
}
async function fetchCurrentValidators(): Promise<UseCurrentValidatorsResult> {
const listed = (await read<GetCurrentValidatorsResponse>('validators')).validators ?? [];
const validators = await Promise.all(listed.map(withDelegators));
return { validators, stats: computeValidatorStats(validators) };
}
export function useCurrentValidators() {
const query = useQuery({
queryKey: [ VALIDATORS_QUERY_KEY ],
queryFn: fetchCurrentValidators,
staleTime: VALIDATORS_STALE_TIME_MS,
retry: 2,
enabled: Boolean(getPChain()),
});
const validators = React.useMemo(
() => query.data?.validators ?? [],
[ query.data?.validators ],
);
const stats = React.useMemo(
() => query.data?.stats ?? {
totalStake: ZERO,
validatorCount: 0,
connectedCount: 0,
delegatorCount: 0,
totalDelegatedStake: ZERO,
averageUptime: 0,
},
[ query.data?.stats ],
);
return {
validators,
stats,
// Did the read actually answer? A failed read leaves `stats` zeroed, and a
// zeroed stat is indistinguishable from a chain with no validators — which
// is how this explorer came to print "0 validators / 0 LUX staked" about a
// network running five with 2,500,000,000 LUX bonded. Every surface asks
// this before rendering a validator figure, and renders an em dash when it
// is false. Deciding it HERE means the four surfaces cannot drift apart.
isKnown: !query.isError && stats.validatorCount > 0,
isLoading: query.isLoading,
isError: query.isError,
error: query.error,
refetch: query.refetch,
};
}