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
148 lines
4.0 KiB
TypeScript
148 lines
4.0 KiB
TypeScript
import { Skeleton } from '@luxfi/ui/skeleton';
|
|
import React from 'react';
|
|
|
|
import { getPChain } from 'configs/app/chainRegistry';
|
|
import type { PChainDelegator, PChainValidator } from 'lib/api/pchain';
|
|
import dayjs from 'lib/date/dayjs';
|
|
|
|
import { formatStake, truncateNodeId } from './utils';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Types
|
|
// ---------------------------------------------------------------------------
|
|
|
|
interface FlatDelegator extends PChainDelegator {
|
|
readonly validatorNodeID: string;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Constants
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const TXID_PREFIX_LEN = 10;
|
|
const TXID_SUFFIX_LEN = 6;
|
|
|
|
function truncateTxId(txId: string): string {
|
|
const minLength = TXID_PREFIX_LEN + TXID_SUFFIX_LEN + 3;
|
|
if (txId.length <= minLength) {
|
|
return txId;
|
|
}
|
|
return `${ txId.slice(0, TXID_PREFIX_LEN) }...${ txId.slice(-TXID_SUFFIX_LEN) }`;
|
|
}
|
|
|
|
function formatTimestamp(unixSeconds: string): string {
|
|
const ts = Number(unixSeconds);
|
|
if (ts === 0) {
|
|
return '\u2014';
|
|
}
|
|
return dayjs.unix(ts).format('lll');
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Component
|
|
// ---------------------------------------------------------------------------
|
|
|
|
interface DelegatorsListProps {
|
|
readonly validators: ReadonlyArray<PChainValidator>;
|
|
readonly isLoading: boolean;
|
|
}
|
|
|
|
const DelegatorsList = ({ validators, isLoading }: DelegatorsListProps) => {
|
|
const symbol = getPChain()?.symbol;
|
|
const delegators = React.useMemo<ReadonlyArray<FlatDelegator>>(() => {
|
|
const result: Array<FlatDelegator> = [];
|
|
for (const v of validators) {
|
|
if (v.delegators) {
|
|
for (const d of v.delegators) {
|
|
result.push({ ...d, validatorNodeID: v.nodeID });
|
|
}
|
|
}
|
|
}
|
|
// Sort by stake descending
|
|
result.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;
|
|
});
|
|
return result;
|
|
}, [ validators ]);
|
|
|
|
return (
|
|
<div
|
|
className="overflow-hidden text-[var(--color-text-primary)]"
|
|
>
|
|
{ /* Header */ }
|
|
<div className="flex">
|
|
<div className="font-semibold">
|
|
TX ID
|
|
</div>
|
|
<div className="font-semibold">
|
|
Node ID
|
|
</div>
|
|
<div className="font-semibold">
|
|
Stake Amount
|
|
</div>
|
|
<div className="font-semibold">
|
|
Start
|
|
</div>
|
|
<div className="font-semibold">
|
|
End
|
|
</div>
|
|
<div className="font-semibold">
|
|
Potential Reward
|
|
</div>
|
|
</div>
|
|
|
|
{ /* Loading */ }
|
|
{ isLoading && (
|
|
<div>
|
|
<Skeleton loading/>
|
|
<Skeleton loading/>
|
|
<Skeleton loading/>
|
|
</div>
|
|
) }
|
|
|
|
{ /* Empty state */ }
|
|
{ !isLoading && delegators.length === 0 && (
|
|
<div>
|
|
No delegators found
|
|
</div>
|
|
) }
|
|
|
|
{ /* Rows */ }
|
|
{ !isLoading && delegators.map((d) => (
|
|
<div className="flex" key={ `${ d.txID }-${ d.validatorNodeID }` }>
|
|
<div
|
|
title={ d.txID }
|
|
className="overflow-hidden"
|
|
>
|
|
{ truncateTxId(d.txID) }
|
|
</div>
|
|
<div
|
|
title={ d.validatorNodeID }
|
|
className="overflow-hidden"
|
|
>
|
|
{ truncateNodeId(d.validatorNodeID) }
|
|
</div>
|
|
<div className="text-left lg:text-right">
|
|
{ formatStake(d.weight) } { symbol }
|
|
</div>
|
|
<div>
|
|
{ formatTimestamp(d.startTime) }
|
|
</div>
|
|
<div>
|
|
{ formatTimestamp(d.endTime) }
|
|
</div>
|
|
<div className="text-left lg:text-right">
|
|
{ d.potentialReward ? `${ formatStake(d.potentialReward) } ${ symbol }` : '\u2014' }
|
|
</div>
|
|
</div>
|
|
)) }
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default React.memo(DelegatorsList);
|