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 estimated_blocks: None,
181 used_by_operation: None,
182 version: 0,
183 };
184
185 db.add_mint_quote(quote.clone()).await.unwrap();
187
188 let retrieved = db.get_mint_quote("e.id).await.unwrap().unwrap();
190 assert_eq!(retrieved.payment_method, *payment_method);
191 assert_eq!(retrieved.amount_issued, Amount::from(0));
192 assert_eq!(retrieved.amount_paid, Amount::from(0));
193 }
194 }
195
196 #[tokio::test]
197 async fn test_get_proofs_by_ys_empty_errors() {
198 use cdk_common::database::Error;
199
200 let path = std::env::temp_dir().to_path_buf().join(format!(
201 "cdk-test-proofs-by-ys-empty-{}.sqlite",
202 uuid::Uuid::new_v4()
203 ));
204
205 #[cfg(feature = "sqlcipher")]
206 let db = WalletSqliteDatabase::new((path, "password".to_string()))
207 .await
208 .unwrap();
209
210 #[cfg(not(feature = "sqlcipher"))]
211 let db = WalletSqliteDatabase::new(path).await.unwrap();
212
213 let result = db.get_proofs_by_ys(vec![]).await;
214 assert!(matches!(result, Err(Error::EmptyInClause(_))));
215 }
216
217 #[tokio::test]
218 async fn test_get_proofs_by_ys() {
219 use cdk_common::mint_url::MintUrl;
220 use cdk_common::nuts::{CurrencyUnit, Id, Proof, SecretKey};
221 use cdk_common::wallet::ProofInfo;
222 use cdk_common::Amount;
223
224 let path = std::env::temp_dir().to_path_buf().join(format!(
225 "cdk-test-proofs-by-ys-{}.sqlite",
226 uuid::Uuid::new_v4()
227 ));
228
229 #[cfg(feature = "sqlcipher")]
230 let db = WalletSqliteDatabase::new((path, "password".to_string()))
231 .await
232 .unwrap();
233
234 #[cfg(not(feature = "sqlcipher"))]
235 let db = WalletSqliteDatabase::new(path).await.unwrap();
236
237 let keyset_id = Id::from_str("00deadbeef123456").unwrap();
238 let mint_url = MintUrl::from_str("https://example.com").unwrap();
239
240 let mut proof_infos = vec![];
241 let mut expected_ys = vec![];
242
243 for _i in 0..5 {
244 let secret = Secret::generate();
245 let secret_key = SecretKey::generate();
246 let c = secret_key.public_key();
247 let proof = Proof::new(Amount::from(64), keyset_id, secret, c);
248 let proof_info =
249 ProofInfo::new(proof, mint_url.clone(), State::Unspent, CurrencyUnit::Sat).unwrap();
250
251 expected_ys.push(proof_info.y);
252 proof_infos.push(proof_info);
253 }
254
255 db.update_proofs(proof_infos.clone(), vec![]).await.unwrap();
256
257 let retrieved_proofs = db.get_proofs_by_ys(expected_ys.clone()).await.unwrap();
259 assert_eq!(retrieved_proofs.len(), 5);
260 for retrieved_proof in &retrieved_proofs {
261 assert!(expected_ys.contains(&retrieved_proof.y));
262 }
263
264 let subset_ys = expected_ys[0..3].to_vec();
266 let subset_proofs = db.get_proofs_by_ys(subset_ys.clone()).await.unwrap();
267 assert_eq!(subset_proofs.len(), 3);
268 for retrieved_proof in &subset_proofs {
269 assert!(subset_ys.contains(&retrieved_proof.y));
270 }
271
272 let non_existent_secret_key = SecretKey::generate();
274 let non_existent_y = non_existent_secret_key.public_key();
275 let mixed_ys = vec![expected_ys[0], non_existent_y, expected_ys[1]];
276 let mixed_proofs = db.get_proofs_by_ys(mixed_ys).await.unwrap();
277 assert_eq!(mixed_proofs.len(), 2);
278
279 let single_y = vec![expected_ys[2]];
281 let single_proof = db.get_proofs_by_ys(single_y).await.unwrap();
282 assert_eq!(single_proof.len(), 1);
283 assert_eq!(single_proof[0].y, proof_infos[2].y);
284 assert_eq!(single_proof[0].proof.amount, proof_infos[2].proof.amount);
285 assert_eq!(single_proof[0].mint_url, proof_infos[2].mint_url);
286 assert_eq!(single_proof[0].state, proof_infos[2].state);
287 }
288
289 #[tokio::test]
290 async fn test_get_unissued_mint_quotes() {
291 use cdk_common::mint_url::MintUrl;
292 use cdk_common::nuts::{CurrencyUnit, MintQuoteState, PaymentMethod};
293 use cdk_common::wallet::MintQuote;
294 use cdk_common::Amount;
295
296 let path = std::env::temp_dir().to_path_buf().join(format!(
298 "cdk-test-unpaid-quotes-{}.sqlite",
299 uuid::Uuid::new_v4()
300 ));
301
302 #[cfg(feature = "sqlcipher")]
303 let db = WalletSqliteDatabase::new((path, "password".to_string()))
304 .await
305 .unwrap();
306
307 #[cfg(not(feature = "sqlcipher"))]
308 let db = WalletSqliteDatabase::new(path).await.unwrap();
309
310 let mint_url = MintUrl::from_str("https://example.com").unwrap();
311
312 let quote1 = MintQuote {
314 id: "quote_fully_paid".to_string(),
315 mint_url: mint_url.clone(),
316 amount: Some(Amount::from(100)),
317 unit: CurrencyUnit::Sat,
318 request: "test_request_1".to_string(),
319 state: MintQuoteState::Paid,
320 expiry: 1000000000,
321 secret_key: None,
322 payment_method: PaymentMethod::Known(KnownMethod::Bolt11),
323 amount_issued: Amount::from(100),
324 amount_paid: Amount::from(100),
325 estimated_blocks: None,
326 used_by_operation: None,
327 version: 0,
328 };
329
330 let quote2 = MintQuote {
332 id: "quote_pending_balance".to_string(),
333 mint_url: mint_url.clone(),
334 amount: Some(Amount::from(100)),
335 unit: CurrencyUnit::Sat,
336 request: "test_request_2".to_string(),
337 state: MintQuoteState::Paid,
338 expiry: 1000000000,
339 secret_key: None,
340 payment_method: PaymentMethod::Known(KnownMethod::Bolt11),
341 amount_issued: Amount::from(0),
342 amount_paid: Amount::from(100),
343 estimated_blocks: None,
344 used_by_operation: None,
345 version: 0,
346 };
347
348 let quote3 = MintQuote {
350 id: "quote_bolt12".to_string(),
351 mint_url: mint_url.clone(),
352 amount: Some(Amount::from(100)),
353 unit: CurrencyUnit::Sat,
354 request: "test_request_3".to_string(),
355 state: MintQuoteState::Unpaid,
356 expiry: 1000000000,
357 secret_key: None,
358 payment_method: PaymentMethod::Known(KnownMethod::Bolt12),
359 amount_issued: Amount::from(0),
360 amount_paid: Amount::from(0),
361 estimated_blocks: None,
362 used_by_operation: None,
363 version: 0,
364 };
365
366 let quote4 = MintQuote {
368 id: "quote_unpaid".to_string(),
369 mint_url: mint_url.clone(),
370 amount: Some(Amount::from(100)),
371 unit: CurrencyUnit::Sat,
372 request: "test_request_4".to_string(),
373 state: MintQuoteState::Unpaid,
374 expiry: 1000000000,
375 secret_key: None,
376 payment_method: PaymentMethod::Known(KnownMethod::Bolt11),
377 amount_issued: Amount::from(0),
378 amount_paid: Amount::from(0),
379 estimated_blocks: None,
380 used_by_operation: None,
381 version: 0,
382 };
383
384 db.add_mint_quote(quote1).await.unwrap();
386 db.add_mint_quote(quote2.clone()).await.unwrap();
387 db.add_mint_quote(quote3.clone()).await.unwrap();
388 db.add_mint_quote(quote4.clone()).await.unwrap();
389
390 let unissued_quotes = db.get_unissued_mint_quotes().await.unwrap();
392
393 assert_eq!(unissued_quotes.len(), 3);
398
399 let quote_ids: Vec<&str> = unissued_quotes.iter().map(|q| q.id.as_str()).collect();
401 assert!(quote_ids.contains(&"quote_pending_balance"));
402 assert!(quote_ids.contains(&"quote_bolt12"));
403 assert!(quote_ids.contains(&"quote_unpaid"));
404
405 assert!(!quote_ids.contains(&"quote_fully_paid"));
407 }
408}