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    pub from_account_type: AccountType,
28    pub to_account_type: AccountType,
29}
30
31impl InternalTransferRequest {
32    pub fn new(
33        transfer_id: String,
34        coin: String,
35        amount: Decimal,
36        from_account_type: AccountType,
37        to_account_type: AccountType,
38    ) -> Self {
39        Self {
40            transfer_id,
41            coin,
42            amount,
43            from_account_type,
44            to_account_type,
45        }
46    }
47}
48
49/// Response for [`Client::internal_transfer`](crate::http::Client::internal_transfer) and
50/// [`Client::universal_transfer`](crate::http::Client::universal_transfer).
51#[derive(Debug, Deserialize, PartialEq)]
52#[serde(rename_all = "camelCase")]
53pub struct TransferResult {
54    pub transfer_id: String,
55    pub status: TransferStatus,
56}
57
58// --- Get Internal Transfer Records ---
59
60/// Query params for
61/// [`Client::get_internal_transfer_records`](crate::http::Client::get_internal_transfer_records).
62#[derive(Clone, Debug, Serialize)]
63#[serde(rename_all = "camelCase")]
64pub struct GetInternalTransferRecordsParams {
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub transfer_id: Option<String>,
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub coin: Option<String>,
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub status: Option<TransferStatus>,
71    /// Without start_time and end_time, returns the past 7 days by default
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub start_time: Option<Timestamp>,
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub end_time: Option<Timestamp>,
76    /// Limit for data size per page. [1, 50]. Default: 20
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub limit: Option<u64>,
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub cursor: Option<String>,
81}
82
83impl GetInternalTransferRecordsParams {
84    pub fn new() -> Self {
85        Self {
86            transfer_id: None,
87            coin: None,
88            status: None,
89            start_time: None,
90            end_time: None,
91            limit: None,
92            cursor: None,
93        }
94    }
95
96    pub fn with_transfer_id(mut self, v: String) -> Self {
97        self.transfer_id = Some(v);
98        self
99    }
100    pub fn with_coin(mut self, v: String) -> Self {
101        self.coin = Some(v);
102        self
103    }
104    pub fn with_status(mut self, v: TransferStatus) -> Self {
105        self.status = Some(v);
106        self
107    }
108    pub fn with_start_time(mut self, v: Timestamp) -> Self {
109        self.start_time = Some(v);
110        self
111    }
112    pub fn with_end_time(mut self, v: Timestamp) -> Self {
113        self.end_time = Some(v);
114        self
115    }
116    pub fn with_limit(mut self, v: u64) -> Self {
117        self.limit = Some(v);
118        self
119    }
120    pub fn with_cursor(mut self, v: String) -> Self {
121        self.cursor = Some(v);
122        self
123    }
124}
125
126impl Default for GetInternalTransferRecordsParams {
127    fn default() -> Self {
128        Self::new()
129    }
130}
131
132#[derive(Debug, Deserialize, PartialEq)]
133#[serde(rename_all = "camelCase")]
134pub struct InternalTransferEntry {
135    pub transfer_id: String,
136    pub coin: String,
137    pub amount: Decimal,
138    pub from_account_type: AccountType,
139    pub to_account_type: AccountType,
140    #[serde(deserialize_with = "number")]
141    pub timestamp: Timestamp,
142    pub status: TransferStatus,
143}
144
145// --- Create Universal Transfer ---
146
147/// Request body for [`Client::universal_transfer`](crate::http::Client::universal_transfer).
148///
149/// Transfers funds between a master account and its sub-accounts (or between two sub-accounts).
150/// Requires the master UID's API key, or a sub-account key with the `SubMemberTransferList`
151/// permission (which may only transfer to the master account).
152#[derive(Debug, Serialize, Clone)]
153#[serde(rename_all = "camelCase")]
154pub struct UniversalTransferRequest {
155    /// UUID. Please manually generate a UUID and ensure it is unique. Used to prevent duplicate transfers
156    pub transfer_id: String,
157    /// Coin name, uppercase only
158    pub coin: String,
159    /// Transfer amount
160    pub amount: Decimal,
161    /// Source UID
162    pub from_member_id: i64,
163    /// Destination UID
164    pub to_member_id: i64,
165    pub from_account_type: AccountType,
166    pub to_account_type: AccountType,
167}
168
169impl UniversalTransferRequest {
170    pub fn new(
171        transfer_id: String,
172        coin: String,
173        amount: Decimal,
174        from_member_id: i64,
175        to_member_id: i64,
176        from_account_type: AccountType,
177        to_account_type: AccountType,
178    ) -> Self {
179        Self {
180            transfer_id,
181            coin,
182            amount,
183            from_member_id,
184            to_member_id,
185            from_account_type,
186            to_account_type,
187        }
188    }
189}
190
191// --- Get Universal Transfer Records ---
192
193/// Query params for
194/// [`Client::get_universal_transfer_records`](crate::http::Client::get_universal_transfer_records).
195#[derive(Clone, Debug, Serialize)]
196#[serde(rename_all = "camelCase")]
197pub struct GetUniversalTransferRecordsParams {
198    #[serde(skip_serializing_if = "Option::is_none")]
199    pub transfer_id: Option<String>,
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub coin: Option<String>,
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub status: Option<TransferStatus>,
204    /// Without start_time and end_time, returns the past 7 days by default
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub start_time: Option<Timestamp>,
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub end_time: Option<Timestamp>,
209    /// Limit for data size per page. [1, 50]. Default: 20
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub limit: Option<u64>,
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub cursor: Option<String>,
214}
215
216impl GetUniversalTransferRecordsParams {
217    pub fn new() -> Self {
218        Self {
219            transfer_id: None,
220            coin: None,
221            status: None,
222            start_time: None,
223            end_time: None,
224            limit: None,
225            cursor: None,
226        }
227    }
228
229    pub fn with_transfer_id(mut self, v: String) -> Self {
230        self.transfer_id = Some(v);
231        self
232    }
233    pub fn with_coin(mut self, v: String) -> Self {
234        self.coin = Some(v);
235        self
236    }
237    pub fn with_status(mut self, v: TransferStatus) -> Self {
238        self.status = Some(v);
239        self
240    }
241    pub fn with_start_time(mut self, v: Timestamp) -> Self {
242        self.start_time = Some(v);
243        self
244    }
245    pub fn with_end_time(mut self, v: Timestamp) -> Self {
246        self.end_time = Some(v);
247        self
248    }
249    pub fn with_limit(mut self, v: u64) -> Self {
250        self.limit = Some(v);
251        self
252    }
253    pub fn with_cursor(mut self, v: String) -> Self {
254        self.cursor = Some(v);
255        self
256    }
257}
258
259impl Default for GetUniversalTransferRecordsParams {
260    fn default() -> Self {
261        Self::new()
262    }
263}
264
265#[derive(Debug, Deserialize, PartialEq)]
266#[serde(rename_all = "camelCase")]
267pub struct UniversalTransferEntry {
268    pub transfer_id: String,
269    pub coin: String,
270    pub amount: Decimal,
271    pub from_member_id: String,
272    pub to_member_id: String,
273    pub from_account_type: AccountType,
274    pub to_account_type: AccountType,
275    #[serde(deserialize_with = "number")]
276    pub timestamp: Timestamp,
277    pub status: TransferStatus,
278}
279
280// --- Get Transferable Coin ---
281
282/// Query params for
283/// [`Client::get_transferable_coins`](crate::http::Client::get_transferable_coins).
284#[derive(Debug, Serialize, Clone)]
285#[serde(rename_all = "camelCase")]
286pub struct GetTransferableCoinsParams {
287    pub from_account_type: AccountType,
288    pub to_account_type: AccountType,
289}
290
291impl GetTransferableCoinsParams {
292    pub fn new(from_account_type: AccountType, to_account_type: AccountType) -> Self {
293        Self {
294            from_account_type,
295            to_account_type,
296        }
297    }
298}
299
300// --- Enable Universal Transfer For Sub UID (deprecated by Bybit; all sub UIDs are now
301// automatically enabled) ---
302
303/// Request body for
304/// [`Client::save_transfer_sub_member`](crate::http::Client::save_transfer_sub_member).
305///
306/// Deprecated by Bybit: sub UIDs no longer need to be configured, as all of them are now
307/// automatically enabled for universal transfer. Kept for completeness.
308#[derive(Debug, Serialize, Clone)]
309#[serde(rename_all = "camelCase")]
310pub struct SaveTransferSubMemberRequest {
311    /// Sub UIDs, separated by comma, e.g. "uid1,uid2,uid3"
312    pub sub_member_ids: String,
313}
314
315impl SaveTransferSubMemberRequest {
316    pub fn new(sub_member_ids: String) -> Self {
317        Self { sub_member_ids }
318    }
319}
320
321// --- Get Sub UID (for universal transfer) ---
322
323/// Response for
324/// [`Client::get_transferable_sub_members`](crate::http::Client::get_transferable_sub_members).
325#[derive(Debug, Deserialize, PartialEq)]
326#[serde(rename_all = "camelCase")]
327pub struct TransferableSubMembers {
328    /// All sub UIDs of the master account
329    pub sub_member_ids: Vec<String>,
330    /// Sub UIDs enabled for universal transfer
331    pub transferable_sub_member_ids: Vec<String>,
332}
333
334// --- Get Single Coin Balance ---
335
336/// Query params for
337/// [`Client::get_account_coin_balance`](crate::http::Client::get_account_coin_balance).
338#[derive(Clone, Debug, Serialize)]
339#[serde(rename_all = "camelCase")]
340pub struct GetAccountCoinBalanceParams {
341    pub account_type: AccountType,
342    /// Coin name, uppercase only
343    pub coin: String,
344    /// Master UID's api key can query itself or its sub UIDs' coin balance. If not passed,
345    /// then by default it queries master UID's coin balance
346    #[serde(skip_serializing_if = "Option::is_none")]
347    pub member_id: Option<String>,
348    /// Used together with `to_account_type` to query the balance safe to transfer between two
349    /// UIDs' wallets
350    #[serde(skip_serializing_if = "Option::is_none")]
351    pub to_member_id: Option<String>,
352    #[serde(skip_serializing_if = "Option::is_none")]
353    pub to_account_type: Option<AccountType>,
354    /// Whether to query bonus. 0: not query, 1: query. Valid for `account_type=FUND`. Default: 0
355    #[serde(skip_serializing_if = "Option::is_none")]
356    pub with_bonus: Option<i64>,
357    /// Whether to query the available amount that is safe to withdraw or transfer.
358    /// 0(default): not query, 1: query
359    #[serde(skip_serializing_if = "Option::is_none")]
360    pub with_transfer_safe_amount: Option<i64>,
361    /// Whether to query the available amount that is safe to transfer under the risk level of
362    /// the OTC loan account. 0(default): not query, 1: query
363    #[serde(skip_serializing_if = "Option::is_none")]
364    pub with_ltv_transfer_safe_amount: Option<i64>,
365}
366
367impl GetAccountCoinBalanceParams {
368    pub fn new(account_type: AccountType, coin: String) -> Self {
369        Self {
370            account_type,
371            coin,
372            member_id: None,
373            to_member_id: None,
374            to_account_type: None,
375            with_bonus: None,
376            with_transfer_safe_amount: None,
377            with_ltv_transfer_safe_amount: None,
378        }
379    }
380
381    pub fn with_member_id(mut self, v: String) -> Self {
382        self.member_id = Some(v);
383        self
384    }
385    pub fn with_to_member_id(mut self, v: String) -> Self {
386        self.to_member_id = Some(v);
387        self
388    }
389    pub fn with_to_account_type(mut self, v: AccountType) -> Self {
390        self.to_account_type = Some(v);
391        self
392    }
393    pub fn with_bonus(mut self, v: i64) -> Self {
394        self.with_bonus = Some(v);
395        self
396    }
397    pub fn with_transfer_safe_amount(mut self, v: i64) -> Self {
398        self.with_transfer_safe_amount = Some(v);
399        self
400    }
401    pub fn with_ltv_transfer_safe_amount(mut self, v: i64) -> Self {
402        self.with_ltv_transfer_safe_amount = Some(v);
403        self
404    }
405}
406
407#[derive(Debug, Deserialize, PartialEq)]
408#[serde(rename_all = "camelCase")]
409pub struct AccountCoinBalance {
410    pub account_type: AccountType,
411    pub biz_type: i64,
412    pub account_id: String,
413    pub member_id: String,
414    pub balance: CoinBalance,
415}
416
417#[derive(Debug, Deserialize, PartialEq)]
418#[serde(rename_all = "camelCase")]
419pub struct CoinBalance {
420    pub coin: String,
421    pub wallet_balance: Decimal,
422    pub transfer_balance: Decimal,
423    pub bonus: Decimal,
424    #[serde(default, deserialize_with = "option_decimal")]
425    pub transfer_safe_amount: Option<Decimal>,
426    #[serde(default, deserialize_with = "option_decimal")]
427    pub ltv_transfer_safe_amount: Option<Decimal>,
428}
429
430// --- Get Asset Info (Spot) ---
431
432/// Query params for [`Client::get_asset_info`](crate::http::Client::get_asset_info).
433#[derive(Debug, Serialize, Clone)]
434#[serde(rename_all = "camelCase")]
435pub struct GetAssetInfoParams {
436    /// Account type. Only `SPOT` is supported
437    pub account_type: AccountType,
438    #[serde(skip_serializing_if = "Option::is_none")]
439    pub coin: Option<String>,
440}
441
442impl GetAssetInfoParams {
443    pub fn new(account_type: AccountType) -> Self {
444        Self {
445            account_type,
446            coin: None,
447        }
448    }
449
450    pub fn with_coin(mut self, v: String) -> Self {
451        self.coin = Some(v);
452        self
453    }
454}
455
456#[derive(Debug, Deserialize, PartialEq)]
457#[serde(rename_all = "camelCase")]
458pub struct AssetInfoResult {
459    pub spot: SpotAssetInfo,
460}
461
462#[derive(Debug, Deserialize, PartialEq)]
463#[serde(rename_all = "camelCase")]
464pub struct SpotAssetInfo {
465    /// `ACCOUNT_STATUS_NORMAL` or `ACCOUNT_STATUS_UNSPECIFIED`
466    pub status: String,
467    pub assets: Vec<SpotAsset>,
468}
469
470#[derive(Debug, Deserialize, PartialEq)]
471#[serde(rename_all = "camelCase")]
472pub struct SpotAsset {
473    pub coin: String,
474    /// Amount locked in active orders
475    pub frozen: Decimal,
476    /// Available balance
477    pub free: Decimal,
478    /// Amount currently being withdrawn
479    pub withdraw: Decimal,
480}
481
482// ════════════════════════════════════════ Deposit ════════════════════════════════════════
483
484// --- Get Allowed Deposit Coin Info ---
485
486/// Query params for
487/// [`Client::get_deposit_allowed_coin_info`](crate::http::Client::get_deposit_allowed_coin_info).
488#[derive(Clone, Debug, Serialize)]
489#[serde(rename_all = "camelCase")]
490pub struct GetDepositAllowedCoinInfoParams {
491    /// Coin name, uppercase only. Must be paired with `chain` if passed
492    #[serde(skip_serializing_if = "Option::is_none")]
493    pub coin: Option<String>,
494    /// Chain name. Must be paired with `coin` if passed
495    #[serde(skip_serializing_if = "Option::is_none")]
496    pub chain: Option<String>,
497    /// Limit for data size per page. [1, 35]. Default: 10
498    #[serde(skip_serializing_if = "Option::is_none")]
499    pub limit: Option<u64>,
500    #[serde(skip_serializing_if = "Option::is_none")]
501    pub cursor: Option<String>,
502}
503
504impl GetDepositAllowedCoinInfoParams {
505    pub fn new() -> Self {
506        Self {
507            coin: None,
508            chain: None,
509            limit: None,
510            cursor: None,
511        }
512    }
513
514    pub fn with_coin(mut self, v: String) -> Self {
515        self.coin = Some(v);
516        self
517    }
518    pub fn with_chain(mut self, v: String) -> Self {
519        self.chain = Some(v);
520        self
521    }
522    pub fn with_limit(mut self, v: u64) -> Self {
523        self.limit = Some(v);
524        self
525    }
526    pub fn with_cursor(mut self, v: String) -> Self {
527        self.cursor = Some(v);
528        self
529    }
530}
531
532impl Default for GetDepositAllowedCoinInfoParams {
533    fn default() -> Self {
534        Self::new()
535    }
536}
537
538#[derive(Debug, Deserialize, PartialEq)]
539#[serde(rename_all = "camelCase")]
540pub struct DepositAllowedCoinInfoResult {
541    pub config_list: Vec<DepositCoinConfig>,
542    #[serde(default, deserialize_with = "empty_string_as_none")]
543    pub next_page_cursor: Option<String>,
544}
545
546#[derive(Debug, Deserialize, PartialEq)]
547#[serde(rename_all = "camelCase")]
548pub struct DepositCoinConfig {
549    pub coin: String,
550    pub chain: String,
551    pub coin_show_name: String,
552    pub chain_type: String,
553    pub block_confirm_number: String,
554    pub min_deposit_amount: Decimal,
555}
556
557// --- Get Deposit Records / Get Sub Deposit Records ---
558
559/// Query params for
560/// [`Client::get_deposit_records`](crate::http::Client::get_deposit_records).
561#[derive(Clone, Debug, Serialize)]
562#[serde(rename_all = "camelCase")]
563pub struct GetDepositRecordsParams {
564    #[serde(skip_serializing_if = "Option::is_none")]
565    pub id: Option<String>,
566    /// Not queryable for records before 2024-01-01
567    #[serde(skip_serializing_if = "Option::is_none")]
568    pub tx_id: Option<String>,
569    #[serde(skip_serializing_if = "Option::is_none")]
570    pub coin: Option<String>,
571    #[serde(skip_serializing_if = "Option::is_none")]
572    pub start_time: Option<Timestamp>,
573    #[serde(skip_serializing_if = "Option::is_none")]
574    pub end_time: Option<Timestamp>,
575    /// Limit for data size per page. [1, 50]. Default: 50
576    #[serde(skip_serializing_if = "Option::is_none")]
577    pub limit: Option<u64>,
578    #[serde(skip_serializing_if = "Option::is_none")]
579    pub cursor: Option<String>,
580}
581
582impl GetDepositRecordsParams {
583    pub fn new() -> Self {
584        Self {
585            id: None,
586            tx_id: None,
587            coin: None,
588            start_time: None,
589            end_time: None,
590            limit: None,
591            cursor: None,
592        }
593    }
594
595    pub fn with_id(mut self, v: String) -> Self {
596        self.id = Some(v);
597        self
598    }
599    pub fn with_tx_id(mut self, v: String) -> Self {
600        self.tx_id = Some(v);
601        self
602    }
603    pub fn with_coin(mut self, v: String) -> Self {
604        self.coin = Some(v);
605        self
606    }
607    pub fn with_start_time(mut self, v: Timestamp) -> Self {
608        self.start_time = Some(v);
609        self
610    }
611    pub fn with_end_time(mut self, v: Timestamp) -> Self {
612        self.end_time = Some(v);
613        self
614    }
615    pub fn with_limit(mut self, v: u64) -> Self {
616        self.limit = Some(v);
617        self
618    }
619    pub fn with_cursor(mut self, v: String) -> Self {
620        self.cursor = Some(v);
621        self
622    }
623}
624
625impl Default for GetDepositRecordsParams {
626    fn default() -> Self {
627        Self::new()
628    }
629}
630
631/// Query params for
632/// [`Client::get_sub_deposit_records`](crate::http::Client::get_sub_deposit_records).
633#[derive(Clone, Debug, Serialize)]
634#[serde(rename_all = "camelCase")]
635pub struct GetSubDepositRecordsParams {
636    pub sub_member_id: String,
637    #[serde(skip_serializing_if = "Option::is_none")]
638    pub id: Option<String>,
639    #[serde(skip_serializing_if = "Option::is_none")]
640    pub tx_id: Option<String>,
641    #[serde(skip_serializing_if = "Option::is_none")]
642    pub coin: Option<String>,
643    #[serde(skip_serializing_if = "Option::is_none")]
644    pub start_time: Option<Timestamp>,
645    #[serde(skip_serializing_if = "Option::is_none")]
646    pub end_time: Option<Timestamp>,
647    /// Limit for data size per page. [1, 50]. Default: 50
648    #[serde(skip_serializing_if = "Option::is_none")]
649    pub limit: Option<u64>,
650    #[serde(skip_serializing_if = "Option::is_none")]
651    pub cursor: Option<String>,
652}
653
654impl GetSubDepositRecordsParams {
655    pub fn new(sub_member_id: String) -> Self {
656        Self {
657            sub_member_id,
658            id: None,
659            tx_id: None,
660            coin: None,
661            start_time: None,
662            end_time: None,
663            limit: None,
664            cursor: None,
665        }
666    }
667
668    pub fn with_id(mut self, v: String) -> Self {
669        self.id = Some(v);
670        self
671    }
672    pub fn with_tx_id(mut self, v: String) -> Self {
673        self.tx_id = Some(v);
674        self
675    }
676    pub fn with_coin(mut self, v: String) -> Self {
677        self.coin = Some(v);
678        self
679    }
680    pub fn with_start_time(mut self, v: Timestamp) -> Self {
681        self.start_time = Some(v);
682        self
683    }
684    pub fn with_end_time(mut self, v: Timestamp) -> Self {
685        self.end_time = Some(v);
686        self
687    }
688    pub fn with_limit(mut self, v: u64) -> Self {
689        self.limit = Some(v);
690        self
691    }
692    pub fn with_cursor(mut self, v: String) -> Self {
693        self.cursor = Some(v);
694        self
695    }
696}
697
698#[derive(Debug, Deserialize, PartialEq)]
699#[serde(rename_all = "camelCase")]
700pub struct DepositRecords {
701    pub rows: Vec<DepositRecord>,
702    #[serde(default, deserialize_with = "empty_string_as_none")]
703    pub next_page_cursor: Option<String>,
704}
705
706#[derive(Debug, Deserialize, PartialEq)]
707#[serde(rename_all = "camelCase")]
708pub struct DepositRecord {
709    pub id: String,
710    pub coin: String,
711    pub chain: String,
712    pub amount: Decimal,
713    #[serde(default, deserialize_with = "empty_string_as_none")]
714    pub tx_id: Option<String>,
715    pub status: DepositStatus,
716    pub to_address: String,
717    #[serde(default, deserialize_with = "empty_string_as_none")]
718    pub tag: Option<String>,
719    pub deposit_fee: Decimal,
720    #[serde(deserialize_with = "option_number")]
721    pub success_at: Option<Timestamp>,
722    pub confirmations: String,
723    pub tx_index: String,
724    pub block_hash: String,
725    /// Daily deposit ceiling. "-1" means unlimited
726    pub batch_release_limit: String,
727    /// 0: normal, 10: limit reached, 20: flagged by AML, 50: blocked
728    pub deposit_type: i64,
729    #[serde(default, deserialize_with = "empty_string_as_none")]
730    pub from_address: Option<String>,
731    #[serde(default, deserialize_with = "empty_string_as_none")]
732    pub tax_deposit_records_id: Option<String>,
733    /// 0: no tax reporting, 1: pending, 2: completed
734    #[serde(default)]
735    pub tax_status: Option<i64>,
736    /// 0: approved, 1: info needed, 2: pending, 3: rejected
737    #[serde(default)]
738    pub travel_rule_status: Option<i64>,
739}
740
741// --- Get Deposit Address / Get Sub Deposit Address ---
742
743/// Query params for
744/// [`Client::get_deposit_address`](crate::http::Client::get_deposit_address).
745#[derive(Debug, Serialize, Clone)]
746#[serde(rename_all = "camelCase")]
747pub struct GetDepositAddressParams {
748    /// Coin name, uppercase only
749    pub coin: String,
750    /// Chain type, from [`Client::get_coin_info`](crate::http::Client::get_coin_info)
751    #[serde(skip_serializing_if = "Option::is_none")]
752    pub chain_type: Option<String>,
753}
754
755impl GetDepositAddressParams {
756    pub fn new(coin: String) -> Self {
757        Self {
758            coin,
759            chain_type: None,
760        }
761    }
762
763    pub fn with_chain_type(mut self, v: String) -> Self {
764        self.chain_type = Some(v);
765        self
766    }
767}
768
769/// Query params for
770/// [`Client::get_sub_deposit_address`](crate::http::Client::get_sub_deposit_address).
771///
772/// Requires the master account API key.
773#[derive(Debug, Serialize, Clone)]
774#[serde(rename_all = "camelCase")]
775pub struct GetSubDepositAddressParams {
776    /// Coin name, uppercase only
777    pub coin: String,
778    /// Chain type, from [`Client::get_coin_info`](crate::http::Client::get_coin_info)
779    pub chain_type: String,
780    pub sub_member_id: String,
781}
782
783impl GetSubDepositAddressParams {
784    pub fn new(coin: String, chain_type: String, sub_member_id: String) -> Self {
785        Self {
786            coin,
787            chain_type,
788            sub_member_id,
789        }
790    }
791}
792
793#[derive(Debug, Deserialize, PartialEq)]
794#[serde(rename_all = "camelCase")]
795pub struct DepositAddress {
796    pub coin: String,
797    pub chains: Vec<DepositChain>,
798}
799
800#[derive(Debug, Deserialize, PartialEq)]
801#[serde(rename_all = "camelCase")]
802pub struct DepositChain {
803    pub chain_type: String,
804    pub address_deposit: String,
805    #[serde(default, deserialize_with = "empty_string_as_none")]
806    pub tag_deposit: Option<String>,
807    pub chain: String,
808    /// Deposit limit. "-1" means unlimited
809    pub batch_release_limit: String,
810    /// Last 6 characters of the contract address. Empty if none
811    #[serde(default, deserialize_with = "empty_string_as_none")]
812    pub contract_address: Option<String>,
813}
814
815// ════════════════════════════════════════ Withdraw ════════════════════════════════════════
816
817// --- Get Withdrawal Records ---
818
819/// Query params for
820/// [`Client::get_withdrawal_records`](crate::http::Client::get_withdrawal_records).
821#[derive(Clone, Debug, Serialize)]
822#[serde(rename_all = "camelCase")]
823pub struct GetWithdrawalRecordsParams {
824    #[serde(skip_serializing_if = "Option::is_none")]
825    pub withdraw_id: Option<String>,
826    #[serde(skip_serializing_if = "Option::is_none")]
827    pub tx_id: Option<String>,
828    #[serde(skip_serializing_if = "Option::is_none")]
829    pub coin: Option<String>,
830    /// 0: on chain, 1: off chain, 2: all. Default: 0
831    #[serde(skip_serializing_if = "Option::is_none")]
832    pub withdraw_type: Option<i64>,
833    #[serde(skip_serializing_if = "Option::is_none")]
834    pub start_time: Option<Timestamp>,
835    #[serde(skip_serializing_if = "Option::is_none")]
836    pub end_time: Option<Timestamp>,
837    /// Limit for data size per page. [1, 50]. Default: 50
838    #[serde(skip_serializing_if = "Option::is_none")]
839    pub limit: Option<u64>,
840    #[serde(skip_serializing_if = "Option::is_none")]
841    pub cursor: Option<String>,
842}
843
844impl GetWithdrawalRecordsParams {
845    pub fn new() -> Self {
846        Self {
847            withdraw_id: None,
848            tx_id: None,
849            coin: None,
850            withdraw_type: None,
851            start_time: None,
852            end_time: None,
853            limit: None,
854            cursor: None,
855        }
856    }
857
858    pub fn with_withdraw_id(mut self, v: String) -> Self {
859        self.withdraw_id = Some(v);
860        self
861    }
862    pub fn with_tx_id(mut self, v: String) -> Self {
863        self.tx_id = Some(v);
864        self
865    }
866    pub fn with_coin(mut self, v: String) -> Self {
867        self.coin = Some(v);
868        self
869    }
870    pub fn with_withdraw_type(mut self, v: i64) -> Self {
871        self.withdraw_type = Some(v);
872        self
873    }
874    pub fn with_start_time(mut self, v: Timestamp) -> Self {
875        self.start_time = Some(v);
876        self
877    }
878    pub fn with_end_time(mut self, v: Timestamp) -> Self {
879        self.end_time = Some(v);
880        self
881    }
882    pub fn with_limit(mut self, v: u64) -> Self {
883        self.limit = Some(v);
884        self
885    }
886    pub fn with_cursor(mut self, v: String) -> Self {
887        self.cursor = Some(v);
888        self
889    }
890}
891
892impl Default for GetWithdrawalRecordsParams {
893    fn default() -> Self {
894        Self::new()
895    }
896}
897
898#[derive(Debug, Deserialize, PartialEq)]
899#[serde(rename_all = "camelCase")]
900pub struct WithdrawRecords {
901    pub rows: Vec<WithdrawRecord>,
902    #[serde(default, deserialize_with = "empty_string_as_none")]
903    pub next_page_cursor: Option<String>,
904}
905
906#[derive(Debug, Deserialize, PartialEq)]
907#[serde(rename_all = "camelCase")]
908pub struct WithdrawRecord {
909    /// Empty if the withdrawal failed or was cancelled
910    #[serde(default, deserialize_with = "empty_string_as_none")]
911    pub tx_id: Option<String>,
912    pub coin: String,
913    pub chain: String,
914    pub amount: Decimal,
915    pub withdraw_fee: Decimal,
916    pub status: WithdrawStatus,
917    /// Destination address. For internal transfers, this is the receiving Bybit UID
918    pub to_address: String,
919    #[serde(default, deserialize_with = "empty_string_as_none")]
920    pub tag: Option<String>,
921    #[serde(deserialize_with = "number")]
922    pub create_time: Timestamp,
923    #[serde(deserialize_with = "number")]
924    pub update_time: Timestamp,
925    pub withdraw_id: String,
926    /// 0: on chain, 1: off chain
927    pub withdraw_type: i64,
928    #[serde(default, deserialize_with = "option_decimal")]
929    pub tax: Option<Decimal>,
930    #[serde(default, deserialize_with = "option_decimal")]
931    pub tax_rate: Option<Decimal>,
932    #[serde(default, deserialize_with = "empty_string_as_none")]
933    pub tax_type: Option<String>,
934}
935
936// --- Withdraw ---
937
938/// Request body for [`Client::withdraw`](crate::http::Client::withdraw).
939#[derive(Debug, Serialize, Clone)]
940#[serde(rename_all = "camelCase")]
941pub struct WithdrawRequest {
942    /// Coin name, uppercase only
943    pub coin: String,
944    /// Destination wallet address, case-sensitive, or a Bybit UID when `force_chain=2`
945    pub address: String,
946    /// Withdraw amount
947    pub amount: Decimal,
948    /// Current timestamp (ms). Used to prevent replay attacks
949    pub timestamp: i64,
950    /// Wallet to withdraw from: `FUND`, `UNIFIED`/`UTA`, `EARN`, or a comma-separated
951    /// combination, e.g. `"FUND,UTA,EARN"`. Not the [`AccountType`] enum: this field accepts
952    /// combinations that enum can't represent
953    pub account_type: String,
954    /// Chain name, from [`Client::get_coin_info`](crate::http::Client::get_coin_info). Required
955    /// unless `force_chain=2`
956    #[serde(skip_serializing_if = "Option::is_none")]
957    pub chain: Option<String>,
958    /// Tag/memo, required if the destination address needs one
959    #[serde(skip_serializing_if = "Option::is_none")]
960    pub tag: Option<String>,
961    /// 0(default): on chain if the address isn't recognized as an internal Bybit address,
962    /// otherwise internal transfer. 1: force on chain. 2: force internal transfer by UID,
963    /// `address` must be a Bybit UID
964    #[serde(skip_serializing_if = "Option::is_none")]
965    pub force_chain: Option<i64>,
966    /// 0(default): withdrawal fee is deducted from `amount`. 1: fee is deducted separately by
967    /// the system
968    #[serde(skip_serializing_if = "Option::is_none")]
969    pub fee_type: Option<i64>,
970    /// Idempotent request ID, 1-32 alphanumeric characters
971    #[serde(skip_serializing_if = "Option::is_none")]
972    pub request_id: Option<String>,
973}
974
975impl WithdrawRequest {
976    pub fn new(
977        coin: String,
978        address: String,
979        amount: Decimal,
980        timestamp: i64,
981        account_type: String,
982    ) -> Self {
983        Self {
984            coin,
985            address,
986            amount,
987            timestamp,
988            account_type,
989            chain: None,
990            tag: None,
991            force_chain: None,
992            fee_type: None,
993            request_id: None,
994        }
995    }
996
997    pub fn with_chain(mut self, v: String) -> Self {
998        self.chain = Some(v);
999        self
1000    }
1001    pub fn with_tag(mut self, v: String) -> Self {
1002        self.tag = Some(v);
1003        self
1004    }
1005    pub fn with_force_chain(mut self, v: i64) -> Self {
1006        self.force_chain = Some(v);
1007        self
1008    }
1009    pub fn with_fee_type(mut self, v: i64) -> Self {
1010        self.fee_type = Some(v);
1011        self
1012    }
1013    pub fn with_request_id(mut self, v: String) -> Self {
1014        self.request_id = Some(v);
1015        self
1016    }
1017}
1018
1019#[derive(Debug, Deserialize, PartialEq)]
1020pub struct WithdrawResult {
1021    pub id: String,
1022}
1023
1024// --- Cancel Withdrawal ---
1025
1026/// Request body for [`Client::cancel_withdrawal`](crate::http::Client::cancel_withdrawal).
1027#[derive(Debug, Serialize, Clone)]
1028pub struct CancelWithdrawalRequest {
1029    pub id: String,
1030}
1031
1032impl CancelWithdrawalRequest {
1033    pub fn new(id: String) -> Self {
1034        Self { id }
1035    }
1036}
1037
1038#[derive(Debug, Deserialize, PartialEq)]
1039pub struct CancelWithdrawalResult {
1040    /// 0: fail, 1: success
1041    pub status: i64,
1042}
1043
1044// --- Get Withdrawable Amount ---
1045
1046/// Query params for
1047/// [`Client::get_withdrawable_amount`](crate::http::Client::get_withdrawable_amount).
1048#[derive(Debug, Serialize, Clone)]
1049#[serde(rename_all = "camelCase")]
1050pub struct GetWithdrawableAmountParams {
1051    /// Coin name, uppercase only
1052    pub coin: String,
1053}
1054
1055impl GetWithdrawableAmountParams {
1056    pub fn new(coin: String) -> Self {
1057        Self { coin }
1058    }
1059}
1060
1061#[derive(Debug, Deserialize, PartialEq)]
1062#[serde(rename_all = "camelCase")]
1063pub struct WithdrawableAmountResult {
1064    /// Amount frozen due to unresolved deposit risk, denominated in USD. "0" means all deposit
1065    /// risks have been released
1066    pub limit_amount_usd: Decimal,
1067    pub withdrawable_amount: WithdrawableAmountByWallet,
1068}
1069
1070#[derive(Debug, Deserialize, PartialEq)]
1071pub struct WithdrawableAmountByWallet {
1072    /// Omitted if the Spot wallet has been removed
1073    #[serde(rename = "SPOT")]
1074    pub spot: Option<WithdrawableAmountEntry>,
1075    #[serde(rename = "FUND")]
1076    pub fund: Option<WithdrawableAmountEntry>,
1077    #[serde(rename = "UTA")]
1078    pub uta: Option<WithdrawableAmountEntry>,
1079    /// Omitted if the coin isn't supported by Earn
1080    #[serde(rename = "EARN")]
1081    pub earn: Option<WithdrawableAmountEntry>,
1082}
1083
1084#[derive(Debug, Deserialize, PartialEq)]
1085#[serde(rename_all = "camelCase")]
1086pub struct WithdrawableAmountEntry {
1087    pub coin: String,
1088    pub withdrawable_amount: Decimal,
1089    pub available_balance: Decimal,
1090}
1091
1092// ═══════════════════════════════════════ Coin / Misc ═══════════════════════════════════════
1093
1094// --- Get Coin Info ---
1095
1096/// Query params for [`Client::get_coin_info`](crate::http::Client::get_coin_info).
1097#[derive(Debug, Serialize, Clone)]
1098#[serde(rename_all = "camelCase")]
1099pub struct GetCoinInfoParams {
1100    #[serde(skip_serializing_if = "Option::is_none")]
1101    pub coin: Option<String>,
1102}
1103
1104impl GetCoinInfoParams {
1105    pub fn new() -> Self {
1106        Self { coin: None }
1107    }
1108
1109    pub fn with_coin(mut self, v: String) -> Self {
1110        self.coin = Some(v);
1111        self
1112    }
1113}
1114
1115impl Default for GetCoinInfoParams {
1116    fn default() -> Self {
1117        Self::new()
1118    }
1119}
1120
1121#[derive(Debug, Deserialize, PartialEq)]
1122#[serde(rename_all = "camelCase")]
1123pub struct CoinInfoResult {
1124    pub rows: Vec<CoinInfo>,
1125}
1126
1127#[derive(Debug, Deserialize, PartialEq)]
1128#[serde(rename_all = "camelCase")]
1129pub struct CoinInfo {
1130    pub name: String,
1131    pub coin: String,
1132    pub chains: Vec<CoinChain>,
1133}
1134
1135#[derive(Debug, Deserialize, PartialEq)]
1136#[serde(rename_all = "camelCase")]
1137pub struct CoinChain {
1138    pub chain: String,
1139    pub chain_type: String,
1140    /// Number of block confirmations required to credit a deposit
1141    pub confirmation: String,
1142    /// Fixed withdrawal fee. Empty if withdrawal isn't supported
1143    #[serde(default, deserialize_with = "option_decimal")]
1144    pub withdraw_fee: Option<Decimal>,
1145    pub deposit_min: Decimal,
1146    pub withdraw_min: Decimal,
1147    /// Decimal precision for transactions on this chain
1148    pub min_accuracy: String,
1149    /// "0": suspended, "1": active
1150    pub chain_deposit: String,
1151    /// "0": suspended, "1": active
1152    pub chain_withdraw: String,
1153    pub withdraw_percentage_fee: Decimal,
1154    #[serde(default, deserialize_with = "empty_string_as_none")]
1155    pub contract_address: Option<String>,
1156    pub safe_confirm_number: String,
1157    /// Maximum withdrawal per transaction. "-1" means unlimited
1158    pub withdraw_max: String,
1159}
1160
1161// --- Get Coin Exchange Records ---
1162
1163/// Query params for
1164/// [`Client::get_exchange_order_record`](crate::http::Client::get_exchange_order_record).
1165#[derive(Clone, Debug, Serialize)]
1166#[serde(rename_all = "camelCase")]
1167pub struct GetExchangeOrderRecordParams {
1168    #[serde(skip_serializing_if = "Option::is_none")]
1169    pub from_coin: Option<String>,
1170    #[serde(skip_serializing_if = "Option::is_none")]
1171    pub to_coin: Option<String>,
1172    /// Limit for data size per page. [1, 50]. Default: 10
1173    #[serde(skip_serializing_if = "Option::is_none")]
1174    pub limit: Option<u64>,
1175    #[serde(skip_serializing_if = "Option::is_none")]
1176    pub cursor: Option<String>,
1177}
1178
1179impl GetExchangeOrderRecordParams {
1180    pub fn new() -> Self {
1181        Self {
1182            from_coin: None,
1183            to_coin: None,
1184            limit: None,
1185            cursor: None,
1186        }
1187    }
1188
1189    pub fn with_from_coin(mut self, v: String) -> Self {
1190        self.from_coin = Some(v);
1191        self
1192    }
1193    pub fn with_to_coin(mut self, v: String) -> Self {
1194        self.to_coin = Some(v);
1195        self
1196    }
1197    pub fn with_limit(mut self, v: u64) -> Self {
1198        self.limit = Some(v);
1199        self
1200    }
1201    pub fn with_cursor(mut self, v: String) -> Self {
1202        self.cursor = Some(v);
1203        self
1204    }
1205}
1206
1207impl Default for GetExchangeOrderRecordParams {
1208    fn default() -> Self {
1209        Self::new()
1210    }
1211}
1212
1213#[derive(Debug, Deserialize, PartialEq)]
1214#[serde(rename_all = "camelCase")]
1215pub struct ExchangeOrderRecords {
1216    pub order_body: Vec<ExchangeOrderRecord>,
1217    #[serde(default, deserialize_with = "empty_string_as_none")]
1218    pub next_page_cursor: Option<String>,
1219}
1220
1221#[derive(Debug, Deserialize, PartialEq)]
1222#[serde(rename_all = "camelCase")]
1223pub struct ExchangeOrderRecord {
1224    pub from_coin: String,
1225    pub from_amount: Decimal,
1226    pub to_coin: String,
1227    pub to_amount: Decimal,
1228    pub exchange_rate: Decimal,
1229    /// Timestamp in seconds
1230    #[serde(deserialize_with = "number")]
1231    pub created_time: Timestamp,
1232    pub exchange_tx_id: String,
1233}
1234
1235// --- Get Delivery Record ---
1236
1237/// Query params for
1238/// [`Client::get_delivery_record`](crate::http::Client::get_delivery_record).
1239#[derive(Clone, Debug, Serialize)]
1240#[serde(rename_all = "camelCase")]
1241pub struct GetDeliveryRecordParams {
1242    /// linear, inverse, option
1243    pub category: Category,
1244    #[serde(skip_serializing_if = "Option::is_none")]
1245    pub symbol: Option<String>,
1246    /// Without start_time and end_time, returns the past 30 days by default
1247    #[serde(skip_serializing_if = "Option::is_none")]
1248    pub start_time: Option<Timestamp>,
1249    #[serde(skip_serializing_if = "Option::is_none")]
1250    pub end_time: Option<Timestamp>,
1251    /// Expiry date, e.g. "25MAR22". Returns all if not specified
1252    #[serde(skip_serializing_if = "Option::is_none")]
1253    pub exp_date: Option<String>,
1254    /// Limit for data size per page. [1, 50]. Default: 20
1255    #[serde(skip_serializing_if = "Option::is_none")]
1256    pub limit: Option<u64>,
1257    #[serde(skip_serializing_if = "Option::is_none")]
1258    pub cursor: Option<String>,
1259}
1260
1261impl GetDeliveryRecordParams {
1262    pub fn new(category: Category) -> Self {
1263        Self {
1264            category,
1265            symbol: None,
1266            start_time: None,
1267            end_time: None,
1268            exp_date: None,
1269            limit: None,
1270            cursor: None,
1271        }
1272    }
1273
1274    pub fn with_symbol(mut self, v: String) -> Self {
1275        self.symbol = Some(v);
1276        self
1277    }
1278    pub fn with_start_time(mut self, v: Timestamp) -> Self {
1279        self.start_time = Some(v);
1280        self
1281    }
1282    pub fn with_end_time(mut self, v: Timestamp) -> Self {
1283        self.end_time = Some(v);
1284        self
1285    }
1286    pub fn with_exp_date(mut self, v: String) -> Self {
1287        self.exp_date = Some(v);
1288        self
1289    }
1290    pub fn with_limit(mut self, v: u64) -> Self {
1291        self.limit = Some(v);
1292        self
1293    }
1294    pub fn with_cursor(mut self, v: String) -> Self {
1295        self.cursor = Some(v);
1296        self
1297    }
1298}
1299
1300#[derive(Debug, Deserialize, PartialEq)]
1301#[serde(rename_all = "camelCase")]
1302pub struct DeliveryRecord {
1303    pub delivery_time: Timestamp,
1304    pub symbol: String,
1305    pub side: Side,
1306    pub position: Decimal,
1307    pub entry_price: Decimal,
1308    pub delivery_price: Decimal,
1309    /// Options only
1310    #[serde(default, deserialize_with = "option_decimal")]
1311    pub strike: Option<Decimal>,
1312    pub fee: Decimal,
1313    pub delivery_rpl: Decimal,
1314}
1315
1316// --- Get USDC Session Settlement ---
1317
1318/// Query params for
1319/// [`Client::get_settlement_record`](crate::http::Client::get_settlement_record).
1320#[derive(Clone, Debug, Serialize)]
1321#[serde(rename_all = "camelCase")]
1322pub struct GetSettlementRecordParams {
1323    /// Must be `linear` (USDC contracts)
1324    pub category: Category,
1325    #[serde(skip_serializing_if = "Option::is_none")]
1326    pub symbol: Option<String>,
1327    /// Without start_time and end_time, returns the past 30 days by default
1328    #[serde(skip_serializing_if = "Option::is_none")]
1329    pub start_time: Option<Timestamp>,
1330    #[serde(skip_serializing_if = "Option::is_none")]
1331    pub end_time: Option<Timestamp>,
1332    /// Limit for data size per page. [1, 50]. Default: 20
1333    #[serde(skip_serializing_if = "Option::is_none")]
1334    pub limit: Option<u64>,
1335    #[serde(skip_serializing_if = "Option::is_none")]
1336    pub cursor: Option<String>,
1337}
1338
1339impl GetSettlementRecordParams {
1340    pub fn new(category: Category) -> Self {
1341        Self {
1342            category,
1343            symbol: None,
1344            start_time: None,
1345            end_time: None,
1346            limit: None,
1347            cursor: None,
1348        }
1349    }
1350
1351    pub fn with_symbol(mut self, v: String) -> Self {
1352        self.symbol = Some(v);
1353        self
1354    }
1355    pub fn with_start_time(mut self, v: Timestamp) -> Self {
1356        self.start_time = Some(v);
1357        self
1358    }
1359    pub fn with_end_time(mut self, v: Timestamp) -> Self {
1360        self.end_time = Some(v);
1361        self
1362    }
1363    pub fn with_limit(mut self, v: u64) -> Self {
1364        self.limit = Some(v);
1365        self
1366    }
1367    pub fn with_cursor(mut self, v: String) -> Self {
1368        self.cursor = Some(v);
1369        self
1370    }
1371}
1372
1373#[derive(Debug, Deserialize, PartialEq)]
1374#[serde(rename_all = "camelCase")]
1375pub struct SettlementRecord {
1376    pub symbol: String,
1377    pub side: Side,
1378    pub size: Decimal,
1379    pub session_avg_price: Decimal,
1380    pub mark_price: Decimal,
1381    pub realised_pnl: Decimal,
1382    #[serde(deserialize_with = "number")]
1383    pub created_time: Timestamp,
1384}
1385
1386// --- Get Coin Greeks ---
1387
1388/// Query params for [`Client::get_coin_greeks`](crate::http::Client::get_coin_greeks).
1389#[derive(Debug, Serialize, Clone)]
1390#[serde(rename_all = "camelCase")]
1391pub struct GetCoinGreeksParams {
1392    #[serde(skip_serializing_if = "Option::is_none")]
1393    pub base_coin: Option<String>,
1394}
1395
1396impl GetCoinGreeksParams {
1397    pub fn new() -> Self {
1398        Self { base_coin: None }
1399    }
1400
1401    pub fn with_base_coin(mut self, v: String) -> Self {
1402        self.base_coin = Some(v);
1403        self
1404    }
1405}
1406
1407impl Default for GetCoinGreeksParams {
1408    fn default() -> Self {
1409        Self::new()
1410    }
1411}
1412
1413#[derive(Debug, Deserialize, PartialEq)]
1414#[serde(rename_all = "camelCase")]
1415pub struct CoinGreeks {
1416    pub base_coin: String,
1417    pub total_delta: Decimal,
1418    pub total_gamma: Decimal,
1419    pub total_vega: Decimal,
1420    pub total_theta: Decimal,
1421}