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(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, FromRepr)]
105#[serde(rename_all = "camelCase")]
106#[repr(u8)]
107pub enum PriceFeedProperty {
108    Price,
109    BestBidPrice,
110    BestAskPrice,
111    PublisherCount,
112    Exponent,
113    Confidence,
114    FundingRate,
115    FundingTimestamp,
116    FundingRateInterval,
117    MarketSession,
118    EmaPrice,
119    EmaConfidence,
120    FeedUpdateTimestamp,
121    // More fields may be added later.
122}
123
124#[derive(Debug, Clone, Deserialize, strum::EnumIter)]
125#[serde(rename_all = "kebab-case")]
126pub enum AssetClass {
127    Crypto,
128    Fx,
129    Equity,
130    Metal,
131    Rates,
132    Nav,
133    Commodity,
134    FundingRate,
135    Eco,
136    Kalshi,
137}
138
139impl AssetClass {
140    pub fn as_str(&self) -> &'static str {
141        match self {
142            AssetClass::Crypto => "crypto",
143            AssetClass::Fx => "fx",
144            AssetClass::Equity => "equity",
145            AssetClass::Metal => "metal",
146            AssetClass::Rates => "rates",
147            AssetClass::Nav => "nav",
148            AssetClass::Commodity => "commodity",
149            AssetClass::FundingRate => "funding-rate",
150            AssetClass::Eco => "eco",
151            AssetClass::Kalshi => "kalshi",
152        }
153    }
154}
155
156// Operation and coefficient for converting value to mantissa.
157enum ExponentFactor {
158    // mantissa = value * factor
159    Mul(i64),
160    // mantissa = value / factor
161    Div(i64),
162}
163
164impl ExponentFactor {
165    fn get(exponent: i16) -> Option<Self> {
166        if exponent >= 0 {
167            let exponent: u32 = exponent.try_into().ok()?;
168            Some(ExponentFactor::Div(10_i64.checked_pow(exponent)?))
169        } else {
170            let minus_exponent: u32 = exponent.checked_neg()?.try_into().ok()?;
171            Some(ExponentFactor::Mul(10_i64.checked_pow(minus_exponent)?))
172        }
173    }
174}
175
176pub fn parse_proto_json<M: MessageFull>(
177    json: &str,
178) -> Result<M, protobuf_json_mapping::ParseError> {
179    protobuf_json_mapping::parse_from_str_with_options::<M>(
180        json,
181        &protobuf_json_mapping::ParseOptions {
182            ignore_unknown_fields: true,
183            ..Default::default()
184        },
185    )
186}
187
188#[test]
189fn magics_in_big_endian() {
190    use crate::{
191        binary_update::BINARY_UPDATE_FORMAT_MAGIC,
192        message::format_magics_le::{
193            EVM_FORMAT_MAGIC, JSON_FORMAT_MAGIC, LE_ECDSA_FORMAT_MAGIC, LE_UNSIGNED_FORMAT_MAGIC,
194            SOLANA_FORMAT_MAGIC,
195        },
196        payload::PAYLOAD_FORMAT_MAGIC,
197    };
198
199    // The values listed in this test can be used when reading the magic headers in BE format
200    // (e.g., on EVM).
201
202    assert_eq!(u32::swap_bytes(BINARY_UPDATE_FORMAT_MAGIC), 1937213467);
203    assert_eq!(u32::swap_bytes(PAYLOAD_FORMAT_MAGIC), 1976813459);
204
205    assert_eq!(u32::swap_bytes(SOLANA_FORMAT_MAGIC), 3103857282);
206    assert_eq!(u32::swap_bytes(JSON_FORMAT_MAGIC), 2584795844);
207    assert_eq!(u32::swap_bytes(EVM_FORMAT_MAGIC), 706910618);
208    assert_eq!(u32::swap_bytes(LE_ECDSA_FORMAT_MAGIC), 3837609805);
209    assert_eq!(u32::swap_bytes(LE_UNSIGNED_FORMAT_MAGIC), 206398297);
210
211    for magic in [
212        BINARY_UPDATE_FORMAT_MAGIC,
213        PAYLOAD_FORMAT_MAGIC,
214        SOLANA_FORMAT_MAGIC,
215        JSON_FORMAT_MAGIC,
216        EVM_FORMAT_MAGIC,
217        LE_ECDSA_FORMAT_MAGIC,
218        LE_UNSIGNED_FORMAT_MAGIC,
219    ] {
220        // Required to distinguish between byte orders.
221        assert_ne!(u32::swap_bytes(magic), magic);
222    }
223}
224
225#[test]
226fn parse_proto_json_works() {
227    use protobuf::descriptor::descriptor_proto::ExtensionRange;
228
229    let ts1 = parse_proto_json::<ExtensionRange>(r#"{"start":1,"end":2}"#).unwrap();
230    assert_eq!(ts1.start, Some(1));
231    assert_eq!(ts1.end, Some(2));
232
233    let ts2 = parse_proto_json::<ExtensionRange>(r#"{"start":3,"extra":5}"#).unwrap();
234    assert_eq!(ts2.start, Some(3));
235    assert_eq!(ts2.end, None);
236}