Skip to main content

ccxt_core/traits/
funding.rs

1//! Funding trait definition.
2//!
3//! The `Funding` trait provides methods for deposit and withdrawal operations
4//! including fetching deposit addresses, withdrawing funds, and transferring
5//! between accounts. These operations require authentication.
6//!
7//! # Timestamp Format
8//!
9//! All timestamp parameters and return values in this trait use the standardized format:
10//! - **Type**: `i64`
11//! - **Unit**: Milliseconds since Unix epoch (January 1, 1970, 00:00:00 UTC)
12//! - **Range**: Supports dates from 1970 to approximately year 294,276
13//!
14//! # Object Safety
15//!
16//! This trait is designed to be object-safe, allowing for dynamic dispatch via
17//! trait objects (`dyn Funding`).
18//!
19//! # Example
20//!
21//! ```rust,ignore
22//! use ccxt_core::traits::Funding;
23//! use ccxt_core::types::params::{WithdrawParams, TransferParams};
24//!
25//! async fn manage_funds(exchange: &dyn Funding) -> Result<(), ccxt_core::Error> {
26//!     // Get deposit address
27//!     let address = exchange.fetch_deposit_address("USDT").await?;
28//!     
29//!     // Fetch deposit history with i64 timestamp
30//!     let since: i64 = chrono::Utc::now().timestamp_millis() - 2592000000; // 30 days ago
31//!     let deposits = exchange.fetch_deposits(Some("USDT"), Some(since), Some(100)).await?;
32//!     
33//!     // Transfer between accounts
34//!     let transfer = exchange.transfer(
35//!         TransferParams::spot_to_futures("USDT", rust_decimal_macros::dec!(1000))
36//!     ).await?;
37//!     
38//!     Ok(())
39//! }
40//! ```
41
42use async_trait::async_trait;
43
44use crate::error::Result;
45use crate::traits::PublicExchange;
46use crate::types::{
47    DepositAddress, Transaction, Transfer,
48    params::{TransferParams, WithdrawParams},
49};
50
51/// Trait for deposit and withdrawal operations.
52///
53/// This trait provides methods for managing deposits, withdrawals, and
54/// inter-account transfers. All methods require authentication and are async.
55///
56/// # Timestamp Format
57///
58/// All timestamp parameters and fields in returned data structures use:
59/// - **Type**: `i64`
60/// - **Unit**: Milliseconds since Unix epoch (January 1, 1970, 00:00:00 UTC)
61/// - **Example**: `1609459200000` represents January 1, 2021, 00:00:00 UTC
62///
63/// # Supertrait
64///
65/// Requires `PublicExchange` as a supertrait to access exchange metadata
66/// and capabilities.
67///
68/// # Thread Safety
69///
70/// This trait requires `Send + Sync` bounds (inherited from `PublicExchange`)
71/// to ensure safe usage across thread boundaries in async contexts.
72#[async_trait]
73pub trait Funding: PublicExchange {
74    // ========================================================================
75    // Deposit Address
76    // ========================================================================
77
78    /// Fetch deposit address for a currency.
79    ///
80    /// Returns the deposit address for the specified currency on the default network.
81    ///
82    /// # Arguments
83    ///
84    /// * `code` - Currency code (e.g., "BTC", "USDT")
85    ///
86    /// # Example
87    ///
88    /// ```rust,ignore
89    /// let address = exchange.fetch_deposit_address("USDT").await?;
90    /// println!("Deposit to: {}", address.address);
91    /// ```
92    async fn fetch_deposit_address(&self, code: &str) -> Result<DepositAddress>;
93
94    /// Fetch deposit address for a specific network.
95    ///
96    /// # Arguments
97    ///
98    /// * `code` - Currency code (e.g., "USDT")
99    /// * `network` - Network name (e.g., "TRC20", "ERC20", "BEP20")
100    ///
101    /// # Example
102    ///
103    /// ```rust,ignore
104    /// let address = exchange.fetch_deposit_address_on_network("USDT", "TRC20").await?;
105    /// println!("TRC20 address: {}", address.address);
106    /// ```
107    async fn fetch_deposit_address_on_network(
108        &self,
109        code: &str,
110        network: &str,
111    ) -> Result<DepositAddress>;
112
113    // ========================================================================
114    // Withdrawal
115    // ========================================================================
116
117    /// Withdraw funds to an external address.
118    ///
119    /// # Arguments
120    ///
121    /// * `params` - Withdrawal parameters including currency, amount, address, and optional network
122    ///
123    /// # Example
124    ///
125    /// ```rust,ignore
126    /// use ccxt_core::types::params::WithdrawParams;
127    /// use rust_decimal_macros::dec;
128    ///
129    /// let tx = exchange.withdraw(
130    ///     WithdrawParams::new("USDT", dec!(100), "TAddress...")
131    ///         .network("TRC20")
132    /// ).await?;
133    /// println!("Withdrawal ID: {}", tx.id);
134    /// ```
135    async fn withdraw(&self, params: WithdrawParams) -> Result<Transaction>;
136
137    // ========================================================================
138    // Transfer
139    // ========================================================================
140
141    /// Transfer funds between accounts.
142    ///
143    /// # Arguments
144    ///
145    /// * `params` - Transfer parameters including currency, amount, source and destination accounts
146    ///
147    /// # Example
148    ///
149    /// ```rust,ignore
150    /// use ccxt_core::types::params::TransferParams;
151    /// use rust_decimal_macros::dec;
152    ///
153    /// // Transfer USDT from spot to futures
154    /// let transfer = exchange.transfer(
155    ///     TransferParams::spot_to_futures("USDT", dec!(1000))
156    /// ).await?;
157    /// ```
158    async fn transfer(&self, params: TransferParams) -> Result<Transfer>;
159
160    // ========================================================================
161    // Transaction History
162    // ========================================================================
163
164    /// Fetch deposit history.
165    ///
166    /// # Arguments
167    ///
168    /// * `code` - Optional currency code to filter by (e.g., "USDT", "BTC")
169    /// * `since` - Optional start timestamp in milliseconds (i64) since Unix epoch
170    /// * `limit` - Optional maximum number of records to return
171    ///
172    /// # Timestamp Format
173    ///
174    /// The `since` parameter uses `i64` milliseconds since Unix epoch:
175    /// - `1609459200000` = January 1, 2021, 00:00:00 UTC
176    /// - `chrono::Utc::now().timestamp_millis()` = Current time
177    /// - `chrono::Utc::now().timestamp_millis() - 2592000000` = 30 days ago
178    ///
179    /// # Example
180    ///
181    /// ```rust,ignore
182    /// // All deposits (no filters)
183    /// let deposits = exchange.fetch_deposits(None, None, None).await?;
184    ///
185    /// // USDT deposits from the last 30 days
186    /// let since = chrono::Utc::now().timestamp_millis() - 2592000000;
187    /// let deposits = exchange.fetch_deposits(Some("USDT"), Some(since), Some(100)).await?;
188    /// ```
189    async fn fetch_deposits(
190        &self,
191        code: Option<&str>,
192        since: Option<i64>,
193        limit: Option<u32>,
194    ) -> Result<Vec<Transaction>>;
195
196    /// Fetch withdrawal history.
197    ///
198    /// # Arguments
199    ///
200    /// * `code` - Optional currency code to filter by (e.g., "BTC", "ETH")
201    /// * `since` - Optional start timestamp in milliseconds (i64) since Unix epoch
202    /// * `limit` - Optional maximum number of records to return
203    ///
204    /// # Timestamp Format
205    ///
206    /// The `since` parameter uses `i64` milliseconds since Unix epoch:
207    /// - `1609459200000` = January 1, 2021, 00:00:00 UTC
208    /// - `chrono::Utc::now().timestamp_millis()` = Current time
209    /// - `chrono::Utc::now().timestamp_millis() - 604800000` = 7 days ago
210    ///
211    /// # Example
212    ///
213    /// ```rust,ignore
214    /// // All withdrawals (no filters)
215    /// let withdrawals = exchange.fetch_withdrawals(None, None, None).await?;
216    ///
217    /// // Recent BTC withdrawals from the last 7 days
218    /// let since = chrono::Utc::now().timestamp_millis() - 604800000;
219    /// let withdrawals = exchange.fetch_withdrawals(Some("BTC"), Some(since), Some(50)).await?;
220    /// ```
221    async fn fetch_withdrawals(
222        &self,
223        code: Option<&str>,
224        since: Option<i64>,
225        limit: Option<u32>,
226    ) -> Result<Vec<Transaction>>;
227
228    // ========================================================================
229    // Deprecated u64 Wrapper Methods (Backward Compatibility)
230    // ========================================================================
231
232    /// Fetch deposit history with u64 timestamp filtering (deprecated).
233    ///
234    /// **DEPRECATED**: Use `fetch_deposits` with i64 timestamps instead.
235    /// This method is provided for backward compatibility during migration.
236    ///
237    /// # Migration
238    ///
239    /// ```rust,ignore
240    /// // Old code (deprecated)
241    /// let deposits = exchange.fetch_deposits_u64(Some("USDT"), Some(1609459200000u64), Some(100)).await?;
242    ///
243    /// // New code (recommended)
244    /// let deposits = exchange.fetch_deposits(Some("USDT"), Some(1609459200000i64), Some(100)).await?;
245    /// ```
246    #[deprecated(
247        since = "0.1.0",
248        note = "Use fetch_deposits with i64 timestamps. Convert using TimestampUtils::u64_to_i64()"
249    )]
250    async fn fetch_deposits_u64(
251        &self,
252        code: Option<&str>,
253        since: Option<u64>,
254        limit: Option<u32>,
255    ) -> Result<Vec<Transaction>> {
256        use crate::time::TimestampConversion;
257
258        let since_i64 = since.to_i64()?;
259        self.fetch_deposits(code, since_i64, limit).await
260    }
261
262    /// Fetch withdrawal history with u64 timestamp filtering (deprecated).
263    ///
264    /// **DEPRECATED**: Use `fetch_withdrawals` with i64 timestamps instead.
265    /// This method is provided for backward compatibility during migration.
266    ///
267    /// # Migration
268    ///
269    /// ```rust,ignore
270    /// // Old code (deprecated)
271    /// let withdrawals = exchange.fetch_withdrawals_u64(Some("BTC"), Some(1609459200000u64), Some(50)).await?;
272    ///
273    /// // New code (recommended)
274    /// let withdrawals = exchange.fetch_withdrawals(Some("BTC"), Some(1609459200000i64), Some(50)).await?;
275    /// ```
276    #[deprecated(
277        since = "0.1.0",
278        note = "Use fetch_withdrawals with i64 timestamps. Convert using TimestampUtils::u64_to_i64()"
279    )]
280    async fn fetch_withdrawals_u64(
281        &self,
282        code: Option<&str>,
283        since: Option<u64>,
284        limit: Option<u32>,
285    ) -> Result<Vec<Transaction>> {
286        use crate::time::TimestampConversion;
287
288        let since_i64 = since.to_i64()?;
289        self.fetch_withdrawals(code, since_i64, limit).await
290    }
291}
292
293/// Type alias for boxed Funding trait object.
294pub type BoxedFunding = Box<dyn Funding>;
295
296#[cfg(test)]
297mod tests {
298    use super::*;
299    use crate::capability::ExchangeCapabilities;
300    use crate::types::Timeframe;
301    use crate::types::transaction::{TransactionStatus, TransactionType};
302    use rust_decimal_macros::dec;
303
304    // Mock implementation for testing trait object safety
305    struct MockExchange;
306
307    impl PublicExchange for MockExchange {
308        fn id(&self) -> &str {
309            "mock"
310        }
311        fn name(&self) -> &str {
312            "Mock Exchange"
313        }
314        fn capabilities(&self) -> ExchangeCapabilities {
315            ExchangeCapabilities::all()
316        }
317        fn timeframes(&self) -> Vec<Timeframe> {
318            vec![Timeframe::H1]
319        }
320    }
321
322    #[async_trait]
323    impl Funding for MockExchange {
324        async fn fetch_deposit_address(&self, code: &str) -> Result<DepositAddress> {
325            Ok(DepositAddress::new(
326                code.to_string(),
327                "0x1234567890abcdef".to_string(),
328            ))
329        }
330
331        async fn fetch_deposit_address_on_network(
332            &self,
333            code: &str,
334            network: &str,
335        ) -> Result<DepositAddress> {
336            let mut address =
337                DepositAddress::new(code.to_string(), "0x1234567890abcdef".to_string());
338            address.network = Some(network.to_string());
339            Ok(address)
340        }
341
342        async fn withdraw(&self, params: WithdrawParams) -> Result<Transaction> {
343            Ok(Transaction::new(
344                "withdraw_123".to_string(),
345                TransactionType::Withdrawal,
346                params.amount,
347                params.currency,
348                TransactionStatus::Pending,
349            ))
350        }
351
352        async fn transfer(&self, params: TransferParams) -> Result<Transfer> {
353            Ok(Transfer {
354                id: Some("transfer_123".to_string()),
355                timestamp: 1609459200000,
356                datetime: "2021-01-01T00:00:00Z".to_string(),
357                currency: params.currency,
358                amount: params.amount.to_string().parse().unwrap_or(0.0),
359                from_account: Some(format!("{:?}", params.from_account)),
360                to_account: Some(format!("{:?}", params.to_account)),
361                status: "success".to_string(),
362                info: None,
363            })
364        }
365
366        async fn fetch_deposits(
367            &self,
368            code: Option<&str>,
369            _since: Option<i64>,
370            _limit: Option<u32>,
371        ) -> Result<Vec<Transaction>> {
372            let currency = code.unwrap_or("USDT").to_string();
373            Ok(vec![Transaction::new(
374                "deposit_123".to_string(),
375                TransactionType::Deposit,
376                dec!(100),
377                currency,
378                TransactionStatus::Ok,
379            )])
380        }
381
382        async fn fetch_withdrawals(
383            &self,
384            code: Option<&str>,
385            _since: Option<i64>,
386            _limit: Option<u32>,
387        ) -> Result<Vec<Transaction>> {
388            let currency = code.unwrap_or("USDT").to_string();
389            Ok(vec![Transaction::new(
390                "withdraw_456".to_string(),
391                TransactionType::Withdrawal,
392                dec!(50),
393                currency,
394                TransactionStatus::Ok,
395            )])
396        }
397    }
398
399    #[test]
400    fn test_trait_object_safety() {
401        // Verify trait is object-safe by creating a trait object
402        let _exchange: BoxedFunding = Box::new(MockExchange);
403    }
404
405    #[tokio::test]
406    async fn test_fetch_deposit_address() {
407        let exchange = MockExchange;
408
409        let address = exchange.fetch_deposit_address("USDT").await.unwrap();
410        assert_eq!(address.currency, "USDT");
411        assert!(!address.address.is_empty());
412    }
413
414    #[tokio::test]
415    async fn test_fetch_deposit_address_on_network() {
416        let exchange = MockExchange;
417
418        let address = exchange
419            .fetch_deposit_address_on_network("USDT", "TRC20")
420            .await
421            .unwrap();
422        assert_eq!(address.currency, "USDT");
423        assert_eq!(address.network, Some("TRC20".to_string()));
424    }
425
426    #[tokio::test]
427    async fn test_withdraw() {
428        let exchange = MockExchange;
429
430        let tx = exchange
431            .withdraw(WithdrawParams::new("USDT", dec!(100), "TAddress123"))
432            .await
433            .unwrap();
434
435        assert_eq!(tx.currency, "USDT");
436        assert_eq!(tx.amount, dec!(100));
437        assert!(tx.is_withdrawal());
438        assert!(tx.is_pending());
439    }
440
441    #[tokio::test]
442    async fn test_transfer() {
443        let exchange = MockExchange;
444
445        let transfer = exchange
446            .transfer(TransferParams::spot_to_futures("USDT", dec!(1000)))
447            .await
448            .unwrap();
449
450        assert_eq!(transfer.currency, "USDT");
451        assert_eq!(transfer.status, "success");
452    }
453
454    #[tokio::test]
455    async fn test_fetch_deposits() {
456        let exchange = MockExchange;
457
458        let deposits = exchange
459            .fetch_deposits(Some("USDT"), None, None)
460            .await
461            .unwrap();
462        assert_eq!(deposits.len(), 1);
463        assert!(deposits[0].is_deposit());
464        assert!(deposits[0].is_completed());
465    }
466
467    #[tokio::test]
468    async fn test_fetch_withdrawals() {
469        let exchange = MockExchange;
470
471        let withdrawals = exchange
472            .fetch_withdrawals(None, None, Some(50))
473            .await
474            .unwrap();
475        assert_eq!(withdrawals.len(), 1);
476        assert!(withdrawals[0].is_withdrawal());
477    }
478}