polyfill_rs/
client.rs

1//! High-performance Rust client for Polymarket
2//!
3//! This module provides a production-ready client for interacting with
4//! Polymarket, optimized for high-frequency trading environments.
5
6use crate::auth::{create_l1_headers, create_l2_headers};
7use crate::errors::{PolyfillError, Result};
8use crate::http_config::{
9    create_colocated_client, create_internet_client, create_optimized_client, prewarm_connections,
10};
11use crate::types::{OrderOptions, PostOrder, SignedOrderRequest};
12use alloy_primitives::U256;
13use alloy_signer_local::PrivateKeySigner;
14use reqwest::header::HeaderName;
15use reqwest::Client;
16use reqwest::{Method, RequestBuilder};
17use rust_decimal::prelude::FromPrimitive;
18use rust_decimal::Decimal;
19use serde_json::Value;
20use std::str::FromStr;
21
22// Re-export types for compatibility
23pub use crate::types::{ApiCredentials as ApiCreds, OrderType, Side};
24
25// Compatibility types
26#[derive(Debug)]
27pub struct OrderArgs {
28    pub token_id: String,
29    pub price: Decimal,
30    pub size: Decimal,
31    pub side: Side,
32}
33
34impl OrderArgs {
35    pub fn new(token_id: &str, price: Decimal, size: Decimal, side: Side) -> Self {
36        Self {
37            token_id: token_id.to_string(),
38            price,
39            size,
40            side,
41        }
42    }
43}
44
45impl Default for OrderArgs {
46    fn default() -> Self {
47        Self {
48            token_id: "".to_string(),
49            price: Decimal::ZERO,
50            size: Decimal::ZERO,
51            side: Side::BUY,
52        }
53    }
54}
55
56/// Main client for interacting with Polymarket API
57pub struct ClobClient {
58    http_client: Client,
59    base_url: String,
60    chain_id: u64,
61    signer: Option<PrivateKeySigner>,
62    api_creds: Option<ApiCreds>,
63    order_builder: Option<crate::orders::OrderBuilder>,
64}
65
66impl ClobClient {
67    /// Create a new client with optimized HTTP settings
68    pub fn new(host: &str) -> Self {
69        Self {
70            http_client: create_optimized_client().unwrap_or_else(|_| Client::new()),
71            base_url: host.to_string(),
72            chain_id: 137, // Default to Polygon
73            signer: None,
74            api_creds: None,
75            order_builder: None,
76        }
77    }
78
79    /// Create a client optimized for co-located environments
80    pub fn new_colocated(host: &str) -> Self {
81        Self {
82            http_client: create_colocated_client().unwrap_or_else(|_| Client::new()),
83            base_url: host.to_string(),
84            chain_id: 137,
85            signer: None,
86            api_creds: None,
87            order_builder: None,
88        }
89    }
90
91    /// Create a client optimized for internet connections
92    pub fn new_internet(host: &str) -> Self {
93        Self {
94            http_client: create_internet_client().unwrap_or_else(|_| Client::new()),
95            base_url: host.to_string(),
96            chain_id: 137,
97            signer: None,
98            api_creds: None,
99            order_builder: None,
100        }
101    }
102
103    /// Create a client with L1 headers (for authentication)
104    pub fn with_l1_headers(host: &str, private_key: &str, chain_id: u64) -> Self {
105        let signer = private_key
106            .parse::<PrivateKeySigner>()
107            .expect("Invalid private key");
108
109        let order_builder = crate::orders::OrderBuilder::new(signer.clone(), None, None);
110
111        Self {
112            http_client: create_optimized_client().unwrap_or_else(|_| Client::new()),
113            base_url: host.to_string(),
114            chain_id,
115            signer: Some(signer),
116            api_creds: None,
117            order_builder: Some(order_builder),
118        }
119    }
120
121    /// Create a client with L2 headers (for API key authentication)
122    pub fn with_l2_headers(
123        host: &str,
124        private_key: &str,
125        chain_id: u64,
126        api_creds: ApiCreds,
127    ) -> Self {
128        let signer = private_key
129            .parse::<PrivateKeySigner>()
130            .expect("Invalid private key");
131
132        let order_builder = crate::orders::OrderBuilder::new(signer.clone(), None, None);
133
134        Self {
135            http_client: create_optimized_client().unwrap_or_else(|_| Client::new()),
136            base_url: host.to_string(),
137            chain_id,
138            signer: Some(signer),
139            api_creds: Some(api_creds),
140            order_builder: Some(order_builder),
141        }
142    }
143
144    /// Set API credentials
145    pub fn set_api_creds(&mut self, api_creds: ApiCreds) {
146        self.api_creds = Some(api_creds);
147    }
148
149    /// Pre-warm connections to reduce first-request latency
150    pub async fn prewarm_connections(&self) -> Result<()> {
151        prewarm_connections(&self.http_client, &self.base_url)
152            .await
153            .map_err(|e| {
154                PolyfillError::network(format!("Failed to prewarm connections: {}", e), e)
155            })?;
156        Ok(())
157    }
158
159    /// Get the wallet address
160    pub fn get_address(&self) -> Option<String> {
161        use alloy_primitives::hex;
162        self.signer
163            .as_ref()
164            .map(|s| hex::encode_prefixed(s.address().as_slice()))
165    }
166
167    /// Get the collateral token address for the current chain
168    pub fn get_collateral_address(&self) -> Option<String> {
169        let config = crate::orders::get_contract_config(self.chain_id, false)?;
170        Some(config.collateral)
171    }
172
173    /// Get the conditional tokens contract address for the current chain
174    pub fn get_conditional_address(&self) -> Option<String> {
175        let config = crate::orders::get_contract_config(self.chain_id, false)?;
176        Some(config.conditional_tokens)
177    }
178
179    /// Get the exchange contract address for the current chain
180    pub fn get_exchange_address(&self) -> Option<String> {
181        let config = crate::orders::get_contract_config(self.chain_id, false)?;
182        Some(config.exchange)
183    }
184
185    /// Test basic connectivity
186    pub async fn get_ok(&self) -> bool {
187        match self
188            .http_client
189            .get(format!("{}/ok", self.base_url))
190            .send()
191            .await
192        {
193            Ok(response) => response.status().is_success(),
194            Err(_) => false,
195        }
196    }
197
198    /// Get server time
199    pub async fn get_server_time(&self) -> Result<u64> {
200        let response = self
201            .http_client
202            .get(format!("{}/time", self.base_url))
203            .send()
204            .await?;
205
206        if !response.status().is_success() {
207            return Err(PolyfillError::api(
208                response.status().as_u16(),
209                "Failed to get server time",
210            ));
211        }
212
213        let time_text = response.text().await?;
214        let timestamp = time_text
215            .trim()
216            .parse::<u64>()
217            .map_err(|e| PolyfillError::parse(format!("Invalid timestamp format: {}", e), None))?;
218
219        Ok(timestamp)
220    }
221
222    /// Get order book for a token
223    pub async fn get_order_book(&self, token_id: &str) -> Result<OrderBookSummary> {
224        let response = self
225            .http_client
226            .get(format!("{}/book", self.base_url))
227            .query(&[("token_id", token_id)])
228            .send()
229            .await?;
230
231        if !response.status().is_success() {
232            return Err(PolyfillError::api(
233                response.status().as_u16(),
234                "Failed to get order book",
235            ));
236        }
237
238        let order_book: OrderBookSummary = response.json().await?;
239        Ok(order_book)
240    }
241
242    /// Get midpoint for a token
243    pub async fn get_midpoint(&self, token_id: &str) -> Result<MidpointResponse> {
244        let response = self
245            .http_client
246            .get(format!("{}/midpoint", self.base_url))
247            .query(&[("token_id", token_id)])
248            .send()
249            .await?;
250
251        if !response.status().is_success() {
252            return Err(PolyfillError::api(
253                response.status().as_u16(),
254                "Failed to get midpoint",
255            ));
256        }
257
258        let midpoint: MidpointResponse = response.json().await?;
259        Ok(midpoint)
260    }
261
262    /// Get spread for a token
263    pub async fn get_spread(&self, token_id: &str) -> Result<SpreadResponse> {
264        let response = self
265            .http_client
266            .get(format!("{}/spread", self.base_url))
267            .query(&[("token_id", token_id)])
268            .send()
269            .await?;
270
271        if !response.status().is_success() {
272            return Err(PolyfillError::api(
273                response.status().as_u16(),
274                "Failed to get spread",
275            ));
276        }
277
278        let spread: SpreadResponse = response.json().await?;
279        Ok(spread)
280    }
281
282    /// Get spreads for multiple tokens (batch)
283    pub async fn get_spreads(
284        &self,
285        token_ids: &[String],
286    ) -> Result<std::collections::HashMap<String, Decimal>> {
287        let request_data: Vec<std::collections::HashMap<&str, String>> = token_ids
288            .iter()
289            .map(|id| {
290                let mut map = std::collections::HashMap::new();
291                map.insert("token_id", id.clone());
292                map
293            })
294            .collect();
295
296        let response = self
297            .http_client
298            .post(format!("{}/spreads", self.base_url))
299            .json(&request_data)
300            .send()
301            .await?;
302
303        if !response.status().is_success() {
304            return Err(PolyfillError::api(
305                response.status().as_u16(),
306                "Failed to get batch spreads",
307            ));
308        }
309
310        response
311            .json::<std::collections::HashMap<String, Decimal>>()
312            .await
313            .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
314    }
315
316    /// Get price for a token and side
317    pub async fn get_price(&self, token_id: &str, side: Side) -> Result<PriceResponse> {
318        let response = self
319            .http_client
320            .get(format!("{}/price", self.base_url))
321            .query(&[("token_id", token_id), ("side", side.as_str())])
322            .send()
323            .await?;
324
325        if !response.status().is_success() {
326            return Err(PolyfillError::api(
327                response.status().as_u16(),
328                "Failed to get price",
329            ));
330        }
331
332        let price: PriceResponse = response.json().await?;
333        Ok(price)
334    }
335
336    /// Get tick size for a token
337    pub async fn get_tick_size(&self, token_id: &str) -> Result<Decimal> {
338        let response = self
339            .http_client
340            .get(format!("{}/tick-size", self.base_url))
341            .query(&[("token_id", token_id)])
342            .send()
343            .await?;
344
345        if !response.status().is_success() {
346            return Err(PolyfillError::api(
347                response.status().as_u16(),
348                "Failed to get tick size",
349            ));
350        }
351
352        let tick_size_response: Value = response.json().await?;
353        let tick_size = tick_size_response["minimum_tick_size"]
354            .as_str()
355            .and_then(|s| Decimal::from_str(s).ok())
356            .or_else(|| {
357                tick_size_response["minimum_tick_size"]
358                    .as_f64()
359                    .map(|f| Decimal::from_f64(f).unwrap_or(Decimal::ZERO))
360            })
361            .ok_or_else(|| PolyfillError::parse("Invalid tick size format", None))?;
362
363        Ok(tick_size)
364    }
365
366    /// Create a new API key
367    pub async fn create_api_key(&self, nonce: Option<U256>) -> Result<ApiCreds> {
368        let signer = self
369            .signer
370            .as_ref()
371            .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
372
373        let headers = create_l1_headers(signer, nonce)?;
374        let req =
375            self.create_request_with_headers(Method::POST, "/auth/api-key", headers.into_iter());
376
377        let response = req.send().await?;
378        if !response.status().is_success() {
379            return Err(PolyfillError::api(
380                response.status().as_u16(),
381                "Failed to create API key",
382            ));
383        }
384
385        Ok(response.json::<ApiCreds>().await?)
386    }
387
388    /// Derive an existing API key
389    pub async fn derive_api_key(&self, nonce: Option<U256>) -> Result<ApiCreds> {
390        let signer = self
391            .signer
392            .as_ref()
393            .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
394
395        let headers = create_l1_headers(signer, nonce)?;
396        let req = self.create_request_with_headers(
397            Method::GET,
398            "/auth/derive-api-key",
399            headers.into_iter(),
400        );
401
402        let response = req.send().await?;
403        if !response.status().is_success() {
404            return Err(PolyfillError::api(
405                response.status().as_u16(),
406                "Failed to derive API key",
407            ));
408        }
409
410        Ok(response.json::<ApiCreds>().await?)
411    }
412
413    /// Create or derive API key (try create first, fallback to derive)
414    pub async fn create_or_derive_api_key(&self, nonce: Option<U256>) -> Result<ApiCreds> {
415        match self.create_api_key(nonce).await {
416            Ok(creds) => Ok(creds),
417            Err(_) => self.derive_api_key(nonce).await,
418        }
419    }
420
421    /// Get all API keys for the authenticated user
422    pub async fn get_api_keys(&self) -> Result<Vec<String>> {
423        let signer = self
424            .signer
425            .as_ref()
426            .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
427        let api_creds = self
428            .api_creds
429            .as_ref()
430            .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
431
432        let method = Method::GET;
433        let endpoint = "/auth/api-keys";
434        let headers =
435            create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
436
437        let response = self
438            .http_client
439            .request(method, format!("{}{}", self.base_url, endpoint))
440            .headers(
441                headers
442                    .into_iter()
443                    .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
444                    .collect(),
445            )
446            .send()
447            .await
448            .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
449
450        let api_keys_response: crate::types::ApiKeysResponse = response
451            .json()
452            .await
453            .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))?;
454
455        Ok(api_keys_response.api_keys)
456    }
457
458    /// Delete the current API key
459    pub async fn delete_api_key(&self) -> Result<String> {
460        let signer = self
461            .signer
462            .as_ref()
463            .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
464        let api_creds = self
465            .api_creds
466            .as_ref()
467            .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
468
469        let method = Method::DELETE;
470        let endpoint = "/auth/api-key";
471        let headers =
472            create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
473
474        let response = self
475            .http_client
476            .request(method, format!("{}{}", self.base_url, endpoint))
477            .headers(
478                headers
479                    .into_iter()
480                    .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
481                    .collect(),
482            )
483            .send()
484            .await
485            .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
486
487        response
488            .text()
489            .await
490            .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
491    }
492
493    /// Helper to create request with headers
494    fn create_request_with_headers(
495        &self,
496        method: Method,
497        endpoint: &str,
498        headers: impl Iterator<Item = (&'static str, String)>,
499    ) -> RequestBuilder {
500        let req = self
501            .http_client
502            .request(method, format!("{}{}", &self.base_url, endpoint));
503        headers.fold(req, |r, (k, v)| r.header(HeaderName::from_static(k), v))
504    }
505
506    /// Get neg risk for a token
507    pub async fn get_neg_risk(&self, token_id: &str) -> Result<bool> {
508        let response = self
509            .http_client
510            .get(format!("{}/neg-risk", self.base_url))
511            .query(&[("token_id", token_id)])
512            .send()
513            .await?;
514
515        if !response.status().is_success() {
516            return Err(PolyfillError::api(
517                response.status().as_u16(),
518                "Failed to get neg risk",
519            ));
520        }
521
522        let neg_risk_response: Value = response.json().await?;
523        let neg_risk = neg_risk_response["neg_risk"]
524            .as_bool()
525            .ok_or_else(|| PolyfillError::parse("Invalid neg risk format", None))?;
526
527        Ok(neg_risk)
528    }
529
530    /// Resolve tick size for an order
531    async fn resolve_tick_size(
532        &self,
533        token_id: &str,
534        tick_size: Option<Decimal>,
535    ) -> Result<Decimal> {
536        let min_tick_size = self.get_tick_size(token_id).await?;
537
538        match tick_size {
539            None => Ok(min_tick_size),
540            Some(t) => {
541                if t < min_tick_size {
542                    Err(PolyfillError::validation(format!(
543                        "Tick size {} is smaller than min_tick_size {} for token_id: {}",
544                        t, min_tick_size, token_id
545                    )))
546                } else {
547                    Ok(t)
548                }
549            },
550        }
551    }
552
553    /// Get filled order options
554    async fn get_filled_order_options(
555        &self,
556        token_id: &str,
557        options: Option<&OrderOptions>,
558    ) -> Result<OrderOptions> {
559        let (tick_size, neg_risk, fee_rate_bps) = match options {
560            Some(o) => (o.tick_size, o.neg_risk, o.fee_rate_bps),
561            None => (None, None, None),
562        };
563
564        let tick_size = self.resolve_tick_size(token_id, tick_size).await?;
565        let neg_risk = match neg_risk {
566            Some(nr) => nr,
567            None => self.get_neg_risk(token_id).await?,
568        };
569
570        Ok(OrderOptions {
571            tick_size: Some(tick_size),
572            neg_risk: Some(neg_risk),
573            fee_rate_bps,
574        })
575    }
576
577    /// Check if price is in valid range
578    fn is_price_in_range(&self, price: Decimal, tick_size: Decimal) -> bool {
579        let min_price = tick_size;
580        let max_price = Decimal::ONE - tick_size;
581        price >= min_price && price <= max_price
582    }
583
584    /// Create an order
585    pub async fn create_order(
586        &self,
587        order_args: &OrderArgs,
588        expiration: Option<u64>,
589        extras: Option<crate::types::ExtraOrderArgs>,
590        options: Option<&OrderOptions>,
591    ) -> Result<SignedOrderRequest> {
592        let order_builder = self
593            .order_builder
594            .as_ref()
595            .ok_or_else(|| PolyfillError::auth("Order builder not initialized"))?;
596
597        let create_order_options = self
598            .get_filled_order_options(&order_args.token_id, options)
599            .await?;
600
601        let expiration = expiration.unwrap_or(0);
602        let extras = extras.unwrap_or_default();
603
604        if !self.is_price_in_range(
605            order_args.price,
606            create_order_options.tick_size.expect("Should be filled"),
607        ) {
608            return Err(PolyfillError::validation(
609                "Price is not in range of tick_size",
610            ));
611        }
612
613        order_builder.create_order(
614            self.chain_id,
615            order_args,
616            expiration,
617            &extras,
618            &create_order_options,
619        )
620    }
621
622    /// Calculate market price from order book
623    async fn calculate_market_price(
624        &self,
625        token_id: &str,
626        side: Side,
627        amount: Decimal,
628    ) -> Result<Decimal> {
629        let book = self.get_order_book(token_id).await?;
630        let order_builder = self
631            .order_builder
632            .as_ref()
633            .ok_or_else(|| PolyfillError::auth("Order builder not initialized"))?;
634
635        // Convert OrderSummary to BookLevel
636        let levels: Vec<crate::types::BookLevel> = match side {
637            Side::BUY => book
638                .asks
639                .into_iter()
640                .map(|s| crate::types::BookLevel {
641                    price: s.price,
642                    size: s.size,
643                })
644                .collect(),
645            Side::SELL => book
646                .bids
647                .into_iter()
648                .map(|s| crate::types::BookLevel {
649                    price: s.price,
650                    size: s.size,
651                })
652                .collect(),
653        };
654
655        order_builder.calculate_market_price(&levels, amount)
656    }
657
658    /// Create a market order
659    pub async fn create_market_order(
660        &self,
661        order_args: &crate::types::MarketOrderArgs,
662        extras: Option<crate::types::ExtraOrderArgs>,
663        options: Option<&OrderOptions>,
664    ) -> Result<SignedOrderRequest> {
665        let order_builder = self
666            .order_builder
667            .as_ref()
668            .ok_or_else(|| PolyfillError::auth("Order builder not initialized"))?;
669
670        let create_order_options = self
671            .get_filled_order_options(&order_args.token_id, options)
672            .await?;
673
674        let extras = extras.unwrap_or_default();
675        let price = self
676            .calculate_market_price(&order_args.token_id, Side::BUY, order_args.amount)
677            .await?;
678
679        if !self.is_price_in_range(
680            price,
681            create_order_options.tick_size.expect("Should be filled"),
682        ) {
683            return Err(PolyfillError::validation(
684                "Price is not in range of tick_size",
685            ));
686        }
687
688        order_builder.create_market_order(
689            self.chain_id,
690            order_args,
691            price,
692            &extras,
693            &create_order_options,
694        )
695    }
696
697    /// Post an order to the exchange
698    pub async fn post_order(
699        &self,
700        order: SignedOrderRequest,
701        order_type: OrderType,
702    ) -> Result<Value> {
703        let signer = self
704            .signer
705            .as_ref()
706            .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
707        let api_creds = self
708            .api_creds
709            .as_ref()
710            .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
711
712        let body = PostOrder::new(order, api_creds.api_key.clone(), order_type);
713
714        let headers = create_l2_headers(signer, api_creds, "POST", "/order", Some(&body))?;
715        let req = self.create_request_with_headers(Method::POST, "/order", headers.into_iter());
716
717        let response = req.json(&body).send().await?;
718        if !response.status().is_success() {
719            return Err(PolyfillError::api(
720                response.status().as_u16(),
721                "Failed to post order",
722            ));
723        }
724
725        Ok(response.json::<Value>().await?)
726    }
727
728    /// Create and post an order in one call
729    pub async fn create_and_post_order(&self, order_args: &OrderArgs) -> Result<Value> {
730        let order = self.create_order(order_args, None, None, None).await?;
731        self.post_order(order, OrderType::GTC).await
732    }
733
734    /// Cancel an order
735    pub async fn cancel(&self, order_id: &str) -> Result<Value> {
736        let signer = self
737            .signer
738            .as_ref()
739            .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
740        let api_creds = self
741            .api_creds
742            .as_ref()
743            .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
744
745        let body = std::collections::HashMap::from([("orderID", order_id)]);
746
747        let headers = create_l2_headers(signer, api_creds, "DELETE", "/order", Some(&body))?;
748        let req = self.create_request_with_headers(Method::DELETE, "/order", headers.into_iter());
749
750        let response = req.json(&body).send().await?;
751        if !response.status().is_success() {
752            return Err(PolyfillError::api(
753                response.status().as_u16(),
754                "Failed to cancel order",
755            ));
756        }
757
758        Ok(response.json::<Value>().await?)
759    }
760
761    /// Cancel multiple orders
762    pub async fn cancel_orders(&self, order_ids: &[String]) -> Result<Value> {
763        let signer = self
764            .signer
765            .as_ref()
766            .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
767        let api_creds = self
768            .api_creds
769            .as_ref()
770            .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
771
772        let headers = create_l2_headers(signer, api_creds, "DELETE", "/orders", Some(order_ids))?;
773        let req = self.create_request_with_headers(Method::DELETE, "/orders", headers.into_iter());
774
775        let response = req.json(order_ids).send().await?;
776        if !response.status().is_success() {
777            return Err(PolyfillError::api(
778                response.status().as_u16(),
779                "Failed to cancel orders",
780            ));
781        }
782
783        Ok(response.json::<Value>().await?)
784    }
785
786    /// Cancel all orders
787    pub async fn cancel_all(&self) -> Result<Value> {
788        let signer = self
789            .signer
790            .as_ref()
791            .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
792        let api_creds = self
793            .api_creds
794            .as_ref()
795            .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
796
797        let headers = create_l2_headers::<Value>(signer, api_creds, "DELETE", "/cancel-all", None)?;
798        let req =
799            self.create_request_with_headers(Method::DELETE, "/cancel-all", headers.into_iter());
800
801        let response = req.send().await?;
802        if !response.status().is_success() {
803            return Err(PolyfillError::api(
804                response.status().as_u16(),
805                "Failed to cancel all orders",
806            ));
807        }
808
809        Ok(response.json::<Value>().await?)
810    }
811
812    /// Get open orders with optional filtering
813    ///
814    /// This retrieves all open orders for the authenticated user. You can filter by:
815    /// - Order ID (exact match)
816    /// - Asset/Token ID (all orders for a specific token)
817    /// - Market ID (all orders for a specific market)
818    ///
819    /// The response includes order status, fill information, and timestamps.
820    pub async fn get_orders(
821        &self,
822        params: Option<&crate::types::OpenOrderParams>,
823        next_cursor: Option<&str>,
824    ) -> Result<Vec<crate::types::OpenOrder>> {
825        let signer = self
826            .signer
827            .as_ref()
828            .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
829        let api_creds = self
830            .api_creds
831            .as_ref()
832            .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
833
834        let method = Method::GET;
835        let endpoint = "/data/orders";
836        let headers =
837            create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
838
839        let query_params = match params {
840            None => Vec::new(),
841            Some(p) => p.to_query_params(),
842        };
843
844        let mut next_cursor = next_cursor.unwrap_or("MA==").to_string(); // INITIAL_CURSOR
845        let mut output = Vec::new();
846
847        while next_cursor != "LTE=" {
848            // END_CURSOR
849            let req = self
850                .http_client
851                .request(method.clone(), format!("{}{}", self.base_url, endpoint))
852                .query(&query_params)
853                .query(&[("next_cursor", &next_cursor)]);
854
855            let r = headers
856                .clone()
857                .into_iter()
858                .fold(req, |r, (k, v)| r.header(HeaderName::from_static(k), v));
859
860            let resp = r
861                .send()
862                .await
863                .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?
864                .json::<Value>()
865                .await
866                .map_err(|e| {
867                    PolyfillError::parse(format!("Failed to parse response: {}", e), None)
868                })?;
869
870            let new_cursor = resp["next_cursor"]
871                .as_str()
872                .ok_or_else(|| {
873                    PolyfillError::parse("Failed to parse next cursor".to_string(), None)
874                })?
875                .to_owned();
876
877            next_cursor = new_cursor;
878
879            let results = resp["data"].clone();
880            let orders =
881                serde_json::from_value::<Vec<crate::types::OpenOrder>>(results).map_err(|e| {
882                    PolyfillError::parse(
883                        format!("Failed to parse data from order response: {}", e),
884                        None,
885                    )
886                })?;
887            output.extend(orders);
888        }
889
890        Ok(output)
891    }
892
893    /// Get trade history with optional filtering
894    ///
895    /// This retrieves historical trades for the authenticated user. You can filter by:
896    /// - Trade ID (exact match)
897    /// - Maker address (trades where you were the maker)
898    /// - Market ID (trades in a specific market)
899    /// - Asset/Token ID (trades for a specific token)
900    /// - Time range (before/after timestamps)
901    ///
902    /// Trades are returned in reverse chronological order (newest first).
903    pub async fn get_trades(
904        &self,
905        trade_params: Option<&crate::types::TradeParams>,
906        next_cursor: Option<&str>,
907    ) -> Result<Vec<Value>> {
908        let signer = self
909            .signer
910            .as_ref()
911            .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
912        let api_creds = self
913            .api_creds
914            .as_ref()
915            .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
916
917        let method = Method::GET;
918        let endpoint = "/data/trades";
919        let headers =
920            create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
921
922        let query_params = match trade_params {
923            None => Vec::new(),
924            Some(p) => p.to_query_params(),
925        };
926
927        let mut next_cursor = next_cursor.unwrap_or("MA==").to_string(); // INITIAL_CURSOR
928        let mut output = Vec::new();
929
930        while next_cursor != "LTE=" {
931            // END_CURSOR
932            let req = self
933                .http_client
934                .request(method.clone(), format!("{}{}", self.base_url, endpoint))
935                .query(&query_params)
936                .query(&[("next_cursor", &next_cursor)]);
937
938            let r = headers
939                .clone()
940                .into_iter()
941                .fold(req, |r, (k, v)| r.header(HeaderName::from_static(k), v));
942
943            let resp = r
944                .send()
945                .await
946                .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?
947                .json::<Value>()
948                .await
949                .map_err(|e| {
950                    PolyfillError::parse(format!("Failed to parse response: {}", e), None)
951                })?;
952
953            let new_cursor = resp["next_cursor"]
954                .as_str()
955                .ok_or_else(|| {
956                    PolyfillError::parse("Failed to parse next cursor".to_string(), None)
957                })?
958                .to_owned();
959
960            next_cursor = new_cursor;
961
962            let results = resp["data"].clone();
963            output.push(results);
964        }
965
966        Ok(output)
967    }
968
969    /// Get balance and allowance information for all assets
970    ///
971    /// This returns the current balance and allowance for each asset in your account.
972    /// Balance is how much you own, allowance is how much the exchange can spend on your behalf.
973    ///
974    /// You need both balance and allowance to place orders - the exchange needs permission
975    /// to move your tokens when orders are filled.
976    pub async fn get_balance_allowance(
977        &self,
978        params: Option<crate::types::BalanceAllowanceParams>,
979    ) -> Result<Value> {
980        let signer = self
981            .signer
982            .as_ref()
983            .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
984        let api_creds = self
985            .api_creds
986            .as_ref()
987            .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
988
989        let mut params = params.unwrap_or_default();
990        if params.signature_type.is_none() {
991            params.set_signature_type(
992                self.order_builder
993                    .as_ref()
994                    .expect("OrderBuilder not set")
995                    .get_sig_type(),
996            );
997        }
998
999        let query_params = params.to_query_params();
1000
1001        let method = Method::GET;
1002        let endpoint = "/balance-allowance";
1003        let headers =
1004            create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1005
1006        let response = self
1007            .http_client
1008            .request(method, format!("{}{}", self.base_url, endpoint))
1009            .headers(
1010                headers
1011                    .into_iter()
1012                    .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1013                    .collect(),
1014            )
1015            .query(&query_params)
1016            .send()
1017            .await
1018            .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1019
1020        response
1021            .json::<Value>()
1022            .await
1023            .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1024    }
1025
1026    /// Set up notifications for order fills and other events
1027    ///
1028    /// This configures push notifications so you get alerted when:
1029    /// - Your orders get filled
1030    /// - Your orders get cancelled
1031    /// - Market conditions change significantly
1032    ///
1033    /// The signature proves you own the account and want to receive notifications.
1034    pub async fn get_notifications(&self) -> Result<Value> {
1035        let signer = self
1036            .signer
1037            .as_ref()
1038            .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
1039        let api_creds = self
1040            .api_creds
1041            .as_ref()
1042            .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
1043
1044        let method = Method::GET;
1045        let endpoint = "/notifications";
1046        let headers =
1047            create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1048
1049        let response = self
1050            .http_client
1051            .request(method, format!("{}{}", self.base_url, endpoint))
1052            .headers(
1053                headers
1054                    .into_iter()
1055                    .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1056                    .collect(),
1057            )
1058            .query(&[(
1059                "signature_type",
1060                &self
1061                    .order_builder
1062                    .as_ref()
1063                    .expect("OrderBuilder not set")
1064                    .get_sig_type()
1065                    .to_string(),
1066            )])
1067            .send()
1068            .await
1069            .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1070
1071        response
1072            .json::<Value>()
1073            .await
1074            .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1075    }
1076
1077    /// Get midpoints for multiple tokens in a single request
1078    ///
1079    /// This is much more efficient than calling get_midpoint() multiple times.
1080    /// Instead of N round trips, you make just 1 request and get all the midpoints back.
1081    ///
1082    /// Midpoints are returned as a HashMap where the key is the token_id and the value
1083    /// is the midpoint price (or None if there's no valid midpoint).
1084    pub async fn get_midpoints(
1085        &self,
1086        token_ids: &[String],
1087    ) -> Result<std::collections::HashMap<String, Decimal>> {
1088        let request_data: Vec<std::collections::HashMap<&str, String>> = token_ids
1089            .iter()
1090            .map(|id| {
1091                let mut map = std::collections::HashMap::new();
1092                map.insert("token_id", id.clone());
1093                map
1094            })
1095            .collect();
1096
1097        let response = self
1098            .http_client
1099            .post(format!("{}/midpoints", self.base_url))
1100            .json(&request_data)
1101            .send()
1102            .await?;
1103
1104        if !response.status().is_success() {
1105            return Err(PolyfillError::api(
1106                response.status().as_u16(),
1107                "Failed to get batch midpoints",
1108            ));
1109        }
1110
1111        let midpoints: std::collections::HashMap<String, Decimal> = response.json().await?;
1112        Ok(midpoints)
1113    }
1114
1115    /// Get bid/ask/mid prices for multiple tokens in a single request
1116    ///
1117    /// This gives you the full price picture for multiple tokens at once.
1118    /// Much more efficient than individual calls, especially when you're tracking
1119    /// a portfolio or comparing multiple markets.
1120    ///
1121    /// Returns bid (best buy price), ask (best sell price), and mid (average) for each token.
1122    pub async fn get_prices(
1123        &self,
1124        book_params: &[crate::types::BookParams],
1125    ) -> Result<std::collections::HashMap<String, std::collections::HashMap<Side, Decimal>>> {
1126        let request_data: Vec<std::collections::HashMap<&str, String>> = book_params
1127            .iter()
1128            .map(|params| {
1129                let mut map = std::collections::HashMap::new();
1130                map.insert("token_id", params.token_id.clone());
1131                map.insert("side", params.side.as_str().to_string());
1132                map
1133            })
1134            .collect();
1135
1136        let response = self
1137            .http_client
1138            .post(format!("{}/prices", self.base_url))
1139            .json(&request_data)
1140            .send()
1141            .await?;
1142
1143        if !response.status().is_success() {
1144            return Err(PolyfillError::api(
1145                response.status().as_u16(),
1146                "Failed to get batch prices",
1147            ));
1148        }
1149
1150        let prices: std::collections::HashMap<String, std::collections::HashMap<Side, Decimal>> =
1151            response.json().await?;
1152        Ok(prices)
1153    }
1154
1155    /// Get order book for multiple tokens (batch) - reference implementation compatible
1156    pub async fn get_order_books(&self, token_ids: &[String]) -> Result<Vec<OrderBookSummary>> {
1157        let request_data: Vec<std::collections::HashMap<&str, String>> = token_ids
1158            .iter()
1159            .map(|id| {
1160                let mut map = std::collections::HashMap::new();
1161                map.insert("token_id", id.clone());
1162                map
1163            })
1164            .collect();
1165
1166        let response = self
1167            .http_client
1168            .post(format!("{}/books", self.base_url))
1169            .json(&request_data)
1170            .send()
1171            .await
1172            .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1173
1174        response
1175            .json::<Vec<OrderBookSummary>>()
1176            .await
1177            .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1178    }
1179
1180    /// Get single order by ID
1181    pub async fn get_order(&self, order_id: &str) -> Result<crate::types::OpenOrder> {
1182        let signer = self
1183            .signer
1184            .as_ref()
1185            .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1186        let api_creds = self
1187            .api_creds
1188            .as_ref()
1189            .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1190
1191        let method = Method::GET;
1192        let endpoint = &format!("/data/order/{}", order_id);
1193        let headers =
1194            create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1195
1196        let response = self
1197            .http_client
1198            .request(method, format!("{}{}", self.base_url, endpoint))
1199            .headers(
1200                headers
1201                    .into_iter()
1202                    .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1203                    .collect(),
1204            )
1205            .send()
1206            .await
1207            .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1208
1209        response
1210            .json::<crate::types::OpenOrder>()
1211            .await
1212            .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1213    }
1214
1215    /// Get last trade price for a token
1216    pub async fn get_last_trade_price(&self, token_id: &str) -> Result<Value> {
1217        let response = self
1218            .http_client
1219            .get(format!("{}/last-trade-price", self.base_url))
1220            .query(&[("token_id", token_id)])
1221            .send()
1222            .await
1223            .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1224
1225        response
1226            .json::<Value>()
1227            .await
1228            .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1229    }
1230
1231    /// Get last trade prices for multiple tokens
1232    pub async fn get_last_trade_prices(&self, token_ids: &[String]) -> Result<Value> {
1233        let request_data: Vec<std::collections::HashMap<&str, String>> = token_ids
1234            .iter()
1235            .map(|id| {
1236                let mut map = std::collections::HashMap::new();
1237                map.insert("token_id", id.clone());
1238                map
1239            })
1240            .collect();
1241
1242        let response = self
1243            .http_client
1244            .post(format!("{}/last-trades-prices", self.base_url))
1245            .json(&request_data)
1246            .send()
1247            .await
1248            .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1249
1250        response
1251            .json::<Value>()
1252            .await
1253            .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1254    }
1255
1256    /// Cancel market orders with optional filters
1257    pub async fn cancel_market_orders(
1258        &self,
1259        market: Option<&str>,
1260        asset_id: Option<&str>,
1261    ) -> Result<Value> {
1262        let signer = self
1263            .signer
1264            .as_ref()
1265            .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1266        let api_creds = self
1267            .api_creds
1268            .as_ref()
1269            .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1270
1271        let method = Method::DELETE;
1272        let endpoint = "/cancel-market-orders";
1273        let body = std::collections::HashMap::from([
1274            ("market", market.unwrap_or("")),
1275            ("asset_id", asset_id.unwrap_or("")),
1276        ]);
1277
1278        let headers = create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(&body))?;
1279
1280        let response = self
1281            .http_client
1282            .request(method, format!("{}{}", self.base_url, endpoint))
1283            .headers(
1284                headers
1285                    .into_iter()
1286                    .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1287                    .collect(),
1288            )
1289            .json(&body)
1290            .send()
1291            .await
1292            .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1293
1294        response
1295            .json::<Value>()
1296            .await
1297            .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1298    }
1299
1300    /// Drop (delete) notifications by IDs
1301    pub async fn drop_notifications(&self, ids: &[String]) -> Result<Value> {
1302        let signer = self
1303            .signer
1304            .as_ref()
1305            .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1306        let api_creds = self
1307            .api_creds
1308            .as_ref()
1309            .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1310
1311        let method = Method::DELETE;
1312        let endpoint = "/notifications";
1313        let headers =
1314            create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1315
1316        let response = self
1317            .http_client
1318            .request(method, format!("{}{}", self.base_url, endpoint))
1319            .headers(
1320                headers
1321                    .into_iter()
1322                    .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1323                    .collect(),
1324            )
1325            .query(&[("ids", ids.join(","))])
1326            .send()
1327            .await
1328            .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1329
1330        response
1331            .json::<Value>()
1332            .await
1333            .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1334    }
1335
1336    /// Update balance allowance
1337    pub async fn update_balance_allowance(
1338        &self,
1339        params: Option<crate::types::BalanceAllowanceParams>,
1340    ) -> Result<Value> {
1341        let signer = self
1342            .signer
1343            .as_ref()
1344            .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1345        let api_creds = self
1346            .api_creds
1347            .as_ref()
1348            .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1349
1350        let mut params = params.unwrap_or_default();
1351        if params.signature_type.is_none() {
1352            params.set_signature_type(
1353                self.order_builder
1354                    .as_ref()
1355                    .expect("OrderBuilder not set")
1356                    .get_sig_type(),
1357            );
1358        }
1359
1360        let query_params = params.to_query_params();
1361
1362        let method = Method::GET;
1363        let endpoint = "/balance-allowance/update";
1364        let headers =
1365            create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1366
1367        let response = self
1368            .http_client
1369            .request(method, format!("{}{}", self.base_url, endpoint))
1370            .headers(
1371                headers
1372                    .into_iter()
1373                    .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1374                    .collect(),
1375            )
1376            .query(&query_params)
1377            .send()
1378            .await
1379            .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1380
1381        response
1382            .json::<Value>()
1383            .await
1384            .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1385    }
1386
1387    /// Check if an order is scoring
1388    pub async fn is_order_scoring(&self, order_id: &str) -> Result<bool> {
1389        let signer = self
1390            .signer
1391            .as_ref()
1392            .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1393        let api_creds = self
1394            .api_creds
1395            .as_ref()
1396            .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1397
1398        let method = Method::GET;
1399        let endpoint = "/order-scoring";
1400        let headers =
1401            create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1402
1403        let response = self
1404            .http_client
1405            .request(method, format!("{}{}", self.base_url, endpoint))
1406            .headers(
1407                headers
1408                    .into_iter()
1409                    .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1410                    .collect(),
1411            )
1412            .query(&[("order_id", order_id)])
1413            .send()
1414            .await
1415            .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1416
1417        let result: Value = response
1418            .json()
1419            .await
1420            .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))?;
1421
1422        Ok(result["scoring"].as_bool().unwrap_or(false))
1423    }
1424
1425    /// Check if multiple orders are scoring
1426    pub async fn are_orders_scoring(
1427        &self,
1428        order_ids: &[&str],
1429    ) -> Result<std::collections::HashMap<String, bool>> {
1430        let signer = self
1431            .signer
1432            .as_ref()
1433            .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1434        let api_creds = self
1435            .api_creds
1436            .as_ref()
1437            .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1438
1439        let method = Method::POST;
1440        let endpoint = "/orders-scoring";
1441        let headers = create_l2_headers(
1442            signer,
1443            api_creds,
1444            method.as_str(),
1445            endpoint,
1446            Some(order_ids),
1447        )?;
1448
1449        let response = self
1450            .http_client
1451            .request(method, format!("{}{}", self.base_url, endpoint))
1452            .headers(
1453                headers
1454                    .into_iter()
1455                    .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1456                    .collect(),
1457            )
1458            .json(order_ids)
1459            .send()
1460            .await
1461            .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1462
1463        response
1464            .json::<std::collections::HashMap<String, bool>>()
1465            .await
1466            .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1467    }
1468
1469    /// Get sampling markets with pagination
1470    pub async fn get_sampling_markets(
1471        &self,
1472        next_cursor: Option<&str>,
1473    ) -> Result<crate::types::MarketsResponse> {
1474        let next_cursor = next_cursor.unwrap_or("MA=="); // INITIAL_CURSOR
1475
1476        let response = self
1477            .http_client
1478            .get(format!("{}/sampling-markets", self.base_url))
1479            .query(&[("next_cursor", next_cursor)])
1480            .send()
1481            .await
1482            .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1483
1484        response
1485            .json::<crate::types::MarketsResponse>()
1486            .await
1487            .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1488    }
1489
1490    /// Get sampling simplified markets with pagination
1491    pub async fn get_sampling_simplified_markets(
1492        &self,
1493        next_cursor: Option<&str>,
1494    ) -> Result<crate::types::SimplifiedMarketsResponse> {
1495        let next_cursor = next_cursor.unwrap_or("MA=="); // INITIAL_CURSOR
1496
1497        let response = self
1498            .http_client
1499            .get(format!("{}/sampling-simplified-markets", self.base_url))
1500            .query(&[("next_cursor", next_cursor)])
1501            .send()
1502            .await
1503            .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1504
1505        response
1506            .json::<crate::types::SimplifiedMarketsResponse>()
1507            .await
1508            .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1509    }
1510
1511    /// Get markets with pagination
1512    pub async fn get_markets(
1513        &self,
1514        next_cursor: Option<&str>,
1515    ) -> Result<crate::types::MarketsResponse> {
1516        let next_cursor = next_cursor.unwrap_or("MA=="); // INITIAL_CURSOR
1517
1518        let response = self
1519            .http_client
1520            .get(format!("{}/markets", self.base_url))
1521            .query(&[("next_cursor", next_cursor)])
1522            .send()
1523            .await
1524            .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1525
1526        response
1527            .json::<crate::types::MarketsResponse>()
1528            .await
1529            .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1530    }
1531
1532    /// Get simplified markets with pagination
1533    pub async fn get_simplified_markets(
1534        &self,
1535        next_cursor: Option<&str>,
1536    ) -> Result<crate::types::SimplifiedMarketsResponse> {
1537        let next_cursor = next_cursor.unwrap_or("MA=="); // INITIAL_CURSOR
1538
1539        let response = self
1540            .http_client
1541            .get(format!("{}/simplified-markets", self.base_url))
1542            .query(&[("next_cursor", next_cursor)])
1543            .send()
1544            .await
1545            .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1546
1547        response
1548            .json::<crate::types::SimplifiedMarketsResponse>()
1549            .await
1550            .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1551    }
1552
1553    /// Get single market by condition ID
1554    pub async fn get_market(&self, condition_id: &str) -> Result<crate::types::Market> {
1555        let response = self
1556            .http_client
1557            .get(format!("{}/markets/{}", self.base_url, condition_id))
1558            .send()
1559            .await
1560            .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1561
1562        response
1563            .json::<crate::types::Market>()
1564            .await
1565            .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1566    }
1567
1568    /// Get market trades events
1569    pub async fn get_market_trades_events(&self, condition_id: &str) -> Result<Value> {
1570        let response = self
1571            .http_client
1572            .get(format!(
1573                "{}/live-activity/events/{}",
1574                self.base_url, condition_id
1575            ))
1576            .send()
1577            .await
1578            .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1579
1580        response
1581            .json::<Value>()
1582            .await
1583            .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1584    }
1585}
1586
1587// Re-export types from the canonical location in types.rs
1588pub use crate::types::{
1589    ExtraOrderArgs, Market, MarketOrderArgs, MarketsResponse, MidpointResponse, NegRiskResponse,
1590    OrderBookSummary, OrderSummary, PriceResponse, Rewards, SpreadResponse, TickSizeResponse,
1591    Token,
1592};
1593
1594// Compatibility types that need to stay in client.rs
1595#[derive(Debug, Default)]
1596pub struct CreateOrderOptions {
1597    pub tick_size: Option<Decimal>,
1598    pub neg_risk: Option<bool>,
1599}
1600
1601// Re-export for compatibility
1602pub type PolyfillClient = ClobClient;
1603
1604#[cfg(test)]
1605mod tests {
1606    use super::{ClobClient, OrderArgs as ClientOrderArgs};
1607    use crate::types::Side;
1608    use crate::{ApiCredentials, PolyfillError};
1609    use mockito::{Matcher, Server};
1610    use rust_decimal::Decimal;
1611    use std::str::FromStr;
1612    use tokio;
1613
1614    fn create_test_client(base_url: &str) -> ClobClient {
1615        ClobClient::new(base_url)
1616    }
1617
1618    fn create_test_client_with_auth(base_url: &str) -> ClobClient {
1619        ClobClient::with_l1_headers(
1620            base_url,
1621            "0x1234567890123456789012345678901234567890123456789012345678901234",
1622            137,
1623        )
1624    }
1625
1626    #[tokio::test]
1627    async fn test_client_creation() {
1628        let client = create_test_client("https://test.example.com");
1629        assert_eq!(client.base_url, "https://test.example.com");
1630        assert!(client.signer.is_none());
1631        assert!(client.api_creds.is_none());
1632    }
1633
1634    #[tokio::test]
1635    async fn test_client_with_l1_headers() {
1636        let client = create_test_client_with_auth("https://test.example.com");
1637        assert_eq!(client.base_url, "https://test.example.com");
1638        assert!(client.signer.is_some());
1639        assert_eq!(client.chain_id, 137);
1640    }
1641
1642    #[tokio::test]
1643    async fn test_client_with_l2_headers() {
1644        let api_creds = ApiCredentials {
1645            api_key: "test_key".to_string(),
1646            secret: "test_secret".to_string(),
1647            passphrase: "test_passphrase".to_string(),
1648        };
1649
1650        let client = ClobClient::with_l2_headers(
1651            "https://test.example.com",
1652            "0x1234567890123456789012345678901234567890123456789012345678901234",
1653            137,
1654            api_creds.clone(),
1655        );
1656
1657        assert_eq!(client.base_url, "https://test.example.com");
1658        assert!(client.signer.is_some());
1659        assert!(client.api_creds.is_some());
1660        assert_eq!(client.chain_id, 137);
1661    }
1662
1663    #[tokio::test]
1664    async fn test_set_api_creds() {
1665        let mut client = create_test_client("https://test.example.com");
1666        assert!(client.api_creds.is_none());
1667
1668        let api_creds = ApiCredentials {
1669            api_key: "test_key".to_string(),
1670            secret: "test_secret".to_string(),
1671            passphrase: "test_passphrase".to_string(),
1672        };
1673
1674        client.set_api_creds(api_creds.clone());
1675        assert!(client.api_creds.is_some());
1676        assert_eq!(client.api_creds.unwrap().api_key, "test_key");
1677    }
1678
1679    #[tokio::test]
1680    async fn test_get_sampling_markets_success() {
1681        let mut server = Server::new_async().await;
1682        let mock_response = r#"{
1683            "limit": "10",
1684            "count": "2", 
1685            "next_cursor": null,
1686            "data": [
1687                {
1688                    "condition_id": "0x123",
1689                    "tokens": [
1690                        {"token_id": "0x456", "outcome": "Yes"},
1691                        {"token_id": "0x789", "outcome": "No"}
1692                    ],
1693                    "rewards": {
1694                        "rates": null,
1695                        "min_size": "1.0",
1696                        "max_spread": "0.1",
1697                        "event_start_date": null,
1698                        "event_end_date": null,
1699                        "in_game_multiplier": null,
1700                        "reward_epoch": null
1701                    },
1702                    "min_incentive_size": null,
1703                    "max_incentive_spread": null,
1704                    "active": true,
1705                    "closed": false,
1706                    "question_id": "0x123",
1707                    "minimum_order_size": "1.0",
1708                    "minimum_tick_size": "0.01",
1709                    "description": "Test market",
1710                    "category": "test",
1711                    "end_date_iso": null,
1712                    "game_start_time": null,
1713                    "question": "Will this test pass?",
1714                    "market_slug": "test-market",
1715                    "seconds_delay": "0",
1716                    "icon": "",
1717                    "fpmm": ""
1718                }
1719            ]
1720        }"#;
1721
1722        let mock = server
1723            .mock("GET", "/sampling-markets")
1724            .match_query(Matcher::UrlEncoded("next_cursor".into(), "MA==".into()))
1725            .with_status(200)
1726            .with_header("content-type", "application/json")
1727            .with_body(mock_response)
1728            .create_async()
1729            .await;
1730
1731        let client = create_test_client(&server.url());
1732        let result = client.get_sampling_markets(None).await;
1733
1734        mock.assert_async().await;
1735        assert!(result.is_ok());
1736        let markets = result.unwrap();
1737        assert_eq!(markets.data.len(), 1);
1738        assert_eq!(markets.data[0].question, "Will this test pass?");
1739    }
1740
1741    #[tokio::test]
1742    async fn test_get_sampling_markets_with_cursor() {
1743        let mut server = Server::new_async().await;
1744        let mock_response = r#"{
1745            "limit": "5",
1746            "count": "0",
1747            "next_cursor": null,
1748            "data": []
1749        }"#;
1750
1751        let mock = server
1752            .mock("GET", "/sampling-markets")
1753            .match_query(Matcher::AllOf(vec![Matcher::UrlEncoded(
1754                "next_cursor".into(),
1755                "test_cursor".into(),
1756            )]))
1757            .with_status(200)
1758            .with_header("content-type", "application/json")
1759            .with_body(mock_response)
1760            .create_async()
1761            .await;
1762
1763        let client = create_test_client(&server.url());
1764        let result = client.get_sampling_markets(Some("test_cursor")).await;
1765
1766        mock.assert_async().await;
1767        assert!(result.is_ok());
1768        let markets = result.unwrap();
1769        assert_eq!(markets.data.len(), 0);
1770    }
1771
1772    #[tokio::test]
1773    async fn test_get_order_book_success() {
1774        let mut server = Server::new_async().await;
1775        let mock_response = r#"{
1776            "market": "0x123",
1777            "asset_id": "0x123",
1778            "hash": "0xabc123",
1779            "timestamp": "1234567890",
1780            "bids": [
1781                {"price": "0.75", "size": "100.0"}
1782            ],
1783            "asks": [
1784                {"price": "0.76", "size": "50.0"}
1785            ]
1786        }"#;
1787
1788        let mock = server
1789            .mock("GET", "/book")
1790            .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into()))
1791            .with_status(200)
1792            .with_header("content-type", "application/json")
1793            .with_body(mock_response)
1794            .create_async()
1795            .await;
1796
1797        let client = create_test_client(&server.url());
1798        let result = client.get_order_book("0x123").await;
1799
1800        mock.assert_async().await;
1801        assert!(result.is_ok());
1802        let book = result.unwrap();
1803        assert_eq!(book.market, "0x123");
1804        assert_eq!(book.bids.len(), 1);
1805        assert_eq!(book.asks.len(), 1);
1806    }
1807
1808    #[tokio::test]
1809    async fn test_get_midpoint_success() {
1810        let mut server = Server::new_async().await;
1811        let mock_response = r#"{
1812            "mid": "0.755"
1813        }"#;
1814
1815        let mock = server
1816            .mock("GET", "/midpoint")
1817            .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into()))
1818            .with_status(200)
1819            .with_header("content-type", "application/json")
1820            .with_body(mock_response)
1821            .create_async()
1822            .await;
1823
1824        let client = create_test_client(&server.url());
1825        let result = client.get_midpoint("0x123").await;
1826
1827        mock.assert_async().await;
1828        assert!(result.is_ok());
1829        let response = result.unwrap();
1830        assert_eq!(response.mid, Decimal::from_str("0.755").unwrap());
1831    }
1832
1833    #[tokio::test]
1834    async fn test_get_spread_success() {
1835        let mut server = Server::new_async().await;
1836        let mock_response = r#"{
1837            "spread": "0.01"
1838        }"#;
1839
1840        let mock = server
1841            .mock("GET", "/spread")
1842            .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into()))
1843            .with_status(200)
1844            .with_header("content-type", "application/json")
1845            .with_body(mock_response)
1846            .create_async()
1847            .await;
1848
1849        let client = create_test_client(&server.url());
1850        let result = client.get_spread("0x123").await;
1851
1852        mock.assert_async().await;
1853        assert!(result.is_ok());
1854        let response = result.unwrap();
1855        assert_eq!(response.spread, Decimal::from_str("0.01").unwrap());
1856    }
1857
1858    #[tokio::test]
1859    async fn test_get_price_success() {
1860        let mut server = Server::new_async().await;
1861        let mock_response = r#"{
1862            "price": "0.76"
1863        }"#;
1864
1865        let mock = server
1866            .mock("GET", "/price")
1867            .match_query(Matcher::AllOf(vec![
1868                Matcher::UrlEncoded("token_id".into(), "0x123".into()),
1869                Matcher::UrlEncoded("side".into(), "BUY".into()),
1870            ]))
1871            .with_status(200)
1872            .with_header("content-type", "application/json")
1873            .with_body(mock_response)
1874            .create_async()
1875            .await;
1876
1877        let client = create_test_client(&server.url());
1878        let result = client.get_price("0x123", Side::BUY).await;
1879
1880        mock.assert_async().await;
1881        assert!(result.is_ok());
1882        let response = result.unwrap();
1883        assert_eq!(response.price, Decimal::from_str("0.76").unwrap());
1884    }
1885
1886    #[tokio::test]
1887    async fn test_get_tick_size_success() {
1888        let mut server = Server::new_async().await;
1889        let mock_response = r#"{
1890            "minimum_tick_size": "0.01"
1891        }"#;
1892
1893        let mock = server
1894            .mock("GET", "/tick-size")
1895            .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into()))
1896            .with_status(200)
1897            .with_header("content-type", "application/json")
1898            .with_body(mock_response)
1899            .create_async()
1900            .await;
1901
1902        let client = create_test_client(&server.url());
1903        let result = client.get_tick_size("0x123").await;
1904
1905        mock.assert_async().await;
1906        assert!(result.is_ok());
1907        let tick_size = result.unwrap();
1908        assert_eq!(tick_size, Decimal::from_str("0.01").unwrap());
1909    }
1910
1911    #[tokio::test]
1912    async fn test_get_neg_risk_success() {
1913        let mut server = Server::new_async().await;
1914        let mock_response = r#"{
1915            "neg_risk": false
1916        }"#;
1917
1918        let mock = server
1919            .mock("GET", "/neg-risk")
1920            .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into()))
1921            .with_status(200)
1922            .with_header("content-type", "application/json")
1923            .with_body(mock_response)
1924            .create_async()
1925            .await;
1926
1927        let client = create_test_client(&server.url());
1928        let result = client.get_neg_risk("0x123").await;
1929
1930        mock.assert_async().await;
1931        assert!(result.is_ok());
1932        let neg_risk = result.unwrap();
1933        assert!(!neg_risk);
1934    }
1935
1936    #[tokio::test]
1937    async fn test_api_error_handling() {
1938        let mut server = Server::new_async().await;
1939
1940        let mock = server
1941            .mock("GET", "/book")
1942            .match_query(Matcher::UrlEncoded(
1943                "token_id".into(),
1944                "invalid_token".into(),
1945            ))
1946            .with_status(404)
1947            .with_header("content-type", "application/json")
1948            .with_body(r#"{"error": "Market not found"}"#)
1949            .create_async()
1950            .await;
1951
1952        let client = create_test_client(&server.url());
1953        let result = client.get_order_book("invalid_token").await;
1954
1955        mock.assert_async().await;
1956        assert!(result.is_err());
1957
1958        let error = result.unwrap_err();
1959        // The error should be either Network or Api error
1960        assert!(
1961            matches!(error, PolyfillError::Network { .. })
1962                || matches!(error, PolyfillError::Api { .. })
1963        );
1964    }
1965
1966    #[tokio::test]
1967    async fn test_network_error_handling() {
1968        // Test with invalid URL to simulate network error
1969        let client = create_test_client("http://invalid-host-that-does-not-exist.com");
1970        let result = client.get_order_book("0x123").await;
1971
1972        assert!(result.is_err());
1973        let error = result.unwrap_err();
1974        assert!(matches!(error, PolyfillError::Network { .. }));
1975    }
1976
1977    #[test]
1978    fn test_client_url_validation() {
1979        let client = create_test_client("https://test.example.com");
1980        assert_eq!(client.base_url, "https://test.example.com");
1981
1982        let client2 = create_test_client("http://localhost:8080");
1983        assert_eq!(client2.base_url, "http://localhost:8080");
1984    }
1985
1986    #[tokio::test]
1987    async fn test_get_midpoints_batch() {
1988        let mut server = Server::new_async().await;
1989        let mock_response = r#"{
1990            "0x123": "0.755",
1991            "0x456": "0.623"
1992        }"#;
1993
1994        let mock = server
1995            .mock("POST", "/midpoints")
1996            .with_header("content-type", "application/json")
1997            .with_status(200)
1998            .with_header("content-type", "application/json")
1999            .with_body(mock_response)
2000            .create_async()
2001            .await;
2002
2003        let client = create_test_client(&server.url());
2004        let token_ids = vec!["0x123".to_string(), "0x456".to_string()];
2005        let result = client.get_midpoints(&token_ids).await;
2006
2007        mock.assert_async().await;
2008        assert!(result.is_ok());
2009        let midpoints = result.unwrap();
2010        assert_eq!(midpoints.len(), 2);
2011        assert_eq!(
2012            midpoints.get("0x123").unwrap(),
2013            &Decimal::from_str("0.755").unwrap()
2014        );
2015        assert_eq!(
2016            midpoints.get("0x456").unwrap(),
2017            &Decimal::from_str("0.623").unwrap()
2018        );
2019    }
2020
2021    #[test]
2022    fn test_client_configuration() {
2023        let client = create_test_client("https://test.example.com");
2024
2025        // Test initial state
2026        assert!(client.signer.is_none());
2027        assert!(client.api_creds.is_none());
2028
2029        // Test with auth
2030        let auth_client = create_test_client_with_auth("https://test.example.com");
2031        assert!(auth_client.signer.is_some());
2032        assert_eq!(auth_client.chain_id, 137);
2033    }
2034
2035    #[tokio::test]
2036    async fn test_get_ok() {
2037        let mut server = Server::new_async().await;
2038        let mock_response = r#"{"status": "ok"}"#;
2039
2040        let mock = server
2041            .mock("GET", "/ok")
2042            .with_header("content-type", "application/json")
2043            .with_status(200)
2044            .with_body(mock_response)
2045            .create_async()
2046            .await;
2047
2048        let client = create_test_client(&server.url());
2049        let result = client.get_ok().await;
2050
2051        mock.assert_async().await;
2052        assert!(result);
2053    }
2054
2055    #[tokio::test]
2056    async fn test_get_prices_batch() {
2057        let mut server = Server::new_async().await;
2058        let mock_response = r#"{
2059            "0x123": {
2060                "BUY": "0.755",
2061                "SELL": "0.745"
2062            },
2063            "0x456": {
2064                "BUY": "0.623",
2065                "SELL": "0.613"
2066            }
2067        }"#;
2068
2069        let mock = server
2070            .mock("POST", "/prices")
2071            .with_header("content-type", "application/json")
2072            .with_status(200)
2073            .with_body(mock_response)
2074            .create_async()
2075            .await;
2076
2077        let client = create_test_client(&server.url());
2078        let book_params = vec![
2079            crate::types::BookParams {
2080                token_id: "0x123".to_string(),
2081                side: Side::BUY,
2082            },
2083            crate::types::BookParams {
2084                token_id: "0x456".to_string(),
2085                side: Side::SELL,
2086            },
2087        ];
2088        let result = client.get_prices(&book_params).await;
2089
2090        mock.assert_async().await;
2091        assert!(result.is_ok());
2092        let prices = result.unwrap();
2093        assert_eq!(prices.len(), 2);
2094        assert!(prices.contains_key("0x123"));
2095        assert!(prices.contains_key("0x456"));
2096    }
2097
2098    #[tokio::test]
2099    async fn test_get_server_time() {
2100        let mut server = Server::new_async().await;
2101        let mock_response = "1234567890"; // Plain text response
2102
2103        let mock = server
2104            .mock("GET", "/time")
2105            .with_status(200)
2106            .with_body(mock_response)
2107            .create_async()
2108            .await;
2109
2110        let client = create_test_client(&server.url());
2111        let result = client.get_server_time().await;
2112
2113        mock.assert_async().await;
2114        assert!(result.is_ok());
2115        let timestamp = result.unwrap();
2116        assert_eq!(timestamp, 1234567890);
2117    }
2118
2119    #[tokio::test]
2120    async fn test_create_or_derive_api_key() {
2121        let mut server = Server::new_async().await;
2122        let mock_response = r#"{
2123            "apiKey": "test-api-key-123",
2124            "secret": "test-secret-456",
2125            "passphrase": "test-passphrase"
2126        }"#;
2127
2128        // Mock both create and derive endpoints since the method tries both
2129        let create_mock = server
2130            .mock("POST", "/auth/api-key")
2131            .with_header("content-type", "application/json")
2132            .with_status(200)
2133            .with_body(mock_response)
2134            .create_async()
2135            .await;
2136
2137        let client = create_test_client_with_auth(&server.url());
2138        let result = client.create_or_derive_api_key(None).await;
2139
2140        create_mock.assert_async().await;
2141        assert!(result.is_ok());
2142        let api_creds = result.unwrap();
2143        assert_eq!(api_creds.api_key, "test-api-key-123");
2144    }
2145
2146    #[tokio::test]
2147    async fn test_get_order_books_batch() {
2148        let mut server = Server::new_async().await;
2149        let mock_response = r#"[
2150            {
2151                "market": "0x123",
2152                "asset_id": "0x123",
2153                "hash": "test-hash",
2154                "timestamp": "1234567890",
2155                "bids": [{"price": "0.75", "size": "100.0"}],
2156                "asks": [{"price": "0.76", "size": "50.0"}]
2157            }
2158        ]"#;
2159
2160        let mock = server
2161            .mock("POST", "/books")
2162            .with_header("content-type", "application/json")
2163            .with_status(200)
2164            .with_body(mock_response)
2165            .create_async()
2166            .await;
2167
2168        let client = create_test_client(&server.url());
2169        let token_ids = vec!["0x123".to_string()];
2170        let result = client.get_order_books(&token_ids).await;
2171
2172        mock.assert_async().await;
2173        if let Err(e) = &result {
2174            println!("Error: {:?}", e);
2175        }
2176        assert!(result.is_ok());
2177        let books = result.unwrap();
2178        assert_eq!(books.len(), 1);
2179    }
2180
2181    #[tokio::test]
2182    async fn test_order_args_creation() {
2183        // Test OrderArgs creation and default values
2184        let order_args = ClientOrderArgs::new(
2185            "0x123",
2186            Decimal::from_str("0.75").unwrap(),
2187            Decimal::from_str("100.0").unwrap(),
2188            Side::BUY,
2189        );
2190
2191        assert_eq!(order_args.token_id, "0x123");
2192        assert_eq!(order_args.price, Decimal::from_str("0.75").unwrap());
2193        assert_eq!(order_args.size, Decimal::from_str("100.0").unwrap());
2194        assert_eq!(order_args.side, Side::BUY);
2195
2196        // Test default
2197        let default_args = ClientOrderArgs::default();
2198        assert_eq!(default_args.token_id, "");
2199        assert_eq!(default_args.price, Decimal::ZERO);
2200        assert_eq!(default_args.size, Decimal::ZERO);
2201        assert_eq!(default_args.side, Side::BUY);
2202    }
2203}