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_price_feeds,
|
||||||
rest::latest_vaas,
|
rest::latest_vaas,
|
||||||
rest::price_feed_ids,
|
rest::price_feed_ids,
|
||||||
|
rest::latest_price_updates,
|
||||||
),
|
),
|
||||||
components(
|
components(
|
||||||
schemas(
|
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_price_feeds", get(rest::latest_price_feeds))
|
||||||
.route("/api/latest_vaas", get(rest::latest_vaas))
|
.route("/api/latest_vaas", get(rest::latest_vaas))
|
||||||
.route("/api/price_feed_ids", get(rest::price_feed_ids))
|
.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("/live", get(rest::live))
|
||||||
.route("/ready", get(rest::ready))
|
.route("/ready", get(rest::ready))
|
||||||
.route("/ws", get(ws::ws_route_handler))
|
.route("/ws", get(ws::ws_route_handler))
|
||||||
|
|
|
@ -19,6 +19,7 @@ mod latest_vaas;
|
||||||
mod live;
|
mod live;
|
||||||
mod price_feed_ids;
|
mod price_feed_ids;
|
||||||
mod ready;
|
mod ready;
|
||||||
|
mod v2;
|
||||||
|
|
||||||
pub use {
|
pub use {
|
||||||
get_price_feed::*,
|
get_price_feed::*,
|
||||||
|
@ -30,6 +31,7 @@ pub use {
|
||||||
live::*,
|
live::*,
|
||||||
price_feed_ids::*,
|
price_feed_ids::*,
|
||||||
ready::*,
|
ready::*,
|
||||||
|
v2::latest_price_updates::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub enum RestError {
|
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_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?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>>",
|
"/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>,
|
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)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||||
pub struct RpcPriceFeed {
|
pub struct RpcPriceFeed {
|
||||||
pub id: RpcPriceIdentifier,
|
pub id: RpcPriceIdentifier,
|
||||||
|
@ -179,3 +189,60 @@ impl RpcPriceIdentifier {
|
||||||
RpcPriceIdentifier(id.to_bytes())
|
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