zeekayandHanzo Dev ecffc8b083 e2e: make the registry parseable and declare the dep that reads it
e2e/env.ts reads NETWORKS.yaml at runtime, so two things on main were fatal to
it and are fixed here rather than assumed:

  - NETWORKS.yaml did not parse. `lux-explorer: Lux Explorer (single Go binary:
    indexer+GraphQL+SPA) — 5 chains` is a plain scalar containing ": ", which
    YAML rejects — js-yaml fails with "bad indentation of a mapping entry
    (219:49)". Quoted.
  - js-yaml / @types/js-yaml were not declared, so the import resolved only on a
    machine that happened to have them.

Verified on the rebased tree: NETWORKS.yaml parses and the suite runs green
against mainnet.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-26 15:46:04 -07:00
2026-07-26 15:46:04 -07:00

universe

Lux Universe

Infrastructure monorepo for Lux Network. Compose files, Kubernetes manifests, gateway configs, and deployment scripts.

Lux Ecosystem

Repo Purpose Stack
lux/node Core blockchain node (luxd) Go
lux/wallet @luxfi/wallet (extension, mobile, web) TypeScript
lux/exchange DEX at lux.exchange (apps/web, apps/mobile) Vite + Tamagui
lux/web lux.network marketing site Next.js 14
lux/ui @luxfi/ui component library React
lux/papers 111 research papers LaTeX
lux/proofs Formal verification (Lean 4, TLA+, Halmos, Tamarin) Multi

Structure

compose*.yml        Docker Compose for local dev (mainnet/testnet/devnet)
k8s/                Kubernetes manifests (lux-k8s cluster)
configs/            Node and gateway configuration
chain-configs/      Per-chain EVM configuration
scripts/            Deployment and management scripts
docker/             Dockerfiles
iam/                IAM init data for lux.id
e2e/                Playwright E2E tests (~488 cases)

Networks

All networks are namespaces on the single do-sfo3-lux-k8s cluster. There is no lux-test-k8s or lux-dev-k8s cluster — no such kube context exists. (k8s/lux-test-k8s/ and k8s/lux-dev-k8s/ are leftovers from when there were; they still pin ghcr.io/luxfi/node:v1.28.16 against a v1.36.23 fleet.)

Network Network ID C-Chain ID Namespace (do-sfo3-lux-k8s) luxd
Mainnet 1 96369 lux-mainnet 5/5
Testnet 2 96368 lux-testnet 5/5
Devnet 3 96367 lux-devnet 5/5
Storm — — lux-storm 5/5

Each network runs a 5-replica luxd StatefulSet in its namespace.

app chain EVM Chain IDs

app chain EVM Coin Mainnet Testnet Devnet
Zoo ZOO 200200 200201 200202
Hanzo AI 36963 36962 36964
SPC SPC 36911 36910 36912
Pars PARS 494949 494950 494951

Quick Start

git clone --recursive [email protected]:luxfi/universe.git
cd universe

# Local development
docker compose -f compose.mainnet.yml up -d

# Production: there is no working aggregate apply. See the note below.

kubectl apply -k k8s/lux-k8s is not the deploy path and never worked. The kustomization does not build (it pulls raw files from sibling directories, which kustomize refuses), and if it did build it would regress live — e.g. hanzo/cloud v1.801.210 → v1.785.19. Live workloads are applied per-file by hand; they carry kubectl's last-applied-configuration and no ownerReferences. Reconcile the pins in k8s/lux-k8s/ against the cluster before trying to make it applyable.

Production (lux-k8s)

LB: 134.199.138.27. All *.lux.network services.

Namespace Services
lux-system hanzo-ingress (Traefik), lux-operator
lux-mainnet 5 luxd validators
lux-testnet 5 luxd validators
lux-devnet 5 luxd validators
lux-gateway Gateway API gateway
lux-explorer Lux Explorer (single Go binary: indexer+GraphQL+SPA, all 5 chains), exchange, indexers, faucets
lux-bridge Bridge UI + server
lux-mpc MPC dashboard + API (5 nodes, threshold 3)
lux-kms KMS API

Key Images (GHCR)

Image Purpose
ghcr.io/luxfi/node Blockchain node
ghcr.io/luxfi/explorer Lux Explorer (single Go binary: indexer+GraphQL+SPA)
ghcr.io/luxfi/operator K8s operator
ghcr.io/luxfi/gateway Gateway

RPC Endpoints

Each network has its own host and serves a flat /v1 path. There is no /mainnet or /ext path segment — never hand-build one.

mainnet  https://api.lux.network/v1/bc/{C,P,X}/rpc         chainId 96369
testnet  https://api.lux-test.network/v1/bc/{C,P,X}/rpc    chainId 96368
devnet   https://api.lux-dev.network/v1/bc/{C,P,X}/rpc     chainId 96367

App chains serve the same shape from their own host: api.zoo.network, api.hanzo.network, api.pars.network (-test / -dev suffix per network).

E2E (Playwright)

One axis, one viewport set, one place where environments are defined.

make e2e-install                 # npm ci + chromium

make e2e-gate                    # the go-live gate — minutes, all three frames
make e2e                         # everything, mainnet (default), all three viewports
make e2e-testnet                 # same specs, testnet endpoints
make e2e-devnet
make e2e-local                   # needs: make start network=main  +  ./local/up.sh

make e2e vp=mobile               # one viewport
make e2e spec=e2e/01-explorer.spec.ts
make e2e-health                  # per-surface targets honour LUX_ENV too
make e2e-report                  # HTML report
make e2e-shots                   # open the screenshots for this env

make e2e is ~1977 cases (659 × 3 frames) and takes hours: the very large legacy broker/exchange specs are serial within their own file, so no amount of workers shortens them. make e2e-gate is the subset you run before a go-live — chain health, explorer, validator lifecycle, DeFi, bridge/MPC, DEX contracts, network independence, app chains, plus the login proof when LUX_E2E_PASSWORD is exported.

Concurrency is per-environment and measured (WORKERS in e2e/env.ts), because the environments do not have the same front door. Mainnet's api host is CDN-fronted and ran the gate at 4 workers with 342/342 green; one worker and four returned an identical result set (154 passed / 2 failed) in 5.9m vs 2.3m. Testnet and devnet resolve straight to the cluster LB with no CDN, and at 4 workers the same gate lost 66 cases to gateway throttling that all passed at 2 — it is aggregate load that tips it, so the budget belongs to the environment rather than to any one spec.

LUX_ENV=local|devnet|testnet|mainnet (default mainnet) is the only environment knob. e2e/env.ts resolves every base URL, RPC endpoint, network ID and chain ID from NETWORKS.yaml — the config, helpers.ts and the specs all derive from that one module, so changing a chain ID in the registry changes the suite. The old two-axis ENV=production|local + LUX_NETWORK=… model is retired and LUX_NETWORK now fails loudly instead of being half-honoured.

local targets the one local stack (127.0.0.1:9630 node, :8899 IAM) and reads the mainnet column, because make start network=main boots a mainnet-genesis chain. Only RPC and explorers are per-network: every app surface (cloud, id, wallet, bridge, exchange, staking, mpc, safe) is a single deployment shared by all remote envs — test.lux.cloud and dev.lux.cloud are 404.

Viewports are projects

mobile 390x844, tablet 834x1194, laptop 1440x900 — so "mobile and desktop" means the same three frames on every run, and every screenshot is comparable across runs and environments.

Screenshots

~/Desktop/lux-e2e/<env>/<surface>-<viewport>.png, written on pass and fail — a failure you cannot look at is a failure you cannot fix. Deliberate frames come from snap(page, surface); a failing test's dying state is copied out of Playwright's run directory by e2e/shot-reporter.ts as fail-<spec>-<test>-<viewport>.png.

Proving sign-in

export LUX_E2E_PASSWORD='…'      # never committed; LUX_E2E_USER defaults to [email protected]
make e2e-login

e2e/50-login.spec.ts drives the real flow — lux.cloud → "Continue with Lux ID" → lux.id → /dashboard — and refuses the cheap answers, because every cheap answer lies here:

  • HTTP 200 on /dashboard proves nothing: it is an SPA, so an anonymous visit is also 200 and also stays on the /dashboard URL.
  • "the password field vanished" proves nothing: the OIDC callback parks on an interstitial that has no form either, and /auth/callback with no code renders "Sign-in failed · Missing authorization code in callback URL" at HTTP 200.

So the proof is the access token: the run ends with a JWT in sessionStorage whose iss is the IdP and whose email is the account that was typed, and GET lux.id/v1/iam/oauth/userinfo accepts it (that endpoint returns 401 for no token and 401 for a junk token, so a 200 is only reachable with a token the IdP really minted). That is the exact leg that breaks in an invalid_client regression — the IdP still issues a code and the browser still lands on /dashboard, but the code→token exchange 401s and no token is ever stored. The spec fails on Sign-in failed, invalid_client, PKCE is required, a missing authorization code, a parked interstitial, or still being on the IdP.

It also asserts the handoff is a public PKCE client (client_id=lux-cloud, code_challenge_method=S256, redirect https://lux.cloud/auth/callback, no secret), and it carries its own negative control: a test that proves an anonymous /dashboard holds no token and offers no sign-out, so the positive assertions cannot go vacuous.

License

Proprietary - Lux Partners Limited. All rights reserved.

S
Description
Declared state for the lux fleet: one values file per service, one ApplicationSet
Readme BSD-3-Clause
6.7 MiB
Languages
TypeScript 66.4%
Shell 14.9%
Python 7.9%
Go 4.8%
Makefile 4.3%
Other 1.7%