1use crate::error::{BybitError, Result};
4use serde::{Deserialize, Serialize};
5use tracing::warn;
6
7#[derive(Debug, Clone, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct CoinInfoResponse {
11 pub rows: Vec<CoinInfo>,
13}
14
15#[derive(Debug, Clone, Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub struct CoinInfo {
19 pub coin: String,
21 #[serde(default)]
23 pub name: String,
24 #[serde(default)]
26 pub remain_amount: String,
27 #[serde(default)]
29 pub chains: Vec<ChainInfo>,
30}
31
32#[derive(Debug, Clone, Deserialize)]
34#[serde(rename_all = "camelCase")]
35pub struct ChainInfo {
36 pub chain: String,
38 #[serde(default)]
40 pub chain_type: String,
41 #[serde(default)]
43 pub confirmation: String,
44 #[serde(default)]
46 pub withdraw_fee: String,
47 #[serde(default)]
49 pub deposit_min: String,
50 #[serde(default)]
52 pub withdraw_min: String,
53 #[serde(default)]
55 pub chain_deposit: String,
56 #[serde(default)]
58 pub chain_withdraw: String,
59}
60
61#[derive(Debug, Clone, Serialize)]
63#[serde(rename_all = "camelCase")]
64pub struct InternalTransferParams {
65 pub transfer_id: String,
67 pub coin: String,
69 pub amount: String,
71 pub from_account_type: String,
73 pub to_account_type: String,
75}
76
77impl InternalTransferParams {
78 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#[derive(Debug, Clone, Deserialize)]
98#[serde(rename_all = "camelCase")]
99pub struct TransferResponse {
100 pub transfer_id: String,
102}
103
104#[derive(Debug, Clone, Deserialize)]
106#[serde(rename_all = "camelCase")]
107pub struct TransferList {
108 pub list: Vec<TransferRecord>,
110 #[serde(default)]
112 pub next_page_cursor: String,
113}
114
115#[derive(Debug, Clone, Deserialize)]
117#[serde(rename_all = "camelCase")]
118pub struct TransferRecord {
119 pub transfer_id: String,
121 pub coin: String,
123 pub amount: String,
125 pub from_account_type: String,
127 pub to_account_type: String,
129 pub timestamp: String,
131 pub status: String,
133}
134
135#[derive(Debug, Clone, Deserialize)]
137#[serde(rename_all = "camelCase")]
138pub struct DepositAddressResponse {
139 pub coin: String,
141 pub chains: Vec<DepositChainAddress>,
143}
144
145#[derive(Debug, Clone, Deserialize)]
147#[serde(rename_all = "camelCase")]
148pub struct DepositChainAddress {
149 pub chain_type: String,
151 pub address_deposit: String,
153 #[serde(default)]
155 pub tag_deposit: String,
156 pub chain: String,
158}
159
160#[derive(Debug, Clone, Deserialize)]
162#[serde(rename_all = "camelCase")]
163pub struct DepositRecords {
164 pub rows: Vec<DepositRecord>,
166 #[serde(default)]
168 pub next_page_cursor: String,
169}
170
171#[derive(Debug, Clone, Deserialize)]
173#[serde(rename_all = "camelCase")]
174pub struct DepositRecord {
175 pub coin: String,
177 pub chain: String,
179 pub amount: String,
181 pub tx_i_d: String,
183 pub status: i32,
185 pub to_address: String,
187 #[serde(default)]
189 pub tag: String,
190 #[serde(default)]
192 pub deposit_fee: String,
193 #[serde(default)]
195 pub success_at: String,
196 #[serde(default)]
198 pub confirmations: String,
199 #[serde(default)]
201 pub tx_index: String,
202 #[serde(default)]
204 pub block_hash: String,
205}
206
207#[derive(Debug, Clone, Serialize)]
209#[serde(rename_all = "camelCase")]
210pub struct WithdrawParams {
211 pub coin: String,
213 pub chain: String,
215 pub address: String,
217 #[serde(skip_serializing_if = "Option::is_none")]
219 pub tag: Option<String>,
220 pub amount: String,
222 pub timestamp: u64,
224 #[serde(skip_serializing_if = "Option::is_none")]
226 pub force_chain: Option<i32>,
227 #[serde(skip_serializing_if = "Option::is_none")]
229 pub account_type: Option<String>,
230}
231
232impl WithdrawParams {
233 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 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 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#[derive(Debug, Clone, Deserialize)]
285#[serde(rename_all = "camelCase")]
286pub struct WithdrawResponse {
287 pub id: String,
289}
290
291#[derive(Debug, Clone, Deserialize)]
293#[serde(rename_all = "camelCase")]
294pub struct WithdrawRecords {
295 pub rows: Vec<WithdrawRecord>,
297 #[serde(default)]
299 pub next_page_cursor: String,
300}
301
302#[derive(Debug, Clone, Deserialize)]
304#[serde(rename_all = "camelCase")]
305pub struct WithdrawRecord {
306 #[serde(default)]
308 pub withdraw_id: String,
309 #[serde(default)]
311 pub tx_i_d: String,
312 #[serde(default)]
314 pub withdraw_type: i32,
315 pub coin: String,
317 pub chain: String,
319 pub amount: String,
321 #[serde(default)]
323 pub withdraw_fee: String,
324 pub status: String,
326 pub to_address: String,
328 #[serde(default)]
330 pub tag: String,
331 pub create_time: String,
333 pub update_time: String,
335}
336
337#[derive(Debug, Clone, Deserialize)]
339#[serde(rename_all = "camelCase")]
340pub struct WithdrawableAmount {
341 #[serde(default)]
343 pub limit_amount_usd: String,
344 pub withdrawable_amount: WithdrawableAmountDetail,
346}
347
348#[derive(Debug, Clone, Deserialize)]
350#[serde(rename_all = "camelCase")]
351pub struct WithdrawableAmountDetail {
352 #[serde(default)]
354 pub s_p_o_t: WithdrawableAmountItem,
355 #[serde(default)]
357 pub f_u_n_d: WithdrawableAmountItem,
358}
359
360#[derive(Debug, Clone, Default, Deserialize)]
362#[serde(rename_all = "camelCase")]
363pub struct WithdrawableAmountItem {
364 #[serde(default)]
366 pub coin: String,
367 #[serde(default)]
369 pub withdrawable_amount: String,
370 #[serde(default)]
372 pub available_balance: String,
373}
374
375#[derive(Debug, Clone, Serialize)]
377#[serde(rename_all = "camelCase")]
378pub struct CancelWithdrawParams {
379 pub id: String,
381}