The FE connected realtime via Phoenix Channels (wss .../socket/v2), but the
Go backend serves no Phoenix endpoint — its realtime is SSE at
/v1/base/realtime (envelope {event,chain,data}). Result: repeating ws 404 ->
'Live updates temporarily delayed'.
lib/socket/sse.ts reimplements the slice of the Phoenix Socket/Channel API the
27 realtime consumers use, backed by one EventSource, behind the existing
SocketProvider/useSocketChannel/useSocketMessage seam — no consumer changes.
Envelope routes via an optional topic field or a hub-channel translation table
(blocks -> blocks:new_block/new_block today). CT harness becomes a tiny SSE
server with the same signatures. 12 vitest cases cover it.
Frontend-only; chain/indexer untouched. Data freshness remains gated on the
separate C-Chain-rollback/indexer-reorg owner items.
15 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
# Development
yarn dev # Start dev server (runs tools/scripts/dev.sh)
yarn dev:preset # Start with preset configuration
yarn build # Production build
yarn build:docker # Build Docker image
yarn start # Start production server
# Linting & Testing
yarn lint:eslint # Run ESLint
yarn lint:tsc # TypeScript check
yarn test:pw # Playwright tests
yarn test:pw:local # Playwright tests locally
# 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/ext/bc/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
yarnfor package management (not npm/pnpm) - TEST UI changes with Playwright before committing
Recent Changes
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/ext/bc/C/rpcvia regex; brand RPCs are…/ext/bc/<chain>/rpc(hanzo:/ext/bc/hanzo/rpc), so the regex no-op'd and the proxy POSTed to…/ext/bc/hanzo/rpc/ext/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.
2024-12-24
- Merged upstream blockscout/frontend (up to commit 5a49ad8b1)
- Updated branding from Blockscout to Lux Network
- Added logo assets
Note: This file is tracked in git. Update it when making significant architectural changes or discoveries about the codebase.