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