diff --git a/src/db/cauldron/tokenlist/token_utils.rs b/src/db/cauldron/tokenlist/token_utils.rs index 030868d..edb4119 100644 --- a/src/db/cauldron/tokenlist/token_utils.rs +++ b/src/db/cauldron/tokenlist/token_utils.rs @@ -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, + } } } diff --git a/src/oracle_cash.rs b/src/oracle_cash.rs index 05c83e9..faba6a8 100644 --- a/src/oracle_cash.rs +++ b/src/oracle_cash.rs @@ -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");