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