1use pretty_simple_display::{DebugPretty, DisplaySimple};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12#[derive(Default)]
13pub enum TransferState {
14 #[default]
16 Prepared,
17 Confirmed,
19 Cancelled,
21 WaitingForAdmin,
23 InsufficientFunds,
25 WithdrawalLimit,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
31#[serde(rename_all = "lowercase")]
32#[derive(Default)]
33pub enum AddressType {
34 #[default]
36 Deposit,
37 Withdrawal,
39 Transfer,
41}
42
43#[derive(DebugPretty, DisplaySimple, Clone, PartialEq, Serialize, Deserialize)]
45pub struct Transfer {
46 pub id: i64,
48 pub currency: String,
50 pub amount: f64,
52 pub fee: f64,
54 pub address: String,
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub transaction_id: Option<String>,
59 pub state: TransferState,
61 pub created_timestamp: i64,
63 pub updated_timestamp: i64,
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub confirmed_timestamp: Option<i64>,
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub transfer_type: Option<String>,
71}
72
73impl Transfer {
74 pub fn new(
76 id: i64,
77 currency: String,
78 amount: f64,
79 fee: f64,
80 address: String,
81 created_timestamp: i64,
82 ) -> Self {
83 Self {
84 id,
85 currency,
86 amount,
87 fee,
88 address,
89 transaction_id: None,
90 state: TransferState::Prepared,
91 created_timestamp,
92 updated_timestamp: created_timestamp,
93 confirmed_timestamp: None,
94 transfer_type: None,
95 }
96 }
97
98 pub fn with_transaction_id(mut self, tx_id: String) -> Self {
100 self.transaction_id = Some(tx_id);
101 self
102 }
103
104 pub fn with_state(mut self, state: TransferState) -> Self {
106 self.state = state;
107 self
108 }
109
110 pub fn with_type(mut self, transfer_type: String) -> Self {
112 self.transfer_type = Some(transfer_type);
113 self
114 }
115
116 pub fn confirm(&mut self, timestamp: i64) {
118 self.state = TransferState::Confirmed;
119 self.confirmed_timestamp = Some(timestamp);
120 self.updated_timestamp = timestamp;
121 }
122
123 pub fn cancel(&mut self, timestamp: i64) {
125 self.state = TransferState::Cancelled;
126 self.updated_timestamp = timestamp;
127 }
128
129 pub fn is_confirmed(&self) -> bool {
131 matches!(self.state, TransferState::Confirmed)
132 }
133
134 pub fn is_cancelled(&self) -> bool {
136 matches!(self.state, TransferState::Cancelled)
137 }
138
139 pub fn is_pending(&self) -> bool {
141 matches!(
142 self.state,
143 TransferState::Prepared | TransferState::WaitingForAdmin
144 )
145 }
146
147 pub fn net_amount(&self) -> f64 {
149 self.amount - self.fee
150 }
151}
152
153#[derive(DebugPretty, DisplaySimple, Clone, PartialEq, Serialize, Deserialize)]
155pub struct AddressBookEntry {
156 pub address: String,
158 pub currency: String,
160 pub label: String,
162 #[serde(rename = "type")]
164 pub address_type: AddressType,
165 pub requires_confirmation: bool,
167 pub creation_timestamp: i64,
169 #[serde(skip_serializing_if = "Option::is_none")]
171 pub personal: Option<bool>,
172 #[serde(skip_serializing_if = "Option::is_none")]
174 pub beneficiary_first_name: Option<String>,
175 #[serde(skip_serializing_if = "Option::is_none")]
177 pub beneficiary_last_name: Option<String>,
178 #[serde(skip_serializing_if = "Option::is_none")]
180 pub beneficiary_address: Option<String>,
181 #[serde(skip_serializing_if = "Option::is_none")]
183 pub beneficiary_vasp_did: Option<String>,
184 #[serde(skip_serializing_if = "Option::is_none")]
186 pub beneficiary_vasp_name: Option<String>,
187}
188
189impl AddressBookEntry {
190 pub fn new(
192 address: String,
193 currency: String,
194 label: String,
195 address_type: AddressType,
196 creation_timestamp: i64,
197 ) -> Self {
198 Self {
199 address,
200 currency,
201 label,
202 address_type,
203 requires_confirmation: false,
204 creation_timestamp,
205 personal: None,
206 beneficiary_first_name: None,
207 beneficiary_last_name: None,
208 beneficiary_address: None,
209 beneficiary_vasp_did: None,
210 beneficiary_vasp_name: None,
211 }
212 }
213
214 pub fn with_confirmation(mut self, requires: bool) -> Self {
216 self.requires_confirmation = requires;
217 self
218 }
219
220 pub fn with_personal(mut self, personal: bool) -> Self {
222 self.personal = Some(personal);
223 self
224 }
225
226 pub fn with_beneficiary(
228 mut self,
229 first_name: String,
230 last_name: String,
231 address: String,
232 ) -> Self {
233 self.beneficiary_first_name = Some(first_name);
234 self.beneficiary_last_name = Some(last_name);
235 self.beneficiary_address = Some(address);
236 self
237 }
238
239 pub fn with_vasp(mut self, vasp_did: String, vasp_name: String) -> Self {
241 self.beneficiary_vasp_did = Some(vasp_did);
242 self.beneficiary_vasp_name = Some(vasp_name);
243 self
244 }
245
246 pub fn is_withdrawal(&self) -> bool {
248 matches!(self.address_type, AddressType::Withdrawal)
249 }
250
251 pub fn is_deposit(&self) -> bool {
253 matches!(self.address_type, AddressType::Deposit)
254 }
255
256 pub fn is_transfer(&self) -> bool {
258 matches!(self.address_type, AddressType::Transfer)
259 }
260}
261
262#[derive(DebugPretty, DisplaySimple, Clone, PartialEq, Serialize, Deserialize)]
264pub struct SubaccountTransfer {
265 pub amount: f64,
267 pub currency: String,
269 pub destination: i64,
271 pub id: i64,
273 pub source: i64,
275 pub state: TransferState,
277 pub timestamp: i64,
279 pub transfer_type: String,
281}
282
283impl SubaccountTransfer {
284 pub fn new(
286 id: i64,
287 amount: f64,
288 currency: String,
289 source: i64,
290 destination: i64,
291 timestamp: i64,
292 ) -> Self {
293 Self {
294 amount,
295 currency,
296 destination,
297 id,
298 source,
299 state: TransferState::Prepared,
300 timestamp,
301 transfer_type: "subaccount".to_string(),
302 }
303 }
304
305 pub fn with_state(mut self, state: TransferState) -> Self {
307 self.state = state;
308 self
309 }
310
311 pub fn with_type(mut self, transfer_type: String) -> Self {
313 self.transfer_type = transfer_type;
314 self
315 }
316
317 pub fn is_main_subaccount_transfer(&self) -> bool {
319 self.source == 0 || self.destination == 0
320 }
321
322 pub fn is_subaccount_to_subaccount(&self) -> bool {
324 self.source != 0 && self.destination != 0
325 }
326}
327
328#[derive(DebugPretty, DisplaySimple, Clone, PartialEq, Serialize, Deserialize)]
330pub struct Transfers {
331 pub transfers: Vec<Transfer>,
333}
334
335impl Transfers {
336 pub fn new() -> Self {
338 Self {
339 transfers: Vec::new(),
340 }
341 }
342
343 pub fn add(&mut self, transfer: Transfer) {
345 self.transfers.push(transfer);
346 }
347
348 pub fn by_currency(&self, currency: String) -> Vec<&Transfer> {
350 self.transfers
351 .iter()
352 .filter(|t| t.currency == currency)
353 .collect()
354 }
355
356 pub fn by_state(&self, state: TransferState) -> Vec<&Transfer> {
358 self.transfers.iter().filter(|t| t.state == state).collect()
359 }
360
361 pub fn pending(&self) -> Vec<&Transfer> {
363 self.transfers.iter().filter(|t| t.is_pending()).collect()
364 }
365
366 pub fn confirmed(&self) -> Vec<&Transfer> {
368 self.transfers.iter().filter(|t| t.is_confirmed()).collect()
369 }
370
371 pub fn total_amount(&self, currency: String) -> f64 {
373 self.transfers
374 .iter()
375 .filter(|t| t.currency == currency)
376 .map(|t| t.amount)
377 .sum()
378 }
379
380 pub fn total_fees(&self, currency: String) -> f64 {
382 self.transfers
383 .iter()
384 .filter(|t| t.currency == currency)
385 .map(|t| t.fee)
386 .sum()
387 }
388}
389
390impl Default for Transfers {
391 fn default() -> Self {
392 Self::new()
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399
400 #[test]
401 fn test_transfer_creation() {
402 let transfer = Transfer::new(
403 12345,
404 "BTC".to_string(),
405 1.0,
406 0.0005,
407 "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh".to_string(),
408 1640995200000,
409 );
410
411 assert_eq!(transfer.id, 12345);
412 assert_eq!(transfer.currency, "BTC");
413 assert_eq!(transfer.amount, 1.0);
414 assert_eq!(transfer.fee, 0.0005);
415 assert_eq!(transfer.net_amount(), 0.9995);
416 assert!(transfer.is_pending());
417 }
418
419 #[test]
420 fn test_transfer_state_changes() {
421 let mut transfer = Transfer::new(
422 1,
423 "BTC".to_string(),
424 1.0,
425 0.001,
426 "address".to_string(),
427 1000,
428 );
429
430 assert!(transfer.is_pending());
431 assert!(!transfer.is_confirmed());
432
433 transfer.confirm(2000);
434 assert!(transfer.is_confirmed());
435 assert!(!transfer.is_pending());
436 assert_eq!(transfer.confirmed_timestamp, Some(2000));
437
438 transfer.cancel(3000);
439 assert!(transfer.is_cancelled());
440 assert_eq!(transfer.updated_timestamp, 3000);
441 }
442
443 #[test]
444 fn test_address_book_entry() {
445 let entry = AddressBookEntry::new(
446 "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh".to_string(),
447 "BTC".to_string(),
448 "Main wallet".to_string(),
449 AddressType::Withdrawal,
450 1640995200000,
451 )
452 .with_confirmation(true)
453 .with_personal(false)
454 .with_beneficiary(
455 "John".to_string(),
456 "Doe".to_string(),
457 "123 Main St".to_string(),
458 );
459
460 assert!(entry.is_withdrawal());
461 assert!(!entry.is_deposit());
462 assert!(entry.requires_confirmation);
463 assert_eq!(entry.beneficiary_first_name, Some("John".to_string()));
464 }
465
466 #[test]
467 fn test_subaccount_transfer() {
468 let transfer = SubaccountTransfer::new(
469 1,
470 100.0,
471 "BTC".to_string(),
472 0, 123, 1640995200000,
475 );
476
477 assert!(transfer.is_main_subaccount_transfer());
478 assert!(!transfer.is_subaccount_to_subaccount());
479 }
480
481 #[test]
482 fn test_transfers_collection() {
483 let mut transfers = Transfers::new();
484
485 transfers.add(
486 Transfer::new(1, "BTC".to_string(), 1.0, 0.001, "addr1".to_string(), 1000)
487 .with_state(TransferState::Confirmed),
488 );
489
490 transfers.add(Transfer::new(
491 2,
492 "BTC".to_string(),
493 0.5,
494 0.0005,
495 "addr2".to_string(),
496 2000,
497 ));
498
499 assert_eq!(transfers.transfers.len(), 2);
500 assert_eq!(transfers.by_currency("BTC".to_string()).len(), 2);
501 assert_eq!(transfers.confirmed().len(), 1);
502 assert_eq!(transfers.pending().len(), 1);
503 assert_eq!(transfers.total_amount("BTC".to_string()), 1.5);
504 assert_eq!(transfers.total_fees("BTC".to_string()), 0.0015);
505 }
506
507 #[test]
508 fn test_serde() {
509 let transfer = Transfer::new(
510 12345,
511 "BTC".to_string(),
512 1.0,
513 0.0005,
514 "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh".to_string(),
515 1640995200000,
516 )
517 .with_transaction_id("tx123".to_string())
518 .with_state(TransferState::Confirmed);
519
520 let json = serde_json::to_string(&transfer).unwrap();
521 let deserialized: Transfer = serde_json::from_str(&json).unwrap();
522 assert_eq!(transfer, deserialized);
523 }
524}