Skip to content

Stellar Index v0.21.8

Choose a tag to compare

@github-actions github-actions released this 31 Jul 20:50

[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/throughput buckets now carry end-of-day chain state
    off each day's last ledger — fee_pool + total_coins (stroop
    strings, ADR-0003) and protocol_version — added as argMax
    aggregates inside the SAME bounded partition-pruned
    stellar.ledgers scan; /network charts 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/priceLines extension to the shared LineChart).
    (2) New GET /v1/divergence/series?pair=&reference=&days= — the Δ%
    history for one (pair, reference) from divergence_observations,
    the same (asset, quote, reference, observed_at) index scan as the
    board minus the DISTINCT ON, last-observation-per-bucket
    downsampled (5m/30m/2h for the whitelisted 1/7/30-day windows, ≤
    ~360 points; firing is bucket-wide any-breach) with the
    operator's real divergence.threshold_pct on the wire;
    /divergences plots the selected board row's history with dashed
    ±threshold lines plus a signed |Δ%| bar list of the current board.
    (3) /v1/anomalies?include=daily adds a per-(UTC day, reason)
    freeze tally (daily: null = not requested, [] = zero freezes
    — degraded ≠ zero) via a new chunk-pruned FreezeDailyReasonCounts
    reader; /anomalies renders a reason-composition donut, a
    freeze-duration histogram over the recovered events served, the
    previously-fetched-never-rendered detail.deviation_pct as 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 shared BespokeSection
    with the standalone USD-volume series de-duplicated against the
    page's own 90d volume panel; (2) /protocols (and every locked
    category page over ProtocolsIndex) charts per-protocol TVL as a
    bar list with the priced/unpriced honesty split — "≥" prefix +
    hatched tail + pools_priced/pools_total on lower-bound rows, the
    server's basis as the tooltip, and NO bar for protocols without a
    TVL derivation; (3) /liquidity-pools expanded 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
    /lending index gets an all-time liquidation-auctions-by-pool bar,
    and /yield swaps its static CategoryHub for the live
    ProtocolsIndex lockedCategory="yield" directory (the /bridges
    pattern).
  • Explorer flagship visuals: SDEX cumulative depth chart + asset
    visual pack.
    /markets/[pair] and /sdex gain a mirrored
    step-area cumulative depth chart with a best-bid/ask + mid/spread
    stat strip over the served /v1/sdex/orderbook levels (new
    DepthChart component; 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/chart fetch
    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.
  • 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.stale while 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/operations op-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}/holders was audited as already
    stale-serving (never hard-misses a previously-seen asset).

Fixed

  • stellarindex_dex_trade_unit_ratio_detected alerts 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 — so decodeSwap now
    surfaces ErrNonDirectionalSwap and the Decoder projects zero rows
    with nil error (same recognized-no-op contract as redstone's empty
    write_prices batches), 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) as EventPairToken — 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 to watched_sep41_contracts.

  • /v1/sdex/orderbook served 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 = 0 on every row, so all same-ledger changes to
    one offer key TIE on ledger_entries_current's ReplacingMergeTree
    version and an arbitrary row survives the merge — an offer updated
    then fully consumed within one ledger can survive as updated
    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 losing removed rows are physically
    gone from ledger_entries_current but 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 gauges stellarindex_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),
    plus verify_ok|verify_error outcomes 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: SourceVolumeHistory looked 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-accumulating partial bucket — a phantom end-of-
    series cliff daily — despite the API contract saying to exclude it;
    the series now drops it like NetworkInsight (UXP-16) always did.
    (3) OpenAPI drift (spec 1.16.1): Source.volume_history_24h[] /
    volume_history_7d[] items were missing the trade_count field
    the server has always emitted (sources.go VolumeBucket) 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 ** decimals from the same AssetDetail row. (5) /convert
    carried a declared-but-never-populated detail.source /
    published_at pair whose "Source: … published …" line could never
    render — dead fields + dead JSX removed. (6) /aggregators'
    routed-volume table filtered to kind === 'router', silently
    dropping every aggregator-vault row 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_stats absent
    on a cold SWR load, ?include=stats soft-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 + /operations operation-mix ("No operations in the
    last 24h"), /network volume-by-venue ("No on-chain volume in the
    last 24h"), /exchanges headline + per-row 24h stats
    ("$0 across 0 trades"), /dexes per-protocol trades/pools
    columns, the /protocols directory'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/holders now serves the real XLM holders board
    instead of holder_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. AssetHolders gains a native arm
    that ranks the lake's account range (a primary-index range read —
    entry_type is 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, the XLM shorthand — the native ↔ crypto:XLM
    dual-form rule) to the one native board 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.Event gains
    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
    like OpArgs, only redstone opts in). Ground truth (r1 ledger
    62056824): write_prices rewrites 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, and ErrAmbiguousSubset still 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.