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);
839
840 let headers = create_l2_headers(signer, api_creds, "POST", "/order", Some(&body))?;
841 let req = self.create_request_with_headers(Method::POST, "/order", headers.into_iter());
842
843 let response = req.json(&body).send().await?;
844 if !response.status().is_success() {
845 return Err(PolyfillError::api(
846 response.status().as_u16(),
847 "Failed to post order",
848 ));
849 }
850
851 Ok(response.json::<Value>().await?)
852 }
853
854 pub async fn create_and_post_order(&self, order_args: &OrderArgs) -> Result<Value> {
856 let order = self.create_order(order_args, None, None, None).await?;
857 self.post_order(order, OrderType::GTC).await
858 }
859
860 pub async fn cancel(&self, order_id: &str) -> Result<Value> {
862 let signer = self
863 .signer
864 .as_ref()
865 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
866 let api_creds = self
867 .api_creds
868 .as_ref()
869 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
870
871 let body = std::collections::HashMap::from([("orderID", order_id)]);
872
873 let headers = create_l2_headers(signer, api_creds, "DELETE", "/order", Some(&body))?;
874 let req = self.create_request_with_headers(Method::DELETE, "/order", headers.into_iter());
875
876 let response = req.json(&body).send().await?;
877 if !response.status().is_success() {
878 return Err(PolyfillError::api(
879 response.status().as_u16(),
880 "Failed to cancel order",
881 ));
882 }
883
884 Ok(response.json::<Value>().await?)
885 }
886
887 pub async fn cancel_orders(&self, order_ids: &[String]) -> Result<Value> {
889 let signer = self
890 .signer
891 .as_ref()
892 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
893 let api_creds = self
894 .api_creds
895 .as_ref()
896 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
897
898 let headers = create_l2_headers(signer, api_creds, "DELETE", "/orders", Some(order_ids))?;
899 let req = self.create_request_with_headers(Method::DELETE, "/orders", headers.into_iter());
900
901 let response = req.json(order_ids).send().await?;
902 if !response.status().is_success() {
903 return Err(PolyfillError::api(
904 response.status().as_u16(),
905 "Failed to cancel orders",
906 ));
907 }
908
909 Ok(response.json::<Value>().await?)
910 }
911
912 pub async fn cancel_all(&self) -> Result<Value> {
914 let signer = self
915 .signer
916 .as_ref()
917 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
918 let api_creds = self
919 .api_creds
920 .as_ref()
921 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
922
923 let headers = create_l2_headers::<Value>(signer, api_creds, "DELETE", "/cancel-all", None)?;
924 let req =
925 self.create_request_with_headers(Method::DELETE, "/cancel-all", headers.into_iter());
926
927 let response = req.send().await?;
928 if !response.status().is_success() {
929 return Err(PolyfillError::api(
930 response.status().as_u16(),
931 "Failed to cancel all orders",
932 ));
933 }
934
935 Ok(response.json::<Value>().await?)
936 }
937
938 pub async fn get_orders(
947 &self,
948 params: Option<&crate::types::OpenOrderParams>,
949 next_cursor: Option<&str>,
950 ) -> Result<Vec<crate::types::OpenOrder>> {
951 let signer = self
952 .signer
953 .as_ref()
954 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
955 let api_creds = self
956 .api_creds
957 .as_ref()
958 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
959
960 let method = Method::GET;
961 let endpoint = "/data/orders";
962 let headers =
963 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
964
965 let query_params = match params {
966 None => Vec::new(),
967 Some(p) => p.to_query_params(),
968 };
969
970 let mut next_cursor = next_cursor.unwrap_or("MA==").to_string(); let mut output = Vec::new();
972
973 while next_cursor != "LTE=" {
974 let req = self
976 .http_client
977 .request(method.clone(), format!("{}{}", self.base_url, endpoint))
978 .query(&query_params)
979 .query(&[("next_cursor", &next_cursor)]);
980
981 let r = headers
982 .clone()
983 .into_iter()
984 .fold(req, |r, (k, v)| r.header(HeaderName::from_static(k), v));
985
986 let resp = r
987 .send()
988 .await
989 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?
990 .json::<Value>()
991 .await
992 .map_err(|e| {
993 PolyfillError::parse(format!("Failed to parse response: {}", e), None)
994 })?;
995
996 let new_cursor = resp["next_cursor"]
997 .as_str()
998 .ok_or_else(|| {
999 PolyfillError::parse("Failed to parse next cursor".to_string(), None)
1000 })?
1001 .to_owned();
1002
1003 next_cursor = new_cursor;
1004
1005 let results = resp["data"].clone();
1006 let orders =
1007 serde_json::from_value::<Vec<crate::types::OpenOrder>>(results).map_err(|e| {
1008 PolyfillError::parse(
1009 format!("Failed to parse data from order response: {}", e),
1010 None,
1011 )
1012 })?;
1013 output.extend(orders);
1014 }
1015
1016 Ok(output)
1017 }
1018
1019 pub async fn get_trades(
1030 &self,
1031 trade_params: Option<&crate::types::TradeParams>,
1032 next_cursor: Option<&str>,
1033 ) -> Result<Vec<Value>> {
1034 let signer = self
1035 .signer
1036 .as_ref()
1037 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
1038 let api_creds = self
1039 .api_creds
1040 .as_ref()
1041 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
1042
1043 let method = Method::GET;
1044 let endpoint = "/data/trades";
1045 let headers =
1046 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1047
1048 let query_params = match trade_params {
1049 None => Vec::new(),
1050 Some(p) => p.to_query_params(),
1051 };
1052
1053 let mut next_cursor = next_cursor.unwrap_or("MA==").to_string(); let mut output = Vec::new();
1055
1056 while next_cursor != "LTE=" {
1057 let req = self
1059 .http_client
1060 .request(method.clone(), format!("{}{}", self.base_url, endpoint))
1061 .query(&query_params)
1062 .query(&[("next_cursor", &next_cursor)]);
1063
1064 let r = headers
1065 .clone()
1066 .into_iter()
1067 .fold(req, |r, (k, v)| r.header(HeaderName::from_static(k), v));
1068
1069 let resp = r
1070 .send()
1071 .await
1072 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?
1073 .json::<Value>()
1074 .await
1075 .map_err(|e| {
1076 PolyfillError::parse(format!("Failed to parse response: {}", e), None)
1077 })?;
1078
1079 let new_cursor = resp["next_cursor"]
1080 .as_str()
1081 .ok_or_else(|| {
1082 PolyfillError::parse("Failed to parse next cursor".to_string(), None)
1083 })?
1084 .to_owned();
1085
1086 next_cursor = new_cursor;
1087
1088 let results = resp["data"].clone();
1089 output.push(results);
1090 }
1091
1092 Ok(output)
1093 }
1094
1095 pub async fn get_balance_allowance(
1103 &self,
1104 params: Option<crate::types::BalanceAllowanceParams>,
1105 ) -> Result<Value> {
1106 let signer = self
1107 .signer
1108 .as_ref()
1109 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
1110 let api_creds = self
1111 .api_creds
1112 .as_ref()
1113 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
1114
1115 let mut params = params.unwrap_or_default();
1116 if params.signature_type.is_none() {
1117 params.set_signature_type(
1118 self.order_builder
1119 .as_ref()
1120 .expect("OrderBuilder not set")
1121 .get_sig_type(),
1122 );
1123 }
1124
1125 let query_params = params.to_query_params();
1126
1127 let method = Method::GET;
1128 let endpoint = "/balance-allowance";
1129 let headers =
1130 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1131
1132 let response = self
1133 .http_client
1134 .request(method, format!("{}{}", self.base_url, endpoint))
1135 .headers(
1136 headers
1137 .into_iter()
1138 .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1139 .collect(),
1140 )
1141 .query(&query_params)
1142 .send()
1143 .await
1144 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1145
1146 response
1147 .json::<Value>()
1148 .await
1149 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1150 }
1151
1152 pub async fn get_notifications(&self) -> Result<Value> {
1161 let signer = self
1162 .signer
1163 .as_ref()
1164 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
1165 let api_creds = self
1166 .api_creds
1167 .as_ref()
1168 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
1169
1170 let method = Method::GET;
1171 let endpoint = "/notifications";
1172 let headers =
1173 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1174
1175 let response = self
1176 .http_client
1177 .request(method, format!("{}{}", self.base_url, endpoint))
1178 .headers(
1179 headers
1180 .into_iter()
1181 .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1182 .collect(),
1183 )
1184 .query(&[(
1185 "signature_type",
1186 &self
1187 .order_builder
1188 .as_ref()
1189 .expect("OrderBuilder not set")
1190 .get_sig_type()
1191 .to_string(),
1192 )])
1193 .send()
1194 .await
1195 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1196
1197 response
1198 .json::<Value>()
1199 .await
1200 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1201 }
1202
1203 pub async fn get_midpoints(
1211 &self,
1212 token_ids: &[String],
1213 ) -> Result<std::collections::HashMap<String, Decimal>> {
1214 let request_data: Vec<std::collections::HashMap<&str, String>> = token_ids
1215 .iter()
1216 .map(|id| {
1217 let mut map = std::collections::HashMap::new();
1218 map.insert("token_id", id.clone());
1219 map
1220 })
1221 .collect();
1222
1223 let response = self
1224 .http_client
1225 .post(format!("{}/midpoints", self.base_url))
1226 .json(&request_data)
1227 .send()
1228 .await?;
1229
1230 if !response.status().is_success() {
1231 return Err(PolyfillError::api(
1232 response.status().as_u16(),
1233 "Failed to get batch midpoints",
1234 ));
1235 }
1236
1237 let midpoints: std::collections::HashMap<String, Decimal> = response.json().await?;
1238 Ok(midpoints)
1239 }
1240
1241 pub async fn get_prices(
1249 &self,
1250 book_params: &[crate::types::BookParams],
1251 ) -> Result<std::collections::HashMap<String, std::collections::HashMap<Side, Decimal>>> {
1252 let request_data: Vec<std::collections::HashMap<&str, String>> = book_params
1253 .iter()
1254 .map(|params| {
1255 let mut map = std::collections::HashMap::new();
1256 map.insert("token_id", params.token_id.clone());
1257 map.insert("side", params.side.as_str().to_string());
1258 map
1259 })
1260 .collect();
1261
1262 let response = self
1263 .http_client
1264 .post(format!("{}/prices", self.base_url))
1265 .json(&request_data)
1266 .send()
1267 .await?;
1268
1269 if !response.status().is_success() {
1270 return Err(PolyfillError::api(
1271 response.status().as_u16(),
1272 "Failed to get batch prices",
1273 ));
1274 }
1275
1276 let prices: std::collections::HashMap<String, std::collections::HashMap<Side, Decimal>> =
1277 response.json().await?;
1278 Ok(prices)
1279 }
1280
1281 pub async fn get_order_books(&self, token_ids: &[String]) -> Result<Vec<OrderBookSummary>> {
1283 let request_data: Vec<std::collections::HashMap<&str, String>> = token_ids
1284 .iter()
1285 .map(|id| {
1286 let mut map = std::collections::HashMap::new();
1287 map.insert("token_id", id.clone());
1288 map
1289 })
1290 .collect();
1291
1292 let response = self
1293 .http_client
1294 .post(format!("{}/books", self.base_url))
1295 .json(&request_data)
1296 .send()
1297 .await
1298 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1299
1300 response
1301 .json::<Vec<OrderBookSummary>>()
1302 .await
1303 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1304 }
1305
1306 pub async fn get_order(&self, order_id: &str) -> Result<crate::types::OpenOrder> {
1308 let signer = self
1309 .signer
1310 .as_ref()
1311 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1312 let api_creds = self
1313 .api_creds
1314 .as_ref()
1315 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1316
1317 let method = Method::GET;
1318 let endpoint = &format!("/data/order/{}", order_id);
1319 let headers =
1320 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1321
1322 let response = self
1323 .http_client
1324 .request(method, format!("{}{}", self.base_url, endpoint))
1325 .headers(
1326 headers
1327 .into_iter()
1328 .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1329 .collect(),
1330 )
1331 .send()
1332 .await
1333 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1334
1335 response
1336 .json::<crate::types::OpenOrder>()
1337 .await
1338 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1339 }
1340
1341 pub async fn get_last_trade_price(&self, token_id: &str) -> Result<Value> {
1343 let response = self
1344 .http_client
1345 .get(format!("{}/last-trade-price", self.base_url))
1346 .query(&[("token_id", token_id)])
1347 .send()
1348 .await
1349 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1350
1351 response
1352 .json::<Value>()
1353 .await
1354 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1355 }
1356
1357 pub async fn get_last_trade_prices(&self, token_ids: &[String]) -> Result<Value> {
1359 let request_data: Vec<std::collections::HashMap<&str, String>> = token_ids
1360 .iter()
1361 .map(|id| {
1362 let mut map = std::collections::HashMap::new();
1363 map.insert("token_id", id.clone());
1364 map
1365 })
1366 .collect();
1367
1368 let response = self
1369 .http_client
1370 .post(format!("{}/last-trades-prices", self.base_url))
1371 .json(&request_data)
1372 .send()
1373 .await
1374 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1375
1376 response
1377 .json::<Value>()
1378 .await
1379 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1380 }
1381
1382 pub async fn cancel_market_orders(
1384 &self,
1385 market: Option<&str>,
1386 asset_id: Option<&str>,
1387 ) -> Result<Value> {
1388 let signer = self
1389 .signer
1390 .as_ref()
1391 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1392 let api_creds = self
1393 .api_creds
1394 .as_ref()
1395 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1396
1397 let method = Method::DELETE;
1398 let endpoint = "/cancel-market-orders";
1399 let body = std::collections::HashMap::from([
1400 ("market", market.unwrap_or("")),
1401 ("asset_id", asset_id.unwrap_or("")),
1402 ]);
1403
1404 let headers = create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(&body))?;
1405
1406 let response = self
1407 .http_client
1408 .request(method, format!("{}{}", self.base_url, endpoint))
1409 .headers(
1410 headers
1411 .into_iter()
1412 .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1413 .collect(),
1414 )
1415 .json(&body)
1416 .send()
1417 .await
1418 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1419
1420 response
1421 .json::<Value>()
1422 .await
1423 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1424 }
1425
1426 pub async fn drop_notifications(&self, ids: &[String]) -> Result<Value> {
1428 let signer = self
1429 .signer
1430 .as_ref()
1431 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1432 let api_creds = self
1433 .api_creds
1434 .as_ref()
1435 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1436
1437 let method = Method::DELETE;
1438 let endpoint = "/notifications";
1439 let headers =
1440 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1441
1442 let response = self
1443 .http_client
1444 .request(method, format!("{}{}", self.base_url, endpoint))
1445 .headers(
1446 headers
1447 .into_iter()
1448 .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1449 .collect(),
1450 )
1451 .query(&[("ids", ids.join(","))])
1452 .send()
1453 .await
1454 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1455
1456 response
1457 .json::<Value>()
1458 .await
1459 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1460 }
1461
1462 pub async fn update_balance_allowance(
1464 &self,
1465 params: Option<crate::types::BalanceAllowanceParams>,
1466 ) -> Result<Value> {
1467 let signer = self
1468 .signer
1469 .as_ref()
1470 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1471 let api_creds = self
1472 .api_creds
1473 .as_ref()
1474 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1475
1476 let mut params = params.unwrap_or_default();
1477 if params.signature_type.is_none() {
1478 params.set_signature_type(
1479 self.order_builder
1480 .as_ref()
1481 .expect("OrderBuilder not set")
1482 .get_sig_type(),
1483 );
1484 }
1485
1486 let query_params = params.to_query_params();
1487
1488 let method = Method::GET;
1489 let endpoint = "/balance-allowance/update";
1490 let headers =
1491 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1492
1493 let response = self
1494 .http_client
1495 .request(method, format!("{}{}", self.base_url, endpoint))
1496 .headers(
1497 headers
1498 .into_iter()
1499 .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1500 .collect(),
1501 )
1502 .query(&query_params)
1503 .send()
1504 .await
1505 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1506
1507 response
1508 .json::<Value>()
1509 .await
1510 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1511 }
1512
1513 pub async fn is_order_scoring(&self, order_id: &str) -> Result<bool> {
1515 let signer = self
1516 .signer
1517 .as_ref()
1518 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1519 let api_creds = self
1520 .api_creds
1521 .as_ref()
1522 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1523
1524 let method = Method::GET;
1525 let endpoint = "/order-scoring";
1526 let headers =
1527 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1528
1529 let response = self
1530 .http_client
1531 .request(method, format!("{}{}", self.base_url, endpoint))
1532 .headers(
1533 headers
1534 .into_iter()
1535 .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1536 .collect(),
1537 )
1538 .query(&[("order_id", order_id)])
1539 .send()
1540 .await
1541 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1542
1543 let result: Value = response
1544 .json()
1545 .await
1546 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))?;
1547
1548 Ok(result["scoring"].as_bool().unwrap_or(false))
1549 }
1550
1551 pub async fn are_orders_scoring(
1553 &self,
1554 order_ids: &[&str],
1555 ) -> Result<std::collections::HashMap<String, bool>> {
1556 let signer = self
1557 .signer
1558 .as_ref()
1559 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1560 let api_creds = self
1561 .api_creds
1562 .as_ref()
1563 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1564
1565 let method = Method::POST;
1566 let endpoint = "/orders-scoring";
1567 let headers = create_l2_headers(
1568 signer,
1569 api_creds,
1570 method.as_str(),
1571 endpoint,
1572 Some(order_ids),
1573 )?;
1574
1575 let response = self
1576 .http_client
1577 .request(method, format!("{}{}", self.base_url, endpoint))
1578 .headers(
1579 headers
1580 .into_iter()
1581 .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1582 .collect(),
1583 )
1584 .json(order_ids)
1585 .send()
1586 .await
1587 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1588
1589 response
1590 .json::<std::collections::HashMap<String, bool>>()
1591 .await
1592 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1593 }
1594
1595 pub async fn get_sampling_markets(
1597 &self,
1598 next_cursor: Option<&str>,
1599 ) -> Result<crate::types::MarketsResponse> {
1600 let next_cursor = next_cursor.unwrap_or("MA=="); let response = self
1603 .http_client
1604 .get(format!("{}/sampling-markets", self.base_url))
1605 .query(&[("next_cursor", next_cursor)])
1606 .send()
1607 .await
1608 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1609
1610 response
1611 .json::<crate::types::MarketsResponse>()
1612 .await
1613 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1614 }
1615
1616 pub async fn get_sampling_simplified_markets(
1618 &self,
1619 next_cursor: Option<&str>,
1620 ) -> Result<crate::types::SimplifiedMarketsResponse> {
1621 let next_cursor = next_cursor.unwrap_or("MA=="); let response = self
1624 .http_client
1625 .get(format!("{}/sampling-simplified-markets", self.base_url))
1626 .query(&[("next_cursor", next_cursor)])
1627 .send()
1628 .await
1629 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1630
1631 response
1632 .json::<crate::types::SimplifiedMarketsResponse>()
1633 .await
1634 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1635 }
1636
1637 pub async fn get_markets(
1639 &self,
1640 next_cursor: Option<&str>,
1641 ) -> Result<crate::types::MarketsResponse> {
1642 let next_cursor = next_cursor.unwrap_or("MA=="); let response = self
1645 .http_client
1646 .get(format!("{}/markets", self.base_url))
1647 .query(&[("next_cursor", next_cursor)])
1648 .send()
1649 .await
1650 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1651
1652 response
1653 .json::<crate::types::MarketsResponse>()
1654 .await
1655 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1656 }
1657
1658 pub async fn get_simplified_markets(
1660 &self,
1661 next_cursor: Option<&str>,
1662 ) -> Result<crate::types::SimplifiedMarketsResponse> {
1663 let next_cursor = next_cursor.unwrap_or("MA=="); let response = self
1666 .http_client
1667 .get(format!("{}/simplified-markets", self.base_url))
1668 .query(&[("next_cursor", next_cursor)])
1669 .send()
1670 .await
1671 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1672
1673 response
1674 .json::<crate::types::SimplifiedMarketsResponse>()
1675 .await
1676 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1677 }
1678
1679 pub async fn get_market(&self, condition_id: &str) -> Result<crate::types::Market> {
1681 let response = self
1682 .http_client
1683 .get(format!("{}/markets/{}", self.base_url, condition_id))
1684 .send()
1685 .await
1686 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1687
1688 response
1689 .json::<crate::types::Market>()
1690 .await
1691 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1692 }
1693
1694 pub async fn get_market_trades_events(&self, condition_id: &str) -> Result<Value> {
1696 let response = self
1697 .http_client
1698 .get(format!(
1699 "{}/live-activity/events/{}",
1700 self.base_url, condition_id
1701 ))
1702 .send()
1703 .await
1704 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1705
1706 response
1707 .json::<Value>()
1708 .await
1709 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1710 }
1711}
1712
1713pub use crate::types::{
1715 ExtraOrderArgs, Market, MarketOrderArgs, MarketsResponse, MidpointResponse, NegRiskResponse,
1716 OrderBookSummary, OrderSummary, PriceResponse, Rewards, SpreadResponse, TickSizeResponse,
1717 Token,
1718};
1719
1720#[derive(Debug, Default)]
1722pub struct CreateOrderOptions {
1723 pub tick_size: Option<Decimal>,
1724 pub neg_risk: Option<bool>,
1725}
1726
1727pub type PolyfillClient = ClobClient;
1729
1730#[cfg(test)]
1731mod tests {
1732 use super::{ClobClient, OrderArgs as ClientOrderArgs};
1733 use crate::types::Side;
1734 use crate::{ApiCredentials, PolyfillError};
1735 use mockito::{Matcher, Server};
1736 use rust_decimal::Decimal;
1737 use std::str::FromStr;
1738 use tokio;
1739
1740 fn create_test_client(base_url: &str) -> ClobClient {
1741 ClobClient::new(base_url)
1742 }
1743
1744 fn create_test_client_with_auth(base_url: &str) -> ClobClient {
1745 ClobClient::with_l1_headers(
1746 base_url,
1747 "0x1234567890123456789012345678901234567890123456789012345678901234",
1748 137,
1749 )
1750 }
1751
1752 #[tokio::test]
1753 async fn test_client_creation() {
1754 let client = create_test_client("https://test.example.com");
1755 assert_eq!(client.base_url, "https://test.example.com");
1756 assert!(client.signer.is_none());
1757 assert!(client.api_creds.is_none());
1758 }
1759
1760 #[tokio::test]
1761 async fn test_client_with_l1_headers() {
1762 let client = create_test_client_with_auth("https://test.example.com");
1763 assert_eq!(client.base_url, "https://test.example.com");
1764 assert!(client.signer.is_some());
1765 assert_eq!(client.chain_id, 137);
1766 }
1767
1768 #[tokio::test]
1769 async fn test_client_with_l2_headers() {
1770 let api_creds = ApiCredentials {
1771 api_key: "test_key".to_string(),
1772 secret: "test_secret".to_string(),
1773 passphrase: "test_passphrase".to_string(),
1774 };
1775
1776 let client = ClobClient::with_l2_headers(
1777 "https://test.example.com",
1778 "0x1234567890123456789012345678901234567890123456789012345678901234",
1779 137,
1780 api_creds.clone(),
1781 );
1782
1783 assert_eq!(client.base_url, "https://test.example.com");
1784 assert!(client.signer.is_some());
1785 assert!(client.api_creds.is_some());
1786 assert_eq!(client.chain_id, 137);
1787 }
1788
1789 #[tokio::test]
1790 async fn test_set_api_creds() {
1791 let mut client = create_test_client("https://test.example.com");
1792 assert!(client.api_creds.is_none());
1793
1794 let api_creds = ApiCredentials {
1795 api_key: "test_key".to_string(),
1796 secret: "test_secret".to_string(),
1797 passphrase: "test_passphrase".to_string(),
1798 };
1799
1800 client.set_api_creds(api_creds.clone());
1801 assert!(client.api_creds.is_some());
1802 assert_eq!(client.api_creds.unwrap().api_key, "test_key");
1803 }
1804
1805 #[tokio::test]
1806 async fn test_get_sampling_markets_success() {
1807 let mut server = Server::new_async().await;
1808 let mock_response = r#"{
1809 "limit": "10",
1810 "count": "2",
1811 "next_cursor": null,
1812 "data": [
1813 {
1814 "condition_id": "0x123",
1815 "tokens": [
1816 {"token_id": "0x456", "outcome": "Yes"},
1817 {"token_id": "0x789", "outcome": "No"}
1818 ],
1819 "rewards": {
1820 "rates": null,
1821 "min_size": "1.0",
1822 "max_spread": "0.1",
1823 "event_start_date": null,
1824 "event_end_date": null,
1825 "in_game_multiplier": null,
1826 "reward_epoch": null
1827 },
1828 "min_incentive_size": null,
1829 "max_incentive_spread": null,
1830 "active": true,
1831 "closed": false,
1832 "question_id": "0x123",
1833 "minimum_order_size": "1.0",
1834 "minimum_tick_size": "0.01",
1835 "description": "Test market",
1836 "category": "test",
1837 "end_date_iso": null,
1838 "game_start_time": null,
1839 "question": "Will this test pass?",
1840 "market_slug": "test-market",
1841 "seconds_delay": "0",
1842 "icon": "",
1843 "fpmm": ""
1844 }
1845 ]
1846 }"#;
1847
1848 let mock = server
1849 .mock("GET", "/sampling-markets")
1850 .match_query(Matcher::UrlEncoded("next_cursor".into(), "MA==".into()))
1851 .with_status(200)
1852 .with_header("content-type", "application/json")
1853 .with_body(mock_response)
1854 .create_async()
1855 .await;
1856
1857 let client = create_test_client(&server.url());
1858 let result = client.get_sampling_markets(None).await;
1859
1860 mock.assert_async().await;
1861 assert!(result.is_ok());
1862 let markets = result.unwrap();
1863 assert_eq!(markets.data.len(), 1);
1864 assert_eq!(markets.data[0].question, "Will this test pass?");
1865 }
1866
1867 #[tokio::test]
1868 async fn test_get_sampling_markets_with_cursor() {
1869 let mut server = Server::new_async().await;
1870 let mock_response = r#"{
1871 "limit": "5",
1872 "count": "0",
1873 "next_cursor": null,
1874 "data": []
1875 }"#;
1876
1877 let mock = server
1878 .mock("GET", "/sampling-markets")
1879 .match_query(Matcher::AllOf(vec![Matcher::UrlEncoded(
1880 "next_cursor".into(),
1881 "test_cursor".into(),
1882 )]))
1883 .with_status(200)
1884 .with_header("content-type", "application/json")
1885 .with_body(mock_response)
1886 .create_async()
1887 .await;
1888
1889 let client = create_test_client(&server.url());
1890 let result = client.get_sampling_markets(Some("test_cursor")).await;
1891
1892 mock.assert_async().await;
1893 assert!(result.is_ok());
1894 let markets = result.unwrap();
1895 assert_eq!(markets.data.len(), 0);
1896 }
1897
1898 #[tokio::test]
1899 async fn test_get_order_book_success() {
1900 let mut server = Server::new_async().await;
1901 let mock_response = r#"{
1902 "market": "0x123",
1903 "asset_id": "0x123",
1904 "hash": "0xabc123",
1905 "timestamp": "1234567890",
1906 "bids": [
1907 {"price": "0.75", "size": "100.0"}
1908 ],
1909 "asks": [
1910 {"price": "0.76", "size": "50.0"}
1911 ]
1912 }"#;
1913
1914 let mock = server
1915 .mock("GET", "/book")
1916 .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into()))
1917 .with_status(200)
1918 .with_header("content-type", "application/json")
1919 .with_body(mock_response)
1920 .create_async()
1921 .await;
1922
1923 let client = create_test_client(&server.url());
1924 let result = client.get_order_book("0x123").await;
1925
1926 mock.assert_async().await;
1927 assert!(result.is_ok());
1928 let book = result.unwrap();
1929 assert_eq!(book.market, "0x123");
1930 assert_eq!(book.bids.len(), 1);
1931 assert_eq!(book.asks.len(), 1);
1932 }
1933
1934 #[tokio::test]
1935 async fn test_get_midpoint_success() {
1936 let mut server = Server::new_async().await;
1937 let mock_response = r#"{
1938 "mid": "0.755"
1939 }"#;
1940
1941 let mock = server
1942 .mock("GET", "/midpoint")
1943 .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into()))
1944 .with_status(200)
1945 .with_header("content-type", "application/json")
1946 .with_body(mock_response)
1947 .create_async()
1948 .await;
1949
1950 let client = create_test_client(&server.url());
1951 let result = client.get_midpoint("0x123").await;
1952
1953 mock.assert_async().await;
1954 assert!(result.is_ok());
1955 let response = result.unwrap();
1956 assert_eq!(response.mid, Decimal::from_str("0.755").unwrap());
1957 }
1958
1959 #[tokio::test]
1960 async fn test_get_spread_success() {
1961 let mut server = Server::new_async().await;
1962 let mock_response = r#"{
1963 "spread": "0.01"
1964 }"#;
1965
1966 let mock = server
1967 .mock("GET", "/spread")
1968 .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into()))
1969 .with_status(200)
1970 .with_header("content-type", "application/json")
1971 .with_body(mock_response)
1972 .create_async()
1973 .await;
1974
1975 let client = create_test_client(&server.url());
1976 let result = client.get_spread("0x123").await;
1977
1978 mock.assert_async().await;
1979 assert!(result.is_ok());
1980 let response = result.unwrap();
1981 assert_eq!(response.spread, Decimal::from_str("0.01").unwrap());
1982 }
1983
1984 #[tokio::test]
1985 async fn test_get_price_success() {
1986 let mut server = Server::new_async().await;
1987 let mock_response = r#"{
1988 "price": "0.76"
1989 }"#;
1990
1991 let mock = server
1992 .mock("GET", "/price")
1993 .match_query(Matcher::AllOf(vec![
1994 Matcher::UrlEncoded("token_id".into(), "0x123".into()),
1995 Matcher::UrlEncoded("side".into(), "BUY".into()),
1996 ]))
1997 .with_status(200)
1998 .with_header("content-type", "application/json")
1999 .with_body(mock_response)
2000 .create_async()
2001 .await;
2002
2003 let client = create_test_client(&server.url());
2004 let result = client.get_price("0x123", Side::BUY).await;
2005
2006 mock.assert_async().await;
2007 assert!(result.is_ok());
2008 let response = result.unwrap();
2009 assert_eq!(response.price, Decimal::from_str("0.76").unwrap());
2010 }
2011
2012 #[tokio::test]
2013 async fn test_get_tick_size_success() {
2014 let mut server = Server::new_async().await;
2015 let mock_response = r#"{
2016 "minimum_tick_size": "0.01"
2017 }"#;
2018
2019 let mock = server
2020 .mock("GET", "/tick-size")
2021 .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into()))
2022 .with_status(200)
2023 .with_header("content-type", "application/json")
2024 .with_body(mock_response)
2025 .create_async()
2026 .await;
2027
2028 let client = create_test_client(&server.url());
2029 let result = client.get_tick_size("0x123").await;
2030
2031 mock.assert_async().await;
2032 assert!(result.is_ok());
2033 let tick_size = result.unwrap();
2034 assert_eq!(tick_size, Decimal::from_str("0.01").unwrap());
2035 }
2036
2037 #[tokio::test]
2038 async fn test_get_neg_risk_success() {
2039 let mut server = Server::new_async().await;
2040 let mock_response = r#"{
2041 "neg_risk": false
2042 }"#;
2043
2044 let mock = server
2045 .mock("GET", "/neg-risk")
2046 .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into()))
2047 .with_status(200)
2048 .with_header("content-type", "application/json")
2049 .with_body(mock_response)
2050 .create_async()
2051 .await;
2052
2053 let client = create_test_client(&server.url());
2054 let result = client.get_neg_risk("0x123").await;
2055
2056 mock.assert_async().await;
2057 assert!(result.is_ok());
2058 let neg_risk = result.unwrap();
2059 assert!(!neg_risk);
2060 }
2061
2062 #[tokio::test]
2063 async fn test_api_error_handling() {
2064 let mut server = Server::new_async().await;
2065
2066 let mock = server
2067 .mock("GET", "/book")
2068 .match_query(Matcher::UrlEncoded(
2069 "token_id".into(),
2070 "invalid_token".into(),
2071 ))
2072 .with_status(404)
2073 .with_header("content-type", "application/json")
2074 .with_body(r#"{"error": "Market not found"}"#)
2075 .create_async()
2076 .await;
2077
2078 let client = create_test_client(&server.url());
2079 let result = client.get_order_book("invalid_token").await;
2080
2081 mock.assert_async().await;
2082 assert!(result.is_err());
2083
2084 let error = result.unwrap_err();
2085 assert!(
2087 matches!(error, PolyfillError::Network { .. })
2088 || matches!(error, PolyfillError::Api { .. })
2089 );
2090 }
2091
2092 #[tokio::test]
2093 async fn test_network_error_handling() {
2094 let client = create_test_client("http://invalid-host-that-does-not-exist.com");
2096 let result = client.get_order_book("0x123").await;
2097
2098 assert!(result.is_err());
2099 let error = result.unwrap_err();
2100 assert!(matches!(error, PolyfillError::Network { .. }));
2101 }
2102
2103 #[test]
2104 fn test_client_url_validation() {
2105 let client = create_test_client("https://test.example.com");
2106 assert_eq!(client.base_url, "https://test.example.com");
2107
2108 let client2 = create_test_client("http://localhost:8080");
2109 assert_eq!(client2.base_url, "http://localhost:8080");
2110 }
2111
2112 #[tokio::test]
2113 async fn test_get_midpoints_batch() {
2114 let mut server = Server::new_async().await;
2115 let mock_response = r#"{
2116 "0x123": "0.755",
2117 "0x456": "0.623"
2118 }"#;
2119
2120 let mock = server
2121 .mock("POST", "/midpoints")
2122 .with_header("content-type", "application/json")
2123 .with_status(200)
2124 .with_header("content-type", "application/json")
2125 .with_body(mock_response)
2126 .create_async()
2127 .await;
2128
2129 let client = create_test_client(&server.url());
2130 let token_ids = vec!["0x123".to_string(), "0x456".to_string()];
2131 let result = client.get_midpoints(&token_ids).await;
2132
2133 mock.assert_async().await;
2134 assert!(result.is_ok());
2135 let midpoints = result.unwrap();
2136 assert_eq!(midpoints.len(), 2);
2137 assert_eq!(
2138 midpoints.get("0x123").unwrap(),
2139 &Decimal::from_str("0.755").unwrap()
2140 );
2141 assert_eq!(
2142 midpoints.get("0x456").unwrap(),
2143 &Decimal::from_str("0.623").unwrap()
2144 );
2145 }
2146
2147 #[test]
2148 fn test_client_configuration() {
2149 let client = create_test_client("https://test.example.com");
2150
2151 assert!(client.signer.is_none());
2153 assert!(client.api_creds.is_none());
2154
2155 let auth_client = create_test_client_with_auth("https://test.example.com");
2157 assert!(auth_client.signer.is_some());
2158 assert_eq!(auth_client.chain_id, 137);
2159 }
2160
2161 #[tokio::test]
2162 async fn test_get_ok() {
2163 let mut server = Server::new_async().await;
2164 let mock_response = r#"{"status": "ok"}"#;
2165
2166 let mock = server
2167 .mock("GET", "/ok")
2168 .with_header("content-type", "application/json")
2169 .with_status(200)
2170 .with_body(mock_response)
2171 .create_async()
2172 .await;
2173
2174 let client = create_test_client(&server.url());
2175 let result = client.get_ok().await;
2176
2177 mock.assert_async().await;
2178 assert!(result);
2179 }
2180
2181 #[tokio::test]
2182 async fn test_get_prices_batch() {
2183 let mut server = Server::new_async().await;
2184 let mock_response = r#"{
2185 "0x123": {
2186 "BUY": "0.755",
2187 "SELL": "0.745"
2188 },
2189 "0x456": {
2190 "BUY": "0.623",
2191 "SELL": "0.613"
2192 }
2193 }"#;
2194
2195 let mock = server
2196 .mock("POST", "/prices")
2197 .with_header("content-type", "application/json")
2198 .with_status(200)
2199 .with_body(mock_response)
2200 .create_async()
2201 .await;
2202
2203 let client = create_test_client(&server.url());
2204 let book_params = vec![
2205 crate::types::BookParams {
2206 token_id: "0x123".to_string(),
2207 side: Side::BUY,
2208 },
2209 crate::types::BookParams {
2210 token_id: "0x456".to_string(),
2211 side: Side::SELL,
2212 },
2213 ];
2214 let result = client.get_prices(&book_params).await;
2215
2216 mock.assert_async().await;
2217 assert!(result.is_ok());
2218 let prices = result.unwrap();
2219 assert_eq!(prices.len(), 2);
2220 assert!(prices.contains_key("0x123"));
2221 assert!(prices.contains_key("0x456"));
2222 }
2223
2224 #[tokio::test]
2225 async fn test_get_server_time() {
2226 let mut server = Server::new_async().await;
2227 let mock_response = "1234567890"; let mock = server
2230 .mock("GET", "/time")
2231 .with_status(200)
2232 .with_body(mock_response)
2233 .create_async()
2234 .await;
2235
2236 let client = create_test_client(&server.url());
2237 let result = client.get_server_time().await;
2238
2239 mock.assert_async().await;
2240 assert!(result.is_ok());
2241 let timestamp = result.unwrap();
2242 assert_eq!(timestamp, 1234567890);
2243 }
2244
2245 #[tokio::test]
2246 async fn test_create_or_derive_api_key() {
2247 let mut server = Server::new_async().await;
2248 let mock_response = r#"{
2249 "apiKey": "test-api-key-123",
2250 "secret": "test-secret-456",
2251 "passphrase": "test-passphrase"
2252 }"#;
2253
2254 let create_mock = server
2256 .mock("POST", "/auth/api-key")
2257 .with_header("content-type", "application/json")
2258 .with_status(200)
2259 .with_body(mock_response)
2260 .create_async()
2261 .await;
2262
2263 let client = create_test_client_with_auth(&server.url());
2264 let result = client.create_or_derive_api_key(None).await;
2265
2266 create_mock.assert_async().await;
2267 assert!(result.is_ok());
2268 let api_creds = result.unwrap();
2269 assert_eq!(api_creds.api_key, "test-api-key-123");
2270 }
2271
2272 #[tokio::test]
2273 async fn test_get_order_books_batch() {
2274 let mut server = Server::new_async().await;
2275 let mock_response = r#"[
2276 {
2277 "market": "0x123",
2278 "asset_id": "0x123",
2279 "hash": "test-hash",
2280 "timestamp": "1234567890",
2281 "bids": [{"price": "0.75", "size": "100.0"}],
2282 "asks": [{"price": "0.76", "size": "50.0"}]
2283 }
2284 ]"#;
2285
2286 let mock = server
2287 .mock("POST", "/books")
2288 .with_header("content-type", "application/json")
2289 .with_status(200)
2290 .with_body(mock_response)
2291 .create_async()
2292 .await;
2293
2294 let client = create_test_client(&server.url());
2295 let token_ids = vec!["0x123".to_string()];
2296 let result = client.get_order_books(&token_ids).await;
2297
2298 mock.assert_async().await;
2299 if let Err(e) = &result {
2300 println!("Error: {:?}", e);
2301 }
2302 assert!(result.is_ok());
2303 let books = result.unwrap();
2304 assert_eq!(books.len(), 1);
2305 }
2306
2307 #[tokio::test]
2308 async fn test_order_args_creation() {
2309 let order_args = ClientOrderArgs::new(
2311 "0x123",
2312 Decimal::from_str("0.75").unwrap(),
2313 Decimal::from_str("100.0").unwrap(),
2314 Side::BUY,
2315 );
2316
2317 assert_eq!(order_args.token_id, "0x123");
2318 assert_eq!(order_args.price, Decimal::from_str("0.75").unwrap());
2319 assert_eq!(order_args.size, Decimal::from_str("100.0").unwrap());
2320 assert_eq!(order_args.side, Side::BUY);
2321
2322 let default_args = ClientOrderArgs::default();
2324 assert_eq!(default_args.token_id, "");
2325 assert_eq!(default_args.price, Decimal::ZERO);
2326 assert_eq!(default_args.size, Decimal::ZERO);
2327 assert_eq!(default_args.side, Side::BUY);
2328 }
2329}