get_vaa.rs 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. use {
  2. super::verify_price_ids_exist,
  3. crate::{
  4. aggregate::{
  5. get_price_feeds_with_update_data,
  6. RequestTime,
  7. UnixTimestamp,
  8. },
  9. api::{
  10. doc_examples,
  11. rest::RestError,
  12. types::PriceIdInput,
  13. },
  14. },
  15. anyhow::Result,
  16. axum::{
  17. extract::State,
  18. Json,
  19. },
  20. base64::{
  21. engine::general_purpose::STANDARD as base64_standard_engine,
  22. Engine as _,
  23. },
  24. pyth_sdk::PriceIdentifier,
  25. serde_qs::axum::QsQuery,
  26. utoipa::{
  27. IntoParams,
  28. ToSchema,
  29. },
  30. };
  31. #[derive(Debug, serde::Deserialize, IntoParams)]
  32. #[into_params(parameter_in=Query)]
  33. pub struct GetVaaQueryParams {
  34. /// The ID of the price feed to get an update for.
  35. id: PriceIdInput,
  36. /// The unix timestamp in seconds. This endpoint will return the first update whose
  37. /// publish_time is >= the provided value.
  38. #[param(value_type = i64)]
  39. #[param(example = 1690576641)]
  40. publish_time: UnixTimestamp,
  41. }
  42. #[derive(Debug, serde::Serialize, ToSchema)]
  43. pub struct GetVaaResponse {
  44. /// The VAA binary represented as a base64 string.
  45. #[schema(example = doc_examples::vaa_example)]
  46. vaa: String,
  47. #[serde(rename = "publishTime")]
  48. #[schema(value_type = i64)]
  49. #[schema(example = 1690576641)]
  50. publish_time: UnixTimestamp,
  51. }
  52. /// Get a VAA for a price feed with a specific timestamp
  53. ///
  54. /// Given a price feed id and timestamp, retrieve the Pyth price update closest to that timestamp.
  55. #[utoipa::path(
  56. get,
  57. path = "/api/get_vaa",
  58. responses(
  59. (status = 200, description = "Price update retrieved successfully", body = GetVaaResponse),
  60. (status = 404, description = "Price update not found", body = String)
  61. ),
  62. params(
  63. GetVaaQueryParams
  64. )
  65. )]
  66. pub async fn get_vaa(
  67. State(state): State<crate::api::ApiState>,
  68. QsQuery(params): QsQuery<GetVaaQueryParams>,
  69. ) -> Result<Json<GetVaaResponse>, RestError> {
  70. let price_id: PriceIdentifier = params.id.into();
  71. verify_price_ids_exist(&state, &[price_id]).await?;
  72. let price_feeds_with_update_data = get_price_feeds_with_update_data(
  73. &*state.state,
  74. &[price_id],
  75. RequestTime::FirstAfter(params.publish_time),
  76. )
  77. .await
  78. .map_err(|e| {
  79. tracing::warn!(
  80. "Error getting price feed {:?} with update data: {:?}",
  81. price_id,
  82. e
  83. );
  84. RestError::UpdateDataNotFound
  85. })?;
  86. let vaa = price_feeds_with_update_data
  87. .update_data
  88. .get(0)
  89. .map(|bytes| base64_standard_engine.encode(bytes))
  90. .ok_or(RestError::UpdateDataNotFound)?;
  91. let price_feed = price_feeds_with_update_data
  92. .price_feeds
  93. .into_iter()
  94. .next()
  95. .ok_or(RestError::UpdateDataNotFound)?;
  96. let publish_time = price_feed.price_feed.get_price_unchecked().publish_time;
  97. // Currently Benchmarks API doesn't support returning the previous publish time. So we
  98. // are assuming that it is doing the same filter as us and returns not found if the
  99. // price update is not unique.
  100. if let Some(prev_publish_time) = price_feed.prev_publish_time {
  101. if prev_publish_time == price_feed.price_feed.get_price_unchecked().publish_time {
  102. return Err(RestError::BenchmarkPriceNotUnique);
  103. }
  104. }
  105. Ok(Json(GetVaaResponse { vaa, publish_time }))
  106. }