Single-binary Lux explorer: chain indexer, GraphQL engine, and SPA frontend
in one Go process. Imports luxfi/indexer
and luxfi/graph as libraries; embeds the
luxfi/explore Next.js build via
go:embed.
ghcr.io/luxfi/explorer
-> indexes one or more EVM chains (luxfi/indexer)
-> exposes per-chain GraphQL (luxfi/graph)
-> serves embedded SPA + REST API (luxfi/explore)
| Path | Purpose |
|---|---|
/ |
SPA (embedded; SPA-routing fallback) |
/envs.js |
Runtime config window.ENV = {...} |
/icon.svg, /logo.svg |
Per-host brand assets (disk override) |
/health |
Service health |
/v1/indexer/* |
Default chain explorer API |
/v1/indexer/{slug}/* |
Per-chain explorer API |
/v1/explorer/{slug}/* |
Legacy alias for /v1/indexer/{slug} |
/v1/graph/{slug}/{subgraph}/graphql |
Per-chain, per-subgraph GraphQL |
/v1/explorer/admin/chains[/{slug}] |
Runtime registry CRUD |
/v1/explorer/realtime |
WebSocket realtime hub |
/v1/explorer/realtime/stats |
Realtime stats |
docker run -p 8090:8090 \
-v $(pwd)/chains.example.yaml:/etc/explorer/chains.yaml:ro \
-v explorer-data:/data \
ghcr.io/luxfi/explorer:latest \
--config /etc/explorer/chains.yamlOr with compose:
docker compose upchains.yaml is the single source of truth. Every aspect of the deploy --
chains, branding, GraphQL subgraphs, network switcher links -- is per-chain
customizable. See chains.example.yaml for the full
schema.
data_dir: /data
http_addr: :8090
brand_default:
name: Lux Explorer
coin: LUX
accent_color: "#5cf"
networks:
- { label: Mainnet, domain: explorer.lux.network, chain_id: 96369 }
chains:
- slug: cchain
name: Lux C-Chain
chain_id: 96369
type: evm
rpc: https://api.lux.network/mainnet/ext/bc/C/rpc
coin: LUX
enabled: true
default: true
indexer:
poll_interval: 30s
graph:
enabled: true
subgraphs:
- { name: amm, schema: amm, enabled: true }
brand:
name: Lux C-Chain
accent_color: "#5cf"$ENV_VAR substitution in rpc: / ws: lets one file template every
network -- mount it read-only and inject secrets via env vars.
The same image powers every deploy. To rebrand for a different network:
- Mount a different
chains.yaml. - Optionally mount
icon.svg/logo.svgat the path referenced bychains.yaml-- they are read from disk per-request, not embedded. - Set environment variables to fill the
${VAR}placeholders.
No image rebuild needed. The SPA reads chain list and brand at runtime from
/envs.js.
Chains can be added, updated, and removed at runtime without restart:
# Add
curl -X POST http://localhost:8090/v1/explorer/admin/chains \
-H 'Content-Type: application/json' \
-d '{"slug":"new","name":"New","chain_id":123,"rpc":"http://node:8545","type":"evm"}'
# List
curl http://localhost:8090/v1/explorer/admin/chains
# Remove
curl -X DELETE http://localhost:8090/v1/explorer/admin/chains/newAdding a chain spawns its indexer + (optional) graph workers; removing cancels them and frees the routes.
Set EXPLORER_MDNS=true to auto-register chains advertised by local Lux
nodes (_luxd._tcp, _zood._tcp, _hanzod._tcp, _parsd._tcp). The
explorer queries info.getChains on the discovered node and registers each
tracked chain. mDNS-sourced chains never override config-sourced entries.
/data/
cchain/
query/indexer.db (SQLite WAL, indexer)
{zapdb}/ (KV store)
graph/
amm/graph.db (SQLite WAL, per-subgraph)
zoo/
...
Each chain (and each subgraph) owns an isolated SQLite + KV store. WAL is
optionally streamed to S3 with PQ encryption when REPLICATE_S3_ENDPOINT
is set -- see luxfi/indexer/daemon.
# Sibling layout: ~/work/lux/{indexer,graph,explorer}
cd ~/work/lux/explorer
go build -o explorer .go.mod uses local replace directives pointing at ../indexer and
../graph. Production CI clones all three repos into the same layout
inside the Docker build.
curl http://localhost:8090/health
curl http://localhost:8090/v1/indexer/cchain/blocks/latest
curl -X POST http://localhost:8090/v1/graph/cchain/amm/graphql \
-H 'Content-Type: application/json' \
-d '{"query":"{ swaps(first:5) { id amount0 amount1 } }"}'luxfi/indexer-- chain indexer libraryluxfi/graph-- per-chain GraphQL libraryluxfi/explore-- Next.js SPA