1use chrono::{DateTime, Duration, Utc};
2use secrecy::{DebugSecret, Secret, SerializableSecret};
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4
5#[cfg(test)]
6use std::collections::HashMap;
7
8use zeroize::Zeroize;
9
10#[derive(Debug, Serialize)]
12pub struct TokenRequest {
13 pub username: String,
14 pub password: String,
15}
16
17#[derive(Debug, Deserialize)]
19pub struct TokenResponse {
20 pub token: String,
21}
22
23#[derive(Clone, Serialize, Deserialize)]
24pub struct Password(pub String);
25
26impl Zeroize for Password {
27 fn zeroize(&mut self) {
28 self.0.zeroize();
29 }
30}
31
32impl From<String> for Password {
33 fn from(s: String) -> Self {
34 Self(s)
35 }
36}
37
38impl SerializableSecret for Password {}
39
40impl DebugSecret for Password {}
41
42#[derive(Debug, Serialize)]
43pub struct ChangePasswordRequest {
44 pub password: Secret<Password>,
45}
46
47#[derive(Debug, Deserialize)]
48pub struct ChangePasswordResponse {
49 pub username: String,
50 pub password: Secret<Password>,
51 pub token: Secret<String>,
52}
53
54#[derive(Debug, Deserialize, Serialize, Clone)]
55#[allow(clippy::struct_excessive_bools)]
56pub struct Asset {
57 pub name: String,
58 pub asset_uuid: String,
59 pub issuer: i64,
60 pub asset_id: String,
61 pub reissuance_token_id: Option<String>,
62 pub requirements: Vec<i64>,
63 pub ticker: Option<String>,
64 pub precision: i64,
65 pub domain: Option<String>,
66 pub pubkey: Option<String>,
67 pub is_registered: bool,
68 pub is_authorized: bool,
69 pub is_locked: bool,
70 pub issuer_authorization_endpoint: Option<String>,
71 pub transfer_restricted: bool,
72}
73
74#[derive(Debug, Serialize)]
75pub struct IssuanceRequest {
76 pub name: String,
77 pub amount: i64,
78 pub destination_address: String,
79 pub domain: String,
80 pub ticker: String,
81 pub pubkey: String,
82 #[serde(skip_serializing_if = "Option::is_none")]
83 pub precision: Option<i64>,
84 #[serde(skip_serializing_if = "Option::is_none")]
85 pub is_confidential: Option<bool>,
86 #[serde(skip_serializing_if = "Option::is_none")]
87 pub is_reissuable: Option<bool>,
88 #[serde(skip_serializing_if = "Option::is_none")]
89 pub reissuance_amount: Option<i64>,
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub reissuance_address: Option<String>,
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub transfer_restricted: Option<bool>,
94}
95
96#[derive(Debug, Deserialize)]
97pub struct IssuanceResponse {
98 pub name: String,
99 pub amount: i64,
100 pub destination_address: String,
101 pub domain: String,
102 pub ticker: String,
103 pub pubkey: String,
104 pub is_confidential: bool,
105 pub is_reissuable: bool,
106 pub reissuance_amount: i64,
107 pub reissuance_address: String,
108 pub asset_id: String,
109 pub reissuance_token_id: Option<String>,
110 pub asset_uuid: String,
111 pub txid: String,
112 pub vin: i64,
113 pub asset_vout: i64,
114 pub reissuance_vout: Option<i64>,
115 pub issuer_authorization_endpoint: Option<String>,
116 pub transfer_restricted: bool,
117 pub issuance_assetblinder: String,
118 pub issuance_tokenblinder: Option<String>,
119}
120
121#[derive(Debug, Deserialize, Serialize, Clone)]
124pub struct RegisterAssetResponse {
125 pub success: bool,
127 pub message: Option<String>,
129 #[serde(flatten)]
131 pub asset_data: Option<Asset>,
132}
133
134#[derive(Debug, Serialize)]
135pub struct EditAssetRequest {
136 pub issuer_authorization_endpoint: String,
137}
138
139#[derive(Debug, Deserialize)]
140pub struct RegisteredUserResponse {
141 pub id: i64,
142 #[serde(rename = "GAID")]
143 pub gaid: Option<String>,
144 pub is_company: bool,
145 pub name: String,
146 pub categories: Vec<i64>,
147 pub creator: i64,
148}
149
150#[derive(Debug, Serialize)]
151pub struct RegisteredUserAdd {
152 pub name: String,
153 #[serde(rename = "GAID")]
154 pub gaid: Option<String>,
155 pub is_company: bool,
156}
157
158#[derive(Debug, Serialize)]
159pub struct RegisteredUserEdit {
160 pub name: Option<String>,
161}
162
163#[derive(Debug, Serialize)]
164pub struct GaidRequest {
165 pub gaid: String,
166}
167
168#[derive(Debug, Serialize)]
169pub struct CategoriesRequest {
170 pub categories: Vec<i64>,
171}
172
173#[derive(Debug, Deserialize)]
174pub struct CategoryResponse {
175 pub id: i64,
176 pub name: String,
177 pub description: Option<String>,
178 pub registered_users: Vec<i64>,
179 pub assets: Vec<String>,
180}
181
182#[derive(Debug, Serialize)]
183pub struct CategoryAdd {
184 pub name: String,
185 pub description: Option<String>,
186}
187
188#[derive(Debug, Serialize)]
189pub struct CategoryEdit {
190 pub name: Option<String>,
191 pub description: Option<String>,
192}
193
194#[derive(Debug, Deserialize)]
195pub struct ValidateGaidResponse {
196 pub is_valid: bool,
197 pub error: Option<String>,
198}
199
200#[derive(Debug, Deserialize)]
201pub struct AddressGaidResponse {
202 pub address: String,
203 pub error: Option<String>,
204}
205
206#[derive(Debug, Deserialize)]
207pub struct Manager {
208 pub username: String,
209 pub id: i64,
210 pub is_locked: bool,
211 pub assets: Vec<String>,
212}
213
214#[derive(Debug, Serialize)]
215pub struct ManagerCreate {
216 pub username: String,
217 pub password: String,
218}
219
220#[derive(Debug, Deserialize, Serialize)]
221#[serde(rename_all = "UPPERCASE")]
222pub enum Status {
223 Unconfirmed,
224 Confirmed,
225}
226
227#[derive(Debug, Deserialize, Serialize)]
228pub struct DistributionAssignment {
229 pub registered_user: i64,
230 pub amount: i64,
231 pub vout: i64,
232}
233
234#[derive(Debug, Deserialize, Serialize)]
235pub struct Transaction {
236 pub txid: String,
237 pub transaction_status: Status,
238 pub included_blockheight: i64,
239 pub confirmed_datetime: String,
240 pub assignments: Vec<DistributionAssignment>,
241}
242
243#[derive(Debug, Deserialize, Serialize)]
244pub struct Distribution {
245 pub distribution_uuid: String,
246 pub distribution_status: Status,
247 pub transactions: Vec<Transaction>,
248}
249
250#[derive(Debug, Serialize, Deserialize, Clone)]
251pub struct CreateAssetAssignmentRequest {
252 pub registered_user: i64,
253 pub amount: i64,
254 #[serde(skip_serializing_if = "Option::is_none")]
255 pub vesting_timestamp: Option<i64>, #[serde(default = "default_ready_for_distribution")]
257 pub ready_for_distribution: bool, }
259
260const fn default_ready_for_distribution() -> bool {
261 false
262}
263
264#[derive(Debug, Serialize)]
265pub struct CreateAssetAssignmentRequestWrapper {
266 pub assignments: Vec<CreateAssetAssignmentRequest>,
267}
268
269#[derive(Debug, Deserialize, Serialize)]
270pub struct Assignment {
271 pub id: i64,
272 pub registered_user: i64,
273 pub amount: i64,
274 pub receiving_address: Option<String>,
275 pub distribution_uuid: Option<String>,
276 pub ready_for_distribution: bool,
277 pub vesting_datetime: Option<String>,
278 pub vesting_timestamp: Option<i64>,
279 pub has_vested: bool,
280 pub is_distributed: bool,
281 pub creator: i64,
282 #[serde(rename = "GAID")]
283 pub gaid: Option<String>,
284 #[serde(skip_serializing_if = "Option::is_none")]
286 pub investor: Option<i64>,
287}
288
289#[derive(Debug, Deserialize, Serialize)]
290pub struct RegisteredUserSummary {
291 pub asset_uuid: String,
292 pub asset_id: String,
293 pub assignments: Vec<Assignment>,
294 pub assignments_sum: i64,
295 pub distributions: Vec<Distribution>,
296 pub distributions_sum: i64,
297 pub balance: i64,
298}
299
300#[derive(Debug, Deserialize, Serialize)]
301pub struct Activity {
302 #[serde(rename = "type")]
303 pub activity_type: String,
304 pub datetime: String,
305 pub description: String,
306 pub txid: String,
307 pub vout: i64,
308 pub blockheight: i64,
309 pub asset_blinder: String,
310 pub amount_blinder: String,
311 #[serde(rename = "registered user")]
312 pub registered_user: Option<i64>,
313 pub amount: i64,
314}
315
316#[derive(Debug, Serialize, Default)]
317pub struct AssetActivityParams {
318 #[serde(skip_serializing_if = "Option::is_none")]
319 pub start: Option<i64>,
320 #[serde(skip_serializing_if = "Option::is_none")]
321 pub count: Option<i64>,
322 #[serde(skip_serializing_if = "Option::is_none")]
323 pub sortcolumn: Option<String>,
324 #[serde(skip_serializing_if = "Option::is_none")]
325 pub sortorder: Option<String>,
326 #[serde(skip_serializing_if = "Option::is_none")]
327 pub height_start: Option<i64>,
328 #[serde(skip_serializing_if = "Option::is_none")]
329 pub height_stop: Option<i64>,
330}
331
332#[derive(Debug, Deserialize, Serialize)]
333pub struct Ownership {
334 pub owner: String,
335 pub amount: i64,
336 #[serde(rename = "GAID")]
337 pub gaid: Option<String>,
338}
339
340#[derive(Debug, Deserialize, Serialize)]
341pub struct Outpoint {
342 pub txid: String,
343 pub vout: i64,
344}
345
346pub type LostOutputs = Vec<Outpoint>;
347
348#[derive(Debug, Deserialize, Serialize)]
349pub struct GaidBalanceEntry {
350 pub asset_uuid: String,
351 pub asset_id: String,
352 pub balance: i64,
353}
354
355pub type Balance = Vec<GaidBalanceEntry>;
356
357#[derive(Debug, Deserialize, Serialize)]
358pub struct AssetLostOutputs {
359 pub lost_outputs: LostOutputs,
360 pub reissuance_lost_outputs: LostOutputs,
361}
362
363#[derive(Debug, Deserialize, Serialize)]
364pub struct AssetSummary {
365 pub asset_id: String,
366 pub reissuance_token_id: Option<String>,
367 pub issued: i64,
368 pub reissued: i64,
369 pub assigned: i64,
370 pub distributed: i64,
371 pub burned: i64,
372 pub blacklisted: i64,
373 pub registered_users: i64,
374 pub active_registered_users: i64,
375 pub active_green_subaccounts: i64,
376 #[serde(rename = "reissuance_tokens")]
377 pub reissuance_tokens: i64,
378}
379
380#[derive(Debug, Deserialize, Serialize)]
381pub struct Utxo {
382 pub txid: String,
383 pub vout: i64,
384 pub asset: String,
385 pub amount: i64,
386 pub registered_user: Option<i64>,
387 pub gaid: Option<String>,
388 pub blacklisted: bool,
389}
390
391#[derive(Debug, Deserialize, Serialize)]
392pub struct Reissuance {
393 pub txid: String,
394 pub vout: i64,
395 pub destination_address: String,
396 pub reissuance_amount: i64,
397 pub confirmed_in_block: String,
398 pub created: String,
399}
400
401#[derive(Debug, Serialize)]
402pub struct ReissueRequest {
403 pub amount_to_reissue: i64,
404}
405
406#[derive(Debug, Serialize)]
407pub struct ReissueConfirmRequest {
408 pub details: serde_json::Value,
409 pub listissuances: Vec<serde_json::Value>,
410 pub reissuance_output: serde_json::Value,
411}
412
413#[derive(Debug, Serialize)]
414pub struct BurnRequest {
415 pub amount: i64,
416}
417
418#[derive(Debug, Deserialize, Serialize)]
419pub struct BurnCreate {
420 pub command: String,
421 pub min_supported_client_script_version: i64,
422 pub base_url: String,
423 pub asset_uuid: String,
424 pub asset_id: String,
425 pub amount: f64,
426 pub utxos: Vec<Outpoint>,
427}
428
429#[derive(Debug, Serialize)]
430pub struct BurnConfirmRequest {
431 pub tx_data: serde_json::Value,
432 pub change_data: Vec<serde_json::Value>,
433}
434
435#[derive(Debug, Serialize)]
436pub struct SetAssetMemoRequest {
437 pub memo: String,
438}
439
440#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
441#[serde(rename_all = "lowercase")]
442pub enum Permission {
443 View,
444 Receive,
445 Transfer,
446 Assign,
447 Distribute,
448 Reissue,
449 Burn,
450 Acquire,
451 Manage,
452 Permissions,
453}
454
455#[derive(Debug, Deserialize, Serialize)]
456pub struct BroadcastResponse {
457 pub txid: String,
458 pub hex: String,
459}
460
461#[derive(Debug, Clone, Serialize, Deserialize)]
463pub struct TokenData {
464 #[serde(with = "secret_serde")]
465 pub token: Secret<String>,
466 pub expires_at: DateTime<Utc>,
467 pub obtained_at: DateTime<Utc>,
468}
469
470impl TokenData {
471 #[must_use]
482 pub fn new(token: String, expires_at: DateTime<Utc>) -> Self {
483 Self {
484 token: Secret::new(token),
485 expires_at,
486 obtained_at: Utc::now(),
487 }
488 }
489
490 #[must_use]
507 pub fn is_expired(&self) -> bool {
508 Utc::now() > self.expires_at
509 }
510
511 #[must_use]
528 pub fn expires_soon(&self, threshold: Duration) -> bool {
529 Utc::now() + threshold > self.expires_at
530 }
531
532 #[must_use]
546 pub fn age(&self) -> Duration {
547 Utc::now() - self.obtained_at
548 }
549}
550
551#[derive(Debug, Clone)]
553pub struct TokenInfo {
554 pub expires_at: DateTime<Utc>,
555 pub obtained_at: DateTime<Utc>,
556 pub expires_in: Duration,
557 pub age: Duration,
558 pub is_expired: bool,
559 pub expires_soon: bool,
560}
561
562impl From<&TokenData> for TokenInfo {
563 fn from(token_data: &TokenData) -> Self {
564 let now = Utc::now();
565 let expires_in = token_data.expires_at - now;
566 let expires_soon_threshold = Duration::minutes(5);
567
568 Self {
569 expires_at: token_data.expires_at,
570 obtained_at: token_data.obtained_at,
571 expires_in,
572 age: token_data.age(),
573 is_expired: token_data.is_expired(),
574 expires_soon: token_data.expires_soon(expires_soon_threshold),
575 }
576 }
577}
578
579#[derive(Debug, Clone, Serialize, Deserialize)]
581pub struct AssetDistributionAssignment {
582 pub user_id: String,
583 pub address: String,
584 pub amount: f64,
585}
586
587#[derive(Debug, Clone, Serialize, Deserialize)]
589pub struct DistributionAssignmentRequest {
590 pub user_uuid: String,
591 pub amount: f64,
592 pub address: String,
593}
594
595#[derive(Debug, Clone, Serialize, Deserialize)]
597pub struct CreateDistributionRequest {
598 pub assignments: Vec<DistributionAssignmentRequest>,
599}
600
601#[derive(Debug, Clone, Serialize, Deserialize)]
603pub struct Unspent {
604 pub txid: String,
605 pub vout: u32,
606 pub amount: f64,
607 pub asset: String,
608 pub address: String,
609 pub spendable: bool,
610 #[serde(skip_serializing_if = "Option::is_none")]
611 pub confirmations: Option<u32>,
612 #[serde(skip_serializing_if = "Option::is_none")]
613 pub scriptpubkey: Option<String>,
614 #[serde(skip_serializing_if = "Option::is_none")]
615 pub redeemscript: Option<String>,
616 #[serde(skip_serializing_if = "Option::is_none")]
617 pub witnessscript: Option<String>,
618 #[serde(skip_serializing_if = "Option::is_none")]
620 pub amountblinder: Option<String>,
621 #[serde(skip_serializing_if = "Option::is_none")]
623 pub assetblinder: Option<String>,
624}
625
626#[derive(Debug, Clone, Serialize, Deserialize)]
628pub struct TransactionDetail {
629 pub txid: String,
630 pub confirmations: u32,
631 #[serde(skip_serializing_if = "Option::is_none")]
632 pub blockheight: Option<u64>,
633 pub hex: String,
634 #[serde(skip_serializing_if = "Option::is_none")]
635 pub blockhash: Option<String>,
636 #[serde(skip_serializing_if = "Option::is_none")]
637 pub blocktime: Option<i64>,
638 #[serde(skip_serializing_if = "Option::is_none")]
639 pub time: Option<i64>,
640 #[serde(skip_serializing_if = "Option::is_none")]
641 pub timereceived: Option<i64>,
642 #[serde(skip_serializing_if = "Option::is_none")]
644 pub details: Option<Vec<serde_json::Value>>,
645}
646
647#[derive(Debug, Clone, Serialize, Deserialize)]
649pub struct TransactionOutputDetail {
650 pub account: String,
651 pub address: String,
652 pub category: String,
653 pub amount: f64,
654 pub vout: u32,
655 #[serde(skip_serializing_if = "Option::is_none")]
656 pub fee: Option<f64>,
657 #[serde(skip_serializing_if = "Option::is_none")]
658 pub confirmations: Option<u32>,
659 #[serde(skip_serializing_if = "Option::is_none")]
660 pub blockhash: Option<String>,
661 #[serde(skip_serializing_if = "Option::is_none")]
662 pub blockindex: Option<u32>,
663 #[serde(skip_serializing_if = "Option::is_none")]
664 pub blocktime: Option<i64>,
665 #[serde(skip_serializing_if = "Option::is_none")]
666 pub txid: Option<String>,
667 #[serde(skip_serializing_if = "Option::is_none")]
668 pub time: Option<i64>,
669 #[serde(skip_serializing_if = "Option::is_none")]
670 pub timereceived: Option<i64>,
671 #[serde(skip_serializing_if = "Option::is_none")]
672 pub asset: Option<String>,
673 #[serde(skip_serializing_if = "Option::is_none")]
674 pub assetblinder: Option<String>,
675 #[serde(skip_serializing_if = "Option::is_none")]
676 pub amountblinder: Option<String>,
677}
678
679#[derive(Debug, Clone, Serialize, Deserialize)]
681pub struct TxInput {
682 pub txid: String,
683 pub vout: u32,
684 #[serde(skip_serializing_if = "Option::is_none")]
685 pub sequence: Option<u32>,
686}
687
688#[derive(Debug, Clone, Serialize, Deserialize)]
690pub struct DistributionResponse {
691 pub distribution_uuid: String,
692 pub map_address_amount: std::collections::HashMap<String, f64>,
693 pub map_address_asset: std::collections::HashMap<String, String>,
694 pub asset_id: String,
695}
696
697#[derive(Debug, Clone, Serialize, Deserialize)]
699pub struct ReceivedByAddress {
700 #[serde(skip_serializing_if = "Option::is_none")]
701 pub address: Option<String>,
702 #[serde(skip_serializing_if = "Option::is_none")]
703 pub amount: Option<std::collections::HashMap<String, f64>>,
704 #[serde(skip_serializing_if = "Option::is_none")]
705 pub confirmations: Option<u32>,
706 #[serde(skip_serializing_if = "Option::is_none")]
707 pub label: Option<String>,
708 #[serde(skip_serializing_if = "Option::is_none")]
709 pub txids: Option<Vec<String>>,
710}
711
712#[derive(Debug, Clone, Serialize, Deserialize)]
714pub struct DistributionTxData {
715 pub details: TransactionDetail,
716 pub txid: String,
717}
718
719#[derive(Debug, Clone, Serialize, Deserialize)]
721pub struct AmpTxData {
722 pub details: serde_json::Value,
724 pub txid: String,
725}
726
727#[derive(Debug, Clone, Serialize, Deserialize)]
729pub struct ConfirmDistributionRequest {
730 pub tx_data: AmpTxData,
731 pub change_data: Vec<Unspent>,
732}
733
734#[cfg(test)]
735mod tests {
736 use super::*;
737
738 #[test]
739 fn test_asset_distribution_assignment_creation() {
740 let assignment = AssetDistributionAssignment {
741 user_id: "user123".to_string(),
742 address: "lq1qq2xvpcvfup5j8zscjq05u2wxxjcyewk7979f9lq".to_string(),
743 amount: 100.5,
744 };
745
746 assert_eq!(assignment.user_id, "user123");
747 assert_eq!(
748 assignment.address,
749 "lq1qq2xvpcvfup5j8zscjq05u2wxxjcyewk7979f9lq"
750 );
751 assert_eq!(assignment.amount, 100.5);
752 }
753
754 #[test]
755 fn test_asset_distribution_assignment_serialization() {
756 let assignment = AssetDistributionAssignment {
757 user_id: "user123".to_string(),
758 address: "lq1qq2xvpcvfup5j8zscjq05u2wxxjcyewk7979f9lq".to_string(),
759 amount: 100.5,
760 };
761
762 let json = serde_json::to_string(&assignment).unwrap();
764 assert!(json.contains("user123"));
765 assert!(json.contains("lq1qq2xvpcvfup5j8zscjq05u2wxxjcyewk7979f9lq"));
766 assert!(json.contains("100.5"));
767
768 let deserialized: AssetDistributionAssignment = serde_json::from_str(&json).unwrap();
770 assert_eq!(deserialized.user_id, assignment.user_id);
771 assert_eq!(deserialized.address, assignment.address);
772 assert_eq!(deserialized.amount, assignment.amount);
773 }
774
775 #[test]
776 fn test_asset_distribution_assignment_clone() {
777 let assignment = AssetDistributionAssignment {
778 user_id: "user123".to_string(),
779 address: "lq1qq2xvpcvfup5j8zscjq05u2wxxjcyewk7979f9lq".to_string(),
780 amount: 100.5,
781 };
782
783 let cloned = assignment.clone();
784 assert_eq!(assignment.user_id, cloned.user_id);
785 assert_eq!(assignment.address, cloned.address);
786 assert_eq!(assignment.amount, cloned.amount);
787 }
788
789 #[test]
790 fn test_unspent_creation_and_serialization() {
791 let unspent = Unspent {
792 txid: "abc123def456".to_string(),
793 vout: 1,
794 amount: 50.0,
795 asset: "asset_id_hex".to_string(),
796 address: "lq1qq2xvpcvfup5j8zscjq05u2wxxjcyewk7979f9lq".to_string(),
797 spendable: true,
798 confirmations: Some(6),
799 scriptpubkey: Some("76a914...88ac".to_string()),
800 redeemscript: None,
801 witnessscript: None,
802 amountblinder: Some(
803 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".to_string(),
804 ),
805 assetblinder: Some(
806 "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210".to_string(),
807 ),
808 };
809
810 let json = serde_json::to_string(&unspent).unwrap();
812 assert!(json.contains("abc123def456"));
813 assert!(json.contains("50.0"));
814 assert!(json.contains("asset_id_hex"));
815
816 let deserialized: Unspent = serde_json::from_str(&json).unwrap();
818 assert_eq!(deserialized.txid, unspent.txid);
819 assert_eq!(deserialized.vout, unspent.vout);
820 assert_eq!(deserialized.amount, unspent.amount);
821 assert_eq!(deserialized.asset, unspent.asset);
822 assert_eq!(deserialized.confirmations, unspent.confirmations);
823 }
824
825 #[test]
826 fn test_transaction_detail_creation() {
827 let tx_detail = TransactionDetail {
828 txid: "def456abc123".to_string(),
829 confirmations: 3,
830 blockheight: Some(12345),
831 hex: "020000000001...".to_string(),
832 blockhash: Some("block_hash_hex".to_string()),
833 details: Some(vec![]),
834 blocktime: Some(1640995200),
835 time: Some(1640995200),
836 timereceived: Some(1640995180),
837 };
838
839 assert_eq!(tx_detail.txid, "def456abc123");
840 assert_eq!(tx_detail.confirmations, 3);
841 assert_eq!(tx_detail.blockheight, Some(12345));
842
843 let json = serde_json::to_string(&tx_detail).unwrap();
845 assert!(json.contains("def456abc123"));
846 assert!(json.contains("\"confirmations\":3"));
847 }
848
849 #[test]
850 fn test_tx_input_creation() {
851 let tx_input = TxInput {
852 txid: "input_txid_123".to_string(),
853 vout: 2,
854 sequence: Some(0xffffffff),
855 };
856
857 assert_eq!(tx_input.txid, "input_txid_123");
858 assert_eq!(tx_input.vout, 2);
859 assert_eq!(tx_input.sequence, Some(0xffffffff));
860
861 let json = serde_json::to_string(&tx_input).unwrap();
863 assert!(json.contains("input_txid_123"));
864 assert!(json.contains("\"vout\":2"));
865 assert!(json.contains("4294967295")); }
867
868 #[test]
869 fn test_distribution_response_creation() {
870 let mut map_address_amount = HashMap::new();
871 map_address_amount.insert("address1".to_string(), 100.0);
872 map_address_amount.insert("address2".to_string(), 50.0);
873
874 let mut map_address_asset = HashMap::new();
875 map_address_asset.insert("address1".to_string(), "asset_id_1".to_string());
876 map_address_asset.insert("address2".to_string(), "asset_id_1".to_string());
877
878 let distribution_response = DistributionResponse {
879 distribution_uuid: "dist_uuid_123".to_string(),
880 map_address_amount,
881 map_address_asset,
882 asset_id: "main_asset_id".to_string(),
883 };
884
885 assert_eq!(distribution_response.distribution_uuid, "dist_uuid_123");
886 assert_eq!(distribution_response.asset_id, "main_asset_id");
887 assert_eq!(distribution_response.map_address_amount.len(), 2);
888 assert_eq!(distribution_response.map_address_asset.len(), 2);
889
890 let json = serde_json::to_string(&distribution_response).unwrap();
892 assert!(json.contains("dist_uuid_123"));
893 assert!(json.contains("main_asset_id"));
894 assert!(json.contains("address1"));
895 assert!(json.contains("100.0"));
896 }
897
898 #[test]
899 fn test_distribution_assignment_request_creation() {
900 let assignment_request = DistributionAssignmentRequest {
901 user_uuid: "user_uuid_123".to_string(),
902 amount: 150.0,
903 address: "lq1qq2xvpcvfup5j8zscjq05u2wxxjcyewk7979f9lq".to_string(),
904 };
905
906 assert_eq!(assignment_request.user_uuid, "user_uuid_123");
907 assert_eq!(assignment_request.amount, 150.0);
908 assert_eq!(
909 assignment_request.address,
910 "lq1qq2xvpcvfup5j8zscjq05u2wxxjcyewk7979f9lq"
911 );
912
913 let json = serde_json::to_string(&assignment_request).unwrap();
915 assert!(json.contains("user_uuid_123"));
916 assert!(json.contains("150.0"));
917 assert!(json.contains("lq1qq2xvpcvfup5j8zscjq05u2wxxjcyewk7979f9lq"));
918
919 let deserialized: DistributionAssignmentRequest = serde_json::from_str(&json).unwrap();
921 assert_eq!(deserialized.user_uuid, assignment_request.user_uuid);
922 assert_eq!(deserialized.amount, assignment_request.amount);
923 assert_eq!(deserialized.address, assignment_request.address);
924 }
925
926 #[test]
927 fn test_create_distribution_request_creation() {
928 let assignments = vec![
929 DistributionAssignmentRequest {
930 user_uuid: "user1".to_string(),
931 amount: 100.0,
932 address: "address1".to_string(),
933 },
934 DistributionAssignmentRequest {
935 user_uuid: "user2".to_string(),
936 amount: 50.0,
937 address: "address2".to_string(),
938 },
939 ];
940
941 let create_request = CreateDistributionRequest {
942 assignments: assignments.clone(),
943 };
944
945 assert_eq!(create_request.assignments.len(), 2);
946 assert_eq!(create_request.assignments[0].user_uuid, "user1");
947 assert_eq!(create_request.assignments[1].amount, 50.0);
948
949 let json = serde_json::to_string(&create_request).unwrap();
951 assert!(json.contains("user1"));
952 assert!(json.contains("user2"));
953 assert!(json.contains("100.0"));
954 assert!(json.contains("50.0"));
955 assert!(json.contains("assignments"));
956
957 let deserialized: CreateDistributionRequest = serde_json::from_str(&json).unwrap();
959 assert_eq!(deserialized.assignments.len(), 2);
960 assert_eq!(deserialized.assignments[0].user_uuid, "user1");
961 assert_eq!(deserialized.assignments[1].user_uuid, "user2");
962 }
963
964 #[test]
965 fn test_distribution_request_serialization_format() {
966 let assignment = DistributionAssignmentRequest {
967 user_uuid: "test_user".to_string(),
968 amount: 123.45,
969 address: "test_address".to_string(),
970 };
971
972 let request = CreateDistributionRequest {
973 assignments: vec![assignment],
974 };
975
976 let json = serde_json::to_string(&request).unwrap();
977
978 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
980
981 assert!(parsed.get("assignments").is_some());
982 let assignments_array = parsed["assignments"].as_array().unwrap();
983 assert_eq!(assignments_array.len(), 1);
984
985 let first_assignment = &assignments_array[0];
986 assert_eq!(first_assignment["user_uuid"], "test_user");
987 assert_eq!(first_assignment["amount"], 123.45);
988 assert_eq!(first_assignment["address"], "test_address");
989 }
990
991 #[test]
992 fn test_distribution_tx_data_creation() {
993 let tx_detail = TransactionDetail {
994 txid: "test_txid_123".to_string(),
995 confirmations: 2,
996 blockheight: Some(12345),
997 hex: "020000000001...".to_string(),
998 blockhash: Some("block_hash_hex".to_string()),
999 blocktime: Some(1640995200),
1000 time: Some(1640995200),
1001 timereceived: Some(1640995180),
1002 details: Some(vec![]),
1003 };
1004
1005 let tx_data = DistributionTxData {
1006 details: tx_detail.clone(),
1007 txid: "test_txid_123".to_string(),
1008 };
1009
1010 assert_eq!(tx_data.txid, "test_txid_123");
1011 assert_eq!(tx_data.details.txid, "test_txid_123");
1012 assert_eq!(tx_data.details.confirmations, 2);
1013
1014 let json = serde_json::to_string(&tx_data).unwrap();
1016 assert!(json.contains("test_txid_123"));
1017 assert!(json.contains("\"confirmations\":2"));
1018
1019 let deserialized: DistributionTxData = serde_json::from_str(&json).unwrap();
1021 assert_eq!(deserialized.txid, tx_data.txid);
1022 assert_eq!(
1023 deserialized.details.confirmations,
1024 tx_data.details.confirmations
1025 );
1026 }
1027
1028 #[test]
1029 fn test_confirm_distribution_request_creation() {
1030 let _tx_detail = TransactionDetail {
1031 txid: "confirm_test_txid".to_string(),
1032 confirmations: 3,
1033 blockheight: Some(54321),
1034 hex: "020000000002...".to_string(),
1035 blockhash: Some("confirm_block_hash".to_string()),
1036 blocktime: Some(1640995300),
1037 time: Some(1640995300),
1038 timereceived: Some(1640995280),
1039 details: Some(vec![]),
1040 };
1041
1042 let tx_data = AmpTxData {
1043 details: serde_json::json!([]),
1044 txid: "confirm_test_txid".to_string(),
1045 };
1046
1047 let change_utxo = Unspent {
1048 txid: "change_txid_123".to_string(),
1049 vout: 1,
1050 amount: 25.0,
1051 asset: "change_asset_id".to_string(),
1052 address: "change_address".to_string(),
1053 spendable: true,
1054 confirmations: Some(3),
1055 scriptpubkey: Some("76a914...88ac".to_string()),
1056 redeemscript: None,
1057 witnessscript: None,
1058 amountblinder: Some(
1059 "1111111111111111111111111111111111111111111111111111111111111111".to_string(),
1060 ),
1061 assetblinder: Some(
1062 "2222222222222222222222222222222222222222222222222222222222222222".to_string(),
1063 ),
1064 };
1065
1066 let confirm_request = ConfirmDistributionRequest {
1067 tx_data,
1068 change_data: vec![change_utxo],
1069 };
1070
1071 assert_eq!(confirm_request.tx_data.txid, "confirm_test_txid");
1072 assert_eq!(confirm_request.change_data.len(), 1);
1073 assert_eq!(confirm_request.change_data[0].txid, "change_txid_123");
1074
1075 let json = serde_json::to_string(&confirm_request).unwrap();
1077 assert!(json.contains("confirm_test_txid"));
1078 assert!(json.contains("change_txid_123"));
1079 assert!(json.contains("tx_data"));
1080 assert!(json.contains("change_data"));
1081
1082 let deserialized: ConfirmDistributionRequest = serde_json::from_str(&json).unwrap();
1084 assert_eq!(deserialized.tx_data.txid, confirm_request.tx_data.txid);
1085 assert_eq!(deserialized.change_data.len(), 1);
1086 assert_eq!(deserialized.change_data[0].txid, "change_txid_123");
1087 }
1088
1089 #[test]
1090 fn test_confirm_distribution_request_serialization_format() {
1091 let _tx_detail = TransactionDetail {
1092 txid: "format_test_txid".to_string(),
1093 confirmations: 2,
1094 blockheight: Some(98765),
1095 hex: "format_test_hex".to_string(),
1096 blockhash: None,
1097 blocktime: None,
1098 time: None,
1099 timereceived: None,
1100 details: Some(vec![]),
1101 };
1102
1103 let tx_data = AmpTxData {
1104 details: serde_json::json!([]),
1105 txid: "format_test_txid".to_string(),
1106 };
1107
1108 let confirm_request = ConfirmDistributionRequest {
1109 tx_data,
1110 change_data: vec![],
1111 };
1112
1113 let json = serde_json::to_string(&confirm_request).unwrap();
1114
1115 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1117
1118 assert!(parsed.get("tx_data").is_some());
1119 assert!(parsed.get("change_data").is_some());
1120
1121 let tx_data_obj = &parsed["tx_data"];
1122 assert!(tx_data_obj.get("details").is_some());
1123 assert!(tx_data_obj.get("txid").is_some());
1124 assert_eq!(tx_data_obj["txid"], "format_test_txid");
1125
1126 let change_data_array = parsed["change_data"].as_array().unwrap();
1127 assert_eq!(change_data_array.len(), 0);
1128 }
1129}
1130
1131pub mod secret_serde {
1133 use super::{Deserialize, Deserializer, Secret, Serialize, Serializer};
1134
1135 pub fn serialize<S>(secret: &Secret<String>, serializer: S) -> Result<S::Ok, S::Error>
1138 where
1139 S: Serializer,
1140 {
1141 use secrecy::ExposeSecret;
1142 secret.expose_secret().serialize(serializer)
1143 }
1144
1145 pub fn deserialize<'de, D>(deserializer: D) -> Result<Secret<String>, D::Error>
1148 where
1149 D: Deserializer<'de>,
1150 {
1151 let s = String::deserialize(deserializer)?;
1152 Ok(Secret::new(s))
1153 }
1154}