Skip to content

feat(blend): periodic SEP-40 oracle price snapshot with Comet BLND/LP leg - #660

Open
aditya1702 wants to merge 11 commits into
blend/pr3-validator-processorfrom
blend/pr4-price-snapshot
Open

feat(blend): periodic SEP-40 oracle price snapshot with Comet BLND/LP leg#660
aditya1702 wants to merge 11 commits into
blend/pr3-validator-processorfrom
blend/pr4-price-snapshot

Conversation

@aditya1702

@aditya1702 aditya1702 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Blend oracle price snapshot task

Fourth of 5 stacked PRs adding Blend Capital v2 lending support (stacked on #659).

Adds a periodic price snapshot task under ingest (live mode only) that keeps blend_oracle_prices fresh for PR5's USD valuation. Each pass: discover targets → fetch prices → one BatchUpsert.

SEP-40 leg — for every (oracle, asset) pair derived from blend_pools × blend_reserves:

  • decimals() once per oracle, lastprice(Asset::Stellar(asset)) per target, via RPC simulation.
  • Skipped, never persisted: Option::None, prices ≤ 0 (invalid), oracle timestamps older than 24h (stale) — the same prices pool.rs::load_price itself rejects. Write-side half of the staleness guard; feat(graphql): Blend positions, pools, and earn discovery #661 adds the read side.
  • Per-oracle failures are isolated (errors.Join) — one bad oracle never aborts the pass.

Comet leg (optional) — BLND spot + backstop LP price derived from the Comet BLND:USDC pool, since no oracle prices the LP token:

  • Reads pool state from its ContractData entries (AllRecordData, TotalShares) in one getLedgerEntries call — atomic, no getter simulations.
  • Refuses to decode unless the instance WASM hash equals the pinned 8abc2891… (contract has no upgrade entrypoint) and both legs' scalar fields match (equal decimals).
  • blndPrice = (usdcBal/usdcWeight)/(blndBal/blndWeight) (Balancer spot, fee-less); lpPrice = pool NAV / LP supply; USDC ≡ $1. Prices rounding to "0" at 7 decimals are rejected.
  • Row convention (PR5 reads this): both rows under oracle_contract_id = <Comet address>; LP row self-priced (asset == oracle); BLND row keyed by the real BLND token address.

Scheduling & metrics

  • Immediate pass at startup, then a full interval after each pass ends — an overrunning pass never triggers a back-to-back one.
  • Metrics: pass duration histogram; fetch counter (success|error|none|stale|invalid); prices-tracked gauge (rows actually written — 0 on upsert failure); oldest-price-age gauge (includes skipped-as-stale prices, so a dead oracle that still serves old timestamps grows it unboundedly).

Config

Var Default Meaning
BLEND_PRICE_INTERVAL 60s 0 disables; negative rejected at startup
BLEND_BACKSTOP_LP_CONTRACT_ID empty Comet pool C-address; empty disables the Comet leg

Verification — all confirmed against live mainnet: SEP-40 Asset::Stellar encoding (spec + Reflector interface + lastprice simulation round-trip), Comet entry keys/shapes/scalars/WASM hash (getLedgerEntries dump), and the spot-price formula vs comet-contracts-v1 source (c_math.rs::calc_spot_price, c_consts.rs, init.rs).

🤖 Generated with Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d86bb208a6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/services/blend/prices.go Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a1197eb096

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/ingest/ingest.go Outdated
@aditya1702
aditya1702 force-pushed the blend/pr4-price-snapshot branch from a1197eb to 5c7d419 Compare July 9, 2026 13:57
@aditya1702
aditya1702 force-pushed the blend/pr4-price-snapshot branch from 5c7d419 to 731499d Compare July 9, 2026 20:12
@aditya1702
aditya1702 force-pushed the blend/pr4-price-snapshot branch 2 times, most recently from 10ccc0a to 9d078d4 Compare July 9, 2026 21:37
@aditya1702
aditya1702 force-pushed the blend/pr4-price-snapshot branch from 9d078d4 to 23486c9 Compare July 10, 2026 11:32
@aditya1702
aditya1702 force-pushed the blend/pr4-price-snapshot branch from 23486c9 to 6cdb564 Compare July 15, 2026 20:52
@aditya1702
aditya1702 force-pushed the blend/pr4-price-snapshot branch from 6cdb564 to 97ab9f5 Compare July 16, 2026 16:56
@aditya1702
aditya1702 force-pushed the blend/pr4-price-snapshot branch 2 times, most recently from adec3b6 to ef562f1 Compare July 21, 2026 18:45
@aditya1702
aditya1702 requested a review from Copilot July 21, 2026 19:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds periodic Blend v2 oracle and Comet LP price snapshots for USD valuation.

Changes:

  • Implements SEP-40 and Comet price fetching and persistence.
  • Adds scheduling, configuration, and advisory-lock gating.
  • Adds Prometheus metrics and database-backed tests.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
internal/services/ingest.go Registers post-lock tasks.
internal/services/ingest_live.go Starts tasks after lock acquisition.
internal/services/blend/scval.go Adds SEP-40 XDR helpers.
internal/services/blend/scval_test.go Tests price encoding and decoding.
internal/services/blend/prices.go Implements snapshot scheduling and persistence.
internal/services/blend/prices_test.go Tests snapshot behavior.
internal/services/blend/comet.go Derives BLND and LP prices.
internal/services/blend/comet_test.go Tests Comet fetching and valuation.
internal/metrics/metrics.go Registers Blend metrics.
internal/metrics/metrics_test.go Verifies metric initialization.
internal/metrics/blend.go Defines snapshot metrics.
internal/metrics/blend_test.go Tests metric collection.
internal/ingest/ingest.go Wires the snapshot service.
internal/data/blend/oracle_prices.go Adds price storage operations.
internal/data/blend/oracle_prices_test.go Tests price SQL operations.
internal/data/blend/models.go Registers the price model.
docker-compose.yaml Documents environment settings.
cmd/utils/global_options.go Defines Blend CLI options.
cmd/ingest.go Exposes options through ingest.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/services/ingest_live.go
Comment thread internal/ingest/ingest.go
Comment thread internal/services/blend/comet.go Outdated
Comment thread internal/services/blend/comet.go Outdated
@aditya1702
aditya1702 requested a review from JiahuiWho July 22, 2026 20:37
Comment thread internal/services/blend/prices.go Outdated
Comment thread internal/services/blend/comet.go Outdated
Comment thread internal/services/blend/prices.go
@aditya1702
aditya1702 force-pushed the blend/pr4-price-snapshot branch from 03c1c7b to 38ebf96 Compare July 30, 2026 16:28
…e live advisory lock

The blend price snapshot task started in setupDeps, so an instance that
never wins the advisory lock (rolling deploy, standby pod) still ran
snapshot passes, duplicating oracle RPC load and writes. Long-lived
background tasks now register as PostLockTasks and launch inside
startLiveIngestion after the lock is held.
The pool contract itself (pool.rs::load_price) rejects prices older than
24h or <= 0, so persisting them only lets the API price positions the
pool would refuse to. Skip both at snapshot time, counted as new
"stale"/"invalid" fetch outcomes, and add a
wallet_blend_price_oldest_age_seconds gauge covering every decoded price
including skipped-as-stale ones, so a dead oracle surfaces as unbounded
gauge growth.
PricesTracked is now set after BatchUpsert - 0 on a failed upsert - so a DB
outage no longer shows healthy row counts. Cover OldestPriceAge in the
metrics tests (registration, record, lint) and align the FetchesTotal and
snapshotOracle godocs with actual counting behavior (Comet success counts
per derived row; invalid <=0 prices are excluded from the age gauge).
Copilot review fixes:
- fetchCometState reads the pool's numeric state twice and requires both
  readings to agree: each getter is its own latest-ledger simulation, so a
  Comet transaction landing mid-read could otherwise mix pre- and post-trade
  values into a state that never existed on-chain. Any mutation changes at
  least one balance or the LP supply, so agreement is a complete witness.
- cometValuation rejects a positive price that rounds to zero at 7 decimals,
  matching the snapshot invariant that non-positive prices are never
  persisted.
- blend-price-interval rejects negative durations at startup instead of
  silently disabling the snapshot task (only 0 disables).
…r simulations

Everything the Comet valuation needs lives in two persistent ContractData
entries (DataKey::AllRecordData's Map<Address, Record{balance, weight,
scalar, index}> and DataKey::TotalShares), so fetchCometState now reads them
plus the contract instance in a single getLedgerEntries call — atomic by
construction, replacing eleven sequential per-getter simulations and the
double-read torn-state guard they required. The instance entry's WASM hash
is asserted against the pinned Comet hash before any storage is decoded
(the contract has no upgrade entrypoint, so a mismatch means the configured
address is not the pinned pool), and the per-token scalar fields must match
(the valuation's raw-balance ratio requires both legs to share decimals).

Key encodings (unit enum variants as single-element ScvVec[Symbol]), Record
field shapes, both legs' 7-decimal scalars, and the instance hash were all
verified against the live mainnet pool via getLedgerEntries.

PriceSnapshotConfig gains an RPC field, required when BackstopLPContractID
is set; the SEP-40 oracle leg still simulates decimals/lastprice (those are
computed values, not storage).
@aditya1702
aditya1702 force-pushed the blend/pr4-price-snapshot branch from 38ebf96 to 84df451 Compare July 30, 2026 21:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants