Files
explore/scripts/generate-well-known.js
T
zeekay 1a686d955f refactor(pchain): Subnet -> Net; add the mainnet explorer ingress
Drops "subnet" from the P-chain surface, which is not our vocabulary:

    useSubnets          -> useNets      (useSubnets.ts deleted)
    PChainSubnet        -> PChainNet
    GetSubnetsResponse  -> GetNetsResponse

One "subnet" deliberately survives — `subnetID` in lib/api/pchain/wire.spec.ts.
That is the UPSTREAM P-chain wire field, read through a raw
Record<string,string> cast, so renaming it would break response parsing. The
rule is about our own language, not about lying to the wire.

Also adds deploy/k8s/explore-fe/ingress-mainnet.yaml. explore.lux.network is
served by the Next.js deployment explore-fe-lux (ghcr.io/luxfi/explore), not
the Go `explorer` binary, which only backs api-explore.lux.network. This
replaces drift where the plain Ingress pointed explore.lux.network at
explorer:80 — the Go binary's embedded, stale build.

Two pre-commit gates had to be satisfied, both legitimate:
- eslint max-len: three className lines (BridgePage 230 chars, ChainRow 188 x2)
  exceeded the 160 limit; wrapped.
- cspell: `platformvm` flagged unknown. It is the P-chain VM's actual name, so
  it is added to cspell.jsonc `words` rather than renamed in code. (Note for
  the next person: .cspell-words.txt is listed under ignorePaths — it is a file
  excluded from checking, NOT the dictionary. Adding a word there does nothing.)

Verified: tsc --noEmit reports ZERO errors in every file this commit touches and
zero stale-symbol errors, so the rename resolves completely. The project's 223
pre-existing errors are all in untouched files and unchanged by this commit.
2026-07-26 09:11:38 -07:00

102 lines
3.9 KiB
JavaScript

#!/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;
}
/** Display name for a federated chain. C-Chain (Contract Chain) is handled
* separately by the caller; other chains use the short `name` field plus
* " Network". Sovereign L1s are Networks (Lux nomenclature; never the
* legacy upstream term for them). */
function networkDisplayName(rawName) {
return `${ rawName.trim() } Network`;
}
/** 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' : networkDisplayName(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();