1use derive_builder::Builder;
19use serde::{Deserialize, Serialize};
20
21use crate::common::enums::{
22 LighterCandleResolution, LighterFundingResolution, LighterOrderBookFilter, LighterTradeType,
23};
24
25#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
26#[serde(rename_all = "snake_case")]
27pub enum LighterTxLookup {
28 Hash,
29 SequenceIndex,
30}
31
32#[derive(Clone, Debug, Deserialize, Serialize, Builder, PartialEq, Eq)]
33pub struct LighterTxQuery {
34 pub by: LighterTxLookup,
35 pub value: String,
36}
37
38#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder, PartialEq, Eq)]
39#[builder(setter(strip_option), default)]
40pub struct LighterOrderBooksQuery {
41 #[serde(skip_serializing_if = "Option::is_none")]
42 pub market_id: Option<i16>,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 pub filter: Option<LighterOrderBookFilter>,
45}
46
47#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder, PartialEq, Eq)]
48#[builder(setter(strip_option), default)]
49pub struct LighterOrderBookDetailsQuery {
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub market_id: Option<i16>,
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub filter: Option<LighterOrderBookFilter>,
54}
55
56#[derive(Clone, Debug, Deserialize, Serialize, Builder, PartialEq, Eq)]
57pub struct LighterOrderBookOrdersQuery {
58 pub market_id: i16,
59 pub limit: u16,
60}
61
62#[derive(Clone, Debug, Deserialize, Serialize, Builder, PartialEq, Eq)]
63pub struct LighterRecentTradesQuery {
64 pub market_id: i16,
65 pub limit: u16,
66}
67
68#[derive(Clone, Debug, Deserialize, Serialize, Builder, PartialEq, Eq)]
69#[builder(setter(strip_option))]
70pub struct LighterCandlesQuery {
71 pub market_id: i16,
72 pub resolution: LighterCandleResolution,
73 pub start_timestamp: i64,
74 pub end_timestamp: i64,
75 pub count_back: i64,
76 #[builder(default)]
77 #[serde(skip_serializing_if = "Option::is_none")]
78 pub set_timestamp_to_end: Option<bool>,
79}
80
81#[derive(Clone, Debug, Deserialize, Serialize, Builder, PartialEq, Eq)]
82#[builder(setter(strip_option))]
83pub struct LighterFundingsQuery {
84 pub market_id: i16,
85 pub resolution: LighterFundingResolution,
86 pub start_timestamp: i64,
87 pub end_timestamp: i64,
88 pub count_back: i64,
89}
90
91#[derive(Clone, Debug, Deserialize, Serialize, Builder, PartialEq, Eq)]
92#[builder(setter(strip_option), default)]
93pub struct LighterTradesQuery {
94 #[builder(setter(into, strip_option))]
95 #[serde(skip_serializing_if = "Option::is_none")]
96 pub authorization: Option<String>,
97 #[builder(setter(into, strip_option))]
98 #[serde(skip_serializing_if = "Option::is_none")]
99 pub auth: Option<String>,
100 #[serde(skip_serializing_if = "Option::is_none")]
101 pub market_id: Option<i16>,
102 #[serde(skip_serializing_if = "Option::is_none")]
103 pub account_index: Option<i64>,
104 #[serde(skip_serializing_if = "Option::is_none")]
105 pub order_index: Option<i64>,
106 pub sort_by: LighterTradeSortBy,
107 #[serde(skip_serializing_if = "Option::is_none")]
108 pub sort_dir: Option<LighterSortDirection>,
109 #[builder(setter(into, strip_option))]
110 #[serde(skip_serializing_if = "Option::is_none")]
111 pub cursor: Option<String>,
112 #[serde(rename = "from")]
113 #[serde(skip_serializing_if = "Option::is_none")]
114 pub from_timestamp: Option<i64>,
115 #[serde(skip_serializing_if = "Option::is_none")]
116 pub ask_filter: Option<i8>,
117 #[serde(skip_serializing_if = "Option::is_none")]
118 pub role: Option<LighterTradeRole>,
119 #[serde(rename = "type")]
120 #[serde(skip_serializing_if = "Option::is_none")]
121 pub trade_type: Option<LighterTradeQueryType>,
122 pub limit: u16,
123 #[serde(skip_serializing_if = "Option::is_none")]
124 pub aggregate: Option<bool>,
125}
126
127#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder, PartialEq, Eq)]
128pub struct LighterNextNonceQuery {
129 pub account_index: i64,
130 pub api_key_index: u8,
131}
132
133#[derive(Clone, Debug, Deserialize, Serialize, Builder, PartialEq, Eq)]
134#[builder(setter(strip_option))]
135pub struct LighterMakerOnlyApiKeysQuery {
136 #[builder(default)]
137 #[builder(setter(into, strip_option))]
138 #[serde(skip_serializing_if = "Option::is_none")]
139 pub authorization: Option<String>,
140 #[builder(default)]
141 #[builder(setter(into, strip_option))]
142 #[serde(skip_serializing_if = "Option::is_none")]
143 pub auth: Option<String>,
144 pub account_index: i64,
145}
146
147#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
148#[serde(rename_all = "snake_case")]
149pub enum LighterAccountLookup {
150 #[default]
151 Index,
152 L1Address,
153}
154
155#[derive(Clone, Debug, Deserialize, Serialize, Builder, PartialEq, Eq)]
156pub struct LighterAccountQuery {
157 pub by: LighterAccountLookup,
158 pub value: String,
159}
160
161#[derive(Clone, Debug, Deserialize, Serialize, Builder, PartialEq, Eq)]
162#[builder(setter(strip_option))]
163pub struct LighterAccountActiveOrdersQuery {
164 #[builder(default)]
165 #[builder(setter(into, strip_option))]
166 #[serde(skip_serializing_if = "Option::is_none")]
167 pub authorization: Option<String>,
168 #[builder(default)]
169 #[builder(setter(into, strip_option))]
170 #[serde(skip_serializing_if = "Option::is_none")]
171 pub auth: Option<String>,
172 pub account_index: i64,
173 pub market_id: i16,
174}
175
176#[derive(Clone, Debug, Deserialize, Serialize, Builder, PartialEq, Eq)]
177#[builder(setter(strip_option))]
178pub struct LighterAccountInactiveOrdersQuery {
179 #[builder(default)]
180 #[builder(setter(into, strip_option))]
181 #[serde(skip_serializing_if = "Option::is_none")]
182 pub authorization: Option<String>,
183 #[builder(default)]
184 #[builder(setter(into, strip_option))]
185 #[serde(skip_serializing_if = "Option::is_none")]
186 pub auth: Option<String>,
187 pub account_index: i64,
188 #[builder(default)]
189 #[serde(skip_serializing_if = "Option::is_none")]
190 pub market_id: Option<i16>,
191 #[builder(default)]
192 #[serde(skip_serializing_if = "Option::is_none")]
193 pub ask_filter: Option<i8>,
194 #[builder(default)]
195 #[builder(setter(into, strip_option))]
196 #[serde(skip_serializing_if = "Option::is_none")]
197 pub between_timestamps: Option<String>,
198 #[builder(default)]
199 #[builder(setter(into, strip_option))]
200 #[serde(skip_serializing_if = "Option::is_none")]
201 pub cursor: Option<String>,
202 pub limit: u16,
203}
204
205#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
206#[serde(rename_all = "snake_case")]
207pub enum LighterTradeSortBy {
208 BlockHeight,
209 Timestamp,
210 #[default]
211 TradeId,
212}
213
214#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
215#[serde(rename_all = "lowercase")]
216pub enum LighterSortDirection {
217 #[default]
218 Desc,
219 Asc,
220}
221
222#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
223#[serde(rename_all = "lowercase")]
224pub enum LighterTradeRole {
225 #[default]
226 All,
227 Maker,
228 Taker,
229}
230
231#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
232#[serde(rename_all = "kebab-case")]
233pub enum LighterTradeQueryType {
234 #[default]
235 All,
236 Trade,
237 Liquidation,
238 Deleverage,
239 MarketSettlement,
240}
241
242impl From<LighterTradeType> for LighterTradeQueryType {
243 fn from(value: LighterTradeType) -> Self {
244 match value {
245 LighterTradeType::Trade => Self::Trade,
246 LighterTradeType::Liquidation => Self::Liquidation,
247 LighterTradeType::Deleverage => Self::Deleverage,
248 LighterTradeType::MarketSettlement => Self::MarketSettlement,
249 }
250 }
251}
252
253impl Default for LighterTradesQuery {
254 fn default() -> Self {
255 Self {
256 authorization: None,
257 auth: None,
258 market_id: None,
259 account_index: None,
260 order_index: None,
261 sort_by: LighterTradeSortBy::default(),
262 sort_dir: None,
263 cursor: None,
264 from_timestamp: None,
265 ask_filter: None,
266 role: None,
267 trade_type: None,
268 limit: 100,
269 aggregate: None,
270 }
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use rstest::rstest;
277
278 use super::*;
279
280 #[rstest]
281 fn test_order_books_query_serializes_filter() {
282 let query = LighterOrderBooksQuery {
283 market_id: Some(0),
284 filter: Some(LighterOrderBookFilter::Perp),
285 };
286
287 let value = serde_json::to_value(query).unwrap();
288
289 assert_eq!(value["market_id"], 0);
290 assert_eq!(value["filter"], "perp");
291 }
292
293 #[rstest]
294 fn test_order_book_details_query_serializes_filter() {
295 let query = LighterOrderBookDetailsQueryBuilder::default()
296 .market_id(1)
297 .filter(LighterOrderBookFilter::Spot)
298 .build()
299 .unwrap();
300
301 let value = serde_json::to_value(query).unwrap();
302
303 assert_eq!(value["market_id"], 1);
304 assert_eq!(value["filter"], "spot");
305 }
306
307 #[rstest]
308 fn test_trades_query_renames_type_and_from() {
309 let query = LighterTradesQuery {
310 market_id: Some(0),
311 from_timestamp: Some(1_700_000_000_000),
312 trade_type: Some(LighterTradeQueryType::MarketSettlement),
313 limit: 50,
314 ..Default::default()
315 };
316
317 let value = serde_json::to_value(query).unwrap();
318
319 assert_eq!(value["sort_by"], "trade_id");
320 assert_eq!(value["from"], 1_700_000_000_000_i64);
321 assert_eq!(value["type"], "market-settlement");
322 assert_eq!(value["limit"], 50);
323 }
324
325 #[rstest]
326 fn test_candles_query_serializes_resolution_and_timestamp_mode() {
327 let query = LighterCandlesQueryBuilder::default()
328 .market_id(0)
329 .resolution(LighterCandleResolution::OneMinute)
330 .start_timestamp(1_700_000_000_000)
331 .end_timestamp(1_700_000_060_000)
332 .count_back(500)
333 .set_timestamp_to_end(false)
334 .build()
335 .unwrap();
336
337 let value = serde_json::to_value(query).unwrap();
338
339 assert_eq!(value["market_id"], 0);
340 assert_eq!(value["resolution"], "1m");
341 assert_eq!(value["start_timestamp"], 1_700_000_000_000_i64);
342 assert_eq!(value["end_timestamp"], 1_700_000_060_000_i64);
343 assert_eq!(value["count_back"], 500);
344 assert_eq!(value["set_timestamp_to_end"], false);
345 }
346
347 #[rstest]
348 fn test_fundings_query_serializes_resolution() {
349 let query = LighterFundingsQueryBuilder::default()
350 .market_id(0)
351 .resolution(LighterFundingResolution::OneHour)
352 .start_timestamp(1_700_000_000_000)
353 .end_timestamp(1_700_003_600_000)
354 .count_back(100)
355 .build()
356 .unwrap();
357
358 let value = serde_json::to_value(query).unwrap();
359
360 assert_eq!(value["market_id"], 0);
361 assert_eq!(value["resolution"], "1h");
362 assert_eq!(value["start_timestamp"], 1_700_000_000_000_i64);
363 assert_eq!(value["end_timestamp"], 1_700_003_600_000_i64);
364 assert_eq!(value["count_back"], 100);
365 }
366
367 #[rstest]
368 fn test_order_book_orders_query_serializes_required_fields() {
369 let query = LighterOrderBookOrdersQuery {
370 market_id: 0,
371 limit: 25,
372 };
373
374 let value = serde_json::to_value(query).unwrap();
375
376 assert_eq!(value["market_id"], 0);
377 assert_eq!(value["limit"], 25);
378 }
379
380 #[rstest]
381 fn test_recent_trades_query_serializes_required_fields() {
382 let query = LighterRecentTradesQueryBuilder::default()
383 .market_id(1)
384 .limit(10)
385 .build()
386 .unwrap();
387
388 let value = serde_json::to_value(query).unwrap();
389
390 assert_eq!(value["market_id"], 1);
391 assert_eq!(value["limit"], 10);
392 }
393
394 #[rstest]
395 fn test_trades_query_serializes_optional_filters() {
396 let query = LighterTradesQuery {
397 authorization: Some("bearer-token".to_string()),
398 auth: Some("auth-token".to_string()),
399 market_id: Some(0),
400 account_index: Some(1234),
401 order_index: Some(5678),
402 sort_by: LighterTradeSortBy::Timestamp,
403 sort_dir: Some(LighterSortDirection::Desc),
404 cursor: Some("cursor-1".to_string()),
405 from_timestamp: Some(1_700_000_000_000),
406 ask_filter: Some(1),
407 role: Some(LighterTradeRole::Maker),
408 trade_type: Some(LighterTradeQueryType::from(LighterTradeType::Liquidation)),
409 limit: 75,
410 aggregate: Some(true),
411 };
412
413 let value = serde_json::to_value(query).unwrap();
414
415 assert_eq!(value["authorization"], "bearer-token");
416 assert_eq!(value["auth"], "auth-token");
417 assert_eq!(value["market_id"], 0);
418 assert_eq!(value["account_index"], 1234);
419 assert_eq!(value["order_index"], 5678);
420 assert_eq!(value["sort_by"], "timestamp");
421 assert_eq!(value["sort_dir"], "desc");
422 assert_eq!(
423 serde_json::to_value(LighterSortDirection::Asc).unwrap(),
424 "asc",
425 );
426 assert_eq!(value["cursor"], "cursor-1");
427 assert_eq!(value["from"], 1_700_000_000_000_i64);
428 assert_eq!(value["ask_filter"], 1);
429 assert_eq!(value["role"], "maker");
430 assert_eq!(value["type"], "liquidation");
431 assert_eq!(value["limit"], 75);
432 assert_eq!(value["aggregate"], true);
433 }
434
435 #[rstest]
436 fn test_account_active_orders_query_serializes_required_fields() {
437 let query = LighterAccountActiveOrdersQueryBuilder::default()
438 .auth("auth-token")
439 .account_index(123)
440 .market_id(0)
441 .build()
442 .unwrap();
443
444 let value = serde_json::to_value(query).unwrap();
445
446 assert_eq!(value["auth"], "auth-token");
447 assert_eq!(value["account_index"], 123);
448 assert_eq!(value["market_id"], 0);
449 assert!(value.get("authorization").is_none());
450 }
451
452 #[rstest]
453 fn test_account_inactive_orders_query_serializes_optional_filters() {
454 let query = LighterAccountInactiveOrdersQueryBuilder::default()
455 .authorization("bearer-token")
456 .account_index(123)
457 .market_id(0)
458 .ask_filter(1)
459 .between_timestamps("1700000000-1700003600")
460 .cursor("cursor-1")
461 .limit(50)
462 .build()
463 .unwrap();
464
465 let value = serde_json::to_value(query).unwrap();
466
467 assert_eq!(value["authorization"], "bearer-token");
468 assert_eq!(value["account_index"], 123);
469 assert_eq!(value["market_id"], 0);
470 assert_eq!(value["ask_filter"], 1);
471 assert_eq!(value["between_timestamps"], "1700000000-1700003600",);
472 assert_eq!(value["cursor"], "cursor-1");
473 assert_eq!(value["limit"], 50);
474 assert!(value.get("auth").is_none());
475 }
476}