The swap list answered anonymously with every swap id, the MPC signature
minted for it and the signed destination tx, on the port the Ingress
publishes. /metrics named the release wallets and reported which of them
could not sign. The deposit poll turned an anonymous request into a
source-chain RPC call with caller-chosen arguments.
All three move to a second listener, BRIDGE_ADMIN_ADDR, along with the
swap mutators, which still need BRIDGE_ADMIN_ROUTES on top. The edge
routes a port, so a port is the thing that can be left unrouted: k8s
publishes 8080 and gives the operator surface 8081, and the ServiceMonitor
scrapes there. A swap read by id keeps the swap's progress and drops the
signature and the raw tx; the list on the operator port carries both.
The legacy proxy exposed the same list twice. `/api/swaps/*` matched the
empty remainder and so forwarded GET /api/swaps to the backend's list;
`+` requires a sub-path. `/v1/bridge/swaps/*` forwarded arbitrary
sub-paths into /swaps/… after the prefix strip — the backend mounts its
swap router at /api/swaps, so that path space holds no routes and the
forwarder only ever produced a 404.
luxd serves chain routes under /v1/chain, and the node, cli and sdk releases
already ship it. The deployed gateways still answer the old prefix, so remote
reads break until they are redeployed; the estate moves together rather than
sitting half converted.
Lines that record an observation - a probe, a status code, a past release -
keep the prefix they were written against, as do URLs on hosts that are not
ours. Avalanche endpoints move to /ext/bc, which is the path that host serves.
The rename to /v1/chain is real in luxd source, but the deployed fleet has not
taken it yet: probed just now, api.lux.network, api.lux-test.network,
api.zoo.network and api.hanzo.network each answer 200 on /v1/bc/C/rpc and 404 on
/v1/chain/C/rpc. Composing /v1/chain against those hosts reaches nothing, which
is the failure the rename set out to prevent.
The move belongs with the node redeploy, not ahead of it.
luxd serves chain routes under /v1/chain; the /v1/bc prefix it replaced is
gone, so anything composing it reaches nothing. Lines that record an
observation - a probe, a status code, a past release - keep the prefix they
were written against, as do URLs on hosts that are not ours.
Lux Partners Limited is not the entity behind this work. Every place the
old name stood — copyright lines, site footers, brand config, legal and
compliance text — now names the company that actually holds it.
lux.fund is the exception and is not renamed to match: that site belongs
to Lux Assets Management LLC, a different company, and it says so.
Two kinds of line were left for a person rather than rewritten. A dated
news item recording that Lux Partners Limited was established in the Isle
of Man is a historical fact, and renaming it would falsify the record. And
a sentence asserting that the company is domiciled in the Isle of Man is a
claim about a jurisdiction, not a name — an Inc or an LLC is not an Isle
of Man company, so swapping the name there would assert something untrue.
Port of the wallet-health work from preserve/lux-bridge-golive onto
main's architecture: canary-sign poller (10m default) against every
minted release wallet, cached snapshot surfaced on /metrics as
bridge_release_wallet_signable / _sign_latency_ms / _last_check_age,
PrometheusRule + ServiceMonitor for the o11y stack, and the operator
runbook section (re-mint, don't retry). Catches the June key-share
desync failure mode before a real payout does.
Every bridge swap creation has failed since the MPC StatefulSet was cut over,
with `dial tcp <mpc-api-svc>:8081: i/o timeout`, so no deposit address was ever
minted and /health showed 229,398 broadcast ticks with 0 attempts and 0
successes. Two independent misconfigurations, each fatal on its own:
1. mpc-api-svc mapped 8081 -> targetPort 8080, but the StatefulSet launches
`mpcd start --listen :9651 --api :9800`. `netstat -ltn` inside mpc-node-0
lists 7301/9800/9653/9651 — nothing has ever bound 8080. The StatefulSet
declares no readinessProbe, so all three pods were published as ready
endpoints on the dead port and the Service read as healthy.
2. mpc-node-ingress admitted only app=bridge-server on 8080/8081. The Go
backend that serves bridge.lux.network is app=lux-bridge, and the real API
port 9800 was on no allow-list at all, so the packets were dropped rather
than rejected — hence a hang instead of a connection error.
Verified against the live cluster: mpc-api-svc:8081/keygen now answers instead
of hanging, and POST /api/swaps advances from 502 `i/o timeout` to 401
`unauthorized`. Auth is a separate, still-open blocker: mpcd's validateJWT
requires iss=mpc.lux.network and aud containing mpc-api, and derives its HS256
key from --jwt-secret / MPC_JWT_SECRET (neither is set on the StatefulSet),
while bridge-mpc-token/MPC_API_TOKEN carries only user_id/org_id/role/iat/exp.
Co-authored-by: Hanzo Dev <[email protected]>
luxd serves exactly one HTTP prefix — /v1 (node server/http/server.go
baseURL). /ext/ was removed some releases ago, so every Lux and Zoo URL
in this repo was pointing at a 404.
Measured 2026-07-27:
api.lux.network/v1/bc/C/rpc 200 eth_chainId 0x17871 (96369)
api.lux.network/ext/bc/C/rpc 404
api.lux-test.network/v1/bc/C/rpc 200 eth_chainId 0x17870 (96368)
api.zoo.network/v1/bc/C/rpc 200 eth_chainId 0x30e08 (200200)
api.zoo-test.network/v1/bc/C/rpc 200 eth_chainId 0x30e09 (200201)
control api.lux.network/v1/bc/ZZZNOPE/rpc 404
ZOO_MAINNET/ZOO_TESTNET were doubly wrong: legacy prefix AND a "Z" chain
alias that 404s on both prefixes. Zoo's EVM answers under bc/C.
Avalanche endpoints deliberately keep /ext/ — that is their API, not
ours (measured: api.avax.network/ext/bc/C/rpc 200 0xa86a, /v1 404).
B-chain/T-chain URLs move to /v1 as well; BridgeVM is still undeployed
(re-verified today: /v1/bc/B/rpc 404 while /v1/bc/C/rpc 200, so the 404
is "no such chain", not "no such prefix").
Squash of the local main lineage onto the canonical origin/main. Net tree only
(62 files): stale docs pages removed, tenant config + tests updated, minor Go
fixes, refreshed opnet-watcher build. No third-party white-label branding in
code or history.
Co-authored-by: Hanzo Dev <[email protected]>
Lands a side-by-side draft of the new single-binary Go explorer at
`ghcr.io/luxfi/explorer` — the canonical replacement for the legacy
Next.js `app/explorer/` (`@luxbridge/explorer`).
Includes:
- ConfigMap `luxfi-explorer-chains` — initial chain registry (lux +
ethereum), hot-reloadable via the binary's /v1/explorer/admin/chains
API once running.
- Deployment — replicas=1, strategy: Recreate (single PVC writer per
the SQLite+ZapDB embedded index docs), readiness + liveness probes
against /healthz, resources sized small (100m/256Mi req → 2c/2Gi limit).
- PVC 20Gi for the embedded chain index.
- Service ClusterIP :80 → :3000.
- Ingress on `bridge-explorer-v2.lux.network` for the soak window —
legacy keeps `bridge-explorer.lux.network`. Cut over after 1-week QA.
Marked DRAFT in the file header. New bridge dev confirms the image tag
(pin off `:latest` before the legacy delete step) and the RPC config
before `kubectl apply`.
Status update on #387 will go in the issue once this lands on main.
The runtime shim (window.__ENV / /__ENV.js) reads BRIDGE_* names from
the container env at boot. The previous VITE_BRIDGE_* prefix only made
sense when the build was Vite-time inlining the values, which is no
longer the case (and was the bug behind manifest+runtime drift). One
canonical name across compose+k8s+entrypoint.
bridge-ui ships as a prebuilt Vite SPA inside ghcr.io/hanzoai/spa
(see app/bridge/Dockerfile final stage), not a Next.js dev container.
The compose + k8s manifests were still describing the old Next shape:
containerPort 3000, `npm run dev`, /app/.next volume mount, and
NEXT_PUBLIC_* env vars. None of that exists in the image anymore.
- compose.testnet.yml / compose.mainnet.yml: port 3001:80 (was 3001:3000);
drop command + .next mount; swap NEXT_PUBLIC_* for VITE_BRIDGE_* env
(the names app/bridge/src/bridge.config.ts reads).
- k8s/mpc-deployment.yaml: containerPort 80 (was 3000); Service
targetPort 80 (was 3000 — broken since the static-SPA cutover); swap
NEXT_PUBLIC_* for VITE_BRIDGE_* on the Deployment env.
- compose.yml: keep local hot-reload UX by mounting the repo root and
running `pnpm -C app/bridge dev --host 0.0.0.0 --port 3001` against
node:22-alpine. Dev port moves 3000 -> 3001 to match the published
SPA convention across envs.
- scripts/start-local.sh: update printed UI URL to :3001.
compose.infra.yml and compose.local.yml don't define bridge-ui — left
untouched. scripts/launch-mpc-bridge.sh already prints :3001 and has no
Next-specific config; check-ci-status.sh only references bridge-ui as a
GHCR package name (image preserved per spec). Both verified clean.
Validated:
- docker compose -f compose.{yml,testnet,mainnet,infra,local}.yml config -q
- kubectl --dry-run=client apply -f k8s/mpc-deployment.yaml
bridge-server uses MPC_URL=http://mpc-api-svc.lux-bridge.svc:8081
mpcd API listens on :8080, so targetPort must be 8080.
Also correct headless mpc-node service ports (zap:9651, api:8080).
RainbowKit with ssr:true crashes server render (WalletConnect tries
localStorage on Node.js). K8s deployment had wrong env var name
(NEXT_PUBLIC_API_URL) instead of NEXT_PUBLIC_BRIDGE_API, causing
getBridgeSettings() to fail and UI to show "Bridge is loading..."