Skip to main content

bybit/http/
asset.rs

1use rust_decimal::{Decimal, serde::str_option::deserialize as option_decimal};
2use serde::{Deserialize, Serialize};
3use serde_aux::prelude::{
4    deserialize_number_from_string as number,
5    deserialize_option_number_from_string as option_number,
6};
7
8use crate::{
9    AccountType, DepositStatus, Side, Timestamp, TransferStatus, WithdrawStatus, enums::Category,
10    serde::empty_string_as_none,
11};
12
13// ════════════════════════════════════════ Transfer ════════════════════════════════════════
14
15// --- Create Internal Transfer ---
16
17/// Request body for [`Client::internal_transfer`](crate::http::Client::internal_transfer).
18#[derive(Debug, Serialize, Clone)]
19#[serde(rename_all = "camelCase")]
20pub struct InternalTransferRequest {
21    /// UUID. Please manually generate a UUID and ensure it is unique. Used to prevent duplicate transfers
22    pub transfer_id: String,
23    /// Coin name, uppercase only
24    pub coin: String,
25    /// Transfer amount
26    pub amount: Decimal,
27    /// From account type.
28    pub from_account_type: AccountType,
29    /// To account type.
30    pub to_account_type: AccountType,
31}
32
33impl InternalTransferRequest {
34    /// Create with the required fields; set the optional ones with the `with_*` methods.
35    pub fn new(
36        transfer_id: impl Into<String>,
37        coin: impl Into<String>,
38        amount: Decimal,
39        from_account_type: AccountType,
40        to_account_type: AccountType,
41    ) -> Self {
42        let transfer_id: String = transfer_id.into();
43        let coin: String = coin.into();
44        Self {
45            transfer_id,
46            coin,
47            amount,
48            from_account_type,
49            to_account_type,
50        }
51    }
52}
53
54/// Response for [`Client::internal_transfer`](crate::http::Client::internal_transfer) and
55/// [`Client::universal_transfer`](crate::http::Client::universal_transfer).
56#[derive(Debug, Deserialize, PartialEq)]
57#[serde(rename_all = "camelCase")]
58pub struct TransferResult {
59    /// UUID.
60    pub transfer_id: String,
61    /// Transfer status STATUS_UNKNOWN SUCCESS PENDING FAILED.
62    pub status: TransferStatus,
63}
64
65// --- Get Internal Transfer Records ---
66
67/// Query params for
68/// [`Client::get_internal_transfer_records`](crate::http::Client::get_internal_transfer_records).
69#[derive(Clone, Debug, Serialize)]
70#[serde(rename_all = "camelCase")]
71pub struct GetInternalTransferRecordsParams {
72    /// UUID. Use the one you generated in createTransfer.
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub transfer_id: Option<String>,
75    /// Coin, uppercase only.
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub coin: Option<String>,
78    /// Transfer status.
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub status: Option<TransferStatus>,
81    /// Without start_time and end_time, returns the past 7 days by default
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub start_time: Option<Timestamp>,
84    /// The end timestamp (ms) Note: the query logic is actually effective based on second level.
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub end_time: Option<Timestamp>,
87    /// Limit for data size per page. [1, 50]. Default: 20
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub limit: Option<u64>,
90    /// Cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub cursor: Option<String>,
93}
94
95impl GetInternalTransferRecordsParams {
96    /// Create with the required fields; set the optional ones with the `with_*` methods.
97    pub fn new() -> Self {
98        Self {
99            transfer_id: None,
100            coin: None,
101            status: None,
102            start_time: None,
103            end_time: None,
104            limit: None,
105            cursor: None,
106        }
107    }
108
109    /// Set `transfer_id`: uUID. Use the one you generated in createTransfer.
110    pub fn with_transfer_id(mut self, v: impl Into<String>) -> Self {
111        self.transfer_id = Some(v.into());
112        self
113    }
114    /// Set `coin`: coin, uppercase only.
115    pub fn with_coin(mut self, v: impl Into<String>) -> Self {
116        self.coin = Some(v.into());
117        self
118    }
119    /// Set `status`: transfer status.
120    pub fn with_status(mut self, v: TransferStatus) -> Self {
121        self.status = Some(v);
122        self
123    }
124    /// Set `start_time`: without start_time and end_time, returns the past 7 days by default.
125    pub fn with_start_time(mut self, v: Timestamp) -> Self {
126        self.start_time = Some(v);
127        self
128    }
129    /// Set `end_time`: the end timestamp (ms) Note: the query logic is actually effective based on second level.
130    pub fn with_end_time(mut self, v: Timestamp) -> Self {
131        self.end_time = Some(v);
132        self
133    }
134    /// Set `limit`: limit for data size per page. [1, 50]. Default: 20.
135    pub fn with_limit(mut self, v: u64) -> Self {
136        self.limit = Some(v);
137        self
138    }
139    /// Set `cursor`: cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
140    pub fn with_cursor(mut self, v: impl Into<String>) -> Self {
141        self.cursor = Some(v.into());
142        self
143    }
144}
145
146impl Default for GetInternalTransferRecordsParams {
147    fn default() -> Self {
148        Self::new()
149    }
150}
151
152/// Item of the list returned by `GET /v5/asset/transfer/query-inter-transfer-list` ([`Client::get_internal_transfer_records`](crate::http::Client::get_internal_transfer_records)).
153#[derive(Debug, Deserialize, PartialEq)]
154#[serde(rename_all = "camelCase")]
155pub struct InternalTransferEntry {
156    /// Transfer ID.
157    pub transfer_id: String,
158    /// Transferred coin.
159    pub coin: String,
160    /// Transferred amount.
161    pub amount: Decimal,
162    /// From account type.
163    pub from_account_type: AccountType,
164    /// To account type.
165    pub to_account_type: AccountType,
166    /// Transfer created timestamp (ms).
167    #[serde(deserialize_with = "number")]
168    pub timestamp: Timestamp,
169    /// Transfer status.
170    pub status: TransferStatus,
171}
172
173// --- Create Universal Transfer ---
174
175/// Request body for [`Client::universal_transfer`](crate::http::Client::universal_transfer).
176///
177/// Transfers funds between a master account and its sub-accounts (or between two sub-accounts).
178/// Requires the master UID's API key, or a sub-account key with the `SubMemberTransferList`
179/// permission (which may only transfer to the master account).
180#[derive(Debug, Serialize, Clone)]
181#[serde(rename_all = "camelCase")]
182pub struct UniversalTransferRequest {
183    /// UUID. Please manually generate a UUID and ensure it is unique. Used to prevent duplicate transfers
184    pub transfer_id: String,
185    /// Coin name, uppercase only
186    pub coin: String,
187    /// Transfer amount
188    pub amount: Decimal,
189    /// Source UID
190    pub from_member_id: i64,
191    /// Destination UID
192    pub to_member_id: i64,
193    /// From account type.
194    pub from_account_type: AccountType,
195    /// To account type.
196    pub to_account_type: AccountType,
197}
198
199impl UniversalTransferRequest {
200    /// Create with the required fields; set the optional ones with the `with_*` methods.
201    pub fn new(
202        transfer_id: impl Into<String>,
203        coin: impl Into<String>,
204        amount: Decimal,
205        from_member_id: i64,
206        to_member_id: i64,
207        from_account_type: AccountType,
208        to_account_type: AccountType,
209    ) -> Self {
210        let transfer_id: String = transfer_id.into();
211        let coin: String = coin.into();
212        Self {
213            transfer_id,
214            coin,
215            amount,
216            from_member_id,
217            to_member_id,
218            from_account_type,
219            to_account_type,
220        }
221    }
222}
223
224// --- Get Universal Transfer Records ---
225
226/// Query params for
227/// [`Client::get_universal_transfer_records`](crate::http::Client::get_universal_transfer_records).
228#[derive(Clone, Debug, Serialize)]
229#[serde(rename_all = "camelCase")]
230pub struct GetUniversalTransferRecordsParams {
231    /// UUID. Use the one you generated in createTransfer.
232    #[serde(skip_serializing_if = "Option::is_none")]
233    pub transfer_id: Option<String>,
234    /// Coin, uppercase only.
235    #[serde(skip_serializing_if = "Option::is_none")]
236    pub coin: Option<String>,
237    /// Transfer status. SUCCESS, FAILED, PENDING.
238    #[serde(skip_serializing_if = "Option::is_none")]
239    pub status: Option<TransferStatus>,
240    /// Without start_time and end_time, returns the past 7 days by default
241    #[serde(skip_serializing_if = "Option::is_none")]
242    pub start_time: Option<Timestamp>,
243    /// The end timestamp (ms) Note: the query logic is actually effective based on second level.
244    #[serde(skip_serializing_if = "Option::is_none")]
245    pub end_time: Option<Timestamp>,
246    /// Limit for data size per page. [1, 50]. Default: 20
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub limit: Option<u64>,
249    /// Cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub cursor: Option<String>,
252}
253
254impl GetUniversalTransferRecordsParams {
255    /// Create with the required fields; set the optional ones with the `with_*` methods.
256    pub fn new() -> Self {
257        Self {
258            transfer_id: None,
259            coin: None,
260            status: None,
261            start_time: None,
262            end_time: None,
263            limit: None,
264            cursor: None,
265        }
266    }
267
268    /// Set `transfer_id`: uUID. Use the one you generated in createTransfer.
269    pub fn with_transfer_id(mut self, v: impl Into<String>) -> Self {
270        self.transfer_id = Some(v.into());
271        self
272    }
273    /// Set `coin`: coin, uppercase only.
274    pub fn with_coin(mut self, v: impl Into<String>) -> Self {
275        self.coin = Some(v.into());
276        self
277    }
278    /// Set `status`: transfer status. SUCCESS, FAILED, PENDING.
279    pub fn with_status(mut self, v: TransferStatus) -> Self {
280        self.status = Some(v);
281        self
282    }
283    /// Set `start_time`: without start_time and end_time, returns the past 7 days by default.
284    pub fn with_start_time(mut self, v: Timestamp) -> Self {
285        self.start_time = Some(v);
286        self
287    }
288    /// Set `end_time`: the end timestamp (ms) Note: the query logic is actually effective based on second level.
289    pub fn with_end_time(mut self, v: Timestamp) -> Self {
290        self.end_time = Some(v);
291        self
292    }
293    /// Set `limit`: limit for data size per page. [1, 50]. Default: 20.
294    pub fn with_limit(mut self, v: u64) -> Self {
295        self.limit = Some(v);
296        self
297    }
298    /// Set `cursor`: cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
299    pub fn with_cursor(mut self, v: impl Into<String>) -> Self {
300        self.cursor = Some(v.into());
301        self
302    }
303}
304
305impl Default for GetUniversalTransferRecordsParams {
306    fn default() -> Self {
307        Self::new()
308    }
309}
310
311/// Item of the list returned by `GET /v5/asset/transfer/query-universal-transfer-list` ([`Client::get_universal_transfer_records`](crate::http::Client::get_universal_transfer_records)).
312#[derive(Debug, Deserialize, PartialEq)]
313#[serde(rename_all = "camelCase")]
314pub struct UniversalTransferEntry {
315    /// Transfer ID.
316    pub transfer_id: String,
317    /// Transferred coin.
318    pub coin: String,
319    /// Transferred amount.
320    pub amount: Decimal,
321    /// From UID.
322    pub from_member_id: String,
323    /// To UID.
324    pub to_member_id: String,
325    /// From account type.
326    pub from_account_type: AccountType,
327    /// To account type.
328    pub to_account_type: AccountType,
329    /// Transfer created timestamp (ms).
330    #[serde(deserialize_with = "number")]
331    pub timestamp: Timestamp,
332    /// Transfer status.
333    pub status: TransferStatus,
334}
335
336// --- Get Transferable Coin ---
337
338/// Query params for
339/// [`Client::get_transferable_coins`](crate::http::Client::get_transferable_coins).
340#[derive(Debug, Serialize, Clone)]
341#[serde(rename_all = "camelCase")]
342pub struct GetTransferableCoinsParams {
343    /// From account type.
344    pub from_account_type: AccountType,
345    /// To account type.
346    pub to_account_type: AccountType,
347}
348
349impl GetTransferableCoinsParams {
350    /// Create with the required fields; set the optional ones with the `with_*` methods.
351    pub fn new(from_account_type: AccountType, to_account_type: AccountType) -> Self {
352        Self {
353            from_account_type,
354            to_account_type,
355        }
356    }
357}
358
359// --- Enable Universal Transfer For Sub UID (deprecated by Bybit; all sub UIDs are now
360// automatically enabled) ---
361
362/// Request body for
363/// [`Client::save_transfer_sub_member`](crate::http::Client::save_transfer_sub_member).
364///
365/// Deprecated by Bybit: sub UIDs no longer need to be configured, as all of them are now
366/// automatically enabled for universal transfer. Kept for completeness.
367#[derive(Debug, Serialize, Clone)]
368#[serde(rename_all = "camelCase")]
369pub struct SaveTransferSubMemberRequest {
370    /// Sub UIDs, separated by comma, e.g. "uid1,uid2,uid3"
371    pub sub_member_ids: String,
372}
373
374impl SaveTransferSubMemberRequest {
375    /// Create with the required fields; set the optional ones with the `with_*` methods.
376    pub fn new(sub_member_ids: impl Into<String>) -> Self {
377        let sub_member_ids: String = sub_member_ids.into();
378        Self { sub_member_ids }
379    }
380}
381
382// --- Get Sub UID (for universal transfer) ---
383
384/// Response for
385/// [`Client::get_transferable_sub_members`](crate::http::Client::get_transferable_sub_members).
386#[derive(Debug, Deserialize, PartialEq)]
387#[serde(rename_all = "camelCase")]
388pub struct TransferableSubMembers {
389    /// All sub UIDs of the master account
390    pub sub_member_ids: Vec<String>,
391    /// Sub UIDs enabled for universal transfer
392    pub transferable_sub_member_ids: Vec<String>,
393}
394
395// --- Get Single Coin Balance ---
396
397/// Query params for
398/// [`Client::get_account_coin_balance`](crate::http::Client::get_account_coin_balance).
399#[derive(Clone, Debug, Serialize)]
400#[serde(rename_all = "camelCase")]
401pub struct GetAccountCoinBalanceParams {
402    /// Account type.
403    pub account_type: AccountType,
404    /// Coin name, uppercase only
405    pub coin: String,
406    /// Master UID's api key can query itself or its sub UIDs' coin balance. If not passed,
407    /// then by default it queries master UID's coin balance
408    #[serde(skip_serializing_if = "Option::is_none")]
409    pub member_id: Option<String>,
410    /// Used together with `to_account_type` to query the balance safe to transfer between two
411    /// UIDs' wallets
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub to_member_id: Option<String>,
414    /// To account type. Required when querying the transferable balance between different account types.
415    #[serde(skip_serializing_if = "Option::is_none")]
416    pub to_account_type: Option<AccountType>,
417    /// Whether to query bonus. 0: not query, 1: query. Valid for `account_type=FUND`. Default: 0
418    #[serde(skip_serializing_if = "Option::is_none")]
419    pub with_bonus: Option<i64>,
420    /// Whether to query the available amount that is safe to withdraw or transfer.
421    /// 0(default): not query, 1: query
422    #[serde(skip_serializing_if = "Option::is_none")]
423    pub with_transfer_safe_amount: Option<i64>,
424    /// Whether to query the available amount that is safe to transfer under the risk level of
425    /// the OTC loan account. 0(default): not query, 1: query
426    #[serde(skip_serializing_if = "Option::is_none")]
427    pub with_ltv_transfer_safe_amount: Option<i64>,
428}
429
430impl GetAccountCoinBalanceParams {
431    /// Create with the required fields; set the optional ones with the `with_*` methods.
432    pub fn new(account_type: AccountType, coin: impl Into<String>) -> Self {
433        let coin: String = coin.into();
434        Self {
435            account_type,
436            coin,
437            member_id: None,
438            to_member_id: None,
439            to_account_type: None,
440            with_bonus: None,
441            with_transfer_safe_amount: None,
442            with_ltv_transfer_safe_amount: None,
443        }
444    }
445
446    /// Set `member_id`: master UID's api key can query itself or its sub UIDs' coin balance. If not passed, then by default it queries master UID's coin balance.
447    pub fn with_member_id(mut self, v: impl Into<String>) -> Self {
448        self.member_id = Some(v.into());
449        self
450    }
451    /// Set `to_member_id`: used together with `to_account_type` to query the balance safe to transfer between two UIDs' wallets.
452    pub fn with_to_member_id(mut self, v: impl Into<String>) -> Self {
453        self.to_member_id = Some(v.into());
454        self
455    }
456    /// Set `to_account_type`: to account type. Required when querying the transferable balance between different account types.
457    pub fn with_to_account_type(mut self, v: AccountType) -> Self {
458        self.to_account_type = Some(v);
459        self
460    }
461    /// Set `withBonus`: 1 includes the bonus in the balance.
462    pub fn with_bonus(mut self, v: i64) -> Self {
463        self.with_bonus = Some(v);
464        self
465    }
466    /// Set `withTransferSafeAmount`: 1 also returns the amount that can be transferred safely.
467    pub fn with_transfer_safe_amount(mut self, v: i64) -> Self {
468        self.with_transfer_safe_amount = Some(v);
469        self
470    }
471    /// Set `withLtvTransferSafeAmount`: 1 also returns the safely transferable amount of an institutional loan account.
472    pub fn with_ltv_transfer_safe_amount(mut self, v: i64) -> Self {
473        self.with_ltv_transfer_safe_amount = Some(v);
474        self
475    }
476}
477
478/// Result of `GET /v5/asset/transfer/query-account-coin-balance` ([`Client::get_account_coin_balance`](crate::http::Client::get_account_coin_balance)).
479#[derive(Debug, Deserialize, PartialEq)]
480#[serde(rename_all = "camelCase")]
481pub struct AccountCoinBalance {
482    /// Account type.
483    pub account_type: AccountType,
484    /// Biz type.
485    pub biz_type: i64,
486    /// Account ID.
487    pub account_id: String,
488    /// Uid.
489    pub member_id: String,
490    /// Total asset (margin coins converted to USDT). When liqStatus != 0, empty string is returned.
491    pub balance: CoinBalance,
492}
493
494/// Part of the response of `/v5/asset/transfer/query-account-coin-balance`.
495#[derive(Debug, Deserialize, PartialEq)]
496#[serde(rename_all = "camelCase")]
497pub struct CoinBalance {
498    /// Coin.
499    pub coin: String,
500    /// Wallet balance.
501    pub wallet_balance: Decimal,
502    /// Transferable balance.
503    pub transfer_balance: Decimal,
504    /// Bonus.
505    pub bonus: Decimal,
506    /// Safe amount to transfer. Keep "" if not query.
507    #[serde(default, deserialize_with = "option_decimal")]
508    pub transfer_safe_amount: Option<Decimal>,
509    /// Transferable amount for ins loan account. Keep "" if not query.
510    #[serde(default, deserialize_with = "option_decimal")]
511    pub ltv_transfer_safe_amount: Option<Decimal>,
512}
513
514// --- Get Asset Info (Spot) ---
515
516/// Query params for [`Client::get_asset_info`](crate::http::Client::get_asset_info).
517#[derive(Debug, Serialize, Clone)]
518#[serde(rename_all = "camelCase")]
519pub struct GetAssetInfoParams {
520    /// Account type. Only `SPOT` is supported
521    pub account_type: AccountType,
522    /// Coin.
523    #[serde(skip_serializing_if = "Option::is_none")]
524    pub coin: Option<String>,
525}
526
527impl GetAssetInfoParams {
528    /// Create with the required fields; set the optional ones with the `with_*` methods.
529    pub fn new(account_type: AccountType) -> Self {
530        Self {
531            account_type,
532            coin: None,
533        }
534    }
535
536    /// Set `coin`: coin.
537    pub fn with_coin(mut self, v: impl Into<String>) -> Self {
538        self.coin = Some(v.into());
539        self
540    }
541}
542
543/// Result of `GET /v5/asset/transfer/query-asset-info` ([`Client::get_asset_info`](crate::http::Client::get_asset_info)).
544#[derive(Debug, Deserialize, PartialEq)]
545#[serde(rename_all = "camelCase")]
546pub struct AssetInfoResult {
547    /// Object. Earning for Spot trading. If do not have any rebate, keep empty array.
548    pub spot: SpotAssetInfo,
549}
550
551/// Part of the response of `/v5/asset/transfer/query-asset-info`.
552#[derive(Debug, Deserialize, PartialEq)]
553#[serde(rename_all = "camelCase")]
554pub struct SpotAssetInfo {
555    /// `ACCOUNT_STATUS_NORMAL` or `ACCOUNT_STATUS_UNSPECIFIED`
556    pub status: String,
557    /// Assets by coin.
558    pub assets: Vec<SpotAsset>,
559}
560
561/// Part of the response of `/v5/asset/transfer/query-asset-info`.
562#[derive(Debug, Deserialize, PartialEq)]
563#[serde(rename_all = "camelCase")]
564pub struct SpotAsset {
565    /// Coin.
566    pub coin: String,
567    /// Amount locked in active orders
568    pub frozen: Decimal,
569    /// Available balance
570    pub free: Decimal,
571    /// Amount currently being withdrawn
572    pub withdraw: Decimal,
573}
574
575// ════════════════════════════════════════ Deposit ════════════════════════════════════════
576
577// --- Get Allowed Deposit Coin Info ---
578
579/// Query params for
580/// [`Client::get_deposit_allowed_coin_info`](crate::http::Client::get_deposit_allowed_coin_info).
581#[derive(Clone, Debug, Serialize)]
582#[serde(rename_all = "camelCase")]
583pub struct GetDepositAllowedCoinInfoParams {
584    /// Coin name, uppercase only. Must be paired with `chain` if passed
585    #[serde(skip_serializing_if = "Option::is_none")]
586    pub coin: Option<String>,
587    /// Chain name. Must be paired with `coin` if passed
588    #[serde(skip_serializing_if = "Option::is_none")]
589    pub chain: Option<String>,
590    /// Limit for data size per page. [1, 35]. Default: 10
591    #[serde(skip_serializing_if = "Option::is_none")]
592    pub limit: Option<u64>,
593    /// Cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
594    #[serde(skip_serializing_if = "Option::is_none")]
595    pub cursor: Option<String>,
596}
597
598impl GetDepositAllowedCoinInfoParams {
599    /// Create with the required fields; set the optional ones with the `with_*` methods.
600    pub fn new() -> Self {
601        Self {
602            coin: None,
603            chain: None,
604            limit: None,
605            cursor: None,
606        }
607    }
608
609    /// Set `coin`: coin name, uppercase only. Must be paired with `chain` if passed.
610    pub fn with_coin(mut self, v: impl Into<String>) -> Self {
611        self.coin = Some(v.into());
612        self
613    }
614    /// Set `chain`: chain name. Must be paired with `coin` if passed.
615    pub fn with_chain(mut self, v: impl Into<String>) -> Self {
616        self.chain = Some(v.into());
617        self
618    }
619    /// Set `limit`: limit for data size per page. [1, 35]. Default: 10.
620    pub fn with_limit(mut self, v: u64) -> Self {
621        self.limit = Some(v);
622        self
623    }
624    /// Set `cursor`: cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
625    pub fn with_cursor(mut self, v: impl Into<String>) -> Self {
626        self.cursor = Some(v.into());
627        self
628    }
629}
630
631impl Default for GetDepositAllowedCoinInfoParams {
632    fn default() -> Self {
633        Self::new()
634    }
635}
636
637/// Result of `GET /v5/asset/deposit/query-allowed-list` ([`Client::get_deposit_allowed_coin_info`](crate::http::Client::get_deposit_allowed_coin_info)).
638#[derive(Debug, Deserialize, PartialEq)]
639#[serde(rename_all = "camelCase")]
640pub struct DepositAllowedCoinInfoResult {
641    /// Coins and chains that accept deposits.
642    pub config_list: Vec<DepositCoinConfig>,
643    /// Refer to the cursor request parameter.
644    #[serde(default, deserialize_with = "empty_string_as_none")]
645    pub next_page_cursor: Option<String>,
646}
647
648/// Part of the response of `/v5/asset/deposit/query-allowed-list`.
649#[derive(Debug, Deserialize, PartialEq)]
650#[serde(rename_all = "camelCase")]
651pub struct DepositCoinConfig {
652    /// Coin.
653    pub coin: String,
654    /// Chain.
655    pub chain: String,
656    /// Display name of the coin.
657    pub coin_show_name: String,
658    /// Chain type.
659    pub chain_type: String,
660    /// Number of block confirmations for the deposit to be credited.
661    #[serde(deserialize_with = "number")]
662    pub block_confirm_number: u32,
663    /// Minimum deposit amount.
664    pub min_deposit_amount: Decimal,
665}
666
667// --- Get Deposit Records / Get Sub Deposit Records ---
668
669/// Query params for
670/// [`Client::get_deposit_records`](crate::http::Client::get_deposit_records).
671#[derive(Clone, Debug, Serialize)]
672#[serde(rename_all = "camelCase")]
673pub struct GetDepositRecordsParams {
674    /// Internal ID: Can be used to uniquely identify and filter the deposit. When combined with other parameters, this field takes the highest priority.
675    #[serde(skip_serializing_if = "Option::is_none")]
676    pub id: Option<String>,
677    /// Not queryable for records before 2024-01-01
678    #[serde(skip_serializing_if = "Option::is_none")]
679    pub tx_id: Option<String>,
680    /// Coin, uppercase only.
681    #[serde(skip_serializing_if = "Option::is_none")]
682    pub coin: Option<String>,
683    /// The start timestamp (ms) Note: the query logic is actually effective based on second level.
684    #[serde(skip_serializing_if = "Option::is_none")]
685    pub start_time: Option<Timestamp>,
686    /// The end timestamp (ms) Note: the query logic is actually effective based on second level.
687    #[serde(skip_serializing_if = "Option::is_none")]
688    pub end_time: Option<Timestamp>,
689    /// Limit for data size per page. [1, 50]. Default: 50
690    #[serde(skip_serializing_if = "Option::is_none")]
691    pub limit: Option<u64>,
692    /// Cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
693    #[serde(skip_serializing_if = "Option::is_none")]
694    pub cursor: Option<String>,
695}
696
697impl GetDepositRecordsParams {
698    /// Create with the required fields; set the optional ones with the `with_*` methods.
699    pub fn new() -> Self {
700        Self {
701            id: None,
702            tx_id: None,
703            coin: None,
704            start_time: None,
705            end_time: None,
706            limit: None,
707            cursor: None,
708        }
709    }
710
711    /// Set `id`: internal ID: Can be used to uniquely identify and filter the deposit. When combined with other parameters, this field takes the highest priority.
712    pub fn with_id(mut self, v: impl Into<String>) -> Self {
713        self.id = Some(v.into());
714        self
715    }
716    /// Set `tx_id`: not queryable for records before 2024-01-01.
717    pub fn with_tx_id(mut self, v: impl Into<String>) -> Self {
718        self.tx_id = Some(v.into());
719        self
720    }
721    /// Set `coin`: coin, uppercase only.
722    pub fn with_coin(mut self, v: impl Into<String>) -> Self {
723        self.coin = Some(v.into());
724        self
725    }
726    /// Set `start_time`: the start timestamp (ms) Note: the query logic is actually effective based on second level.
727    pub fn with_start_time(mut self, v: Timestamp) -> Self {
728        self.start_time = Some(v);
729        self
730    }
731    /// Set `end_time`: the end timestamp (ms) Note: the query logic is actually effective based on second level.
732    pub fn with_end_time(mut self, v: Timestamp) -> Self {
733        self.end_time = Some(v);
734        self
735    }
736    /// Set `limit`: limit for data size per page. [1, 50]. Default: 50.
737    pub fn with_limit(mut self, v: u64) -> Self {
738        self.limit = Some(v);
739        self
740    }
741    /// Set `cursor`: cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
742    pub fn with_cursor(mut self, v: impl Into<String>) -> Self {
743        self.cursor = Some(v.into());
744        self
745    }
746}
747
748impl Default for GetDepositRecordsParams {
749    fn default() -> Self {
750        Self::new()
751    }
752}
753
754/// Query params for
755/// [`Client::get_sub_deposit_records`](crate::http::Client::get_sub_deposit_records).
756#[derive(Clone, Debug, Serialize)]
757#[serde(rename_all = "camelCase")]
758pub struct GetSubDepositRecordsParams {
759    /// Sub UID.
760    pub sub_member_id: String,
761    /// Internal ID: Can be used to uniquely identify and filter the deposit. When combined with other parameters, this field takes the highest priority.
762    #[serde(skip_serializing_if = "Option::is_none")]
763    pub id: Option<String>,
764    /// Transaction id (hash) on the chain.
765    #[serde(skip_serializing_if = "Option::is_none")]
766    pub tx_id: Option<String>,
767    /// Coin, uppercase only.
768    #[serde(skip_serializing_if = "Option::is_none")]
769    pub coin: Option<String>,
770    /// The start timestamp (ms) Note: the query logic is actually effective based on second level.
771    #[serde(skip_serializing_if = "Option::is_none")]
772    pub start_time: Option<Timestamp>,
773    /// The end timestamp (ms) Note: the query logic is actually effective based on second level.
774    #[serde(skip_serializing_if = "Option::is_none")]
775    pub end_time: Option<Timestamp>,
776    /// Limit for data size per page. [1, 50]. Default: 50
777    #[serde(skip_serializing_if = "Option::is_none")]
778    pub limit: Option<u64>,
779    /// Cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
780    #[serde(skip_serializing_if = "Option::is_none")]
781    pub cursor: Option<String>,
782}
783
784impl GetSubDepositRecordsParams {
785    /// Create with the required fields; set the optional ones with the `with_*` methods.
786    pub fn new(sub_member_id: impl Into<String>) -> Self {
787        let sub_member_id: String = sub_member_id.into();
788        Self {
789            sub_member_id,
790            id: None,
791            tx_id: None,
792            coin: None,
793            start_time: None,
794            end_time: None,
795            limit: None,
796            cursor: None,
797        }
798    }
799
800    /// Set `id`: internal ID: Can be used to uniquely identify and filter the deposit. When combined with other parameters, this field takes the highest priority.
801    pub fn with_id(mut self, v: impl Into<String>) -> Self {
802        self.id = Some(v.into());
803        self
804    }
805    /// Set `tx_id`: transaction id (hash) on the chain.
806    pub fn with_tx_id(mut self, v: impl Into<String>) -> Self {
807        self.tx_id = Some(v.into());
808        self
809    }
810    /// Set `coin`: coin, uppercase only.
811    pub fn with_coin(mut self, v: impl Into<String>) -> Self {
812        self.coin = Some(v.into());
813        self
814    }
815    /// Set `start_time`: the start timestamp (ms) Note: the query logic is actually effective based on second level.
816    pub fn with_start_time(mut self, v: Timestamp) -> Self {
817        self.start_time = Some(v);
818        self
819    }
820    /// Set `end_time`: the end timestamp (ms) Note: the query logic is actually effective based on second level.
821    pub fn with_end_time(mut self, v: Timestamp) -> Self {
822        self.end_time = Some(v);
823        self
824    }
825    /// Set `limit`: limit for data size per page. [1, 50]. Default: 50.
826    pub fn with_limit(mut self, v: u64) -> Self {
827        self.limit = Some(v);
828        self
829    }
830    /// Set `cursor`: cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
831    pub fn with_cursor(mut self, v: impl Into<String>) -> Self {
832        self.cursor = Some(v.into());
833        self
834    }
835}
836
837/// Result of `GET /v5/asset/deposit/query-record` ([`Client::get_deposit_records`](crate::http::Client::get_deposit_records), [`Client::get_sub_deposit_records`](crate::http::Client::get_sub_deposit_records)).
838#[derive(Debug, Deserialize, PartialEq)]
839#[serde(rename_all = "camelCase")]
840pub struct DepositRecords {
841    /// Items.
842    pub rows: Vec<DepositRecord>,
843    /// Refer to the cursor request parameter.
844    #[serde(default, deserialize_with = "empty_string_as_none")]
845    pub next_page_cursor: Option<String>,
846}
847
848/// Part of the response of `/v5/asset/deposit/query-record`.
849#[derive(Debug, Deserialize, PartialEq)]
850#[serde(rename_all = "camelCase")]
851pub struct DepositRecord {
852    /// Unique ID.
853    pub id: String,
854    /// Coin.
855    pub coin: String,
856    /// Chain.
857    pub chain: String,
858    /// Amount.
859    pub amount: Decimal,
860    /// Transaction id (hash) on the chain.
861    #[serde(default, deserialize_with = "empty_string_as_none")]
862    pub tx_id: Option<String>,
863    /// Deposit status.
864    pub status: DepositStatus,
865    /// Deposit target address.
866    pub to_address: String,
867    /// Tag of deposit target address.
868    #[serde(default, deserialize_with = "empty_string_as_none")]
869    pub tag: Option<String>,
870    /// Deposit fee.
871    pub deposit_fee: Decimal,
872    /// Deposit's success time.
873    #[serde(deserialize_with = "option_number")]
874    pub success_at: Option<Timestamp>,
875    /// Number of confirmation blocks.
876    pub confirmations: String,
877    /// Transaction sequence number.
878    pub tx_index: String,
879    /// Hash number on the chain.
880    pub block_hash: String,
881    /// Daily deposit ceiling. "-1" means unlimited
882    pub batch_release_limit: String,
883    /// 0: normal, 10: limit reached, 20: flagged by AML, 50: blocked
884    pub deposit_type: i64,
885    /// From address of deposit, only shown when the deposit comes from on-chain and from address is unique, otherwise gives "".
886    #[serde(default, deserialize_with = "empty_string_as_none")]
887    pub from_address: Option<String>,
888    /// This field is used for tax purposes by Bybit EU (Austria) users, declare tax id.
889    #[serde(default, deserialize_with = "empty_string_as_none")]
890    pub tax_deposit_records_id: Option<String>,
891    /// 0: no tax reporting, 1: pending, 2: completed
892    #[serde(default)]
893    pub tax_status: Option<i64>,
894    /// 0: approved, 1: info needed, 2: pending, 3: rejected
895    #[serde(default)]
896    pub travel_rule_status: Option<i64>,
897}
898
899// --- Get Deposit Address / Get Sub Deposit Address ---
900
901/// Query params for
902/// [`Client::get_deposit_address`](crate::http::Client::get_deposit_address).
903#[derive(Debug, Serialize, Clone)]
904#[serde(rename_all = "camelCase")]
905pub struct GetDepositAddressParams {
906    /// Coin name, uppercase only
907    pub coin: String,
908    /// Chain type, from [`Client::get_coin_info`](crate::http::Client::get_coin_info)
909    #[serde(skip_serializing_if = "Option::is_none")]
910    pub chain_type: Option<String>,
911}
912
913impl GetDepositAddressParams {
914    /// Create with the required fields; set the optional ones with the `with_*` methods.
915    pub fn new(coin: impl Into<String>) -> Self {
916        let coin: String = coin.into();
917        Self {
918            coin,
919            chain_type: None,
920        }
921    }
922
923    /// Set `chain_type`: chain type, from [`Client::get_coin_info`](crate::http::Client::get_coin_info).
924    pub fn with_chain_type(mut self, v: impl Into<String>) -> Self {
925        self.chain_type = Some(v.into());
926        self
927    }
928}
929
930/// Query params for
931/// [`Client::get_sub_deposit_address`](crate::http::Client::get_sub_deposit_address).
932///
933/// Requires the master account API key.
934#[derive(Debug, Serialize, Clone)]
935#[serde(rename_all = "camelCase")]
936pub struct GetSubDepositAddressParams {
937    /// Coin name, uppercase only
938    pub coin: String,
939    /// Chain type, from [`Client::get_coin_info`](crate::http::Client::get_coin_info)
940    pub chain_type: String,
941    /// Sub user ID.
942    pub sub_member_id: String,
943}
944
945impl GetSubDepositAddressParams {
946    /// Create with the required fields; set the optional ones with the `with_*` methods.
947    pub fn new(
948        coin: impl Into<String>,
949        chain_type: impl Into<String>,
950        sub_member_id: impl Into<String>,
951    ) -> Self {
952        let coin: String = coin.into();
953        let chain_type: String = chain_type.into();
954        let sub_member_id: String = sub_member_id.into();
955        Self {
956            coin,
957            chain_type,
958            sub_member_id,
959        }
960    }
961}
962
963/// Result of `GET /v5/asset/deposit/query-address` ([`Client::get_deposit_address`](crate::http::Client::get_deposit_address), [`Client::get_sub_deposit_address`](crate::http::Client::get_sub_deposit_address)).
964#[derive(Debug, Deserialize, PartialEq)]
965#[serde(rename_all = "camelCase")]
966pub struct DepositAddress {
967    /// Coin.
968    pub coin: String,
969    /// Deposit addresses by chain.
970    pub chains: Vec<DepositChain>,
971}
972
973/// Part of the response of `/v5/asset/deposit/query-address`.
974#[derive(Debug, Deserialize, PartialEq)]
975#[serde(rename_all = "camelCase")]
976pub struct DepositChain {
977    /// Chain type.
978    pub chain_type: String,
979    /// The address for deposit.
980    pub address_deposit: String,
981    /// Tag of deposit.
982    #[serde(default, deserialize_with = "empty_string_as_none")]
983    pub tag_deposit: Option<String>,
984    /// Chain.
985    pub chain: String,
986    /// Deposit limit. "-1" means unlimited
987    pub batch_release_limit: String,
988    /// Last 6 characters of the contract address. Empty if none
989    #[serde(default, deserialize_with = "empty_string_as_none")]
990    pub contract_address: Option<String>,
991}
992
993// ════════════════════════════════════════ Withdraw ════════════════════════════════════════
994
995// --- Get Withdrawal Records ---
996
997/// Query params for
998/// [`Client::get_withdrawal_records`](crate::http::Client::get_withdrawal_records).
999#[derive(Clone, Debug, Serialize)]
1000#[serde(rename_all = "camelCase")]
1001pub struct GetWithdrawalRecordsParams {
1002    /// Withdraw ID.
1003    #[serde(skip_serializing_if = "Option::is_none")]
1004    pub withdraw_id: Option<String>,
1005    /// Transaction id (hash) on the chain.
1006    #[serde(skip_serializing_if = "Option::is_none")]
1007    pub tx_id: Option<String>,
1008    /// Coin, uppercase only.
1009    #[serde(skip_serializing_if = "Option::is_none")]
1010    pub coin: Option<String>,
1011    /// 0: on chain, 1: off chain, 2: all. Default: 0
1012    #[serde(skip_serializing_if = "Option::is_none")]
1013    pub withdraw_type: Option<i64>,
1014    /// The start timestamp (ms).
1015    #[serde(skip_serializing_if = "Option::is_none")]
1016    pub start_time: Option<Timestamp>,
1017    /// The end timestamp (ms).
1018    #[serde(skip_serializing_if = "Option::is_none")]
1019    pub end_time: Option<Timestamp>,
1020    /// Limit for data size per page. [1, 50]. Default: 50
1021    #[serde(skip_serializing_if = "Option::is_none")]
1022    pub limit: Option<u64>,
1023    /// Cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
1024    #[serde(skip_serializing_if = "Option::is_none")]
1025    pub cursor: Option<String>,
1026}
1027
1028impl GetWithdrawalRecordsParams {
1029    /// Create with the required fields; set the optional ones with the `with_*` methods.
1030    pub fn new() -> Self {
1031        Self {
1032            withdraw_id: None,
1033            tx_id: None,
1034            coin: None,
1035            withdraw_type: None,
1036            start_time: None,
1037            end_time: None,
1038            limit: None,
1039            cursor: None,
1040        }
1041    }
1042
1043    /// Set `withdraw_id`: withdraw ID.
1044    pub fn with_withdraw_id(mut self, v: impl Into<String>) -> Self {
1045        self.withdraw_id = Some(v.into());
1046        self
1047    }
1048    /// Set `tx_id`: transaction id (hash) on the chain.
1049    pub fn with_tx_id(mut self, v: impl Into<String>) -> Self {
1050        self.tx_id = Some(v.into());
1051        self
1052    }
1053    /// Set `coin`: coin, uppercase only.
1054    pub fn with_coin(mut self, v: impl Into<String>) -> Self {
1055        self.coin = Some(v.into());
1056        self
1057    }
1058    /// Set `withdraw_type`: 0: on chain, 1: off chain, 2: all. Default: 0.
1059    pub fn with_withdraw_type(mut self, v: i64) -> Self {
1060        self.withdraw_type = Some(v);
1061        self
1062    }
1063    /// Set `start_time`: the start timestamp (ms).
1064    pub fn with_start_time(mut self, v: Timestamp) -> Self {
1065        self.start_time = Some(v);
1066        self
1067    }
1068    /// Set `end_time`: the end timestamp (ms).
1069    pub fn with_end_time(mut self, v: Timestamp) -> Self {
1070        self.end_time = Some(v);
1071        self
1072    }
1073    /// Set `limit`: limit for data size per page. [1, 50]. Default: 50.
1074    pub fn with_limit(mut self, v: u64) -> Self {
1075        self.limit = Some(v);
1076        self
1077    }
1078    /// Set `cursor`: cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
1079    pub fn with_cursor(mut self, v: impl Into<String>) -> Self {
1080        self.cursor = Some(v.into());
1081        self
1082    }
1083}
1084
1085impl Default for GetWithdrawalRecordsParams {
1086    fn default() -> Self {
1087        Self::new()
1088    }
1089}
1090
1091/// Result of `GET /v5/asset/withdraw/query-record` ([`Client::get_withdrawal_records`](crate::http::Client::get_withdrawal_records)).
1092#[derive(Debug, Deserialize, PartialEq)]
1093#[serde(rename_all = "camelCase")]
1094pub struct WithdrawRecords {
1095    /// Items.
1096    pub rows: Vec<WithdrawRecord>,
1097    /// Cursor. Used for pagination.
1098    #[serde(default, deserialize_with = "empty_string_as_none")]
1099    pub next_page_cursor: Option<String>,
1100}
1101
1102/// Part of the response of `/v5/asset/withdraw/query-record`.
1103#[derive(Debug, Deserialize, PartialEq)]
1104#[serde(rename_all = "camelCase")]
1105pub struct WithdrawRecord {
1106    /// Empty if the withdrawal failed or was cancelled
1107    #[serde(default, deserialize_with = "empty_string_as_none")]
1108    pub tx_id: Option<String>,
1109    /// Coin.
1110    pub coin: String,
1111    /// Chain.
1112    pub chain: String,
1113    /// Amount.
1114    pub amount: Decimal,
1115    /// Withdraw fee.
1116    pub withdraw_fee: Decimal,
1117    /// Withdraw status.
1118    pub status: WithdrawStatus,
1119    /// Destination address. For internal transfers, this is the receiving Bybit UID
1120    pub to_address: String,
1121    /// Tag.
1122    #[serde(default, deserialize_with = "empty_string_as_none")]
1123    pub tag: Option<String>,
1124    /// Withdraw created timestamp (ms).
1125    #[serde(deserialize_with = "number")]
1126    pub create_time: Timestamp,
1127    /// Withdraw updated timestamp (ms).
1128    #[serde(deserialize_with = "number")]
1129    pub update_time: Timestamp,
1130    /// Withdraw ID.
1131    pub withdraw_id: String,
1132    /// 0: on chain, 1: off chain
1133    pub withdraw_type: i64,
1134    /// Tax amount. Applicable to specific users based on Tax Centre configuration. Default: "0".
1135    #[serde(default, deserialize_with = "option_decimal")]
1136    pub tax: Option<Decimal>,
1137    /// Tax rate. Applicable to specific users based on Tax Centre configuration. Default: "0".
1138    #[serde(default, deserialize_with = "option_decimal")]
1139    pub tax_rate: Option<Decimal>,
1140    /// Tax type. Applicable to specific users based on Tax Centre configuration. Default: "".
1141    #[serde(default, deserialize_with = "empty_string_as_none")]
1142    pub tax_type: Option<String>,
1143}
1144
1145// --- Withdraw ---
1146
1147/// Request body for [`Client::withdraw`](crate::http::Client::withdraw).
1148#[derive(Debug, Serialize, Clone)]
1149#[serde(rename_all = "camelCase")]
1150pub struct WithdrawRequest {
1151    /// Coin name, uppercase only
1152    pub coin: String,
1153    /// Destination wallet address, case-sensitive, or a Bybit UID when `force_chain=2`
1154    pub address: String,
1155    /// Withdraw amount
1156    pub amount: Decimal,
1157    /// Current timestamp (ms). Used to prevent replay attacks
1158    pub timestamp: i64,
1159    /// Wallet to withdraw from: `FUND`, `UNIFIED`/`UTA`, `EARN`, or a comma-separated
1160    /// combination, e.g. `"FUND,UTA,EARN"`. Not the [`AccountType`] enum: this field accepts
1161    /// combinations that enum can't represent
1162    pub account_type: String,
1163    /// Chain name, from [`Client::get_coin_info`](crate::http::Client::get_coin_info). Required
1164    /// unless `force_chain=2`
1165    #[serde(skip_serializing_if = "Option::is_none")]
1166    pub chain: Option<String>,
1167    /// Tag/memo, required if the destination address needs one
1168    #[serde(skip_serializing_if = "Option::is_none")]
1169    pub tag: Option<String>,
1170    /// 0(default): on chain if the address isn't recognized as an internal Bybit address,
1171    /// otherwise internal transfer. 1: force on chain. 2: force internal transfer by UID,
1172    /// `address` must be a Bybit UID
1173    #[serde(skip_serializing_if = "Option::is_none")]
1174    pub force_chain: Option<i64>,
1175    /// 0(default): withdrawal fee is deducted from `amount`. 1: fee is deducted separately by
1176    /// the system
1177    #[serde(skip_serializing_if = "Option::is_none")]
1178    pub fee_type: Option<i64>,
1179    /// Idempotent request ID, 1-32 alphanumeric characters
1180    #[serde(skip_serializing_if = "Option::is_none")]
1181    pub request_id: Option<String>,
1182}
1183
1184impl WithdrawRequest {
1185    /// Create with the required fields; set the optional ones with the `with_*` methods.
1186    pub fn new(
1187        coin: impl Into<String>,
1188        address: impl Into<String>,
1189        amount: Decimal,
1190        timestamp: i64,
1191        account_type: impl Into<String>,
1192    ) -> Self {
1193        let coin: String = coin.into();
1194        let address: String = address.into();
1195        let account_type: String = account_type.into();
1196        Self {
1197            coin,
1198            address,
1199            amount,
1200            timestamp,
1201            account_type,
1202            chain: None,
1203            tag: None,
1204            force_chain: None,
1205            fee_type: None,
1206            request_id: None,
1207        }
1208    }
1209
1210    /// Set `chain`: chain name, from [`Client::get_coin_info`](crate::http::Client::get_coin_info). Required unless `force_chain=2`.
1211    pub fn with_chain(mut self, v: impl Into<String>) -> Self {
1212        self.chain = Some(v.into());
1213        self
1214    }
1215    /// Set `tag`: tag/memo, required if the destination address needs one.
1216    pub fn with_tag(mut self, v: impl Into<String>) -> Self {
1217        self.tag = Some(v.into());
1218        self
1219    }
1220    /// Set `force_chain`: 0(default): on chain if the address isn't recognized as an internal Bybit address, otherwise internal transfer. 1: force on chain. 2: force internal transfer by UID, `address` must be a Bybit UID.
1221    pub fn with_force_chain(mut self, v: i64) -> Self {
1222        self.force_chain = Some(v);
1223        self
1224    }
1225    /// Set `fee_type`: 0(default): withdrawal fee is deducted from `amount`. 1: fee is deducted separately by the system.
1226    pub fn with_fee_type(mut self, v: i64) -> Self {
1227        self.fee_type = Some(v);
1228        self
1229    }
1230    /// Set `request_id`: idempotent request ID, 1-32 alphanumeric characters.
1231    pub fn with_request_id(mut self, v: impl Into<String>) -> Self {
1232        self.request_id = Some(v.into());
1233        self
1234    }
1235}
1236
1237/// Result of `POST /v5/asset/withdraw/create` ([`Client::withdraw`](crate::http::Client::withdraw)).
1238#[derive(Debug, Deserialize, PartialEq)]
1239pub struct WithdrawResult {
1240    /// Withdrawal ID.
1241    pub id: String,
1242}
1243
1244// --- Cancel Withdrawal ---
1245
1246/// Request body for [`Client::cancel_withdrawal`](crate::http::Client::cancel_withdrawal).
1247#[derive(Debug, Serialize, Clone)]
1248pub struct CancelWithdrawalRequest {
1249    /// Withdrawal ID.
1250    pub id: String,
1251}
1252
1253impl CancelWithdrawalRequest {
1254    /// Create with the required fields; set the optional ones with the `with_*` methods.
1255    pub fn new(id: impl Into<String>) -> Self {
1256        let id: String = id.into();
1257        Self { id }
1258    }
1259}
1260
1261/// Result of `POST /v5/asset/withdraw/cancel` ([`Client::cancel_withdrawal`](crate::http::Client::cancel_withdrawal)).
1262#[derive(Debug, Deserialize, PartialEq)]
1263pub struct CancelWithdrawalResult {
1264    /// 0: fail, 1: success
1265    pub status: i64,
1266}
1267
1268// --- Get Withdrawable Amount ---
1269
1270/// Query params for
1271/// [`Client::get_withdrawable_amount`](crate::http::Client::get_withdrawable_amount).
1272#[derive(Debug, Serialize, Clone)]
1273#[serde(rename_all = "camelCase")]
1274pub struct GetWithdrawableAmountParams {
1275    /// Coin name, uppercase only
1276    pub coin: String,
1277}
1278
1279impl GetWithdrawableAmountParams {
1280    /// Create with the required fields; set the optional ones with the `with_*` methods.
1281    pub fn new(coin: impl Into<String>) -> Self {
1282        let coin: String = coin.into();
1283        Self { coin }
1284    }
1285}
1286
1287/// Result of `GET /v5/asset/withdraw/withdrawable-amount` ([`Client::get_withdrawable_amount`](crate::http::Client::get_withdrawable_amount)).
1288#[derive(Debug, Deserialize, PartialEq)]
1289#[serde(rename_all = "camelCase")]
1290pub struct WithdrawableAmountResult {
1291    /// Amount frozen due to unresolved deposit risk, denominated in USD. "0" means all deposit
1292    /// risks have been released
1293    pub limit_amount_usd: Decimal,
1294    /// Amount that can be withdrawn.
1295    pub withdrawable_amount: WithdrawableAmountByWallet,
1296}
1297
1298/// Part of the response of `/v5/asset/withdraw/withdrawable-amount`.
1299#[derive(Debug, Deserialize, PartialEq)]
1300pub struct WithdrawableAmountByWallet {
1301    /// Omitted if the Spot wallet has been removed
1302    #[serde(rename = "SPOT")]
1303    pub spot: Option<WithdrawableAmountEntry>,
1304    /// Funding wallet.
1305    #[serde(rename = "FUND")]
1306    pub fund: Option<WithdrawableAmountEntry>,
1307    /// Unified wallet.
1308    #[serde(rename = "UTA")]
1309    pub uta: Option<WithdrawableAmountEntry>,
1310    /// Omitted if the coin isn't supported by Earn
1311    #[serde(rename = "EARN")]
1312    pub earn: Option<WithdrawableAmountEntry>,
1313}
1314
1315/// Part of the response of `/v5/asset/withdraw/withdrawable-amount`.
1316#[derive(Debug, Deserialize, PartialEq)]
1317#[serde(rename_all = "camelCase")]
1318pub struct WithdrawableAmountEntry {
1319    /// Coin name.
1320    pub coin: String,
1321    /// Amount that can be withdrawn.
1322    pub withdrawable_amount: Decimal,
1323    /// Available balance.
1324    pub available_balance: Decimal,
1325}
1326
1327// ═══════════════════════════════════════ Coin / Misc ═══════════════════════════════════════
1328
1329// --- Get Coin Info ---
1330
1331/// Query params for [`Client::get_coin_info`](crate::http::Client::get_coin_info).
1332#[derive(Debug, Serialize, Clone)]
1333#[serde(rename_all = "camelCase")]
1334pub struct GetCoinInfoParams {
1335    /// Coin, uppercase only.
1336    #[serde(skip_serializing_if = "Option::is_none")]
1337    pub coin: Option<String>,
1338}
1339
1340impl GetCoinInfoParams {
1341    /// Create with the required fields; set the optional ones with the `with_*` methods.
1342    pub fn new() -> Self {
1343        Self { coin: None }
1344    }
1345
1346    /// Set `coin`: coin, uppercase only.
1347    pub fn with_coin(mut self, v: impl Into<String>) -> Self {
1348        self.coin = Some(v.into());
1349        self
1350    }
1351}
1352
1353impl Default for GetCoinInfoParams {
1354    fn default() -> Self {
1355        Self::new()
1356    }
1357}
1358
1359/// Result of `GET /v5/asset/coin/query-info` ([`Client::get_coin_info`](crate::http::Client::get_coin_info)).
1360#[derive(Debug, Deserialize, PartialEq)]
1361#[serde(rename_all = "camelCase")]
1362pub struct CoinInfoResult {
1363    /// Items.
1364    pub rows: Vec<CoinInfo>,
1365}
1366
1367/// Part of the response of `/v5/asset/coin/query-info`.
1368#[derive(Debug, Deserialize, PartialEq)]
1369#[serde(rename_all = "camelCase")]
1370pub struct CoinInfo {
1371    /// Coin name.
1372    pub name: String,
1373    /// Coin.
1374    pub coin: String,
1375    /// Chains of the coin with their deposit and withdrawal settings.
1376    pub chains: Vec<CoinChain>,
1377}
1378
1379/// Part of the response of `/v5/asset/coin/query-info`.
1380#[derive(Debug, Deserialize, PartialEq)]
1381#[serde(rename_all = "camelCase")]
1382pub struct CoinChain {
1383    /// Chain.
1384    pub chain: String,
1385    /// Chain type.
1386    pub chain_type: String,
1387    /// Number of block confirmations required to credit a deposit
1388    pub confirmation: String,
1389    /// Fixed withdrawal fee. Empty if withdrawal isn't supported
1390    #[serde(default, deserialize_with = "empty_string_as_none")]
1391    pub withdraw_fee: Option<Decimal>,
1392    /// Min. deposit.
1393    #[serde(default, deserialize_with = "empty_string_as_none")]
1394    pub deposit_min: Option<Decimal>,
1395    /// Min. withdraw.
1396    #[serde(default, deserialize_with = "empty_string_as_none")]
1397    pub withdraw_min: Option<Decimal>,
1398    /// Decimal precision for transactions on this chain
1399    pub min_accuracy: String,
1400    /// "0": suspended, "1": active
1401    pub chain_deposit: String,
1402    /// "0": suspended, "1": active
1403    pub chain_withdraw: String,
1404    /// The withdraw fee percentage. It is a real figure, e.g., 0.022 means 2.2%.
1405    #[serde(default, deserialize_with = "empty_string_as_none")]
1406    pub withdraw_percentage_fee: Option<Decimal>,
1407    /// Contract address. "" means no contract address.
1408    #[serde(default, deserialize_with = "empty_string_as_none")]
1409    pub contract_address: Option<String>,
1410    /// Number of security confirmations: Once this number is reached, your USD equivalent worth funds will be fully unlocked and available for withdrawal.
1411    pub safe_confirm_number: String,
1412    /// Maximum withdrawal per transaction. "-1" means unlimited
1413    pub withdraw_max: String,
1414}
1415
1416// --- Get Coin Exchange Records ---
1417
1418/// Query params for
1419/// [`Client::get_exchange_order_record`](crate::http::Client::get_exchange_order_record).
1420#[derive(Clone, Debug, Serialize)]
1421#[serde(rename_all = "camelCase")]
1422pub struct GetExchangeOrderRecordParams {
1423    /// The currency to convert from, uppercase only. e.g, BTC.
1424    #[serde(skip_serializing_if = "Option::is_none")]
1425    pub from_coin: Option<String>,
1426    /// The currency to convert to, uppercase only. e.g, USDT.
1427    #[serde(skip_serializing_if = "Option::is_none")]
1428    pub to_coin: Option<String>,
1429    /// Limit for data size per page. [1, 50]. Default: 10
1430    #[serde(skip_serializing_if = "Option::is_none")]
1431    pub limit: Option<u64>,
1432    /// Cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
1433    #[serde(skip_serializing_if = "Option::is_none")]
1434    pub cursor: Option<String>,
1435}
1436
1437impl GetExchangeOrderRecordParams {
1438    /// Create with the required fields; set the optional ones with the `with_*` methods.
1439    pub fn new() -> Self {
1440        Self {
1441            from_coin: None,
1442            to_coin: None,
1443            limit: None,
1444            cursor: None,
1445        }
1446    }
1447
1448    /// Set `from_coin`: the currency to convert from, uppercase only. e.g, BTC.
1449    pub fn with_from_coin(mut self, v: impl Into<String>) -> Self {
1450        self.from_coin = Some(v.into());
1451        self
1452    }
1453    /// Set `to_coin`: the currency to convert to, uppercase only. e.g, USDT.
1454    pub fn with_to_coin(mut self, v: impl Into<String>) -> Self {
1455        self.to_coin = Some(v.into());
1456        self
1457    }
1458    /// Set `limit`: limit for data size per page. [1, 50]. Default: 10.
1459    pub fn with_limit(mut self, v: u64) -> Self {
1460        self.limit = Some(v);
1461        self
1462    }
1463    /// Set `cursor`: cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
1464    pub fn with_cursor(mut self, v: impl Into<String>) -> Self {
1465        self.cursor = Some(v.into());
1466        self
1467    }
1468}
1469
1470impl Default for GetExchangeOrderRecordParams {
1471    fn default() -> Self {
1472        Self::new()
1473    }
1474}
1475
1476/// Result of `GET /v5/asset/exchange/order-record` ([`Client::get_exchange_order_record`](crate::http::Client::get_exchange_order_record)).
1477#[derive(Debug, Deserialize, PartialEq)]
1478#[serde(rename_all = "camelCase")]
1479pub struct ExchangeOrderRecords {
1480    /// Convert records.
1481    pub order_body: Vec<ExchangeOrderRecord>,
1482    /// Refer to the cursor request parameter.
1483    #[serde(default, deserialize_with = "empty_string_as_none")]
1484    pub next_page_cursor: Option<String>,
1485}
1486
1487/// Part of the response of `/v5/asset/exchange/order-record`.
1488#[derive(Debug, Deserialize, PartialEq)]
1489#[serde(rename_all = "camelCase")]
1490pub struct ExchangeOrderRecord {
1491    /// The currency to convert from.
1492    pub from_coin: String,
1493    /// The amount to convert from.
1494    pub from_amount: Decimal,
1495    /// The currency to convert to.
1496    pub to_coin: String,
1497    /// The amount to convert to.
1498    pub to_amount: Decimal,
1499    /// Exchange rate.
1500    pub exchange_rate: Decimal,
1501    /// Timestamp in seconds
1502    #[serde(deserialize_with = "number")]
1503    pub created_time: Timestamp,
1504    /// Exchange transaction ID.
1505    pub exchange_tx_id: String,
1506}
1507
1508// --- Get Delivery Record ---
1509
1510/// Query params for
1511/// [`Client::get_delivery_record`](crate::http::Client::get_delivery_record).
1512#[derive(Clone, Debug, Serialize)]
1513#[serde(rename_all = "camelCase")]
1514pub struct GetDeliveryRecordParams {
1515    /// linear, inverse, option
1516    pub category: Category,
1517    /// Symbol name, like BTCUSDT, uppercase only.
1518    #[serde(skip_serializing_if = "Option::is_none")]
1519    pub symbol: Option<String>,
1520    /// Without start_time and end_time, returns the past 30 days by default
1521    #[serde(skip_serializing_if = "Option::is_none")]
1522    pub start_time: Option<Timestamp>,
1523    /// The end timestamp (ms).
1524    #[serde(skip_serializing_if = "Option::is_none")]
1525    pub end_time: Option<Timestamp>,
1526    /// Expiry date, e.g. "25MAR22". Returns all if not specified
1527    #[serde(skip_serializing_if = "Option::is_none")]
1528    pub exp_date: Option<String>,
1529    /// Limit for data size per page. [1, 50]. Default: 20
1530    #[serde(skip_serializing_if = "Option::is_none")]
1531    pub limit: Option<u64>,
1532    /// Cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
1533    #[serde(skip_serializing_if = "Option::is_none")]
1534    pub cursor: Option<String>,
1535}
1536
1537impl GetDeliveryRecordParams {
1538    /// Create with the required fields; set the optional ones with the `with_*` methods.
1539    pub fn new(category: Category) -> Self {
1540        Self {
1541            category,
1542            symbol: None,
1543            start_time: None,
1544            end_time: None,
1545            exp_date: None,
1546            limit: None,
1547            cursor: None,
1548        }
1549    }
1550
1551    /// Set `symbol`: symbol name, like BTCUSDT, uppercase only.
1552    pub fn with_symbol(mut self, v: impl Into<String>) -> Self {
1553        self.symbol = Some(v.into());
1554        self
1555    }
1556    /// Set `start_time`: without start_time and end_time, returns the past 30 days by default.
1557    pub fn with_start_time(mut self, v: Timestamp) -> Self {
1558        self.start_time = Some(v);
1559        self
1560    }
1561    /// Set `end_time`: the end timestamp (ms).
1562    pub fn with_end_time(mut self, v: Timestamp) -> Self {
1563        self.end_time = Some(v);
1564        self
1565    }
1566    /// Set `exp_date`: expiry date, e.g. "25MAR22". Returns all if not specified.
1567    pub fn with_exp_date(mut self, v: impl Into<String>) -> Self {
1568        self.exp_date = Some(v.into());
1569        self
1570    }
1571    /// Set `limit`: limit for data size per page. [1, 50]. Default: 20.
1572    pub fn with_limit(mut self, v: u64) -> Self {
1573        self.limit = Some(v);
1574        self
1575    }
1576    /// Set `cursor`: cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
1577    pub fn with_cursor(mut self, v: impl Into<String>) -> Self {
1578        self.cursor = Some(v.into());
1579        self
1580    }
1581}
1582
1583/// Item of the list returned by `GET /v5/asset/delivery-record` ([`Client::get_delivery_record`](crate::http::Client::get_delivery_record)).
1584#[derive(Debug, Deserialize, PartialEq)]
1585#[serde(rename_all = "camelCase")]
1586pub struct DeliveryRecord {
1587    /// Delivery time (ms).
1588    pub delivery_time: Timestamp,
1589    /// Symbol name.
1590    pub symbol: String,
1591    /// Buy, Sell.
1592    pub side: Side,
1593    /// Executed size.
1594    pub position: Decimal,
1595    /// Avg entry price.
1596    pub entry_price: Decimal,
1597    /// Delivery price.
1598    pub delivery_price: Decimal,
1599    /// Options only
1600    #[serde(default, deserialize_with = "option_decimal")]
1601    pub strike: Option<Decimal>,
1602    /// Trading fee.
1603    pub fee: Decimal,
1604    /// Realized PnL of the delivery.
1605    pub delivery_rpl: Decimal,
1606}
1607
1608// --- Get USDC Session Settlement ---
1609
1610/// Query params for
1611/// [`Client::get_settlement_record`](crate::http::Client::get_settlement_record).
1612#[derive(Clone, Debug, Serialize)]
1613#[serde(rename_all = "camelCase")]
1614pub struct GetSettlementRecordParams {
1615    /// Must be `linear` (USDC contracts)
1616    pub category: Category,
1617    /// Symbol name, like BTCPERP, uppercase only.
1618    #[serde(skip_serializing_if = "Option::is_none")]
1619    pub symbol: Option<String>,
1620    /// Without start_time and end_time, returns the past 30 days by default
1621    #[serde(skip_serializing_if = "Option::is_none")]
1622    pub start_time: Option<Timestamp>,
1623    /// The end time. timestamp (ms).
1624    #[serde(skip_serializing_if = "Option::is_none")]
1625    pub end_time: Option<Timestamp>,
1626    /// Limit for data size per page. [1, 50]. Default: 20
1627    #[serde(skip_serializing_if = "Option::is_none")]
1628    pub limit: Option<u64>,
1629    /// Cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
1630    #[serde(skip_serializing_if = "Option::is_none")]
1631    pub cursor: Option<String>,
1632}
1633
1634impl GetSettlementRecordParams {
1635    /// Create with the required fields; set the optional ones with the `with_*` methods.
1636    pub fn new(category: Category) -> Self {
1637        Self {
1638            category,
1639            symbol: None,
1640            start_time: None,
1641            end_time: None,
1642            limit: None,
1643            cursor: None,
1644        }
1645    }
1646
1647    /// Set `symbol`: symbol name, like BTCPERP, uppercase only.
1648    pub fn with_symbol(mut self, v: impl Into<String>) -> Self {
1649        self.symbol = Some(v.into());
1650        self
1651    }
1652    /// Set `start_time`: without start_time and end_time, returns the past 30 days by default.
1653    pub fn with_start_time(mut self, v: Timestamp) -> Self {
1654        self.start_time = Some(v);
1655        self
1656    }
1657    /// Set `end_time`: the end time. timestamp (ms).
1658    pub fn with_end_time(mut self, v: Timestamp) -> Self {
1659        self.end_time = Some(v);
1660        self
1661    }
1662    /// Set `limit`: limit for data size per page. [1, 50]. Default: 20.
1663    pub fn with_limit(mut self, v: u64) -> Self {
1664        self.limit = Some(v);
1665        self
1666    }
1667    /// Set `cursor`: cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
1668    pub fn with_cursor(mut self, v: impl Into<String>) -> Self {
1669        self.cursor = Some(v.into());
1670        self
1671    }
1672}
1673
1674/// Item of the list returned by `GET /v5/asset/settlement-record` ([`Client::get_settlement_record`](crate::http::Client::get_settlement_record)).
1675#[derive(Debug, Deserialize, PartialEq)]
1676#[serde(rename_all = "camelCase")]
1677pub struct SettlementRecord {
1678    /// Symbol name.
1679    pub symbol: String,
1680    /// Buy, Sell.
1681    pub side: Side,
1682    /// Position size.
1683    pub size: Decimal,
1684    /// Settlement price.
1685    pub session_avg_price: Decimal,
1686    /// Mark price.
1687    pub mark_price: Decimal,
1688    /// Realised PnL.
1689    pub realised_pnl: Decimal,
1690    /// Created time (ms).
1691    #[serde(deserialize_with = "number")]
1692    pub created_time: Timestamp,
1693}
1694
1695// --- Get Coin Greeks ---
1696
1697/// Query params for [`Client::get_coin_greeks`](crate::http::Client::get_coin_greeks).
1698#[derive(Debug, Serialize, Clone)]
1699#[serde(rename_all = "camelCase")]
1700pub struct GetCoinGreeksParams {
1701    /// Base coin, uppercase only. If not passed, all supported base coin greeks will be returned by default.
1702    #[serde(skip_serializing_if = "Option::is_none")]
1703    pub base_coin: Option<String>,
1704}
1705
1706impl GetCoinGreeksParams {
1707    /// Create with the required fields; set the optional ones with the `with_*` methods.
1708    pub fn new() -> Self {
1709        Self { base_coin: None }
1710    }
1711
1712    /// Set `base_coin`: base coin, uppercase only. If not passed, all supported base coin greeks will be returned by default.
1713    pub fn with_base_coin(mut self, v: impl Into<String>) -> Self {
1714        self.base_coin = Some(v.into());
1715        self
1716    }
1717}
1718
1719impl Default for GetCoinGreeksParams {
1720    fn default() -> Self {
1721        Self::new()
1722    }
1723}
1724
1725/// Item of the list returned by `GET /v5/asset/coin-greeks` ([`Client::get_coin_greeks`](crate::http::Client::get_coin_greeks)).
1726#[derive(Debug, Deserialize, PartialEq)]
1727#[serde(rename_all = "camelCase")]
1728pub struct CoinGreeks {
1729    /// Base coin. e.g., BTC, ETH, SOL.
1730    pub base_coin: String,
1731    /// Delta value.
1732    pub total_delta: Decimal,
1733    /// Gamma value.
1734    pub total_gamma: Decimal,
1735    /// Vega value.
1736    pub total_vega: Decimal,
1737    /// Theta value.
1738    pub total_theta: Decimal,
1739}
1740
1741#[cfg(test)]
1742mod tests {
1743    use super::*;
1744
1745    #[test]
1746    fn coin_chain_with_empty_limits() {
1747        // Chains that do not support deposits/withdrawals send "" limits.
1748        let json = r#"{"rows":[{"name":"USDT","coin":"USDT","remainAmount":"100","chains":[{
1749            "chainType":"X","confirmation":"","withdrawFee":"","depositMin":"","withdrawMin":"",
1750            "chain":"X","chainDeposit":"0","chainWithdraw":"0","minAccuracy":"8",
1751            "withdrawPercentageFee":"","contractAddress":"","safeConfirmNumber":"","withdrawMax":""
1752        }]}]}"#;
1753
1754        let result: CoinInfoResult = serde_json::from_str(json).unwrap();
1755
1756        let chain = &result.rows[0].chains[0];
1757        assert_eq!(chain.deposit_min, None);
1758        assert_eq!(chain.withdraw_fee, None);
1759        assert_eq!(chain.contract_address, None);
1760    }
1761
1762    #[test]
1763    fn deposit_coin_config_with_numeric_block_confirm_number() {
1764        let json = r#"{"configList":[{"coin":"USDT","chain":"ETH","coinShowName":"USDT",
1765            "chainType":"ERC20","blockConfirmNumber":1,"minDepositAmount":"0.01"}],
1766            "nextPageCursor":""}"#;
1767
1768        let result: DepositAllowedCoinInfoResult = serde_json::from_str(json).unwrap();
1769
1770        assert_eq!(result.config_list[0].block_confirm_number, 1);
1771    }
1772}