The two mains shared no history, so this is a per-file three-way merge:
for each file the 65 forge-only commits touched (by patch-id), the base is
the newest version both histories held. 105 files merged or took one side
cleanly, 48 were already identical, and the 45 true conflicts and 31
modify/delete pairs were resolved by what runs on the cluster:
- The forge wins where it is live: explorer 1.5.56 (digest 45db11ff) and
its archive readers, explore-fe-hanzo's ConfigMap env (AI coin,
/v1/indexer/hanzo, /v1/chain/hanzo/rpc), exchange-api reading
luxd-archive, and explore-fe-{lux,zoo}-testnet, pinned to what they run
(v2.8.5-lux / v2.8.4-lux).
- GitHub wins where it is live: explore-fe-lux v2.8.5-lux, explore-fe-zoo
v2.8.4-lux, bankd v1.0.18, lux-build's routes.
- The forge's retirement of workloads that run nowhere stands: lux-mainnet
dex, lux-hb2, lux-market, traffic, wallet-web; the lux-testnet frontends,
indexer, exchange and gateways; lux-operator. deploy/lux-testnet/luxd.yaml
stays, since roll_luxd.sh reads it.
- GitHub's deletions stand: lux-finance and lux-financial (static sites
now), the k8s/ operator manifests, .hanzo/workflows/cicd.yml.
- Paths are /v1/chain/C/rpc where the forge had written /v1/chain/c. All
four spellings answer; this one is the documented form. The forge's
/{network}/v1 doc lines are dropped: a network is a host, and the prefix
404s.
Dry-run against the running objects (server-side, as CD applies), the
merged explorer shows no change where GitHub's showed 152 lines, and no
other live workload moves further from what runs. Every deploy/*/*.yaml
renders with charts/app; price.yaml, which failed on GitHub, now renders.
Claude-Session: https://claude.ai/code/session_01TzirYkGmD88w4GipqivUtV
explorer 1.5.56 embeds indexer v1.5.12: a null block from a lagging hanzod
seat is retried rather than stored as an empty block, and a start-up audit
indexed the 525 Hanzo heights earlier runs had skipped.
Chain 36963's native coin is AI, never HANZO: the Hanzo explorer page and the
compose explorers for Hanzo mainnet, testnet and devnet now say so.
Co-authored-by: Hanzo Dev <[email protected]>
The Hanzo explorer was re-applied twice on 2026-09-24 from outside this repo
(v1.1.30, then v1.1.32), after this file had pinned v2.8.5-lux. explore.hanzo.ai
and explore.hanzo.network are Hanzo's names and explore-hanzo.lux.network now
redirects to explore.hanzo.ai, so this pin follows the running image rather than
the Lux release.
Co-authored-by: Hanzo Dev <[email protected]>
k8s/dns is re-exported from the live zones: bridge.lux.network, the ACME TXT
left for bridge, bridge-api, mpc and api.mpc, lux.chat's Azure asuid records and
the GoDaddy _domainconnect CNAMEs on lux.review and lux.training are gone, and
lux.build lists the AWS ingress it has pointed at since 2026-09-19.
kms.lux.cloud has no AWS backend: Lux's own KMS (lux-kms-go) runs on no cluster
here. kms-gen-validator-keys.sh and kms-seed.sh no longer default to it; they
require KMS_API_URL, because a default that does not resolve turned every key
push into a "SKIP" line. NETWORKS.yaml stops listing mpc-api.lux.network,
mpc.lux.network and kms.lux.cloud for the MPC topology.
explore-fe-lux and explore-fe-zoo run one replica each; with maxUnavailable 1 a
rollout stopped the only pod before its replacement was Ready, and the explorer
answered 503 until it was. maxUnavailable is 0 now, as applied live.
Co-authored-by: Hanzo Dev <[email protected]>
hanzod runs Hanzo's own committee and no P-Chain, so every platform.* read
v1.1.30 sent answered "no such chain: P". v1.1.32 (luxfi/explore 546ed00dc)
sends none there and drops the validators and chains pages and nav entries.
Applied to lux-mainnet; the live Deployment matches this file.
Claude-Session: https://claude.ai/code/session_011Ge9UnfkEK37Lapx5nfPQK
deploy/lux-{mainnet,testnet}/luxd-archive.yaml are charts/app values for
the two luxd archives that serve api.lux.network, api.lux.cloud and
api.lux-test.network. They replace the hand-applied k8s/ manifests and
deploy/lux-api/route.yaml, which declared the same objects.
Rendered and dry-run against the running objects (server-side, the way
CD applies), a sync adds the chart's labels, its config checksum and
HANZO_VERSION, and nothing else: same container, genesis, chain config,
volumes and routes. So cd.automated is off until someone syncs one.
Co-authored-by: Hanzo Dev <[email protected]>
bridge.lux.network points at the AWS ingress again, where it redirects to the
bridge in the Lux wallet; forty-nine Lux pages link to it. ama.lux.network
forwarded to a Calendly URL that Calendly reports as not valid, and no page
links to it; the record and its page rule are deleted.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
v2.8.5-lux lists only chains that have an explorer: the SPC and Pars cards
linked names that do not resolve, and every page load asked api.pars.network
for validators and got 502. The Hanzo explorer was still on v1.1.30; it runs
the same image as Lux and Zoo now, from ghcr.io/hanzoai. The Lux testnet
explorer (explore-fe-lux-testnet, not declared here) runs the same image.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
On the Zoo explorer the validators page counts only Zoo, /dex answers 404, and
the footer links point at the zooai org and Zoo's own accounts. Applied on AWS
with the same image digest and env.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
An RPC that answers eth_chainId says nothing about whether the chain still makes
blocks, and the status page could not tell a halted chain from a live one.
chain-tip answers /v1/tip/<chain> with height, timestamp and age_seconds for
lux-mainnet and lux-testnet, read from their luxd-archive Services. The chains
are fixed in its env; a request names a chain, never a URL. ClusterIP only.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
The Service and route were live with no Deployment behind them, so every
/v1/trade path on api.lux.network and api.lux.cloud answered 503. lux-chain,
the in-cluster name LUX_RPC used, no longer exists; luxd-archive is the Service
the public /v1/chain route serves. The pods run on the apps node, beside the
other lux-mainnet frontends.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
explore, api-explore and cdn.lux.cloud were answered only through *.lux.cloud,
which also answered admin, kms and s3.lux.cloud with a 404 after their own
records were deleted. The three served hosts now have records of their own.
Co-authored-by: Hanzo Dev <[email protected]>
lux-ci is a service client (client_credentials only) in org lux. A KMS
login with it yields an org-lux token, so bin/kms reads /orgs/lux
HANZO_DEPLOY_TOKEN and a site publishes under lux/<slug>. It is set as a
repo-level secret where a repo must stop inheriting the lux forge org's
KMS_CLIENT_ID/SECRET, which are a Hanzo identity.
lux-cloud's live registration also holds https://lux.id/callback, which
its hosts do not derive. The upsert replaces the URI list, so it is
declared as a redirect exception rather than deleted by this converge.
Co-authored-by: Hanzo Dev <[email protected]>
Every web record names the AWS ingress, and names with nothing behind them
are gone: dead Cloudflare tunnels (dchain, 01-05.luxno.de), disabled Vercel
and Netlify deployments, the Stripe checkout, GoDaddy parking, unactivated
Google custom URLs, the devnet hosts (no Lux devnet runs), bridge, mpc,
liquid, the SPC and Pars explorers, kms/s3/admin.lux.cloud and
v1.lux.exchange. status.lux.financial is new.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
api.lux-test.network/ returned 404 while api.lux.network/ is the C-chain
endpoint; the testnet route now carries the same root rule.
explore-fe-spc and explore-fe-pars (lux-mainnet) are gone: neither chain has
a node for the explorer to index. lux-fleet keeps running with no public
host: api.cloud.lux.network was two labels under lux.network, past the edge
certificate, so no browser could complete a handshake.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
hanzoai/universe charts/app/values/hanzo/hanzo-domains.yaml routes lux.tel,
www.lux.tel and console.lux.tel to cloud, which serves project tel. Two
more claims lived here: deploy/lux-tel (a static route off /var/www/tel,
priority 100) and k8s/lux-k8s/tel (Deployment + Ingress on
ghcr.io/luxfi/tel:0.1.1, referenced by no kustomization). Both go, so one
declaration claims those hosts.
lux-fleet deletes the Application with preserveResourcesOnDeletion and
no delete verb on the cluster, so removing the file removes nothing live.
Co-authored-by: Hanzo Dev <[email protected]>
The door existed to keep mainnet's admin API off the public path while its
import needed it on and a restart was not yet safe. Both are over: the
import finished at 1,082,780 and 1.37.9 restarts cleanly, so mainnet now runs
with the admin API off like the others. luxd itself refuses admin_, debug_,
personal_ and txpool_ (none are enabled) and answers CORS, which is
everything the door did.
Routes name luxd-archive:9630. Mainnet restarted into this shape at the tip
and was answering at 1,082,780 with history, state and every sampled
transaction intact. The door had capped an in-cluster indexer at 2.5 blocks/s
against 167 direct.
Co-authored-by: Hanzo Dev <[email protected]>
The lockfile pins Playwright 1.58.2; the runner image carries 1.58.0's
chromium in /opt/playwright, read-only to the job, so installing the pinned
build failed with EACCES there. PLAYWRIGHT_BROWSERS_PATH=0 puts it in
node_modules, which the job owns, on any runner.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
The fleet mints a runner only for a job that names self-hosted or a
linux-<arch> label. A job asking for lux-build-amd64 alone was never given
one, and it still matched runners minted for other jobs that named
[self-hosted, lux-build-amd64], so it took theirs and left them queued.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
setup-node v4 reads `cache` as a package-manager name, so `cache: false`
asked it to cache for a manager called "false" and it failed the step
with "Caching for 'false' is not supported". Leaving the input unset is how
v4 caches nothing, which is what the setting meant.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
GitHub fails any job that references actions/upload-artifact v3 at
"Set up job", before a step runs, so every E2E leg failed without
testing anything. v4 takes the same name, path and retention-days.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
get-helm-3 installs into HELM_INSTALL_DIR and then runs `helm` from PATH to
check the install, so with the directory not yet on PATH it reported
"helm not found" and failed a good install.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
bankd v1.0.18 takes only a lux.id access token issued to lux-financial for
the lux org, maps only a member of the admin org to _superusers, and listens
on HTTP alone. Its pod reads none of the bank-secrets keys, so the five env
references to them are gone.
bank-docs states the amd64 node selector its pod already runs with.
e2e/40-lux-financial proves the one way in: neither console serves a password
input or ships a credential in its JS; every sign-in screen hands off to lux.id
with client lux-financial, PKCE S256 and the console's /auth/callback; bankd
answers 404 on every password and Base sign-in route, the code exchange takes
only lux-financial by code or refresh, and a token lux.id did not issue to
lux-financial reads as anonymous. The password-form sign-in and the seeded
ledger totals it asserted are deleted: neither exists.
k8s/lux-k8s/{bank-api,bank-dash,bank-docs,bank-sandbox,bankd,financial} and
scripts/seed-bank-admin.ts are deleted. deploy/lux-bank declares the bank,
lux.financial is a static site, and no host those files name resolves.
Co-authored-by: Hanzo Dev <[email protected]>
The render job piped get-helm-3 into bash, which reaches /usr/local/bin through
sudo, and then imported yaml from a system python that has none; on the lux
fleet the runner is unprivileged and sudo cannot work, so the job failed at its
first step. helm now lands in RUNNER_TEMP and PyYAML in a venv there.
E2E jobs asked for lux-build-amd64 alone. The fleet mints a runner only for a
job naming self-hosted or a linux-<arch> label, so those jobs were never
picked up. They name [self-hosted, lux-build-amd64], as render does, and
install chromium without --with-deps: the runner image carries its libraries.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
1.37.8 wrote the acceptor tip for a whole import round before committing that
round's tries one block at a time, so a restart partway through left a tip
above the last accepted block, naming blocks with no state, and the C-Chain
could not initialize ("required historical state unavailable (reexec=2)").
1.37.9 (luxfi/evm v1.104.56) writes tip and trie together per block, and moves
a stale tip back to the accepted block on boot.
Measured on live lux-mainnet: stopped mid-import on 1.37.8 at 217,500 with the
tip at 220,000; on 1.37.9 the C-Chain came up at 217,500 immediately and the
luxd-import Job resumed with no wipe. The pod may now be restarted at any point.
Co-authored-by: Hanzo Dev <[email protected]>
luxfi/evm#152's cause was the import skipping the per-block accept step (tx
index and trie commit), so every 1.37.7-imported chain answered about one
transaction in two hundred by hash and could not survive a restart. On
1.37.8 lux-testnet answers all 219 by hash, with receipts.
Mainnet is re-imported from an empty database under 1.37.8, driven by the
luxd-import Job, which is a one-off and not in the kustomization. The pod
must not be deleted while it runs: 1.37.8 still cannot initialize a C-Chain
restarted DURING an import. After a completed import, restarts are safe.
The mainnet node asks for 1000m. The chains node is shared, and under
contention CPU divides by request; at 300m the import got a tenth of a core.
Co-authored-by: Hanzo Dev <[email protected]>
The customer app and the admin console share one public PKCE client,
lux-financial, registered for app.lux.financial and admin.lux.financial with
tokens that live an hour and refresh for a day. lux.financial itself is static
files with no sign-in, so its redirect goes. lux-bank stays bankd's service
identity.
bankd and bank-dash are pinned to v1.0.16 by digest: no password route, no
password form. bank-dash carries only the env its server reads. admin is a
static site, so the Deployment declarations for a bank-admin pod go, in deploy/
and in the k8s/ reference tree.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
WLUX, both factories, the quoter and the router are the addresses with code on
96369 now that its history is the one imported from luxfi/state.
Co-authored-by: Hanzo Dev <[email protected]>
Both hosts are one static export of luxfi/financial, served from
s3://hanzo-sites/lux/financial by the lux-financial router in the edge's
sites table (hanzoai/universe infra/lan/ingress.yaml).
lux-finance served lux.finance from a September export in the node-local
/var/www store, and that build crashed in every browser ("Cannot read
properties of undefined (reading 'type')"). lux-financial ran the same era
as a Next server pod behind lux.financial. Both are gone from the cluster.
The canonical C-Chains are the RLP archives, 1,082,781 blocks on mainnet and
218 on testnet. Each runs as luxd-archive on the chains node and answers
publicly through the rpc door, which passes only eth_/net_/web3_ methods and
answers admin_, debug_, personal_ and txpool_ itself.
Testnet is frozen: its database is an emptyDir rebuilt from the archive
inside VM initialization on every boot, so its admin API is never on and no
restart depends on state surviving one. Mainnet is too large for that and
is imported once with admin_importChain; its admin API is behind the door,
and the pod is OnDelete and must not be deleted until luxfi/evm#152 is fixed
(after an import, a restart leaves the C-Chain unable to initialize).
Genesis is declared. The luxd-genesis ConfigMaps now hold the documents the
archives chain to: mainnet block 0 0x3f4fa2a0…, testnet 0x1c5fe377…,
each verified by booting it. The testnet document here before was a
different one (gasLimit 0x3b9aca00) that refuses every archived block, and
the mainnet one carried a C-Chain genesis that hashes to 0x43befc4e….
api.lux.network and api.lux.cloud route /v1/chain and / to the door at
priority 300, above the unified api-* router, and no longer point at
validators on the LAN.
The five-validator luxd sets are 0. They start from a genesis the archive
does not chain to and import nothing, so any block they made would fork
it. They come back on the canonical genesis once #152 is fixed.
lux-bank/bankd serves ghcr.io/luxfi/bank:v1.0.15, a single linux/amd64
manifest at sha256:a1197959: v1.0.14 with the sandbox login bounded per
client address and per account, answering 429 with Retry-After past
either budget.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_016dDw5Lp9sZFctwvZLn4nVz
safe-cgw.lux.network is served by two replicas on AWS, declared by the overlay
hanzoai/universe infra/aws/values/hanzo/lux-safe-cgw.yaml over this file, which
also pins v0.1.6 by digest.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_016dDw5Lp9sZFctwvZLn4nVz
Declare the container port, /health probes, requests, and the
docs.lux.network route. Drop the ghcr-luxfi-fresh pull secret, which
no cluster holds.
Co-authored-by: Hanzo Dev <[email protected]>
The backend's values were deleted as a copy while it was paused for the lux
mainnet import; it kept running from a live config with one chain, because on
09-09 only lux answered. Zoo (200200) and hanzo (36963) answer in-cluster now,
so they get rows: zoo's venue contracts and 2T genesis are read on its chain,
hanzo's new chain has no venue yet.
The frontends' wallet RPC pointed at api.zoo.ngo (served by cloud, not the
chain) and /v1/chain/hanzo (a 404); both now name the chain endpoint.
Co-authored-by: Hanzo Dev <[email protected]>
latest now names sha256:d34e9c1c (amd64+arm64); the digest fixes which
bytes run. ghcr-pull exists in no cluster; ghcr-secret is the credential
the other lux apps pull with.
Co-authored-by: Hanzo Dev <[email protected]>
explorer, explore-fe-lux and lux-ns/exchange-api now run on the AWS
cluster. The chain is reached through the `rpc` door in lux-mainnet, the
explorer index sits on the data node's gp3 volume, and explore-env-lux is
declared here instead of hand-applied.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_016dDw5Lp9sZFctwvZLn4nVz
imagePullSecrets named ghcr-pull, a Secret that exists in no cluster --
the pods sat in ImagePullBackOff on the LAN and again on AWS. ghcr-secret
is the one every other lux-* app pulls with.
Co-authored-by: Hanzo Dev <[email protected]>
A 20Gi RWO claim takes local-path on k3s, which puts the volume on one
node and pins the pod there — the image is a multi-arch index and the
claim was the only reason it never ran on the other node.
Co-authored-by: Hanzo Dev <[email protected]>
Every provenance line and manifest still named a DigitalOcean context, and
the ARC values described a pool that died with it. A cluster gets a name
here; the address it answers on belongs to the private record.
Co-authored-by: Hanzo Dev <[email protected]>
Five explore-fe-* deployments and two explorers serve one function. Three
hosts answer 200 -- lux, hanzo and pars -- and are kept. explore.spc.network
does not resolve at all and explorer.lux.network answers 502, so those two
carry a pod each for nobody.
Co-authored-by: Hanzo Dev <[email protected]>
Each of these declares replicas: 0 -- no pod, no endpoint, no caller, and no
record of when it was parked. A declaration that runs nothing is not a
deployment, it is a note nobody can date, and it keeps a slot in every fleet
listing and every sync. Git history holds them if one was paused rather than
retired.
Co-authored-by: Hanzo Dev <[email protected]>
work.hanzo.network is served by hanzo-mainnet/work-board and answers 200. This
deployment carries the same image behind a Service with no Ingress, and
work.lux.network answers 404 — so it has taken a pod and an amd64 node slot
without ever having a caller.
Co-authored-by: Hanzo Dev <[email protected]>
Chart 0.1.14 rejects kms.hostAPI and kms.credentialsSecret, so the
Application could not render at all and sat in Unknown. Where KMS is and
which identity reads it come from the operator's environment.
Co-authored-by: Hanzo Dev <[email protected]>
edge.yaml states the invariant: exactly one ziti edge router, advertising a
publicly resolvable address. zrok grants every identity `#all` edge routers, so
the controller hands out whichever are online and one unreachable router poisons
the pool for every external client. hanzo-zt-router was removed for exactly this
— a ClusterIP edge router — and removed rather than zeroed, because a zeroed
Deployment is one `kubectl scale` from re-breaking every laptop. This one is the
same shape: :3022 behind ClusterIP 10.43.53.162.
Co-authored-by: Hanzo Dev <[email protected]>
edge.yaml states the invariant: exactly one ziti edge router, advertising a
publicly resolvable address. zrok grants every identity `#all` edge routers, so
the controller hands out whichever are online and one unreachable router poisons
the pool for every external client. hanzo-zt-router was removed for exactly this
— a ClusterIP edge router — and removed rather than zeroed, because a zeroed
Deployment is one `kubectl scale` from re-breaking every laptop. This one is the
same shape: :3022 behind ClusterIP 10.43.53.162.
Co-authored-by: Hanzo Dev <[email protected]>
144 of the 185 images this estate runs publish amd64 alone, and evo is the one
amd64 node. Where the image is an index containerd refuses it and the pod sits
in ImagePullBackOff; where it is a bare manifest there is no platform check, so
spark's qemu-x86_64 binfmt runs it emulated until it faults. work-board is at
678 restarts that way and testnet's exchange-api at 617.
lux-testnet serves one route, chain-api, and it answers from luxd-archive. The
five deployments zeroed here have no route and no caller.
status.hanzo.ai, status.lux.network and status.zoo.network are served from
hanzo/ and zoo/. The lux-system copies duplicate them and have been unrouted
and unready since the migration. maintenance-page goes with them: nothing
routes to it and its page was never written.
Co-authored-by: Hanzo Dev <[email protected]>
144 of the 185 images this estate runs publish amd64 alone, and evo is the one
amd64 node. Where the image is an index containerd refuses it and the pod sits
in ImagePullBackOff; where it is a bare manifest there is no platform check, so
spark's qemu-x86_64 binfmt runs it emulated until it faults. work-board is at
678 restarts that way and testnet's exchange-api at 617. trade.yaml already
states the constraint for dex; these state it for the rest.
lux-testnet serves one route, chain-api, and it answers from luxd-archive. The
five deployments zeroed here have no route and no caller.
status.hanzo.ai, status.lux.network and status.zoo.network are served from
hanzo/ and zoo/. The lux-system copies duplicate them and have been unrouted
and unready since the migration. maintenance-page goes with them: nothing
routes to it and its page was never written.
Co-authored-by: Hanzo Dev <[email protected]>
lux-ns holds no KMS identity, so exchange-api ImagePullBackOff'd on
ghcr.io/luxfi/exchange-api with no dockerconfigjson in the namespace.
Project the shared hanzo ghcr-secret (reads luxfi images) as a
dockerconfigjson pull secret, matching how every other namespace
declares one.
Co-authored-by: Hanzo Dev <[email protected]>
One build serves every label and this file is the only thing that
distinguishes them, so without it the pod waited forty-three hours on a
ConfigMap that could not arrive. Content is the wallet repo's own default
brand.
Co-authored-by: Hanzo Dev <[email protected]>
One build serves every label and this file is the only thing that
distinguishes them, so without it the pod waited forty-three hours on a
ConfigMap that could not arrive. Content is the wallet repo's own default
brand.
Claude-Session: https://claude.ai/code/session_018U2e2U4PPc8UaXYyeT9iNu
api.lux-test.network reached the ingress and answered 404 — no route claimed it,
because the testnet's luxd StatefulSet never came up. The chain now lives in an
archive node on evo (218 blocks, full historical state), and this routes the
public contract /v1/chain/c/rpc to it. Admin and debug are off on that node.
Co-authored-by: Hanzo Dev <[email protected]>
Pins were authored by lux-ci <[email protected]>, so every deploy the pipeline
moved stayed off the owner's history. Pinned-by still says whether CI or a
person ran it.
Co-authored-by: Hanzo Dev <[email protected]>
The pin was two versions behind the surface. The binary's paths moved under a
single namespace, so the router named both shapes as a union while the pin sat
on the older one — a route may lead a pin only that far, and only for as long
as the older spellings still answer.
They no longer do. v1.14.56 is published, the deployment is running it, and a
token list answers at /v1/trade: 403 rows on Ethereum, WETH priced at 2460.35
from a v3 pool read against USDC. So the older spellings come off here, in the
commit that retires the version that served them, and the router now describes
the same service the pin does.
strip-role goes with them. It blanked a claim in a request header because a
handler behind it once read one; nothing reads it now, and a middleware kept
for a handler that does not exist is a rule whose reason a later reader cannot
check.
The digest moves with the tag. A digest naming the old image under a new tag
makes the tag decorative and the pin a guess.
Co-authored-by: Hanzo Dev <[email protected]>
A prefix in the router that no handler stands behind is a public address whose
reply is whatever binary happens to be at the other end, and the list was
written against a different version than the one pinned here. Nothing in this
tree reads /v1/history and the current source serves no such path, so
publishing it can only reach an older handler.
The remaining legacy prefixes come off in the commit that moves the pin, which
is when the router and the binary describe the same service again.
Co-authored-by: Hanzo Dev <[email protected]>
The image is amd64-only, so it must run on evo, and evo carries
hanzo.ai/no-ci=true:NoSchedule to keep CI off the production node. The chart
defaults the matching toleration from 0.1.14, but the render CD produced for
this app did not carry it -- the amd64 pod sat Pending on "untolerated
taint(s)" while the two emulated ones on spark kept dying. The workload states
the toleration itself now, so scheduling does not depend on which chart
artifact was cut.
Co-authored-by: Hanzo Dev <[email protected]>
ghcr.io/luxfi/exchange-api is published for linux/amd64 alone. Unpinned, both
replicas landed on the arm64 node, where the kubelet ran them under the
qemu-x86_64 binfmt handler and each died before it could listen -- QEMU
internal SIGSEGV -- so the Service had no ready endpoint and exchange.lux.cloud
answered 503 the moment it was routed. Pinned to amd64; emulation is not a
scheduling strategy.
Co-authored-by: Hanzo Dev <[email protected]>
v1.lux.exchange reads every market, chart, quote and swap through
exchange.lux.cloud, and the only object that ever named that host was a
hand-applied Ingress under k8s/lux-ns that nothing reconciles. Live, nothing
routed it: the host answered 404 and every chart on the exchange was empty.
One rule to exchange-api:4000. The old Ingress listed six prefixes and sent
them all to the same backend, so the bare host says the same thing. exchange-gw
is not in the path -- the edge is the ingress, the service is this one.
Co-authored-by: Hanzo Dev <[email protected]>
v1.lux.exchange is built against api.lux.network/v1/bc/C/rpc, the path luxd
served before v1.36.179. The lux-api rules matched /v1/chain and / only, so the
exchange's balance and chain calls have answered 404 since the endpoint moved
to the sync node. The sync node is on .148 and serves /v1/bc natively, so the
rule passes it through untouched. Moving the node to .181 either adds the
reverse rewrite here or rebuilds v1 against /v1/chain; the second is the one
path the fleet keeps.
Co-authored-by: Hanzo Dev <[email protected]>
cchain read lux-chain, the Service whose EndpointSlice named the five docker
validators. Those were a fork of mainnet and are stopped, so every poll was
`connection refused` and the explorer showed the fork's 59 blocks frozen. It
reads luxd-rpc now -- the sync node holding the real chain, mid-import at
437,500 and climbing -- at the /v1/bc path its v1.36.148 build serves.
Co-authored-by: Hanzo Dev <[email protected]>
Both lux-api rules now send to luxd-rpc, the Service that selects the sync
node, and not to lux-chain, whose hand-made EndpointSlice named the five docker
validators on evo. Those five were a fork of mainnet -- same genesis, a
different block 1, real transactions on both sides -- and they are stopped.
Serving a partially imported real chain beats serving a complete wrong one.
bc-path rewrites /v1/chain/c/... to /v1/bc/C/... because the sync node is on
luxd v1.36.148, which serves the EVM at the old path, while every caller of
api.lux.network speaks the /v1/chain path that v1.36.181 serves. It is the one
thing to delete when the node moves to .181.
Recorded here and applied live by hand: lux-mainnet-luxd has never been synced,
and syncing it now would stand a second luxd beside the hand-applied sync
StatefulSet. When that app is adopted, it carries this route.
Co-authored-by: Hanzo Dev <[email protected]>
Both lux-api rules now send to luxd-rpc, the Service that selects the sync
node, and not to lux-chain, whose hand-made EndpointSlice named the five docker
validators on evo. Those five were a fork of mainnet -- same genesis, a
different block 1, real transactions on both sides -- and they are stopped.
Serving a partially imported real chain beats serving a complete wrong one.
bc-path rewrites /v1/chain/c/... to /v1/bc/C/... because the sync node is on
luxd v1.36.148, which serves the EVM at the old path, while every caller of
api.lux.network speaks the /v1/chain path that v1.36.181 serves. It is the one
thing to delete when the node moves to .181.
Recorded here and applied live by hand: lux-mainnet-luxd has never been synced,
and syncing it now would stand a second luxd beside the hand-applied sync
StatefulSet. When that app is adopted, it carries this route.
Co-authored-by: Hanzo Dev <[email protected]>
lux-mainnet alone named a `traffic-key` secret that exists in no namespace, so
its sender could never start -- CreateContainerConfigError, and a chain with no
sender stays flat. lux-testnet, lux-devnet, lux-hb2 and hanzo-mainnet all read
lux-deployer/LUX_MNEMONIC already; mainnet now does too.
Index 0 of that mnemonic is funded on all three chains, and TRAFFIC_WALLETS
derives the rest so value circulates instead of piling up in one account.
Co-authored-by: Hanzo Dev <[email protected]>
The volume named traffic-chains-gc7kb89hm9, a hash-suffixed ConfigMap no
generator in this repo produces, so it never existed and the pod has sat in
ContainerCreating -- MountVolume.SetUp failed, configmap not found -- for as
long as the Deployment has. A chain with no sender does not build blocks, which
is why mainnet has been flat.
The list names lux-chain, the address that answers eth_chainId 0x17871 and a
rising height. luxd-rpc fronts the StatefulSet whose bootstrap peers no longer
accept a connection, so every path under it is 404.
Co-authored-by: Hanzo Dev <[email protected]>
Asked to register both with CreateChainTx on Lux mainnet. Neither was created,
and the record now says why rather than leaving the next reader to rediscover it.
Each brand already holds a chain identity elsewhere: hanzod serves 36963 on its
own primary network, bootstrapped, and Zoo's C-chain carries 13,348 blocks on
network 200200 since the 2026-07-14 cutover. A CreateChainTx here would mint a
second identity for the same brand and orphan that history. It also could not
have run: platformvm now serves one handler at /ops and nothing at the chain
base, so the JSON-RPC client both tools open with returns 404 before it reads
anything, and every genesis allocation that could pay the fee is either staked
or behind a mnemonic that is not on this machine.
Records the eleven blockchain ids the primary network does carry, read from the
P-chain rather than from a manifest. T is gone; F and M are there in its place,
which the 2026-06-02 inventory predates. Marks that inventory and the brand-L2
follow-on it promised as superseded, since both still read as the plan.
Co-authored-by: Hanzo Dev <[email protected]>
/v1/trade carries venues, quote, quotes, swap and the four allowance paths, and
api/openapi.yaml in luxfi/dex describes exactly those — a test there holds the
document to the route table in both directions.
The other half is not missing, it moved. Enumerating a chain — tokens, pools,
positions, TVL, price history — is an index read, and api-explore.lux.cloud
answers it: 16 pools and $39.9M of TVL for 96369 with real symbols and
decimals, measured. Those paths were on the gateway too and answered 500 on
every request, because a quoting surface reads one pool by address and cannot
walk every pool on Ethereum. A screen wanting a market list asks the indexer; a
screen wanting a price for a size asks the gateway.
The swap is built by the venue that gave the price, against the router it read,
and the record of that is the eth_call replay on 96369's live SwapRouter02
returning the quoted amount exactly. Permit2 is not deployed on 96369, so both
permit2 paths answer 404 there naming the reason rather than calldata addressed
to an empty account.
Also records why the route names both path shapes today and why strip-role
stays: both are properties of the image the pin still names, and both come out
in the commit that moves it.
Co-authored-by: Hanzo Dev <[email protected]>
evo carries hanzo.ai/no-ci=true:NoSchedule and evo is the only amd64 node this
cluster has. The image is a single amd64 manifest, so nodeSelector arch=amd64
and this toleration are one statement: without the second, the workload has
nowhere at all to run.
NoSchedule does not evict, which is what made that invisible. Two replicas kept
answering while a surge pod sat Pending for three minutes — 0/2 nodes available,
one wrong architecture and one tainted — and the surface would have stayed up
until the first restart, reboot or rollout and then had nowhere to come back to.
Forty-five pods already carry this toleration, including the CD control plane
and the hanzod validators.
Verified by rolling both replicas out and back: quotes answer on 1, 8453 and
96369, /v1/chain/C/rpc is 0x17871, preflight 204.
The fix that removes the need is upstream: the published image is one amd64
manifest rather than an index, so nothing about this workload could schedule on
spark even if the taint went away.
Co-authored-by: Hanzo Dev <[email protected]>
/v1/trade is where this surface is going: one prefix, the whole thing under it,
and the nine-clause allowlist retired along with the /v1/admin handlers it was
written to keep off the internet. That end state is right and the file now says
so.
It cannot be the whole rule yet. The pin is v1.14.40 and v1.14.40 answers the
older spellings; every v1.14.4x tag cut for the new shape has had its image
build cancelled, so there is nothing published to move the pin to. For fifteen
minutes this route named /v1/trade alone and lux.exchange got a hanzoai/cloud
404 for every quote it asked — a surface reachable, running, and addressed by a
name it does not answer to.
So both are named, which is a union and therefore safe in both directions, and
the nine come out in the same commit that moves the pin past the version serving
them. strip-role stays for the same reason: v1.14.40 still authorises
/v1/admin/* on a header the caller writes, and no rule here should be the only
thing standing between that and the internet.
Measured after: 1 WETH -> 2514.355261 USDC on Ethereum, 2514.426028 on Base,
1 WLUX -> 0.997001483075056760 LUSD on 96369, /v1/chain/C/rpc still 0x17871.
Co-authored-by: Hanzo Dev <[email protected]>
The RPC paragraph had it exactly backwards — it said /v1/bc/C/rpc returned 200
and /v1/chain/C/rpc returned 404, which stopped being true when
deploy/lux-api/route.yaml landed. Measured both, and the bare host, and wrote
the readings down.
Adds the trading surface on the same two hosts: where its declaration lives, the
three priorities that share those hosts, and where the router that answers
everything else actually comes from — the ingress file provider, which is
enabled and whose routers are easy to look for in the wrong repository.
Co-authored-by: Hanzo Dev <[email protected]>
zoo, hanzo, spc and pars each declared their own chain id and ticker while
naming api.lux.network/v1/chain/c/rpc as their RPC, so every one of them served
Lux C-Chain blocks relabelled -- the same block 59 and the same miner
0xf66b0259... appearing as ZOO, PARS and SPC. Presenting another chain's
transactions as your own is worse than presenting none.
Each now names its own chain. None of the four answers yet, because those chains
are not running, so the pages show nothing -- which is true -- and they start
working the day their chain does, with no further edit here.
Co-authored-by: Hanzo Dev <[email protected]>
`ghcr.io/luxfi/dex` carries a second binary, `gateway`, and nothing ran it, so
every quote a Lux interface asked for reached nothing. It runs here as `trade`
— not `gateway`, because this namespace already has one in front of the
validators' RPC.
Two routers on one host and the chain's is the more specific: /v1/chain keeps
priority 300 and this takes 200, above the bare-host router in the ingress file
provider that has been answering these paths out of hanzoai/cloud.
The paths are an allowlist. `PathPrefix(/v1)` would be shorter and would publish
/v1/admin/pool/freeze, which the binary authorises on a request header the
caller writes; those handlers stay reachable from inside the cluster and from
nowhere else, and the header is stripped on the way in as well.
96369 is read through the V3 quoter that landed with the AMM today, and read
over the cluster Service rather than back through Cloudflare — same endpoint,
15ms instead of 149ms, and no dependency on the edge that is serving the answer.
Co-authored-by: Hanzo Dev <[email protected]>
The AMM landed on 96369 on 2026-09-09 (luxfi/standard script/venue.ts,
recorded at deployments/venue/96369.json), so the four fields this file
left unset because nothing was deployed here have addresses to carry:
the two factories whose PairCreated and PoolCreated the subgraph trusts,
the quoter that prices a concentrated route, and the SwapRouter02 a swap
is addressed to.
Changing either factory changes the store's source fingerprint, so the
indexer drops its derived rows and re-reads from block 0 rather than
looking for events already behind its cursor. Applied and restarted: it
read blocks 1..59, 255 events, and values 16 pools at $39,899,893.58 with
LUX at one dollar.
Co-authored-by: Hanzo Dev <[email protected]>
Every card on all five explorers was a skeleton that never resolved. The UI
reads a Blockscout-shaped API, and this backend mounts one per chain at
/v1/indexer/<slug>; with NEXT_PUBLIC_API_BASE_PATH "/" the page asked for
/stats, /main-page/blocks and /config/backend-version, which 404, while the
same paths under the prefix answer 200.
All five also named the same slug's host as their own APP_HOST, so each brand
described itself as explore.lux.network. Each now names its own chain and its
own host: zoo reads zoo, hanzo reads hanzo, pars reads pars, spc reads spc.
Co-authored-by: Hanzo Dev <[email protected]>
One toolchain across the estate. The tree carried 1.26.0, .1, .3, .4 and .5
at once, so which compiler built a binary depended on which repo it came
from.
Co-authored-by: Hanzo Dev <[email protected]>
Each explore-fe Service publishes ui:80 -> 3000, so a router that asks for 3000
is answered with `service port not found: 3000` and the host serves 404. The
five routers now name 80.
Co-authored-by: Hanzo AI <[email protected]>
The four headline cards on explore.lux.network were zeros because the indexer
was pointed at luxd-rpc, and luxd-rpc fronts a StatefulSet whose five bootstrap
addresses are DigitalOcean droplets that no longer accept a connection. That
node never names a frontier, never creates the C-chain, and answers 404 for
every path under /v1/chain/c/rpc; the indexer read nothing from it and reported
`indexed: true` over an empty database. lux-chain answers eth_chainId 0x17871
and a height, so it is what cchain reads now. public_rpc gains the /rpc the
browser needs -- the path it named 404s, so every RPC-backed control on the
page had nothing to dial.
The five explore-fe deployments had no way in. Each host resolves to Cloudflare
and Cloudflare handed the request to an ingress with no router that matched,
which is the 404 they have all been serving. Each brand now names its own hosts,
carried over from the by-hand ingress-chains.yaml minus explore.spc.network,
which does not resolve.
The explorer pod's `explore` sidecar was explore-fe-lux a second time -- same
image, same digest, same brand -- published on a Service port no router named.
The indexer keeps the job only it does.
Co-authored-by: Hanzo AI <[email protected]>
It pulled ghcr.io/parsdao/explore, an image whose repository does not
resolve, and sat in ImagePullBackOff while the other four chains ran the
same luxfi/explore image branded by their own configmap. A second image for
a frontend that is already configured per chain is a second thing to keep
current, and this one was never current.
Co-authored-by: Hanzo AI <[email protected]>
evo carries hanzo.ai/no-ci=true:NoSchedule and is the cluster's only
amd64 node, so an amd64 workload that does not tolerate it has nowhere
to go:
0/2 nodes are available: 1 node(s) didn't match Pod's node
affinity/selector, 1 node(s) had untolerated taint(s)
The 175 pods already on evo predate the taint -- NoSchedule does not
evict -- so none of them tolerates it either, and nothing new can land
beside them. The taint is hand-applied and declared in no repo.
It exists to keep CI runners off the box, which is a fair thing to want.
This is not CI; it is the chain's explorer, and amd64-only. It says so
here rather than being held out by a rule aimed at something else.
Co-authored-by: Hanzo AI <[email protected]>
Each of these mounts a configmap named explore-env-<chain> that was declared
nowhere: the deployments referenced it, nothing created it, and every
frontend sat in CreateContainerConfigError. The same shape as the chain
nodes, whose genesis and startup config never migrated either.
The values are the RUNTIME set only. luxfi/explore's .env.example.lux mixes
build-time and runtime variables in one file — NEXT_PUBLIC_BRAND is read by
`pnpm build`, and the branding, marketplace and homepage URLs were dropped
from the image — so feeding that file to a container fails the validator on
congruity, then on deprecation, then on unknown names.
Co-authored-by: Hanzo AI <[email protected]>
/v1/bank/config answers demoLogin: true with demoEmail: [email protected]
to anyone who asks, and the deployed build falls back to a hardcoded
password when BANK_DEMO_PASSWORD is unset. That fallback is the estate's
documented org convention, so the advertised email and the password were
a pair anyone could assemble -- from the moment api.lux.financial was
routed, which was an hour ago.
The value is a Secret in the namespace, not a line in this file: the
reference belongs in git and the credential does not. It should move to
KMS with the rest.
This keeps the demo working. Turning BANK_SANDBOX off would close the
same door by removing the thing the host exists to show.
Co-authored-by: Hanzo AI <[email protected]>
The console at admin.lux.financial builds absolute URLs against this
host -- const zm="https://api.lux.financial" in its bundle, with no
same-origin path to proxy -- so it rendered and could not sign in while
nothing named the host.
app.lux.financial deliberately did not get this: dashd proxies /v1 to
this Service inside the cluster, so the dash works without the bank's
API being published. That is why it was routed first and alone, and why
this is a separate decision recorded separately.
What this publishes was read off the running pod, not assumed:
/v1/bank/health and /v1/bank/config answer 200, /v1/bank/overview 401,
and /api/collections -- the Base admin surface underneath bankd -- 404.
The framework's own console is not reachable through this route.
Co-authored-by: Hanzo AI <[email protected]>
Both hosts reach this ingress and, with no router naming them, got Go's
`404 page not found` from an empty route table — which is what every
lux.exchange screen that reads an indexer has been drawing.
The indexer is `explorer`, and there is only one. luxfi/indexer builds a single
binary whose own package doc says it "serves only the /v1/indexer/* HTTP API. No
graph engine", so it cannot answer /v1/graph/cchain/amm/graphql at any version
— the only thing the exchange asks an indexer for. luxfi/explorer composes that
same daemon package with the graph engine and mounts both dispatchers. So
lux-indexer.yaml goes: it declared a second deployment for the half this one
already serves, at replicas 0, against a Service (luxd-headless) that does not
exist. Nothing named lux-indexer runs in this namespace.
One chain in the config, because one chain answers. `/v1/chain/C/rpc` on a
mainnet validator returns 0x17871; P X A B Z T D G by name are 404, so are the
four L2 blockchain ids this file carried, and so are api.pars.network,
api.zoo.ngo, api.hanzo.network and api.osage.network. Q, K and M are served and
are not EVM. A row for a chain that is not there polls a 404 forever and decodes
plain text as JSON-RPC.
The chain is reached at `lux-chain.lux-mainnet.svc`, the selector-less Service
whose endpoints are the LAN validators, declared once in deploy/lux-api — so the
public route and this process reach the chain the same way. `luxd-rpc` selects on
a label a gate writes onto luxd PODS, and the validators are not pods here.
Measured after: 20 blocks and 780 transactions ingested from an empty volume in
7s, and lux.exchange/explore draws gLUX off four queries at ~110ms each.
Addresses are read off this chain rather than inherited. native is WLUX at
0x66dbed95… (symbol() answers, 2874 bytes); the 0x4888E4a2… this file named
holds no code here. factory_v2 and factory_v3 are dropped for the same reason —
they name the one contract a subgraph trusts, and neither is deployed on this
chain, so the AMM indexer watches the settlement precompile at 0x9999 and
answers an empty venue for the contract AMM that is not there.
lux-testnet and lux-devnet still carry lux-indexer.yaml. The same reasoning
applies and their chains have not been measured.
Co-authored-by: Hanzo Dev <[email protected]>