luxd serves chain APIs under one segment from constants.ChainAliasPrefix (constants v1.6.4, node v1.36.181): /v1/bc/<alias>/... -> /v1/chain/<alias>/... /ext/bc/<alias>/... -> /v1/chain/<alias>/... Observations keep their original addresses: a dated note or a recorded status code is a fact about when it was written, not an instruction to follow.
52 KiB
Lux Network Explorer - AI Knowledge Base
Last Updated: 2024-12-24
Project: explore
Organization: luxfi
Upstream: blockscout/frontend
Project Overview
Lux Network Explorer is a fork of Blockscout Frontend, customized for the Lux blockchain ecosystem. It provides a web-based interface for exploring blocks, transactions, addresses, tokens, and smart contracts on Lux Network chains (C-Chain, Zoo chains).
Essential Commands
The package manager is pnpm (packageManager: [email protected], only pnpm-lock.yaml
exists). yarn in older notes below is wrong — yarn install would write a
second lockfile.
# Development — THE dev command
pnpm dev # tools/scripts/dev.sh, serves on $NEXT_PUBLIC_APP_PORT (3055), NOT 3000
pnpm build:next # Production build WITH assets/sprite/envs.js
pnpm start # Start production server
# Linting & Testing
pnpm vitest run # Unit suite — the only gate in .hanzo/workflows/ci.yml
pnpm lint:eslint # ESLint (repo-wide is red on main; keep YOUR files clean)
pnpm lint:tsc # TypeScript check (also red on main — see .hanzo/workflows/ci.yml)
pnpm test:pw # Playwright
What pnpm dev actually does
tools/scripts/dev.sh deletes and regenerates the runtime env bundle each start:
rm -rf public/assets/{configs,multichain,envs.js}download_assets.sh— skips every unsetNEXT_PUBLIC_*asset URL, so it is offline-safe with the committed local env filesbuild_sprite.sh— needsjqandmd5sum(not macOS defaults)make_envs_script.sh→public/assets/envs.js— this is the file the browser reads.pages/_document.tsxloads it as a blocking<script>andconfigs/app/utils.ts:getEnvValuereadswindow.__envsfrom it. Plainnext devdoes NOT generate it, and without it every API-backed page throwsAPI config for general not found.next dev -p $NEXT_PUBLIC_APP_PORT
Env precedence is first -e wins (dotenv-cli, override: false):
.env.secrets → .env.development.local → .env.local → .env.development → .env.
Missing files are skipped silently, which is why the absent .env.secrets is harmless.
⚠️ .env.development.local sets NEXT_PUBLIC_APP_HOST=explore.lux.network, so
the local app self-identifies as production for absolute links and OIDC redirects.
🪤 .nvmrc contains >=22.14.0, which is a semver range, not an nvm version —
nvm use fails in this directory. CI pins the real value, 22.14.0.
🪤 CSS comments do not nest. A /* … */ inside a block comment in
nextjs/global.css ends it early, and the remaining prose is parsed as CSS —
Turbopack then serves a full-page Build Error overlay instead of the app, so
pnpm dev "starts" but nothing renders. Fixed once; do not reintroduce.
# Docker
docker compose up # Dev environment
docker compose -f compose.prod.yml up # Production
Architecture
explore/
├── pages/ # Next.js pages (file-based routing)
├── ui/ # UI components by feature
│ ├── address/ # Address page components
│ ├── block/ # Block page components
│ ├── tx/ # Transaction page components
│ ├── token/ # Token page components
│ ├── shared/ # Shared components
│ └── snippets/ # Header, footer, navigation
├── lib/ # Core business logic
│ ├── api/ # API client & services
│ ├── hooks/ # React hooks
│ ├── contexts/ # React contexts
│ └── web3/ # Web3 integration
├── toolkit/ # UI toolkit (Chakra-based)
│ ├── components/ # Reusable components
│ ├── theme/ # Theme configuration
│ └── chakra/ # Chakra UI customization
├── configs/ # Configuration files
│ ├── app/ # App features config
│ └── envs/ # Environment presets
├── deploy/ # Deployment tools
│ ├── scripts/ # Build scripts
│ └── tools/ # Deployment utilities
├── mocks/ # Mock data for testing
├── stubs/ # API type stubs
├── types/ # TypeScript types
└── nextjs/ # Next.js configuration
Key Technologies
| Category | Technology |
|---|---|
| Framework | Next.js 14 (Pages Router) |
| Language | TypeScript |
| UI Library | Chakra UI v3 |
| State | React Query (TanStack) |
| Web3 | wagmi, viem, RainbowKit |
| Testing | Playwright, Vitest |
| Styling | Emotion CSS-in-JS |
| Build | Turbopack |
Configuration
Environment Variables
Key environment variables (see docs/ENVS.md for full list):
NEXT_PUBLIC_NETWORK_NAME=Lux Network
NEXT_PUBLIC_NETWORK_ID=96369
NEXT_PUBLIC_NETWORK_RPC_URL=https://api.lux.network/v1/chain/C/rpc
NEXT_PUBLIC_API_HOST=https://explore.lux.network
NEXT_PUBLIC_APP_HOST=https://explore.lux.network
Lux Branding
Lux-specific branding is configured in:
configs/app/ui/- UI customizationpublic/logos/- Logo assetstoolkit/theme/- Theme colors
Development Workflow
Syncing with Upstream
# Fetch upstream changes
git fetch upstream
# Merge upstream into main
git merge upstream/main
# Resolve conflicts, preserving Lux branding
git add . && git commit
Adding New Features
- Check if feature exists in upstream Blockscout
- If not, implement in
ui/orlib/directories - Add TypeScript types in
types/ - Add mock data in
mocks/for testing - Test with Playwright
Lux-Specific Changes
Key Lux customizations:
ui/snippets/networkLogo/- Lux logo componenttoolkit/theme/foundations/- Lux color palettepublic/logos/- Lux brand assetsDockerfile.lux- Lux-specific Docker build
Chain Configuration
| Chain | Chain ID | Explorer URL |
|---|---|---|
| Lux Mainnet (C-Chain) | 96369 | explore.lux.network |
| Lux Testnet | 96368 | testnet.explore.lux.network |
| Zoo Mainnet | 200200 | zoo.explore.lux.network |
| Zoo Testnet | 200201 | zoo-testnet.explore.lux.network |
API Integration
The explorer connects to:
- Blockscout API: Block/tx/address data
- Stats API: Chain statistics
- RPC Node: Direct chain queries
API services are in lib/api/services/.
Context for AI Assistants
This file (LLM.md) is symlinked as:
AGENTS.mdCLAUDE.mdQWEN.mdGEMINI.md
All symlinks reference this single source of truth.
Rules for AI Assistants
- ALWAYS update LLM.md with significant discoveries
- PRESERVE Lux branding when merging upstream changes
- DO NOT modify Blockscout-specific CI workflows
- USE
pnpmfor package management (repo ispackageManager: [email protected]; npm/yarn break the@hanzoguioverrides) - TEST UI changes with Playwright before committing
Recent Changes
Duplicated tokens filter, colliding footer row, lower-case addresses (v1.1.27)
ui/tokens/TokensActionBar.tsxwas the one action bar the Chakra→Tailwind port stripped. Upstream has two blocks on purpose —<HStack display={{base: 'flex', lg:'none'}}>(filter + sort + search, mobile) and adisplay={{base: 'none', lg:'flex'}}block inside the stickyActionBar(filter + search, desktop). The port turned both into bare<div>s, so both rendered at every width (two Filter buttons, two search inputs) and, with noflex, their children stacked instead of sitting in a row. Every sibling kept the pattern —VerifiedContracts,NameDomainsActionBar,ClustersActionBarall useflex lg:hidden/hidden lg:flex— so the fix restores it rather than inventing a second way. 🪤 When a page-level control looks doubled, count the elements before blaming CSS: two things at one spot are usually two things. ⚠️NameDomainsActionBarwrites its desktop block asflex hidden lg:block—block, so its filter and input still stack at lg. Same family, not fixed here.- @luxfi/ui's
ButtonFramenever setsflexDirection. It isstyled(View)on a react-nativeView, whose default iscolumn, while the frame setsinline-flex,alignItems,gap, a fixedheightandoverflow: hidden— all of which only make sense for a row. Any Button with two or more children stacks icon over label and the fixed height clips the label off. Swept 39 visible LuxButtons over 9 pages × 2 viewports: only 3 have >1 child and onlyFilterclipped, so this is fixed atui/shared/filters/FilterButton.tsx(the single component behind every filter in the app) with an inlinestyle={{ flexDirection: 'row' }}— inline because the frame's ownis_Viewclass outranks a Tailwindflex-rowutility. The primitive is still wrong; fixButtonFrameupstream and drop the call-site style. Footer's no-custom-links branch never wrapped its left column. The grid islg:grid-cols-[minmax(auto,470px)_1fr]but that branch passesrenderNetworkInfo() / renderProjectInfo() / renderRecaptcha()as three separate children, so auto-placement scattered them and the link row landed in the 470px track — where five 160px columns cannot fit. The two mobilegrid-cols-2tracks were also still the only explicit tracks atlg, so they took1freach, collapsed to width 0, and Contribute / X (Twitter) / Discord painted on top of each other bottom-left. Both halves fixed: wrap the left column in<div className="min-w-0">exactly as the custom-links branch already does, and addlg:grid-cols-none. 🪤 A zero-width element does not register as an overlap in a rect-intersection test — its text still paints over the neighbour. Comparemax(width, scrollWidth), and treat width 0 as a defect.- The API case-folds address hashes, so never trust the payload's casing. The
backend lower-cases
{hash}/{addr}path params before lookup (deliberate — a checksummed URL used to 404).ui/pages/Address.tsxandui/multichain/address/MultichainAddress.tsxboth readaddressQuery.data?.hash ?? getCheckedSummedAddress(hash), and the??took the lower-case payload whenever the address existed. Both now checksum whatever they display;getAddressaccepts any case and is idempotent. Display-side only — the backend keeps folding. The page<title>was always right (it is built from the route), which is why this only ever showed in the header.
Search adornments, ⌘K assistant, cross-L1 validator count (v1.1.23)
- @luxfi/ui
InputGroupnever applied its measured padding. It measures the start/end adornments with a ResizeObserver and then discards the result: the clone is guarded ondisplayName === 'FieldInput', a component that does not exist in the package, and the props it would set (ps/pe) are Chakra style props that the Tailwind/guiInputspreads onto the DOM and ignores. Both are Chakra→Tailwind port leftovers, so everyInputGroupin the app was unpadded — the header search icon sat on the placeholder's leading letter (measured: 24px left overlap, 52px right, at 1440x900). Fixed at the primitive viapnpm patch→patches/@[email protected](pnpm install --frozen-lockfileapplies it, so CI gets it too). Publish the same fix upstream and the patch can be dropped. 🪤 Removing the dead guard makes the rest of that clone live: it also blankedvalue/placeholderon first render, which flips the field from controlled to uncontrolled. Dropped, and measurement moved to a layout effect so there is no unpadded first frame instead. ClearButtonspreadclassNameover its own visibility classes, so any caller passing one clobberedinvisible— the header showed a clear button on an empty field. Merged withcnat the component.- Validators counted one L1 of four.
useCurrentValidatorsasks a single P-Chain (the oneNEXT_PUBLIC_NETWORK_RPC_URLpoints at). Lux 96369, Hanzo 36963, Zoo 200200 and Pars 494949 each run their own P-Chain with their own validator set — verified 2026-08-06, five each, four disjoint NodeID sets, so 20 not 5.ChainEntry.nodeApiUrlis the per-chain node origin;useNetworkValidatorsfans out through the/v1/pchain?chain=proxy. ⚠️ Pars DOES have a P-Chain and answersplatform.getCurrentValidators— do not assume otherwise. SPC and Osage have no publicly reachable node and are reportedunavailable, never 0. Stake is not summed across chains: each bonds its own currency. - ⌘K assistant. Nothing in this repo,
@luxfi/uior@hanzo/guiships a command palette or chat (nocmdk), so it is the existing Dialog + one global listener +pages/api/ai.ts, which holds the credential server-side. Gated onNEXT_PUBLIC_AI_ASSISTANT_ENABLED;AI_API_KEYis not aNEXT_PUBLIC_var. 🪤 The gui Dialog'ssize={{ lgDown: 'full', lg: 'md' }}leaksfull'smin-h-dvh rounded-noneinto every width —sizeClassesonly prefixes thelghalf. Use a plain size. 🪤 The guiInputsizes itself to 100% and ignores flex utilities; wrap it to make the wrapper the flex item.
🔴 Neither CI plane builds this image — measured 2026-08-06/07
.github/workflows/*isruns-on: lux-build-amd64, andgh api /orgs/luxfi/actions/runnersreturns 0 — those jobs queue forever (the runs on main sat 12–24h and were cancelled)..hanzo/workflows/release.ymlnames a label the fleet DOES carry, but those runners register with git.hanzo.ai, and this repo is a pull mirror there:git push hanzo mainis rejected 3/3 whilegit ls-remote hanzoshows GitHub pushes arriving within ~15s. A mirror sync raises no Actions event, so the workflow never fires.v1.1.23sat tagged with no image for ~2h.- The Gitea REST API 404s on everything, including a repo that exists, so it cannot be used to check run status. Probe GHCR instead, always with a negative control.
- Neither
v1.1.21norv1.1.22carriesNEXT_PUBLIC_GIT_TAGor the OCI labels the release workflow sets — more evidence they were built by hand.
What works: build on our own amd64 nodes.
🪤 A local --platform linux/amd64 cross-build on an arm64 Mac cannot build
this image: colima emulates with QEMU, and pnpm build dies with
QEMU internal SIGILL / uncaught target signal 4.
🪤 buildx's kubernetes driver also fails here — its port-forward drops while
streaming (load build context ... err="EOF"), and when the client dies the
in-pod build keeps burning CPU but nothing is ever pushed.
✅ The reliable path is a Job running buildctl in-cluster, so no local
client is in the loop (scratchpad/buildjob.yaml shape): moby/buildkit
privileged, nodeSelector: runner-pool=1tb + the dedicated=ci-runner
toleration, --opt context=https://github.com/luxfi/explore.git#<tag> so
buildkit clones server-side, and DOCKER_CONFIG pointing at a mounted GHCR
secret so the push happens from inside the cluster.
🪤 Do not read "stalled" from CPU graphs. kubectl top pod reads ~3m CPU /
15Mi for a buildkit pod building flat out — it does not attribute the nested
build cgroup. kubectl top node is no better: the runner-pool-1tb nodes also
host 4 git-runner-* pods. The only honest check is inside the pod —
kubectl exec <pod> -- ps -o pid,etime,time,rss,args; a next build with
14 minutes of CPU TIME and 3.7 GB RSS is working, not hung. Collecting build traces (@vercel/nft) is the long pole on this app, several minutes on its own.
📌 hanzo-build already runs a buildkitd-node DaemonSet (7 nodes, amd64) —
prefer wiring a build to that over creating a namespace and Job by hand as this
note's recipe does.
Runtime ENV plumbing — three places, all required
Adding a NEXT_PUBLIC_* variable needs all of:
configs/app/features/<name>.ts(+ thefeatures/index.tsexport)deploy/tools/envs-validator/schemas/features/<name>.ts(+ itsindex.tsand a.concat()inschema.ts) — the schema is.noUnknown(true)andentrypoint.shvalidates at container start, so an undeclared variable stops the pod booting (unless that deployment setsSKIP_ENVS_VALIDATION, whichexplore-env-luxdoes)docs/ENVS.md—collect_envs.shgreps it to build.env.registry, andcheckPlaceholdersCongruitythrows for a runtime var with no placeholder
🪤 NEXT_PUBLIC_GIT_TAG cannot be set from a ConfigMap. The image bakes a
four-key .env (NEXT_PUBLIC_BRAND, GIT_COMMIT_SHA, GIT_TAG,
ICON_SPRITE_HASH) at build time, and make_envs_script.sh does source .env
before emitting envs.js — so the build-time value wins over the pod env. That
is correct: those four are properties of the image. Verified in-pod on
2026-08-06: printenv NEXT_PUBLIC_GIT_TAG = v1.1.19 while envs.js carried
"". The stray key was the only drift on explore-env-lux (absent from
last-applied-configuration and from both git sources) and was removed,
not adopted — the version must come from the image or the footer can lie about
which build is running.
Shell overflow + white-on-white values + broken Menu button (v1.1.12)
Three write-once-deploy-4 root causes from the 4-breakpoint UI audit. Built
on-cluster (buildkit Job in hanzo-build ns on do-sfo3-hanzo-k8s
runner-pool-32g, git-context clone of luxfi/explore#main), fanned out with
crane to ghcr.io/{luxfi,zooai,hanzoai,parsdao}/explore:v1.1.12 (same digest
sha256:223b3ca2…), rolled to all four explore-fe-* on lux-k8s/lux-mainnet.
Verified LIVE (Playwright/Chromium) 4 brands × {390,768,1280,1920}, 16/16
pass: document.scrollWidth == innerWidth everywhere, all four home values
≥4.5:1, Menu button 32px (lg+)/40px (mobile) with no clip, zero hamburgers at
lg+ (1 at <lg = the correct mobile nav trigger).
- Horizontal overflow (454px @1280, 38px @1920).
Container.tsxforcedmin-w-[100vw] lg:min-w-[fit-content], which promoted the whole page to its ~1734px max-content width (theMainColumngrow shell measured 1734 at 1280) so stat/block cards clipped past the viewport. Fix: fluid column —Container→w-full min-w-0, andMainColumngainsmin-w-0so the flex chain shrinks and the block-rowoverflow-x-autoscrolls internally instead of pushing the page. docSW 1734→1280 / 1958→1920 verified. - White-on-white values (block Reward / tx Value / tx Fee /
network-utilization%, all computed
rgb(255,255,255)@1.04:1). Root cause: legacy Chakra dot-tokens (color="text.secondary",green.600…) were passed straight into an inline CSScolor, which browsers drop as invalid → the text inherited white. Fix: onelib/utils/colorToken.tsresolveColorToken()(dot-token →var(--color-*)) applied inSimpleValue(the value chokepoint for Reward/Value/Fee →#737373= 4.54:1 on the#FAFAFAcard bg), plus new semantic status vars--color-status-good/warn/bad(#1A7A47/#B45309/#B91C1C light = 5.1/4.8/6.1:1; light shades for dark) thatgetNetworkUtilizationParamsnow returns (utilization = 5.13:1). NOTE:@luxfi/uiSkeletonsetsstyle.color = props.colorverbatim — pass it a CSS value/var, never a Chakra dot-token. ~160color="x.y"call sites remain repo-wide; funnel value colors throughSimpleValue. - Broken "Menu" button (
UserWalletButton.tsx): icon+label were stacked in a bare<div>(20px icon over ~20px text) inside a 32pxoverflow:hiddenbutton → the "51px in 32px" clip that read as a stray hamburger at lg+. Fix:flex items-center gap-2+ sized icon (w-5 h-5 shrink-0). Lux shows "Sign in" instead (OIDC auth) — that path was never broken.
Org explorers un-stubbed + mobile detail pages fixed (v1.1.10)
Two mobile-QA defects across the block explorers. DEPLOYED + VERIFIED: image
built on-cluster via buildkit (a Job on do-sfo3-hanzo-k8s
runner-pool-32g — NO GHA; GitHub Actions is disabled for the account) from
commit 7e35a0a, fanned out with crane to ghcr.io/{luxfi,zooai,hanzoai,
parsdao}/explore:v1.1.10 (same digest 6f7c19d0), rolled to all four
explore-fe-* on lux-k8s/lux-mainnet. Verified 390px: docSW==390 (no clip),
labels legible, tab strip scrolls (sw 945, no overlap); desktop 1280px
unchanged; org branding intact (explore.zoo.network = Zoo, networkId 200200,
frontend 1.1.10). CI note: build-lux.yml only builds/deploys luxfi and its
deploy job is currently broken (expired DigitalOcean token → doctl 401);
org rollout is the buildkit+crane path above.
-
Defect 1 (org explorers showed goerli stub data, not their real chains). Root cause was purely TLS:
api-explore.{zoo,hanzo,pars}.networkserved the self-signedCN=INGRESS DEFAULT CERT(no cert-manager Ingress for those SNIs, only lux had one). Real browsers reject it → every FE data fetch fails → react-query falls back to thestubs/placeholders (block #8988736, "Stub Token (goerli)") behind stuck skeletons. The FE wiring was already correct (configmaps point atapi-explore.<org>.network+ right chainId) and the backend already serves real per-chain data — the unifiedexplorerpod has all 6 chains ({"chains":6}) and the org IngressRoutes route by Host /blockscout-prefix-<brand>→/v1/indexer/<slug>(proven: cchain 1.09M blocks, zoo 13,381, hanzo 11, pars 63). Fix = add a dedicated cert-managerletsencrypt-prodIngress per org api host →explorer-unified:8090, mirroringapi-explore-lux-network(declared iningress-api-mainnet.yaml, in the kustomize resources). Certs issued in ~60s; all three nowssl_verify=0. If an org explorer ever goes blank/stubs again, checkkubectl -n lux-mainnet get certificate api-explore-<org>-network-tlsfirst. The@luxfi/uibrowser (Playwright) ignores TLS errors, so it renders real data even when the cert is bad — verify Defect-1-class bugs with a REAL cert-enforcing client (openssl/curl without -k), not the MCP browser. -
Defect 2 (mobile 390px detail pages: dropped labels, horizontal clip, tab overlap). Three shared-UI fixes (fix all 4 explorers at once):
- Labels invisible was the v1.1.9 white-on-white color fix (already landed); deploying v1.1.10 to the org images (they ran v1.0.14) is what makes it show.
- Horizontal clip — the real page-overflow driver at 390px was the FOOTER,
not the field rows.
ui/snippets/footer/Footer.tsxused inlinegridTemplateColumns: 'minmax(auto,470px) 1fr'+repeat(colNum,160px)with no mobile breakpoint → a 470px column past the 390 viewport. Made itgrid-cols-1 lg:grid-cols-[minmax(auto,470px)_1fr](single column on mobile; linksgrid-cols-2via a--footer-colsCSS var for the desktop count). docSW 486→390 verified on a freshnext devrender. - Detail values —
DetailedInfo.ItemValueforcedwhitespace-nowrapwith nomin-w-0, so a long address/hash couldn't shrink and its dynamic shortener (HashStringShortenDynamic, which sizes off its parent width) never bounded. Addedmin-w-0 whitespace-normal break-words overflow-x-hidden lg:whitespace-nowrap lg:overflow-x-visible— desktop unchanged. - Tab bar overlap/collide —
@luxfi/uiTabsList/TabsTriggersilently DISCARD all Chakra style props (overflowX,w,mx,cssscroll-snap,flexShrink, sticky…): they destructure them to_overflowXetc. and only forwardclassName/style. So the mobile scroll +flexShrink={0}the consumer passed never applied → triggers shrank and collided, no scroll. Fixed at the consumer (AdaptiveTabsList.tsx): express the mobile scroll as TailwindclassNameon TabsList (overflow-x-auto snap-x -mx-3 px-3 w-[calc(100%+24px)] lg:…) andclassName="shrink-0 snap-start"on triggers. Verified: tab strip becomes a scroll container (sw 373→921) with no overlap. NOTE for future work: any Chakra-style prop on@luxfi/uiTabs is a no-op — useclassName.DetailedInfo.Container'stemplateColumnsprop is likewise dropped (hardcoded grid); mobile is already single-column so it's benign. - Dev caveat:
next devcrashes tab pages inAdaptiveTabsMenu(pre-existing dev-onlyicons/dots.svg/popover import resolving to object); prod build is fine. Verify tab/detail rendering on the deployed prod URL, footer on dev.
api-explore.lux.network TLS cert fix — explorer data now loads (infra)
The explorer FE was stuck in skeleton loaders ("0/5 CONNECTED", no blocks/txns)
because every api-explore.lux.network request failed in the browser with
ERR_CERT_AUTHORITY_INVALID. Root cause: the explorer Go backend answers by
Host header via the ingress default backend, so the API responded (200) even
with no ingress rule for that host — but with no TLS block for that SNI the
controller served the self-signed CN=INGRESS DEFAULT CERT, which browsers
reject. (curl -k worked; a real browser did not.) A stale
api-explore-lux-network-tls secret existed but nothing referenced/renewed it.
Fix: a dedicated Ingress for api-explore.lux.network → explorer-unified:8090
with a letsencrypt-prod TLS block (mirrors the working
api-explore.osage.network). cert-manager issued a real LE cert (CN=YR2) in
~60s; curl ssl_verify_result=0. Verified live (Playwright): explore.lux.network
now renders 1.09M blocks, 16.9k txns, live blocks + real tx hashes.
Declared in deploy/k8s/explore-fe/ingress-api-mainnet.yaml (in the kustomize
resources) so it survives redeploys. If explorer data ever goes blank, check
this ingress + its cert first (kubectl -n lux-mainnet get certificate api-explore-lux-network-tls). Sibling brands (hanzo/pars/spc) have api-explore
TLS secrets too — if their explorers go blank, they likely need the same
dedicated api-explore ingress.
Native all-chains wallet, lux.id login, menu/panel fix, real deploy path (v1.1.6)
Four owner-reported UI fixes + the deploy/runtime wiring to actually ship them. (v1.1.4 shipped a crash; v1.1.5 fixed it; v1.1.6 fixes the chain switcher.) Verified live on explore.lux.network (Playwright): homepage + /blocks render with live blocks; header menus/panels open with solid backgrounds; Sign in → lux.id login page; chain switcher lists Lux/Zoo/Hanzo/SPC/Pars/Osage.
- Chain switcher didn't open (v1.1.6). ChainSwitcher put onClick={handleToggle} on the PopoverTrigger button, double-toggling against PopoverTrigger's own handler (net: stays closed) — no chains were selectable. Removed the redundant onClick + unused handleToggle (match UserProfileDesktop's pattern).
Flagged, NOT fixed here (need owner input / secrets):
-
Wallet connect is disabled: blockchainInteraction only enables when NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID is set (a real reown/WalletConnect projectId, a vault secret per deploy/values/*.gotmpl). It's absent in the Lux configmap → no wallet-connect UI. brandFamilyChains (the code side of fix c) is in place; enable by adding the projectId secret. Don't use a placeholder.
-
Homepage intermittently error-boundaries ("length" of undefined, after an async update) — pre-existing (old build too); recovers on reload; header + all other pages unaffected. No source maps, couldn't pin the exact widget.
-
CI deploy job: build succeeds on ARC; the deploy job's KMS/DO-token step still needs the DO token (fixed set -e / jq aborts + DIGITALOCEAN_ACCESS_TOKEN fallback). Deploy is done via
kubectl set image explore-fe-lux …:sha-<short>(the documented method) meanwhile. -
App-crash fix (v1.1.4 regression). v1.1.4 changed
useProviderto returnnull(react-query v5 forbidsundefined) but only switched thelib/web3hooks; four UI consumers still didconst { data: { wallet } = {} } = useProvider().= {}defaults onlyundefined, notnull, sonulldata crashed them → whole-page error boundary once account/OIDC put a wallet-using popover in the header. Fixed touseProvider().data ?? {}in ChainWidget, MultichainEcosystemsTableItem, NetworkAddToWallet, AddressAddToWallet. -
Menu / floating panels (the big visible break). The prior build threw ~7k
setReferencecrashes — every popover/menu/drawer (chain switcher, sign-in, mobile nav) rendered transparent (page bled through). Two causes: duplicate@hanzogui/*React contexts (pinned to one 3.0.2 instance) and Tailwind v4 not scanning@luxfi/uiso the panel-surface utilities were purged. Fix:@hanzoguipins +@source "../node_modules/@luxfi/ui"innextjs/global.css- a
--color-popover-bordertoken.
- a
-
Wallet / all-chains.
lib/web3/chains.tsderivesbrandFamilyChainsfromconfigs/app/chainRegistry(+ matching wagmi transports); headerChainSwitcherlists every registry chain on the primary C-Chain explorer.useProviderreturnsnullnotundefined(react-query v5 crashed the wallet hooks — that also broke the menu). -
lux.id login is RUNTIME config, not code. The account feature is gated on env that was never in the deployed ConfigMap, so no sign-in button appeared. Enabled on
explore-env-lux(lux-mainnet) +deploy/k8s/explore-fe/configmap-mainnet.yaml:IS_ACCOUNT_SUPPORTED=true,ACCOUNT_AUTH_PROVIDER=oidc,OIDC_SERVER_URL=https://lux.id,OIDC_CLIENT_ID=lux-explore-client-id(issuer live; authorizehttps://lux.id/v1/iam/oauth/authorize). -
Deploy path (was broken). explore.lux.network is served by Deployment
explore-fe-lux(nslux-mainnet,ghcr.io/luxfi/explore:sha-<short>, IfNotPresent).build-lux.ymlbuilds on thelux-buildARC pool; the deploy job nowkubectl set image explore-fe-luxto the freshly built sha + waits on rollout — it previously restarted a non-existentlux-frontend-mainnetand its KMSDO_API_TOKENfetch returned empty (deploys silently skipped). Added aDIGITALOCEAN_ACCESS_TOKENfallback; footer version now from package.json. -
Manifest.
scripts/generate-well-known.jsregeneratespublic/.well-known/explore.jsonat build; now emits<Brand> Network, never "Subnet".
chainId reconciliation + RPC → /v1 (v1.1.7): registry now matches the LIVE
chains AND the canonical genesis source of truth
(~/work/lux/genesis/configs/lp182_chain_id_map.go, the LP-018 (family,env)→
EVMChainID map). One value, one way.
- Pars mainnet 494949 (was the stale 7070) —
api.pars.networkreportseth_chainId=0x78d65/net_version=494949. Pars testnet 494950, devnet 494951 (were 7071/7072) — matches the LP-018 map's 4949xx theme. Note the LP-018 map itself was ALSO stale on Pars mainnet (7070) and was corrected to 494949 in the genesis repo; testnet/devnet 494950/494951 were already canonical there. - Lux devnet 96367 (was the stale 96370) —
api.lux-dev.networkreports 96367. Canonical Lux: mainnet 96369 / testnet 96368 / devnet 96367. chainRegistry.spec.tsnow asserts Pars 494949 (was the stale-7070 assert).- Fixed in:
configs/app/chainRegistry.ts,chainRegistry.spec.ts,public/.well-known/explore.json,lib/web3/chains.ts(comment),deploy/k8s/explore-fe/configmap-{mainnet,testnet}.yaml,deploy/scripts/branded_build.sh,tools/send_test_txs.py. - Chain RPC migrated
/v1/chain/<alias>/rpc→/v1/chain/C/rpc(gateway canonical; every sovereign L1 exposes its own C-Chain at/v1/chain/C/rpc, so zoo/hanzo/pars drop their old brand-aliased/v1/bc/{zoo,hanzo,pars}/rpcpaths — which were already 404ing). Verified returning blocks BEFORE switching: Lux/Zoo/Hanzo/Pars mainnet + Lux testnet. Deliberately still on/ext(their/v1gateway route is not up yet — do NOT switch until a block returns): Lux devnet, Zoo/Hanzo/Pars testnet, and the local-node compose +send_test_txs.pyURLs (raw luxd serves/extnatively, no gateway). P-chain proxy (pages/api/pchain.ts) now dials/v1/bc/P(verified).useChainHeights.tsandluxnet/instance.tsno longer strip/v1/bc/C/rpc: they use the env RPC URL (or its origin) directly, so the C-chain height widget + luxnet SDK work under either path scheme.
Chain-visibility + 10-VM/DEX handling (v1.0.14)
Two orthogonal correctness fixes shipped in one build cycle.
- Chain-visibility rule (PR #7):
isPrimaryNetworkExplorer()now requires!isWhiteLabelMode() && vm === 'EVM', so unregistered/brand hosts stop rendering the Lux primary panels + cross-L1 list. Registeredexplore.zoo.network/.ngo+explore.pars.networkas own-scoped L2s (brand parity with Hanzo), added Osage L1, and set Pars chainId (interim 7070, since reconciled → 494949 in v1.1.7 above). Rule: lux explorer = ALL chains; hanzo/zoo/pars = ONLY their own. Proven byconfigs/app/chainRegistry.spec.ts(12 cases). - 10-VM / DEX handling: the Lux primary network is a family of VM-specialized
chains (C=EVM, X=UTXO, D=DEX, P=platform, + A/B/Q/T/Z/G/K/O/R/I/M), mirrored
from
~/work/lux/node/node/vms.go. New single source of truthconfigs/app/primaryChains.ts(PRIMARY_VMS,getPrimaryVm,view,hasBespokeView,chainsAwaitingBespokeView) decomplects the two lists that used to duplicate this (ChainsPage / ChainDetailPage now consume it). Each VM routes at/chains/<slug>;viewselects the render:- D-Chain → the DEX order-book UI (
ui/dex/DexPage, wired to the indexer./dexadapter vialib/api/dchain) —ChainDetailPagerenders<DexPage/>forview: 'dex', not a generic block list. - C-Chain = the EVM explorer core; P-Chain = platform/validators data;
X-Chain + the custom VMs (A/B/Q/T/Z/G/K/O/R/I/M) render the generic
chain-detail fallback (info + indexer DAG stats + validators) — they do NOT
crash/blank.
chainsAwaitingBespokeView()FLAGS the backlog: X-Chain (UTXO) and the 11 custom VMs still need a bespoke explorer view; only C/P/D have one today.
- D-Chain → the DEX order-book UI (
- VMs are Lux-primary → lux-only: the primary-VM surfaces (Chains / DEX /
Bridge / AI Compute nav + the
/chains,/chains/[slug],/dexpages) are gated onisPrimaryNetworkExplorer(). Nav hides them on brand/white-label hosts;ui/shared/PrimaryNetworkGuardbackstops direct-URL access so the 10 VMs can never leak onto Hanzo/Zoo/Pars. Proven byconfigs/app/primaryChains.spec.ts(26 cases). - Build/deploy: single multi-destination kaniko Job (NO GHA), context
git://github.com/luxfi/explore.git#main, Dockerfile (base Next.js build), fan-out--destination=ghcr.io/{luxfi,zooai,hanzoai,parsdao}/explore:v1.0.14(parsdao ADDED — it had no v1.0.1x image). White-label is 100% runtime via theexplore-env-{brand}configmaps (APP_HOST), not baked per-brand. Deploy = bump all fourexplore-fe-*images to v1.0.14 inluxfi/universe k8s/lux-mainnet/explore-fe.yaml, operator reconcile.
Realtime transport: Phoenix WebSocket → SSE (v1.0.13)
- Bug: the FE connected realtime via Phoenix Channels
(
wss://api-explore.lux.network/socket/v2/websocket), but the Go backend (luxfi/explorer) serves no Phoenix endpoint — its realtime is SSE at/v1/base/realtime(multiplexed, envelope{event, chain, data}, initialevent: CONNECT,: pingkeep-alive). Result: repeatingws 404→ "Live updates temporarily delayed". This is a frontend-only transport mismatch; the chain/indexer are untouched. - Fix:
lib/socket/sse.tsreimplements the slice of the PhoenixSocket/ChannelAPI the 27 realtime consumers use, backed by ONEEventSource. The seam is unchanged —SocketProvider/useSocketChannel/useSocketMessageand every consumer keep working; only the transport underneath swapped. No morephoeniximport in app code. - Routing: envelope
{event, chain, data, topic?}. Withtopic→ delivered straight to that channel/event (scopedaddresses:*/tokens:*, and the component-test harness). Without → theTRANSLATIONStable maps the backend hub channel to a Blockscout(topic, event); today the indexer broadcasts onlyblocks→blocks:new_block/new_block.join()resolvesokon stream open; EventSource auto-reconnects. URL normaliser collapses anywss://…/socket*(or testws://host:port) to<https>/v1/base/realtime. - Tests:
lib/socket/sse.spec.ts(12 vitest cases, all green) covers URL normalisation, blocks→new_block translation, direct-topic routing, joinok, on/off, onError, disconnect. CT harnessplaywright/fixtures/socketServer.tsis now a tiny SSE server (samecreateSocket/joinChannel/sendMessagesignatures) so the 12 consumer specs are unchanged. - Still owner-gated (separate, NOT this fix): data freshness on
explore.lux.network is blocked on the C-Chain rollback + indexer reorg
handling — no fresh blocks flow until that lands. When it does, align the
EVMBlock→Blockscout
Blockfield shapes in theblockstranslation (or have the backend emit Blockscout-shaped blocks); that is the only remaining piece and it can only be verified end-to-end once data flows again.
Investor-grade data: real validators + honest-empty stats/DEX (v1.0.11)
Removed every fabricated placeholder; data is now real-from-chain or honestly empty, never faked. Root causes + fixes:
- Validators/Stake = 0 was a proxy bug, not missing data. P-chain
platform.getCurrentValidatorsreturns real validators (5, total weight 2.5e18 on hanzo).pages/api/pchain.tsderived its base URL by stripping ONLY/v1/chain/C/rpcvia regex; brand RPCs are…/v1/chain/<chain>/rpc(hanzo:/v1/chain/hanzo/rpc), so the regex no-op'd and the proxy POSTed to…/v1/bc/hanzo/rpc/v1/bc/P→ HTML 404 → "Unexpected non-whitespace character after JSON at position 4" → 502. Fix:new URL(rpcUrl).origin(works for every brand) + defensive text→JSON parse with a clear error.ui/stats/lux/NetworkStats.tsx(Network Overview: Total Chains / Validators / Connected / Total Stake / Avg Uptime) already had honest—fallbacks and now renders REAL values.lib/api/luxnet/instance.tswas already safe (usesnew URL().hostname), so B/D-Chain SDK calls were unaffected. - "Placeholder Counter 9.074M ×8" came from
stubs/stats.tsSTATS_COUNTER(value:'9074405', title:'Placeholder Counter') used as React-QueryplaceholderDatainui/stats/NumberWidgetsList.tsx. When the Blockscout stats microservice is absent (hanzo has NONEXT_PUBLIC_STATS_API_HOST; everyapi-explore.hanzo.network/api/v1|v2/*404s) the stub leaked as "real". Fix: neutralize the stub (title:'', value:'0') AND gate the counters/charts inui/pages/Stats.tsxonconfig.features.stats.isEnabled— show the live chain-sourced Network Overview always, with an honest "being indexed" note when no stats service. Aggregate counters (total txns/addresses, gas-used- today, history charts) genuinely need a stats indexer that isn't deployed. - DEX showed static demo numbers.
lib/api/dchain/useDexData.tshad hardcodedDEMO_SYMBOLS/ORDERS/TRADES/POOLS/OVERVIEW(LUX/USDT 24.85, etc.) served when the D-Chain (DexVM) is unavailable. Deleted ALL demo data → real D-Chain data or honest-empty;ui/dex/DexPage.tsxrenders an honest empty/ error message per tab. - Bridge signers = 0 is already honest (
lib/api/bchain/useBridgeData.tsreturns real B-Chain signer set orEMPTY_STATS; B-Chain not yet reporting). - Homepage is already investor-grade: real RPC blocks (#8–#13), tx hashes,
gas (2.5 Gwei), Total blocks/txns/wallets —
ui/home/Stats.tsxdegrades to RPC when the general indexer errors;HOMEPAGE_STATSstub is placeholder-only. - Blockchain dropdown clipped its last item ("Verified Contracts"):
@luxfi/uiTooltipvariant="popover"Content defaults tooverflow-hidden px-3 py-2, clipping a tall menu<ul>. Fixed at the consumer (ui/snippets/navigation/horizontal/NavLinkGroup.tsx) viacontentProps={{ className:'overflow-visible px-1.5 py-2' }}(twMerge wins)placement:'bottom-start'. No node_modules patch.
- Still needs a real indexer (honest-empty until then): aggregate Stats counters + history charts (Blockscout stats microservice), DEX live markets/orders/trades (DexVM indexer), Bridge signer set (B-Chain).
- Built v1.0.11 via the kaniko Job pattern (NO GHA) →
ghcr.io/{hanzoai,zooai, luxfi}/explore:v1.0.11; deploy = image patch ofexplore-fe-*Deployments on do-sfo3-lux-k8s nslux-mainnet(plain Deployments, not operator-managed).
Validators page: visible in light mode + brand currency (v1.0.12)
v1.0.11 fetched REAL P-chain validators (5, total weight 2.5e18 on hanzo) but
a Playwright pass found the /validators view rendered white-on-white in
light mode — the data was present in the DOM yet invisible to users (only the
tab bar showed because it alone used text-[var(--color-text-primary)]). The
three custom Lux components (ui/validators/lux/{ValidatorsDashboard, ValidatorsList,DelegatorsList}.tsx) set NO text color, so they inherited a
white color that doesn't flip with the theme. Fixes:
- Set
text-[var(--color-text-primary)]on each component's root container — one place per component; the token flips perstyles/tokens.css(lightrgba(16,17,18,.80)/ darkrgba(255,255,255,.80)), so all descendants now render the mode-correct color (dark on light, light on dark). - Stake unit was hardcoded
LUXon every brand; nowconfig.chain.currency. symbol || 'LUX'(=AIon Hanzo) — matchesNetworkStats.tsx's pattern, DRY across all 6 stake labels in the three components. - NOT changed:
formatStakeusesLUX_DECIMALS = 6, the codebase-wide convention (NetworkStats + ChainDetailPage use the same); P-chaingetMinStakereturnsminValidatorStake:2000000000(= 2000 at 6dp), consistent with 6. (Three copies of this helper is a latent DRY violation to consolidate later, but the value is correct — left as-is to avoid drift.) next.config.jshastypescript.ignoreBuildErrors:true; repo carries ~224 pre-existing tsc errors (all in*.pw.tsx/ unrelated) — build is unaffected. Dockerfile uses pnpm (not yarn). Edited files lint+typecheck clean.- Built v1.0.12 via the kaniko Job (NO GHA) →
ghcr.io/{hanzoai,zooai,luxfi}/ explore:v1.0.12; deployed by image patch ofexplore-fe-{hanzo,lux,zoo}on do-sfo3-lux-k8s nslux-mainnet. (Built on do-sfo3-hanzo-k8s nshanzo.)
Multi-brand hostname-driven header logo (explore v1.0.10)
- ONE image white-labels by request Host:
configs/app/chainRegistry.tsgetCurrentChain()resolves the chain (and its brand) from the hostname, and the header renderschain.branding.logoContent(an inline-SVG mark usingcurrentColor). NO hardcoded logo, no image-URL→triangle fallback. - Per-brand marks live in
chainRegistry.ts: Lux = downward triangle (viewBox0 0 100 100), Hanzo = real blocky-H (@hanzo/logo, viewBox0 0 67 67, 5 paths —M22.21 67…), Zoo = interlocking circles (0 0 1024 1024), Pars = 8-pointed star, SPC = unicorn. - The header logo renders in THREE spots, all reading
branding:ui/snippets/topBar/TopBar.tsx(desktop), andui/snippets/networkLogo/{NetworkLogo,NetworkIcon}.tsx(mobile headerui/snippets/header/HeaderMobile.tsx+ chain-switcher). The stale pre-v1.0.10 mobileNetworkIconhardcoded aviewBox="0 0 50 50"<polygon>triangle mislabeledaria-label="Hanzo icon"— fixed in4369780(mobile) +79dbe63(desktop, single header per breakpoint). - Multi-tenant L1/L2/L3: each
ChainEntryhas avmfield (EVM= Lux primary L1,L2= brand sovereign chains) gatingisPrimaryNetworkExplorer()so brand explorers show ONLY their own chain. mainnet/testnet/devnet/localnet entries per brand; hostnames cover both canonical (explore-<brand>.lux.network) and brand-domain (explore.<brand>.network,explore.hanzo.ai) hosts.
Build + deploy (NO GitHub builders)
- Built on-cluster via kaniko (do-sfo3-hanzo-k8s ns
hanzo, node poolrunner-pool-32g, secretkaniko-ghcr-multi, tolerationdedicated=ci-runner): contextgit://github.com/luxfi/explore.git#main,--dockerfile Dockerfile, fan-out--destinationto per-brand GHCR orgsghcr.io/{luxfi,zooai,hanzoai}/explore:v1.0.10(one digest,sha256:4f980291…). pars (ghcr.io/parsdao/explore) not in the fan-out yet → stays v1.0.4. - Runs on do-sfo3-lux-k8s ns
lux-mainnetasexplore-fe-<brand>Deployments (selector{app:explore-fe,brand,network}), env fromexplore-env-<brand>ConfigMap, pull secretghcr-luxfi, svc 80→3000. NOT operator-managed (plain Deployments) → bump via image patch / apply. Declared state:luxfi/universe k8s/lux-mainnet/explore-fe.yaml(+k8s/lux-devnet/explore-fe.yaml). - App hydrates client-side (SSR HTML is an 8 KB shell) — verify the logo
with a real browser (Playwright), not
curl | grep.
Auth — Hanzo IAM is the only gate (do not add a second)
- Sign-in is OIDC against Hanzo IAM. Never build local password/OTP/SIWE
auth, and never re-point Blockscout's
/account/*auth at our backend — rewriting it is building custom auth. The auth0 (email + one-time-code + reCAPTCHA), Dynamic.xyz and CSRF paths were deleted, not ported: every/account/*endpoint 404s onapi-explore.*(measured), so none of it could ever have worked here. lib/oidc.tsis the ONE place that knows IAM's endpoints. Never rebuild a path at a call site. IAM publishes discovery at<issuer>/.well-known/openid-configuration; everything lives under/v1/iam/oauth/*(authorize,token,userinfo,logout). The bare/oauth/*and/api/userinfopaths are the IdP's SPA shell or a 404 and fail silently.- PKCE is mandatory. IAM rejects a public client without it:
error=invalid_request, PKCE is required for public clients. Our clients are public (a browser holds no secret), so the S256 challenge is not optional polish — it is the thing that makes login work at all. - Client ids are
<org>-<app>(HIP-0111):lux-explore,zoo-explore,hanzo-explore,pars-explore. The tree shippedNEXT_PUBLIC_OIDC_CLIENT_ID=lux-explore-client-id, a client that never existed →400 unknown client_id. Issuers arelux.id,zoolabs.id(NOTzoo.id— it does not resolve),hanzo.id,pars.id. - IAM state is changed ONLY by
make iam-provisionfrom that org'suniverse/infra/k8s/iam/provision.yaml. 🪤 The upsert REPLACESredirectUris— it does not merge. Any live URI not derivable fromhosts:must be stated underredirects:or the next converge deletes it.
UI stack — the migration is already done; Tailwind is NOT removable
- Measured:
@chakra-ui0 files,@emotion0,styled-system0,@radix-ui0 files in our code.@luxfi/uiis imported by ~787 files. - The ~17
@radix-ui/*entries inpackage.jsonhave zero call sites — they duplicate@luxfi/ui's own 13 Radix dependencies. Dead declarations, safe to drop, but they are our library's internals and Radix stays in the tree. - Tailwind v4 is structural, not legacy cruft.
nextjs/global.csshas@source "../node_modules/@luxfi/ui"precisely so Tailwind emits the utilities@luxfi/uiitself uses; purge them and the library breaks. There is notailwind.config— v4 is configured in CSS (@theme,@custom-variant). - Real remaining debt: ~137 files still pass Chakra-shaped style props
(
fontSize="sm",w,borderRadius) that@luxfi/uiabsorbs via anany-typed compat shim. If that shim goes, those files break.
Deployment traps
- The four
ghcr.io/{luxfi,hanzoai,zooai,parsdao}/exploretags are byte- identical copies (same digest) — brand comes entirely from theexplore-env-<brand>ConfigMap at runtime. A version bump is thereforecrane copy, not four builds. Never mix org registries. - 🪤 CI cannot build this repo from GitHub:
gh api /orgs/luxfi/actions/runnersreturns 0.runs-on: lux-build-amd64is a valid legacy alias, but its pool lives on the arcd/git.hanzo.ai fleet, so GitHub runs queue for 12–24h and get cancelled. git.hanzo.ai mirrorsmainautomatically. - 🪤
tsconfig.jsonhasincremental: true. A staletsconfig.tsbuildinforeports errors against file contents that no longer exist. Delete it before trusting a typecheck diff. - 🪤
.cspell-words.txtis in cspell's ignorePaths, not a dictionary. New words go in thewordsarray incspell.jsoncor the pre-commit hook fails.
The primary-network chains: what is real, and what only looks it
Measured 2026-08-08 against all three networks. These are facts about the network, not about this app, so check them before believing any chain page.
- The chain list is
platform.getBlockchains, never a table in this repo. mainnet and testnet report nine chains (A B C D G K Q X Z); devnet reports ten, adding M.configs/app/primaryChains.tsadditionally names T, R, I and O, which are registered on NO network — the node answers "there is no ID with alias: T". That table is presentation metadata; the network is the source of truth./chainsand/chains/<slug>join the two. - The public gateway routes exactly three chains:
/v1/chain/P,/v1/chain/Xand/v1/chain/C/rpc. Everything else 404s — including C-Chain's own blockchain ID,/v1/bc/25td8att…/rpc, which proves the 404 is the route and not the chain. D, A, B, Z, G, K are registered AND bootstrapped on the node; a browser simply cannot reach them. Render that as unreachable, never as a height of 0. - Q-Chain is
isBootstrapped: falseon all three networks and is the sole entry in/v1/health's not-bootstrapped list — which is why all three networks reporthealthy: false. Q's page says Syncing; that is correct. - P and X both report height 0 on all three. That is the RPC's real answer (genesis-era P timestamps confirm it), not a failure.
- X speaks
xvm.*, notavm.*.avmreturns "can't find service".xvm.getBlockchainIDandxvm.getTxFeeare not in this build either. - C-Chain chain ids: mainnet 96369, testnet 96368, devnet 96367.
- 🪤
api-indexer-<x>chain.lux.network— all fifteen were NXDOMAIN. Deleted. Do not reintroduce a per-chain indexer host map without probing DNS first.
Reaching the node
/v1/node/<endpoint> (pages/api/node/[endpoint].ts) is the ONE proxy. The
browser cannot call the node directly — the gateway 404s the OPTIONS preflight
for /v1/chain/*. The endpoint is resolved against the primary-chain table and
never used as a URL. A chain the gateway does not route answers 200 with a
JSON-RPC error, not 502: nothing is broken, and 502 painted a failed request
into the console of eight of the ten chain pages.
🪤 Deployment does not follow the tag
.hanzo/workflows/release.yml builds the image and copies it to each brand
registry, then prints "Pin all five brands to in luxfi/universe
deploy/lux-mainnet/". Nothing performs that pin. On 2026-08-08 GHCR held
v1.1.28, v1.1.29 and v1.1.26 while universe still declared v1.1.26 — so
v1.1.27 (never built) and v1.1.28/29 (built, undeployed) had all been sitting.
Moving the pin is a hand commit in luxfi/universe; the history does exactly
that ("move all five brands to v1.1.26").
🪤 Only deploy/lux-mainnet/explore-fe-*.yaml drives CD (cd.automated: true,
"CD applies this file within ~90s"). The testnet and devnet files are
snapshots generated from the live Deployment by hack/live2values.py and say
so in their header — editing them deploys nothing. Those two clusters were on
v1.0.4 / sha-caf792d while mainnet ran v1.1.26.
2024-12-24
- Merged upstream blockscout/frontend (up to commit 5a49ad8b1)
- Updated branding from Blockscout to Lux Network
- Added logo assets
Cleanup
- Removed 8 stale
*.orig/*.bakbackups (~1.9 MB) left by the Blockscout→Lux rebrand commits; two still carried raw<<<<<<< HEADconflict markers. Nothing referenced them.*.orig/*.rej/*.bakare now in.gitignore(upstream's had*.orig; the fork dropped it, which is how they got committed).
Note: This file is tracked in git. Update it when making significant architectural changes or discoveries about the codebase.