//! This module communicates with Pyth Benchmarks, an API for historical price feeds and their updates. use { crate::{ aggregate::{ PriceFeedUpdate, PriceFeedsWithUpdateData, UnixTimestamp, }, api::types::PriceUpdate, }, anyhow::Result, base64::{ engine::general_purpose::STANDARD as base64_standard_engine, Engine as _, }, pyth_sdk::{ Price, PriceFeed, PriceIdentifier, }, serde::Deserialize, }; const BENCHMARKS_REQUEST_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30); #[derive(Deserialize, Debug, Clone)] enum BlobEncoding { #[serde(rename = "base64")] Base64, #[serde(rename = "hex")] Hex, } #[derive(Deserialize, Debug, Clone)] struct BinaryBlob { pub encoding: BlobEncoding, pub data: Vec, } impl TryFrom for PriceFeedsWithUpdateData { type Error = anyhow::Error; fn try_from(price_update: PriceUpdate) -> Result { let price_feeds = match price_update.parsed { Some(parsed_updates) => parsed_updates .into_iter() .map(|parsed_price_update| { Ok(PriceFeedUpdate { price_feed: PriceFeed::new( parsed_price_update.id, Price { price: parsed_price_update.price.price, conf: parsed_price_update.price.conf, expo: parsed_price_update.price.expo, publish_time: parsed_price_update.price.publish_time, }, Price { price: parsed_price_update.ema_price.price, conf: parsed_price_update.ema_price.conf, expo: parsed_price_update.ema_price.expo, publish_time: parsed_price_update.ema_price.publish_time, }, ), slot: parsed_price_update.metadata.slot, received_at: parsed_price_update.metadata.proof_available_time, update_data: None, // This field is not available in ParsedPriceUpdate prev_publish_time: parsed_price_update.metadata.prev_publish_time, }) }) .collect::>>(), None => Err(anyhow::anyhow!("No parsed price updates available")), }?; let update_data = price_update .binary .data .iter() .map(|hex_str| hex::decode(hex_str).unwrap_or_default()) .collect::>>(); Ok(PriceFeedsWithUpdateData { price_feeds, update_data, }) } } #[async_trait::async_trait] pub trait Benchmarks { async fn get_verified_price_feeds( &self, price_ids: &[PriceIdentifier], publish_time: UnixTimestamp, ) -> Result; } #[async_trait::async_trait] impl Benchmarks for crate::state::State { async fn get_verified_price_feeds( &self, price_ids: &[PriceIdentifier], publish_time: UnixTimestamp, ) -> Result { let endpoint = self .benchmarks_endpoint .as_ref() .ok_or_else(|| anyhow::anyhow!("Benchmarks endpoint is not set"))? .join(&format!("/v1/updates/price/{}", publish_time)) .unwrap(); let client = reqwest::Client::new(); let mut request = client .get(endpoint) .timeout(BENCHMARKS_REQUEST_TIMEOUT) .query(&[("encoding", "hex")]) .query(&[("parsed", "true")]); for price_id in price_ids { request = request.query(&[("ids", price_id)]) } let response = request.send().await?; if response.status() != reqwest::StatusCode::OK { return Err(anyhow::anyhow!(format!( "Price update for price ids {:?} with publish time {} not found in benchmarks. Status code: {}, message: {}", price_ids, publish_time, response.status(), response.text().await? ))); } let price_update: PriceUpdate = response.json().await?; price_update.try_into() } }