1use crate::router::{Channel, Price, PriceFeedId, Rate, TimestampUs};
2use crate::symbol_state::SymbolState;
3use serde::{Deserialize, Serialize};
4use std::time::Duration;
5
6#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
7pub struct PythLazerAgentJrpcV1 {
8 pub jsonrpc: JsonRpcVersion,
9 #[serde(flatten)]
10 pub params: JrpcCall,
11 pub id: i64,
12}
13
14#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
15#[serde(tag = "method", content = "params")]
16#[serde(rename_all = "snake_case")]
17pub enum JrpcCall {
18 PushUpdate(FeedUpdateParams),
19 GetMetadata(GetMetadataParams),
20}
21
22#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
23pub struct FeedUpdateParams {
24 pub feed_id: PriceFeedId,
25 pub source_timestamp: TimestampUs,
26 pub update: UpdateParams,
27}
28
29#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
30#[serde(tag = "type")]
31pub enum UpdateParams {
32 #[serde(rename = "price")]
33 PriceUpdate {
34 price: Price,
35 best_bid_price: Option<Price>,
36 best_ask_price: Option<Price>,
37 },
38 #[serde(rename = "funding_rate")]
39 FundingRateUpdate { price: Option<Price>, rate: Rate },
40}
41
42#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
43pub struct Filter {
44 pub name: Option<String>,
45 pub asset_type: Option<String>,
46}
47
48#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
49pub struct GetMetadataParams {
50 pub names: Option<Vec<String>>,
51 pub asset_types: Option<Vec<String>>,
52}
53
54#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
55pub enum JsonRpcVersion {
56 #[serde(rename = "2.0")]
57 V2,
58}
59
60#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
61pub enum JrpcResponse<T> {
62 Success(JrpcSuccessResponse<T>),
63 Error(JrpcErrorResponse),
64}
65
66#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
67pub struct JrpcSuccessResponse<T> {
68 pub jsonrpc: JsonRpcVersion,
69 pub result: T,
70 pub id: i64,
71}
72
73#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
74pub struct JrpcErrorResponse {
75 pub jsonrpc: JsonRpcVersion,
76 pub error: JrpcErrorObject,
77 pub id: Option<i64>,
78}
79
80#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
81pub struct JrpcErrorObject {
82 pub code: i64,
83 pub message: String,
84 #[serde(skip_serializing_if = "Option::is_none")]
85 pub data: Option<serde_json::Value>,
86}
87
88#[derive(Debug, Eq, PartialEq)]
89pub enum JrpcError {
90 ParseError(String),
91 InternalError,
92}
93
94impl From<JrpcError> for JrpcErrorObject {
96 fn from(error: JrpcError) -> Self {
97 match error {
98 JrpcError::ParseError(error_message) => JrpcErrorObject {
99 code: -32700,
100 message: "Parse error".to_string(),
101 data: Some(error_message.into()),
102 },
103 JrpcError::InternalError => JrpcErrorObject {
104 code: -32603,
105 message: "Internal error".to_string(),
106 data: None,
107 },
108 }
109 }
110}
111
112#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
113pub struct SymbolMetadata {
114 pub pyth_lazer_id: PriceFeedId,
115 pub name: String,
116 pub symbol: String,
117 pub description: String,
118 pub asset_type: String,
119 pub exponent: i16,
120 pub cmc_id: Option<u32>,
121 #[serde(default, with = "humantime_serde", alias = "interval")]
122 pub funding_rate_interval: Option<Duration>,
123 pub min_publishers: u16,
124 pub min_channel: Channel,
125 pub state: SymbolState,
126 pub hermes_id: Option<String>,
127 pub quote_currency: Option<String>,
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use crate::jrpc::JrpcCall::{GetMetadata, PushUpdate};
134
135 #[test]
136 fn test_push_update_price() {
137 let json = r#"
138 {
139 "jsonrpc": "2.0",
140 "method": "push_update",
141 "params": {
142 "feed_id": 1,
143 "source_timestamp": 124214124124,
144
145 "update": {
146 "type": "price",
147 "price": 1234567890,
148 "best_bid_price": 1234567891,
149 "best_ask_price": 1234567892
150 }
151 },
152 "id": 1
153 }
154 "#;
155
156 let expected = PythLazerAgentJrpcV1 {
157 jsonrpc: JsonRpcVersion::V2,
158 params: PushUpdate(FeedUpdateParams {
159 feed_id: PriceFeedId(1),
160 source_timestamp: TimestampUs(124214124124),
161 update: UpdateParams::PriceUpdate {
162 price: Price::from_integer(1234567890, 0).unwrap(),
163 best_bid_price: Some(Price::from_integer(1234567891, 0).unwrap()),
164 best_ask_price: Some(Price::from_integer(1234567892, 0).unwrap()),
165 },
166 }),
167 id: 1,
168 };
169
170 assert_eq!(
171 serde_json::from_str::<PythLazerAgentJrpcV1>(json).unwrap(),
172 expected
173 );
174 }
175
176 #[test]
177 fn test_push_update_price_without_bid_ask() {
178 let json = r#"
179 {
180 "jsonrpc": "2.0",
181 "method": "push_update",
182 "params": {
183 "feed_id": 1,
184 "source_timestamp": 124214124124,
185
186 "update": {
187 "type": "price",
188 "price": 1234567890
189 }
190 },
191 "id": 1
192 }
193 "#;
194
195 let expected = PythLazerAgentJrpcV1 {
196 jsonrpc: JsonRpcVersion::V2,
197 params: PushUpdate(FeedUpdateParams {
198 feed_id: PriceFeedId(1),
199 source_timestamp: TimestampUs(124214124124),
200 update: UpdateParams::PriceUpdate {
201 price: Price::from_integer(1234567890, 0).unwrap(),
202 best_bid_price: None,
203 best_ask_price: None,
204 },
205 }),
206 id: 1,
207 };
208
209 assert_eq!(
210 serde_json::from_str::<PythLazerAgentJrpcV1>(json).unwrap(),
211 expected
212 );
213 }
214
215 #[test]
216 fn test_push_update_funding_rate() {
217 let json = r#"
218 {
219 "jsonrpc": "2.0",
220 "method": "push_update",
221 "params": {
222 "feed_id": 1,
223 "source_timestamp": 124214124124,
224
225 "update": {
226 "type": "funding_rate",
227 "price": 1234567890,
228 "rate": 1234567891
229 }
230 },
231 "id": 1
232 }
233 "#;
234
235 let expected = PythLazerAgentJrpcV1 {
236 jsonrpc: JsonRpcVersion::V2,
237 params: PushUpdate(FeedUpdateParams {
238 feed_id: PriceFeedId(1),
239 source_timestamp: TimestampUs(124214124124),
240 update: UpdateParams::FundingRateUpdate {
241 price: Some(Price::from_integer(1234567890, 0).unwrap()),
242 rate: Rate::from_integer(1234567891, 0).unwrap(),
243 },
244 }),
245 id: 1,
246 };
247
248 assert_eq!(
249 serde_json::from_str::<PythLazerAgentJrpcV1>(json).unwrap(),
250 expected
251 );
252 }
253 #[test]
254 fn test_push_update_funding_rate_without_price() {
255 let json = r#"
256 {
257 "jsonrpc": "2.0",
258 "method": "push_update",
259 "params": {
260 "feed_id": 1,
261 "source_timestamp": 124214124124,
262
263 "update": {
264 "type": "funding_rate",
265 "rate": 1234567891
266 }
267 },
268 "id": 1
269 }
270 "#;
271
272 let expected = PythLazerAgentJrpcV1 {
273 jsonrpc: JsonRpcVersion::V2,
274 params: PushUpdate(FeedUpdateParams {
275 feed_id: PriceFeedId(1),
276 source_timestamp: TimestampUs(124214124124),
277 update: UpdateParams::FundingRateUpdate {
278 price: None,
279 rate: Rate::from_integer(1234567891, 0).unwrap(),
280 },
281 }),
282 id: 1,
283 };
284
285 assert_eq!(
286 serde_json::from_str::<PythLazerAgentJrpcV1>(json).unwrap(),
287 expected
288 );
289 }
290
291 #[test]
292 fn test_send_get_metadata() {
293 let json = r#"
294 {
295 "jsonrpc": "2.0",
296 "method": "get_metadata",
297 "params": {
298 "names": ["BTC/USD"],
299 "asset_types": ["crypto"]
300 },
301 "id": 1
302 }
303 "#;
304
305 let expected = PythLazerAgentJrpcV1 {
306 jsonrpc: JsonRpcVersion::V2,
307 params: GetMetadata(GetMetadataParams {
308 names: Some(vec!["BTC/USD".to_string()]),
309 asset_types: Some(vec!["crypto".to_string()]),
310 }),
311 id: 1,
312 };
313
314 assert_eq!(
315 serde_json::from_str::<PythLazerAgentJrpcV1>(json).unwrap(),
316 expected
317 );
318 }
319
320 #[test]
321 fn test_get_metadata_without_filters() {
322 let json = r#"
323 {
324 "jsonrpc": "2.0",
325 "method": "get_metadata",
326 "params": {},
327 "id": 1
328 }
329 "#;
330
331 let expected = PythLazerAgentJrpcV1 {
332 jsonrpc: JsonRpcVersion::V2,
333 params: GetMetadata(GetMetadataParams {
334 names: None,
335 asset_types: None,
336 }),
337 id: 1,
338 };
339
340 assert_eq!(
341 serde_json::from_str::<PythLazerAgentJrpcV1>(json).unwrap(),
342 expected
343 );
344 }
345
346 #[test]
347 fn test_response_format_error() {
348 let response = serde_json::from_str::<JrpcErrorResponse>(
349 r#"
350 {
351 "jsonrpc": "2.0",
352 "id": 2,
353 "error": {
354 "message": "Internal error",
355 "code": -32603
356 }
357 }
358 "#,
359 )
360 .unwrap();
361
362 assert_eq!(
363 response,
364 JrpcErrorResponse {
365 jsonrpc: JsonRpcVersion::V2,
366 error: JrpcErrorObject {
367 code: -32603,
368 message: "Internal error".to_string(),
369 data: None,
370 },
371 id: Some(2),
372 }
373 );
374 }
375
376 #[test]
377 pub fn test_response_format_success() {
378 let response = serde_json::from_str::<JrpcSuccessResponse<String>>(
379 r#"
380 {
381 "jsonrpc": "2.0",
382 "id": 2,
383 "result": "success"
384 }
385 "#,
386 )
387 .unwrap();
388
389 assert_eq!(
390 response,
391 JrpcSuccessResponse::<String> {
392 jsonrpc: JsonRpcVersion::V2,
393 result: "success".to_string(),
394 id: 2,
395 }
396 );
397 }
398}