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