Stellar Index v0.21.8
[v0.21.8] — 2026-07-31
Added
- Visuals wave 3 — /network chain economics, /divergences Δ% history,
/anomalies visual pack (spec 1.17.0). Three M-effort items off the
visuals survey, each a small backend addition + charts over it:
(1)/v1/network/throughputbuckets now carry end-of-day chain state
off each day's last ledger —fee_pool+total_coins(stroop
strings, ADR-0003) andprotocol_version— added asargMax
aggregates inside the SAME bounded partition-pruned
stellar.ledgersscan;/networkcharts daily fee burn (delta of
consecutive complete days), a total-XLM line, and protocol-upgrade
step markers on the throughput chart (via a new optional
markers/priceLinesextension to the sharedLineChart).
(2) NewGET /v1/divergence/series?pair=&reference=&days=— the Δ%
history for one (pair, reference) fromdivergence_observations,
the same(asset, quote, reference, observed_at)index scan as the
board minus theDISTINCT ON, last-observation-per-bucket
downsampled (5m/30m/2h for the whitelisted 1/7/30-day windows, ≤
~360 points;firingis bucket-wide any-breach) with the
operator's realdivergence.threshold_pcton the wire;
/divergencesplots the selected board row's history with dashed
±threshold lines plus a signed |Δ%| bar list of the current board.
(3)/v1/anomalies?include=dailyadds a per-(UTC day, reason)
freeze tally (daily:null= not requested,[]= zero freezes
— degraded ≠ zero) via a new chunk-prunedFreezeDailyReasonCounts
reader;/anomaliesrenders a reason-composition donut, a
freeze-duration histogram over the recovered events served, the
previously-fetched-never-rendereddetail.deviation_pctas a
table column, and a day×reason calendar heatmap (new token-driven
ReasonHeatmap, sequential single-hue). API minor → 1.17.0; all
three spec artifacts regenerated. - Explorer visuals — quick-win wave over data already on the wire.
Five under-visualized surfaces now chart what their pages already
fetch, reusing the shared chart system plus three new bar
primitives (web/explorer/src/components/charts/Bars.tsx:
HBarList,PairedBars,DivergingColumns): (1)/dexes/[source]
renders the full/v1/protocols/{name}bespoke DEX suite (KPI
cards, trades + unique-traders series, top-pairs multi-line,
volume-by-pair donut, window pills) via a sharedBespokeSection
with the standalone USD-volume series de-duplicated against the
page's own 90d volume panel; (2)/protocols(and every locked
category page overProtocolsIndex) charts per-protocol TVL as a
bar list with the priced/unpriced honesty split — "≥" prefix +
hatched tail +pools_priced/pools_totalon lower-bound rows, the
server'sbasisas the tooltip, and NO bar for protocols without a
TVL derivation; (3)/liquidity-poolsexpanded rows replace the
nested depth tables with per-direction slippage/depth bars (one
chart per direction — different units never share an axis) and a
reserve-composition donut valued at the pool's own mid price;
(4)/accounts/[g]gains an account-character pack: ops-by-type
bars, cumulative-USD trade area + volume-by-venue donut over the
loaded priced trades, daily in/out diverging movement counts
(counts, not cross-asset amount sums), and inbound-vs-outbound
bridge bars; (5)/lending/[pool]reserves get real
supplied-vs-borrowed, fixed-scale utilization (near-cap flagged),
and supply-vs-borrow APR bars replacing the 16px CSS strips, the
/lendingindex gets an all-time liquidation-auctions-by-pool bar,
and/yieldswaps its staticCategoryHubfor the live
ProtocolsIndex lockedCategory="yield"directory (the/bridges
pattern). - Explorer flagship visuals: SDEX cumulative depth chart + asset
visual pack./markets/[pair]and/sdexgain a mirrored
step-area cumulative depth chart with a best-bid/ask + mid/spread
stat strip over the served/v1/sdex/orderbooklevels (new
DepthChartcomponent; a crossed snapshot renders an explicit
warning instead of a negative spread, and/sdex— previously a
static brochure — now shows the live book for four headline classic
pairs plus the daily USD-volume series + KPIs from
/v1/protocols/sdex)./assets/[slug]gains the asset visual
pack: the volume-by-source donut (/v1/markets/sources?asset=,
handler + component both existed unwired), per-market 24h volume
sparklines in the Markets tab (?include=sparkline), a live
1h/24h/7d/30d change strip + streak + honestly-labelled
"low since indexed" chip from the previously consumer-less
/v1/changes/coin/{id}endpoint (absent windows render "—"), a
holders concentration donut explicitly scoped to the served top-100
rows, and mint/burn/clawback comparison bars in the Supply tab.
Also fixes the/markets/[pair]build-time/v1/chartfetch
discarding ~95% of its series (now rendered as a 24h VWAP sparkline- summed 24h volume stat) and the OrderBookPanel test's cross-test
mock leak.
- summed 24h volume stat) and the OrderBookPanel test's cross-test
- Protocol analytics are warm by construction, and degradation is
now explicit on the wire (spec 1.16.0). Under replay load every
on-demand/v1/protocols/{name}bespoke build died at the request
deadline ("protocol bespoke build failed", 2026-07-31), pages lost
their visual suites, and the omitted block was indistinguishable
from a protocol with no data. Three changes: (1) a dedicated API
prewarm worker sweeps ALL registry protocols ×?days=windows
(one build at a time, 2 s pause, re-swept 10 min after each sweep
ends — sized from r1 timings under replay: soroswap 90d bespoke
~1.9 s, cctp ~0.4 s) so every protocol page + window is built
before anyone asks, with the detail cache now stale-serving past
its (raised, 20 min) TTL — a previously-built view is served with
flags.stalewhile ONE detached rebuild (own 90 s budget, never a
request deadline) refreshes it, and a failed/timed-out rebuild
keeps the old entry instead of blanking the block; (2) a new
analytics: {status: ok|stale|unavailable, as_of}object on the
detail view so clients can tell fresh / stale-but-served / degraded
apart from real zeros — the explorer renders an explicit
"temporarily unavailable" hint from the status instead of inferring
from field absence; (3) the/v1/operationsop-type panel joins
the API's 5-minute prewarm loop (it had SWR + detached refresh but
nothing warmed it at boot, so the first directory hit after every
deploy rendered without it; the aggregate measured ~70 ms warm on
r1). New paired metrics
stellarindex_protocol_detail_refresh_{total,duration_seconds}
(ok|degraded|timeout) cover the prewarm sweep and request-kicked
revalidations;/v1/assets/{id}/holderswas audited as already
stale-serving (never hard-misses a previously-seen asset).
Fixed
-
stellarindex_dex_trade_unit_ratio_detectedalerts on the FRACTION
of a source's flow (>50% AND >25 count), not absolute count — a busy
wrapper-redemption morning (yUSDC/USDC, yETH/ETH parity pairs) tripped
the old threshold at ~2% of sdex flow; the decode-bug fingerprint the
alert exists for makes EVERY trade 1:1. Both rule trees + runbook. -
soroswap: non-directional swaps are recognized no-ops, not decode
errors — closes the last soroswap undecodable-but-matched blind
ledger (57,403,300). The lake disproves the "a well-formed swap
has exactly one in/out pair non-zero" assumption:pair.swap()is
directly invokable (Uniswap-v2-style) and accepts any argument
combination keeping K non-decreasing, so a swap can settle with all
value movement confined to ONE token side (the real case: pair
CAM7DY…, tx be7028b9…,amount_1_in=265, amount_1_out=70, both
token0 amounts zero). That is a real, recognized on-chain event but
NOT a trade — no (base, quote, price) exists — sodecodeSwapnow
surfacesErrNonDirectionalSwapand the Decoder projects zero rows
with nil error (same recognized-no-op contract as redstone's empty
write_pricesbatches), flipping the blind ledger to verifiable
expected-zero under the ADR-0033 honest-blind accounting. Golden
test pins the real lake bytes. Alongside,classify()now
enumerates the pair WASM's LP-share SEP-41 token events
(transfer/mint/burn/approve, lake-verified: 1,622/907/333/0
across all 230 registered pairs) asEventPairToken— classified
per the EVERY-event principle but deliberately NOT claimed by
Matches(): they are the sep41 domain, and the dispatcher is
first-match-wins, so claiming them would silently swallow any
LP-share token later added towatched_sep41_contracts. -
/v1/sdex/orderbookserved CROSSED books — zombie offers dead
since 2021 quarantined out and lake-verified. The live XLM/USDC
book quoted best bid 0.4327 vs best ask 0.1722 and carried ~46k
bids / ~40k asks where the real pair book is a few hundred offers.
Root cause: historical entry-change backfill wrote
intra_ledger_seq = 0on every row, so all same-ledger changes to
one offer key TIE onledger_entries_current's ReplacingMergeTree
version and an arbitrary row survives the merge — an offer updated
then fully consumed within one ledger can survive asupdated
forever (founding zombies: offers 845025288 / 845025425 / 845025699
/ 845028065, consumed at ledger 38,224,736± on 2021-11-10, still
"live" on 2026-07-31; their losingremovedrows are physically
gone fromledger_entries_currentbut intact in
ledger_entry_changes). Class fix in the book maintainer: loaded
offers whose winning version carries intra 0 (the tie-ambiguous
class) are quarantined out of the served book and drained at
2,500/tick by a new batched, partition-pruned
OfferRemovedAt(ledger, key)probe against the change stream —
proven-dead offers are dropped for good (offer LedgerKeys are never
reused, so a same-ledger removal is conclusive), proven-live ones
graduate back in (~1.5 ms/key measured on r1; a fully zombie-laden
book converges in hours while serving honestly from the first
snapshot). New gaugesstellarindex_sdex_orderbook_crossed_pairs
(the invariant tripwire — a resting book can never be crossed
on-chain; sustained non-zero now means a removal the lake never
ingested, i.e. a coverage gap) and
stellarindex_sdex_orderbook_pending_offers(quarantine depth),
plusverify_ok|verify_erroroutcomes on the existing maintain
metrics. -
Six defects from the 2026-07-31 visuals survey. (1) The
/dexes/*90d volume panel silently rendered null on all five DEX
pages:SourceVolumeHistorylooked up series'Daily USD volume'
but the backend emits"USD volume"(bespoke_dex.go
dexSeriesVolume) — and its test mocked the wrong name, green-
lighting the blank. Now matched case-insensitively by substring
(grouped"Top pairs · …"names excluded) with the test pinned to
the real wire name. (2)/network's throughput chart plotted
today's still-accumulatingpartialbucket — a phantom end-of-
series cliff daily — despite the API contract saying to exclude it;
the series now drops it likeNetworkInsight(UXP-16) always did.
(3) OpenAPI drift (spec 1.16.1):Source.volume_history_24h[]/
volume_history_7d[]items were missing thetrade_countfield
the server has always emitted (sources.goVolumeBucket) and the
venue chart consumes — added to the spec + all three generated
artifacts. (4)/issuers/[g]scaled circulating supply with a
hardcoded/1e7— wrong for any non-7-decimals SEP-41 asset; now
10 ** decimalsfrom the sameAssetDetailrow. (5)/convert
carried a declared-but-never-populateddetail.source/
published_atpair whose "Source: … published …" line could never
render — dead fields + dead JSX removed. (6)/aggregators'
routed-volume table filtered tokind === 'router', silently
dropping everyaggregator-vaultrow the endpoint returns — all
registry rows now render with a Kind column; vault rows show an
honest "n/a — holds capital, doesn't route trades" instead of the
endpoint's placeholder zeros. -
Explorer honesty sweep: absent API data no longer renders as a
zero or a "no data exists" claim. The API omits analytics blocks
when a reader degrades (omitempty— e.g. the protocol roster's
per-contract fill missing the lake budget,op_type_statsabsent
on a cold SWR load,?include=statssoft-failing wholesale), but
several explorer panels coerced that absence into false empirical
claims. Now absent renders an honest unavailable state ('—' inline;
a muted "unavailable — refreshing" note for panels) while
present-and-empty keeps the genuine empty copy: the protocol
contract-roster Events column ('0 events' per contract),
/network+/operationsoperation-mix ("No operations in the
last 24h"),/networkvolume-by-venue ("No on-chain volume in the
last 24h"),/exchangesheadline + per-row 24h stats
("$0 across 0 trades"),/dexesper-protocol trades/pools
columns, the/protocolsdirectory's static fallback cards
("0 events · 24h" when the API is unreachable), and
/diagnostics' cross-anchor archive badge (green "complete" when
the scan block was absent). Regression tests pin absent ≠ empty for
the operation-mix panel. -
/v1/assets/native/holdersnow serves the real XLM holders board
instead ofholder_count: 0. The holders read was trustline-shaped
and native XLM has no trustlines — every account holds XLM in its
AccountEntry balance — so the native board was empty BY CONSTRUCTION
while every issued asset's worked.AssetHoldersgains a native arm
that ranks the lake's account range (a primary-index range read —
entry_typeis the first ORDER BY column; measured on r1: 2.4s
ranking + 2.1s exact funded-account count under the standard scan
pin, served through the existing holders SWR cache, never on a
request deadline once warm). The handler folds XLM's alias forms
(crypto:XLM, theXLMshorthand — thenative ↔ crypto:XLM
dual-form rule) to the onenativeboard key so all spellings share
a single cache entry; the SAC C-address deliberately keeps its own
(contract-balance) domain. The explorer's holders panel no longer
blames the trustline backfill for an empty native board — that copy
was false for XLM; issued assets keep it (there it's accurate). -
Redstone's last 15 provably-ambiguous blind ledgers now attribute
exactly, via ledger-entry state-write keys.events.Eventgains
StateWriteKeys— the LedgerKeys of the contract-data entries whose
VALUE the event's operation changed — populated by the dispatcher
from LCM tx meta and by the ClickHouse readers from the lake's
ledger_entry_changes(batched point lookups; per-source opt-in
likeOpArgs, only redstone opts in). Ground truth (r1 ledger
62056824):write_pricesrewrites every REQUESTED feed's entry
byte-identical when the freshness verifier rejects it, and only
ACCEPTED feeds' stored PriceData changes — so the value-changed keys
name the accepted subset with zero heuristics, resolving the class
where one surviving price matches two feeds' payload medians (the
BENJI/iBENJI twins) that order-preserving median alignment provably
cannot. Payload-median alignment stays as the fallback when keys are
absent, andErrAmbiguousSubsetstill refuses anything unresolved —
keys can cause a fallback, never a misattribution. Golden fixture
from the real ledger-62056824 event + change rows pins the exact
path, the keys-absent ambiguity, and the arity-mismatch fallback.