1use crate::DeribitHttpClient;
4use crate::constants::endpoints::*;
5use crate::error::HttpError;
6use crate::model::account::Subaccount;
7use crate::model::api_key::{ApiKeyInfo, CreateApiKeyRequest, EditApiKeyRequest};
8use crate::model::position::Position;
9use crate::model::request::mass_quote::MassQuoteRequest;
10use crate::model::request::order::OrderRequest;
11use crate::model::request::position::MovePositionTrade;
12use crate::model::request::trade::TradesRequest;
13use crate::model::response::api_response::ApiResponse;
14use crate::model::response::deposit::DepositsResponse;
15use crate::model::response::margin::{MarginsResponse, OrderMargin};
16use crate::model::response::mass_quote::MassQuoteResponse;
17use crate::model::response::mmp::{MmpConfig, MmpStatus, SetMmpConfigRequest};
18use crate::model::response::order::{OrderInfoResponse, OrderResponse};
19use crate::model::response::other::{
20 AccountSummariesResponse, AccountSummaryResponse, SettlementsResponse, TransactionLogResponse,
21 TransferResultResponse,
22};
23use crate::model::response::position::MovePositionResult;
24use crate::model::response::subaccount::SubaccountDetails;
25use crate::model::response::transfer::{InternalTransfer, TransfersResponse};
26use crate::model::response::trigger::TriggerOrderHistoryResponse;
27use crate::model::response::withdrawal::WithdrawalsResponse;
28use crate::model::{
29 TransactionLogRequest, UserTradeResponseByOrder, UserTradeWithPaginationResponse,
30};
31use crate::prelude::Trigger;
32
33impl DeribitHttpClient {
35 pub async fn get_subaccounts(
53 &self,
54 with_portfolio: Option<bool>,
55 ) -> Result<Vec<Subaccount>, HttpError> {
56 let mut query_params = Vec::new();
57
58 if let Some(with_portfolio) = with_portfolio {
59 query_params.push(("with_portfolio".to_string(), with_portfolio.to_string()));
60 }
61
62 let query_string = if query_params.is_empty() {
63 String::new()
64 } else {
65 "?".to_string()
66 + &query_params
67 .iter()
68 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
69 .collect::<Vec<_>>()
70 .join("&")
71 };
72
73 let url = format!("{}{}{}", self.base_url(), GET_SUBACCOUNTS, query_string);
74
75 let response = self.make_authenticated_request(&url).await?;
76
77 if !response.status().is_success() {
78 let error_text = response
79 .text()
80 .await
81 .unwrap_or_else(|_| "Unknown error".to_string());
82 return Err(HttpError::RequestFailed(format!(
83 "Get subaccounts failed: {}",
84 error_text
85 )));
86 }
87
88 let response_text = response.text().await.map_err(|e| {
90 HttpError::InvalidResponse(format!("Failed to read response text: {}", e))
91 })?;
92
93 tracing::debug!("Raw API response: {}", response_text);
94
95 let api_response: ApiResponse<Vec<Subaccount>> = serde_json::from_str(&response_text)
96 .map_err(|e| {
97 HttpError::InvalidResponse(format!(
98 "Failed to parse JSON: {} - Raw response: {}",
99 e, response_text
100 ))
101 })?;
102
103 if let Some(error) = api_response.error {
104 return Err(HttpError::RequestFailed(format!(
105 "API error: {} - {}",
106 error.code, error.message
107 )));
108 }
109
110 api_response.result.ok_or_else(|| {
111 HttpError::InvalidResponse("No subaccounts data in response".to_string())
112 })
113 }
114
115 pub async fn get_subaccounts_details(
135 &self,
136 currency: &str,
137 with_open_orders: Option<bool>,
138 ) -> Result<Vec<SubaccountDetails>, HttpError> {
139 let mut query = format!("?currency={}", urlencoding::encode(currency));
140 if let Some(with_open_orders) = with_open_orders {
141 query.push_str(&format!("&with_open_orders={}", with_open_orders));
142 }
143 self.private_get(GET_SUBACCOUNTS_DETAILS, &query).await
144 }
145
146 pub async fn create_subaccount(&self) -> Result<Subaccount, HttpError> {
168 self.private_get(CREATE_SUBACCOUNT, "").await
169 }
170
171 pub async fn remove_subaccount(&self, subaccount_id: u64) -> Result<String, HttpError> {
197 let query = format!("?subaccount_id={}", subaccount_id);
198 self.private_get(REMOVE_SUBACCOUNT, &query).await
199 }
200
201 pub async fn change_subaccount_name(&self, sid: u64, name: &str) -> Result<String, HttpError> {
228 let query = format!("?sid={}&name={}", sid, urlencoding::encode(name));
229 self.private_get(CHANGE_SUBACCOUNT_NAME, &query).await
230 }
231
232 pub async fn toggle_subaccount_login(
260 &self,
261 sid: u64,
262 state: &str,
263 ) -> Result<String, HttpError> {
264 let query = format!("?sid={}&state={}", sid, urlencoding::encode(state));
265 self.private_get(TOGGLE_SUBACCOUNT_LOGIN, &query).await
266 }
267
268 pub async fn set_email_for_subaccount(
296 &self,
297 sid: u64,
298 email: &str,
299 ) -> Result<String, HttpError> {
300 let query = format!("?sid={}&email={}", sid, urlencoding::encode(email));
301 self.private_get(SET_EMAIL_FOR_SUBACCOUNT, &query).await
302 }
303
304 pub async fn toggle_notifications_from_subaccount(
331 &self,
332 sid: u64,
333 state: bool,
334 ) -> Result<String, HttpError> {
335 let query = format!("?sid={}&state={}", sid, state);
336 self.private_get(TOGGLE_NOTIFICATIONS_FROM_SUBACCOUNT, &query)
337 .await
338 }
339
340 pub async fn get_transaction_log(
364 &self,
365 request: TransactionLogRequest,
366 ) -> Result<TransactionLogResponse, HttpError> {
367 let mut query_params = vec![
368 ("currency", request.currency.to_string()),
369 ("start_timestamp", request.start_timestamp.to_string()),
370 ("end_timestamp", request.end_timestamp.to_string()),
371 ];
372 if let Some(query) = request.query {
373 query_params.push(("query", query));
374 }
375 if let Some(count) = request.count {
376 query_params.push(("count", count.to_string()));
377 }
378 if let Some(subaccount_id) = request.subaccount_id {
379 query_params.push(("subaccount_id", subaccount_id.to_string()));
380 }
381 if let Some(continuation) = request.continuation {
382 query_params.push(("continuation", continuation.to_string()));
383 }
384 let query = format!(
385 "?{}",
386 query_params
387 .iter()
388 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
389 .collect::<Vec<_>>()
390 .join("&")
391 );
392 self.private_get(GET_TRANSACTION_LOG, &query).await
393 }
394
395 pub async fn get_deposits(
415 &self,
416 currency: &str,
417 count: Option<u32>,
418 offset: Option<u32>,
419 ) -> Result<DepositsResponse, HttpError> {
420 let mut query = format!("?currency={}", urlencoding::encode(currency));
421 if let Some(count) = count {
422 query.push_str(&format!("&count={}", count));
423 }
424 if let Some(offset) = offset {
425 query.push_str(&format!("&offset={}", offset));
426 }
427 self.private_get(GET_DEPOSITS, &query).await
428 }
429
430 pub async fn get_withdrawals(
450 &self,
451 currency: &str,
452 count: Option<u32>,
453 offset: Option<u32>,
454 ) -> Result<WithdrawalsResponse, HttpError> {
455 let mut query = format!("?currency={}", urlencoding::encode(currency));
456 if let Some(count) = count {
457 query.push_str(&format!("&count={}", count));
458 }
459 if let Some(offset) = offset {
460 query.push_str(&format!("&offset={}", offset));
461 }
462 self.private_get(GET_WITHDRAWALS, &query).await
463 }
464
465 pub async fn submit_transfer_to_subaccount(
485 &self,
486 currency: &str,
487 amount: f64,
488 destination: u64,
489 ) -> Result<TransferResultResponse, HttpError> {
490 let query = format!(
491 "?currency={}&amount={}&destination={}",
492 urlencoding::encode(currency),
493 amount,
494 destination
495 );
496 self.private_get(SUBMIT_TRANSFER_TO_SUBACCOUNT, &query)
497 .await
498 }
499
500 pub async fn submit_transfer_to_user(
520 &self,
521 currency: &str,
522 amount: f64,
523 destination: &str,
524 ) -> Result<TransferResultResponse, HttpError> {
525 let query = format!(
526 "?currency={}&amount={}&destination={}",
527 urlencoding::encode(currency),
528 amount,
529 urlencoding::encode(destination)
530 );
531 self.private_get(SUBMIT_TRANSFER_TO_USER, &query).await
532 }
533
534 pub async fn get_transfers(
562 &self,
563 currency: &str,
564 count: Option<u32>,
565 offset: Option<u32>,
566 ) -> Result<TransfersResponse, HttpError> {
567 let mut query = format!("?currency={}", urlencoding::encode(currency));
568 if let Some(c) = count {
569 query.push_str(&format!("&count={}", c));
570 }
571 if let Some(o) = offset {
572 query.push_str(&format!("&offset={}", o));
573 }
574 self.private_get(GET_TRANSFERS, &query).await
575 }
576
577 pub async fn cancel_transfer_by_id(
604 &self,
605 currency: &str,
606 id: i64,
607 ) -> Result<InternalTransfer, HttpError> {
608 let query = format!("?currency={}&id={}", urlencoding::encode(currency), id);
609 self.private_get(CANCEL_TRANSFER_BY_ID, &query).await
610 }
611
612 pub async fn submit_transfer_between_subaccounts(
641 &self,
642 currency: &str,
643 amount: f64,
644 destination: i64,
645 source: Option<i64>,
646 ) -> Result<InternalTransfer, HttpError> {
647 let mut query = format!(
648 "?currency={}&amount={}&destination={}",
649 urlencoding::encode(currency),
650 amount,
651 destination
652 );
653 if let Some(s) = source {
654 query.push_str(&format!("&source={}", s));
655 }
656 self.private_get(SUBMIT_TRANSFER_BETWEEN_SUBACCOUNTS, &query)
657 .await
658 }
659
660 pub async fn buy_order(&self, request: OrderRequest) -> Result<OrderResponse, HttpError> {
669 let mut query_params = vec![
670 ("instrument_name".to_string(), request.instrument_name),
671 (
672 "amount".to_string(),
673 request
674 .amount
675 .map_or_else(|| "0".to_string(), |a| a.to_string()),
676 ),
677 ];
678
679 if let Some(order_type) = request.type_ {
680 query_params.push(("type".to_string(), order_type.as_str().to_string()));
681 }
682
683 if let Some(price) = request.price {
684 query_params.push(("price".to_string(), price.to_string()));
685 }
686
687 if let Some(label) = request.label {
688 query_params.push(("label".to_string(), label));
689 }
690
691 if let Some(time_in_force) = request.time_in_force {
692 query_params.push((
693 "time_in_force".to_string(),
694 time_in_force.as_str().to_string(),
695 ));
696 }
697
698 if let Some(post_only) = request.post_only
699 && post_only
700 {
701 query_params.push(("post_only".to_string(), "true".to_string()));
702 }
703
704 if let Some(reduce_only) = request.reduce_only
705 && reduce_only
706 {
707 query_params.push(("reduce_only".to_string(), "true".to_string()));
708 }
709
710 if let Some(trigger_price) = request.trigger_price {
711 query_params.push(("trigger_price".to_string(), trigger_price.to_string()));
712 }
713
714 if let Some(trigger) = request.trigger {
715 let trigger_str = match trigger {
716 Trigger::IndexPrice => "index_price",
717 Trigger::MarkPrice => "mark_price",
718 Trigger::LastPrice => "last_price",
719 };
720 query_params.push(("trigger".to_string(), trigger_str.to_string()));
721 }
722
723 let query_string = query_params
724 .iter()
725 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
726 .collect::<Vec<_>>()
727 .join("&");
728
729 let url = format!("{}{}?{}", self.base_url(), BUY, query_string);
730
731 let response = self.make_authenticated_request(&url).await?;
732
733 if !response.status().is_success() {
734 let error_text = response
735 .text()
736 .await
737 .unwrap_or_else(|_| "Unknown error".to_string());
738 return Err(HttpError::RequestFailed(format!(
739 "Buy order failed: {}",
740 error_text
741 )));
742 }
743
744 let response_text = response
746 .text()
747 .await
748 .map_err(|e| HttpError::NetworkError(e.to_string()))?;
749
750 tracing::debug!("Raw API response: {}", response_text);
751
752 let api_response: ApiResponse<OrderResponse> = serde_json::from_str(&response_text)
753 .map_err(|e| {
754 HttpError::InvalidResponse(format!(
755 "Failed to parse JSON: {} - Raw response: {}",
756 e, response_text
757 ))
758 })?;
759
760 if let Some(error) = api_response.error {
761 return Err(HttpError::RequestFailed(format!(
762 "API error: {} - {}",
763 error.code, error.message
764 )));
765 }
766
767 api_response
768 .result
769 .ok_or_else(|| HttpError::InvalidResponse("No order data in response".to_string()))
770 }
771
772 pub async fn sell_order(&self, request: OrderRequest) -> Result<OrderResponse, HttpError> {
780 let mut query_params = vec![
781 ("instrument_name".to_string(), request.instrument_name),
782 ("amount".to_string(), request.amount.unwrap().to_string()),
783 ];
784
785 if let Some(order_type) = request.type_ {
786 query_params.push(("type".to_string(), order_type.as_str().to_string()));
787 }
788
789 if let Some(price) = request.price {
790 query_params.push(("price".to_string(), price.to_string()));
791 }
792
793 if let Some(label) = request.label {
794 query_params.push(("label".to_string(), label));
795 }
796
797 if let Some(time_in_force) = request.time_in_force {
798 query_params.push((
799 "time_in_force".to_string(),
800 time_in_force.as_str().to_string(),
801 ));
802 }
803
804 if let Some(post_only) = request.post_only
805 && post_only
806 {
807 query_params.push(("post_only".to_string(), "true".to_string()));
808 }
809
810 if let Some(reduce_only) = request.reduce_only
811 && reduce_only
812 {
813 query_params.push(("reduce_only".to_string(), "true".to_string()));
814 }
815
816 if let Some(trigger_price) = request.trigger_price {
817 query_params.push(("trigger_price".to_string(), trigger_price.to_string()));
818 }
819
820 if let Some(trigger) = request.trigger {
821 let trigger_str = match trigger {
822 Trigger::IndexPrice => "index_price",
823 Trigger::MarkPrice => "mark_price",
824 Trigger::LastPrice => "last_price",
825 };
826 query_params.push(("trigger".to_string(), trigger_str.to_string()));
827 }
828
829 let query_string = query_params
830 .iter()
831 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
832 .collect::<Vec<_>>()
833 .join("&");
834
835 let url = format!("{}{}?{}", self.base_url(), SELL, query_string);
836
837 let response = self.make_authenticated_request(&url).await?;
838
839 if !response.status().is_success() {
840 let error_text = response
841 .text()
842 .await
843 .unwrap_or_else(|_| "Unknown error".to_string());
844 return Err(HttpError::RequestFailed(format!(
845 "Sell order failed: {}",
846 error_text
847 )));
848 }
849
850 let api_response: ApiResponse<OrderResponse> = response
851 .json()
852 .await
853 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
854
855 if let Some(error) = api_response.error {
856 return Err(HttpError::RequestFailed(format!(
857 "API error: {} - {}",
858 error.code, error.message
859 )));
860 }
861
862 api_response
863 .result
864 .ok_or_else(|| HttpError::InvalidResponse("No order data in response".to_string()))
865 }
866
867 pub async fn cancel_order(&self, order_id: &str) -> Result<OrderInfoResponse, HttpError> {
876 let query = format!("?order_id={}", urlencoding::encode(order_id));
877 self.private_get(CANCEL, &query).await
878 }
879
880 pub async fn cancel_all(&self) -> Result<u32, HttpError> {
888 self.private_get(CANCEL_ALL, "").await
889 }
890
891 pub async fn cancel_all_by_currency(&self, currency: &str) -> Result<u32, HttpError> {
903 let query = format!("?currency={}", urlencoding::encode(currency));
904 self.private_get(CANCEL_ALL_BY_CURRENCY, &query).await
905 }
906
907 pub async fn cancel_all_by_currency_pair(&self, currency_pair: &str) -> Result<u32, HttpError> {
919 let query = format!("?currency_pair={}", urlencoding::encode(currency_pair));
920 self.private_get(CANCEL_ALL_BY_CURRENCY_PAIR, &query).await
921 }
922
923 pub async fn cancel_all_by_instrument(&self, instrument_name: &str) -> Result<u32, HttpError> {
935 let query = format!("?instrument_name={}", urlencoding::encode(instrument_name));
936 self.private_get(CANCEL_ALL_BY_INSTRUMENT, &query).await
937 }
938
939 pub async fn cancel_all_by_kind_or_type(
952 &self,
953 kind: Option<&str>,
954 order_type: Option<&str>,
955 ) -> Result<u32, HttpError> {
956 let mut query_params = Vec::new();
957 if let Some(kind) = kind {
958 query_params.push(format!("kind={}", urlencoding::encode(kind)));
959 }
960 if let Some(order_type) = order_type {
961 query_params.push(format!("type={}", urlencoding::encode(order_type)));
962 }
963 let query = if query_params.is_empty() {
964 String::new()
965 } else {
966 format!("?{}", query_params.join("&"))
967 };
968 self.private_get(CANCEL_ALL_BY_KIND_OR_TYPE, &query).await
969 }
970
971 pub async fn cancel_by_label(&self, label: &str) -> Result<u32, HttpError> {
983 let query = format!("?label={}", urlencoding::encode(label));
984 self.private_get(CANCEL_BY_LABEL, &query).await
985 }
986
987 pub async fn get_account_summary(
997 &self,
998 currency: &str,
999 extended: Option<bool>,
1000 ) -> Result<AccountSummaryResponse, HttpError> {
1001 let mut query = format!("?currency={}", urlencoding::encode(currency));
1002 if let Some(extended) = extended {
1003 query.push_str(&format!("&extended={}", extended));
1004 }
1005 self.private_get(GET_ACCOUNT_SUMMARY, &query).await
1006 }
1007
1008 pub async fn get_account_summaries(
1029 &self,
1030 subaccount_id: Option<i64>,
1031 extended: Option<bool>,
1032 ) -> Result<AccountSummariesResponse, HttpError> {
1033 let mut params = Vec::new();
1034 if let Some(subaccount_id) = subaccount_id {
1035 params.push(format!("subaccount_id={}", subaccount_id));
1036 }
1037 if let Some(extended) = extended {
1038 params.push(format!("extended={}", extended));
1039 }
1040 let query = if params.is_empty() {
1041 String::new()
1042 } else {
1043 format!("?{}", params.join("&"))
1044 };
1045 self.private_get(GET_ACCOUNT_SUMMARIES, &query).await
1046 }
1047
1048 pub async fn get_positions(
1068 &self,
1069 currency: Option<&str>,
1070 kind: Option<&str>,
1071 subaccount_id: Option<i32>,
1072 ) -> Result<Vec<Position>, HttpError> {
1073 let mut params = Vec::new();
1074 if let Some(currency) = currency {
1075 params.push(format!("currency={}", urlencoding::encode(currency)));
1076 }
1077 if let Some(kind) = kind {
1078 params.push(format!("kind={}", urlencoding::encode(kind)));
1079 }
1080 if let Some(subaccount_id) = subaccount_id {
1081 params.push(format!("subaccount_id={}", subaccount_id));
1082 }
1083 let query = if params.is_empty() {
1084 String::new()
1085 } else {
1086 format!("?{}", params.join("&"))
1087 };
1088 self.private_get(GET_POSITIONS, &query).await
1089 }
1090
1091 pub async fn get_position(&self, instrument_name: &str) -> Result<Vec<Position>, HttpError> {
1104 let query = format!("?instrument_name={}", urlencoding::encode(instrument_name));
1105 self.private_get(GET_POSITION, &query).await
1106 }
1107
1108 pub async fn edit_order(&self, request: OrderRequest) -> Result<OrderResponse, HttpError> {
1117 let order_id = request.order_id.ok_or_else(|| {
1118 HttpError::RequestFailed("order_id is required for edit_order".to_string())
1119 })?;
1120 let mut query_params = vec![("order_id", order_id.as_str())];
1121
1122 let amount_str;
1123 if let Some(amount) = request.amount {
1124 amount_str = amount.to_string();
1125 query_params.push(("amount", amount_str.as_str()));
1126 }
1127
1128 let price_str;
1129 if let Some(price) = request.price {
1130 price_str = price.to_string();
1131 query_params.push(("price", price_str.as_str()));
1132 }
1133
1134 if let Some(post_only) = request.post_only
1135 && post_only
1136 {
1137 query_params.push(("post_only", "true"));
1138 }
1139
1140 if let Some(reduce_only) = request.reduce_only
1141 && reduce_only
1142 {
1143 query_params.push(("reduce_only", "true"));
1144 }
1145
1146 let query_string = query_params
1147 .iter()
1148 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
1149 .collect::<Vec<_>>()
1150 .join("&");
1151
1152 let url = format!("{}{}?{}", self.base_url(), EDIT, query_string);
1153
1154 let response = self.make_authenticated_request(&url).await?;
1155
1156 if !response.status().is_success() {
1157 let error_text = response
1158 .text()
1159 .await
1160 .unwrap_or_else(|_| "Unknown error".to_string());
1161 return Err(HttpError::RequestFailed(format!(
1162 "Edit order failed: {}",
1163 error_text
1164 )));
1165 }
1166
1167 let api_response: ApiResponse<OrderResponse> = response
1168 .json()
1169 .await
1170 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1171
1172 if let Some(error) = api_response.error {
1173 return Err(HttpError::RequestFailed(format!(
1174 "API error: {} - {}",
1175 error.code, error.message
1176 )));
1177 }
1178
1179 api_response
1180 .result
1181 .ok_or_else(|| HttpError::InvalidResponse("No order data in response".to_string()))
1182 }
1183
1184 pub async fn edit_order_by_label(
1210 &self,
1211 request: OrderRequest,
1212 ) -> Result<OrderResponse, HttpError> {
1213 let label = request.label.ok_or_else(|| {
1214 HttpError::RequestFailed("label is required for edit_order_by_label".to_string())
1215 })?;
1216
1217 let mut query_params = vec![
1218 ("label".to_string(), label),
1219 ("instrument_name".to_string(), request.instrument_name),
1220 ];
1221
1222 if let Some(amount) = request.amount {
1223 query_params.push(("amount".to_string(), amount.to_string()));
1224 }
1225
1226 if let Some(contracts) = request.contracts {
1227 query_params.push(("contracts".to_string(), contracts.to_string()));
1228 }
1229
1230 if let Some(price) = request.price {
1231 query_params.push(("price".to_string(), price.to_string()));
1232 }
1233
1234 if let Some(post_only) = request.post_only
1235 && post_only
1236 {
1237 query_params.push(("post_only".to_string(), "true".to_string()));
1238 }
1239
1240 if let Some(reduce_only) = request.reduce_only
1241 && reduce_only
1242 {
1243 query_params.push(("reduce_only".to_string(), "true".to_string()));
1244 }
1245
1246 if let Some(reject_post_only) = request.reject_post_only
1247 && reject_post_only
1248 {
1249 query_params.push(("reject_post_only".to_string(), "true".to_string()));
1250 }
1251
1252 if let Some(advanced) = request.advanced {
1253 let advanced_str = match advanced {
1254 crate::model::request::order::AdvancedOrderType::Usd => "usd",
1255 crate::model::request::order::AdvancedOrderType::Implv => "implv",
1256 };
1257 query_params.push(("advanced".to_string(), advanced_str.to_string()));
1258 }
1259
1260 if let Some(trigger_price) = request.trigger_price {
1261 query_params.push(("trigger_price".to_string(), trigger_price.to_string()));
1262 }
1263
1264 if let Some(mmp) = request.mmp
1265 && mmp
1266 {
1267 query_params.push(("mmp".to_string(), "true".to_string()));
1268 }
1269
1270 if let Some(valid_until) = request.valid_until {
1271 query_params.push(("valid_until".to_string(), valid_until.to_string()));
1272 }
1273
1274 let query_string = query_params
1275 .iter()
1276 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
1277 .collect::<Vec<_>>()
1278 .join("&");
1279
1280 let url = format!("{}{}?{}", self.base_url(), EDIT_BY_LABEL, query_string);
1281
1282 let response = self.make_authenticated_request(&url).await?;
1283
1284 if !response.status().is_success() {
1285 let error_text = response
1286 .text()
1287 .await
1288 .unwrap_or_else(|_| "Unknown error".to_string());
1289 return Err(HttpError::RequestFailed(format!(
1290 "Edit order by label failed: {}",
1291 error_text
1292 )));
1293 }
1294
1295 let api_response: ApiResponse<OrderResponse> = response
1296 .json()
1297 .await
1298 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1299
1300 if let Some(error) = api_response.error {
1301 return Err(HttpError::RequestFailed(format!(
1302 "API error: {} - {}",
1303 error.code, error.message
1304 )));
1305 }
1306
1307 api_response
1308 .result
1309 .ok_or_else(|| HttpError::InvalidResponse("No order data in response".to_string()))
1310 }
1311
1312 pub async fn close_position(
1335 &self,
1336 instrument_name: &str,
1337 order_type: &str,
1338 price: Option<f64>,
1339 ) -> Result<OrderResponse, HttpError> {
1340 let mut query = format!(
1341 "?instrument_name={}&type={}",
1342 urlencoding::encode(instrument_name),
1343 urlencoding::encode(order_type)
1344 );
1345 if let Some(price) = price {
1346 query.push_str(&format!("&price={}", price));
1347 }
1348 self.private_get(CLOSE_POSITION, &query).await
1349 }
1350
1351 pub async fn get_margins(
1373 &self,
1374 instrument_name: &str,
1375 amount: f64,
1376 price: f64,
1377 ) -> Result<MarginsResponse, HttpError> {
1378 let query = format!(
1379 "?instrument_name={}&amount={}&price={}",
1380 urlencoding::encode(instrument_name),
1381 amount,
1382 price
1383 );
1384 self.private_get(GET_MARGINS, &query).await
1385 }
1386
1387 pub async fn get_order_margin_by_ids(
1406 &self,
1407 ids: &[&str],
1408 ) -> Result<Vec<OrderMargin>, HttpError> {
1409 if ids.is_empty() {
1410 return Err(HttpError::RequestFailed(
1411 "ids array cannot be empty".to_string(),
1412 ));
1413 }
1414
1415 let ids_json = serde_json::to_string(ids)
1417 .map_err(|e| HttpError::InvalidResponse(format!("Failed to serialize ids: {}", e)))?;
1418
1419 let query_string = format!("ids={}", urlencoding::encode(&ids_json));
1420 let url = format!(
1421 "{}{}?{}",
1422 self.base_url(),
1423 GET_ORDER_MARGIN_BY_IDS,
1424 query_string
1425 );
1426
1427 let response = self.make_authenticated_request(&url).await?;
1428
1429 if !response.status().is_success() {
1430 let error_text = response
1431 .text()
1432 .await
1433 .unwrap_or_else(|_| "Unknown error".to_string());
1434 return Err(HttpError::RequestFailed(format!(
1435 "Get order margin by IDs failed: {}",
1436 error_text
1437 )));
1438 }
1439
1440 let api_response: ApiResponse<Vec<OrderMargin>> = response
1441 .json()
1442 .await
1443 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1444
1445 if let Some(error) = api_response.error {
1446 return Err(HttpError::RequestFailed(format!(
1447 "API error: {} - {}",
1448 error.code, error.message
1449 )));
1450 }
1451
1452 api_response.result.ok_or_else(|| {
1453 HttpError::InvalidResponse("No order margin data in response".to_string())
1454 })
1455 }
1456
1457 pub async fn get_order_state_by_label(
1478 &self,
1479 currency: &str,
1480 label: &str,
1481 ) -> Result<Vec<OrderInfoResponse>, HttpError> {
1482 let query = format!(
1483 "?currency={}&label={}",
1484 urlencoding::encode(currency),
1485 urlencoding::encode(label)
1486 );
1487 self.private_get(GET_ORDER_STATE_BY_LABEL, &query).await
1488 }
1489
1490 pub async fn get_settlement_history_by_currency(
1513 &self,
1514 currency: &str,
1515 settlement_type: Option<&str>,
1516 count: Option<u32>,
1517 continuation: Option<&str>,
1518 search_start_timestamp: Option<u64>,
1519 ) -> Result<SettlementsResponse, HttpError> {
1520 let mut query = format!("?currency={}", urlencoding::encode(currency));
1521 if let Some(settlement_type) = settlement_type {
1522 query.push_str(&format!("&type={}", urlencoding::encode(settlement_type)));
1523 }
1524 if let Some(count) = count {
1525 query.push_str(&format!("&count={}", count));
1526 }
1527 if let Some(continuation) = continuation {
1528 query.push_str(&format!(
1529 "&continuation={}",
1530 urlencoding::encode(continuation)
1531 ));
1532 }
1533 if let Some(search_start_timestamp) = search_start_timestamp {
1534 query.push_str(&format!(
1535 "&search_start_timestamp={}",
1536 search_start_timestamp
1537 ));
1538 }
1539 self.private_get(GET_SETTLEMENT_HISTORY_BY_CURRENCY, &query)
1540 .await
1541 }
1542
1543 pub async fn get_settlement_history_by_instrument(
1566 &self,
1567 instrument_name: &str,
1568 settlement_type: Option<&str>,
1569 count: Option<u32>,
1570 continuation: Option<&str>,
1571 search_start_timestamp: Option<u64>,
1572 ) -> Result<SettlementsResponse, HttpError> {
1573 let mut query = format!("?instrument_name={}", urlencoding::encode(instrument_name));
1574 if let Some(settlement_type) = settlement_type {
1575 query.push_str(&format!("&type={}", urlencoding::encode(settlement_type)));
1576 }
1577 if let Some(count) = count {
1578 query.push_str(&format!("&count={}", count));
1579 }
1580 if let Some(continuation) = continuation {
1581 query.push_str(&format!(
1582 "&continuation={}",
1583 urlencoding::encode(continuation)
1584 ));
1585 }
1586 if let Some(search_start_timestamp) = search_start_timestamp {
1587 query.push_str(&format!(
1588 "&search_start_timestamp={}",
1589 search_start_timestamp
1590 ));
1591 }
1592 self.private_get(GET_SETTLEMENT_HISTORY_BY_INSTRUMENT, &query)
1593 .await
1594 }
1595
1596 pub async fn get_trigger_order_history(
1618 &self,
1619 currency: &str,
1620 instrument_name: Option<&str>,
1621 count: Option<u32>,
1622 continuation: Option<&str>,
1623 ) -> Result<TriggerOrderHistoryResponse, HttpError> {
1624 let mut query = format!("?currency={}", urlencoding::encode(currency));
1625 if let Some(instrument_name) = instrument_name {
1626 query.push_str(&format!(
1627 "&instrument_name={}",
1628 urlencoding::encode(instrument_name)
1629 ));
1630 }
1631 if let Some(count) = count {
1632 query.push_str(&format!("&count={}", count));
1633 }
1634 if let Some(continuation) = continuation {
1635 query.push_str(&format!(
1636 "&continuation={}",
1637 urlencoding::encode(continuation)
1638 ));
1639 }
1640 self.private_get(GET_TRIGGER_ORDER_HISTORY, &query).await
1641 }
1642
1643 pub async fn move_positions(
1675 &self,
1676 currency: &str,
1677 source_uid: i64,
1678 target_uid: i64,
1679 trades: &[MovePositionTrade],
1680 ) -> Result<Vec<MovePositionResult>, HttpError> {
1681 let mut url = format!(
1682 "{}{}?currency={}&source_uid={}&target_uid={}",
1683 self.base_url(),
1684 MOVE_POSITIONS,
1685 urlencoding::encode(currency),
1686 source_uid,
1687 target_uid
1688 );
1689
1690 let trades_json = serde_json::to_string(trades).map_err(|e| {
1692 HttpError::InvalidResponse(format!("Failed to serialize trades: {}", e))
1693 })?;
1694 url.push_str(&format!("&trades={}", urlencoding::encode(&trades_json)));
1695
1696 let response = self.make_authenticated_request(&url).await?;
1697
1698 if !response.status().is_success() {
1699 let error_text = response
1700 .text()
1701 .await
1702 .unwrap_or_else(|_| "Unknown error".to_string());
1703 return Err(HttpError::RequestFailed(format!(
1704 "Move positions failed: {}",
1705 error_text
1706 )));
1707 }
1708
1709 let api_response: ApiResponse<Vec<MovePositionResult>> = response
1710 .json()
1711 .await
1712 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1713
1714 if let Some(error) = api_response.error {
1715 return Err(HttpError::RequestFailed(format!(
1716 "API error: {} - {}",
1717 error.code, error.message
1718 )));
1719 }
1720
1721 api_response.result.ok_or_else(|| {
1722 HttpError::InvalidResponse("No move positions data in response".to_string())
1723 })
1724 }
1725
1726 pub async fn get_mmp_config(
1746 &self,
1747 index_name: Option<&str>,
1748 mmp_group: Option<&str>,
1749 block_rfq: Option<bool>,
1750 ) -> Result<Vec<MmpConfig>, HttpError> {
1751 let mut params = Vec::new();
1752 if let Some(index) = index_name {
1753 params.push(format!("index_name={}", urlencoding::encode(index)));
1754 }
1755 if let Some(group) = mmp_group {
1756 params.push(format!("mmp_group={}", urlencoding::encode(group)));
1757 }
1758 if let Some(rfq) = block_rfq
1759 && rfq
1760 {
1761 params.push("block_rfq=true".to_string());
1762 }
1763 let query = if params.is_empty() {
1764 String::new()
1765 } else {
1766 format!("?{}", params.join("&"))
1767 };
1768 self.private_get(GET_MMP_CONFIG, &query).await
1769 }
1770
1771 pub async fn get_mmp_status(
1791 &self,
1792 index_name: Option<&str>,
1793 mmp_group: Option<&str>,
1794 block_rfq: Option<bool>,
1795 ) -> Result<Vec<MmpStatus>, HttpError> {
1796 let mut params = Vec::new();
1797 if let Some(index) = index_name {
1798 params.push(format!("index_name={}", urlencoding::encode(index)));
1799 }
1800 if let Some(group) = mmp_group {
1801 params.push(format!("mmp_group={}", urlencoding::encode(group)));
1802 }
1803 if let Some(rfq) = block_rfq
1804 && rfq
1805 {
1806 params.push("block_rfq=true".to_string());
1807 }
1808 let query = if params.is_empty() {
1809 String::new()
1810 } else {
1811 format!("?{}", params.join("&"))
1812 };
1813 self.private_get(GET_MMP_STATUS, &query).await
1814 }
1815
1816 pub async fn set_mmp_config(
1843 &self,
1844 request: SetMmpConfigRequest,
1845 ) -> Result<MmpConfig, HttpError> {
1846 let mut query_params = vec![
1847 ("index_name".to_string(), request.index_name),
1848 ("interval".to_string(), request.interval.to_string()),
1849 ("frozen_time".to_string(), request.frozen_time.to_string()),
1850 ];
1851
1852 if let Some(quantity_limit) = request.quantity_limit {
1853 query_params.push(("quantity_limit".to_string(), quantity_limit.to_string()));
1854 }
1855
1856 if let Some(delta_limit) = request.delta_limit {
1857 query_params.push(("delta_limit".to_string(), delta_limit.to_string()));
1858 }
1859
1860 if let Some(vega_limit) = request.vega_limit {
1861 query_params.push(("vega_limit".to_string(), vega_limit.to_string()));
1862 }
1863
1864 if let Some(max_quote_quantity) = request.max_quote_quantity {
1865 query_params.push((
1866 "max_quote_quantity".to_string(),
1867 max_quote_quantity.to_string(),
1868 ));
1869 }
1870
1871 if let Some(mmp_group) = request.mmp_group {
1872 query_params.push(("mmp_group".to_string(), mmp_group));
1873 }
1874
1875 if let Some(block_rfq) = request.block_rfq
1876 && block_rfq
1877 {
1878 query_params.push(("block_rfq".to_string(), "true".to_string()));
1879 }
1880
1881 let query_string = query_params
1882 .iter()
1883 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
1884 .collect::<Vec<_>>()
1885 .join("&");
1886
1887 let url = format!("{}{}?{}", self.base_url(), SET_MMP_CONFIG, query_string);
1888
1889 let response = self.make_authenticated_request(&url).await?;
1890
1891 if !response.status().is_success() {
1892 let error_text = response
1893 .text()
1894 .await
1895 .unwrap_or_else(|_| "Unknown error".to_string());
1896 return Err(HttpError::RequestFailed(format!(
1897 "Set MMP config failed: {}",
1898 error_text
1899 )));
1900 }
1901
1902 let api_response: ApiResponse<MmpConfig> = response
1903 .json()
1904 .await
1905 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1906
1907 if let Some(error) = api_response.error {
1908 return Err(HttpError::RequestFailed(format!(
1909 "API error: {} - {}",
1910 error.code, error.message
1911 )));
1912 }
1913
1914 api_response
1915 .result
1916 .ok_or_else(|| HttpError::InvalidResponse("No MMP config data in response".to_string()))
1917 }
1918
1919 pub async fn reset_mmp(
1939 &self,
1940 index_name: &str,
1941 mmp_group: Option<&str>,
1942 block_rfq: Option<bool>,
1943 ) -> Result<String, HttpError> {
1944 let mut query = format!("?index_name={}", urlencoding::encode(index_name));
1945 if let Some(group) = mmp_group {
1946 query.push_str(&format!("&mmp_group={}", urlencoding::encode(group)));
1947 }
1948 if let Some(rfq) = block_rfq
1949 && rfq
1950 {
1951 query.push_str("&block_rfq=true");
1952 }
1953 self.private_get(RESET_MMP, &query).await
1954 }
1955
1956 pub async fn mass_quote(
1965 &self,
1966 _quotes: MassQuoteRequest,
1967 ) -> Result<MassQuoteResponse, HttpError> {
1968 Err(HttpError::ConfigError(
1969 "Mass quote endpoint is only available via WebSocket connections. \
1970 According to Deribit's technical specifications, private/mass_quote requires \
1971 WebSocket for real-time quote management, MMP group integration, and \
1972 Cancel-on-Disconnect functionality. Please use the deribit-websocket client \
1973 for mass quote operations."
1974 .to_string(),
1975 ))
1976 }
1977
1978 pub async fn get_user_trades_by_instrument(
1992 &self,
1993 instrument_name: &str,
1994 start_seq: Option<u64>,
1995 end_seq: Option<u64>,
1996 count: Option<u32>,
1997 include_old: Option<bool>,
1998 sorting: Option<&str>,
1999 ) -> Result<UserTradeWithPaginationResponse, HttpError> {
2000 let mut query_params = vec![("instrument_name".to_string(), instrument_name.to_string())];
2001
2002 if let Some(start_seq) = start_seq {
2003 query_params.push(("start_seq".to_string(), start_seq.to_string()));
2004 }
2005
2006 if let Some(end_seq) = end_seq {
2007 query_params.push(("end_seq".to_string(), end_seq.to_string()));
2008 }
2009
2010 if let Some(count) = count {
2011 query_params.push(("count".to_string(), count.to_string()));
2012 }
2013
2014 if let Some(include_old) = include_old {
2015 query_params.push(("include_old".to_string(), include_old.to_string()));
2016 }
2017
2018 if let Some(sorting) = sorting {
2019 query_params.push(("sorting".to_string(), sorting.to_string()));
2020 }
2021
2022 let query_string = query_params
2023 .iter()
2024 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
2025 .collect::<Vec<_>>()
2026 .join("&");
2027
2028 let url = format!(
2029 "{}{}?{}",
2030 self.base_url(),
2031 GET_USER_TRADES_BY_INSTRUMENT,
2032 query_string
2033 );
2034
2035 let response = self.make_authenticated_request(&url).await?;
2036
2037 if !response.status().is_success() {
2038 let error_text = response
2039 .text()
2040 .await
2041 .unwrap_or_else(|_| "Unknown error".to_string());
2042 return Err(HttpError::RequestFailed(format!(
2043 "Get user trades by instrument failed: {}",
2044 error_text
2045 )));
2046 }
2047
2048 let response_text = response.text().await.map_err(|e| {
2050 HttpError::InvalidResponse(format!("Failed to read response text: {}", e))
2051 })?;
2052
2053 tracing::debug!(
2054 "Raw API response for get_user_trades_by_instrument: {}",
2055 response_text
2056 );
2057
2058 let api_response: ApiResponse<UserTradeWithPaginationResponse> =
2060 serde_json::from_str(&response_text).map_err(|e| {
2061 HttpError::InvalidResponse(format!(
2062 "error decoding response body: {} - Raw response: {}",
2063 e, response_text
2064 ))
2065 })?;
2066
2067 if let Some(error) = api_response.error {
2068 return Err(HttpError::RequestFailed(format!(
2069 "API error: {} - {}",
2070 error.code, error.message
2071 )));
2072 }
2073
2074 api_response.result.ok_or_else(|| {
2075 HttpError::InvalidResponse("No user trades data in response".to_string())
2076 })
2077 }
2078
2079 pub async fn cancel_quotes(&self, cancel_type: Option<&str>) -> Result<u32, HttpError> {
2088 let query = format!(
2089 "?cancel_type={}",
2090 urlencoding::encode(cancel_type.unwrap_or("all"))
2091 );
2092 self.private_get(CANCEL_QUOTES, &query).await
2093 }
2094
2095 pub async fn get_open_orders(
2105 &self,
2106 kind: Option<&str>,
2107 order_type: Option<&str>,
2108 ) -> Result<Vec<OrderInfoResponse>, HttpError> {
2109 let mut params = Vec::new();
2110 if let Some(kind) = kind {
2111 params.push(format!("kind={}", urlencoding::encode(kind)));
2112 }
2113 if let Some(order_type) = order_type {
2114 params.push(format!("type={}", urlencoding::encode(order_type)));
2115 }
2116 let query = if params.is_empty() {
2117 String::new()
2118 } else {
2119 format!("?{}", params.join("&"))
2120 };
2121 self.private_get(GET_OPEN_ORDERS, &query).await
2122 }
2123
2124 pub async fn get_open_orders_by_label(
2134 &self,
2135 label: &str,
2136 currency: &str,
2137 ) -> Result<Vec<OrderInfoResponse>, HttpError> {
2138 let query = format!(
2139 "?label={}¤cy={}",
2140 urlencoding::encode(label),
2141 urlencoding::encode(currency)
2142 );
2143 self.private_get(GET_OPEN_ORDERS_BY_LABEL, &query).await
2144 }
2145
2146 pub async fn get_order_state(&self, order_id: &str) -> Result<OrderInfoResponse, HttpError> {
2155 let query = format!("?order_id={}", urlencoding::encode(order_id));
2156 self.private_get(GET_ORDER_STATE, &query).await
2157 }
2158
2159 pub async fn get_open_orders_by_currency(
2170 &self,
2171 currency: &str,
2172 kind: Option<&str>,
2173 order_type: Option<&str>,
2174 ) -> Result<Vec<OrderInfoResponse>, HttpError> {
2175 let mut query = format!("?currency={}", urlencoding::encode(currency));
2176 if let Some(kind) = kind {
2177 query.push_str(&format!("&kind={}", urlencoding::encode(kind)));
2178 }
2179 if let Some(order_type) = order_type {
2180 query.push_str(&format!("&type={}", urlencoding::encode(order_type)));
2181 }
2182 self.private_get(GET_OPEN_ORDERS_BY_CURRENCY, &query).await
2183 }
2184
2185 pub async fn get_open_orders_by_instrument(
2195 &self,
2196 instrument_name: &str,
2197 order_type: Option<&str>,
2198 ) -> Result<Vec<OrderInfoResponse>, HttpError> {
2199 let mut query = format!("?instrument_name={}", urlencoding::encode(instrument_name));
2200 if let Some(order_type) = order_type {
2201 query.push_str(&format!("&type={}", urlencoding::encode(order_type)));
2202 }
2203 self.private_get(GET_OPEN_ORDERS_BY_INSTRUMENT, &query)
2204 .await
2205 }
2206
2207 pub async fn get_order_history(
2219 &self,
2220 currency: &str,
2221 kind: Option<&str>,
2222 count: Option<u32>,
2223 offset: Option<u32>,
2224 ) -> Result<Vec<OrderInfoResponse>, HttpError> {
2225 let mut query = format!("?currency={}", urlencoding::encode(currency));
2226 if let Some(kind) = kind {
2227 query.push_str(&format!("&kind={}", urlencoding::encode(kind)));
2228 }
2229 if let Some(count) = count {
2230 query.push_str(&format!("&count={}", count));
2231 }
2232 if let Some(offset) = offset {
2233 query.push_str(&format!("&offset={}", offset));
2234 }
2235 self.private_get(GET_ORDER_HISTORY_BY_CURRENCY, &query)
2236 .await
2237 }
2238
2239 pub async fn get_order_history_by_currency(
2251 &self,
2252 currency: &str,
2253 kind: Option<&str>,
2254 count: Option<u32>,
2255 offset: Option<u32>,
2256 ) -> Result<Vec<OrderInfoResponse>, HttpError> {
2257 self.get_order_history(currency, kind, count, offset).await
2259 }
2260
2261 pub async fn get_order_history_by_instrument(
2272 &self,
2273 instrument_name: &str,
2274 count: Option<u32>,
2275 offset: Option<u32>,
2276 ) -> Result<Vec<OrderInfoResponse>, HttpError> {
2277 let mut query = format!("?instrument_name={}", urlencoding::encode(instrument_name));
2278 if let Some(count) = count {
2279 query.push_str(&format!("&count={}", count));
2280 }
2281 if let Some(offset) = offset {
2282 query.push_str(&format!("&offset={}", offset));
2283 }
2284 self.private_get(GET_ORDER_HISTORY_BY_INSTRUMENT, &query)
2285 .await
2286 }
2287
2288 #[allow(clippy::too_many_arguments)]
2308 pub async fn get_user_trades_by_currency(
2309 &self,
2310 request: TradesRequest,
2311 ) -> Result<UserTradeWithPaginationResponse, HttpError> {
2312 let mut query_params = vec![("currency".to_string(), request.currency.to_string())];
2313
2314 if let Some(kind) = request.kind {
2315 query_params.push(("kind".to_string(), kind.to_string()));
2316 }
2317
2318 if let Some(start_id) = request.start_id {
2319 query_params.push(("start_id".to_string(), start_id));
2320 }
2321
2322 if let Some(end_id) = request.end_id {
2323 query_params.push(("end_id".to_string(), end_id));
2324 }
2325
2326 if let Some(count) = request.count {
2327 query_params.push(("count".to_string(), count.to_string()));
2328 }
2329
2330 if let Some(start_timestamp) = request.start_timestamp {
2331 query_params.push(("start_timestamp".to_string(), start_timestamp.to_string()));
2332 }
2333
2334 if let Some(end_timestamp) = request.end_timestamp {
2335 query_params.push(("end_timestamp".to_string(), end_timestamp.to_string()));
2336 }
2337
2338 if let Some(sorting) = request.sorting {
2339 query_params.push(("sorting".to_string(), sorting.to_string()));
2340 }
2341
2342 if let Some(historical) = request.historical {
2343 query_params.push(("historical".to_string(), historical.to_string()));
2344 }
2345
2346 if let Some(subaccount_id) = request.subaccount_id {
2347 query_params.push(("subaccount_id".to_string(), subaccount_id.to_string()));
2348 }
2349
2350 let query_string = query_params
2351 .iter()
2352 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
2353 .collect::<Vec<_>>()
2354 .join("&");
2355
2356 let url = format!(
2357 "{}{}?{}",
2358 self.base_url(),
2359 GET_USER_TRADES_BY_CURRENCY,
2360 query_string
2361 );
2362
2363 let response = self.make_authenticated_request(&url).await?;
2364
2365 if !response.status().is_success() {
2366 let error_text = response
2367 .text()
2368 .await
2369 .unwrap_or_else(|_| "Unknown error".to_string());
2370 return Err(HttpError::RequestFailed(format!(
2371 "Get user trades by currency failed: {}",
2372 error_text
2373 )));
2374 }
2375
2376 let response_text = response.text().await.map_err(|e| {
2378 HttpError::InvalidResponse(format!("Failed to read response text: {}", e))
2379 })?;
2380
2381 tracing::debug!(
2382 "Raw API response for get_user_trades_by_order: {}",
2383 response_text
2384 );
2385
2386 let api_response: ApiResponse<UserTradeWithPaginationResponse> =
2388 serde_json::from_str(&response_text).map_err(|e| {
2389 HttpError::InvalidResponse(format!(
2390 "error decoding response body: {} - Raw response: {}",
2391 e, response_text
2392 ))
2393 })?;
2394
2395 if let Some(error) = api_response.error {
2396 return Err(HttpError::RequestFailed(format!(
2397 "API error: {} - {}",
2398 error.code, error.message
2399 )));
2400 }
2401
2402 api_response.result.ok_or_else(|| {
2403 HttpError::InvalidResponse("No user trades data in response".to_string())
2404 })
2405 }
2406
2407 #[allow(clippy::too_many_arguments)]
2427 pub async fn get_user_trades_by_currency_and_time(
2428 &self,
2429 request: TradesRequest,
2430 ) -> Result<UserTradeWithPaginationResponse, HttpError> {
2431 let mut query_params = vec![("currency".to_string(), request.currency.to_string())];
2432
2433 if let Some(kind) = request.kind {
2434 query_params.push(("kind".to_string(), kind.to_string()));
2435 }
2436
2437 if let Some(start_id) = request.start_id {
2438 query_params.push(("start_id".to_string(), start_id));
2439 }
2440
2441 if let Some(end_id) = request.end_id {
2442 query_params.push(("end_id".to_string(), end_id));
2443 }
2444
2445 if let Some(count) = request.count {
2446 query_params.push(("count".to_string(), count.to_string()));
2447 }
2448
2449 if let Some(start_timestamp) = request.start_timestamp {
2450 query_params.push(("start_timestamp".to_string(), start_timestamp.to_string()));
2451 }
2452
2453 if let Some(end_timestamp) = request.end_timestamp {
2454 query_params.push(("end_timestamp".to_string(), end_timestamp.to_string()));
2455 }
2456
2457 if let Some(sorting) = request.sorting {
2458 query_params.push(("sorting".to_string(), sorting.to_string()));
2459 }
2460
2461 if let Some(historical) = request.historical {
2462 query_params.push(("historical".to_string(), historical.to_string()));
2463 }
2464
2465 if let Some(subaccount_id) = request.subaccount_id {
2466 query_params.push(("subaccount_id".to_string(), subaccount_id.to_string()));
2467 }
2468
2469 let query_string = query_params
2470 .iter()
2471 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
2472 .collect::<Vec<_>>()
2473 .join("&");
2474
2475 let url = format!(
2476 "{}{}?{}",
2477 self.base_url(),
2478 GET_USER_TRADES_BY_CURRENCY_AND_TIME,
2479 query_string
2480 );
2481
2482 let response = self.make_authenticated_request(&url).await?;
2483
2484 if !response.status().is_success() {
2485 let error_text = response
2486 .text()
2487 .await
2488 .unwrap_or_else(|_| "Unknown error".to_string());
2489 return Err(HttpError::RequestFailed(format!(
2490 "Get user trades by currency and time failed: {}",
2491 error_text
2492 )));
2493 }
2494
2495 let response_text = response.text().await.map_err(|e| {
2497 HttpError::InvalidResponse(format!("Failed to read response text: {}", e))
2498 })?;
2499
2500 tracing::debug!(
2501 "Raw API response for get_user_trades_by_order: {}",
2502 response_text
2503 );
2504
2505 let api_response: ApiResponse<UserTradeWithPaginationResponse> =
2507 serde_json::from_str(&response_text).map_err(|e| {
2508 HttpError::InvalidResponse(format!(
2509 "error decoding response body: {} - Raw response: {}",
2510 e, response_text
2511 ))
2512 })?;
2513
2514 if let Some(error) = api_response.error {
2515 return Err(HttpError::RequestFailed(format!(
2516 "API error: {} - {}",
2517 error.code, error.message
2518 )));
2519 }
2520
2521 api_response.result.ok_or_else(|| {
2522 HttpError::InvalidResponse("No user trades data in response".to_string())
2523 })
2524 }
2525
2526 pub async fn get_user_trades_by_instrument_and_time(
2540 &self,
2541 instrument_name: &str,
2542 start_timestamp: u64,
2543 end_timestamp: u64,
2544 count: Option<u32>,
2545 include_old: Option<bool>,
2546 sorting: Option<&str>,
2547 ) -> Result<UserTradeWithPaginationResponse, HttpError> {
2548 let mut query_params = vec![
2549 ("instrument_name".to_string(), instrument_name.to_string()),
2550 ("start_timestamp".to_string(), start_timestamp.to_string()),
2551 ("end_timestamp".to_string(), end_timestamp.to_string()),
2552 ];
2553
2554 if let Some(count) = count {
2555 query_params.push(("count".to_string(), count.to_string()));
2556 }
2557
2558 if let Some(include_old) = include_old {
2559 query_params.push(("include_old".to_string(), include_old.to_string()));
2560 }
2561
2562 if let Some(sorting) = sorting {
2563 query_params.push(("sorting".to_string(), sorting.to_string()));
2564 }
2565
2566 let query_string = query_params
2567 .iter()
2568 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
2569 .collect::<Vec<_>>()
2570 .join("&");
2571
2572 let url = format!(
2573 "{}{}?{}",
2574 self.base_url(),
2575 GET_USER_TRADES_BY_INSTRUMENT_AND_TIME,
2576 query_string
2577 );
2578
2579 let response = self.make_authenticated_request(&url).await?;
2580
2581 if !response.status().is_success() {
2582 let error_text = response
2583 .text()
2584 .await
2585 .unwrap_or_else(|_| "Unknown error".to_string());
2586 return Err(HttpError::RequestFailed(format!(
2587 "Get user trades by instrument and time failed: {}",
2588 error_text
2589 )));
2590 }
2591
2592 let response_text = response.text().await.map_err(|e| {
2594 HttpError::InvalidResponse(format!("Failed to read response text: {}", e))
2595 })?;
2596
2597 tracing::debug!(
2598 "Raw API response for get_user_trades_by_instrument_and_time: {}",
2599 response_text
2600 );
2601
2602 let api_response: ApiResponse<UserTradeWithPaginationResponse> =
2604 serde_json::from_str(&response_text).map_err(|e| {
2605 HttpError::InvalidResponse(format!(
2606 "error decoding response body: {} - Raw response: {}",
2607 e, response_text
2608 ))
2609 })?;
2610
2611 if let Some(error) = api_response.error {
2612 return Err(HttpError::RequestFailed(format!(
2613 "API error: {} - {}",
2614 error.code, error.message
2615 )));
2616 }
2617
2618 api_response.result.ok_or_else(|| {
2619 HttpError::InvalidResponse("No user trades data in response".to_string())
2620 })
2621 }
2622
2623 pub async fn get_user_trades_by_order(
2633 &self,
2634 order_id: &str,
2635 sorting: Option<&str>,
2636 historical: bool,
2637 ) -> Result<Vec<UserTradeResponseByOrder>, HttpError> {
2638 let mut query = format!("?order_id={}", urlencoding::encode(order_id));
2639 if let Some(sorting) = sorting {
2640 query.push_str(&format!("&sorting={}", urlencoding::encode(sorting)));
2641 }
2642 if historical {
2643 query.push_str("&historical=true");
2644 }
2645 self.private_get(GET_USER_TRADES_BY_ORDER, &query).await
2646 }
2647
2648 pub async fn create_api_key(
2684 &self,
2685 request: CreateApiKeyRequest,
2686 ) -> Result<ApiKeyInfo, HttpError> {
2687 let mut query_params = vec![("max_scope".to_string(), request.max_scope)];
2688
2689 if let Some(name) = request.name {
2690 query_params.push(("name".to_string(), name));
2691 }
2692
2693 if let Some(public_key) = request.public_key {
2694 query_params.push(("public_key".to_string(), public_key));
2695 }
2696
2697 if let Some(enabled_features) = request.enabled_features {
2698 for feature in enabled_features {
2699 query_params.push(("enabled_features".to_string(), feature));
2700 }
2701 }
2702
2703 let query_string = query_params
2704 .iter()
2705 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
2706 .collect::<Vec<_>>()
2707 .join("&");
2708
2709 let url = format!("{}{}?{}", self.base_url(), CREATE_API_KEY, query_string);
2710
2711 let response = self.make_authenticated_request(&url).await?;
2712
2713 if !response.status().is_success() {
2714 let error_text = response
2715 .text()
2716 .await
2717 .unwrap_or_else(|_| "Unknown error".to_string());
2718 return Err(HttpError::RequestFailed(format!(
2719 "Create API key failed: {}",
2720 error_text
2721 )));
2722 }
2723
2724 let api_response: ApiResponse<ApiKeyInfo> = response
2725 .json()
2726 .await
2727 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
2728
2729 if let Some(error) = api_response.error {
2730 return Err(HttpError::RequestFailed(format!(
2731 "API error: {} - {}",
2732 error.code, error.message
2733 )));
2734 }
2735
2736 api_response
2737 .result
2738 .ok_or_else(|| HttpError::InvalidResponse("No API key data in response".to_string()))
2739 }
2740
2741 pub async fn edit_api_key(&self, request: EditApiKeyRequest) -> Result<ApiKeyInfo, HttpError> {
2757 let mut query_params = vec![
2758 ("id".to_string(), request.id.to_string()),
2759 ("max_scope".to_string(), request.max_scope),
2760 ];
2761
2762 if let Some(name) = request.name {
2763 query_params.push(("name".to_string(), name));
2764 }
2765
2766 if let Some(enabled) = request.enabled {
2767 query_params.push(("enabled".to_string(), enabled.to_string()));
2768 }
2769
2770 if let Some(enabled_features) = request.enabled_features {
2771 for feature in enabled_features {
2772 query_params.push(("enabled_features".to_string(), feature));
2773 }
2774 }
2775
2776 if let Some(ip_whitelist) = request.ip_whitelist {
2777 for ip in ip_whitelist {
2778 query_params.push(("ip_whitelist".to_string(), ip));
2779 }
2780 }
2781
2782 let query_string = query_params
2783 .iter()
2784 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
2785 .collect::<Vec<_>>()
2786 .join("&");
2787
2788 let url = format!("{}{}?{}", self.base_url(), EDIT_API_KEY, query_string);
2789
2790 let response = self.make_authenticated_request(&url).await?;
2791
2792 if !response.status().is_success() {
2793 let error_text = response
2794 .text()
2795 .await
2796 .unwrap_or_else(|_| "Unknown error".to_string());
2797 return Err(HttpError::RequestFailed(format!(
2798 "Edit API key failed: {}",
2799 error_text
2800 )));
2801 }
2802
2803 let api_response: ApiResponse<ApiKeyInfo> = response
2804 .json()
2805 .await
2806 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
2807
2808 if let Some(error) = api_response.error {
2809 return Err(HttpError::RequestFailed(format!(
2810 "API error: {} - {}",
2811 error.code, error.message
2812 )));
2813 }
2814
2815 api_response
2816 .result
2817 .ok_or_else(|| HttpError::InvalidResponse("No API key data in response".to_string()))
2818 }
2819
2820 pub async fn disable_api_key(&self, id: u64) -> Result<ApiKeyInfo, HttpError> {
2837 let query = format!("?id={}", id);
2838 self.private_get(DISABLE_API_KEY, &query).await
2839 }
2840
2841 pub async fn enable_api_key(&self, id: u64) -> Result<ApiKeyInfo, HttpError> {
2857 let query = format!("?id={}", id);
2858 self.private_get(ENABLE_API_KEY, &query).await
2859 }
2860
2861 pub async fn list_api_keys(&self) -> Result<Vec<ApiKeyInfo>, HttpError> {
2888 self.private_get(LIST_API_KEYS, "").await
2889 }
2890
2891 pub async fn remove_api_key(&self, id: u64) -> Result<String, HttpError> {
2907 let query = format!("?id={}", id);
2908 self.private_get(REMOVE_API_KEY, &query).await
2909 }
2910
2911 pub async fn reset_api_key(&self, id: u64) -> Result<ApiKeyInfo, HttpError> {
2928 let query = format!("?id={}", id);
2929 self.private_get(RESET_API_KEY, &query).await
2930 }
2931
2932 pub async fn change_api_key_name(&self, id: u64, name: &str) -> Result<ApiKeyInfo, HttpError> {
2949 let query = format!("?id={}&name={}", id, urlencoding::encode(name));
2950 self.private_get(CHANGE_API_KEY_NAME, &query).await
2951 }
2952
2953 pub async fn change_scope_in_api_key(
2970 &self,
2971 id: u64,
2972 max_scope: &str,
2973 ) -> Result<ApiKeyInfo, HttpError> {
2974 let query = format!("?id={}&max_scope={}", id, urlencoding::encode(max_scope));
2975 self.private_get(CHANGE_SCOPE_IN_API_KEY, &query).await
2976 }
2977
2978 pub async fn save_address_beneficiary(
3019 &self,
3020 request: &crate::model::SaveAddressBeneficiaryRequest,
3021 ) -> Result<crate::model::AddressBeneficiary, HttpError> {
3022 let mut params = vec![
3023 format!("currency={}", urlencoding::encode(&request.currency)),
3024 format!("address={}", urlencoding::encode(&request.address)),
3025 format!("agreed={}", request.agreed),
3026 format!("personal={}", request.personal),
3027 format!("unhosted={}", request.unhosted),
3028 format!(
3029 "beneficiary_vasp_name={}",
3030 urlencoding::encode(&request.beneficiary_vasp_name)
3031 ),
3032 format!(
3033 "beneficiary_vasp_did={}",
3034 urlencoding::encode(&request.beneficiary_vasp_did)
3035 ),
3036 format!(
3037 "beneficiary_address={}",
3038 urlencoding::encode(&request.beneficiary_address)
3039 ),
3040 ];
3041
3042 if let Some(ref tag) = request.tag {
3043 params.push(format!("tag={}", urlencoding::encode(tag)));
3044 }
3045 if let Some(ref website) = request.beneficiary_vasp_website {
3046 params.push(format!(
3047 "beneficiary_vasp_website={}",
3048 urlencoding::encode(website)
3049 ));
3050 }
3051 if let Some(ref first_name) = request.beneficiary_first_name {
3052 params.push(format!(
3053 "beneficiary_first_name={}",
3054 urlencoding::encode(first_name)
3055 ));
3056 }
3057 if let Some(ref last_name) = request.beneficiary_last_name {
3058 params.push(format!(
3059 "beneficiary_last_name={}",
3060 urlencoding::encode(last_name)
3061 ));
3062 }
3063 if let Some(ref company_name) = request.beneficiary_company_name {
3064 params.push(format!(
3065 "beneficiary_company_name={}",
3066 urlencoding::encode(company_name)
3067 ));
3068 }
3069
3070 let url = format!(
3071 "{}{}?{}",
3072 self.base_url(),
3073 SAVE_ADDRESS_BENEFICIARY,
3074 params.join("&")
3075 );
3076
3077 let response = self.make_authenticated_request(&url).await?;
3078
3079 if !response.status().is_success() {
3080 let error_text = response
3081 .text()
3082 .await
3083 .unwrap_or_else(|_| "Unknown error".to_string());
3084 return Err(HttpError::RequestFailed(format!(
3085 "Save address beneficiary failed: {}",
3086 error_text
3087 )));
3088 }
3089
3090 let api_response: ApiResponse<crate::model::AddressBeneficiary> = response
3091 .json()
3092 .await
3093 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
3094
3095 if let Some(error) = api_response.error {
3096 return Err(HttpError::RequestFailed(format!(
3097 "API error: {} - {}",
3098 error.code, error.message
3099 )));
3100 }
3101
3102 api_response.result.ok_or_else(|| {
3103 HttpError::InvalidResponse("No beneficiary data in response".to_string())
3104 })
3105 }
3106
3107 pub async fn delete_address_beneficiary(
3133 &self,
3134 currency: &str,
3135 address: &str,
3136 tag: Option<&str>,
3137 ) -> Result<String, HttpError> {
3138 let mut query = format!(
3139 "?currency={}&address={}",
3140 urlencoding::encode(currency),
3141 urlencoding::encode(address)
3142 );
3143 if let Some(t) = tag {
3144 query.push_str(&format!("&tag={}", urlencoding::encode(t)));
3145 }
3146 self.private_get(DELETE_ADDRESS_BENEFICIARY, &query).await
3147 }
3148
3149 pub async fn get_address_beneficiary(
3175 &self,
3176 currency: &str,
3177 address: &str,
3178 tag: Option<&str>,
3179 ) -> Result<crate::model::AddressBeneficiary, HttpError> {
3180 let mut query = format!(
3181 "?currency={}&address={}",
3182 urlencoding::encode(currency),
3183 urlencoding::encode(address)
3184 );
3185 if let Some(t) = tag {
3186 query.push_str(&format!("&tag={}", urlencoding::encode(t)));
3187 }
3188 self.private_get(GET_ADDRESS_BENEFICIARY, &query).await
3189 }
3190
3191 pub async fn list_address_beneficiaries(
3221 &self,
3222 request: Option<&crate::model::ListAddressBeneficiariesRequest>,
3223 ) -> Result<crate::model::ListAddressBeneficiariesResponse, HttpError> {
3224 let mut params: Vec<String> = Vec::new();
3225
3226 if let Some(req) = request {
3227 if let Some(ref currency) = req.currency {
3228 params.push(format!("currency={}", urlencoding::encode(currency)));
3229 }
3230 if let Some(ref address) = req.address {
3231 params.push(format!("address={}", urlencoding::encode(address)));
3232 }
3233 if let Some(ref tag) = req.tag {
3234 params.push(format!("tag={}", urlencoding::encode(tag)));
3235 }
3236 if let Some(created_before) = req.created_before {
3237 params.push(format!("created_before={}", created_before));
3238 }
3239 if let Some(created_after) = req.created_after {
3240 params.push(format!("created_after={}", created_after));
3241 }
3242 if let Some(updated_before) = req.updated_before {
3243 params.push(format!("updated_before={}", updated_before));
3244 }
3245 if let Some(updated_after) = req.updated_after {
3246 params.push(format!("updated_after={}", updated_after));
3247 }
3248 if let Some(personal) = req.personal {
3249 params.push(format!("personal={}", personal));
3250 }
3251 if let Some(unhosted) = req.unhosted {
3252 params.push(format!("unhosted={}", unhosted));
3253 }
3254 if let Some(ref vasp_name) = req.beneficiary_vasp_name {
3255 params.push(format!(
3256 "beneficiary_vasp_name={}",
3257 urlencoding::encode(vasp_name)
3258 ));
3259 }
3260 if let Some(ref vasp_did) = req.beneficiary_vasp_did {
3261 params.push(format!(
3262 "beneficiary_vasp_did={}",
3263 urlencoding::encode(vasp_did)
3264 ));
3265 }
3266 if let Some(ref vasp_website) = req.beneficiary_vasp_website {
3267 params.push(format!(
3268 "beneficiary_vasp_website={}",
3269 urlencoding::encode(vasp_website)
3270 ));
3271 }
3272 if let Some(limit) = req.limit {
3273 params.push(format!("limit={}", limit));
3274 }
3275 if let Some(ref continuation) = req.continuation {
3276 params.push(format!(
3277 "continuation={}",
3278 urlencoding::encode(continuation)
3279 ));
3280 }
3281 }
3282
3283 let url = if params.is_empty() {
3284 format!("{}{}", self.base_url(), LIST_ADDRESS_BENEFICIARIES)
3285 } else {
3286 format!(
3287 "{}{}?{}",
3288 self.base_url(),
3289 LIST_ADDRESS_BENEFICIARIES,
3290 params.join("&")
3291 )
3292 };
3293
3294 let response = self.make_authenticated_request(&url).await?;
3295
3296 if !response.status().is_success() {
3297 let error_text = response
3298 .text()
3299 .await
3300 .unwrap_or_else(|_| "Unknown error".to_string());
3301 return Err(HttpError::RequestFailed(format!(
3302 "List address beneficiaries failed: {}",
3303 error_text
3304 )));
3305 }
3306
3307 let api_response: ApiResponse<crate::model::ListAddressBeneficiariesResponse> = response
3308 .json()
3309 .await
3310 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
3311
3312 if let Some(error) = api_response.error {
3313 return Err(HttpError::RequestFailed(format!(
3314 "API error: {} - {}",
3315 error.code, error.message
3316 )));
3317 }
3318
3319 api_response.result.ok_or_else(|| {
3320 HttpError::InvalidResponse("No beneficiaries data in response".to_string())
3321 })
3322 }
3323
3324 pub async fn set_clearance_originator(
3364 &self,
3365 deposit_id: &crate::model::DepositId,
3366 originator: &crate::model::Originator,
3367 ) -> Result<crate::model::ClearanceDepositResult, HttpError> {
3368 let deposit_id_json = serde_json::to_string(deposit_id).map_err(|e| {
3369 HttpError::InvalidResponse(format!("Failed to serialize deposit_id: {}", e))
3370 })?;
3371 let originator_json = serde_json::to_string(originator).map_err(|e| {
3372 HttpError::InvalidResponse(format!("Failed to serialize originator: {}", e))
3373 })?;
3374
3375 let url = format!(
3376 "{}{}?deposit_id={}&originator={}",
3377 self.base_url(),
3378 SET_CLEARANCE_ORIGINATOR,
3379 urlencoding::encode(&deposit_id_json),
3380 urlencoding::encode(&originator_json)
3381 );
3382
3383 let response = self.make_authenticated_request(&url).await?;
3384
3385 if !response.status().is_success() {
3386 let error_text = response
3387 .text()
3388 .await
3389 .unwrap_or_else(|_| "Unknown error".to_string());
3390 return Err(HttpError::RequestFailed(format!(
3391 "Set clearance originator failed: {}",
3392 error_text
3393 )));
3394 }
3395
3396 let api_response: ApiResponse<crate::model::ClearanceDepositResult> = response
3397 .json()
3398 .await
3399 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
3400
3401 if let Some(error) = api_response.error {
3402 return Err(HttpError::RequestFailed(format!(
3403 "API error: {} - {}",
3404 error.code, error.message
3405 )));
3406 }
3407
3408 api_response
3409 .result
3410 .ok_or_else(|| HttpError::InvalidResponse("No deposit result in response".to_string()))
3411 }
3412
3413 pub async fn get_access_log(
3423 &self,
3424 count: Option<u32>,
3425 offset: Option<u32>,
3426 ) -> Result<crate::model::AccessLogResponse, HttpError> {
3427 let mut params = Vec::new();
3428 if let Some(count) = count {
3429 params.push(format!("count={}", count));
3430 }
3431 if let Some(offset) = offset {
3432 params.push(format!("offset={}", offset));
3433 }
3434 let query = if params.is_empty() {
3435 String::new()
3436 } else {
3437 format!("?{}", params.join("&"))
3438 };
3439 self.private_get(crate::constants::endpoints::GET_ACCESS_LOG, &query)
3440 .await
3441 }
3442
3443 pub async fn get_user_locks(&self) -> Result<Vec<crate::model::UserLock>, HttpError> {
3448 self.private_get(crate::constants::endpoints::GET_USER_LOCKS, "")
3449 .await
3450 }
3451
3452 pub async fn list_custody_accounts(
3461 &self,
3462 currency: &str,
3463 ) -> Result<Vec<crate::model::CustodyAccount>, HttpError> {
3464 let query = format!("?currency={}", urlencoding::encode(currency));
3465 self.private_get(crate::constants::endpoints::LIST_CUSTODY_ACCOUNTS, &query)
3466 .await
3467 }
3468
3469 pub async fn simulate_portfolio(
3478 &self,
3479 request: crate::model::SimulatePortfolioRequest,
3480 ) -> Result<crate::model::SimulatePortfolioResponse, HttpError> {
3481 let mut query_params = vec![format!(
3482 "currency={}",
3483 urlencoding::encode(&request.currency)
3484 )];
3485
3486 if let Some(add_positions) = request.add_positions {
3487 query_params.push(format!("add_positions={}", add_positions));
3488 }
3489
3490 if let Some(ref positions) = request.simulated_positions {
3491 let positions_json = serde_json::to_string(positions)
3492 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
3493 query_params.push(format!(
3494 "simulated_positions={}",
3495 urlencoding::encode(&positions_json)
3496 ));
3497 }
3498
3499 let url = format!(
3500 "{}{}?{}",
3501 self.base_url(),
3502 crate::constants::endpoints::SIMULATE_PORTFOLIO,
3503 query_params.join("&")
3504 );
3505
3506 let response = self.make_authenticated_request(&url).await?;
3507
3508 if !response.status().is_success() {
3509 let error_text = response
3510 .text()
3511 .await
3512 .unwrap_or_else(|_| "Unknown error".to_string());
3513 return Err(HttpError::RequestFailed(format!(
3514 "Simulate portfolio failed: {}",
3515 error_text
3516 )));
3517 }
3518
3519 let api_response: ApiResponse<crate::model::SimulatePortfolioResponse> = response
3520 .json()
3521 .await
3522 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
3523
3524 if let Some(error) = api_response.error {
3525 return Err(HttpError::RequestFailed(format!(
3526 "API error: {} - {}",
3527 error.code, error.message
3528 )));
3529 }
3530
3531 api_response.result.ok_or_else(|| {
3532 HttpError::InvalidResponse("No portfolio simulation data in response".to_string())
3533 })
3534 }
3535
3536 pub async fn pme_simulate(
3545 &self,
3546 currency: &str,
3547 ) -> Result<crate::model::PmeSimulateResponse, HttpError> {
3548 let query = format!("?currency={}", urlencoding::encode(currency));
3549 self.private_get(crate::constants::endpoints::PME_SIMULATE, &query)
3550 .await
3551 }
3552
3553 pub async fn change_margin_model(
3564 &self,
3565 margin_model: crate::model::MarginModel,
3566 user_id: Option<u64>,
3567 dry_run: Option<bool>,
3568 ) -> Result<crate::model::ChangeMarginModelResponse, HttpError> {
3569 let mut query_params = vec![format!("margin_model={}", margin_model.as_str())];
3570
3571 if let Some(user_id) = user_id {
3572 query_params.push(format!("user_id={}", user_id));
3573 }
3574
3575 if let Some(dry_run) = dry_run {
3576 query_params.push(format!("dry_run={}", dry_run));
3577 }
3578
3579 let url = format!(
3580 "{}{}?{}",
3581 self.base_url(),
3582 crate::constants::endpoints::CHANGE_MARGIN_MODEL,
3583 query_params.join("&")
3584 );
3585
3586 let response = self.make_authenticated_request(&url).await?;
3587
3588 if !response.status().is_success() {
3589 let error_text = response
3590 .text()
3591 .await
3592 .unwrap_or_else(|_| "Unknown error".to_string());
3593 return Err(HttpError::RequestFailed(format!(
3594 "Change margin model failed: {}",
3595 error_text
3596 )));
3597 }
3598
3599 let api_response: ApiResponse<crate::model::ChangeMarginModelResponse> = response
3600 .json()
3601 .await
3602 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
3603
3604 if let Some(error) = api_response.error {
3605 return Err(HttpError::RequestFailed(format!(
3606 "API error: {} - {}",
3607 error.code, error.message
3608 )));
3609 }
3610
3611 api_response.result.ok_or_else(|| {
3612 HttpError::InvalidResponse("No margin model change data in response".to_string())
3613 })
3614 }
3615
3616 pub async fn set_self_trading_config(
3627 &self,
3628 mode: crate::model::SelfTradingMode,
3629 extended_to_subaccounts: bool,
3630 block_rfq_self_match_prevention: Option<bool>,
3631 ) -> Result<bool, HttpError> {
3632 let mut query_params = vec![
3633 format!("mode={}", mode.as_str()),
3634 format!("extended_to_subaccounts={}", extended_to_subaccounts),
3635 ];
3636
3637 if let Some(block_rfq) = block_rfq_self_match_prevention {
3638 query_params.push(format!("block_rfq_self_match_prevention={}", block_rfq));
3639 }
3640
3641 let url = format!(
3642 "{}{}?{}",
3643 self.base_url(),
3644 crate::constants::endpoints::SET_SELF_TRADING_CONFIG,
3645 query_params.join("&")
3646 );
3647
3648 let response = self.make_authenticated_request(&url).await?;
3649
3650 if !response.status().is_success() {
3651 let error_text = response
3652 .text()
3653 .await
3654 .unwrap_or_else(|_| "Unknown error".to_string());
3655 return Err(HttpError::RequestFailed(format!(
3656 "Set self trading config failed: {}",
3657 error_text
3658 )));
3659 }
3660
3661 let api_response: ApiResponse<String> = response
3662 .json()
3663 .await
3664 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
3665
3666 if let Some(error) = api_response.error {
3667 return Err(HttpError::RequestFailed(format!(
3668 "API error: {} - {}",
3669 error.code, error.message
3670 )));
3671 }
3672
3673 Ok(api_response.result.map(|s| s == "ok").unwrap_or(true))
3674 }
3675
3676 pub async fn set_disabled_trading_products(
3686 &self,
3687 trading_products: &[crate::model::TradingProduct],
3688 user_id: u64,
3689 ) -> Result<bool, HttpError> {
3690 let products: Vec<&str> = trading_products.iter().map(|p| p.as_str()).collect();
3691 let products_json = serde_json::to_string(&products)
3692 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
3693
3694 let url = format!(
3695 "{}{}?trading_products={}&user_id={}",
3696 self.base_url(),
3697 crate::constants::endpoints::SET_DISABLED_TRADING_PRODUCTS,
3698 urlencoding::encode(&products_json),
3699 user_id
3700 );
3701
3702 let response = self.make_authenticated_request(&url).await?;
3703
3704 if !response.status().is_success() {
3705 let error_text = response
3706 .text()
3707 .await
3708 .unwrap_or_else(|_| "Unknown error".to_string());
3709 return Err(HttpError::RequestFailed(format!(
3710 "Set disabled trading products failed: {}",
3711 error_text
3712 )));
3713 }
3714
3715 let api_response: ApiResponse<String> = response
3716 .json()
3717 .await
3718 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
3719
3720 if let Some(error) = api_response.error {
3721 return Err(HttpError::RequestFailed(format!(
3722 "API error: {} - {}",
3723 error.code, error.message
3724 )));
3725 }
3726
3727 Ok(api_response.result.map(|s| s == "ok").unwrap_or(true))
3728 }
3729
3730 pub async fn get_new_announcements(
3735 &self,
3736 ) -> Result<Vec<crate::model::Announcement>, HttpError> {
3737 self.private_get(crate::constants::endpoints::GET_NEW_ANNOUNCEMENTS, "")
3738 .await
3739 }
3740
3741 pub async fn set_announcement_as_read(&self, announcement_id: u64) -> Result<bool, HttpError> {
3750 let query = format!("?announcement_id={}", announcement_id);
3751 let result: String = self
3752 .private_get(
3753 crate::constants::endpoints::SET_ANNOUNCEMENT_AS_READ,
3754 &query,
3755 )
3756 .await?;
3757 Ok(result == "ok")
3758 }
3759
3760 pub async fn enable_affiliate_program(&self) -> Result<bool, HttpError> {
3765 let result: String = self
3766 .private_get(crate::constants::endpoints::ENABLE_AFFILIATE_PROGRAM, "")
3767 .await?;
3768 Ok(result == "ok")
3769 }
3770
3771 pub async fn get_affiliate_program_info(
3776 &self,
3777 ) -> Result<crate::model::AffiliateProgramInfo, HttpError> {
3778 self.private_get(crate::constants::endpoints::GET_AFFILIATE_PROGRAM_INFO, "")
3779 .await
3780 }
3781
3782 pub async fn set_email_language(
3791 &self,
3792 language: crate::model::EmailLanguage,
3793 ) -> Result<bool, HttpError> {
3794 let query = format!("?language={}", language.as_str());
3795 let result: String = self
3796 .private_get(crate::constants::endpoints::SET_EMAIL_LANGUAGE, &query)
3797 .await?;
3798 Ok(result == "ok")
3799 }
3800
3801 pub async fn get_email_language(&self) -> Result<String, HttpError> {
3806 self.private_get(crate::constants::endpoints::GET_EMAIL_LANGUAGE, "")
3807 .await
3808 }
3809
3810 pub async fn withdraw(
3834 &self,
3835 currency: &str,
3836 address: &str,
3837 amount: f64,
3838 priority: Option<crate::model::wallet::WithdrawalPriorityLevel>,
3839 ) -> Result<crate::model::Withdrawal, HttpError> {
3840 let mut query_params = vec![
3841 ("currency".to_string(), currency.to_string()),
3842 ("address".to_string(), address.to_string()),
3843 ("amount".to_string(), amount.to_string()),
3844 ];
3845
3846 if let Some(p) = priority {
3847 query_params.push(("priority".to_string(), p.as_str().to_string()));
3848 }
3849
3850 let query_string = query_params
3851 .iter()
3852 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
3853 .collect::<Vec<_>>()
3854 .join("&");
3855
3856 let url = format!("{}{}?{}", self.base_url(), WITHDRAW, query_string);
3857
3858 let response = self.make_authenticated_request(&url).await?;
3859
3860 if !response.status().is_success() {
3861 let error_text = response
3862 .text()
3863 .await
3864 .unwrap_or_else(|_| "Unknown error".to_string());
3865 return Err(HttpError::RequestFailed(format!(
3866 "Withdraw failed: {}",
3867 error_text
3868 )));
3869 }
3870
3871 let api_response: ApiResponse<crate::model::Withdrawal> = response
3872 .json()
3873 .await
3874 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
3875
3876 if let Some(error) = api_response.error {
3877 return Err(HttpError::RequestFailed(format!(
3878 "API error: {} - {}",
3879 error.code, error.message
3880 )));
3881 }
3882
3883 api_response
3884 .result
3885 .ok_or_else(|| HttpError::InvalidResponse("No withdrawal data in response".to_string()))
3886 }
3887
3888 pub async fn cancel_withdrawal(
3905 &self,
3906 currency: &str,
3907 id: u64,
3908 ) -> Result<crate::model::Withdrawal, HttpError> {
3909 let query = format!("?currency={}&id={}", urlencoding::encode(currency), id);
3910 self.private_get(CANCEL_WITHDRAWAL, &query).await
3911 }
3912
3913 pub async fn create_deposit_address(
3929 &self,
3930 currency: &str,
3931 ) -> Result<crate::model::wallet::DepositAddress, HttpError> {
3932 let query = format!("?currency={}", urlencoding::encode(currency));
3933 self.private_get(CREATE_DEPOSIT_ADDRESS, &query).await
3934 }
3935
3936 pub async fn get_current_deposit_address(
3952 &self,
3953 currency: &str,
3954 ) -> Result<crate::model::wallet::DepositAddress, HttpError> {
3955 let query = format!("?currency={}", urlencoding::encode(currency));
3956 self.private_get(GET_CURRENT_DEPOSIT_ADDRESS, &query).await
3957 }
3958
3959 pub async fn add_to_address_book(
3979 &self,
3980 currency: &str,
3981 address_type: crate::model::wallet::AddressBookType,
3982 address: &str,
3983 label: Option<&str>,
3984 tag: Option<&str>,
3985 ) -> Result<crate::model::wallet::AddressBookEntry, HttpError> {
3986 let mut query_params = vec![
3987 ("currency".to_string(), currency.to_string()),
3988 ("type".to_string(), address_type.as_str().to_string()),
3989 ("address".to_string(), address.to_string()),
3990 ];
3991
3992 if let Some(l) = label {
3993 query_params.push(("label".to_string(), l.to_string()));
3994 }
3995
3996 if let Some(t) = tag {
3997 query_params.push(("tag".to_string(), t.to_string()));
3998 }
3999
4000 let query_string = query_params
4001 .iter()
4002 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
4003 .collect::<Vec<_>>()
4004 .join("&");
4005
4006 let url = format!(
4007 "{}{}?{}",
4008 self.base_url(),
4009 ADD_TO_ADDRESS_BOOK,
4010 query_string
4011 );
4012
4013 let response = self.make_authenticated_request(&url).await?;
4014
4015 if !response.status().is_success() {
4016 let error_text = response
4017 .text()
4018 .await
4019 .unwrap_or_else(|_| "Unknown error".to_string());
4020 return Err(HttpError::RequestFailed(format!(
4021 "Add to address book failed: {}",
4022 error_text
4023 )));
4024 }
4025
4026 let api_response: ApiResponse<crate::model::wallet::AddressBookEntry> = response
4027 .json()
4028 .await
4029 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
4030
4031 if let Some(error) = api_response.error {
4032 return Err(HttpError::RequestFailed(format!(
4033 "API error: {} - {}",
4034 error.code, error.message
4035 )));
4036 }
4037
4038 api_response.result.ok_or_else(|| {
4039 HttpError::InvalidResponse("No address book entry in response".to_string())
4040 })
4041 }
4042
4043 pub async fn remove_from_address_book(
4061 &self,
4062 currency: &str,
4063 address_type: crate::model::wallet::AddressBookType,
4064 address: &str,
4065 ) -> Result<bool, HttpError> {
4066 let query = format!(
4067 "?currency={}&type={}&address={}",
4068 urlencoding::encode(currency),
4069 urlencoding::encode(address_type.as_str()),
4070 urlencoding::encode(address)
4071 );
4072 let result: String = self.private_get(REMOVE_FROM_ADDRESS_BOOK, &query).await?;
4073 Ok(result == "ok")
4074 }
4075
4076 pub async fn update_in_address_book(
4093 &self,
4094 request: &crate::model::request::wallet::UpdateInAddressBookRequest,
4095 ) -> Result<bool, HttpError> {
4096 let mut query_params = vec![
4097 ("currency".to_string(), request.currency.clone()),
4098 (
4099 "type".to_string(),
4100 request.address_type.as_str().to_string(),
4101 ),
4102 ("address".to_string(), request.address.clone()),
4103 ("label".to_string(), request.label.clone()),
4104 ("agreed".to_string(), request.agreed.to_string()),
4105 ("personal".to_string(), request.personal.to_string()),
4106 (
4107 "beneficiary_vasp_name".to_string(),
4108 request.beneficiary_vasp_name.clone(),
4109 ),
4110 (
4111 "beneficiary_vasp_did".to_string(),
4112 request.beneficiary_vasp_did.clone(),
4113 ),
4114 (
4115 "beneficiary_address".to_string(),
4116 request.beneficiary_address.clone(),
4117 ),
4118 ];
4119
4120 if let Some(ref website) = request.beneficiary_vasp_website {
4121 query_params.push(("beneficiary_vasp_website".to_string(), website.clone()));
4122 }
4123
4124 if let Some(ref first_name) = request.beneficiary_first_name {
4125 query_params.push(("beneficiary_first_name".to_string(), first_name.clone()));
4126 }
4127
4128 if let Some(ref last_name) = request.beneficiary_last_name {
4129 query_params.push(("beneficiary_last_name".to_string(), last_name.clone()));
4130 }
4131
4132 if let Some(ref company_name) = request.beneficiary_company_name {
4133 query_params.push(("beneficiary_company_name".to_string(), company_name.clone()));
4134 }
4135
4136 if let Some(ref tag) = request.tag {
4137 query_params.push(("tag".to_string(), tag.clone()));
4138 }
4139
4140 let query_string = query_params
4141 .iter()
4142 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
4143 .collect::<Vec<_>>()
4144 .join("&");
4145
4146 let url = format!(
4147 "{}{}?{}",
4148 self.base_url(),
4149 UPDATE_IN_ADDRESS_BOOK,
4150 query_string
4151 );
4152
4153 let response = self.make_authenticated_request(&url).await?;
4154
4155 if !response.status().is_success() {
4156 let error_text = response
4157 .text()
4158 .await
4159 .unwrap_or_else(|_| "Unknown error".to_string());
4160 return Err(HttpError::RequestFailed(format!(
4161 "Update in address book failed: {}",
4162 error_text
4163 )));
4164 }
4165
4166 let api_response: ApiResponse<String> = response
4167 .json()
4168 .await
4169 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
4170
4171 if let Some(error) = api_response.error {
4172 return Err(HttpError::RequestFailed(format!(
4173 "API error: {} - {}",
4174 error.code, error.message
4175 )));
4176 }
4177
4178 Ok(api_response.result.map(|s| s == "ok").unwrap_or(true))
4179 }
4180
4181 pub async fn get_address_book(
4198 &self,
4199 currency: &str,
4200 address_type: crate::model::wallet::AddressBookType,
4201 ) -> Result<Vec<crate::model::wallet::AddressBookEntry>, HttpError> {
4202 let query = format!(
4203 "?currency={}&type={}",
4204 urlencoding::encode(currency),
4205 urlencoding::encode(address_type.as_str())
4206 );
4207 self.private_get(GET_ADDRESS_BOOK, &query).await
4208 }
4209
4210 pub async fn approve_block_trade(
4235 &self,
4236 timestamp: u64,
4237 nonce: &str,
4238 role: crate::model::block_trade::BlockTradeRole,
4239 ) -> Result<bool, HttpError> {
4240 let query = format!(
4241 "?timestamp={}&nonce={}&role={}",
4242 timestamp,
4243 urlencoding::encode(nonce),
4244 urlencoding::encode(&role.to_string())
4245 );
4246 let result: String = self.private_get(APPROVE_BLOCK_TRADE, &query).await?;
4247 Ok(result == "ok")
4248 }
4249
4250 pub async fn execute_block_trade(
4268 &self,
4269 request: &crate::model::block_trade::ExecuteBlockTradeRequest,
4270 ) -> Result<crate::model::block_trade::BlockTradeResult, HttpError> {
4271 let trades_json = serde_json::to_string(&request.trades).map_err(|e| {
4272 HttpError::InvalidResponse(format!("Failed to serialize trades: {}", e))
4273 })?;
4274
4275 let query_params = [
4276 ("timestamp".to_string(), request.timestamp.to_string()),
4277 ("nonce".to_string(), request.nonce.clone()),
4278 ("role".to_string(), request.role.to_string()),
4279 ("trades".to_string(), trades_json),
4280 (
4281 "counterparty_signature".to_string(),
4282 request.counterparty_signature.clone(),
4283 ),
4284 ];
4285
4286 let query_string = query_params
4287 .iter()
4288 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
4289 .collect::<Vec<_>>()
4290 .join("&");
4291
4292 let url = format!(
4293 "{}{}?{}",
4294 self.base_url(),
4295 EXECUTE_BLOCK_TRADE,
4296 query_string
4297 );
4298
4299 let response = self.make_authenticated_request(&url).await?;
4300
4301 if !response.status().is_success() {
4302 let error_text = response
4303 .text()
4304 .await
4305 .unwrap_or_else(|_| "Unknown error".to_string());
4306 return Err(HttpError::RequestFailed(format!(
4307 "Execute block trade failed: {}",
4308 error_text
4309 )));
4310 }
4311
4312 let api_response: ApiResponse<crate::model::block_trade::BlockTradeResult> = response
4313 .json()
4314 .await
4315 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
4316
4317 if let Some(error) = api_response.error {
4318 return Err(HttpError::RequestFailed(format!(
4319 "API error: {} - {}",
4320 error.code, error.message
4321 )));
4322 }
4323
4324 api_response.result.ok_or_else(|| {
4325 HttpError::InvalidResponse("No block trade result in response".to_string())
4326 })
4327 }
4328
4329 pub async fn get_block_trade(
4345 &self,
4346 id: &str,
4347 ) -> Result<crate::model::block_trade::BlockTrade, HttpError> {
4348 let query = format!("?id={}", urlencoding::encode(id));
4349 self.private_get(GET_BLOCK_TRADE, &query).await
4350 }
4351
4352 pub async fn get_block_trade_requests(
4370 &self,
4371 broker_code: Option<&str>,
4372 ) -> Result<Vec<crate::model::block_trade::BlockTradeRequest>, HttpError> {
4373 let mut query_params = Vec::new();
4374
4375 if let Some(code) = broker_code {
4376 query_params.push(("broker_code".to_string(), code.to_string()));
4377 }
4378
4379 let query_string = if query_params.is_empty() {
4380 String::new()
4381 } else {
4382 format!(
4383 "?{}",
4384 query_params
4385 .iter()
4386 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
4387 .collect::<Vec<_>>()
4388 .join("&")
4389 )
4390 };
4391
4392 let url = format!(
4393 "{}{}{}",
4394 self.base_url(),
4395 GET_BLOCK_TRADE_REQUESTS,
4396 query_string
4397 );
4398
4399 let response = self.make_authenticated_request(&url).await?;
4400
4401 if !response.status().is_success() {
4402 let error_text = response
4403 .text()
4404 .await
4405 .unwrap_or_else(|_| "Unknown error".to_string());
4406 return Err(HttpError::RequestFailed(format!(
4407 "Get block trade requests failed: {}",
4408 error_text
4409 )));
4410 }
4411
4412 let api_response: ApiResponse<Vec<crate::model::block_trade::BlockTradeRequest>> = response
4413 .json()
4414 .await
4415 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
4416
4417 if let Some(error) = api_response.error {
4418 return Err(HttpError::RequestFailed(format!(
4419 "API error: {} - {}",
4420 error.code, error.message
4421 )));
4422 }
4423
4424 Ok(api_response.result.unwrap_or_default())
4425 }
4426
4427 pub async fn get_block_trades(
4443 &self,
4444 request: &crate::model::block_trade::GetBlockTradesRequest,
4445 ) -> Result<Vec<crate::model::block_trade::BlockTrade>, HttpError> {
4446 let mut query_params = Vec::new();
4447
4448 if let Some(ref currency) = request.currency {
4449 query_params.push(("currency".to_string(), currency.clone()));
4450 }
4451 if let Some(count) = request.count {
4452 query_params.push(("count".to_string(), count.to_string()));
4453 }
4454 if let Some(ref continuation) = request.continuation {
4455 query_params.push(("continuation".to_string(), continuation.clone()));
4456 }
4457 if let Some(start_ts) = request.start_timestamp {
4458 query_params.push(("start_timestamp".to_string(), start_ts.to_string()));
4459 }
4460 if let Some(end_ts) = request.end_timestamp {
4461 query_params.push(("end_timestamp".to_string(), end_ts.to_string()));
4462 }
4463
4464 let query_string = if query_params.is_empty() {
4465 String::new()
4466 } else {
4467 format!(
4468 "?{}",
4469 query_params
4470 .iter()
4471 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
4472 .collect::<Vec<_>>()
4473 .join("&")
4474 )
4475 };
4476
4477 let url = format!("{}{}{}", self.base_url(), GET_BLOCK_TRADES, query_string);
4478
4479 let response = self.make_authenticated_request(&url).await?;
4480
4481 if !response.status().is_success() {
4482 let error_text = response
4483 .text()
4484 .await
4485 .unwrap_or_else(|_| "Unknown error".to_string());
4486 return Err(HttpError::RequestFailed(format!(
4487 "Get block trades failed: {}",
4488 error_text
4489 )));
4490 }
4491
4492 let api_response: ApiResponse<Vec<crate::model::block_trade::BlockTrade>> = response
4493 .json()
4494 .await
4495 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
4496
4497 if let Some(error) = api_response.error {
4498 return Err(HttpError::RequestFailed(format!(
4499 "API error: {} - {}",
4500 error.code, error.message
4501 )));
4502 }
4503
4504 Ok(api_response.result.unwrap_or_default())
4505 }
4506
4507 pub async fn get_broker_trade_requests(
4519 &self,
4520 ) -> Result<Vec<crate::model::block_trade::BlockTradeRequest>, HttpError> {
4521 self.private_get(GET_BROKER_TRADE_REQUESTS, "").await
4522 }
4523
4524 pub async fn get_broker_trades(
4540 &self,
4541 request: &crate::model::block_trade::GetBlockTradesRequest,
4542 ) -> Result<Vec<crate::model::block_trade::BlockTrade>, HttpError> {
4543 let mut query_params = Vec::new();
4544
4545 if let Some(ref currency) = request.currency {
4546 query_params.push(("currency".to_string(), currency.clone()));
4547 }
4548 if let Some(count) = request.count {
4549 query_params.push(("count".to_string(), count.to_string()));
4550 }
4551 if let Some(ref continuation) = request.continuation {
4552 query_params.push(("continuation".to_string(), continuation.clone()));
4553 }
4554 if let Some(start_ts) = request.start_timestamp {
4555 query_params.push(("start_timestamp".to_string(), start_ts.to_string()));
4556 }
4557 if let Some(end_ts) = request.end_timestamp {
4558 query_params.push(("end_timestamp".to_string(), end_ts.to_string()));
4559 }
4560
4561 let query_string = if query_params.is_empty() {
4562 String::new()
4563 } else {
4564 format!(
4565 "?{}",
4566 query_params
4567 .iter()
4568 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
4569 .collect::<Vec<_>>()
4570 .join("&")
4571 )
4572 };
4573
4574 let url = format!("{}{}{}", self.base_url(), GET_BROKER_TRADES, query_string);
4575
4576 let response = self.make_authenticated_request(&url).await?;
4577
4578 if !response.status().is_success() {
4579 let error_text = response
4580 .text()
4581 .await
4582 .unwrap_or_else(|_| "Unknown error".to_string());
4583 return Err(HttpError::RequestFailed(format!(
4584 "Get broker trades failed: {}",
4585 error_text
4586 )));
4587 }
4588
4589 let api_response: ApiResponse<Vec<crate::model::block_trade::BlockTrade>> = response
4590 .json()
4591 .await
4592 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
4593
4594 if let Some(error) = api_response.error {
4595 return Err(HttpError::RequestFailed(format!(
4596 "API error: {} - {}",
4597 error.code, error.message
4598 )));
4599 }
4600
4601 Ok(api_response.result.unwrap_or_default())
4602 }
4603
4604 pub async fn invalidate_block_trade_signature(
4620 &self,
4621 signature: &str,
4622 ) -> Result<bool, HttpError> {
4623 let query = format!("?signature={}", urlencoding::encode(signature));
4624 let result: String = self
4625 .private_get(INVALIDATE_BLOCK_TRADE_SIGNATURE, &query)
4626 .await?;
4627 Ok(result == "ok")
4628 }
4629
4630 pub async fn reject_block_trade(
4648 &self,
4649 timestamp: u64,
4650 nonce: &str,
4651 role: crate::model::block_trade::BlockTradeRole,
4652 ) -> Result<bool, HttpError> {
4653 let query = format!(
4654 "?timestamp={}&nonce={}&role={}",
4655 timestamp,
4656 urlencoding::encode(nonce),
4657 urlencoding::encode(&role.to_string())
4658 );
4659 let result: String = self.private_get(REJECT_BLOCK_TRADE, &query).await?;
4660 Ok(result == "ok")
4661 }
4662
4663 pub async fn simulate_block_trade(
4679 &self,
4680 request: &crate::model::block_trade::SimulateBlockTradeRequest,
4681 ) -> Result<bool, HttpError> {
4682 let trades_json = serde_json::to_string(&request.trades).map_err(|e| {
4683 HttpError::InvalidResponse(format!("Failed to serialize trades: {}", e))
4684 })?;
4685
4686 let mut query_params = vec![("trades".to_string(), trades_json)];
4687
4688 if let Some(ref role) = request.role {
4689 query_params.push(("role".to_string(), role.to_string()));
4690 }
4691
4692 let query_string = query_params
4693 .iter()
4694 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
4695 .collect::<Vec<_>>()
4696 .join("&");
4697
4698 let url = format!(
4699 "{}{}?{}",
4700 self.base_url(),
4701 SIMULATE_BLOCK_TRADE,
4702 query_string
4703 );
4704
4705 let response = self.make_authenticated_request(&url).await?;
4706
4707 if !response.status().is_success() {
4708 let error_text = response
4709 .text()
4710 .await
4711 .unwrap_or_else(|_| "Unknown error".to_string());
4712 return Err(HttpError::RequestFailed(format!(
4713 "Simulate block trade failed: {}",
4714 error_text
4715 )));
4716 }
4717
4718 let api_response: ApiResponse<bool> = response
4719 .json()
4720 .await
4721 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
4722
4723 if let Some(error) = api_response.error {
4724 return Err(HttpError::RequestFailed(format!(
4725 "API error: {} - {}",
4726 error.code, error.message
4727 )));
4728 }
4729
4730 Ok(api_response.result.unwrap_or(false))
4731 }
4732
4733 pub async fn verify_block_trade(
4750 &self,
4751 request: &crate::model::block_trade::VerifyBlockTradeRequest,
4752 ) -> Result<crate::model::block_trade::BlockTradeSignature, HttpError> {
4753 let trades_json = serde_json::to_string(&request.trades).map_err(|e| {
4754 HttpError::InvalidResponse(format!("Failed to serialize trades: {}", e))
4755 })?;
4756
4757 let query_params = [
4758 ("timestamp".to_string(), request.timestamp.to_string()),
4759 ("nonce".to_string(), request.nonce.clone()),
4760 ("role".to_string(), request.role.to_string()),
4761 ("trades".to_string(), trades_json),
4762 ];
4763
4764 let query_string = query_params
4765 .iter()
4766 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
4767 .collect::<Vec<_>>()
4768 .join("&");
4769
4770 let url = format!("{}{}?{}", self.base_url(), VERIFY_BLOCK_TRADE, query_string);
4771
4772 let response = self.make_authenticated_request(&url).await?;
4773
4774 if !response.status().is_success() {
4775 let error_text = response
4776 .text()
4777 .await
4778 .unwrap_or_else(|_| "Unknown error".to_string());
4779 return Err(HttpError::RequestFailed(format!(
4780 "Verify block trade failed: {}",
4781 error_text
4782 )));
4783 }
4784
4785 let api_response: ApiResponse<crate::model::block_trade::BlockTradeSignature> = response
4786 .json()
4787 .await
4788 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
4789
4790 if let Some(error) = api_response.error {
4791 return Err(HttpError::RequestFailed(format!(
4792 "API error: {} - {}",
4793 error.code, error.message
4794 )));
4795 }
4796
4797 api_response
4798 .result
4799 .ok_or_else(|| HttpError::InvalidResponse("No signature in response".to_string()))
4800 }
4801
4802 pub async fn create_combo(
4836 &self,
4837 trades: &[crate::model::ComboTrade],
4838 ) -> Result<crate::model::Combo, HttpError> {
4839 let trades_json = serde_json::to_string(trades).map_err(|e| {
4840 HttpError::InvalidResponse(format!("Failed to serialize trades: {}", e))
4841 })?;
4842
4843 let url = format!(
4844 "{}{}?trades={}",
4845 self.base_url(),
4846 CREATE_COMBO,
4847 urlencoding::encode(&trades_json)
4848 );
4849
4850 let response = self.make_authenticated_request(&url).await?;
4851
4852 if !response.status().is_success() {
4853 let error_text = response
4854 .text()
4855 .await
4856 .unwrap_or_else(|_| "Unknown error".to_string());
4857 return Err(HttpError::RequestFailed(format!(
4858 "Create combo failed: {}",
4859 error_text
4860 )));
4861 }
4862
4863 let api_response: ApiResponse<crate::model::Combo> = response
4864 .json()
4865 .await
4866 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
4867
4868 if let Some(error) = api_response.error {
4869 return Err(HttpError::RequestFailed(format!(
4870 "API error: {} - {}",
4871 error.code, error.message
4872 )));
4873 }
4874
4875 api_response
4876 .result
4877 .ok_or_else(|| HttpError::InvalidResponse("No combo data in response".to_string()))
4878 }
4879
4880 pub async fn get_leg_prices(
4912 &self,
4913 legs: &[crate::model::LegInput],
4914 price: f64,
4915 ) -> Result<crate::model::LegPricesResponse, HttpError> {
4916 let legs_json = serde_json::to_string(legs)
4917 .map_err(|e| HttpError::InvalidResponse(format!("Failed to serialize legs: {}", e)))?;
4918
4919 let url = format!(
4920 "{}{}?legs={}&price={}",
4921 self.base_url(),
4922 GET_LEG_PRICES,
4923 urlencoding::encode(&legs_json),
4924 price
4925 );
4926
4927 let response = self.make_authenticated_request(&url).await?;
4928
4929 if !response.status().is_success() {
4930 let error_text = response
4931 .text()
4932 .await
4933 .unwrap_or_else(|_| "Unknown error".to_string());
4934 return Err(HttpError::RequestFailed(format!(
4935 "Get leg prices failed: {}",
4936 error_text
4937 )));
4938 }
4939
4940 let api_response: ApiResponse<crate::model::LegPricesResponse> = response
4941 .json()
4942 .await
4943 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
4944
4945 if let Some(error) = api_response.error {
4946 return Err(HttpError::RequestFailed(format!(
4947 "API error: {} - {}",
4948 error.code, error.message
4949 )));
4950 }
4951
4952 api_response
4953 .result
4954 .ok_or_else(|| HttpError::InvalidResponse("No leg prices in response".to_string()))
4955 }
4956
4957 pub async fn create_block_rfq(
4976 &self,
4977 legs: &[crate::model::response::BlockRfqLeg],
4978 hedge: Option<&crate::model::response::BlockRfqHedge>,
4979 label: Option<&str>,
4980 makers: Option<&[&str]>,
4981 non_anonymous: Option<bool>,
4982 trade_allocations: Option<&[crate::model::response::BlockRfqTradeAllocation]>,
4983 ) -> Result<crate::model::response::BlockRfq, HttpError> {
4984 let legs_json = serde_json::to_string(legs)
4985 .map_err(|e| HttpError::InvalidResponse(format!("Failed to serialize legs: {}", e)))?;
4986
4987 let mut query_params = vec![format!("legs={}", urlencoding::encode(&legs_json))];
4988
4989 if let Some(h) = hedge {
4990 let hedge_json = serde_json::to_string(h).map_err(|e| {
4991 HttpError::InvalidResponse(format!("Failed to serialize hedge: {}", e))
4992 })?;
4993 query_params.push(format!("hedge={}", urlencoding::encode(&hedge_json)));
4994 }
4995
4996 if let Some(l) = label {
4997 query_params.push(format!("label={}", urlencoding::encode(l)));
4998 }
4999
5000 if let Some(m) = makers {
5001 let makers_json = serde_json::to_string(m).map_err(|e| {
5002 HttpError::InvalidResponse(format!("Failed to serialize makers: {}", e))
5003 })?;
5004 query_params.push(format!("makers={}", urlencoding::encode(&makers_json)));
5005 }
5006
5007 if let Some(na) = non_anonymous {
5008 query_params.push(format!("non_anonymous={}", na));
5009 }
5010
5011 if let Some(ta) = trade_allocations {
5012 let ta_json = serde_json::to_string(ta).map_err(|e| {
5013 HttpError::InvalidResponse(format!("Failed to serialize trade_allocations: {}", e))
5014 })?;
5015 query_params.push(format!(
5016 "trade_allocations={}",
5017 urlencoding::encode(&ta_json)
5018 ));
5019 }
5020
5021 let url = format!(
5022 "{}{}?{}",
5023 self.base_url(),
5024 crate::constants::endpoints::CREATE_BLOCK_RFQ,
5025 query_params.join("&")
5026 );
5027
5028 let response = self.make_authenticated_request(&url).await?;
5029
5030 if !response.status().is_success() {
5031 let error_text = response
5032 .text()
5033 .await
5034 .unwrap_or_else(|_| "Unknown error".to_string());
5035 return Err(HttpError::RequestFailed(format!(
5036 "Create Block RFQ failed: {}",
5037 error_text
5038 )));
5039 }
5040
5041 let api_response: ApiResponse<crate::model::response::BlockRfq> = response
5042 .json()
5043 .await
5044 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
5045
5046 if let Some(error) = api_response.error {
5047 return Err(HttpError::RequestFailed(format!(
5048 "API error: {} - {}",
5049 error.code, error.message
5050 )));
5051 }
5052
5053 api_response
5054 .result
5055 .ok_or_else(|| HttpError::InvalidResponse("No Block RFQ in response".to_string()))
5056 }
5057
5058 pub async fn cancel_block_rfq(
5068 &self,
5069 block_rfq_id: i64,
5070 ) -> Result<crate::model::response::BlockRfq, HttpError> {
5071 let query = format!("?block_rfq_id={}", block_rfq_id);
5072 self.private_get(crate::constants::endpoints::CANCEL_BLOCK_RFQ, &query)
5073 .await
5074 }
5075
5076 #[allow(clippy::too_many_arguments)]
5092 pub async fn accept_block_rfq(
5093 &self,
5094 block_rfq_id: i64,
5095 legs: &[crate::model::response::BlockRfqLeg],
5096 price: f64,
5097 direction: crate::model::types::Direction,
5098 amount: f64,
5099 time_in_force: Option<crate::model::response::BlockRfqTimeInForce>,
5100 hedge: Option<&crate::model::response::BlockRfqHedge>,
5101 ) -> Result<crate::model::response::AcceptBlockRfqResponse, HttpError> {
5102 let legs_json = serde_json::to_string(legs)
5103 .map_err(|e| HttpError::InvalidResponse(format!("Failed to serialize legs: {}", e)))?;
5104
5105 let direction_str = match direction {
5106 crate::model::types::Direction::Buy => "buy",
5107 crate::model::types::Direction::Sell => "sell",
5108 crate::model::types::Direction::Unknown => "buy",
5109 };
5110
5111 let mut query_params = vec![
5112 format!("block_rfq_id={}", block_rfq_id),
5113 format!("legs={}", urlencoding::encode(&legs_json)),
5114 format!("price={}", price),
5115 format!("direction={}", direction_str),
5116 format!("amount={}", amount),
5117 ];
5118
5119 if let Some(tif) = time_in_force {
5120 let tif_str = match tif {
5121 crate::model::response::BlockRfqTimeInForce::FillOrKill => "fill_or_kill",
5122 crate::model::response::BlockRfqTimeInForce::GoodTilCancelled => {
5123 "good_til_cancelled"
5124 }
5125 };
5126 query_params.push(format!("time_in_force={}", tif_str));
5127 }
5128
5129 if let Some(h) = hedge {
5130 let hedge_json = serde_json::to_string(h).map_err(|e| {
5131 HttpError::InvalidResponse(format!("Failed to serialize hedge: {}", e))
5132 })?;
5133 query_params.push(format!("hedge={}", urlencoding::encode(&hedge_json)));
5134 }
5135
5136 let url = format!(
5137 "{}{}?{}",
5138 self.base_url(),
5139 crate::constants::endpoints::ACCEPT_BLOCK_RFQ,
5140 query_params.join("&")
5141 );
5142
5143 let response = self.make_authenticated_request(&url).await?;
5144
5145 if !response.status().is_success() {
5146 let error_text = response
5147 .text()
5148 .await
5149 .unwrap_or_else(|_| "Unknown error".to_string());
5150 return Err(HttpError::RequestFailed(format!(
5151 "Accept Block RFQ failed: {}",
5152 error_text
5153 )));
5154 }
5155
5156 let api_response: ApiResponse<crate::model::response::AcceptBlockRfqResponse> = response
5157 .json()
5158 .await
5159 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
5160
5161 if let Some(error) = api_response.error {
5162 return Err(HttpError::RequestFailed(format!(
5163 "API error: {} - {}",
5164 error.code, error.message
5165 )));
5166 }
5167
5168 api_response
5169 .result
5170 .ok_or_else(|| HttpError::InvalidResponse("No accept Block RFQ response".to_string()))
5171 }
5172
5173 pub async fn get_block_rfqs(
5188 &self,
5189 count: Option<u32>,
5190 state: Option<crate::model::response::BlockRfqState>,
5191 role: Option<crate::model::response::BlockRfqRole>,
5192 continuation: Option<&str>,
5193 block_rfq_id: Option<i64>,
5194 currency: Option<&str>,
5195 ) -> Result<crate::model::response::BlockRfqsResponse, HttpError> {
5196 let mut query_params: Vec<String> = Vec::new();
5197
5198 if let Some(c) = count {
5199 query_params.push(format!("count={}", c));
5200 }
5201
5202 if let Some(s) = state {
5203 let state_str = match s {
5204 crate::model::response::BlockRfqState::Open => "open",
5205 crate::model::response::BlockRfqState::Filled => "filled",
5206 crate::model::response::BlockRfqState::Traded => "traded",
5207 crate::model::response::BlockRfqState::Cancelled => "cancelled",
5208 crate::model::response::BlockRfqState::Expired => "expired",
5209 crate::model::response::BlockRfqState::Closed => "closed",
5210 crate::model::response::BlockRfqState::Created => "created",
5211 };
5212 query_params.push(format!("state={}", state_str));
5213 }
5214
5215 if let Some(r) = role {
5216 let role_str = match r {
5217 crate::model::response::BlockRfqRole::Taker => "taker",
5218 crate::model::response::BlockRfqRole::Maker => "maker",
5219 crate::model::response::BlockRfqRole::Any => "any",
5220 };
5221 query_params.push(format!("role={}", role_str));
5222 }
5223
5224 if let Some(cont) = continuation {
5225 query_params.push(format!("continuation={}", urlencoding::encode(cont)));
5226 }
5227
5228 if let Some(id) = block_rfq_id {
5229 query_params.push(format!("block_rfq_id={}", id));
5230 }
5231
5232 if let Some(curr) = currency {
5233 query_params.push(format!("currency={}", curr));
5234 }
5235
5236 let url = if query_params.is_empty() {
5237 format!(
5238 "{}{}",
5239 self.base_url(),
5240 crate::constants::endpoints::GET_BLOCK_RFQS
5241 )
5242 } else {
5243 format!(
5244 "{}{}?{}",
5245 self.base_url(),
5246 crate::constants::endpoints::GET_BLOCK_RFQS,
5247 query_params.join("&")
5248 )
5249 };
5250
5251 let response = self.make_authenticated_request(&url).await?;
5252
5253 if !response.status().is_success() {
5254 let error_text = response
5255 .text()
5256 .await
5257 .unwrap_or_else(|_| "Unknown error".to_string());
5258 return Err(HttpError::RequestFailed(format!(
5259 "Get Block RFQs failed: {}",
5260 error_text
5261 )));
5262 }
5263
5264 let api_response: ApiResponse<crate::model::response::BlockRfqsResponse> = response
5265 .json()
5266 .await
5267 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
5268
5269 if let Some(error) = api_response.error {
5270 return Err(HttpError::RequestFailed(format!(
5271 "API error: {} - {}",
5272 error.code, error.message
5273 )));
5274 }
5275
5276 api_response
5277 .result
5278 .ok_or_else(|| HttpError::InvalidResponse("No Block RFQs in response".to_string()))
5279 }
5280
5281 pub async fn get_block_rfq_quotes(
5293 &self,
5294 block_rfq_id: Option<i64>,
5295 label: Option<&str>,
5296 block_rfq_quote_id: Option<i64>,
5297 ) -> Result<Vec<crate::model::response::BlockRfqQuote>, HttpError> {
5298 let mut query_params: Vec<String> = Vec::new();
5299
5300 if let Some(id) = block_rfq_id {
5301 query_params.push(format!("block_rfq_id={}", id));
5302 }
5303
5304 if let Some(l) = label {
5305 query_params.push(format!("label={}", urlencoding::encode(l)));
5306 }
5307
5308 if let Some(qid) = block_rfq_quote_id {
5309 query_params.push(format!("block_rfq_quote_id={}", qid));
5310 }
5311
5312 let url = if query_params.is_empty() {
5313 format!(
5314 "{}{}",
5315 self.base_url(),
5316 crate::constants::endpoints::GET_BLOCK_RFQ_QUOTES
5317 )
5318 } else {
5319 format!(
5320 "{}{}?{}",
5321 self.base_url(),
5322 crate::constants::endpoints::GET_BLOCK_RFQ_QUOTES,
5323 query_params.join("&")
5324 )
5325 };
5326
5327 let response = self.make_authenticated_request(&url).await?;
5328
5329 if !response.status().is_success() {
5330 let error_text = response
5331 .text()
5332 .await
5333 .unwrap_or_else(|_| "Unknown error".to_string());
5334 return Err(HttpError::RequestFailed(format!(
5335 "Get Block RFQ quotes failed: {}",
5336 error_text
5337 )));
5338 }
5339
5340 let api_response: ApiResponse<Vec<crate::model::response::BlockRfqQuote>> = response
5341 .json()
5342 .await
5343 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
5344
5345 if let Some(error) = api_response.error {
5346 return Err(HttpError::RequestFailed(format!(
5347 "API error: {} - {}",
5348 error.code, error.message
5349 )));
5350 }
5351
5352 api_response.result.ok_or_else(|| {
5353 HttpError::InvalidResponse("No Block RFQ quotes in response".to_string())
5354 })
5355 }
5356
5357 #[allow(clippy::too_many_arguments)]
5374 pub async fn add_block_rfq_quote(
5375 &self,
5376 block_rfq_id: i64,
5377 amount: f64,
5378 direction: crate::model::types::Direction,
5379 legs: &[crate::model::response::BlockRfqLeg],
5380 label: Option<&str>,
5381 hedge: Option<&crate::model::response::BlockRfqHedge>,
5382 execution_instruction: Option<crate::model::response::ExecutionInstruction>,
5383 expires_at: Option<i64>,
5384 ) -> Result<crate::model::response::BlockRfqQuote, HttpError> {
5385 let legs_json = serde_json::to_string(legs)
5386 .map_err(|e| HttpError::InvalidResponse(format!("Failed to serialize legs: {}", e)))?;
5387
5388 let direction_str = match direction {
5389 crate::model::types::Direction::Buy => "buy",
5390 crate::model::types::Direction::Sell => "sell",
5391 crate::model::types::Direction::Unknown => "buy",
5392 };
5393
5394 let mut query_params = vec![
5395 format!("block_rfq_id={}", block_rfq_id),
5396 format!("amount={}", amount),
5397 format!("direction={}", direction_str),
5398 format!("legs={}", urlencoding::encode(&legs_json)),
5399 ];
5400
5401 if let Some(l) = label {
5402 query_params.push(format!("label={}", urlencoding::encode(l)));
5403 }
5404
5405 if let Some(h) = hedge {
5406 let hedge_json = serde_json::to_string(h).map_err(|e| {
5407 HttpError::InvalidResponse(format!("Failed to serialize hedge: {}", e))
5408 })?;
5409 query_params.push(format!("hedge={}", urlencoding::encode(&hedge_json)));
5410 }
5411
5412 if let Some(ei) = execution_instruction {
5413 let ei_str = match ei {
5414 crate::model::response::ExecutionInstruction::AllOrNone => "all_or_none",
5415 crate::model::response::ExecutionInstruction::AnyPartOf => "any_part_of",
5416 };
5417 query_params.push(format!("execution_instruction={}", ei_str));
5418 }
5419
5420 if let Some(exp) = expires_at {
5421 query_params.push(format!("expires_at={}", exp));
5422 }
5423
5424 let url = format!(
5425 "{}{}?{}",
5426 self.base_url(),
5427 crate::constants::endpoints::ADD_BLOCK_RFQ_QUOTE,
5428 query_params.join("&")
5429 );
5430
5431 let response = self.make_authenticated_request(&url).await?;
5432
5433 if !response.status().is_success() {
5434 let error_text = response
5435 .text()
5436 .await
5437 .unwrap_or_else(|_| "Unknown error".to_string());
5438 return Err(HttpError::RequestFailed(format!(
5439 "Add Block RFQ quote failed: {}",
5440 error_text
5441 )));
5442 }
5443
5444 let api_response: ApiResponse<crate::model::response::BlockRfqQuote> = response
5445 .json()
5446 .await
5447 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
5448
5449 if let Some(error) = api_response.error {
5450 return Err(HttpError::RequestFailed(format!(
5451 "API error: {} - {}",
5452 error.code, error.message
5453 )));
5454 }
5455
5456 api_response
5457 .result
5458 .ok_or_else(|| HttpError::InvalidResponse("No Block RFQ quote in response".to_string()))
5459 }
5460
5461 #[allow(clippy::too_many_arguments)]
5478 pub async fn edit_block_rfq_quote(
5479 &self,
5480 block_rfq_quote_id: Option<i64>,
5481 block_rfq_id: Option<i64>,
5482 label: Option<&str>,
5483 amount: Option<f64>,
5484 legs: Option<&[crate::model::response::BlockRfqLeg]>,
5485 hedge: Option<&crate::model::response::BlockRfqHedge>,
5486 execution_instruction: Option<crate::model::response::ExecutionInstruction>,
5487 expires_at: Option<i64>,
5488 ) -> Result<crate::model::response::BlockRfqQuote, HttpError> {
5489 let mut query_params: Vec<String> = Vec::new();
5490
5491 if let Some(qid) = block_rfq_quote_id {
5492 query_params.push(format!("block_rfq_quote_id={}", qid));
5493 }
5494
5495 if let Some(id) = block_rfq_id {
5496 query_params.push(format!("block_rfq_id={}", id));
5497 }
5498
5499 if let Some(l) = label {
5500 query_params.push(format!("label={}", urlencoding::encode(l)));
5501 }
5502
5503 if let Some(a) = amount {
5504 query_params.push(format!("amount={}", a));
5505 }
5506
5507 if let Some(l) = legs {
5508 let legs_json = serde_json::to_string(l).map_err(|e| {
5509 HttpError::InvalidResponse(format!("Failed to serialize legs: {}", e))
5510 })?;
5511 query_params.push(format!("legs={}", urlencoding::encode(&legs_json)));
5512 }
5513
5514 if let Some(h) = hedge {
5515 let hedge_json = serde_json::to_string(h).map_err(|e| {
5516 HttpError::InvalidResponse(format!("Failed to serialize hedge: {}", e))
5517 })?;
5518 query_params.push(format!("hedge={}", urlencoding::encode(&hedge_json)));
5519 }
5520
5521 if let Some(ei) = execution_instruction {
5522 let ei_str = match ei {
5523 crate::model::response::ExecutionInstruction::AllOrNone => "all_or_none",
5524 crate::model::response::ExecutionInstruction::AnyPartOf => "any_part_of",
5525 };
5526 query_params.push(format!("execution_instruction={}", ei_str));
5527 }
5528
5529 if let Some(exp) = expires_at {
5530 query_params.push(format!("expires_at={}", exp));
5531 }
5532
5533 let url = format!(
5534 "{}{}?{}",
5535 self.base_url(),
5536 crate::constants::endpoints::EDIT_BLOCK_RFQ_QUOTE,
5537 query_params.join("&")
5538 );
5539
5540 let response = self.make_authenticated_request(&url).await?;
5541
5542 if !response.status().is_success() {
5543 let error_text = response
5544 .text()
5545 .await
5546 .unwrap_or_else(|_| "Unknown error".to_string());
5547 return Err(HttpError::RequestFailed(format!(
5548 "Edit Block RFQ quote failed: {}",
5549 error_text
5550 )));
5551 }
5552
5553 let api_response: ApiResponse<crate::model::response::BlockRfqQuote> = response
5554 .json()
5555 .await
5556 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
5557
5558 if let Some(error) = api_response.error {
5559 return Err(HttpError::RequestFailed(format!(
5560 "API error: {} - {}",
5561 error.code, error.message
5562 )));
5563 }
5564
5565 api_response
5566 .result
5567 .ok_or_else(|| HttpError::InvalidResponse("No Block RFQ quote in response".to_string()))
5568 }
5569
5570 pub async fn cancel_block_rfq_quote(
5582 &self,
5583 block_rfq_quote_id: Option<i64>,
5584 block_rfq_id: Option<i64>,
5585 label: Option<&str>,
5586 ) -> Result<crate::model::response::BlockRfqQuote, HttpError> {
5587 let mut query_params: Vec<String> = Vec::new();
5588
5589 if let Some(qid) = block_rfq_quote_id {
5590 query_params.push(format!("block_rfq_quote_id={}", qid));
5591 }
5592
5593 if let Some(id) = block_rfq_id {
5594 query_params.push(format!("block_rfq_id={}", id));
5595 }
5596
5597 if let Some(l) = label {
5598 query_params.push(format!("label={}", urlencoding::encode(l)));
5599 }
5600
5601 let url = format!(
5602 "{}{}?{}",
5603 self.base_url(),
5604 crate::constants::endpoints::CANCEL_BLOCK_RFQ_QUOTE,
5605 query_params.join("&")
5606 );
5607
5608 let response = self.make_authenticated_request(&url).await?;
5609
5610 if !response.status().is_success() {
5611 let error_text = response
5612 .text()
5613 .await
5614 .unwrap_or_else(|_| "Unknown error".to_string());
5615 return Err(HttpError::RequestFailed(format!(
5616 "Cancel Block RFQ quote failed: {}",
5617 error_text
5618 )));
5619 }
5620
5621 let api_response: ApiResponse<crate::model::response::BlockRfqQuote> = response
5622 .json()
5623 .await
5624 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
5625
5626 if let Some(error) = api_response.error {
5627 return Err(HttpError::RequestFailed(format!(
5628 "API error: {} - {}",
5629 error.code, error.message
5630 )));
5631 }
5632
5633 api_response
5634 .result
5635 .ok_or_else(|| HttpError::InvalidResponse("No Block RFQ quote in response".to_string()))
5636 }
5637
5638 pub async fn cancel_all_block_rfq_quotes(
5644 &self,
5645 ) -> Result<Vec<crate::model::response::BlockRfqQuote>, HttpError> {
5646 self.private_get(crate::constants::endpoints::CANCEL_ALL_BLOCK_RFQ_QUOTES, "")
5647 .await
5648 }
5649}