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.
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").
Operator-facing configuration assets from origin/whispers/bridgev2.
No code changes — purely declarative configs and ops docs.
cmd/bridge/networks.mainnet.yaml (NEW, 270 LOC)
cmd/bridge/networks.testnet.yaml (refresh, +186 LOC)
Per-network deposit + release wallet configuration for the
cmd/bridge daemon. mainnet config is the first production-ready
file in tree.
compose.mainnet.yml (NEW, +78 LOC)
compose.testnet.yml (refresh, +56 LOC)
Compose stacks for the standalone mainnet/testnet deployments
— Postgres + bridge-server + bridge-ui, MPC/KMS/IAM env blocks
set per environment.
docs/operator-deploy-phase-1-4.md (NEW, 168 LOC)
Phase 1.4 deploy runbook: what cmd/bridge expects from
BridgeVM, mpcd, KMS, and IAM at boot, and how to wire them in
a real prod cluster. Pairs with REQUIREMENTS.md §13 to bridge
spec → deploy.
Build + cmd/bridge tests (5.0s) clean.
NOT included in this PR (saved for follow-ups):
- k8s/bridge-deployment.yaml — 199 LOC delta; review separately
so deployment edits aren't bundled with new config files
- REQUIREMENTS.md R6 spec refresh — track in its own doc PR
- app/bridge/src/bridge.config.ts Lux mainnet/testnet wagmi
additions — small but a tenant-frontend change, separate slice
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