mirror of
https://github.com/luxfi/explore.git
synced 2026-10-30 16:57:53 +00:00
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
126 lines
3.9 KiB
TypeScript
126 lines
3.9 KiB
TypeScript
// 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,
|
||
};
|
||
}
|