1use crate::prelude::Currency;
8use crate::{impl_json_debug_pretty, impl_json_display};
9use serde::{Deserialize, Serialize};
10
11#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[serde(rename_all = "snake_case")]
14pub enum TransferState {
15 Prepared,
17 Confirmed,
19 Cancelled,
21 WaitingForAdmin,
23 InsufficientFunds,
25 WithdrawalLimit,
27}
28
29impl Default for TransferState {
30 fn default() -> Self {
31 Self::Prepared
32 }
33}
34
35#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
37#[serde(rename_all = "lowercase")]
38pub enum AddressType {
39 Deposit,
41 Withdrawal,
43 Transfer,
45}
46
47impl Default for AddressType {
48 fn default() -> Self {
49 Self::Deposit
50 }
51}
52
53#[derive(Clone, PartialEq, Serialize, Deserialize)]
55pub struct Transfer {
56 pub id: i64,
58 pub currency: Currency,
60 pub amount: f64,
62 pub fee: f64,
64 pub address: String,
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub transaction_id: Option<String>,
69 pub state: TransferState,
71 pub created_timestamp: i64,
73 pub updated_timestamp: i64,
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub confirmed_timestamp: Option<i64>,
78 #[serde(skip_serializing_if = "Option::is_none")]
80 pub transfer_type: Option<String>,
81}
82
83impl Transfer {
84 pub fn new(
86 id: i64,
87 currency: Currency,
88 amount: f64,
89 fee: f64,
90 address: String,
91 created_timestamp: i64,
92 ) -> Self {
93 Self {
94 id,
95 currency,
96 amount,
97 fee,
98 address,
99 transaction_id: None,
100 state: TransferState::Prepared,
101 created_timestamp,
102 updated_timestamp: created_timestamp,
103 confirmed_timestamp: None,
104 transfer_type: None,
105 }
106 }
107
108 pub fn with_transaction_id(mut self, tx_id: String) -> Self {
110 self.transaction_id = Some(tx_id);
111 self
112 }
113
114 pub fn with_state(mut self, state: TransferState) -> Self {
116 self.state = state;
117 self
118 }
119
120 pub fn with_type(mut self, transfer_type: String) -> Self {
122 self.transfer_type = Some(transfer_type);
123 self
124 }
125
126 pub fn confirm(&mut self, timestamp: i64) {
128 self.state = TransferState::Confirmed;
129 self.confirmed_timestamp = Some(timestamp);
130 self.updated_timestamp = timestamp;
131 }
132
133 pub fn cancel(&mut self, timestamp: i64) {
135 self.state = TransferState::Cancelled;
136 self.updated_timestamp = timestamp;
137 }
138
139 pub fn is_confirmed(&self) -> bool {
141 matches!(self.state, TransferState::Confirmed)
142 }
143
144 pub fn is_cancelled(&self) -> bool {
146 matches!(self.state, TransferState::Cancelled)
147 }
148
149 pub fn is_pending(&self) -> bool {
151 matches!(
152 self.state,
153 TransferState::Prepared | TransferState::WaitingForAdmin
154 )
155 }
156
157 pub fn net_amount(&self) -> f64 {
159 self.amount - self.fee
160 }
161}
162
163impl_json_display!(Transfer);
164impl_json_debug_pretty!(Transfer);
165
166#[derive(Clone, PartialEq, Serialize, Deserialize)]
168pub struct AddressBookEntry {
169 pub address: String,
171 pub currency: Currency,
173 pub label: String,
175 #[serde(rename = "type")]
177 pub address_type: AddressType,
178 pub requires_confirmation: bool,
180 pub creation_timestamp: i64,
182 #[serde(skip_serializing_if = "Option::is_none")]
184 pub personal: Option<bool>,
185 #[serde(skip_serializing_if = "Option::is_none")]
187 pub beneficiary_first_name: Option<String>,
188 #[serde(skip_serializing_if = "Option::is_none")]
190 pub beneficiary_last_name: Option<String>,
191 #[serde(skip_serializing_if = "Option::is_none")]
193 pub beneficiary_address: Option<String>,
194 #[serde(skip_serializing_if = "Option::is_none")]
196 pub beneficiary_vasp_did: Option<String>,
197 #[serde(skip_serializing_if = "Option::is_none")]
199 pub beneficiary_vasp_name: Option<String>,
200}
201
202impl AddressBookEntry {
203 pub fn new(
205 address: String,
206 currency: Currency,
207 label: String,
208 address_type: AddressType,
209 creation_timestamp: i64,
210 ) -> Self {
211 Self {
212 address,
213 currency,
214 label,
215 address_type,
216 requires_confirmation: false,
217 creation_timestamp,
218 personal: None,
219 beneficiary_first_name: None,
220 beneficiary_last_name: None,
221 beneficiary_address: None,
222 beneficiary_vasp_did: None,
223 beneficiary_vasp_name: None,
224 }
225 }
226
227 pub fn with_confirmation(mut self, requires: bool) -> Self {
229 self.requires_confirmation = requires;
230 self
231 }
232
233 pub fn with_personal(mut self, personal: bool) -> Self {
235 self.personal = Some(personal);
236 self
237 }
238
239 pub fn with_beneficiary(
241 mut self,
242 first_name: String,
243 last_name: String,
244 address: String,
245 ) -> Self {
246 self.beneficiary_first_name = Some(first_name);
247 self.beneficiary_last_name = Some(last_name);
248 self.beneficiary_address = Some(address);
249 self
250 }
251
252 pub fn with_vasp(mut self, vasp_did: String, vasp_name: String) -> Self {
254 self.beneficiary_vasp_did = Some(vasp_did);
255 self.beneficiary_vasp_name = Some(vasp_name);
256 self
257 }
258
259 pub fn is_withdrawal(&self) -> bool {
261 matches!(self.address_type, AddressType::Withdrawal)
262 }
263
264 pub fn is_deposit(&self) -> bool {
266 matches!(self.address_type, AddressType::Deposit)
267 }
268
269 pub fn is_transfer(&self) -> bool {
271 matches!(self.address_type, AddressType::Transfer)
272 }
273}
274
275impl_json_display!(AddressBookEntry);
276impl_json_debug_pretty!(AddressBookEntry);
277
278#[derive(Clone, PartialEq, Serialize, Deserialize)]
280pub struct SubaccountTransfer {
281 pub amount: f64,
283 pub currency: Currency,
285 pub destination: i64,
287 pub id: i64,
289 pub source: i64,
291 pub state: TransferState,
293 pub timestamp: i64,
295 pub transfer_type: String,
297}
298
299impl SubaccountTransfer {
300 pub fn new(
302 id: i64,
303 amount: f64,
304 currency: Currency,
305 source: i64,
306 destination: i64,
307 timestamp: i64,
308 ) -> Self {
309 Self {
310 amount,
311 currency,
312 destination,
313 id,
314 source,
315 state: TransferState::Prepared,
316 timestamp,
317 transfer_type: "subaccount".to_string(),
318 }
319 }
320
321 pub fn with_state(mut self, state: TransferState) -> Self {
323 self.state = state;
324 self
325 }
326
327 pub fn with_type(mut self, transfer_type: String) -> Self {
329 self.transfer_type = transfer_type;
330 self
331 }
332
333 pub fn is_main_subaccount_transfer(&self) -> bool {
335 self.source == 0 || self.destination == 0
336 }
337
338 pub fn is_subaccount_to_subaccount(&self) -> bool {
340 self.source != 0 && self.destination != 0
341 }
342}
343
344impl_json_display!(SubaccountTransfer);
345impl_json_debug_pretty!(SubaccountTransfer);
346
347#[derive(Clone, PartialEq, Serialize, Deserialize)]
349pub struct Transfers {
350 pub transfers: Vec<Transfer>,
352}
353
354impl Transfers {
355 pub fn new() -> Self {
357 Self {
358 transfers: Vec::new(),
359 }
360 }
361
362 pub fn add(&mut self, transfer: Transfer) {
364 self.transfers.push(transfer);
365 }
366
367 pub fn by_currency(&self, currency: Currency) -> Vec<&Transfer> {
369 self.transfers
370 .iter()
371 .filter(|t| t.currency == currency)
372 .collect()
373 }
374
375 pub fn by_state(&self, state: TransferState) -> Vec<&Transfer> {
377 self.transfers.iter().filter(|t| t.state == state).collect()
378 }
379
380 pub fn pending(&self) -> Vec<&Transfer> {
382 self.transfers.iter().filter(|t| t.is_pending()).collect()
383 }
384
385 pub fn confirmed(&self) -> Vec<&Transfer> {
387 self.transfers.iter().filter(|t| t.is_confirmed()).collect()
388 }
389
390 pub fn total_amount(&self, currency: Currency) -> f64 {
392 self.transfers
393 .iter()
394 .filter(|t| t.currency == currency)
395 .map(|t| t.amount)
396 .sum()
397 }
398
399 pub fn total_fees(&self, currency: Currency) -> f64 {
401 self.transfers
402 .iter()
403 .filter(|t| t.currency == currency)
404 .map(|t| t.fee)
405 .sum()
406 }
407}
408
409impl Default for Transfers {
410 fn default() -> Self {
411 Self::new()
412 }
413}
414
415impl_json_display!(Transfers);
416impl_json_debug_pretty!(Transfers);
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421
422 #[test]
423 fn test_transfer_creation() {
424 let transfer = Transfer::new(
425 12345,
426 Currency::Bitcoin,
427 1.0,
428 0.0005,
429 "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh".to_string(),
430 1640995200000,
431 );
432
433 assert_eq!(transfer.id, 12345);
434 assert_eq!(transfer.currency, Currency::Bitcoin);
435 assert_eq!(transfer.amount, 1.0);
436 assert_eq!(transfer.fee, 0.0005);
437 assert_eq!(transfer.net_amount(), 0.9995);
438 assert!(transfer.is_pending());
439 }
440
441 #[test]
442 fn test_transfer_state_changes() {
443 let mut transfer = Transfer::new(
444 1,
445 Currency::Bitcoin,
446 1.0,
447 0.001,
448 "address".to_string(),
449 1000,
450 );
451
452 assert!(transfer.is_pending());
453 assert!(!transfer.is_confirmed());
454
455 transfer.confirm(2000);
456 assert!(transfer.is_confirmed());
457 assert!(!transfer.is_pending());
458 assert_eq!(transfer.confirmed_timestamp, Some(2000));
459
460 transfer.cancel(3000);
461 assert!(transfer.is_cancelled());
462 assert_eq!(transfer.updated_timestamp, 3000);
463 }
464
465 #[test]
466 fn test_address_book_entry() {
467 let entry = AddressBookEntry::new(
468 "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh".to_string(),
469 Currency::Bitcoin,
470 "Main wallet".to_string(),
471 AddressType::Withdrawal,
472 1640995200000,
473 )
474 .with_confirmation(true)
475 .with_personal(false)
476 .with_beneficiary(
477 "John".to_string(),
478 "Doe".to_string(),
479 "123 Main St".to_string(),
480 );
481
482 assert!(entry.is_withdrawal());
483 assert!(!entry.is_deposit());
484 assert!(entry.requires_confirmation);
485 assert_eq!(entry.beneficiary_first_name, Some("John".to_string()));
486 }
487
488 #[test]
489 fn test_subaccount_transfer() {
490 let transfer = SubaccountTransfer::new(
491 1,
492 100.0,
493 Currency::Bitcoin,
494 0, 123, 1640995200000,
497 );
498
499 assert!(transfer.is_main_subaccount_transfer());
500 assert!(!transfer.is_subaccount_to_subaccount());
501 }
502
503 #[test]
504 fn test_transfers_collection() {
505 let mut transfers = Transfers::new();
506
507 transfers.add(
508 Transfer::new(1, Currency::Bitcoin, 1.0, 0.001, "addr1".to_string(), 1000)
509 .with_state(TransferState::Confirmed),
510 );
511
512 transfers.add(Transfer::new(
513 2,
514 Currency::Bitcoin,
515 0.5,
516 0.0005,
517 "addr2".to_string(),
518 2000,
519 ));
520
521 assert_eq!(transfers.transfers.len(), 2);
522 assert_eq!(transfers.by_currency(Currency::Bitcoin).len(), 2);
523 assert_eq!(transfers.confirmed().len(), 1);
524 assert_eq!(transfers.pending().len(), 1);
525 assert_eq!(transfers.total_amount(Currency::Bitcoin), 1.5);
526 assert_eq!(transfers.total_fees(Currency::Bitcoin), 0.0015);
527 }
528
529 #[test]
530 fn test_serde() {
531 let transfer = Transfer::new(
532 12345,
533 Currency::Bitcoin,
534 1.0,
535 0.0005,
536 "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh".to_string(),
537 1640995200000,
538 )
539 .with_transaction_id("tx123".to_string())
540 .with_state(TransferState::Confirmed);
541
542 let json = serde_json::to_string(&transfer).unwrap();
543 let deserialized: Transfer = serde_json::from_str(&json).unwrap();
544 assert_eq!(transfer, deserialized);
545 }
546}