Rates Engine v0.5.0-rc.61
Pre-release
Pre-release
·
671 commits
to main
since this release
[v0.5.0-rc.61] — 2026-05-20
Fixed
entriescounter expanded to ALL observer-driven sinks
(follow-up to the blend/router/defindex fix). Same session,
same root cause: the seed query only knew abouttrades+
oracle_updates, leaving the supply observers' tables
(account_observations,trustline_observations,
claimable_observations,lp_reserve_observations,
sac_balance_observations,sep41_supply_events) silently
excluded from per-source entries even though they're the
primary observable activity surface for those sources. Now:
(1)SeedSourceEntryCountsUNIONs all six observer tables
with literal source names matching each observer's
SourceNameconstant; (2) each persister
(persistAccountObservation,persistTrustlineObservation,
…,persistSEP41SupplyEvent) callsbumpEntryCountafter a
successful insert so the steady-state counter stays current
between seed reconciliations. The result:/v1/diagnostics/ingestion
surfaces entries foraccounts,trustlines,
claimable_balances,liquidity_pools,sac_balances,
sep41_supplyalongside the trade + oracle sources — the
full "total decoded protocol activity" the user asked for.entriescounter now tracks total protocol activity, not just
trades. User-reported:/v1/diagnostics/ingestionshowed 0
entries for blend (writes toblend_auctions, nottrades),
defindex (Phase A log-only sink — no storage table),
soroswap-router (same — log-only). Root cause:
SeedSourceEntryCountsonly UNION-ALL'd overtrades+
oracle_updates. Three-part fix:
(1)SeedSourceEntryCountsquery extended to also UNION
blend_auctions(literal source'blend') andfx_quotes
(its nullable source column COALESCE'd to'unknown-fx'so
unlabelled rows still surface rather than vanish). (2) New
Store.BumpSourceEntryCount(ctx, source, n)method — single
UPSERT withON CONFLICT DO UPDATE SET entry_count = entry_count + EXCLUDED.entry_count. Cheap enough for per-event
use on low-volume sinks. (3)sink.gowires the bump into
every Phase A log-only case (soroswap-router, defindex) and
every blend-auction persister (new / fill / delete). Shared
helperbumpEntryCountlogs failures at Warn — bump errors
don't fail the underlying decode (operator's
ratesengine-ops seed-entry-countsreconciles drift). Other
observer-driven sinks (account_observations,
classic_supply_*, sep41_supply_events) are surfaced via the
supply-observer pages per ADR-0023, not source-attributed
entries — documented in the updated seed-query comment.
Added
internal/sources/cctpdecoder for Circle CCTP v2 events
(#40 Phase 1). Pure-function decoder package for all four CCTP
events:DepositForBurn(outbound USDC burn from
TokenMessengerMinter),MintAndWithdraw(inbound mint after
attestation),MessageSent(wire envelope, paired with
DepositForBurn),MessageReceived(wire envelope, paired with
MintAndWithdraw).events.godefines the four canonical Go
types with full BytesN<32>-as-hex serialisation for the
cross-chain address fields (mint_recipient,
destination_token_messenger,destination_caller,nonce,
sender);decode.goexposesClassify+ fourDecode*
functions with explicitErrMalformedTopic/
ErrMalformedBodysentinels for schema-drift detection;
decode_test.gocovers 16 cases including all four event
types' happy paths, ADR-0003 large-i128 round-trip on
DepositForBurn's amount, short-topic + missing-body-field
drift signals, MessageSent's dual ScMap/raw-Bytes paths
(forward-compat against macro layout shifts), and topic-symbol
encoding stability for all four. Per ADR-0013 the decoder
doesn't importxdrdirectly — uses inferred-type entries
throughscvalreturns, same pattern as Soroswap.
NOT yet wired — no registry entry, noconsumer.Source,
no migration. Wiring follows the storage-shape decision
(bridge_eventsshared with Rozo vscctp_eventsseparate)
perdocs/architecture/cctp-stellar-coverage.md§Storage.
CCTP + Rozo decoders shipping in parallel means the storage
layer can be designed against both event shapes at once.internal/sources/rozodecoder for Rozo v1 Payment events
(#41 Phase 1). Pure-function decoder package —events.go
defines the canonicalPayment+FlushGo types and the
pre-encoded topic-symbol constants;decode.goexposes
Classify,DecodePayment,DecodeFlushwith explicit
ErrMalformedBodyfor field-missing surfacing;decode_test.go
carries 12 parallel tests including the ADR-0003 large-i128
round-trip (locks the*big.Int → stringprecision invariant
against the int64-truncation bug class) and topic-symbol
encoding stability guards (re-encoded bytes must match the
package-init constants — drift would silently break
Classify). The package is NOT yet wired — no
registration ininternal/sources/external/registry.go, no
consumer.Sourceimpl, nodispatcher_adapter.go. Wiring +
storage layer follows thebridge_eventsvsrozo_events
shape decision (operator-gated; see
docs/architecture/rozo-stellar-coverage.md§Storage).
Capturing the decoder logic in code with tests means the
implementation phase doesn't have to re-derive the on-chain
event schema from the contract source — it's the
smallest-possible-PR that advances #41 without committing to a
specific storage shape.ClassBridgesource class for cross-chain transfer protocols
(#40 + #41 unblock). AddsClassBridge Class = "bridge"to
internal/sources/external/framework.goalongside the existing
six classes. Bridges (Circle CCTP, Rozo) move tokens between
chains rather than exchanging them at a price — a
deposit_for_burnon Stellar +mint_and_withdrawon Ethereum
is one logical USDC transfer, not a two-leg trade. Excluded
from VWAP by default (IncludeInVWAP: false); reported
alongside for cross-chain flow attribution and as the
cross-chain side of Algorithm 3 supply accounting (complements
ADR-0023's SEP-41 supply observer that already tracks
classic trustline-driven mints/burns). The
TestRegistry_ClassPolicyinvariant ("only ClassExchange may
VWAP-contribute") covers ClassBridge unchanged.
TestClassBridge_Definedlocks the wire value so a downstream
rename surfaces as a build break here rather than as a silent
classification miss. Removes the primary operator gate from
the #40 / #41 design docs — implementation can now proceed on
the storage-shape decision alone (bridge_eventsshared vs
per-protocol tables).
Docs
docs/architecture/rozo-stellar-coverage.md— Rozo intents
decoder + storage design (#41 design pass). Captures three
distinct contract variants discovered in
RozoAI/rozo-intents-contracts: (1) v1 Payment LIVE on
mainnet atCAC5SKP5FJT2ZZ7YLV4UCOM6Z5SQCCVPZWHLLLVQNQG2RWWOOSP3IYRL
(verified via StellarExpert) — emitsPaymentEvent { from, destination, amount, memo }on("payment", from)topic and
FlushEvent { token, destination, amount }on("flush",)
topic; (2) v2 Forwarder + IntentBridge pre-mainnet with
topic shapes("forward", sender),("memo_set",),
("created",),("filled",),("refunded",); (3) a newer
rozo-intents package emitting("intent_created", intent_id: BytesN<32>)+intent_filled/intent_failed/
intent_refundedlong-form symbols (status unclear — possibly
v2.1 or v3 unifying variant). Shares CCTP's
ClassBridge-or-not design question + the storage shape
question (bridge_eventsshared with CCTP vsrozo_events
separate). Recommends three-phase rollout: ship v1 Payment
decoder now (the only live contract — user direction was "v1
and v2", but v2 isn't deployed yet so Phase 1 covers reality),
Phase 2 for v2 contracts when they deploy to mainnet, Phase 3
for the rozo-intents variant after schema-status
clarification. Implementation gated on the same operator
decisions as CCTP.docs/architecture/cctp-stellar-coverage.md— CCTP-Stellar
decoder + storage design (#40 design pass). Captures the
three mainnet contract addresses (TokenMessengerMinter,
MessageTransmitter, CctpForwarder), the four canonical event
schemas (DepositForBurn,MintAndWithdraw,MessageSent,
MessageReceived) extracted verbatim from
circlefin/stellar-cctp/contracts/{token-messenger-minter-v2, message-transmitter-v2}/src/lib.rs, decoder strategy
recommendation (Option A: topic-based via existing dispatcher,
same pattern as Soroswap / Phoenix / Aquarius), and storage
shape recommendation (newcctp_eventshypertable via
migration 0037 — bridge events don't fittrades). Surfaces
five operator-gated design questions, primary being whether
CCTP warrants a newClassBridgesource class (it doesn't fit
ClassExchange— no trades, no price signal — or the existing
ClassRoutersemantic, which elides the cross-chain
dimension). Implementation lands after class-design sign-off
(per CLAUDE.md "Add a new on-chain Soroban DEX" + WASM-history
walk beforeBackfillSafe: true). The user direction was
"CCTP shouldn't have any history because it is brand new" —
so initial implementation is live-only ingest from current
ledger forward.
Changed
- Issuer-filter pushdown into
listCoinsBaseSelectCTEs (#27).
Live r1EXPLAIN ANALYZEon/v1/assets?issuer=GA5Z…:
per_asset_24h_vol'sPartial HashAggregatescanned 256,724
rows ofprices_1mto materialise stats for every asset, then
the outer SELECT discarded all but 9 (the actual issuer's
asset count). 1.3M shared-buffer hits for a single-issuer
query. The PostgreSQL 12+ default of inlining CTEs doesn't
help here because each per-asset CTE has an aggregate
(SUM/DISTINCT ON) that becomes a predicate-pushdown barrier;
the issuer filter onca.issuer_g_strkeyis unrelated to the
CTEs'GROUP BY asset_id, so the planner can't push through.
Fix: whenissueris set inbuildCoinsQuery,
listCoinsBaseSelectSQLprepends achosen_assetsCTE that
materialises the issuer's asset_id set once, and each of the
nine per-asset CTEs addsAND base_asset IN (SELECT asset_id FROM chosen_assets)(per_asset_24h_volalso adds the
symmetricquote_asset INto the union's quote-side branch).
The fourxlm_usdCTEs deliberately stay unfiltered (they
look up XLM specifically, not the caller's asset). Sentinel
comments (/*PUSHDOWN_BASE*/,/*PUSHDOWN_QUOTE*/) embedded
in the SQL get replaced in-place — keeping the const + the
renderer adjacent rather than maintaining two parallel
300-line SQL strings. q-search pushdown intentionally deferred
—LIKEpatterns on three columns combined with the outer
LIKErules don't reduce as predictably; if profiling later
shows that path is hot, a q-sidechosen_assetsvariant can
be added. Backfill behaviour unchanged: unfiltered LIST
(the dominant traffic pattern) is byte-for-byte the same SQL.
Six tests cover the renderer + buildCoinsQuery branches
(no-pushdown, with-pushdown, no-issuer, issuer-only,
q-only-no-pushdown, issuer+q).
Fixed
- Prewarm extended to verified-currency canonical asset_ids
(#37 follow-up). Real measurement on r1 post-rc.60:
/v1/assets/usdc(slug form) was 144ms but
/v1/assets/USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN
(canonical form) was 3.3s — same underlying
getCoinBySlugSQL, but the canonical form missed cache because
prewarmLight only warmedListCoinsExt+GetNativeCoinRow.
Programmatic clients + the explorer's drill-out paths navigate
by canonical asset_id, so this was the dominant user-visible
slowness post-rc.59's native-only prewarm. Fix: catalogue load
moved BEFORE the prewarm goroutine;prewarmCaches+
prewarmLightnow take a[]string verifiedAssetIDsextracted
from the catalogue (each Stellar-network entry'sAssetID,
excluding native + empty). Each entry feeds a
coins.GetCoinByAssetIDcall alongside the existing
GetNativeCoinRow. Drift-safe —GetCoinByAssetIDis the
exact reader the/v1/assets/{id}handler calls
(assets_coin_extension.go:215); same wire path = same cache
key.