1use alloy_primitives::Address;
7use chrono::{DateTime, Utc};
8use rust_decimal::prelude::ToPrimitive;
9use rust_decimal::Decimal;
10use serde::{Deserialize, Serialize};
11
12pub type Price = u32;
43
44pub type Qty = i64;
56
57pub const SCALE_FACTOR: i64 = 10_000;
63
64pub const MAX_PRICE_TICKS: Price = Price::MAX;
67
68pub const MIN_PRICE_TICKS: Price = 1;
70
71pub const MAX_QTY: Qty = Qty::MAX / 2; pub fn decimal_to_price(decimal: Decimal) -> std::result::Result<Price, &'static str> {
92 decimal_to_price_exact(decimal)
93}
94
95pub fn decimal_to_price_exact(decimal: Decimal) -> std::result::Result<Price, &'static str> {
100 let scaled = decimal * Decimal::from(SCALE_FACTOR);
101
102 let ticks = scaled
103 .to_u32()
104 .ok_or("Price too large, negative, or fractional")?;
105
106 if Decimal::from(ticks) != scaled {
107 return Err("Price is not exactly representable at 4 decimal places");
108 }
109 if ticks < MIN_PRICE_TICKS {
110 return Err("Price below minimum");
111 }
112
113 Ok(ticks)
114}
115
116pub fn decimal_to_price_lossy(decimal: Decimal) -> std::result::Result<Price, &'static str> {
127 let scaled = decimal * Decimal::from(SCALE_FACTOR);
129
130 let rounded = scaled.round();
132
133 let as_u64 = rounded.to_u64().ok_or("Price too large or negative")?;
135
136 if as_u64 < MIN_PRICE_TICKS as u64 {
138 return Ok(MIN_PRICE_TICKS); }
140 if as_u64 > MAX_PRICE_TICKS as u64 {
141 return Err("Price exceeds maximum");
142 }
143
144 Ok(as_u64 as Price)
145}
146
147pub fn price_to_decimal(ticks: Price) -> Decimal {
156 Decimal::from(ticks) / Decimal::from(SCALE_FACTOR)
157}
158
159pub fn decimal_to_qty(decimal: Decimal) -> std::result::Result<Qty, &'static str> {
168 let scaled = decimal * Decimal::from(SCALE_FACTOR);
169 let rounded = scaled.round();
170
171 let as_i64 = rounded.to_i64().ok_or("Quantity too large")?;
172
173 if as_i64.abs() > MAX_QTY {
174 return Err("Quantity exceeds maximum");
175 }
176
177 Ok(as_i64)
178}
179
180pub fn qty_to_decimal(units: Qty) -> Decimal {
186 Decimal::from(units) / Decimal::from(SCALE_FACTOR)
187}
188
189pub fn is_price_tick_aligned(decimal: Decimal, tick_size_decimal: Decimal) -> bool {
199 let tick_size_ticks = match decimal_to_price_exact(tick_size_decimal) {
201 Ok(ticks) => ticks,
202 Err(_) => return false,
203 };
204
205 let price_ticks = match decimal_to_price_exact(decimal) {
207 Ok(ticks) => ticks,
208 Err(_) => return false,
209 };
210
211 if tick_size_ticks == 0 {
214 return true;
215 }
216
217 price_ticks % tick_size_ticks == 0
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
222#[allow(clippy::upper_case_acronyms)]
223pub enum Side {
224 BUY = 0,
225 SELL = 1,
226}
227
228impl Side {
229 pub fn as_str(&self) -> &'static str {
230 match self {
231 Side::BUY => "BUY",
232 Side::SELL => "SELL",
233 }
234 }
235
236 pub fn opposite(&self) -> Self {
237 match self {
238 Side::BUY => Side::SELL,
239 Side::SELL => Side::BUY,
240 }
241 }
242}
243
244#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
246#[allow(clippy::upper_case_acronyms)]
247pub enum OrderType {
248 #[default]
249 GTC,
250 FOK,
251 GTD,
252 FAK,
253}
254
255impl OrderType {
256 pub fn as_str(&self) -> &'static str {
257 match self {
258 OrderType::GTC => "GTC",
259 OrderType::FOK => "FOK",
260 OrderType::GTD => "GTD",
261 OrderType::FAK => "FAK",
262 }
263 }
264}
265
266#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
268pub enum OrderStatus {
269 #[serde(rename = "LIVE")]
270 Live,
271 #[serde(rename = "CANCELLED")]
272 Cancelled,
273 #[serde(rename = "FILLED")]
274 Filled,
275 #[serde(rename = "PARTIAL")]
276 Partial,
277 #[serde(rename = "EXPIRED")]
278 Expired,
279}
280
281#[derive(Debug, Clone, Serialize, Deserialize)]
283pub struct MarketSnapshot {
284 pub token_id: String,
285 pub market_id: String,
286 pub timestamp: DateTime<Utc>,
287 pub bid: Option<Decimal>,
288 pub ask: Option<Decimal>,
289 pub mid: Option<Decimal>,
290 pub spread: Option<Decimal>,
291 pub last_price: Option<Decimal>,
292 pub volume_24h: Option<Decimal>,
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct BookLevel {
301 #[serde(with = "rust_decimal::serde::str")]
302 pub price: Decimal,
303 #[serde(with = "rust_decimal::serde::str")]
304 pub size: Decimal,
305}
306
307#[derive(Debug, Clone, Copy, PartialEq, Eq)]
318pub struct FastBookLevel {
319 pub price: Price, pub size: Qty, }
322
323impl FastBookLevel {
324 pub fn new(price: Price, size: Qty) -> Self {
326 Self { price, size }
327 }
328
329 pub fn to_book_level(self) -> BookLevel {
332 BookLevel {
333 price: price_to_decimal(self.price),
334 size: qty_to_decimal(self.size),
335 }
336 }
337
338 pub fn from_book_level(level: &BookLevel) -> std::result::Result<Self, &'static str> {
341 let price = decimal_to_price_exact(level.price)?;
342 let size = decimal_to_qty(level.size)?;
343 Ok(Self::new(price, size))
344 }
345
346 pub fn notional(self) -> i64 {
353 let price_i64 = self.price as i64;
355 (price_i64 * self.size) / SCALE_FACTOR
357 }
358}
359
360#[derive(Debug, Clone, Serialize)]
362pub struct OrderBook {
363 pub token_id: String,
365 pub timestamp: DateTime<Utc>,
367 pub bids: Vec<BookLevel>,
369 pub asks: Vec<BookLevel>,
371 pub sequence: u64,
377 pub last_delta_sequence: u64,
379 pub last_snapshot_timestamp_ms: u64,
381}
382
383impl<'de> Deserialize<'de> for OrderBook {
384 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
385 where
386 D: serde::Deserializer<'de>,
387 {
388 #[derive(Deserialize)]
389 struct WireOrderBook {
390 token_id: String,
391 timestamp: DateTime<Utc>,
392 bids: Vec<BookLevel>,
393 asks: Vec<BookLevel>,
394 sequence: u64,
395 #[serde(default)]
396 last_delta_sequence: Option<u64>,
397 #[serde(default)]
398 last_snapshot_timestamp_ms: Option<u64>,
399 }
400
401 let wire = WireOrderBook::deserialize(deserializer)?;
402 Ok(Self {
403 token_id: wire.token_id,
404 timestamp: wire.timestamp,
405 bids: wire.bids,
406 asks: wire.asks,
407 sequence: wire.sequence,
408 last_delta_sequence: wire.last_delta_sequence.unwrap_or(wire.sequence),
409 last_snapshot_timestamp_ms: wire.last_snapshot_timestamp_ms.unwrap_or_default(),
410 })
411 }
412}
413
414#[derive(Debug, Clone, Serialize, Deserialize)]
419pub struct OrderDelta {
420 pub token_id: String,
421 pub timestamp: DateTime<Utc>,
422 pub side: Side,
423 pub price: Decimal,
424 pub size: Decimal, pub sequence: u64,
426}
427
428#[derive(Debug, Clone, Copy, PartialEq, Eq)]
439pub struct FastOrderDelta {
440 pub token_id_hash: u64, pub timestamp: DateTime<Utc>,
442 pub side: Side,
443 pub price: Price, pub size: Qty, pub sequence: u64,
446}
447
448impl FastOrderDelta {
449 pub fn from_order_delta(
455 delta: &OrderDelta,
456 tick_size: Option<Decimal>,
457 ) -> std::result::Result<Self, &'static str> {
458 if let Some(tick_size) = tick_size {
460 if !is_price_tick_aligned(delta.price, tick_size) {
461 return Err("Price not aligned to tick size");
462 }
463 }
464
465 let price = decimal_to_price_exact(delta.price)?;
467 let size = decimal_to_qty(delta.size)?;
468
469 let token_id_hash = {
472 use std::collections::hash_map::DefaultHasher;
473 use std::hash::{Hash, Hasher};
474 let mut hasher = DefaultHasher::new();
475 delta.token_id.hash(&mut hasher);
476 hasher.finish()
477 };
478
479 Ok(Self {
480 token_id_hash,
481 timestamp: delta.timestamp,
482 side: delta.side,
483 price,
484 size,
485 sequence: delta.sequence,
486 })
487 }
488
489 pub fn to_order_delta(self, token_id: String) -> OrderDelta {
492 OrderDelta {
493 token_id,
494 timestamp: self.timestamp,
495 side: self.side,
496 price: price_to_decimal(self.price),
497 size: qty_to_decimal(self.size),
498 sequence: self.sequence,
499 }
500 }
501
502 pub fn is_removal(self) -> bool {
504 self.size == 0
505 }
506}
507
508#[derive(Debug, Clone, Serialize, Deserialize)]
510pub struct FillEvent {
511 pub id: String,
512 pub order_id: String,
513 pub token_id: String,
514 pub side: Side,
515 pub price: Decimal,
516 pub size: Decimal,
517 pub timestamp: DateTime<Utc>,
518 pub maker_address: Address,
519 pub taker_address: Address,
520 pub fee: Decimal,
521}
522
523#[derive(Debug, Clone)]
525pub struct OrderRequest {
526 pub token_id: String,
527 pub side: Side,
528 pub price: Decimal,
529 pub size: Decimal,
530 pub order_type: OrderType,
531 pub expiration: Option<DateTime<Utc>>,
532 pub client_id: Option<String>,
533}
534
535#[derive(Debug, Clone)]
537pub struct MarketOrderRequest {
538 pub token_id: String,
539 pub side: Side,
540 pub amount: Decimal, pub slippage_tolerance: Option<Decimal>,
542 pub client_id: Option<String>,
543}
544
545#[derive(Debug, Clone, Serialize, Deserialize)]
547pub struct Order {
548 pub id: String,
549 pub token_id: String,
550 pub side: Side,
551 pub price: Decimal,
552 pub original_size: Decimal,
553 pub filled_size: Decimal,
554 pub remaining_size: Decimal,
555 pub status: OrderStatus,
556 pub order_type: OrderType,
557 pub created_at: DateTime<Utc>,
558 pub updated_at: DateTime<Utc>,
559 pub expiration: Option<DateTime<Utc>>,
560 pub client_id: Option<String>,
561}
562
563#[derive(Debug, Clone, Serialize, Deserialize, Default)]
565pub struct ApiCredentials {
566 #[serde(rename = "apiKey")]
567 pub api_key: String,
568 pub secret: String,
569 pub passphrase: String,
570}
571
572#[derive(Debug, Clone, PartialEq)]
574pub struct OrderArgs {
575 pub token_id: String,
576 pub price: Decimal,
577 pub size: Decimal,
578 pub side: Side,
579 pub expiration: Option<u64>,
580 pub builder_code: Option<String>,
581 pub metadata: Option<String>,
582}
583
584impl OrderArgs {
585 pub fn new(token_id: &str, price: Decimal, size: Decimal, side: Side) -> Self {
586 Self {
587 token_id: token_id.to_string(),
588 price,
589 size,
590 side,
591 expiration: None,
592 builder_code: None,
593 metadata: None,
594 }
595 }
596}
597
598impl Default for OrderArgs {
599 fn default() -> Self {
600 Self {
601 token_id: String::new(),
602 price: Decimal::ZERO,
603 size: Decimal::ZERO,
604 side: Side::BUY,
605 expiration: None,
606 builder_code: None,
607 metadata: None,
608 }
609 }
610}
611
612#[derive(Debug, Clone, PartialEq)]
614pub struct MarketOrderArgs {
615 pub token_id: String,
616 pub amount: Decimal,
617 pub side: Side,
618 pub order_type: OrderType,
619 pub price_limit: Option<Decimal>,
620 pub user_usdc_balance: Option<Decimal>,
621 pub builder_code: Option<String>,
622 pub metadata: Option<String>,
623}
624
625impl MarketOrderArgs {
626 pub fn new(token_id: &str, amount: Decimal, side: Side, order_type: OrderType) -> Self {
627 Self {
628 token_id: token_id.to_string(),
629 amount,
630 side,
631 order_type,
632 price_limit: None,
633 user_usdc_balance: None,
634 builder_code: None,
635 metadata: None,
636 }
637 }
638}
639
640#[derive(Debug, Clone, Copy, Default, PartialEq)]
642pub struct CreateOrderOptions {
643 pub tick_size: Option<Decimal>,
644 pub neg_risk: Option<bool>,
645}
646
647#[derive(Debug, Clone, Copy, PartialEq, Eq)]
649pub struct PostOrderOptions {
650 pub order_type: OrderType,
651 pub post_only: bool,
652 pub defer_exec: bool,
653}
654
655impl Default for PostOrderOptions {
656 fn default() -> Self {
657 Self {
658 order_type: OrderType::GTC,
659 post_only: false,
660 defer_exec: false,
661 }
662 }
663}
664
665#[derive(Debug, Clone, Serialize, Deserialize)]
667#[serde(rename_all = "camelCase")]
668pub struct SignedOrderRequest {
669 pub salt: u64,
670 pub maker: String,
671 pub signer: String,
672 pub token_id: String,
673 pub maker_amount: String,
674 pub taker_amount: String,
675 pub expiration: String,
676 pub side: String,
677 pub signature_type: u8,
678 pub timestamp: String,
679 pub metadata: String,
680 pub builder: String,
681 pub signature: String,
682}
683
684#[derive(Debug, Serialize)]
686#[serde(rename_all = "camelCase")]
687pub struct PostOrder {
688 pub order: SignedOrderRequest,
689 pub owner: String,
690 pub order_type: OrderType,
691 pub post_only: bool,
692 pub defer_exec: bool,
693}
694
695impl PostOrder {
696 pub fn new(order: SignedOrderRequest, owner: String, options: PostOrderOptions) -> Self {
697 Self {
698 order,
699 owner,
700 order_type: options.order_type,
701 post_only: options.post_only,
702 defer_exec: options.defer_exec,
703 }
704 }
705}
706
707#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
709#[serde(rename_all = "camelCase")]
710pub struct PostOrderResponse {
711 pub success: bool,
712 #[serde(rename = "orderID")]
713 pub order_id: String,
714 pub status: String,
715 pub making_amount: String,
716 pub taking_amount: String,
717 #[serde(default)]
718 pub transactions_hashes: Vec<String>,
719 #[serde(default)]
720 pub trade_ids: Vec<String>,
721 #[serde(default)]
722 pub error_msg: String,
723}
724
725#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
727#[serde(rename_all = "camelCase")]
728pub struct CancelOrdersResponse {
729 #[serde(default)]
730 pub canceled: Vec<String>,
731 #[serde(default, alias = "not_canceled")]
732 pub not_canceled: std::collections::HashMap<String, String>,
733}
734
735#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
737pub struct ClobTokenInfo {
738 pub t: String,
739 pub o: String,
740}
741
742#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
744pub struct ClobFeeDetails {
745 #[serde(deserialize_with = "crate::decode::deserializers::decimal_from_string")]
746 pub r: Decimal,
747 pub e: u32,
748 #[serde(default)]
749 pub to: bool,
750}
751
752#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
754pub struct ClobMarketInfo {
755 #[serde(default)]
756 pub c: Option<String>,
757 #[serde(default)]
758 pub gst: Option<String>,
759 #[serde(default)]
760 pub r: serde_json::Value,
761 #[serde(default)]
762 pub t: Vec<ClobTokenInfo>,
763 #[serde(
764 default,
765 deserialize_with = "crate::decode::deserializers::decimal_from_string_or_zero"
766 )]
767 pub mos: Decimal,
768 #[serde(
769 default,
770 deserialize_with = "crate::decode::deserializers::decimal_from_string_or_zero"
771 )]
772 pub mts: Decimal,
773 #[serde(
774 default,
775 deserialize_with = "crate::decode::deserializers::decimal_from_string_or_zero"
776 )]
777 pub mbf: Decimal,
778 #[serde(
779 default,
780 deserialize_with = "crate::decode::deserializers::decimal_from_string_or_zero"
781 )]
782 pub tbf: Decimal,
783 #[serde(default)]
784 pub rfqe: bool,
785 #[serde(default)]
786 pub itode: bool,
787 #[serde(default)]
788 pub ibce: bool,
789 #[serde(default)]
790 pub nr: Option<bool>,
791 #[serde(default)]
792 pub fd: Option<ClobFeeDetails>,
793 #[serde(
794 default,
795 deserialize_with = "crate::decode::deserializers::optional_number_from_string"
796 )]
797 pub oas: Option<u64>,
798}
799
800#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
802#[serde(rename_all = "camelCase")]
803pub struct BuilderFeeRateResponse {
804 #[serde(alias = "builder_maker_fee_rate_bps")]
805 pub builder_maker_fee_rate_bps: u32,
806 #[serde(alias = "builder_taker_fee_rate_bps")]
807 pub builder_taker_fee_rate_bps: u32,
808}
809
810#[derive(Debug, Clone, Serialize, Deserialize)]
812pub struct Market {
813 pub condition_id: String,
814 pub tokens: [Token; 2],
815 pub rewards: Rewards,
816 pub min_incentive_size: Option<String>,
817 pub max_incentive_spread: Option<String>,
818 pub active: bool,
819 pub closed: bool,
820 pub question_id: String,
821 pub minimum_order_size: Decimal,
822 pub minimum_tick_size: Decimal,
823 pub description: String,
824 pub category: Option<String>,
825 pub end_date_iso: Option<String>,
826 pub game_start_time: Option<String>,
827 pub question: String,
828 pub market_slug: String,
829 pub seconds_delay: Decimal,
830 pub icon: String,
831 pub fpmm: String,
832 #[serde(default)]
834 pub enable_order_book: bool,
835 #[serde(default)]
836 pub archived: bool,
837 #[serde(default)]
838 pub accepting_orders: bool,
839 #[serde(default)]
840 pub accepting_order_timestamp: Option<String>,
841 #[serde(default)]
842 pub maker_base_fee: Decimal,
843 #[serde(default)]
844 pub taker_base_fee: Decimal,
845 #[serde(default)]
846 pub notifications_enabled: bool,
847 #[serde(default)]
848 pub neg_risk: bool,
849 #[serde(default)]
850 pub neg_risk_market_id: String,
851 #[serde(default)]
852 pub neg_risk_request_id: String,
853 #[serde(default)]
854 pub image: String,
855 #[serde(default)]
856 pub is_50_50_outcome: bool,
857}
858
859#[derive(Debug, Clone, Serialize, Deserialize)]
861pub struct Token {
862 pub token_id: String,
863 pub outcome: String,
864 pub price: Decimal,
865 #[serde(default)]
866 pub winner: bool,
867}
868
869#[derive(Debug, Clone, Serialize, Deserialize)]
871pub struct ClientConfig {
872 pub base_url: String,
874 pub chain: u64,
876 pub private_key: Option<String>,
878 pub api_credentials: Option<ApiCredentials>,
880 pub builder_code: Option<String>,
882 pub signature_type: Option<u8>,
884 pub funder: Option<String>,
887 pub timeout: Option<std::time::Duration>,
889 pub max_connections: Option<usize>,
891}
892
893impl Default for ClientConfig {
894 fn default() -> Self {
895 Self {
896 base_url: "https://clob.polymarket.com".to_string(),
897 chain: 137, private_key: None,
899 api_credentials: None,
900 builder_code: None,
901 signature_type: None,
902 funder: None,
903 timeout: Some(std::time::Duration::from_secs(30)),
904 max_connections: Some(100),
905 }
906 }
907}
908
909pub type WssAuth = ApiCredentials;
914
915#[derive(Debug, Clone, Serialize, Deserialize)]
917pub struct WssSubscription {
918 #[serde(rename = "type")]
920 pub channel_type: String,
921 #[serde(skip_serializing_if = "Option::is_none")]
923 pub operation: Option<String>,
924 #[serde(default)]
926 pub markets: Vec<String>,
927 #[serde(rename = "assets_ids", default)]
930 pub asset_ids: Vec<String>,
931 #[serde(skip_serializing_if = "Option::is_none")]
933 pub initial_dump: Option<bool>,
934 #[serde(skip_serializing_if = "Option::is_none")]
936 pub custom_feature_enabled: Option<bool>,
937 #[serde(skip_serializing_if = "Option::is_none")]
939 pub auth: Option<WssAuth>,
940}
941
942#[derive(Debug, Clone, Serialize, Deserialize)]
944#[serde(tag = "event_type")]
945pub enum StreamMessage {
946 #[serde(rename = "book")]
948 Book(BookUpdate),
949 #[serde(rename = "price_change")]
951 PriceChange(PriceChange),
952 #[serde(rename = "tick_size_change")]
954 TickSizeChange(TickSizeChange),
955 #[serde(rename = "last_trade_price")]
957 LastTradePrice(LastTradePrice),
958 #[serde(rename = "best_bid_ask")]
960 BestBidAsk(BestBidAsk),
961 #[serde(rename = "new_market")]
963 NewMarket(NewMarket),
964 #[serde(rename = "market_resolved")]
966 MarketResolved(MarketResolved),
967 #[serde(rename = "trade")]
969 Trade(TradeMessage),
970 #[serde(rename = "order")]
972 Order(OrderMessage),
973 #[serde(other)]
975 Unknown,
976}
977
978#[derive(Debug, Clone, Serialize, Deserialize)]
985pub struct BookUpdate {
986 pub asset_id: String,
987 pub market: String,
988 #[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
990 pub timestamp: u64,
991 #[serde(deserialize_with = "crate::decode::deserializers::vec_from_null")]
992 pub bids: Vec<OrderSummary>,
993 #[serde(deserialize_with = "crate::decode::deserializers::vec_from_null")]
994 pub asks: Vec<OrderSummary>,
995 #[serde(default)]
1000 pub hash: Option<String>,
1001}
1002
1003#[derive(Debug, Clone, Serialize, Deserialize)]
1005pub struct PriceChange {
1006 pub market: String,
1007 #[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
1008 pub timestamp: u64,
1009 #[serde(
1010 default,
1011 deserialize_with = "crate::decode::deserializers::vec_from_null"
1012 )]
1013 pub price_changes: Vec<PriceChangeEntry>,
1014}
1015
1016#[derive(Debug, Clone, Serialize, Deserialize)]
1017pub struct PriceChangeEntry {
1018 pub asset_id: String,
1019 pub price: Decimal,
1020 #[serde(
1021 default,
1022 deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
1023 )]
1024 pub size: Option<Decimal>,
1025 pub side: Side,
1026 #[serde(default)]
1027 pub hash: Option<String>,
1028 #[serde(
1029 default,
1030 deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
1031 )]
1032 pub best_bid: Option<Decimal>,
1033 #[serde(
1034 default,
1035 deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
1036 )]
1037 pub best_ask: Option<Decimal>,
1038}
1039
1040#[derive(Debug, Clone, Serialize, Deserialize)]
1042pub struct TickSizeChange {
1043 pub asset_id: String,
1044 pub market: String,
1045 pub old_tick_size: Decimal,
1046 pub new_tick_size: Decimal,
1047 #[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
1048 pub timestamp: u64,
1049}
1050
1051#[derive(Debug, Clone, Serialize, Deserialize)]
1053pub struct LastTradePrice {
1054 pub asset_id: String,
1055 pub market: String,
1056 pub price: Decimal,
1057 #[serde(default)]
1058 pub side: Option<Side>,
1059 #[serde(
1060 default,
1061 deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
1062 )]
1063 pub size: Option<Decimal>,
1064 #[serde(
1065 default,
1066 deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
1067 )]
1068 pub fee_rate_bps: Option<Decimal>,
1069 #[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
1070 pub timestamp: u64,
1071}
1072
1073#[derive(Debug, Clone, Serialize, Deserialize)]
1075pub struct BestBidAsk {
1076 pub market: String,
1077 pub asset_id: String,
1078 pub best_bid: Decimal,
1079 pub best_ask: Decimal,
1080 pub spread: Decimal,
1081 #[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
1082 pub timestamp: u64,
1083}
1084
1085#[derive(Debug, Clone, Serialize, Deserialize)]
1087pub struct NewMarket {
1088 pub id: String,
1089 pub question: String,
1090 pub market: String,
1091 pub slug: String,
1092 pub description: String,
1093 #[serde(rename = "assets_ids", alias = "asset_ids")]
1094 pub asset_ids: Vec<String>,
1095 #[serde(
1096 default,
1097 deserialize_with = "crate::decode::deserializers::vec_from_null"
1098 )]
1099 pub outcomes: Vec<String>,
1100 #[serde(default)]
1101 pub event_message: Option<EventMessage>,
1102 #[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
1103 pub timestamp: u64,
1104}
1105
1106#[derive(Debug, Clone, Serialize, Deserialize)]
1108pub struct MarketResolved {
1109 pub id: String,
1110 #[serde(default)]
1111 pub question: Option<String>,
1112 pub market: String,
1113 #[serde(default)]
1114 pub slug: Option<String>,
1115 #[serde(default)]
1116 pub description: Option<String>,
1117 #[serde(rename = "assets_ids", alias = "asset_ids")]
1118 pub asset_ids: Vec<String>,
1119 #[serde(
1120 default,
1121 deserialize_with = "crate::decode::deserializers::vec_from_null"
1122 )]
1123 pub outcomes: Vec<String>,
1124 pub winning_asset_id: String,
1125 pub winning_outcome: String,
1126 #[serde(default)]
1127 pub event_message: Option<EventMessage>,
1128 #[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
1129 pub timestamp: u64,
1130}
1131
1132#[derive(Debug, Clone, Serialize, Deserialize)]
1134pub struct EventMessage {
1135 pub id: String,
1136 pub ticker: String,
1137 pub slug: String,
1138 pub title: String,
1139 pub description: String,
1140}
1141
1142#[derive(Debug, Clone, Serialize, Deserialize)]
1144pub struct TradeMessage {
1145 pub id: String,
1146 pub market: String,
1147 pub asset_id: String,
1148 pub side: Side,
1149 pub size: Decimal,
1150 pub price: Decimal,
1151 #[serde(default)]
1152 pub status: Option<String>,
1153 #[serde(rename = "type", default)]
1154 pub msg_type: Option<String>,
1155 #[serde(
1156 default,
1157 deserialize_with = "crate::decode::deserializers::optional_number_from_string"
1158 )]
1159 pub last_update: Option<u64>,
1160 #[serde(
1161 default,
1162 alias = "match_time",
1163 deserialize_with = "crate::decode::deserializers::optional_number_from_string"
1164 )]
1165 pub matchtime: Option<u64>,
1166 #[serde(
1167 default,
1168 deserialize_with = "crate::decode::deserializers::optional_number_from_string"
1169 )]
1170 pub timestamp: Option<u64>,
1171}
1172
1173#[derive(Debug, Clone, Serialize, Deserialize)]
1175pub struct OrderMessage {
1176 pub id: String,
1177 pub market: String,
1178 pub asset_id: String,
1179 pub side: Side,
1180 pub price: Decimal,
1181 #[serde(rename = "type", default)]
1182 pub msg_type: Option<String>,
1183 #[serde(
1184 default,
1185 deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
1186 )]
1187 pub original_size: Option<Decimal>,
1188 #[serde(
1189 default,
1190 deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
1191 )]
1192 pub size_matched: Option<Decimal>,
1193 #[serde(
1194 default,
1195 deserialize_with = "crate::decode::deserializers::optional_number_from_string"
1196 )]
1197 pub timestamp: Option<u64>,
1198 #[serde(default)]
1199 pub associate_trades: Option<Vec<String>>,
1200 #[serde(default)]
1201 pub status: Option<String>,
1202}
1203
1204#[derive(Debug, Clone, Serialize, Deserialize)]
1206pub struct Subscription {
1207 pub token_ids: Vec<String>,
1208 pub channels: Vec<String>,
1209}
1210
1211#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1213pub enum WssChannelType {
1214 #[serde(rename = "USER")]
1215 User,
1216 #[serde(rename = "MARKET")]
1217 Market,
1218}
1219
1220impl WssChannelType {
1221 pub fn as_str(&self) -> &'static str {
1222 match self {
1223 WssChannelType::User => "USER",
1224 WssChannelType::Market => "MARKET",
1225 }
1226 }
1227}
1228
1229#[derive(Debug, Clone, Serialize, Deserialize)]
1231pub struct Quote {
1232 pub token_id: String,
1233 pub side: Side,
1234 #[serde(with = "rust_decimal::serde::str")]
1235 pub price: Decimal,
1236 pub timestamp: DateTime<Utc>,
1237}
1238
1239#[derive(Debug, Clone, Serialize, Deserialize)]
1241pub struct Balance {
1242 pub token_id: String,
1243 pub available: Decimal,
1244 pub locked: Decimal,
1245 pub total: Decimal,
1246}
1247
1248#[derive(Debug, Clone)]
1250pub struct Metrics {
1251 pub orders_per_second: f64,
1252 pub avg_latency_ms: f64,
1253 pub error_rate: f64,
1254 pub uptime_pct: f64,
1255}
1256
1257pub type TokenId = String;
1259pub type OrderId = String;
1260pub type MarketId = String;
1261pub type ClientId = String;
1262
1263#[derive(Debug, Clone)]
1265pub struct OpenOrderParams {
1266 pub id: Option<String>,
1267 pub asset_id: Option<String>,
1268 pub market: Option<String>,
1269}
1270
1271impl OpenOrderParams {
1272 pub fn to_query_params(&self) -> Vec<(&str, &String)> {
1273 let mut params = Vec::with_capacity(3);
1274
1275 if let Some(x) = &self.id {
1276 params.push(("id", x));
1277 }
1278
1279 if let Some(x) = &self.asset_id {
1280 params.push(("asset_id", x));
1281 }
1282
1283 if let Some(x) = &self.market {
1284 params.push(("market", x));
1285 }
1286 params
1287 }
1288}
1289
1290#[derive(Debug, Clone)]
1292pub struct TradeParams {
1293 pub id: Option<String>,
1294 pub maker_address: Option<String>,
1295 pub market: Option<String>,
1296 pub asset_id: Option<String>,
1297 pub before: Option<u64>,
1298 pub after: Option<u64>,
1299}
1300
1301impl TradeParams {
1302 pub fn to_query_params(&self) -> Vec<(&str, String)> {
1303 let mut params = Vec::with_capacity(6);
1304
1305 if let Some(x) = &self.id {
1306 params.push(("id", x.clone()));
1307 }
1308
1309 if let Some(x) = &self.asset_id {
1310 params.push(("asset_id", x.clone()));
1311 }
1312
1313 if let Some(x) = &self.market {
1314 params.push(("market", x.clone()));
1315 }
1316
1317 if let Some(x) = &self.maker_address {
1318 params.push(("maker_address", x.clone()));
1319 }
1320
1321 if let Some(x) = &self.before {
1322 params.push(("before", x.to_string()));
1323 }
1324
1325 if let Some(x) = &self.after {
1326 params.push(("after", x.to_string()));
1327 }
1328
1329 params
1330 }
1331}
1332
1333#[derive(Debug, Clone, Serialize, Deserialize)]
1335pub struct OpenOrder {
1336 pub associate_trades: Vec<String>,
1337 pub id: String,
1338 pub status: String,
1339 pub market: String,
1340 #[serde(with = "rust_decimal::serde::str")]
1341 pub original_size: Decimal,
1342 pub outcome: String,
1343 pub maker_address: String,
1344 pub owner: String,
1345 #[serde(with = "rust_decimal::serde::str")]
1346 pub price: Decimal,
1347 pub side: Side,
1348 #[serde(with = "rust_decimal::serde::str")]
1349 pub size_matched: Decimal,
1350 pub asset_id: String,
1351 #[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
1352 pub expiration: u64,
1353 #[serde(rename = "type", alias = "order_type", alias = "orderType", default)]
1354 pub order_type: OrderType,
1355 #[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
1356 pub created_at: u64,
1357}
1358
1359#[derive(Debug, Clone, Serialize, Deserialize)]
1361pub struct BalanceAllowance {
1362 pub asset_id: String,
1363 #[serde(with = "rust_decimal::serde::str")]
1364 pub balance: Decimal,
1365 #[serde(with = "rust_decimal::serde::str")]
1366 pub allowance: Decimal,
1367}
1368
1369#[derive(Default)]
1371pub struct BalanceAllowanceParams {
1372 pub asset_type: Option<AssetType>,
1373 pub token_id: Option<String>,
1374 pub signature_type: Option<u8>,
1375}
1376
1377impl BalanceAllowanceParams {
1378 pub fn to_query_params(&self) -> Vec<(&str, String)> {
1379 let mut params = Vec::with_capacity(3);
1380
1381 if let Some(x) = &self.asset_type {
1382 params.push(("asset_type", x.to_string()));
1383 }
1384
1385 if let Some(x) = &self.token_id {
1386 params.push(("token_id", x.to_string()));
1387 }
1388
1389 if let Some(x) = &self.signature_type {
1390 params.push(("signature_type", x.to_string()));
1391 }
1392 params
1393 }
1394
1395 pub fn set_signature_type(&mut self, s: u8) {
1396 self.signature_type = Some(s);
1397 }
1398}
1399
1400#[allow(clippy::upper_case_acronyms)]
1402pub enum AssetType {
1403 COLLATERAL,
1404 CONDITIONAL,
1405}
1406
1407impl std::fmt::Display for AssetType {
1408 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1409 match self {
1410 AssetType::COLLATERAL => write!(f, "COLLATERAL"),
1411 AssetType::CONDITIONAL => write!(f, "CONDITIONAL"),
1412 }
1413 }
1414}
1415
1416#[derive(Debug, Clone, Serialize, Deserialize)]
1418pub struct NotificationParams {
1419 pub signature: String,
1420 pub timestamp: u64,
1421}
1422
1423#[derive(Debug, Clone, Serialize, Deserialize)]
1425pub struct BatchMidpointRequest {
1426 pub token_ids: Vec<String>,
1427}
1428
1429#[derive(Debug, Clone, Serialize, Deserialize)]
1431pub struct BatchMidpointResponse {
1432 pub midpoints: std::collections::HashMap<String, Option<Decimal>>,
1433}
1434
1435#[derive(Debug, Clone, Serialize, Deserialize)]
1437pub struct BatchPriceRequest {
1438 pub token_ids: Vec<String>,
1439}
1440
1441#[derive(Debug, Clone, Serialize, Deserialize)]
1443pub struct TokenPrice {
1444 pub token_id: String,
1445 #[serde(skip_serializing_if = "Option::is_none")]
1446 pub bid: Option<Decimal>,
1447 #[serde(skip_serializing_if = "Option::is_none")]
1448 pub ask: Option<Decimal>,
1449 #[serde(skip_serializing_if = "Option::is_none")]
1450 pub mid: Option<Decimal>,
1451}
1452
1453#[derive(Debug, Clone, Serialize, Deserialize)]
1455pub struct BatchPriceResponse {
1456 pub prices: Vec<TokenPrice>,
1457}
1458
1459#[derive(Debug, Deserialize)]
1461pub struct ApiKeysResponse {
1462 #[serde(rename = "apiKeys")]
1463 pub api_keys: Vec<String>,
1464}
1465
1466#[derive(Debug, Deserialize)]
1467pub struct MidpointResponse {
1468 #[serde(with = "rust_decimal::serde::str")]
1469 pub mid: Decimal,
1470}
1471
1472#[derive(Debug, Deserialize)]
1473pub struct PriceResponse {
1474 #[serde(with = "rust_decimal::serde::str")]
1475 pub price: Decimal,
1476}
1477
1478#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1487pub enum PricesHistoryInterval {
1488 OneMinute,
1489 OneHour,
1490 SixHours,
1491 OneDay,
1492 OneWeek,
1493}
1494
1495impl PricesHistoryInterval {
1496 pub const fn as_str(self) -> &'static str {
1497 match self {
1498 Self::OneMinute => "1m",
1499 Self::OneHour => "1h",
1500 Self::SixHours => "6h",
1501 Self::OneDay => "1d",
1502 Self::OneWeek => "1w",
1503 }
1504 }
1505}
1506
1507#[derive(Debug, Clone, Serialize, Deserialize)]
1512pub struct PricesHistoryResponse {
1513 pub history: Vec<serde_json::Value>,
1514}
1515
1516#[derive(Debug, Deserialize)]
1517pub struct SpreadResponse {
1518 #[serde(with = "rust_decimal::serde::str")]
1519 pub spread: Decimal,
1520}
1521
1522#[derive(Debug, Deserialize)]
1523pub struct TickSizeResponse {
1524 #[serde(with = "rust_decimal::serde::str")]
1525 pub minimum_tick_size: Decimal,
1526}
1527
1528#[derive(Debug, Deserialize)]
1529pub struct NegRiskResponse {
1530 pub neg_risk: bool,
1531}
1532
1533#[derive(Debug, Serialize, Deserialize)]
1534pub struct BookParams {
1535 pub token_id: String,
1536 pub side: Side,
1537}
1538
1539#[derive(Debug, Deserialize)]
1540pub struct OrderBookSummary {
1541 pub market: String,
1542 pub asset_id: String,
1543 #[serde(default)]
1544 pub hash: Option<String>,
1545 #[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
1546 pub timestamp: u64,
1547 #[serde(
1548 default,
1549 deserialize_with = "crate::decode::deserializers::vec_from_null"
1550 )]
1551 pub bids: Vec<OrderSummary>,
1552 #[serde(
1553 default,
1554 deserialize_with = "crate::decode::deserializers::vec_from_null"
1555 )]
1556 pub asks: Vec<OrderSummary>,
1557 pub min_order_size: Decimal,
1558 pub neg_risk: bool,
1559 pub tick_size: Decimal,
1560 #[serde(
1561 default,
1562 deserialize_with = "crate::decode::deserializers::optional_decimal_from_string_default_on_error"
1563 )]
1564 pub last_trade_price: Option<Decimal>,
1565}
1566
1567#[derive(Debug, Clone, Serialize, Deserialize)]
1568pub struct OrderSummary {
1569 #[serde(with = "rust_decimal::serde::str")]
1570 pub price: Decimal,
1571 #[serde(with = "rust_decimal::serde::str")]
1572 pub size: Decimal,
1573}
1574
1575#[derive(Debug, Serialize, Deserialize)]
1576pub struct MarketsResponse {
1577 pub limit: usize,
1578 pub count: usize,
1579 pub next_cursor: Option<String>,
1580 pub data: Vec<Market>,
1581}
1582
1583#[derive(Debug, Serialize, Deserialize)]
1584pub struct SimplifiedMarketsResponse {
1585 pub limit: usize,
1586 pub count: usize,
1587 pub next_cursor: Option<String>,
1588 pub data: Vec<SimplifiedMarket>,
1589}
1590
1591#[derive(Debug, Serialize, Deserialize)]
1593pub struct SimplifiedMarket {
1594 pub condition_id: String,
1595 pub tokens: [Token; 2],
1596 pub rewards: Rewards,
1597 pub min_incentive_size: Option<String>,
1598 pub max_incentive_spread: Option<String>,
1599 pub active: bool,
1600 pub closed: bool,
1601}
1602
1603#[derive(Debug, Clone, Serialize, Deserialize)]
1605pub struct Rewards {
1606 pub rates: Option<serde_json::Value>,
1607 pub min_size: Decimal,
1609 pub max_spread: Decimal,
1610 #[serde(default)]
1611 pub event_start_date: Option<String>,
1612 #[serde(default)]
1613 pub event_end_date: Option<String>,
1614 #[serde(skip_serializing_if = "Option::is_none", default)]
1615 pub in_game_multiplier: Option<Decimal>,
1616 #[serde(skip_serializing_if = "Option::is_none", default)]
1617 pub reward_epoch: Option<Decimal>,
1618}
1619
1620#[derive(Debug, Clone, Serialize, Deserialize)]
1626pub struct FeeRateResponse {
1627 #[serde(alias = "fee_rate_bps")]
1628 pub base_fee: u32,
1629}
1630
1631#[derive(Debug, Clone, Serialize)]
1633#[serde(rename_all = "camelCase")]
1634pub struct RfqCreateRequest {
1635 pub asset_in: String,
1636 pub asset_out: String,
1637 pub amount_in: String,
1638 pub amount_out: String,
1639 pub user_type: u8,
1640}
1641
1642#[derive(Debug, Clone, Serialize, Deserialize)]
1643#[serde(rename_all = "camelCase")]
1644pub struct RfqCreateRequestResponse {
1645 pub request_id: String,
1646 pub expiry: u64,
1647}
1648
1649#[derive(Debug, Clone, Serialize)]
1651#[serde(rename_all = "camelCase")]
1652pub struct RfqCancelRequest {
1653 pub request_id: String,
1654}
1655
1656#[derive(Debug, Clone, Default)]
1658pub struct RfqRequestsParams {
1659 pub offset: Option<String>,
1660 pub limit: Option<u32>,
1661 pub state: Option<String>,
1662 pub request_ids: Vec<String>,
1663 pub markets: Vec<String>,
1664 pub size_min: Option<Decimal>,
1665 pub size_max: Option<Decimal>,
1666 pub size_usdc_min: Option<Decimal>,
1667 pub size_usdc_max: Option<Decimal>,
1668 pub price_min: Option<Decimal>,
1669 pub price_max: Option<Decimal>,
1670 pub sort_by: Option<String>,
1671 pub sort_dir: Option<String>,
1672}
1673
1674impl RfqRequestsParams {
1675 pub fn to_query_params(&self) -> Vec<(String, String)> {
1676 let mut params = Vec::new();
1677
1678 if let Some(x) = &self.offset {
1679 params.push(("offset".to_string(), x.clone()));
1680 }
1681 if let Some(x) = self.limit {
1682 params.push(("limit".to_string(), x.to_string()));
1683 }
1684 if let Some(x) = &self.state {
1685 params.push(("state".to_string(), x.clone()));
1686 }
1687 for x in &self.request_ids {
1688 params.push(("requestIds[]".to_string(), x.clone()));
1689 }
1690 for x in &self.markets {
1691 params.push(("markets[]".to_string(), x.clone()));
1692 }
1693
1694 if let Some(x) = self.size_min {
1695 params.push(("sizeMin".to_string(), x.to_string()));
1696 }
1697 if let Some(x) = self.size_max {
1698 params.push(("sizeMax".to_string(), x.to_string()));
1699 }
1700 if let Some(x) = self.size_usdc_min {
1701 params.push(("sizeUsdcMin".to_string(), x.to_string()));
1702 }
1703 if let Some(x) = self.size_usdc_max {
1704 params.push(("sizeUsdcMax".to_string(), x.to_string()));
1705 }
1706 if let Some(x) = self.price_min {
1707 params.push(("priceMin".to_string(), x.to_string()));
1708 }
1709 if let Some(x) = self.price_max {
1710 params.push(("priceMax".to_string(), x.to_string()));
1711 }
1712
1713 if let Some(x) = &self.sort_by {
1714 params.push(("sortBy".to_string(), x.clone()));
1715 }
1716 if let Some(x) = &self.sort_dir {
1717 params.push(("sortDir".to_string(), x.clone()));
1718 }
1719
1720 params
1721 }
1722}
1723
1724#[derive(Debug, Clone, Serialize, Deserialize)]
1726#[serde(rename_all = "camelCase")]
1727pub struct RfqRequestData {
1728 pub request_id: String,
1729 pub user_address: String,
1730 pub proxy_address: String,
1731 pub condition: String,
1732 pub token: String,
1733 pub complement: String,
1734 pub side: Side,
1735 #[serde(deserialize_with = "crate::decode::deserializers::decimal_from_string")]
1736 pub size_in: Decimal,
1737 #[serde(deserialize_with = "crate::decode::deserializers::decimal_from_string")]
1738 pub size_out: Decimal,
1739 #[serde(deserialize_with = "crate::decode::deserializers::decimal_from_string")]
1740 pub price: Decimal,
1741 pub state: String,
1742 pub expiry: u64,
1743}
1744
1745#[derive(Debug, Clone, Serialize)]
1747#[serde(rename_all = "camelCase")]
1748pub struct RfqCreateQuote {
1749 pub request_id: String,
1750 pub asset_in: String,
1751 pub asset_out: String,
1752 pub amount_in: String,
1753 pub amount_out: String,
1754 pub user_type: u8,
1755}
1756
1757#[derive(Debug, Clone, Serialize, Deserialize)]
1758#[serde(rename_all = "camelCase")]
1759pub struct RfqCreateQuoteResponse {
1760 pub quote_id: String,
1761}
1762
1763#[derive(Debug, Clone, Serialize)]
1765#[serde(rename_all = "camelCase")]
1766pub struct RfqCancelQuote {
1767 pub quote_id: String,
1768}
1769
1770#[derive(Debug, Clone, Default)]
1772pub struct RfqQuotesParams {
1773 pub offset: Option<String>,
1774 pub limit: Option<u32>,
1775 pub state: Option<String>,
1776 pub quote_ids: Vec<String>,
1777 pub request_ids: Vec<String>,
1778 pub markets: Vec<String>,
1779 pub size_min: Option<Decimal>,
1780 pub size_max: Option<Decimal>,
1781 pub size_usdc_min: Option<Decimal>,
1782 pub size_usdc_max: Option<Decimal>,
1783 pub price_min: Option<Decimal>,
1784 pub price_max: Option<Decimal>,
1785 pub sort_by: Option<String>,
1786 pub sort_dir: Option<String>,
1787}
1788
1789impl RfqQuotesParams {
1790 pub fn to_query_params(&self) -> Vec<(String, String)> {
1791 let mut params = Vec::new();
1792
1793 if let Some(x) = &self.offset {
1794 params.push(("offset".to_string(), x.clone()));
1795 }
1796 if let Some(x) = self.limit {
1797 params.push(("limit".to_string(), x.to_string()));
1798 }
1799 if let Some(x) = &self.state {
1800 params.push(("state".to_string(), x.clone()));
1801 }
1802 for x in &self.quote_ids {
1803 params.push(("quoteIds[]".to_string(), x.clone()));
1804 }
1805 for x in &self.request_ids {
1806 params.push(("requestIds[]".to_string(), x.clone()));
1807 }
1808 for x in &self.markets {
1809 params.push(("markets[]".to_string(), x.clone()));
1810 }
1811
1812 if let Some(x) = self.size_min {
1813 params.push(("sizeMin".to_string(), x.to_string()));
1814 }
1815 if let Some(x) = self.size_max {
1816 params.push(("sizeMax".to_string(), x.to_string()));
1817 }
1818 if let Some(x) = self.size_usdc_min {
1819 params.push(("sizeUsdcMin".to_string(), x.to_string()));
1820 }
1821 if let Some(x) = self.size_usdc_max {
1822 params.push(("sizeUsdcMax".to_string(), x.to_string()));
1823 }
1824 if let Some(x) = self.price_min {
1825 params.push(("priceMin".to_string(), x.to_string()));
1826 }
1827 if let Some(x) = self.price_max {
1828 params.push(("priceMax".to_string(), x.to_string()));
1829 }
1830
1831 if let Some(x) = &self.sort_by {
1832 params.push(("sortBy".to_string(), x.clone()));
1833 }
1834 if let Some(x) = &self.sort_dir {
1835 params.push(("sortDir".to_string(), x.clone()));
1836 }
1837
1838 params
1839 }
1840}
1841
1842#[derive(Debug, Clone, Serialize, Deserialize)]
1844#[serde(rename_all = "camelCase")]
1845pub struct RfqQuoteData {
1846 pub quote_id: String,
1847 pub request_id: String,
1848 pub user_address: String,
1849 pub proxy_address: String,
1850 pub condition: String,
1851 pub token: String,
1852 pub complement: String,
1853 pub side: Side,
1854 #[serde(deserialize_with = "crate::decode::deserializers::decimal_from_string")]
1855 pub size_in: Decimal,
1856 #[serde(deserialize_with = "crate::decode::deserializers::decimal_from_string")]
1857 pub size_out: Decimal,
1858 #[serde(deserialize_with = "crate::decode::deserializers::decimal_from_string")]
1859 pub price: Decimal,
1860 pub match_type: String,
1861 pub state: String,
1862}
1863
1864#[derive(Debug, Clone, Serialize, Deserialize)]
1866pub struct RfqListResponse<T> {
1867 pub data: Vec<T>,
1868 pub next_cursor: Option<String>,
1869 pub limit: u32,
1870 pub count: u32,
1871}
1872
1873#[derive(Debug, Clone, Serialize)]
1875#[serde(rename_all = "camelCase")]
1876pub struct RfqOrderExecutionRequest {
1877 pub request_id: String,
1878 pub quote_id: String,
1879 pub maker: String,
1880 pub signer: String,
1881 pub taker: String,
1882 pub expiration: u64,
1883 pub nonce: String,
1884 pub fee_rate_bps: String,
1885 pub side: String,
1886 pub token_id: String,
1887 pub maker_amount: String,
1888 pub taker_amount: String,
1889 pub signature_type: u8,
1890 pub signature: String,
1891 pub salt: u64,
1892 pub owner: String,
1893}
1894
1895#[derive(Debug, Clone, Serialize, Deserialize)]
1896#[serde(rename_all = "camelCase")]
1897pub struct RfqApproveOrderResponse {
1898 pub trade_ids: Vec<String>,
1899}
1900
1901pub type ClientResult<T> = anyhow::Result<T>;
1903
1904pub type Result<T> = std::result::Result<T, crate::errors::PolyfillError>;
1906
1907pub type ApiCreds = ApiCredentials;
1909
1910#[cfg(test)]
1911mod tests {
1912 use super::*;
1913 use std::str::FromStr;
1914
1915 #[test]
1916 fn decimal_to_price_exact_accepts_representable_prices() {
1917 assert_eq!(
1918 decimal_to_price_exact(Decimal::from_str("0.6543").unwrap()).unwrap(),
1919 6543
1920 );
1921 assert_eq!(
1922 decimal_to_price_exact(Decimal::from_str("1.0000").unwrap()).unwrap(),
1923 10_000
1924 );
1925 assert_eq!(
1926 decimal_to_price(Decimal::from_str("0.0001").unwrap()).unwrap(),
1927 MIN_PRICE_TICKS
1928 );
1929 }
1930
1931 #[test]
1932 fn decimal_to_price_exact_rejects_fractional_or_clamped_prices() {
1933 assert!(decimal_to_price_exact(Decimal::from_str("0.00005").unwrap()).is_err());
1934 assert!(decimal_to_price_exact(Decimal::from_str("0.00009").unwrap()).is_err());
1935 assert!(decimal_to_price_exact(Decimal::ZERO).is_err());
1936 assert!(decimal_to_price_exact(Decimal::from_str("-0.01").unwrap()).is_err());
1937 }
1938
1939 #[test]
1940 fn decimal_to_price_lossy_preserves_rounding_and_clamping_behavior() {
1941 assert_eq!(
1942 decimal_to_price_lossy(Decimal::from_str("0.65434").unwrap()).unwrap(),
1943 6543
1944 );
1945 assert_eq!(
1946 decimal_to_price_lossy(Decimal::from_str("0.65435").unwrap()).unwrap(),
1947 6544
1948 );
1949 assert_eq!(
1950 decimal_to_price_lossy(Decimal::from_str("0.00005").unwrap()).unwrap(),
1951 MIN_PRICE_TICKS
1952 );
1953 }
1954
1955 #[test]
1956 fn price_tick_alignment_uses_exact_conversion() {
1957 assert!(is_price_tick_aligned(
1958 Decimal::from_str("0.5100").unwrap(),
1959 Decimal::from_str("0.0100").unwrap()
1960 ));
1961 assert!(!is_price_tick_aligned(
1962 Decimal::from_str("0.5150").unwrap(),
1963 Decimal::from_str("0.0100").unwrap()
1964 ));
1965 assert!(!is_price_tick_aligned(
1966 Decimal::from_str("0.51005").unwrap(),
1967 Decimal::from_str("0.0100").unwrap()
1968 ));
1969 assert!(!is_price_tick_aligned(
1970 Decimal::from_str("0.5100").unwrap(),
1971 Decimal::from_str("0.00005").unwrap()
1972 ));
1973 }
1974
1975 #[test]
1976 fn order_book_snapshot_clocks_default_for_legacy_payloads() {
1977 let json = r#"{
1978 "token_id":"test_token",
1979 "timestamp":"2026-06-23T00:00:00Z",
1980 "bids":[],
1981 "asks":[],
1982 "sequence":42
1983 }"#;
1984
1985 let book: OrderBook = serde_json::from_str(json).unwrap();
1986 assert_eq!(book.sequence, 42);
1987 assert_eq!(book.last_delta_sequence, 42);
1988 assert_eq!(book.last_snapshot_timestamp_ms, 0);
1989 }
1990}