Files
universe/k8s/lux-ns/exchange-lux-cloud.yaml
121113ee34 exchange: a chain's graph is read through the exchange, not around it
The app asks for a chain by name in the path, and only the explorer had a route
per chain — so that request went straight to the raw rows. Its token list named
bridged ether LETH beside a token page calling the same contract ETH, and Zoo's
own coin introduced itself as Wrapped LUX, because only one of those two paths
came through the service that knows what a token is.

exchange-api serves the same shape now and dresses what it forwards with the
registry for the chain in the path rather than the one it is pinned to. That is
the part the explorer cannot do: Lux and Zoo share contract addresses, so the
address alone cannot say which coin is meant and the path is what does.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-28 12:41:01 -07:00

146 lines
6.9 KiB
YAML

# The exchange adapter, on the cloud name.
#
# lux.network is the chain: RPC, blocks, anything a third party could run for
# themselves. lux.cloud is what WE host. This adapter reads the chain and serves
# derived aggregates — market data, quotes, rankings — so it is the second kind,
# and api-exchange.lux.network read as protocol infrastructure.
#
# The WHOLE host maps to exchange-gw, deliberately. Hanging it off
# api.lux.cloud as a path looked tidier, but that host's `/` already belongs to
# cloud:8000, so only the paths enumerated here would reach the exchange and the
# rest — /v1/quote, /v1/swappable_tokens, the Connect-RPC /data.v1.* routes —
# would silently answer from the wrong service. One host, one backend, no list
# of paths to keep in sync with the app.
#
# Identical routing to api-exchange.lux.network, which stays until the built app
# stops asking for it.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: exchange-lux-cloud
namespace: lux-ns
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: ingress
rules:
- host: exchange.lux.cloud
http:
paths:
# The chain belongs in the path, and the answer belongs to the
# exchange. The explorer indexes every chain on one service — cchain,
# zoo, pars, osage — and pointing here at it directly is what let the
# app read a chain's raw rows: its list named bridged ether LETH while
# a token page called the same contract ETH, because only one of those
# two came through the service that knows what a token is.
#
# exchange-api serves the same shape now and dresses what it forwards
# with the registry for the chain in the PATH, not the one it is
# pinned to — which is the part the explorer cannot do, since Lux and
# Zoo share contract addresses and only the path says which is meant.
#
# The trailing slash is load-bearing. This controller matches a
# prefix as a string, not by path element, so `/v1/graph` also claims
# `/v1/graphql` — a different route on the same service, with a
# different handler behind it.
- path: /v1/graph/
pathType: Prefix
backend:
service:
name: exchange-api
port:
number: 4000
# The swap form's four questions, answered by the explorer from the
# book it already indexes rather than by a second service keeping a
# second copy. Checked against exchange-api before moving: same fields,
# same routing, same output amount to the wei, same pool, same gas
# estimate.
#
# Prefix, not Exact: this cluster has no Exact rule anywhere, so its
# behaviour here is untested, and an untested match on the path a
# swap is priced through is not worth the tidiness. Prefix is
# unambiguous for all four — `/v1/swap` also claims
# `/v1/swappable_tokens` as a string, which costs nothing because
# both go to the same place.
#
# /v1/graphql deliberately stays below on exchange-api: the explorer
# answers it, but not for the seven operations the app actually
# sends, and a 200 carrying `unknown field` is worse than a proxy.
# The trading endpoints answer from the service that owns trading.
#
# They used to go to the indexer, and its quote returns nothing: ask
# both for 1000 LUX into LUSD and exchange-api answers 0.5634 LUSD
# over LUX -> LUSD with a gas estimate and a price impact, while the
# indexer answers an amount of 0, an empty route, no gas and no
# impact. A swap panel given that has nothing to show and nothing to
# submit, which is why the app fell through to quoting on-chain in the
# browser — and that fallback then died on the preflight, so the panel
# was empty twice over.
- path: /v1/quote
pathType: Prefix
backend:
service:
name: exchange-api
port:
number: 4000
- path: /v1/swap
pathType: Prefix
backend:
service:
name: exchange-api
port:
number: 4000
# /v1/swappable_tokens needs its own rule even now: `/v1/swap` above
# matches it as a STRING prefix, the way `/v1/graph` would claim
# `/v1/graphql`, so its destination is stated rather than inherited
# from whichever rule happens to sort first.
#
# It goes to exchange-api, which is where a token's identity lives.
# The indexer knows what trades; it does not know what a token is
# called or what it looks like, so it built an icon URL by pasting the
# symbol into a path — icon-png/wlux.png, icon-png/z.png,
# icon-png/usdc.png — and every one of those is a 404, a row of broken
# images in the token selector. The registry holds addresses verified
# on-chain, the names we display, and icon URLs that were checked.
- path: /v1/swappable_tokens
pathType: Prefix
backend:
service:
name: exchange-api
port:
number: 4000
- path: /v1/check_approval
pathType: Prefix
backend:
service:
name: exchange-api
port:
number: 4000
- path: /
pathType: Prefix
backend:
# exchange-api, NOT exchange-gw. The gateway sets its own
# Access-Control-Allow-Origin on a response that exchange-api
# (Express) has already set one on, so a browser sees the header
# twice and refuses the response outright — "contains multiple
# values". Every page rendered empty with no failed request to
# show for it.
#
# api-exchange.lux.network, which has worked all along, reaches
# exchange-api directly: its response carries no x-krakend header
# and exactly one ACAO. Testnet and devnet were already wired this
# way. This is mainnet matching the three that work.
#
# The gateway's five endpoints all proxy to this same
# exchange-api:4000, so nothing is lost by the shorter path. The
# duplicate-header bug is the gateway's to fix, and until it is
# fixed no browser-facing host can sit behind it.
service:
name: exchange-api
port:
number: 4000
tls:
- hosts:
- exchange.lux.cloud
secretName: exchange-lux-cloud-tls