Files
universe/ARCHITECTURE.md
zeekay 51460827d4 Merge the forge's lux/universe into GitHub's, the one main from here
The two mains shared no history, so this is a per-file three-way merge:
for each file the 65 forge-only commits touched (by patch-id), the base is
the newest version both histories held. 105 files merged or took one side
cleanly, 48 were already identical, and the 45 true conflicts and 31
modify/delete pairs were resolved by what runs on the cluster:

- The forge wins where it is live: explorer 1.5.56 (digest 45db11ff) and
  its archive readers, explore-fe-hanzo's ConfigMap env (AI coin,
  /v1/indexer/hanzo, /v1/chain/hanzo/rpc), exchange-api reading
  luxd-archive, and explore-fe-{lux,zoo}-testnet, pinned to what they run
  (v2.8.5-lux / v2.8.4-lux).
- GitHub wins where it is live: explore-fe-lux v2.8.5-lux, explore-fe-zoo
  v2.8.4-lux, bankd v1.0.18, lux-build's routes.
- The forge's retirement of workloads that run nowhere stands: lux-mainnet
  dex, lux-hb2, lux-market, traffic, wallet-web; the lux-testnet frontends,
  indexer, exchange and gateways; lux-operator. deploy/lux-testnet/luxd.yaml
  stays, since roll_luxd.sh reads it.
- GitHub's deletions stand: lux-finance and lux-financial (static sites
  now), the k8s/ operator manifests, .hanzo/workflows/cicd.yml.
- Paths are /v1/chain/C/rpc where the forge had written /v1/chain/c. All
  four spellings answer; this one is the documented form. The forge's
  /{network}/v1 doc lines are dropped: a network is a host, and the prefix
  404s.

Dry-run against the running objects (server-side, as CD applies), the
merged explorer shows no change where GitHub's showed 152 lines, and no
other live workload moves further from what runs. Every deploy/*/*.yaml
renders with charts/app; price.yaml, which failed on GitHub, now renders.

Claude-Session: https://claude.ai/code/session_01TzirYkGmD88w4GipqivUtV
2026-09-23 20:20:03 -07:00

35 KiB

White-Label Platform Architecture

Canonical architecture for the Lux ecosystem white-label platform. All services run on lux-k8s.

Last updated: 2026-03-24


1. Service Topology

                         Cloudflare DNS (DNS-only, no proxy)
                                    |
                            134.199.138.27
                                    |
                      hanzo-ingress (Traefik DaemonSet)
                          lux-system namespace
                                    |
        +---------------------------+---------------------------+
        |           |           |           |           |       |
   lux-gateway  lux-explorer  lux-bridge  lux-mpc  lux-kms  bootnode
        |           |           |           |           |       |
   Gateway      Lux Explorer Bridge UI   MPC Dash   KMS API  Cloud UI
   API GW       (Go binary,  + Bridge    + MPC API   (2 rep)  + API
                indexer +      Server    + 5 nodes
                GraphQL +
                SPA in one
                process)
        |           |           |           |           |       |
        +---------------------------+---------------------------+
                                    |
                    lux-mainnet / lux-testnet / lux-devnet
                         5 luxd validators each

Namespace Layout (lux-k8s)

Namespace Services Pod Count
lux-system hanzo-ingress (Traefik), lux-operator ~6
lux-mainnet 5 luxd validators (StatefulSet) 5
lux-testnet 5 luxd validators (StatefulSet) 5
lux-devnet 5 luxd validators (StatefulSet) 5
lux-gateway Gateway API gateway 2
lux-explorer Lux Explorer (single Go binary serves indexer+GraphQL+SPA for all 5 chains: C, Zoo, Hanzo, SPC, Pars), exchange (N brands), exchange-api, exchange-proxy, graph-node, indexers (13), faucets (N) ~30
lux-bridge bridge-ui, bridge-server 4
lux-mpc mpc-dashboard, mpc-api, 5 MPC nodes 7
lux-kms KMS API (shared), PostgreSQL 3
bootnode cloud-web (N brands), bootnode-api ~6

2. Hostname-to-Service Mapping

Explorer (Lux Explorer — single Go binary)

Hostname Service Chain Brand
explore.lux.network lux-frontend-mainnet C-Chain Lux
explore-zoo.lux.network lux-frontend-zoo Zoo Lux
explore.zoo.network lux-frontend-zoo Zoo Zoo
explore.zoo.ngo lux-frontend-zoo Zoo Zoo
explore-hanzo.lux.network lux-frontend-hanzo Hanzo Lux
explore.hanzo.network lux-frontend-hanzo Hanzo Hanzo
explore.hanzo.ai lux-frontend-hanzo Hanzo Hanzo
explore-spc.lux.network lux-frontend-spc SPC Lux
explore-pars.lux.network lux-frontend-pars Pars Lux
explore.pars.network lux-frontend-pars Pars Pars
<dropped — tenant-managed> (tenant frontend) (tenant EVM) (tenant)

The Lux Explorer Go binary embeds indexer + GraphQL + SPA in one process and indexes all 5 chains from a single chains.yaml ConfigMap. Hostname- based branding is served at runtime via /envs.js so a single deployment handles all brand hostnames without an image rebuild.

Explorer API (Lux Explorer Go binary, port 8090)

The same Lux Explorer pods serve both UI and API on :8090. Per-chain data lives under /v1/indexer/{slug}/* and /v1/graph/{slug}/*.

Hostname Service Chain Slug
api-explore.lux.network explorer:8090 C-Chain cchain
api-explore-zoo.lux.network explorer:8090 Zoo zoo
api-explore.zoo.network explorer:8090 Zoo zoo
api-explore-hanzo.lux.network explorer:8090 Hanzo hanzo
api-explore.hanzo.network explorer:8090 Hanzo hanzo
api-explore-spc.lux.network explorer:8090 SPC spc
api-explore-pars.lux.network explorer:8090 Pars pars
api-explore.pars.network explorer:8090 Pars pars

Exchange (DEX frontend)

Hostname Service Primary Chain Brand
lux.exchange lux-exchange:3000 C-Chain Lux
exchange.lux.network lux-exchange:3000 C-Chain Lux
markets.lux.network lux-exchange:3000 C-Chain Lux
zoo.exchange zoo-exchange:3000 Zoo Zoo
pars.market pars-exchange:3000 Pars Pars
<dropped — tenant-managed> (tenant exchange):3000 (tenant EVM) (tenant)

Each exchange deployment is the same ghcr.io/luxfi/exchange:latest image with different NEXT_PUBLIC_BRAND_* env vars injected via ConfigMap.

Exchange API + Proxy

Hostname Service Purpose
api-exchange.lux.network exchange-api:4000 GraphQL proxy
gw.lux.exchange exchange-proxy:8088 Uniswap API proxy

Shared. All exchange brands use the same API backend.

Bridge

Hostname Service Purpose
bridge.lux.network bridge-ui:3000 Bridge frontend
bridge-api.lux.network bridge-server:3000 Bridge API

Single deployment. Multi-chain by design (routes via MPC).

MPC

Hostname Service Purpose
mpc.lux.network mpc-dashboard-svc:3000 MPC dashboard
mpc-api.lux.network mpc-api-svc:8081 MPC API
mpc.zoo.network mpc-dashboard-svc:3000 (alias)
mpc-api.zoo.network mpc-api-svc:8081 (alias)
mpc-api.pars.network mpc-api-svc:8081 (alias)

Single MPC cluster (5 nodes, threshold 3). Serves all orgs.

KMS

Hostname Service Isolation
kms.lux.cloud lux-kms:8443 Shared (Lux/Zoo/Hanzo/Pars/SPC)
<dropped — tenant-managed> (tenant-kms):8443 Dedicated (tenant-only)

Cloud (Bootnode)

Hostname Service Brand
cloud.lux.network lux-cloud-web:3001 Lux
api.cloud.lux.network bootnode-api:80 Lux
ws.cloud.lux.network bootnode-api:80 Lux
cloud.pars.network pars-cloud-web:3001 Pars
api.cloud.pars.network bootnode-api:80 Pars
web3.hanzo.ai hanzo-cloud-web:3001 Hanzo
cloud.zoo.network zoo-cloud-web:3001 Zoo

RPC Gateway

Hostname Service Purpose
api.lux.network gateway:8080 All RPC: /v1/chain/{chain}/rpc (the network is the host, not a prefix)
api.zoo.network gateway:8080 Alias for Zoo RPC

Faucet (NEW)

Hostname Service Network Chain
faucet.lux.network faucet-lux:3000 Testnet C-Chain
faucet-zoo.lux.network faucet-zoo:3000 Testnet Zoo
faucet.zoo.network faucet-zoo:3000 Testnet Zoo
faucet-hanzo.lux.network faucet-hanzo:3000 Testnet Hanzo
faucet-spc.lux.network faucet-spc:3000 Testnet SPC
faucet-pars.lux.network faucet-pars:3000 Testnet Pars
faucet.pars.network faucet-pars:3000 Testnet Pars

Wallet (NEW -- future)

Hostname Service Brand
wallet.lux.network lux-wallet:3000 Lux

3. Ingress Routing Rules

All IngressRoute CRDs use hanzo.ai/v1alpha1 (NOT traefik.io/v1alpha1). All live in the namespace of their target service. TLS via certResolver: letsencrypt with Cloudflare DNS-01 challenge.

Pattern: Multi-Hostname to Single Service

apiVersion: hanzo.ai/v1alpha1
kind: IngressRoute
metadata:
  name: <service>-<brand>
  namespace: <namespace>
  labels:
    app: <service>
    brand: <brand>
spec:
  entryPoints:
    - websecure
  routes:
    - kind: Rule
      match: Host(`primary.domain`) || Host(`alias.domain`)
      services:
        - name: <service-name>
          port: <port>
  tls:
    certResolver: letsencrypt
    domains:
      - main: primary.domain
      - main: alias.domain

Key Design Rule

One Deployment per (service, brand, chain) tuple. One IngressRoute per Deployment, matching all hostnames for that tuple. One ConfigMap per Deployment, holding all brand/chain-specific env vars.

Exception: The Lux Explorer pods index all chains in one process. Every brand hostname for every chain routes to the same set of pods; per-brand appearance is served at runtime via /envs.js and chains.yaml brand: overrides — no image rebuild.

Existing IngressRoutes (Keep)

Name Namespace Match
lux-explorer-zoo lux-explorer explore-zoo.lux.network, explore.zoo.network
lux-explorer-hanzo lux-explorer explore-hanzo.lux.network, explore.hanzo.network
lux-explorer-spc lux-explorer explore-spc.lux.network
lux-explorer-pars lux-explorer explore-pars.lux.network, explore.pars.network
lux-exchange lux-explorer exchange.lux.network, markets.lux.network, lux.exchange
exchange-api lux-explorer api-exchange.lux.network
exchange-proxy lux-explorer api-exchange.lux.network/proxy/*, gw.lux.exchange
lux-bridge lux-bridge bridge.lux.network, bridge-api.lux.network
lux-mpc lux-mpc mpc.lux.network, mpc-api.lux.network

IngressRoutes to Add

Name Namespace Match Service
lux-explorer-hanzo-ai lux-explorer explore.hanzo.ai lux-frontend-hanzo:3000
lux-explorer-zoo-ngo lux-explorer explore.zoo.ngo lux-frontend-zoo:3000
zoo-exchange lux-explorer zoo.exchange zoo-exchange:3000
pars-exchange lux-explorer pars.market pars-exchange:3000
lux-explorer-tenant lux-explorer <dropped — tenant-managed> lux-frontend-tenant:3000
tenant-exchange lux-explorer tenant-exchange:3000
lux-kms lux-kms kms.lux.cloud lux-kms:8443
tenant-kms lux-kms <dropped — tenant-managed> tenant-kms:8443
faucet-lux lux-explorer faucet.lux.network faucet-lux:3000
faucet-zoo lux-explorer faucet-zoo.lux.network, faucet.zoo.network faucet-zoo:3000
faucet-hanzo lux-explorer faucet-hanzo.lux.network faucet-hanzo:3000
faucet-spc lux-explorer faucet-spc.lux.network faucet-spc:3000
faucet-pars lux-explorer faucet-pars.lux.network, faucet.pars.network faucet-pars:3000
mpc-zoo lux-mpc mpc.zoo.network, mpc-api.zoo.network (same MPC services)
mpc-pars lux-mpc mpc-api.pars.network (same MPC services)
hanzo-cloud-web bootnode web3.hanzo.ai hanzo-cloud-web:3001
zoo-cloud-web bootnode cloud.zoo.network zoo-cloud-web:3001

4. ConfigMap Structure for Per-Brand Customization

Explorer Frontend ConfigMap

One ConfigMap per (chain, brand) tuple. Naming: frontend-config-{chain} (current) or frontend-config-{chain}-{brand} if the same chain needs different branding for different hostnames.

Current state: each chain has one ConfigMap (Zoo chain = Zoo brand). This is correct for now because each app chain EVM IS a brand. If a chain ever needs multiple brand presentations, add a second ConfigMap and a second Deployment.

Schema (all fields are NEXT_PUBLIC_* env vars):

# API routing
NEXT_PUBLIC_API_HOST           # api-explore-{chain}.lux.network
NEXT_PUBLIC_API_PROTOCOL       # https
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL  # wss
NEXT_PUBLIC_APP_HOST           # explore-{chain}.lux.network

# Network identity
NEXT_PUBLIC_NETWORK_NAME       # Zoo, Hanzo, SPC, Pars
NEXT_PUBLIC_NETWORK_ID         # EVM chain ID (200200, 36963, etc.)
NEXT_PUBLIC_NETWORK_CURRENCY_NAME    # ZOO, AI, SPC, PARS
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL  # same
NEXT_PUBLIC_NETWORK_RPC_URL    # https://api.lux.network/v1/chain/{blockchain_id}/rpc
NEXT_PUBLIC_IS_TESTNET         # false

# Theme
NEXT_PUBLIC_COLOR_THEME_DEFAULT  # dark

# Disable third-party branding / ad providers
NEXT_PUBLIC_AD_BANNER_PROVIDER   # none
NEXT_PUBLIC_AD_TEXT_PROVIDER     # none

Exchange ConfigMap

One ConfigMap per exchange brand. Naming: exchange-config-{brand}.

Schema (env vars from config/brand.ts):

NEXT_PUBLIC_BRAND_NAME         # Lux Exchange, Zoo Exchange, Pars Market
NEXT_PUBLIC_BRAND_TITLE        # same
NEXT_PUBLIC_BRAND_DESCRIPTION  # tagline
NEXT_PUBLIC_BRAND_URL          # https://lux.exchange
NEXT_PUBLIC_BRAND_SITE_NAME    # same as name
NEXT_PUBLIC_BRAND_SHORT_NAME   # Lux, Zoo, Pars
NEXT_PUBLIC_BRAND_AUTHOR       # Lux Industries Inc Zoo Labs Foundation, etc.
NEXT_PUBLIC_NETWORK_NAME       # Lux Network, Zoo Network, etc.
NEXT_PUBLIC_COIN_NAME          # LUX, ZOO, PARS
NEXT_PUBLIC_LOGO_URL           # /tokens/{brand}.svg
NEXT_PUBLIC_PRIMARY_COLOR      # hex color
NEXT_PUBLIC_TWITTER_HANDLE     # @luxnetwork, etc.
NEXT_PUBLIC_API_ENDPOINT       # https://api.lux.network

Cloud (Bootnode) ConfigMap

Brand is set via NEXT_PUBLIC_BRAND env var on the Deployment directly. IAM client ID varies per brand. API URL varies per brand domain.

NEXT_PUBLIC_BRAND              # lux, zoo, pars, hanzo, <tenants...>
NEXT_PUBLIC_API_URL            # https://api.cloud.{domain}
NEXT_PUBLIC_WS_URL             # wss://ws.cloud.{domain}
NEXT_PUBLIC_IAM_URL            # https://lux.id (shared)
NEXT_PUBLIC_IAM_CLIENT_ID      # {brand}-cloud
NEXT_PUBLIC_IAM_DOMAIN         # lux.id (shared) or tenant.id (dedicated)

Faucet ConfigMap

One per chain. Naming: faucet-config-{chain}.

FAUCET_CHAIN_NAME              # Lux Testnet, Zoo Testnet, etc.
FAUCET_CHAIN_ID                # testnet EVM chain ID
FAUCET_RPC_URL                 # https://api.lux-test.network/v1/chain/{chain}/rpc
FAUCET_AMOUNT                  # drip amount per request (e.g., "1000000000000000000" = 1 token)
FAUCET_CURRENCY_SYMBOL         # LUX, ZOO, AI, SPC, PARS
FAUCET_RATE_LIMIT_SECONDS      # 86400 (once per day)
FAUCET_BRAND_COLOR             # hex
FAUCET_LOGO_URL                # /logo.svg
# Secret: FAUCET_PRIVATE_KEY loaded from KMSSecret, never in ConfigMap

5. KMS Isolation Model

Shared KMS (kms.lux.cloud)

Serves: Lux, Zoo, Hanzo, Pars, SPC.

Organization isolation via KMS projects:

KMS Project Org Secrets
lux Lux Network node staking keys, fee keys, deployer keys, validator BLS
zoo Zoo Labs faucet keys, deployer keys
hanzo Hanzo AI faucet keys, deployer keys
spc SPC faucet keys, deployer keys
pars Pars faucet keys, deployer keys

Access control: IAM OIDC owner claim scopes KMS API calls to the org's project. No org can read another org's secrets.

Dedicated KMS (<dropped — tenant-managed>)

Serves: a single tenant in dedicated mode.

Separate KMS deployment in lux-kms namespace with its own PostgreSQL database. A tenant is a paying client with regulatory requirements for key isolation. Their KMS has its own staking keys, MPC key shares, and deployer keys.

KMS Secret Flow

KMS API (kms.lux.cloud)
    |
    v
KMSSecret CRD (in target namespace)
    |
    v
lux-operator reconciles -> K8s Secret
    |
    v
Pod mounts Secret as env or volume

No secrets in ConfigMaps. No secrets in manifests. No secrets in env files committed to git.


6. Bootnode (Cloud Admin) Integration Points

Bootnode (~/work/bootnode/) is the admin control plane for the white-label platform.

What Bootnode Manages

  1. Org onboarding -- create org in IAM, create KMS project, provision DNS
  2. Chain deployment -- trigger app chain EVM creation via lux-operator CRDs
  3. Service provisioning -- generate K8s manifests for new WL tenant
  4. Monitoring -- health checks, chain sync status, validator status
  5. User management -- IAM users, roles, API keys per org

Architecture

cloud.{brand}.{tld}  -->  bootnode-web (Next.js 15, brand from env)
                               |
api.cloud.{brand}.{tld}  -->  bootnode-api (FastAPI, Python 3.12+)
                               |
                    +----------+----------+
                    |          |          |
                lux-operator  IAM API   KMS API
                (K8s CRDs)   (hanzo.id) (kms.lux.cloud)

Bootnode API Endpoints (Relevant to WL)

Endpoint Purpose
POST /v1/orgs Create new org (provisions IAM + KMS)
POST /v1/orgs/{id}/chains Deploy new app chain EVM chain
POST /v1/orgs/{id}/services Provision explorer/exchange/faucet for org
GET /v1/orgs/{id}/status Health of all org services
POST /v1/orgs/{id}/dns Configure DNS records via CF API
POST /v1/orgs/{id}/secrets Store secret in org's KMS project

Bootnode-Generated Manifests

When onboarding a new client, Bootnode API generates:

  1. ConfigMap for explorer frontend (chain identity, RPC URL, theme)
  2. ConfigMap for exchange (brand vars)
  3. ConfigMap for faucet (chain, drip amount, rate limit)
  4. Deployment + Service for each frontend
  5. IngressRoute for each hostname
  6. KMSSecret CRDs for deployer/faucet keys

These are applied via kubectl apply from the bootnode-api pod (ServiceAccount with RBAC scoped to lux-explorer, lux-kms namespaces).


7. IAM/Auth Flow Per Org

Identity Domains

Domain Org Codebase
hanzo.id Hanzo ~/work/hanzo/login/
lux.id Lux ~/work/hanzo/login/ (same)
id.zoo.network Zoo ~/work/hanzo/login/ (same)
id.pars.network Pars ~/work/hanzo/login/ (same)
(tenant) (tenant) ~/work/hanzo/login/ (same)

Single codebase, hostname detection determines org config (logo, colors, allowed OAuth providers).

Auth Flow

1. User visits cloud.pars.network
2. Frontend redirects to id.pars.network/oauth/authorize?client_id=pars-cloud&redirect_uri=...
3. User authenticates (email/pass, Google, GitHub, Web3 wallet)
4. id.pars.network issues JWT with:
   - sub: user ID
   - owner: "pars"              <-- org scope
   - aud: "pars-cloud"          <-- client ID
   - iss: "https://id.pars.network"
5. Frontend stores JWT, sends as Authorization: Bearer header
6. API validates JWT against IAM JWKS endpoint
7. API scopes all queries to owner="pars"

Tenant Isolation

Every API query MUST be scoped to the owner claim from the JWT. No cross-org data leakage. Enforced at:

  • KMS: project = owner
  • Bootnode API: org = owner
  • Explorer: public data (no auth needed)
  • Exchange: public data (no auth needed, wallet-based)
  • Bridge: wallet-based auth (no org scoping needed)
  • MPC: API key per org, validated against IAM

IAM Client Registration Per Org

Each service per org gets an OIDC client:

Client ID Redirect URI
lux-cloud https://cloud.lux.network/callback
pars-cloud https://cloud.pars.network/callback
zoo-cloud https://cloud.zoo.network/callback
lux-exchange https://lux.exchange/callback
zoo-exchange https://zoo.exchange/callback
pars-market https://pars.market/callback

8. Faucet Deployment Model

Image

ghcr.io/luxfi/faucet:latest -- single image, configured via env vars.

Architecture

User -> faucet.{domain} -> faucet-{chain}:3000
                                |
                          Rate limiter (Valkey)
                                |
                          Fund tx via private key
                                |
                          luxd RPC (testnet)

Deployment Pattern

One Deployment per chain (not per brand). Multiple brand hostnames route to the same faucet Deployment for a given chain.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: faucet-{chain}
  namespace: lux-explorer
  labels:
    app: lux-faucet
    chain: {chain}
spec:
  replicas: 1
  selector:
    matchLabels:
      app: lux-faucet
      chain: {chain}
  template:
    spec:
      containers:
        - name: faucet
          image: ghcr.io/luxfi/faucet:latest
          envFrom:
            - configMapRef:
                name: faucet-config-{chain}
          env:
            - name: FAUCET_PRIVATE_KEY
              valueFrom:
                secretKeyRef:
                  name: faucet-key-{chain}
                  key: private-key
          ports:
            - containerPort: 3000
          resources:
            requests: { cpu: 50m, memory: 64Mi }
            limits: { cpu: 200m, memory: 256Mi }

Rate Limiting

Per-address, per-chain. One request per 24 hours. Backed by Valkey (shared instance in lux-explorer namespace). Key format: faucet:{chain_id}:{address} with TTL=86400.

Faucet Key Management

Each chain's faucet private key is stored in KMS under the org's project. Synced to K8s via KMSSecret CRD -> faucet-key-{chain} Secret.


9. DNS Requirements Per Org

Record Type

All records are A records pointing to 134.199.138.27. All DNS-only (NOT Cloudflare proxied) -- Traefik handles TLS. Exception: apex domains on GitHub Pages use their own IPs.

Lux (lux.network zone + lux.exchange zone)

# lux.network (zone 287bdfd07cf016bd102f6394892e4759)
explore.lux.network         A 134.199.138.27
api-explore.lux.network     A 134.199.138.27
explore-{zoo,hanzo,spc,pars}.lux.network  A 134.199.138.27
api-explore-{zoo,hanzo,spc,pars}.lux.network  A 134.199.138.27
exchange.lux.network        A 134.199.138.27
markets.lux.network         A 134.199.138.27
bridge.lux.network          A 134.199.138.27
bridge-api.lux.network      A 134.199.138.27
mpc.lux.network             A 134.199.138.27
mpc-api.lux.network         A 134.199.138.27
kms.lux.cloud             A 134.199.138.27
faucet.lux.network          A 134.199.138.27
faucet-{zoo,hanzo,spc,pars}.lux.network  A 134.199.138.27
wallet.lux.network          A 134.199.138.27
cloud.lux.network           A 134.199.138.27  (currently 134.199.136.205)
api.cloud.lux.network       A 134.199.138.27  (currently 134.199.136.205)
ws.cloud.lux.network        A 134.199.138.27  (currently 134.199.136.205)
api.lux.network             A 134.199.138.27  (gateway)
api-exchange.lux.network    A 134.199.138.27
api-indexer*.lux.network    A 134.199.138.27

# lux.exchange (zone 99ba4822517691fef46757c763490ad5)
lux.exchange                A 134.199.138.27
gw.lux.exchange             A 134.199.138.27
www.lux.exchange            A 134.199.138.27

Zoo (zoo.network zone)

# zoo.network (zone d9c81c5b54f0f713105d483ddbd61656)
explore.zoo.network         A 134.199.138.27  (exists)
api-explore.zoo.network     A 134.199.138.27  (exists)
api.zoo.network             A 134.199.138.27  (exists)
mpc.zoo.network             A 134.199.138.27  (exists)
mpc-api.zoo.network         A 134.199.138.27  (exists)
# NEW:
exchange.zoo.network        A 134.199.138.27
faucet.zoo.network          A 134.199.138.27
cloud.zoo.network           A 134.199.138.27
api.cloud.zoo.network       A 134.199.138.27

Pars (pars.network zone)

# pars.network (zone 83fa188f7872fba1077981000498ea42)
explore.pars.network        A 134.199.138.27  (exists)
api-explore.pars.network    A 134.199.138.27  (exists)
bridge.pars.network         A 134.199.138.27  (exists)
mpc-api.pars.network        A 134.199.138.27  (exists)
# NEW:
faucet.pars.network         A 134.199.138.27
cloud.pars.network          A 134.199.138.27  (exists in bootnode ingress)
api.cloud.pars.network      A 134.199.138.27  (exists in bootnode ingress)

Hanzo (hanzo.network + hanzo.ai zones)

# hanzo.network (zone c7b46289ad8a039c2cf4ccc24b11128a)
explore.hanzo.network       A 134.199.138.27  (exists)
api-explore.hanzo.network   A 134.199.138.27  (exists)

# hanzo.ai (zone bac51c3900e73fd28aa75a59898bcee0)
explore.hanzo.ai            A 134.199.138.27  (NEW)
api-explore.hanzo.ai        A 134.199.138.27  (NEW -- if needed)
web3.hanzo.ai               A 134.199.138.27  (NEW -- bootnode cloud)
<dropped — tenant-managed>        A 134.199.138.27
<dropped — tenant-managed>            A 134.199.138.27
 (tenant)             CNAME hanzo-id.pages.dev (proxied)

Each tenant manages their own Cloudflare zone. We provide the target IP and IngressRoute specs. They create the records.


10. Onboarding Checklist for New L1/L2 Clients

Prerequisites (Client Provides)

  • Organization name (slug, e.g., acme)
  • Domain name (e.g., acme.network)
  • Cloudflare zone ID (or delegate DNS to us)
  • Logo files (SVG preferred: icon, wordmark, favicon)
  • Brand colors (primary hex, optional secondary)
  • Coin name and symbol (e.g., ACME)
  • EVM chain ID (must be globally unique, verify against chainlist.org)
  • Genesis config (validators, allocations, gas config)
  • KMS isolation: shared or dedicated

Step 1: IAM Setup

  • Create org in IAM: POST /api/v1/orgs { name: "acme", slug: "acme" }
  • Create OIDC clients: acme-cloud, acme-exchange
  • Configure identity domain: id.acme.network -> hanzo-id.pages.dev (or login deployment)
  • Add org admin user

Step 2: KMS Setup

  • Create KMS project: acme
  • Generate deployer key -> store in KMS
  • Generate faucet key -> store in KMS
  • Generate fee key -> store in KMS (for P-chain operations)
  • Create KMSSecret CRDs for each key

Step 3: Chain Deployment

  • Add app chain EVM config to NETWORKS.yaml
  • Deploy app chain EVM via lux-operator LuxChain CRD
  • Verify chain is tracking on all 5 validators (mainnet/testnet)
  • Deploy standard contracts (WLUX, bridged tokens, AMM, governance -- 20 contracts)
  • Verify contract deployments

Step 4: Explorer

  • Add the new chain to the existing Lux Explorer chains.yaml ConfigMap; reload (no new Deployment needed)
  • Create PostgreSQL database (or schema in shared instance)
  • Create frontend ConfigMap (frontend-config-acme)
  • Create frontend Deployment + Service (lux-frontend-acme)
  • Create IngressRoute (explore-acme.lux.network, explore.acme.network)
  • Verify indexing from block 0

Step 5: Exchange (if client wants WL DEX)

  • Create exchange ConfigMap (exchange-config-acme)
  • Create exchange Deployment + Service (acme-exchange)
  • Create IngressRoute (acme.exchange or exchange.acme.network)
  • Add chain to exchange-api supported chains
  • Deploy AMM pools with initial liquidity

Step 6: Faucet

  • Create faucet ConfigMap (faucet-config-acme)
  • Create KMSSecret for faucet key
  • Create faucet Deployment + Service (faucet-acme)
  • Create IngressRoute (faucet.acme.network, faucet-acme.lux.network)
  • Fund faucet address on testnet

Step 7: Cloud (Bootnode)

  • Create cloud-web Deployment with NEXT_PUBLIC_BRAND=acme
  • Create IngressRoute (cloud.acme.network, api.cloud.acme.network)
  • Verify auth flow: cloud.acme.network -> id.acme.network -> callback

Step 8: DNS

  • Add A records to client's Cloudflare zone (or our zone if delegated):
    • explore.acme.network -> 134.199.138.27
    • api-explore.acme.network -> 134.199.138.27
    • faucet.acme.network -> 134.199.138.27
    • cloud.acme.network -> 134.199.138.27
    • api.cloud.acme.network -> 134.199.138.27
    • (exchange, bridge, MPC as needed)
  • Add ACME challenge TXT records for Traefik cert issuance
  • Add subdomain records to lux.network zone:
    • explore-acme.lux.network -> 134.199.138.27
    • faucet-acme.lux.network -> 134.199.138.27
  • Update k8s/dns/lux-network.yaml and client zone YAML
  • Run dns-sync to apply

Step 9: Verification

  • Explorer loads and indexes blocks
  • Exchange loads with correct branding
  • Faucet drips tokens on testnet
  • Cloud dashboard authenticates via IAM
  • KMS secrets accessible only to org
  • RPC gateway returns chain data
  • All TLS certs issued and valid

Step 10: Handoff

  • Document client's chain IDs in NETWORKS.yaml
  • Add to monitoring (lux-operator health checks)
  • Provide client with:
    • IAM admin credentials (hashed, via KMS)
    • API documentation
    • RPC endpoints
    • Explorer URLs
    • Faucet URLs

11. Migration Plan: Current State to Target State

Phase 0: Prerequisites (Already Done)

  • lux-k8s cluster operational with all 3 networks
  • Lux Explorer (Go binary) indexing all 5 chains (C, Zoo, Hanzo, SPC, Pars)
  • Exchange deployed (lux.exchange, exchange.lux.network, markets.lux.network)
  • Bridge deployed (bridge.lux.network)
  • MPC deployed (mpc.lux.network)
  • Gateway deployed (api.lux.network)
  • Bootnode (cloud) deployed for Lux and Pars brands
  • DNS zone YAMLs for all zones
  • Exchange brand.ts config-driven branding

Phase 1: Fix Ingress CRDs (Week 1)

Problem: Bootnode ingress uses networking.k8s.io/v1 Ingress with cert-manager, not hanzo.ai/v1alpha1 IngressRoute with certResolver: letsencrypt.

Action:

  1. Convert k8s/bootnode/ingress.yaml from K8s Ingress to hanzo.ai IngressRoute CRDs
  2. Remove cert-manager annotations, use certResolver: letsencrypt
  3. Add explore.hanzo.ai and explore.zoo.ngo to existing explorer IngressRoutes
  4. Add MPC alias hostnames (mpc.zoo.network, mpc-api.pars.network)

Risk: Low. IngressRoute is additive. Old Ingress objects can be deleted after verification.

Phase 2: White-Label Exchange Deployments (Week 1-2)

Problem: Exchange is a single deployment. Zoo/Pars/tenants have no dedicated exchange instance.

Action:

  1. Create exchange-config-zoo ConfigMap from .env.example.zoo
  2. Create exchange-config-pars ConfigMap from .env.example.pars
  3. Create Deployment + Service for zoo-exchange and pars-exchange
  4. Create IngressRoutes for zoo.exchange, pars.market
  5. All use the same image (ghcr.io/luxfi/exchange:latest), different ConfigMaps

Risk: Low. Same image, different env vars. The brand.ts already reads from env.

Phase 3: Faucet Deployment (Week 2)

Problem: No faucets exist.

Action:

  1. Build faucet image: single Next.js app with RPC integration and rate limiting
  2. One Deployment per chain (6 total: lux, zoo, hanzo, spc, pars + C-chain devnet)
  3. Store faucet private keys in KMS, sync via KMSSecret
  4. Deploy Valkey instance in lux-explorer for rate limiting (or use existing if present)
  5. Add DNS records and IngressRoutes

Risk: Medium. New service. Needs rate limiting to prevent draining. Testnet only.

Phase 4: KMS Namespace + Tenant Isolation (Week 2-3)

Problem: KMS runs on hanzo-k8s. Need lux-kms namespace on lux-k8s for:

  • Shared KMS (Lux/Zoo/Hanzo/Pars/SPC)
  • Dedicated KMS (per-tenant)

Action:

  1. Deploy KMS in lux-kms namespace on lux-k8s
  2. Create two KMS Deployments: lux-kms (shared) and tenant-kms (dedicated)
  3. Each has its own PostgreSQL database
  4. Migrate existing KMS projects from hanzo-k8s to lux-k8s KMS
  5. Update all KMSSecret CRDs to point to new KMS endpoint

Risk: High. KMS migration requires careful secret rotation. Do in maintenance window. Keep hanzo-k8s KMS running as fallback until migration is verified.

Phase 5: Bootnode Admin Automation (Week 3-4)

Problem: Onboarding is manual (write YAML, kubectl apply).

Action:

  1. Add /v1/orgs/{id}/services endpoint to bootnode-api
  2. Implement manifest generation: ConfigMap + Deployment + Service + IngressRoute
  3. Implement kubectl apply via ServiceAccount with scoped RBAC
  4. Add IAM org creation endpoint (proxy to IAM API)
  5. Add KMS project creation endpoint (proxy to KMS API)
  6. Add DNS record creation endpoint (proxy to CF API)

Risk: Medium. Automation of cluster operations requires careful RBAC. ServiceAccount should only have access to lux-explorer and lux-kms namespaces.

Phase 6: Tenant Onboarding (Week 4)

Problem: the tenant is not yet deployed.

Action:

  1. Follow onboarding checklist (Section 10) for the new tenant
  2. Deploy tenant app chain EVM (chain IDs from NETWORKS.yaml)
  3. Dedicated KMS instance (Phase 4 prerequisite)
  4. Explorer, exchange, faucet, cloud
  5. Each tenant manages their own DNS zone -- we provide IP + IngressRoute specs

Risk: Medium. First external paying client. Template for all future clients.

Phase 7: Wallet Integration (Week 5+)

Problem: No wallet UI deployed.

Action: Deferred. wallet.lux.network reserved in DNS. Deploy when wallet codebase is production-ready.


Appendix A: Image Registry

All images on ghcr.io. Built via CI/CD (platform.hanzo.ai Tekton pipelines). Never build images locally.

Image Purpose
ghcr.io/luxfi/node luxd validator node
ghcr.io/luxfi/explorer Lux Explorer (single Go binary: indexer+GraphQL+SPA)
ghcr.io/luxfi/exchange Exchange frontend (Vite SPA)
ghcr.io/luxfi/exchange-api Exchange GraphQL API
ghcr.io/luxfi/exchange-proxy Uniswap API proxy
ghcr.io/luxfi/gateway Gateway API gateway
ghcr.io/luxfi/operator K8s operator (Rust)
ghcr.io/luxfi/indexer Chain indexer
ghcr.io/luxfi/faucet Faucet (NEW)
ghcr.io/hanzoai/bootnode Cloud API + web
ghcr.io/hanzoai/ingress Traefik-based ingress
ghcr.io/hanzoai/kms KMS API

Appendix B: Port Map (Cluster-Internal)

Service Port Protocol
luxd HTTP 9630 (mainnet), 9640 (testnet), 9650 (devnet) HTTP
luxd staking 9631/9641/9651 TCP
Explorer backend 4000 HTTP
Explorer frontend 3000 HTTP
Exchange frontend 3000 HTTP
Exchange API 4000 HTTP
Exchange proxy 8088 HTTP
Bridge UI 3000 HTTP
Bridge API 3000 HTTP
MPC dashboard 3000 HTTP
MPC API 8081 HTTP
KMS API 8443 HTTPS
Gateway 8080 HTTP
Faucet 3000 HTTP
Cloud web 3001 HTTP
Cloud API 80 HTTP
PostgreSQL 5432 TCP
Valkey 6379 TCP

Appendix C: Design Decisions

Decision: One Deployment per (service, brand) tuple, NOT hostname-based routing to a single pod

Rationale:

  • NEXT_PUBLIC_* env vars are baked at build time (or container start). Cannot vary per request.
  • Each brand needs its own container with its own env vars.
  • Cost is ~64-256Mi per pod. Acceptable for 5-10 brands.

Trade-off: More pods than a single multi-tenant container. Acceptable because it eliminates runtime hostname detection complexity and keeps each brand's config immutable and auditable.

Decision: Single Lux Explorer pod set, runtime-branded frontend

Rationale:

  • The Lux Explorer Go binary indexes a chain, not a brand. Chain data is the same regardless of branding.
  • Branding (logo, colors, domain) is served at runtime from chains.yaml and /envs.js. The same image serves every brand on every hostname.
  • Per-chain SQLite under /data/{slug} avoids the shared-PostgreSQL bottleneck of the legacy Elixir backend.

Trade-off: All brand hostnames for a given chain hit the same pod set. If one brand needs different API behaviour (e.g., different rate limits), add middleware in front of the explorer Service rather than a second backend.

Decision: Faucets in lux-explorer namespace, not a separate namespace

Rationale:

  • Faucets are lightweight (50m CPU, 64Mi RAM).
  • They share the same Valkey instance as explorer indexers.
  • Fewer namespaces = simpler RBAC and service discovery.

Decision: Tenants can get dedicated KMS, all others share defaults

Rationale:

  • (tenant) is a paying external client with regulatory requirements.
  • Internal orgs (Zoo, Hanzo, Pars, SPC) are all under Lux Network governance.
  • KMS project isolation (within shared KMS) is sufficient for internal orgs.
  • Dedicated KMS means separate database, separate keys, separate audit log.

Trade-off: Operational overhead of running two KMS instances. Acceptable because this is the template for paying clients with isolation requirements.

Decision: hanzo.ai/v1alpha1 IngressRoute for everything, no K8s Ingress

Rationale:

  • One way to do things. (PHILOSOPHY.md principle 1.)
  • hanzo-ingress (Traefik) is the only ingress controller on lux-k8s.
  • K8s Ingress objects work but bypass Traefik's certResolver.
  • IngressRoute gives direct access to Traefik middlewares (strip prefix, rate limit, etc.).

Action: Convert remaining K8s Ingress objects in bootnode namespace to IngressRoute.