1use 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
22pub use crate::types::{ApiCredentials as ApiCreds, OrderType, Side};
24
25#[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
56pub 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 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, signer: None,
74 api_creds: None,
75 order_builder: None,
76 }
77 }
78
79 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 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 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 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 pub fn set_api_creds(&mut self, api_creds: ApiCreds) {
146 self.api_creds = Some(api_creds);
147 }
148
149 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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(); let mut output = Vec::new();
846
847 while next_cursor != "LTE=" {
848 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 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(); let mut output = Vec::new();
929
930 while next_cursor != "LTE=" {
931 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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=="); 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 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=="); 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 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=="); 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 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=="); 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 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 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
1587pub use crate::types::{
1589 ExtraOrderArgs, Market, MarketOrderArgs, MarketsResponse, MidpointResponse, NegRiskResponse,
1590 OrderBookSummary, OrderSummary, PriceResponse, Rewards, SpreadResponse, TickSizeResponse,
1591 Token,
1592};
1593
1594#[derive(Debug, Default)]
1596pub struct CreateOrderOptions {
1597 pub tick_size: Option<Decimal>,
1598 pub neg_risk: Option<bool>,
1599}
1600
1601pub 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 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 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 assert!(client.signer.is_none());
2027 assert!(client.api_creds.is_none());
2028
2029 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"; 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 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 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 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}