Brand explorers show only their own chain on validators and DEX

On a Zoo, Hanzo, Pars, SPC or Osage host the validators page listed every L1
in the registry, and /dex rendered the Lux D-Chain markets. The network total
now sums every L1 only on the Lux primary-network explorer, and /dex answers
404 anywhere else. isPrimaryNetworkHost() is the same predicate as
isPrimaryNetworkExplorer(), taking the host from the request.


Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-09-23 16:09:21 -07:00
co-authored by Hanzo Dev
parent e57351a54c
commit a23a81dee7
6 changed files with 35 additions and 10 deletions
+7 -1
View File
@@ -413,7 +413,13 @@ export function isPrimaryNetworkExplorer(): boolean {
// primary network's chains or the cross-L1 list. Gating purely on vm==='EVM'
// wrongly promoted unregistered hosts (buildWhiteLabelChain defaults to
// vm:'EVM') to primary, leaking the parent network's chains.
return !isWhiteLabelMode() && getCurrentChain().vm === 'EVM';
return isPrimaryNetworkHost(getHostname());
}
/** isPrimaryNetworkExplorer() for an explicit host, e.g. a request's Host header. */
export function isPrimaryNetworkHost(hostname: string): boolean {
const chain = CHAINS.find((c) => c.hostnames.includes(hostname));
return chain !== undefined && chain.vm === 'EVM';
}
export function getCurrentNetwork(): NetworkEntry {
+8 -5
View File
@@ -1,5 +1,6 @@
// Validator counts across every network in the registry, not just the one this
// explorer is pointed at.
// Validator counts. The Lux primary-network explorer sums every L1 in the
// registry; a brand explorer (Zoo, Hanzo, Pars, SPC, Osage) counts only its own
// chain and never names another network.
//
// Lux, Hanzo, Zoo and Pars are each a sovereign L1 with its OWN P-Chain and its
// own validator set, so "how many validators" is a sum over chains and there is
@@ -15,7 +16,7 @@ import React from 'react';
import type { PChainValidator } from './types';
import { CHAINS, getCurrentChain } from 'configs/app/chainRegistry';
import { CHAINS, getCurrentChain, isPrimaryNetworkExplorer } from 'configs/app/chainRegistry';
const STALE_TIME_MS = 60_000;
const PRIMARY_NETWORK_ID = '11111111111111111111111111111111LpoYY';
@@ -65,8 +66,10 @@ export function useNetworkValidators() {
// Same network as this deployment: a mainnet explorer must not count testnet
// validators.
const { all, readable } = React.useMemo(() => {
const { network } = getCurrentChain();
const chains = CHAINS.filter((c) => c.network === network);
const current = getCurrentChain();
const chains = isPrimaryNetworkExplorer() ?
CHAINS.filter((c) => c.network === current.network) :
CHAINS.filter((c) => c.network === current.network && c.name === current.name);
return { all: chains, readable: chains.filter((c) => c.nodeApiUrl) };
}, []);
+14
View File
@@ -6,6 +6,7 @@ import type { Route } from 'nextjs-routes';
import type { Props } from 'nextjs/getServerSideProps/handlers';
import config from 'configs/app';
import { isPrimaryNetworkHost } from 'configs/app/chainRegistry';
export type Guard = (chainConfig: typeof config) => <Pathname extends Route['pathname'] = never>(context: GetServerSidePropsContext) =>
Promise<GetServerSidePropsResult<Props<Pathname>> | undefined>;
@@ -342,3 +343,16 @@ export const megaEth: Guard = () => async() => {
};
}
};
// Pages that read the Lux primary network (the D-Chain DEX) exist only on the
// Lux C-Chain explorer. A brand explorer answers 404 for them rather than
// rendering another network's markets under its own name.
export const primaryNetwork: Guard = () => async(context: GetServerSidePropsContext) => {
const header = context.req.headers['x-forwarded-host'] ?? context.req.headers.host ?? '';
const host = String(Array.isArray(header) ? header[0] : header).split(',')[0].trim().replace(/:\d+$/, '');
if (!isPrimaryNetworkHost(host)) {
return {
notFound: true,
};
}
};
+1
View File
@@ -31,6 +31,7 @@ export const pools = factory([ guards.pools ]);
export const megaEth = factory([ guards.megaEth ]);
export const zetaChainCCTX = factory([ guards.zetaChainCCTX ]);
export const crossChainTxs = factory([ guards.notMultichain, guards.crossChainTxs ]);
export const dex = factory([ guards.primaryNetwork ]);
// ROLLUPS
export const rollup = factory([ guards.rollup ]);
+1 -1
View File
@@ -15,4 +15,4 @@ const Page: NextPage = () => {
export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps/main';
export { dex as getServerSideProps } from 'nextjs/getServerSideProps/main';
+4 -3
View File
@@ -32,9 +32,10 @@ const PERCENTAGE_SCALE = 100;
// that shows one network's share is answering a narrower question than the one
// being asked.
//
// Every network is listed, including ones that did not answer. A chain dropped
// from the list would read as a chain with no validators, and the total says
// how many answered so a partial sum is never mistaken for the whole.
// On the Lux primary-network explorer every network is listed, including ones
// that did not answer: a chain dropped from the list would read as a chain with
// no validators, and the total says how many answered. A brand explorer has
// only its own chain, so the block does not render there.
const NetworkValidators = () => {
const { networks, total, isKnown, isLoading, answeredCount, queriedCount } = useNetworkValidators();