1use rust_decimal::Decimal;
19use serde::{Deserialize, Serialize};
20use ustr::Ustr;
21
22use crate::common::{
23 enums::{
24 PolymarketLiquiditySide, PolymarketOrderSide, PolymarketOrderStatus, PolymarketOrderType,
25 PolymarketOutcome, PolymarketTradeStatus, SignatureType,
26 },
27 models::PolymarketMakerOrder,
28 parse::{
29 deserialize_decimal_from_str, deserialize_optional_polymarket_game_id,
30 serialize_decimal_as_str,
31 },
32};
33
34#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
43#[serde(rename_all = "camelCase")]
44pub struct PolymarketOrder {
45 pub salt: u64,
46 pub maker: String,
47 pub signer: String,
48 pub token_id: Ustr,
49 #[serde(
50 serialize_with = "serialize_decimal_as_str",
51 deserialize_with = "deserialize_decimal_from_str"
52 )]
53 pub maker_amount: Decimal,
54 #[serde(
55 serialize_with = "serialize_decimal_as_str",
56 deserialize_with = "deserialize_decimal_from_str"
57 )]
58 pub taker_amount: Decimal,
59 pub side: PolymarketOrderSide,
60 pub signature_type: SignatureType,
61 pub expiration: String,
64 pub timestamp: String,
67 pub metadata: String,
69 pub builder: String,
71 pub signature: String,
72}
73
74#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
78pub struct PolymarketOpenOrder {
79 pub associate_trades: Option<Vec<String>>,
80 pub id: String,
81 pub status: PolymarketOrderStatus,
82 pub market: Ustr,
83 #[serde(
84 serialize_with = "serialize_decimal_as_str",
85 deserialize_with = "deserialize_decimal_from_str"
86 )]
87 pub original_size: Decimal,
88 pub outcome: PolymarketOutcome,
89 pub maker_address: String,
90 pub owner: String,
91 #[serde(
92 serialize_with = "serialize_decimal_as_str",
93 deserialize_with = "deserialize_decimal_from_str"
94 )]
95 pub price: Decimal,
96 pub side: PolymarketOrderSide,
97 #[serde(
98 serialize_with = "serialize_decimal_as_str",
99 deserialize_with = "deserialize_decimal_from_str"
100 )]
101 pub size_matched: Decimal,
102 pub asset_id: Ustr,
103 pub expiration: Option<String>,
104 pub order_type: PolymarketOrderType,
105 pub created_at: u64,
106}
107
108#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
112pub struct PolymarketTradeReport {
113 pub id: String,
114 pub taker_order_id: String,
115 pub market: Ustr,
116 pub asset_id: Ustr,
117 pub side: PolymarketOrderSide,
118 #[serde(
119 serialize_with = "serialize_decimal_as_str",
120 deserialize_with = "deserialize_decimal_from_str"
121 )]
122 pub size: Decimal,
123 #[serde(
124 serialize_with = "serialize_decimal_as_str",
125 deserialize_with = "deserialize_decimal_from_str"
126 )]
127 pub fee_rate_bps: Decimal,
128 #[serde(
129 serialize_with = "serialize_decimal_as_str",
130 deserialize_with = "deserialize_decimal_from_str"
131 )]
132 pub price: Decimal,
133 pub status: PolymarketTradeStatus,
134 pub match_time: String,
135 pub last_update: String,
136 pub outcome: PolymarketOutcome,
137 pub bucket_index: u64,
138 pub owner: String,
139 pub maker_address: String,
140 pub transaction_hash: String,
141 pub maker_orders: Vec<PolymarketMakerOrder>,
142 pub trader_side: PolymarketLiquiditySide,
143}
144
145#[derive(Clone, Debug, Deserialize)]
149#[serde(rename_all = "camelCase")]
150pub struct GammaMarket {
151 pub id: String,
153 pub condition_id: String,
155 #[serde(rename = "questionID")]
157 pub question_id: Option<String>,
158 #[serde(default)]
160 pub clob_token_ids: String,
161 #[serde(default)]
163 pub outcomes: String,
164 pub question: String,
166 pub description: Option<String>,
168 pub start_date: Option<String>,
170 pub end_date: Option<String>,
172 pub active: Option<bool>,
174 pub closed: Option<bool>,
176 pub accepting_orders: Option<bool>,
178 pub enable_order_book: Option<bool>,
180 pub order_price_min_tick_size: Option<f64>,
182 pub order_min_size: Option<f64>,
184 pub maker_base_fee: Option<i64>,
186 pub taker_base_fee: Option<i64>,
188 #[serde(rename = "slug")]
190 pub market_slug: Option<String>,
191 #[serde(rename = "negRisk")]
193 pub neg_risk: Option<bool>,
194 pub liquidity_num: Option<f64>,
196 pub volume_num: Option<f64>,
198 #[serde(rename = "volume24hr")]
200 pub volume_24hr: Option<f64>,
201 pub outcome_prices: Option<String>,
203 pub best_bid: Option<f64>,
205 pub best_ask: Option<f64>,
207 pub spread: Option<f64>,
209 pub last_trade_price: Option<f64>,
211 pub one_day_price_change: Option<f64>,
213 pub one_week_price_change: Option<f64>,
215 #[serde(rename = "volume1wk")]
217 pub volume_1wk: Option<f64>,
218 #[serde(rename = "volume1mo")]
220 pub volume_1mo: Option<f64>,
221 #[serde(rename = "volume1yr")]
223 pub volume_1yr: Option<f64>,
224 pub rewards_min_size: Option<f64>,
226 pub rewards_max_spread: Option<f64>,
228 pub competitive: Option<f64>,
230 pub category: Option<String>,
232 #[serde(rename = "negRiskMarketID")]
234 pub neg_risk_market_id: Option<String>,
235 pub fee_schedule: Option<FeeSchedule>,
237 #[serde(default, deserialize_with = "deserialize_optional_polymarket_game_id")]
241 pub game_id: Option<u64>,
242 pub events: Option<Vec<GammaEvent>>,
244}
245
246#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
247#[serde(rename_all = "camelCase")]
248pub struct FeeSchedule {
249 pub exponent: f64,
250 pub rate: f64,
251 pub taker_only: bool,
252 pub rebate_rate: f64,
253}
254
255#[derive(Clone, Debug, Deserialize)]
261#[serde(rename_all = "camelCase")]
262pub struct GammaEvent {
263 pub id: String,
264 pub slug: Option<String>,
265 pub title: Option<String>,
266 pub description: Option<String>,
267 pub start_date: Option<String>,
268 pub end_date: Option<String>,
269 pub active: Option<bool>,
270 pub closed: Option<bool>,
271 pub archived: Option<bool>,
272 #[serde(default)]
273 pub markets: Vec<GammaMarket>,
274 pub liquidity: Option<f64>,
276 pub volume: Option<f64>,
278 pub open_interest: Option<f64>,
280 #[serde(rename = "volume24hr")]
282 pub volume_24hr: Option<f64>,
283 pub category: Option<String>,
285 pub neg_risk: Option<bool>,
287 #[serde(rename = "negRiskMarketID")]
289 pub neg_risk_market_id: Option<String>,
290 pub featured: Option<bool>,
292 #[serde(default, deserialize_with = "deserialize_optional_polymarket_game_id")]
296 pub game_id: Option<u64>,
297}
298
299#[derive(Clone, Debug, Deserialize)]
301pub struct GammaTag {
302 pub id: String,
304 pub label: Option<String>,
306 pub slug: Option<String>,
308}
309
310#[derive(Clone, Debug, Deserialize)]
312pub struct SearchResponse {
313 #[serde(default)]
315 pub markets: Option<Vec<GammaMarket>>,
316 #[serde(default)]
318 pub events: Option<Vec<GammaEvent>>,
319}
320
321#[derive(Clone, Debug, Deserialize)]
325pub struct TickSizeResponse {
326 pub minimum_tick_size: f64,
328}
329
330#[derive(Clone, Debug, Deserialize)]
334pub struct FeeRateResponse {
335 pub base_fee: Decimal,
337}
338
339#[derive(Clone, Debug, Deserialize)]
341pub struct ClobBookLevel {
342 pub price: String,
343 pub size: String,
344}
345
346#[derive(Clone, Debug, Deserialize)]
350pub struct ClobBookResponse {
351 pub bids: Vec<ClobBookLevel>,
352 pub asks: Vec<ClobBookLevel>,
353}
354
355#[derive(Clone, Debug, Deserialize)]
357pub struct DataApiPosition {
358 pub asset: String,
359 #[serde(alias = "conditionId", alias = "condition_id")]
360 pub condition_id: String,
361 pub size: f64,
362 #[serde(alias = "avgPrice", alias = "avg_price")]
363 pub avg_price: Option<f64>,
364}
365
366#[derive(Clone, Debug, Deserialize)]
368#[serde(rename_all = "camelCase")]
369pub struct DataApiTrade {
370 pub asset: String,
371 pub condition_id: String,
372 pub side: PolymarketOrderSide,
373 pub price: f64,
374 pub size: f64,
375 pub timestamp: i64,
376 pub transaction_hash: String,
377}
378
379#[cfg(test)]
380mod tests {
381 use rstest::rstest;
382 use rust_decimal_macros::dec;
383
384 use super::*;
385 use crate::common::enums::{PolymarketOrderStatus, PolymarketTradeStatus, SignatureType};
386
387 fn load<T: serde::de::DeserializeOwned>(filename: &str) -> T {
388 let path = format!("test_data/{filename}");
389 let content = std::fs::read_to_string(path).expect("Failed to read test data");
390 serde_json::from_str(&content).expect("Failed to parse test data")
391 }
392
393 #[rstest]
394 fn test_open_order_live_buy_gtc() {
395 let order: PolymarketOpenOrder = load("http_open_order.json");
396
397 assert_eq!(
398 order.id,
399 "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12"
400 );
401 assert_eq!(order.status, PolymarketOrderStatus::Live);
402 assert_eq!(order.side, PolymarketOrderSide::Buy);
403 assert_eq!(order.order_type, PolymarketOrderType::GTC);
404 assert_eq!(order.outcome, PolymarketOutcome::yes());
405 assert_eq!(order.original_size, dec!(100.0000));
406 assert_eq!(order.price, dec!(0.5000));
407 assert_eq!(order.size_matched, dec!(25.0000));
408 assert_eq!(order.created_at, 1703875200);
409 assert!(order.expiration.is_none());
410 assert_eq!(order.associate_trades, Some(vec!["0xabc001".to_string()]));
411 }
412
413 #[rstest]
414 fn test_open_order_matched_sell_fok() {
415 let order: PolymarketOpenOrder = load("http_open_order_sell_fok.json");
416
417 assert_eq!(order.status, PolymarketOrderStatus::Matched);
418 assert_eq!(order.side, PolymarketOrderSide::Sell);
419 assert_eq!(order.order_type, PolymarketOrderType::FOK);
420 assert_eq!(order.outcome, PolymarketOutcome::no());
421 assert_eq!(order.size_matched, dec!(50.0000));
422 assert_eq!(order.expiration, Some("1735689600".to_string()));
423 assert!(order.associate_trades.is_none());
424 }
425
426 #[rstest]
427 fn test_open_order_roundtrip() {
428 let order: PolymarketOpenOrder = load("http_open_order.json");
429 let json = serde_json::to_string(&order).unwrap();
430 let order2: PolymarketOpenOrder = serde_json::from_str(&json).unwrap();
431 assert_eq!(order, order2);
432 }
433
434 #[rstest]
435 fn test_trade_report_fields() {
436 let trade: PolymarketTradeReport = load("http_trade_report.json");
437
438 assert_eq!(trade.id, "trade-0xabcdef1234");
439 assert_eq!(
440 trade.taker_order_id,
441 "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12"
442 );
443 assert_eq!(trade.side, PolymarketOrderSide::Buy);
444 assert_eq!(trade.size, dec!(25.0000));
445 assert_eq!(trade.fee_rate_bps, dec!(0));
446 assert_eq!(trade.price, dec!(0.5000));
447 assert_eq!(trade.status, PolymarketTradeStatus::Confirmed);
448 assert_eq!(trade.outcome, PolymarketOutcome::yes());
449 assert_eq!(trade.bucket_index, 0);
450 assert_eq!(trade.trader_side, PolymarketLiquiditySide::Taker);
451 assert_eq!(trade.maker_orders.len(), 2);
452 }
453
454 #[rstest]
455 fn test_trade_report_maker_orders() {
456 let trade: PolymarketTradeReport = load("http_trade_report.json");
457
458 let first = &trade.maker_orders[0];
459 assert_eq!(first.matched_amount, dec!(25.0000));
460 assert_eq!(first.price, dec!(0.5000));
461 assert_eq!(first.outcome, PolymarketOutcome::yes());
462
463 let second = &trade.maker_orders[1];
464 assert_eq!(second.matched_amount, dec!(5.0000));
465 }
466
467 #[rstest]
468 fn test_trade_report_roundtrip() {
469 let trade: PolymarketTradeReport = load("http_trade_report.json");
470 let json = serde_json::to_string(&trade).unwrap();
471 let trade2: PolymarketTradeReport = serde_json::from_str(&json).unwrap();
472 assert_eq!(trade, trade2);
473 }
474
475 #[rstest]
476 fn test_signed_order_camel_case_fields() {
477 let order: PolymarketOrder = load("http_signed_order.json");
478
479 assert_eq!(order.salt, 123456789);
480 assert_eq!(order.maker, "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266");
481 assert_eq!(order.maker_amount, dec!(100000000));
482 assert_eq!(order.taker_amount, dec!(50000000));
483 assert_eq!(order.expiration, "0");
484 assert_eq!(order.timestamp, "1713398400000");
485 assert_eq!(
486 order.metadata,
487 "0x0000000000000000000000000000000000000000000000000000000000000000"
488 );
489 assert_eq!(
490 order.builder,
491 "0x0000000000000000000000000000000000000000000000000000000000000000"
492 );
493 assert_eq!(order.side, PolymarketOrderSide::Buy);
494 assert_eq!(order.signature_type, SignatureType::Eoa);
495 }
496
497 #[rstest]
498 fn test_signed_order_roundtrip() {
499 let order: PolymarketOrder = load("http_signed_order.json");
500 let json = serde_json::to_string(&order).unwrap();
501 let order2: PolymarketOrder = serde_json::from_str(&json).unwrap();
502 assert_eq!(order, order2);
503 }
504
505 #[rstest]
506 fn test_signed_order_serializes_camel_case() {
507 let order: PolymarketOrder = load("http_signed_order.json");
508 let json = serde_json::to_string(&order).unwrap();
509
510 assert!(json.contains("\"tokenId\""));
512 assert!(json.contains("\"makerAmount\""));
513 assert!(json.contains("\"takerAmount\""));
514 assert!(json.contains("\"signatureType\""));
515 assert!(json.contains("\"expiration\""));
516 assert!(json.contains("\"timestamp\""));
517 assert!(json.contains("\"metadata\""));
518 assert!(json.contains("\"builder\""));
519 }
520
521 #[rstest]
522 fn test_signed_order_omits_v1_fields() {
523 let order: PolymarketOrder = load("http_signed_order.json");
527 let json = serde_json::to_string(&order).unwrap();
528
529 assert!(
530 !json.contains("\"taker\""),
531 "wire body must not include `taker`: {json}"
532 );
533 assert!(
534 !json.contains("\"nonce\""),
535 "wire body must not include `nonce`: {json}"
536 );
537 assert!(
538 !json.contains("\"feeRateBps\""),
539 "wire body must not include `feeRateBps`: {json}"
540 );
541 }
542
543 #[rstest]
544 fn test_signed_order_v2_docs_example_roundtrips() {
545 let docs_example = r#"{
548 "salt": 12345,
549 "maker": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
550 "signer": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
551 "tokenId": "102936",
552 "makerAmount": "1000000",
553 "takerAmount": "2000000",
554 "side": "BUY",
555 "signatureType": 1,
556 "expiration": "0",
557 "timestamp": "1713398400000",
558 "metadata": "0x0000000000000000000000000000000000000000000000000000000000000000",
559 "builder": "0x0000000000000000000000000000000000000000000000000000000000000000",
560 "signature": "0xdeadbeef"
561 }"#;
562
563 let order: PolymarketOrder = serde_json::from_str(docs_example).unwrap();
564 assert_eq!(order.salt, 12345);
565 assert_eq!(order.token_id.as_str(), "102936");
566 assert_eq!(order.maker_amount, dec!(1000000));
567 assert_eq!(order.taker_amount, dec!(2000000));
568 assert_eq!(order.side, PolymarketOrderSide::Buy);
569 assert_eq!(order.signature_type, SignatureType::PolyProxy);
570 assert_eq!(order.expiration, "0");
571 assert_eq!(order.timestamp, "1713398400000");
572
573 let json = serde_json::to_string(&order).unwrap();
575 let order2: PolymarketOrder = serde_json::from_str(&json).unwrap();
576 assert_eq!(order, order2);
577 }
578
579 #[rstest]
580 fn test_gamma_event_deserialization() {
581 let events: Vec<GammaEvent> = load("gamma_event.json");
582
583 assert_eq!(events.len(), 1);
584 let event = &events[0];
585 assert_eq!(event.id, "30829");
586 assert_eq!(
587 event.slug.as_deref(),
588 Some("democratic-presidential-nominee-2028")
589 );
590 assert_eq!(
591 event.title.as_deref(),
592 Some("Democratic Presidential Nominee 2028")
593 );
594 assert_eq!(event.active, Some(true));
595 assert_eq!(event.closed, Some(false));
596 assert_eq!(event.archived, Some(false));
597 assert_eq!(event.markets.len(), 2);
598 assert_eq!(
599 event.markets[0].condition_id,
600 "0xc8f1cf5d4f26e0fd9c8fe89f2a7b3263b902cf14fde7bfccef525753bb492e47"
601 );
602 assert_eq!(
603 event.markets[1].condition_id,
604 "0xe39adea057926dc197fe30a441f57a340b2a232d5a687010f78bba9b6e02620f"
605 );
606 }
607
608 #[rstest]
609 fn test_gamma_event_empty_markets() {
610 let json = r#"[{"id": "evt-002"}]"#;
611 let events: Vec<GammaEvent> = serde_json::from_str(json).unwrap();
612
613 assert_eq!(events.len(), 1);
614 assert_eq!(events[0].id, "evt-002");
615 assert!(events[0].markets.is_empty());
616 assert!(events[0].slug.is_none());
617 }
618
619 #[rstest]
620 fn test_sports_market_are_weird() {
621 let money_line: GammaMarket = load("gamma_market_sports_market_money_line.json");
622 let map_handicap: GammaMarket = load("gamma_market_sports_market_map_handicap.json");
623
624 assert_eq!(
626 money_line.events.as_ref().unwrap()[0].game_id,
627 map_handicap.events.as_ref().unwrap()[0].game_id
628 );
629
630 assert!(map_handicap.game_id.is_none());
632 assert_eq!(money_line.game_id, Some(1_427_074));
633 }
634
635 #[rstest]
636 fn test_gamma_market_enriched_fields() {
637 let market: GammaMarket = load("gamma_market.json");
638
639 assert_eq!(market.best_bid, Some(0.5));
640 assert_eq!(market.best_ask, Some(0.51));
641 assert_eq!(market.spread, Some(0.009));
642 assert_eq!(market.last_trade_price, Some(0.51));
643 assert!(market.one_day_price_change.is_none());
644 assert!(market.one_week_price_change.is_none());
645 assert_eq!(market.volume_1wk, Some(9.999997));
646 assert_eq!(market.volume_1mo, Some(9.999997));
647 assert_eq!(market.volume_1yr, Some(9.999997));
648 assert_eq!(market.rewards_min_size, Some(50.0));
649 assert_eq!(market.rewards_max_spread, Some(4.5));
650 assert_eq!(market.competitive, Some(0.9999750006249843));
651 assert!(market.category.is_none());
652 assert!(market.neg_risk_market_id.is_none());
653 assert_eq!(
654 market.outcome_prices.as_deref(),
655 Some("[\"0.505\", \"0.495\"]")
656 );
657 }
658
659 #[rstest]
660 fn test_gamma_market_enriched_fields_default_to_none() {
661 let json = r#"{"id": "m1", "conditionId": "0xcond", "clobTokenIds": "[]", "outcomes": "[]", "question": "Q?"}"#;
663 let market: GammaMarket = serde_json::from_str(json).unwrap();
664
665 assert!(market.best_bid.is_none());
666 assert!(market.spread.is_none());
667 assert!(market.volume_1wk.is_none());
668 assert!(market.rewards_min_size.is_none());
669 assert!(market.competitive.is_none());
670 assert!(market.category.is_none());
671 assert!(market.neg_risk_market_id.is_none());
672 }
673
674 #[rstest]
675 fn test_gamma_event_enriched_fields() {
676 let events: Vec<GammaEvent> = load("gamma_event.json");
677 let event = &events[0];
678
679 assert_eq!(event.liquidity, Some(43042905.16152));
680 assert_eq!(event.volume, Some(799823812.487094));
681 assert_eq!(event.open_interest, Some(0.0));
682 assert_eq!(event.volume_24hr, Some(5669354.219446001));
683 assert!(event.category.is_none());
684 assert_eq!(event.neg_risk, Some(true));
685 assert_eq!(
686 event.neg_risk_market_id.as_deref(),
687 Some("0x2c3d7e0eee6f058be3006baabf0d54a07da254ba47fe6e3e095e7990c7814700")
688 );
689 assert_eq!(event.featured, Some(false));
690 }
691
692 #[rstest]
693 fn test_gamma_tag_deserialization() {
694 let tags: Vec<GammaTag> = load("gamma_tags.json");
695
696 assert_eq!(tags.len(), 5);
697 assert_eq!(tags[0].id, "101259");
698 assert_eq!(tags[0].label.as_deref(), Some("Health and Human Services"));
699 assert_eq!(tags[0].slug.as_deref(), Some("health-and-human-services"));
700 assert_eq!(tags[2].slug.as_deref(), Some("attorney-general"));
701 }
702
703 #[rstest]
704 fn test_search_response_deserialization() {
705 let response: SearchResponse = load("search_response.json");
706
707 assert!(response.markets.is_none());
709
710 let events = response.events.as_ref().unwrap();
711 assert_eq!(events.len(), 1);
712 assert_eq!(events[0].slug.as_deref(), Some("bitcoin-above-on-march-11"));
713 assert_eq!(events[0].markets.len(), 1);
714 }
715
716 #[rstest]
717 fn test_search_response_empty_fields() {
718 let json = "{}";
719 let response: SearchResponse = serde_json::from_str(json).unwrap();
720 assert!(response.markets.is_none());
721 assert!(response.events.is_none());
722 }
723
724 #[rstest]
725 fn test_clob_book_response_deserialization() {
726 let response: ClobBookResponse = load("clob_book_response.json");
727
728 assert_eq!(response.bids.len(), 3);
729 assert_eq!(response.asks.len(), 3);
730
731 assert_eq!(response.bids[0].price, "0.48");
732 assert_eq!(response.bids[0].size, "100.00");
733 assert_eq!(response.bids[2].price, "0.50");
734 assert_eq!(response.bids[2].size, "150.00");
735
736 assert_eq!(response.asks[0].price, "0.51");
737 assert_eq!(response.asks[0].size, "120.00");
738 assert_eq!(response.asks[2].price, "0.53");
739 assert_eq!(response.asks[2].size, "90.00");
740 }
741
742 #[rstest]
743 fn test_clob_book_response_ignores_extra_fields() {
744 let json = r#"{
750 "market": "0xabc",
751 "asset_id": "123",
752 "hash": "0x1",
753 "timestamp": "123",
754 "bids": [],
755 "asks": [],
756 "tick_size": "0.01",
757 "min_order_size": "5",
758 "neg_risk": false,
759 "last_trade_price": "0.55"
760 }"#;
761 let response: ClobBookResponse = serde_json::from_str(json).unwrap();
762 assert!(response.bids.is_empty());
763 assert!(response.asks.is_empty());
764 }
765
766 #[rstest]
767 fn test_fee_rate_response_zero() {
768 let response: FeeRateResponse = load("clob_fee_rate_response_zero.json");
769 assert_eq!(response.base_fee, dec!(0));
770 }
771
772 #[rstest]
773 fn test_fee_rate_response_nonzero() {
774 let response: FeeRateResponse = load("clob_fee_rate_response_nonzero.json");
775 assert_eq!(response.base_fee, dec!(150));
776 }
777
778 #[rstest]
779 fn test_data_api_position_deserialization() {
780 let positions: Vec<DataApiPosition> = load("data_api_positions_response.json");
781
782 assert_eq!(positions.len(), 4);
783 assert_eq!(
784 positions[0].asset,
785 "71321045863084981365469005770620412523470745398083994982746259498689308907982"
786 );
787 assert_eq!(
788 positions[0].condition_id,
789 "0xc8f1cf5d4f26e0fd9c8fe89f2a7b3263b902cf14fde7bfccef525753bb492e47"
790 );
791 assert_eq!(positions[0].size, 150.5);
792 assert_eq!(positions[0].avg_price, Some(0.55));
793
794 assert_eq!(positions[1].size, 0.0);
796 assert_eq!(positions[1].avg_price, Some(0.45));
797
798 assert_eq!(
800 positions[2].condition_id,
801 "0xabc123def456789012345678901234567890abcdef1234567890abcdef123456"
802 );
803 assert_eq!(positions[2].size, 42.0);
804 assert_eq!(positions[2].avg_price, Some(0.3));
805
806 assert_eq!(positions[3].size, 0.005);
808 assert_eq!(positions[3].avg_price, Some(0.7));
809 }
810
811 #[rstest]
812 fn test_data_api_trade_deserialization() {
813 let trades: Vec<DataApiTrade> = load("data_api_trades_response.json");
814
815 assert_eq!(trades.len(), 3);
816 assert_eq!(
817 trades[0].asset,
818 "71321045863084981365469005770620412523470745398083994982746259498689308907982"
819 );
820 assert_eq!(
821 trades[0].condition_id,
822 "0xc8f1cf5d4f26e0fd9c8fe89f2a7b3263b902cf14fde7bfccef525753bb492e47"
823 );
824 assert_eq!(trades[0].side, PolymarketOrderSide::Buy);
825 assert_eq!(trades[0].price, 0.55);
826 assert_eq!(trades[0].size, 100.0);
827 assert_eq!(trades[0].timestamp, 1710000000);
828 assert_eq!(
829 trades[0].transaction_hash,
830 "0xabc123def456789012345678901234567890abcdef1234567890abcdef123456"
831 );
832
833 assert_eq!(trades[1].side, PolymarketOrderSide::Sell);
834 assert_eq!(trades[1].price, 0.53);
835
836 assert_eq!(
838 trades[2].asset,
839 "99999999999999999999999999999999999999999999999999999999999999999999999999999"
840 );
841 }
842}