explore v1.1.4: native all-chains wallet-connect, login/menu fixes, correct chain manifest

- wallet: brandFamilyChains derives sibling networks (Lux mainnet/testnet/devnet)
  from the multi-tenant chain registry so connect/add presents EVERY registered
  network for the brand, not just the env chain; dedupe by id.
- login/menu: useProvider queryFn returns null (react-query v5 forbids undefined)
  + callers switched from '= {}' destructure default to '?? {}' so a null result
  no longer crashes the wallet hooks (the busted-menu root cause).
- manifest: fix Pars chainId 7070 -> 494949 (regression) and drop the forbidden
  'Subnet' term from sovereign L1 network names (-> '<Brand> Network').
- Validators page + /api/pchain proxy present; rebuild makes the live 5-validator
  view visible on explore.lux.network.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
zeekay
2026-07-08 11:44:04 -07:00
co-authored by Hanzo Dev
parent 75559ce932
commit bbcb1596fd
20 changed files with 1204 additions and 18 deletions
@@ -0,0 +1,76 @@
---
# Per-env runtime config for Blockscout FE. Loaded into the pod via
# Deployment `envFrom: configMapRef:`. Blockscout's `make_envs_script.sh`
# at container start renders /assets/envs.js from process env, so any
# change here requires a rollout-restart, NOT an image rebuild.
#
# Brand identity (NETWORK_NAME, OIDC client ID, colors, currency) lives in
# the image itself (ghcr.io/{org}/explore:vX.Y.Z); env-specific knobs
# (NETWORK_ID, IS_TESTNET, *_HOST, RPC_URL) live here.
apiVersion: v1
kind: ConfigMap
metadata:
name: explore-env-lux
namespace: lux-mainnet
labels: {app: explore-fe, brand: lux, network: mainnet}
data:
NEXT_PUBLIC_NETWORK_ID: "96369"
NEXT_PUBLIC_IS_TESTNET: "false"
NEXT_PUBLIC_API_HOST: api-explore.lux.network
NEXT_PUBLIC_APP_HOST: explore.lux.network
NEXT_PUBLIC_NETWORK_RPC_URL: https://api.lux.network/ext/bc/C/rpc
NEXT_PUBLIC_STATS_API_HOST: https://api-explore.lux.network
NEXT_PUBLIC_VISUALIZE_API_HOST: https://api-explore.lux.network
NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://api-explore.lux.network
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://api-explore.lux.network
---
apiVersion: v1
kind: ConfigMap
metadata:
name: explore-env-zoo
namespace: lux-mainnet
labels: {app: explore-fe, brand: zoo, network: mainnet}
data:
NEXT_PUBLIC_NETWORK_ID: "200200"
NEXT_PUBLIC_IS_TESTNET: "false"
NEXT_PUBLIC_API_HOST: api-explore.zoo.network
NEXT_PUBLIC_APP_HOST: explore.zoo.network
NEXT_PUBLIC_NETWORK_RPC_URL: https://api.zoo.network/ext/bc/zoo/rpc
NEXT_PUBLIC_STATS_API_HOST: https://api-explore.zoo.network
NEXT_PUBLIC_VISUALIZE_API_HOST: https://api-explore.zoo.network
NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://api-explore.zoo.network
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://api-explore.zoo.network
---
apiVersion: v1
kind: ConfigMap
metadata:
name: explore-env-hanzo
namespace: lux-mainnet
labels: {app: explore-fe, brand: hanzo, network: mainnet}
data:
NEXT_PUBLIC_NETWORK_ID: "36963"
NEXT_PUBLIC_IS_TESTNET: "false"
NEXT_PUBLIC_API_HOST: api-explore.hanzo.network
NEXT_PUBLIC_APP_HOST: explore.hanzo.network
NEXT_PUBLIC_NETWORK_RPC_URL: https://api.hanzo.network/ext/bc/hanzo/rpc
NEXT_PUBLIC_STATS_API_HOST: https://api-explore.hanzo.network
NEXT_PUBLIC_VISUALIZE_API_HOST: https://api-explore.hanzo.network
NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://api-explore.hanzo.network
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://api-explore.hanzo.network
---
apiVersion: v1
kind: ConfigMap
metadata:
name: explore-env-pars
namespace: lux-mainnet
labels: {app: explore-fe, brand: pars, network: mainnet}
data:
NEXT_PUBLIC_NETWORK_ID: "7070"
NEXT_PUBLIC_IS_TESTNET: "false"
NEXT_PUBLIC_API_HOST: api-explore.pars.network
NEXT_PUBLIC_APP_HOST: explore.pars.network
NEXT_PUBLIC_NETWORK_RPC_URL: https://api.pars.network/ext/bc/pars/rpc
NEXT_PUBLIC_STATS_API_HOST: https://api-explore.pars.network
NEXT_PUBLIC_VISUALIZE_API_HOST: https://api-explore.pars.network
NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://api-explore.pars.network
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://api-explore.pars.network
@@ -0,0 +1,71 @@
---
# See configmap-mainnet.yaml for the architecture rationale. This file holds
# the testnet runtime overrides for the 4 brand Deployments living in
# namespace `lux-testnet`. Same image as mainnet, different env.
apiVersion: v1
kind: ConfigMap
metadata:
name: explore-env-lux
namespace: lux-testnet
labels: {app: explore-fe, brand: lux, network: testnet}
data:
NEXT_PUBLIC_NETWORK_ID: "96368"
NEXT_PUBLIC_IS_TESTNET: "true"
NEXT_PUBLIC_API_HOST: api-explorer.lux-test.network
NEXT_PUBLIC_APP_HOST: explorer.lux-test.network
NEXT_PUBLIC_NETWORK_RPC_URL: https://api.lux-test.network/ext/bc/C/rpc
NEXT_PUBLIC_STATS_API_HOST: https://api-explorer.lux-test.network
NEXT_PUBLIC_VISUALIZE_API_HOST: https://api-explorer.lux-test.network
NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://api-explorer.lux-test.network
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://api-explorer.lux-test.network
---
apiVersion: v1
kind: ConfigMap
metadata:
name: explore-env-zoo
namespace: lux-testnet
labels: {app: explore-fe, brand: zoo, network: testnet}
data:
NEXT_PUBLIC_NETWORK_ID: "200201"
NEXT_PUBLIC_IS_TESTNET: "true"
NEXT_PUBLIC_API_HOST: api-explorer.zoo-test.network
NEXT_PUBLIC_APP_HOST: explorer.zoo-test.network
NEXT_PUBLIC_NETWORK_RPC_URL: https://api.zoo-test.network/ext/bc/zoo/rpc
NEXT_PUBLIC_STATS_API_HOST: https://api-explorer.zoo-test.network
NEXT_PUBLIC_VISUALIZE_API_HOST: https://api-explorer.zoo-test.network
NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://api-explorer.zoo-test.network
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://api-explorer.zoo-test.network
---
apiVersion: v1
kind: ConfigMap
metadata:
name: explore-env-hanzo
namespace: lux-testnet
labels: {app: explore-fe, brand: hanzo, network: testnet}
data:
NEXT_PUBLIC_NETWORK_ID: "36962"
NEXT_PUBLIC_IS_TESTNET: "true"
NEXT_PUBLIC_API_HOST: api-explorer.hanzo-test.network
NEXT_PUBLIC_APP_HOST: explorer.hanzo-test.network
NEXT_PUBLIC_NETWORK_RPC_URL: https://api.hanzo-test.network/ext/bc/hanzo/rpc
NEXT_PUBLIC_STATS_API_HOST: https://api-explorer.hanzo-test.network
NEXT_PUBLIC_VISUALIZE_API_HOST: https://api-explorer.hanzo-test.network
NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://api-explorer.hanzo-test.network
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://api-explorer.hanzo-test.network
---
apiVersion: v1
kind: ConfigMap
metadata:
name: explore-env-pars
namespace: lux-testnet
labels: {app: explore-fe, brand: pars, network: testnet}
data:
NEXT_PUBLIC_NETWORK_ID: "7071"
NEXT_PUBLIC_IS_TESTNET: "true"
NEXT_PUBLIC_API_HOST: api-explorer.pars-test.network
NEXT_PUBLIC_APP_HOST: explorer.pars-test.network
NEXT_PUBLIC_NETWORK_RPC_URL: https://api.pars-test.network/ext/bc/pars/rpc
NEXT_PUBLIC_STATS_API_HOST: https://api-explorer.pars-test.network
NEXT_PUBLIC_VISUALIZE_API_HOST: https://api-explorer.pars-test.network
NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://api-explorer.pars-test.network
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://api-explorer.pars-test.network
@@ -0,0 +1,244 @@
---
# Lux Mainnet — canonical brand image + env from ConfigMap.
apiVersion: apps/v1
kind: Deployment
metadata:
name: explore-fe-lux
namespace: lux-mainnet
labels: {app: explore-fe, brand: lux, network: mainnet}
spec:
replicas: 2
selector:
matchLabels: {app: explore-fe, brand: lux, network: mainnet}
strategy:
type: RollingUpdate
rollingUpdate: {maxUnavailable: 1, maxSurge: 1}
template:
metadata:
labels: {app: explore-fe, brand: lux, network: mainnet}
spec:
imagePullSecrets: [{name: ghcr-luxfi}]
containers:
- name: explore
image: ghcr.io/luxfi/explore:v1.0.1
imagePullPolicy: IfNotPresent
ports: [{containerPort: 3000, name: ui}]
env:
- {name: PORT, value: "3000"}
envFrom:
- configMapRef: {name: explore-env-lux}
resources:
requests: {cpu: 100m, memory: 256Mi}
limits: {cpu: 1, memory: 2Gi}
startupProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 10
failureThreshold: 30
readinessProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 30
periodSeconds: 15
timeoutSeconds: 10
failureThreshold: 6
livenessProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 90
periodSeconds: 60
timeoutSeconds: 10
failureThreshold: 5
---
apiVersion: v1
kind: Service
metadata:
name: explore-fe-lux
namespace: lux-mainnet
labels: {app: explore-fe, brand: lux, network: mainnet}
spec:
type: ClusterIP
selector: {app: explore-fe, brand: lux, network: mainnet}
ports: [{name: http, port: 80, targetPort: 3000, protocol: TCP}]
---
# Zoo Mainnet
apiVersion: apps/v1
kind: Deployment
metadata:
name: explore-fe-zoo
namespace: lux-mainnet
labels: {app: explore-fe, brand: zoo, network: mainnet}
spec:
replicas: 2
selector:
matchLabels: {app: explore-fe, brand: zoo, network: mainnet}
strategy:
type: RollingUpdate
rollingUpdate: {maxUnavailable: 1, maxSurge: 1}
template:
metadata:
labels: {app: explore-fe, brand: zoo, network: mainnet}
spec:
imagePullSecrets: [{name: ghcr-luxfi}]
containers:
- name: explore
image: ghcr.io/zooai/explore:v1.0.1
imagePullPolicy: IfNotPresent
ports: [{containerPort: 3000, name: ui}]
env:
- {name: PORT, value: "3000"}
envFrom:
- configMapRef: {name: explore-env-zoo}
resources:
requests: {cpu: 100m, memory: 256Mi}
limits: {cpu: 1, memory: 2Gi}
startupProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 10
failureThreshold: 30
readinessProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 30
periodSeconds: 15
timeoutSeconds: 10
failureThreshold: 6
livenessProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 90
periodSeconds: 60
timeoutSeconds: 10
failureThreshold: 5
---
apiVersion: v1
kind: Service
metadata:
name: explore-fe-zoo
namespace: lux-mainnet
labels: {app: explore-fe, brand: zoo, network: mainnet}
spec:
type: ClusterIP
selector: {app: explore-fe, brand: zoo, network: mainnet}
ports: [{name: http, port: 80, targetPort: 3000, protocol: TCP}]
---
# Hanzo Mainnet
apiVersion: apps/v1
kind: Deployment
metadata:
name: explore-fe-hanzo
namespace: lux-mainnet
labels: {app: explore-fe, brand: hanzo, network: mainnet}
spec:
replicas: 2
selector:
matchLabels: {app: explore-fe, brand: hanzo, network: mainnet}
strategy:
type: RollingUpdate
rollingUpdate: {maxUnavailable: 1, maxSurge: 1}
template:
metadata:
labels: {app: explore-fe, brand: hanzo, network: mainnet}
spec:
imagePullSecrets: [{name: ghcr-luxfi}]
containers:
- name: explore
image: ghcr.io/hanzoai/explore:v1.0.1
imagePullPolicy: IfNotPresent
ports: [{containerPort: 3000, name: ui}]
env:
- {name: PORT, value: "3000"}
envFrom:
- configMapRef: {name: explore-env-hanzo}
resources:
requests: {cpu: 100m, memory: 256Mi}
limits: {cpu: 1, memory: 2Gi}
startupProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 10
failureThreshold: 30
readinessProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 30
periodSeconds: 15
timeoutSeconds: 10
failureThreshold: 6
livenessProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 90
periodSeconds: 60
timeoutSeconds: 10
failureThreshold: 5
---
apiVersion: v1
kind: Service
metadata:
name: explore-fe-hanzo
namespace: lux-mainnet
labels: {app: explore-fe, brand: hanzo, network: mainnet}
spec:
type: ClusterIP
selector: {app: explore-fe, brand: hanzo, network: mainnet}
ports: [{name: http, port: 80, targetPort: 3000, protocol: TCP}]
---
# Pars Mainnet
apiVersion: apps/v1
kind: Deployment
metadata:
name: explore-fe-pars
namespace: lux-mainnet
labels: {app: explore-fe, brand: pars, network: mainnet}
spec:
replicas: 2
selector:
matchLabels: {app: explore-fe, brand: pars, network: mainnet}
strategy:
type: RollingUpdate
rollingUpdate: {maxUnavailable: 1, maxSurge: 1}
template:
metadata:
labels: {app: explore-fe, brand: pars, network: mainnet}
spec:
imagePullSecrets: [{name: ghcr-luxfi}]
containers:
- name: explore
image: ghcr.io/parsdao/explore:v1.0.1
imagePullPolicy: IfNotPresent
ports: [{containerPort: 3000, name: ui}]
env:
- {name: PORT, value: "3000"}
envFrom:
- configMapRef: {name: explore-env-pars}
resources:
requests: {cpu: 100m, memory: 256Mi}
limits: {cpu: 1, memory: 2Gi}
startupProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 10
failureThreshold: 30
readinessProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 30
periodSeconds: 15
timeoutSeconds: 10
failureThreshold: 6
livenessProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 90
periodSeconds: 60
timeoutSeconds: 10
failureThreshold: 5
---
apiVersion: v1
kind: Service
metadata:
name: explore-fe-pars
namespace: lux-mainnet
labels: {app: explore-fe, brand: pars, network: mainnet}
spec:
type: ClusterIP
selector: {app: explore-fe, brand: pars, network: mainnet}
ports: [{name: http, port: 80, targetPort: 3000, protocol: TCP}]
@@ -0,0 +1,244 @@
---
# Lux Testnet — canonical brand image + env from ConfigMap.
apiVersion: apps/v1
kind: Deployment
metadata:
name: explore-fe-lux
namespace: lux-testnet
labels: {app: explore-fe, brand: lux, network: testnet}
spec:
replicas: 2
selector:
matchLabels: {app: explore-fe, brand: lux, network: testnet}
strategy:
type: RollingUpdate
rollingUpdate: {maxUnavailable: 1, maxSurge: 1}
template:
metadata:
labels: {app: explore-fe, brand: lux, network: testnet}
spec:
imagePullSecrets: [{name: ghcr-luxfi}]
containers:
- name: explore
image: ghcr.io/luxfi/explore:v1.0.1
imagePullPolicy: IfNotPresent
ports: [{containerPort: 3000, name: ui}]
env:
- {name: PORT, value: "3000"}
envFrom:
- configMapRef: {name: explore-env-lux}
resources:
requests: {cpu: 100m, memory: 256Mi}
limits: {cpu: 1, memory: 2Gi}
startupProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 10
failureThreshold: 30
readinessProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 30
periodSeconds: 15
timeoutSeconds: 10
failureThreshold: 6
livenessProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 90
periodSeconds: 60
timeoutSeconds: 10
failureThreshold: 5
---
apiVersion: v1
kind: Service
metadata:
name: explore-fe-lux
namespace: lux-testnet
labels: {app: explore-fe, brand: lux, network: testnet}
spec:
type: ClusterIP
selector: {app: explore-fe, brand: lux, network: testnet}
ports: [{name: http, port: 80, targetPort: 3000, protocol: TCP}]
---
# Zoo Testnet
apiVersion: apps/v1
kind: Deployment
metadata:
name: explore-fe-zoo
namespace: lux-testnet
labels: {app: explore-fe, brand: zoo, network: testnet}
spec:
replicas: 2
selector:
matchLabels: {app: explore-fe, brand: zoo, network: testnet}
strategy:
type: RollingUpdate
rollingUpdate: {maxUnavailable: 1, maxSurge: 1}
template:
metadata:
labels: {app: explore-fe, brand: zoo, network: testnet}
spec:
imagePullSecrets: [{name: ghcr-luxfi}]
containers:
- name: explore
image: ghcr.io/zooai/explore:v1.0.1
imagePullPolicy: IfNotPresent
ports: [{containerPort: 3000, name: ui}]
env:
- {name: PORT, value: "3000"}
envFrom:
- configMapRef: {name: explore-env-zoo}
resources:
requests: {cpu: 100m, memory: 256Mi}
limits: {cpu: 1, memory: 2Gi}
startupProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 10
failureThreshold: 30
readinessProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 30
periodSeconds: 15
timeoutSeconds: 10
failureThreshold: 6
livenessProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 90
periodSeconds: 60
timeoutSeconds: 10
failureThreshold: 5
---
apiVersion: v1
kind: Service
metadata:
name: explore-fe-zoo
namespace: lux-testnet
labels: {app: explore-fe, brand: zoo, network: testnet}
spec:
type: ClusterIP
selector: {app: explore-fe, brand: zoo, network: testnet}
ports: [{name: http, port: 80, targetPort: 3000, protocol: TCP}]
---
# Hanzo Testnet
apiVersion: apps/v1
kind: Deployment
metadata:
name: explore-fe-hanzo
namespace: lux-testnet
labels: {app: explore-fe, brand: hanzo, network: testnet}
spec:
replicas: 2
selector:
matchLabels: {app: explore-fe, brand: hanzo, network: testnet}
strategy:
type: RollingUpdate
rollingUpdate: {maxUnavailable: 1, maxSurge: 1}
template:
metadata:
labels: {app: explore-fe, brand: hanzo, network: testnet}
spec:
imagePullSecrets: [{name: ghcr-luxfi}]
containers:
- name: explore
image: ghcr.io/hanzoai/explore:v1.0.1
imagePullPolicy: IfNotPresent
ports: [{containerPort: 3000, name: ui}]
env:
- {name: PORT, value: "3000"}
envFrom:
- configMapRef: {name: explore-env-hanzo}
resources:
requests: {cpu: 100m, memory: 256Mi}
limits: {cpu: 1, memory: 2Gi}
startupProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 10
failureThreshold: 30
readinessProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 30
periodSeconds: 15
timeoutSeconds: 10
failureThreshold: 6
livenessProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 90
periodSeconds: 60
timeoutSeconds: 10
failureThreshold: 5
---
apiVersion: v1
kind: Service
metadata:
name: explore-fe-hanzo
namespace: lux-testnet
labels: {app: explore-fe, brand: hanzo, network: testnet}
spec:
type: ClusterIP
selector: {app: explore-fe, brand: hanzo, network: testnet}
ports: [{name: http, port: 80, targetPort: 3000, protocol: TCP}]
---
# Pars Testnet
apiVersion: apps/v1
kind: Deployment
metadata:
name: explore-fe-pars
namespace: lux-testnet
labels: {app: explore-fe, brand: pars, network: testnet}
spec:
replicas: 2
selector:
matchLabels: {app: explore-fe, brand: pars, network: testnet}
strategy:
type: RollingUpdate
rollingUpdate: {maxUnavailable: 1, maxSurge: 1}
template:
metadata:
labels: {app: explore-fe, brand: pars, network: testnet}
spec:
imagePullSecrets: [{name: ghcr-luxfi}]
containers:
- name: explore
image: ghcr.io/parsdao/explore:v1.0.1
imagePullPolicy: IfNotPresent
ports: [{containerPort: 3000, name: ui}]
env:
- {name: PORT, value: "3000"}
envFrom:
- configMapRef: {name: explore-env-pars}
resources:
requests: {cpu: 100m, memory: 256Mi}
limits: {cpu: 1, memory: 2Gi}
startupProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 10
failureThreshold: 30
readinessProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 30
periodSeconds: 15
timeoutSeconds: 10
failureThreshold: 6
livenessProbe:
httpGet: {path: /, port: ui}
initialDelaySeconds: 90
periodSeconds: 60
timeoutSeconds: 10
failureThreshold: 5
---
apiVersion: v1
kind: Service
metadata:
name: explore-fe-pars
namespace: lux-testnet
labels: {app: explore-fe, brand: pars, network: testnet}
spec:
type: ClusterIP
selector: {app: explore-fe, brand: pars, network: testnet}
ports: [{name: http, port: 80, targetPort: 3000, protocol: TCP}]
+13
View File
@@ -0,0 +1,13 @@
# Per-brand × per-env explore frontend, decomposed.
#
# Image is brand-only (ghcr.io/{org}/explore:vX.Y.Z); env (NETWORK_ID,
# IS_TESTNET, *_HOST, RPC_URL) lives in ConfigMaps mounted via
# Deployment.envFrom. Same image runs mainnet+testnet — only the
# ConfigMap differs.
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- configmap-mainnet.yaml
- configmap-testnet.yaml
- deployments-mainnet.yaml
- deployments-testnet.yaml
@@ -0,0 +1,128 @@
---
# Lux Mainnet: explore.lux.network + explorer.lux.network → explore-fe-lux + Go binary API
apiVersion: hanzo.ai/v1alpha1
kind: IngressRoute
metadata:
name: lux-explorer-mainnet
namespace: lux-mainnet
labels: {app: lux-explorer, network: mainnet}
spec:
entryPoints: [websecure]
tls: {certResolver: letsencrypt}
routes:
# API + brand-asset paths → Go binary (port 8090)
- kind: Rule
priority: 10000
match: (Host(`explore.lux.network`) || Host(`explorer.lux.network`)) && (PathPrefix(`/v1/`) || Path(`/health`) || Path(`/envs.js`) || Path(`/icon.svg`) || Path(`/logo.svg`) || Path(`/icon-zoo.svg`) || Path(`/icon-lux.svg`) || Path(`/logo-zoo.svg`) || Path(`/logo-lux.svg`))
middlewares: [{name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
# Catch-all → per-brand Next.js FE (port 80 → 3000)
- kind: Rule
match: Host(`explore.lux.network`) || Host(`explorer.lux.network`)
services: [{name: explore-fe-lux, port: 80}]
# api-explore.lux.network — sub-paths
- kind: Rule
match: (Host(`api-explore.lux.network`) || Host(`api-explorer.lux.network`)) && (PathPrefix(`/v1/graph`) || PathPrefix(`/v1/explorer`) || PathPrefix(`/v1/base/realtime`) || PathPrefix(`/v1/indexer`) || Path(`/health`))
middlewares: [{name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
# api-explore.lux.network — catch-all, rewrite to Blockscout
- kind: Rule
match: Host(`api-explore.lux.network`) || Host(`api-explorer.lux.network`)
middlewares: [{name: mainpage-rewrite}, {name: blockscout-prefix-cchain}, {name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
---
# Lux C-Chain (umbrella → re-uses explore-fe-lux because it IS the Lux primary)
apiVersion: hanzo.ai/v1alpha1
kind: IngressRoute
metadata:
name: lux-explorer-cchain
namespace: lux-mainnet
labels: {app: lux-explorer, chain: cchain, network: mainnet}
spec:
entryPoints: [websecure]
tls: {certResolver: letsencrypt}
routes:
- kind: Rule
priority: 10000
match: (Host(`explore-cchain.lux.network`) || Host(`explore-c.lux.network`)) && (PathPrefix(`/v1/`) || Path(`/health`) || Path(`/envs.js`) || Path(`/icon.svg`) || Path(`/logo.svg`))
middlewares: [{name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
- kind: Rule
match: Host(`explore-cchain.lux.network`) || Host(`explore-c.lux.network`)
services: [{name: explore-fe-lux, port: 80}]
- kind: Rule
match: Host(`api-explore-cchain.lux.network`) || Host(`api-explore-c.lux.network`)
middlewares: [{name: mainpage-rewrite}, {name: blockscout-prefix-cchain}, {name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
---
# Hanzo Mainnet
apiVersion: hanzo.ai/v1alpha1
kind: IngressRoute
metadata:
name: lux-explorer-hanzo
namespace: lux-mainnet
labels: {app: lux-explorer, chain: hanzo, network: mainnet}
spec:
entryPoints: [websecure]
tls: {certResolver: letsencrypt}
routes:
- kind: Rule
priority: 10000
match: (Host(`explore-hanzo.lux.network`) || Host(`explore.hanzo.network`) || Host(`explore.hanzo.ai`)) && (PathPrefix(`/v1/`) || Path(`/health`) || Path(`/envs.js`) || Path(`/icon.svg`) || Path(`/logo.svg`))
middlewares: [{name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
- kind: Rule
match: Host(`explore-hanzo.lux.network`) || Host(`explore.hanzo.network`) || Host(`explore.hanzo.ai`)
services: [{name: explore-fe-hanzo, port: 80}]
- kind: Rule
match: Host(`api-explore-hanzo.lux.network`) || Host(`api-explore.hanzo.network`) || Host(`api-explore.hanzo.ai`)
middlewares: [{name: mainpage-rewrite}, {name: blockscout-prefix-hanzo}, {name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
---
# Zoo Mainnet
apiVersion: hanzo.ai/v1alpha1
kind: IngressRoute
metadata:
name: lux-explorer-zoo
namespace: lux-mainnet
labels: {app: lux-explorer, chain: zoo, network: mainnet}
spec:
entryPoints: [websecure]
tls: {certResolver: letsencrypt}
routes:
- kind: Rule
priority: 10000
match: (Host(`explore-zoo.lux.network`) || Host(`explore.zoo.network`) || Host(`explore.zoo.ngo`) || Host(`explorer.zoo.network`) || Host(`explorer-zoo.lux.network`)) && (PathPrefix(`/v1/`) || Path(`/health`) || Path(`/envs.js`) || Path(`/icon.svg`) || Path(`/logo.svg`) || Path(`/icon-zoo.svg`) || Path(`/logo-zoo.svg`))
middlewares: [{name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
- kind: Rule
match: Host(`explore-zoo.lux.network`) || Host(`explore.zoo.network`) || Host(`explore.zoo.ngo`) || Host(`explorer.zoo.network`) || Host(`explorer-zoo.lux.network`)
services: [{name: explore-fe-zoo, port: 80}]
- kind: Rule
match: Host(`api-explore-zoo.lux.network`) || Host(`api-explore.zoo.network`) || Host(`api-explore.zoo.ngo`) || Host(`api-explorer.zoo.network`) || Host(`api-explorer-zoo.lux.network`)
middlewares: [{name: mainpage-rewrite}, {name: blockscout-prefix-zoo}, {name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
---
# Pars Mainnet
apiVersion: hanzo.ai/v1alpha1
kind: IngressRoute
metadata:
name: lux-explorer-pars
namespace: lux-mainnet
labels: {app: lux-explorer, chain: pars, network: mainnet}
spec:
entryPoints: [websecure]
tls: {certResolver: letsencrypt}
routes:
- kind: Rule
priority: 10000
match: (Host(`explore-pars.lux.network`) || Host(`explore.pars.network`)) && (PathPrefix(`/v1/`) || Path(`/health`) || Path(`/envs.js`) || Path(`/icon.svg`) || Path(`/logo.svg`))
middlewares: [{name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
- kind: Rule
match: Host(`explore-pars.lux.network`) || Host(`explore.pars.network`)
services: [{name: explore-fe-pars, port: 80}]
- kind: Rule
match: Host(`api-explore-pars.lux.network`) || Host(`api-explore.pars.network`)
middlewares: [{name: mainpage-rewrite}, {name: blockscout-prefix-pars}, {name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
@@ -0,0 +1,124 @@
---
# Lux Testnet
apiVersion: hanzo.ai/v1alpha1
kind: IngressRoute
metadata:
name: lux-explorer-testnet
namespace: lux-testnet
labels: {app: lux-explorer, network: testnet}
spec:
entryPoints: [websecure]
tls: {certResolver: letsencrypt}
routes:
- kind: Rule
priority: 10000
match: (Host(`explore.lux-test.network`) || Host(`explorer.lux-test.network`) || Host(`explore-test.lux.network`)) && (PathPrefix(`/v1/`) || Path(`/health`) || Path(`/envs.js`) || Path(`/icon.svg`) || Path(`/logo.svg`) || Path(`/icon-zoo.svg`) || Path(`/icon-lux.svg`) || Path(`/logo-zoo.svg`) || Path(`/logo-lux.svg`))
middlewares: [{name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
- kind: Rule
match: Host(`explore.lux-test.network`) || Host(`explorer.lux-test.network`) || Host(`explore-test.lux.network`)
services: [{name: explore-fe-lux, port: 80}]
- kind: Rule
match: (Host(`api-explore.lux-test.network`) || Host(`api-explorer.lux-test.network`) || Host(`api-explore-test.lux.network`)) && (PathPrefix(`/v1/graph`) || PathPrefix(`/v1/explorer`) || PathPrefix(`/v1/base/realtime`) || PathPrefix(`/v1/indexer`) || Path(`/health`))
middlewares: [{name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
- kind: Rule
match: Host(`api-explore.lux-test.network`) || Host(`api-explorer.lux-test.network`) || Host(`api-explore-test.lux.network`)
middlewares: [{name: mainpage-rewrite}, {name: blockscout-prefix-cchain}, {name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
---
# C-Chain Testnet (umbrella → lux FE)
apiVersion: hanzo.ai/v1alpha1
kind: IngressRoute
metadata:
name: lux-explorer-cchain-testnet
namespace: lux-testnet
labels: {app: lux-explorer, chain: cchain, network: testnet}
spec:
entryPoints: [websecure]
tls: {certResolver: letsencrypt}
routes:
- kind: Rule
priority: 10000
match: (Host(`explore-cchain-test.lux.network`) || Host(`explore-c-test.lux.network`)) && (PathPrefix(`/v1/`) || Path(`/health`) || Path(`/envs.js`) || Path(`/icon.svg`) || Path(`/logo.svg`))
middlewares: [{name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
- kind: Rule
match: Host(`explore-cchain-test.lux.network`) || Host(`explore-c-test.lux.network`)
services: [{name: explore-fe-lux, port: 80}]
- kind: Rule
match: Host(`api-explore-cchain-test.lux.network`) || Host(`api-explore-c-test.lux.network`)
middlewares: [{name: mainpage-rewrite}, {name: blockscout-prefix-cchain}, {name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
---
# Hanzo Testnet
apiVersion: hanzo.ai/v1alpha1
kind: IngressRoute
metadata:
name: lux-explorer-hanzo-testnet
namespace: lux-testnet
labels: {app: lux-explorer, chain: hanzo, network: testnet}
spec:
entryPoints: [websecure]
tls: {certResolver: letsencrypt}
routes:
- kind: Rule
priority: 10000
match: (Host(`explore-hanzo-test.lux.network`) || Host(`explore.hanzo-test.network`) || Host(`explorer.hanzo-test.network`)) && (PathPrefix(`/v1/`) || Path(`/health`) || Path(`/envs.js`) || Path(`/icon.svg`) || Path(`/logo.svg`))
middlewares: [{name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
- kind: Rule
match: Host(`explore-hanzo-test.lux.network`) || Host(`explore.hanzo-test.network`) || Host(`explorer.hanzo-test.network`)
services: [{name: explore-fe-hanzo, port: 80}]
- kind: Rule
match: Host(`api-explore-hanzo-test.lux.network`) || Host(`api-explore.hanzo-test.network`) || Host(`api-explorer.hanzo-test.network`)
middlewares: [{name: mainpage-rewrite}, {name: blockscout-prefix-hanzo}, {name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
---
# Zoo Testnet
apiVersion: hanzo.ai/v1alpha1
kind: IngressRoute
metadata:
name: lux-explorer-zoo-testnet
namespace: lux-testnet
labels: {app: lux-explorer, chain: zoo, network: testnet}
spec:
entryPoints: [websecure]
tls: {certResolver: letsencrypt}
routes:
- kind: Rule
priority: 10000
match: (Host(`explore-zoo-test.lux.network`) || Host(`explore.zoo-test.network`) || Host(`explorer.zoo-test.network`) || Host(`explorer-zoo-test.lux.network`)) && (PathPrefix(`/v1/`) || Path(`/health`) || Path(`/envs.js`) || Path(`/icon.svg`) || Path(`/logo.svg`) || Path(`/icon-zoo.svg`) || Path(`/logo-zoo.svg`))
middlewares: [{name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
- kind: Rule
match: Host(`explore-zoo-test.lux.network`) || Host(`explore.zoo-test.network`) || Host(`explorer.zoo-test.network`) || Host(`explorer-zoo-test.lux.network`)
services: [{name: explore-fe-zoo, port: 80}]
- kind: Rule
match: Host(`api-explore-zoo-test.lux.network`) || Host(`api-explore.zoo-test.network`) || Host(`api-explorer.zoo-test.network`) || Host(`api-explorer-zoo-test.lux.network`)
middlewares: [{name: mainpage-rewrite}, {name: blockscout-prefix-zoo}, {name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
---
# Pars Testnet
apiVersion: hanzo.ai/v1alpha1
kind: IngressRoute
metadata:
name: lux-explorer-pars-testnet
namespace: lux-testnet
labels: {app: lux-explorer, chain: pars, network: testnet}
spec:
entryPoints: [websecure]
tls: {certResolver: letsencrypt}
routes:
- kind: Rule
priority: 10000
match: (Host(`explore-pars-test.lux.network`) || Host(`explore.pars-test.network`)) && (PathPrefix(`/v1/`) || Path(`/health`) || Path(`/envs.js`) || Path(`/icon.svg`) || Path(`/logo.svg`))
middlewares: [{name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
- kind: Rule
match: Host(`explore-pars-test.lux.network`) || Host(`explore.pars-test.network`)
services: [{name: explore-fe-pars, port: 80}]
- kind: Rule
match: Host(`api-explore-pars-test.lux.network`) || Host(`api-explore.pars-test.network`)
middlewares: [{name: mainpage-rewrite}, {name: blockscout-prefix-pars}, {name: cors-allow-all}]
services: [{name: explorer, port: 8090}]
+205
View File
@@ -0,0 +1,205 @@
#!/usr/bin/env bash
# Build 4 canonical, brand-only Blockscout overlay images. One image per brand —
# env-specific config (NETWORK_ID, IS_TESTNET, API_HOST, …) lives in K8s
# ConfigMaps and lands on the pod via Deployment `envFrom`. Brand identity
# (NETWORK_NAME, OIDC client ID, currency, brand colors) is baked here.
#
# Outputs:
# ghcr.io/luxfi/explore:v1.0.0
# ghcr.io/zooai/explore:v1.0.0
# ghcr.io/hanzoai/explore:v1.0.0
# ghcr.io/parsdao/explore:v1.0.0
#
# Replaces the older 8-image `explore-{env}` scheme. See env-specific
# ConfigMaps under deploy/k8s/explore-fe/configmap-*.yaml.
set -euo pipefail
VERSION="${VERSION:-v1.0.0}"
BASE_IMAGE="${BASE_IMAGE:-ghcr.io/luxfi/explore:latest}"
TMPDIR_BASE="${TMPDIR:-/tmp}/canonical-build-$$"
mkdir -p "$TMPDIR_BASE"
trap "rm -rf '$TMPDIR_BASE'" EXIT
# Per-brand identity. Values that must vary by env (NETWORK_ID, IS_TESTNET,
# API_HOST, RPC_URL, APP_HOST) are intentionally OMITTED here — they come
# from ConfigMaps at runtime via Deployment `envFrom`.
brands() {
cat <<'EOF'
lux ghcr.io/luxfi/explore
zoo ghcr.io/zooai/explore
hanzo ghcr.io/hanzoai/explore
pars ghcr.io/parsdao/explore
EOF
}
brand_args_for() {
case "$1" in
lux)
cat <<'EOF'
NEXT_PUBLIC_BRAND=lux
NEXT_PUBLIC_NETWORK_NAME=Lux Network
NEXT_PUBLIC_NETWORK_SHORT_NAME=LUX
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=LUX
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=LUX
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
NEXT_PUBLIC_OIDC_SERVER_URL=https://lux.id
NEXT_PUBLIC_OIDC_CLIENT_ID=lux-explore-client-id
EOF
;;
zoo)
cat <<'EOF'
NEXT_PUBLIC_BRAND=zoo
NEXT_PUBLIC_NETWORK_NAME=Zoo Network
NEXT_PUBLIC_NETWORK_SHORT_NAME=ZOO
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Zoo
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ZOO
NEXT_PUBLIC_NETWORK_ORG_NAME=Zoo Labs Foundation
NEXT_PUBLIC_NETWORK_WEBSITE_URL=https://zoo.network
NEXT_PUBLIC_NETWORK_DESCRIPTION=Zoo Network — decentralized AI research blockchain.
NEXT_PUBLIC_NETWORK_GITHUB_URL=https://github.com/zooai
NEXT_PUBLIC_NETWORK_TWITTER_URL=https://x.com/zoo_network
NEXT_PUBLIC_NETWORK_DISCORD_URL=https://discord.gg/zoonetwork
NEXT_PUBLIC_OIDC_SERVER_URL=https://zoo.id
NEXT_PUBLIC_OIDC_CLIENT_ID=zoo-explore-client-id
EOF
;;
hanzo)
cat <<'EOF'
NEXT_PUBLIC_BRAND=hanzo
NEXT_PUBLIC_NETWORK_NAME=Hanzo Network
NEXT_PUBLIC_NETWORK_SHORT_NAME=AI
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=AI
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=AI
NEXT_PUBLIC_NETWORK_ORG_NAME=Hanzo AI Inc.
NEXT_PUBLIC_NETWORK_WEBSITE_URL=https://hanzo.ai
NEXT_PUBLIC_NETWORK_DESCRIPTION=Hanzo Network — AI infrastructure blockchain.
NEXT_PUBLIC_NETWORK_GITHUB_URL=https://github.com/hanzoai
NEXT_PUBLIC_NETWORK_TWITTER_URL=https://x.com/hanzoai
NEXT_PUBLIC_NETWORK_DISCORD_URL=https://discord.gg/hanzoai
NEXT_PUBLIC_OIDC_SERVER_URL=https://hanzo.id
NEXT_PUBLIC_OIDC_CLIENT_ID=hanzo-explore-client-id
EOF
;;
pars)
cat <<'EOF'
NEXT_PUBLIC_BRAND=pars
NEXT_PUBLIC_NETWORK_NAME=Pars Network
NEXT_PUBLIC_NETWORK_SHORT_NAME=PARS
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Pars
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=PARS
NEXT_PUBLIC_NETWORK_ORG_NAME=Pars DAO
NEXT_PUBLIC_NETWORK_WEBSITE_URL=https://pars.network
NEXT_PUBLIC_NETWORK_DESCRIPTION=Pars Network — Persian community L1 blockchain.
NEXT_PUBLIC_NETWORK_GITHUB_URL=https://github.com/parsdao
NEXT_PUBLIC_NETWORK_TWITTER_URL=https://x.com/parsdao
NEXT_PUBLIC_NETWORK_DISCORD_URL=https://discord.gg/parsdao
NEXT_PUBLIC_OIDC_SERVER_URL=https://pars.id
NEXT_PUBLIC_OIDC_CLIENT_ID=pars-explore-client-id
EOF
;;
*) echo "unknown brand: $1" >&2; return 1;;
esac
# Common, brand-invariant runtime defaults that don't vary per env.
cat <<'EOF'
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_API_PROTOCOL=https
NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=wss
NEXT_PUBLIC_APP_PROTOCOL=https
NEXT_PUBLIC_NETWORK_LOGO=/logo.svg
NEXT_PUBLIC_NETWORK_ICON=/icon.svg
NEXT_PUBLIC_HOMEPAGE_CHARTS=["daily_txs","coin_price","market_cap"]
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_COLOR_THEME_DEFAULT=dark
NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER=oidc
NEXT_TELEMETRY_DISABLED=1
ENVS_PRESET=none
SKIP_ENVS_VALIDATION=true
EOF
}
if [ "${1:-}" = "--list" ]; then
brands | while read -r brand target; do
[ -z "${brand:-}" ] && continue
echo "${brand} ${target}:${VERSION}"
done
exit 0
fi
if [ "${1:-}" = "--brand-args" ] && [ -n "${2:-}" ]; then
brand_args_for "$2"
exit 0
fi
FAIL=""
SUCCESS=""
while read -r brand target; do
[ -z "${brand:-}" ] && continue
full="${target}:${VERSION}"
echo "=== ${brand}${full} ==="
# Step 1: mutate process env for child processes (Next.js server reads via env).
env_flags=()
brand_kv="$(brand_args_for "$brand")"
while IFS='=' read -r k v; do
[ -z "$k" ] && continue
env_flags+=("-e" "${k}=${v}")
done <<< "$brand_kv"
# Mutate to a temp tag, then append a /app/.env overlay so Blockscout's
# entrypoint `source .env` exports brand identity vars into the shell
# before make_envs_script.sh iterates env. ConfigMap-supplied env vars
# (NETWORK_ID, IS_TESTNET, *_HOST, RPC_URL) are NOT in .env so they
# come from process env at runtime, which the iteration also picks up.
tmp_image="ghcr.io/luxfi/explore-tmp:${brand}-$$"
if ! crane mutate "$BASE_IMAGE" "${env_flags[@]}" -t "$tmp_image" 2>&1 | tail -1; then
FAIL="${FAIL}${full}: mutate-failed
"
continue
fi
# Step 2: build a layer tar containing /app/.env with brand identity values.
tuple_dir="${TMPDIR_BASE}/${brand}"
mkdir -p "${tuple_dir}/app"
while IFS='=' read -r k v; do
[ -z "$k" ] && continue
# JSON-array vals like NEXT_PUBLIC_HOMEPAGE_CHARTS need single-quoting
# so bash `source` preserves the embedded double quotes.
if [[ "$v" == \[* ]]; then
echo "$k='$v'"
else
echo "$k=$v"
fi
done <<< "$brand_kv" > "${tuple_dir}/app/.env"
tarfile="${tuple_dir}/layer.tar"
tar -C "$tuple_dir" -cf "$tarfile" app
if ! crane append -b "$tmp_image" -f "$tarfile" -t "$full" 2>&1 | tail -1; then
FAIL="${FAIL}${full}: append-failed
"
continue
fi
digest=$(crane digest "$full" 2>/dev/null || echo "?")
SUCCESS="${SUCCESS}${full}@${digest}
"
echo "OK: ${full}@${digest}"
done < <(brands)
echo
echo "=== Canonical brand images ==="
printf '%s' "$SUCCESS" | sed 's/^/ + /'
if [ -n "$FAIL" ]; then
echo "Failed:"
printf '%s' "$FAIL" | sed 's/^/ - /'
exit 1
fi
+40 -3
View File
@@ -1,6 +1,7 @@
import type { Chain } from 'viem';
import appConfig from 'configs/app';
import { CHAINS as REGISTRY_CHAINS, getCurrentChain } from 'configs/app/chainRegistry';
import essentialDappsChainsConfig from 'configs/essential-dapps-chains';
import multichainConfig from 'configs/multichain';
@@ -94,17 +95,53 @@ export const essentialDappsChains: Array<Chain> | undefined = (() => {
return config.chains.map(({ app_config: config, contracts, logo }) => getChainInfo(config, contracts, logo)).filter(Boolean);
})();
// Sibling networks of the current brand, sourced from the multi-tenant chain
// registry so the wallet-connect flow presents / can add EVERY registered
// network for this brand — e.g. Lux mainnet (96369), testnet (96368) and
// devnet (96370) — instead of only the single env-configured chain. This is
// the "native all-chains" connect: the list is derived from the registry
// (the same source of truth as the header chain switcher), never hardcoded to
// one chain. Each sibling's read RPC is its brand explorer's Blockscout
// eth-rpc proxy (mirrors the read-only transport wagmiConfig builds for the
// primary chain); the current chain keeps its own direct RPC from env.
export const brandFamilyChains: Array<Chain> = (() => {
if (!currentChain) {
return [];
}
const current = getCurrentChain();
const titleCase = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
return REGISTRY_CHAINS
.filter((c) => c.branding.brandName === current.branding.brandName && c.chainId !== currentChain.id)
.map((c): Chain => ({
id: c.chainId,
name: c.network === 'mainnet' ? current.branding.brandName : `${ current.branding.brandName } ${ titleCase(c.network) }`,
nativeCurrency: currentChain.nativeCurrency,
rpcUrls: {
'default': { http: [ `${ c.apiUrl }/api/eth-rpc` ] },
},
blockExplorers: {
'default': { name: `${ current.branding.brandName } Explorer`, url: c.explorerUrl },
},
testnet: c.network !== 'mainnet',
}));
})();
export const chains = (() => {
const dedupeById = (list: Array<Chain | undefined>): Array<Chain> =>
list.filter(Boolean).filter((chain, index, arr) => arr.findIndex((c) => c?.id === chain?.id) === index) as Array<Chain>;
if (essentialDappsChains) {
const hasCurrentChain = essentialDappsChains.some((chain) => chain.id === currentChain?.id);
const hasParentChain = essentialDappsChains.some((chain) => chain.id === parentChain?.id);
return [
return dedupeById([
...essentialDappsChains,
hasCurrentChain ? undefined : currentChain,
hasParentChain ? undefined : parentChain,
].filter(Boolean);
...brandFamilyChains,
]);
}
return [ currentChain, parentChain, ...(clusterChains ?? []) ].filter(Boolean);
return dedupeById([ currentChain, ...brandFamilyChains, parentChain, ...(clusterChains ?? []) ]);
})();
+2 -1
View File
@@ -32,7 +32,8 @@ interface Params {
}
export default function useAddChain(params?: Params) {
const { data: { wallet, provider } = {} } = useProvider();
const { data: providerData } = useProvider();
const { wallet, provider } = providerData ?? {};
const { trackUsage } = useRewardsActivity();
const multichainContext = useMultichainContext();
+3 -2
View File
@@ -1,7 +1,7 @@
import { toaster } from '@luxfi/ui/toaster';
import React from 'react';
import * as mixpanel from 'lib/mixpanel/index';
import { toaster } from '@luxfi/ui/toaster';
import useAddChain from './useAddChain';
import useProvider from './useProvider';
@@ -13,7 +13,8 @@ interface Props {
}
export default function useAddChainClick({ source, onSuccess }: Props) {
const { data: { wallet, provider } = {} } = useProvider();
const { data: providerData } = useProvider();
const { wallet, provider } = providerData ?? {};
const addChain = useAddChain();
const switchChain = useSwitchChain();
+7 -3
View File
@@ -17,9 +17,11 @@ export default function useProvider() {
return useQuery({
queryKey: [ 'web3-wallet' ],
// react-query v5 forbids a queryFn resolving to `undefined` ("Query data
// cannot be undefined"). Every no-wallet-found branch returns `null`.
queryFn: async() => {
if (!feature.isEnabled || !wallets) {
return;
return null;
}
if (!('ethereum' in window && window.ethereum)) {
@@ -42,14 +44,14 @@ export default function useProvider() {
shouldShimWeb3: true,
});
} else {
return;
return null;
}
}
// have to check again in case provider was not set as window.ethereum in the previous step for MM in FF
// and also it makes typescript happy
if (!('ethereum' in window && window.ethereum)) {
return;
return null;
}
for (const wallet of wallets) {
@@ -65,6 +67,8 @@ export default function useProvider() {
return detectedWallet;
}
}
return null;
},
enabled: Boolean(feature.isEnabled || !wallets),
refetchOnMount: false,
+2 -1
View File
@@ -19,7 +19,8 @@ interface Params {
}
export default function useSwitchChain(params?: Params) {
const { data: { wallet, provider } = {} } = useProvider();
const { data: providerData } = useProvider();
const { wallet, provider } = providerData ?? {};
const multichainContext = useMultichainContext();
const chainConfig = params?.chainConfig || multichainContext?.chain.app_config || config;
+2 -1
View File
@@ -13,7 +13,8 @@ interface Params {
}
export default function useSwitchOrAddChain(params?: Params) {
const { data: { wallet, provider } = {} } = useProvider();
const { data: providerData } = useProvider();
const { wallet, provider } = providerData ?? {};
const addChain = useAddChain(params);
const switchChain = useSwitchChain(params);
+15 -1
View File
@@ -7,10 +7,22 @@ import { createConfig } from 'wagmi';
import appConfig from 'configs/app';
import essentialDappsChainsConfig from 'configs/essential-dapps-chains';
import multichainConfig from 'configs/multichain';
import { chains, parentChain } from 'lib/web3/chains';
import { brandFamilyChains, chains, parentChain } from 'lib/web3/chains';
const feature = appConfig.features.blockchainInteraction;
// Transports for the brand's sibling networks (Lux testnet/devnet, …) added by
// lib/web3/chains. wagmi requires a transport per chain; each sibling uses the
// Blockscout eth-rpc endpoint carried on its viem chain definition.
const getBrandFamilyTransports = (): Record<string, Transport> =>
brandFamilyChains.reduce((acc, chain) => {
const url = chain.rpcUrls.default.http[0];
if (url) {
acc[chain.id] = http(url, { batch: { wait: 100, batchSize: 5 } });
}
return acc;
}, {} as Record<string, Transport>);
const getChainTransportFromConfig = (config: Partial<typeof appConfig> | undefined, readOnly?: boolean): Record<string, Transport> => {
if (!config?.chain?.id) {
return {};
@@ -53,6 +65,7 @@ const wagmi = (() => {
transports: {
...getChainTransportFromConfig(appConfig, true),
...(parentChain ? { [parentChain.id]: http(parentChain.rpcUrls.default.http[0]) } : {}),
...getBrandFamilyTransports(),
...reduceExternalChainsToTransportConfig(true),
},
ssr: true,
@@ -69,6 +82,7 @@ const wagmi = (() => {
transports: {
...getChainTransportFromConfig(appConfig, false),
...(parentChain ? { [parentChain.id]: http() } : {}),
...getBrandFamilyTransports(),
...reduceExternalChainsToTransportConfig(false),
},
projectId: feature.reown.projectId,
+1 -1
View File
@@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import './.next/dev/types/routes.d.ts';
import './.next/types/routes.d.ts';
// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
+16
View File
@@ -3,6 +3,22 @@
@plugin "tailwindcss-animate";
/*
* Scan the @luxfi/ui component library for Tailwind classes.
*
* @luxfi/ui ships pre-built components whose className strings use arbitrary
* Tailwind utilities that resolve design tokens — e.g. the popover / menu /
* drawer surfaces are styled with bg-[var(--color-popover-bg)],
* bg-[var(--color-drawer-bg)], shadow-[var(--shadow-popover)] and
* border-[var(--color-popover-border)]. Tailwind v4 does not scan node_modules
* by default, so without this @source those utilities are purged and every
* floating panel (Sign-in popover, Blockchain menu, chain/network selectors,
* mobile nav drawer) renders with a transparent background — the page bleeds
* through and the content is unreadable. Sourcing the package makes Tailwind
* emit the utilities so the panels get their solid token-driven backgrounds.
*/
@source "../node_modules/@luxfi/ui";
/*
* Tailwind v4 theme configuration
*
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@luxfi/explore",
"version": "1.0.0",
"version": "1.1.4",
"private": false,
"homepage": "https://github.com/luxfi/explore#readme",
"engines": {
+8 -4
View File
@@ -20,19 +20,23 @@
},
{
"id": 200200,
"name": "Zoo Subnet"
"name": "Zoo Network"
},
{
"id": 36963,
"name": "Hanzo Subnet"
"name": "Hanzo Network"
},
{
"id": 36911,
"name": "SPC Subnet"
"name": "SPC Network"
},
{
"id": 494949,
"name": "Pars Subnet"
"name": "Pars Network"
},
{
"id": 1872,
"name": "Osage Network"
}
],
"peers": [],
+2
View File
@@ -185,6 +185,7 @@
--color-dialog-bg: #FFFFFF;
--color-dialog-fg: rgba(16, 17, 18, 0.80);
--color-popover-bg: #FFFFFF;
--color-popover-border: rgba(16, 17, 18, 0.10);
--color-popover-shadow: rgba(16, 17, 18, 0.08);
--color-drawer-bg: #FFFFFF;
@@ -434,6 +435,7 @@
--color-dialog-bg: #171717;
--color-dialog-fg: rgba(255, 255, 255, 0.80);
--color-popover-bg: #171717;
--color-popover-border: rgba(255, 255, 255, 0.12);
--color-popover-shadow: rgba(255, 255, 255, 0.16);
--color-drawer-bg: #171717;