parent
7674a3a07c
commit
07b01118d2
|
@ -35,14 +35,23 @@ To set up and run a Hermes node, follow the steps below:
|
||||||
```
|
```
|
||||||
This will create a binary in the target/release directory.
|
This will create a binary in the target/release directory.
|
||||||
5. **Run the node**: To run Hermes for Pythnet, use the following command:
|
5. **Run the node**: To run Hermes for Pythnet, use the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./target/release/hermes run \
|
./target/release/hermes run \
|
||||||
--pythnet-http-endpoint https://pythnet-rpc/ \
|
--pythnet-http-endpoint https://pythnet-rpc/ \
|
||||||
--pythnet-ws-endpoint wss://pythnet-rpc/
|
--pythnet-ws-endpoint wss://pythnet-rpc/
|
||||||
```
|
```
|
||||||
|
|
||||||
Your Hermes node will now start and connect to the specified networks. You
|
Your Hermes node will now start and connect to the specified networks. You
|
||||||
can interact with the node using the REST and Websocket APIs on port 33999.
|
can interact with the node using the REST and Websocket APIs on port 33999.
|
||||||
|
|
||||||
|
For local development, you can also run the node with cargo watch to restart
|
||||||
|
it automatically when the code changes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo watch -w src -x "run -- run --pythnet-http-endpoint https://pythnet.rpcpool.com --pythnet-ws-endpoint wss://pythnet.rpcpool.com"
|
||||||
|
```
|
||||||
|
|
||||||
## Architecture Overview
|
## Architecture Overview
|
||||||
|
|
||||||
For users who simply want to run the software, this section can be skipped.
|
For users who simply want to run the software, this section can be skipped.
|
||||||
|
|
|
@ -3,24 +3,18 @@ use {
|
||||||
crate::store::Store,
|
crate::store::Store,
|
||||||
anyhow::Result,
|
anyhow::Result,
|
||||||
axum::{
|
axum::{
|
||||||
|
extract::Extension,
|
||||||
routing::get,
|
routing::get,
|
||||||
Router,
|
Router,
|
||||||
},
|
},
|
||||||
|
serde_qs::axum::QsQueryConfig,
|
||||||
std::sync::Arc,
|
std::sync::Arc,
|
||||||
tokio::{
|
tokio::{
|
||||||
signal,
|
signal,
|
||||||
sync::mpsc::Receiver,
|
sync::mpsc::Receiver,
|
||||||
},
|
},
|
||||||
tower_http::cors::CorsLayer,
|
tower_http::cors::CorsLayer,
|
||||||
utoipa::{
|
utoipa::OpenApi,
|
||||||
openapi::security::{
|
|
||||||
ApiKey,
|
|
||||||
ApiKeyValue,
|
|
||||||
SecurityScheme,
|
|
||||||
},
|
|
||||||
Modify,
|
|
||||||
OpenApi,
|
|
||||||
},
|
|
||||||
utoipa_swagger_ui::SwaggerUi,
|
utoipa_swagger_ui::SwaggerUi,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -54,7 +48,7 @@ pub async fn run(store: Arc<Store>, mut update_rx: Receiver<()>, rpc_addr: Strin
|
||||||
rest::latest_price_feeds,
|
rest::latest_price_feeds,
|
||||||
),
|
),
|
||||||
components(
|
components(
|
||||||
schemas(types::RpcPriceFeedMetadata, types::RpcPriceFeed)
|
schemas(types::RpcPriceFeedMetadata, types::RpcPriceFeed, types::PriceIdInput)
|
||||||
),
|
),
|
||||||
tags(
|
tags(
|
||||||
(name = "hermes", description = "Pyth Real-Time Pricing API")
|
(name = "hermes", description = "Pyth Real-Time Pricing API")
|
||||||
|
@ -80,7 +74,11 @@ pub async fn run(store: Arc<Store>, mut update_rx: Receiver<()>, rpc_addr: Strin
|
||||||
.route("/api/get_vaa_ccip", get(rest::get_vaa_ccip))
|
.route("/api/get_vaa_ccip", get(rest::get_vaa_ccip))
|
||||||
.route("/api/price_feed_ids", get(rest::price_feed_ids))
|
.route("/api/price_feed_ids", get(rest::price_feed_ids))
|
||||||
.with_state(state.clone())
|
.with_state(state.clone())
|
||||||
.layer(CorsLayer::permissive()); // Permissive CORS layer to allow all origins
|
// Permissive CORS layer to allow all origins
|
||||||
|
.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)));
|
||||||
|
|
||||||
|
|
||||||
// Call dispatch updates to websocket every 1 seconds
|
// Call dispatch updates to websocket every 1 seconds
|
||||||
|
|
|
@ -97,26 +97,34 @@ pub async fn latest_vaas(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, IntoParams)]
|
#[derive(Debug, serde::Deserialize, IntoParams)]
|
||||||
|
#[into_params(parameter_in=Query)]
|
||||||
pub struct LatestPriceFeedsQueryParams {
|
pub struct LatestPriceFeedsQueryParams {
|
||||||
#[param(value_type = String)]
|
/// Get the most recent price update 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>,
|
ids: Vec<PriceIdInput>,
|
||||||
|
/// If true, include the `metadata` field in the response with additional metadata about
|
||||||
|
/// the price update.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[param(value_type = Option<bool>, required = false, nullable = true)]
|
|
||||||
verbose: bool,
|
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)]
|
#[serde(default)]
|
||||||
#[param(value_type = Option<bool>, required = false, nullable = true)]
|
|
||||||
binary: bool,
|
binary: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the latest prices by price feed ids.
|
/// Get the latest price updates by price feed id.
|
||||||
///
|
|
||||||
/// Get the latest price updates for a provided collection of price feed ids.
|
|
||||||
///
|
///
|
||||||
|
/// Given a collection of price feed ids, retrieve the latest Pyth price for each price feed.
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
get,
|
get,
|
||||||
path = "/api/latest_price_feeds",
|
path = "/api/latest_price_feeds",
|
||||||
responses(
|
responses(
|
||||||
(status = 200, description = "Price feeds retrieved successfully", body = [Vec<RpcPriceFeed>])
|
(status = 200, description = "Price updates retrieved successfully", body = [Vec<RpcPriceFeed>])
|
||||||
),
|
),
|
||||||
params(
|
params(
|
||||||
LatestPriceFeedsQueryParams
|
LatestPriceFeedsQueryParams
|
||||||
|
|
|
@ -19,23 +19,21 @@ use {
|
||||||
Price,
|
Price,
|
||||||
PriceIdentifier,
|
PriceIdentifier,
|
||||||
},
|
},
|
||||||
utoipa::{
|
utoipa::ToSchema,
|
||||||
openapi::{
|
|
||||||
RefOr,
|
|
||||||
Schema,
|
|
||||||
},
|
|
||||||
IntoParams,
|
|
||||||
ToSchema,
|
|
||||||
},
|
|
||||||
wormhole_sdk::Chain,
|
wormhole_sdk::Chain,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/// PriceIdInput is a wrapper around a 32-byte hex string.
|
/// A price id is a 32-byte hex string, optionally prefixed with "0x".
|
||||||
/// that supports a flexible deserialization from a hex string.
|
/// Price ids are case insensitive.
|
||||||
/// It supports both 0x-prefixed and non-prefixed hex strings,
|
///
|
||||||
/// and also supports both lower and upper case characters.
|
/// Examples:
|
||||||
#[derive(Debug, Clone, Deref, DerefMut)]
|
/// * 0x63f341689d98a12ef60a5cff1d7f85c70a9e17bf1575f0e7c0b2512d48b1c8b3
|
||||||
|
/// * 63f341689d98a12ef60a5cff1d7f85c70a9e17bf1575f0e7c0b2512d48b1c8b3
|
||||||
|
///
|
||||||
|
/// 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)]
|
||||||
pub struct PriceIdInput([u8; 32]);
|
pub struct PriceIdInput([u8; 32]);
|
||||||
// TODO: Use const generics instead of macro.
|
// TODO: Use const generics instead of macro.
|
||||||
impl_deserialize_for_hex_string_wrapper!(PriceIdInput, 32);
|
impl_deserialize_for_hex_string_wrapper!(PriceIdInput, 32);
|
||||||
|
@ -50,15 +48,17 @@ type Base64String = String;
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||||
pub struct RpcPriceFeedMetadata {
|
pub struct RpcPriceFeedMetadata {
|
||||||
#[schema(value_type = u64)]
|
#[schema(value_type = u64, example=85480034)]
|
||||||
pub slot: Slot,
|
pub slot: Slot,
|
||||||
|
#[schema(example = 26)]
|
||||||
pub emitter_chain: u16,
|
pub emitter_chain: u16,
|
||||||
#[schema(value_type = i64)]
|
#[schema(value_type = i64, example=1690576641)]
|
||||||
pub price_service_receive_time: UnixTimestamp,
|
pub price_service_receive_time: UnixTimestamp,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
|
||||||
pub struct RpcPriceFeed {
|
pub struct RpcPriceFeed {
|
||||||
|
#[schema(example = "e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43")]
|
||||||
pub id: PriceIdentifier,
|
pub id: PriceIdentifier,
|
||||||
pub price: Price,
|
pub price: Price,
|
||||||
pub ema_price: Price,
|
pub ema_price: Price,
|
||||||
|
@ -66,7 +66,7 @@ pub struct RpcPriceFeed {
|
||||||
pub metadata: Option<RpcPriceFeedMetadata>,
|
pub metadata: Option<RpcPriceFeedMetadata>,
|
||||||
/// Vaa binary represented in base64.
|
/// Vaa binary represented in base64.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[schema(value_type = Option<String>)]
|
#[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>,
|
pub vaa: Option<Base64String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue