1#![warn(clippy::unused_async)]
6
7pub mod database;
8pub mod error;
9pub mod multi_mint_wallet;
10pub mod token;
11pub mod types;
12pub mod wallet;
13
14pub use database::*;
15pub use error::*;
16pub use multi_mint_wallet::*;
17pub use types::*;
18pub use wallet::*;
19
20uniffi::setup_scaffolding!();
21
22#[cfg(test)]
23mod tests {
24 use super::*;
25
26 #[test]
27 fn test_amount_conversion() {
28 let amount = Amount::new(1000);
29 assert_eq!(amount.value, 1000);
30 assert!(!amount.is_zero());
31
32 let zero = Amount::zero();
33 assert!(zero.is_zero());
34 }
35
36 #[test]
37 fn test_currency_unit_conversion() {
38 use cdk::nuts::CurrencyUnit as CdkCurrencyUnit;
39
40 let unit = CurrencyUnit::Sat;
41 let cdk_unit: CdkCurrencyUnit = unit.into();
42 let back: CurrencyUnit = cdk_unit.into();
43 assert_eq!(back, CurrencyUnit::Sat);
44 }
45
46 #[test]
47 fn test_mint_url_creation() {
48 let url = MintUrl::new("https://mint.example.com".to_string());
49 assert!(url.is_ok());
50
51 let invalid_url = MintUrl::new("not-a-url".to_string());
52 assert!(invalid_url.is_err());
53 }
54
55 #[test]
56 fn test_send_options_default() {
57 let options = SendOptions::default();
58 assert!(options.memo.is_none());
59 assert!(options.conditions.is_none());
60 assert!(matches!(options.amount_split_target, SplitTarget::None));
61 assert!(matches!(options.send_kind, SendKind::OnlineExact));
62 assert!(!options.include_fee);
63 assert!(options.max_proofs.is_none());
64 assert!(options.metadata.is_empty());
65 }
66
67 #[test]
68 fn test_receive_options_default() {
69 let options = ReceiveOptions::default();
70 assert!(matches!(options.amount_split_target, SplitTarget::None));
71 assert!(options.p2pk_signing_keys.is_empty());
72 assert!(options.preimages.is_empty());
73 assert!(options.metadata.is_empty());
74 }
75
76 #[test]
77 fn test_send_memo() {
78 let memo_text = "Test memo".to_string();
79 let memo = SendMemo {
80 memo: memo_text.clone(),
81 include_memo: true,
82 };
83
84 assert_eq!(memo.memo, memo_text);
85 assert!(memo.include_memo);
86 }
87
88 #[test]
89 fn test_split_target_variants() {
90 let split_none = SplitTarget::None;
91 assert!(matches!(split_none, SplitTarget::None));
92
93 let amount = Amount::new(1000);
94 let split_value = SplitTarget::Value { amount };
95 assert!(matches!(split_value, SplitTarget::Value { .. }));
96
97 let amounts = vec![Amount::new(100), Amount::new(200)];
98 let split_values = SplitTarget::Values { amounts };
99 assert!(matches!(split_values, SplitTarget::Values { .. }));
100 }
101
102 #[test]
103 fn test_send_kind_variants() {
104 let online_exact = SendKind::OnlineExact;
105 assert!(matches!(online_exact, SendKind::OnlineExact));
106
107 let tolerance = Amount::new(50);
108 let online_tolerance = SendKind::OnlineTolerance { tolerance };
109 assert!(matches!(online_tolerance, SendKind::OnlineTolerance { .. }));
110
111 let offline_exact = SendKind::OfflineExact;
112 assert!(matches!(offline_exact, SendKind::OfflineExact));
113
114 let offline_tolerance = SendKind::OfflineTolerance { tolerance };
115 assert!(matches!(
116 offline_tolerance,
117 SendKind::OfflineTolerance { .. }
118 ));
119 }
120
121 #[test]
122 fn test_secret_key_from_hex() {
123 let valid_hex = "a".repeat(64);
125 let secret_key = SecretKey::from_hex(valid_hex.clone());
126 assert!(secret_key.is_ok());
127 assert_eq!(secret_key.unwrap().hex, valid_hex);
128
129 let invalid_length = "a".repeat(32); let secret_key = SecretKey::from_hex(invalid_length);
132 assert!(secret_key.is_err());
133
134 let invalid_chars = "g".repeat(64); let secret_key = SecretKey::from_hex(invalid_chars);
137 assert!(secret_key.is_err());
138 }
139
140 #[test]
141 fn test_secret_key_random() {
142 let key1 = SecretKey::random();
143 let key2 = SecretKey::random();
144
145 assert_ne!(key1.hex, key2.hex);
147
148 assert_eq!(key1.hex.len(), 64);
150 assert_eq!(key2.hex.len(), 64);
151 assert!(key1.hex.chars().all(|c| c.is_ascii_hexdigit()));
152 assert!(key2.hex.chars().all(|c| c.is_ascii_hexdigit()));
153 }
154
155 #[test]
156 fn test_send_options_with_all_fields() {
157 use std::collections::HashMap;
158
159 let memo = SendMemo {
160 memo: "Test memo".to_string(),
161 include_memo: true,
162 };
163
164 let mut metadata = HashMap::new();
165 metadata.insert("key1".to_string(), "value1".to_string());
166
167 let conditions = SpendingConditions::P2PK {
168 pubkey: "02a1633cafcc01ebfb6d78e39f687a1f0995c62fc95f51ead10a02ee0be551b5dc"
169 .to_string(),
170 conditions: None,
171 };
172
173 let options = SendOptions {
174 memo: Some(memo),
175 conditions: Some(conditions),
176 amount_split_target: SplitTarget::Value {
177 amount: Amount::new(1000),
178 },
179 send_kind: SendKind::OnlineTolerance {
180 tolerance: Amount::new(50),
181 },
182 include_fee: true,
183 max_proofs: Some(10),
184 metadata,
185 };
186
187 assert!(options.memo.is_some());
188 assert!(options.conditions.is_some());
189 assert!(matches!(
190 options.amount_split_target,
191 SplitTarget::Value { .. }
192 ));
193 assert!(matches!(
194 options.send_kind,
195 SendKind::OnlineTolerance { .. }
196 ));
197 assert!(options.include_fee);
198 assert_eq!(options.max_proofs, Some(10));
199 assert!(!options.metadata.is_empty());
200 }
201
202 #[test]
203 fn test_receive_options_with_all_fields() {
204 use std::collections::HashMap;
205
206 let secret_key = SecretKey::random();
207 let mut metadata = HashMap::new();
208 metadata.insert("key1".to_string(), "value1".to_string());
209
210 let options = ReceiveOptions {
211 amount_split_target: SplitTarget::Values {
212 amounts: vec![Amount::new(100), Amount::new(200)],
213 },
214 p2pk_signing_keys: vec![secret_key],
215 preimages: vec!["preimage1".to_string(), "preimage2".to_string()],
216 metadata,
217 };
218
219 assert!(matches!(
220 options.amount_split_target,
221 SplitTarget::Values { .. }
222 ));
223 assert_eq!(options.p2pk_signing_keys.len(), 1);
224 assert_eq!(options.preimages.len(), 2);
225 assert!(!options.metadata.is_empty());
226 }
227
228 #[test]
229 fn test_wallet_config() {
230 let config = WalletConfig {
231 target_proof_count: None,
232 };
233 assert!(config.target_proof_count.is_none());
234
235 let config_with_values = WalletConfig {
236 target_proof_count: Some(5),
237 };
238 assert_eq!(config_with_values.target_proof_count, Some(5));
239 }
240
241 #[test]
242 fn test_mnemonic_generation() {
243 let mnemonic = generate_mnemonic().unwrap();
245 assert!(!mnemonic.is_empty());
246 assert_eq!(mnemonic.split_whitespace().count(), 12);
247
248 use bip39::Mnemonic;
250 let parsed = Mnemonic::parse(&mnemonic);
251 assert!(parsed.is_ok());
252 }
253
254 #[test]
255 fn test_mnemonic_validation() {
256 let mnemonic = generate_mnemonic().unwrap();
258 use bip39::Mnemonic;
259 let parsed = Mnemonic::parse(&mnemonic);
260 assert!(parsed.is_ok());
261
262 let invalid_mnemonic = "invalid mnemonic phrase that should not work";
264 let parsed_invalid = Mnemonic::parse(invalid_mnemonic);
265 assert!(parsed_invalid.is_err());
266
267 let mnemonic_12 = generate_mnemonic().unwrap();
269 assert_eq!(mnemonic_12.split_whitespace().count(), 12);
270 }
271
272 #[test]
273 fn test_mnemonic_to_entropy() {
274 let mnemonic = generate_mnemonic().unwrap();
276 let entropy = mnemonic_to_entropy(mnemonic.clone()).unwrap();
277
278 assert_eq!(entropy.len(), 16);
280
281 use bip39::Mnemonic;
283 let recreated_mnemonic = Mnemonic::from_entropy(&entropy).unwrap();
284 assert_eq!(recreated_mnemonic.to_string(), mnemonic);
285
286 let invalid_result = mnemonic_to_entropy("invalid mnemonic".to_string());
288 assert!(invalid_result.is_err());
289 }
290}