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]>
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]>
luxfi/indexer v1.5.6 asked eth_getTransactionReceipt once per transaction and
dropped the transaction on any error — `if err != nil || tx == nil { continue }`,
twice over, with nothing logged. Measured on lux mainnet 96369: block 2 carries
571 transactions, the node serves all 571 and all 571 receipts, and the store
holds 562. The nine that vanished are transactionIndex 407..415, one contiguous
run in the middle of 571 back-to-back calls against one node.
v1.5.8 reads eth_getBlockByNumber(full) plus eth_getBlockReceipts — two round
trips per block where this cost two per transaction — so a receipt read that
fails takes the execution result with it and leaves the transaction, and a
transaction can no longer disappear because the node was busy.
The release job also asked for a runner label nothing carries. Every runner this
forge spawns registers `linux-amd64`; `lux-build-linux-amd64` has never existed,
and a job naming a label no runner has does not fail, it waits — which is why
1.5.49 is the newest image.
Co-authored-by: Hanzo Dev <[email protected]>
The swaps index on pool was BINARY and the read compares NOCASE, so a pool's
trades were a scan of every swap on the chain — seconds per pool. The index now
matches the comparison and carries the timestamp.
Co-authored-by: Hanzo Dev <[email protected]>
A token showed $74,100 of volume over zero transactions, and the protocol claimed
16,062 trades above pools holding 14,827. The counts were tallied as events
arrived while the volume had become a fact about the whole table; they are folds
of the same read now.
Co-authored-by: Hanzo Dev <[email protected]>
Valuing the history fixed the protocol's total and left every pool reporting the
window a pass had just read — $206,718 above a list of pools summing to $4,159.
One grouped read of the swap table now answers both.
Co-authored-by: Hanzo Dev <[email protected]>
This chain holds 1,018,429 trades and had priced the newest 50,000. The rest
were stored at zero, so the all-time total summed them as worthless and every
day before that window was missing from every chart. A pass now walks forward
from a mark as well as reading the head, and covers the table a batch at a time.
Co-authored-by: Hanzo Dev <[email protected]>
v1.2.47 summed the day series, whose rows are folded from the same bounded
window a pass reads — so the total was that window again. The value of a trade
is a column now and the total is one scan of it.
Co-authored-by: Hanzo Dev <[email protected]>
The protocol reported the volume of its newest 50,000 swaps as its total. With a
million swaps behind this chain that was the last twelve percent. It now sums
the day series, which is written once per day and kept.
Co-authored-by: Hanzo Dev <[email protected]>
The allow-list named Content-Type and Authorization, and a browser refuses the
whole request over one header missing from it. The exchange sends x-api-key, so
/v1/quote and /v1/swappable_tokens both failed their preflight and the swap
panel had neither a token list nor a price. A literal "*" would not have helped:
Safari does not honour the wildcard here.
Carries graph v1.2.46, which does the same at the graph's own server.
Co-authored-by: Hanzo Dev <[email protected]>
The valuation pass reads every token into memory at the top and writes those
copies back near the end; the native supply publish landed in between and was
overwritten every pass. Zoo's two trillion reported as the fifteen billion
sitting in its wrapper, so its market cap and fully diluted value were both
wrong by a factor of 133 while the log said "2000000000000 minted" every thirty
seconds.
Co-authored-by: Hanzo Dev <[email protected]>
Asking for one pool's last N trades read the N newest on the whole chain and
filtered what the limit had already discarded, so every pool but the busiest
answered with nothing.
Co-authored-by: Hanzo Dev <[email protected]>
v1.2.42 dated the first 50,000 rows and stopped, reading "this window has no
failures left" as "there is nothing left". 862,492 trades stayed dated to 1970.
Co-authored-by: Hanzo Dev <[email protected]>
Every pool's trade history is in this store already; the pass that dates it
looked for undated rows among the newest, where a row holding a block number
never sorts. Seventeen pools' worth of swaps become visible on the token pages
without reindexing anything.
Co-authored-by: Hanzo Dev <[email protected]>
graph v1.2.41 offers only what a pool holds, plus the chain's own coin under the
zero address. Both facts are per chain and already declared here, so pass them
rather than let the list guess.
Co-authored-by: Hanzo Dev <[email protected]>
A quote states the chain it came from, and the dispatcher read that last. A
query parameter, or a chainId sitting beside the quote rather than inside it,
could send someone's quote to another chain's router — and these chains share
deployment addresses, so the same numbers name real pools on the destination.
The narrowest statement wins now: the quote's own chain, then the body, then
the URL. The handler refuses a foreign quote as well, so neither layer is load
bearing alone.
Whether a chain can build a swap is one question asked in one place. Both the
router and the wrapped native become a twenty-byte argument, so a blank or
malformed one produced a call whose arguments had slid; a chain missing either
serves no swap route rather than an unsignable one.
A chain may declare more than one subgraph holding a book, each in its own
goroutine, so the book behind the unprefixed routes was whichever registered
last — a different one across restarts. The config names it now.
The dispatcher buffered ten times what the handlers will read, and called an
oversized body malformed JSON. It reads their limit and says which failure it
was.
Co-authored-by: Hanzo Dev <[email protected]>
The swap form calls /v1/quote and /v1/swappable_tokens, not
/v1/graph/{chain}/{subgraph}/quote. Only the prefixed form existed here, so a
TypeScript service stayed in front of this one to serve the unprefixed paths,
and the two answered the same questions from two copies of the book.
A swap request already says which chain it is for — a quote names the chain of
each token, an approval names its chain, a swap carries the quote it came from.
So the chain comes out of the request rather than the path, and the request is
handed to the mux that chain's subgraph already registered. The handler values
are the ones the prefixed routes use; nothing is built twice.
Swap and approval need this chain's router. Without one configured there is no
address to address a transaction to, so those two routes are absent rather than
serving calldata signed against nothing.
The subgraph that backs a chain's swap routes is the one holding an AMM book.
Picking whichever subgraph started first would have been a race, and a CLOB
schema has no pools to price against.
Co-authored-by: Hanzo Dev <[email protected]>
Two questions a swap form asks that GraphQL does not answer in a shape a form
can use: what can I trade here, and what do I get for this. Both read the store
each subgraph already fills, so they mount beside its /graphql rather than
behind a second service keeping a second copy of the book.
quoter_v2 is per chain because the periphery quoter is deployed per chain. With
it set the chain prices a V3 route directly; without it the quote falls back to
the closed form, which is exact for V2 and an estimate for V3.
Co-authored-by: Hanzo Dev <[email protected]>
Market cap and fully diluted value printed a dash on every token page. The
supply behind both was never read for a token already indexed: a real symbol
was taken as proof the row was finished. graph v1.2.31 asks what is actually
missing, keeps every field it already had, and reports a supply in whole
tokens whatever the source. Asking is what a start does now, so the flag that
used to gate it is gone.
The same pin brings the sqlite the whole process opens under one name.
csqlite is the house amalgamation, registered "sqlite3"; upstream mattn
compiled a second copy of that same C and the link failed on darwin, while
the hanzoai/sqlite wrapper takes "sqlite" — already modernc's, which arrives
with hanzoai/replicate — and panics in init. One C, three packages, no
argument.
Co-authored-by: Hanzo Dev <[email protected]>
Supply cannot be read from a node — the genesis alloc is not exposed and summing
every account is not a query — so the mint is declared per chain beside the
factory addresses. The treasury address is declared too, and its balance read
live, because that is the number that falls as tokens reach holders.
Co-authored-by: Hanzo Dev <[email protected]>
The valuation line now says how many trades it priced, so a write that changes
no rows is visible instead of silent.
Co-authored-by: Hanzo Dev <[email protected]>
Pool pages showed volume over trades that each reported nothing. The indexer
computed each trade's worth and dropped it; it now writes it, batched, so a
swap list adds up to the total printed above it.
Co-authored-by: Hanzo Dev <[email protected]>
Zoo relaunched and its index stayed on the chain that was replaced, serving
24,607 blocks of it while the live chain sat at 9,564. The block indexer
resumed from a cursor left above the new head, so it had no way back on its
own. v1.5.4 reads block 0's hash to tell a rebuilt chain from a slow node,
and starts over only for the former.
Every chain in chains.yaml is indexed by this binary, so each of them was
exposed to it; zoo is the one it happened to.
Co-authored-by: Hanzo Dev <[email protected]>
parseTopFields split top-level fields on newlines, so a minified document was
answered with its first field alone and no error. The explore page asks for its
bundle, factory, tokens, pools and day series in one document.
Co-authored-by: Hanzo Dev <[email protected]>
Every price chart read "Missing chart data": tokenDayDatas, poolDayDatas
and uniswapDayDatas each answered [] from a stub, and the client draws
nothing under three points. They now carry a day per subject rolled out of
the trades already indexed, so the history appears at once rather than
starting today. Swap timestamps become the block's time — they held the
block number, which is also why no day could be computed from them.
Co-authored-by: Hanzo Dev <[email protected]>
Removes the indexer's Lux-mainnet fallback for FactoryV2/FactoryV3/
Native, which every chain but Lux was silently inheriting, and adds ZUSD
to the stable anchors so Zoo's book prices in dollars instead of
reporting 0.00.
Co-authored-by: Hanzo Dev <[email protected]>
v1.2.15 publishes what the valuation pass had always computed: bundle
ethPriceUSD and per-token derivedETH. Without them every market on
lux.exchange and the explorer showed real TVL and volume beside a blank price
and no market cap.
Co-authored-by: Hanzo Dev <[email protected]>
One binary answers explore.lux.network, explore.hanzo.network and
explore.zoo.network. Only /envs.js and the slug-prefixed /v1/indexer/{slug}
routes knew that. Everything else read whichever chain the config called
default, so Hanzo's home page printed 1,107,383 total blocks above its own
Latest blocks list topping out at 29,805 — Lux's number, on Hanzo's page,
overstating Hanzo's chain 37x and Zoo's 49x. Measured on all three brands.
Config.Serves(host) is now the one thing that answers "which chain is this
request about", and the three surfaces that were guessing ask it:
* StatsService keeps a read handle per chain instead of one pinned at
startup, so /v1/counters, /v1/lines, /v1/pages/* and /v1/stats report the
host's chain: 1107259 / 29805 / 22728 on the three brand hosts, where all
three read 1107259 before.
* The realtime stream carries the host's chain. It carried all of them: a
45s read of /v1/base/realtime returned the same 6 cchain + 14 hanzo + 5 zoo
events on every brand host, and the SPA appends what it is sent, so a
50-block page rendered 63 rows with 2 Lux on top and 8 Zoo at the bottom.
?chain= still overrides; an empty filter no longer means "all chains",
because no brand host wants another brand's blocks.
* A slug-less /v1/indexer/* request follows the host, falling back to the
default only when the host names no chain (api-explore.lux.network) or
names one that is not up.
Also here, because they were all the same mistake about the same column: the
timestamp in evm_blocks is a Go time.Time bound into a column declared
TIMESTAMP, so SQLite stores a datetime STRING, and everything that read it as
a unix epoch got nothing and said nothing.
* /stats' daily charts were empty on all three brands. strftime with the
'unixepoch' modifier returns NULL on a datetime string, the scan failed,
and handlePagesMain discarded the error into `daily, _ :=` — a nil slice
marshals to `null`, which the page renders as "No data". Now 22 points.
* Average block time read 0.0s on a chain producing a block every 10s: the
three-value Scan failed and its error was never checked, so the zero went
out as the answer. Now 60.0 on a one-block-per-minute fixture.
* /v1/blocks reported `"timestamp": 2026` for every block — CAST of a
datetime string takes its leading digits, and 2026 looks like a number
rather than a failure. Now a real epoch.
* Transactions (24h) was the lifetime total. SQLite orders every INTEGER
below every TEXT, so `timestamp > <epoch>` was true for all 1.1M rows.
blockEpoch and blockDay are the only two spellings allowed to touch that
column now. The fixtures were the reason none of this failed a test: they
declared the column TIMESTAMP and then inserted integers into it, so the suite
exercised a database the indexer never writes. They bind a time.Time now, and
on the old queries they fail with exactly what production showed —
average_block_time "0.0", empty charts, timestamp 2026.
Deleted: the eight per-channel SSE routes at bare /blocks, /transactions,
/tokens, /gas-tracker, /stats, /validators, /token-transfers and /internal-txs.
Every one of those is also an SPA page path, and one mux cannot serve both; the
SPA opens only the multiplexed stream, so they had no subscriber to lose. With
them goes the second envelope, the per-channel framing built on every
broadcast, and the client's channel field — SSE has one endpoint and one shape.
indexer v1.5.3 for the block transaction-count name.
Co-authored-by: Hanzo Dev <[email protected]>
Picks up the two API fixes. A checksummed address (what every wallet emits)
404'd and rendered a 149M-LUX contract as an empty EOA; /tokens emitted
address/holders where the SPA reads address_hash/holders_count, crashing the
page on an undefined row key.
Co-authored-by: Hanzo Dev <[email protected]>
The dex subgraph answered `{"data":{"markets":null}}` on a 200 while being
mounted, subscribed to the 0x9999 DEXFill topic and caught up to the chain
head. A GraphQL list field answering null reads as "not deployed / erroring",
so a DEX that had simply seen no fills was indistinguishable from a dead pipe.
luxfi/graph v1.2.13 makes an empty result an empty list.
CI moves to .hanzo/workflows/release.yml on the native git.hanzo.ai runners,
matching luxfi/graph. .github/workflows/docker.yml ran on `runs-on: lux-build`,
an ARC pool that no longer exists — the clusters have no ARC CRDs and
github.com/luxfi reports zero runners at org and repo scope, so every v* tag
queued a job nothing could pick up and waited instead of failing. That is why
v1.5.4 is a tag with no image. One CI path, one that builds.
LLM.md/CLAUDE.md become local-only, per the org convention.
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]>
indexer v1.4.6 -> v1.4.7 and pass the chain's RPC to the standalone API, so
native-coin balances are read from the node rather than from
evm_addresses.balance — a column the indexer writes as the literal '0' on
first sight and never updates. Every address page reported a balance of
zero, WLUX's 159,126,795.518 LUX included. v1.4.7 also counts token holders
and transfers from the rows that hold them, stops calling the base fee
"burnt" on a chain that credits the whole fee to the coinbase, and returns
addresses on the evm_* schema instead of an empty page.
The network switcher advertised testnet.explore.lux.network and
devnet.explore.lux.network. Neither resolves; the live hosts are
explorer.lux-{test,dev}.network. networksForHost already derives sibling
hostnames from the request host and derives them correctly, so the
switcher now takes its domains from there and config keeps only what
config knows — the label and the chain ID. Devnet's was wrong too:
96370, where the devnet genesis cChainGenesis.config.chainId and a live
eth_chainId both say 96367.
Co-authored-by: Hanzo Dev <[email protected]>
v1.2.11 stopped the pass writing a row per swap; v1.2.12 stops it reading the
whole table too. The C-Chain's AMM swap history spans a million blocks in a
732 MB store, so an unbounded rescan every 30s grows with chain age forever.
Co-authored-by: Hanzo Dev <[email protected]>
v1.5.1 in the cluster showed one chain's valuation pass reporting and two
others silent — and the log gave no way to tell which chain any line belonged
to, because the graph indexer was the one component here that did not prefix
its output. Config.Label (new in graph v1.2.11) fixes that: pass the same
"slug/name" this supervisor already uses everywhere else.
v1.2.11 also stops the valuation pass writing a row per swap (which is what
wedged the busy chains) and reads V3 swap amounts as the int256 they are.
Co-authored-by: Hanzo Dev <[email protected]>
v1.2.10 adds the valuation pass that derives the AMM graph's USD aggregates
from on-chain balances. Without it the explorer's /v1/graph/<chain>/amm/graphql
served pools and tokens whose totalValueLockedUSD, volumeUSD and token0Price
were empty strings — which is what left every TVL / 24h-volume / active-pool
figure on the exchange blank.
Co-authored-by: Hanzo Dev <[email protected]>
graph v1.2.9 enriches the 0x9999 DEX Market with a human pair symbol + assetsBound
so the exchange-api real-asset gate admits native markets (was stripping all of
them on the poolId-hex symbol). Pulls luxfi/database v1.19.3 transitively. go.sum
regenerated for luxfi modules (upstream re-tag staleness on age/pq).