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