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
116 lines
4.2 KiB
TypeScript
116 lines
4.2 KiB
TypeScript
// What is true about ONE primary-network chain, read from that chain.
|
|
//
|
|
// Every /chains/<slug> page used to print the same four numbers — the
|
|
// network-wide validator count, total stake, chain count and threshold — so
|
|
// Q-Chain and Z-Chain and K-Chain were the same page with the title swapped.
|
|
// None of it was about the chain you opened.
|
|
//
|
|
// Three facts are per-chain and real:
|
|
// * the blockchain ID, from the P-Chain registry — unique, and the thing that
|
|
// actually distinguishes one chain from another
|
|
// * whether the node has bootstrapped it, from info.isBootstrapped, which the
|
|
// gateway routes for every chain
|
|
// * its head, from its own RPC, in whatever dialect it speaks
|
|
//
|
|
// The last one is only available where the gateway routes the chain. Today that
|
|
// is P, X and C on all three networks; the rest are registered and bootstrapped
|
|
// but answer 404 from the browser's only reachable origin. That is reported as
|
|
// `unreachable`, never as a height of zero — a chain whose head we cannot read and
|
|
// a chain sitting at genesis are different states and must not render alike.
|
|
|
|
import { useQuery } from '@tanstack/react-query';
|
|
|
|
import type { GetHeightResponse } from 'lib/api/pchain/types';
|
|
|
|
import type { PrimaryVm } from 'configs/app/primaryChains';
|
|
import { read } from 'lib/api/pchain/read';
|
|
|
|
const STALE_MS = 15_000;
|
|
|
|
export type Head =
|
|
{ readonly kind: 'height'; readonly height: number } |
|
|
{ readonly kind: 'unreachable' } |
|
|
{ readonly kind: 'unknown' };
|
|
|
|
export interface ChainFacts {
|
|
readonly blockchainId: string | null;
|
|
readonly bootstrapped: boolean | null;
|
|
readonly head: Head;
|
|
readonly chainId: number | null;
|
|
}
|
|
|
|
async function rpc(endpoint: string, method: string, params: unknown): Promise<unknown> {
|
|
const res = await fetch(`/v1/node/${ endpoint }`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }),
|
|
});
|
|
if (!res.ok) {
|
|
return null;
|
|
}
|
|
const body = await res.json() as { result?: unknown };
|
|
return body.result ?? null;
|
|
}
|
|
|
|
// Each dialect names its head differently, and asking the wrong one gets a
|
|
// "can't find service" error rather than an answer — X speaks xvm, not avm. The
|
|
// P-Chain speaks no JSON-RPC at all: its head is GET /v1/chain/P/ops/height.
|
|
async function readHead(vm: PrimaryVm): Promise<Head> {
|
|
try {
|
|
if (vm.view === 'evm' || vm.view === 'dex') {
|
|
const hex = await rpc(vm.slug, 'eth_blockNumber', []) as string | null;
|
|
return hex ? { kind: 'height', height: parseInt(hex, 16) } : { kind: 'unreachable' };
|
|
}
|
|
const out = vm.view === 'platform' ?
|
|
await read<GetHeightResponse>('height') :
|
|
await rpc(vm.slug, 'xvm.getHeight', {}) as { height?: string } | null;
|
|
return out?.height !== undefined ?
|
|
{ kind: 'height', height: Number(out.height) } :
|
|
{ kind: 'unreachable' };
|
|
} catch {
|
|
return { kind: 'unreachable' };
|
|
}
|
|
}
|
|
|
|
async function readChainId(vm: PrimaryVm): Promise<number | null> {
|
|
if (vm.view !== 'evm' && vm.view !== 'dex') {
|
|
return null;
|
|
}
|
|
const hex = await rpc(vm.slug, 'eth_chainId', []) as string | null;
|
|
return hex ? parseInt(hex, 16) : null;
|
|
}
|
|
|
|
export function useChainFacts(vm: PrimaryVm | undefined, blockchainId: string | null): {
|
|
readonly facts: ChainFacts;
|
|
readonly isLoading: boolean;
|
|
} {
|
|
const query = useQuery({
|
|
queryKey: [ 'chain:facts', vm?.slug ],
|
|
enabled: Boolean(vm),
|
|
staleTime: STALE_MS,
|
|
refetchInterval: STALE_MS,
|
|
queryFn: async(): Promise<Omit<ChainFacts, 'blockchainId'>> => {
|
|
if (!vm) {
|
|
return { bootstrapped: null, head: { kind: 'unknown' }, chainId: null };
|
|
}
|
|
const alias = vm.slug.charAt(0).toUpperCase();
|
|
const [ boot, head, chainId ] = await Promise.all([
|
|
rpc('info', 'info.isBootstrapped', { chain: alias }) as Promise<{ isBootstrapped?: boolean } | null>,
|
|
readHead(vm),
|
|
readChainId(vm),
|
|
]);
|
|
return { bootstrapped: boot?.isBootstrapped ?? null, head, chainId };
|
|
},
|
|
});
|
|
|
|
return {
|
|
facts: {
|
|
blockchainId,
|
|
bootstrapped: query.data?.bootstrapped ?? null,
|
|
head: query.data?.head ?? { kind: 'unknown' },
|
|
chainId: query.data?.chainId ?? vm?.chainId ?? null,
|
|
},
|
|
isLoading: query.isLoading,
|
|
};
|
|
}
|