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::AdvancedOrderType;
11use crate::model::request::order::OrderRequest;
12use crate::model::request::position::MovePositionTrade;
13use crate::model::request::trade::TradesRequest;
14use crate::model::response::api_response::ApiResponse;
15use crate::model::response::deposit::DepositsResponse;
16use crate::model::response::margin::{MarginsResponse, OrderMargin};
17use crate::model::response::mass_quote::MassQuoteResponse;
18use crate::model::response::mmp::{MmpConfig, MmpStatus, SetMmpConfigRequest};
19use crate::model::response::order::LinkedOrderType;
20use crate::model::response::order::{OrderInfoResponse, OrderResponse};
21use crate::model::response::other::{
22 AccountSummariesResponse, AccountSummaryResponse, SettlementsResponse, TransactionLogResponse,
23 TransferResultResponse,
24};
25use crate::model::response::position::MovePositionResult;
26use crate::model::response::subaccount::SubaccountDetails;
27use crate::model::response::transfer::{InternalTransfer, TransfersResponse};
28use crate::model::response::trigger::TriggerOrderHistoryResponse;
29use crate::model::response::withdrawal::WithdrawalsResponse;
30use crate::model::trigger::TriggerFillCondition;
31use crate::model::{
32 TransactionLogRequest, UserTradeResponseByOrder, UserTradeWithPaginationResponse,
33};
34use crate::prelude::Trigger;
35
36fn trigger_str(t: &Trigger) -> &'static str {
37 match t {
38 Trigger::IndexPrice => "index_price",
39 Trigger::MarkPrice => "mark_price",
40 Trigger::LastPrice => "last_price",
41 }
42}
43
44fn linked_order_str(t: &LinkedOrderType) -> &'static str {
45 match t {
46 LinkedOrderType::OneTriggersOther => "one_triggers_other",
47 LinkedOrderType::OneCancelsOther => "one_cancels_other",
48 LinkedOrderType::OneTriggersOneCancelsOther => "one_triggers_one_cancels_other",
49 }
50}
51
52fn trigger_fill_str(t: &TriggerFillCondition) -> &'static str {
53 match t {
54 TriggerFillCondition::FirstHit => "first_hit",
55 TriggerFillCondition::CompleteFill => "complete_fill",
56 TriggerFillCondition::Incremental => "incremental",
57 }
58}
59
60fn advanced_str(a: &AdvancedOrderType) -> &'static str {
61 match a {
62 AdvancedOrderType::Usd => "usd",
63 AdvancedOrderType::Implv => "implv",
64 }
65}
66
67fn append_order_params(
70 out: &mut Vec<(String, String)>,
71 req: &crate::model::request::order::OrderRequest,
72) {
73 if let Some(a) = req.amount {
74 out.push(("amount".into(), a.to_string()));
75 }
76 if let Some(c) = req.contracts {
77 out.push(("contracts".into(), c.to_string()));
78 }
79 if let Some(t) = req.type_ {
80 out.push(("type".into(), t.as_str().into()));
81 }
82 if let Some(label) = req.label.as_ref() {
83 out.push(("label".into(), label.clone()));
84 }
85 if let Some(p) = req.price {
86 out.push(("price".into(), p.to_string()));
87 }
88 if let Some(tif) = req.time_in_force {
89 out.push(("time_in_force".into(), tif.as_str().into()));
90 }
91 if let Some(d) = req.display_amount {
92 out.push(("display_amount".into(), d.to_string()));
93 }
94 if req.post_only == Some(true) {
95 out.push(("post_only".into(), "true".into()));
96 }
97 if req.reject_post_only == Some(true) {
98 out.push(("reject_post_only".into(), "true".into()));
99 }
100 if req.reduce_only == Some(true) {
101 out.push(("reduce_only".into(), "true".into()));
102 }
103 if let Some(tp) = req.trigger_price {
104 out.push(("trigger_price".into(), tp.to_string()));
105 }
106 if let Some(to) = req.trigger_offset {
107 out.push(("trigger_offset".into(), to.to_string()));
108 }
109 if let Some(t) = req.trigger.as_ref() {
110 out.push(("trigger".into(), trigger_str(t).into()));
111 }
112 if let Some(a) = req.advanced.as_ref() {
113 out.push(("advanced".into(), advanced_str(a).into()));
114 }
115 if req.mmp == Some(true) {
116 out.push(("mmp".into(), "true".into()));
117 }
118 if let Some(vu) = req.valid_until {
119 out.push(("valid_until".into(), vu.to_string()));
120 }
121 if let Some(l) = req.linked_order_type.as_ref() {
122 out.push(("linked_order_type".into(), linked_order_str(l).into()));
123 }
124 if let Some(tfc) = req.trigger_fill_condition.as_ref() {
125 out.push((
126 "trigger_fill_condition".into(),
127 trigger_fill_str(tfc).into(),
128 ));
129 }
130 if let Some(cfg) = req.otoco_config.as_ref()
131 && !cfg.is_empty()
132 && let Ok(json) = serde_json::to_string(cfg)
133 {
134 out.push(("otoco_config".into(), json));
135 }
136}
137
138impl DeribitHttpClient {
140 pub async fn get_subaccounts(
158 &self,
159 with_portfolio: Option<bool>,
160 ) -> Result<Vec<Subaccount>, HttpError> {
161 let mut query_params = Vec::new();
162
163 if let Some(with_portfolio) = with_portfolio {
164 query_params.push(("with_portfolio".to_string(), with_portfolio.to_string()));
165 }
166
167 let query_string = if query_params.is_empty() {
168 String::new()
169 } else {
170 "?".to_string()
171 + &query_params
172 .iter()
173 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
174 .collect::<Vec<_>>()
175 .join("&")
176 };
177
178 let url = format!("{}{}{}", self.base_url(), GET_SUBACCOUNTS, query_string);
179
180 let response = self.make_authenticated_request(&url).await?;
181
182 if !response.status().is_success() {
183 let error_text = response
184 .text()
185 .await
186 .unwrap_or_else(|_| "Unknown error".to_string());
187 return Err(HttpError::RequestFailed(format!(
188 "Get subaccounts failed: {}",
189 error_text
190 )));
191 }
192
193 let response_text = response.text().await.map_err(|e| {
195 HttpError::InvalidResponse(format!("Failed to read response text: {}", e))
196 })?;
197
198 tracing::debug!("Raw API response: {}", response_text);
199
200 let api_response: ApiResponse<Vec<Subaccount>> = serde_json::from_str(&response_text)
201 .map_err(|e| {
202 HttpError::InvalidResponse(format!(
203 "Failed to parse JSON: {} - Raw response: {}",
204 e, response_text
205 ))
206 })?;
207
208 if let Some(error) = api_response.error {
209 return Err(HttpError::RequestFailed(format!(
210 "API error: {} - {}",
211 error.code, error.message
212 )));
213 }
214
215 api_response.result.ok_or_else(|| {
216 HttpError::InvalidResponse("No subaccounts data in response".to_string())
217 })
218 }
219
220 pub async fn get_subaccounts_details(
240 &self,
241 currency: &str,
242 with_open_orders: Option<bool>,
243 ) -> Result<Vec<SubaccountDetails>, HttpError> {
244 let mut query = format!("?currency={}", urlencoding::encode(currency));
245 if let Some(with_open_orders) = with_open_orders {
246 query.push_str(&format!("&with_open_orders={}", with_open_orders));
247 }
248 self.private_get(GET_SUBACCOUNTS_DETAILS, &query).await
249 }
250
251 pub async fn create_subaccount(&self) -> Result<Subaccount, HttpError> {
273 self.private_get(CREATE_SUBACCOUNT, "").await
274 }
275
276 pub async fn remove_subaccount(&self, subaccount_id: u64) -> Result<String, HttpError> {
302 let query = format!("?subaccount_id={}", subaccount_id);
303 self.private_get(REMOVE_SUBACCOUNT, &query).await
304 }
305
306 pub async fn change_subaccount_name(&self, sid: u64, name: &str) -> Result<String, HttpError> {
333 let query = format!("?sid={}&name={}", sid, urlencoding::encode(name));
334 self.private_get(CHANGE_SUBACCOUNT_NAME, &query).await
335 }
336
337 pub async fn toggle_subaccount_login(
365 &self,
366 sid: u64,
367 state: &str,
368 ) -> Result<String, HttpError> {
369 let query = format!("?sid={}&state={}", sid, urlencoding::encode(state));
370 self.private_get(TOGGLE_SUBACCOUNT_LOGIN, &query).await
371 }
372
373 pub async fn set_email_for_subaccount(
401 &self,
402 sid: u64,
403 email: &str,
404 ) -> Result<String, HttpError> {
405 let query = format!("?sid={}&email={}", sid, urlencoding::encode(email));
406 self.private_get(SET_EMAIL_FOR_SUBACCOUNT, &query).await
407 }
408
409 pub async fn toggle_notifications_from_subaccount(
436 &self,
437 sid: u64,
438 state: bool,
439 ) -> Result<String, HttpError> {
440 let query = format!("?sid={}&state={}", sid, state);
441 self.private_get(TOGGLE_NOTIFICATIONS_FROM_SUBACCOUNT, &query)
442 .await
443 }
444
445 pub async fn get_transaction_log(
469 &self,
470 request: TransactionLogRequest,
471 ) -> Result<TransactionLogResponse, HttpError> {
472 let mut query_params = vec![
473 ("currency", request.currency.to_string()),
474 ("start_timestamp", request.start_timestamp.to_string()),
475 ("end_timestamp", request.end_timestamp.to_string()),
476 ];
477 if let Some(query) = request.query {
478 query_params.push(("query", query));
479 }
480 if let Some(count) = request.count {
481 query_params.push(("count", count.to_string()));
482 }
483 if let Some(subaccount_id) = request.subaccount_id {
484 query_params.push(("subaccount_id", subaccount_id.to_string()));
485 }
486 if let Some(continuation) = request.continuation {
487 query_params.push(("continuation", continuation.to_string()));
488 }
489 let query = format!(
490 "?{}",
491 query_params
492 .iter()
493 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
494 .collect::<Vec<_>>()
495 .join("&")
496 );
497 self.private_get(GET_TRANSACTION_LOG, &query).await
498 }
499
500 pub async fn get_deposits(
520 &self,
521 currency: &str,
522 count: Option<u32>,
523 offset: Option<u32>,
524 ) -> Result<DepositsResponse, HttpError> {
525 let mut query = format!("?currency={}", urlencoding::encode(currency));
526 if let Some(count) = count {
527 query.push_str(&format!("&count={}", count));
528 }
529 if let Some(offset) = offset {
530 query.push_str(&format!("&offset={}", offset));
531 }
532 self.private_get(GET_DEPOSITS, &query).await
533 }
534
535 pub async fn get_withdrawals(
555 &self,
556 currency: &str,
557 count: Option<u32>,
558 offset: Option<u32>,
559 ) -> Result<WithdrawalsResponse, HttpError> {
560 let mut query = format!("?currency={}", urlencoding::encode(currency));
561 if let Some(count) = count {
562 query.push_str(&format!("&count={}", count));
563 }
564 if let Some(offset) = offset {
565 query.push_str(&format!("&offset={}", offset));
566 }
567 self.private_get(GET_WITHDRAWALS, &query).await
568 }
569
570 pub async fn submit_transfer_to_subaccount(
590 &self,
591 currency: &str,
592 amount: f64,
593 destination: u64,
594 ) -> Result<TransferResultResponse, HttpError> {
595 let query = format!(
596 "?currency={}&amount={}&destination={}",
597 urlencoding::encode(currency),
598 amount,
599 destination
600 );
601 self.private_get(SUBMIT_TRANSFER_TO_SUBACCOUNT, &query)
602 .await
603 }
604
605 pub async fn submit_transfer_to_user(
625 &self,
626 currency: &str,
627 amount: f64,
628 destination: &str,
629 ) -> Result<TransferResultResponse, HttpError> {
630 let query = format!(
631 "?currency={}&amount={}&destination={}",
632 urlencoding::encode(currency),
633 amount,
634 urlencoding::encode(destination)
635 );
636 self.private_get(SUBMIT_TRANSFER_TO_USER, &query).await
637 }
638
639 pub async fn get_transfers(
667 &self,
668 currency: &str,
669 count: Option<u32>,
670 offset: Option<u32>,
671 ) -> Result<TransfersResponse, HttpError> {
672 let mut query = format!("?currency={}", urlencoding::encode(currency));
673 if let Some(c) = count {
674 query.push_str(&format!("&count={}", c));
675 }
676 if let Some(o) = offset {
677 query.push_str(&format!("&offset={}", o));
678 }
679 self.private_get(GET_TRANSFERS, &query).await
680 }
681
682 pub async fn cancel_transfer_by_id(
709 &self,
710 currency: &str,
711 id: i64,
712 ) -> Result<InternalTransfer, HttpError> {
713 let query = format!("?currency={}&id={}", urlencoding::encode(currency), id);
714 self.private_get(CANCEL_TRANSFER_BY_ID, &query).await
715 }
716
717 pub async fn submit_transfer_between_subaccounts(
746 &self,
747 currency: &str,
748 amount: f64,
749 destination: i64,
750 source: Option<i64>,
751 ) -> Result<InternalTransfer, HttpError> {
752 let mut query = format!(
753 "?currency={}&amount={}&destination={}",
754 urlencoding::encode(currency),
755 amount,
756 destination
757 );
758 if let Some(s) = source {
759 query.push_str(&format!("&source={}", s));
760 }
761 self.private_get(SUBMIT_TRANSFER_BETWEEN_SUBACCOUNTS, &query)
762 .await
763 }
764
765 pub async fn buy_order(&self, request: OrderRequest) -> Result<OrderResponse, HttpError> {
778 self.submit_order(request, BUY, "Buy order").await
779 }
780
781 pub async fn sell_order(&self, request: OrderRequest) -> Result<OrderResponse, HttpError> {
794 self.submit_order(request, SELL, "Sell order").await
795 }
796
797 async fn submit_order(
798 &self,
799 request: OrderRequest,
800 endpoint: &str,
801 op_name: &str,
802 ) -> Result<OrderResponse, HttpError> {
803 if request.amount.is_none() && request.contracts.is_none() {
804 return Err(HttpError::RequestFailed(format!(
805 "{}: either `amount` or `contracts` must be set",
806 op_name
807 )));
808 }
809
810 let mut query_params: Vec<(String, String)> = Vec::with_capacity(16);
811 query_params.push((
812 "instrument_name".to_string(),
813 request.instrument_name.clone(),
814 ));
815 append_order_params(&mut query_params, &request);
816
817 let query_string = query_params
818 .iter()
819 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
820 .collect::<Vec<_>>()
821 .join("&");
822
823 let url = format!("{}{}?{}", self.base_url(), endpoint, query_string);
824
825 let response = self.make_authenticated_request(&url).await?;
826
827 if !response.status().is_success() {
828 let error_text = response
829 .text()
830 .await
831 .unwrap_or_else(|_| "Unknown error".to_string());
832 return Err(HttpError::RequestFailed(format!(
833 "{} failed: {}",
834 op_name, error_text
835 )));
836 }
837
838 let response_text = response
839 .text()
840 .await
841 .map_err(|e| HttpError::NetworkError(e.to_string()))?;
842
843 tracing::debug!("Raw API response: {}", response_text);
844
845 let api_response: ApiResponse<OrderResponse> = serde_json::from_str(&response_text)
846 .map_err(|e| {
847 HttpError::InvalidResponse(format!(
848 "Failed to parse JSON: {} - Raw response: {}",
849 e, response_text
850 ))
851 })?;
852
853 if let Some(error) = api_response.error {
854 return Err(HttpError::RequestFailed(format!(
855 "API error: {} - {}",
856 error.code, error.message
857 )));
858 }
859
860 api_response
861 .result
862 .ok_or_else(|| HttpError::InvalidResponse("No order data in response".to_string()))
863 }
864
865 pub async fn cancel_order(&self, order_id: &str) -> Result<OrderInfoResponse, HttpError> {
874 let query = format!("?order_id={}", urlencoding::encode(order_id));
875 self.private_get(CANCEL, &query).await
876 }
877
878 pub async fn cancel_all(&self) -> Result<u32, HttpError> {
886 self.private_get(CANCEL_ALL, "").await
887 }
888
889 pub async fn cancel_all_by_currency(&self, currency: &str) -> Result<u32, HttpError> {
901 let query = format!("?currency={}", urlencoding::encode(currency));
902 self.private_get(CANCEL_ALL_BY_CURRENCY, &query).await
903 }
904
905 pub async fn cancel_all_by_currency_pair(&self, currency_pair: &str) -> Result<u32, HttpError> {
917 let query = format!("?currency_pair={}", urlencoding::encode(currency_pair));
918 self.private_get(CANCEL_ALL_BY_CURRENCY_PAIR, &query).await
919 }
920
921 pub async fn cancel_all_by_instrument(&self, instrument_name: &str) -> Result<u32, HttpError> {
933 let query = format!("?instrument_name={}", urlencoding::encode(instrument_name));
934 self.private_get(CANCEL_ALL_BY_INSTRUMENT, &query).await
935 }
936
937 pub async fn cancel_all_by_kind_or_type(
950 &self,
951 kind: Option<&str>,
952 order_type: Option<&str>,
953 ) -> Result<u32, HttpError> {
954 let mut query_params = Vec::new();
955 if let Some(kind) = kind {
956 query_params.push(format!("kind={}", urlencoding::encode(kind)));
957 }
958 if let Some(order_type) = order_type {
959 query_params.push(format!("type={}", urlencoding::encode(order_type)));
960 }
961 let query = if query_params.is_empty() {
962 String::new()
963 } else {
964 format!("?{}", query_params.join("&"))
965 };
966 self.private_get(CANCEL_ALL_BY_KIND_OR_TYPE, &query).await
967 }
968
969 pub async fn cancel_by_label(&self, label: &str) -> Result<u32, HttpError> {
981 let query = format!("?label={}", urlencoding::encode(label));
982 self.private_get(CANCEL_BY_LABEL, &query).await
983 }
984
985 pub async fn get_account_summary(
995 &self,
996 currency: &str,
997 extended: Option<bool>,
998 ) -> Result<AccountSummaryResponse, HttpError> {
999 let mut query = format!("?currency={}", urlencoding::encode(currency));
1000 if let Some(extended) = extended {
1001 query.push_str(&format!("&extended={}", extended));
1002 }
1003 self.private_get(GET_ACCOUNT_SUMMARY, &query).await
1004 }
1005
1006 pub async fn get_account_summaries(
1027 &self,
1028 subaccount_id: Option<i64>,
1029 extended: Option<bool>,
1030 ) -> Result<AccountSummariesResponse, HttpError> {
1031 let mut params = Vec::new();
1032 if let Some(subaccount_id) = subaccount_id {
1033 params.push(format!("subaccount_id={}", subaccount_id));
1034 }
1035 if let Some(extended) = extended {
1036 params.push(format!("extended={}", extended));
1037 }
1038 let query = if params.is_empty() {
1039 String::new()
1040 } else {
1041 format!("?{}", params.join("&"))
1042 };
1043 self.private_get(GET_ACCOUNT_SUMMARIES, &query).await
1044 }
1045
1046 pub async fn get_positions(
1066 &self,
1067 currency: Option<&str>,
1068 kind: Option<&str>,
1069 subaccount_id: Option<i32>,
1070 ) -> Result<Vec<Position>, HttpError> {
1071 let mut params = Vec::new();
1072 if let Some(currency) = currency {
1073 params.push(format!("currency={}", urlencoding::encode(currency)));
1074 }
1075 if let Some(kind) = kind {
1076 params.push(format!("kind={}", urlencoding::encode(kind)));
1077 }
1078 if let Some(subaccount_id) = subaccount_id {
1079 params.push(format!("subaccount_id={}", subaccount_id));
1080 }
1081 let query = if params.is_empty() {
1082 String::new()
1083 } else {
1084 format!("?{}", params.join("&"))
1085 };
1086 self.private_get(GET_POSITIONS, &query).await
1087 }
1088
1089 pub async fn get_position(&self, instrument_name: &str) -> Result<Vec<Position>, HttpError> {
1102 let query = format!("?instrument_name={}", urlencoding::encode(instrument_name));
1103 self.private_get(GET_POSITION, &query).await
1104 }
1105
1106 pub async fn edit_order(&self, request: OrderRequest) -> Result<OrderResponse, HttpError> {
1115 let order_id = request.order_id.clone().ok_or_else(|| {
1116 HttpError::RequestFailed("order_id is required for edit_order".to_string())
1117 })?;
1118 let mut query_params: Vec<(String, String)> = vec![("order_id".into(), order_id)];
1119 append_order_params(&mut query_params, &request);
1120
1121 let query_string = query_params
1122 .iter()
1123 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
1124 .collect::<Vec<_>>()
1125 .join("&");
1126
1127 let url = format!("{}{}?{}", self.base_url(), EDIT, query_string);
1128
1129 let response = self.make_authenticated_request(&url).await?;
1130
1131 if !response.status().is_success() {
1132 let error_text = response
1133 .text()
1134 .await
1135 .unwrap_or_else(|_| "Unknown error".to_string());
1136 return Err(HttpError::RequestFailed(format!(
1137 "Edit order failed: {}",
1138 error_text
1139 )));
1140 }
1141
1142 let api_response: ApiResponse<OrderResponse> = response
1143 .json()
1144 .await
1145 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1146
1147 if let Some(error) = api_response.error {
1148 return Err(HttpError::RequestFailed(format!(
1149 "API error: {} - {}",
1150 error.code, error.message
1151 )));
1152 }
1153
1154 api_response
1155 .result
1156 .ok_or_else(|| HttpError::InvalidResponse("No order data in response".to_string()))
1157 }
1158
1159 pub async fn edit_order_by_label(
1185 &self,
1186 request: OrderRequest,
1187 ) -> Result<OrderResponse, HttpError> {
1188 let label = request.label.ok_or_else(|| {
1189 HttpError::RequestFailed("label is required for edit_order_by_label".to_string())
1190 })?;
1191
1192 let mut query_params = vec![
1193 ("label".to_string(), label),
1194 ("instrument_name".to_string(), request.instrument_name),
1195 ];
1196
1197 if let Some(amount) = request.amount {
1198 query_params.push(("amount".to_string(), amount.to_string()));
1199 }
1200
1201 if let Some(contracts) = request.contracts {
1202 query_params.push(("contracts".to_string(), contracts.to_string()));
1203 }
1204
1205 if let Some(price) = request.price {
1206 query_params.push(("price".to_string(), price.to_string()));
1207 }
1208
1209 if let Some(post_only) = request.post_only
1210 && post_only
1211 {
1212 query_params.push(("post_only".to_string(), "true".to_string()));
1213 }
1214
1215 if let Some(reduce_only) = request.reduce_only
1216 && reduce_only
1217 {
1218 query_params.push(("reduce_only".to_string(), "true".to_string()));
1219 }
1220
1221 if let Some(reject_post_only) = request.reject_post_only
1222 && reject_post_only
1223 {
1224 query_params.push(("reject_post_only".to_string(), "true".to_string()));
1225 }
1226
1227 if let Some(advanced) = request.advanced {
1228 let advanced_str = match advanced {
1229 crate::model::request::order::AdvancedOrderType::Usd => "usd",
1230 crate::model::request::order::AdvancedOrderType::Implv => "implv",
1231 };
1232 query_params.push(("advanced".to_string(), advanced_str.to_string()));
1233 }
1234
1235 if let Some(trigger_price) = request.trigger_price {
1236 query_params.push(("trigger_price".to_string(), trigger_price.to_string()));
1237 }
1238
1239 if let Some(mmp) = request.mmp
1240 && mmp
1241 {
1242 query_params.push(("mmp".to_string(), "true".to_string()));
1243 }
1244
1245 if let Some(valid_until) = request.valid_until {
1246 query_params.push(("valid_until".to_string(), valid_until.to_string()));
1247 }
1248
1249 let query_string = query_params
1250 .iter()
1251 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
1252 .collect::<Vec<_>>()
1253 .join("&");
1254
1255 let url = format!("{}{}?{}", self.base_url(), EDIT_BY_LABEL, query_string);
1256
1257 let response = self.make_authenticated_request(&url).await?;
1258
1259 if !response.status().is_success() {
1260 let error_text = response
1261 .text()
1262 .await
1263 .unwrap_or_else(|_| "Unknown error".to_string());
1264 return Err(HttpError::RequestFailed(format!(
1265 "Edit order by label failed: {}",
1266 error_text
1267 )));
1268 }
1269
1270 let api_response: ApiResponse<OrderResponse> = response
1271 .json()
1272 .await
1273 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1274
1275 if let Some(error) = api_response.error {
1276 return Err(HttpError::RequestFailed(format!(
1277 "API error: {} - {}",
1278 error.code, error.message
1279 )));
1280 }
1281
1282 api_response
1283 .result
1284 .ok_or_else(|| HttpError::InvalidResponse("No order data in response".to_string()))
1285 }
1286
1287 pub async fn close_position(
1310 &self,
1311 instrument_name: &str,
1312 order_type: &str,
1313 price: Option<f64>,
1314 ) -> Result<OrderResponse, HttpError> {
1315 let mut query = format!(
1316 "?instrument_name={}&type={}",
1317 urlencoding::encode(instrument_name),
1318 urlencoding::encode(order_type)
1319 );
1320 if let Some(price) = price {
1321 query.push_str(&format!("&price={}", price));
1322 }
1323 self.private_get(CLOSE_POSITION, &query).await
1324 }
1325
1326 pub async fn get_margins(
1348 &self,
1349 instrument_name: &str,
1350 amount: f64,
1351 price: f64,
1352 ) -> Result<MarginsResponse, HttpError> {
1353 let query = format!(
1354 "?instrument_name={}&amount={}&price={}",
1355 urlencoding::encode(instrument_name),
1356 amount,
1357 price
1358 );
1359 self.private_get(GET_MARGINS, &query).await
1360 }
1361
1362 pub async fn get_order_margin_by_ids(
1381 &self,
1382 ids: &[&str],
1383 ) -> Result<Vec<OrderMargin>, HttpError> {
1384 if ids.is_empty() {
1385 return Err(HttpError::RequestFailed(
1386 "ids array cannot be empty".to_string(),
1387 ));
1388 }
1389
1390 let ids_json = serde_json::to_string(ids)
1392 .map_err(|e| HttpError::InvalidResponse(format!("Failed to serialize ids: {}", e)))?;
1393
1394 let query_string = format!("ids={}", urlencoding::encode(&ids_json));
1395 let url = format!(
1396 "{}{}?{}",
1397 self.base_url(),
1398 GET_ORDER_MARGIN_BY_IDS,
1399 query_string
1400 );
1401
1402 let response = self.make_authenticated_request(&url).await?;
1403
1404 if !response.status().is_success() {
1405 let error_text = response
1406 .text()
1407 .await
1408 .unwrap_or_else(|_| "Unknown error".to_string());
1409 return Err(HttpError::RequestFailed(format!(
1410 "Get order margin by IDs failed: {}",
1411 error_text
1412 )));
1413 }
1414
1415 let api_response: ApiResponse<Vec<OrderMargin>> = response
1416 .json()
1417 .await
1418 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1419
1420 if let Some(error) = api_response.error {
1421 return Err(HttpError::RequestFailed(format!(
1422 "API error: {} - {}",
1423 error.code, error.message
1424 )));
1425 }
1426
1427 api_response.result.ok_or_else(|| {
1428 HttpError::InvalidResponse("No order margin data in response".to_string())
1429 })
1430 }
1431
1432 pub async fn get_order_state_by_label(
1453 &self,
1454 currency: &str,
1455 label: &str,
1456 ) -> Result<Vec<OrderInfoResponse>, HttpError> {
1457 let query = format!(
1458 "?currency={}&label={}",
1459 urlencoding::encode(currency),
1460 urlencoding::encode(label)
1461 );
1462 self.private_get(GET_ORDER_STATE_BY_LABEL, &query).await
1463 }
1464
1465 pub async fn get_settlement_history_by_currency(
1488 &self,
1489 currency: &str,
1490 settlement_type: Option<&str>,
1491 count: Option<u32>,
1492 continuation: Option<&str>,
1493 search_start_timestamp: Option<u64>,
1494 ) -> Result<SettlementsResponse, HttpError> {
1495 let mut query = format!("?currency={}", urlencoding::encode(currency));
1496 if let Some(settlement_type) = settlement_type {
1497 query.push_str(&format!("&type={}", urlencoding::encode(settlement_type)));
1498 }
1499 if let Some(count) = count {
1500 query.push_str(&format!("&count={}", count));
1501 }
1502 if let Some(continuation) = continuation {
1503 query.push_str(&format!(
1504 "&continuation={}",
1505 urlencoding::encode(continuation)
1506 ));
1507 }
1508 if let Some(search_start_timestamp) = search_start_timestamp {
1509 query.push_str(&format!(
1510 "&search_start_timestamp={}",
1511 search_start_timestamp
1512 ));
1513 }
1514 self.private_get(GET_SETTLEMENT_HISTORY_BY_CURRENCY, &query)
1515 .await
1516 }
1517
1518 pub async fn get_settlement_history_by_instrument(
1541 &self,
1542 instrument_name: &str,
1543 settlement_type: Option<&str>,
1544 count: Option<u32>,
1545 continuation: Option<&str>,
1546 search_start_timestamp: Option<u64>,
1547 ) -> Result<SettlementsResponse, HttpError> {
1548 let mut query = format!("?instrument_name={}", urlencoding::encode(instrument_name));
1549 if let Some(settlement_type) = settlement_type {
1550 query.push_str(&format!("&type={}", urlencoding::encode(settlement_type)));
1551 }
1552 if let Some(count) = count {
1553 query.push_str(&format!("&count={}", count));
1554 }
1555 if let Some(continuation) = continuation {
1556 query.push_str(&format!(
1557 "&continuation={}",
1558 urlencoding::encode(continuation)
1559 ));
1560 }
1561 if let Some(search_start_timestamp) = search_start_timestamp {
1562 query.push_str(&format!(
1563 "&search_start_timestamp={}",
1564 search_start_timestamp
1565 ));
1566 }
1567 self.private_get(GET_SETTLEMENT_HISTORY_BY_INSTRUMENT, &query)
1568 .await
1569 }
1570
1571 pub async fn get_trigger_order_history(
1593 &self,
1594 currency: &str,
1595 instrument_name: Option<&str>,
1596 count: Option<u32>,
1597 continuation: Option<&str>,
1598 ) -> Result<TriggerOrderHistoryResponse, HttpError> {
1599 let mut query = format!("?currency={}", urlencoding::encode(currency));
1600 if let Some(instrument_name) = instrument_name {
1601 query.push_str(&format!(
1602 "&instrument_name={}",
1603 urlencoding::encode(instrument_name)
1604 ));
1605 }
1606 if let Some(count) = count {
1607 query.push_str(&format!("&count={}", count));
1608 }
1609 if let Some(continuation) = continuation {
1610 query.push_str(&format!(
1611 "&continuation={}",
1612 urlencoding::encode(continuation)
1613 ));
1614 }
1615 self.private_get(GET_TRIGGER_ORDER_HISTORY, &query).await
1616 }
1617
1618 pub async fn move_positions(
1650 &self,
1651 currency: &str,
1652 source_uid: i64,
1653 target_uid: i64,
1654 trades: &[MovePositionTrade],
1655 ) -> Result<Vec<MovePositionResult>, HttpError> {
1656 let mut url = format!(
1657 "{}{}?currency={}&source_uid={}&target_uid={}",
1658 self.base_url(),
1659 MOVE_POSITIONS,
1660 urlencoding::encode(currency),
1661 source_uid,
1662 target_uid
1663 );
1664
1665 let trades_json = serde_json::to_string(trades).map_err(|e| {
1667 HttpError::InvalidResponse(format!("Failed to serialize trades: {}", e))
1668 })?;
1669 url.push_str(&format!("&trades={}", urlencoding::encode(&trades_json)));
1670
1671 let response = self.make_authenticated_request(&url).await?;
1672
1673 if !response.status().is_success() {
1674 let error_text = response
1675 .text()
1676 .await
1677 .unwrap_or_else(|_| "Unknown error".to_string());
1678 return Err(HttpError::RequestFailed(format!(
1679 "Move positions failed: {}",
1680 error_text
1681 )));
1682 }
1683
1684 let api_response: ApiResponse<Vec<MovePositionResult>> = response
1685 .json()
1686 .await
1687 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1688
1689 if let Some(error) = api_response.error {
1690 return Err(HttpError::RequestFailed(format!(
1691 "API error: {} - {}",
1692 error.code, error.message
1693 )));
1694 }
1695
1696 api_response.result.ok_or_else(|| {
1697 HttpError::InvalidResponse("No move positions data in response".to_string())
1698 })
1699 }
1700
1701 pub async fn get_mmp_config(
1721 &self,
1722 index_name: Option<&str>,
1723 mmp_group: Option<&str>,
1724 block_rfq: Option<bool>,
1725 ) -> Result<Vec<MmpConfig>, HttpError> {
1726 let mut params = Vec::new();
1727 if let Some(index) = index_name {
1728 params.push(format!("index_name={}", urlencoding::encode(index)));
1729 }
1730 if let Some(group) = mmp_group {
1731 params.push(format!("mmp_group={}", urlencoding::encode(group)));
1732 }
1733 if let Some(rfq) = block_rfq
1734 && rfq
1735 {
1736 params.push("block_rfq=true".to_string());
1737 }
1738 let query = if params.is_empty() {
1739 String::new()
1740 } else {
1741 format!("?{}", params.join("&"))
1742 };
1743 self.private_get(GET_MMP_CONFIG, &query).await
1744 }
1745
1746 pub async fn get_mmp_status(
1766 &self,
1767 index_name: Option<&str>,
1768 mmp_group: Option<&str>,
1769 block_rfq: Option<bool>,
1770 ) -> Result<Vec<MmpStatus>, HttpError> {
1771 let mut params = Vec::new();
1772 if let Some(index) = index_name {
1773 params.push(format!("index_name={}", urlencoding::encode(index)));
1774 }
1775 if let Some(group) = mmp_group {
1776 params.push(format!("mmp_group={}", urlencoding::encode(group)));
1777 }
1778 if let Some(rfq) = block_rfq
1779 && rfq
1780 {
1781 params.push("block_rfq=true".to_string());
1782 }
1783 let query = if params.is_empty() {
1784 String::new()
1785 } else {
1786 format!("?{}", params.join("&"))
1787 };
1788 self.private_get(GET_MMP_STATUS, &query).await
1789 }
1790
1791 pub async fn set_mmp_config(
1818 &self,
1819 request: SetMmpConfigRequest,
1820 ) -> Result<MmpConfig, HttpError> {
1821 let mut query_params = vec![
1822 ("index_name".to_string(), request.index_name),
1823 ("interval".to_string(), request.interval.to_string()),
1824 ("frozen_time".to_string(), request.frozen_time.to_string()),
1825 ];
1826
1827 if let Some(quantity_limit) = request.quantity_limit {
1828 query_params.push(("quantity_limit".to_string(), quantity_limit.to_string()));
1829 }
1830
1831 if let Some(delta_limit) = request.delta_limit {
1832 query_params.push(("delta_limit".to_string(), delta_limit.to_string()));
1833 }
1834
1835 if let Some(vega_limit) = request.vega_limit {
1836 query_params.push(("vega_limit".to_string(), vega_limit.to_string()));
1837 }
1838
1839 if let Some(max_quote_quantity) = request.max_quote_quantity {
1840 query_params.push((
1841 "max_quote_quantity".to_string(),
1842 max_quote_quantity.to_string(),
1843 ));
1844 }
1845
1846 if let Some(mmp_group) = request.mmp_group {
1847 query_params.push(("mmp_group".to_string(), mmp_group));
1848 }
1849
1850 if let Some(block_rfq) = request.block_rfq
1851 && block_rfq
1852 {
1853 query_params.push(("block_rfq".to_string(), "true".to_string()));
1854 }
1855
1856 let query_string = query_params
1857 .iter()
1858 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
1859 .collect::<Vec<_>>()
1860 .join("&");
1861
1862 let url = format!("{}{}?{}", self.base_url(), SET_MMP_CONFIG, query_string);
1863
1864 let response = self.make_authenticated_request(&url).await?;
1865
1866 if !response.status().is_success() {
1867 let error_text = response
1868 .text()
1869 .await
1870 .unwrap_or_else(|_| "Unknown error".to_string());
1871 return Err(HttpError::RequestFailed(format!(
1872 "Set MMP config failed: {}",
1873 error_text
1874 )));
1875 }
1876
1877 let api_response: ApiResponse<MmpConfig> = response
1878 .json()
1879 .await
1880 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1881
1882 if let Some(error) = api_response.error {
1883 return Err(HttpError::RequestFailed(format!(
1884 "API error: {} - {}",
1885 error.code, error.message
1886 )));
1887 }
1888
1889 api_response
1890 .result
1891 .ok_or_else(|| HttpError::InvalidResponse("No MMP config data in response".to_string()))
1892 }
1893
1894 pub async fn reset_mmp(
1914 &self,
1915 index_name: &str,
1916 mmp_group: Option<&str>,
1917 block_rfq: Option<bool>,
1918 ) -> Result<String, HttpError> {
1919 let mut query = format!("?index_name={}", urlencoding::encode(index_name));
1920 if let Some(group) = mmp_group {
1921 query.push_str(&format!("&mmp_group={}", urlencoding::encode(group)));
1922 }
1923 if let Some(rfq) = block_rfq
1924 && rfq
1925 {
1926 query.push_str("&block_rfq=true");
1927 }
1928 self.private_get(RESET_MMP, &query).await
1929 }
1930
1931 pub async fn mass_quote(
1940 &self,
1941 _quotes: MassQuoteRequest,
1942 ) -> Result<MassQuoteResponse, HttpError> {
1943 Err(HttpError::ConfigError(
1944 "Mass quote endpoint is only available via WebSocket connections. \
1945 According to Deribit's technical specifications, private/mass_quote requires \
1946 WebSocket for real-time quote management, MMP group integration, and \
1947 Cancel-on-Disconnect functionality. Please use the deribit-websocket client \
1948 for mass quote operations."
1949 .to_string(),
1950 ))
1951 }
1952
1953 pub async fn get_user_trades_by_instrument(
1967 &self,
1968 instrument_name: &str,
1969 start_seq: Option<u64>,
1970 end_seq: Option<u64>,
1971 count: Option<u32>,
1972 include_old: Option<bool>,
1973 sorting: Option<&str>,
1974 ) -> Result<UserTradeWithPaginationResponse, HttpError> {
1975 let mut query_params = vec![("instrument_name".to_string(), instrument_name.to_string())];
1976
1977 if let Some(start_seq) = start_seq {
1978 query_params.push(("start_seq".to_string(), start_seq.to_string()));
1979 }
1980
1981 if let Some(end_seq) = end_seq {
1982 query_params.push(("end_seq".to_string(), end_seq.to_string()));
1983 }
1984
1985 if let Some(count) = count {
1986 query_params.push(("count".to_string(), count.to_string()));
1987 }
1988
1989 if let Some(include_old) = include_old {
1990 query_params.push(("include_old".to_string(), include_old.to_string()));
1991 }
1992
1993 if let Some(sorting) = sorting {
1994 query_params.push(("sorting".to_string(), sorting.to_string()));
1995 }
1996
1997 let query_string = query_params
1998 .iter()
1999 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
2000 .collect::<Vec<_>>()
2001 .join("&");
2002
2003 let url = format!(
2004 "{}{}?{}",
2005 self.base_url(),
2006 GET_USER_TRADES_BY_INSTRUMENT,
2007 query_string
2008 );
2009
2010 let response = self.make_authenticated_request(&url).await?;
2011
2012 if !response.status().is_success() {
2013 let error_text = response
2014 .text()
2015 .await
2016 .unwrap_or_else(|_| "Unknown error".to_string());
2017 return Err(HttpError::RequestFailed(format!(
2018 "Get user trades by instrument failed: {}",
2019 error_text
2020 )));
2021 }
2022
2023 let response_text = response.text().await.map_err(|e| {
2025 HttpError::InvalidResponse(format!("Failed to read response text: {}", e))
2026 })?;
2027
2028 tracing::debug!(
2029 "Raw API response for get_user_trades_by_instrument: {}",
2030 response_text
2031 );
2032
2033 let api_response: ApiResponse<UserTradeWithPaginationResponse> =
2035 serde_json::from_str(&response_text).map_err(|e| {
2036 HttpError::InvalidResponse(format!(
2037 "error decoding response body: {} - Raw response: {}",
2038 e, response_text
2039 ))
2040 })?;
2041
2042 if let Some(error) = api_response.error {
2043 return Err(HttpError::RequestFailed(format!(
2044 "API error: {} - {}",
2045 error.code, error.message
2046 )));
2047 }
2048
2049 api_response.result.ok_or_else(|| {
2050 HttpError::InvalidResponse("No user trades data in response".to_string())
2051 })
2052 }
2053
2054 pub async fn cancel_quotes(&self, cancel_type: Option<&str>) -> Result<u32, HttpError> {
2063 let query = format!(
2064 "?cancel_type={}",
2065 urlencoding::encode(cancel_type.unwrap_or("all"))
2066 );
2067 self.private_get(CANCEL_QUOTES, &query).await
2068 }
2069
2070 pub async fn get_open_orders(
2080 &self,
2081 kind: Option<&str>,
2082 order_type: Option<&str>,
2083 ) -> Result<Vec<OrderInfoResponse>, HttpError> {
2084 let mut params = Vec::new();
2085 if let Some(kind) = kind {
2086 params.push(format!("kind={}", urlencoding::encode(kind)));
2087 }
2088 if let Some(order_type) = order_type {
2089 params.push(format!("type={}", urlencoding::encode(order_type)));
2090 }
2091 let query = if params.is_empty() {
2092 String::new()
2093 } else {
2094 format!("?{}", params.join("&"))
2095 };
2096 self.private_get(GET_OPEN_ORDERS, &query).await
2097 }
2098
2099 pub async fn get_open_orders_by_label(
2109 &self,
2110 label: &str,
2111 currency: &str,
2112 ) -> Result<Vec<OrderInfoResponse>, HttpError> {
2113 let query = format!(
2114 "?label={}¤cy={}",
2115 urlencoding::encode(label),
2116 urlencoding::encode(currency)
2117 );
2118 self.private_get(GET_OPEN_ORDERS_BY_LABEL, &query).await
2119 }
2120
2121 pub async fn get_order_state(&self, order_id: &str) -> Result<OrderInfoResponse, HttpError> {
2130 let query = format!("?order_id={}", urlencoding::encode(order_id));
2131 self.private_get(GET_ORDER_STATE, &query).await
2132 }
2133
2134 pub async fn get_open_orders_by_currency(
2145 &self,
2146 currency: &str,
2147 kind: Option<&str>,
2148 order_type: Option<&str>,
2149 ) -> Result<Vec<OrderInfoResponse>, HttpError> {
2150 let mut query = format!("?currency={}", urlencoding::encode(currency));
2151 if let Some(kind) = kind {
2152 query.push_str(&format!("&kind={}", urlencoding::encode(kind)));
2153 }
2154 if let Some(order_type) = order_type {
2155 query.push_str(&format!("&type={}", urlencoding::encode(order_type)));
2156 }
2157 self.private_get(GET_OPEN_ORDERS_BY_CURRENCY, &query).await
2158 }
2159
2160 pub async fn get_open_orders_by_instrument(
2170 &self,
2171 instrument_name: &str,
2172 order_type: Option<&str>,
2173 ) -> Result<Vec<OrderInfoResponse>, HttpError> {
2174 let mut query = format!("?instrument_name={}", urlencoding::encode(instrument_name));
2175 if let Some(order_type) = order_type {
2176 query.push_str(&format!("&type={}", urlencoding::encode(order_type)));
2177 }
2178 self.private_get(GET_OPEN_ORDERS_BY_INSTRUMENT, &query)
2179 .await
2180 }
2181
2182 pub async fn get_order_history(
2194 &self,
2195 currency: &str,
2196 kind: Option<&str>,
2197 count: Option<u32>,
2198 offset: Option<u32>,
2199 ) -> Result<Vec<OrderInfoResponse>, HttpError> {
2200 let mut query = format!("?currency={}", urlencoding::encode(currency));
2201 if let Some(kind) = kind {
2202 query.push_str(&format!("&kind={}", urlencoding::encode(kind)));
2203 }
2204 if let Some(count) = count {
2205 query.push_str(&format!("&count={}", count));
2206 }
2207 if let Some(offset) = offset {
2208 query.push_str(&format!("&offset={}", offset));
2209 }
2210 self.private_get(GET_ORDER_HISTORY_BY_CURRENCY, &query)
2211 .await
2212 }
2213
2214 pub async fn get_order_history_by_currency(
2226 &self,
2227 currency: &str,
2228 kind: Option<&str>,
2229 count: Option<u32>,
2230 offset: Option<u32>,
2231 ) -> Result<Vec<OrderInfoResponse>, HttpError> {
2232 self.get_order_history(currency, kind, count, offset).await
2234 }
2235
2236 pub async fn get_order_history_by_instrument(
2247 &self,
2248 instrument_name: &str,
2249 count: Option<u32>,
2250 offset: Option<u32>,
2251 ) -> Result<Vec<OrderInfoResponse>, HttpError> {
2252 let mut query = format!("?instrument_name={}", urlencoding::encode(instrument_name));
2253 if let Some(count) = count {
2254 query.push_str(&format!("&count={}", count));
2255 }
2256 if let Some(offset) = offset {
2257 query.push_str(&format!("&offset={}", offset));
2258 }
2259 self.private_get(GET_ORDER_HISTORY_BY_INSTRUMENT, &query)
2260 .await
2261 }
2262
2263 #[allow(clippy::too_many_arguments)]
2283 pub async fn get_user_trades_by_currency(
2284 &self,
2285 request: TradesRequest,
2286 ) -> Result<UserTradeWithPaginationResponse, HttpError> {
2287 let mut query_params = vec![("currency".to_string(), request.currency.to_string())];
2288
2289 if let Some(kind) = request.kind {
2290 query_params.push(("kind".to_string(), kind.to_string()));
2291 }
2292
2293 if let Some(start_id) = request.start_id {
2294 query_params.push(("start_id".to_string(), start_id));
2295 }
2296
2297 if let Some(end_id) = request.end_id {
2298 query_params.push(("end_id".to_string(), end_id));
2299 }
2300
2301 if let Some(count) = request.count {
2302 query_params.push(("count".to_string(), count.to_string()));
2303 }
2304
2305 if let Some(start_timestamp) = request.start_timestamp {
2306 query_params.push(("start_timestamp".to_string(), start_timestamp.to_string()));
2307 }
2308
2309 if let Some(end_timestamp) = request.end_timestamp {
2310 query_params.push(("end_timestamp".to_string(), end_timestamp.to_string()));
2311 }
2312
2313 if let Some(sorting) = request.sorting {
2314 query_params.push(("sorting".to_string(), sorting.to_string()));
2315 }
2316
2317 if let Some(historical) = request.historical {
2318 query_params.push(("historical".to_string(), historical.to_string()));
2319 }
2320
2321 if let Some(subaccount_id) = request.subaccount_id {
2322 query_params.push(("subaccount_id".to_string(), subaccount_id.to_string()));
2323 }
2324
2325 let query_string = query_params
2326 .iter()
2327 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
2328 .collect::<Vec<_>>()
2329 .join("&");
2330
2331 let url = format!(
2332 "{}{}?{}",
2333 self.base_url(),
2334 GET_USER_TRADES_BY_CURRENCY,
2335 query_string
2336 );
2337
2338 let response = self.make_authenticated_request(&url).await?;
2339
2340 if !response.status().is_success() {
2341 let error_text = response
2342 .text()
2343 .await
2344 .unwrap_or_else(|_| "Unknown error".to_string());
2345 return Err(HttpError::RequestFailed(format!(
2346 "Get user trades by currency failed: {}",
2347 error_text
2348 )));
2349 }
2350
2351 let response_text = response.text().await.map_err(|e| {
2353 HttpError::InvalidResponse(format!("Failed to read response text: {}", e))
2354 })?;
2355
2356 tracing::debug!(
2357 "Raw API response for get_user_trades_by_order: {}",
2358 response_text
2359 );
2360
2361 let api_response: ApiResponse<UserTradeWithPaginationResponse> =
2363 serde_json::from_str(&response_text).map_err(|e| {
2364 HttpError::InvalidResponse(format!(
2365 "error decoding response body: {} - Raw response: {}",
2366 e, response_text
2367 ))
2368 })?;
2369
2370 if let Some(error) = api_response.error {
2371 return Err(HttpError::RequestFailed(format!(
2372 "API error: {} - {}",
2373 error.code, error.message
2374 )));
2375 }
2376
2377 api_response.result.ok_or_else(|| {
2378 HttpError::InvalidResponse("No user trades data in response".to_string())
2379 })
2380 }
2381
2382 #[allow(clippy::too_many_arguments)]
2402 pub async fn get_user_trades_by_currency_and_time(
2403 &self,
2404 request: TradesRequest,
2405 ) -> Result<UserTradeWithPaginationResponse, HttpError> {
2406 let mut query_params = vec![("currency".to_string(), request.currency.to_string())];
2407
2408 if let Some(kind) = request.kind {
2409 query_params.push(("kind".to_string(), kind.to_string()));
2410 }
2411
2412 if let Some(start_id) = request.start_id {
2413 query_params.push(("start_id".to_string(), start_id));
2414 }
2415
2416 if let Some(end_id) = request.end_id {
2417 query_params.push(("end_id".to_string(), end_id));
2418 }
2419
2420 if let Some(count) = request.count {
2421 query_params.push(("count".to_string(), count.to_string()));
2422 }
2423
2424 if let Some(start_timestamp) = request.start_timestamp {
2425 query_params.push(("start_timestamp".to_string(), start_timestamp.to_string()));
2426 }
2427
2428 if let Some(end_timestamp) = request.end_timestamp {
2429 query_params.push(("end_timestamp".to_string(), end_timestamp.to_string()));
2430 }
2431
2432 if let Some(sorting) = request.sorting {
2433 query_params.push(("sorting".to_string(), sorting.to_string()));
2434 }
2435
2436 if let Some(historical) = request.historical {
2437 query_params.push(("historical".to_string(), historical.to_string()));
2438 }
2439
2440 if let Some(subaccount_id) = request.subaccount_id {
2441 query_params.push(("subaccount_id".to_string(), subaccount_id.to_string()));
2442 }
2443
2444 let query_string = query_params
2445 .iter()
2446 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
2447 .collect::<Vec<_>>()
2448 .join("&");
2449
2450 let url = format!(
2451 "{}{}?{}",
2452 self.base_url(),
2453 GET_USER_TRADES_BY_CURRENCY_AND_TIME,
2454 query_string
2455 );
2456
2457 let response = self.make_authenticated_request(&url).await?;
2458
2459 if !response.status().is_success() {
2460 let error_text = response
2461 .text()
2462 .await
2463 .unwrap_or_else(|_| "Unknown error".to_string());
2464 return Err(HttpError::RequestFailed(format!(
2465 "Get user trades by currency and time failed: {}",
2466 error_text
2467 )));
2468 }
2469
2470 let response_text = response.text().await.map_err(|e| {
2472 HttpError::InvalidResponse(format!("Failed to read response text: {}", e))
2473 })?;
2474
2475 tracing::debug!(
2476 "Raw API response for get_user_trades_by_order: {}",
2477 response_text
2478 );
2479
2480 let api_response: ApiResponse<UserTradeWithPaginationResponse> =
2482 serde_json::from_str(&response_text).map_err(|e| {
2483 HttpError::InvalidResponse(format!(
2484 "error decoding response body: {} - Raw response: {}",
2485 e, response_text
2486 ))
2487 })?;
2488
2489 if let Some(error) = api_response.error {
2490 return Err(HttpError::RequestFailed(format!(
2491 "API error: {} - {}",
2492 error.code, error.message
2493 )));
2494 }
2495
2496 api_response.result.ok_or_else(|| {
2497 HttpError::InvalidResponse("No user trades data in response".to_string())
2498 })
2499 }
2500
2501 pub async fn get_user_trades_by_instrument_and_time(
2515 &self,
2516 instrument_name: &str,
2517 start_timestamp: u64,
2518 end_timestamp: u64,
2519 count: Option<u32>,
2520 include_old: Option<bool>,
2521 sorting: Option<&str>,
2522 ) -> Result<UserTradeWithPaginationResponse, HttpError> {
2523 let mut query_params = vec![
2524 ("instrument_name".to_string(), instrument_name.to_string()),
2525 ("start_timestamp".to_string(), start_timestamp.to_string()),
2526 ("end_timestamp".to_string(), end_timestamp.to_string()),
2527 ];
2528
2529 if let Some(count) = count {
2530 query_params.push(("count".to_string(), count.to_string()));
2531 }
2532
2533 if let Some(include_old) = include_old {
2534 query_params.push(("include_old".to_string(), include_old.to_string()));
2535 }
2536
2537 if let Some(sorting) = sorting {
2538 query_params.push(("sorting".to_string(), sorting.to_string()));
2539 }
2540
2541 let query_string = query_params
2542 .iter()
2543 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
2544 .collect::<Vec<_>>()
2545 .join("&");
2546
2547 let url = format!(
2548 "{}{}?{}",
2549 self.base_url(),
2550 GET_USER_TRADES_BY_INSTRUMENT_AND_TIME,
2551 query_string
2552 );
2553
2554 let response = self.make_authenticated_request(&url).await?;
2555
2556 if !response.status().is_success() {
2557 let error_text = response
2558 .text()
2559 .await
2560 .unwrap_or_else(|_| "Unknown error".to_string());
2561 return Err(HttpError::RequestFailed(format!(
2562 "Get user trades by instrument and time failed: {}",
2563 error_text
2564 )));
2565 }
2566
2567 let response_text = response.text().await.map_err(|e| {
2569 HttpError::InvalidResponse(format!("Failed to read response text: {}", e))
2570 })?;
2571
2572 tracing::debug!(
2573 "Raw API response for get_user_trades_by_instrument_and_time: {}",
2574 response_text
2575 );
2576
2577 let api_response: ApiResponse<UserTradeWithPaginationResponse> =
2579 serde_json::from_str(&response_text).map_err(|e| {
2580 HttpError::InvalidResponse(format!(
2581 "error decoding response body: {} - Raw response: {}",
2582 e, response_text
2583 ))
2584 })?;
2585
2586 if let Some(error) = api_response.error {
2587 return Err(HttpError::RequestFailed(format!(
2588 "API error: {} - {}",
2589 error.code, error.message
2590 )));
2591 }
2592
2593 api_response.result.ok_or_else(|| {
2594 HttpError::InvalidResponse("No user trades data in response".to_string())
2595 })
2596 }
2597
2598 pub async fn get_user_trades_by_order(
2608 &self,
2609 order_id: &str,
2610 sorting: Option<&str>,
2611 historical: bool,
2612 ) -> Result<Vec<UserTradeResponseByOrder>, HttpError> {
2613 let mut query = format!("?order_id={}", urlencoding::encode(order_id));
2614 if let Some(sorting) = sorting {
2615 query.push_str(&format!("&sorting={}", urlencoding::encode(sorting)));
2616 }
2617 if historical {
2618 query.push_str("&historical=true");
2619 }
2620 self.private_get(GET_USER_TRADES_BY_ORDER, &query).await
2621 }
2622
2623 pub async fn create_api_key(
2659 &self,
2660 request: CreateApiKeyRequest,
2661 ) -> Result<ApiKeyInfo, HttpError> {
2662 let mut query_params = vec![("max_scope".to_string(), request.max_scope)];
2663
2664 if let Some(name) = request.name {
2665 query_params.push(("name".to_string(), name));
2666 }
2667
2668 if let Some(public_key) = request.public_key {
2669 query_params.push(("public_key".to_string(), public_key));
2670 }
2671
2672 if let Some(enabled_features) = request.enabled_features {
2673 for feature in enabled_features {
2674 query_params.push(("enabled_features".to_string(), feature));
2675 }
2676 }
2677
2678 let query_string = query_params
2679 .iter()
2680 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
2681 .collect::<Vec<_>>()
2682 .join("&");
2683
2684 let url = format!("{}{}?{}", self.base_url(), CREATE_API_KEY, query_string);
2685
2686 let response = self.make_authenticated_request(&url).await?;
2687
2688 if !response.status().is_success() {
2689 let error_text = response
2690 .text()
2691 .await
2692 .unwrap_or_else(|_| "Unknown error".to_string());
2693 return Err(HttpError::RequestFailed(format!(
2694 "Create API key failed: {}",
2695 error_text
2696 )));
2697 }
2698
2699 let api_response: ApiResponse<ApiKeyInfo> = response
2700 .json()
2701 .await
2702 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
2703
2704 if let Some(error) = api_response.error {
2705 return Err(HttpError::RequestFailed(format!(
2706 "API error: {} - {}",
2707 error.code, error.message
2708 )));
2709 }
2710
2711 api_response
2712 .result
2713 .ok_or_else(|| HttpError::InvalidResponse("No API key data in response".to_string()))
2714 }
2715
2716 pub async fn edit_api_key(&self, request: EditApiKeyRequest) -> Result<ApiKeyInfo, HttpError> {
2732 let mut query_params = vec![
2733 ("id".to_string(), request.id.to_string()),
2734 ("max_scope".to_string(), request.max_scope),
2735 ];
2736
2737 if let Some(name) = request.name {
2738 query_params.push(("name".to_string(), name));
2739 }
2740
2741 if let Some(enabled) = request.enabled {
2742 query_params.push(("enabled".to_string(), enabled.to_string()));
2743 }
2744
2745 if let Some(enabled_features) = request.enabled_features {
2746 for feature in enabled_features {
2747 query_params.push(("enabled_features".to_string(), feature));
2748 }
2749 }
2750
2751 if let Some(ip_whitelist) = request.ip_whitelist {
2752 for ip in ip_whitelist {
2753 query_params.push(("ip_whitelist".to_string(), ip));
2754 }
2755 }
2756
2757 let query_string = query_params
2758 .iter()
2759 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
2760 .collect::<Vec<_>>()
2761 .join("&");
2762
2763 let url = format!("{}{}?{}", self.base_url(), EDIT_API_KEY, query_string);
2764
2765 let response = self.make_authenticated_request(&url).await?;
2766
2767 if !response.status().is_success() {
2768 let error_text = response
2769 .text()
2770 .await
2771 .unwrap_or_else(|_| "Unknown error".to_string());
2772 return Err(HttpError::RequestFailed(format!(
2773 "Edit API key failed: {}",
2774 error_text
2775 )));
2776 }
2777
2778 let api_response: ApiResponse<ApiKeyInfo> = response
2779 .json()
2780 .await
2781 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
2782
2783 if let Some(error) = api_response.error {
2784 return Err(HttpError::RequestFailed(format!(
2785 "API error: {} - {}",
2786 error.code, error.message
2787 )));
2788 }
2789
2790 api_response
2791 .result
2792 .ok_or_else(|| HttpError::InvalidResponse("No API key data in response".to_string()))
2793 }
2794
2795 pub async fn disable_api_key(&self, id: u64) -> Result<ApiKeyInfo, HttpError> {
2812 let query = format!("?id={}", id);
2813 self.private_get(DISABLE_API_KEY, &query).await
2814 }
2815
2816 pub async fn enable_api_key(&self, id: u64) -> Result<ApiKeyInfo, HttpError> {
2832 let query = format!("?id={}", id);
2833 self.private_get(ENABLE_API_KEY, &query).await
2834 }
2835
2836 pub async fn list_api_keys(&self) -> Result<Vec<ApiKeyInfo>, HttpError> {
2863 self.private_get(LIST_API_KEYS, "").await
2864 }
2865
2866 pub async fn remove_api_key(&self, id: u64) -> Result<String, HttpError> {
2882 let query = format!("?id={}", id);
2883 self.private_get(REMOVE_API_KEY, &query).await
2884 }
2885
2886 pub async fn reset_api_key(&self, id: u64) -> Result<ApiKeyInfo, HttpError> {
2903 let query = format!("?id={}", id);
2904 self.private_get(RESET_API_KEY, &query).await
2905 }
2906
2907 pub async fn change_api_key_name(&self, id: u64, name: &str) -> Result<ApiKeyInfo, HttpError> {
2924 let query = format!("?id={}&name={}", id, urlencoding::encode(name));
2925 self.private_get(CHANGE_API_KEY_NAME, &query).await
2926 }
2927
2928 pub async fn change_scope_in_api_key(
2945 &self,
2946 id: u64,
2947 max_scope: &str,
2948 ) -> Result<ApiKeyInfo, HttpError> {
2949 let query = format!("?id={}&max_scope={}", id, urlencoding::encode(max_scope));
2950 self.private_get(CHANGE_SCOPE_IN_API_KEY, &query).await
2951 }
2952
2953 pub async fn save_address_beneficiary(
2994 &self,
2995 request: &crate::model::SaveAddressBeneficiaryRequest,
2996 ) -> Result<crate::model::AddressBeneficiary, HttpError> {
2997 let mut params = vec![
2998 format!("currency={}", urlencoding::encode(&request.currency)),
2999 format!("address={}", urlencoding::encode(&request.address)),
3000 format!("agreed={}", request.agreed),
3001 format!("personal={}", request.personal),
3002 format!("unhosted={}", request.unhosted),
3003 format!(
3004 "beneficiary_vasp_name={}",
3005 urlencoding::encode(&request.beneficiary_vasp_name)
3006 ),
3007 format!(
3008 "beneficiary_vasp_did={}",
3009 urlencoding::encode(&request.beneficiary_vasp_did)
3010 ),
3011 format!(
3012 "beneficiary_address={}",
3013 urlencoding::encode(&request.beneficiary_address)
3014 ),
3015 ];
3016
3017 if let Some(ref tag) = request.tag {
3018 params.push(format!("tag={}", urlencoding::encode(tag)));
3019 }
3020 if let Some(ref website) = request.beneficiary_vasp_website {
3021 params.push(format!(
3022 "beneficiary_vasp_website={}",
3023 urlencoding::encode(website)
3024 ));
3025 }
3026 if let Some(ref first_name) = request.beneficiary_first_name {
3027 params.push(format!(
3028 "beneficiary_first_name={}",
3029 urlencoding::encode(first_name)
3030 ));
3031 }
3032 if let Some(ref last_name) = request.beneficiary_last_name {
3033 params.push(format!(
3034 "beneficiary_last_name={}",
3035 urlencoding::encode(last_name)
3036 ));
3037 }
3038 if let Some(ref company_name) = request.beneficiary_company_name {
3039 params.push(format!(
3040 "beneficiary_company_name={}",
3041 urlencoding::encode(company_name)
3042 ));
3043 }
3044
3045 let url = format!(
3046 "{}{}?{}",
3047 self.base_url(),
3048 SAVE_ADDRESS_BENEFICIARY,
3049 params.join("&")
3050 );
3051
3052 let response = self.make_authenticated_request(&url).await?;
3053
3054 if !response.status().is_success() {
3055 let error_text = response
3056 .text()
3057 .await
3058 .unwrap_or_else(|_| "Unknown error".to_string());
3059 return Err(HttpError::RequestFailed(format!(
3060 "Save address beneficiary failed: {}",
3061 error_text
3062 )));
3063 }
3064
3065 let api_response: ApiResponse<crate::model::AddressBeneficiary> = response
3066 .json()
3067 .await
3068 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
3069
3070 if let Some(error) = api_response.error {
3071 return Err(HttpError::RequestFailed(format!(
3072 "API error: {} - {}",
3073 error.code, error.message
3074 )));
3075 }
3076
3077 api_response.result.ok_or_else(|| {
3078 HttpError::InvalidResponse("No beneficiary data in response".to_string())
3079 })
3080 }
3081
3082 pub async fn delete_address_beneficiary(
3108 &self,
3109 currency: &str,
3110 address: &str,
3111 tag: Option<&str>,
3112 ) -> Result<String, HttpError> {
3113 let mut query = format!(
3114 "?currency={}&address={}",
3115 urlencoding::encode(currency),
3116 urlencoding::encode(address)
3117 );
3118 if let Some(t) = tag {
3119 query.push_str(&format!("&tag={}", urlencoding::encode(t)));
3120 }
3121 self.private_get(DELETE_ADDRESS_BENEFICIARY, &query).await
3122 }
3123
3124 pub async fn get_address_beneficiary(
3150 &self,
3151 currency: &str,
3152 address: &str,
3153 tag: Option<&str>,
3154 ) -> Result<crate::model::AddressBeneficiary, HttpError> {
3155 let mut query = format!(
3156 "?currency={}&address={}",
3157 urlencoding::encode(currency),
3158 urlencoding::encode(address)
3159 );
3160 if let Some(t) = tag {
3161 query.push_str(&format!("&tag={}", urlencoding::encode(t)));
3162 }
3163 self.private_get(GET_ADDRESS_BENEFICIARY, &query).await
3164 }
3165
3166 pub async fn list_address_beneficiaries(
3196 &self,
3197 request: Option<&crate::model::ListAddressBeneficiariesRequest>,
3198 ) -> Result<crate::model::ListAddressBeneficiariesResponse, HttpError> {
3199 let mut params: Vec<String> = Vec::new();
3200
3201 if let Some(req) = request {
3202 if let Some(ref currency) = req.currency {
3203 params.push(format!("currency={}", urlencoding::encode(currency)));
3204 }
3205 if let Some(ref address) = req.address {
3206 params.push(format!("address={}", urlencoding::encode(address)));
3207 }
3208 if let Some(ref tag) = req.tag {
3209 params.push(format!("tag={}", urlencoding::encode(tag)));
3210 }
3211 if let Some(created_before) = req.created_before {
3212 params.push(format!("created_before={}", created_before));
3213 }
3214 if let Some(created_after) = req.created_after {
3215 params.push(format!("created_after={}", created_after));
3216 }
3217 if let Some(updated_before) = req.updated_before {
3218 params.push(format!("updated_before={}", updated_before));
3219 }
3220 if let Some(updated_after) = req.updated_after {
3221 params.push(format!("updated_after={}", updated_after));
3222 }
3223 if let Some(personal) = req.personal {
3224 params.push(format!("personal={}", personal));
3225 }
3226 if let Some(unhosted) = req.unhosted {
3227 params.push(format!("unhosted={}", unhosted));
3228 }
3229 if let Some(ref vasp_name) = req.beneficiary_vasp_name {
3230 params.push(format!(
3231 "beneficiary_vasp_name={}",
3232 urlencoding::encode(vasp_name)
3233 ));
3234 }
3235 if let Some(ref vasp_did) = req.beneficiary_vasp_did {
3236 params.push(format!(
3237 "beneficiary_vasp_did={}",
3238 urlencoding::encode(vasp_did)
3239 ));
3240 }
3241 if let Some(ref vasp_website) = req.beneficiary_vasp_website {
3242 params.push(format!(
3243 "beneficiary_vasp_website={}",
3244 urlencoding::encode(vasp_website)
3245 ));
3246 }
3247 if let Some(limit) = req.limit {
3248 params.push(format!("limit={}", limit));
3249 }
3250 if let Some(ref continuation) = req.continuation {
3251 params.push(format!(
3252 "continuation={}",
3253 urlencoding::encode(continuation)
3254 ));
3255 }
3256 }
3257
3258 let url = if params.is_empty() {
3259 format!("{}{}", self.base_url(), LIST_ADDRESS_BENEFICIARIES)
3260 } else {
3261 format!(
3262 "{}{}?{}",
3263 self.base_url(),
3264 LIST_ADDRESS_BENEFICIARIES,
3265 params.join("&")
3266 )
3267 };
3268
3269 let response = self.make_authenticated_request(&url).await?;
3270
3271 if !response.status().is_success() {
3272 let error_text = response
3273 .text()
3274 .await
3275 .unwrap_or_else(|_| "Unknown error".to_string());
3276 return Err(HttpError::RequestFailed(format!(
3277 "List address beneficiaries failed: {}",
3278 error_text
3279 )));
3280 }
3281
3282 let api_response: ApiResponse<crate::model::ListAddressBeneficiariesResponse> = response
3283 .json()
3284 .await
3285 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
3286
3287 if let Some(error) = api_response.error {
3288 return Err(HttpError::RequestFailed(format!(
3289 "API error: {} - {}",
3290 error.code, error.message
3291 )));
3292 }
3293
3294 api_response.result.ok_or_else(|| {
3295 HttpError::InvalidResponse("No beneficiaries data in response".to_string())
3296 })
3297 }
3298
3299 pub async fn set_clearance_originator(
3339 &self,
3340 deposit_id: &crate::model::DepositId,
3341 originator: &crate::model::Originator,
3342 ) -> Result<crate::model::ClearanceDepositResult, HttpError> {
3343 let deposit_id_json = serde_json::to_string(deposit_id).map_err(|e| {
3344 HttpError::InvalidResponse(format!("Failed to serialize deposit_id: {}", e))
3345 })?;
3346 let originator_json = serde_json::to_string(originator).map_err(|e| {
3347 HttpError::InvalidResponse(format!("Failed to serialize originator: {}", e))
3348 })?;
3349
3350 let url = format!(
3351 "{}{}?deposit_id={}&originator={}",
3352 self.base_url(),
3353 SET_CLEARANCE_ORIGINATOR,
3354 urlencoding::encode(&deposit_id_json),
3355 urlencoding::encode(&originator_json)
3356 );
3357
3358 let response = self.make_authenticated_request(&url).await?;
3359
3360 if !response.status().is_success() {
3361 let error_text = response
3362 .text()
3363 .await
3364 .unwrap_or_else(|_| "Unknown error".to_string());
3365 return Err(HttpError::RequestFailed(format!(
3366 "Set clearance originator failed: {}",
3367 error_text
3368 )));
3369 }
3370
3371 let api_response: ApiResponse<crate::model::ClearanceDepositResult> = response
3372 .json()
3373 .await
3374 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
3375
3376 if let Some(error) = api_response.error {
3377 return Err(HttpError::RequestFailed(format!(
3378 "API error: {} - {}",
3379 error.code, error.message
3380 )));
3381 }
3382
3383 api_response
3384 .result
3385 .ok_or_else(|| HttpError::InvalidResponse("No deposit result in response".to_string()))
3386 }
3387
3388 pub async fn get_access_log(
3398 &self,
3399 count: Option<u32>,
3400 offset: Option<u32>,
3401 ) -> Result<crate::model::AccessLogResponse, HttpError> {
3402 let mut params = Vec::new();
3403 if let Some(count) = count {
3404 params.push(format!("count={}", count));
3405 }
3406 if let Some(offset) = offset {
3407 params.push(format!("offset={}", offset));
3408 }
3409 let query = if params.is_empty() {
3410 String::new()
3411 } else {
3412 format!("?{}", params.join("&"))
3413 };
3414 self.private_get(crate::constants::endpoints::GET_ACCESS_LOG, &query)
3415 .await
3416 }
3417
3418 pub async fn get_user_locks(&self) -> Result<Vec<crate::model::UserLock>, HttpError> {
3423 self.private_get(crate::constants::endpoints::GET_USER_LOCKS, "")
3424 .await
3425 }
3426
3427 pub async fn list_custody_accounts(
3436 &self,
3437 currency: &str,
3438 ) -> Result<Vec<crate::model::CustodyAccount>, HttpError> {
3439 let query = format!("?currency={}", urlencoding::encode(currency));
3440 self.private_get(crate::constants::endpoints::LIST_CUSTODY_ACCOUNTS, &query)
3441 .await
3442 }
3443
3444 pub async fn simulate_portfolio(
3453 &self,
3454 request: crate::model::SimulatePortfolioRequest,
3455 ) -> Result<crate::model::SimulatePortfolioResponse, HttpError> {
3456 let mut query_params = vec![format!(
3457 "currency={}",
3458 urlencoding::encode(&request.currency)
3459 )];
3460
3461 if let Some(add_positions) = request.add_positions {
3462 query_params.push(format!("add_positions={}", add_positions));
3463 }
3464
3465 if let Some(ref positions) = request.simulated_positions {
3466 let positions_json = serde_json::to_string(positions)
3467 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
3468 query_params.push(format!(
3469 "simulated_positions={}",
3470 urlencoding::encode(&positions_json)
3471 ));
3472 }
3473
3474 let url = format!(
3475 "{}{}?{}",
3476 self.base_url(),
3477 crate::constants::endpoints::SIMULATE_PORTFOLIO,
3478 query_params.join("&")
3479 );
3480
3481 let response = self.make_authenticated_request(&url).await?;
3482
3483 if !response.status().is_success() {
3484 let error_text = response
3485 .text()
3486 .await
3487 .unwrap_or_else(|_| "Unknown error".to_string());
3488 return Err(HttpError::RequestFailed(format!(
3489 "Simulate portfolio failed: {}",
3490 error_text
3491 )));
3492 }
3493
3494 let api_response: ApiResponse<crate::model::SimulatePortfolioResponse> = response
3495 .json()
3496 .await
3497 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
3498
3499 if let Some(error) = api_response.error {
3500 return Err(HttpError::RequestFailed(format!(
3501 "API error: {} - {}",
3502 error.code, error.message
3503 )));
3504 }
3505
3506 api_response.result.ok_or_else(|| {
3507 HttpError::InvalidResponse("No portfolio simulation data in response".to_string())
3508 })
3509 }
3510
3511 pub async fn pme_simulate(
3520 &self,
3521 currency: &str,
3522 ) -> Result<crate::model::PmeSimulateResponse, HttpError> {
3523 let query = format!("?currency={}", urlencoding::encode(currency));
3524 self.private_get(crate::constants::endpoints::PME_SIMULATE, &query)
3525 .await
3526 }
3527
3528 pub async fn change_margin_model(
3539 &self,
3540 margin_model: crate::model::MarginModel,
3541 user_id: Option<u64>,
3542 dry_run: Option<bool>,
3543 ) -> Result<crate::model::ChangeMarginModelResponse, HttpError> {
3544 let mut query_params = vec![format!("margin_model={}", margin_model.as_str())];
3545
3546 if let Some(user_id) = user_id {
3547 query_params.push(format!("user_id={}", user_id));
3548 }
3549
3550 if let Some(dry_run) = dry_run {
3551 query_params.push(format!("dry_run={}", dry_run));
3552 }
3553
3554 let url = format!(
3555 "{}{}?{}",
3556 self.base_url(),
3557 crate::constants::endpoints::CHANGE_MARGIN_MODEL,
3558 query_params.join("&")
3559 );
3560
3561 let response = self.make_authenticated_request(&url).await?;
3562
3563 if !response.status().is_success() {
3564 let error_text = response
3565 .text()
3566 .await
3567 .unwrap_or_else(|_| "Unknown error".to_string());
3568 return Err(HttpError::RequestFailed(format!(
3569 "Change margin model failed: {}",
3570 error_text
3571 )));
3572 }
3573
3574 let api_response: ApiResponse<crate::model::ChangeMarginModelResponse> = response
3575 .json()
3576 .await
3577 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
3578
3579 if let Some(error) = api_response.error {
3580 return Err(HttpError::RequestFailed(format!(
3581 "API error: {} - {}",
3582 error.code, error.message
3583 )));
3584 }
3585
3586 api_response.result.ok_or_else(|| {
3587 HttpError::InvalidResponse("No margin model change data in response".to_string())
3588 })
3589 }
3590
3591 pub async fn set_self_trading_config(
3602 &self,
3603 mode: crate::model::SelfTradingMode,
3604 extended_to_subaccounts: bool,
3605 block_rfq_self_match_prevention: Option<bool>,
3606 ) -> Result<bool, HttpError> {
3607 let mut query_params = vec![
3608 format!("mode={}", mode.as_str()),
3609 format!("extended_to_subaccounts={}", extended_to_subaccounts),
3610 ];
3611
3612 if let Some(block_rfq) = block_rfq_self_match_prevention {
3613 query_params.push(format!("block_rfq_self_match_prevention={}", block_rfq));
3614 }
3615
3616 let url = format!(
3617 "{}{}?{}",
3618 self.base_url(),
3619 crate::constants::endpoints::SET_SELF_TRADING_CONFIG,
3620 query_params.join("&")
3621 );
3622
3623 let response = self.make_authenticated_request(&url).await?;
3624
3625 if !response.status().is_success() {
3626 let error_text = response
3627 .text()
3628 .await
3629 .unwrap_or_else(|_| "Unknown error".to_string());
3630 return Err(HttpError::RequestFailed(format!(
3631 "Set self trading config failed: {}",
3632 error_text
3633 )));
3634 }
3635
3636 let api_response: ApiResponse<String> = response
3637 .json()
3638 .await
3639 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
3640
3641 if let Some(error) = api_response.error {
3642 return Err(HttpError::RequestFailed(format!(
3643 "API error: {} - {}",
3644 error.code, error.message
3645 )));
3646 }
3647
3648 Ok(api_response.result.map(|s| s == "ok").unwrap_or(true))
3649 }
3650
3651 pub async fn set_disabled_trading_products(
3661 &self,
3662 trading_products: &[crate::model::TradingProduct],
3663 user_id: u64,
3664 ) -> Result<bool, HttpError> {
3665 let products: Vec<&str> = trading_products.iter().map(|p| p.as_str()).collect();
3666 let products_json = serde_json::to_string(&products)
3667 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
3668
3669 let url = format!(
3670 "{}{}?trading_products={}&user_id={}",
3671 self.base_url(),
3672 crate::constants::endpoints::SET_DISABLED_TRADING_PRODUCTS,
3673 urlencoding::encode(&products_json),
3674 user_id
3675 );
3676
3677 let response = self.make_authenticated_request(&url).await?;
3678
3679 if !response.status().is_success() {
3680 let error_text = response
3681 .text()
3682 .await
3683 .unwrap_or_else(|_| "Unknown error".to_string());
3684 return Err(HttpError::RequestFailed(format!(
3685 "Set disabled trading products failed: {}",
3686 error_text
3687 )));
3688 }
3689
3690 let api_response: ApiResponse<String> = response
3691 .json()
3692 .await
3693 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
3694
3695 if let Some(error) = api_response.error {
3696 return Err(HttpError::RequestFailed(format!(
3697 "API error: {} - {}",
3698 error.code, error.message
3699 )));
3700 }
3701
3702 Ok(api_response.result.map(|s| s == "ok").unwrap_or(true))
3703 }
3704
3705 pub async fn get_new_announcements(
3710 &self,
3711 ) -> Result<Vec<crate::model::Announcement>, HttpError> {
3712 self.private_get(crate::constants::endpoints::GET_NEW_ANNOUNCEMENTS, "")
3713 .await
3714 }
3715
3716 pub async fn set_announcement_as_read(&self, announcement_id: u64) -> Result<bool, HttpError> {
3725 let query = format!("?announcement_id={}", announcement_id);
3726 let result: String = self
3727 .private_get(
3728 crate::constants::endpoints::SET_ANNOUNCEMENT_AS_READ,
3729 &query,
3730 )
3731 .await?;
3732 Ok(result == "ok")
3733 }
3734
3735 pub async fn enable_affiliate_program(&self) -> Result<bool, HttpError> {
3740 let result: String = self
3741 .private_get(crate::constants::endpoints::ENABLE_AFFILIATE_PROGRAM, "")
3742 .await?;
3743 Ok(result == "ok")
3744 }
3745
3746 pub async fn get_affiliate_program_info(
3751 &self,
3752 ) -> Result<crate::model::AffiliateProgramInfo, HttpError> {
3753 self.private_get(crate::constants::endpoints::GET_AFFILIATE_PROGRAM_INFO, "")
3754 .await
3755 }
3756
3757 pub async fn set_email_language(
3766 &self,
3767 language: crate::model::EmailLanguage,
3768 ) -> Result<bool, HttpError> {
3769 let query = format!("?language={}", language.as_str());
3770 let result: String = self
3771 .private_get(crate::constants::endpoints::SET_EMAIL_LANGUAGE, &query)
3772 .await?;
3773 Ok(result == "ok")
3774 }
3775
3776 pub async fn get_email_language(&self) -> Result<String, HttpError> {
3781 self.private_get(crate::constants::endpoints::GET_EMAIL_LANGUAGE, "")
3782 .await
3783 }
3784
3785 pub async fn withdraw(
3809 &self,
3810 currency: &str,
3811 address: &str,
3812 amount: f64,
3813 priority: Option<crate::model::wallet::WithdrawalPriorityLevel>,
3814 ) -> Result<crate::model::Withdrawal, HttpError> {
3815 let mut query_params = vec![
3816 ("currency".to_string(), currency.to_string()),
3817 ("address".to_string(), address.to_string()),
3818 ("amount".to_string(), amount.to_string()),
3819 ];
3820
3821 if let Some(p) = priority {
3822 query_params.push(("priority".to_string(), p.as_str().to_string()));
3823 }
3824
3825 let query_string = query_params
3826 .iter()
3827 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
3828 .collect::<Vec<_>>()
3829 .join("&");
3830
3831 let url = format!("{}{}?{}", self.base_url(), WITHDRAW, query_string);
3832
3833 let response = self.make_authenticated_request(&url).await?;
3834
3835 if !response.status().is_success() {
3836 let error_text = response
3837 .text()
3838 .await
3839 .unwrap_or_else(|_| "Unknown error".to_string());
3840 return Err(HttpError::RequestFailed(format!(
3841 "Withdraw failed: {}",
3842 error_text
3843 )));
3844 }
3845
3846 let api_response: ApiResponse<crate::model::Withdrawal> = response
3847 .json()
3848 .await
3849 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
3850
3851 if let Some(error) = api_response.error {
3852 return Err(HttpError::RequestFailed(format!(
3853 "API error: {} - {}",
3854 error.code, error.message
3855 )));
3856 }
3857
3858 api_response
3859 .result
3860 .ok_or_else(|| HttpError::InvalidResponse("No withdrawal data in response".to_string()))
3861 }
3862
3863 pub async fn cancel_withdrawal(
3880 &self,
3881 currency: &str,
3882 id: u64,
3883 ) -> Result<crate::model::Withdrawal, HttpError> {
3884 let query = format!("?currency={}&id={}", urlencoding::encode(currency), id);
3885 self.private_get(CANCEL_WITHDRAWAL, &query).await
3886 }
3887
3888 pub async fn create_deposit_address(
3904 &self,
3905 currency: &str,
3906 ) -> Result<crate::model::wallet::DepositAddress, HttpError> {
3907 let query = format!("?currency={}", urlencoding::encode(currency));
3908 self.private_get(CREATE_DEPOSIT_ADDRESS, &query).await
3909 }
3910
3911 pub async fn get_current_deposit_address(
3927 &self,
3928 currency: &str,
3929 ) -> Result<crate::model::wallet::DepositAddress, HttpError> {
3930 let query = format!("?currency={}", urlencoding::encode(currency));
3931 self.private_get(GET_CURRENT_DEPOSIT_ADDRESS, &query).await
3932 }
3933
3934 pub async fn add_to_address_book(
3954 &self,
3955 currency: &str,
3956 address_type: crate::model::wallet::AddressBookType,
3957 address: &str,
3958 label: Option<&str>,
3959 tag: Option<&str>,
3960 ) -> Result<crate::model::wallet::AddressBookEntry, HttpError> {
3961 let mut query_params = vec![
3962 ("currency".to_string(), currency.to_string()),
3963 ("type".to_string(), address_type.as_str().to_string()),
3964 ("address".to_string(), address.to_string()),
3965 ];
3966
3967 if let Some(l) = label {
3968 query_params.push(("label".to_string(), l.to_string()));
3969 }
3970
3971 if let Some(t) = tag {
3972 query_params.push(("tag".to_string(), t.to_string()));
3973 }
3974
3975 let query_string = query_params
3976 .iter()
3977 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
3978 .collect::<Vec<_>>()
3979 .join("&");
3980
3981 let url = format!(
3982 "{}{}?{}",
3983 self.base_url(),
3984 ADD_TO_ADDRESS_BOOK,
3985 query_string
3986 );
3987
3988 let response = self.make_authenticated_request(&url).await?;
3989
3990 if !response.status().is_success() {
3991 let error_text = response
3992 .text()
3993 .await
3994 .unwrap_or_else(|_| "Unknown error".to_string());
3995 return Err(HttpError::RequestFailed(format!(
3996 "Add to address book failed: {}",
3997 error_text
3998 )));
3999 }
4000
4001 let api_response: ApiResponse<crate::model::wallet::AddressBookEntry> = response
4002 .json()
4003 .await
4004 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
4005
4006 if let Some(error) = api_response.error {
4007 return Err(HttpError::RequestFailed(format!(
4008 "API error: {} - {}",
4009 error.code, error.message
4010 )));
4011 }
4012
4013 api_response.result.ok_or_else(|| {
4014 HttpError::InvalidResponse("No address book entry in response".to_string())
4015 })
4016 }
4017
4018 pub async fn remove_from_address_book(
4036 &self,
4037 currency: &str,
4038 address_type: crate::model::wallet::AddressBookType,
4039 address: &str,
4040 ) -> Result<bool, HttpError> {
4041 let query = format!(
4042 "?currency={}&type={}&address={}",
4043 urlencoding::encode(currency),
4044 urlencoding::encode(address_type.as_str()),
4045 urlencoding::encode(address)
4046 );
4047 let result: String = self.private_get(REMOVE_FROM_ADDRESS_BOOK, &query).await?;
4048 Ok(result == "ok")
4049 }
4050
4051 pub async fn update_in_address_book(
4068 &self,
4069 request: &crate::model::request::wallet::UpdateInAddressBookRequest,
4070 ) -> Result<bool, HttpError> {
4071 let mut query_params = vec![
4072 ("currency".to_string(), request.currency.clone()),
4073 (
4074 "type".to_string(),
4075 request.address_type.as_str().to_string(),
4076 ),
4077 ("address".to_string(), request.address.clone()),
4078 ("label".to_string(), request.label.clone()),
4079 ("agreed".to_string(), request.agreed.to_string()),
4080 ("personal".to_string(), request.personal.to_string()),
4081 (
4082 "beneficiary_vasp_name".to_string(),
4083 request.beneficiary_vasp_name.clone(),
4084 ),
4085 (
4086 "beneficiary_vasp_did".to_string(),
4087 request.beneficiary_vasp_did.clone(),
4088 ),
4089 (
4090 "beneficiary_address".to_string(),
4091 request.beneficiary_address.clone(),
4092 ),
4093 ];
4094
4095 if let Some(ref website) = request.beneficiary_vasp_website {
4096 query_params.push(("beneficiary_vasp_website".to_string(), website.clone()));
4097 }
4098
4099 if let Some(ref first_name) = request.beneficiary_first_name {
4100 query_params.push(("beneficiary_first_name".to_string(), first_name.clone()));
4101 }
4102
4103 if let Some(ref last_name) = request.beneficiary_last_name {
4104 query_params.push(("beneficiary_last_name".to_string(), last_name.clone()));
4105 }
4106
4107 if let Some(ref company_name) = request.beneficiary_company_name {
4108 query_params.push(("beneficiary_company_name".to_string(), company_name.clone()));
4109 }
4110
4111 if let Some(ref tag) = request.tag {
4112 query_params.push(("tag".to_string(), tag.clone()));
4113 }
4114
4115 let query_string = query_params
4116 .iter()
4117 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
4118 .collect::<Vec<_>>()
4119 .join("&");
4120
4121 let url = format!(
4122 "{}{}?{}",
4123 self.base_url(),
4124 UPDATE_IN_ADDRESS_BOOK,
4125 query_string
4126 );
4127
4128 let response = self.make_authenticated_request(&url).await?;
4129
4130 if !response.status().is_success() {
4131 let error_text = response
4132 .text()
4133 .await
4134 .unwrap_or_else(|_| "Unknown error".to_string());
4135 return Err(HttpError::RequestFailed(format!(
4136 "Update in address book failed: {}",
4137 error_text
4138 )));
4139 }
4140
4141 let api_response: ApiResponse<String> = response
4142 .json()
4143 .await
4144 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
4145
4146 if let Some(error) = api_response.error {
4147 return Err(HttpError::RequestFailed(format!(
4148 "API error: {} - {}",
4149 error.code, error.message
4150 )));
4151 }
4152
4153 Ok(api_response.result.map(|s| s == "ok").unwrap_or(true))
4154 }
4155
4156 pub async fn get_address_book(
4173 &self,
4174 currency: &str,
4175 address_type: crate::model::wallet::AddressBookType,
4176 ) -> Result<Vec<crate::model::wallet::AddressBookEntry>, HttpError> {
4177 let query = format!(
4178 "?currency={}&type={}",
4179 urlencoding::encode(currency),
4180 urlencoding::encode(address_type.as_str())
4181 );
4182 self.private_get(GET_ADDRESS_BOOK, &query).await
4183 }
4184
4185 pub async fn approve_block_trade(
4210 &self,
4211 timestamp: u64,
4212 nonce: &str,
4213 role: crate::model::block_trade::BlockTradeRole,
4214 ) -> Result<bool, HttpError> {
4215 let query = format!(
4216 "?timestamp={}&nonce={}&role={}",
4217 timestamp,
4218 urlencoding::encode(nonce),
4219 urlencoding::encode(&role.to_string())
4220 );
4221 let result: String = self.private_get(APPROVE_BLOCK_TRADE, &query).await?;
4222 Ok(result == "ok")
4223 }
4224
4225 pub async fn execute_block_trade(
4243 &self,
4244 request: &crate::model::block_trade::ExecuteBlockTradeRequest,
4245 ) -> Result<crate::model::block_trade::BlockTradeResult, HttpError> {
4246 let trades_json = serde_json::to_string(&request.trades).map_err(|e| {
4247 HttpError::InvalidResponse(format!("Failed to serialize trades: {}", e))
4248 })?;
4249
4250 let query_params = [
4251 ("timestamp".to_string(), request.timestamp.to_string()),
4252 ("nonce".to_string(), request.nonce.clone()),
4253 ("role".to_string(), request.role.to_string()),
4254 ("trades".to_string(), trades_json),
4255 (
4256 "counterparty_signature".to_string(),
4257 request.counterparty_signature.clone(),
4258 ),
4259 ];
4260
4261 let query_string = query_params
4262 .iter()
4263 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
4264 .collect::<Vec<_>>()
4265 .join("&");
4266
4267 let url = format!(
4268 "{}{}?{}",
4269 self.base_url(),
4270 EXECUTE_BLOCK_TRADE,
4271 query_string
4272 );
4273
4274 let response = self.make_authenticated_request(&url).await?;
4275
4276 if !response.status().is_success() {
4277 let error_text = response
4278 .text()
4279 .await
4280 .unwrap_or_else(|_| "Unknown error".to_string());
4281 return Err(HttpError::RequestFailed(format!(
4282 "Execute block trade failed: {}",
4283 error_text
4284 )));
4285 }
4286
4287 let api_response: ApiResponse<crate::model::block_trade::BlockTradeResult> = response
4288 .json()
4289 .await
4290 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
4291
4292 if let Some(error) = api_response.error {
4293 return Err(HttpError::RequestFailed(format!(
4294 "API error: {} - {}",
4295 error.code, error.message
4296 )));
4297 }
4298
4299 api_response.result.ok_or_else(|| {
4300 HttpError::InvalidResponse("No block trade result in response".to_string())
4301 })
4302 }
4303
4304 pub async fn get_block_trade(
4320 &self,
4321 id: &str,
4322 ) -> Result<crate::model::block_trade::BlockTrade, HttpError> {
4323 let query = format!("?id={}", urlencoding::encode(id));
4324 self.private_get(GET_BLOCK_TRADE, &query).await
4325 }
4326
4327 pub async fn get_block_trade_requests(
4345 &self,
4346 broker_code: Option<&str>,
4347 ) -> Result<Vec<crate::model::block_trade::BlockTradeRequest>, HttpError> {
4348 let mut query_params = Vec::new();
4349
4350 if let Some(code) = broker_code {
4351 query_params.push(("broker_code".to_string(), code.to_string()));
4352 }
4353
4354 let query_string = if query_params.is_empty() {
4355 String::new()
4356 } else {
4357 format!(
4358 "?{}",
4359 query_params
4360 .iter()
4361 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
4362 .collect::<Vec<_>>()
4363 .join("&")
4364 )
4365 };
4366
4367 let url = format!(
4368 "{}{}{}",
4369 self.base_url(),
4370 GET_BLOCK_TRADE_REQUESTS,
4371 query_string
4372 );
4373
4374 let response = self.make_authenticated_request(&url).await?;
4375
4376 if !response.status().is_success() {
4377 let error_text = response
4378 .text()
4379 .await
4380 .unwrap_or_else(|_| "Unknown error".to_string());
4381 return Err(HttpError::RequestFailed(format!(
4382 "Get block trade requests failed: {}",
4383 error_text
4384 )));
4385 }
4386
4387 let api_response: ApiResponse<Vec<crate::model::block_trade::BlockTradeRequest>> = response
4388 .json()
4389 .await
4390 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
4391
4392 if let Some(error) = api_response.error {
4393 return Err(HttpError::RequestFailed(format!(
4394 "API error: {} - {}",
4395 error.code, error.message
4396 )));
4397 }
4398
4399 Ok(api_response.result.unwrap_or_default())
4400 }
4401
4402 pub async fn get_block_trades(
4418 &self,
4419 request: &crate::model::block_trade::GetBlockTradesRequest,
4420 ) -> Result<Vec<crate::model::block_trade::BlockTrade>, HttpError> {
4421 let mut query_params = Vec::new();
4422
4423 if let Some(ref currency) = request.currency {
4424 query_params.push(("currency".to_string(), currency.clone()));
4425 }
4426 if let Some(count) = request.count {
4427 query_params.push(("count".to_string(), count.to_string()));
4428 }
4429 if let Some(ref continuation) = request.continuation {
4430 query_params.push(("continuation".to_string(), continuation.clone()));
4431 }
4432 if let Some(start_ts) = request.start_timestamp {
4433 query_params.push(("start_timestamp".to_string(), start_ts.to_string()));
4434 }
4435 if let Some(end_ts) = request.end_timestamp {
4436 query_params.push(("end_timestamp".to_string(), end_ts.to_string()));
4437 }
4438
4439 let query_string = if query_params.is_empty() {
4440 String::new()
4441 } else {
4442 format!(
4443 "?{}",
4444 query_params
4445 .iter()
4446 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
4447 .collect::<Vec<_>>()
4448 .join("&")
4449 )
4450 };
4451
4452 let url = format!("{}{}{}", self.base_url(), GET_BLOCK_TRADES, query_string);
4453
4454 let response = self.make_authenticated_request(&url).await?;
4455
4456 if !response.status().is_success() {
4457 let error_text = response
4458 .text()
4459 .await
4460 .unwrap_or_else(|_| "Unknown error".to_string());
4461 return Err(HttpError::RequestFailed(format!(
4462 "Get block trades failed: {}",
4463 error_text
4464 )));
4465 }
4466
4467 let api_response: ApiResponse<Vec<crate::model::block_trade::BlockTrade>> = response
4468 .json()
4469 .await
4470 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
4471
4472 if let Some(error) = api_response.error {
4473 return Err(HttpError::RequestFailed(format!(
4474 "API error: {} - {}",
4475 error.code, error.message
4476 )));
4477 }
4478
4479 Ok(api_response.result.unwrap_or_default())
4480 }
4481
4482 pub async fn get_broker_trade_requests(
4494 &self,
4495 ) -> Result<Vec<crate::model::block_trade::BlockTradeRequest>, HttpError> {
4496 self.private_get(GET_BROKER_TRADE_REQUESTS, "").await
4497 }
4498
4499 pub async fn get_broker_trades(
4515 &self,
4516 request: &crate::model::block_trade::GetBlockTradesRequest,
4517 ) -> Result<Vec<crate::model::block_trade::BlockTrade>, HttpError> {
4518 let mut query_params = Vec::new();
4519
4520 if let Some(ref currency) = request.currency {
4521 query_params.push(("currency".to_string(), currency.clone()));
4522 }
4523 if let Some(count) = request.count {
4524 query_params.push(("count".to_string(), count.to_string()));
4525 }
4526 if let Some(ref continuation) = request.continuation {
4527 query_params.push(("continuation".to_string(), continuation.clone()));
4528 }
4529 if let Some(start_ts) = request.start_timestamp {
4530 query_params.push(("start_timestamp".to_string(), start_ts.to_string()));
4531 }
4532 if let Some(end_ts) = request.end_timestamp {
4533 query_params.push(("end_timestamp".to_string(), end_ts.to_string()));
4534 }
4535
4536 let query_string = if query_params.is_empty() {
4537 String::new()
4538 } else {
4539 format!(
4540 "?{}",
4541 query_params
4542 .iter()
4543 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
4544 .collect::<Vec<_>>()
4545 .join("&")
4546 )
4547 };
4548
4549 let url = format!("{}{}{}", self.base_url(), GET_BROKER_TRADES, query_string);
4550
4551 let response = self.make_authenticated_request(&url).await?;
4552
4553 if !response.status().is_success() {
4554 let error_text = response
4555 .text()
4556 .await
4557 .unwrap_or_else(|_| "Unknown error".to_string());
4558 return Err(HttpError::RequestFailed(format!(
4559 "Get broker trades failed: {}",
4560 error_text
4561 )));
4562 }
4563
4564 let api_response: ApiResponse<Vec<crate::model::block_trade::BlockTrade>> = response
4565 .json()
4566 .await
4567 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
4568
4569 if let Some(error) = api_response.error {
4570 return Err(HttpError::RequestFailed(format!(
4571 "API error: {} - {}",
4572 error.code, error.message
4573 )));
4574 }
4575
4576 Ok(api_response.result.unwrap_or_default())
4577 }
4578
4579 pub async fn invalidate_block_trade_signature(
4595 &self,
4596 signature: &str,
4597 ) -> Result<bool, HttpError> {
4598 let query = format!("?signature={}", urlencoding::encode(signature));
4599 let result: String = self
4600 .private_get(INVALIDATE_BLOCK_TRADE_SIGNATURE, &query)
4601 .await?;
4602 Ok(result == "ok")
4603 }
4604
4605 pub async fn reject_block_trade(
4623 &self,
4624 timestamp: u64,
4625 nonce: &str,
4626 role: crate::model::block_trade::BlockTradeRole,
4627 ) -> Result<bool, HttpError> {
4628 let query = format!(
4629 "?timestamp={}&nonce={}&role={}",
4630 timestamp,
4631 urlencoding::encode(nonce),
4632 urlencoding::encode(&role.to_string())
4633 );
4634 let result: String = self.private_get(REJECT_BLOCK_TRADE, &query).await?;
4635 Ok(result == "ok")
4636 }
4637
4638 pub async fn simulate_block_trade(
4654 &self,
4655 request: &crate::model::block_trade::SimulateBlockTradeRequest,
4656 ) -> Result<bool, HttpError> {
4657 let trades_json = serde_json::to_string(&request.trades).map_err(|e| {
4658 HttpError::InvalidResponse(format!("Failed to serialize trades: {}", e))
4659 })?;
4660
4661 let mut query_params = vec![("trades".to_string(), trades_json)];
4662
4663 if let Some(ref role) = request.role {
4664 query_params.push(("role".to_string(), role.to_string()));
4665 }
4666
4667 let query_string = query_params
4668 .iter()
4669 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
4670 .collect::<Vec<_>>()
4671 .join("&");
4672
4673 let url = format!(
4674 "{}{}?{}",
4675 self.base_url(),
4676 SIMULATE_BLOCK_TRADE,
4677 query_string
4678 );
4679
4680 let response = self.make_authenticated_request(&url).await?;
4681
4682 if !response.status().is_success() {
4683 let error_text = response
4684 .text()
4685 .await
4686 .unwrap_or_else(|_| "Unknown error".to_string());
4687 return Err(HttpError::RequestFailed(format!(
4688 "Simulate block trade failed: {}",
4689 error_text
4690 )));
4691 }
4692
4693 let api_response: ApiResponse<bool> = response
4694 .json()
4695 .await
4696 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
4697
4698 if let Some(error) = api_response.error {
4699 return Err(HttpError::RequestFailed(format!(
4700 "API error: {} - {}",
4701 error.code, error.message
4702 )));
4703 }
4704
4705 Ok(api_response.result.unwrap_or(false))
4706 }
4707
4708 pub async fn verify_block_trade(
4725 &self,
4726 request: &crate::model::block_trade::VerifyBlockTradeRequest,
4727 ) -> Result<crate::model::block_trade::BlockTradeSignature, HttpError> {
4728 let trades_json = serde_json::to_string(&request.trades).map_err(|e| {
4729 HttpError::InvalidResponse(format!("Failed to serialize trades: {}", e))
4730 })?;
4731
4732 let query_params = [
4733 ("timestamp".to_string(), request.timestamp.to_string()),
4734 ("nonce".to_string(), request.nonce.clone()),
4735 ("role".to_string(), request.role.to_string()),
4736 ("trades".to_string(), trades_json),
4737 ];
4738
4739 let query_string = query_params
4740 .iter()
4741 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
4742 .collect::<Vec<_>>()
4743 .join("&");
4744
4745 let url = format!("{}{}?{}", self.base_url(), VERIFY_BLOCK_TRADE, query_string);
4746
4747 let response = self.make_authenticated_request(&url).await?;
4748
4749 if !response.status().is_success() {
4750 let error_text = response
4751 .text()
4752 .await
4753 .unwrap_or_else(|_| "Unknown error".to_string());
4754 return Err(HttpError::RequestFailed(format!(
4755 "Verify block trade failed: {}",
4756 error_text
4757 )));
4758 }
4759
4760 let api_response: ApiResponse<crate::model::block_trade::BlockTradeSignature> = response
4761 .json()
4762 .await
4763 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
4764
4765 if let Some(error) = api_response.error {
4766 return Err(HttpError::RequestFailed(format!(
4767 "API error: {} - {}",
4768 error.code, error.message
4769 )));
4770 }
4771
4772 api_response
4773 .result
4774 .ok_or_else(|| HttpError::InvalidResponse("No signature in response".to_string()))
4775 }
4776
4777 pub async fn create_combo(
4811 &self,
4812 trades: &[crate::model::ComboTrade],
4813 ) -> Result<crate::model::Combo, HttpError> {
4814 let trades_json = serde_json::to_string(trades).map_err(|e| {
4815 HttpError::InvalidResponse(format!("Failed to serialize trades: {}", e))
4816 })?;
4817
4818 let url = format!(
4819 "{}{}?trades={}",
4820 self.base_url(),
4821 CREATE_COMBO,
4822 urlencoding::encode(&trades_json)
4823 );
4824
4825 let response = self.make_authenticated_request(&url).await?;
4826
4827 if !response.status().is_success() {
4828 let error_text = response
4829 .text()
4830 .await
4831 .unwrap_or_else(|_| "Unknown error".to_string());
4832 return Err(HttpError::RequestFailed(format!(
4833 "Create combo failed: {}",
4834 error_text
4835 )));
4836 }
4837
4838 let api_response: ApiResponse<crate::model::Combo> = response
4839 .json()
4840 .await
4841 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
4842
4843 if let Some(error) = api_response.error {
4844 return Err(HttpError::RequestFailed(format!(
4845 "API error: {} - {}",
4846 error.code, error.message
4847 )));
4848 }
4849
4850 api_response
4851 .result
4852 .ok_or_else(|| HttpError::InvalidResponse("No combo data in response".to_string()))
4853 }
4854
4855 pub async fn get_leg_prices(
4887 &self,
4888 legs: &[crate::model::LegInput],
4889 price: f64,
4890 ) -> Result<crate::model::LegPricesResponse, HttpError> {
4891 let legs_json = serde_json::to_string(legs)
4892 .map_err(|e| HttpError::InvalidResponse(format!("Failed to serialize legs: {}", e)))?;
4893
4894 let url = format!(
4895 "{}{}?legs={}&price={}",
4896 self.base_url(),
4897 GET_LEG_PRICES,
4898 urlencoding::encode(&legs_json),
4899 price
4900 );
4901
4902 let response = self.make_authenticated_request(&url).await?;
4903
4904 if !response.status().is_success() {
4905 let error_text = response
4906 .text()
4907 .await
4908 .unwrap_or_else(|_| "Unknown error".to_string());
4909 return Err(HttpError::RequestFailed(format!(
4910 "Get leg prices failed: {}",
4911 error_text
4912 )));
4913 }
4914
4915 let api_response: ApiResponse<crate::model::LegPricesResponse> = response
4916 .json()
4917 .await
4918 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
4919
4920 if let Some(error) = api_response.error {
4921 return Err(HttpError::RequestFailed(format!(
4922 "API error: {} - {}",
4923 error.code, error.message
4924 )));
4925 }
4926
4927 api_response
4928 .result
4929 .ok_or_else(|| HttpError::InvalidResponse("No leg prices in response".to_string()))
4930 }
4931
4932 pub async fn create_block_rfq(
4951 &self,
4952 legs: &[crate::model::response::BlockRfqLeg],
4953 hedge: Option<&crate::model::response::BlockRfqHedge>,
4954 label: Option<&str>,
4955 makers: Option<&[&str]>,
4956 non_anonymous: Option<bool>,
4957 trade_allocations: Option<&[crate::model::response::BlockRfqTradeAllocation]>,
4958 ) -> Result<crate::model::response::BlockRfq, HttpError> {
4959 let legs_json = serde_json::to_string(legs)
4960 .map_err(|e| HttpError::InvalidResponse(format!("Failed to serialize legs: {}", e)))?;
4961
4962 let mut query_params = vec![format!("legs={}", urlencoding::encode(&legs_json))];
4963
4964 if let Some(h) = hedge {
4965 let hedge_json = serde_json::to_string(h).map_err(|e| {
4966 HttpError::InvalidResponse(format!("Failed to serialize hedge: {}", e))
4967 })?;
4968 query_params.push(format!("hedge={}", urlencoding::encode(&hedge_json)));
4969 }
4970
4971 if let Some(l) = label {
4972 query_params.push(format!("label={}", urlencoding::encode(l)));
4973 }
4974
4975 if let Some(m) = makers {
4976 let makers_json = serde_json::to_string(m).map_err(|e| {
4977 HttpError::InvalidResponse(format!("Failed to serialize makers: {}", e))
4978 })?;
4979 query_params.push(format!("makers={}", urlencoding::encode(&makers_json)));
4980 }
4981
4982 if let Some(na) = non_anonymous {
4983 query_params.push(format!("non_anonymous={}", na));
4984 }
4985
4986 if let Some(ta) = trade_allocations {
4987 let ta_json = serde_json::to_string(ta).map_err(|e| {
4988 HttpError::InvalidResponse(format!("Failed to serialize trade_allocations: {}", e))
4989 })?;
4990 query_params.push(format!(
4991 "trade_allocations={}",
4992 urlencoding::encode(&ta_json)
4993 ));
4994 }
4995
4996 let url = format!(
4997 "{}{}?{}",
4998 self.base_url(),
4999 crate::constants::endpoints::CREATE_BLOCK_RFQ,
5000 query_params.join("&")
5001 );
5002
5003 let response = self.make_authenticated_request(&url).await?;
5004
5005 if !response.status().is_success() {
5006 let error_text = response
5007 .text()
5008 .await
5009 .unwrap_or_else(|_| "Unknown error".to_string());
5010 return Err(HttpError::RequestFailed(format!(
5011 "Create Block RFQ failed: {}",
5012 error_text
5013 )));
5014 }
5015
5016 let api_response: ApiResponse<crate::model::response::BlockRfq> = response
5017 .json()
5018 .await
5019 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
5020
5021 if let Some(error) = api_response.error {
5022 return Err(HttpError::RequestFailed(format!(
5023 "API error: {} - {}",
5024 error.code, error.message
5025 )));
5026 }
5027
5028 api_response
5029 .result
5030 .ok_or_else(|| HttpError::InvalidResponse("No Block RFQ in response".to_string()))
5031 }
5032
5033 pub async fn cancel_block_rfq(
5043 &self,
5044 block_rfq_id: i64,
5045 ) -> Result<crate::model::response::BlockRfq, HttpError> {
5046 let query = format!("?block_rfq_id={}", block_rfq_id);
5047 self.private_get(crate::constants::endpoints::CANCEL_BLOCK_RFQ, &query)
5048 .await
5049 }
5050
5051 #[allow(clippy::too_many_arguments)]
5067 pub async fn accept_block_rfq(
5068 &self,
5069 block_rfq_id: i64,
5070 legs: &[crate::model::response::BlockRfqLeg],
5071 price: f64,
5072 direction: crate::model::types::Direction,
5073 amount: f64,
5074 time_in_force: Option<crate::model::response::BlockRfqTimeInForce>,
5075 hedge: Option<&crate::model::response::BlockRfqHedge>,
5076 ) -> Result<crate::model::response::AcceptBlockRfqResponse, HttpError> {
5077 let legs_json = serde_json::to_string(legs)
5078 .map_err(|e| HttpError::InvalidResponse(format!("Failed to serialize legs: {}", e)))?;
5079
5080 let direction_str = match direction {
5081 crate::model::types::Direction::Buy => "buy",
5082 crate::model::types::Direction::Sell => "sell",
5083 crate::model::types::Direction::Unknown => "buy",
5084 };
5085
5086 let mut query_params = vec![
5087 format!("block_rfq_id={}", block_rfq_id),
5088 format!("legs={}", urlencoding::encode(&legs_json)),
5089 format!("price={}", price),
5090 format!("direction={}", direction_str),
5091 format!("amount={}", amount),
5092 ];
5093
5094 if let Some(tif) = time_in_force {
5095 let tif_str = match tif {
5096 crate::model::response::BlockRfqTimeInForce::FillOrKill => "fill_or_kill",
5097 crate::model::response::BlockRfqTimeInForce::GoodTilCancelled => {
5098 "good_til_cancelled"
5099 }
5100 };
5101 query_params.push(format!("time_in_force={}", tif_str));
5102 }
5103
5104 if let Some(h) = hedge {
5105 let hedge_json = serde_json::to_string(h).map_err(|e| {
5106 HttpError::InvalidResponse(format!("Failed to serialize hedge: {}", e))
5107 })?;
5108 query_params.push(format!("hedge={}", urlencoding::encode(&hedge_json)));
5109 }
5110
5111 let url = format!(
5112 "{}{}?{}",
5113 self.base_url(),
5114 crate::constants::endpoints::ACCEPT_BLOCK_RFQ,
5115 query_params.join("&")
5116 );
5117
5118 let response = self.make_authenticated_request(&url).await?;
5119
5120 if !response.status().is_success() {
5121 let error_text = response
5122 .text()
5123 .await
5124 .unwrap_or_else(|_| "Unknown error".to_string());
5125 return Err(HttpError::RequestFailed(format!(
5126 "Accept Block RFQ failed: {}",
5127 error_text
5128 )));
5129 }
5130
5131 let api_response: ApiResponse<crate::model::response::AcceptBlockRfqResponse> = response
5132 .json()
5133 .await
5134 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
5135
5136 if let Some(error) = api_response.error {
5137 return Err(HttpError::RequestFailed(format!(
5138 "API error: {} - {}",
5139 error.code, error.message
5140 )));
5141 }
5142
5143 api_response
5144 .result
5145 .ok_or_else(|| HttpError::InvalidResponse("No accept Block RFQ response".to_string()))
5146 }
5147
5148 pub async fn get_block_rfqs(
5163 &self,
5164 count: Option<u32>,
5165 state: Option<crate::model::response::BlockRfqState>,
5166 role: Option<crate::model::response::BlockRfqRole>,
5167 continuation: Option<&str>,
5168 block_rfq_id: Option<i64>,
5169 currency: Option<&str>,
5170 ) -> Result<crate::model::response::BlockRfqsResponse, HttpError> {
5171 let mut query_params: Vec<String> = Vec::new();
5172
5173 if let Some(c) = count {
5174 query_params.push(format!("count={}", c));
5175 }
5176
5177 if let Some(s) = state {
5178 let state_str = match s {
5179 crate::model::response::BlockRfqState::Open => "open",
5180 crate::model::response::BlockRfqState::Filled => "filled",
5181 crate::model::response::BlockRfqState::Traded => "traded",
5182 crate::model::response::BlockRfqState::Cancelled => "cancelled",
5183 crate::model::response::BlockRfqState::Expired => "expired",
5184 crate::model::response::BlockRfqState::Closed => "closed",
5185 crate::model::response::BlockRfqState::Created => "created",
5186 };
5187 query_params.push(format!("state={}", state_str));
5188 }
5189
5190 if let Some(r) = role {
5191 let role_str = match r {
5192 crate::model::response::BlockRfqRole::Taker => "taker",
5193 crate::model::response::BlockRfqRole::Maker => "maker",
5194 crate::model::response::BlockRfqRole::Any => "any",
5195 };
5196 query_params.push(format!("role={}", role_str));
5197 }
5198
5199 if let Some(cont) = continuation {
5200 query_params.push(format!("continuation={}", urlencoding::encode(cont)));
5201 }
5202
5203 if let Some(id) = block_rfq_id {
5204 query_params.push(format!("block_rfq_id={}", id));
5205 }
5206
5207 if let Some(curr) = currency {
5208 query_params.push(format!("currency={}", curr));
5209 }
5210
5211 let url = if query_params.is_empty() {
5212 format!(
5213 "{}{}",
5214 self.base_url(),
5215 crate::constants::endpoints::GET_BLOCK_RFQS
5216 )
5217 } else {
5218 format!(
5219 "{}{}?{}",
5220 self.base_url(),
5221 crate::constants::endpoints::GET_BLOCK_RFQS,
5222 query_params.join("&")
5223 )
5224 };
5225
5226 let response = self.make_authenticated_request(&url).await?;
5227
5228 if !response.status().is_success() {
5229 let error_text = response
5230 .text()
5231 .await
5232 .unwrap_or_else(|_| "Unknown error".to_string());
5233 return Err(HttpError::RequestFailed(format!(
5234 "Get Block RFQs failed: {}",
5235 error_text
5236 )));
5237 }
5238
5239 let api_response: ApiResponse<crate::model::response::BlockRfqsResponse> = response
5240 .json()
5241 .await
5242 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
5243
5244 if let Some(error) = api_response.error {
5245 return Err(HttpError::RequestFailed(format!(
5246 "API error: {} - {}",
5247 error.code, error.message
5248 )));
5249 }
5250
5251 api_response
5252 .result
5253 .ok_or_else(|| HttpError::InvalidResponse("No Block RFQs in response".to_string()))
5254 }
5255
5256 pub async fn get_block_rfq_quotes(
5268 &self,
5269 block_rfq_id: Option<i64>,
5270 label: Option<&str>,
5271 block_rfq_quote_id: Option<i64>,
5272 ) -> Result<Vec<crate::model::response::BlockRfqQuote>, HttpError> {
5273 let mut query_params: Vec<String> = Vec::new();
5274
5275 if let Some(id) = block_rfq_id {
5276 query_params.push(format!("block_rfq_id={}", id));
5277 }
5278
5279 if let Some(l) = label {
5280 query_params.push(format!("label={}", urlencoding::encode(l)));
5281 }
5282
5283 if let Some(qid) = block_rfq_quote_id {
5284 query_params.push(format!("block_rfq_quote_id={}", qid));
5285 }
5286
5287 let url = if query_params.is_empty() {
5288 format!(
5289 "{}{}",
5290 self.base_url(),
5291 crate::constants::endpoints::GET_BLOCK_RFQ_QUOTES
5292 )
5293 } else {
5294 format!(
5295 "{}{}?{}",
5296 self.base_url(),
5297 crate::constants::endpoints::GET_BLOCK_RFQ_QUOTES,
5298 query_params.join("&")
5299 )
5300 };
5301
5302 let response = self.make_authenticated_request(&url).await?;
5303
5304 if !response.status().is_success() {
5305 let error_text = response
5306 .text()
5307 .await
5308 .unwrap_or_else(|_| "Unknown error".to_string());
5309 return Err(HttpError::RequestFailed(format!(
5310 "Get Block RFQ quotes failed: {}",
5311 error_text
5312 )));
5313 }
5314
5315 let api_response: ApiResponse<Vec<crate::model::response::BlockRfqQuote>> = response
5316 .json()
5317 .await
5318 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
5319
5320 if let Some(error) = api_response.error {
5321 return Err(HttpError::RequestFailed(format!(
5322 "API error: {} - {}",
5323 error.code, error.message
5324 )));
5325 }
5326
5327 api_response.result.ok_or_else(|| {
5328 HttpError::InvalidResponse("No Block RFQ quotes in response".to_string())
5329 })
5330 }
5331
5332 #[allow(clippy::too_many_arguments)]
5349 pub async fn add_block_rfq_quote(
5350 &self,
5351 block_rfq_id: i64,
5352 amount: f64,
5353 direction: crate::model::types::Direction,
5354 legs: &[crate::model::response::BlockRfqLeg],
5355 label: Option<&str>,
5356 hedge: Option<&crate::model::response::BlockRfqHedge>,
5357 execution_instruction: Option<crate::model::response::ExecutionInstruction>,
5358 expires_at: Option<i64>,
5359 ) -> Result<crate::model::response::BlockRfqQuote, HttpError> {
5360 let legs_json = serde_json::to_string(legs)
5361 .map_err(|e| HttpError::InvalidResponse(format!("Failed to serialize legs: {}", e)))?;
5362
5363 let direction_str = match direction {
5364 crate::model::types::Direction::Buy => "buy",
5365 crate::model::types::Direction::Sell => "sell",
5366 crate::model::types::Direction::Unknown => "buy",
5367 };
5368
5369 let mut query_params = vec![
5370 format!("block_rfq_id={}", block_rfq_id),
5371 format!("amount={}", amount),
5372 format!("direction={}", direction_str),
5373 format!("legs={}", urlencoding::encode(&legs_json)),
5374 ];
5375
5376 if let Some(l) = label {
5377 query_params.push(format!("label={}", urlencoding::encode(l)));
5378 }
5379
5380 if let Some(h) = hedge {
5381 let hedge_json = serde_json::to_string(h).map_err(|e| {
5382 HttpError::InvalidResponse(format!("Failed to serialize hedge: {}", e))
5383 })?;
5384 query_params.push(format!("hedge={}", urlencoding::encode(&hedge_json)));
5385 }
5386
5387 if let Some(ei) = execution_instruction {
5388 let ei_str = match ei {
5389 crate::model::response::ExecutionInstruction::AllOrNone => "all_or_none",
5390 crate::model::response::ExecutionInstruction::AnyPartOf => "any_part_of",
5391 };
5392 query_params.push(format!("execution_instruction={}", ei_str));
5393 }
5394
5395 if let Some(exp) = expires_at {
5396 query_params.push(format!("expires_at={}", exp));
5397 }
5398
5399 let url = format!(
5400 "{}{}?{}",
5401 self.base_url(),
5402 crate::constants::endpoints::ADD_BLOCK_RFQ_QUOTE,
5403 query_params.join("&")
5404 );
5405
5406 let response = self.make_authenticated_request(&url).await?;
5407
5408 if !response.status().is_success() {
5409 let error_text = response
5410 .text()
5411 .await
5412 .unwrap_or_else(|_| "Unknown error".to_string());
5413 return Err(HttpError::RequestFailed(format!(
5414 "Add Block RFQ quote failed: {}",
5415 error_text
5416 )));
5417 }
5418
5419 let api_response: ApiResponse<crate::model::response::BlockRfqQuote> = response
5420 .json()
5421 .await
5422 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
5423
5424 if let Some(error) = api_response.error {
5425 return Err(HttpError::RequestFailed(format!(
5426 "API error: {} - {}",
5427 error.code, error.message
5428 )));
5429 }
5430
5431 api_response
5432 .result
5433 .ok_or_else(|| HttpError::InvalidResponse("No Block RFQ quote in response".to_string()))
5434 }
5435
5436 #[allow(clippy::too_many_arguments)]
5453 pub async fn edit_block_rfq_quote(
5454 &self,
5455 block_rfq_quote_id: Option<i64>,
5456 block_rfq_id: Option<i64>,
5457 label: Option<&str>,
5458 amount: Option<f64>,
5459 legs: Option<&[crate::model::response::BlockRfqLeg]>,
5460 hedge: Option<&crate::model::response::BlockRfqHedge>,
5461 execution_instruction: Option<crate::model::response::ExecutionInstruction>,
5462 expires_at: Option<i64>,
5463 ) -> Result<crate::model::response::BlockRfqQuote, HttpError> {
5464 let mut query_params: Vec<String> = Vec::new();
5465
5466 if let Some(qid) = block_rfq_quote_id {
5467 query_params.push(format!("block_rfq_quote_id={}", qid));
5468 }
5469
5470 if let Some(id) = block_rfq_id {
5471 query_params.push(format!("block_rfq_id={}", id));
5472 }
5473
5474 if let Some(l) = label {
5475 query_params.push(format!("label={}", urlencoding::encode(l)));
5476 }
5477
5478 if let Some(a) = amount {
5479 query_params.push(format!("amount={}", a));
5480 }
5481
5482 if let Some(l) = legs {
5483 let legs_json = serde_json::to_string(l).map_err(|e| {
5484 HttpError::InvalidResponse(format!("Failed to serialize legs: {}", e))
5485 })?;
5486 query_params.push(format!("legs={}", urlencoding::encode(&legs_json)));
5487 }
5488
5489 if let Some(h) = hedge {
5490 let hedge_json = serde_json::to_string(h).map_err(|e| {
5491 HttpError::InvalidResponse(format!("Failed to serialize hedge: {}", e))
5492 })?;
5493 query_params.push(format!("hedge={}", urlencoding::encode(&hedge_json)));
5494 }
5495
5496 if let Some(ei) = execution_instruction {
5497 let ei_str = match ei {
5498 crate::model::response::ExecutionInstruction::AllOrNone => "all_or_none",
5499 crate::model::response::ExecutionInstruction::AnyPartOf => "any_part_of",
5500 };
5501 query_params.push(format!("execution_instruction={}", ei_str));
5502 }
5503
5504 if let Some(exp) = expires_at {
5505 query_params.push(format!("expires_at={}", exp));
5506 }
5507
5508 let url = format!(
5509 "{}{}?{}",
5510 self.base_url(),
5511 crate::constants::endpoints::EDIT_BLOCK_RFQ_QUOTE,
5512 query_params.join("&")
5513 );
5514
5515 let response = self.make_authenticated_request(&url).await?;
5516
5517 if !response.status().is_success() {
5518 let error_text = response
5519 .text()
5520 .await
5521 .unwrap_or_else(|_| "Unknown error".to_string());
5522 return Err(HttpError::RequestFailed(format!(
5523 "Edit Block RFQ quote failed: {}",
5524 error_text
5525 )));
5526 }
5527
5528 let api_response: ApiResponse<crate::model::response::BlockRfqQuote> = response
5529 .json()
5530 .await
5531 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
5532
5533 if let Some(error) = api_response.error {
5534 return Err(HttpError::RequestFailed(format!(
5535 "API error: {} - {}",
5536 error.code, error.message
5537 )));
5538 }
5539
5540 api_response
5541 .result
5542 .ok_or_else(|| HttpError::InvalidResponse("No Block RFQ quote in response".to_string()))
5543 }
5544
5545 pub async fn cancel_block_rfq_quote(
5557 &self,
5558 block_rfq_quote_id: Option<i64>,
5559 block_rfq_id: Option<i64>,
5560 label: Option<&str>,
5561 ) -> Result<crate::model::response::BlockRfqQuote, HttpError> {
5562 let mut query_params: Vec<String> = Vec::new();
5563
5564 if let Some(qid) = block_rfq_quote_id {
5565 query_params.push(format!("block_rfq_quote_id={}", qid));
5566 }
5567
5568 if let Some(id) = block_rfq_id {
5569 query_params.push(format!("block_rfq_id={}", id));
5570 }
5571
5572 if let Some(l) = label {
5573 query_params.push(format!("label={}", urlencoding::encode(l)));
5574 }
5575
5576 let url = format!(
5577 "{}{}?{}",
5578 self.base_url(),
5579 crate::constants::endpoints::CANCEL_BLOCK_RFQ_QUOTE,
5580 query_params.join("&")
5581 );
5582
5583 let response = self.make_authenticated_request(&url).await?;
5584
5585 if !response.status().is_success() {
5586 let error_text = response
5587 .text()
5588 .await
5589 .unwrap_or_else(|_| "Unknown error".to_string());
5590 return Err(HttpError::RequestFailed(format!(
5591 "Cancel Block RFQ quote failed: {}",
5592 error_text
5593 )));
5594 }
5595
5596 let api_response: ApiResponse<crate::model::response::BlockRfqQuote> = response
5597 .json()
5598 .await
5599 .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
5600
5601 if let Some(error) = api_response.error {
5602 return Err(HttpError::RequestFailed(format!(
5603 "API error: {} - {}",
5604 error.code, error.message
5605 )));
5606 }
5607
5608 api_response
5609 .result
5610 .ok_or_else(|| HttpError::InvalidResponse("No Block RFQ quote in response".to_string()))
5611 }
5612
5613 pub async fn cancel_all_block_rfq_quotes(
5619 &self,
5620 ) -> Result<Vec<crate::model::response::BlockRfqQuote>, HttpError> {
5621 self.private_get(crate::constants::endpoints::CANCEL_ALL_BLOCK_RFQ_QUOTES, "")
5622 .await
5623 }
5624}