The explorer lists only chains that have an explorer

SPC, Pars and Osage have no explorer and no public node, the devnet has no
explorer, and the Hanzo testnet explorer has no DNS name. Their registry entries
put links to names that do not resolve on every page (the "SPC L1" and "Pars L1"
cards, the chain switcher, the network selector), and the Pars entry sent a
node request to api.pars.network on every page load that failed with 502.

The P-Chain still lists SPC and Pars; their cards render without a link.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
2026-09-23 16:08:09 -07:00
co-authored by Claude Opus 5.5
parent 858d9ca25b
commit e57351a54c
5 changed files with 11 additions and 273 deletions
+5 -10
View File
@@ -39,9 +39,11 @@ describe('chain-visibility rule', () => {
expect(getCurrentChain().name).toBe('C-Chain');
});
it('lists every sovereign L1 incl. Osage', () => {
const names = switcherChainNames();
expect(names).toEqual(expect.arrayContaining([ 'C-Chain', 'Zoo', 'Hanzo', 'SPC', 'Pars', 'Osage' ]));
// The switcher lists the chains that have an explorer to switch to, and
// no others: every entry is a link, and a link to a name that does not
// resolve is a dead end on every page.
it('lists exactly the mainnet chains with an explorer', () => {
expect(switcherChainNames()).toEqual([ 'C-Chain', 'Zoo', 'Hanzo' ]);
});
});
@@ -49,8 +51,6 @@ describe('chain-visibility rule', () => {
const cases = [
{ host: 'explore.hanzo.network', name: 'Hanzo', brandName: 'Hanzo AI' },
{ host: 'explore.zoo.network', name: 'Zoo', brandName: 'Zoo Chain' },
{ host: 'explore.pars.network', name: 'Pars', brandName: 'Pars Network' },
{ host: 'explore.osage.network', name: 'Osage', brandName: 'Osage' },
];
it.each(cases)('$host is a registered L2, NOT the primary explorer (no leak)', ({ host, brandName }) => {
@@ -65,11 +65,6 @@ describe('chain-visibility rule', () => {
atHost(host);
expect(switcherChainNames()).toEqual([ name ]);
});
it('pars uses its live deployed chainId 494949 (api.pars.network eth_chainId=0x78d65 / net_version=494949)', () => {
atHost('explore.pars.network');
expect(getCurrentChain().chainId).toBe(494949);
});
});
// The footer renders branding.sourceUrl and branding.orgName verbatim, so a
+6 -245
View File
@@ -84,9 +84,9 @@ export interface ChainEntry {
* P-Chain and its own validator set, so this is what makes a validator count
* readable per chain.
*
* Absent means there is no publicly reachable node for that chain (SPC and
* Osage are in-cluster only). Absence is the honest signal: a chain without
* this is reported as unknown rather than counted as zero.
* Absent means there is no publicly reachable node for that chain. Absence
* is the honest signal: a chain without this is reported as unknown rather
* than counted as zero.
*/
readonly nodeApiUrl?: string;
readonly branding: ChainBranding;
@@ -206,93 +206,6 @@ const HANZO_BRANDING: ChainBranding = {
'</g>',
};
/** SPC — unicorn */
const SPC_BRANDING: ChainBranding = {
brandName: 'SPC Chain',
productName: 'Explorer',
orgName: 'Sparkle Pony Club',
websiteUrl: 'https://sparkleponyclub.com',
description: 'SPC chain.',
githubUrl: 'https://github.com/luxfi',
sourceUrl: 'https://github.com/luxfi/explore',
twitterUrl: 'https://x.com/luxdefi',
discordUrl: 'https://discord.gg/luxnetwork',
logoViewBox: '0 0 64 64',
logoContent:
'<text x="32" y="46" text-anchor="middle" font-size="42" fill="currentColor">&#x1F984;</text>',
faviconContent:
'<rect width="512" height="512" rx="64" fill="#000"/>' +
'<text x="256" y="350" text-anchor="middle" font-size="320">&#x1F984;</text>',
};
/** Pars — Persian 8-pointed star from ~/work/pars/logo/docs/assets/pars-logo-mono.svg */
const PARS_BRANDING: ChainBranding = {
brandName: 'Pars Network',
productName: 'Explorer',
orgName: 'Parsis Foundation',
websiteUrl: 'https://pars.network',
description: 'Pars blockchain — financial infrastructure for the Persian-speaking world.',
githubUrl: 'https://github.com/luxfi',
sourceUrl: 'https://github.com/luxfi/explore',
twitterUrl: 'https://x.com/parsnetwork',
discordUrl: 'https://discord.gg/luxnetwork',
logoViewBox: '-120 -120 240 240',
logoContent:
// Outer 8-pointed star
'<path d="M0,-100 L30,-60 L100,-40 L60,0 L100,40 L30,60 L0,100 L-30,60 L-100,40 L-60,0 L-100,-40 L-30,-60 Z"' +
' fill="none" stroke="currentColor" stroke-width="4" stroke-linejoin="round"/>' +
// Inner star
'<path d="M0,-70 L22,-42 L70,-28 L42,0 L70,28 L22,42 L0,70 L-22,42 L-70,28 L-42,0 L-70,-28 L-22,-42 Z"' +
' fill="currentColor" fill-opacity="0.15" stroke="currentColor" stroke-width="3" stroke-linejoin="round"/>' +
// Recursive inner star
'<path d="M0,-45 L14,-27 L45,-18 L27,0 L45,18 L14,27 L0,45 L-14,27 L-45,18 L-27,0 L-45,-18 L-14,-27 Z"' +
' fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round" opacity="0.6"/>' +
// Interlaced circles
'<circle r="55" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.4"/>' +
'<circle r="35" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.4"/>' +
// Center rosette
'<circle r="8" fill="currentColor"/>' +
'<path d="M0,-20 L6,-6 L20,0 L6,6 L0,20 L-6,6 L-20,0 L-6,-6 Z"' +
' fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" opacity="0.8"/>',
faviconContent:
'<rect width="512" height="512" rx="64" fill="#000"/>' +
'<g transform="translate(256,256) scale(2)">' +
'<path d="M0,-100 L30,-60 L100,-40 L60,0 L100,40 L30,60 L0,100 L-30,60 L-100,40 L-60,0 L-100,-40 L-30,-60 Z"' +
' fill="none" stroke="#fff" stroke-width="4" stroke-linejoin="round"/>' +
'<path d="M0,-70 L22,-42 L70,-28 L42,0 L70,28 L22,42 L0,70 L-22,42 L-70,28 L-42,0 L-70,-28 L-22,-42 Z"' +
' fill="#fff" fill-opacity="0.15" stroke="#fff" stroke-width="3" stroke-linejoin="round"/>' +
'<path d="M0,-45 L14,-27 L45,-18 L27,0 L45,18 L14,27 L0,45 L-14,27 L-45,18 L-27,0 L-45,-18 L-14,-27 Z"' +
' fill="none" stroke="#fff" stroke-width="2" stroke-linejoin="round" opacity="0.6"/>' +
'<circle r="55" fill="none" stroke="#fff" stroke-width="1.5" opacity="0.4"/>' +
'<circle r="35" fill="none" stroke="#fff" stroke-width="1.5" opacity="0.4"/>' +
'<circle r="8" fill="#fff"/>' +
'<path d="M0,-20 L6,-6 L20,0 L6,6 L0,20 L-6,6 L-20,0 L-6,-6 Z"' +
' fill="none" stroke="#fff" stroke-width="1.5" stroke-linejoin="round" opacity="0.8"/>' +
'</g>',
};
/** Osage — sovereign L1 (coin OSG). Concentric-ring mark; refine via
* NEXT_PUBLIC_BRAND_* at deploy time when the real logo lands. */
const OSAGE_BRANDING: ChainBranding = {
brandName: 'Osage',
productName: 'Explorer',
orgName: 'Osage',
websiteUrl: 'https://osage.network',
description: 'Osage blockchain explorer.',
githubUrl: 'https://github.com/luxfi',
sourceUrl: 'https://github.com/luxfi/explore',
twitterUrl: 'https://x.com/luxdefi',
discordUrl: 'https://discord.gg/luxnetwork',
logoViewBox: '0 0 100 100',
logoContent:
'<circle cx="50" cy="50" r="40" fill="none" stroke="currentColor" stroke-width="8"/>' +
'<circle cx="50" cy="50" r="14" fill="currentColor"/>',
faviconContent:
'<rect width="512" height="512" rx="64" fill="#dc2626"/>' +
'<circle cx="256" cy="256" r="150" fill="none" stroke="#fff" stroke-width="36"/>' +
'<circle cx="256" cy="256" r="56" fill="#fff"/>',
};
export const CHAINS: ReadonlyArray<ChainEntry> = [
{
name: 'C-Chain',
@@ -332,40 +245,6 @@ export const CHAINS: ReadonlyArray<ChainEntry> = [
nodeApiUrl: 'https://api.hanzo.network',
branding: HANZO_BRANDING,
},
{
name: 'SPC',
label: 'SPC Chain',
vm: 'L2',
network: 'mainnet',
chainId: 36911,
hostnames: [ 'explore-spc.lux.network' ],
explorerUrl: 'https://explore-spc.lux.network',
apiUrl: 'https://api-explore-spc.lux.network',
branding: SPC_BRANDING,
},
{
name: 'Pars',
label: 'Pars Network',
vm: 'L2',
network: 'mainnet',
chainId: 494949,
hostnames: [ 'explore-pars.lux.network', 'explore.pars.network', 'explorer.pars.network' ],
explorerUrl: 'https://explore-pars.lux.network',
apiUrl: 'https://api-explore-pars.lux.network',
nodeApiUrl: 'https://api.pars.network',
branding: PARS_BRANDING,
},
{
name: 'Osage',
label: 'Osage Chain',
vm: 'L2',
network: 'mainnet',
chainId: 1872,
hostnames: [ 'explore-osage.lux.network', 'explore.osage.network', 'explore.osage.group' ],
explorerUrl: 'https://explore-osage.lux.network',
apiUrl: 'https://api-explore-osage.lux.network',
branding: OSAGE_BRANDING,
},
// Testnet chains
{
name: 'C-Chain',
@@ -389,117 +268,6 @@ export const CHAINS: ReadonlyArray<ChainEntry> = [
apiUrl: 'https://api-explore-zoo-test.lux.network',
branding: ZOO_BRANDING,
},
{
name: 'Hanzo',
label: 'Hanzo AI (Testnet)',
vm: 'L2',
network: 'testnet',
chainId: 36962,
hostnames: [ 'explore-hanzo-test.lux.network', 'explore-test.hanzo.network' ],
explorerUrl: 'https://explore-hanzo-test.lux.network',
apiUrl: 'https://api-explore-hanzo-test.lux.network',
branding: HANZO_BRANDING,
},
{
name: 'SPC',
label: 'SPC Chain (Testnet)',
vm: 'L2',
network: 'testnet',
chainId: 36910,
hostnames: [ 'explore-spc-test.lux.network' ],
explorerUrl: 'https://explore-spc-test.lux.network',
apiUrl: 'https://api-explore-spc-test.lux.network',
branding: SPC_BRANDING,
},
{
name: 'Pars',
label: 'Pars Network (Testnet)',
vm: 'L2',
network: 'testnet',
chainId: 494950,
hostnames: [ 'explore-pars-test.lux.network' ],
explorerUrl: 'https://explore-pars-test.lux.network',
apiUrl: 'https://api-explore-pars-test.lux.network',
branding: PARS_BRANDING,
},
{
name: 'Osage',
label: 'Osage Chain (Testnet)',
vm: 'L2',
network: 'testnet',
chainId: 1871,
hostnames: [ 'explore-osage-test.lux.network' ],
explorerUrl: 'https://explore-osage-test.lux.network',
apiUrl: 'https://api-explore-osage-test.lux.network',
branding: OSAGE_BRANDING,
},
// Devnet chains
{
name: 'C-Chain',
label: 'Contract Chain',
vm: 'EVM',
network: 'devnet',
chainId: 96367,
hostnames: [ 'explore-dev.lux.network', 'explore.lux-dev.network' ],
explorerUrl: 'https://explore-dev.lux.network',
apiUrl: 'https://api-explore-dev.lux.network',
branding: LUX_BRANDING,
},
{
name: 'Zoo',
label: 'Zoo Chain (Devnet)',
vm: 'L2',
network: 'devnet',
chainId: 200202,
hostnames: [ 'explore-zoo-dev.lux.network' ],
explorerUrl: 'https://explore-zoo-dev.lux.network',
apiUrl: 'https://api-explore-zoo-dev.lux.network',
branding: ZOO_BRANDING,
},
{
name: 'Hanzo',
label: 'Hanzo AI (Devnet)',
vm: 'L2',
network: 'devnet',
chainId: 36964,
hostnames: [ 'explore-hanzo-dev.lux.network', 'explore-dev.hanzo.network' ],
explorerUrl: 'https://explore-hanzo-dev.lux.network',
apiUrl: 'https://api-explore-hanzo-dev.lux.network',
branding: HANZO_BRANDING,
},
{
name: 'SPC',
label: 'SPC Chain (Devnet)',
vm: 'L2',
network: 'devnet',
chainId: 36912,
hostnames: [ 'explore-spc-dev.lux.network' ],
explorerUrl: 'https://explore-spc-dev.lux.network',
apiUrl: 'https://api-explore-spc-dev.lux.network',
branding: SPC_BRANDING,
},
{
name: 'Pars',
label: 'Pars Network (Devnet)',
vm: 'L2',
network: 'devnet',
chainId: 494951,
hostnames: [ 'explore-pars-dev.lux.network' ],
explorerUrl: 'https://explore-pars-dev.lux.network',
apiUrl: 'https://api-explore-pars-dev.lux.network',
branding: PARS_BRANDING,
},
{
name: 'Osage',
label: 'Osage Chain (Devnet)',
vm: 'L2',
network: 'devnet',
chainId: 1873,
hostnames: [ 'explore-osage-dev.lux.network' ],
explorerUrl: 'https://explore-osage-dev.lux.network',
apiUrl: 'https://api-explore-osage-dev.lux.network',
branding: OSAGE_BRANDING,
},
// Localnet — chainId 1337. Listing localhost / 127.0.0.1 / 0.0.0.0 here
// keeps isWhiteLabelMode() false in local dev so the chain + network
@@ -532,13 +300,6 @@ export const NETWORKS: ReadonlyArray<NetworkEntry> = [
baseHostname: 'explore.lux-test.network',
explorerUrl: 'https://explore.lux-test.network',
},
{
name: 'Devnet',
label: 'Development network',
network: 'devnet',
baseHostname: 'explore.lux-dev.network',
explorerUrl: 'https://explore.lux-dev.network',
},
{
name: 'Localnet',
label: 'Local development (chainId 1337)',
@@ -642,13 +403,13 @@ export function getCurrentChain(): ChainEntry {
/**
* True only on the Lux PRIMARY-network explorer (the Lux C-Chain host +
* the unregistered white-label fallback, both `vm: 'EVM'`). Brand explorers
* (Hanzo / Zoo / SPC / Pars are `vm: 'L2'`) return false: they show ONLY
* (Hanzo / Zoo are `vm: 'L2'`) return false: they show ONLY
* their own chain, never the Lux primary-network chains / cross-L1 lists.
*/
export function isPrimaryNetworkExplorer(): boolean {
// The Lux primary-network explorer is the REGISTERED Lux C-Chain host only.
// White-label / unregistered hosts are sovereign brand explorers (Hanzo /
// Zoo / Pars / SPC / Osage): they show ONLY their own chain — never the Lux
// White-label / unregistered hosts are sovereign brand explorers: they
// show ONLY their own chain — never the Lux
// 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.
-14
View File
@@ -24,20 +24,6 @@ const explorers = [
minBlocks: 9,
minTxs: 45,
},
{
name: 'SPC L1',
url: 'https://explore-spc.lux.network',
apiUrl: 'https://api-explore-spc.lux.network',
minBlocks: 9,
minTxs: 45,
},
{
name: 'Pars L1',
url: 'https://explore-pars.lux.network',
apiUrl: 'https://api-explore-pars.lux.network',
minBlocks: 9,
minTxs: 45,
},
];
for (const explorer of explorers) {
-2
View File
@@ -41,8 +41,6 @@ const L1_DESCRIPTIONS: Readonly<Record<string, string>> = {
const EXPLORER_URLS: Readonly<Record<string, string>> = {
zoo: 'https://explore-zoo.lux.network',
hanzo: 'https://explore-hanzo.lux.network',
spc: 'https://explore-spc.lux.network',
pars: 'https://explore-pars.lux.network',
};
const KNOWN_VM_IDS: Readonly<Record<string, string>> = {
-2
View File
@@ -49,8 +49,6 @@ const KNOWN_L1_CHAINS: ReadonlyArray<{ readonly name: string; readonly href: str
const L1_EXPLORER_URLS: Readonly<Record<string, string>> = {
Zoo: 'https://explore-zoo.lux.network',
Hanzo: 'https://explore-hanzo.lux.network',
SPC: 'https://explore-spc.lux.network',
Pars: 'https://explore-pars.lux.network',
};
function formatStake(nanoLux: bigint): string {