Commit Graph
19 Commits
Author SHA1 Message Date
zeekayandHanzo Dev b0fed15c5f The live stream answers under the page's API base, and block time reads in seconds
The page builds its socket URL from its API base, so a brand page whose base is
/v1/indexer/<slug> opened /v1/indexer/<slug>/v1/base/realtime and got a 404.
The stream now answers there, scoped to <slug>.

indexer v1.5.10 reports average_block_time in milliseconds, which the page
divides by 1000. The image builds on Go 1.27, the version go.mod requires.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-22 23:41:28 -07:00
zeekayandHanzo Dev 9c49512095 run on nothing but the binary
alpine carried a shell, a package manager, libc and sqlite-libs so that a
dynamically linked binary could find them. Linking statically removes the
reason for all four: the image is the binary and the TLS roots, on
distroless static, as nonroot.

The explorer serves its frontend from //go:embed, so nothing else was ever
needed at runtime.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 23:21:12 -07:00
zeekay 38370cc0d3 build: name only the private namespace in GOPRIVATE
luxfi/* is public, so it resolves through the module proxy — which serves each
version immutably — and verifies against the checksum database under the h1:
hashes in go.sum. A tag that moves upstream therefore cannot change the bytes
this image compiles. GOPRIVATE names github.com/hanzoai/* and nothing else.
2026-08-20 08:21:59 -07:00
zeekayandHanzo Dev 8558c7d562 honour chains[].enabled — it was declared and read nowhere
`ChainConfig.Enabled` existed on the struct, was written in every chains.yaml
we ship, and was consulted by nothing. Only `Graph.Enabled` and the subgraph
flag were ever read (supervisor.go). So `enabled: false` registered the chain,
spawned its indexer and polled its RPC exactly like `enabled: true` — a switch
that looked like a switch and was wired to nothing.

Found by using it. lux-mainnet's config lists spc, which is a sovereign L1 and
has never been a chain on that network. Setting `enabled: false` and rolling
the config changed nothing: the pod still logged

    [registry] added chain spc (…/v1/bc/spc/rpc) source=config
    [spc/amm] [indexer] starting
    [evm] Failed to get latest block: http status 404

at 1,800 errors an hour, against a chain that was already switched off.

Honoured in ChainRegistry.Add, which is the single door — config, env, mDNS
and the admin API all arrive there — so this is the whole of what the field
means, in one place. A disabled chain is not registered, so it does not index,
mounts no routes, and does not reach the SPA's chain list (which reads
registry.List()).

Enabled is now *bool because absent must not mean off. Every config written
while the field was inert says `enabled: true` out of convention; a plain bool
would read a future config that simply omits the key as false and make that
chain vanish with no error and no log. Nil is no opinion; only an explicit
`enabled: false` disables.

Tests: registration gated in all three states (true / false / absent), a
disabled chain proven not to reach the supervisor or the chain list, and
LoadConfig proven to decode into the pointer — a wrong yaml tag would leave it
nil and silently re-enable everything it disables. Verified by defeating the
check: the false and supervisor cases fail, the absent case still passes.

Also: the Dockerfile now runs vet + tests in the builder stage. This repo has
exactly one workflow, release.yml, which builds on a v* tag; nothing ran the
test files at all. Putting them where cgo and sqlite-dev already exist costs
no new job, no new runner dependency, and means a release that fails them
never becomes an image.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 12:06:16 -07:00
zeekayandHanzo Dev f12932e2c1 build: drop the luxfi exceptions — graph and indexer are public now
The list named github.com/luxfi/graph and github.com/luxfi/indexer because they
were private and 404 on proxy.golang.org. Both are open source now and both
serve from the proxy, so the exceptions have nothing left to do — and every name
on a GOPRIVATE list is a module Go fetches from git and trusts unverified.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-28 17:02:22 -07:00
zeekayandHanzo Dev 7571f94150 build: mark lux-private private, not all of luxfi
GOPRIVATE switches off the module proxy AND checksum verification together.
Covering github.com/luxfi/* meant every luxfi module was fetched straight from
git and trusted without verification — which is how moved tags got captured into
module caches and served from them indefinitely, making the same commit build on
one machine and fail on another.

Nearly all of luxfi is public and on proxy.golang.org, so it belongs under
normal verification. The private org is lux-private.

Twelve luxfi repos are still private AND still 404 on the proxy, so they are
listed individually rather than by wildcard — coverage now matches what actually
needs a direct fetch, and every other luxfi module is verified against
sum.golang.org again:

  bridge broker compliance erc20-go fasthttp graph hsm indexer
  mpc ordering password pool

The right end state is those repos moving to lux-private; until then the
explicit list keeps the exemption honest instead of blanket.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-28 16:05:25 -07:00
zeekay 066a8060c6 fix(docker): resolve private luxfi modules + GHCR push via cross-org PAT
The v1.3.1 image build reached the runner (lux-build works) but failed two
ways that the local build now proves fixed:

1. Dockerfile cloned private luxfi/{indexer,graph} over unauthenticated
   https -> 'could not read Username'. Those clones were vestigial: explorer
   has no replace directive, so under -mod=mod graph v1.2.3 + indexer v1.4.5
   resolve from the module cache, not siblings. Drop the clones; add the
   node-style BuildKit-secret git auth (ghtok) + GOPRIVATE/GONOSUMDB/GONOPROXY
   before go mod download so private modules resolve.

2. GHCR push would be denied (repo-scoped GITHUB_TOKEN). Log in with the
   cross-org PAT (GH_TOKEN/UNIVERSE_PAT) and stamp
   org.opencontainers.image.source so the package stays linked.

Verified locally: docker buildx build --secret id=ghtok,... compiles the
unified FE+indexer+graph binary green (go mod download 74s, go build 98s,
image exported).
2026-06-21 15:07:07 -07:00
zeekay 3959357be3 feat(v1.3.0): drop embedded SPA — per-brand FE Deployment owns the UI
The Go binary is now a data-layer service only. The `/` catch-all that
served an embedded SPA shell is gone; v1.3.0 keeps just the endpoints
the FE depends on:

  - /v1/*       — indexer + blockscout-compat REST APIs
  - /health     — health probe
  - /envs.js    — runtime brand/chain config (window.ENV)
  - /icon.svg, /logo.svg — per-host brand SVGs (overlay-friendly)
  - /icon-{slug}.svg, /logo-{slug}.svg — extra named brand assets

Every other path returns 404 so the IngressRoute hands it off to the
per-brand Blockscout FE Deployment (ghcr.io/{brand-org}/explore-{env}).

Concrete deltas:
  - frontend.go: drop handleSPA, writeIndex, f.index; replace with
    handleNamedBrandAsset + brandAssetNames discovery
  - frontend_test.go: TestFrontendNoSPA + TestFrontendBrandAssetsServed
    replace the index-html overlay tests
  - Dockerfile: drop Next.js frontend build stage; runtime image is just
    the Go binary + brand SVGs (no embedded HTML)
  - static/index.html: removed (60-byte placeholder)

The per-brand FE Deployments + IngressRoute patches that complete this
split live in the cluster (lux-mainnet + lux-testnet namespaces). Source
of the FE images: luxfi/explore via Dockerfile.branded.
2026-06-10 13:21:01 -07:00
zeekay 73749c70f4 chore: scrub default from Dockerfile NETWORK_CURRENCY_SYMBOL
Replace the default  currency symbol +  network ID
(8675312) defaults with Lux mainnet defaults (LUX, 96369). The
explorer binary white-labels per-tenant via runtime env, not via
-specific build defaults.
2026-06-10 12:17:24 -07:00
zeekay 1e602d24e9 feat(frontend): runtime static-dir overlay + substantive SPA shell
Two related changes that get the explorer SPA rendering real chain
data without waiting for upstream luxfi/explore to ship a Next.js
static export.

1. Runtime SPA overlay (config.go, frontend.go)

   New cfg.StaticDir (also EXPLORER_STATIC_DIR env var). When set,
   the frontend prefers the on-disk copy of any static asset over the
   embedded FS — so a ConfigMap-mounted SPA shell replaces the
   embedded 60-byte placeholder without rebuilding the binary. The
   embedded FS remains the source of truth at build time and the
   fallback when no overlay is configured.

   handleSPA + serveBrandFile both check overlay > embedded. Tests
   cover overlay-present, overlay-missing-file fallback, no-overlay,
   and env-var-only configuration.

2. Substantive SPA shell (static/index.html)

   Replaced the 60-byte stub (<div id="root"></div>) with a vanilla
   JS SPA — ~7KB, zero dependencies, no React/Next.js. Reads
   window.ENV from /envs.js, fetches /v1/indexer/{slug}/{stats,blocks,
   transactions}, renders KPIs + Latest Blocks + Latest Transactions
   with per-chain switching and brand color/title overrides. Polls
   every 15s. Stores selected chain in URL hash + localStorage.

   This is the explorer's default UI until upstream luxfi/explore
   ships output: 'export' and the Dockerfile's optional pnpm-build
   overlay activates.

Dockerfile: drop the leftover merge-conflict markers around the SPA
build stage, keep the explanatory comment.

Tests: 4 new in frontend_test.go covering overlay precedence + env-var
fallback. All existing tests (registry, realtime) still pass.
2026-06-05 11:05:53 -07:00
zeekay 4e7bb24121 chore: brand-neutral cleanup — remove cross-tenant references 2026-05-25 15:13:23 -07:00
Darkhorse7starsandGitHub 296eee1f72 indexer: wire chains[].indexer.start_block + default devnet chainId (#10)
Two related fixes for running the explorer against a mainnet-fork
devnet:

1. Honor the per-chain `indexer.start_block` field from chains.yaml
   when the indexer DB is fresh. Without this the indexer always
   backfills from block 0, which on a mainnet fork is 25M+ blocks of
   irrelevant pre-fork history. Now an operator can set:

       chains:
         - slug: evm
           rpc: http://anvil:8545
           indexer:
             start_block: 25162000

   and the indexer skips to that block on first run. Once any rows
   exist in evm_blocks, start_block is ignored and indexing resumes
   from MAX(number)+1 as before — backward-compatible for
   fresh-genesis chains.

2. Default the bundled SPA's NETWORK_ID to 8675312 (the Liquid
   devnet chain ID). The Dockerfile previously inherited
   luxfi/explore's default of 8675311 — a stale leftover from before
   the devnet chainId migration. Adds NETWORK_ID / NETWORK_NAME /
   CURRENCY_SYMBOL as build-args so the same Dockerfile produces
   correct images for devnet/testnet/mainnet.

Requires luxfi/indexer #8 (StartBlock field on evm.Config) — bumped
via `go mod edit` to the post-merge commit d5e65d4b50c1.
2026-05-24 00:44:12 -07:00
zeekay 50fc1cf497 ci(docker): go build -mod=mod populates go.sum on the fly 2026-05-05 21:54:05 -07:00
zeekay 7844e67ea6 ci: ARC has no_new_privs (no sudo); GOSUMDB=off everywhere 2026-05-05 21:44:22 -07:00
zeekay b50a7e1be6 ci: regenerate go.sum at build time (proxy cache inconsistency) 2026-05-05 21:34:25 -07:00
zeekay 0a97dedfe9 unified explorer: single Go binary with embedded SPA + per-chain graph
Module renamed to github.com/luxfi/explorer (was -unified). Imports
luxfi/indexer/{daemon,evm,storage,explorer} and luxfi/graph/{engine,
indexer,storage} as libraries; embeds the luxfi/explore SPA via go:embed
all:static.

A new ChainSupervisor wraps the existing ChainRegistry: adding a chain
(via config / mDNS / admin API) spawns a per-chain indexer goroutine plus
one graph engine + indexer per enabled subgraph; removing cancels them
and frees the routes from a sync.Map dispatcher mounted on /v1/indexer/,
/v1/explorer/ (legacy alias), and /v1/graph/.

Config schema is fully per-chain: indexer poll interval, list of graph
subgraphs (each can pick a built-in schema like amm, securities, fhe…),
brand (name/coin/accent + on-disk icon/logo paths), token features, and
top-level networks list. /envs.js exposes the chain list and active
brand to the SPA at runtime, so a deploy can rebrand by remounting
chains.yaml + assets — no image rebuild.

Dockerfile clones luxfi/explore + luxfi/indexer + luxfi/graph as
siblings inside the build, populates ./static from the SPA build, and
produces one binary. CI checks out the three repos as siblings so the
local replace in go.mod works without tagged releases.
2026-05-05 21:03:52 -07:00
zeekay 7ee5150d27 feat: include graph engine (subgraph replacement) in combined image
4 repos, 4 images:
  indexer  → chain data (blocks, txs, tokens, contracts)
  graph    → subgraph events (swaps, pools, DeFi, GraphQL)
  explore  → Next.js frontend
  explorer → all three combined (this)

Single image contains 3 binaries: indexer, graph, explorer.
Each can run standalone or together.
2026-04-11 13:50:07 -07:00
zeekay cd4378e3f9 fix: use pre-built indexer image as base, layer frontend on top 2026-04-11 13:40:07 -07:00
zeekay a2b779c20c feat: combined explorer image (indexer backend + explore frontend)
Three repos, three images:
  luxfi/indexer  → ghcr.io/luxfi/indexer  (Go backend, API + indexing)
  luxfi/explore  → ghcr.io/luxfi/explore  (Next.js frontend)
  luxfi/explorer → ghcr.io/luxfi/explorer (this: combined single image)

Dockerfile pulls both repos at build time, embeds the Next.js static
export into the Go binary via go:embed, produces a single container.
2026-04-11 13:28:48 -07:00