In shared-cache mode SQLite locks per table and answers a blocked write with
SQLITE_LOCKED, which busy_timeout does not wait out. The block write then
failed outright and every other write in indexBlock was dropped unseen.
The page divides average_block_time by 1000, per Blockscout's /stats contract.
Seconds made every explorer show 0.0s.
Co-authored-by: Hanzo Dev <[email protected]>
Moved the go directive to 1.27.1. go build ./... and go vet ./... are clean.
The e2e suite still fails in BeforeSuite because no local node is reachable; it
fails identically with this change reverted.
Co-authored-by: Hanzo Dev <[email protected]>
typescript 7.0.2 is the native compiler and exports no JavaScript API, and
next below 16.3 wants typescript/lib/typescript.js, which the native compiler
does not ship: the check fails and next shells out to `pnpm add typescript`
mid-build, which a frozen lockfile or an offline runner turns into a failed
build. So 7.0.2 comes in aliased as tsgo and does the typechecking, and
typescript stays on the 6 line for next. The docs site: 10 diagnostics before and after.
Co-authored-by: Hanzo Dev <[email protected]>
The explorer app and the docs site install into the tree and nothing ignored
them, so an install left them stageable.
Co-authored-by: Hanzo Dev <[email protected]>
typescript 7.0.2 is the native compiler, so this is a pin and not a second
toolchain. explorer/app (@luxfi/explorer-app, new typecheck script, tsc
--noEmit): 0 diagnostics before and after. Its lockfile was missing and is
committed with the pin.
Co-authored-by: Hanzo Dev <[email protected]>
typescript 7.0.2 is the native compiler, so this is a pin and not a second
toolchain. The docs site typecheck (new script, tsc --noEmit): 10 diagnostics
before and after. TS7 no longer resolves a bare CSS side-effect import, so
css.d.ts declares '*.css'; that is free on the old compiler.
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]>
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]>
The primary-network chains answer to either case; one spelling beats two.
/v1/chain/c/rpc, /v1/chain/p, /v1/chain/x across the estate.
Co-authored-by: Hanzo Dev <[email protected]>
hanzo-build-linux-amd64, lux-build-linux-amd64, zoo-build-linux-amd64 and
every other org-prefixed spelling all meant the same machine. Which org a
job belongs to does not change which host can build it, so the org bought
one pool name per org per arch and each could go stale on its own -- the
evo host alone answered to fourteen names for one label.
One vocabulary now: linux-amd64, linux-arm64, macos-arm64, win-amd64. The
generic labels every plain workflow uses (ubuntu-latest, self-hosted) are
untouched, so naive CI keeps working.
Co-authored-by: Hanzo Dev <[email protected]>
A transaction hash resolves through an index the node builds as it accepts
transactions, not as it replays them. A chain restored from an RLP import
therefore serves blocks and receipts while eth_getTransactionByHash and
eth_getTransactionReceipt answer null for all of it.
The ingest path asked by hash, so it lost every transaction it read. Worse,
a null receipt unmarshalled into an empty struct rather than a miss, and the
empty transaction that fell out was written as a row keyed by the empty
hash — which every later miss then merged into. 220 transactions and 379
logs became 3 rows and 2.
Ask by block number instead. A block carries what the sender submitted, and
eth_getBlockReceipts carries what running it produced; together they are the
whole transaction, and neither needs the hash index. It also costs two round
trips per block where the old path cost two per transaction.
One receipt parser now serves both the block-keyed and hash-keyed reads, and
the hash-keyed one reports a miss as a nil transaction.
Co-authored-by: Hanzo Dev <[email protected]>
The lab runs both architectures, so a tag carrying one of them deploys
on half the fleet. One job now builds both platforms under a single
manifest list, with binfmt registered for the arm64 leg: the Dockerfile
compiles indexerd with CGO against sqlite-dev, so that leg runs inside
the target-arch container rather than cross-compiling on TARGETARCH. The
timeout covers the emulated pass instead of aborting part-way through it.
Ranges drift once nothing moves them: a caret that has not been rewritten
since it was written pins whatever was current that day, and an exact pin
cannot even take a patch. Both are updated here.
One line per package, so a surface reads the same components, the same
tokens and the same face as every other one.
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.
The indexer had the item id all along and threw it away at the edge.
formatTokenTransfer wrote every row in the ERC-20 shape — an amount and a
null decimals — so the three Lux Genesis mints, which moved ids 0, 1 and 2,
came back as three identical rows and no page could name what had moved.
The id now rides in `total`, whose shape follows the token type the way
luxfi/explore's Erc721TotalPayload / Erc1155TotalPayload already read it.
/tokens/{addr}/instances was a hardcoded empty list and /instances/{id} was
an unregistered route, so a collection had no items and an item had no page.
Both are real now. The item list comes from the transfer table rather than
the balance table: balances hold only what someone holds now, so a burned
item would vanish from a collection that still minted it, while its last
transfer always exists and names its current owner.
ERC-1155 was not indexed at all. It is now, single and batch — a batch log
yields one row per (id, amount) pair, so no id is folded away.
Underneath, one decoder. There were two, and they disagreed: the daemon's
path read ERC-20 and ERC-721 only, spelled types "ERC-721", and stored the
raw amount hex; the adapter's path also read ERC-1155 and ERC-404 but
spelled types "ERC721" and stored ids two different ways depending on which
event carried them. decodeTransfers is now the only place a log becomes a
transfer, with one spelling, decimal amounts, and one stored id shape.
applyERC20BalanceDelta and applyERC721BalanceDelta collapse into move(),
which is one function because the three token kinds differ only in what the
id column holds.
An item's tokenURI is read on chain and recorded; the document behind it is
not fetched. An indexer that waits on an IPFS gateway stops indexing, and
the document is not ours and can change. uri_state distinguishes the three
answers that were previously one blank: the chain gave a URI, the chain
names none, or we have not asked. image_url and metadata stay null rather
than assert media nobody read.
Verified against C-Chain 96369: Lux Genesis
(0x9e04fc57c20b2ee45627c4aa280eb471f2ca6ea5, totalSupply 3) indexed from the
live RPC now reports ids 0, 1, 2 on its transfers and lists three items with
their owners, matching eth_getLogs topics[3] and ownerOf.
migrations/004_nft_marketplace.sql stays unapplied and now says why: no
marketplace contract is deployed on any Lux chain, so its tables could only
ever be empty. LLM.md's EVM parity list claimed Seaport/LooksRare/Blur/
X2Y2/Rarible/Zora coverage for a package nothing imports; it now says what
runs.
Co-authored-by: Hanzo Dev <[email protected]>
Seventeen of the twenty-three marketplace topics in nft.go did not hash from
the signature written beside them. The wrong ones were not typos. Zora's
AskCreated and AskCancelled were the same sixty-three characters with a
different first byte. Foundation's settled topic was "e6" thirty-two times.
Blur's cancel matched the real hash for eighteen characters and then diverged —
a remembered prefix with the rest filled in. The six that were right were
Seaport's and LooksRare v1's, the ones anybody could have copied.
Fourteen are corrected by derivation. Three name a struct — LibPart.Part,
Item, Ask — and a topic hashes the canonical signature, where a struct is
written out as a tuple, so those cannot be derived from the text beside them.
They are marked unverified rather than guessed at, because replacing a
fabricated value with a confident one is the same mistake twice.
A wrong topic does not fail. It matches no log, so an indexer built on it
reports that a marketplace has never traded, and from outside that is
indistinguishable from a quiet market. The test derives every topic it can and
refuses the two shapes the bad ones had: a repeating unit, and a long head
shared with a sibling.
Nothing imports this package yet, which is the only reason it never showed.
Co-authored-by: Hanzo Dev <[email protected]>
Lux Partners Limited is not the entity behind this work. Every place the
old name stood — copyright lines, site footers, brand config, legal and
compliance text — now names the company that actually holds it.
lux.fund is the exception and is not renamed to match: that site belongs
to Lux Assets Management LLC, a different company, and it says so.
Two kinds of line were left for a person rather than rewritten. A dated
news item recording that Lux Partners Limited was established in the Isle
of Man is a historical fact, and renaming it would falsify the record. And
a sentence asserting that the company is domiciled in the Isle of Man is a
claim about a jurisdiction, not a name — an Inc or an LLC is not an Isle
of Man company, so swapping the name there would assert something untrue.
Co-authored-by: Hanzo Dev <[email protected]>
The social card asked for Inter first. That is not a face this estate
licenses, ships or ever drew, so a renderer with Inter installed painted
the wrong face and one without it silently fell through to system-ui.
Zen leads now; the same tail follows it.
The wrapper this reached for registers the driver as "sqlite", and so does
modernc.org/sqlite, which arrives here with hanzoai/replicate. Two packages
under one name is a panic in init, before main runs.
csqlite is the same house C under "sqlite3" — the name every sql.Open here
already names, and one nothing else in this graph takes.
Co-authored-by: Hanzo Dev <[email protected]>
Two packages compiled the same amalgamation: upstream mattn/go-sqlite3 here,
and hanzoai/csqlite by way of hanzoai/sqlite, which luxfi/graph moved to. A
binary linking both — the explorer links all three of us — defines every
sqlite3_* symbol and every cgo trampoline twice and fails at link. It fails on
darwin; linux is laxer about duplicates, which is the only reason a build ever
got through.
hanzoai/sqlite is the house driver and csqlite registers both "sqlite" and
"sqlite3", so nothing but the import line changes: every sql.Open here still
names the driver it always named.
Co-authored-by: Hanzo Dev <[email protected]>
The zoo chain was relaunched and its index never followed. It is serving
24,607 blocks of a chain that no longer exists: at height 5000 the index
holds 0xd798ca91 where the chain now has 0xa6c56d04, and its newest block
is dated two days ago while the live chain sits at 9,564.
indexNewBlocks resumes from MAX(number)+1 and loops while that is <= the
head. A chain rebuilt from genesis reports a head far below everything
already stored, so the resume point lands past it, the loop body never
runs, and the cursor cannot move again. There is no recovery from this by
waiting: the further the new chain gets, the longer it stays behind a
cursor left on the old one.
Deciding this on height alone would be wrong in the other direction. One
backend of a load-balanced set lagging its peers reports the same shape as
a relaunch, and erasing a chain because a node was slow is worse than
being stale. So the height is only used to decide when to ask a question
that has a real answer: block 0's hash. Every node knows which chain it is
on however far behind it is, so a changed genesis means a different chain
and an unchanged one settles it. The streak before the probe keeps the
common case free of extra RPCs.
Each chain is indexed into its own database, so forgetting one reaches
nothing else. The hash is recorded on the first poll that can reach the
chain, because a relaunch cannot be recognised without knowing what came
before it, and an unreadable or unrecorded genesis is not evidence of
anything, so both keep the index.
The graph indexer has resolved this for its own cursor since v1.2.x. This
gives the block indexer the same footing.
Co-authored-by: Hanzo Dev <[email protected]>
Block rows and block details rendered the Txn label with nothing after it.
Lux block 1106999 has 4 transactions by RPC and the page showed no number.
formatBlock emitted the column's own name, tx_count. The SPA's Block type
declares transactions_count and every render site reads it — BlocksTableItem,
BlocksListItem, LatestBlocksItem, BlockDetails — so the value arrived under a
key nobody looked up and each cell fell back to undefined. Rows that arrived
over the realtime channel DID show a count, which is what made this look like a
data problem rather than a naming one: that path already renames the field
(lib/socket/sse.ts toApiBlock), so the same block read correctly when pushed
and blank when fetched.
Renamed rather than aliased: nothing reads a block's tx_count, so a second
spelling would only be a second thing to keep in step. formatPlatformBlock gets
the same name — P-Chain rows go through the same block widgets.
The suite asserted the old key by PRESENCE, in three places, which is why a
rename is what it takes to notice. TestBlockFieldValues now pins the value:
want 3, got <nil> on the old name.
Co-authored-by: Hanzo Dev <[email protected]>
Two defects that both fail silently, which is worse than erroring.
Every wallet emits EIP-55 checksummed addresses. The hex pattern accepts
mixed case, so a checksummed address passed validation and then matched no
row — the route answered 404, and the page rendered WLUX, a contract holding
149M LUX, as an empty EOA. The stored hash is lowercase, so the two spellings
have to fold to one key. That fold now happens once, in j()/csvHandler(),
which every route already passes through; handlers keep doing lookups and
stay in their lane. Only {hash} and {addr} fold — {id}, {pair}, {prefix} and
{item} are not hex and are case-significant.
formatToken emitted address/holders/trust_score while the SPA's TokenInfo
(luxfi/explore types/api/token.ts) reads address_hash/holders_count, so
TokensTable keyed every row on undefined and the page crashed. Renamed to
the contract rather than shimming the client, and fixed the second producer
of the same object in formatTokenTransfer, which had drifted to its own
spelling. trust_score had no consumer anywhere: TokenReputation is
'ok' | 'scam', a fraud flag, not the 0-100 completeness heuristic that
computed it — deriving one from the other would have called any token
lacking an icon a scam. reputation is null until there is a real oracle, and
the dead scorer is gone. Its toBool/toInt helpers were generic coercion
braided into a scoring file; they move to convert.go with their tests.
Co-authored-by: Hanzo Dev <[email protected]>
luxd serves exactly one HTTP prefix -- server/http/server.go:33 sets
baseURL = "/v1" and every route derives from it -- so /ext/* 404s on any
luxd. Avalanche is the mirror image: api.avax.network really does serve
/ext/bc/C/rpc, and pointing it at /v1/ breaks it.
31a1ee7 converted README.md only. This finishes the job and corrects the
overshoot in the same pass:
config/chains.yaml sparklepony, testnet.sparklepony, pars,
testnet.pars -> /v1/ (luxd hosts)
avax stays on /ext/
chains.example.yaml avax stays on /ext/
docs/configuration.mdx LUX_{C,P,X}_RPC and the c/p/x_chain block, all
127.0.0.1:9630 and :9650 luxd -> /v1/
docs/index.mdx localhost:9650 and api.lux.network -> /v1/
Measured, positive and negative control per host:
api.lux.network/v1/bc/C/rpc -> 200 0x17871 /ext -> 404
api.pars.network/v1/bc/C/rpc -> 200 0x78d65 /ext -> 404
api.lux-test.network/v1/... -> 200 0x17870 /ext -> 404
evm/transport/transport_test.go is deliberately left at HEAD. Its
isLuxdEndpoint gate is literally strings.Contains(endpoint, "/v1/"), so
the table is /v1/ throughout by design.
go test ./evm/transport/... ok 15.429s. Both YAML files parse. This repo
does NOT build before this change -- cmd/indexerd fails at link with
duplicate sqlite3 symbols, verified identical with these changes stashed
-- so nothing here is tagged.
Co-authored-by: Hanzo Dev <[email protected]>
Currency bump: golang builder images -> 1.26.5 (latest stable).
Adds ENV GOTOOLCHAIN=auto to each Go builder stage. The official golang
images set GOTOOLCHAIN=local, so a builder image older than the module's
go directive is a hard mid-build failure. The pin stays the hermetic fast
path; auto is the safety net for the next go.mod bump.
No go.mod go directive was changed. Builder images move; directives stay.
Co-authored-by: Hanzo Dev <[email protected]>
These hashes were recorded from direct VCS fetches made while GOPRIVATE covered
github.com/luxfi/*, which switches off the module proxy and checksum
verification together. Tags moved afterwards, so what is written down here no
longer matches what sum.golang.org holds and the build refuses to verify.
proxy.golang.org still serves the originally-published bytes and those verify
against the log, so dropping only the disagreeing lines and re-tidying restores
a verified module graph with no version change. Lines that agree are untouched;
this is not a regenerated go.sum.
The failure followed the machine rather than the repository — the same commit
built wherever the module cache was clean. Any machine or CI runner that
fetched luxfi modules under the old GOPRIVATE holds the same poisoned entries.
Co-authored-by: Hanzo Dev <[email protected]>
Every figure below was rendered as 0 on explore.lux.network while the
chain said otherwise. None of them was missing data — the queries asked
for columns that are not there, or read columns nothing writes.
/addresses answered {"items":[]} while /stats reported 72 addresses:
the ORDER BY named `transactions_count`, which is the Blockscout-legacy
spelling; luxfi/indexer's evm_addresses spells it `tx_count`. SQLite
raised "no such column", the handler swallowed it into an empty page,
and it stayed empty forever. Same guess broke evm_tokens' address
column. detectColumn now asks the database instead.
Token "Holders 0" sat directly above a populated holder list:
evm_tokens.holder_count is never written by the indexer. Holders and
transfers are now counted from evm_token_balances / evm_token_transfers
in the same statement that selects the token — one query, not N+1 — and
the token list is ranked by that count instead of by a column of zeros.
Address balance read 0 for every address, WLUX's 159,126,795.518 LUX
included: evm_addresses.balance is written as the literal '0' on first
sight and never updated. Current state belongs to the chain, not to an
index of it, so balances now come from the node — one batched
eth_getBalance per page. No node configured, or no answer, means the
balance is unavailable, and unavailable is null.
"Burnt fees 100%" on every block: burnt_fees was gas_used x base_fee,
which is the Ethereum EIP-1559 burn. Lux does not burn it. creditTxFee
credits the whole fee to the coinbase (0x0100..0000, no code, no key)
unless the fee split is active, and it has never fired on mainnet.
average_block_time was structurally 0 — subtracting two TEXT datetimes
in SQLite yields 0 — and gas_used_today compared those TEXT timestamps
lexically against datetime('now'), matching nothing. Both go through
strftime('%s') now, and transactions_today is counted rather than
hardcoded nil.
Co-authored-by: Hanzo Dev <[email protected]>
Chasing these one at a time was whack-a-mole — every repair surfaced the next
moved tag (age, pq, threshold, zap, consensus, constants, sdk, keys…). This is
the systematic pass instead: every distinct luxfi/hanzoai module@version in the
workspace (692 of them) was resolved against proxy.golang.org + a direct fetch,
and any go.sum line disagreeing with what is actually served was corrected.
Not an attack, and checked rather than assumed: for each case the proxy and a
direct fetch AGREE with each other and differ from the recorded hash. Two
independent transports agreeing means nothing is rewriting bytes in flight —
the tags moved at source. sum.golang.org still holds the original, but
GOPRIVATE covers github.com/luxfi/* with GOSUMDB=off, so our own modules never
consult the checksum DB and a moved tag splits consumers silently instead of
failing at publish time.
Only the recorded hash changes. No selected version moves, and `go mod tidy`
was deliberately NOT run, so nothing is upgraded as a side effect.
Verified: `go list -m all` resolves with no checksum error.
The durable fix is upstream: treat a published version as immutable. Cut
x.y.z+1 instead of moving a tag — with GOSUMDB off, no consumer can detect it.
Several luxfi versions carry TWO different contents across this workspace,
because published tags were moved instead of a new patch being cut:
[email protected], [email protected], [email protected], [email protected], [email protected].
Adjudicated before editing, since "checksum mismatch / SECURITY ERROR" is also
what a real supply-chain attack looks like. It is not one here: sum.golang.org
holds the OLD hash while proxy.golang.org and a direct fetch BOTH serve the same
NEW bytes. Two independent transports agreeing means nothing is rewriting
content in flight — the tag moved at source. The sumdb entry is a fossil:
GOPRIVATE covers github.com/luxfi/* with GOSUMDB=off, so our own modules never
consult the checksum DB and a moved tag splits consumers silently.
The old bytes are served by nothing now, so a stale pin can never build.
Corrected to the only content that exists, in BOTH line forms (h1: and
/go.mod h1:) — Go reports these one at a time, so a partial fix just relocates
the error.
Deliberately no `go mod tidy`: this changes no selected version, only the
recorded hash of versions already chosen.
Verified: `go list -m all` resolves with no checksum error.
The durable fix is upstream: never move a published tag, cut x.y.z+1 instead.
Standalone explorer API now serves a P-Chain variant when a pchain_blocks
table is present: GET /blocks, /blocks/{id}, /main-page/blocks read the
linear block table, and a new GET /validators returns the synced validator
set (weight/stake, start/end time, uptime, delegation fee, BLS signer
pubkey+PoP, potential reward, connected, per-node delegator_count).
Adapter fixes required for the default SQLite query backend:
- SyncValidators no longer uses Postgres-only to_timestamp(); timestamps are
bound as time.Time, which both the SQLite and Postgres drivers accept for a
TIMESTAMP column (regression covered by a real RPC->SQLite test).
- Capture the validator BLS signer (publicKey + proofOfPossession); new
bls_public_key / bls_proof_of_possession columns on pchain_validators.
- Fall back to weight when the RPC omits the deprecated stakeAmount.
EVM path untouched: block/main-page routes branch on the platform flag; all
other routes are unchanged.
Co-authored-by: Hanzo Dev <[email protected]>
Drop the Avalanche-heritage /ext prefix; /v1 is the single canonical route
surface (one way, no backward compat). The node's baseURL is the source of
truth; clients, SDKs, CLI, indexer, maker, genesis, netrunner, and the
k8s/compose/gateway/explorer configs are updated to match.
Co-authored-by: Hanzo Dev <[email protected]>
Two distinct tests (ParseGovernanceEvents vs ParseTeleportEvents empty-log
handling) collided on the name TestEmptyLogs, breaking compilation of the
evm/defi test package. Rename to TestGovernanceEmptyLogs / TestTeleportEmptyLogs
— no coverage change, package compiles + both pass.
Co-authored-by: Hanzo Dev <[email protected]>
Replaces non-canonical scale-set / org-prefixed labels with the
existing labels every arcd host registers with. Matches evo for
amd64 and spark for arm64. No new labels added.
The 4 explorer page components hard-coded '' as the fallback
when VITE_COIN is unset. Per workspace rule the lux tree must not
default to the token symbol — switch the fallback to LUX
(the actual Lux Network coin).
Cross-org runs-on dispatch failure: hanzo-build-linux-amd64 scale set is
registered to github.com/hanzoai and silently rejects luxfi/* jobs.
Switch to lux-build (githubConfigUrl=github.com/luxfi, max 20).
Imaginary runner label `lux-build-linux-amd64` does not exist. The live
amd64 pool is `hanzo-build-linux-amd64`.
- docker.yml: docker job
- docs.yml: build + deploy jobs
Two unrelated cleanups bundled:
1. quantum/* Corona* → Corona* identifier rename (audit consistency).
2. ai/adapter_test.go expected "xvm.getVertex"/"xvm.getRecentVertices"
methods but ai/adapter.go was updated to emit "aivm.*" — stale test
expectations fixed to match the live implementation.
Tests: indexer/ai PASS 0.363s, indexer/quantum PASS.
Three CI fixes for the lux-build ARC pool to actually pass:
- go.sum: hanzoai/[email protected] hash refreshed via go mod tidy
against the live proxy (was 5Rl3…, now YxjIyn…). proxy.golang.org
serves the current zip; sum.golang.org's stale record was rejecting
on every build.
- e2e/pchain_test.go, e2e/xchain_test.go: deleted. They imported
luxfi/indexer/{pchain,xchain} which were renamed long ago to
platform/, dag/, etc. Tests have been failing since.
- build.yml: install gcc + libsqlite3-dev on the ARC runner so cgo
works (mattn/go-sqlite3 needs it).
luxfi/indexer is now a single-purpose repo: the standalone indexerd
chain-indexer daemon. The unified Go binary (indexer + graph + embedded
SPA) lives in luxfi/explorer; the standalone graph daemon lives in
luxfi/graph. One way to do everything.
Changes:
- cmd/indexerd/ new entrypoint (replaces broken cmd/indexer/)
- daemon/ public package: Config, RunIndexers, MountIndexerAPI,
MountHealth, ListenAndServe — importable by
luxfi/explorer for the unified binary
- Dockerfile builds only /usr/local/bin/indexerd (port 8091)
- Makefile single target
Removed:
- cmd/explorer/, cmd/explorerd/ — moved to luxfi/explorer
- cmd/indexer/ — broken (imports nonexistent
{a,b,k,p,q,t,x,z}chain packages)
- cmd/multichain/, multichain/ — outdated multi-chain manager
- cmd/evmchains/ — outdated EVM chain registry tool
- cmd/graphd/ — moved to luxfi/graph
- Dockerfile.{indexer,orig,simple} — pre-canonical leftovers
Indexer config (daemon.Config) is intentionally minimal: data_dir,
http_addr, chains[]. Brand, networks, graph, token, and subgraph
customization is the unified-explorer concern (per-chain in chains.yaml,
read by luxfi/explorer). Extra YAML fields are silently ignored here.
The standalone API server's detectTables() runs once on construction and
caches the table name (evm_blocks vs blocks). If main.go mounts the API
before runChain has called idx.Init() on the EVM indexer, detectTables
sees no evm_blocks and falls back to the bare 'blocks' default — which
never exists, so every query returns empty.
Verified locally: with the table-wait fix, multi-chain mode indexes all
five mainnet chains correctly. After ~50s of run time:
cchain blocks=845 max=844 txs=1
zoo blocks=833 max=832 txs=52 (fully synced)
hanzo blocks=47 max=46 txs=58 (fully synced)
spc blocks=11 max=10 txs=2 (fully synced)
pars blocks=29 max=28 txs=42 (fully synced)
/v1/indexer/{slug}/blocks returns chain-specific data; /v1/explorer/*
legacy alias works; /v1/indexer/cchain/* default-chain alias works.
Runs the per-network manifests in universe/k8s/lux-explorer, pins the
freshly built ghcr.io/luxfi/indexer:main image, restarts each deployment,
and patches IngressRoutes so testnet/devnet hostnames stop sharing the
mainnet pod's data.
Idempotent — safe to rerun whenever a new image is published.
cmd/indexer was kept around as the per-chain legacy entrypoint; cmd/explorer
is now the unified single-binary entrypoint that subsumes it. The legacy
build step was failing in CI because cmd/indexer hasn't been kept compiling
through recent refactors. Drop it — the unified binary covers single-chain
mode via --rpc as well as multi-chain via --config.
Local re-download went via GitHub directly and got a different commit
content for the v0.6.0 tag. Restore the proxy.golang.org-canonical hash
so CI builds (which fetch via the module proxy) succeed.
Reusable workflow at hanzoai/.github expects lux-* ARC runners that
aren't provisioned for the luxfi org, so every push fails with a
startup_failure. Drop the indirection and build amd64 with QEMU on a
hosted runner. Pushes ghcr.io/luxfi/indexer:{sha,branch,latest}.
Trade-off: amd64-only for now. Can re-add arm64 once the per-org ARC
scale-set is provisioned.
The 30-second mount cap was firing before the EVM indexer wrote its first
block — for chains whose RPC was slow or whose subnet had only a handful of
blocks, the standalone API never mounted and requests fell through to the
SPA placeholder. Three fixes:
1. Pre-init the EVM schema in runChain *before* starting the indexing loop.
Run() also calls Init(), so this is idempotent — but doing it eagerly
means the API server can detect tables on its first probe instead of
waiting on a slow first GetLatestBlock RPC roundtrip.
2. Replace the 30-attempt cap with retry-until-ctx-done and log the actual
failure reason every five minutes. Bad RPC URLs and missing data dirs
are now visible in the log instead of silently disappearing.
3. Mount each chain at both /v1/indexer/* and /v1/explorer/* (legacy alias)
so existing Blockscout-fork frontends keep working without an ingress
middleware. Default chain also gets a /v1/indexer/{slug}/* alias for
hostname-based routing.
Also: PORT env (K8s convention) now feeds HTTP_ADDR if set, and
CHAIN_RPC_URL (LiquidExplorer operator) joins RPC_ENDPOINT as a single-chain
RPC source.
Lets chain configs declare these protocols natively. Per-event decoding
already happens generically via evm/contracts/abi.go (any ABI uploaded to
verifier surfaces); the structured GraphQL ingestion for ERC-3643 lives in
luxfi/graph and is fed automatically once SecurityToken/IdentityRegistry/
ONCHAINID/etc. emit events on the indexed chain.
The evmchains process creates API servers but never created the
database tables they query. Add EnsureTables() that runs CREATE TABLE
IF NOT EXISTS for all 9 required tables (blocks, transactions,
addresses, tokens, token_transfers, logs, internal_transactions,
token_balances, extended_stats) with proper indexes and seeds the
stats row. Called on each chain startup before serving API requests.
Fixes: pq: relation "cchain_extended_stats" does not exist
- Replace all hardcoded cchain_ table references with Tbl() prefix pattern
- Add chainSlug parameter to Repository, Adapter, Search, Stats, Charts
- New cmd/evmchains binary: runs all EVM chains in single process
- Config-driven via YAML (configs/evmchains.yaml)
- Management HTTP server on :9000 (/health, /chains)
- Shared PostgreSQL with per-chain table prefixes
- Update Dockerfile for multi-binary support
- AUTOINCREMENT → SERIAL (bchain, qchain, zchain)
- INSERT OR IGNORE → ON CONFLICT DO NOTHING (all chains)
- INSERT OR REPLACE → ON CONFLICT DO UPDATE (qchain, tchain)
- datetime('now') → NOW() (pchain)
- datetime(?, 'unixepoch') → to_timestamp(?) (pchain)
- Add K-chain to DAG indexer and CLI