Skip to main content

bybit_api/models/
asset.rs

1//! Asset models.
2
3use crate::error::{BybitError, Result};
4use serde::{Deserialize, Serialize};
5use tracing::warn;
6
7/// Coin info response.
8#[derive(Debug, Clone, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct CoinInfoResponse {
11    /// List of coins
12    pub rows: Vec<CoinInfo>,
13}
14
15/// Coin info.
16#[derive(Debug, Clone, Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub struct CoinInfo {
19    /// Coin name
20    pub coin: String,
21    /// Full name
22    #[serde(default)]
23    pub name: String,
24    /// Remain amount
25    #[serde(default)]
26    pub remain_amount: String,
27    /// Chain list
28    #[serde(default)]
29    pub chains: Vec<ChainInfo>,
30}
31
32/// Chain info.
33#[derive(Debug, Clone, Deserialize)]
34#[serde(rename_all = "camelCase")]
35pub struct ChainInfo {
36    /// Chain
37    pub chain: String,
38    /// Chain type
39    #[serde(default)]
40    pub chain_type: String,
41    /// Confirmation
42    #[serde(default)]
43    pub confirmation: String,
44    /// Withdraw fee
45    #[serde(default)]
46    pub withdraw_fee: String,
47    /// Deposit min
48    #[serde(default)]
49    pub deposit_min: String,
50    /// Withdraw min
51    #[serde(default)]
52    pub withdraw_min: String,
53    /// Chain deposit enabled
54    #[serde(default)]
55    pub chain_deposit: String,
56    /// Chain withdraw enabled
57    #[serde(default)]
58    pub chain_withdraw: String,
59}
60
61/// Internal transfer request.
62#[derive(Debug, Clone, Serialize)]
63#[serde(rename_all = "camelCase")]
64pub struct InternalTransferParams {
65    /// Transfer ID (UUID)
66    pub transfer_id: String,
67    /// Coin
68    pub coin: String,
69    /// Amount
70    pub amount: String,
71    /// From account type
72    pub from_account_type: String,
73    /// To account type
74    pub to_account_type: String,
75}
76
77impl InternalTransferParams {
78    /// Validate transfer parameters.
79    pub fn validate(&self) -> Result<()> {
80        if self.coin.is_empty() {
81            return Err(BybitError::InvalidParam("coin cannot be empty".into()));
82        }
83
84        let amount: rust_decimal::Decimal = self
85            .amount
86            .parse()
87            .map_err(|_| BybitError::InvalidParam("amount must be a valid number".into()))?;
88        if amount <= rust_decimal::Decimal::ZERO {
89            return Err(BybitError::InvalidParam("amount must be positive".into()));
90        }
91
92        Ok(())
93    }
94}
95
96/// Transfer response.
97#[derive(Debug, Clone, Deserialize)]
98#[serde(rename_all = "camelCase")]
99pub struct TransferResponse {
100    /// Transfer ID
101    pub transfer_id: String,
102}
103
104/// Transfer list response.
105#[derive(Debug, Clone, Deserialize)]
106#[serde(rename_all = "camelCase")]
107pub struct TransferList {
108    /// List of transfers
109    pub list: Vec<TransferRecord>,
110    /// Next page cursor
111    #[serde(default)]
112    pub next_page_cursor: String,
113}
114
115/// Transfer record.
116#[derive(Debug, Clone, Deserialize)]
117#[serde(rename_all = "camelCase")]
118pub struct TransferRecord {
119    /// Transfer ID
120    pub transfer_id: String,
121    /// Coin
122    pub coin: String,
123    /// Amount
124    pub amount: String,
125    /// From account type
126    pub from_account_type: String,
127    /// To account type
128    pub to_account_type: String,
129    /// Timestamp
130    pub timestamp: String,
131    /// Status
132    pub status: String,
133}
134
135/// Deposit address response.
136#[derive(Debug, Clone, Deserialize)]
137#[serde(rename_all = "camelCase")]
138pub struct DepositAddressResponse {
139    /// Coin
140    pub coin: String,
141    /// Chains
142    pub chains: Vec<DepositChainAddress>,
143}
144
145/// Deposit chain address.
146#[derive(Debug, Clone, Deserialize)]
147#[serde(rename_all = "camelCase")]
148pub struct DepositChainAddress {
149    /// Chain type
150    pub chain_type: String,
151    /// Address deposit
152    pub address_deposit: String,
153    /// Tag deposit
154    #[serde(default)]
155    pub tag_deposit: String,
156    /// Chain
157    pub chain: String,
158}
159
160/// Deposit records response.
161#[derive(Debug, Clone, Deserialize)]
162#[serde(rename_all = "camelCase")]
163pub struct DepositRecords {
164    /// Rows
165    pub rows: Vec<DepositRecord>,
166    /// Next page cursor
167    #[serde(default)]
168    pub next_page_cursor: String,
169}
170
171/// Deposit record.
172#[derive(Debug, Clone, Deserialize)]
173#[serde(rename_all = "camelCase")]
174pub struct DepositRecord {
175    /// Coin
176    pub coin: String,
177    /// Chain
178    pub chain: String,
179    /// Amount
180    pub amount: String,
181    /// Tx ID
182    pub tx_i_d: String,
183    /// Status
184    pub status: i32,
185    /// To address
186    pub to_address: String,
187    /// Tag
188    #[serde(default)]
189    pub tag: String,
190    /// Deposit fee
191    #[serde(default)]
192    pub deposit_fee: String,
193    /// Success at
194    #[serde(default)]
195    pub success_at: String,
196    /// Confirmations
197    #[serde(default)]
198    pub confirmations: String,
199    /// Tx index
200    #[serde(default)]
201    pub tx_index: String,
202    /// Block hash
203    #[serde(default)]
204    pub block_hash: String,
205}
206
207/// Withdraw request.
208#[derive(Debug, Clone, Serialize)]
209#[serde(rename_all = "camelCase")]
210pub struct WithdrawParams {
211    /// Coin
212    pub coin: String,
213    /// Chain
214    pub chain: String,
215    /// Address
216    pub address: String,
217    /// Tag
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub tag: Option<String>,
220    /// Amount
221    pub amount: String,
222    /// Timestamp
223    pub timestamp: u64,
224    /// Force chain (for tokens on multiple chains)
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub force_chain: Option<i32>,
227    /// Account type
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub account_type: Option<String>,
230}
231
232impl WithdrawParams {
233    /// Create withdraw params.
234    pub fn new(coin: &str, chain: &str, address: &str, amount: &str) -> Self {
235        Self {
236            coin: coin.to_string(),
237            chain: chain.to_string(),
238            address: address.to_string(),
239            tag: None,
240            amount: amount.to_string(),
241            timestamp: crate::auth::get_timestamp(),
242            force_chain: None,
243            account_type: None,
244        }
245    }
246
247    /// Validate withdraw parameters (FUND SAFETY).
248    pub fn validate(&self) -> Result<()> {
249        if self.coin.is_empty() {
250            return Err(BybitError::InvalidParam("coin cannot be empty".into()));
251        }
252
253        if self.chain.is_empty() {
254            return Err(BybitError::InvalidParam("chain cannot be empty".into()));
255        }
256
257        if self.address.is_empty() {
258            return Err(BybitError::InvalidParam("address cannot be empty".into()));
259        }
260
261        let amount: rust_decimal::Decimal = self
262            .amount
263            .parse()
264            .map_err(|_| BybitError::InvalidParam("amount must be a valid number".into()))?;
265        if amount <= rust_decimal::Decimal::ZERO {
266            return Err(BybitError::InvalidParam("amount must be positive".into()));
267        }
268
269        // Log warning for large withdrawals
270        if amount > rust_decimal::Decimal::from(10000) {
271            warn!(
272                coin = %self.coin,
273                amount = %self.amount,
274                address = %self.address,
275                "Large withdrawal detected - please verify details"
276            );
277        }
278
279        Ok(())
280    }
281}
282
283/// Withdraw response.
284#[derive(Debug, Clone, Deserialize)]
285#[serde(rename_all = "camelCase")]
286pub struct WithdrawResponse {
287    /// Withdraw ID
288    pub id: String,
289}
290
291/// Withdraw records response.
292#[derive(Debug, Clone, Deserialize)]
293#[serde(rename_all = "camelCase")]
294pub struct WithdrawRecords {
295    /// Rows
296    pub rows: Vec<WithdrawRecord>,
297    /// Next page cursor
298    #[serde(default)]
299    pub next_page_cursor: String,
300}
301
302/// Withdraw record.
303#[derive(Debug, Clone, Deserialize)]
304#[serde(rename_all = "camelCase")]
305pub struct WithdrawRecord {
306    /// Withdraw ID
307    #[serde(default)]
308    pub withdraw_id: String,
309    /// Tx ID
310    #[serde(default)]
311    pub tx_i_d: String,
312    /// Withdraw type
313    #[serde(default)]
314    pub withdraw_type: i32,
315    /// Coin
316    pub coin: String,
317    /// Chain
318    pub chain: String,
319    /// Amount
320    pub amount: String,
321    /// Withdraw fee
322    #[serde(default)]
323    pub withdraw_fee: String,
324    /// Status
325    pub status: String,
326    /// To address
327    pub to_address: String,
328    /// Tag
329    #[serde(default)]
330    pub tag: String,
331    /// Create time
332    pub create_time: String,
333    /// Update time
334    pub update_time: String,
335}
336
337/// Withdrawable amount response.
338#[derive(Debug, Clone, Deserialize)]
339#[serde(rename_all = "camelCase")]
340pub struct WithdrawableAmount {
341    /// Limit amount info
342    #[serde(default)]
343    pub limit_amount_usd: String,
344    /// Withdrawable amount
345    pub withdrawable_amount: WithdrawableAmountDetail,
346}
347
348/// Withdrawable amount detail.
349#[derive(Debug, Clone, Deserialize)]
350#[serde(rename_all = "camelCase")]
351pub struct WithdrawableAmountDetail {
352    /// Spot
353    #[serde(default)]
354    pub s_p_o_t: WithdrawableAmountItem,
355    /// Fund
356    #[serde(default)]
357    pub f_u_n_d: WithdrawableAmountItem,
358}
359
360/// Withdrawable amount item.
361#[derive(Debug, Clone, Default, Deserialize)]
362#[serde(rename_all = "camelCase")]
363pub struct WithdrawableAmountItem {
364    /// Coin
365    #[serde(default)]
366    pub coin: String,
367    /// Withdrawable amount
368    #[serde(default)]
369    pub withdrawable_amount: String,
370    /// Available balance
371    #[serde(default)]
372    pub available_balance: String,
373}
374
375/// Cancel withdraw request.
376#[derive(Debug, Clone, Serialize)]
377#[serde(rename_all = "camelCase")]
378pub struct CancelWithdrawParams {
379    /// Withdraw ID
380    pub id: String,
381}