135 lines
4.5 KiB
Rust
135 lines
4.5 KiB
Rust
//! 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<String>,
|
|
}
|
|
|
|
impl TryFrom<PriceUpdate> for PriceFeedsWithUpdateData {
|
|
type Error = anyhow::Error;
|
|
fn try_from(price_update: PriceUpdate) -> Result<Self> {
|
|
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::<Result<Vec<_>>>(),
|
|
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::<Vec<Vec<u8>>>();
|
|
|
|
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<PriceFeedsWithUpdateData>;
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl Benchmarks for crate::state::State {
|
|
async fn get_verified_price_feeds(
|
|
&self,
|
|
price_ids: &[PriceIdentifier],
|
|
publish_time: UnixTimestamp,
|
|
) -> Result<PriceFeedsWithUpdateData> {
|
|
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()
|
|
}
|
|
}
|