From ed9440bd7011102c39c687fb8c348d7418b14383 Mon Sep 17 00:00:00 2001 From: Hanzo AI Date: Tue, 5 May 2026 19:07:22 -0700 Subject: [PATCH 1/2] explore: scrub Avalabs IP + liquidity cross-contamination Remove Liquidity Network chain entries (mainnet/testnet/devnet/localnet) and LIQUIDITY_BRANDING constant from chainRegistry.ts. Lux explorer must not register liquidity.io/satschel hostnames. Replace "Liquid EVM Explorer" top-bar tagline with "Lux Explorer". --- configs/app/chainRegistry.ts | 68 +++-------------------------------- ui/snippets/topBar/TopBar.tsx | 2 +- 2 files changed, 6 insertions(+), 64 deletions(-) diff --git a/configs/app/chainRegistry.ts b/configs/app/chainRegistry.ts index c4cc62f500..1f0fe63322 100644 --- a/configs/app/chainRegistry.ts +++ b/configs/app/chainRegistry.ts @@ -214,22 +214,6 @@ const PARS_BRANDING: ChainBranding = { '', }; -/** Liquidity — wordmark "L" from ~/work/liquidity/brand/ */ -const LIQUIDITY_BRANDING: ChainBranding = { - brandName: 'Liquidity Network', - orgName: 'Liquidity Inc.', - websiteUrl: 'https://liquidity.io', - description: 'Digital securities exchange — ATS, broker-dealer, transfer agent.', - githubUrl: 'https://github.com/liquidityio', - twitterUrl: 'https://x.com/liquidityio', - discordUrl: 'https://discord.gg/liquidityio', - logoViewBox: '0 0 100 100', - logoContent: '', - faviconContent: - '' + - '', -}; - export const CHAINS: ReadonlyArray = [ { name: 'C-Chain', @@ -283,16 +267,6 @@ export const CHAINS: ReadonlyArray = [ apiUrl: 'https://api-explore-pars.lux.network', branding: PARS_BRANDING, }, - { - name: 'Liquidity', - label: 'Liquidity Network', - vm: 'Liquid EVM', - network: 'mainnet', - hostnames: [ 'explore.liquidity.io', 'explore.satschel.com' ], - explorerUrl: 'https://explore.liquidity.io', - apiUrl: 'https://api-explore.liquidity.io', - branding: LIQUIDITY_BRANDING, - }, // Testnet chains { name: 'C-Chain', @@ -304,16 +278,6 @@ export const CHAINS: ReadonlyArray = [ apiUrl: 'https://api-explore-test.lux.network', branding: LUX_BRANDING, }, - { - name: 'Liquidity', - label: 'Liquidity Network', - vm: 'Liquid EVM', - network: 'testnet', - hostnames: [ 'explore.test.satschel.com' ], - explorerUrl: 'https://explore.test.satschel.com', - apiUrl: 'https://api-explore.test.satschel.com', - branding: LIQUIDITY_BRANDING, - }, // Devnet chains { name: 'C-Chain', @@ -325,20 +289,10 @@ export const CHAINS: ReadonlyArray = [ apiUrl: 'https://api-explore-dev.lux.network', branding: LUX_BRANDING, }, - { - name: 'Liquidity', - label: 'Liquidity Network', - vm: 'Liquid EVM', - network: 'devnet', - hostnames: [ 'explore.dev.satschel.com' ], - explorerUrl: 'https://explore.dev.satschel.com', - apiUrl: 'https://api-explore.dev.satschel.com', - branding: LIQUIDITY_BRANDING, - }, - // Localnet — chainId 1337 (Lux) / 8675312 (Liquidity). Listing localhost / - // 127.0.0.1 / 0.0.0.0 here keeps isWhiteLabelMode() false in local dev so - // the chain + network selectors remain visible without env gymnastics. + // Localnet — chainId 1337. Listing localhost / 127.0.0.1 / 0.0.0.0 here + // keeps isWhiteLabelMode() false in local dev so the chain + network + // selectors remain visible without env gymnastics. { name: 'C-Chain', label: 'Contract Chain', @@ -349,16 +303,6 @@ export const CHAINS: ReadonlyArray = [ apiUrl: 'http://localhost:4000', branding: LUX_BRANDING, }, - { - name: 'Liquidity', - label: 'Liquidity Network', - vm: 'Liquid EVM', - network: 'localnet', - hostnames: [ 'explore.local.liquidity.io', 'explore.local.satschel.com' ], - explorerUrl: 'http://explore.local.liquidity.io', - apiUrl: 'http://api-explore.local.liquidity.io', - branding: LIQUIDITY_BRANDING, - }, ]; export const NETWORKS: ReadonlyArray = [ @@ -443,13 +387,11 @@ function getHostname(): string { // health checks, etc.). try { // Lazy require so browser bundles don't pick up the ALS module. - // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require + const { getRequestHost } = require('lib/requestHost') as { getRequestHost: () => string }; const reqHost = getRequestHost(); if (reqHost) return reqHost; - } catch { - // Falls through to env fallback. - } + } catch { /* Falls through to env fallback. */ } return getEnvValue('NEXT_PUBLIC_APP_HOST') || 'explore.lux.network'; } diff --git a/ui/snippets/topBar/TopBar.tsx b/ui/snippets/topBar/TopBar.tsx index e49a0275ab..33a7c9a830 100644 --- a/ui/snippets/topBar/TopBar.tsx +++ b/ui/snippets/topBar/TopBar.tsx @@ -103,7 +103,7 @@ const TopBar = () => { 'text-[10px] tracking-[0.03em] whitespace-nowrap text-[var(--color-text-secondary)] transition-all duration-300', isHome ? 'max-h-[14px] opacity-100 mt-[2px]' : 'max-h-0 opacity-0 mt-0', ) }> - Liquid EVM Explorer + Lux Explorer From d4132b0ccdf77e186da3afd16836aa78f683720e Mon Sep 17 00:00:00 2001 From: hanzo-dev Date: Tue, 5 May 2026 20:50:10 -0700 Subject: [PATCH 2/2] feat: multi-brand build matrix + white-label support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build the same source four ways from one repo: - ghcr.io/luxfi/explore (lux brand, default) - ghcr.io/luxfi/explore-hanzo (hanzo brand) - ghcr.io/luxfi/explore-zoo (zoo brand) - ghcr.io/luxfi/explore-pars (pars brand) Each is built for linux/amd64 + linux/arm64 in parallel on hanzo ARC runners (no QEMU), then merged into a multi-arch manifest. Brand selection at build time via NEXT_PUBLIC_BRAND, with all brand-specific assets (logo, favicon, theme, IAM URL) loaded from NEXT_PUBLIC_* env vars — no hardcoded brand strings in source. External operators set NEXT_PUBLIC_BRAND=other and supply their own NEXT_PUBLIC_* env vars (see .env.example.external). The repo carries no third-party trademarks. Files: - lib/white-label.ts brand detection + helpers - .env.example.{lux,hanzo,zoo,pars,external} deploy templates - .github/workflows/build-lux.yml 4-brand × 2-arch matrix - Dockerfile accept NEXT_PUBLIC_BRAND - configs/app/chainRegistry.ts add SPC + Pars devnet, chainId fields, white-label chain builder --- .env.example.external | 49 ++++++++++++++++ .env.example.hanzo | 39 +++++++++++++ .env.example.lux | 40 +++++++++++++ .env.example.pars | 39 +++++++++++++ .env.example.zoo | 39 +++++++++++++ .github/workflows/build-lux.yml | 80 +++++++++++++++++++++---- Dockerfile | 3 + configs/app/chainRegistry.ts | 100 ++++++++++++++++++++++++++++++++ lib/white-label.ts | 67 +++++++++++++++++++++ 9 files changed, 446 insertions(+), 10 deletions(-) create mode 100644 .env.example.external create mode 100644 .env.example.hanzo create mode 100644 .env.example.lux create mode 100644 .env.example.pars create mode 100644 .env.example.zoo create mode 100644 lib/white-label.ts diff --git a/.env.example.external b/.env.example.external new file mode 100644 index 0000000000..28c7db44ad --- /dev/null +++ b/.env.example.external @@ -0,0 +1,49 @@ +# External white-label deployment template. +# +# This file documents the env vars an external operator must set when running +# this image with their own brand. The source code is brand-neutral — all +# strings below are operator-supplied at deploy time. +# +# Copy and customize for your deployment: +# cp .env.example.external .env.local +# +# Replace every with your own values. NEVER commit a populated +# version of this file with third-party trademarks back to this repo. + +NEXT_PUBLIC_BRAND=other + +# Network +NEXT_PUBLIC_NETWORK_NAME= +NEXT_PUBLIC_NETWORK_SHORT_NAME= +NEXT_PUBLIC_NETWORK_ID= +NEXT_PUBLIC_NETWORK_CURRENCY_NAME= +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL= +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_RPC_URL=https:///rpc + +# API +NEXT_PUBLIC_API_HOST= +NEXT_PUBLIC_API_PROTOCOL=https +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=wss + +# App +NEXT_PUBLIC_APP_HOST= +NEXT_PUBLIC_APP_PROTOCOL=https +NEXT_PUBLIC_IS_TESTNET=false + +# Branding (operator-supplied) +NEXT_PUBLIC_NETWORK_ORG_NAME= +NEXT_PUBLIC_NETWORK_WEBSITE_URL=https:// +NEXT_PUBLIC_NETWORK_DESCRIPTION= +NEXT_PUBLIC_NETWORK_GITHUB_URL= +NEXT_PUBLIC_NETWORK_TWITTER_URL= +NEXT_PUBLIC_NETWORK_DISCORD_URL= + +# Auth (IAM) — operator-supplied OIDC provider +NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER=oidc +NEXT_PUBLIC_OIDC_SERVER_URL= +NEXT_PUBLIC_OIDC_CLIENT_ID= + +# Theme +NEXT_PUBLIC_COLOR_THEME_DEFAULT=dark diff --git a/.env.example.hanzo b/.env.example.hanzo new file mode 100644 index 0000000000..be81b89a76 --- /dev/null +++ b/.env.example.hanzo @@ -0,0 +1,39 @@ +# 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 diff --git a/.env.example.lux b/.env.example.lux new file mode 100644 index 0000000000..2ad3963df8 --- /dev/null +++ b/.env.example.lux @@ -0,0 +1,40 @@ +# Lux Explorer — default brand. +# `pnpm build` with no NEXT_PUBLIC_BRAND override produces a Lux-branded site. + +NEXT_PUBLIC_BRAND=lux + +# Network +NEXT_PUBLIC_NETWORK_NAME=Lux Network +NEXT_PUBLIC_NETWORK_SHORT_NAME=LUX +NEXT_PUBLIC_NETWORK_ID=96369 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=LUX +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=LUX +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_RPC_URL=https://api.lux.network/ext/bc/C/rpc + +# API +NEXT_PUBLIC_API_HOST=api-explore.lux.network +NEXT_PUBLIC_API_PROTOCOL=https +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=wss + +# App +NEXT_PUBLIC_APP_HOST=explore.lux.network +NEXT_PUBLIC_APP_PROTOCOL=https +NEXT_PUBLIC_IS_TESTNET=false + +# Branding +NEXT_PUBLIC_NETWORK_ORG_NAME=Lux Industries Inc. +NEXT_PUBLIC_NETWORK_WEBSITE_URL=https://lux.network +NEXT_PUBLIC_NETWORK_DESCRIPTION=High-performance blockchain for decentralized applications. +NEXT_PUBLIC_NETWORK_GITHUB_URL=https://github.com/luxfi +NEXT_PUBLIC_NETWORK_TWITTER_URL=https://x.com/luxdefi +NEXT_PUBLIC_NETWORK_DISCORD_URL=https://discord.gg/luxnetwork + +# Auth (IAM) +NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER=oidc +NEXT_PUBLIC_OIDC_SERVER_URL=https://lux.id +NEXT_PUBLIC_OIDC_CLIENT_ID=lux-explore-client-id + +# Theme +NEXT_PUBLIC_COLOR_THEME_DEFAULT=dark diff --git a/.env.example.pars b/.env.example.pars new file mode 100644 index 0000000000..e716a38428 --- /dev/null +++ b/.env.example.pars @@ -0,0 +1,39 @@ +# 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 diff --git a/.env.example.zoo b/.env.example.zoo new file mode 100644 index 0000000000..abaaa7a516 --- /dev/null +++ b/.env.example.zoo @@ -0,0 +1,39 @@ +# 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 diff --git a/.github/workflows/build-lux.yml b/.github/workflows/build-lux.yml index 1e228352a6..5055cb0787 100644 --- a/.github/workflows/build-lux.yml +++ b/.github/workflows/build-lux.yml @@ -21,14 +21,28 @@ concurrency: env: REGISTRY: ghcr.io - IMAGE_NAME: luxfi/explore jobs: build: - runs-on: hanzo-build-linux-amd64 + name: Build ${{ matrix.brand }} (${{ matrix.arch }}) + runs-on: hanzo-build-linux-${{ matrix.arch }} permissions: contents: read packages: write + strategy: + fail-fast: false + matrix: + brand: [ lux, hanzo, zoo, pars ] + arch: [ amd64, arm64 ] + include: + - brand: lux + image: luxfi/explore + - brand: hanzo + image: luxfi/explore-hanzo + - brand: zoo + image: luxfi/explore-zoo + - brand: pars + image: luxfi/explore-pars steps: - name: Checkout uses: actions/checkout@v4 @@ -47,32 +61,78 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + images: ${{ env.REGISTRY }}/${{ matrix.image }} flavor: | latest=false tags: | - type=raw,value=latest,enable={{is_default_branch}} - type=ref,event=branch - type=semver,pattern={{version}} + type=raw,value=latest-${{ matrix.arch }},enable={{is_default_branch}} + type=ref,event=branch,suffix=-${{ matrix.arch }} + type=semver,pattern={{version}},suffix=-${{ matrix.arch }} - name: Build and push image id: build uses: docker/build-push-action@v6 with: context: . - platforms: linux/amd64 + platforms: linux/${{ matrix.arch }} labels: ${{ steps.meta.outputs.labels }} tags: ${{ steps.meta.outputs.tags }} - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=${{ matrix.brand }}-${{ matrix.arch }} + cache-to: type=gha,mode=max,scope=${{ matrix.brand }}-${{ matrix.arch }} provenance: false push: ${{ github.event_name != 'pull_request' }} build-args: | GIT_COMMIT_SHA=${{ github.sha }} GIT_TAG=${{ github.ref_type == 'tag' && github.ref_name || '' }} + NEXT_PUBLIC_BRAND=${{ matrix.brand }} - deploy: + manifest: + name: Multi-arch manifest (${{ matrix.brand }}) needs: build + if: github.event_name != 'pull_request' + runs-on: hanzo-build-linux-amd64 + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + include: + - brand: lux + image: luxfi/explore + - brand: hanzo + image: luxfi/explore-hanzo + - brand: zoo + image: luxfi/explore-zoo + - brand: pars + image: luxfi/explore-pars + steps: + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Create and push manifest (latest) + if: github.ref == 'refs/heads/main' + run: | + docker buildx imagetools create \ + -t ${{ env.REGISTRY }}/${{ matrix.image }}:latest \ + ${{ env.REGISTRY }}/${{ matrix.image }}:latest-amd64 \ + ${{ env.REGISTRY }}/${{ matrix.image }}:latest-arm64 + + - name: Create and push manifest (tag) + if: github.ref_type == 'tag' + run: | + TAG="${GITHUB_REF_NAME#v}" + docker buildx imagetools create \ + -t ${{ env.REGISTRY }}/${{ matrix.image }}:${TAG} \ + ${{ env.REGISTRY }}/${{ matrix.image }}:${TAG}-amd64 \ + ${{ env.REGISTRY }}/${{ matrix.image }}:${TAG}-arm64 + + deploy: + needs: manifest if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' runs-on: hanzo-deploy-linux-amd64 permissions: diff --git a/Dockerfile b/Dockerfile index 6d7ca6c7f6..35dec07269 100644 --- a/Dockerfile +++ b/Dockerfile @@ -56,6 +56,9 @@ ARG GIT_TAG ENV NEXT_PUBLIC_GIT_TAG=$GIT_TAG ARG NEXT_OPEN_TELEMETRY_ENABLED ENV NEXT_OPEN_TELEMETRY_ENABLED=$NEXT_OPEN_TELEMETRY_ENABLED +# White-label brand selector (lux | hanzo | zoo | pars | other). Defaults to lux. +ARG NEXT_PUBLIC_BRAND=lux +ENV NEXT_PUBLIC_BRAND=$NEXT_PUBLIC_BRAND ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 ENV NODE_OPTIONS="--max-old-space-size=8192" diff --git a/configs/app/chainRegistry.ts b/configs/app/chainRegistry.ts index 1f0fe63322..bec4868292 100644 --- a/configs/app/chainRegistry.ts +++ b/configs/app/chainRegistry.ts @@ -39,6 +39,9 @@ export interface ChainEntry { readonly label: string; readonly vm: string; readonly network: 'mainnet' | 'testnet' | 'devnet' | 'localnet'; + + /** EVM chain ID (uint64). Canonical values tracked in MEMORY.md. */ + readonly chainId: number; readonly hostnames: ReadonlyArray; readonly explorerUrl: string; readonly apiUrl: string; @@ -220,6 +223,7 @@ export const CHAINS: ReadonlyArray = [ label: 'Contract Chain', vm: 'EVM', network: 'mainnet', + chainId: 96369, // explore.lux.build is a legacy alias — the runtime ingress 301-redirects // it to explore.lux.network, so we only register the canonical host here. hostnames: [ 'explore.lux.network', 'localhost', '127.0.0.1', '0.0.0.0' ], @@ -232,6 +236,7 @@ export const CHAINS: ReadonlyArray = [ label: 'Zoo Chain', vm: 'L2', network: 'mainnet', + chainId: 200200, hostnames: [ 'explore-zoo.lux.network', 'explore.zoo.network', 'explore.zoo.ngo' ], explorerUrl: 'https://explore-zoo.lux.network', apiUrl: 'https://api-explore-zoo.lux.network', @@ -242,6 +247,7 @@ export const CHAINS: ReadonlyArray = [ label: 'Hanzo AI', vm: 'L2', network: 'mainnet', + chainId: 36963, hostnames: [ 'explore-hanzo.lux.network', 'explore.hanzo.network', 'explore.hanzo.ai' ], explorerUrl: 'https://explore-hanzo.lux.network', apiUrl: 'https://api-explore-hanzo.lux.network', @@ -252,6 +258,7 @@ export const CHAINS: ReadonlyArray = [ 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', @@ -262,6 +269,7 @@ export const CHAINS: ReadonlyArray = [ label: 'Pars Network', vm: 'L2', network: 'mainnet', + chainId: 494949, hostnames: [ 'explore-pars.lux.network', 'explore.pars.network' ], explorerUrl: 'https://explore-pars.lux.network', apiUrl: 'https://api-explore-pars.lux.network', @@ -273,22 +281,112 @@ export const CHAINS: ReadonlyArray = [ label: 'Contract Chain', vm: 'EVM', network: 'testnet', + chainId: 96368, hostnames: [ 'explore-test.lux.network', 'explore.lux-test.network' ], explorerUrl: 'https://explore-test.lux.network', apiUrl: 'https://api-explore-test.lux.network', branding: LUX_BRANDING, }, + { + name: 'Zoo', + label: 'Zoo Chain (Testnet)', + vm: 'L2', + network: 'testnet', + chainId: 200201, + hostnames: [ 'explore-zoo-test.lux.network' ], + explorerUrl: 'https://explore-zoo-test.lux.network', + apiUrl: 'https://api-explore-zoo-test.lux.network', + branding: ZOO_BRANDING, + }, + { + name: 'Hanzo', + label: 'Hanzo AI (Testnet)', + vm: 'L2', + network: 'testnet', + chainId: 36964, + hostnames: [ 'explore-hanzo-test.lux.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: 7071, + hostnames: [ 'explore-pars-test.lux.network' ], + explorerUrl: 'https://explore-pars-test.lux.network', + apiUrl: 'https://api-explore-pars-test.lux.network', + branding: PARS_BRANDING, + }, // Devnet chains { name: 'C-Chain', label: 'Contract Chain', vm: 'EVM', network: 'devnet', + chainId: 96370, 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' ], + 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, + }, // Localnet — chainId 1337. Listing localhost / 127.0.0.1 / 0.0.0.0 here // keeps isWhiteLabelMode() false in local dev so the chain + network @@ -298,6 +396,7 @@ export const CHAINS: ReadonlyArray = [ label: 'Contract Chain', vm: 'EVM', network: 'localnet', + chainId: 1337, hostnames: [ 'explore.localnet', 'explore-local.lux.network' ], explorerUrl: 'http://localhost:3000', apiUrl: 'http://localhost:4000', @@ -368,6 +467,7 @@ function buildWhiteLabelChain(hostname: string): ChainEntry { label: branding.brandName, vm: 'EVM', network: (getEnvValue('NEXT_PUBLIC_IS_TESTNET') === 'true' ? 'testnet' : 'mainnet') as 'mainnet' | 'testnet' | 'devnet' | 'localnet', + chainId: Number(getEnvValue('NEXT_PUBLIC_NETWORK_ID')) || 0, hostnames: [ hostname ], explorerUrl: `${ protocol }://${ appHost }`, apiUrl: apiHost ? `${ apiProtocol }://${ apiHost }` : '', diff --git a/lib/white-label.ts b/lib/white-label.ts new file mode 100644 index 0000000000..a54588eb33 --- /dev/null +++ b/lib/white-label.ts @@ -0,0 +1,67 @@ +// 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. +// +// 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. + +import { getEnvValue } from 'configs/app/utils'; + +export type WhiteLabelBrand = 'lux' | 'hanzo' | 'zoo' | 'pars' | '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' }, +]; + +export function getWhiteLabelBrand(hostname?: string): WhiteLabelBrand { + // 1. Explicit env var wins (CI/CD, k8s ConfigMap) + const envBrand = getEnvValue('NEXT_PUBLIC_BRAND'); + if (envBrand) { + const normalized = envBrand.toLowerCase(); + if ( + normalized === 'lux' || + normalized === 'hanzo' || + normalized === 'zoo' || + normalized === 'pars' || + normalized === 'other' + ) { + return normalized; + } + // Any unknown brand string → 'other' (env-driven white-label) + return 'other'; + } + + // 2. Hostname-based detection + const host = (hostname || (typeof window !== 'undefined' ? window.location.hostname : '')).toLowerCase(); + if (host) { + for (const entry of BRAND_HOSTNAME_SUFFIXES) { + if (host === entry.suffix.slice(1) || host.endsWith(entry.suffix)) { + return entry.brand; + } + } + } + + // 3. Default + return 'lux'; +} + +export function isWhiteLabelExternal(brand: WhiteLabelBrand): boolean { + return brand === 'other'; +}