[hermes] Document most of the endpoints (#997)
* add rough docs for most stuff * cleanup * bunch of docs * bunch of docs * gr
This commit is contained in:
parent
175dfef5bd
commit
cb49236a58
|
@ -46,9 +46,14 @@ pub async fn run(store: Arc<Store>, mut update_rx: Receiver<()>, rpc_addr: Strin
|
|||
#[openapi(
|
||||
paths(
|
||||
rest::latest_price_feeds,
|
||||
rest::latest_vaas,
|
||||
rest::get_price_feed,
|
||||
rest::get_vaa,
|
||||
rest::get_vaa_ccip,
|
||||
rest::price_feed_ids,
|
||||
),
|
||||
components(
|
||||
schemas(types::RpcPriceFeedMetadata, types::RpcPriceFeed, types::PriceIdInput)
|
||||
schemas(types::RpcPriceFeedMetadata, types::RpcPriceFeed, types::RpcPrice, types::RpcPriceIdentifier, types::PriceIdInput, rest::GetVaaResponse, rest::GetVaaCcipResponse, rest::GetVaaCcipInput)
|
||||
),
|
||||
tags(
|
||||
(name = "hermes", description = "Pyth Real-Time Pricing API")
|
||||
|
@ -78,7 +83,7 @@ pub async fn run(store: Arc<Store>, mut update_rx: Receiver<()>, rpc_addr: Strin
|
|||
.layer(CorsLayer::permissive())
|
||||
// non-strict mode permits escaped [] in URL parameters.
|
||||
// 5 is the allowed depth (also the default value for this parameter).
|
||||
.layer(Extension(QsQueryConfig::new(false)));
|
||||
.layer(Extension(QsQueryConfig::new(5, false)));
|
||||
|
||||
|
||||
// Call dispatch updates to websocket every 1 seconds
|
||||
|
|
|
@ -2,6 +2,7 @@ use {
|
|||
super::types::{
|
||||
PriceIdInput,
|
||||
RpcPriceFeed,
|
||||
RpcPriceIdentifier,
|
||||
},
|
||||
crate::{
|
||||
impl_deserialize_for_hex_string_wrapper,
|
||||
|
@ -30,8 +31,10 @@ use {
|
|||
},
|
||||
pyth_sdk::PriceIdentifier,
|
||||
serde_qs::axum::QsQuery,
|
||||
std::collections::HashSet,
|
||||
utoipa::IntoParams,
|
||||
utoipa::{
|
||||
IntoParams,
|
||||
ToSchema,
|
||||
},
|
||||
};
|
||||
|
||||
pub enum RestError {
|
||||
|
@ -63,19 +66,57 @@ impl IntoResponse for RestError {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the set of price feed ids.
|
||||
///
|
||||
/// Get all of the price feed ids for which price updates can be retrieved.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/price_feed_ids",
|
||||
responses(
|
||||
(status = 200, description = "Price feed ids retrieved successfully", body = Vec<RpcPriceIdentifier>)
|
||||
),
|
||||
params()
|
||||
)]
|
||||
pub async fn price_feed_ids(
|
||||
State(state): State<super::State>,
|
||||
) -> Result<Json<HashSet<PriceIdentifier>>, RestError> {
|
||||
let price_feeds = state.store.get_price_feed_ids().await;
|
||||
Ok(Json(price_feeds))
|
||||
) -> Result<Json<Vec<RpcPriceIdentifier>>, RestError> {
|
||||
let price_feed_ids = state
|
||||
.store
|
||||
.get_price_feed_ids()
|
||||
.await
|
||||
.iter()
|
||||
.map(|id| RpcPriceIdentifier::from(&id))
|
||||
.collect();
|
||||
Ok(Json(price_feed_ids))
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
#[derive(Debug, serde::Deserialize, IntoParams)]
|
||||
#[into_params(parameter_in=Query)]
|
||||
pub struct LatestVaasQueryParams {
|
||||
/// Get the VAAs for these price feed ids.
|
||||
/// Provide this parameter multiple times to retrieve multiple price updates,
|
||||
/// ids[]=a12...&ids[]=b4c...
|
||||
#[param(
|
||||
rename = "ids[]",
|
||||
example = "e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"
|
||||
)]
|
||||
ids: Vec<PriceIdInput>,
|
||||
}
|
||||
|
||||
|
||||
/// Get VAAs for a set of price feed ids.
|
||||
///
|
||||
/// Given a collection of price feed ids, retrieve the latest VAA for them. The returned
|
||||
/// VAA(s) can be submitted to the Pyth contract to update the on-chain price
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/latest_vaas",
|
||||
responses(
|
||||
(status = 200, description = "VAAs retrieved successfully", body = Vec<String>)
|
||||
),
|
||||
params(
|
||||
LatestVaasQueryParams
|
||||
)
|
||||
)]
|
||||
pub async fn latest_vaas(
|
||||
State(state): State<super::State>,
|
||||
QsQuery(params): QsQuery<LatestVaasQueryParams>,
|
||||
|
@ -124,7 +165,7 @@ pub struct LatestPriceFeedsQueryParams {
|
|||
get,
|
||||
path = "/api/latest_price_feeds",
|
||||
responses(
|
||||
(status = 200, description = "Price updates retrieved successfully", body = [Vec<RpcPriceFeed>])
|
||||
(status = 200, description = "Price updates retrieved successfully", body = Vec<RpcPriceFeed>)
|
||||
),
|
||||
params(
|
||||
LatestPriceFeedsQueryParams
|
||||
|
@ -151,16 +192,38 @@ pub async fn latest_price_feeds(
|
|||
))
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
#[derive(Debug, serde::Deserialize, IntoParams)]
|
||||
#[into_params(parameter_in=Query)]
|
||||
pub struct GetPriceFeedQueryParams {
|
||||
/// The id of the price feed to get an update for.
|
||||
id: PriceIdInput,
|
||||
/// The unix timestamp in seconds. This endpoint will return the first update
|
||||
/// whose publish_time is >= the provided value.
|
||||
#[param(value_type = i64, example=1690576641)]
|
||||
publish_time: UnixTimestamp,
|
||||
/// If true, include the `metadata` field in the response with additional metadata about
|
||||
/// the price update.
|
||||
#[serde(default)]
|
||||
verbose: bool,
|
||||
/// If true, include the binary price update in the `vaa` field of each returned feed.
|
||||
/// This binary data can be submitted to Pyth contracts to update the on-chain price.
|
||||
#[serde(default)]
|
||||
binary: bool,
|
||||
}
|
||||
|
||||
/// Get a price update for a price feed with a specific timestamp
|
||||
///
|
||||
/// Given a price feed id and timestamp, retrieve the Pyth price update closest to that timestamp.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/get_price_feed",
|
||||
responses(
|
||||
(status = 200, description = "Price update retrieved successfully", body = RpcPriceFeed)
|
||||
),
|
||||
params(
|
||||
GetPriceFeedQueryParams
|
||||
)
|
||||
)]
|
||||
pub async fn get_price_feed(
|
||||
State(state): State<super::State>,
|
||||
QsQuery(params): QsQuery<GetPriceFeedQueryParams>,
|
||||
|
@ -187,19 +250,40 @@ pub async fn get_price_feed(
|
|||
)))
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
#[derive(Debug, serde::Deserialize, IntoParams)]
|
||||
#[into_params(parameter_in=Query)]
|
||||
pub struct GetVaaQueryParams {
|
||||
/// The id of the price feed to get an update for.
|
||||
id: PriceIdInput,
|
||||
/// The unix timestamp in seconds. This endpoint will return the first update
|
||||
/// whose publish_time is >= the provided value.
|
||||
#[param(value_type = i64, example=1690576641)]
|
||||
publish_time: UnixTimestamp,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
#[derive(Debug, serde::Serialize, ToSchema)]
|
||||
pub struct GetVaaResponse {
|
||||
/// The VAA binary represented as a base64 string.
|
||||
vaa: String,
|
||||
#[serde(rename = "publishTime")]
|
||||
#[schema(value_type = i64, example=1690576641)]
|
||||
publish_time: UnixTimestamp,
|
||||
}
|
||||
|
||||
/// Get a VAA for a price feed with a specific timestamp
|
||||
///
|
||||
/// Given a price feed id and timestamp, retrieve the Pyth price update closest to that timestamp.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/get_vaa",
|
||||
responses(
|
||||
(status = 200, description = "Price update retrieved successfully", body = GetVaaResponse),
|
||||
(status = 404, description = "Price update not found", body = String)
|
||||
),
|
||||
params(
|
||||
GetVaaQueryParams
|
||||
)
|
||||
)]
|
||||
pub async fn get_vaa(
|
||||
State(state): State<super::State>,
|
||||
QsQuery(params): QsQuery<GetVaaQueryParams>,
|
||||
|
@ -231,20 +315,35 @@ pub async fn get_vaa(
|
|||
Ok(Json(GetVaaResponse { vaa, publish_time }))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deref, DerefMut)]
|
||||
#[derive(Debug, Clone, Deref, DerefMut, ToSchema)]
|
||||
pub struct GetVaaCcipInput([u8; 40]);
|
||||
impl_deserialize_for_hex_string_wrapper!(GetVaaCcipInput, 40);
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
#[derive(Debug, serde::Deserialize, IntoParams)]
|
||||
#[into_params(parameter_in=Query)]
|
||||
pub struct GetVaaCcipQueryParams {
|
||||
data: GetVaaCcipInput,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
#[derive(Debug, serde::Serialize, ToSchema)]
|
||||
pub struct GetVaaCcipResponse {
|
||||
data: String, // TODO: Use a typed wrapper for the hex output with leading 0x.
|
||||
}
|
||||
|
||||
/// Get a VAA for a price feed using CCIP
|
||||
///
|
||||
/// This endpoint accepts a single argument which is a hex-encoded byte string of the following form:
|
||||
/// `<price feed id (32 bytes> <publish time as unix timestamp (8 bytes, big endian)>`
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/get_vaa_ccip",
|
||||
responses(
|
||||
(status = 200, description = "Price update retrieved successfully", body = GetVaaCcipResponse)
|
||||
),
|
||||
params(
|
||||
GetVaaCcipQueryParams
|
||||
)
|
||||
)]
|
||||
pub async fn get_vaa_ccip(
|
||||
State(state): State<super::State>,
|
||||
QsQuery(params): QsQuery<GetVaaCcipQueryParams>,
|
||||
|
|
|
@ -11,14 +11,16 @@ use {
|
|||
engine::general_purpose::STANDARD as base64_standard_engine,
|
||||
Engine as _,
|
||||
},
|
||||
borsh::{
|
||||
BorshDeserialize,
|
||||
BorshSerialize,
|
||||
},
|
||||
derive_more::{
|
||||
Deref,
|
||||
DerefMut,
|
||||
},
|
||||
pyth_sdk::{
|
||||
Price,
|
||||
PriceIdentifier,
|
||||
},
|
||||
hex::FromHexError,
|
||||
pyth_sdk::PriceIdentifier,
|
||||
utoipa::ToSchema,
|
||||
wormhole_sdk::Chain,
|
||||
};
|
||||
|
@ -33,7 +35,7 @@ use {
|
|||
///
|
||||
/// See https://pyth.network/developers/price-feed-ids for a list of all price feed ids.
|
||||
#[derive(Debug, Clone, Deref, DerefMut, ToSchema)]
|
||||
#[schema(value_type=String)]
|
||||
#[schema(value_type=String, example="63f341689d98a12ef60a5cff1d7f85c70a9e17bf1575f0e7c0b2512d48b1c8b3")]
|
||||
pub struct PriceIdInput([u8; 32]);
|
||||
// TODO: Use const generics instead of macro.
|
||||
impl_deserialize_for_hex_string_wrapper!(PriceIdInput, 32);
|
||||
|
@ -58,13 +60,12 @@ pub struct RpcPriceFeedMetadata {
|
|||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||
pub struct RpcPriceFeed {
|
||||
#[schema(example = "e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43")]
|
||||
pub id: PriceIdentifier,
|
||||
pub price: Price,
|
||||
pub ema_price: Price,
|
||||
pub id: RpcPriceIdentifier,
|
||||
pub price: RpcPrice,
|
||||
pub ema_price: RpcPrice,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub metadata: Option<RpcPriceFeedMetadata>,
|
||||
/// Vaa binary represented in base64.
|
||||
/// The VAA binary represented as a base64 string.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[schema(value_type = Option<String>, example="UE5BVQEAAAADuAEAAAADDQC1H7meY5fTed0FsykIb8dt+7nKpbuzfvU2DplDi+dcUl8MC+UIkS65+rkiq+zmNBxE2gaxkBkjdIicZ/fBo+X7AAEqp+WtlWb84np8jJfLpuQ2W+l5KXTigsdAhz5DyVgU3xs+EnaIZxBwcE7EKzjMam+V9rlRy0CGsiQ1kjqqLzfAAQLsoVO0Vu5gVmgc8XGQ7xYhoz36rsBgMjG+e3l/B01esQi/KzPuBf/Ar8Sg5aSEOvEU0muSDb+KIr6d8eEC+FtcAAPZEaBSt4ysXVL84LUcJemQD3SiG30kOfUpF8o7/wI2M2Jf/LyCsbKEQUyLtLbZqnJBSfZJR5AMsrnHDqngMLEGAAY4UDG9GCpRuPvg8hOlsrXuPP3zq7yVPqyG0SG+bNo8rEhP5b1vXlHdG4bZsutX47d5VZ6xnFROKudx3T3/fnWUAQgAU1+kUFc3e0ZZeX1dLRVEryNIVyxMQIcxWwdey+jlIAYowHRM0fJX3Scs80OnT/CERwh5LMlFyU1w578NqxW+AQl2E/9fxjgUTi8crOfDpwsUsmOWw0+Q5OUGhELv/2UZoHAjsaw9OinWUggKACo4SdpPlHYldoWF+J2yGWOW+F4iAQre4c+ocb6a9uSWOnTldFkioqhd9lhmV542+VonCvuy4Tu214NP+2UNd/4Kk3KJCf3iziQJrCBeLi1cLHdLUikgAQtvRFR/nepcF9legl+DywAkUHi5/1MNjlEQvlHyh2XbMiS85yu7/9LgM6Sr+0ukfZY5mSkOcvUkpHn+T+Nw/IrQAQ7lty5luvKUmBpI3ITxSmojJ1aJ0kj/dc0ZcQk+/qo0l0l3/eRLkYjw5j+MZKA8jEubrHzUCke98eSoj8l08+PGAA+DAKNtCwNZe4p6J1Ucod8Lo5RKFfA84CPLVyEzEPQFZ25U9grUK6ilF4GhEia/ndYXLBt3PGW3qa6CBBPM7rH3ABGAyYEtUwzB4CeVedA5o6cKpjRkIebqDNSOqltsr+w7kXdfFVtsK2FMGFZNt5rbpIR+ppztoJ6eOKHmKmi9nQ99ARKkTxRErOs9wJXNHaAuIRV38o1pxRrlQRzGsRuKBqxcQEpC8OPFpyKYcp6iD5l7cO/gRDTamLFyhiUBwKKMP07FAWTEJv8AAAAAABrhAfrtrFhR4yubI7X5QRqMK6xKrj7U3XuBHdGnLqSqcQAAAAAAGp0GAUFVV1YAAAAAAAUYUmIAACcQBsfKUtr4PgZbIXRxRESU79PjE4IBAFUA5i32yLSoX+GmfbRNwS3l2zMPesZrctxliv7fD0pBW0MAAAKqqMJFwAAAAAAqE/NX////+AAAAABkxCb7AAAAAGTEJvoAAAKqIcWxYAAAAAAlR5m4CP/mPsh1IezjYpDlJ4GRb5q4fTs2LjtyO6M0XgVimrIQ4kSh1qg7JKW4gbGkyRntVFR9JO/GNd3FPDit0BK6M+JzXh/h12YNCz9wxlZTvXrNtWNbzqT+91pvl5cphhSPMfAHyEzTPaGR9tKDy9KNu56pmhaY32d2vfEWQmKo22guegeR98oDxs67MmnUraco46a3zEnac2Bm80pasUgMO24=")]
|
||||
pub vaa: Option<Base64String>,
|
||||
|
@ -81,14 +82,14 @@ impl RpcPriceFeed {
|
|||
let price_feed_message = price_feed_update.price_feed;
|
||||
|
||||
Self {
|
||||
id: PriceIdentifier::new(price_feed_message.feed_id),
|
||||
price: Price {
|
||||
id: RpcPriceIdentifier::new(price_feed_message.feed_id),
|
||||
price: RpcPrice {
|
||||
price: price_feed_message.price,
|
||||
conf: price_feed_message.conf,
|
||||
expo: price_feed_message.exponent,
|
||||
publish_time: price_feed_message.publish_time,
|
||||
},
|
||||
ema_price: Price {
|
||||
ema_price: RpcPrice {
|
||||
price: price_feed_message.ema_price,
|
||||
conf: price_feed_message.ema_conf,
|
||||
expo: price_feed_message.exponent,
|
||||
|
@ -105,3 +106,72 @@ impl RpcPriceFeed {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A price with a degree of uncertainty at a certain time, represented as a price +- a confidence
|
||||
/// interval.
|
||||
///
|
||||
/// The confidence interval roughly corresponds to the standard error of a normal distribution.
|
||||
/// Both the price and confidence are stored in a fixed-point numeric representation, `x *
|
||||
/// 10^expo`, where `expo` is the exponent. For example:
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Default,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
BorshSerialize,
|
||||
BorshDeserialize,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
ToSchema,
|
||||
)]
|
||||
pub struct RpcPrice {
|
||||
/// The price itself, stored as a string to avoid precision loss
|
||||
#[serde(with = "pyth_sdk::utils::as_string")]
|
||||
#[schema(value_type = String, example="2920679499999")]
|
||||
pub price: i64,
|
||||
/// The confidence interval associated with the price, stored as a string to avoid precision loss
|
||||
#[serde(with = "pyth_sdk::utils::as_string")]
|
||||
#[schema(value_type = String, example="509500001")]
|
||||
pub conf: u64,
|
||||
/// The exponent associated with both the price and confidence interval. Multiply those values
|
||||
/// by `10^expo` to get the real value.
|
||||
#[schema(example=-8)]
|
||||
pub expo: i32,
|
||||
/// When the price was published. The `publish_time` is a unix timestamp, i.e., the number of
|
||||
/// seconds since the Unix epoch (00:00:00 UTC on 1 Jan 1970).
|
||||
#[schema(value_type = i64, example=1690576641)]
|
||||
pub publish_time: UnixTimestamp,
|
||||
}
|
||||
|
||||
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Default,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Hash,
|
||||
BorshSerialize,
|
||||
BorshDeserialize,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
ToSchema,
|
||||
)]
|
||||
#[repr(C)]
|
||||
#[schema(value_type = String, example = "e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43")]
|
||||
pub struct RpcPriceIdentifier(#[serde(with = "hex")] [u8; 32]);
|
||||
|
||||
impl RpcPriceIdentifier {
|
||||
pub fn new(bytes: [u8; 32]) -> RpcPriceIdentifier {
|
||||
RpcPriceIdentifier(bytes)
|
||||
}
|
||||
|
||||
pub fn from(id: &PriceIdentifier) -> RpcPriceIdentifier {
|
||||
RpcPriceIdentifier(id.to_bytes().clone())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,10 @@ pub struct ProofSet {
|
|||
}
|
||||
|
||||
pub type Slot = u64;
|
||||
|
||||
/// The number of seconds since the Unix epoch (00:00:00 UTC on 1 Jan 1970). The timestamp is
|
||||
/// always positive, but represented as a signed integer because that's the standard on Unix
|
||||
/// systems and allows easy subtraction to compute durations.
|
||||
pub type UnixTimestamp = i64;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
|
|
Loading…
Reference in New Issue