k8s(api): white-label api gateway pattern + api.zoo.exchange ingress

Lux-ecosystem KrakenD gateway (lux-mainnet/api-gateway-gateway) is now
documented as a shared multi-tenant backend. Any white-label app —
Zoo, Hanzo, Pars, <tenant>, third-party — points api.<their-domain>
at the same service via a 5-line Ingress + DNS A record. CORS is
wide-open (allow_origins=*) so browser SPAs from any origin work.

This commit:
- k8s/lux-mainnet/api-zoo-exchange-ingress.yaml — first WL ingress
  (api.zoo.exchange → api-gateway-gateway:80, TLS via cert-manager)
- k8s/lux-mainnet/WHITE-LABEL-API.md — onboarding recipe + endpoint
  list + backend topology diagram

zoo.exchange's SPA env (SPA_RPC_HOST, SPA_API_HOST, SPA_GATEWAY_HOST)
all now point at api.zoo.exchange. JS bundle was rewritten by the init
container to swap api.lux.network/* and gateway.{lux,uniswap}.org
references for api.zoo.exchange — zero third-party domains visible
in browser network tab.

Out-of-band: removed api.zoo.exchange from the conflicting
lux-ns/exchange-api Traefik IngressRoute so the standard Ingress
takes the route.
This commit is contained in:
Hanzo AI
2026-04-25 20:52:18 -07:00
parent 6065a70d56
commit 19aba237f0
2 changed files with 181 additions and 0 deletions
+152
View File
@@ -0,0 +1,152 @@
# White-Label API Gateway (`api-gateway-gateway`)
The Lux ecosystem's KrakenD gateway at `lux-mainnet/api-gateway-gateway:80`
is a **shared multi-tenant backend**. Any white-label app — Zoo, Hanzo,
Pars, Liquidity, or third-party — points its `api.<their-domain>` at the
gateway and gets:
- All Lux-ecosystem chain RPCs (`/ext/bc/{C,X,P,zoo,hanzo,spc,pars,Q,Z,T,A,D}/rpc`)
- Indexer endpoints (`/api/indexer/indexer-{c,zoo,hanzo,spc,pars}`)
- Health, info, and admin endpoints (`/ext/health`, `/ext/info`)
- DAG chain stats (`/api/v2/dag/{q,z,t,a}chain/{health,stats}`)
- DEX precompile / gateway routes
- TLS via cert-manager letsencrypt
- CORS already wide-open (`Access-Control-Allow-Origin: *`)
Zero per-tenant config needed in the gateway itself.
## Onboarding a new white-label app
1. **Pick a hostname**: `api.<your-app>.<tld>` (e.g. `api.zoo.exchange`,
`api.hanzo.network`, `api.pars.market`).
2. **Create an Ingress** in `lux-mainnet` ns:
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-<your-app>
namespace: lux-mainnet
annotations:
cert-manager.io/cluster-issuer: letsencrypt
spec:
ingressClassName: ingress
tls:
- hosts: [api.<your-app>.<tld>]
secretName: api-<your-app>-tls
rules:
- host: api.<your-app>.<tld>
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-gateway-gateway
port: { number: 80 }
```
3. **Add DNS**: `A` record for `api.<your-app>.<tld>` → `134.199.138.27`
(`hanzo-ingress` LB on `do-sfo3-lux-k8s`). Cloudflare-proxied is fine.
4. **Verify**:
```bash
curl -X POST -H 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' \
https://api.<your-app>.<tld>/ext/bc/zoo/rpc
# → {"jsonrpc":"2.0","id":1,"result":"0x30e08"}
```
5. **Point your SPA** at the new hostname. With `hanzoai/spa` runtime,
set the env vars:
```yaml
- { name: SPA_RPC_HOST, value: https://api.<your-app>.<tld>/ext/bc/<chain>/rpc }
- { name: SPA_API_HOST, value: https://api.<your-app>.<tld> }
- { name: SPA_GATEWAY_HOST, value: https://api.<your-app>.<tld> }
```
These get templated into `/config.json` at pod start; the SPA reads them
on boot and uses them for every backend call.
## Live deployments
| host | namespace | ingress |
|---|---|---|
| `api.lux.network` | `lux-mainnet` | (legacy direct DNS) |
| `api.zoo.exchange` | `lux-mainnet` | `api-zoo-exchange` |
| `api.hanzo.network` | _(reserve)_ | _(create when needed)_ |
| `api.pars.market` | _(reserve)_ | _(create when needed)_ |
| `api.liquid.exchange` | _(reserve)_ | _(create when needed)_ |
All point to the same `api-gateway-gateway` service; each just gets its
own TLS cert and DNS record.
## Endpoints exposed
```
# Chain RPCs (40+ endpoints across 12 chains × 3 envs each)
POST /ext/bc/C/rpc # Lux C-Chain (96369)
POST /ext/bc/zoo/rpc # Zoo (200200)
POST /ext/bc/hanzo/rpc # Hanzo (36963)
POST /ext/bc/pars/rpc # Pars (494949)
POST /ext/bc/spc/rpc # SPC (36911)
POST /ext/bc/{Q,Z,T,A,D}/rpc # primary network chains
POST /ext/bc/{X,P} # X-Chain, P-Chain
GET /ext/info
GET /ext/health{,/liveness}
# Indexer (block/tx data per chain)
GET /api/indexer/indexer-{c,zoo,hanzo,spc,pars}/...
# DAG / consensus stats
GET /api/v2/dag/{q,z,t,a}chain/{health,stats}
GET /api/chains
```
Add a new endpoint by editing `lux-mainnet/api-gateway-gateway-config`
ConfigMap (krakend.json `endpoints[]`) and rolling the deployment.
## CORS
Wide-open by default — any browser Origin is allowed:
```json
"security/cors": {
"allow_origins": ["*"],
"allow_methods": ["GET","POST","OPTIONS"],
"allow_headers": ["Content-Type","Authorization"],
"expose_headers": ["Content-Length"],
"max_age": "12h"
}
```
To tighten to a specific allow-list, replace `["*"]` with the explicit
origin list and roll `api-gateway-gateway` deployment in `lux-mainnet`.
## Backend topology
```
Browser (zoo.exchange / hanzo.exchange / pars.market / ...)
│
│ https://api.<app>.<tld>/ext/bc/zoo/rpc
▼
Cloudflare (TLS termination + caching)
│
▼
hanzo-ingress LB (lux-system, 134.199.138.27)
│ routes by Host header → matching K8s Ingress
▼
api-gateway-gateway (lux-mainnet, KrakenD :80)
│ matches /ext/bc/<alias>/rpc → looks up backend host
▼
luxd-headless.lux-mainnet.svc.cluster.local:9630
│ StatefulSet: 5 luxd validators on Lux Mainnet
▼
Subnet-EVM plugin (per chain) → on-disk badgerdb state
```
The SPA never needs to know any of this — it just calls
`https://api.<their-domain>/ext/bc/<chain>/rpc` and KrakenD does the rest.
@@ -0,0 +1,29 @@
---
# api.zoo.exchange — white-label of api.lux.network. Same Lux KrakenD
# gateway (lux-mainnet/api-gateway-gateway:8080) backs both. SPA hits
# api.zoo.exchange for /ext/bc/zoo/rpc, /api/indexer/indexer-zoo, etc.
# Zero third-party traffic visible to user.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-zoo-exchange
namespace: lux-mainnet
annotations:
cert-manager.io/cluster-issuer: letsencrypt
# Allow all origins for browser SDK calls from zoo.exchange
hanzo.ai/ingress.cors-allow-origin: "https://zoo.exchange,https://www.zoo.exchange"
spec:
ingressClassName: ingress
tls:
- hosts: [api.zoo.exchange]
secretName: api-zoo-exchange-tls
rules:
- host: api.zoo.exchange
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-gateway-gateway # KrakenD already wired with all chain RPCs + indexers
port: { number: 8080 }