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