ccxt_exchanges/binance/rest/
funding.rs

1//! Binance funding operations.
2//!
3//! This module contains all deposit and withdrawal methods including
4//! deposit addresses, withdrawals, transfers, and transaction history.
5
6use super::super::{Binance, parser};
7use ccxt_core::{
8    Error, Result,
9    types::{DepositAddress, DepositWithdrawFee, Transaction, TransactionType},
10};
11use std::collections::BTreeMap;
12
13impl Binance {
14    // ==================== Deposit Address Methods ====================
15
16    /// Fetch deposit address for a currency.
17    ///
18    /// # Arguments
19    ///
20    /// * `code` - Currency code (e.g., "BTC", "USDT").
21    /// * `params` - Optional parameters:
22    ///   - `network`: Network type (e.g., "ETH", "BSC", "TRX").
23    ///
24    /// # Returns
25    ///
26    /// Returns a [`DepositAddress`] with the deposit address and optional tag.
27    ///
28    /// # Errors
29    ///
30    /// Returns an error if authentication fails or the API request fails.
31    ///
32    /// # Notes
33    ///
34    /// - Binance automatically generates a new address if none exists.
35    /// - Some currencies require the network parameter (e.g., USDT can be on ETH/BSC/TRX).
36    ///
37    /// # Example
38    ///
39    /// ```no_run
40    /// # use ccxt_exchanges::binance::Binance;
41    /// # use ccxt_core::ExchangeConfig;
42    /// # use std::collections::BTreeMap;
43    /// # async fn example() -> ccxt_core::Result<()> {
44    /// let binance = Binance::new(ExchangeConfig::default())?;
45    ///
46    /// // Fetch BTC deposit address
47    /// let addr = binance.fetch_deposit_address("BTC", None).await?;
48    /// println!("BTC address: {}", addr.address);
49    ///
50    /// // Fetch USDT deposit address on TRX network
51    /// let mut params = BTreeMap::new();
52    /// params.insert("network".to_string(), "TRX".to_string());
53    /// let addr = binance.fetch_deposit_address("USDT", Some(params)).await?;
54    /// println!("USDT-TRX address: {}", addr.address);
55    /// # Ok(())
56    /// # }
57    /// ```
58    pub async fn fetch_deposit_address(
59        &self,
60        code: &str,
61        params: Option<BTreeMap<String, String>>,
62    ) -> Result<DepositAddress> {
63        let url = format!("{}/capital/deposit/address", self.urls().sapi);
64
65        let data = self
66            .signed_request(url)
67            .param("coin", code.to_uppercase())
68            .params(params.unwrap_or_default())
69            .execute()
70            .await?;
71
72        parser::parse_deposit_address(&data)
73    }
74
75    // ==================== Withdrawal Methods ====================
76
77    /// Withdraw funds to an external address.
78    ///
79    /// # Arguments
80    ///
81    /// * `code` - Currency code (e.g., "BTC", "USDT").
82    /// * `amount` - Withdrawal amount.
83    /// * `address` - Withdrawal address.
84    /// * `params` - Optional parameters:
85    ///   - `tag`: Address tag (e.g., XRP tag).
86    ///   - `network`: Network type (e.g., "ETH", "BSC", "TRX").
87    ///   - `addressTag`: Address tag (alias for `tag`).
88    ///   - `name`: Address memo name.
89    ///   - `walletType`: Wallet type (0=spot, 1=funding).
90    ///
91    /// # Returns
92    ///
93    /// Returns a [`Transaction`] with withdrawal details.
94    ///
95    /// # Errors
96    ///
97    /// Returns an error if authentication fails or the API request fails.
98    ///
99    /// # Example
100    ///
101    /// ```no_run
102    /// # use ccxt_exchanges::binance::Binance;
103    /// # use ccxt_core::ExchangeConfig;
104    /// # use std::collections::BTreeMap;
105    /// # async fn example() -> ccxt_core::Result<()> {
106    /// let binance = Binance::new(ExchangeConfig::default())?;
107    ///
108    /// // Basic withdrawal
109    /// let tx = binance.withdraw("USDT", "100.0", "TXxxx...", None).await?;
110    /// println!("Withdrawal ID: {}", tx.id);
111    ///
112    /// // Withdrawal with specific network
113    /// let mut params = BTreeMap::new();
114    /// params.insert("network".to_string(), "TRX".to_string());
115    /// let tx = binance.withdraw("USDT", "100.0", "TXxxx...", Some(params)).await?;
116    /// # Ok(())
117    /// # }
118    /// ```
119    pub async fn withdraw(
120        &self,
121        code: &str,
122        amount: &str,
123        address: &str,
124        params: Option<BTreeMap<String, String>>,
125    ) -> Result<Transaction> {
126        let url = format!("{}/capital/withdraw/apply", self.urls().sapi);
127
128        let mut request_params = params.unwrap_or_default();
129
130        // Handle optional tag parameter (supports both 'tag' and 'addressTag' names)
131        if let Some(tag) = request_params.get("tag").cloned() {
132            request_params.insert("addressTag".to_string(), tag);
133        }
134
135        let data = self
136            .signed_request(url)
137            .method(super::super::signed_request::HttpMethod::Post)
138            .param("coin", code.to_uppercase())
139            .param("address", address)
140            .param("amount", amount)
141            .params(request_params)
142            .execute()
143            .await?;
144
145        parser::parse_transaction(&data, TransactionType::Withdrawal)
146    }
147
148    // ==================== Transaction History Methods ====================
149
150    /// Fetch deposit history.
151    ///
152    /// # Arguments
153    ///
154    /// * `code` - Optional currency code (e.g., "BTC", "USDT").
155    /// * `since` - Optional start timestamp in milliseconds.
156    /// * `limit` - Optional quantity limit.
157    /// * `params` - Optional parameters:
158    ///   - `coin`: Currency (overrides code parameter).
159    ///   - `status`: Status filter (0=pending, 6=credited, 1=success).
160    ///   - `startTime`: Start timestamp in milliseconds.
161    ///   - `endTime`: End timestamp in milliseconds.
162    ///   - `offset`: Offset for pagination.
163    ///   - `txId`: Transaction ID filter.
164    ///
165    /// # Returns
166    ///
167    /// Returns a vector of [`Transaction`] records.
168    ///
169    /// # Notes
170    ///
171    /// - Maximum query range: 90 days.
172    /// - Default returns last 90 days if no time range provided.
173    ///
174    /// # Example
175    ///
176    /// ```no_run
177    /// # use ccxt_exchanges::binance::Binance;
178    /// # use ccxt_core::ExchangeConfig;
179    /// # async fn example() -> ccxt_core::Result<()> {
180    /// let binance = Binance::new(ExchangeConfig::default())?;
181    ///
182    /// // Query all deposits
183    /// let deposits = binance.fetch_deposits(None, None, Some(100), None).await?;
184    /// println!("Total deposits: {}", deposits.len());
185    ///
186    /// // Query BTC deposits
187    /// let btc_deposits = binance.fetch_deposits(Some("BTC"), None, None, None).await?;
188    /// # Ok(())
189    /// # }
190    /// ```
191    pub async fn fetch_deposits(
192        &self,
193        code: Option<&str>,
194        since: Option<i64>,
195        limit: Option<i64>,
196        params: Option<BTreeMap<String, String>>,
197    ) -> Result<Vec<Transaction>> {
198        let url = format!("{}/capital/deposit/hisrec", self.urls().sapi);
199
200        let data = self
201            .signed_request(url)
202            .optional_param("coin", code.map(str::to_uppercase))
203            .optional_param("startTime", since)
204            .optional_param("limit", limit)
205            .params(params.unwrap_or_default())
206            .execute()
207            .await?;
208
209        if let Some(arr) = data.as_array() {
210            arr.iter()
211                .map(|item| parser::parse_transaction(item, TransactionType::Deposit))
212                .collect()
213        } else {
214            Err(Error::invalid_request("Expected array response"))
215        }
216    }
217
218    /// Fetch withdrawal history.
219    ///
220    /// # Arguments
221    ///
222    /// * `code` - Optional currency code (e.g., "BTC", "USDT").
223    /// * `since` - Optional start timestamp in milliseconds.
224    /// * `limit` - Optional quantity limit.
225    /// * `params` - Optional parameters:
226    ///   - `coin`: Currency (overrides code parameter).
227    ///   - `withdrawOrderId`: Withdrawal order ID.
228    ///   - `status`: Status filter (0=email sent, 1=cancelled, 2=awaiting approval,
229    ///     3=rejected, 4=processing, 5=failure, 6=completed).
230    ///   - `startTime`: Start timestamp in milliseconds.
231    ///   - `endTime`: End timestamp in milliseconds.
232    ///   - `offset`: Offset for pagination.
233    ///
234    /// # Returns
235    ///
236    /// Returns a vector of [`Transaction`] records.
237    ///
238    /// # Notes
239    ///
240    /// - Maximum query range: 90 days.
241    /// - Default returns last 90 days if no time range provided.
242    ///
243    /// # Example
244    ///
245    /// ```no_run
246    /// # use ccxt_exchanges::binance::Binance;
247    /// # use ccxt_core::ExchangeConfig;
248    /// # async fn example() -> ccxt_core::Result<()> {
249    /// let binance = Binance::new(ExchangeConfig::default())?;
250    ///
251    /// // Query all withdrawals
252    /// let withdrawals = binance.fetch_withdrawals(None, None, Some(100), None).await?;
253    /// println!("Total withdrawals: {}", withdrawals.len());
254    ///
255    /// // Query USDT withdrawals
256    /// let usdt_withdrawals = binance.fetch_withdrawals(Some("USDT"), None, None, None).await?;
257    /// # Ok(())
258    /// # }
259    /// ```
260    pub async fn fetch_withdrawals(
261        &self,
262        code: Option<&str>,
263        since: Option<i64>,
264        limit: Option<i64>,
265        params: Option<BTreeMap<String, String>>,
266    ) -> Result<Vec<Transaction>> {
267        let url = format!("{}/capital/withdraw/history", self.urls().sapi);
268
269        let data = self
270            .signed_request(url)
271            .optional_param("coin", code.map(str::to_uppercase))
272            .optional_param("startTime", since)
273            .optional_param("limit", limit)
274            .params(params.unwrap_or_default())
275            .execute()
276            .await?;
277
278        if let Some(arr) = data.as_array() {
279            arr.iter()
280                .map(|item| parser::parse_transaction(item, TransactionType::Withdrawal))
281                .collect()
282        } else {
283            Err(Error::invalid_request("Expected array response"))
284        }
285    }
286
287    // ==================== Fee Methods ====================
288
289    /// Fetch deposit and withdrawal fees for currencies.
290    ///
291    /// # Arguments
292    ///
293    /// * `currency` - Optional currency code to filter results.
294    /// * `params` - Optional additional parameters.
295    ///
296    /// # Returns
297    ///
298    /// Returns a vector of [`DepositWithdrawFee`] structures.
299    ///
300    /// # Example
301    ///
302    /// ```no_run
303    /// # use ccxt_exchanges::binance::Binance;
304    /// # use ccxt_core::ExchangeConfig;
305    /// # async fn example() -> ccxt_core::Result<()> {
306    /// let binance = Binance::new(ExchangeConfig::default())?;
307    ///
308    /// // Fetch all fees
309    /// let fees = binance.fetch_deposit_withdraw_fees(None, None).await?;
310    ///
311    /// // Fetch fees for specific currency
312    /// let btc_fees = binance.fetch_deposit_withdraw_fees(Some("BTC"), None).await?;
313    /// # Ok(())
314    /// # }
315    /// ```
316    pub async fn fetch_deposit_withdraw_fees(
317        &self,
318        currency: Option<&str>,
319        params: Option<BTreeMap<String, String>>,
320    ) -> Result<Vec<DepositWithdrawFee>> {
321        // Note: Binance /sapi/v1/capital/config/getall endpoint returns all currencies
322        // If currency is specified, client-side filtering is required
323
324        let url = format!("{}/capital/config/getall", self.urls().sapi);
325
326        let data = self
327            .signed_request(url)
328            .params(params.unwrap_or_default())
329            .execute()
330            .await?;
331
332        let all_fees = parser::parse_deposit_withdraw_fees(&data)?;
333
334        // Filter results if currency is specified
335        if let Some(code) = currency {
336            let code_upper = code.to_uppercase();
337            Ok(all_fees
338                .into_iter()
339                .filter(|fee| fee.currency == code_upper)
340                .collect())
341        } else {
342            Ok(all_fees)
343        }
344    }
345}