1use crate::constants::{END_CURSOR, INITIAL_CURSOR};
2use crate::endpoints::*;
3use crate::errors::ClobError;
4use crate::http_helpers::RequestOptions;
6use crate::order_builder::{BuilderConfig as ObBuilderConfig, OrderBuilder};
7use crate::signer_adapter::EthersSigner;
8use crate::types::OrderBookSummary;
9use crate::types::{ApiKeyCreds, ApiKeyRaw};
10use crate::types::{DeleteReadonlyApiKeyRequest, ReadonlyApiKeyResponse};
11use crate::types::{HeartbeatResponse, PostOrdersArgs};
12use crate::types::{
13 Notification, OpenOrder, Order, OrderResponse, OrderType, Reward, SignedOrder, Trade,
14 UserMarketOrder, UserOrder,
15};
16use serde::Deserialize;
18use serde_json::Value;
19
20#[allow(dead_code)]
21#[derive(Deserialize)]
22struct OkResp {
23 ok: bool,
24}
25
26#[allow(dead_code)]
27#[derive(Deserialize)]
28struct SuccessResp {
29 success: bool,
30}
31#[derive(Deserialize)]
33#[serde(untagged)]
34enum MaybeVec<T> {
35 Vec(Vec<T>),
36 Data { data: Vec<T> },
37}
38
39impl<T> MaybeVec<T> {
40 fn into_vec(self) -> Vec<T> {
41 match self {
42 MaybeVec::Vec(v) => v,
43 MaybeVec::Data { data } => data,
44 }
45 }
46}
47
48#[derive(Deserialize)]
49#[serde(untagged)]
50enum MaybeItem<T> {
51 Item(T),
52 Data { data: T },
53}
54
55impl<T> MaybeItem<T> {
56 fn into_item(self) -> T {
57 match self {
58 MaybeItem::Item(i) => i,
59 MaybeItem::Data { data } => data,
60 }
61 }
62}
63use std::collections::HashMap;
64use std::sync::Arc;
65
66#[derive(Clone, Debug, serde::Serialize)]
69pub struct OrderPayloadParity {
70 #[serde(rename = "orderID")]
71 pub order_id: String,
72}
73
74impl OrderPayloadParity {
75 pub fn new(order_id: impl Into<String>) -> Self {
76 Self {
77 order_id: order_id.into(),
78 }
79 }
80}
81
82pub struct ClobClient {
83 pub host: String,
84 pub chain_id: i64,
85 pub http_client: reqwest::Client,
91 pub signer: Option<Arc<EthersSigner>>,
92 pub creds: Option<ApiKeyCreds>,
93 pub use_server_time: bool,
94 pub geo_block_token: Option<String>,
99 pub tick_sizes: HashMap<String, String>,
100 pub neg_risk: HashMap<String, bool>,
101 pub fee_rates: HashMap<String, f64>,
102 pub builder_signer: Option<builder_signing_sdk_rs::BuilderSigner>,
104 pub builder_config: Option<ObBuilderConfig>,
106}
107
108#[derive(Clone, Debug, serde::Serialize)]
110pub struct OrderMarketCancelParams {
111 #[serde(skip_serializing_if = "Option::is_none")]
112 pub market: Option<String>,
113 #[serde(skip_serializing_if = "Option::is_none")]
114 pub asset_id: Option<String>,
115}
116
117impl ClobClient {
118 #[allow(non_snake_case)]
125 pub async fn getMarkets(
126 &self,
127 params: Option<std::collections::HashMap<String, String>>,
128 ) -> Result<Vec<crate::types::Market>, ClobError> {
129 self.get_markets(params).await
130 }
131
132 #[allow(non_snake_case)]
134 pub async fn getMarket(
135 &self,
136 market_id: &str,
137 params: Option<std::collections::HashMap<String, String>>,
138 ) -> Result<crate::types::Market, ClobError> {
139 self.get_market(market_id, params).await
140 }
141
142 #[allow(non_snake_case)]
143 pub async fn getOrderBook(&mut self, token_id: &str) -> Result<OrderBookSummary, ClobError> {
144 self.get_order_book(token_id).await
145 }
146
147 #[allow(non_snake_case)]
148 pub async fn getTickSize(&mut self, token_id: &str) -> Result<String, ClobError> {
149 self.get_tick_size(token_id).await
150 }
151
152 #[allow(non_snake_case)]
153 pub async fn getNegRisk(&mut self, token_id: &str) -> Result<bool, ClobError> {
154 self.get_neg_risk(token_id).await
155 }
156
157 #[allow(non_snake_case)]
158 pub async fn getFeeRate(&mut self, token_id: &str) -> Result<f64, ClobError> {
159 self.get_fee_rate(token_id).await
160 }
161
162 #[allow(non_snake_case)]
163 pub async fn getOpenOrders(
164 &self,
165 params: Option<std::collections::HashMap<String, String>>,
166 ) -> Result<Vec<SignedOrder>, ClobError> {
167 self.get_open_orders(params).await
168 }
169
170 #[allow(non_snake_case)]
171 pub async fn getOrder(&self, order_id: &str) -> Result<OpenOrder, ClobError> {
172 self.get_order(order_id).await
173 }
174
175 #[allow(non_snake_case)]
176 pub async fn postSignedOrder(
177 &self,
178 signed_order: &SignedOrder,
179 ) -> Result<OrderResponse, ClobError> {
180 self.post_signed_order(signed_order, OrderType::GTC, false, None)
181 .await
182 }
183
184 #[allow(non_snake_case)]
185 pub async fn postOrders(
186 &self,
187 orders: Vec<PostOrdersArgs>,
188 defer_exec: bool,
189 default_post_only: bool,
190 ) -> Result<Vec<Order>, ClobError> {
191 self.post_orders(orders, defer_exec, default_post_only)
192 .await
193 }
194
195 #[allow(non_snake_case)]
196 pub async fn createOrder(
197 &mut self,
198 user_order: UserOrder,
199 options_tick: Option<&str>,
200 ) -> Result<SignedOrder, ClobError> {
201 self.create_order(user_order, options_tick).await
202 }
203
204 #[allow(non_snake_case)]
205 pub async fn createMarketOrder(
206 &mut self,
207 user_market_order: UserMarketOrder,
208 options_tick: Option<&str>,
209 ) -> Result<SignedOrder, ClobError> {
210 self.create_market_order(user_market_order, options_tick)
211 .await
212 }
213
214 #[allow(non_snake_case)]
215 pub async fn createAndPostOrder(
216 &mut self,
217 user_order: UserOrder,
218 options_tick: Option<&str>,
219 order_type: Option<OrderType>,
220 defer_exec: bool,
221 post_only: bool,
222 ) -> Result<OrderResponse, ClobError> {
223 self.create_and_post_order(
224 user_order,
225 options_tick,
226 order_type,
227 defer_exec,
228 Some(post_only),
229 )
230 .await
231 }
232
233 #[allow(non_snake_case)]
234 pub async fn createAndPostMarketOrder(
235 &mut self,
236 user_market_order: UserMarketOrder,
237 options_tick: Option<&str>,
238 order_type: Option<OrderType>,
239 defer_exec: bool,
240 ) -> Result<OrderResponse, ClobError> {
241 self.create_and_post_market_order(user_market_order, options_tick, order_type, defer_exec)
242 .await
243 }
244
245 #[allow(non_snake_case)]
247 pub async fn cancelOrder(&self, payload: OrderPayloadParity) -> Result<Value, ClobError> {
248 self.cancel_order_payload_raw(payload).await
249 }
250
251 #[allow(non_snake_case)]
252 pub async fn cancelOrders(&self, order_ids: Vec<String>) -> Result<Vec<Order>, ClobError> {
253 self.cancel_orders(order_ids).await
254 }
255
256 #[allow(non_snake_case)]
257 pub async fn cancelAll(&self) -> Result<Vec<Order>, ClobError> {
258 self.cancel_all().await
259 }
260
261 #[allow(non_snake_case)]
262 pub async fn cancelMarketOrders(
263 &self,
264 payload: OrderMarketCancelParams,
265 ) -> Result<Vec<Order>, ClobError> {
266 self.cancel_market_orders(payload).await
267 }
268
269 #[allow(non_snake_case)]
270 pub async fn isOrderScoring(
271 &self,
272 params: Option<std::collections::HashMap<String, String>>,
273 ) -> Result<crate::types::OrderScoring, ClobError> {
274 self.is_order_scoring(params).await
275 }
276
277 #[allow(non_snake_case)]
278 pub async fn areOrdersScoring(
279 &self,
280 order_ids: Option<Vec<String>>,
281 ) -> Result<crate::types::OrdersScoring, ClobError> {
282 self.are_orders_scoring(order_ids).await
283 }
284
285 #[allow(non_snake_case)]
286 pub async fn getTrades(
287 &self,
288 params: Option<std::collections::HashMap<String, String>>,
289 only_first_page: bool,
290 next_cursor: Option<String>,
291 ) -> Result<Vec<Value>, ClobError> {
292 self.get_trades(params, only_first_page, next_cursor).await
293 }
294
295 #[allow(non_snake_case)]
296 pub async fn getTradesTyped(
297 &self,
298 params: Option<std::collections::HashMap<String, String>>,
299 only_first_page: bool,
300 next_cursor: Option<String>,
301 ) -> Result<Vec<Trade>, ClobError> {
302 self.get_trades_typed(params, only_first_page, next_cursor)
303 .await
304 }
305
306 #[allow(non_snake_case)]
307 pub async fn getNotifications(&self) -> Result<Vec<Notification>, ClobError> {
308 self.get_notifications().await
309 }
310
311 #[allow(non_snake_case)]
312 pub async fn dropNotifications(&self, ids: Option<&Vec<String>>) -> Result<(), ClobError> {
313 self.drop_notifications(ids).await
314 }
315
316 #[allow(non_snake_case)]
317 pub async fn getBalanceAllowance(
318 &self,
319 params: Option<std::collections::HashMap<String, String>>,
320 ) -> Result<crate::types::BalanceAllowanceResponse, ClobError> {
321 self.get_balance_allowance(params).await
322 }
323
324 #[allow(non_snake_case)]
325 pub async fn updateBalanceAllowance(
326 &self,
327 params: Option<std::collections::HashMap<String, String>>,
328 ) -> Result<(), ClobError> {
329 self.update_balance_allowance(params).await
330 }
331
332 #[allow(non_snake_case)]
333 pub async fn getTime(&self) -> Result<u64, ClobError> {
334 self.get_server_time().await
335 }
336
337 #[allow(non_snake_case)]
339 pub async fn getApiKeys(&self) -> Result<Vec<crate::types::ApiKeyCreds>, ClobError> {
340 self.get_api_keys().await
341 }
342 #[allow(non_snake_case)]
343 pub async fn deleteApiKey(&self) -> Result<(), ClobError> {
344 self.delete_api_key().await
345 }
346 #[allow(non_snake_case)]
347 pub async fn getClosedOnlyMode(&self) -> Result<crate::types::BanStatus, ClobError> {
348 self.get_closed_only_mode().await
349 }
350
351 #[allow(non_snake_case)]
353 pub async fn createApiKey(&self, nonce: Option<u64>) -> Result<ApiKeyCreds, ClobError> {
354 self.create_api_key(nonce).await
355 }
356 #[allow(non_snake_case)]
357 pub async fn deriveApiKey(
358 &self,
359 params: Option<std::collections::HashMap<String, String>>,
360 ) -> Result<ApiKeyCreds, ClobError> {
361 self.derive_api_key(params).await
362 }
363
364 #[allow(non_snake_case)]
366 pub async fn createBuilderApiKey(&self) -> Result<ApiKeyCreds, ClobError> {
367 self.create_builder_api_key().await
368 }
369 #[allow(non_snake_case)]
370 pub async fn getBuilderApiKeys(&self) -> Result<Vec<ApiKeyCreds>, ClobError> {
371 self.get_builder_api_keys().await
372 }
373 #[allow(non_snake_case)]
374 pub async fn revokeBuilderApiKey(&self) -> Result<(), ClobError> {
375 self.revoke_builder_api_key().await
376 }
377
378 #[allow(non_snake_case)]
380 pub async fn getSimplifiedMarkets(
381 &self,
382 params: Option<std::collections::HashMap<String, String>>,
383 ) -> Result<Vec<crate::types::Market>, ClobError> {
384 self.get_simplified_markets(params).await
385 }
386 #[allow(non_snake_case)]
387 pub async fn getSamplingMarkets(
388 &self,
389 params: Option<std::collections::HashMap<String, String>>,
390 ) -> Result<Vec<crate::types::Market>, ClobError> {
391 self.get_sampling_markets(params).await
392 }
393 #[allow(non_snake_case)]
394 pub async fn getSamplingSimplifiedMarkets(
395 &self,
396 params: Option<std::collections::HashMap<String, String>>,
397 ) -> Result<Vec<crate::types::Market>, ClobError> {
398 self.get_sampling_simplified_markets(params).await
399 }
400 #[allow(non_snake_case)]
401 pub async fn getOrderBooks(
402 &self,
403 params: &[crate::types::BookParams],
404 ) -> Result<Vec<crate::types::OrderBookSummary>, ClobError> {
405 self.get_order_books(params).await
406 }
407 #[allow(non_snake_case)]
408 pub async fn getMidpoint(
409 &self,
410 params: Option<std::collections::HashMap<String, String>>,
411 ) -> Result<crate::types::MidpointResponse, ClobError> {
412 self.get_midpoint(params).await
413 }
414 #[allow(non_snake_case)]
415 pub async fn getMidpoints(
416 &self,
417 params: &[crate::types::BookParams],
418 ) -> Result<serde_json::Value, ClobError> {
419 self.get_midpoints(params).await
420 }
421 #[allow(non_snake_case)]
422 pub async fn getPrices(
423 &self,
424 params: &[crate::types::BookParams],
425 ) -> Result<serde_json::Value, ClobError> {
426 self.get_prices(params).await
427 }
428 #[allow(non_snake_case)]
429 pub async fn getSpreads(
430 &self,
431 params: &[crate::types::BookParams],
432 ) -> Result<serde_json::Value, ClobError> {
433 self.get_spreads(params).await
434 }
435 #[allow(non_snake_case)]
436 pub async fn getLastTradesPrices(
437 &self,
438 params: &[crate::types::BookParams],
439 ) -> Result<serde_json::Value, ClobError> {
440 self.get_last_trades_prices(params).await
441 }
442 #[allow(non_snake_case)]
443 pub async fn getPricesHistory(
444 &self,
445 params: Option<std::collections::HashMap<String, String>>,
446 ) -> Result<Vec<crate::types::MarketPrice>, ClobError> {
447 self.get_prices_history(params).await
448 }
449 #[allow(non_snake_case)]
450 pub async fn getMarketTradesEvents(
451 &self,
452 market_id: &str,
453 params: Option<std::collections::HashMap<String, String>>,
454 ) -> Result<Vec<crate::types::Trade>, ClobError> {
455 self.get_market_trades_events(market_id, params).await
456 }
457
458 #[allow(non_snake_case)]
460 pub async fn getBuilderTrades(
461 &self,
462 params: Option<std::collections::HashMap<String, String>>,
463 ) -> Result<Vec<crate::types::BuilderTrade>, ClobError> {
464 self.get_builder_trades(params).await
465 }
466
467 #[allow(non_snake_case)]
469 pub async fn getEarningsForUserForDay(
470 &self,
471 params: Option<std::collections::HashMap<String, String>>,
472 ) -> Result<Vec<crate::types::Reward>, ClobError> {
473 self.get_earnings_for_user_for_day(params).await
474 }
475 #[allow(non_snake_case)]
476 pub async fn getEarningsForUserForDayTyped(
477 &self,
478 params: Option<std::collections::HashMap<String, String>>,
479 ) -> Result<Vec<crate::types::Reward>, ClobError> {
480 self.get_rewards_user_for_day_typed(params).await
481 }
482 #[allow(non_snake_case)]
483 pub async fn getTotalEarningsForUserForDay(
484 &self,
485 params: Option<std::collections::HashMap<String, String>>,
486 ) -> Result<Vec<crate::types::Reward>, ClobError> {
487 self.get_total_earnings_for_user_for_day(params).await
488 }
489 #[allow(non_snake_case)]
490 pub async fn getLiquidityRewardPercentages(
491 &self,
492 params: Option<std::collections::HashMap<String, String>>,
493 ) -> Result<std::collections::HashMap<String, f64>, ClobError> {
494 self.get_liquidity_reward_percentages(params).await
495 }
496 #[allow(non_snake_case)]
497 pub async fn getRewardsMarketsCurrent(
498 &self,
499 params: Option<std::collections::HashMap<String, String>>,
500 ) -> Result<Vec<crate::types::RewardsMarket>, ClobError> {
501 self.get_rewards_markets_current(params).await
502 }
503 #[allow(non_snake_case)]
504 pub async fn getRewardsEarningsPercentages(
505 &self,
506 params: Option<std::collections::HashMap<String, String>>,
507 ) -> Result<Vec<crate::types::Reward>, ClobError> {
508 self.get_rewards_earnings_percentages(params).await
509 }
510 pub fn new(
517 host: &str,
518 chain_id: i64,
519 signer: Option<Arc<EthersSigner>>,
520 creds: Option<ApiKeyCreds>,
521 use_server_time: bool,
522 http_client: Option<reqwest::Client>,
523 ) -> Self {
524 Self {
525 host: if let Some(stripped) = host.strip_suffix('/') {
526 stripped.to_string()
527 } else {
528 host.to_string()
529 },
530 chain_id,
531 http_client: http_client
532 .unwrap_or_else(|| crate::http_helpers::default_client().clone()),
533 signer,
534 creds,
535 use_server_time,
536 geo_block_token: None,
537 tick_sizes: HashMap::new(),
538 neg_risk: HashMap::new(),
539 fee_rates: HashMap::new(),
540 builder_signer: None,
541 builder_config: None,
542 }
543 }
544
545 pub fn with_geo_block_token(mut self, token: String) -> Self {
546 self.geo_block_token = Some(token);
547 self
548 }
549
550 pub fn set_geo_block_token(&mut self, token: Option<String>) {
551 self.geo_block_token = token;
552 }
553
554 fn attach_geo_request_options<B>(
555 &self,
556 options: Option<RequestOptions<B>>,
557 ) -> Option<RequestOptions<B>> {
558 let tok = match self
559 .geo_block_token
560 .as_ref()
561 .filter(|t| !t.trim().is_empty())
562 {
563 Some(t) => t.clone(),
564 None => return options,
565 };
566
567 let mut opts = options.unwrap_or(RequestOptions {
568 headers: None,
569 data: None,
570 params: None,
571 });
572
573 let params = opts
574 .params
575 .get_or_insert_with(std::collections::HashMap::new);
576 params.insert("geo_block_token".to_string(), tok);
579 Some(opts)
580 }
581
582 async fn http_get(
583 &self,
584 endpoint: &str,
585 options: Option<RequestOptions<serde_json::Value>>,
586 ) -> Result<serde_json::Value, ClobError> {
587 crate::http_helpers::get(
588 &self.http_client,
589 endpoint,
590 self.attach_geo_request_options(options),
591 )
592 .await
593 }
594
595 async fn http_post(
596 &self,
597 endpoint: &str,
598 options: Option<RequestOptions<serde_json::Value>>,
599 ) -> Result<serde_json::Value, ClobError> {
600 crate::http_helpers::post(
601 &self.http_client,
602 endpoint,
603 self.attach_geo_request_options(options),
604 )
605 .await
606 }
607
608 async fn http_get_typed<R>(
609 &self,
610 endpoint: &str,
611 options: Option<RequestOptions<serde_json::Value>>,
612 ) -> Result<R, ClobError>
613 where
614 R: serde::de::DeserializeOwned,
615 {
616 crate::http_helpers::get_typed(
617 &self.http_client,
618 endpoint,
619 self.attach_geo_request_options(options),
620 )
621 .await
622 }
623
624 async fn http_post_typed<R, B>(
625 &self,
626 endpoint: &str,
627 options: Option<RequestOptions<B>>,
628 ) -> Result<R, ClobError>
629 where
630 R: serde::de::DeserializeOwned,
631 B: serde::Serialize,
632 {
633 crate::http_helpers::post_typed(
634 &self.http_client,
635 endpoint,
636 self.attach_geo_request_options(options),
637 )
638 .await
639 }
640
641 async fn http_del_typed<R, B>(
642 &self,
643 endpoint: &str,
644 options: Option<RequestOptions<B>>,
645 ) -> Result<R, ClobError>
646 where
647 R: serde::de::DeserializeOwned,
648 B: serde::Serialize,
649 {
650 crate::http_helpers::del_typed(
651 &self.http_client,
652 endpoint,
653 self.attach_geo_request_options(options),
654 )
655 .await
656 }
657
658 pub fn with_builder_signer(
660 mut self,
661 key: String,
662 secret_b64: String,
663 passphrase: String,
664 ) -> Self {
665 let creds = builder_signing_sdk_rs::BuilderApiKeyCreds {
666 key,
667 secret: secret_b64,
668 passphrase,
669 };
670 self.builder_signer = Some(builder_signing_sdk_rs::BuilderSigner::new(creds));
671 self
672 }
673
674 pub fn with_builder_config(mut self, cfg: ObBuilderConfig) -> Self {
676 self.builder_config = Some(cfg);
677 self
678 }
679
680 pub fn rfq(&mut self) -> crate::rfq_client::RfqClient<'_> {
686 crate::rfq_client::RfqClient::new(self)
687 }
688
689 pub async fn get_order_book(&mut self, token_id: &str) -> Result<OrderBookSummary, ClobError> {
690 let endpoint = format!("{}{}", self.host, GET_ORDER_BOOK);
691 let mut params = std::collections::HashMap::new();
692 params.insert("token_id".to_string(), token_id.to_string());
693 let opts = RequestOptions {
694 headers: None,
695 data: None,
696 params: Some(params),
697 };
698 let val = self.http_get(&endpoint, Some(opts)).await?;
699 let obs: OrderBookSummary =
700 serde_json::from_value(val).map_err(|e| ClobError::Other(e.to_string()))?;
701 Ok(obs)
702 }
703
704 pub async fn get_tick_size(&mut self, token_id: &str) -> Result<String, ClobError> {
705 if let Some(v) = self.tick_sizes.get(token_id) {
706 return Ok(v.clone());
707 }
708 let endpoint = format!("{}{}", self.host, GET_TICK_SIZE);
709 let mut params = std::collections::HashMap::new();
710 params.insert("token_id".to_string(), token_id.to_string());
711 let opts = RequestOptions {
712 headers: None,
713 data: None,
714 params: Some(params),
715 };
716 let val = self.http_get(&endpoint, Some(opts)).await?;
717 let tick_val = val
718 .get("minimum_tick_size")
719 .ok_or(ClobError::Other("invalid tick response".to_string()))?;
720 let tick = match tick_val {
722 serde_json::Value::String(s) => s.clone(),
723 serde_json::Value::Number(n) => n.to_string(),
724 _ => return Err(ClobError::Other("invalid tick response".to_string())),
725 };
726 self.tick_sizes.insert(token_id.to_string(), tick.clone());
727 Ok(tick)
728 }
729
730 pub async fn get_neg_risk(&mut self, token_id: &str) -> Result<bool, ClobError> {
731 if let Some(v) = self.neg_risk.get(token_id) {
732 return Ok(*v);
733 }
734 let endpoint = format!("{}{}", self.host, GET_NEG_RISK);
735 let mut params = std::collections::HashMap::new();
736 params.insert("token_id".to_string(), token_id.to_string());
737 let opts = RequestOptions {
738 headers: None,
739 data: None,
740 params: Some(params),
741 };
742 let val = self.http_get(&endpoint, Some(opts)).await?;
743 let rr = val
744 .get("neg_risk")
745 .and_then(|v| v.as_bool())
746 .ok_or(ClobError::Other("invalid neg risk response".to_string()))?;
747 self.neg_risk.insert(token_id.to_string(), rr);
748 Ok(rr)
749 }
750
751 pub async fn get_fee_rate(&mut self, token_id: &str) -> Result<f64, ClobError> {
752 if let Some(v) = self.fee_rates.get(token_id) {
753 return Ok(*v);
754 }
755 let endpoint = format!("{}{}", self.host, GET_FEE_RATE);
756 let mut params = std::collections::HashMap::new();
757 params.insert("token_id".to_string(), token_id.to_string());
758 let opts = RequestOptions {
759 headers: None,
760 data: None,
761 params: Some(params),
762 };
763 let val = self.http_get(&endpoint, Some(opts)).await?;
764 let fee = val
765 .get("base_fee")
766 .and_then(|v| v.as_f64())
767 .ok_or(ClobError::Other("invalid fee response".to_string()))?;
768 self.fee_rates.insert(token_id.to_string(), fee);
769 Ok(fee)
770 }
771
772 pub async fn post_order(&self, signed_order: &SignedOrder) -> Result<OrderResponse, ClobError> {
776 self.post_signed_order(signed_order, OrderType::GTC, false, None)
778 .await
779 }
780
781 pub async fn get_api_keys(&self) -> Result<Vec<crate::types::ApiKeyCreds>, ClobError> {
782 if self.creds.is_none() {
783 return Err(ClobError::Other("L2 creds required".to_string()));
784 }
785 let signer_arc = self
786 .signer
787 .as_ref()
788 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
789 let signer_ref: &EthersSigner = signer_arc.as_ref();
790 let endpoint = format!("{}{}", self.host, GET_API_KEYS);
791 let ts = if self.use_server_time {
792 Some(self.get_server_time().await?)
793 } else {
794 None
795 };
796 let headers = crate::headers::create_l2_headers(
797 signer_ref,
798 self.creds.as_ref().unwrap(),
799 "GET",
800 GET_API_KEYS,
801 None,
802 ts,
803 )
804 .await?;
805 let resp: crate::types::ApiKeysResponse = self
806 .http_get_typed(
807 &endpoint,
808 Some(RequestOptions::<Value> {
809 headers: Some(headers),
810 data: None,
811 params: None,
812 }),
813 )
814 .await?;
815 Ok(resp.api_keys)
816 }
817
818 pub async fn create_readonly_api_key(&self) -> Result<ReadonlyApiKeyResponse, ClobError> {
822 if self.creds.is_none() {
823 return Err(ClobError::Other("L2 creds required".to_string()));
824 }
825 let signer_arc = self
826 .signer
827 .as_ref()
828 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
829 let signer_ref: &EthersSigner = signer_arc.as_ref();
830
831 let ts = if self.use_server_time {
832 Some(self.get_server_time().await?)
833 } else {
834 None
835 };
836 let headers = crate::headers::create_l2_headers(
837 signer_ref,
838 self.creds.as_ref().unwrap(),
839 "POST",
840 CREATE_READONLY_API_KEY,
841 None,
842 ts,
843 )
844 .await?;
845
846 let endpoint = format!("{}{}", self.host, CREATE_READONLY_API_KEY);
847 let resp: ReadonlyApiKeyResponse = self
848 .http_post_typed(
849 &endpoint,
850 Some(RequestOptions::<Value> {
851 headers: Some(headers),
852 data: None,
853 params: None,
854 }),
855 )
856 .await?;
857 Ok(resp)
858 }
859
860 pub async fn get_readonly_api_keys(&self) -> Result<Vec<String>, ClobError> {
864 if self.creds.is_none() {
865 return Err(ClobError::Other("L2 creds required".to_string()));
866 }
867 let signer_arc = self
868 .signer
869 .as_ref()
870 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
871 let signer_ref: &EthersSigner = signer_arc.as_ref();
872
873 let ts = if self.use_server_time {
874 Some(self.get_server_time().await?)
875 } else {
876 None
877 };
878 let headers = crate::headers::create_l2_headers(
879 signer_ref,
880 self.creds.as_ref().unwrap(),
881 "GET",
882 GET_READONLY_API_KEYS,
883 None,
884 ts,
885 )
886 .await?;
887
888 let endpoint = format!("{}{}", self.host, GET_READONLY_API_KEYS);
889 let resp: Vec<String> = self
890 .http_get_typed(
891 &endpoint,
892 Some(RequestOptions::<Value> {
893 headers: Some(headers),
894 data: None,
895 params: None,
896 }),
897 )
898 .await?;
899 Ok(resp)
900 }
901
902 pub async fn delete_readonly_api_key(&self, key: &str) -> Result<bool, ClobError> {
906 if self.creds.is_none() {
907 return Err(ClobError::Other("L2 creds required".to_string()));
908 }
909 let signer_arc = self
910 .signer
911 .as_ref()
912 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
913 let signer_ref: &EthersSigner = signer_arc.as_ref();
914
915 let payload = DeleteReadonlyApiKeyRequest {
916 key: key.to_string(),
917 };
918 let body_str =
919 serde_json::to_string(&payload).map_err(|e| ClobError::Other(e.to_string()))?;
920
921 let ts = if self.use_server_time {
922 Some(self.get_server_time().await?)
923 } else {
924 None
925 };
926 let headers = crate::headers::create_l2_headers(
927 signer_ref,
928 self.creds.as_ref().unwrap(),
929 "DELETE",
930 DELETE_READONLY_API_KEY,
931 Some(&body_str),
932 ts,
933 )
934 .await?;
935
936 let endpoint = format!("{}{}", self.host, DELETE_READONLY_API_KEY);
937 let resp: bool = self
938 .http_del_typed(
939 &endpoint,
940 Some(RequestOptions {
941 headers: Some(headers),
942 data: Some(payload),
943 params: None,
944 }),
945 )
946 .await?;
947 Ok(resp)
948 }
949
950 pub async fn validate_readonly_api_key(
956 &self,
957 address: &str,
958 key: &str,
959 ) -> Result<String, ClobError> {
960 let endpoint = format!("{}{}", self.host, VALIDATE_READONLY_API_KEY);
961 let mut params = std::collections::HashMap::new();
962 params.insert("address".to_string(), address.to_string());
963 params.insert("key".to_string(), key.to_string());
964 let val = self
965 .http_get(
966 &endpoint,
967 Some(RequestOptions::<Value> {
968 headers: None,
969 data: None,
970 params: Some(params),
971 }),
972 )
973 .await?;
974
975 if let Some(s) = val.as_str() {
976 return Ok(s.to_string());
977 }
978 if let Some(s) = val
979 .get("result")
980 .and_then(|v| v.as_str())
981 .or_else(|| val.get("address").and_then(|v| v.as_str()))
982 {
983 return Ok(s.to_string());
984 }
985 Ok(val.to_string())
986 }
987
988 #[allow(non_snake_case)]
990 pub async fn createReadonlyApiKey(&self) -> Result<ReadonlyApiKeyResponse, ClobError> {
991 self.create_readonly_api_key().await
992 }
993
994 #[allow(non_snake_case)]
995 pub async fn getReadonlyApiKeys(&self) -> Result<Vec<String>, ClobError> {
996 self.get_readonly_api_keys().await
997 }
998
999 #[allow(non_snake_case)]
1000 pub async fn deleteReadonlyApiKey(&self, key: &str) -> Result<bool, ClobError> {
1001 self.delete_readonly_api_key(key).await
1002 }
1003
1004 #[allow(non_snake_case)]
1005 pub async fn validateReadonlyApiKey(
1006 &self,
1007 address: &str,
1008 key: &str,
1009 ) -> Result<String, ClobError> {
1010 self.validate_readonly_api_key(address, key).await
1011 }
1012
1013 pub async fn get_closed_only_mode(&self) -> Result<crate::types::BanStatus, ClobError> {
1014 if self.creds.is_none() {
1015 return Err(ClobError::Other("L2 creds required".to_string()));
1016 }
1017 let signer_arc = self
1018 .signer
1019 .as_ref()
1020 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
1021 let signer_ref: &EthersSigner = signer_arc.as_ref();
1022 let endpoint = format!("{}{}", self.host, CLOSED_ONLY);
1023 let ts = if self.use_server_time {
1024 Some(self.get_server_time().await?)
1025 } else {
1026 None
1027 };
1028 let headers = crate::headers::create_l2_headers(
1029 signer_ref,
1030 self.creds.as_ref().unwrap(),
1031 "GET",
1032 CLOSED_ONLY,
1033 None,
1034 ts,
1035 )
1036 .await?;
1037 let resp: crate::types::BanStatus = self
1038 .http_get_typed(
1039 &endpoint,
1040 Some(RequestOptions::<Value> {
1041 headers: Some(headers),
1042 data: None,
1043 params: None,
1044 }),
1045 )
1046 .await?;
1047 Ok(resp)
1048 }
1049
1050 pub async fn delete_api_key(&self) -> Result<(), ClobError> {
1051 if self.creds.is_none() {
1052 return Err(ClobError::Other("L2 creds required".to_string()));
1053 }
1054 let signer_arc = self
1055 .signer
1056 .as_ref()
1057 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
1058 let signer_ref: &EthersSigner = signer_arc.as_ref();
1059 let endpoint = format!("{}{}", self.host, DELETE_API_KEY);
1060 let ts = if self.use_server_time {
1061 Some(self.get_server_time().await?)
1062 } else {
1063 None
1064 };
1065 let headers = crate::headers::create_l2_headers(
1066 signer_ref,
1067 self.creds.as_ref().unwrap(),
1068 "DELETE",
1069 DELETE_API_KEY,
1070 None,
1071 ts,
1072 )
1073 .await?;
1074 let _val: () = self
1075 .http_del_typed::<(), Value>(
1076 &endpoint,
1077 Some(RequestOptions::<Value> {
1078 headers: Some(headers),
1079 data: None,
1080 params: None,
1081 }),
1082 )
1083 .await?;
1084 Ok(())
1085 }
1086
1087 pub async fn get_trades(
1088 &self,
1089 params: Option<std::collections::HashMap<String, String>>,
1090 only_first_page: bool,
1091 next_cursor: Option<String>,
1092 ) -> Result<Vec<Value>, ClobError> {
1093 if self.creds.is_none() {
1094 return Err(ClobError::Other("L2 creds required".to_string()));
1095 }
1096 let signer_arc = self
1097 .signer
1098 .as_ref()
1099 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
1100 let signer_ref: &EthersSigner = signer_arc.as_ref();
1101 let ts = if self.use_server_time {
1102 Some(self.get_server_time().await?)
1103 } else {
1104 None
1105 };
1106 let headers = crate::headers::create_l2_headers(
1107 signer_ref,
1108 self.creds.as_ref().unwrap(),
1109 "GET",
1110 GET_TRADES,
1111 None,
1112 ts,
1113 )
1114 .await?;
1115 let mut results: Vec<Value> = vec![];
1116 let endpoint = format!("{}{}", self.host, GET_TRADES);
1117 let mut cursor = next_cursor.unwrap_or_else(|| INITIAL_CURSOR.to_string());
1118 while cursor != END_CURSOR {
1119 if only_first_page && cursor != INITIAL_CURSOR {
1120 break;
1121 }
1122 let mut p = params.clone().unwrap_or_default();
1123 p.insert("next_cursor".to_string(), cursor.clone());
1124 let val = self
1125 .http_get(
1126 &endpoint,
1127 Some(RequestOptions {
1128 headers: Some(headers.clone()),
1129 data: None,
1130 params: Some(p),
1131 }),
1132 )
1133 .await?;
1134 let data = val
1135 .get("data")
1136 .and_then(|v| v.as_array())
1137 .cloned()
1138 .unwrap_or_default();
1139 for item in data {
1140 results.push(item);
1141 }
1142 cursor = val
1143 .get("next_cursor")
1144 .and_then(|v| v.as_str())
1145 .unwrap_or(END_CURSOR)
1146 .to_string();
1147 }
1148 Ok(results)
1149 }
1150
1151 pub async fn get_trades_typed(
1153 &self,
1154 params: Option<std::collections::HashMap<String, String>>,
1155 only_first_page: bool,
1156 next_cursor: Option<String>,
1157 ) -> Result<Vec<Trade>, ClobError> {
1158 let vals = self
1159 .get_trades(params, only_first_page, next_cursor)
1160 .await?;
1161 let mut trades: Vec<Trade> = Vec::new();
1162 for v in vals {
1163 let t: Trade =
1164 serde_json::from_value(v).map_err(|e| ClobError::Other(e.to_string()))?;
1165 trades.push(t);
1166 }
1167 Ok(trades)
1168 }
1169
1170 pub async fn get_notifications(&self) -> Result<Vec<Notification>, ClobError> {
1171 if self.creds.is_none() {
1172 return Err(ClobError::Other("L2 creds required".to_string()));
1173 }
1174 let signer_arc = self
1175 .signer
1176 .as_ref()
1177 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
1178 let signer_ref: &EthersSigner = signer_arc.as_ref();
1179 let endpoint = format!("{}{}", self.host, GET_NOTIFICATIONS);
1180 let mut params = std::collections::HashMap::new();
1181 params.insert("signature_type".to_string(), "EOA".to_string());
1182 let ts = if self.use_server_time {
1183 Some(self.get_server_time().await?)
1184 } else {
1185 None
1186 };
1187 let headers = crate::headers::create_l2_headers(
1188 signer_ref,
1189 self.creds.as_ref().unwrap(),
1190 "GET",
1191 GET_NOTIFICATIONS,
1192 None,
1193 ts,
1194 )
1195 .await?;
1196 let resp: MaybeVec<Notification> = self
1197 .http_get_typed(
1198 &endpoint,
1199 Some(RequestOptions::<Value> {
1200 headers: Some(headers),
1201 data: None,
1202 params: Some(params),
1203 }),
1204 )
1205 .await?;
1206 Ok(resp.into_vec())
1207 }
1208
1209 pub async fn get_notifications_typed(&self) -> Result<Vec<Notification>, ClobError> {
1211 self.get_notifications().await
1213 }
1214
1215 pub async fn drop_notifications(&self, ids: Option<&Vec<String>>) -> Result<(), ClobError> {
1216 if self.creds.is_none() {
1217 return Err(ClobError::Other("L2 creds required".to_string()));
1218 }
1219 let signer_arc = self
1220 .signer
1221 .as_ref()
1222 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
1223 let signer_ref: &EthersSigner = signer_arc.as_ref();
1224 let endpoint = format!("{}{}", self.host, DROP_NOTIFICATIONS);
1225 let params = crate::http_helpers::parse_drop_notification_params(ids);
1226 let ts = if self.use_server_time {
1227 Some(self.get_server_time().await?)
1228 } else {
1229 None
1230 };
1231 let headers = crate::headers::create_l2_headers(
1232 signer_ref,
1233 self.creds.as_ref().unwrap(),
1234 "DELETE",
1235 DROP_NOTIFICATIONS,
1236 None,
1237 ts,
1238 )
1239 .await?;
1240 let _raw: SuccessResp = self
1241 .http_del_typed(
1242 &endpoint,
1243 Some(RequestOptions::<Value> {
1244 headers: Some(headers),
1245 data: None,
1246 params: Some(params),
1247 }),
1248 )
1249 .await?;
1250 Ok(())
1251 }
1252
1253 pub async fn get_balance_allowance(
1254 &self,
1255 params: Option<std::collections::HashMap<String, String>>,
1256 ) -> Result<crate::types::BalanceAllowanceResponse, ClobError> {
1257 if self.creds.is_none() {
1258 return Err(ClobError::Other("L2 creds required".to_string()));
1259 }
1260 let signer_arc = self
1261 .signer
1262 .as_ref()
1263 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
1264 let signer_ref: &EthersSigner = signer_arc.as_ref();
1265 let ts = if self.use_server_time {
1266 Some(self.get_server_time().await?)
1267 } else {
1268 None
1269 };
1270 let headers = crate::headers::create_l2_headers(
1271 signer_ref,
1272 self.creds.as_ref().unwrap(),
1273 "GET",
1274 GET_BALANCE_ALLOWANCE,
1275 None,
1276 ts,
1277 )
1278 .await?;
1279 let endpoint = format!("{}{}", self.host, GET_BALANCE_ALLOWANCE);
1280 let resp: crate::types::BalanceAllowanceResponse = self
1281 .http_get_typed(
1282 &endpoint,
1283 Some(RequestOptions::<Value> {
1284 headers: Some(headers),
1285 data: None,
1286 params,
1287 }),
1288 )
1289 .await?;
1290 Ok(resp)
1291 }
1292
1293 pub async fn update_balance_allowance(
1294 &self,
1295 params: Option<std::collections::HashMap<String, String>>,
1296 ) -> Result<(), ClobError> {
1297 if self.creds.is_none() {
1298 return Err(ClobError::Other("L2 creds required".to_string()));
1299 }
1300 let signer_arc = self
1301 .signer
1302 .as_ref()
1303 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
1304 let signer_ref: &EthersSigner = signer_arc.as_ref();
1305 let ts = if self.use_server_time {
1306 Some(self.get_server_time().await?)
1307 } else {
1308 None
1309 };
1310 let headers = crate::headers::create_l2_headers(
1311 signer_ref,
1312 self.creds.as_ref().unwrap(),
1313 "GET",
1314 UPDATE_BALANCE_ALLOWANCE,
1315 None,
1316 ts,
1317 )
1318 .await?;
1319 let endpoint = format!("{}{}", self.host, UPDATE_BALANCE_ALLOWANCE);
1321 let _resp: OkResp = self
1322 .http_get_typed(
1323 &endpoint,
1324 Some(RequestOptions::<Value> {
1325 headers: Some(headers),
1326 data: None,
1327 params,
1328 }),
1329 )
1330 .await?;
1331 Ok(())
1332 }
1333
1334 pub async fn get_rewards_user_for_day_typed(
1337 &self,
1338 params: Option<std::collections::HashMap<String, String>>,
1339 ) -> Result<Vec<Reward>, ClobError> {
1340 let endpoint = format!("{}{}", self.host, GET_EARNINGS_FOR_USER_FOR_DAY);
1341 let val = self
1342 .http_get(
1343 &endpoint,
1344 Some(RequestOptions {
1345 headers: None,
1346 data: None,
1347 params,
1348 }),
1349 )
1350 .await?;
1351
1352 let arr = if let Some(a) = val.get("data").and_then(|v| v.as_array()) {
1353 a.clone()
1354 } else if val.is_array() {
1355 val.as_array().cloned().unwrap_or_default()
1356 } else {
1357 Vec::new()
1358 };
1359 let mut out: Vec<Reward> = Vec::new();
1360 for v in arr {
1361 let r: Reward =
1362 serde_json::from_value(v).map_err(|e| ClobError::Other(e.to_string()))?;
1363 out.push(r);
1364 }
1365 Ok(out)
1366 }
1367
1368 pub async fn create_order(
1369 &self,
1370 user_order: UserOrder,
1371 options_tick: Option<&str>,
1372 ) -> Result<SignedOrder, ClobError> {
1373 self.can_l1_auth()?;
1375 let signer_arc = self
1376 .signer
1377 .as_ref()
1378 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
1379 let signer_ref: &EthersSigner = signer_arc.as_ref();
1380 let ob = if let Some(cfg) = &self.builder_config {
1381 OrderBuilder::with_config(signer_ref, self.chain_id, cfg)
1382 } else {
1383 OrderBuilder::new(signer_ref, self.chain_id, None, None)
1384 };
1385 let exchange_addr = self.resolve_exchange_address(&user_order.token_id);
1387 let signed = ob
1388 .build_order(&exchange_addr, &user_order, options_tick.unwrap_or("0.01"))
1389 .await?;
1390 Ok(signed)
1391 }
1392
1393 pub async fn create_market_order(
1394 &self,
1395 user_market_order: UserMarketOrder,
1396 options_tick: Option<&str>,
1397 ) -> Result<SignedOrder, ClobError> {
1398 self.can_l1_auth()?;
1399 let signer_arc = self
1400 .signer
1401 .as_ref()
1402 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
1403 let signer_ref: &EthersSigner = signer_arc.as_ref();
1404 let ob = if let Some(cfg) = &self.builder_config {
1405 OrderBuilder::with_config(signer_ref, self.chain_id, cfg)
1406 } else {
1407 OrderBuilder::new(signer_ref, self.chain_id, None, None)
1408 };
1409 let exchange_addr = self.resolve_exchange_address(&user_market_order.token_id);
1410 let signed = ob
1411 .build_market_order(
1412 &exchange_addr,
1413 &user_market_order,
1414 options_tick.unwrap_or("0.01"),
1415 )
1416 .await?;
1417 Ok(signed)
1418 }
1419
1420 fn resolve_exchange_address(&self, _token_id: &str) -> String {
1423 let neg = self
1424 .builder_config
1425 .as_ref()
1426 .and_then(|c| c.neg_risk)
1427 .unwrap_or(false);
1428 match (self.chain_id, neg) {
1429 (137, false) => "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E".to_string(),
1430 (137, true) => "0xC5d563A36AE78145C45a50134d48A1215220f80a".to_string(),
1431 (80002, false) => "0xdFE02Eb6733538f8Ea35D585af8DE5958AD99E40".to_string(),
1432 (80002, true) => "0xC5d563A36AE78145C45a50134d48A1215220f80a".to_string(),
1433 (_other, _) => "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E".to_string(),
1434 }
1435 }
1436
1437 pub async fn post_orders(
1452 &self,
1453 orders: Vec<PostOrdersArgs>,
1454 defer_exec: bool,
1455 default_post_only: bool,
1456 ) -> Result<Vec<Order>, ClobError> {
1457 if self.creds.is_none() {
1458 return Err(ClobError::Other("L2 creds required".to_string()));
1459 }
1460 let signer_arc = self
1461 .signer
1462 .as_ref()
1463 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
1464 let signer_ref: &EthersSigner = signer_arc.as_ref();
1465
1466 let owner = self.creds.as_ref().unwrap().key.clone();
1468
1469 let new_orders: Vec<crate::types::NewOrder> = orders
1471 .into_iter()
1472 .map(|arg| {
1473 let post_only = arg.post_only.or(Some(default_post_only));
1475 crate::utilities::order_to_json(
1476 &arg.order,
1477 &owner,
1478 arg.order_type,
1479 defer_exec,
1480 post_only,
1481 )
1482 })
1483 .collect();
1484
1485 let body_str =
1486 serde_json::to_string(&new_orders).map_err(|e| ClobError::Other(e.to_string()))?;
1487 let ts = if self.use_server_time {
1488 Some(self.get_server_time().await?)
1489 } else {
1490 None
1491 };
1492 let mut headers = crate::headers::create_l2_headers(
1493 signer_ref,
1494 self.creds.as_ref().unwrap(),
1495 "POST",
1496 POST_ORDERS,
1497 Some(&body_str),
1498 ts,
1499 )
1500 .await?;
1501 if let Some(b) = &self.builder_signer {
1502 let b_payload = b
1503 .create_builder_header_payload("POST", POST_ORDERS, Some(&body_str), None)
1504 .map_err(|e| ClobError::Other(format!("builder header error: {}", e)))?;
1505 headers = crate::headers::inject_builder_headers(headers, &b_payload);
1506 }
1507 let endpoint = format!("{}{}", self.host, POST_ORDERS);
1508 let raw: MaybeVec<Order> = self
1509 .http_post_typed(
1510 &endpoint,
1511 Some(RequestOptions {
1512 headers: Some(headers),
1513 data: Some(new_orders),
1514 params: None,
1515 }),
1516 )
1517 .await?;
1518 Ok(raw.into_vec())
1519 }
1520
1521 pub async fn post_signed_order(
1531 &self,
1532 signed_order: &SignedOrder,
1533 order_type: OrderType,
1534 defer_exec: bool,
1535 post_only: Option<bool>,
1536 ) -> Result<OrderResponse, ClobError> {
1537 if self.creds.is_none() {
1539 return Err(ClobError::Other("L2 creds required".to_string()));
1540 }
1541 let signer_arc = self
1542 .signer
1543 .as_ref()
1544 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
1545 let signer_ref: &EthersSigner = signer_arc.as_ref();
1546
1547 let owner = self.creds.as_ref().unwrap().key.clone();
1550
1551 let new_order = crate::utilities::order_to_json(
1553 signed_order,
1554 &owner,
1555 order_type,
1556 defer_exec,
1557 post_only,
1558 );
1559
1560 let body_str =
1561 serde_json::to_string(&new_order).map_err(|e| ClobError::Other(e.to_string()))?;
1562 let ts = if self.use_server_time {
1564 Some(self.get_server_time().await?)
1565 } else {
1566 None
1567 };
1568 let mut headers = crate::headers::create_l2_headers(
1569 signer_ref,
1570 self.creds.as_ref().unwrap(),
1571 "POST",
1572 POST_ORDER,
1573 Some(&body_str),
1574 ts,
1575 )
1576 .await?;
1577 if let Some(b) = &self.builder_signer {
1579 let b_payload = b
1580 .create_builder_header_payload("POST", POST_ORDER, Some(&body_str), None)
1581 .map_err(|e| ClobError::Other(format!("builder header error: {}", e)))?;
1582 headers = crate::headers::inject_builder_headers(headers, &b_payload);
1583 }
1584 let endpoint = format!("{}{}", self.host, POST_ORDER);
1585 let opts = RequestOptions {
1586 headers: Some(headers),
1587 data: Some(new_order),
1588 params: None,
1589 };
1590 let res: MaybeItem<OrderResponse> = self.http_post_typed(&endpoint, Some(opts)).await?;
1591 Ok(res.into_item())
1592 }
1593
1594 pub async fn post_signed_order_typed(
1597 &self,
1598 signed_order: &SignedOrder,
1599 order_type: OrderType,
1600 defer_exec: bool,
1601 post_only: Option<bool>,
1602 ) -> Result<OrderResponse, ClobError> {
1603 self.post_signed_order(signed_order, order_type, defer_exec, post_only)
1604 .await
1605 }
1606
1607 pub async fn create_and_post_order(
1611 &mut self,
1612 user_order: UserOrder,
1613 options_tick: Option<&str>,
1614 order_type: Option<OrderType>,
1615 defer_exec: bool,
1616 post_only: Option<bool>,
1617 ) -> Result<OrderResponse, ClobError> {
1618 let tick = if let Some(t) = options_tick {
1620 t.to_string()
1621 } else if let Some(cfg_tick) = self
1622 .builder_config
1623 .as_ref()
1624 .and_then(|c| c.tick_size.as_ref())
1625 {
1626 cfg_tick.clone()
1627 } else {
1628 "0.01".to_string()
1629 };
1630 let signed = self.create_order(user_order, Some(&tick)).await?;
1631 let order_type = order_type.unwrap_or(OrderType::GTC);
1632 self.post_signed_order(&signed, order_type, defer_exec, post_only)
1633 .await
1634 }
1635
1636 pub async fn create_and_post_market_order(
1640 &mut self,
1641 user_market_order: UserMarketOrder,
1642 options_tick: Option<&str>,
1643 order_type: Option<OrderType>,
1644 defer_exec: bool,
1645 ) -> Result<OrderResponse, ClobError> {
1646 let tick = if let Some(t) = options_tick {
1648 t.to_string()
1649 } else if let Some(cfg_tick) = self
1650 .builder_config
1651 .as_ref()
1652 .and_then(|c| c.tick_size.as_ref())
1653 {
1654 cfg_tick.clone()
1655 } else {
1656 "0.01".to_string()
1657 };
1658 let signed = self
1659 .create_market_order(user_market_order, Some(&tick))
1660 .await?;
1661 let order_type = order_type.unwrap_or(OrderType::FOK);
1662 self.post_signed_order(&signed, order_type, defer_exec, None)
1663 .await
1664 }
1665
1666 pub async fn post_orders_typed(
1669 &self,
1670 orders: Vec<SignedOrder>,
1671 _defer_exec: bool,
1672 ) -> Result<Value, ClobError> {
1673 if self.creds.is_none() {
1674 return Err(ClobError::Other("L2 creds required".to_string()));
1675 }
1676 let signer_arc = self
1677 .signer
1678 .as_ref()
1679 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
1680 let signer_ref: &EthersSigner = signer_arc.as_ref();
1681 let body_str =
1682 serde_json::to_string(&orders).map_err(|e| ClobError::Other(e.to_string()))?;
1683 let ts = if self.use_server_time {
1684 Some(self.get_server_time().await?)
1685 } else {
1686 None
1687 };
1688 let headers = crate::headers::create_l2_headers(
1689 signer_ref,
1690 self.creds.as_ref().unwrap(),
1691 "POST",
1692 POST_ORDERS,
1693 Some(&body_str),
1694 ts,
1695 )
1696 .await?;
1697 let headers = if let Some(b) = &self.builder_signer {
1698 let b_payload = b
1699 .create_builder_header_payload("POST", POST_ORDERS, Some(&body_str), None)
1700 .map_err(|e| ClobError::Other(format!("builder header error: {}", e)))?;
1701 crate::headers::inject_builder_headers(headers, &b_payload)
1702 } else {
1703 headers
1704 };
1705 let endpoint = format!("{}{}", self.host, POST_ORDERS);
1706 let res: Value = self
1707 .http_post_typed(
1708 &endpoint,
1709 Some(RequestOptions {
1710 headers: Some(headers),
1711 data: Some(orders),
1712 params: None,
1713 }),
1714 )
1715 .await?;
1716 Ok(res)
1717 }
1718
1719 pub async fn post_orders_typed_parsed(
1723 &self,
1724 orders: Vec<SignedOrder>,
1725 _defer_exec: bool,
1726 ) -> Result<Vec<Order>, ClobError> {
1727 if self.creds.is_none() {
1729 return Err(ClobError::Other("L2 creds required".to_string()));
1730 }
1731 let signer_arc = self
1732 .signer
1733 .as_ref()
1734 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
1735 let signer_ref: &EthersSigner = signer_arc.as_ref();
1736 let body_str =
1737 serde_json::to_string(&orders).map_err(|e| ClobError::Other(e.to_string()))?;
1738 let ts = if self.use_server_time {
1739 Some(self.get_server_time().await?)
1740 } else {
1741 None
1742 };
1743 let headers = crate::headers::create_l2_headers(
1744 signer_ref,
1745 self.creds.as_ref().unwrap(),
1746 "POST",
1747 POST_ORDERS,
1748 Some(&body_str),
1749 ts,
1750 )
1751 .await?;
1752 let headers = if let Some(b) = &self.builder_signer {
1753 let b_payload = b
1754 .create_builder_header_payload("POST", POST_ORDERS, Some(&body_str), None)
1755 .map_err(|e| ClobError::Other(format!("builder header error: {}", e)))?;
1756 crate::headers::inject_builder_headers(headers, &b_payload)
1757 } else {
1758 headers
1759 };
1760 let endpoint = format!("{}{}", self.host, POST_ORDERS);
1761 let raw: MaybeVec<Order> = self
1762 .http_post_typed(
1763 &endpoint,
1764 Some(RequestOptions {
1765 headers: Some(headers),
1766 data: Some(orders),
1767 params: None,
1768 }),
1769 )
1770 .await?;
1771 Ok(raw.into_vec())
1772 }
1773
1774 pub async fn cancel_all(&self) -> Result<Vec<Order>, ClobError> {
1775 if self.creds.is_none() {
1776 return Err(ClobError::Other("L2 creds required".to_string()));
1777 }
1778 let signer_arc = self
1779 .signer
1780 .as_ref()
1781 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
1782 let signer_ref: &EthersSigner = signer_arc.as_ref();
1783 let ts = if self.use_server_time {
1784 Some(self.get_server_time().await?)
1785 } else {
1786 None
1787 };
1788 let mut headers = crate::headers::create_l2_headers(
1789 signer_ref,
1790 self.creds.as_ref().unwrap(),
1791 "DELETE",
1792 CANCEL_ALL,
1793 None,
1794 ts,
1795 )
1796 .await?;
1797 if let Some(b) = &self.builder_signer {
1798 let b_payload = b
1799 .create_builder_header_payload("DELETE", CANCEL_ALL, None, None)
1800 .map_err(|e| ClobError::Other(format!("builder header error: {}", e)))?;
1801 headers = crate::headers::inject_builder_headers(headers, &b_payload);
1802 }
1803 let endpoint = format!("{}{}", self.host, CANCEL_ALL);
1804 let raw: MaybeVec<Order> = self
1805 .http_del_typed(
1806 &endpoint,
1807 Some(RequestOptions::<Value> {
1808 headers: Some(headers),
1809 data: None,
1810 params: None,
1811 }),
1812 )
1813 .await?;
1814 Ok(raw.into_vec())
1815 }
1816
1817 pub async fn cancel_market_orders(
1818 &self,
1819 payload: OrderMarketCancelParams,
1820 ) -> Result<Vec<Order>, ClobError> {
1821 if self.creds.is_none() {
1822 return Err(ClobError::Other("L2 creds required".to_string()));
1823 }
1824 let signer_arc = self
1825 .signer
1826 .as_ref()
1827 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
1828 let signer_ref: &EthersSigner = signer_arc.as_ref();
1829 let body_str =
1830 serde_json::to_string(&payload).map_err(|e| ClobError::Other(e.to_string()))?;
1831 let ts = if self.use_server_time {
1832 Some(self.get_server_time().await?)
1833 } else {
1834 None
1835 };
1836 let mut headers = crate::headers::create_l2_headers(
1837 signer_ref,
1838 self.creds.as_ref().unwrap(),
1839 "DELETE",
1840 CANCEL_MARKET_ORDERS,
1841 Some(&body_str),
1842 ts,
1843 )
1844 .await?;
1845 if let Some(b) = &self.builder_signer {
1846 let b_payload = b
1847 .create_builder_header_payload(
1848 "DELETE",
1849 CANCEL_MARKET_ORDERS,
1850 Some(&body_str),
1851 None,
1852 )
1853 .map_err(|e| ClobError::Other(format!("builder header error: {}", e)))?;
1854 headers = crate::headers::inject_builder_headers(headers, &b_payload);
1855 }
1856 let endpoint = format!("{}{}", self.host, CANCEL_MARKET_ORDERS);
1857 let body_val =
1859 serde_json::to_value(&payload).map_err(|e| ClobError::Other(e.to_string()))?;
1860 let raw: MaybeVec<Order> = self
1861 .http_del_typed(
1862 &endpoint,
1863 Some(RequestOptions::<Value> {
1864 headers: Some(headers),
1865 data: Some(body_val),
1866 params: None,
1867 }),
1868 )
1869 .await?;
1870 Ok(raw.into_vec())
1871 }
1872
1873 pub async fn is_order_scoring(
1874 &self,
1875 params: Option<std::collections::HashMap<String, String>>,
1876 ) -> Result<crate::types::OrderScoring, ClobError> {
1877 if self.creds.is_none() {
1878 return Err(ClobError::Other("L2 creds required".to_string()));
1879 }
1880 let signer_arc = self
1881 .signer
1882 .as_ref()
1883 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
1884 let signer_ref: &EthersSigner = signer_arc.as_ref();
1885 let mut headers = crate::headers::create_l2_headers(
1886 signer_ref,
1887 self.creds.as_ref().unwrap(),
1888 "GET",
1889 IS_ORDER_SCORING,
1890 None,
1891 if self.use_server_time {
1892 Some(self.get_server_time().await?)
1893 } else {
1894 None
1895 },
1896 )
1897 .await?;
1898 if let Some(b) = &self.builder_signer {
1899 let b_payload = b
1900 .create_builder_header_payload("GET", IS_ORDER_SCORING, None, None)
1901 .map_err(|e| ClobError::Other(format!("builder header error: {}", e)))?;
1902 headers = crate::headers::inject_builder_headers(headers, &b_payload);
1903 }
1904 let endpoint = format!("{}{}", self.host, IS_ORDER_SCORING);
1905 let resp: crate::types::OrderScoring = self
1906 .http_get_typed(
1907 &endpoint,
1908 Some(RequestOptions::<Value> {
1909 headers: Some(headers),
1910 data: None,
1911 params,
1912 }),
1913 )
1914 .await?;
1915 Ok(resp)
1916 }
1917
1918 pub async fn are_orders_scoring(
1919 &self,
1920 order_ids: Option<Vec<String>>,
1921 ) -> Result<crate::types::OrdersScoring, ClobError> {
1922 if self.creds.is_none() {
1923 return Err(ClobError::Other("L2 creds required".to_string()));
1924 }
1925 let signer_arc = self
1926 .signer
1927 .as_ref()
1928 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
1929 let signer_ref: &EthersSigner = signer_arc.as_ref();
1930 let body_str =
1931 serde_json::to_string(&order_ids).map_err(|e| ClobError::Other(e.to_string()))?;
1932 let ts = if self.use_server_time {
1933 Some(self.get_server_time().await?)
1934 } else {
1935 None
1936 };
1937 let mut headers = crate::headers::create_l2_headers(
1938 signer_ref,
1939 self.creds.as_ref().unwrap(),
1940 "POST",
1941 ARE_ORDERS_SCORING,
1942 Some(&body_str),
1943 ts,
1944 )
1945 .await?;
1946 if let Some(b) = &self.builder_signer {
1947 let b_payload = b
1948 .create_builder_header_payload("POST", ARE_ORDERS_SCORING, Some(&body_str), None)
1949 .map_err(|e| ClobError::Other(format!("builder header error: {}", e)))?;
1950 headers = crate::headers::inject_builder_headers(headers, &b_payload);
1951 }
1952 let body_json =
1954 serde_json::to_value(&order_ids).map_err(|e| ClobError::Other(e.to_string()))?;
1955 let endpoint = format!("{}{}", self.host, ARE_ORDERS_SCORING);
1956 let resp: crate::types::OrdersScoring = self
1957 .http_post_typed(
1958 &endpoint,
1959 Some(RequestOptions::<Value> {
1960 headers: Some(headers),
1961 data: Some(body_json),
1962 params: None,
1963 }),
1964 )
1965 .await?;
1966 Ok(resp)
1967 }
1968
1969 pub async fn cancel_order_payload(
1971 &self,
1972 payload: OrderPayloadParity,
1973 ) -> Result<Order, ClobError> {
1974 if self.creds.is_none() {
1975 return Err(ClobError::Other("L2 creds required".to_string()));
1976 }
1977 let creds = self.creds.as_ref().unwrap();
1978 let signer_arc = self
1979 .signer
1980 .as_ref()
1981 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
1982 let signer_ref: &EthersSigner = signer_arc.as_ref();
1983 let body_str =
1984 serde_json::to_string(&payload).map_err(|e| ClobError::Other(e.to_string()))?;
1985 let ts = if self.use_server_time {
1986 Some(self.get_server_time().await?)
1987 } else {
1988 None
1989 };
1990 let mut headers = crate::headers::create_l2_headers(
1991 signer_ref,
1992 creds,
1993 "DELETE",
1994 CANCEL_ORDER,
1995 Some(&body_str),
1996 ts,
1997 )
1998 .await?;
1999 if let Some(b) = &self.builder_signer {
2000 let b_payload = b
2001 .create_builder_header_payload("DELETE", CANCEL_ORDER, Some(&body_str), None)
2002 .map_err(|e| ClobError::Other(format!("builder header error: {}", e)))?;
2003 headers = crate::headers::inject_builder_headers(headers, &b_payload);
2004 }
2005 let endpoint = format!("{}{}", self.host, CANCEL_ORDER);
2006 let body_val =
2008 serde_json::to_value(&payload).map_err(|e| ClobError::Other(e.to_string()))?;
2009 let opts: RequestOptions<Value> = RequestOptions::<Value> {
2010 headers: Some(headers),
2011 data: Some(body_val),
2012 params: None,
2013 };
2014 let raw: MaybeItem<Order> = self.http_del_typed(&endpoint, Some(opts)).await?;
2015 Ok(raw.into_item())
2016 }
2017
2018 pub async fn cancel_order_payload_raw(
2020 &self,
2021 payload: OrderPayloadParity,
2022 ) -> Result<Value, ClobError> {
2023 if self.creds.is_none() {
2024 return Err(ClobError::Other("L2 creds required".to_string()));
2025 }
2026 let creds = self.creds.as_ref().unwrap();
2027 let signer_arc = self
2028 .signer
2029 .as_ref()
2030 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
2031 let signer_ref: &EthersSigner = signer_arc.as_ref();
2032 let body_str =
2033 serde_json::to_string(&payload).map_err(|e| ClobError::Other(e.to_string()))?;
2034 let ts = if self.use_server_time {
2035 Some(self.get_server_time().await?)
2036 } else {
2037 None
2038 };
2039 let mut headers = crate::headers::create_l2_headers(
2040 signer_ref,
2041 creds,
2042 "DELETE",
2043 CANCEL_ORDER,
2044 Some(&body_str),
2045 ts,
2046 )
2047 .await?;
2048 if let Some(b) = &self.builder_signer {
2049 let b_payload = b
2050 .create_builder_header_payload("DELETE", CANCEL_ORDER, Some(&body_str), None)
2051 .map_err(|e| ClobError::Other(format!("builder header error: {}", e)))?;
2052 headers = crate::headers::inject_builder_headers(headers, &b_payload);
2053 }
2054 let endpoint = format!("{}{}", self.host, CANCEL_ORDER);
2055 let body_val =
2056 serde_json::to_value(&payload).map_err(|e| ClobError::Other(e.to_string()))?;
2057 let opts: RequestOptions<Value> = RequestOptions::<Value> {
2058 headers: Some(headers),
2059 data: Some(body_val),
2060 params: None,
2061 };
2062 let raw: MaybeItem<Value> = self.http_del_typed(&endpoint, Some(opts)).await?;
2063 Ok(raw.into_item())
2064 }
2065
2066 pub async fn cancel_orders(&self, order_ids: Vec<String>) -> Result<Vec<Order>, ClobError> {
2067 if self.creds.is_none() {
2068 return Err(ClobError::Other("L2 creds required".to_string()));
2069 }
2070 let creds = self.creds.as_ref().unwrap();
2071 let signer_arc = self
2072 .signer
2073 .as_ref()
2074 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
2075 let signer_ref: &EthersSigner = signer_arc.as_ref();
2076 let body_str =
2077 serde_json::to_string(&order_ids).map_err(|e| ClobError::Other(e.to_string()))?;
2078 let ts = if self.use_server_time {
2079 Some(self.get_server_time().await?)
2080 } else {
2081 None
2082 };
2083 let mut headers = crate::headers::create_l2_headers(
2084 signer_ref,
2085 creds,
2086 "DELETE",
2087 CANCEL_ORDERS,
2088 Some(&body_str),
2089 ts,
2090 )
2091 .await?;
2092 if let Some(b) = &self.builder_signer {
2093 let b_payload = b
2094 .create_builder_header_payload("DELETE", CANCEL_ORDERS, Some(&body_str), None)
2095 .map_err(|e| ClobError::Other(format!("builder header error: {}", e)))?;
2096 headers = crate::headers::inject_builder_headers(headers, &b_payload);
2097 }
2098 let endpoint = format!("{}{}", self.host, CANCEL_ORDERS);
2099 let opts = RequestOptions {
2100 headers: Some(headers),
2101 data: Some(order_ids.clone()),
2102 params: None,
2103 };
2104 let raw: MaybeVec<Order> = self.http_del_typed(&endpoint, Some(opts)).await?;
2105 Ok(raw.into_vec())
2106 }
2107
2108 pub async fn get_order(&self, order_id: &str) -> Result<OpenOrder, ClobError> {
2109 self.get_order_typed(order_id).await
2111 }
2112
2113 pub async fn get_order_typed(&self, order_id: &str) -> Result<OpenOrder, ClobError> {
2115 if self.creds.is_none() {
2117 return Err(ClobError::Other("L2 creds required".to_string()));
2118 }
2119 let signer_arc = self
2120 .signer
2121 .as_ref()
2122 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
2123 let signer_ref: &EthersSigner = signer_arc.as_ref();
2124
2125 let request_path = format!("{}{}", GET_ORDER, order_id);
2127 let endpoint = format!("{}{}", self.host, request_path);
2128
2129 let ts = if self.use_server_time {
2130 Some(self.get_server_time().await?)
2131 } else {
2132 None
2133 };
2134 let mut headers = crate::headers::create_l2_headers(
2135 signer_ref,
2136 self.creds.as_ref().unwrap(),
2137 "GET",
2138 &request_path,
2139 None,
2140 ts,
2141 )
2142 .await?;
2143
2144 if let Some(b) = &self.builder_signer {
2145 let b_payload = b
2146 .create_builder_header_payload("GET", &request_path, None, None)
2147 .map_err(|e| ClobError::Other(format!("builder header error: {}", e)))?;
2148 headers = crate::headers::inject_builder_headers(headers, &b_payload);
2149 }
2150
2151 let opts = RequestOptions {
2152 headers: Some(headers),
2153 data: None,
2154 params: None,
2155 };
2156 let val = self.http_get(&endpoint, Some(opts)).await?;
2157 if val.is_object() && val.get("id").is_some() {
2159 let o: OpenOrder =
2160 serde_json::from_value(val).map_err(|e| ClobError::Other(e.to_string()))?;
2161 Ok(o)
2162 } else if let Some(d) = val.get("data") {
2163 let o: OpenOrder =
2164 serde_json::from_value(d.clone()).map_err(|e| ClobError::Other(e.to_string()))?;
2165 Ok(o)
2166 } else {
2167 Err(ClobError::Other(format!(
2168 "unexpected order response shape: {}",
2169 val
2170 )))
2171 }
2172 }
2173
2174 pub async fn get_open_orders(
2175 &self,
2176 params: Option<std::collections::HashMap<String, String>>,
2177 ) -> Result<Vec<SignedOrder>, ClobError> {
2178 self.get_open_orders_typed(params).await
2180 }
2181
2182 pub async fn get_open_orders_typed(
2184 &self,
2185 params: Option<std::collections::HashMap<String, String>>,
2186 ) -> Result<Vec<SignedOrder>, ClobError> {
2187 if self.creds.is_none() {
2189 return Err(ClobError::Other("L2 creds required".to_string()));
2190 }
2191 let signer_arc = self
2192 .signer
2193 .as_ref()
2194 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
2195 let signer_ref: &EthersSigner = signer_arc.as_ref();
2196
2197 let endpoint = format!("{}{}", self.host, GET_OPEN_ORDERS);
2198 let ts = if self.use_server_time {
2199 Some(self.get_server_time().await?)
2200 } else {
2201 None
2202 };
2203 let mut headers = crate::headers::create_l2_headers(
2204 signer_ref,
2205 self.creds.as_ref().unwrap(),
2206 "GET",
2207 GET_OPEN_ORDERS,
2208 None,
2209 ts,
2210 )
2211 .await?;
2212
2213 if let Some(b) = &self.builder_signer {
2214 let b_payload = b
2215 .create_builder_header_payload("GET", GET_OPEN_ORDERS, None, None)
2216 .map_err(|e| ClobError::Other(format!("builder header error: {}", e)))?;
2217 headers = crate::headers::inject_builder_headers(headers, &b_payload);
2218 }
2219
2220 let query = params.unwrap_or_default();
2221 let opts = RequestOptions {
2222 headers: Some(headers),
2223 data: None,
2224 params: if query.is_empty() { None } else { Some(query) },
2225 };
2226 let val = self.http_get(&endpoint, Some(opts)).await?;
2227 let arr = if val.is_array() {
2229 val
2230 } else if let Some(d) = val.get("data") {
2231 d.clone()
2232 } else {
2233 return Err(ClobError::Other(
2234 "unexpected open orders response shape".to_string(),
2235 ));
2236 };
2237 let orders: Vec<SignedOrder> =
2238 serde_json::from_value(arr).map_err(|e| ClobError::Other(e.to_string()))?;
2239 Ok(orders)
2240 }
2241
2242 pub async fn get_markets(
2243 &self,
2244 params: Option<std::collections::HashMap<String, String>>,
2245 ) -> Result<Vec<crate::types::Market>, ClobError> {
2246 let endpoint = format!("{}{}", self.host, GET_MARKETS);
2247 let opts = RequestOptions {
2248 headers: None,
2249 data: None,
2250 params,
2251 };
2252 let val = self.http_get(&endpoint, Some(opts)).await?;
2253 let arr = if val.is_array() {
2255 val
2256 } else if let Some(d) = val.get("data") {
2257 d.clone()
2258 } else {
2259 return Err(ClobError::Other(
2260 "unexpected markets response shape".to_string(),
2261 ));
2262 };
2263 let markets: Vec<crate::types::Market> =
2264 serde_json::from_value(arr).map_err(|e| ClobError::Other(e.to_string()))?;
2265 Ok(markets)
2266 }
2267
2268 pub async fn get_market(
2269 &self,
2270 market_id: &str,
2271 params: Option<std::collections::HashMap<String, String>>,
2272 ) -> Result<crate::types::Market, ClobError> {
2273 let endpoint = format!("{}{}{}", self.host, GET_MARKET, market_id);
2274 let opts = RequestOptions {
2275 headers: None,
2276 data: None,
2277 params,
2278 };
2279 let val = self.http_get(&endpoint, Some(opts)).await?;
2280 let m: crate::types::Market =
2282 serde_json::from_value(val).map_err(|e| ClobError::Other(e.to_string()))?;
2283 Ok(m)
2284 }
2285
2286 pub async fn get_simplified_markets(
2287 &self,
2288 params: Option<std::collections::HashMap<String, String>>,
2289 ) -> Result<Vec<crate::types::Market>, ClobError> {
2290 let endpoint = format!("{}{}", self.host, GET_SIMPLIFIED_MARKETS);
2291 let opts = RequestOptions {
2292 headers: None,
2293 data: None,
2294 params,
2295 };
2296 let val = self.http_get(&endpoint, Some(opts)).await?;
2297 let arr = if val.is_array() {
2298 val
2299 } else if let Some(d) = val.get("data") {
2300 d.clone()
2301 } else {
2302 return Err(ClobError::Other(
2303 "unexpected simplified markets response shape".to_string(),
2304 ));
2305 };
2306 let markets: Vec<crate::types::Market> =
2307 serde_json::from_value(arr).map_err(|e| ClobError::Other(e.to_string()))?;
2308 Ok(markets)
2309 }
2310
2311 pub async fn get_sampling_markets(
2312 &self,
2313 params: Option<std::collections::HashMap<String, String>>,
2314 ) -> Result<Vec<crate::types::Market>, ClobError> {
2315 let endpoint = format!("{}{}", self.host, GET_SAMPLING_MARKETS);
2316 let opts = RequestOptions {
2317 headers: None,
2318 data: None,
2319 params,
2320 };
2321 let val = self.http_get(&endpoint, Some(opts)).await?;
2322 let arr = if val.is_array() {
2323 val
2324 } else if let Some(d) = val.get("data") {
2325 d.clone()
2326 } else {
2327 return Err(ClobError::Other(
2328 "unexpected sampling markets response shape".to_string(),
2329 ));
2330 };
2331 let markets: Vec<crate::types::Market> =
2332 serde_json::from_value(arr).map_err(|e| ClobError::Other(e.to_string()))?;
2333 Ok(markets)
2334 }
2335
2336 pub async fn get_server_time(&self) -> Result<u64, ClobError> {
2337 let endpoint = format!("{}{}", self.host, TIME);
2338 let val = self.http_get(&endpoint, None).await?;
2339 if val.is_number() {
2341 Ok(val
2342 .as_u64()
2343 .ok_or(ClobError::Other("invalid time value".to_string()))?)
2344 } else if val.get("time").is_some() {
2345 Ok(val
2346 .get("time")
2347 .and_then(|v| v.as_u64())
2348 .ok_or(ClobError::Other("invalid time value".to_string()))?)
2349 } else {
2350 Err(ClobError::Other(
2351 "unexpected server time response".to_string(),
2352 ))
2353 }
2354 }
2355
2356 fn can_l1_auth(&self) -> Result<(), ClobError> {
2357 if self.signer.is_none() {
2358 return Err(ClobError::Other("L1 auth required".to_string()));
2359 }
2360 Ok(())
2361 }
2362
2363 pub async fn create_api_key(&self, nonce: Option<u64>) -> Result<ApiKeyCreds, ClobError> {
2364 self.can_l1_auth()?;
2365 let signer_arc = self.signer.as_ref().unwrap();
2366 let signer_ref: &EthersSigner = signer_arc.as_ref();
2367 let ts = if self.use_server_time {
2368 Some(self.get_server_time().await?)
2369 } else {
2370 None
2371 };
2372 let headers =
2373 crate::headers::create_l1_headers(signer_ref, self.chain_id as i32, nonce, ts).await?;
2374 let endpoint = format!("{}{}", self.host, CREATE_API_KEY);
2375 let opts = RequestOptions {
2376 headers: Some(headers),
2377 data: None,
2378 params: None,
2379 };
2380 let val = self.http_post(&endpoint, Some(opts)).await?;
2381 let api_raw: ApiKeyRaw =
2383 serde_json::from_value(val).map_err(|e| ClobError::Other(e.to_string()))?;
2384 let api_key = ApiKeyCreds {
2385 key: api_raw.api_key,
2386 secret: api_raw.secret,
2387 passphrase: api_raw.passphrase,
2388 };
2389 Ok(api_key)
2390 }
2391
2392 pub async fn derive_api_key(
2394 &self,
2395 params: Option<std::collections::HashMap<String, String>>,
2396 ) -> Result<ApiKeyCreds, ClobError> {
2397 self.can_l1_auth()?;
2398 let signer_arc = self.signer.as_ref().unwrap();
2399 let signer_ref: &EthersSigner = signer_arc.as_ref();
2400 let ts = if self.use_server_time {
2401 Some(self.get_server_time().await?)
2402 } else {
2403 None
2404 };
2405 let headers =
2406 crate::headers::create_l1_headers(signer_ref, self.chain_id as i32, None, ts).await?;
2407 let endpoint = format!("{}{}", self.host, DERIVE_API_KEY);
2408 let opts = RequestOptions {
2409 headers: Some(headers),
2410 data: None,
2411 params,
2412 };
2413 let val = self.http_get(&endpoint, Some(opts)).await?;
2414 let api_raw: ApiKeyRaw =
2416 serde_json::from_value(val).map_err(|e| ClobError::Other(e.to_string()))?;
2417 let api_key = ApiKeyCreds {
2418 key: api_raw.api_key,
2419 secret: api_raw.secret,
2420 passphrase: api_raw.passphrase,
2421 };
2422 Ok(api_key)
2423 }
2424
2425 pub async fn create_builder_api_key(&self) -> Result<crate::types::ApiKeyCreds, ClobError> {
2426 if self.creds.is_none() {
2427 return Err(ClobError::Other("L2 creds required".to_string()));
2428 }
2429 let signer_arc = self
2430 .signer
2431 .as_ref()
2432 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
2433 let signer_ref: &EthersSigner = signer_arc.as_ref();
2434 let ts = if self.use_server_time {
2435 Some(self.get_server_time().await?)
2436 } else {
2437 None
2438 };
2439 let headers = crate::headers::create_l2_headers(
2440 signer_ref,
2441 self.creds.as_ref().unwrap(),
2442 "POST",
2443 CREATE_BUILDER_API_KEY,
2444 None,
2445 ts,
2446 )
2447 .await?;
2448 let endpoint = format!("{}{}", self.host, CREATE_BUILDER_API_KEY);
2449 let resp: crate::types::ApiKeyCreds = self
2450 .http_post_typed(
2451 &endpoint,
2452 Some(RequestOptions::<Value> {
2453 headers: Some(headers),
2454 data: None,
2455 params: None,
2456 }),
2457 )
2458 .await?;
2459 Ok(resp)
2460 }
2461
2462 pub async fn get_builder_api_keys(&self) -> Result<Vec<crate::types::ApiKeyCreds>, ClobError> {
2463 if self.creds.is_none() {
2464 return Err(ClobError::Other("L2 creds required".to_string()));
2465 }
2466 let signer_arc = self
2467 .signer
2468 .as_ref()
2469 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
2470 let signer_ref: &EthersSigner = signer_arc.as_ref();
2471 let ts = if self.use_server_time {
2472 Some(self.get_server_time().await?)
2473 } else {
2474 None
2475 };
2476 let headers = crate::headers::create_l2_headers(
2477 signer_ref,
2478 self.creds.as_ref().unwrap(),
2479 "GET",
2480 GET_BUILDER_API_KEYS,
2481 None,
2482 ts,
2483 )
2484 .await?;
2485 let endpoint = format!("{}{}", self.host, GET_BUILDER_API_KEYS);
2486 let resp: Vec<crate::types::ApiKeyCreds> = self
2487 .http_get_typed(
2488 &endpoint,
2489 Some(RequestOptions::<Value> {
2490 headers: Some(headers),
2491 data: None,
2492 params: None,
2493 }),
2494 )
2495 .await?;
2496 Ok(resp)
2497 }
2498
2499 pub async fn revoke_builder_api_key(&self) -> Result<(), ClobError> {
2500 if self.builder_signer.is_none() {
2501 return Err(ClobError::Other("Builder signer required".to_string()));
2502 }
2503 let b_signer = self.builder_signer.as_ref().unwrap();
2504 let headers_map = b_signer
2505 .create_builder_header_payload("DELETE", REVOKE_BUILDER_API_KEY, None, None)
2506 .map_err(|e| ClobError::Other(format!("builder header error: {}", e)))?;
2507
2508 let mut headers = crate::headers::Headers::new();
2509 for (k, v) in headers_map {
2510 headers.insert(k, v);
2511 }
2512
2513 let endpoint = format!("{}{}", self.host, REVOKE_BUILDER_API_KEY);
2514 let _val: () = self
2515 .http_del_typed::<(), Value>(
2516 &endpoint,
2517 Some(RequestOptions::<Value> {
2518 headers: Some(headers),
2519 data: None,
2520 params: None,
2521 }),
2522 )
2523 .await?;
2524 Ok(())
2525 }
2526
2527 pub async fn get_sampling_simplified_markets(
2528 &self,
2529 params: Option<std::collections::HashMap<String, String>>,
2530 ) -> Result<Vec<crate::types::Market>, ClobError> {
2531 let endpoint = format!("{}{}", self.host, GET_SAMPLING_SIMPLIFIED_MARKETS);
2532 let opts = RequestOptions {
2533 headers: None,
2534 data: None,
2535 params,
2536 };
2537 let val = self.http_get(&endpoint, Some(opts)).await?;
2538 let arr = if val.is_object() && val.get("data").is_some() {
2539 val.get("data").cloned().unwrap_or_default()
2540 } else if val.is_array() {
2541 val
2542 } else {
2543 return Err(ClobError::Other(
2544 "unexpected sampling simplified markets response shape".to_string(),
2545 ));
2546 };
2547 let markets: Vec<crate::types::Market> =
2548 serde_json::from_value(arr).map_err(|e| ClobError::Other(e.to_string()))?;
2549 Ok(markets)
2550 }
2551
2552 pub async fn get_order_books(
2554 &self,
2555 params: &[crate::types::BookParams],
2556 ) -> Result<Vec<crate::types::OrderBookSummary>, ClobError> {
2557 let endpoint = format!("{}{}", self.host, GET_ORDER_BOOKS);
2558 let body = serde_json::to_value(params).map_err(|e| ClobError::Other(e.to_string()))?;
2559 let opts = RequestOptions {
2560 headers: None,
2561 data: Some(body),
2562 params: None,
2563 };
2564 let val = self.http_post(&endpoint, Some(opts)).await?;
2565 let arr = if val.is_array() {
2566 val
2567 } else if let Some(d) = val.get("data") {
2568 d.clone()
2569 } else {
2570 return Err(ClobError::Other(
2571 "unexpected order books response shape".to_string(),
2572 ));
2573 };
2574 let books: Vec<crate::types::OrderBookSummary> =
2575 serde_json::from_value(arr).map_err(|e| ClobError::Other(e.to_string()))?;
2576 Ok(books)
2577 }
2578
2579 pub async fn get_midpoint(
2581 &self,
2582 params: Option<std::collections::HashMap<String, String>>,
2583 ) -> Result<crate::types::MidpointResponse, ClobError> {
2584 let endpoint = format!("{}{}", self.host, GET_MIDPOINT);
2585 let opts = RequestOptions {
2586 headers: None,
2587 data: None,
2588 params,
2589 };
2590 let val = self.http_get(&endpoint, Some(opts)).await?;
2591 let resp: crate::types::MidpointResponse =
2592 serde_json::from_value(val).map_err(|e| ClobError::Other(e.to_string()))?;
2593 Ok(resp)
2594 }
2595
2596 pub async fn get_midpoints(
2598 &self,
2599 params: &[crate::types::BookParams],
2600 ) -> Result<serde_json::Value, ClobError> {
2601 let endpoint = format!("{}{}", self.host, GET_MIDPOINTS);
2602 let body = serde_json::to_value(params).map_err(|e| ClobError::Other(e.to_string()))?;
2603 let opts = RequestOptions {
2604 headers: None,
2605 data: Some(body),
2606 params: None,
2607 };
2608 self.http_post(&endpoint, Some(opts)).await
2609 }
2610
2611 pub async fn get_price(
2613 &self,
2614 token_id: &str,
2615 side: &str,
2616 ) -> Result<crate::types::PriceResponse, ClobError> {
2617 let endpoint = format!("{}{}", self.host, GET_PRICE);
2618 let mut qp = std::collections::HashMap::new();
2619 qp.insert("token_id".to_string(), token_id.to_string());
2620 qp.insert("side".to_string(), side.to_string());
2621 let opts = RequestOptions {
2622 headers: None,
2623 data: None,
2624 params: Some(qp),
2625 };
2626 let val = self.http_get(&endpoint, Some(opts)).await?;
2627 let resp: crate::types::PriceResponse =
2628 serde_json::from_value(val).map_err(|e| ClobError::Other(e.to_string()))?;
2629 Ok(resp)
2630 }
2631
2632 pub async fn get_prices(
2634 &self,
2635 params: &[crate::types::BookParams],
2636 ) -> Result<serde_json::Value, ClobError> {
2637 let endpoint = format!("{}{}", self.host, GET_PRICES);
2638 let body = serde_json::to_value(params).map_err(|e| ClobError::Other(e.to_string()))?;
2639 let opts = RequestOptions {
2640 headers: None,
2641 data: Some(body),
2642 params: None,
2643 };
2644 self.http_post(&endpoint, Some(opts)).await
2645 }
2646
2647 pub async fn get_spread(
2649 &self,
2650 token_id: &str,
2651 ) -> Result<crate::types::SpreadResponse, ClobError> {
2652 let endpoint = format!("{}{}", self.host, GET_SPREAD);
2653 let mut qp = std::collections::HashMap::new();
2654 qp.insert("token_id".to_string(), token_id.to_string());
2655 let opts = RequestOptions {
2656 headers: None,
2657 data: None,
2658 params: Some(qp),
2659 };
2660 let val = self.http_get(&endpoint, Some(opts)).await?;
2661 let resp: crate::types::SpreadResponse =
2662 serde_json::from_value(val).map_err(|e| ClobError::Other(e.to_string()))?;
2663 Ok(resp)
2664 }
2665
2666 pub async fn get_spreads(
2668 &self,
2669 params: &[crate::types::BookParams],
2670 ) -> Result<serde_json::Value, ClobError> {
2671 let endpoint = format!("{}{}", self.host, GET_SPREADS);
2672 let body = serde_json::to_value(params).map_err(|e| ClobError::Other(e.to_string()))?;
2673 let opts = RequestOptions {
2674 headers: None,
2675 data: Some(body),
2676 params: None,
2677 };
2678 self.http_post(&endpoint, Some(opts)).await
2679 }
2680
2681 pub async fn get_last_trade_price(
2683 &self,
2684 token_id: &str,
2685 ) -> Result<crate::types::LastTradePriceResponse, ClobError> {
2686 let endpoint = format!("{}{}", self.host, GET_LAST_TRADE_PRICE);
2687 let mut qp = std::collections::HashMap::new();
2688 qp.insert("token_id".to_string(), token_id.to_string());
2689 let opts = RequestOptions {
2690 headers: None,
2691 data: None,
2692 params: Some(qp),
2693 };
2694 let val = self.http_get(&endpoint, Some(opts)).await?;
2695 let resp: crate::types::LastTradePriceResponse =
2696 serde_json::from_value(val).map_err(|e| ClobError::Other(e.to_string()))?;
2697 Ok(resp)
2698 }
2699
2700 pub async fn get_last_trades_prices(
2702 &self,
2703 params: &[crate::types::BookParams],
2704 ) -> Result<serde_json::Value, ClobError> {
2705 let endpoint = format!("{}{}", self.host, GET_LAST_TRADES_PRICES);
2706 let body = serde_json::to_value(params).map_err(|e| ClobError::Other(e.to_string()))?;
2707 let opts = RequestOptions {
2708 headers: None,
2709 data: Some(body),
2710 params: None,
2711 };
2712 self.http_post(&endpoint, Some(opts)).await
2713 }
2714
2715 pub async fn get_prices_history(
2717 &self,
2718 params: Option<std::collections::HashMap<String, String>>,
2719 ) -> Result<Vec<crate::types::MarketPrice>, ClobError> {
2720 let endpoint = format!("{}{}", self.host, GET_PRICES_HISTORY);
2721 let opts = RequestOptions {
2722 headers: None,
2723 data: None,
2724 params,
2725 };
2726 let val = self.http_get(&endpoint, Some(opts)).await?;
2727 if let Some(history) = val.get("history") {
2729 let prices: Vec<crate::types::MarketPrice> = serde_json::from_value(history.clone())
2730 .map_err(|e| ClobError::Other(e.to_string()))?;
2731 Ok(prices)
2732 } else if val.is_array() {
2733 let prices: Vec<crate::types::MarketPrice> =
2735 serde_json::from_value(val).map_err(|e| ClobError::Other(e.to_string()))?;
2736 Ok(prices)
2737 } else {
2738 Err(ClobError::Other(
2739 "unexpected prices history response shape".to_string(),
2740 ))
2741 }
2742 }
2743
2744 pub async fn get_market_trades_events(
2745 &self,
2746 market_id: &str,
2747 params: Option<std::collections::HashMap<String, String>>,
2748 ) -> Result<Vec<crate::types::Trade>, ClobError> {
2749 let endpoint = format!("{}{}{}", self.host, GET_MARKET_TRADES_EVENTS, market_id);
2750 let opts = RequestOptions {
2751 headers: None,
2752 data: None,
2753 params,
2754 };
2755 let val = self.http_get(&endpoint, Some(opts)).await?;
2756 let arr = if val.is_array() {
2757 val
2758 } else if let Some(d) = val.get("data") {
2759 d.clone()
2760 } else {
2761 return Err(ClobError::Other(
2762 "unexpected market trades events response shape".to_string(),
2763 ));
2764 };
2765 let trades: Vec<crate::types::Trade> =
2766 serde_json::from_value(arr).map_err(|e| ClobError::Other(e.to_string()))?;
2767 Ok(trades)
2768 }
2769
2770 pub async fn get_earnings_for_user_for_day(
2772 &self,
2773 params: Option<std::collections::HashMap<String, String>>,
2774 ) -> Result<Vec<crate::types::Reward>, ClobError> {
2775 let endpoint = format!("{}{}", self.host, GET_EARNINGS_FOR_USER_FOR_DAY);
2776 let opts = RequestOptions {
2777 headers: None,
2778 data: None,
2779 params,
2780 };
2781 let val = self.http_get(&endpoint, Some(opts)).await?;
2782 let arr = if val.is_array() {
2783 val
2784 } else if let Some(d) = val.get("data") {
2785 d.clone()
2786 } else {
2787 return Err(ClobError::Other(
2788 "unexpected earnings response shape".to_string(),
2789 ));
2790 };
2791 let rewards: Vec<crate::types::Reward> =
2792 serde_json::from_value(arr).map_err(|e| ClobError::Other(e.to_string()))?;
2793 Ok(rewards)
2794 }
2795
2796 pub async fn get_total_earnings_for_user_for_day(
2797 &self,
2798 params: Option<std::collections::HashMap<String, String>>,
2799 ) -> Result<Vec<crate::types::Reward>, ClobError> {
2800 let endpoint = format!("{}{}", self.host, GET_TOTAL_EARNINGS_FOR_USER_FOR_DAY);
2801 let opts = RequestOptions {
2802 headers: None,
2803 data: None,
2804 params,
2805 };
2806 let val = self.http_get(&endpoint, Some(opts)).await?;
2807 let arr = if val.is_array() {
2808 val
2809 } else if let Some(d) = val.get("data") {
2810 d.clone()
2811 } else {
2812 return Err(ClobError::Other(
2813 "unexpected total earnings response shape".to_string(),
2814 ));
2815 };
2816 let rewards: Vec<crate::types::Reward> =
2817 serde_json::from_value(arr).map_err(|e| ClobError::Other(e.to_string()))?;
2818 Ok(rewards)
2819 }
2820
2821 pub async fn get_total_earnings_for_user_for_day_typed(
2823 &self,
2824 params: Option<std::collections::HashMap<String, String>>,
2825 ) -> Result<Vec<crate::types::Reward>, ClobError> {
2826 let val = self.get_total_earnings_for_user_for_day(params).await?;
2827 Ok(val)
2828 }
2829
2830 pub async fn get_liquidity_reward_percentages(
2831 &self,
2832 params: Option<std::collections::HashMap<String, String>>,
2833 ) -> Result<std::collections::HashMap<String, f64>, ClobError> {
2834 let endpoint = format!("{}{}", self.host, GET_LIQUIDITY_REWARD_PERCENTAGES);
2835 let opts = RequestOptions {
2836 headers: None,
2837 data: None,
2838 params,
2839 };
2840 let val = self.http_get(&endpoint, Some(opts)).await?;
2841 let obj = if val.is_object() {
2843 val
2844 } else if let Some(d) = val.get("data") {
2845 d.clone()
2846 } else {
2847 return Err(ClobError::Other(
2848 "unexpected liquidity percentages response shape".to_string(),
2849 ));
2850 };
2851 let map: std::collections::HashMap<String, f64> =
2852 serde_json::from_value(obj).map_err(|e| ClobError::Other(e.to_string()))?;
2853 Ok(map)
2854 }
2855
2856 pub async fn get_liquidity_reward_percentages_typed(
2858 &self,
2859 params: Option<std::collections::HashMap<String, String>>,
2860 ) -> Result<std::collections::HashMap<String, f64>, ClobError> {
2861 let val = self.get_liquidity_reward_percentages(params).await?;
2862 Ok(val)
2863 }
2864
2865 pub async fn get_rewards_markets_current(
2866 &self,
2867 params: Option<std::collections::HashMap<String, String>>,
2868 ) -> Result<Vec<crate::types::RewardsMarket>, ClobError> {
2869 let endpoint = format!("{}{}", self.host, GET_REWARDS_MARKETS_CURRENT);
2870 let opts = RequestOptions {
2871 headers: None,
2872 data: None,
2873 params,
2874 };
2875 let val = self.http_get(&endpoint, Some(opts)).await?;
2876 let arr = if val.is_array() {
2877 val
2878 } else if let Some(d) = val.get("data") {
2879 d.clone()
2880 } else {
2881 return Err(ClobError::Other(
2882 "unexpected rewards markets response shape".to_string(),
2883 ));
2884 };
2885 let rewards: Vec<crate::types::RewardsMarket> =
2886 serde_json::from_value(arr).map_err(|e| ClobError::Other(e.to_string()))?;
2887 Ok(rewards)
2888 }
2889
2890 pub async fn get_rewards_markets(
2891 &self,
2892 market_id: &str,
2893 params: Option<std::collections::HashMap<String, String>>,
2894 ) -> Result<Vec<crate::types::Reward>, ClobError> {
2895 let endpoint = format!("{}{}{}", self.host, GET_REWARDS_MARKETS, market_id);
2896 let opts = RequestOptions {
2897 headers: None,
2898 data: None,
2899 params,
2900 };
2901 let val = self.http_get(&endpoint, Some(opts)).await?;
2902 let arr = if val.is_array() {
2903 val
2904 } else if let Some(d) = val.get("data") {
2905 d.clone()
2906 } else {
2907 return Err(ClobError::Other(
2908 "unexpected rewards markets response shape".to_string(),
2909 ));
2910 };
2911 let rewards: Vec<crate::types::Reward> =
2912 serde_json::from_value(arr).map_err(|e| ClobError::Other(e.to_string()))?;
2913 Ok(rewards)
2914 }
2915
2916 pub async fn get_rewards_markets_typed(
2918 &self,
2919 market_id: &str,
2920 params: Option<std::collections::HashMap<String, String>>,
2921 ) -> Result<Vec<crate::types::Reward>, ClobError> {
2922 let val = self.get_rewards_markets(market_id, params).await?;
2923 Ok(val)
2924 }
2925
2926 pub async fn get_rewards_earnings_percentages(
2927 &self,
2928 params: Option<std::collections::HashMap<String, String>>,
2929 ) -> Result<Vec<crate::types::Reward>, ClobError> {
2930 let endpoint = format!("{}{}", self.host, GET_REWARDS_EARNINGS_PERCENTAGES);
2931 let opts = RequestOptions {
2932 headers: None,
2933 data: None,
2934 params,
2935 };
2936 let val = self.http_get(&endpoint, Some(opts)).await?;
2937 let arr = if val.is_array() {
2938 val
2939 } else if let Some(d) = val.get("data") {
2940 d.clone()
2941 } else {
2942 return Err(ClobError::Other(
2943 "unexpected rewards earnings percentages response shape".to_string(),
2944 ));
2945 };
2946 let rewards: Vec<crate::types::Reward> =
2947 serde_json::from_value(arr).map_err(|e| ClobError::Other(e.to_string()))?;
2948 Ok(rewards)
2949 }
2950
2951 pub async fn get_rewards_earnings_percentages_typed(
2953 &self,
2954 params: Option<std::collections::HashMap<String, String>>,
2955 ) -> Result<Vec<crate::types::Reward>, ClobError> {
2956 let val = self.get_rewards_earnings_percentages(params).await?;
2957 Ok(val)
2958 }
2959
2960 pub async fn get_builder_trades(
2961 &self,
2962 params: Option<std::collections::HashMap<String, String>>,
2963 ) -> Result<Vec<crate::types::BuilderTrade>, ClobError> {
2964 if self.builder_signer.is_none() {
2965 return Err(ClobError::Other("Builder signer required".to_string()));
2966 }
2967 let b_signer = self.builder_signer.as_ref().unwrap();
2968 let headers_map = b_signer
2969 .create_builder_header_payload("GET", GET_BUILDER_TRADES, None, None)
2970 .map_err(|e| ClobError::Other(format!("builder header error: {}", e)))?;
2971
2972 let mut headers = crate::headers::Headers::new();
2974 for (k, v) in headers_map {
2975 headers.insert(k, v);
2976 }
2977
2978 let endpoint = format!("{}{}", self.host, GET_BUILDER_TRADES);
2979 let opts = RequestOptions {
2980 headers: Some(headers),
2981 data: None,
2982 params,
2983 };
2984 let val = self.http_get(&endpoint, Some(opts)).await?;
2985 let arr = if val.is_array() {
2986 val
2987 } else if let Some(d) = val.get("data") {
2988 d.clone()
2989 } else {
2990 return Err(ClobError::Other(
2991 "unexpected builder trades response shape".to_string(),
2992 ));
2993 };
2994 let trades: Vec<crate::types::BuilderTrade> =
2995 serde_json::from_value(arr).map_err(|e| ClobError::Other(e.to_string()))?;
2996 Ok(trades)
2997 }
2998
2999 pub async fn get_builder_trades_typed(
3001 &self,
3002 params: Option<std::collections::HashMap<String, String>>,
3003 ) -> Result<Vec<crate::types::BuilderTrade>, ClobError> {
3004 self.get_builder_trades(params).await
3005 }
3006
3007 pub async fn get_earnings_for_user_for_day_typed(
3009 &self,
3010 params: Option<std::collections::HashMap<String, String>>,
3011 ) -> Result<Vec<crate::types::Reward>, ClobError> {
3012 let val = self.get_earnings_for_user_for_day(params).await?;
3013 Ok(val)
3014 }
3015
3016 pub async fn get_rewards_markets_current_typed(
3018 &self,
3019 params: Option<std::collections::HashMap<String, String>>,
3020 ) -> Result<Vec<crate::types::RewardsMarket>, ClobError> {
3021 let val = self.get_rewards_markets_current(params).await?;
3022 Ok(val)
3023 }
3024
3025 pub async fn post_heartbeat(
3039 &self,
3040 heartbeat_id: Option<&str>,
3041 ) -> Result<HeartbeatResponse, ClobError> {
3042 if self.creds.is_none() {
3043 return Err(ClobError::Other("L2 creds required".to_string()));
3044 }
3045 let signer_arc = self
3046 .signer
3047 .as_ref()
3048 .ok_or(ClobError::Other("L1 signer required".to_string()))?;
3049 let signer_ref: &EthersSigner = signer_arc.as_ref();
3050
3051 let body = if let Some(id) = heartbeat_id {
3053 serde_json::json!({ "heartbeatId": id })
3054 } else {
3055 serde_json::json!({})
3056 };
3057 let body_str = serde_json::to_string(&body).map_err(|e| ClobError::Other(e.to_string()))?;
3058
3059 let ts = if self.use_server_time {
3060 Some(self.get_server_time().await?)
3061 } else {
3062 None
3063 };
3064 let headers = crate::headers::create_l2_headers(
3065 signer_ref,
3066 self.creds.as_ref().unwrap(),
3067 "POST",
3068 POST_HEARTBEAT,
3069 Some(&body_str),
3070 ts,
3071 )
3072 .await?;
3073
3074 let endpoint = format!("{}{}", self.host, POST_HEARTBEAT);
3075 let resp: HeartbeatResponse = self
3076 .http_post_typed(
3077 &endpoint,
3078 Some(RequestOptions {
3079 headers: Some(headers),
3080 data: Some(body),
3081 params: None,
3082 }),
3083 )
3084 .await?;
3085 Ok(resp)
3086 }
3087}