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