Skip to main content

pyth_lazer_protocol/
lib.rs

1//! Lazer type definitions and utilities.
2
3/// Types describing Lazer HTTP and WebSocket APIs.
4pub mod api;
5/// Binary delivery format for WebSocket.
6pub mod binary_update;
7mod dynamic_value;
8mod exchange_enums;
9mod feed_kind;
10pub mod hermes;
11/// Lazer Agent JSON-RPC API.
12pub mod jrpc;
13/// Types describing Lazer's verifiable messages containing signature and payload.
14pub mod message;
15/// Types describing Lazer's message payload.
16pub mod payload;
17mod price;
18/// Legacy Websocket API for publishers.
19pub mod publisher;
20mod rate;
21mod serde_price_as_i64;
22mod serde_str;
23mod symbol_state;
24/// Lazer's types for time representation.
25pub mod time;
26
27use {
28    protobuf::MessageFull,
29    serde::{Deserialize, Serialize},
30};
31
32use {
33    derive_more::{From, Into},
34    strum::FromRepr,
35};
36
37pub use crate::{
38    dynamic_value::DynamicValue,
39    exchange_enums::{ExchangeAssetClass, ExchangeAssetSector, ExchangeAssetSubclass},
40    feed_kind::FeedKind,
41    price::{Price, PriceError},
42    rate::{Rate, RateError},
43    symbol_state::SymbolState,
44};
45
46#[derive(
47    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, From, Into,
48)]
49pub struct AssetId(pub u32);
50
51#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
52#[derive(
53    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, From, Into,
54)]
55#[cfg_attr(feature = "utoipa", schema(value_type = u32))]
56pub struct ExchangeId(pub u32);
57
58#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
59#[derive(
60    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, From, Into,
61)]
62#[cfg_attr(feature = "utoipa", schema(value_type = u16))]
63pub struct PublisherId(pub u16);
64
65#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
66#[derive(
67    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, From, Into,
68)]
69#[cfg_attr(feature = "utoipa", schema(value_type = u32))]
70pub struct PriceFeedId(pub u32);
71
72#[derive(
73    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, From, Into,
74)]
75pub struct ChannelId(pub u8);
76
77/// Per-publisher datapoint in an aggregate. Represents a price mantissa for
78/// price feeds or a rate mantissa for funding rate feeds.
79#[derive(
80    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, From, Into,
81)]
82pub struct PublisherDatapoint(pub i64);
83
84impl From<Price> for PublisherDatapoint {
85    fn from(price: Price) -> Self {
86        Self(price.mantissa_i64())
87    }
88}
89
90impl From<Rate> for PublisherDatapoint {
91    fn from(rate: Rate) -> Self {
92        Self(rate.mantissa())
93    }
94}
95
96impl ChannelId {
97    pub const REAL_TIME: ChannelId = ChannelId(1);
98    pub const FIXED_RATE_50: ChannelId = ChannelId(2);
99    pub const FIXED_RATE_200: ChannelId = ChannelId(3);
100    pub const FIXED_RATE_1000: ChannelId = ChannelId(4);
101}
102
103#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
104#[derive(
105    Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, FromRepr, strum::EnumIter,
106)]
107#[serde(rename_all = "camelCase")]
108#[repr(u8)]
109pub enum PriceFeedProperty {
110    Price,
111    BestBidPrice,
112    BestAskPrice,
113    PublisherCount,
114    Exponent,
115    Confidence,
116    FundingRate,
117    FundingTimestamp,
118    FundingRateInterval,
119    MarketSession,
120    EmaPrice,
121    EmaConfidence,
122    FeedUpdateTimestamp,
123    // More fields may be added later.
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, strum::IntoStaticStr, strum::EnumIter)]
127#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
128#[serde(rename_all = "kebab-case")]
129#[strum(serialize_all = "kebab-case")]
130pub enum AssetClass {
131    Crypto,
132    Fx,
133    Equity,
134    Metal,
135    Rates,
136    Nav,
137    Commodity,
138    FundingRate,
139    Eco,
140    Kalshi,
141    InterestRate,
142}
143
144impl AssetClass {
145    pub fn as_str(&self) -> &'static str {
146        self.into()
147    }
148}
149
150#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
151#[derive(Debug, Clone, Copy, Serialize, Deserialize, strum::IntoStaticStr)]
152#[serde(rename_all = "kebab-case")]
153#[strum(serialize_all = "kebab-case")]
154pub enum InstrumentType {
155    Spot,
156    Rate,
157    Future,
158    Index,
159    Nav,
160}
161
162// Operation and coefficient for converting value to mantissa.
163enum ExponentFactor {
164    // mantissa = value * factor
165    Mul(i64),
166    // mantissa = value / factor
167    Div(i64),
168}
169
170impl ExponentFactor {
171    fn get(exponent: i16) -> Option<Self> {
172        if exponent >= 0 {
173            let exponent: u32 = exponent.try_into().ok()?;
174            Some(ExponentFactor::Div(10_i64.checked_pow(exponent)?))
175        } else {
176            let minus_exponent: u32 = exponent.checked_neg()?.try_into().ok()?;
177            Some(ExponentFactor::Mul(10_i64.checked_pow(minus_exponent)?))
178        }
179    }
180}
181
182pub fn parse_proto_json<M: MessageFull>(
183    json: &str,
184) -> Result<M, protobuf_json_mapping::ParseError> {
185    protobuf_json_mapping::parse_from_str_with_options::<M>(
186        json,
187        &protobuf_json_mapping::ParseOptions {
188            ignore_unknown_fields: true,
189            ..Default::default()
190        },
191    )
192}
193
194#[test]
195fn magics_in_big_endian() {
196    use crate::{
197        binary_update::BINARY_UPDATE_FORMAT_MAGIC,
198        message::format_magics_le::{
199            EVM_FORMAT_MAGIC, JSON_FORMAT_MAGIC, LE_ECDSA_FORMAT_MAGIC, LE_UNSIGNED_FORMAT_MAGIC,
200            SOLANA_FORMAT_MAGIC,
201        },
202        payload::PAYLOAD_FORMAT_MAGIC,
203    };
204
205    // The values listed in this test can be used when reading the magic headers in BE format
206    // (e.g., on EVM).
207
208    assert_eq!(u32::swap_bytes(BINARY_UPDATE_FORMAT_MAGIC), 1937213467);
209    assert_eq!(u32::swap_bytes(PAYLOAD_FORMAT_MAGIC), 1976813459);
210
211    assert_eq!(u32::swap_bytes(SOLANA_FORMAT_MAGIC), 3103857282);
212    assert_eq!(u32::swap_bytes(JSON_FORMAT_MAGIC), 2584795844);
213    assert_eq!(u32::swap_bytes(EVM_FORMAT_MAGIC), 706910618);
214    assert_eq!(u32::swap_bytes(LE_ECDSA_FORMAT_MAGIC), 3837609805);
215    assert_eq!(u32::swap_bytes(LE_UNSIGNED_FORMAT_MAGIC), 206398297);
216
217    for magic in [
218        BINARY_UPDATE_FORMAT_MAGIC,
219        PAYLOAD_FORMAT_MAGIC,
220        SOLANA_FORMAT_MAGIC,
221        JSON_FORMAT_MAGIC,
222        EVM_FORMAT_MAGIC,
223        LE_ECDSA_FORMAT_MAGIC,
224        LE_UNSIGNED_FORMAT_MAGIC,
225    ] {
226        // Required to distinguish between byte orders.
227        assert_ne!(u32::swap_bytes(magic), magic);
228    }
229}
230
231#[test]
232fn parse_proto_json_works() {
233    use protobuf::descriptor::descriptor_proto::ExtensionRange;
234
235    let ts1 = parse_proto_json::<ExtensionRange>(r#"{"start":1,"end":2}"#).unwrap();
236    assert_eq!(ts1.start, Some(1));
237    assert_eq!(ts1.end, Some(2));
238
239    let ts2 = parse_proto_json::<ExtensionRange>(r#"{"start":3,"extra":5}"#).unwrap();
240    assert_eq!(ts2.start, Some(3));
241    assert_eq!(ts2.end, None);
242}