Skip to main content

bybit/http/
client.rs

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::{self, Method, RequestBuilder, header::HeaderMap};
46
47use super::rate_limiter::{RateLimitKey, RateLimiter, RateLimiterConfig};
48
49pub struct Config {
50    pub base_url: String,
51    pub api_key: Option<SensitiveString>,
52    pub api_secret: Option<SensitiveString>,
53    /// Milliseconds.
54    pub recv_window: Timestamp,
55    /// HTTP the header for broker users only.
56    pub referer: Option<String>,
57    /// Optional rate limiter. When `Some`, every request is pre-flight-checked
58    /// against a local token bucket and the last server-reported limit status.
59    /// Set to `None` to disable rate limiting entirely.
60    pub rate_limiter: Option<RateLimiterConfig>,
61}
62
63// TODO: use proxy
64#[derive(Debug)]
65pub struct Client {
66    base_url: String,
67    headers: HeaderMap,
68    client: reqwest::Client,
69    signer: Option<Signer>,
70    rate_limiter: Option<RateLimiter>,
71}
72
73impl Client {
74    pub fn new(cfg: Config) -> Result<Self, Error> {
75        let mut headers = HeaderMap::new();
76
77        if let Some(api_key) = cfg.api_key.as_ref() {
78            let api_key = api_key.expose().parse()?;
79            headers.append(HEADER_X_BAPI_API_KEY, api_key);
80        }
81
82        let recv_window = cfg.recv_window.to_string().parse()?;
83        headers.append(HEADER_X_BAPI_RECV_WINDOW, recv_window);
84
85        if let Some(referer) = cfg.referer {
86            let referer = referer.parse()?;
87            headers.append(HEADER_X_REFERER, referer);
88        }
89
90        let signer = cfg
91            .api_secret
92            .map(|api_secret| -> Result<Signer, Error> {
93                let api_key = cfg
94                    .api_key
95                    .ok_or_else(|| Error::from("api_key is required when api_secret is set"))?;
96                Ok(Signer::new(api_key, api_secret, cfg.recv_window, None))
97            })
98            .transpose()?;
99
100        let rate_limiter = cfg.rate_limiter.map(RateLimiter::new);
101
102        Ok(Self {
103            base_url: cfg.base_url,
104            headers,
105            client: reqwest::Client::builder().build()?,
106            signer,
107            rate_limiter,
108        })
109    }
110
111    fn get_signed_headers(&self, s: &str) -> HeaderMap {
112        let mut headers = self.headers.clone();
113
114        let (signature, timestamp) = self.signer.as_ref().unwrap().sign(s);
115        let signature = signature.parse().unwrap();
116        headers.append(HEADER_X_BAPI_SIGN, signature);
117        let timestamp = timestamp.parse().unwrap();
118        headers.append(HEADER_X_BAPI_TIMESTAMP, timestamp);
119
120        headers
121    }
122}
123
124// Market.
125impl Client {
126    #[tracing::instrument(skip(self), err)]
127    pub async fn get_server_time(&self) -> Result<Response<ServerTime>, Error> {
128        let url = format!("{}{}", self.base_url, Path::MarketServerTime);
129
130        let request = self.client.request(Method::GET, url);
131
132        let response = self.send(Path::MarketServerTime, request).await?;
133        Ok(response)
134    }
135
136    #[tracing::instrument(skip(self), err)]
137    pub async fn get_kline(&self, params: &GetKLinesParams) -> Result<Response<KLine>, Error> {
138        let url = format!("{}{}", self.base_url, Path::MarketKline);
139
140        let request = self.client.request(Method::GET, url).query(params);
141
142        let response = self.send(Path::MarketKline, request).await?;
143        Ok(response)
144    }
145
146    /// Get Mark Price Kline.
147    /// Query the mark price kline data. Charts are returned in groups based on the requested interval.
148    ///
149    /// Covers: linear / inverse
150    #[tracing::instrument(skip(self), err)]
151    pub async fn get_mark_price_kline(
152        &self,
153        params: &GetKLinesParams,
154    ) -> Result<Response<KLine>, Error> {
155        let url = format!("{}{}", self.base_url, Path::MarketMarkPriceKline);
156
157        let request = self.client.request(Method::GET, url).query(params);
158
159        let response = self.send(Path::MarketMarkPriceKline, request).await?;
160        Ok(response)
161    }
162
163    /// Get Index Price Kline.
164    /// Query the index price kline data. Charts are returned in groups based on the requested interval.
165    ///
166    /// Covers: linear / inverse
167    #[tracing::instrument(skip(self), err)]
168    pub async fn get_index_price_kline(
169        &self,
170        params: &GetKLinesParams,
171    ) -> Result<Response<KLine>, Error> {
172        let url = format!("{}{}", self.base_url, Path::MarketIndexPriceKline);
173
174        let request = self.client.request(Method::GET, url).query(params);
175
176        let response = self.send(Path::MarketIndexPriceKline, request).await?;
177        Ok(response)
178    }
179
180    /// Get Premium Index Price Kline.
181    /// Retrieve the premium index price kline data. Charts are returned in groups based on the requested interval.
182    ///
183    /// Covers: linear
184    #[tracing::instrument(skip(self), err)]
185    pub async fn get_premium_index_price_kline(
186        &self,
187        params: &GetKLinesParams,
188    ) -> Result<Response<KLine>, Error> {
189        let url = format!("{}{}", self.base_url, Path::MarketPremiumIndexPriceKline);
190
191        let request = self.client.request(Method::GET, url).query(params);
192
193        let response = self
194            .send(Path::MarketPremiumIndexPriceKline, request)
195            .await?;
196        Ok(response)
197    }
198
199    /// Get Tickers
200    /// Query for the latest price snapshot, best bid/ask price, and trading volume in the last 24 hours.
201    /// If category=option, symbol or baseCoin must be passed.
202    #[tracing::instrument(skip(self), err)]
203    pub async fn get_tickers(&self, params: &GetTickersParams) -> Result<Response<Ticker>, Error> {
204        let url = format!("{}{}", self.base_url, Path::MarketTickers);
205
206        let request = self.client.request(Method::GET, url).query(params);
207
208        let response = self.send(Path::MarketTickers, request).await?;
209        Ok(response)
210    }
211
212    /// Get Orderbook.
213    /// Query for orderbook depth data.
214    ///
215    /// Covers: Spot / USDT contract / USDC contract / Inverse contract / Option
216    #[tracing::instrument(skip(self), err)]
217    pub async fn get_orderbook(
218        &self,
219        params: &GetOrderbookParams,
220    ) -> Result<Response<Orderbook>, Error> {
221        let url = format!("{}{}", self.base_url, Path::MarketOrderbook);
222
223        let request = self.client.request(Method::GET, url).query(params);
224
225        let response = self.send(Path::MarketOrderbook, request).await?;
226        Ok(response)
227    }
228
229    #[tracing::instrument(skip(self), err)]
230    pub async fn get_instruments_info(
231        &self,
232        params: &GetInstrumentsInfoParams,
233    ) -> Result<Response<InstrumentsInfo>, Error> {
234        let url = format!("{}{}", self.base_url, Path::MarketInstrumentsInfo);
235
236        let request = self.client.request(Method::GET, url).query(params);
237
238        let response = self.send(Path::MarketInstrumentsInfo, request).await?;
239        Ok(response)
240    }
241
242    #[tracing::instrument(skip(self), err)]
243    pub async fn get_public_recent_trading_history(
244        &self,
245        params: &GetTradesParams,
246    ) -> Result<Response<Trade>, Error> {
247        let url = format!("{}{}", self.base_url, Path::MarketRecentTrade);
248
249        let request = self.client.request(Method::GET, url).query(params);
250
251        let response = self.send(Path::MarketRecentTrade, request).await?;
252        Ok(response)
253    }
254
255    /// Get Funding Rate History.
256    /// Query for historical funding rates. Each request returns up to 200 rows of data.
257    ///
258    /// Covers: linear / inverse
259    #[tracing::instrument(skip(self), err)]
260    pub async fn get_funding_rate_history(
261        &self,
262        params: &GetFundingRateHistoryParams,
263    ) -> Result<Response<FundingRateHistory>, Error> {
264        let url = format!("{}{}", self.base_url, Path::MarketFundingHistory);
265
266        let request = self.client.request(Method::GET, url).query(params);
267
268        let response = self.send(Path::MarketFundingHistory, request).await?;
269        Ok(response)
270    }
271
272    /// Get Open Interest.
273    /// Get the open interest of each symbol.
274    ///
275    /// Covers: linear / inverse
276    #[tracing::instrument(skip(self), err)]
277    pub async fn get_open_interest(
278        &self,
279        params: &GetOpenInterestParams,
280    ) -> Result<Response<OpenInterest>, Error> {
281        let url = format!("{}{}", self.base_url, Path::MarketOpenInterest);
282
283        let request = self.client.request(Method::GET, url).query(params);
284
285        let response = self.send(Path::MarketOpenInterest, request).await?;
286        Ok(response)
287    }
288
289    /// Get Historical Volatility.
290    /// Query option historical volatility.
291    ///
292    /// Covers: option only
293    ///
294    /// Note: the result is returned as a top-level JSON array, so the response
295    /// wraps `Vec<HistoricalVolatilityEntry>` directly.
296    #[tracing::instrument(skip(self), err)]
297    pub async fn get_historical_volatility(
298        &self,
299        params: &GetHistoricalVolatilityParams,
300    ) -> Result<Response<Vec<HistoricalVolatilityEntry>>, Error> {
301        let url = format!("{}{}", self.base_url, Path::MarketHistoricalVolatility);
302
303        let request = self.client.request(Method::GET, url).query(params);
304
305        let response = self.send(Path::MarketHistoricalVolatility, request).await?;
306        Ok(response)
307    }
308
309    /// Get Insurance.
310    /// Query for Bybit insurance pool data (1 day delay).
311    #[tracing::instrument(skip(self), err)]
312    pub async fn get_insurance(
313        &self,
314        params: &GetInsuranceParams,
315    ) -> Result<Response<Insurance>, Error> {
316        let url = format!("{}{}", self.base_url, Path::MarketInsurance);
317
318        let request = self.client.request(Method::GET, url).query(params);
319
320        let response = self.send(Path::MarketInsurance, request).await?;
321        Ok(response)
322    }
323
324    /// Get Risk Limit.
325    /// Query for the risk limit.
326    ///
327    /// Covers: linear / inverse
328    #[tracing::instrument(skip(self), err)]
329    pub async fn get_risk_limit(
330        &self,
331        params: &GetRiskLimitParams,
332    ) -> Result<Response<RiskLimit>, Error> {
333        let url = format!("{}{}", self.base_url, Path::MarketRiskLimit);
334
335        let request = self.client.request(Method::GET, url).query(params);
336
337        let response = self.send(Path::MarketRiskLimit, request).await?;
338        Ok(response)
339    }
340
341    /// Get Delivery Price.
342    /// Get the delivery price for option and USDC futures contracts.
343    ///
344    /// Covers: linear / inverse / option
345    #[tracing::instrument(skip(self), err)]
346    pub async fn get_delivery_price(
347        &self,
348        params: &GetDeliveryPriceParams,
349    ) -> Result<Response<DeliveryPrice>, Error> {
350        let url = format!("{}{}", self.base_url, Path::MarketDeliveryPrice);
351
352        let request = self.client.request(Method::GET, url).query(params);
353
354        let response = self.send(Path::MarketDeliveryPrice, request).await?;
355        Ok(response)
356    }
357}
358
359// Trade.
360impl Client {
361    /// Place Order
362    /// This endpoint supports to create the order for Spot, Margin trading, USDT perpetual, USDT futures, USDC perpetual, USDC futures, Inverse Futures and Options.
363    ///
364    /// INFO:
365    /// Supported order type (orderType):
366    /// Limit order: orderType=Limit, it is necessary to specify order qty and price.
367    ///
368    /// Market order: orderType=Market, execute at the best price in the Bybit market until the transaction is completed. When selecting a market order, the "price" can be empty. In the futures trading system, in order to protect traders against the serious slippage of the Market order, Bybit trading engine will convert the market order into an IOC limit order for matching. If there are no orderbook entries within price slippage limit, the order will not be executed. If there is insufficient liquidity, the order will be cancelled. The slippage threshold refers to the percentage that the order price deviates from the mark price. You can learn more here: Adjustments to Bybit's Derivative Trading Price Limit Mechanism
369    /// Supported timeInForce strategy:
370    /// GTC
371    /// IOC
372    /// FOK
373    /// PostOnly: If the order would be filled immediately when submitted, it will be cancelled. The purpose of this is to protect your order during the submission process. If the matching system cannot entrust the order to the order book due to price changes on the market, it will be cancelled.
374    /// RPI: Retail Price Improvement order. Assigned market maker can place this kind of order, and it is a post only order, only match with the order from Web or APP.
375    ///
376    /// How to create a conditional order:
377    /// When submitting an order, if triggerPrice is set, the order will be automatically converted into a conditional order. In addition, the conditional order does not occupy the margin. If the margin is insufficient after the conditional order is triggered, the order will be cancelled.
378    ///
379    /// Take profit / Stop loss: You can set TP/SL while placing orders. Besides, you could modify the position's TP/SL.
380    ///
381    /// Order quantity: The quantity of perpetual contracts you are going to buy/sell. For the order quantity, Bybit only supports positive number at present.
382    ///
383    /// Order price: Place a limit order, this parameter is required. If you have position, the price should be higher than the liquidation price. For the minimum unit of the price change, please refer to the priceFilter > tickSize field in the instruments-info endpoint.
384    ///
385    /// orderLinkId: You can customize the active order ID. We can link this ID to the order ID in the system. Once the active order is successfully created, we will send the unique order ID in the system to you. Then, you can use this order ID to cancel active orders, and if both orderId and orderLinkId are entered in the parameter input, Bybit will prioritize the orderId to process the corresponding order. Meanwhile, your customized order ID should be no longer than 36 characters and should be unique.
386    ///
387    /// Open orders up limit:
388    /// Perps & Futures:
389    /// a) Each account can hold a maximum of 500 active orders simultaneously per symbol.
390    /// b) conditional orders: each account can hold a maximum of 10 active orders simultaneously per symbol.
391    /// Spot: 500 orders in total, including a maximum of 30 open TP/SL orders, a maximum of 30 open conditional orders for each symbol per account
392    /// Option: a maximum of 50 open orders per account
393    ///
394    /// Rate limit:
395    /// Please refer to rate limit table. If you need to raise the rate limit, please contact your client manager or submit an application via here
396    ///
397    /// Risk control limit notice:
398    /// Bybit will monitor on your API requests. When the total number of orders of a single user (aggregated the number of orders across main account and subaccounts) within a day (UTC 0 - UTC 24) exceeds a certain upper limit, the platform will reserve the right to remind, warn, and impose necessary restrictions. Customers who use API default to acceptance of these terms and have the obligation to cooperate with adjustments.
399    ///
400    /// Reduce only orders:
401    /// If reduceOnly=true and order qty > max order qty, the order will automatically be split up into multiple orders.
402    ///
403    /// Spot Stop Order
404    /// Spot supports TP/SL order, Conditional order, however, the system logic is different between classic account and Unified account
405    /// classic account: When the stop order is created, you will get an order ID. After it is triggered, you will get a new order ID
406    /// Unified account: When the stop order is created, you will get an order ID. After it is triggered, the order ID will not be changed
407    #[tracing::instrument(skip(self), err)]
408    pub async fn place_order(
409        &self,
410        request: &PlaceOrderRequest,
411    ) -> Result<Response<PlaceOrderResponse>, Error> {
412        let category = request.category;
413        let url = format!("{}{}", self.base_url, Path::TradeOrderCreate);
414        let json = serialize_json(request)?;
415        let headers = self.get_signed_headers(&json);
416
417        let request = self
418            .client
419            .request(Method::POST, url)
420            .headers(headers)
421            .body(json);
422
423        let response = self
424            .send_weighted(Path::TradeOrderCreate, Some(category), 1, request)
425            .await?;
426        Ok(response)
427    }
428
429    /// Amend Order
430    /// info
431    /// You can only modify unfilled or partially filled orders.
432    #[tracing::instrument(skip(self), err)]
433    pub async fn amend_order(
434        &self,
435        request: &AmendOrderRequest,
436    ) -> Result<Response<AmendOrderResponse>, Error> {
437        let category = request.category;
438        let url = format!("{}{}", self.base_url, Path::TradeOrderAmend);
439        let json = serialize_json(request)?;
440        let headers = self.get_signed_headers(&json);
441
442        let request = self
443            .client
444            .request(Method::POST, url)
445            .headers(headers)
446            .body(json);
447
448        let response = self
449            .send_weighted(Path::TradeOrderAmend, Some(category), 1, request)
450            .await?;
451        Ok(response)
452    }
453
454    /// Cancel Order
455    /// important
456    /// You must specify orderId or orderLinkId to cancel the order.
457    /// If orderId and orderLinkId do not match, the system will process orderId first.
458    /// You can only cancel unfilled or partially filled orders.
459    #[tracing::instrument(skip(self), err)]
460    pub async fn cancel_order(
461        &self,
462        request: &CancelOrderRequest,
463    ) -> Result<Response<CancelOrderResponse>, Error> {
464        let category = request.category;
465        let url = format!("{}{}", self.base_url, Path::TradeOrderCancel);
466        let json = serialize_json(request)?;
467        let headers = self.get_signed_headers(&json);
468
469        let request = self
470            .client
471            .request(Method::POST, url)
472            .headers(headers)
473            .body(json);
474
475        let response = self
476            .send_weighted(Path::TradeOrderCancel, Some(category), 1, request)
477            .await?;
478        Ok(response)
479    }
480
481    /// Get Open & Closed Orders.
482    /// Primarily query unfilled or partially filled orders in real-time, but also supports querying recent 500 closed status (Cancelled, Filled) orders. Please see the usage of request param openOnly.
483    /// And to query older order records, please use the order history interface.
484    ///
485    /// Tip
486    /// UTA2.0 can query filled, canceled, and rejected orders to the most recent 500 orders for spot, linear, inverse and option categories
487    /// UTA1.0 can query filled, canceled, and rejected orders to the most recent 500 orders for spot, linear, and option categories. The inverse category is not subject to this limitation.
488    /// You can query by symbol, baseCoin, orderId and orderLinkId, and if you pass multiple params, the system will process them according to this priority: orderId > orderLinkId > symbol > baseCoin.
489    /// The records are sorted by the createdTime from newest to oldest.
490    ///
491    /// info
492    /// classic account spot can return open orders only
493    /// After a server release or restart, filled, canceled, and rejected orders of Unified account should only be queried through order history.
494    #[tracing::instrument(skip(self), err)]
495    pub async fn get_open_closed_orders(
496        &self,
497        params: &GetOpenClosedOrdersParams,
498    ) -> Result<Response<CursorPagination<Order>>, Error> {
499        let query = serialize_query(params)?;
500        let url = format!("{}{}?{query}", self.base_url, Path::TradeOrderRealtime);
501        let headers = self.get_signed_headers(&query);
502
503        let request = self.client.request(Method::GET, url).headers(headers);
504
505        let response = self.send(Path::TradeOrderRealtime, request).await?;
506        Ok(response)
507    }
508
509    /// Collect all pages of open/closed orders into a single `Vec`.
510    /// Repeatedly calls [`get_open_closed_orders`] following `next_page_cursor`
511    /// until the last page is reached.
512    #[tracing::instrument(skip(self), err)]
513    pub async fn get_open_closed_orders_all(
514        &self,
515        params: &GetOpenClosedOrdersParams,
516    ) -> Result<Vec<Order>, Error> {
517        let mut all = Vec::new();
518        let mut p = params.clone();
519        loop {
520            let page = self.get_open_closed_orders(&p).await?;
521            all.extend(page.result.list);
522            match page.result.next_page_cursor {
523                Some(cursor) => p = p.with_cursor(cursor),
524                None => break,
525            }
526        }
527        Ok(all)
528    }
529
530    /// Cancel All Orders.
531    /// Cancel all open orders. Support linear, inverse, spot, and option.
532    #[tracing::instrument(skip(self), err)]
533    pub async fn cancel_all_orders(
534        &self,
535        request: &CancelAllOrdersRequest,
536    ) -> Result<Response<CancelAllOrdersResponse>, Error> {
537        let category = request.category;
538        let url = format!("{}{}", self.base_url, Path::TradeOrderCancelAll);
539        let json = serialize_json(request)?;
540        let headers = self.get_signed_headers(&json);
541
542        let request = self
543            .client
544            .request(Method::POST, url)
545            .headers(headers)
546            .body(json);
547
548        let response = self
549            .send_weighted(Path::TradeOrderCancelAll, Some(category), 1, request)
550            .await?;
551        Ok(response)
552    }
553
554    /// Get Order History.
555    /// Query order history. As order creation/cancellation is asynchronous, the data returned may be delayed.
556    /// Supports up to 2 years of data.
557    #[tracing::instrument(skip(self), err)]
558    pub async fn get_order_history(
559        &self,
560        params: &GetOrderHistoryParams,
561    ) -> Result<Response<CursorPagination<Order>>, Error> {
562        let query = serialize_query(params)?;
563        let url = format!("{}{}?{query}", self.base_url, Path::TradeOrderHistory);
564        let headers = self.get_signed_headers(&query);
565
566        let request = self.client.request(Method::GET, url).headers(headers);
567
568        let response = self.send(Path::TradeOrderHistory, request).await?;
569        Ok(response)
570    }
571
572    /// Collect all pages of order history into a single `Vec`.
573    #[tracing::instrument(skip(self), err)]
574    pub async fn get_order_history_all(
575        &self,
576        params: &GetOrderHistoryParams,
577    ) -> Result<Vec<Order>, Error> {
578        let mut all = Vec::new();
579        let mut p = params.clone();
580        loop {
581            let page = self.get_order_history(&p).await?;
582            all.extend(page.result.list);
583            match page.result.next_page_cursor {
584                Some(cursor) => p = p.with_cursor(cursor),
585                None => break,
586            }
587        }
588        Ok(all)
589    }
590
591    /// Place Batch Orders.
592    /// Supports up to 20 orders per request.
593    /// Per-item results are in `response.ret_ext_info.list` (parallel to `response.result.list`).
594    #[tracing::instrument(skip(self), err)]
595    pub async fn place_orders_batch(
596        &self,
597        request: &PlaceOrderBatchRequest,
598    ) -> Result<Response<List<PlaceOrderBatchResult>>, Error> {
599        let category = request.category;
600        let cost = request.request.len() as u32;
601        let url = format!("{}{}", self.base_url, Path::TradeOrderCreateBatch);
602        let json = serialize_json(request)?;
603        let headers = self.get_signed_headers(&json);
604
605        let request = self
606            .client
607            .request(Method::POST, url)
608            .headers(headers)
609            .body(json);
610
611        let response = self
612            .send_weighted(Path::TradeOrderCreateBatch, Some(category), cost, request)
613            .await?;
614        Ok(response)
615    }
616
617    /// Amend Batch Orders.
618    /// Supports up to 20 orders per request.
619    /// Per-item results are in `response.ret_ext_info.list` (parallel to `response.result.list`).
620    #[tracing::instrument(skip(self), err)]
621    pub async fn amend_orders_batch(
622        &self,
623        request: &AmendOrderBatchRequest,
624    ) -> Result<Response<List<AmendOrderBatchResult>>, Error> {
625        let category = request.category;
626        let cost = request.request.len() as u32;
627        let url = format!("{}{}", self.base_url, Path::TradeOrderAmendBatch);
628        let json = serialize_json(request)?;
629        let headers = self.get_signed_headers(&json);
630
631        let request = self
632            .client
633            .request(Method::POST, url)
634            .headers(headers)
635            .body(json);
636
637        let response = self
638            .send_weighted(Path::TradeOrderAmendBatch, Some(category), cost, request)
639            .await?;
640        Ok(response)
641    }
642
643    /// Cancel Batch Orders.
644    /// Supports up to 20 orders per request.
645    /// Per-item results are in `response.ret_ext_info.list` (parallel to `response.result.list`).
646    #[tracing::instrument(skip(self), err)]
647    pub async fn cancel_orders_batch(
648        &self,
649        request: &CancelOrderBatchRequest,
650    ) -> Result<Response<List<CancelOrderBatchResult>>, Error> {
651        let category = request.category;
652        let cost = request.request.len() as u32;
653        let url = format!("{}{}", self.base_url, Path::TradeOrderCancelBatch);
654        let json = serialize_json(request)?;
655        let headers = self.get_signed_headers(&json);
656
657        let request = self
658            .client
659            .request(Method::POST, url)
660            .headers(headers)
661            .body(json);
662
663        let response = self
664            .send_weighted(Path::TradeOrderCancelBatch, Some(category), cost, request)
665            .await?;
666        Ok(response)
667    }
668
669    /// Get Spot Borrow Check.
670    /// Query the maximum quantity for purchase or sale, and check the borrowable quantity based on the fee types.
671    ///
672    /// Covers: spot only. Requires authentication.
673    #[tracing::instrument(skip(self), err)]
674    pub async fn get_spot_borrow_check(
675        &self,
676        params: &GetSpotBorrowCheckParams,
677    ) -> Result<Response<SpotBorrowCheck>, Error> {
678        let url = format!("{}{}", self.base_url, Path::TradeOrderSpotBorrowCheck);
679        let query = serialize_query(params)?;
680        let headers = self.get_signed_headers(&query);
681
682        let request = self
683            .client
684            .request(Method::GET, url)
685            .headers(headers)
686            .query(params);
687
688        let response = self.send(Path::TradeOrderSpotBorrowCheck, request).await?;
689        Ok(response)
690    }
691}
692
693// Position.
694impl Client {
695    /// Query real-time position data, such as position size, cumulative realized PNL, etc.
696    /// Query real-time position data, such as position size, cumulative realized PNL, etc.
697    ///
698    /// INFO
699    // UTA2.0(inverse)
700    // You can query all open positions with /v5/position/list?category=inverse;
701    // Cannot query multiple symbols in one request
702    // UTA1.0(inverse) & Classic (inverse)
703    // You can query all open positions with /v5/position/list?category=inverse;
704    // symbol parameter can pass up to 10 symbols, e.g., symbol=BTCUSD,ETHUSD
705    #[tracing::instrument(skip(self), err)]
706    pub async fn get_position_info(
707        &self,
708        params: &GetPositionInfoParams,
709    ) -> Result<Response<CursorPagination<Position>>, Error> {
710        let query = serialize_query(params)?;
711        let url = format!("{}{}?{query}", self.base_url, Path::PositionList);
712        let headers = self.get_signed_headers(&query);
713
714        let request = self.client.request(Method::GET, url).headers(headers);
715
716        let response = self.send(Path::PositionList, request).await?;
717        Ok(response)
718    }
719
720    /// Collect all pages of position info into a single `Vec`.
721    /// Repeatedly calls [`get_position_info`] following `next_page_cursor`
722    /// until the last page is reached.
723    #[tracing::instrument(skip(self), err)]
724    pub async fn get_position_info_all(
725        &self,
726        params: &GetPositionInfoParams,
727    ) -> Result<Vec<Position>, Error> {
728        let mut all = Vec::new();
729        let mut p = params.clone();
730        loop {
731            let page = self.get_position_info(&p).await?;
732            all.extend(page.result.list);
733            match page.result.next_page_cursor {
734                Some(cursor) => p = p.with_cursor(cursor),
735                None => break,
736            }
737        }
738        Ok(all)
739    }
740
741    /// Set Leverage.
742    /// Set the leverage for a position. Only for isolated margin mode.
743    #[tracing::instrument(skip(self), err)]
744    pub async fn set_leverage(
745        &self,
746        request: &SetLeverageRequest,
747    ) -> Result<Response<EmptyResult>, Error> {
748        let url = format!("{}{}", self.base_url, Path::PositionSetLeverage);
749        let json = serialize_json(request)?;
750        let headers = self.get_signed_headers(&json);
751
752        let request = self
753            .client
754            .request(Method::POST, url)
755            .headers(headers)
756            .body(json);
757
758        let response = self.send(Path::PositionSetLeverage, request).await?;
759        Ok(response)
760    }
761
762    /// Set Trading Stop.
763    /// Set take profit, stop loss, or trailing stop for a position.
764    #[tracing::instrument(skip(self), err)]
765    pub async fn set_trading_stop(
766        &self,
767        request: &SetTradingStopRequest,
768    ) -> Result<Response<EmptyResult>, Error> {
769        let url = format!("{}{}", self.base_url, Path::PositionTradingStop);
770        let json = serialize_json(request)?;
771        let headers = self.get_signed_headers(&json);
772
773        let request = self
774            .client
775            .request(Method::POST, url)
776            .headers(headers)
777            .body(json);
778
779        let response = self.send(Path::PositionTradingStop, request).await?;
780        Ok(response)
781    }
782
783    /// Switch Cross/Isolated Margin.
784    /// Switch the margin mode for a symbol between cross and isolated.
785    #[tracing::instrument(skip(self), err)]
786    pub async fn switch_cross_isolated_margin(
787        &self,
788        request: &SwitchCrossIsolatedMarginRequest,
789    ) -> Result<Response<EmptyResult>, Error> {
790        let url = format!("{}{}", self.base_url, Path::PositionSwitchIsolated);
791        let json = serialize_json(request)?;
792        let headers = self.get_signed_headers(&json);
793
794        let request = self
795            .client
796            .request(Method::POST, url)
797            .headers(headers)
798            .body(json);
799
800        let response = self.send(Path::PositionSwitchIsolated, request).await?;
801        Ok(response)
802    }
803
804    /// Switch Position Mode.
805    /// Switch between one-way (merged single) and hedge (both sides) position mode.
806    #[tracing::instrument(skip(self), err)]
807    pub async fn switch_position_mode(
808        &self,
809        request: &SwitchPositionModeRequest,
810    ) -> Result<Response<EmptyResult>, Error> {
811        let url = format!("{}{}", self.base_url, Path::PositionSwitchMode);
812        let json = serialize_json(request)?;
813        let headers = self.get_signed_headers(&json);
814
815        let request = self
816            .client
817            .request(Method::POST, url)
818            .headers(headers)
819            .body(json);
820
821        let response = self.send(Path::PositionSwitchMode, request).await?;
822        Ok(response)
823    }
824
825    /// Set Auto Add Margin.
826    /// Turn on/off auto-add-margin for an isolated margin position.
827    #[tracing::instrument(skip(self), err)]
828    pub async fn set_auto_add_margin(
829        &self,
830        request: &SetAutoAddMarginRequest,
831    ) -> Result<Response<EmptyResult>, Error> {
832        let url = format!("{}{}", self.base_url, Path::PositionSetAutoAddMargin);
833        let json = serialize_json(request)?;
834        let headers = self.get_signed_headers(&json);
835
836        let request = self
837            .client
838            .request(Method::POST, url)
839            .headers(headers)
840            .body(json);
841
842        let response = self.send(Path::PositionSetAutoAddMargin, request).await?;
843        Ok(response)
844    }
845
846    /// Set Risk Limit.
847    /// Set the risk limit for a position. The response includes the new risk limit and its value.
848    #[tracing::instrument(skip(self), err)]
849    pub async fn set_risk_limit(
850        &self,
851        request: &SetRiskLimitRequest,
852    ) -> Result<Response<SetRiskLimitResponse>, Error> {
853        let url = format!("{}{}", self.base_url, Path::PositionSetRiskLimit);
854        let json = serialize_json(request)?;
855        let headers = self.get_signed_headers(&json);
856
857        let request = self
858            .client
859            .request(Method::POST, url)
860            .headers(headers)
861            .body(json);
862
863        let response = self.send(Path::PositionSetRiskLimit, request).await?;
864        Ok(response)
865    }
866
867    /// Get Closed P&L.
868    /// Query the closed profit and loss records of positions.
869    #[tracing::instrument(skip(self), err)]
870    pub async fn get_closed_pnl(
871        &self,
872        params: &GetClosedPnlParams,
873    ) -> Result<Response<CursorPagination<ClosedPnl>>, Error> {
874        let query = serialize_query(params)?;
875        let url = format!("{}{}?{query}", self.base_url, Path::PositionClosedPnl);
876        let headers = self.get_signed_headers(&query);
877
878        let request = self.client.request(Method::GET, url).headers(headers);
879
880        let response = self.send(Path::PositionClosedPnl, request).await?;
881        Ok(response)
882    }
883
884    /// Collect all pages of closed P&L into a single `Vec`.
885    #[tracing::instrument(skip(self), err)]
886    pub async fn get_closed_pnl_all(
887        &self,
888        params: &GetClosedPnlParams,
889    ) -> Result<Vec<ClosedPnl>, Error> {
890        let mut all = Vec::new();
891        let mut p = params.clone();
892        loop {
893            let page = self.get_closed_pnl(&p).await?;
894            all.extend(page.result.list);
895            match page.result.next_page_cursor {
896                Some(cursor) => p = p.with_cursor(cursor),
897                None => break,
898            }
899        }
900        Ok(all)
901    }
902
903    /// Get Execution List.
904    /// Query users' execution (trading) records, sorted by execTime descending.
905    #[tracing::instrument(skip(self), err)]
906    pub async fn get_execution_list(
907        &self,
908        params: &GetExecutionListParams,
909    ) -> Result<Response<CursorPagination<ExecutionEntry>>, Error> {
910        let query = serialize_query(params)?;
911        let url = format!("{}{}?{query}", self.base_url, Path::ExecutionList);
912        let headers = self.get_signed_headers(&query);
913
914        let request = self.client.request(Method::GET, url).headers(headers);
915
916        let response = self.send(Path::ExecutionList, request).await?;
917        Ok(response)
918    }
919
920    /// Collect all pages of execution list entries into a single `Vec`.
921    #[tracing::instrument(skip(self), err)]
922    pub async fn get_execution_list_all(
923        &self,
924        params: &GetExecutionListParams,
925    ) -> Result<Vec<ExecutionEntry>, Error> {
926        let mut all = Vec::new();
927        let mut p = params.clone();
928        loop {
929            let page = self.get_execution_list(&p).await?;
930            all.extend(page.result.list);
931            match page.result.next_page_cursor {
932                Some(cursor) => p = p.with_cursor(cursor),
933                None => break,
934            }
935        }
936        Ok(all)
937    }
938}
939
940// Account.
941impl Client {
942    /// Obtain wallet balance, query asset information of each currency. By default, currency information with assets or liabilities of 0 is not returned.
943    #[tracing::instrument(skip(self), err)]
944    pub async fn get_wallet_balance(
945        &self,
946        params: &GetWalletBalanceParams,
947    ) -> Result<Response<List<WalletBalance>>, Error> {
948        let query = serialize_query(params)?;
949        let url = format!("{}{}?{query}", self.base_url, Path::AccountWalletBalance);
950        let headers = self.get_signed_headers(&query);
951
952        let request = self.client.request(Method::GET, url).headers(headers);
953
954        let response = self.send(Path::AccountWalletBalance, request).await?;
955        Ok(response)
956    }
957
958    /// Get Transaction Log
959    /// Query for transaction logs in your Unified account. It supports up to 2 years worth of data.
960    #[tracing::instrument(skip(self), err)]
961    pub async fn get_transaction_log(
962        &self,
963        params: &GetTransactionLogParams,
964    ) -> Result<Response<CursorPagination<TransactionLog>>, Error> {
965        let query = serialize_query(params)?;
966        let url = format!("{}{}?{query}", self.base_url, Path::AccountTransactionLog);
967        let headers = self.get_signed_headers(&query);
968
969        let request = self.client.request(Method::GET, url).headers(headers);
970
971        let response = self.send(Path::AccountTransactionLog, request).await?;
972        Ok(response)
973    }
974
975    /// Collect all pages of transaction log entries into a single `Vec`.
976    /// Repeatedly calls [`get_transaction_log`] following `next_page_cursor`
977    /// until the last page is reached.
978    #[tracing::instrument(skip(self), err)]
979    pub async fn get_transaction_log_all(
980        &self,
981        params: &GetTransactionLogParams,
982    ) -> Result<Vec<TransactionLog>, Error> {
983        let mut all = Vec::new();
984        let mut p = params.clone();
985        loop {
986            let page = self.get_transaction_log(&p).await?;
987            all.extend(page.result.list);
988            match page.result.next_page_cursor {
989                Some(cursor) => p = p.with_cursor(cursor),
990                None => break,
991            }
992        }
993        Ok(all)
994    }
995
996    /// Query the account information, like margin mode, account mode, etc.
997    #[tracing::instrument(skip(self), err)]
998    pub async fn get_account_info(&self) -> Result<Response<AccountInfo>, Error> {
999        let url = format!("{}{}", self.base_url, Path::AccountInfo);
1000        let query = "";
1001        let headers = self.get_signed_headers(query);
1002
1003        let request = self.client.request(Method::GET, url).headers(headers);
1004
1005        let response = self.send(Path::AccountInfo, request).await?;
1006        Ok(response)
1007    }
1008
1009    /// Get Fee Rate.
1010    /// Get the trading fee rate.
1011    ///
1012    /// Requires authentication.
1013    #[tracing::instrument(skip(self), err)]
1014    pub async fn get_fee_rate(
1015        &self,
1016        params: &GetFeeRateParams,
1017    ) -> Result<Response<List<FeeRateEntry>>, Error> {
1018        let url = format!("{}{}", self.base_url, Path::AccountFeeRate);
1019        let query = serialize_query(params)?;
1020        let headers = self.get_signed_headers(&query);
1021
1022        let request = self
1023            .client
1024            .request(Method::GET, url)
1025            .headers(headers)
1026            .query(params);
1027
1028        let response = self.send(Path::AccountFeeRate, request).await?;
1029        Ok(response)
1030    }
1031
1032    /// Set Margin Mode.
1033    /// Switch between regular margin, isolated margin, and portfolio margin.
1034    ///
1035    /// Requires authentication.
1036    #[tracing::instrument(skip(self), err)]
1037    pub async fn set_margin_mode(
1038        &self,
1039        request_body: &SetMarginModeRequest,
1040    ) -> Result<Response<SetMarginModeResponse>, Error> {
1041        let url = format!("{}{}", self.base_url, Path::AccountSetMarginMode);
1042        let body = serialize_json(request_body)?;
1043        let headers = self.get_signed_headers(&body);
1044
1045        let request = self
1046            .client
1047            .request(Method::POST, url)
1048            .headers(headers)
1049            .body(body);
1050
1051        let response = self.send(Path::AccountSetMarginMode, request).await?;
1052        Ok(response)
1053    }
1054
1055    /// Upgrade to Unified Account (Pro).
1056    /// Upgrades an account to Unified Account Pro (UTA2.0 Pro). No open orders may exist
1057    /// during the upgrade.
1058    ///
1059    /// Requires authentication.
1060    #[tracing::instrument(skip(self), err)]
1061    pub async fn upgrade_to_uta(&self) -> Result<Response<UpgradeToUtaResult>, Error> {
1062        let url = format!("{}{}", self.base_url, Path::AccountUpgradeToUta);
1063        let body = "{}".to_owned();
1064        let headers = self.get_signed_headers(&body);
1065
1066        let request = self
1067            .client
1068            .request(Method::POST, url)
1069            .headers(headers)
1070            .body(body);
1071
1072        let response = self.send(Path::AccountUpgradeToUta, request).await?;
1073        Ok(response)
1074    }
1075
1076    /// Get Borrow History.
1077    /// Get interest records, sorted in reverse order of creation time.
1078    ///
1079    /// Requires authentication. Unified account only.
1080    #[tracing::instrument(skip(self), err)]
1081    pub async fn get_borrow_history(
1082        &self,
1083        params: &GetBorrowHistoryParams,
1084    ) -> Result<Response<CursorPagination<BorrowHistoryEntry>>, Error> {
1085        let query = serialize_query(params)?;
1086        let url = format!("{}{}?{query}", self.base_url, Path::AccountBorrowHistory);
1087        let headers = self.get_signed_headers(&query);
1088
1089        let request = self.client.request(Method::GET, url).headers(headers);
1090
1091        let response = self.send(Path::AccountBorrowHistory, request).await?;
1092        Ok(response)
1093    }
1094
1095    /// Collect all pages of borrow history entries into a single `Vec`.
1096    /// Repeatedly calls [`get_borrow_history`](Client::get_borrow_history) following
1097    /// `next_page_cursor` until the last page is reached.
1098    #[tracing::instrument(skip(self), err)]
1099    pub async fn get_borrow_history_all(
1100        &self,
1101        params: &GetBorrowHistoryParams,
1102    ) -> Result<Vec<BorrowHistoryEntry>, Error> {
1103        let mut all = Vec::new();
1104        let mut p = params.clone();
1105        loop {
1106            let page = self.get_borrow_history(&p).await?;
1107            all.extend(page.result.list);
1108            match page.result.next_page_cursor {
1109                Some(cursor) => p = p.with_cursor(cursor),
1110                None => break,
1111            }
1112        }
1113        Ok(all)
1114    }
1115
1116    /// Get Collateral Info.
1117    /// Query the collateral ratio, and information of the collateral ratio tier, for each
1118    /// currency.
1119    ///
1120    /// Requires authentication. Unified account only.
1121    #[tracing::instrument(skip(self), err)]
1122    pub async fn get_collateral_info(
1123        &self,
1124        params: &GetCollateralInfoParams,
1125    ) -> Result<Response<List<CollateralInfoEntry>>, Error> {
1126        let url = format!("{}{}", self.base_url, Path::AccountCollateralInfo);
1127        let query = serialize_query(params)?;
1128        let headers = self.get_signed_headers(&query);
1129
1130        let request = self
1131            .client
1132            .request(Method::GET, url)
1133            .headers(headers)
1134            .query(params);
1135
1136        let response = self.send(Path::AccountCollateralInfo, request).await?;
1137        Ok(response)
1138    }
1139}
1140
1141// User.
1142impl Client {
1143    /// Get API Key Information.
1144    /// Get the information of the api key. Use the api key pending to be checked to call the endpoint. Both master and sub user's api key are applicable.
1145    #[tracing::instrument(skip(self), err)]
1146    pub async fn get_api_key_information(&self) -> Result<Response<APIKeyInformation>, Error> {
1147        let url = format!("{}{}", self.base_url, Path::UserQueryApi);
1148        let query = "";
1149        let headers = self.get_signed_headers(query);
1150
1151        let request = self.client.request(Method::GET, url).headers(headers);
1152
1153        let response = self.send(Path::UserQueryApi, request).await?;
1154        Ok(response)
1155    }
1156}
1157
1158// Asset.
1159impl Client {
1160    /// Create Internal Transfer.
1161    /// Transfer between different account types under the same UID.
1162    ///
1163    /// Requires authentication.
1164    #[tracing::instrument(skip(self), err)]
1165    pub async fn internal_transfer(
1166        &self,
1167        request: &InternalTransferRequest,
1168    ) -> Result<Response<TransferResult>, Error> {
1169        let url = format!("{}{}", self.base_url, Path::AssetTransferInterTransfer);
1170        let body = serialize_json(request)?;
1171        let headers = self.get_signed_headers(&body);
1172
1173        let request = self
1174            .client
1175            .request(Method::POST, url)
1176            .headers(headers)
1177            .body(body);
1178
1179        let response = self.send(Path::AssetTransferInterTransfer, request).await?;
1180        Ok(response)
1181    }
1182
1183    /// Get Internal Transfer Records.
1184    /// Query the internal transfer records between different account types under the same UID.
1185    ///
1186    /// Requires authentication.
1187    #[tracing::instrument(skip(self), err)]
1188    pub async fn get_internal_transfer_records(
1189        &self,
1190        params: &GetInternalTransferRecordsParams,
1191    ) -> Result<Response<CursorPagination<InternalTransferEntry>>, Error> {
1192        let query = serialize_query(params)?;
1193        let url = format!(
1194            "{}{}?{query}",
1195            self.base_url,
1196            Path::AssetTransferQueryInterTransferList
1197        );
1198        let headers = self.get_signed_headers(&query);
1199
1200        let request = self.client.request(Method::GET, url).headers(headers);
1201
1202        let response = self
1203            .send(Path::AssetTransferQueryInterTransferList, request)
1204            .await?;
1205        Ok(response)
1206    }
1207
1208    /// Create Universal Transfer.
1209    /// Transfer between a master account and its sub-accounts, or between two sub-accounts.
1210    ///
1211    /// Requires authentication. Requires the master UID's API key, or a sub-account key with
1212    /// the `SubMemberTransferList` permission (may only transfer to the master account).
1213    #[tracing::instrument(skip(self), err)]
1214    pub async fn universal_transfer(
1215        &self,
1216        request: &UniversalTransferRequest,
1217    ) -> Result<Response<TransferResult>, Error> {
1218        let url = format!("{}{}", self.base_url, Path::AssetTransferUniversalTransfer);
1219        let body = serialize_json(request)?;
1220        let headers = self.get_signed_headers(&body);
1221
1222        let request = self
1223            .client
1224            .request(Method::POST, url)
1225            .headers(headers)
1226            .body(body);
1227
1228        let response = self
1229            .send(Path::AssetTransferUniversalTransfer, request)
1230            .await?;
1231        Ok(response)
1232    }
1233
1234    /// Get Universal Transfer Records.
1235    ///
1236    /// Requires authentication. Requires the master UID's API key, or a sub-account key with
1237    /// the `SubMemberTransferList` permission.
1238    #[tracing::instrument(skip(self), err)]
1239    pub async fn get_universal_transfer_records(
1240        &self,
1241        params: &GetUniversalTransferRecordsParams,
1242    ) -> Result<Response<CursorPagination<UniversalTransferEntry>>, Error> {
1243        let query = serialize_query(params)?;
1244        let url = format!(
1245            "{}{}?{query}",
1246            self.base_url,
1247            Path::AssetTransferQueryUniversalTransferList
1248        );
1249        let headers = self.get_signed_headers(&query);
1250
1251        let request = self.client.request(Method::GET, url).headers(headers);
1252
1253        let response = self
1254            .send(Path::AssetTransferQueryUniversalTransferList, request)
1255            .await?;
1256        Ok(response)
1257    }
1258
1259    /// Get Transferable Coin.
1260    /// Query the transferable coins between two account types.
1261    ///
1262    /// Requires authentication.
1263    #[tracing::instrument(skip(self), err)]
1264    pub async fn get_transferable_coins(
1265        &self,
1266        params: &GetTransferableCoinsParams,
1267    ) -> Result<Response<List<String>>, Error> {
1268        let query = serialize_query(params)?;
1269        let url = format!(
1270            "{}{}?{query}",
1271            self.base_url,
1272            Path::AssetTransferQueryTransferCoinList
1273        );
1274        let headers = self.get_signed_headers(&query);
1275
1276        let request = self.client.request(Method::GET, url).headers(headers);
1277
1278        let response = self
1279            .send(Path::AssetTransferQueryTransferCoinList, request)
1280            .await?;
1281        Ok(response)
1282    }
1283
1284    /// Enable Universal Transfer For Sub UID.
1285    ///
1286    /// Deprecated by Bybit: all sub UIDs are now automatically enabled for universal transfer,
1287    /// so this call is no longer necessary. Kept for completeness. Requires the master UID's
1288    /// API key.
1289    #[tracing::instrument(skip(self), err)]
1290    pub async fn save_transfer_sub_member(
1291        &self,
1292        request: &SaveTransferSubMemberRequest,
1293    ) -> Result<Response<EmptyResult>, Error> {
1294        let url = format!(
1295            "{}{}",
1296            self.base_url,
1297            Path::AssetTransferSaveTransferSubMember
1298        );
1299        let body = serialize_json(request)?;
1300        let headers = self.get_signed_headers(&body);
1301
1302        let request = self
1303            .client
1304            .request(Method::POST, url)
1305            .headers(headers)
1306            .body(body);
1307
1308        let response = self
1309            .send(Path::AssetTransferSaveTransferSubMember, request)
1310            .await?;
1311        Ok(response)
1312    }
1313
1314    /// Get Sub UID (for universal transfer).
1315    /// Query the sub UIDs under a master account, and which of them are enabled for universal
1316    /// transfer.
1317    ///
1318    /// Requires authentication. Requires the master UID's API key.
1319    #[tracing::instrument(skip(self), err)]
1320    pub async fn get_transferable_sub_members(
1321        &self,
1322    ) -> Result<Response<TransferableSubMembers>, Error> {
1323        let url = format!("{}{}", self.base_url, Path::AssetTransferQuerySubMemberList);
1324        let query = "";
1325        let headers = self.get_signed_headers(query);
1326
1327        let request = self.client.request(Method::GET, url).headers(headers);
1328
1329        let response = self
1330            .send(Path::AssetTransferQuerySubMemberList, request)
1331            .await?;
1332        Ok(response)
1333    }
1334
1335    /// Get Single Coin Balance.
1336    /// Query the balance of a specific coin in a specific account type, or the balance that is
1337    /// safe to transfer between two accounts.
1338    ///
1339    /// Requires authentication.
1340    #[tracing::instrument(skip(self), err)]
1341    pub async fn get_account_coin_balance(
1342        &self,
1343        params: &GetAccountCoinBalanceParams,
1344    ) -> Result<Response<AccountCoinBalance>, Error> {
1345        let query = serialize_query(params)?;
1346        let url = format!(
1347            "{}{}?{query}",
1348            self.base_url,
1349            Path::AssetTransferQueryAccountCoinBalance
1350        );
1351        let headers = self.get_signed_headers(&query);
1352
1353        let request = self.client.request(Method::GET, url).headers(headers);
1354
1355        let response = self
1356            .send(Path::AssetTransferQueryAccountCoinBalance, request)
1357            .await?;
1358        Ok(response)
1359    }
1360
1361    /// Get Asset Info (Spot).
1362    /// Query for the balance of the Spot account.
1363    ///
1364    /// Requires authentication.
1365    #[tracing::instrument(skip(self), err)]
1366    pub async fn get_asset_info(
1367        &self,
1368        params: &GetAssetInfoParams,
1369    ) -> Result<Response<AssetInfoResult>, Error> {
1370        let query = serialize_query(params)?;
1371        let url = format!(
1372            "{}{}?{query}",
1373            self.base_url,
1374            Path::AssetTransferQueryAssetInfo
1375        );
1376        let headers = self.get_signed_headers(&query);
1377
1378        let request = self.client.request(Method::GET, url).headers(headers);
1379
1380        let response = self
1381            .send(Path::AssetTransferQueryAssetInfo, request)
1382            .await?;
1383        Ok(response)
1384    }
1385
1386    /// Get Allowed Deposit Coin Info.
1387    ///
1388    /// Requires authentication.
1389    #[tracing::instrument(skip(self), err)]
1390    pub async fn get_deposit_allowed_coin_info(
1391        &self,
1392        params: &GetDepositAllowedCoinInfoParams,
1393    ) -> Result<Response<DepositAllowedCoinInfoResult>, Error> {
1394        let query = serialize_query(params)?;
1395        let url = format!(
1396            "{}{}?{query}",
1397            self.base_url,
1398            Path::AssetDepositQueryAllowedList
1399        );
1400        let headers = self.get_signed_headers(&query);
1401
1402        let request = self.client.request(Method::GET, url).headers(headers);
1403
1404        let response = self
1405            .send(Path::AssetDepositQueryAllowedList, request)
1406            .await?;
1407        Ok(response)
1408    }
1409
1410    /// Get Deposit Records (on-chain).
1411    ///
1412    /// Requires authentication.
1413    #[tracing::instrument(skip(self), err)]
1414    pub async fn get_deposit_records(
1415        &self,
1416        params: &GetDepositRecordsParams,
1417    ) -> Result<Response<DepositRecords>, Error> {
1418        let query = serialize_query(params)?;
1419        let url = format!("{}{}?{query}", self.base_url, Path::AssetDepositQueryRecord);
1420        let headers = self.get_signed_headers(&query);
1421
1422        let request = self.client.request(Method::GET, url).headers(headers);
1423
1424        let response = self.send(Path::AssetDepositQueryRecord, request).await?;
1425        Ok(response)
1426    }
1427
1428    /// Get Sub Account Deposit Records (on-chain).
1429    ///
1430    /// Requires authentication. Requires the master UID's API key.
1431    #[tracing::instrument(skip(self), err)]
1432    pub async fn get_sub_deposit_records(
1433        &self,
1434        params: &GetSubDepositRecordsParams,
1435    ) -> Result<Response<DepositRecords>, Error> {
1436        let query = serialize_query(params)?;
1437        let url = format!(
1438            "{}{}?{query}",
1439            self.base_url,
1440            Path::AssetDepositQuerySubMemberRecord
1441        );
1442        let headers = self.get_signed_headers(&query);
1443
1444        let request = self.client.request(Method::GET, url).headers(headers);
1445
1446        let response = self
1447            .send(Path::AssetDepositQuerySubMemberRecord, request)
1448            .await?;
1449        Ok(response)
1450    }
1451
1452    /// Get Master Deposit Address.
1453    ///
1454    /// Requires authentication.
1455    #[tracing::instrument(skip(self), err)]
1456    pub async fn get_deposit_address(
1457        &self,
1458        params: &GetDepositAddressParams,
1459    ) -> Result<Response<DepositAddress>, Error> {
1460        let query = serialize_query(params)?;
1461        let url = format!(
1462            "{}{}?{query}",
1463            self.base_url,
1464            Path::AssetDepositQueryAddress
1465        );
1466        let headers = self.get_signed_headers(&query);
1467
1468        let request = self.client.request(Method::GET, url).headers(headers);
1469
1470        let response = self.send(Path::AssetDepositQueryAddress, request).await?;
1471        Ok(response)
1472    }
1473
1474    /// Get Sub Account Deposit Address.
1475    ///
1476    /// Requires authentication. Requires the master account's API key.
1477    #[tracing::instrument(skip(self), err)]
1478    pub async fn get_sub_deposit_address(
1479        &self,
1480        params: &GetSubDepositAddressParams,
1481    ) -> Result<Response<DepositAddress>, Error> {
1482        let query = serialize_query(params)?;
1483        let url = format!(
1484            "{}{}?{query}",
1485            self.base_url,
1486            Path::AssetDepositQuerySubMemberAddress
1487        );
1488        let headers = self.get_signed_headers(&query);
1489
1490        let request = self.client.request(Method::GET, url).headers(headers);
1491
1492        let response = self
1493            .send(Path::AssetDepositQuerySubMemberAddress, request)
1494            .await?;
1495        Ok(response)
1496    }
1497
1498    /// Get Withdrawal Records.
1499    ///
1500    /// Requires authentication.
1501    #[tracing::instrument(skip(self), err)]
1502    pub async fn get_withdrawal_records(
1503        &self,
1504        params: &GetWithdrawalRecordsParams,
1505    ) -> Result<Response<WithdrawRecords>, Error> {
1506        let query = serialize_query(params)?;
1507        let url = format!(
1508            "{}{}?{query}",
1509            self.base_url,
1510            Path::AssetWithdrawQueryRecord
1511        );
1512        let headers = self.get_signed_headers(&query);
1513
1514        let request = self.client.request(Method::GET, url).headers(headers);
1515
1516        let response = self.send(Path::AssetWithdrawQueryRecord, request).await?;
1517        Ok(response)
1518    }
1519
1520    /// Get Withdrawable Amount.
1521    /// Query the amount available to withdraw for a coin, combined across the FUND, UTA, SPOT
1522    /// and EARN wallets, along with any amount still frozen pending deposit risk review.
1523    ///
1524    /// Requires authentication.
1525    #[tracing::instrument(skip(self), err)]
1526    pub async fn get_withdrawable_amount(
1527        &self,
1528        params: &GetWithdrawableAmountParams,
1529    ) -> Result<Response<WithdrawableAmountResult>, Error> {
1530        let query = serialize_query(params)?;
1531        let url = format!(
1532            "{}{}?{query}",
1533            self.base_url,
1534            Path::AssetWithdrawWithdrawableAmount
1535        );
1536        let headers = self.get_signed_headers(&query);
1537
1538        let request = self.client.request(Method::GET, url).headers(headers);
1539
1540        let response = self
1541            .send(Path::AssetWithdrawWithdrawableAmount, request)
1542            .await?;
1543        Ok(response)
1544    }
1545
1546    /// Withdraw.
1547    /// Create a withdrawal request.
1548    ///
1549    /// Requires authentication.
1550    #[tracing::instrument(skip(self), err)]
1551    pub async fn withdraw(
1552        &self,
1553        request: &WithdrawRequest,
1554    ) -> Result<Response<WithdrawResult>, Error> {
1555        let url = format!("{}{}", self.base_url, Path::AssetWithdrawCreate);
1556        let body = serialize_json(request)?;
1557        let headers = self.get_signed_headers(&body);
1558
1559        let request = self
1560            .client
1561            .request(Method::POST, url)
1562            .headers(headers)
1563            .body(body);
1564
1565        let response = self.send(Path::AssetWithdrawCreate, request).await?;
1566        Ok(response)
1567    }
1568
1569    /// Cancel Withdrawal.
1570    /// Cancel a pending withdrawal request.
1571    ///
1572    /// Requires authentication.
1573    #[tracing::instrument(skip(self), err)]
1574    pub async fn cancel_withdrawal(
1575        &self,
1576        request: &CancelWithdrawalRequest,
1577    ) -> Result<Response<CancelWithdrawalResult>, Error> {
1578        let url = format!("{}{}", self.base_url, Path::AssetWithdrawCancel);
1579        let body = serialize_json(request)?;
1580        let headers = self.get_signed_headers(&body);
1581
1582        let request = self
1583            .client
1584            .request(Method::POST, url)
1585            .headers(headers)
1586            .body(body);
1587
1588        let response = self.send(Path::AssetWithdrawCancel, request).await?;
1589        Ok(response)
1590    }
1591
1592    /// Get Coin Info.
1593    /// Query coin information, including chains, deposit/withdrawal limits, and fees.
1594    ///
1595    /// Requires authentication.
1596    #[tracing::instrument(skip(self), err)]
1597    pub async fn get_coin_info(
1598        &self,
1599        params: &GetCoinInfoParams,
1600    ) -> Result<Response<CoinInfoResult>, Error> {
1601        let url = format!("{}{}", self.base_url, Path::AssetCoinQueryInfo);
1602        let query = serialize_query(params)?;
1603        let headers = self.get_signed_headers(&query);
1604
1605        let request = self
1606            .client
1607            .request(Method::GET, url)
1608            .headers(headers)
1609            .query(params);
1610
1611        let response = self.send(Path::AssetCoinQueryInfo, request).await?;
1612        Ok(response)
1613    }
1614
1615    /// Get Coin Exchange Records.
1616    ///
1617    /// Requires authentication.
1618    #[tracing::instrument(skip(self), err)]
1619    pub async fn get_exchange_order_record(
1620        &self,
1621        params: &GetExchangeOrderRecordParams,
1622    ) -> Result<Response<ExchangeOrderRecords>, Error> {
1623        let query = serialize_query(params)?;
1624        let url = format!(
1625            "{}{}?{query}",
1626            self.base_url,
1627            Path::AssetExchangeOrderRecord
1628        );
1629        let headers = self.get_signed_headers(&query);
1630
1631        let request = self.client.request(Method::GET, url).headers(headers);
1632
1633        let response = self.send(Path::AssetExchangeOrderRecord, request).await?;
1634        Ok(response)
1635    }
1636
1637    /// Get Delivery Record.
1638    /// Query delivery records of USDC futures and Options, sorted by descending delivery time.
1639    ///
1640    /// Requires authentication.
1641    #[tracing::instrument(skip(self), err)]
1642    pub async fn get_delivery_record(
1643        &self,
1644        params: &GetDeliveryRecordParams,
1645    ) -> Result<Response<CursorPagination<DeliveryRecord>>, Error> {
1646        let query = serialize_query(params)?;
1647        let url = format!("{}{}?{query}", self.base_url, Path::AssetDeliveryRecord);
1648        let headers = self.get_signed_headers(&query);
1649
1650        let request = self.client.request(Method::GET, url).headers(headers);
1651
1652        let response = self.send(Path::AssetDeliveryRecord, request).await?;
1653        Ok(response)
1654    }
1655
1656    /// Get USDC Session Settlement.
1657    /// Query session settlement records of USDC perpetual contracts.
1658    ///
1659    /// Requires authentication.
1660    #[tracing::instrument(skip(self), err)]
1661    pub async fn get_settlement_record(
1662        &self,
1663        params: &GetSettlementRecordParams,
1664    ) -> Result<Response<CursorPagination<SettlementRecord>>, Error> {
1665        let query = serialize_query(params)?;
1666        let url = format!("{}{}?{query}", self.base_url, Path::AssetSettlementRecord);
1667        let headers = self.get_signed_headers(&query);
1668
1669        let request = self.client.request(Method::GET, url).headers(headers);
1670
1671        let response = self.send(Path::AssetSettlementRecord, request).await?;
1672        Ok(response)
1673    }
1674
1675    /// Get Coin Greeks.
1676    /// Query the current account's Greeks information. Option only.
1677    ///
1678    /// Requires authentication.
1679    #[tracing::instrument(skip(self), err)]
1680    pub async fn get_coin_greeks(
1681        &self,
1682        params: &GetCoinGreeksParams,
1683    ) -> Result<Response<List<CoinGreeks>>, Error> {
1684        let query = serialize_query(params)?;
1685        let url = format!("{}{}?{query}", self.base_url, Path::AssetCoinGreeks);
1686        let headers = self.get_signed_headers(&query);
1687
1688        let request = self.client.request(Method::GET, url).headers(headers);
1689
1690        let response = self.send(Path::AssetCoinGreeks, request).await?;
1691        Ok(response)
1692    }
1693}
1694
1695// Spot Leveraged Token.
1696impl Client {
1697    /// Get Leverage Token Info.
1698    /// Query leveraged token information, such as purchase/redeem limits and fees.
1699    ///
1700    /// No authentication required.
1701    #[tracing::instrument(skip(self), err)]
1702    pub async fn get_leverage_token_info(
1703        &self,
1704        params: &GetLeverageTokenInfoParams,
1705    ) -> Result<Response<List<LeverageTokenInfo>>, Error> {
1706        let url = format!("{}{}", self.base_url, Path::SpotLeverTokenInfo);
1707
1708        let request = self.client.request(Method::GET, url).query(params);
1709
1710        let response = self.send(Path::SpotLeverTokenInfo, request).await?;
1711        Ok(response)
1712    }
1713
1714    /// Get Leverage Token Market.
1715    /// Query the leveraged token market data, such as net asset value and real leverage.
1716    ///
1717    /// No authentication required.
1718    #[tracing::instrument(skip(self), err)]
1719    pub async fn get_leverage_token_market(
1720        &self,
1721        params: &GetLeverageTokenMarketParams,
1722    ) -> Result<Response<LeverageTokenMarket>, Error> {
1723        let url = format!("{}{}", self.base_url, Path::SpotLeverTokenReference);
1724
1725        let request = self.client.request(Method::GET, url).query(params);
1726
1727        let response = self.send(Path::SpotLeverTokenReference, request).await?;
1728        Ok(response)
1729    }
1730
1731    /// Purchase.
1732    /// Purchase a leveraged token.
1733    ///
1734    /// Requires authentication.
1735    #[tracing::instrument(skip(self), err)]
1736    pub async fn purchase_leverage_token(
1737        &self,
1738        request: &PurchaseLeverageTokenRequest,
1739    ) -> Result<Response<PurchaseLeverageTokenResult>, Error> {
1740        let url = format!("{}{}", self.base_url, Path::SpotLeverTokenPurchase);
1741        let body = serialize_json(request)?;
1742        let headers = self.get_signed_headers(&body);
1743
1744        let request = self
1745            .client
1746            .request(Method::POST, url)
1747            .headers(headers)
1748            .body(body);
1749
1750        let response = self.send(Path::SpotLeverTokenPurchase, request).await?;
1751        Ok(response)
1752    }
1753
1754    /// Redeem.
1755    /// Redeem a leveraged token.
1756    ///
1757    /// Requires authentication.
1758    #[tracing::instrument(skip(self), err)]
1759    pub async fn redeem_leverage_token(
1760        &self,
1761        request: &RedeemLeverageTokenRequest,
1762    ) -> Result<Response<RedeemLeverageTokenResult>, Error> {
1763        let url = format!("{}{}", self.base_url, Path::SpotLeverTokenRedeem);
1764        let body = serialize_json(request)?;
1765        let headers = self.get_signed_headers(&body);
1766
1767        let request = self
1768            .client
1769            .request(Method::POST, url)
1770            .headers(headers)
1771            .body(body);
1772
1773        let response = self.send(Path::SpotLeverTokenRedeem, request).await?;
1774        Ok(response)
1775    }
1776
1777    /// Get Purchase/Redemption Records.
1778    ///
1779    /// Requires authentication.
1780    #[tracing::instrument(skip(self), err)]
1781    pub async fn get_leverage_token_order_records(
1782        &self,
1783        params: &GetLeverageTokenOrderRecordsParams,
1784    ) -> Result<Response<List<LeverageTokenOrderRecord>>, Error> {
1785        let query = serialize_query(params)?;
1786        let url = format!(
1787            "{}{}?{query}",
1788            self.base_url,
1789            Path::SpotLeverTokenOrderRecord
1790        );
1791        let headers = self.get_signed_headers(&query);
1792
1793        let request = self.client.request(Method::GET, url).headers(headers);
1794
1795        let response = self.send(Path::SpotLeverTokenOrderRecord, request).await?;
1796        Ok(response)
1797    }
1798}
1799
1800// Spot Margin Trade (UTA).
1801impl Client {
1802    /// Toggle Margin Trade.
1803    /// Turn spot margin trade on or off for the Unified account.
1804    ///
1805    /// Requires authentication. The account must have completed spot margin activation
1806    /// (including the required quiz) beforehand.
1807    #[tracing::instrument(skip(self), err)]
1808    pub async fn switch_spot_margin_mode(
1809        &self,
1810        request: &SwitchSpotMarginModeRequest,
1811    ) -> Result<Response<SwitchSpotMarginModeResult>, Error> {
1812        let url = format!("{}{}", self.base_url, Path::SpotMarginTradeSwitchMode);
1813        let body = serialize_json(request)?;
1814        let headers = self.get_signed_headers(&body);
1815
1816        let request = self
1817            .client
1818            .request(Method::POST, url)
1819            .headers(headers)
1820            .body(body);
1821
1822        let response = self.send(Path::SpotMarginTradeSwitchMode, request).await?;
1823        Ok(response)
1824    }
1825
1826    /// Set Leverage.
1827    /// Set the maximum leverage for spot margin trade.
1828    ///
1829    /// Requires authentication. The account must have completed spot margin activation
1830    /// (including the required quiz) beforehand.
1831    #[tracing::instrument(skip(self), err)]
1832    pub async fn set_spot_margin_leverage(
1833        &self,
1834        request: &SetSpotMarginLeverageRequest,
1835    ) -> Result<Response<EmptyResult>, Error> {
1836        let url = format!("{}{}", self.base_url, Path::SpotMarginTradeSetLeverage);
1837        let body = serialize_json(request)?;
1838        let headers = self.get_signed_headers(&body);
1839
1840        let request = self
1841            .client
1842            .request(Method::POST, url)
1843            .headers(headers)
1844            .body(body);
1845
1846        let response = self.send(Path::SpotMarginTradeSetLeverage, request).await?;
1847        Ok(response)
1848    }
1849}
1850
1851impl Client {
1852    /// Send a request against a category-independent rate-limit bucket, with a cost of 1.
1853    async fn send<T>(&self, path: Path, request: RequestBuilder) -> Result<Response<T>, Error>
1854    where
1855        T: serde::de::DeserializeOwned,
1856    {
1857        self.send_weighted(path, None, 1, request).await
1858    }
1859
1860    /// Send a request against the bucket identified by `path` and `category`, consuming
1861    /// `cost` units of that bucket's quota. Use `cost > 1` for batch endpoints, where Bybit
1862    /// consumes one unit per order in the batch rather than one per request.
1863    async fn send_weighted<T>(
1864        &self,
1865        path: Path,
1866        category: Option<Category>,
1867        cost: u32,
1868        request: RequestBuilder,
1869    ) -> Result<Response<T>, Error>
1870    where
1871        T: serde::de::DeserializeOwned,
1872    {
1873        let key = RateLimitKey { path, category };
1874
1875        if let Some(rl) = &self.rate_limiter {
1876            rl.check(key, cost)
1877                .map_err(|retry_after_ms| Error::RateLimited { retry_after_ms })?;
1878        }
1879
1880        let start = std::time::Instant::now();
1881        let response = request.send().await?;
1882        let elapsed_ms = start.elapsed().as_millis();
1883        let headers = parse_headers(response.headers());
1884
1885        if let Some(rl) = &self.rate_limiter {
1886            rl.update(
1887                key,
1888                headers.api_limit_status,
1889                headers.api_limit_reset_timestamp,
1890            );
1891        }
1892
1893        let json = response.text().await?;
1894        if !headers.is_ret_code_ok() {
1895            let msg: APIErrorResponse = deserialize_json(&json)?;
1896            tracing::debug!(elapsed_ms, ret_code = msg.ret_code, "api error response");
1897            return Err(msg.into());
1898        }
1899
1900        tracing::debug!(
1901            elapsed_ms,
1902            api_limit = headers.api_limit,
1903            api_limit_status = headers.api_limit_status,
1904            "api call completed"
1905        );
1906        let response: Resp<_> = deserialize_json(&json)?;
1907        let response = Response {
1908            result: response.result,
1909            time: response.time,
1910            headers,
1911            ret_ext_info: response.ret_ext_info,
1912        };
1913        Ok(response)
1914    }
1915}
1916
1917/// Parse response headers: ret_code, traceid, timenow, X-Bapi-Limit, X-Bapi-Limit-Status, X-Bapi-Limit-Reset-Timestamp
1918fn parse_headers(headers: &HeaderMap) -> Headers {
1919    let ret_code = headers
1920        .get(HEADER_RET_CODE)
1921        .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
1922    let trace_id = headers
1923        .get(HEADER_TRACE_ID)
1924        .and_then(|h| h.to_str().map(|str| str.into()).ok());
1925    let time_now = headers
1926        .get(HEADER_TIME_NOW)
1927        .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
1928
1929    let api_limit = headers
1930        .get(HEADER_X_BAPI_LIMIT)
1931        .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
1932    let api_limit_status = headers
1933        .get(HEADER_X_BAPI_LIMIT_STATUS)
1934        .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
1935    let api_limit_reset_timestamp = headers
1936        .get(HEADER_X_BAPI_LIMIT_RESET_TIMESTAMP)
1937        .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
1938
1939    Headers {
1940        ret_code,
1941        trace_id,
1942        time_now,
1943        api_limit,
1944        api_limit_status,
1945        api_limit_reset_timestamp,
1946    }
1947}