feat(hermes): release v2 initial API (#1293)
The V2 API provide more functionality compared to the V1 such as supporting benchmark proofs for multiple ids. This change bumps the Hermes version to initiate a release and also fixes a couple of minor things: - Update build.rs to not panic on rebuilds - Remove an unused benchmarks file - Add all the V2 endpoints to docs
This commit is contained in:
parent
5dcf5cac4f
commit
22579edc6e
|
@ -1574,7 +1574,7 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermes"
|
name = "hermes"
|
||||||
version = "0.4.5"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "hermes"
|
name = "hermes"
|
||||||
version = "0.4.5"
|
version = "0.5.0"
|
||||||
description = "Hermes is an agent that provides Verified Prices from the Pythnet Pyth Oracle."
|
description = "Hermes is an agent that provides Verified Prices from the Pythnet Pyth Oracle."
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
|
|
@ -14,12 +14,15 @@ fn main() {
|
||||||
// directory as a mini-repo with wormhole and googleapis as remotes, so we can copy out the
|
// directory as a mini-repo with wormhole and googleapis as remotes, so we can copy out the
|
||||||
// TREEISH paths we want.
|
// TREEISH paths we want.
|
||||||
let protobuf_setup = r#"
|
let protobuf_setup = r#"
|
||||||
|
set -euo pipefail
|
||||||
git init .
|
git init .
|
||||||
git clean -df
|
git clean -df
|
||||||
git remote add wormhole https://github.com/wormhole-foundation/wormhole.git
|
git remote add wormhole https://github.com/wormhole-foundation/wormhole.git || true
|
||||||
git remote add googleapis https://github.com/googleapis/googleapis.git
|
git remote add googleapis https://github.com/googleapis/googleapis.git || true
|
||||||
git fetch --depth=1 wormhole main
|
git fetch --depth=1 wormhole main
|
||||||
git fetch --depth=1 googleapis master
|
git fetch --depth=1 googleapis master
|
||||||
|
git reset
|
||||||
|
rm -rf proto/
|
||||||
git read-tree --prefix=proto/ -u wormhole/main:proto
|
git read-tree --prefix=proto/ -u wormhole/main:proto
|
||||||
git read-tree --prefix=proto/google/api/ -u googleapis/master:google/api
|
git read-tree --prefix=proto/google/api/ -u googleapis/master:google/api
|
||||||
"#;
|
"#;
|
||||||
|
|
|
@ -122,6 +122,7 @@ pub async fn run(opts: RunOptions, state: ApiState) -> Result<()> {
|
||||||
rest::latest_vaas,
|
rest::latest_vaas,
|
||||||
rest::price_feed_ids,
|
rest::price_feed_ids,
|
||||||
rest::latest_price_updates,
|
rest::latest_price_updates,
|
||||||
|
rest::timestamp_price_updates,
|
||||||
),
|
),
|
||||||
components(
|
components(
|
||||||
schemas(
|
schemas(
|
||||||
|
|
|
@ -1,134 +0,0 @@
|
||||||
//! 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()
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue