1use cdk_sql_common::SQLWalletDatabase;
4
5use crate::common::SqliteConnectionManager;
6
7pub mod memory;
8
9pub type WalletSqliteDatabase = SQLWalletDatabase<SqliteConnectionManager>;
11
12#[cfg(test)]
13mod tests {
14 use cdk_common::wallet_db_test;
15
16 use super::memory;
17
18 async fn provide_db(_test_name: String) -> super::WalletSqliteDatabase {
19 memory::empty().await.unwrap()
20 }
21
22 wallet_db_test!(provide_db);
23 use std::str::FromStr;
24
25 use cdk_common::database::WalletDatabase;
26 use cdk_common::nut00::KnownMethod;
27 use cdk_common::nuts::{ProofDleq, State};
28 use cdk_common::secret::Secret;
29
30 use crate::WalletSqliteDatabase;
31
32 #[tokio::test]
33 #[cfg(feature = "sqlcipher")]
34 async fn test_sqlcipher() {
35 use cdk_common::mint_url::MintUrl;
36 use cdk_common::MintInfo;
37
38 use super::*;
39 let path = std::env::temp_dir()
40 .to_path_buf()
41 .join(format!("cdk-test-{}.sqlite", uuid::Uuid::new_v4()));
42 let db = WalletSqliteDatabase::new((path, "password".to_string()))
43 .await
44 .unwrap();
45
46 let mint_info = MintInfo::new().description("test");
47 let mint_url = MintUrl::from_str("https://mint.xyz").unwrap();
48
49 db.add_mint(mint_url.clone(), Some(mint_info.clone()))
50 .await
51 .unwrap();
52
53 let res = db.get_mint(mint_url).await.unwrap();
54 assert_eq!(mint_info, res.clone().unwrap());
55 assert_eq!("test", &res.unwrap().description.unwrap());
56 }
57
58 #[tokio::test]
59 async fn test_proof_with_dleq() {
60 use cdk_common::mint_url::MintUrl;
61 use cdk_common::nuts::{CurrencyUnit, Id, Proof, PublicKey, SecretKey};
62 use cdk_common::wallet::ProofInfo;
63 use cdk_common::Amount;
64
65 let path = std::env::temp_dir()
67 .to_path_buf()
68 .join(format!("cdk-test-dleq-{}.sqlite", uuid::Uuid::new_v4()));
69
70 #[cfg(feature = "sqlcipher")]
71 let db = WalletSqliteDatabase::new((path, "password".to_string()))
72 .await
73 .unwrap();
74
75 #[cfg(not(feature = "sqlcipher"))]
76 let db = WalletSqliteDatabase::new(path).await.unwrap();
77
78 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
80 let mint_url = MintUrl::from_str("https://example.com").unwrap();
81 let secret = Secret::new("test_secret_for_dleq");
82
83 let e = SecretKey::generate();
85 let s = SecretKey::generate();
86 let r = SecretKey::generate();
87
88 let dleq = ProofDleq::new(e.clone(), s.clone(), r.clone());
89
90 let mut proof = Proof::new(
91 Amount::from(64),
92 keyset_id,
93 secret,
94 PublicKey::from_hex(
95 "02deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
96 )
97 .unwrap(),
98 );
99
100 proof.dleq = Some(dleq);
102
103 let proof_info =
105 ProofInfo::new(proof, mint_url.clone(), State::Unspent, CurrencyUnit::Sat).unwrap();
106
107 db.update_proofs(vec![proof_info.clone()], vec![])
109 .await
110 .unwrap();
111
112 let retrieved_proofs = db
114 .get_proofs(
115 Some(mint_url),
116 Some(CurrencyUnit::Sat),
117 Some(vec![State::Unspent]),
118 None,
119 )
120 .await
121 .unwrap();
122
123 assert_eq!(retrieved_proofs.len(), 1);
125
126 let retrieved_proof = &retrieved_proofs[0];
128 assert!(retrieved_proof.proof.dleq.is_some());
129
130 let retrieved_dleq = retrieved_proof.proof.dleq.as_ref().unwrap();
131
132 assert_eq!(retrieved_dleq.e.to_string(), e.to_string());
134 assert_eq!(retrieved_dleq.s.to_string(), s.to_string());
135 assert_eq!(retrieved_dleq.r.to_string(), r.to_string());
136 }
137
138 #[tokio::test]
139 async fn test_mint_quote_payment_method_read_and_write() {
140 use cdk_common::mint_url::MintUrl;
141 use cdk_common::nuts::{CurrencyUnit, MintQuoteState, PaymentMethod};
142 use cdk_common::wallet::MintQuote;
143 use cdk_common::Amount;
144
145 let path = std::env::temp_dir().to_path_buf().join(format!(
147 "cdk-test-migration-{}.sqlite",
148 uuid::Uuid::new_v4()
149 ));
150
151 #[cfg(feature = "sqlcipher")]
152 let db = WalletSqliteDatabase::new((path, "password".to_string()))
153 .await
154 .unwrap();
155
156 #[cfg(not(feature = "sqlcipher"))]
157 let db = WalletSqliteDatabase::new(path).await.unwrap();
158
159 let mint_url = MintUrl::from_str("https://example.com").unwrap();
161 let payment_methods = [
162 PaymentMethod::Known(KnownMethod::Bolt11),
163 PaymentMethod::Known(KnownMethod::Bolt11),
164 PaymentMethod::Custom("custom".to_string()),
165 ];
166
167 for (i, payment_method) in payment_methods.iter().enumerate() {
168 let quote = MintQuote {
169 id: format!("test_quote_{}", i),
170 mint_url: mint_url.clone(),
171 amount: Some(Amount::from(100)),
172 unit: CurrencyUnit::Sat,
173 request: "test_request".to_string(),
174 state: MintQuoteState::Unpaid,
175 expiry: 1000000000,
176 secret_key: None,
177 payment_method: payment_method.clone(),
178 amount_issued: Amount::from(0),
179 amount_paid: Amount::from(0),
180 used_by_operation: None,
181 version: 0,
182 };
183
184 db.add_mint_quote(quote.clone()).await.unwrap();
186
187 let retrieved = db.get_mint_quote("e.id).await.unwrap().unwrap();
189 assert_eq!(retrieved.payment_method, *payment_method);
190 assert_eq!(retrieved.amount_issued, Amount::from(0));
191 assert_eq!(retrieved.amount_paid, Amount::from(0));
192 }
193 }
194
195 #[tokio::test]
196 async fn test_get_proofs_by_ys() {
197 use cdk_common::mint_url::MintUrl;
198 use cdk_common::nuts::{CurrencyUnit, Id, Proof, SecretKey};
199 use cdk_common::wallet::ProofInfo;
200 use cdk_common::Amount;
201
202 let path = std::env::temp_dir().to_path_buf().join(format!(
204 "cdk-test-proofs-by-ys-{}.sqlite",
205 uuid::Uuid::new_v4()
206 ));
207
208 #[cfg(feature = "sqlcipher")]
209 let db = WalletSqliteDatabase::new((path, "password".to_string()))
210 .await
211 .unwrap();
212
213 #[cfg(not(feature = "sqlcipher"))]
214 let db = WalletSqliteDatabase::new(path).await.unwrap();
215
216 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
218 let mint_url = MintUrl::from_str("https://example.com").unwrap();
219
220 let mut proof_infos = vec![];
221 let mut expected_ys = vec![];
222
223 for _i in 0..5 {
225 let secret = Secret::generate();
226
227 let secret_key = SecretKey::generate();
229 let c = secret_key.public_key();
230
231 let proof = Proof::new(Amount::from(64), keyset_id, secret, c);
232
233 let proof_info =
234 ProofInfo::new(proof, mint_url.clone(), State::Unspent, CurrencyUnit::Sat).unwrap();
235
236 expected_ys.push(proof_info.y);
237 proof_infos.push(proof_info);
238 }
239
240 db.update_proofs(proof_infos.clone(), vec![]).await.unwrap();
242
243 let retrieved_proofs = db.get_proofs_by_ys(expected_ys.clone()).await.unwrap();
245
246 assert_eq!(retrieved_proofs.len(), 5);
247 for retrieved_proof in &retrieved_proofs {
248 assert!(expected_ys.contains(&retrieved_proof.y));
249 }
250
251 let subset_ys = expected_ys[0..3].to_vec();
253 let subset_proofs = db.get_proofs_by_ys(subset_ys.clone()).await.unwrap();
254
255 assert_eq!(subset_proofs.len(), 3);
256 for retrieved_proof in &subset_proofs {
257 assert!(subset_ys.contains(&retrieved_proof.y));
258 }
259
260 let non_existent_secret_key = SecretKey::generate();
262 let non_existent_y = non_existent_secret_key.public_key();
263 let mixed_ys = vec![expected_ys[0], non_existent_y, expected_ys[1]];
264 let mixed_proofs = db.get_proofs_by_ys(mixed_ys).await.unwrap();
265
266 assert_eq!(mixed_proofs.len(), 2);
268
269 let empty_result = db.get_proofs_by_ys(vec![]).await.unwrap();
271 assert_eq!(empty_result.len(), 0);
272
273 let single_y = vec![expected_ys[2]];
275 let single_proof = db.get_proofs_by_ys(single_y).await.unwrap();
276
277 assert_eq!(single_proof.len(), 1);
278 assert_eq!(single_proof[0].y, proof_infos[2].y);
279 assert_eq!(single_proof[0].proof.amount, proof_infos[2].proof.amount);
280 assert_eq!(single_proof[0].mint_url, proof_infos[2].mint_url);
281 assert_eq!(single_proof[0].state, proof_infos[2].state);
282 }
283
284 #[tokio::test]
285 async fn test_get_unissued_mint_quotes() {
286 use cdk_common::mint_url::MintUrl;
287 use cdk_common::nuts::{CurrencyUnit, MintQuoteState, PaymentMethod};
288 use cdk_common::wallet::MintQuote;
289 use cdk_common::Amount;
290
291 let path = std::env::temp_dir().to_path_buf().join(format!(
293 "cdk-test-unpaid-quotes-{}.sqlite",
294 uuid::Uuid::new_v4()
295 ));
296
297 #[cfg(feature = "sqlcipher")]
298 let db = WalletSqliteDatabase::new((path, "password".to_string()))
299 .await
300 .unwrap();
301
302 #[cfg(not(feature = "sqlcipher"))]
303 let db = WalletSqliteDatabase::new(path).await.unwrap();
304
305 let mint_url = MintUrl::from_str("https://example.com").unwrap();
306
307 let quote1 = MintQuote {
309 id: "quote_fully_paid".to_string(),
310 mint_url: mint_url.clone(),
311 amount: Some(Amount::from(100)),
312 unit: CurrencyUnit::Sat,
313 request: "test_request_1".to_string(),
314 state: MintQuoteState::Paid,
315 expiry: 1000000000,
316 secret_key: None,
317 payment_method: PaymentMethod::Known(KnownMethod::Bolt11),
318 amount_issued: Amount::from(100),
319 amount_paid: Amount::from(100),
320 used_by_operation: None,
321 version: 0,
322 };
323
324 let quote2 = MintQuote {
326 id: "quote_pending_balance".to_string(),
327 mint_url: mint_url.clone(),
328 amount: Some(Amount::from(100)),
329 unit: CurrencyUnit::Sat,
330 request: "test_request_2".to_string(),
331 state: MintQuoteState::Paid,
332 expiry: 1000000000,
333 secret_key: None,
334 payment_method: PaymentMethod::Known(KnownMethod::Bolt11),
335 amount_issued: Amount::from(0),
336 amount_paid: Amount::from(100),
337 used_by_operation: None,
338 version: 0,
339 };
340
341 let quote3 = MintQuote {
343 id: "quote_bolt12".to_string(),
344 mint_url: mint_url.clone(),
345 amount: Some(Amount::from(100)),
346 unit: CurrencyUnit::Sat,
347 request: "test_request_3".to_string(),
348 state: MintQuoteState::Unpaid,
349 expiry: 1000000000,
350 secret_key: None,
351 payment_method: PaymentMethod::Known(KnownMethod::Bolt12),
352 amount_issued: Amount::from(0),
353 amount_paid: Amount::from(0),
354 used_by_operation: None,
355 version: 0,
356 };
357
358 let quote4 = MintQuote {
360 id: "quote_unpaid".to_string(),
361 mint_url: mint_url.clone(),
362 amount: Some(Amount::from(100)),
363 unit: CurrencyUnit::Sat,
364 request: "test_request_4".to_string(),
365 state: MintQuoteState::Unpaid,
366 expiry: 1000000000,
367 secret_key: None,
368 payment_method: PaymentMethod::Known(KnownMethod::Bolt11),
369 amount_issued: Amount::from(0),
370 amount_paid: Amount::from(0),
371 used_by_operation: None,
372 version: 0,
373 };
374
375 db.add_mint_quote(quote1).await.unwrap();
377 db.add_mint_quote(quote2.clone()).await.unwrap();
378 db.add_mint_quote(quote3.clone()).await.unwrap();
379 db.add_mint_quote(quote4.clone()).await.unwrap();
380
381 let unissued_quotes = db.get_unissued_mint_quotes().await.unwrap();
383
384 assert_eq!(unissued_quotes.len(), 3);
389
390 let quote_ids: Vec<&str> = unissued_quotes.iter().map(|q| q.id.as_str()).collect();
392 assert!(quote_ids.contains(&"quote_pending_balance"));
393 assert!(quote_ids.contains(&"quote_bolt12"));
394 assert!(quote_ids.contains(&"quote_unpaid"));
395
396 assert!(!quote_ids.contains(&"quote_fully_paid"));
398 }
399}