Use delphi for indexer price calculation.

This commit is contained in:
Jakob Notland 2026-06-09 12:18:45 +02:00
parent d8edfe15fb
commit 12f50429de
2 changed files with 37 additions and 17 deletions

View file

@ -12,6 +12,7 @@ use crate::db::blob::display_hex_to_blob;
use crate::db::cauldron::pool::{get_injections_between, get_pool_period_snapshot};
use crate::db::oracle::get_closest;
use crate::db::oracle::oracle_cash::get_oracle_cash_closest;
use crate::db::oracle::v2::v2_bchusd_token_id;
use crate::rpc::apy::apyaggregator::APYAggregator;
use crate::rpc::apy::poolperiod::split_at_injections;
use malachite::base::num::arithmetic::traits::FloorSqrt;
@ -58,13 +59,14 @@ pub const SATS_PER_BCH: i64 = 100_000_000;
/// On-chain Delphi oracle scale: prices are stored in cents
/// (e.g. $384.24 → 38424). Same unit as the oracles.cash feed.
pub const ORACLE_SCALE: i64 = 100;
/// Timestamps before this value are looked up in the on-chain Delphi indexed table.
/// Timestamps on or after are looked up in the oracle_cash table (oracles.cash feed).
/// Value: 2026-04-23 00:00:00 UTC (last reliable Delphi oracle update).
/// Delphi v1 stopped publishing at this timestamp (2026-04-23 00:00:00 UTC).
const ORACLE_CUTOFF_TS: i64 = 1776902400;
/// Delphi v2 went live at this timestamp (2026-05-04 00:00:00 UTC).
const V2_DEPLOY_TS: i64 = 1777852800;
pub async fn usd_per_bch_at_or_before(oracle_pool: &SqlitePool, ts: i64) -> Decimal {
if ts < ORACLE_CUTOFF_TS {
// Delphi v1: on-chain historical data
match get_closest(oracle_pool, &None, ts).await {
Ok(Some(e)) => {
Decimal::from_i64(e.oracle_price).unwrap_or(Decimal::ZERO)
@ -72,14 +74,24 @@ pub async fn usd_per_bch_at_or_before(oracle_pool: &SqlitePool, ts: i64) -> Deci
}
_ => Decimal::ZERO,
}
} else {
// oracles.cash prices are in cents; divide by 100 to get USD/BCH
} else if ts < V2_DEPLOY_TS {
// oracles.cash: bridges the Apr 23 – May 4 gap
match get_oracle_cash_closest(oracle_pool, ts).await {
Ok(Some(e)) => {
Decimal::from_i64(e.oracle_price).unwrap_or(Decimal::ZERO) / Decimal::from(100)
}
_ => Decimal::ZERO,
}
} else {
// Delphi v2: on-chain data from May 4 2026 onward
let token_id = Some(v2_bchusd_token_id().clone());
match get_closest(oracle_pool, &token_id, ts).await {
Ok(Some(e)) => {
Decimal::from_i64(e.oracle_price).unwrap_or(Decimal::ZERO)
/ Decimal::from_i64(ORACLE_SCALE).unwrap()
}
_ => Decimal::ZERO,
}
}
}

View file

@ -3,9 +3,11 @@
// This software is licensed under the GNU Affero General Public License (AGPL), version 3.0 or later.
// A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/agpl-3.0.html
//! Background task that polls oracles.cash and keeps oracle_cash_price up-to-date.
//! This is a parallel feed alongside the on-chain Delphi oracle; both write to
//! oracle.db but to separate tables.
//! Background task that maintains oracle_cash_price for the historical gap period
//! (Apr 23 – May 4 2026) between Delphi v1 and v2.
//!
//! Set `USE_ORACLES_CASH_LIVE_FEED = true` to re-enable continuous polling as a
//! live price fallback (e.g. if the on-chain v2 oracle becomes unavailable).
use std::{
sync::{
@ -28,14 +30,18 @@ const ORACLES_CASH_URL: &str = "https://oracles.generalprotocols.com";
const BCH_USD_ORACLE_PUBKEY: &str =
"02d09db08af1ff4e8453919cc866a4be427d7bfe18f2c05e5444c196fcf6fd2818";
/// Set to true to continuously poll oracles.cash for live price updates.
/// False: only backfill the gap period; Delphi v2 is the live source.
const USE_ORACLES_CASH_LIVE_FEED: bool = false;
const POLL_INTERVAL: Duration = Duration::from_secs(300); // 5 minutes
const REQUEST_TIMEOUT: Duration = Duration::from_secs(20);
const BACKFILL_AGGREGATION: i64 = 3600; // 1-hour buckets
/// Backfill oracle_cash data starting from this timestamp.
/// Everything before this point is served from the on-chain Delphi indexed table,
/// so oracle_cash only needs to cover from here onward.
/// Value: 2026-04-23 00:00:00 UTC (last reliable Delphi oracle update).
/// Backfill starts from when Delphi v1 stopped (2026-04-23 00:00:00 UTC).
const ORACLE_CUTOFF_TS: i64 = 1776902400;
/// Backfill ends when Delphi v2 went live (2026-05-04 00:00:00 UTC).
/// Only the gap between these two timestamps needs oracles.cash coverage.
const V2_DEPLOY_TS: i64 = 1777852800;
// ── API response types ────────────────────────────────────────────────────────
@ -109,14 +115,11 @@ async fn fetch_current_price(client: &reqwest::Client) -> anyhow::Result<(i64, i
))
}
/// Fetches hourly price history from ORACLE_CUTOFF_TS to now and inserts into the DB.
/// Fetches hourly price history for the gap period [ORACLE_CUTOFF_TS, V2_DEPLOY_TS].
async fn backfill_history(client: &reqwest::Client, pool: &SqlitePool) -> anyhow::Result<()> {
let now = time_now();
let min_ts = ORACLE_CUTOFF_TS;
let url = format!(
"{}/api/v2/priceGraphPoints?publicKey={}&minMessageTimestamp={}&maxMessageTimestamp={}&aggregationPeriod={}",
ORACLES_CASH_URL, BCH_USD_ORACLE_PUBKEY, min_ts, now, BACKFILL_AGGREGATION
ORACLES_CASH_URL, BCH_USD_ORACLE_PUBKEY, ORACLE_CUTOFF_TS, V2_DEPLOY_TS, BACKFILL_AGGREGATION
);
let resp: PriceGraphResponse = client
@ -194,6 +197,11 @@ impl OracleCashFetcher {
}
}
if !USE_ORACLES_CASH_LIVE_FEED {
info!("oracle_cash: live feed disabled, gap backfill complete");
return;
}
loop {
if !keep_running.load(Ordering::Relaxed) {
info!("oracle_cash: exiting fetch task");