16 KiB
Full Stack Standup — Lux + White-Label Exchanges
This document describes how to bring up a complete Lux-ecosystem deploy that hosts:
- Lux Mainnet validators (Lux primary network, network ID 1)
- app chain EVM chains: Zoo (200200), Hanzo (36963), Pars (494949), SPC (36911)
- Per-chain backends: Gateway + indexer + GraphQL subgraph + explorer frontend
- White-label DEX SPAs:
zoo.exchange,hanzo.exchange,pars.market,lux.exchange— all running the canonical@luxfi/exchangesource with their own brand, chain default, andapi.<their-domain>backend - Cross-chain bridge:
bridge.lux.network+ per-brand white-label deployments via~/work/lux/bridge
Repository map
~/work/lux/
├── exchange/ ← canonical DEX frontend (forked from Uniswap; rebranded)
│ publishes @luxfi/exchange to npm
├── bridge/ ← cross-chain bridge (MPC 2-of-3, Warp messaging)
│ deploys: bridge.<brand>.<tld>, multi-tenant by domain
├── explorer/ ← UNIFIED indexer + graph + explore frontend
│ ghcr.io/luxfi/explorer:latest serves :8090
│ ghcr.io/luxfi/indexer ← chain data (blocks, txs, contracts)
│ ghcr.io/luxfi/graph ← subgraph events (DeFi, GraphQL)
│ ghcr.io/luxfi/explore ← Next.js frontend
├── graph/ ← GraphQL subgraph engine (Postgres-backed)
├── universe/ ← K8s manifests for everything (this dir)
├── standard/ ← Solidity contracts (V2/V3 AMM, bridge tokens, governance)
├── genesis/ ← Per-chain genesis files (alloc, chain config, precompiles)
└── cli/ ← lux-cli for chain ops (chain create/deploy, network start)
~/work/zoo/
├── exchange/ ← Zoo white-label of @luxfi/exchange (canonical example)
├── contracts/ ← Zoo bridge tokens (Z-prefix: ZETH, ZBTC, ZUSD, ZLUX, …)
├── brand/ ← @zooai/brand on npm (colors, fonts, socials, translations)
└── logo/ ← @zooai/logo on npm (SVG generators)
~/work/hanzo/
├── exchange/ ← (NEW) Hanzo white-label — see "Forking pattern" below
├── brand/ ← @hanzoai/brand
└── logo/ ← @hanzoai/logo
~/work/pars/
├── exchange/ ← (NEW) Pars white-label
├── brand/ ← @parsdao/brand
└── logo/ ← @pars/logo
Network topology
┌─────────────┐
┌──────────────────────────► │ Browser │
│ │ zoo.exchange│
│ HTML+JS (hanzoai/spa) │ │
│ └──────┬──────┘
│ │
│ api.zoo.exchange/...
│ │
┌───────────┴────────────┐ ┌───────▼─────────┐
│ hanzoai/ingress (DOKS) │ │ Cloudflare │
│ TLS via cert-manager │◄──────────────┤ DNS + edge │
└───────────┬────────────┘ └─────────────────┘
│
│ routes by Host header to in-cluster Service
│
┌────────────────────┼────────────────────┬──────────────────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌───────────┐ ┌───────────────┐ ┌──────────────┐ ┌──────────────┐
│zoo-exchange│ │api-gateway- │ │explorer │ │bridge-ui │
│(SPA on │ │ gateway:80 │ │(unified │ │(MPC bridge │
│ hanzoai/ │ │(Gateway, │ │ luxfi/explorer│ │ frontend) │
│ spa) │ │ all RPC routes│ │ :8090) │ │ │
└───────────┘ │ + indexer + …)│ └──────┬───────┘ └──────┬───────┘
└───────┬───────┘ │ │
│ proxies │ indexer reads │ MPC nodes
│ │ graph reads │ sign txs
▼ ▼ ▼
┌──────────────────────────────────────────────────────────┐
│ luxd validators (StatefulSet, 5 replicas) │
│ Primary network + all app chain EVMs (Zoo, Hanzo, Pars, SPC) │
│ /v1/chain/C/rpc → Lux C-Chain (96369) │
│ /v1/chain/zoo/rpc → Zoo (200200) │
│ /v1/chain/hanzo/rpc → Hanzo (36963) │
│ /v1/chain/pars/rpc → Pars (494949) │
│ /v1/chain/spc/rpc → SPC (36911) │
│ /v1/chain/{Q,Z,T,A,D,B,K}/rpc → primary-network chains │
└──────────────────────────────────────────────────────────┘
Per-chain gateway + explorer architecture
Each chain runs ONE backend service per concern:
| chain | gateway | indexer | graph (subgraph) | explorer frontend |
|---|---|---|---|---|
| Lux C-Chain | api.lux.network/v1/chain/c |
indexer-c-indexer |
(rolled into explorer-mainnet-v2) |
explore.lux.network |
| Zoo | api.lux.network/v1/chain/zoo/rpc |
indexer-zoo-indexer |
indexer + graph + explore in ghcr.io/luxfi/explorer |
explore.zoo.network |
| Hanzo | api.lux.network/v1/chain/hanzo/rpc |
indexer-hanzo-indexer |
same | explore.hanzo.ai |
| Pars | api.lux.network/v1/chain/pars/rpc |
indexer-pars-indexer |
same | explore.pars.network |
| SPC | api.lux.network/v1/chain/spc/rpc |
indexer-spc-indexer |
same | explore.spc.network |
Unified explorer image (ghcr.io/luxfi/explorer:latest, source ~/work/lux/explorer/):
- Single container, port 8090
- Combines
ghcr.io/luxfi/indexer(chain data) +ghcr.io/luxfi/graph(DeFi events, GraphQL endpoint at/v1/graphql) +ghcr.io/luxfi/explore(Next.js UI) chains.yamlConfigMap declares which chains it indexes; default tracks all primary + app chain EVM chains- Storage: per-chain SQLite at
/data/<chain>/index.db(WAL mode) - Optional snapshot replication to
s3.lux.cloud
White-label exchanges hit:
api.<brand>.<tld>/v1/chain/<chain>/rpcfor raw RPC (proxied via Gateway to luxd)api.<brand>.<tld>/v1/graphqlfor swaps/pools/positions/historical (proxied to graph engine)api.<brand>.<tld>/api/indexer/indexer-<chain>for block/tx/contract metadata
If a brand wants their OWN per-chain backend (sovereign indexer, custom subgraph), they can:
- Run
ghcr.io/luxfi/explorerwith their ownchains.yaml(track only their chain) - Point
api.<brand>.<tld>at THEIR explorer instead of the sharedapi-gateway-gateway - Resolve cross-chain queries via Warp messaging or the canonical
api.lux.network
White-label DEX standup
Every WL exchange follows the same shape:
~/work/<brand>/exchange/
├── apps/web/src/main.tsx ← thin shim, ~50 lines
│ imports @luxfi/exchange + @<brand>ai/brand + @<brand>ai/logo
├── apps/mobile/ ← Expo iOS+Android
├── brand-assets/ ← logo.svg, favicon.svg, wordmark.svg
├── csp.json ← brand-scoped Content Security Policy
├── featured-tokens.ts ← landing-page token cloud (28 tokens)
├── Dockerfile ← FROM ghcr.io/luxfi/exchange + overlays
└── README.md
main.tsx contract (declarative React, one component):
import { createRoot } from 'react-dom/client'
import Exchange, { canonicalChains, zooMainnet } from '@luxfi/exchange'
import brand from '@zooai/brand' // swap for @hanzoai/brand, @parsdao/brand, …
import Logo from '@zooai/logo'
// import translations…
createRoot(document.getElementById('root')!).render(
<Exchange
{...brand} logo={Logo}
chains={canonicalChains} // lux + hanzo + zoo + liquid (12 chains)
defaultChain={zooMainnet} // → 200200
dex={{ kind: 'gateway', url: '/' }} // SPA hits same-origin /v1/chain/<chain>/rpc
auth={{ provider: 'iam', issuer: 'https://zoolabs.id', clientId: 'zoo-exchange', idHost: 'https://zoolabs.id' }}
kms={{ url: 'https://kms.zoo.network' }}
bridge={{ url: 'https://bridge.zoo.ngo' }} // ~/work/lux/bridge — multi-tenant by domain
i18n={{ 'en-US': en, 'es-ES': es, 'zh-CN': zh }}
features={{ swap, pool, portfolio, limit, send, buy, bridge, explore, activity, nft }}
featured={[ /* stocks (tenant-gated) / private / native chain tokens */ ]}
/>,
)
Onboarding a new white-label app (e.g. ~/work/hanzo/exchange)
-
Fork from canonical Zoo template:
cp -r ~/work/zoo/exchange ~/work/hanzo/exchange cd ~/work/hanzo/exchange git remote set-url origin [email protected]:hanzoai/exchange.git -
Swap brand imports in
apps/web/src/main.tsx:- import brand from '@zooai/brand' - import Logo from '@zooai/logo' + import brand from '@hanzoai/brand' + import Logo from '@hanzoai/logo' - defaultChain={zooMainnet} + defaultChain={hanzoMainnet} -
Replace
brand-assets/SVGs with the brand's mono+color logos. -
Update
Dockerfile:- tar -xzf "$(npm pack @zooai/brand@latest)" --strip-components=1 + tar -xzf "$(npm pack @hanzoai/brand@latest)" --strip-components=1 -
Stand up
api.hanzo.exchangeingress (seek8s/lux-mainnet/WHITE-LABEL-API.md):# k8s/<brand>-exchange/api-ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: { name: api-hanzo-exchange, namespace: lux-mainnet } spec: ingressClassName: ingress tls: [{ hosts: [api.hanzo.exchange], secretName: api-hanzo-exchange-tls }] rules: - host: api.hanzo.exchange http: { paths: [{ path: /, pathType: Prefix, backend: { service: { name: api-gateway-gateway, port: { number: 80 } } } }] } -
Stand up the SPA pod (re-use
k8s/exchange/overlays/zoo-mainnet/deployment.yaml, change namespace + ingress hosts + brand env vars). -
DNS:
hanzo.exchange→134.199.138.27(hanzo-ingress LB), Cloudflare proxiedapi.hanzo.exchange→ same LB
That's it. The new brand inherits all chain support, all swap/pool/limit/bridge functionality, all canonical token lists, all canonical RPC infra. Per-app cost is ~50 lines of main.tsx + Dockerfile overlay.
Bridge integration
~/work/lux/bridge is multi-tenant by Host header. The bridge UI at
bridge.lux.network, bridge.zoo.ngo, bridge.hanzo.ai, bridge.pars.network
all serve from the SAME deployment in lux-bridge namespace; the frontend
detects domain and renders matching brand. To add a brand's bridge:
- Add the host to the
bridge-ingressingress - Configure brand in
app/bridge/src/lib/tenant.ts(or setBRIDGE_TENANT_CONFIGenv) - Point DNS at
134.199.138.27
The MPC mode (lux-mpc 3-node cluster vs t-chain ThresholdVM) is set per-tenant
in the same tenant config. All Lux ecosystem brands share the same MPC custody
network unless explicitly opted out.
Local dev — full stack on one machine
# 1. Lux validator (anvil-style dev mode, chain id 31337)
cd ~/work/lux/cli && go build -o bin/lux ./main.go
./bin/lux network start --local
# 2. Deploy contracts (V2 + V3 + bridge tokens + governance)
cd ~/work/lux/standard
LUX_MNEMONIC="<your-mnemonic>" forge script script/DeployFullStack.s.sol --rpc-url http://127.0.0.1:8545 --broadcast
# 3. Unified explorer + graph (with chains.example.yaml: only local chain)
cd ~/work/lux/explorer
docker compose up -d # explorer:8090 with indexer + graph + frontend
# 4. Bridge MPC nodes
cd ~/work/lux/bridge && make up && make start-mpc-nodes
# 5. Pick ONE white-label exchange to run
cd ~/work/zoo/exchange && pnpm install && pnpm --dir apps/web dev # vite on :5173
# Open http://localhost:5173 — talks to local luxd via api.lux.network override
Cluster deploy — production
# All manifests live under ~/work/lux/universe/k8s/
kubectl --context=lux-k8s apply -f k8s/lux-mainnet/ # validators + gateway + indexers
kubectl --context=lux-k8s apply -f k8s/lux-explorer/ # unified luxfi/explorer pod
kubectl --context=lux-k8s apply -f k8s/lux-bridge/ # MPC bridge
kubectl --context=lux-k8s apply -k k8s/exchange/overlays/zoo-mainnet # Zoo SPA + ingress
# Lux Uniswap fork (lux.exchange + markets.lux.network + exchange.lux.network):
kubectl --context=lux-k8s apply -k k8s/exchange/overlays/lux-mainnet
# Per-env Lux exchanges (testnet/devnet) and pars-mainnet share the same base.
kubectl --context=lux-k8s apply -f k8s/lux-mainnet/api-zoo-exchange-ingress.yaml
# (replicate api ingresses for each brand)
DNS (managed in Cloudflare):
| host | type | target | proxied | brand |
|---|---|---|---|---|
lux.exchange |
A | 134.199.138.27 | ✓ | Lux |
api.lux.network |
A | 134.199.138.27 | ✓ | Lux |
zoo.exchange |
A | 134.199.138.27 | ✓ | Zoo |
api.zoo.exchange |
A | 134.199.138.27 | ✓ | Zoo |
hanzo.exchange |
A | 134.199.138.27 | ✓ | Hanzo (TBD) |
api.hanzo.exchange |
A | 134.199.138.27 | ✓ | Hanzo (TBD) |
pars.market |
A | 134.199.138.27 | ✓ | Pars (TBD) |
api.pars.market |
A | 134.199.138.27 | ✓ | Pars (TBD) |
<dropped — tenant-managed> |
A | 134.199.138.27 | ✓ | white-label tenant |
<dropped — tenant-managed> |
A | 134.199.138.27 | ✓ | white-label tenant |
Reference
k8s/lux-mainnet/WHITE-LABEL-API.md— gateway onboarding recipek8s/exchange/— canonical base + per-env overlays (lux-mainnet/testnet/devnet, zoo-mainnet, pars-mainnet)~/work/lux/explorer/README.md— unified indexer+graph+explore image~/work/lux/bridge/docs/DEPLOYMENT.md— bridge MPC standup~/work/lux/genesis/configs/— per-chain genesis files~/work/lux/standard/DEPLOYMENTS.md— deployed contract addresses per chain