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