mirror of
https://github.com/luxfi/explore.git
synced 2026-07-27 03:13:50 +00:00
brand: audit, rip non-lux references, add .well-known/explore.json
Per the no-crossover rule, explorer source contains only Lux brand data.
Per-chain identity (subnet logo, chain name) preserved as network metadata.
Removed:
- .env.example.{hanzo,zoo,pars} — non-Lux brand build recipes
- Dockerfile.liquidity — regulated US securities brand, must not appear in OSS
- Third-party-domain hostnames from chainRegistry.ts (explore.zoo.ngo etc.)
- Non-Lux hostname suffixes from lib/white-label.ts (.hanzo.ai, .zoo.network, etc.)
- "" mention from lib/requestHost.ts comment
Kept (chain identity on Lux primary network):
- ZOO/HANZO/SPC/PARS_BRANDING constants in chainRegistry.ts
- explore-{org}.lux.network hostnames (Lux-served subnet explorers)
- DeFi terms ( Provider, add) — generic finance vocab
Added:
- public/.well-known/explore.json — federation manifest
- scripts/generate-well-known.js — regenerates manifest from chain registry
- Wired into npm build / build:next scripts
- BRAND-AUDIT.md — survey + decisions
This commit is contained in:
@@ -1,39 +0,0 @@
|
||||
# Hanzo Explorer — Hanzo AI brand.
|
||||
# Use: cp .env.example.hanzo .env.local && pnpm build
|
||||
|
||||
NEXT_PUBLIC_BRAND=hanzo
|
||||
|
||||
# Network (Hanzo subnet on Lux mainnet)
|
||||
NEXT_PUBLIC_NETWORK_NAME=Hanzo AI
|
||||
NEXT_PUBLIC_NETWORK_SHORT_NAME=HANZO
|
||||
NEXT_PUBLIC_NETWORK_ID=36963
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=HANZO
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=HANZO
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
|
||||
|
||||
# API
|
||||
NEXT_PUBLIC_API_HOST=api-explore-hanzo.lux.network
|
||||
NEXT_PUBLIC_API_PROTOCOL=https
|
||||
NEXT_PUBLIC_API_BASE_PATH=/
|
||||
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=wss
|
||||
|
||||
# App
|
||||
NEXT_PUBLIC_APP_HOST=explore.hanzo.ai
|
||||
NEXT_PUBLIC_APP_PROTOCOL=https
|
||||
NEXT_PUBLIC_IS_TESTNET=false
|
||||
|
||||
# Branding
|
||||
NEXT_PUBLIC_NETWORK_ORG_NAME=Hanzo Industries Inc.
|
||||
NEXT_PUBLIC_NETWORK_WEBSITE_URL=https://hanzo.ai
|
||||
NEXT_PUBLIC_NETWORK_DESCRIPTION=AI blockchain — decentralized compute and inference.
|
||||
NEXT_PUBLIC_NETWORK_GITHUB_URL=https://github.com/hanzoai
|
||||
NEXT_PUBLIC_NETWORK_TWITTER_URL=https://x.com/hanaboratory
|
||||
NEXT_PUBLIC_NETWORK_DISCORD_URL=https://discord.gg/hanzoai
|
||||
|
||||
# Auth (IAM)
|
||||
NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER=oidc
|
||||
NEXT_PUBLIC_OIDC_SERVER_URL=https://hanzo.id
|
||||
NEXT_PUBLIC_OIDC_CLIENT_ID=hanzo-explore-client-id
|
||||
|
||||
# Theme
|
||||
NEXT_PUBLIC_COLOR_THEME_DEFAULT=dark
|
||||
@@ -1,39 +0,0 @@
|
||||
# Pars Explorer — Parsis Foundation brand.
|
||||
# Use: cp .env.example.pars .env.local && pnpm build
|
||||
|
||||
NEXT_PUBLIC_BRAND=pars
|
||||
|
||||
# Network (Pars subnet on Lux mainnet)
|
||||
NEXT_PUBLIC_NETWORK_NAME=Pars Network
|
||||
NEXT_PUBLIC_NETWORK_SHORT_NAME=PARS
|
||||
NEXT_PUBLIC_NETWORK_ID=494949
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=PARS
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=PARS
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
|
||||
|
||||
# API
|
||||
NEXT_PUBLIC_API_HOST=api-explore-pars.lux.network
|
||||
NEXT_PUBLIC_API_PROTOCOL=https
|
||||
NEXT_PUBLIC_API_BASE_PATH=/
|
||||
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=wss
|
||||
|
||||
# App
|
||||
NEXT_PUBLIC_APP_HOST=explore.pars.network
|
||||
NEXT_PUBLIC_APP_PROTOCOL=https
|
||||
NEXT_PUBLIC_IS_TESTNET=false
|
||||
|
||||
# Branding
|
||||
NEXT_PUBLIC_NETWORK_ORG_NAME=Parsis Foundation
|
||||
NEXT_PUBLIC_NETWORK_WEBSITE_URL=https://pars.network
|
||||
NEXT_PUBLIC_NETWORK_DESCRIPTION=Pars blockchain — financial infrastructure for the Persian-speaking world.
|
||||
NEXT_PUBLIC_NETWORK_GITHUB_URL=https://github.com/luxfi
|
||||
NEXT_PUBLIC_NETWORK_TWITTER_URL=https://x.com/parsnetwork
|
||||
NEXT_PUBLIC_NETWORK_DISCORD_URL=https://discord.gg/luxnetwork
|
||||
|
||||
# Auth (IAM)
|
||||
NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER=oidc
|
||||
NEXT_PUBLIC_OIDC_SERVER_URL=https://pars.id
|
||||
NEXT_PUBLIC_OIDC_CLIENT_ID=pars-explore-client-id
|
||||
|
||||
# Theme
|
||||
NEXT_PUBLIC_COLOR_THEME_DEFAULT=dark
|
||||
@@ -1,39 +0,0 @@
|
||||
# Zoo Explorer — Zoo Labs Foundation brand.
|
||||
# Use: cp .env.example.zoo .env.local && pnpm build
|
||||
|
||||
NEXT_PUBLIC_BRAND=zoo
|
||||
|
||||
# Network (Zoo subnet on Lux mainnet)
|
||||
NEXT_PUBLIC_NETWORK_NAME=Zoo Chain
|
||||
NEXT_PUBLIC_NETWORK_SHORT_NAME=ZOO
|
||||
NEXT_PUBLIC_NETWORK_ID=200200
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=ZOO
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ZOO
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
|
||||
|
||||
# API
|
||||
NEXT_PUBLIC_API_HOST=api-explore-zoo.lux.network
|
||||
NEXT_PUBLIC_API_PROTOCOL=https
|
||||
NEXT_PUBLIC_API_BASE_PATH=/
|
||||
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=wss
|
||||
|
||||
# App
|
||||
NEXT_PUBLIC_APP_HOST=explore.zoo.ngo
|
||||
NEXT_PUBLIC_APP_PROTOCOL=https
|
||||
NEXT_PUBLIC_IS_TESTNET=false
|
||||
|
||||
# Branding
|
||||
NEXT_PUBLIC_NETWORK_ORG_NAME=Zoo Labs Foundation
|
||||
NEXT_PUBLIC_NETWORK_WEBSITE_URL=https://zoo.ngo
|
||||
NEXT_PUBLIC_NETWORK_DESCRIPTION=Open AI research network — decentralized AI and science.
|
||||
NEXT_PUBLIC_NETWORK_GITHUB_URL=https://github.com/zooai
|
||||
NEXT_PUBLIC_NETWORK_TWITTER_URL=https://x.com/zoolabs
|
||||
NEXT_PUBLIC_NETWORK_DISCORD_URL=https://discord.gg/zoolabs
|
||||
|
||||
# Auth (IAM)
|
||||
NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER=oidc
|
||||
NEXT_PUBLIC_OIDC_SERVER_URL=https://zoo.id
|
||||
NEXT_PUBLIC_OIDC_CLIENT_ID=zoo-explore-client-id
|
||||
|
||||
# Theme
|
||||
NEXT_PUBLIC_COLOR_THEME_DEFAULT=dark
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
# Brand Audit — luxfi/explore
|
||||
|
||||
**Rule:** luxfi/explore committed source contains only Lux brand. Per-chain
|
||||
identity for subnets on Lux primary network (Zoo, Hanzo, SPC, Pars) is
|
||||
permitted as network metadata. No external-org explorer recipes, no Liquidity
|
||||
references.
|
||||
|
||||
## Scope surveyed
|
||||
|
||||
- All committed source under `~/work/lux/explore/` (excluding `node_modules/`,
|
||||
`.next/`, `test-results/`, `qa-screenshots/`).
|
||||
- Source globs: `*.{json,ts,tsx,js,jsx,md,yaml,yml,html,Dockerfile,env*}`.
|
||||
|
||||
## Findings
|
||||
|
||||
### Cross-brand recipes (REMOVED)
|
||||
|
||||
| Path | Why removed |
|
||||
|------|-------------|
|
||||
| `.env.example.hanzo` | Configured the image to be built and served as **Hanzo Explorer** under Hanzo's domain. That makes luxfi/explore a Hanzo brand surface — not allowed. |
|
||||
| `.env.example.zoo` | Same as above for Zoo. |
|
||||
| `.env.example.pars` | Same as above for Pars. |
|
||||
| `Dockerfile.liquidity` | Liquidity is regulated US securities — must NEVER appear in OSS Lux. |
|
||||
|
||||
### Third-party-domain hostnames in `configs/app/chainRegistry.ts` (REMOVED)
|
||||
|
||||
Removed hostnames from the chain registry that asserted "this image, served
|
||||
at the third-party org's canonical domain, IS that org's branded explorer":
|
||||
|
||||
- `explore.zoo.network`, `explore.zoo.ngo` (Zoo entry)
|
||||
- `explore.hanzo.network`, `explore.hanzo.ai` (Hanzo entry)
|
||||
- `explore.pars.network` (Pars entry)
|
||||
|
||||
Kept: `explore-{zoo,hanzo,spc,pars}.lux.network` — these are Lux-served
|
||||
subnet explorer hosts where the chain identity (logo + name) is rendered as
|
||||
network metadata for a chain running on the Lux primary network. That is the
|
||||
per-chain branding the audit rules explicitly allow.
|
||||
|
||||
### Multi-brand selector (REWRITTEN)
|
||||
|
||||
`lib/white-label.ts` previously contained a hostname-suffix table mapping
|
||||
`*.hanzo.ai`, `*.hanzo.network`, `*.zoo.ngo`, `*.zoo.network`,
|
||||
`*.pars.network` to brand identifiers `hanzo`/`zoo`/`pars`. The intent was a
|
||||
single image that becomes a different org's explorer based on serving host.
|
||||
|
||||
Replaced with: only Lux suffixes (`.lux.network`, `.lux.build`,
|
||||
`.lux-test.network`, `.lux-dev.network`). External operators continue to be
|
||||
supported via `NEXT_PUBLIC_BRAND=other` + `NEXT_PUBLIC_NETWORK_*` env vars
|
||||
(see `.env.example.external`) — they supply their own brand strings at
|
||||
deploy time; none are hardcoded here.
|
||||
|
||||
### Comment edits
|
||||
|
||||
- `lib/requestHost.ts`: dropped "Liquidity" from the multi-host narrative.
|
||||
|
||||
### Per-chain identity (KEPT — allowed)
|
||||
|
||||
- `configs/app/chainRegistry.ts` — `ZOO_BRANDING`, `HANZO_BRANDING`,
|
||||
`SPC_BRANDING`, `PARS_BRANDING` constants. These render as **chain
|
||||
identity** (which subnet this block / address belongs to) on
|
||||
`explore-{org}.lux.network` hosts. That's network metadata for chains
|
||||
running on Lux primary network, not a third-party brand on the explorer.
|
||||
- `ui/chains/ChainDetailPage.tsx` `SUBNET_CHAIN_IDS` / `SUBNET_DESCRIPTIONS`
|
||||
maps — chain identity for subnets, kept.
|
||||
|
||||
### Generic finance terms (KEPT — not brand)
|
||||
|
||||
DeFi vocabulary that incidentally contains the substring "Liquidity":
|
||||
|
||||
- `ui/pool/PoolInfo.tsx`, `ui/pools/PoolsTable.tsx`,
|
||||
`ui/pools/PoolsListItem.tsx`, `ui/pages/Pool.tsx` — "Liquidity Provider",
|
||||
"Liquidity" column header.
|
||||
- `stubs/token.ts` — `addLiquidity` method literal in transaction stub.
|
||||
- `lib/api/dchain/useDexData.ts` — `totalLiquidity` field name.
|
||||
- `ui/showcases/Tabs.tsx` — generic "Liquidity staking" tab demo.
|
||||
|
||||
None of these reference the regulated Liquidity brand or its parent entity.
|
||||
|
||||
## Federation manifest
|
||||
|
||||
Added `public/.well-known/explore.json` plus a generator at
|
||||
`scripts/generate-well-known.js`. The generator parses
|
||||
`configs/app/chainRegistry.ts` so the published federation manifest stays in
|
||||
sync with the chain registry.
|
||||
|
||||
- Wired into `npm scripts`: `build`, `build:next`, `wellknown:generate`,
|
||||
`wellknown:check`.
|
||||
- Mainnet chains emitted: C-Chain (Lux Mainnet) + Lux Testnet + Lux Devnet +
|
||||
Zoo/Hanzo/SPC/Pars subnets (chain identity, not org brand).
|
||||
- `peers: []` is populated at runtime by a ConfigMap mount that overlays
|
||||
`public/.well-known/explore.json` in production; each deploy environment
|
||||
knows which other federation peers (Hanzo Explorer, Zoo Explorer, etc.)
|
||||
are reachable.
|
||||
|
||||
## Files touched
|
||||
|
||||
```
|
||||
D .env.example.hanzo
|
||||
D .env.example.pars
|
||||
D .env.example.zoo
|
||||
D Dockerfile.liquidity
|
||||
M configs/app/chainRegistry.ts
|
||||
M lib/requestHost.ts
|
||||
M lib/white-label.ts
|
||||
M package.json
|
||||
A public/.well-known/explore.json
|
||||
A scripts/generate-well-known.js
|
||||
A BRAND-AUDIT.md
|
||||
```
|
||||
|
||||
## Follow-ups
|
||||
|
||||
1. Federation peers list — agree on the ConfigMap schema across Hanzo / Zoo /
|
||||
Pars explorer deployments; current `peers: []` placeholder is correct for
|
||||
build-time defaults.
|
||||
2. `LUX_BRANDING_SETUP.md` still mentions an `nginx` reverse proxy and a
|
||||
`docker-luxnet` path that no longer exist. Rewrite next pass — not blocking.
|
||||
3. `Dockerfile.branded` is a stale env-overlay image; superseded by
|
||||
`Dockerfile.lux`. Candidate for removal in a follow-up clean-up.
|
||||
@@ -1,13 +0,0 @@
|
||||
FROM node:22-alpine
|
||||
WORKDIR /app
|
||||
COPY .next/standalone ./
|
||||
COPY .next/static .next/static
|
||||
COPY public public
|
||||
COPY scripts/download-external-assets.sh scripts/
|
||||
COPY scripts/validate-envs.sh scripts/
|
||||
COPY configs/envs configs/envs
|
||||
COPY entrypoint.sh .
|
||||
RUN chmod +x entrypoint.sh scripts/*.sh
|
||||
ENV PORT=3000 HOSTNAME=0.0.0.0
|
||||
EXPOSE 3000
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
@@ -268,7 +268,7 @@ export const CHAINS: ReadonlyArray<ChainEntry> = [
|
||||
vm: 'L2',
|
||||
network: 'mainnet',
|
||||
chainId: 200200,
|
||||
hostnames: [ 'explore-zoo.lux.network', 'explore.zoo.network', 'explore.zoo.ngo' ],
|
||||
hostnames: [ 'explore-zoo.lux.network' ],
|
||||
explorerUrl: 'https://explore-zoo.lux.network',
|
||||
apiUrl: 'https://api-explore-zoo.lux.network',
|
||||
branding: ZOO_BRANDING,
|
||||
@@ -279,7 +279,7 @@ export const CHAINS: ReadonlyArray<ChainEntry> = [
|
||||
vm: 'L2',
|
||||
network: 'mainnet',
|
||||
chainId: 36963,
|
||||
hostnames: [ 'explore-hanzo.lux.network', 'explore.hanzo.network', 'explore.hanzo.ai' ],
|
||||
hostnames: [ 'explore-hanzo.lux.network' ],
|
||||
explorerUrl: 'https://explore-hanzo.lux.network',
|
||||
apiUrl: 'https://api-explore-hanzo.lux.network',
|
||||
branding: HANZO_BRANDING,
|
||||
@@ -301,7 +301,7 @@ export const CHAINS: ReadonlyArray<ChainEntry> = [
|
||||
vm: 'L2',
|
||||
network: 'mainnet',
|
||||
chainId: 494949,
|
||||
hostnames: [ 'explore-pars.lux.network', 'explore.pars.network' ],
|
||||
hostnames: [ 'explore-pars.lux.network' ],
|
||||
explorerUrl: 'https://explore-pars.lux.network',
|
||||
apiUrl: 'https://api-explore-pars.lux.network',
|
||||
branding: PARS_BRANDING,
|
||||
|
||||
+3
-2
@@ -7,8 +7,9 @@
|
||||
//
|
||||
// This store captures `req.headers.host` at the start of each SSR render
|
||||
// (see `_document.tsx`), so `getHostname()` on the server returns the
|
||||
// actual requested host. The same docker image now serves Lux, Zoo,
|
||||
// Hanzo, SPC, Pars, and Liquidity interchangeably — one image, N hosts.
|
||||
// actual requested host. The same docker image serves all chains on the
|
||||
// Lux primary network (C, Zoo, Hanzo, SPC, Pars subnets) at distinct
|
||||
// explore-*.lux.network hosts — one image, N hosts.
|
||||
//
|
||||
// Client side is unaffected: `window.location.hostname` continues to be
|
||||
// the source of truth post-hydration.
|
||||
|
||||
+12
-27
@@ -1,33 +1,24 @@
|
||||
// White-label brand detection.
|
||||
//
|
||||
// Source code in this repo is brand-neutral. Brand selection happens at
|
||||
// deploy time via `NEXT_PUBLIC_BRAND` env var or by hostname inspection.
|
||||
// All brand-specific assets (logo, title, favicon, color theme, IAM URL)
|
||||
// load from `NEXT_PUBLIC_*` env vars — no hardcoded brand strings here.
|
||||
// Source code in this repo is Lux-only. Per-chain identity (Zoo / Hanzo / SPC /
|
||||
// Pars subnets on Lux primary network) is rendered with each chain's logo and
|
||||
// name as network metadata — see `configs/app/chainRegistry.ts`. The explorer
|
||||
// brand itself is always Lux.
|
||||
//
|
||||
// Supported brands:
|
||||
// - lux (default)
|
||||
// - hanzo
|
||||
// - zoo
|
||||
// - pars
|
||||
// - other (any external white-label deploy: branding from env vars)
|
||||
//
|
||||
// External deployments select their brand by setting NEXT_PUBLIC_BRAND in
|
||||
// the runtime environment. They do NOT add their brand here — keeping this
|
||||
// repo free of any third-party trademarks.
|
||||
// External operators running their own EVM chain can deploy this image as an
|
||||
// unbranded ("other") explorer by setting NEXT_PUBLIC_BRAND=other plus the
|
||||
// NEXT_PUBLIC_NETWORK_* env vars (see `.env.example.external`). They MUST
|
||||
// supply their own brand strings at runtime — none are hardcoded here.
|
||||
|
||||
import { getEnvValue } from 'configs/app/utils';
|
||||
|
||||
export type WhiteLabelBrand = 'lux' | 'hanzo' | 'zoo' | 'pars' | 'other';
|
||||
export type WhiteLabelBrand = 'lux' | 'other';
|
||||
|
||||
const BRAND_HOSTNAME_SUFFIXES: ReadonlyArray<{ readonly suffix: string; readonly brand: WhiteLabelBrand }> = [
|
||||
{ suffix: '.lux.network', brand: 'lux' },
|
||||
{ suffix: '.lux.build', brand: 'lux' },
|
||||
{ suffix: '.hanzo.ai', brand: 'hanzo' },
|
||||
{ suffix: '.hanzo.network', brand: 'hanzo' },
|
||||
{ suffix: '.zoo.ngo', brand: 'zoo' },
|
||||
{ suffix: '.zoo.network', brand: 'zoo' },
|
||||
{ suffix: '.pars.network', brand: 'pars' },
|
||||
{ suffix: '.lux-test.network', brand: 'lux' },
|
||||
{ suffix: '.lux-dev.network', brand: 'lux' },
|
||||
];
|
||||
|
||||
export function getWhiteLabelBrand(hostname?: string): WhiteLabelBrand {
|
||||
@@ -35,13 +26,7 @@ export function getWhiteLabelBrand(hostname?: string): WhiteLabelBrand {
|
||||
const envBrand = getEnvValue('NEXT_PUBLIC_BRAND');
|
||||
if (envBrand) {
|
||||
const normalized = envBrand.toLowerCase();
|
||||
if (
|
||||
normalized === 'lux' ||
|
||||
normalized === 'hanzo' ||
|
||||
normalized === 'zoo' ||
|
||||
normalized === 'pars' ||
|
||||
normalized === 'other'
|
||||
) {
|
||||
if (normalized === 'lux' || normalized === 'other') {
|
||||
return normalized;
|
||||
}
|
||||
// Any unknown brand string → 'other' (env-driven white-label)
|
||||
|
||||
+4
-2
@@ -11,8 +11,10 @@
|
||||
"dev": "./tools/scripts/dev.sh",
|
||||
"dev:preset": "./tools/scripts/dev.preset.sh",
|
||||
"dev:preset:sync": "tsc -p ./tools/preset-sync/tsconfig.json && node ./tools/preset-sync/index.js",
|
||||
"build": "next build --webpack",
|
||||
"build:next": "./deploy/scripts/download_assets.sh ./public/assets/configs && pnpm svg:build-sprite && ./deploy/scripts/make_envs_script.sh && next build --webpack",
|
||||
"build": "node scripts/generate-well-known.js && next build --webpack",
|
||||
"build:next": "./deploy/scripts/download_assets.sh ./public/assets/configs && pnpm svg:build-sprite && ./deploy/scripts/make_envs_script.sh && node scripts/generate-well-known.js && next build --webpack",
|
||||
"wellknown:generate": "node scripts/generate-well-known.js",
|
||||
"wellknown:check": "node scripts/generate-well-known.js --check",
|
||||
"build:docker": "./tools/scripts/build.docker.sh",
|
||||
"start": "next start",
|
||||
"start:docker:local": "docker run -p 3000:3000 --env-file .env.local lux-explore:local",
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"brandId": "lux",
|
||||
"appId": "explore",
|
||||
"title": "Lux Explorer",
|
||||
"domain": "explore.lux.network",
|
||||
"url": "https://explore.lux.network",
|
||||
"github": "https://github.com/luxfi/explore",
|
||||
"chains": [
|
||||
{
|
||||
"id": 96369,
|
||||
"name": "Lux Mainnet"
|
||||
},
|
||||
{
|
||||
"id": 96368,
|
||||
"name": "Lux Testnet"
|
||||
},
|
||||
{
|
||||
"id": 96370,
|
||||
"name": "Lux Devnet"
|
||||
},
|
||||
{
|
||||
"id": 200200,
|
||||
"name": "Zoo Subnet"
|
||||
},
|
||||
{
|
||||
"id": 36963,
|
||||
"name": "Hanzo Subnet"
|
||||
},
|
||||
{
|
||||
"id": 36911,
|
||||
"name": "SPC Subnet"
|
||||
},
|
||||
{
|
||||
"id": 494949,
|
||||
"name": "Pars Subnet"
|
||||
}
|
||||
],
|
||||
"peers": [],
|
||||
"capabilities": [
|
||||
"blocks",
|
||||
"transactions",
|
||||
"accounts",
|
||||
"tokens"
|
||||
],
|
||||
"apiVersion": "1"
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Generate public/.well-known/explore.json from configs/app/chainRegistry.ts.
|
||||
*
|
||||
* Run at build time so the federation manifest is always in sync with the
|
||||
* chains the explorer actually knows about. Output is byte-identical when
|
||||
* the registry hasn't changed (stable key order, trailing newline).
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/generate-well-known.js # write public/.well-known/explore.json
|
||||
* node scripts/generate-well-known.js --check # exit 1 if file out of sync
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const REPO_ROOT = path.resolve(__dirname, '..');
|
||||
const REGISTRY_PATH = path.join(REPO_ROOT, 'configs/app/chainRegistry.ts');
|
||||
const OUT_PATH = path.join(REPO_ROOT, 'public/.well-known/explore.json');
|
||||
|
||||
/** Parse a minimal subset of chainRegistry.ts to extract { chainId, label, network } per CHAINS[] entry.
|
||||
* Avoids running TypeScript at build time — the registry is hand-edited and stable enough for regex.
|
||||
*/
|
||||
function parseChainEntries(src) {
|
||||
const start = src.indexOf('export const CHAINS');
|
||||
if (start < 0) throw new Error('CHAINS export not found in chainRegistry.ts');
|
||||
const end = src.indexOf('];', start);
|
||||
if (end < 0) throw new Error('CHAINS export not terminated');
|
||||
const block = src.slice(start, end);
|
||||
|
||||
// Match every chain object literal: { name: ..., label: ..., network: ..., chainId: <N>, ... }
|
||||
const entryRe = /name:\s*'([^']+)',[\s\S]*?label:\s*'([^']+)',[\s\S]*?network:\s*'([^']+)',[\s\S]*?chainId:\s*(\d+)/g;
|
||||
const entries = [];
|
||||
let m;
|
||||
while ((m = entryRe.exec(block)) !== null) {
|
||||
entries.push({ name: m[1], label: m[2], network: m[3], chainId: Number(m[4]) });
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
/** Subnet display name. C-Chain (Contract Chain) is handled separately by
|
||||
* the caller; for other chains use the short `name` field plus " Subnet". */
|
||||
function normalizeSubnetName(rawName) {
|
||||
return `${ rawName.trim() } Subnet`;
|
||||
}
|
||||
|
||||
/** Build the federation manifest. Mainnet chains only — testnet/devnet are operator-visible
|
||||
* but not federation peers. Localnet is excluded entirely (chainId 1337 is reserved).
|
||||
*/
|
||||
function buildManifest(entries) {
|
||||
const chains = entries
|
||||
.filter((e) => e.network === 'mainnet')
|
||||
.map((e) => ({
|
||||
id: e.chainId,
|
||||
name: e.label === 'Contract Chain' ? 'Lux Mainnet' : normalizeSubnetName(e.name),
|
||||
}));
|
||||
|
||||
// Always include explicit Lux Testnet/Devnet so peers can discover them.
|
||||
const testnet = entries.find((e) => e.network === 'testnet' && e.label === 'Contract Chain');
|
||||
const devnet = entries.find((e) => e.network === 'devnet' && e.label === 'Contract Chain');
|
||||
if (testnet) chains.splice(1, 0, { id: testnet.chainId, name: 'Lux Testnet' });
|
||||
if (devnet) chains.splice(2, 0, { id: devnet.chainId, name: 'Lux Devnet' });
|
||||
|
||||
return {
|
||||
brandId: 'lux',
|
||||
appId: 'explore',
|
||||
title: 'Lux Explorer',
|
||||
domain: 'explore.lux.network',
|
||||
url: 'https://explore.lux.network',
|
||||
github: 'https://github.com/luxfi/explore',
|
||||
chains,
|
||||
peers: [],
|
||||
capabilities: [ 'blocks', 'transactions', 'accounts', 'tokens' ],
|
||||
apiVersion: '1',
|
||||
};
|
||||
}
|
||||
|
||||
function main() {
|
||||
const src = fs.readFileSync(REGISTRY_PATH, 'utf8');
|
||||
const manifest = buildManifest(parseChainEntries(src));
|
||||
const out = JSON.stringify(manifest, null, 2) + '\n';
|
||||
|
||||
const check = process.argv.includes('--check');
|
||||
if (check) {
|
||||
const current = fs.existsSync(OUT_PATH) ? fs.readFileSync(OUT_PATH, 'utf8') : '';
|
||||
if (current !== out) {
|
||||
process.stderr.write(`${ path.relative(REPO_ROOT, OUT_PATH) } is out of sync. Run: node scripts/generate-well-known.js\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
fs.mkdirSync(path.dirname(OUT_PATH), { recursive: true });
|
||||
fs.writeFileSync(OUT_PATH, out);
|
||||
process.stdout.write(`Wrote ${ path.relative(REPO_ROOT, OUT_PATH) } (${ manifest.chains.length } chains)\n`);
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user