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}