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