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