add /v2/updates/price/latest endpoint (#1225)
* add /v2/updates/price/latest endpoint * Update data type in BinaryPriceUpdate struct * support compressed update data * Update API endpoint in index.rs * Update hermes/src/api/types.rs Co-authored-by: Ali Behjati <bahjatia@gmail.com> * move to v2 module and address comments * address more comments * address more comments --------- Co-authored-by: Ali Behjati <bahjatia@gmail.com>
This commit is contained in:
parent
be8447324c
commit
69e4fee501
|
@ -121,6 +121,7 @@ pub async fn run(opts: RunOptions, state: ApiState) -> Result<()> {
|
|||
rest::latest_price_feeds,
|
||||
rest::latest_vaas,
|
||||
rest::price_feed_ids,
|
||||
rest::latest_price_updates,
|
||||
),
|
||||
components(
|
||||
schemas(
|
||||
|
@ -152,6 +153,7 @@ pub async fn run(opts: RunOptions, state: ApiState) -> Result<()> {
|
|||
.route("/api/latest_price_feeds", get(rest::latest_price_feeds))
|
||||
.route("/api/latest_vaas", get(rest::latest_vaas))
|
||||
.route("/api/price_feed_ids", get(rest::price_feed_ids))
|
||||
.route("/v2/updates/price/latest", get(rest::latest_price_updates))
|
||||
.route("/live", get(rest::live))
|
||||
.route("/ready", get(rest::ready))
|
||||
.route("/ws", get(ws::ws_route_handler))
|
||||
|
|
|
@ -19,6 +19,7 @@ mod latest_vaas;
|
|||
mod live;
|
||||
mod price_feed_ids;
|
||||
mod ready;
|
||||
mod v2;
|
||||
|
||||
pub use {
|
||||
get_price_feed::*,
|
||||
|
@ -30,6 +31,7 @@ pub use {
|
|||
live::*,
|
||||
price_feed_ids::*,
|
||||
ready::*,
|
||||
v2::latest_price_updates::*,
|
||||
};
|
||||
|
||||
pub enum RestError {
|
||||
|
|
|
@ -16,5 +16,6 @@ pub async fn index() -> impl IntoResponse {
|
|||
"/api/get_price_feed?id=<price_feed_id>&publish_time=<publish_time_in_unix_timestamp>(&verbose=true)(&binary=true)",
|
||||
"/api/get_vaa?id=<price_feed_id>&publish_time=<publish_time_in_unix_timestamp>",
|
||||
"/api/get_vaa_ccip?data=<0x<price_feed_id_32_bytes>+<publish_time_unix_timestamp_be_8_bytes>>",
|
||||
"/v2/updates/price/latest?ids[]=<price_feed_id>&ids[]=<price_feed_id_2>&..(&encoding=hex|base64)(&parsed=false)",
|
||||
])
|
||||
}
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
use {
|
||||
crate::{
|
||||
aggregate::RequestTime,
|
||||
api::{
|
||||
rest::{
|
||||
verify_price_ids_exist,
|
||||
RestError,
|
||||
},
|
||||
types::{
|
||||
BinaryPriceUpdate,
|
||||
EncodingType,
|
||||
ParsedPriceUpdate,
|
||||
PriceIdInput,
|
||||
PriceUpdate,
|
||||
},
|
||||
},
|
||||
},
|
||||
anyhow::Result,
|
||||
axum::{
|
||||
extract::State,
|
||||
Json,
|
||||
},
|
||||
base64::{
|
||||
engine::general_purpose::STANDARD as base64_standard_engine,
|
||||
Engine as _,
|
||||
},
|
||||
pyth_sdk::PriceIdentifier,
|
||||
serde_qs::axum::QsQuery,
|
||||
utoipa::IntoParams,
|
||||
};
|
||||
|
||||
|
||||
#[derive(Debug, serde::Deserialize, IntoParams)]
|
||||
#[into_params(parameter_in=Query)]
|
||||
pub struct LatestPriceUpdatesQueryParams {
|
||||
/// Get the most recent price update for this set of price feed ids.
|
||||
///
|
||||
/// This parameter can be provided multiple times to retrieve multiple price updates,
|
||||
/// for example see the following query string:
|
||||
///
|
||||
/// ```
|
||||
/// ?ids[]=a12...&ids[]=b4c...
|
||||
/// ```
|
||||
#[param(rename = "ids[]")]
|
||||
#[param(example = "e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43")]
|
||||
ids: Vec<PriceIdInput>,
|
||||
|
||||
/// If true, include the parsed price update in the `parsed` field of each returned feed.
|
||||
#[serde(default)]
|
||||
encoding: EncodingType,
|
||||
|
||||
/// If true, include the parsed price update in the `parsed` field of each returned feed.
|
||||
#[serde(default = "default_true")]
|
||||
parsed: bool,
|
||||
}
|
||||
|
||||
fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Get the latest price updates by price feed id.
|
||||
///
|
||||
/// Given a collection of price feed ids, retrieve the latest Pyth price for each price feed.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/v2/updates/price/latest",
|
||||
responses(
|
||||
(status = 200, description = "Price updates retrieved successfully", body = Vec<PriceUpdate>),
|
||||
(status = 404, description = "Price ids not found", body = String)
|
||||
),
|
||||
params(
|
||||
LatestPriceUpdatesQueryParams
|
||||
)
|
||||
)]
|
||||
pub async fn latest_price_updates(
|
||||
State(state): State<crate::api::ApiState>,
|
||||
QsQuery(params): QsQuery<LatestPriceUpdatesQueryParams>,
|
||||
) -> Result<Json<Vec<PriceUpdate>>, RestError> {
|
||||
let price_ids: Vec<PriceIdentifier> = params.ids.into_iter().map(|id| id.into()).collect();
|
||||
|
||||
verify_price_ids_exist(&state, &price_ids).await?;
|
||||
|
||||
let price_feeds_with_update_data = crate::aggregate::get_price_feeds_with_update_data(
|
||||
&*state.state,
|
||||
&price_ids,
|
||||
RequestTime::Latest,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::warn!(
|
||||
"Error getting price feeds {:?} with update data: {:?}",
|
||||
price_ids,
|
||||
e
|
||||
);
|
||||
RestError::UpdateDataNotFound
|
||||
})?;
|
||||
|
||||
let price_update_data = price_feeds_with_update_data.update_data;
|
||||
let encoded_data: Vec<String> = price_update_data
|
||||
.into_iter()
|
||||
.map(|data| match params.encoding {
|
||||
EncodingType::Base64 => base64_standard_engine.encode(data),
|
||||
EncodingType::Hex => hex::encode(data),
|
||||
})
|
||||
.collect();
|
||||
let binary_price_update = BinaryPriceUpdate {
|
||||
encoding: params.encoding,
|
||||
data: encoded_data,
|
||||
};
|
||||
let parsed_price_updates: Option<Vec<ParsedPriceUpdate>> = if params.parsed {
|
||||
Some(
|
||||
price_feeds_with_update_data
|
||||
.price_feeds
|
||||
.into_iter()
|
||||
.map(|price_feed| price_feed.into())
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let compressed_price_update = PriceUpdate {
|
||||
binary: binary_price_update,
|
||||
parsed: parsed_price_updates,
|
||||
};
|
||||
|
||||
|
||||
Ok(Json(vec![compressed_price_update]))
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
pub mod latest_price_updates;
|
|
@ -58,6 +58,16 @@ pub struct RpcPriceFeedMetadata {
|
|||
pub prev_publish_time: Option<UnixTimestamp>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
pub struct RpcPriceFeedMetadataV2 {
|
||||
#[schema(value_type = Option<u64>, example=85480034)]
|
||||
pub slot: Option<Slot>,
|
||||
#[schema(value_type = Option<i64>, example=doc_examples::timestamp_example)]
|
||||
pub proof_available_time: Option<UnixTimestamp>,
|
||||
#[schema(value_type = Option<i64>, example=doc_examples::timestamp_example)]
|
||||
pub prev_publish_time: Option<UnixTimestamp>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
pub struct RpcPriceFeed {
|
||||
pub id: RpcPriceIdentifier,
|
||||
|
@ -179,3 +189,60 @@ impl RpcPriceIdentifier {
|
|||
RpcPriceIdentifier(id.to_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, serde::Deserialize, serde::Serialize)]
|
||||
pub enum EncodingType {
|
||||
#[default]
|
||||
#[serde(rename = "hex")]
|
||||
Hex,
|
||||
#[serde(rename = "base64")]
|
||||
Base64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
pub struct BinaryPriceUpdate {
|
||||
pub encoding: EncodingType,
|
||||
pub data: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
pub struct ParsedPriceUpdate {
|
||||
pub id: String,
|
||||
pub price: RpcPrice,
|
||||
pub ema_price: RpcPrice,
|
||||
pub metadata: RpcPriceFeedMetadataV2,
|
||||
}
|
||||
|
||||
impl From<PriceFeedUpdate> for ParsedPriceUpdate {
|
||||
fn from(price_feed_update: PriceFeedUpdate) -> Self {
|
||||
let price_feed = price_feed_update.price_feed;
|
||||
|
||||
Self {
|
||||
id: price_feed.id.to_string(),
|
||||
price: RpcPrice {
|
||||
price: price_feed.get_price_unchecked().price,
|
||||
conf: price_feed.get_price_unchecked().conf,
|
||||
expo: price_feed.get_price_unchecked().expo,
|
||||
publish_time: price_feed.get_price_unchecked().publish_time,
|
||||
},
|
||||
ema_price: RpcPrice {
|
||||
price: price_feed.get_ema_price_unchecked().price,
|
||||
conf: price_feed.get_ema_price_unchecked().conf,
|
||||
expo: price_feed.get_ema_price_unchecked().expo,
|
||||
publish_time: price_feed.get_ema_price_unchecked().publish_time,
|
||||
},
|
||||
metadata: RpcPriceFeedMetadataV2 {
|
||||
proof_available_time: price_feed_update.received_at,
|
||||
slot: price_feed_update.slot,
|
||||
prev_publish_time: price_feed_update.prev_publish_time,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
pub struct PriceUpdate {
|
||||
pub binary: BinaryPriceUpdate,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub parsed: Option<Vec<ParsedPriceUpdate>>,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue