Skip to content

Stellar Index v0.7.2

Choose a tag to compare

@github-actions github-actions released this 03 Jul 18:10

[v0.7.2] — 2026-07-03

Added

  • ADR-0044 (Proposed): explorer rendering moves to edge SSR — the
    2026-07-03 site audit traced three production failure classes (the
    silent 20k-file deploy freeze, bake-time poisoned pages, between-deploy
    staleness) to rendering at build time; OpenNext on Cloudflare Workers
    with per-route-family edge caching replaces static export, with a
    staged migration and the pruned static path supported until cutover.

Fixed

  • Three site-audit P0/P1 fixes in one pass (S-006/S-009/crawl):
    (a) contract pages now identify uncaptured-instance SACs from their
    CAP-67 event topics with a spoof-proof derivation cross-check — ~55k
    contracts (e.g. the upvoteICE SAC) stop rendering an unexplained void
    and instead say which asset's SAC they are; (b) the API's
    trailing-slash 308 now runs INSIDE the CORS middleware — it previously
    carried no Access-Control-Allow-Origin, so browsers killed the
    redirect and every trailing-slash API URL was as dead as the 404 the
    redirect exists to prevent; (c) market-pair pages no longer canonical
    to double-encoded 404 URLs (the route param arrives pre-encoded;
    ~500 pages were telling crawlers their real URL was a dead page).
  • The /assets listing serves the real universe (site audit
    S-002/S-011): page 1 of the unified listing now fills from the ~191K
    classic long tail when the curated catalogue is shorter than the limit
    (the pager's own doc always promised this; in practice page 1 was 11
    rows presented as the entire asset universe), and the search box's q=
    now actually filters server-side across both phases (the storage layer
    supported it all along — the handler never passed it).
  • Navigation tells the truth (site audit S-001/S-017/S-019): "DEX /
    AMM" now lands on /dexes (the per-protocol venue + pools view that
    already existed) instead of the verification index; the rail's
    single-protocol "Soroswap Router" entry becomes "Verification" (the whole
    15-protocol index); the search palette's "Account" entry points at
    /dashboard instead of a route that never existed.
  • Impersonated identities no longer render on flagged issuers (site
    audit S-010): the "LOBSTR — SCAM" row was actually a stellar.expert-listed
    counterfeiter whose on-chain home_domain impersonates lobstr.co — our
    pipeline was serving the stolen identity as the row's name, indicting the
    victim brand. Flagged, UNVERIFIED issuers now serve no self-declared
    org_name/home_domain (the G-key + reason are the honest identity); the
    explorer badge is category-aware (SCAM / DEPRECATED / UNSAFE), so a real
    org's deprecated legacy issuer no longer wears a red SCAM tag.
  • ClickHouse snapshot rows were being merge-destroyed (site audit,
    P0-data): checkpoint state-snapshot rows all carried the same
    (tx_hash="", op_index=-1, change_index=0) tail of the ReplacingMergeTree
    sort key, so every snapshot entry modified in the same ledger collapsed
    to ONE arbitrary survivor at merge time — measured >55% of the 48M-entry
    Phase-C set already gone (blast radius: account-state, trustline, supply,
    wasm readers). ChangeIndex is now crc32(key_xdr) — per-key unique,
    re-run idempotent; the wasm/SAC instance reads repoint at
    ledger_entries_current (current-state MV, merge-immune, PK-prefix
    lookup). Operator: re-run state-snapshot -write -scope all to re-land
    the destroyed entries.

Added

  • Asset logos for wallets (board #47): /v1/assets/verified rows now
    carry image (the issuer's SEP-1 logo URL, https-only + sanitized — the
    bulk surface for wallet icon loading; per-asset detail already served it).
    Root-caused the biggest asset's missing metadata en route: Circle's
    on-chain home_domain (circle.com) 404s its stellar.toml — a curated
    domain-override map (same hand-vetted pattern as knownIssuers) redirects
    the FETCH to the still-serving centre.io while the on-chain value stays
    authoritative for identity; the bidirectional org-verification still
    holds because centre.io's TOML lists the issuer back.
  • Point-in-time price: GET /v1/price/at?asset=&ts= (board #46, the
    wallet-builder accommodation the RFP audit recommended): the closed
    1-minute VWAP bucket at-or-before a historical instant — the
    cost-basis/PnL/tax lookup every portfolio tool needs. observed_at is
    the bucket's own close time (never the requested ts), and a nearest
    bucket more than 24h before ts is an honest 404 instead of fabricated
    continuity across dead markets. SDK gains Client.PriceAt.
  • Deep CEX history + queryable per-market inception (board #44):
    kraken's raw-fills endpoint (/Trades, full history, nanosecond-cursor
    pagination) is now a backfill path — backfill-external -raw-trades
    reaches 2018-era XLM/USD where the OHLC endpoint's 720-candle horizon
    returned nothing (golden-tested on a real captured 2018 frame; venue
    rate-limit paced). /v1/markets?include=inception serves
    first_trade_at per market — the RFP's "since inception = first
    recorded trade" as a queryable fact rather than a footnote.
  • 1-month OHLC granularity + query-selectable price window (board #43,
    the last RFP-text gaps): /v1/ohlc?interval=1mo serves true
    calendar-month bars from the prices_1mo CAGG (which existed since
    migration 0002 — only the endpoint's validator rejected it), completing
    the RFP's suggested-granularity ladder; /v1/price?window=300|3600|86400
    serves the aggregator's continuously-published rolling VWAP for that
    window (default 60 = the closed-1m-bucket behavior, unchanged) — an
    unpublished window is an honest 404, never a silent substitution.
  • Tip freshness hardening (board #42): an empty rolling window on
    /v1/price/tip now escalates once to the 30s SLA bound before ANY
    closed-bucket fallback. Live samples had shown ~90s staleness on quiet
    seconds — the 5s default window missed and fell straight through to the
    closed-minute store price; staleness now exceeds 30s only when the pair
    genuinely had no trade in the last 30s (window_seconds reports the window
    actually used; test pins the quiet-pair path). The /price-vs-/price/tip
    freshness contract is now spelled out in the spec.
  • Batch price rows carry change_24h_pct (board #41): /v1/price/batch
    rows with a fiat:USD quote pair current price with the signed trailing-24h
    change — the exact bulk shape the Freighter RFP names for portfolio
    screens. Silent-nil when no comparison bucket exists (a missing change
    never costs the price itself). Audit corrected en route: fdv_usd,
    max_supply, and detail change_24h_pct already existed with correct
    null-omission semantics — the first pass mistook null-omission for
    absence.
  • SAC contract-address lookups now return the wrapped asset's full detail
    and price
    (board #40, the RFP audit's biggest wallet-facing gap): a
    /v1/assets/{C...} lookup whose contract is a Stellar Asset Contract
    resolves to the classic (or native) identity it wraps — trust-anchored on
    the lake instance's core-minted StellarAsset executable AND a
    derivation cross-check (the resolved asset must re-derive to the queried
    address, so a spoofed metadata name can never redirect pricing;
    adversarial test pins it). Classic + native asset detail now carries
    contract_id (the deterministically-derived SAC address, valid even
    pre-deployment), golden-tested against the on-chain USDC + XLM SACs.

Changed

  • Legacy-brand purge (pre-rebrand naming), everywhere it was still live.
    The API's diagnostic cache header is now X-Stellarindex-Cache;
    region.home_domain serves the current domain; the legacy-domain Caddy
    alias is REMOVED (hard cutover — the old domain no longer serves); the
    spec + docs drop the legacy key-prefix examples and the last legacy-prefix
    API key was deleted from the store; MinIO's root identity and the
    duplicate legacy reader user/policy are renamed/removed on r1 along with
    the old hostname, orphaned /etc/default/* env files, and /opt remnants;
    the prod-target guard and load-test allowlists drop the old hosts.
    Deliberately kept: immutable history (changelog entries, frozen audit
    records) and operator instructions that name legacy external artifacts
    slated for deletion.

Added

  • Ansible is now r1's config deployment path, with drift guardrails.
    After the two-way audit: 18 --check --diff rounds reconciled the
    archival-node role against live r1 (the dry runs caught an inventory
    pointing the partition-carver at a live pool disk, a placeholder
    authorized_keys that would have locked out operator + deploy, a
    pre-ADR-0034 toml template that would have dropped the ClickHouse config,
    and stale/broken vault secrets), then staged application converged the
    host: services de-privileged to the stellarindex user (CS-118/119),
    galexie moved off MinIO root creds onto the dedicated writer user,
    postgres config single-sourced (the hand-tuned 8GB max_wal_size had been
    inert behind postgresql.auto.conf all along). One real incident during
    apply — the role downgrade-broke the upstream OpenZFS userspace and
    deleted the dkms module (recovered in minutes from the migration debs;
    packages now apt-mark held, install gated, three new assertions).
    Guardrails: weekly ansible-drift.yml (fails on divergence), CI ansible
    syntax+lint job, hourly config-assertions (now 12 checks), and the
    CLAUDE.md rule: every r1 host change lands in configs/ansible in the
    same PR.
  • r1 ↔ ansible drift audit + config-assertion watchdog. Follow-up to the
    rsyslog apply-gap finding: audited BOTH directions between r1's live state
    and the ansible roles. Live-only fixes that a playbook render would have
    erased are now codified (CS-010 supply reserves → template + r1 inventory
    vars; redis maxmemory → archival-node task; ssh root-access var pinned);
    repo-ahead apply-gaps closed (three alert groups + rebrand wording synced
    to r1's rules). New hourly config-assertions.sh timer on r1 asserts the
    load-bearing guard configs' CONTENT (9 assertions, all green) with
    stellarindex_config_assertion_failed/_stale alerts in both trees;
    full findings table + standing "check --diff first" rule in
    docs/operations/r1-ansible-drift-2026-07-03.md.
  • Root-disk fast-fill early warning (stellarindex_node_root_disk_filling_fast,
    both rule trees + runbook + catalog): pages when root trends to full within
    30 minutes — the 2026-06-11 ClickHouse log-wedge loop filled root at
    ~3.8 GB/min, going healthy→full in ~5 minutes, faster than the static <10%
    page can be acted on. Deployed live to r1.

Fixed

  • The 2026-06-11 log-discipline rsyslog rules were never live on r1
    the loki + clickhouse-server stop rules (the belt-and-braces that keeps a
    journald flood out of /var/log/syslog) existed only in ansible role
    15-log-discipline.yml, which does not auto-run against r1; the postmortem
    recorded codified-as-applied, leaving the syslog half of the root-fill loop
    open. Applied and probe-verified (tagged test line reaches journald, not
    syslog); the file names the ansible role as source of truth.