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 ClobMarketToken {
358 pub token_id: String,
359 pub outcome: String,
360 pub winner: bool,
361}
362
363#[derive(Clone, Debug, Deserialize)]
365pub struct ClobMarketResponse {
366 pub condition_id: String,
367 pub closed: bool,
368 pub tokens: Vec<ClobMarketToken>,
369}
370
371#[derive(Clone, Debug, Deserialize)]
373pub struct DataApiPosition {
374 pub asset: String,
375 #[serde(alias = "conditionId", alias = "condition_id")]
376 pub condition_id: String,
377 pub size: f64,
378 #[serde(alias = "avgPrice", alias = "avg_price")]
379 pub avg_price: Option<f64>,
380}
381
382#[derive(Clone, Debug, Deserialize)]
384#[serde(rename_all = "camelCase")]
385pub struct DataApiTrade {
386 pub asset: String,
387 pub condition_id: String,
388 pub side: PolymarketOrderSide,
389 pub price: f64,
390 pub size: f64,
391 pub timestamp: i64,
392 pub transaction_hash: String,
393}
394
395#[cfg(test)]
396mod tests {
397 use rstest::rstest;
398 use rust_decimal_macros::dec;
399
400 use super::*;
401 use crate::common::enums::{PolymarketOrderStatus, PolymarketTradeStatus, SignatureType};
402
403 fn load<T: serde::de::DeserializeOwned>(filename: &str) -> T {
404 let path = format!("test_data/{filename}");
405 let content = std::fs::read_to_string(path).expect("Failed to read test data");
406 serde_json::from_str(&content).expect("Failed to parse test data")
407 }
408
409 #[rstest]
410 fn test_open_order_live_buy_gtc() {
411 let order: PolymarketOpenOrder = load("http_open_order.json");
412
413 assert_eq!(
414 order.id,
415 "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12"
416 );
417 assert_eq!(order.status, PolymarketOrderStatus::Live);
418 assert_eq!(order.side, PolymarketOrderSide::Buy);
419 assert_eq!(order.order_type, PolymarketOrderType::GTC);
420 assert_eq!(order.outcome, PolymarketOutcome::yes());
421 assert_eq!(order.original_size, dec!(100.0000));
422 assert_eq!(order.price, dec!(0.5000));
423 assert_eq!(order.size_matched, dec!(25.0000));
424 assert_eq!(order.created_at, 1703875200);
425 assert!(order.expiration.is_none());
426 assert_eq!(order.associate_trades, Some(vec!["0xabc001".to_string()]));
427 }
428
429 #[rstest]
430 fn test_open_order_matched_sell_fok() {
431 let order: PolymarketOpenOrder = load("http_open_order_sell_fok.json");
432
433 assert_eq!(order.status, PolymarketOrderStatus::Matched);
434 assert_eq!(order.side, PolymarketOrderSide::Sell);
435 assert_eq!(order.order_type, PolymarketOrderType::FOK);
436 assert_eq!(order.outcome, PolymarketOutcome::no());
437 assert_eq!(order.size_matched, dec!(50.0000));
438 assert_eq!(order.expiration, Some("1735689600".to_string()));
439 assert!(order.associate_trades.is_none());
440 }
441
442 #[rstest]
443 fn test_open_order_roundtrip() {
444 let order: PolymarketOpenOrder = load("http_open_order.json");
445 let json = serde_json::to_string(&order).unwrap();
446 let order2: PolymarketOpenOrder = serde_json::from_str(&json).unwrap();
447 assert_eq!(order, order2);
448 }
449
450 #[rstest]
451 fn test_trade_report_fields() {
452 let trade: PolymarketTradeReport = load("http_trade_report.json");
453
454 assert_eq!(trade.id, "trade-0xabcdef1234");
455 assert_eq!(
456 trade.taker_order_id,
457 "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12"
458 );
459 assert_eq!(trade.side, PolymarketOrderSide::Buy);
460 assert_eq!(trade.size, dec!(25.0000));
461 assert_eq!(trade.fee_rate_bps, dec!(0));
462 assert_eq!(trade.price, dec!(0.5000));
463 assert_eq!(trade.status, PolymarketTradeStatus::Confirmed);
464 assert_eq!(trade.outcome, PolymarketOutcome::yes());
465 assert_eq!(trade.bucket_index, 0);
466 assert_eq!(trade.trader_side, PolymarketLiquiditySide::Taker);
467 assert_eq!(trade.maker_orders.len(), 2);
468 }
469
470 #[rstest]
471 fn test_trade_report_maker_orders() {
472 let trade: PolymarketTradeReport = load("http_trade_report.json");
473
474 let first = &trade.maker_orders[0];
475 assert_eq!(first.matched_amount, dec!(25.0000));
476 assert_eq!(first.price, dec!(0.5000));
477 assert_eq!(first.outcome, PolymarketOutcome::yes());
478
479 let second = &trade.maker_orders[1];
480 assert_eq!(second.matched_amount, dec!(5.0000));
481 }
482
483 #[rstest]
484 fn test_trade_report_roundtrip() {
485 let trade: PolymarketTradeReport = load("http_trade_report.json");
486 let json = serde_json::to_string(&trade).unwrap();
487 let trade2: PolymarketTradeReport = serde_json::from_str(&json).unwrap();
488 assert_eq!(trade, trade2);
489 }
490
491 #[rstest]
492 fn test_signed_order_camel_case_fields() {
493 let order: PolymarketOrder = load("http_signed_order.json");
494
495 assert_eq!(order.salt, 123456789);
496 assert_eq!(order.maker, "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266");
497 assert_eq!(order.maker_amount, dec!(100000000));
498 assert_eq!(order.taker_amount, dec!(50000000));
499 assert_eq!(order.expiration, "0");
500 assert_eq!(order.timestamp, "1713398400000");
501 assert_eq!(
502 order.metadata,
503 "0x0000000000000000000000000000000000000000000000000000000000000000"
504 );
505 assert_eq!(
506 order.builder,
507 "0x0000000000000000000000000000000000000000000000000000000000000000"
508 );
509 assert_eq!(order.side, PolymarketOrderSide::Buy);
510 assert_eq!(order.signature_type, SignatureType::Eoa);
511 }
512
513 #[rstest]
514 fn test_signed_order_roundtrip() {
515 let order: PolymarketOrder = load("http_signed_order.json");
516 let json = serde_json::to_string(&order).unwrap();
517 let order2: PolymarketOrder = serde_json::from_str(&json).unwrap();
518 assert_eq!(order, order2);
519 }
520
521 #[rstest]
522 fn test_signed_order_serializes_camel_case() {
523 let order: PolymarketOrder = load("http_signed_order.json");
524 let json = serde_json::to_string(&order).unwrap();
525
526 assert!(json.contains("\"tokenId\""));
528 assert!(json.contains("\"makerAmount\""));
529 assert!(json.contains("\"takerAmount\""));
530 assert!(json.contains("\"signatureType\""));
531 assert!(json.contains("\"expiration\""));
532 assert!(json.contains("\"timestamp\""));
533 assert!(json.contains("\"metadata\""));
534 assert!(json.contains("\"builder\""));
535 }
536
537 #[rstest]
538 fn test_signed_order_omits_v1_fields() {
539 let order: PolymarketOrder = load("http_signed_order.json");
543 let json = serde_json::to_string(&order).unwrap();
544
545 assert!(
546 !json.contains("\"taker\""),
547 "wire body must not include `taker`: {json}"
548 );
549 assert!(
550 !json.contains("\"nonce\""),
551 "wire body must not include `nonce`: {json}"
552 );
553 assert!(
554 !json.contains("\"feeRateBps\""),
555 "wire body must not include `feeRateBps`: {json}"
556 );
557 }
558
559 #[rstest]
560 fn test_signed_order_v2_docs_example_roundtrips() {
561 let docs_example = r#"{
564 "salt": 12345,
565 "maker": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
566 "signer": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
567 "tokenId": "102936",
568 "makerAmount": "1000000",
569 "takerAmount": "2000000",
570 "side": "BUY",
571 "signatureType": 1,
572 "expiration": "0",
573 "timestamp": "1713398400000",
574 "metadata": "0x0000000000000000000000000000000000000000000000000000000000000000",
575 "builder": "0x0000000000000000000000000000000000000000000000000000000000000000",
576 "signature": "0xdeadbeef"
577 }"#;
578
579 let order: PolymarketOrder = serde_json::from_str(docs_example).unwrap();
580 assert_eq!(order.salt, 12345);
581 assert_eq!(order.token_id.as_str(), "102936");
582 assert_eq!(order.maker_amount, dec!(1000000));
583 assert_eq!(order.taker_amount, dec!(2000000));
584 assert_eq!(order.side, PolymarketOrderSide::Buy);
585 assert_eq!(order.signature_type, SignatureType::PolyProxy);
586 assert_eq!(order.expiration, "0");
587 assert_eq!(order.timestamp, "1713398400000");
588
589 let json = serde_json::to_string(&order).unwrap();
591 let order2: PolymarketOrder = serde_json::from_str(&json).unwrap();
592 assert_eq!(order, order2);
593 }
594
595 #[rstest]
596 fn test_gamma_event_deserialization() {
597 let events: Vec<GammaEvent> = load("gamma_event.json");
598
599 assert_eq!(events.len(), 1);
600 let event = &events[0];
601 assert_eq!(event.id, "30829");
602 assert_eq!(
603 event.slug.as_deref(),
604 Some("democratic-presidential-nominee-2028")
605 );
606 assert_eq!(
607 event.title.as_deref(),
608 Some("Democratic Presidential Nominee 2028")
609 );
610 assert_eq!(event.active, Some(true));
611 assert_eq!(event.closed, Some(false));
612 assert_eq!(event.archived, Some(false));
613 assert_eq!(event.markets.len(), 2);
614 assert_eq!(
615 event.markets[0].condition_id,
616 "0xc8f1cf5d4f26e0fd9c8fe89f2a7b3263b902cf14fde7bfccef525753bb492e47"
617 );
618 assert_eq!(
619 event.markets[1].condition_id,
620 "0xe39adea057926dc197fe30a441f57a340b2a232d5a687010f78bba9b6e02620f"
621 );
622 }
623
624 #[rstest]
625 fn test_gamma_event_empty_markets() {
626 let json = r#"[{"id": "evt-002"}]"#;
627 let events: Vec<GammaEvent> = serde_json::from_str(json).unwrap();
628
629 assert_eq!(events.len(), 1);
630 assert_eq!(events[0].id, "evt-002");
631 assert!(events[0].markets.is_empty());
632 assert!(events[0].slug.is_none());
633 }
634
635 #[rstest]
636 fn test_sports_market_are_weird() {
637 let money_line: GammaMarket = load("gamma_market_sports_market_money_line.json");
638 let map_handicap: GammaMarket = load("gamma_market_sports_market_map_handicap.json");
639
640 assert_eq!(
642 money_line.events.as_ref().unwrap()[0].game_id,
643 map_handicap.events.as_ref().unwrap()[0].game_id
644 );
645
646 assert!(map_handicap.game_id.is_none());
648 assert_eq!(money_line.game_id, Some(1_427_074));
649 }
650
651 #[rstest]
652 fn test_gamma_market_enriched_fields() {
653 let market: GammaMarket = load("gamma_market.json");
654
655 assert_eq!(market.best_bid, Some(0.5));
656 assert_eq!(market.best_ask, Some(0.51));
657 assert_eq!(market.spread, Some(0.009));
658 assert_eq!(market.last_trade_price, Some(0.51));
659 assert!(market.one_day_price_change.is_none());
660 assert!(market.one_week_price_change.is_none());
661 assert_eq!(market.volume_1wk, Some(9.999997));
662 assert_eq!(market.volume_1mo, Some(9.999997));
663 assert_eq!(market.volume_1yr, Some(9.999997));
664 assert_eq!(market.rewards_min_size, Some(50.0));
665 assert_eq!(market.rewards_max_spread, Some(4.5));
666 assert_eq!(market.competitive, Some(0.9999750006249843));
667 assert!(market.category.is_none());
668 assert!(market.neg_risk_market_id.is_none());
669 assert_eq!(
670 market.outcome_prices.as_deref(),
671 Some("[\"0.505\", \"0.495\"]")
672 );
673 }
674
675 #[rstest]
676 fn test_gamma_market_enriched_fields_default_to_none() {
677 let json = r#"{"id": "m1", "conditionId": "0xcond", "clobTokenIds": "[]", "outcomes": "[]", "question": "Q?"}"#;
679 let market: GammaMarket = serde_json::from_str(json).unwrap();
680
681 assert!(market.best_bid.is_none());
682 assert!(market.spread.is_none());
683 assert!(market.volume_1wk.is_none());
684 assert!(market.rewards_min_size.is_none());
685 assert!(market.competitive.is_none());
686 assert!(market.category.is_none());
687 assert!(market.neg_risk_market_id.is_none());
688 }
689
690 #[rstest]
691 fn test_gamma_event_enriched_fields() {
692 let events: Vec<GammaEvent> = load("gamma_event.json");
693 let event = &events[0];
694
695 assert_eq!(event.liquidity, Some(43042905.16152));
696 assert_eq!(event.volume, Some(799823812.487094));
697 assert_eq!(event.open_interest, Some(0.0));
698 assert_eq!(event.volume_24hr, Some(5669354.219446001));
699 assert!(event.category.is_none());
700 assert_eq!(event.neg_risk, Some(true));
701 assert_eq!(
702 event.neg_risk_market_id.as_deref(),
703 Some("0x2c3d7e0eee6f058be3006baabf0d54a07da254ba47fe6e3e095e7990c7814700")
704 );
705 assert_eq!(event.featured, Some(false));
706 }
707
708 #[rstest]
709 fn test_gamma_tag_deserialization() {
710 let tags: Vec<GammaTag> = load("gamma_tags.json");
711
712 assert_eq!(tags.len(), 5);
713 assert_eq!(tags[0].id, "101259");
714 assert_eq!(tags[0].label.as_deref(), Some("Health and Human Services"));
715 assert_eq!(tags[0].slug.as_deref(), Some("health-and-human-services"));
716 assert_eq!(tags[2].slug.as_deref(), Some("attorney-general"));
717 }
718
719 #[rstest]
720 fn test_search_response_deserialization() {
721 let response: SearchResponse = load("search_response.json");
722
723 assert!(response.markets.is_none());
725
726 let events = response.events.as_ref().unwrap();
727 assert_eq!(events.len(), 1);
728 assert_eq!(events[0].slug.as_deref(), Some("bitcoin-above-on-march-11"));
729 assert_eq!(events[0].markets.len(), 1);
730 }
731
732 #[rstest]
733 fn test_search_response_empty_fields() {
734 let json = "{}";
735 let response: SearchResponse = serde_json::from_str(json).unwrap();
736 assert!(response.markets.is_none());
737 assert!(response.events.is_none());
738 }
739
740 #[rstest]
741 fn test_clob_book_response_deserialization() {
742 let response: ClobBookResponse = load("clob_book_response.json");
743
744 assert_eq!(response.bids.len(), 3);
745 assert_eq!(response.asks.len(), 3);
746
747 assert_eq!(response.bids[0].price, "0.48");
748 assert_eq!(response.bids[0].size, "100.00");
749 assert_eq!(response.bids[2].price, "0.50");
750 assert_eq!(response.bids[2].size, "150.00");
751
752 assert_eq!(response.asks[0].price, "0.51");
753 assert_eq!(response.asks[0].size, "120.00");
754 assert_eq!(response.asks[2].price, "0.53");
755 assert_eq!(response.asks[2].size, "90.00");
756 }
757
758 #[rstest]
759 fn test_clob_book_response_ignores_extra_fields() {
760 let json = r#"{
766 "market": "0xabc",
767 "asset_id": "123",
768 "hash": "0x1",
769 "timestamp": "123",
770 "bids": [],
771 "asks": [],
772 "tick_size": "0.01",
773 "min_order_size": "5",
774 "neg_risk": false,
775 "last_trade_price": "0.55"
776 }"#;
777 let response: ClobBookResponse = serde_json::from_str(json).unwrap();
778 assert!(response.bids.is_empty());
779 assert!(response.asks.is_empty());
780 }
781
782 #[rstest]
783 fn test_clob_market_response_deserialization_accepting_false() {
784 let response: ClobMarketResponse = load("clob_market_closed_binary_accepting_false.json");
785 assert_eq!(
786 response.condition_id,
787 "0x8ccc3f4951ff02c1d34b87988752b4444ad17228732780a6cf22afefe8478bb6"
788 );
789 assert!(response.closed);
790 assert_eq!(response.tokens.len(), 2);
791 assert_eq!(response.tokens[0].outcome, "Yes");
792 assert!(!response.tokens[0].winner);
793 assert_eq!(response.tokens[1].outcome, "No");
794 assert!(response.tokens[1].winner);
795 }
796
797 #[rstest]
798 fn test_clob_market_response_deserialization_accepting_true() {
799 let response: ClobMarketResponse = load("clob_market_closed_binary_accepting_true.json");
800 assert_eq!(
801 response.condition_id,
802 "0xd57eed0d44f5b8ca54925d8d6ff440b146b3e6e071da18136ee3ee572d34479e"
803 );
804 assert!(response.closed);
805 assert_eq!(response.tokens.len(), 2);
806 assert_eq!(response.tokens[0].outcome, "Yes");
807 assert!(response.tokens[0].winner);
808 assert_eq!(response.tokens[1].outcome, "No");
809 assert!(!response.tokens[1].winner);
810 }
811
812 #[rstest]
813 fn test_fee_rate_response_zero() {
814 let response: FeeRateResponse = load("clob_fee_rate_response_zero.json");
815 assert_eq!(response.base_fee, dec!(0));
816 }
817
818 #[rstest]
819 fn test_fee_rate_response_nonzero() {
820 let response: FeeRateResponse = load("clob_fee_rate_response_nonzero.json");
821 assert_eq!(response.base_fee, dec!(150));
822 }
823
824 #[rstest]
825 fn test_data_api_position_deserialization() {
826 let positions: Vec<DataApiPosition> = load("data_api_positions_response.json");
827
828 assert_eq!(positions.len(), 4);
829 assert_eq!(
830 positions[0].asset,
831 "71321045863084981365469005770620412523470745398083994982746259498689308907982"
832 );
833 assert_eq!(
834 positions[0].condition_id,
835 "0xc8f1cf5d4f26e0fd9c8fe89f2a7b3263b902cf14fde7bfccef525753bb492e47"
836 );
837 assert_eq!(positions[0].size, 150.5);
838 assert_eq!(positions[0].avg_price, Some(0.55));
839
840 assert_eq!(positions[1].size, 0.0);
842 assert_eq!(positions[1].avg_price, Some(0.45));
843
844 assert_eq!(
846 positions[2].condition_id,
847 "0xabc123def456789012345678901234567890abcdef1234567890abcdef123456"
848 );
849 assert_eq!(positions[2].size, 42.0);
850 assert_eq!(positions[2].avg_price, Some(0.3));
851
852 assert_eq!(positions[3].size, 0.005);
854 assert_eq!(positions[3].avg_price, Some(0.7));
855 }
856
857 #[rstest]
858 fn test_data_api_trade_deserialization() {
859 let trades: Vec<DataApiTrade> = load("data_api_trades_response.json");
860
861 assert_eq!(trades.len(), 3);
862 assert_eq!(
863 trades[0].asset,
864 "71321045863084981365469005770620412523470745398083994982746259498689308907982"
865 );
866 assert_eq!(
867 trades[0].condition_id,
868 "0xc8f1cf5d4f26e0fd9c8fe89f2a7b3263b902cf14fde7bfccef525753bb492e47"
869 );
870 assert_eq!(trades[0].side, PolymarketOrderSide::Buy);
871 assert_eq!(trades[0].price, 0.55);
872 assert_eq!(trades[0].size, 100.0);
873 assert_eq!(trades[0].timestamp, 1710000000);
874 assert_eq!(
875 trades[0].transaction_hash,
876 "0xabc123def456789012345678901234567890abcdef1234567890abcdef123456"
877 );
878
879 assert_eq!(trades[1].side, PolymarketOrderSide::Sell);
880 assert_eq!(trades[1].price, 0.53);
881
882 assert_eq!(
884 trades[2].asset,
885 "99999999999999999999999999999999999999999999999999999999999999999999999999999"
886 );
887 }
888}