1use crate::{
2 Error, Timestamp,
3 crypto::{SensitiveString, Signer},
4 enums::Category,
5 http::{
6 APIErrorResponse, APIKeyInformation, AccountCoinBalance, AccountInfo,
7 AmendOrderBatchRequest, AmendOrderBatchResult, AmendOrderRequest, AmendOrderResponse,
8 AssetInfoResult, BorrowHistoryEntry, CancelAllOrdersRequest, CancelAllOrdersResponse,
9 CancelOrderBatchRequest, CancelOrderBatchResult, CancelOrderRequest, CancelOrderResponse,
10 CancelWithdrawalRequest, CancelWithdrawalResult, ClosedPnl, CoinGreeks, CoinInfoResult,
11 CollateralInfoEntry, CursorPagination, DeliveryPrice, DeliveryRecord, DepositAddress,
12 DepositAllowedCoinInfoResult, DepositRecords, EmptyResult, ExchangeOrderRecords,
13 ExecutionEntry, FeeRateEntry, FundingRateHistory, GetAccountCoinBalanceParams,
14 GetAssetInfoParams, GetBorrowHistoryParams, GetClosedPnlParams, GetCoinGreeksParams,
15 GetCoinInfoParams, GetCollateralInfoParams, GetDeliveryPriceParams,
16 GetDeliveryRecordParams, GetDepositAddressParams, GetDepositAllowedCoinInfoParams,
17 GetDepositRecordsParams, GetExchangeOrderRecordParams, GetExecutionListParams,
18 GetFeeRateParams, GetFundingRateHistoryParams, GetHistoricalVolatilityParams,
19 GetInstrumentsInfoParams, GetInsuranceParams, GetInternalTransferRecordsParams,
20 GetKLinesParams, GetLeverageTokenInfoParams, GetLeverageTokenMarketParams,
21 GetLeverageTokenOrderRecordsParams, GetOpenClosedOrdersParams, GetOpenInterestParams,
22 GetOrderHistoryParams, GetOrderbookParams, GetPositionInfoParams, GetRiskLimitParams,
23 GetSettlementRecordParams, GetSpotBorrowCheckParams, GetSubDepositAddressParams,
24 GetSubDepositRecordsParams, GetTickersParams, GetTradesParams, GetTransactionLogParams,
25 GetTransferableCoinsParams, GetUniversalTransferRecordsParams, GetWalletBalanceParams,
26 GetWithdrawableAmountParams, GetWithdrawalRecordsParams, Headers,
27 HistoricalVolatilityEntry, InstrumentsInfo, Insurance, InternalTransferEntry,
28 InternalTransferRequest, KLine, LeverageTokenInfo, LeverageTokenMarket,
29 LeverageTokenOrderRecord, List, OpenInterest, Order, Orderbook, PlaceOrderBatchRequest,
30 PlaceOrderBatchResult, PlaceOrderRequest, PlaceOrderResponse, Position,
31 PurchaseLeverageTokenRequest, PurchaseLeverageTokenResult, RedeemLeverageTokenRequest,
32 RedeemLeverageTokenResult, Resp, Response, RiskLimit, SaveTransferSubMemberRequest,
33 ServerTime, SetAutoAddMarginRequest, SetLeverageRequest, SetMarginModeRequest,
34 SetMarginModeResponse, SetRiskLimitRequest, SetRiskLimitResponse,
35 SetSpotMarginLeverageRequest, SetTradingStopRequest, SettlementRecord, SpotBorrowCheck,
36 SwitchCrossIsolatedMarginRequest, SwitchPositionModeRequest, SwitchSpotMarginModeRequest,
37 SwitchSpotMarginModeResult, Ticker, Trade, TransactionLog, TransferResult,
38 TransferableSubMembers, UniversalTransferEntry, UniversalTransferRequest,
39 UpgradeToUtaResult, WalletBalance, WithdrawRecords, WithdrawRequest, WithdrawResult,
40 WithdrawableAmountResult,
41 },
42 serde::{deserialize_json, serialize_json, serialize_query},
43 url::*,
44};
45use reqwest::{
46 self, Method, RequestBuilder, StatusCode,
47 header::{CONTENT_TYPE, HeaderMap, HeaderValue},
48};
49use std::time::Duration;
50
51use super::rate_limiter::{RateLimitKey, RateLimiter, RateLimiterConfig, Rejection};
52
53pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
55pub const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_secs(5);
57pub const DEFAULT_RECV_WINDOW: Timestamp = 5000;
59
60const MAX_ERROR_BODY_LEN: usize = 1024;
62
63pub struct Config {
64 pub base_url: String,
65 pub api_key: Option<SensitiveString>,
66 pub api_secret: Option<SensitiveString>,
67 pub recv_window: Timestamp,
69 pub referer: Option<String>,
71 pub rate_limiter: Option<RateLimiterConfig>,
75 pub timeout: Option<Duration>,
79 pub connect_timeout: Option<Duration>,
81}
82
83impl Config {
84 pub fn new(base_url: impl Into<String>) -> Self {
86 Self {
87 base_url: base_url.into(),
88 api_key: None,
89 api_secret: None,
90 recv_window: DEFAULT_RECV_WINDOW,
91 referer: None,
92 rate_limiter: None,
93 timeout: Some(DEFAULT_TIMEOUT),
94 connect_timeout: Some(DEFAULT_CONNECT_TIMEOUT),
95 }
96 }
97
98 pub fn credentials(
100 mut self,
101 api_key: impl Into<SensitiveString>,
102 api_secret: impl Into<SensitiveString>,
103 ) -> Self {
104 self.api_key = Some(api_key.into());
105 self.api_secret = Some(api_secret.into());
106 self
107 }
108
109 pub fn recv_window(mut self, recv_window: Timestamp) -> Self {
111 self.recv_window = recv_window;
112 self
113 }
114
115 pub fn referer(mut self, referer: impl Into<String>) -> Self {
116 self.referer = Some(referer.into());
117 self
118 }
119
120 pub fn rate_limiter(mut self, rate_limiter: RateLimiterConfig) -> Self {
121 self.rate_limiter = Some(rate_limiter);
122 self
123 }
124
125 pub fn timeout(mut self, timeout: Option<Duration>) -> Self {
126 self.timeout = timeout;
127 self
128 }
129
130 pub fn connect_timeout(mut self, connect_timeout: Option<Duration>) -> Self {
131 self.connect_timeout = connect_timeout;
132 self
133 }
134}
135
136#[derive(Debug)]
138pub struct Client {
139 base_url: String,
140 headers: HeaderMap,
141 client: reqwest::Client,
142 signer: Option<Signer>,
143 rate_limiter: Option<RateLimiter>,
144}
145
146impl Client {
147 pub fn new(cfg: Config) -> Result<Self, Error> {
148 let mut headers = HeaderMap::new();
149
150 if let Some(api_key) = cfg.api_key.as_ref() {
151 let api_key = api_key.expose().parse()?;
152 headers.append(HEADER_X_BAPI_API_KEY, api_key);
153 }
154
155 let recv_window = cfg.recv_window.to_string().parse()?;
156 headers.append(HEADER_X_BAPI_RECV_WINDOW, recv_window);
157
158 if let Some(referer) = cfg.referer {
159 let referer = referer.parse()?;
160 headers.append(HEADER_X_REFERER, referer);
161 }
162
163 let signer = cfg
164 .api_secret
165 .map(|api_secret| -> Result<Signer, Error> {
166 let api_key = cfg
167 .api_key
168 .ok_or_else(|| Error::from("api_key is required when api_secret is set"))?;
169 Ok(Signer::new(api_key, api_secret, cfg.recv_window, None))
170 })
171 .transpose()?;
172
173 let rate_limiter = cfg.rate_limiter.map(RateLimiter::new);
174
175 let mut default_headers = HeaderMap::new();
178 default_headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
179 let mut builder = reqwest::Client::builder().default_headers(default_headers);
180 if let Some(timeout) = cfg.timeout {
181 builder = builder.timeout(timeout);
182 }
183 if let Some(connect_timeout) = cfg.connect_timeout {
184 builder = builder.connect_timeout(connect_timeout);
185 }
186
187 Ok(Self {
188 base_url: cfg.base_url,
189 headers,
190 client: builder.build()?,
191 signer,
192 rate_limiter,
193 })
194 }
195
196 fn get_signed_headers(&self, s: &str) -> Result<HeaderMap, Error> {
197 let signer = self.signer.as_ref().ok_or(Error::MissingCredentials)?;
198 let mut headers = self.headers.clone();
199
200 let (signature, timestamp) = signer.sign(s);
201 headers.append(HEADER_X_BAPI_SIGN, signature.parse()?);
202 headers.append(HEADER_X_BAPI_TIMESTAMP, timestamp.parse()?);
203
204 Ok(headers)
205 }
206}
207
208impl Client {
210 #[tracing::instrument(skip(self), err)]
211 pub async fn get_server_time(&self) -> Result<Response<ServerTime>, Error> {
212 let url = format!("{}{}", self.base_url, Path::MarketServerTime);
213
214 let request = self.client.request(Method::GET, url);
215
216 let response = self.send(Path::MarketServerTime, request).await?;
217 Ok(response)
218 }
219
220 #[tracing::instrument(skip(self), err)]
221 pub async fn get_kline(&self, params: &GetKLinesParams) -> Result<Response<KLine>, Error> {
222 let url = format!("{}{}", self.base_url, Path::MarketKline);
223
224 let request = self.client.request(Method::GET, url).query(params);
225
226 let response = self.send(Path::MarketKline, request).await?;
227 Ok(response)
228 }
229
230 #[tracing::instrument(skip(self), err)]
235 pub async fn get_mark_price_kline(
236 &self,
237 params: &GetKLinesParams,
238 ) -> Result<Response<KLine>, Error> {
239 let url = format!("{}{}", self.base_url, Path::MarketMarkPriceKline);
240
241 let request = self.client.request(Method::GET, url).query(params);
242
243 let response = self.send(Path::MarketMarkPriceKline, request).await?;
244 Ok(response)
245 }
246
247 #[tracing::instrument(skip(self), err)]
252 pub async fn get_index_price_kline(
253 &self,
254 params: &GetKLinesParams,
255 ) -> Result<Response<KLine>, Error> {
256 let url = format!("{}{}", self.base_url, Path::MarketIndexPriceKline);
257
258 let request = self.client.request(Method::GET, url).query(params);
259
260 let response = self.send(Path::MarketIndexPriceKline, request).await?;
261 Ok(response)
262 }
263
264 #[tracing::instrument(skip(self), err)]
269 pub async fn get_premium_index_price_kline(
270 &self,
271 params: &GetKLinesParams,
272 ) -> Result<Response<KLine>, Error> {
273 let url = format!("{}{}", self.base_url, Path::MarketPremiumIndexPriceKline);
274
275 let request = self.client.request(Method::GET, url).query(params);
276
277 let response = self
278 .send(Path::MarketPremiumIndexPriceKline, request)
279 .await?;
280 Ok(response)
281 }
282
283 #[tracing::instrument(skip(self), err)]
287 pub async fn get_tickers(&self, params: &GetTickersParams) -> Result<Response<Ticker>, Error> {
288 let url = format!("{}{}", self.base_url, Path::MarketTickers);
289
290 let request = self.client.request(Method::GET, url).query(params);
291
292 let response = self.send(Path::MarketTickers, request).await?;
293 Ok(response)
294 }
295
296 #[tracing::instrument(skip(self), err)]
301 pub async fn get_orderbook(
302 &self,
303 params: &GetOrderbookParams,
304 ) -> Result<Response<Orderbook>, Error> {
305 let url = format!("{}{}", self.base_url, Path::MarketOrderbook);
306
307 let request = self.client.request(Method::GET, url).query(params);
308
309 let response = self.send(Path::MarketOrderbook, request).await?;
310 Ok(response)
311 }
312
313 #[tracing::instrument(skip(self), err)]
314 pub async fn get_instruments_info(
315 &self,
316 params: &GetInstrumentsInfoParams,
317 ) -> Result<Response<InstrumentsInfo>, Error> {
318 let url = format!("{}{}", self.base_url, Path::MarketInstrumentsInfo);
319
320 let request = self.client.request(Method::GET, url).query(params);
321
322 let response = self.send(Path::MarketInstrumentsInfo, request).await?;
323 Ok(response)
324 }
325
326 #[tracing::instrument(skip(self), err)]
327 pub async fn get_public_recent_trading_history(
328 &self,
329 params: &GetTradesParams,
330 ) -> Result<Response<Trade>, Error> {
331 let url = format!("{}{}", self.base_url, Path::MarketRecentTrade);
332
333 let request = self.client.request(Method::GET, url).query(params);
334
335 let response = self.send(Path::MarketRecentTrade, request).await?;
336 Ok(response)
337 }
338
339 #[tracing::instrument(skip(self), err)]
344 pub async fn get_funding_rate_history(
345 &self,
346 params: &GetFundingRateHistoryParams,
347 ) -> Result<Response<FundingRateHistory>, Error> {
348 let url = format!("{}{}", self.base_url, Path::MarketFundingHistory);
349
350 let request = self.client.request(Method::GET, url).query(params);
351
352 let response = self.send(Path::MarketFundingHistory, request).await?;
353 Ok(response)
354 }
355
356 #[tracing::instrument(skip(self), err)]
361 pub async fn get_open_interest(
362 &self,
363 params: &GetOpenInterestParams,
364 ) -> Result<Response<OpenInterest>, Error> {
365 let url = format!("{}{}", self.base_url, Path::MarketOpenInterest);
366
367 let request = self.client.request(Method::GET, url).query(params);
368
369 let response = self.send(Path::MarketOpenInterest, request).await?;
370 Ok(response)
371 }
372
373 #[tracing::instrument(skip(self), err)]
381 pub async fn get_historical_volatility(
382 &self,
383 params: &GetHistoricalVolatilityParams,
384 ) -> Result<Response<Vec<HistoricalVolatilityEntry>>, Error> {
385 let url = format!("{}{}", self.base_url, Path::MarketHistoricalVolatility);
386
387 let request = self.client.request(Method::GET, url).query(params);
388
389 let response = self.send(Path::MarketHistoricalVolatility, request).await?;
390 Ok(response)
391 }
392
393 #[tracing::instrument(skip(self), err)]
396 pub async fn get_insurance(
397 &self,
398 params: &GetInsuranceParams,
399 ) -> Result<Response<Insurance>, Error> {
400 let url = format!("{}{}", self.base_url, Path::MarketInsurance);
401
402 let request = self.client.request(Method::GET, url).query(params);
403
404 let response = self.send(Path::MarketInsurance, request).await?;
405 Ok(response)
406 }
407
408 #[tracing::instrument(skip(self), err)]
413 pub async fn get_risk_limit(
414 &self,
415 params: &GetRiskLimitParams,
416 ) -> Result<Response<RiskLimit>, Error> {
417 let url = format!("{}{}", self.base_url, Path::MarketRiskLimit);
418
419 let request = self.client.request(Method::GET, url).query(params);
420
421 let response = self.send(Path::MarketRiskLimit, request).await?;
422 Ok(response)
423 }
424
425 #[tracing::instrument(skip(self), err)]
430 pub async fn get_delivery_price(
431 &self,
432 params: &GetDeliveryPriceParams,
433 ) -> Result<Response<DeliveryPrice>, Error> {
434 let url = format!("{}{}", self.base_url, Path::MarketDeliveryPrice);
435
436 let request = self.client.request(Method::GET, url).query(params);
437
438 let response = self.send(Path::MarketDeliveryPrice, request).await?;
439 Ok(response)
440 }
441}
442
443impl Client {
445 #[tracing::instrument(skip(self), err)]
492 pub async fn place_order(
493 &self,
494 request: &PlaceOrderRequest,
495 ) -> Result<Response<PlaceOrderResponse>, Error> {
496 let category = request.category;
497 let url = format!("{}{}", self.base_url, Path::TradeOrderCreate);
498 let json = serialize_json(request)?;
499 let headers = self.get_signed_headers(&json)?;
500
501 let request = self
502 .client
503 .request(Method::POST, url)
504 .headers(headers)
505 .body(json);
506
507 let response = self
508 .send_weighted(Path::TradeOrderCreate, Some(category), 1, request)
509 .await?;
510 Ok(response)
511 }
512
513 #[tracing::instrument(skip(self), err)]
517 pub async fn amend_order(
518 &self,
519 request: &AmendOrderRequest,
520 ) -> Result<Response<AmendOrderResponse>, Error> {
521 let category = request.category;
522 let url = format!("{}{}", self.base_url, Path::TradeOrderAmend);
523 let json = serialize_json(request)?;
524 let headers = self.get_signed_headers(&json)?;
525
526 let request = self
527 .client
528 .request(Method::POST, url)
529 .headers(headers)
530 .body(json);
531
532 let response = self
533 .send_weighted(Path::TradeOrderAmend, Some(category), 1, request)
534 .await?;
535 Ok(response)
536 }
537
538 #[tracing::instrument(skip(self), err)]
544 pub async fn cancel_order(
545 &self,
546 request: &CancelOrderRequest,
547 ) -> Result<Response<CancelOrderResponse>, Error> {
548 let category = request.category;
549 let url = format!("{}{}", self.base_url, Path::TradeOrderCancel);
550 let json = serialize_json(request)?;
551 let headers = self.get_signed_headers(&json)?;
552
553 let request = self
554 .client
555 .request(Method::POST, url)
556 .headers(headers)
557 .body(json);
558
559 let response = self
560 .send_weighted(Path::TradeOrderCancel, Some(category), 1, request)
561 .await?;
562 Ok(response)
563 }
564
565 #[tracing::instrument(skip(self), err)]
579 pub async fn get_open_closed_orders(
580 &self,
581 params: &GetOpenClosedOrdersParams,
582 ) -> Result<Response<CursorPagination<Order>>, Error> {
583 let query = serialize_query(params)?;
584 let url = format!("{}{}?{query}", self.base_url, Path::TradeOrderRealtime);
585 let headers = self.get_signed_headers(&query)?;
586
587 let request = self.client.request(Method::GET, url).headers(headers);
588
589 let response = self.send(Path::TradeOrderRealtime, request).await?;
590 Ok(response)
591 }
592
593 #[tracing::instrument(skip(self), err)]
597 pub async fn get_open_closed_orders_all(
598 &self,
599 params: &GetOpenClosedOrdersParams,
600 ) -> Result<Vec<Order>, Error> {
601 collect_pages(|cursor| {
602 let p = match cursor {
603 Some(cursor) => params.clone().with_cursor(cursor),
604 None => params.clone(),
605 };
606 async move { self.get_open_closed_orders(&p).await.map(|r| r.result) }
607 })
608 .await
609 }
610
611 #[tracing::instrument(skip(self), err)]
614 pub async fn cancel_all_orders(
615 &self,
616 request: &CancelAllOrdersRequest,
617 ) -> Result<Response<CancelAllOrdersResponse>, Error> {
618 let category = request.category;
619 let url = format!("{}{}", self.base_url, Path::TradeOrderCancelAll);
620 let json = serialize_json(request)?;
621 let headers = self.get_signed_headers(&json)?;
622
623 let request = self
624 .client
625 .request(Method::POST, url)
626 .headers(headers)
627 .body(json);
628
629 let response = self
630 .send_weighted(Path::TradeOrderCancelAll, Some(category), 1, request)
631 .await?;
632 Ok(response)
633 }
634
635 #[tracing::instrument(skip(self), err)]
639 pub async fn get_order_history(
640 &self,
641 params: &GetOrderHistoryParams,
642 ) -> Result<Response<CursorPagination<Order>>, Error> {
643 let query = serialize_query(params)?;
644 let url = format!("{}{}?{query}", self.base_url, Path::TradeOrderHistory);
645 let headers = self.get_signed_headers(&query)?;
646
647 let request = self.client.request(Method::GET, url).headers(headers);
648
649 let response = self.send(Path::TradeOrderHistory, request).await?;
650 Ok(response)
651 }
652
653 #[tracing::instrument(skip(self), err)]
655 pub async fn get_order_history_all(
656 &self,
657 params: &GetOrderHistoryParams,
658 ) -> Result<Vec<Order>, Error> {
659 collect_pages(|cursor| {
660 let p = match cursor {
661 Some(cursor) => params.clone().with_cursor(cursor),
662 None => params.clone(),
663 };
664 async move { self.get_order_history(&p).await.map(|r| r.result) }
665 })
666 .await
667 }
668
669 #[tracing::instrument(skip(self), err)]
673 pub async fn place_orders_batch(
674 &self,
675 request: &PlaceOrderBatchRequest,
676 ) -> Result<Response<List<PlaceOrderBatchResult>>, Error> {
677 let category = request.category;
678 let cost = request.request.len() as u32;
679 let url = format!("{}{}", self.base_url, Path::TradeOrderCreateBatch);
680 let json = serialize_json(request)?;
681 let headers = self.get_signed_headers(&json)?;
682
683 let request = self
684 .client
685 .request(Method::POST, url)
686 .headers(headers)
687 .body(json);
688
689 let response = self
690 .send_weighted(Path::TradeOrderCreateBatch, Some(category), cost, request)
691 .await?;
692 Ok(response)
693 }
694
695 #[tracing::instrument(skip(self), err)]
699 pub async fn amend_orders_batch(
700 &self,
701 request: &AmendOrderBatchRequest,
702 ) -> Result<Response<List<AmendOrderBatchResult>>, Error> {
703 let category = request.category;
704 let cost = request.request.len() as u32;
705 let url = format!("{}{}", self.base_url, Path::TradeOrderAmendBatch);
706 let json = serialize_json(request)?;
707 let headers = self.get_signed_headers(&json)?;
708
709 let request = self
710 .client
711 .request(Method::POST, url)
712 .headers(headers)
713 .body(json);
714
715 let response = self
716 .send_weighted(Path::TradeOrderAmendBatch, Some(category), cost, request)
717 .await?;
718 Ok(response)
719 }
720
721 #[tracing::instrument(skip(self), err)]
725 pub async fn cancel_orders_batch(
726 &self,
727 request: &CancelOrderBatchRequest,
728 ) -> Result<Response<List<CancelOrderBatchResult>>, Error> {
729 let category = request.category;
730 let cost = request.request.len() as u32;
731 let url = format!("{}{}", self.base_url, Path::TradeOrderCancelBatch);
732 let json = serialize_json(request)?;
733 let headers = self.get_signed_headers(&json)?;
734
735 let request = self
736 .client
737 .request(Method::POST, url)
738 .headers(headers)
739 .body(json);
740
741 let response = self
742 .send_weighted(Path::TradeOrderCancelBatch, Some(category), cost, request)
743 .await?;
744 Ok(response)
745 }
746
747 #[tracing::instrument(skip(self), err)]
752 pub async fn get_spot_borrow_check(
753 &self,
754 params: &GetSpotBorrowCheckParams,
755 ) -> Result<Response<SpotBorrowCheck>, Error> {
756 let url = format!("{}{}", self.base_url, Path::TradeOrderSpotBorrowCheck);
757 let query = serialize_query(params)?;
758 let headers = self.get_signed_headers(&query)?;
759
760 let request = self
761 .client
762 .request(Method::GET, url)
763 .headers(headers)
764 .query(params);
765
766 let response = self.send(Path::TradeOrderSpotBorrowCheck, request).await?;
767 Ok(response)
768 }
769}
770
771impl Client {
773 #[tracing::instrument(skip(self), err)]
784 pub async fn get_position_info(
785 &self,
786 params: &GetPositionInfoParams,
787 ) -> Result<Response<CursorPagination<Position>>, Error> {
788 let query = serialize_query(params)?;
789 let url = format!("{}{}?{query}", self.base_url, Path::PositionList);
790 let headers = self.get_signed_headers(&query)?;
791
792 let request = self.client.request(Method::GET, url).headers(headers);
793
794 let response = self.send(Path::PositionList, request).await?;
795 Ok(response)
796 }
797
798 #[tracing::instrument(skip(self), err)]
802 pub async fn get_position_info_all(
803 &self,
804 params: &GetPositionInfoParams,
805 ) -> Result<Vec<Position>, Error> {
806 collect_pages(|cursor| {
807 let p = match cursor {
808 Some(cursor) => params.clone().with_cursor(cursor),
809 None => params.clone(),
810 };
811 async move { self.get_position_info(&p).await.map(|r| r.result) }
812 })
813 .await
814 }
815
816 #[tracing::instrument(skip(self), err)]
819 pub async fn set_leverage(
820 &self,
821 request: &SetLeverageRequest,
822 ) -> Result<Response<EmptyResult>, Error> {
823 let url = format!("{}{}", self.base_url, Path::PositionSetLeverage);
824 let json = serialize_json(request)?;
825 let headers = self.get_signed_headers(&json)?;
826
827 let request = self
828 .client
829 .request(Method::POST, url)
830 .headers(headers)
831 .body(json);
832
833 let response = self.send(Path::PositionSetLeverage, request).await?;
834 Ok(response)
835 }
836
837 #[tracing::instrument(skip(self), err)]
840 pub async fn set_trading_stop(
841 &self,
842 request: &SetTradingStopRequest,
843 ) -> Result<Response<EmptyResult>, Error> {
844 let url = format!("{}{}", self.base_url, Path::PositionTradingStop);
845 let json = serialize_json(request)?;
846 let headers = self.get_signed_headers(&json)?;
847
848 let request = self
849 .client
850 .request(Method::POST, url)
851 .headers(headers)
852 .body(json);
853
854 let response = self.send(Path::PositionTradingStop, request).await?;
855 Ok(response)
856 }
857
858 #[tracing::instrument(skip(self), err)]
861 pub async fn switch_cross_isolated_margin(
862 &self,
863 request: &SwitchCrossIsolatedMarginRequest,
864 ) -> Result<Response<EmptyResult>, Error> {
865 let url = format!("{}{}", self.base_url, Path::PositionSwitchIsolated);
866 let json = serialize_json(request)?;
867 let headers = self.get_signed_headers(&json)?;
868
869 let request = self
870 .client
871 .request(Method::POST, url)
872 .headers(headers)
873 .body(json);
874
875 let response = self.send(Path::PositionSwitchIsolated, request).await?;
876 Ok(response)
877 }
878
879 #[tracing::instrument(skip(self), err)]
882 pub async fn switch_position_mode(
883 &self,
884 request: &SwitchPositionModeRequest,
885 ) -> Result<Response<EmptyResult>, Error> {
886 let url = format!("{}{}", self.base_url, Path::PositionSwitchMode);
887 let json = serialize_json(request)?;
888 let headers = self.get_signed_headers(&json)?;
889
890 let request = self
891 .client
892 .request(Method::POST, url)
893 .headers(headers)
894 .body(json);
895
896 let response = self.send(Path::PositionSwitchMode, request).await?;
897 Ok(response)
898 }
899
900 #[tracing::instrument(skip(self), err)]
903 pub async fn set_auto_add_margin(
904 &self,
905 request: &SetAutoAddMarginRequest,
906 ) -> Result<Response<EmptyResult>, Error> {
907 let url = format!("{}{}", self.base_url, Path::PositionSetAutoAddMargin);
908 let json = serialize_json(request)?;
909 let headers = self.get_signed_headers(&json)?;
910
911 let request = self
912 .client
913 .request(Method::POST, url)
914 .headers(headers)
915 .body(json);
916
917 let response = self.send(Path::PositionSetAutoAddMargin, request).await?;
918 Ok(response)
919 }
920
921 #[tracing::instrument(skip(self), err)]
924 pub async fn set_risk_limit(
925 &self,
926 request: &SetRiskLimitRequest,
927 ) -> Result<Response<SetRiskLimitResponse>, Error> {
928 let url = format!("{}{}", self.base_url, Path::PositionSetRiskLimit);
929 let json = serialize_json(request)?;
930 let headers = self.get_signed_headers(&json)?;
931
932 let request = self
933 .client
934 .request(Method::POST, url)
935 .headers(headers)
936 .body(json);
937
938 let response = self.send(Path::PositionSetRiskLimit, request).await?;
939 Ok(response)
940 }
941
942 #[tracing::instrument(skip(self), err)]
945 pub async fn get_closed_pnl(
946 &self,
947 params: &GetClosedPnlParams,
948 ) -> Result<Response<CursorPagination<ClosedPnl>>, Error> {
949 let query = serialize_query(params)?;
950 let url = format!("{}{}?{query}", self.base_url, Path::PositionClosedPnl);
951 let headers = self.get_signed_headers(&query)?;
952
953 let request = self.client.request(Method::GET, url).headers(headers);
954
955 let response = self.send(Path::PositionClosedPnl, request).await?;
956 Ok(response)
957 }
958
959 #[tracing::instrument(skip(self), err)]
961 pub async fn get_closed_pnl_all(
962 &self,
963 params: &GetClosedPnlParams,
964 ) -> Result<Vec<ClosedPnl>, Error> {
965 collect_pages(|cursor| {
966 let p = match cursor {
967 Some(cursor) => params.clone().with_cursor(cursor),
968 None => params.clone(),
969 };
970 async move { self.get_closed_pnl(&p).await.map(|r| r.result) }
971 })
972 .await
973 }
974
975 #[tracing::instrument(skip(self), err)]
978 pub async fn get_execution_list(
979 &self,
980 params: &GetExecutionListParams,
981 ) -> Result<Response<CursorPagination<ExecutionEntry>>, Error> {
982 let query = serialize_query(params)?;
983 let url = format!("{}{}?{query}", self.base_url, Path::ExecutionList);
984 let headers = self.get_signed_headers(&query)?;
985
986 let request = self.client.request(Method::GET, url).headers(headers);
987
988 let response = self.send(Path::ExecutionList, request).await?;
989 Ok(response)
990 }
991
992 #[tracing::instrument(skip(self), err)]
994 pub async fn get_execution_list_all(
995 &self,
996 params: &GetExecutionListParams,
997 ) -> Result<Vec<ExecutionEntry>, Error> {
998 collect_pages(|cursor| {
999 let p = match cursor {
1000 Some(cursor) => params.clone().with_cursor(cursor),
1001 None => params.clone(),
1002 };
1003 async move { self.get_execution_list(&p).await.map(|r| r.result) }
1004 })
1005 .await
1006 }
1007}
1008
1009impl Client {
1011 #[tracing::instrument(skip(self), err)]
1013 pub async fn get_wallet_balance(
1014 &self,
1015 params: &GetWalletBalanceParams,
1016 ) -> Result<Response<List<WalletBalance>>, Error> {
1017 let query = serialize_query(params)?;
1018 let url = format!("{}{}?{query}", self.base_url, Path::AccountWalletBalance);
1019 let headers = self.get_signed_headers(&query)?;
1020
1021 let request = self.client.request(Method::GET, url).headers(headers);
1022
1023 let response = self.send(Path::AccountWalletBalance, request).await?;
1024 Ok(response)
1025 }
1026
1027 #[tracing::instrument(skip(self), err)]
1030 pub async fn get_transaction_log(
1031 &self,
1032 params: &GetTransactionLogParams,
1033 ) -> Result<Response<CursorPagination<TransactionLog>>, Error> {
1034 let query = serialize_query(params)?;
1035 let url = format!("{}{}?{query}", self.base_url, Path::AccountTransactionLog);
1036 let headers = self.get_signed_headers(&query)?;
1037
1038 let request = self.client.request(Method::GET, url).headers(headers);
1039
1040 let response = self.send(Path::AccountTransactionLog, request).await?;
1041 Ok(response)
1042 }
1043
1044 #[tracing::instrument(skip(self), err)]
1048 pub async fn get_transaction_log_all(
1049 &self,
1050 params: &GetTransactionLogParams,
1051 ) -> Result<Vec<TransactionLog>, Error> {
1052 collect_pages(|cursor| {
1053 let p = match cursor {
1054 Some(cursor) => params.clone().with_cursor(cursor),
1055 None => params.clone(),
1056 };
1057 async move { self.get_transaction_log(&p).await.map(|r| r.result) }
1058 })
1059 .await
1060 }
1061
1062 #[tracing::instrument(skip(self), err)]
1064 pub async fn get_account_info(&self) -> Result<Response<AccountInfo>, Error> {
1065 let url = format!("{}{}", self.base_url, Path::AccountInfo);
1066 let query = "";
1067 let headers = self.get_signed_headers(query)?;
1068
1069 let request = self.client.request(Method::GET, url).headers(headers);
1070
1071 let response = self.send(Path::AccountInfo, request).await?;
1072 Ok(response)
1073 }
1074
1075 #[tracing::instrument(skip(self), err)]
1080 pub async fn get_fee_rate(
1081 &self,
1082 params: &GetFeeRateParams,
1083 ) -> Result<Response<List<FeeRateEntry>>, Error> {
1084 let url = format!("{}{}", self.base_url, Path::AccountFeeRate);
1085 let query = serialize_query(params)?;
1086 let headers = self.get_signed_headers(&query)?;
1087
1088 let request = self
1089 .client
1090 .request(Method::GET, url)
1091 .headers(headers)
1092 .query(params);
1093
1094 let response = self.send(Path::AccountFeeRate, request).await?;
1095 Ok(response)
1096 }
1097
1098 #[tracing::instrument(skip(self), err)]
1103 pub async fn set_margin_mode(
1104 &self,
1105 request_body: &SetMarginModeRequest,
1106 ) -> Result<Response<SetMarginModeResponse>, Error> {
1107 let url = format!("{}{}", self.base_url, Path::AccountSetMarginMode);
1108 let body = serialize_json(request_body)?;
1109 let headers = self.get_signed_headers(&body)?;
1110
1111 let request = self
1112 .client
1113 .request(Method::POST, url)
1114 .headers(headers)
1115 .body(body);
1116
1117 let response = self.send(Path::AccountSetMarginMode, request).await?;
1118 Ok(response)
1119 }
1120
1121 #[tracing::instrument(skip(self), err)]
1127 pub async fn upgrade_to_uta(&self) -> Result<Response<UpgradeToUtaResult>, Error> {
1128 let url = format!("{}{}", self.base_url, Path::AccountUpgradeToUta);
1129 let body = "{}".to_owned();
1130 let headers = self.get_signed_headers(&body)?;
1131
1132 let request = self
1133 .client
1134 .request(Method::POST, url)
1135 .headers(headers)
1136 .body(body);
1137
1138 let response = self.send(Path::AccountUpgradeToUta, request).await?;
1139 Ok(response)
1140 }
1141
1142 #[tracing::instrument(skip(self), err)]
1147 pub async fn get_borrow_history(
1148 &self,
1149 params: &GetBorrowHistoryParams,
1150 ) -> Result<Response<CursorPagination<BorrowHistoryEntry>>, Error> {
1151 let query = serialize_query(params)?;
1152 let url = format!("{}{}?{query}", self.base_url, Path::AccountBorrowHistory);
1153 let headers = self.get_signed_headers(&query)?;
1154
1155 let request = self.client.request(Method::GET, url).headers(headers);
1156
1157 let response = self.send(Path::AccountBorrowHistory, request).await?;
1158 Ok(response)
1159 }
1160
1161 #[tracing::instrument(skip(self), err)]
1165 pub async fn get_borrow_history_all(
1166 &self,
1167 params: &GetBorrowHistoryParams,
1168 ) -> Result<Vec<BorrowHistoryEntry>, Error> {
1169 collect_pages(|cursor| {
1170 let p = match cursor {
1171 Some(cursor) => params.clone().with_cursor(cursor),
1172 None => params.clone(),
1173 };
1174 async move { self.get_borrow_history(&p).await.map(|r| r.result) }
1175 })
1176 .await
1177 }
1178
1179 #[tracing::instrument(skip(self), err)]
1185 pub async fn get_collateral_info(
1186 &self,
1187 params: &GetCollateralInfoParams,
1188 ) -> Result<Response<List<CollateralInfoEntry>>, Error> {
1189 let url = format!("{}{}", self.base_url, Path::AccountCollateralInfo);
1190 let query = serialize_query(params)?;
1191 let headers = self.get_signed_headers(&query)?;
1192
1193 let request = self
1194 .client
1195 .request(Method::GET, url)
1196 .headers(headers)
1197 .query(params);
1198
1199 let response = self.send(Path::AccountCollateralInfo, request).await?;
1200 Ok(response)
1201 }
1202}
1203
1204impl Client {
1206 #[tracing::instrument(skip(self), err)]
1209 pub async fn get_api_key_information(&self) -> Result<Response<APIKeyInformation>, Error> {
1210 let url = format!("{}{}", self.base_url, Path::UserQueryApi);
1211 let query = "";
1212 let headers = self.get_signed_headers(query)?;
1213
1214 let request = self.client.request(Method::GET, url).headers(headers);
1215
1216 let response = self.send(Path::UserQueryApi, request).await?;
1217 Ok(response)
1218 }
1219}
1220
1221impl Client {
1223 #[tracing::instrument(skip(self), err)]
1228 pub async fn internal_transfer(
1229 &self,
1230 request: &InternalTransferRequest,
1231 ) -> Result<Response<TransferResult>, Error> {
1232 let url = format!("{}{}", self.base_url, Path::AssetTransferInterTransfer);
1233 let body = serialize_json(request)?;
1234 let headers = self.get_signed_headers(&body)?;
1235
1236 let request = self
1237 .client
1238 .request(Method::POST, url)
1239 .headers(headers)
1240 .body(body);
1241
1242 let response = self.send(Path::AssetTransferInterTransfer, request).await?;
1243 Ok(response)
1244 }
1245
1246 #[tracing::instrument(skip(self), err)]
1251 pub async fn get_internal_transfer_records(
1252 &self,
1253 params: &GetInternalTransferRecordsParams,
1254 ) -> Result<Response<CursorPagination<InternalTransferEntry>>, Error> {
1255 let query = serialize_query(params)?;
1256 let url = format!(
1257 "{}{}?{query}",
1258 self.base_url,
1259 Path::AssetTransferQueryInterTransferList
1260 );
1261 let headers = self.get_signed_headers(&query)?;
1262
1263 let request = self.client.request(Method::GET, url).headers(headers);
1264
1265 let response = self
1266 .send(Path::AssetTransferQueryInterTransferList, request)
1267 .await?;
1268 Ok(response)
1269 }
1270
1271 #[tracing::instrument(skip(self), err)]
1277 pub async fn universal_transfer(
1278 &self,
1279 request: &UniversalTransferRequest,
1280 ) -> Result<Response<TransferResult>, Error> {
1281 let url = format!("{}{}", self.base_url, Path::AssetTransferUniversalTransfer);
1282 let body = serialize_json(request)?;
1283 let headers = self.get_signed_headers(&body)?;
1284
1285 let request = self
1286 .client
1287 .request(Method::POST, url)
1288 .headers(headers)
1289 .body(body);
1290
1291 let response = self
1292 .send(Path::AssetTransferUniversalTransfer, request)
1293 .await?;
1294 Ok(response)
1295 }
1296
1297 #[tracing::instrument(skip(self), err)]
1302 pub async fn get_universal_transfer_records(
1303 &self,
1304 params: &GetUniversalTransferRecordsParams,
1305 ) -> Result<Response<CursorPagination<UniversalTransferEntry>>, Error> {
1306 let query = serialize_query(params)?;
1307 let url = format!(
1308 "{}{}?{query}",
1309 self.base_url,
1310 Path::AssetTransferQueryUniversalTransferList
1311 );
1312 let headers = self.get_signed_headers(&query)?;
1313
1314 let request = self.client.request(Method::GET, url).headers(headers);
1315
1316 let response = self
1317 .send(Path::AssetTransferQueryUniversalTransferList, request)
1318 .await?;
1319 Ok(response)
1320 }
1321
1322 #[tracing::instrument(skip(self), err)]
1327 pub async fn get_transferable_coins(
1328 &self,
1329 params: &GetTransferableCoinsParams,
1330 ) -> Result<Response<List<String>>, Error> {
1331 let query = serialize_query(params)?;
1332 let url = format!(
1333 "{}{}?{query}",
1334 self.base_url,
1335 Path::AssetTransferQueryTransferCoinList
1336 );
1337 let headers = self.get_signed_headers(&query)?;
1338
1339 let request = self.client.request(Method::GET, url).headers(headers);
1340
1341 let response = self
1342 .send(Path::AssetTransferQueryTransferCoinList, request)
1343 .await?;
1344 Ok(response)
1345 }
1346
1347 #[tracing::instrument(skip(self), err)]
1353 pub async fn save_transfer_sub_member(
1354 &self,
1355 request: &SaveTransferSubMemberRequest,
1356 ) -> Result<Response<EmptyResult>, Error> {
1357 let url = format!(
1358 "{}{}",
1359 self.base_url,
1360 Path::AssetTransferSaveTransferSubMember
1361 );
1362 let body = serialize_json(request)?;
1363 let headers = self.get_signed_headers(&body)?;
1364
1365 let request = self
1366 .client
1367 .request(Method::POST, url)
1368 .headers(headers)
1369 .body(body);
1370
1371 let response = self
1372 .send(Path::AssetTransferSaveTransferSubMember, request)
1373 .await?;
1374 Ok(response)
1375 }
1376
1377 #[tracing::instrument(skip(self), err)]
1383 pub async fn get_transferable_sub_members(
1384 &self,
1385 ) -> Result<Response<TransferableSubMembers>, Error> {
1386 let url = format!("{}{}", self.base_url, Path::AssetTransferQuerySubMemberList);
1387 let query = "";
1388 let headers = self.get_signed_headers(query)?;
1389
1390 let request = self.client.request(Method::GET, url).headers(headers);
1391
1392 let response = self
1393 .send(Path::AssetTransferQuerySubMemberList, request)
1394 .await?;
1395 Ok(response)
1396 }
1397
1398 #[tracing::instrument(skip(self), err)]
1404 pub async fn get_account_coin_balance(
1405 &self,
1406 params: &GetAccountCoinBalanceParams,
1407 ) -> Result<Response<AccountCoinBalance>, Error> {
1408 let query = serialize_query(params)?;
1409 let url = format!(
1410 "{}{}?{query}",
1411 self.base_url,
1412 Path::AssetTransferQueryAccountCoinBalance
1413 );
1414 let headers = self.get_signed_headers(&query)?;
1415
1416 let request = self.client.request(Method::GET, url).headers(headers);
1417
1418 let response = self
1419 .send(Path::AssetTransferQueryAccountCoinBalance, request)
1420 .await?;
1421 Ok(response)
1422 }
1423
1424 #[tracing::instrument(skip(self), err)]
1429 pub async fn get_asset_info(
1430 &self,
1431 params: &GetAssetInfoParams,
1432 ) -> Result<Response<AssetInfoResult>, Error> {
1433 let query = serialize_query(params)?;
1434 let url = format!(
1435 "{}{}?{query}",
1436 self.base_url,
1437 Path::AssetTransferQueryAssetInfo
1438 );
1439 let headers = self.get_signed_headers(&query)?;
1440
1441 let request = self.client.request(Method::GET, url).headers(headers);
1442
1443 let response = self
1444 .send(Path::AssetTransferQueryAssetInfo, request)
1445 .await?;
1446 Ok(response)
1447 }
1448
1449 #[tracing::instrument(skip(self), err)]
1453 pub async fn get_deposit_allowed_coin_info(
1454 &self,
1455 params: &GetDepositAllowedCoinInfoParams,
1456 ) -> Result<Response<DepositAllowedCoinInfoResult>, Error> {
1457 let query = serialize_query(params)?;
1458 let url = format!(
1459 "{}{}?{query}",
1460 self.base_url,
1461 Path::AssetDepositQueryAllowedList
1462 );
1463 let headers = self.get_signed_headers(&query)?;
1464
1465 let request = self.client.request(Method::GET, url).headers(headers);
1466
1467 let response = self
1468 .send(Path::AssetDepositQueryAllowedList, request)
1469 .await?;
1470 Ok(response)
1471 }
1472
1473 #[tracing::instrument(skip(self), err)]
1477 pub async fn get_deposit_records(
1478 &self,
1479 params: &GetDepositRecordsParams,
1480 ) -> Result<Response<DepositRecords>, Error> {
1481 let query = serialize_query(params)?;
1482 let url = format!("{}{}?{query}", self.base_url, Path::AssetDepositQueryRecord);
1483 let headers = self.get_signed_headers(&query)?;
1484
1485 let request = self.client.request(Method::GET, url).headers(headers);
1486
1487 let response = self.send(Path::AssetDepositQueryRecord, request).await?;
1488 Ok(response)
1489 }
1490
1491 #[tracing::instrument(skip(self), err)]
1495 pub async fn get_sub_deposit_records(
1496 &self,
1497 params: &GetSubDepositRecordsParams,
1498 ) -> Result<Response<DepositRecords>, Error> {
1499 let query = serialize_query(params)?;
1500 let url = format!(
1501 "{}{}?{query}",
1502 self.base_url,
1503 Path::AssetDepositQuerySubMemberRecord
1504 );
1505 let headers = self.get_signed_headers(&query)?;
1506
1507 let request = self.client.request(Method::GET, url).headers(headers);
1508
1509 let response = self
1510 .send(Path::AssetDepositQuerySubMemberRecord, request)
1511 .await?;
1512 Ok(response)
1513 }
1514
1515 #[tracing::instrument(skip(self), err)]
1519 pub async fn get_deposit_address(
1520 &self,
1521 params: &GetDepositAddressParams,
1522 ) -> Result<Response<DepositAddress>, Error> {
1523 let query = serialize_query(params)?;
1524 let url = format!(
1525 "{}{}?{query}",
1526 self.base_url,
1527 Path::AssetDepositQueryAddress
1528 );
1529 let headers = self.get_signed_headers(&query)?;
1530
1531 let request = self.client.request(Method::GET, url).headers(headers);
1532
1533 let response = self.send(Path::AssetDepositQueryAddress, request).await?;
1534 Ok(response)
1535 }
1536
1537 #[tracing::instrument(skip(self), err)]
1541 pub async fn get_sub_deposit_address(
1542 &self,
1543 params: &GetSubDepositAddressParams,
1544 ) -> Result<Response<DepositAddress>, Error> {
1545 let query = serialize_query(params)?;
1546 let url = format!(
1547 "{}{}?{query}",
1548 self.base_url,
1549 Path::AssetDepositQuerySubMemberAddress
1550 );
1551 let headers = self.get_signed_headers(&query)?;
1552
1553 let request = self.client.request(Method::GET, url).headers(headers);
1554
1555 let response = self
1556 .send(Path::AssetDepositQuerySubMemberAddress, request)
1557 .await?;
1558 Ok(response)
1559 }
1560
1561 #[tracing::instrument(skip(self), err)]
1565 pub async fn get_withdrawal_records(
1566 &self,
1567 params: &GetWithdrawalRecordsParams,
1568 ) -> Result<Response<WithdrawRecords>, Error> {
1569 let query = serialize_query(params)?;
1570 let url = format!(
1571 "{}{}?{query}",
1572 self.base_url,
1573 Path::AssetWithdrawQueryRecord
1574 );
1575 let headers = self.get_signed_headers(&query)?;
1576
1577 let request = self.client.request(Method::GET, url).headers(headers);
1578
1579 let response = self.send(Path::AssetWithdrawQueryRecord, request).await?;
1580 Ok(response)
1581 }
1582
1583 #[tracing::instrument(skip(self), err)]
1589 pub async fn get_withdrawable_amount(
1590 &self,
1591 params: &GetWithdrawableAmountParams,
1592 ) -> Result<Response<WithdrawableAmountResult>, Error> {
1593 let query = serialize_query(params)?;
1594 let url = format!(
1595 "{}{}?{query}",
1596 self.base_url,
1597 Path::AssetWithdrawWithdrawableAmount
1598 );
1599 let headers = self.get_signed_headers(&query)?;
1600
1601 let request = self.client.request(Method::GET, url).headers(headers);
1602
1603 let response = self
1604 .send(Path::AssetWithdrawWithdrawableAmount, request)
1605 .await?;
1606 Ok(response)
1607 }
1608
1609 #[tracing::instrument(skip(self), err)]
1614 pub async fn withdraw(
1615 &self,
1616 request: &WithdrawRequest,
1617 ) -> Result<Response<WithdrawResult>, Error> {
1618 let url = format!("{}{}", self.base_url, Path::AssetWithdrawCreate);
1619 let body = serialize_json(request)?;
1620 let headers = self.get_signed_headers(&body)?;
1621
1622 let request = self
1623 .client
1624 .request(Method::POST, url)
1625 .headers(headers)
1626 .body(body);
1627
1628 let response = self.send(Path::AssetWithdrawCreate, request).await?;
1629 Ok(response)
1630 }
1631
1632 #[tracing::instrument(skip(self), err)]
1637 pub async fn cancel_withdrawal(
1638 &self,
1639 request: &CancelWithdrawalRequest,
1640 ) -> Result<Response<CancelWithdrawalResult>, Error> {
1641 let url = format!("{}{}", self.base_url, Path::AssetWithdrawCancel);
1642 let body = serialize_json(request)?;
1643 let headers = self.get_signed_headers(&body)?;
1644
1645 let request = self
1646 .client
1647 .request(Method::POST, url)
1648 .headers(headers)
1649 .body(body);
1650
1651 let response = self.send(Path::AssetWithdrawCancel, request).await?;
1652 Ok(response)
1653 }
1654
1655 #[tracing::instrument(skip(self), err)]
1660 pub async fn get_coin_info(
1661 &self,
1662 params: &GetCoinInfoParams,
1663 ) -> Result<Response<CoinInfoResult>, Error> {
1664 let url = format!("{}{}", self.base_url, Path::AssetCoinQueryInfo);
1665 let query = serialize_query(params)?;
1666 let headers = self.get_signed_headers(&query)?;
1667
1668 let request = self
1669 .client
1670 .request(Method::GET, url)
1671 .headers(headers)
1672 .query(params);
1673
1674 let response = self.send(Path::AssetCoinQueryInfo, request).await?;
1675 Ok(response)
1676 }
1677
1678 #[tracing::instrument(skip(self), err)]
1682 pub async fn get_exchange_order_record(
1683 &self,
1684 params: &GetExchangeOrderRecordParams,
1685 ) -> Result<Response<ExchangeOrderRecords>, Error> {
1686 let query = serialize_query(params)?;
1687 let url = format!(
1688 "{}{}?{query}",
1689 self.base_url,
1690 Path::AssetExchangeOrderRecord
1691 );
1692 let headers = self.get_signed_headers(&query)?;
1693
1694 let request = self.client.request(Method::GET, url).headers(headers);
1695
1696 let response = self.send(Path::AssetExchangeOrderRecord, request).await?;
1697 Ok(response)
1698 }
1699
1700 #[tracing::instrument(skip(self), err)]
1705 pub async fn get_delivery_record(
1706 &self,
1707 params: &GetDeliveryRecordParams,
1708 ) -> Result<Response<CursorPagination<DeliveryRecord>>, Error> {
1709 let query = serialize_query(params)?;
1710 let url = format!("{}{}?{query}", self.base_url, Path::AssetDeliveryRecord);
1711 let headers = self.get_signed_headers(&query)?;
1712
1713 let request = self.client.request(Method::GET, url).headers(headers);
1714
1715 let response = self.send(Path::AssetDeliveryRecord, request).await?;
1716 Ok(response)
1717 }
1718
1719 #[tracing::instrument(skip(self), err)]
1724 pub async fn get_settlement_record(
1725 &self,
1726 params: &GetSettlementRecordParams,
1727 ) -> Result<Response<CursorPagination<SettlementRecord>>, Error> {
1728 let query = serialize_query(params)?;
1729 let url = format!("{}{}?{query}", self.base_url, Path::AssetSettlementRecord);
1730 let headers = self.get_signed_headers(&query)?;
1731
1732 let request = self.client.request(Method::GET, url).headers(headers);
1733
1734 let response = self.send(Path::AssetSettlementRecord, request).await?;
1735 Ok(response)
1736 }
1737
1738 #[tracing::instrument(skip(self), err)]
1743 pub async fn get_coin_greeks(
1744 &self,
1745 params: &GetCoinGreeksParams,
1746 ) -> Result<Response<List<CoinGreeks>>, Error> {
1747 let query = serialize_query(params)?;
1748 let url = format!("{}{}?{query}", self.base_url, Path::AssetCoinGreeks);
1749 let headers = self.get_signed_headers(&query)?;
1750
1751 let request = self.client.request(Method::GET, url).headers(headers);
1752
1753 let response = self.send(Path::AssetCoinGreeks, request).await?;
1754 Ok(response)
1755 }
1756}
1757
1758impl Client {
1760 #[tracing::instrument(skip(self), err)]
1765 pub async fn get_leverage_token_info(
1766 &self,
1767 params: &GetLeverageTokenInfoParams,
1768 ) -> Result<Response<List<LeverageTokenInfo>>, Error> {
1769 let url = format!("{}{}", self.base_url, Path::SpotLeverTokenInfo);
1770
1771 let request = self.client.request(Method::GET, url).query(params);
1772
1773 let response = self.send(Path::SpotLeverTokenInfo, request).await?;
1774 Ok(response)
1775 }
1776
1777 #[tracing::instrument(skip(self), err)]
1782 pub async fn get_leverage_token_market(
1783 &self,
1784 params: &GetLeverageTokenMarketParams,
1785 ) -> Result<Response<LeverageTokenMarket>, Error> {
1786 let url = format!("{}{}", self.base_url, Path::SpotLeverTokenReference);
1787
1788 let request = self.client.request(Method::GET, url).query(params);
1789
1790 let response = self.send(Path::SpotLeverTokenReference, request).await?;
1791 Ok(response)
1792 }
1793
1794 #[tracing::instrument(skip(self), err)]
1799 pub async fn purchase_leverage_token(
1800 &self,
1801 request: &PurchaseLeverageTokenRequest,
1802 ) -> Result<Response<PurchaseLeverageTokenResult>, Error> {
1803 let url = format!("{}{}", self.base_url, Path::SpotLeverTokenPurchase);
1804 let body = serialize_json(request)?;
1805 let headers = self.get_signed_headers(&body)?;
1806
1807 let request = self
1808 .client
1809 .request(Method::POST, url)
1810 .headers(headers)
1811 .body(body);
1812
1813 let response = self.send(Path::SpotLeverTokenPurchase, request).await?;
1814 Ok(response)
1815 }
1816
1817 #[tracing::instrument(skip(self), err)]
1822 pub async fn redeem_leverage_token(
1823 &self,
1824 request: &RedeemLeverageTokenRequest,
1825 ) -> Result<Response<RedeemLeverageTokenResult>, Error> {
1826 let url = format!("{}{}", self.base_url, Path::SpotLeverTokenRedeem);
1827 let body = serialize_json(request)?;
1828 let headers = self.get_signed_headers(&body)?;
1829
1830 let request = self
1831 .client
1832 .request(Method::POST, url)
1833 .headers(headers)
1834 .body(body);
1835
1836 let response = self.send(Path::SpotLeverTokenRedeem, request).await?;
1837 Ok(response)
1838 }
1839
1840 #[tracing::instrument(skip(self), err)]
1844 pub async fn get_leverage_token_order_records(
1845 &self,
1846 params: &GetLeverageTokenOrderRecordsParams,
1847 ) -> Result<Response<List<LeverageTokenOrderRecord>>, Error> {
1848 let query = serialize_query(params)?;
1849 let url = format!(
1850 "{}{}?{query}",
1851 self.base_url,
1852 Path::SpotLeverTokenOrderRecord
1853 );
1854 let headers = self.get_signed_headers(&query)?;
1855
1856 let request = self.client.request(Method::GET, url).headers(headers);
1857
1858 let response = self.send(Path::SpotLeverTokenOrderRecord, request).await?;
1859 Ok(response)
1860 }
1861}
1862
1863impl Client {
1865 #[tracing::instrument(skip(self), err)]
1871 pub async fn switch_spot_margin_mode(
1872 &self,
1873 request: &SwitchSpotMarginModeRequest,
1874 ) -> Result<Response<SwitchSpotMarginModeResult>, Error> {
1875 let url = format!("{}{}", self.base_url, Path::SpotMarginTradeSwitchMode);
1876 let body = serialize_json(request)?;
1877 let headers = self.get_signed_headers(&body)?;
1878
1879 let request = self
1880 .client
1881 .request(Method::POST, url)
1882 .headers(headers)
1883 .body(body);
1884
1885 let response = self.send(Path::SpotMarginTradeSwitchMode, request).await?;
1886 Ok(response)
1887 }
1888
1889 #[tracing::instrument(skip(self), err)]
1895 pub async fn set_spot_margin_leverage(
1896 &self,
1897 request: &SetSpotMarginLeverageRequest,
1898 ) -> Result<Response<EmptyResult>, Error> {
1899 let url = format!("{}{}", self.base_url, Path::SpotMarginTradeSetLeverage);
1900 let body = serialize_json(request)?;
1901 let headers = self.get_signed_headers(&body)?;
1902
1903 let request = self
1904 .client
1905 .request(Method::POST, url)
1906 .headers(headers)
1907 .body(body);
1908
1909 let response = self.send(Path::SpotMarginTradeSetLeverage, request).await?;
1910 Ok(response)
1911 }
1912}
1913
1914impl Client {
1915 async fn send<T>(&self, path: Path, request: RequestBuilder) -> Result<Response<T>, Error>
1917 where
1918 T: serde::de::DeserializeOwned,
1919 {
1920 self.send_weighted(path, None, 1, request).await
1921 }
1922
1923 async fn send_weighted<T>(
1927 &self,
1928 path: Path,
1929 category: Option<Category>,
1930 cost: u32,
1931 request: RequestBuilder,
1932 ) -> Result<Response<T>, Error>
1933 where
1934 T: serde::de::DeserializeOwned,
1935 {
1936 let key = RateLimitKey { path, category };
1937
1938 if let Some(rl) = &self.rate_limiter {
1939 rl.check(key, cost).map_err(|rejection| match rejection {
1940 Rejection::RetryAfter(retry_after_ms) => Error::RateLimited { retry_after_ms },
1941 Rejection::Unsatisfiable { cost, burst } => {
1942 Error::RateLimitUnsatisfiable { cost, burst }
1943 }
1944 })?;
1945 }
1946
1947 let start = std::time::Instant::now();
1948 let response = request.send().await?;
1949 let elapsed_ms = start.elapsed().as_millis();
1950 let status = response.status();
1951 let headers = parse_headers(response.headers());
1952
1953 if let Some(rl) = &self.rate_limiter {
1954 rl.update(
1955 key,
1956 headers.api_limit_status,
1957 headers.api_limit_reset_timestamp,
1958 );
1959 }
1960
1961 let body = response.text().await?;
1962 let result = parse_response(status, headers, &body);
1963 match &result {
1964 Ok(response) => tracing::debug!(
1965 elapsed_ms,
1966 api_limit = response.headers.api_limit,
1967 api_limit_status = response.headers.api_limit_status,
1968 "api call completed"
1969 ),
1970 Err(e) => tracing::debug!(elapsed_ms, error = %e, "api call failed"),
1971 }
1972 result
1973 }
1974}
1975
1976pub const MAX_PAGES: usize = 1000;
1978
1979async fn collect_pages<T, F, Fut>(mut fetch: F) -> Result<Vec<T>, Error>
1986where
1987 F: FnMut(Option<String>) -> Fut,
1988 Fut: std::future::Future<Output = Result<CursorPagination<T>, Error>>,
1989{
1990 let mut all = Vec::new();
1991 let mut cursor: Option<String> = None;
1992 for _ in 0..MAX_PAGES {
1993 let page = fetch(cursor.clone()).await?;
1994 let empty = page.list.is_empty();
1995 all.extend(page.list);
1996 match page.next_page_cursor {
1997 Some(next) if !empty && cursor.as_ref() != Some(&next) => cursor = Some(next),
1998 _ => return Ok(all),
1999 }
2000 }
2001 Err(Error::TooManyPages { max: MAX_PAGES })
2002}
2003
2004fn parse_response<T>(status: StatusCode, headers: Headers, body: &str) -> Result<Response<T>, Error>
2010where
2011 T: serde::de::DeserializeOwned,
2012{
2013 let envelope = deserialize_json::<APIErrorResponse>(body);
2014
2015 if !status.is_success() {
2016 return Err(match envelope {
2017 Ok(envelope) if envelope.ret_code != 0 => envelope.into(),
2018 _ => Error::Http {
2019 status: status.as_u16(),
2020 body: truncate(body, MAX_ERROR_BODY_LEN).to_owned(),
2021 },
2022 });
2023 }
2024
2025 let envelope = envelope?;
2026 if envelope.ret_code != 0 {
2027 return Err(envelope.into());
2028 }
2029
2030 let response: Resp<T> = deserialize_json(body)?;
2031 Ok(Response {
2032 result: response.result,
2033 time: response.time,
2034 headers,
2035 ret_ext_info: response.ret_ext_info,
2036 })
2037}
2038
2039fn truncate(s: &str, max: usize) -> &str {
2041 if s.len() <= max {
2042 return s;
2043 }
2044 let mut end = max;
2045 while !s.is_char_boundary(end) {
2046 end -= 1;
2047 }
2048 &s[..end]
2049}
2050
2051fn parse_headers(headers: &HeaderMap) -> Headers {
2053 let ret_code = headers
2054 .get(HEADER_RET_CODE)
2055 .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
2056 let trace_id = headers
2057 .get(HEADER_TRACE_ID)
2058 .and_then(|h| h.to_str().map(|str| str.into()).ok());
2059 let time_now = headers
2060 .get(HEADER_TIME_NOW)
2061 .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
2062
2063 let api_limit = headers
2064 .get(HEADER_X_BAPI_LIMIT)
2065 .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
2066 let api_limit_status = headers
2067 .get(HEADER_X_BAPI_LIMIT_STATUS)
2068 .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
2069 let api_limit_reset_timestamp = headers
2070 .get(HEADER_X_BAPI_LIMIT_RESET_TIMESTAMP)
2071 .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
2072
2073 Headers {
2074 ret_code,
2075 trace_id,
2076 time_now,
2077 api_limit,
2078 api_limit_status,
2079 api_limit_reset_timestamp,
2080 }
2081}
2082
2083#[cfg(test)]
2084mod tests {
2085 use super::*;
2086
2087 fn headers() -> Headers {
2088 parse_headers(&HeaderMap::new())
2089 }
2090
2091 #[test]
2092 fn parse_response_success() {
2093 let body = r#"{"retCode":0,"retMsg":"OK","result":{"timeSecond":"1688639403","timeNano":"1688639403423213947"},"retExtInfo":{},"time":1688639403423}"#;
2094
2095 let response: Response<ServerTime> =
2096 parse_response(StatusCode::OK, headers(), body).unwrap();
2097
2098 assert_eq!(response.time, Some(1688639403423));
2099 }
2100
2101 #[test]
2102 fn parse_response_api_error_with_http_200() {
2103 let body = r#"{"retCode":10001,"retMsg":"params error","result":{},"retExtInfo":{},"time":1688639403423}"#;
2104
2105 let err = parse_response::<ServerTime>(StatusCode::OK, headers(), body).unwrap_err();
2106
2107 assert!(matches!(err, Error::Api { code: 10001, ref msg } if msg == "params error"));
2108 }
2109
2110 #[test]
2111 fn parse_response_api_error_with_http_error_status() {
2112 let body = r#"{"retCode":10006,"retMsg":"Too many visits!","result":{},"retExtInfo":{},"time":1688639403423}"#;
2113
2114 let err = parse_response::<ServerTime>(StatusCode::TOO_MANY_REQUESTS, headers(), body)
2115 .unwrap_err();
2116
2117 assert!(matches!(err, Error::Api { code: 10006, .. }));
2118 }
2119
2120 #[test]
2121 fn parse_response_non_json_error_page() {
2122 let body = "<html><body>403 Forbidden</body></html>";
2123
2124 let err = parse_response::<ServerTime>(StatusCode::FORBIDDEN, headers(), body).unwrap_err();
2125
2126 assert!(matches!(err, Error::Http { status: 403, body: ref b } if b == body));
2127 }
2128
2129 #[test]
2130 fn truncate_respects_char_boundaries() {
2131 assert_eq!(truncate("abc", 10), "abc");
2132 assert_eq!(truncate("ab\u{00e9}c", 3), "ab");
2133 }
2134
2135 fn page(list: Vec<u32>, cursor: Option<&str>) -> CursorPagination<u32> {
2136 CursorPagination {
2137 category: None,
2138 next_page_cursor: cursor.map(String::from),
2139 list,
2140 }
2141 }
2142
2143 async fn collect_from(
2145 pages: Vec<CursorPagination<u32>>,
2146 ) -> (Result<Vec<u32>, Error>, Vec<Option<String>>) {
2147 let mut pages = pages.into_iter();
2148 let mut requested = Vec::new();
2149 let result = collect_pages(|cursor| {
2150 requested.push(cursor);
2151 let page = pages.next().expect("unexpected extra request");
2152 async move { Ok(page) }
2153 })
2154 .await;
2155 (result, requested)
2156 }
2157
2158 #[tokio::test]
2159 async fn collect_pages_follows_cursor_until_none() {
2160 let (result, requested) = collect_from(vec![
2161 page(vec![1, 2], Some("a")),
2162 page(vec![3], Some("b")),
2163 page(vec![4], None),
2164 ])
2165 .await;
2166
2167 assert_eq!(result.unwrap(), vec![1, 2, 3, 4]);
2168 assert_eq!(requested, vec![None, Some("a".into()), Some("b".into())]);
2169 }
2170
2171 #[tokio::test]
2172 async fn collect_pages_stops_on_repeated_cursor() {
2173 let (result, _) =
2174 collect_from(vec![page(vec![1], Some("a")), page(vec![2], Some("a"))]).await;
2175
2176 assert_eq!(result.unwrap(), vec![1, 2]);
2177 }
2178
2179 #[tokio::test]
2180 async fn collect_pages_stops_on_empty_page_with_cursor() {
2181 let (result, _) =
2182 collect_from(vec![page(vec![1], Some("a")), page(vec![], Some("b"))]).await;
2183
2184 assert_eq!(result.unwrap(), vec![1]);
2185 }
2186
2187 #[tokio::test]
2188 async fn collect_pages_fails_after_max_pages() {
2189 let mut n = 0;
2190 let result = collect_pages(|_| {
2191 n += 1;
2192 let cursor = n.to_string();
2193 async move { Ok(page(vec![1], Some(&cursor))) }
2194 })
2195 .await;
2196
2197 assert!(matches!(
2198 result,
2199 Err(Error::TooManyPages { max: MAX_PAGES })
2200 ));
2201 assert_eq!(n, MAX_PAGES);
2202 }
2203
2204 #[tokio::test]
2205 async fn private_endpoint_without_credentials_returns_error() {
2206 let client = Client::new(Config::new("http://127.0.0.1:1")).unwrap();
2207
2208 let err = client.get_api_key_information().await.unwrap_err();
2209
2210 assert!(matches!(err, Error::MissingCredentials));
2211 }
2212}