1use std::collections::HashMap;
4use std::sync::Arc;
5
6use cdk::cdk_database::WalletDatabase as CdkWalletDatabase;
7
8use crate::error::FfiError;
9#[cfg(feature = "postgres")]
10use crate::postgres::WalletPostgresDatabase;
11use crate::sqlite::WalletSqliteDatabase;
12use crate::types::*;
13
14#[uniffi::export(with_foreign)]
17#[async_trait::async_trait]
18pub trait WalletDatabase: Send + Sync {
19 async fn add_mint(
22 &self,
23 mint_url: MintUrl,
24 mint_info: Option<MintInfo>,
25 ) -> Result<(), FfiError>;
26
27 async fn remove_mint(&self, mint_url: MintUrl) -> Result<(), FfiError>;
29
30 async fn get_mint(&self, mint_url: MintUrl) -> Result<Option<MintInfo>, FfiError>;
32
33 async fn get_mints(&self) -> Result<HashMap<MintUrl, Option<MintInfo>>, FfiError>;
35
36 async fn update_mint_url(
38 &self,
39 old_mint_url: MintUrl,
40 new_mint_url: MintUrl,
41 ) -> Result<(), FfiError>;
42
43 async fn add_mint_keysets(
46 &self,
47 mint_url: MintUrl,
48 keysets: Vec<KeySetInfo>,
49 ) -> Result<(), FfiError>;
50
51 async fn get_mint_keysets(
53 &self,
54 mint_url: MintUrl,
55 ) -> Result<Option<Vec<KeySetInfo>>, FfiError>;
56
57 async fn get_keyset_by_id(&self, keyset_id: Id) -> Result<Option<KeySetInfo>, FfiError>;
59
60 async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), FfiError>;
63
64 async fn get_mint_quote(&self, quote_id: String) -> Result<Option<MintQuote>, FfiError>;
66
67 async fn get_mint_quotes(&self) -> Result<Vec<MintQuote>, FfiError>;
69
70 async fn remove_mint_quote(&self, quote_id: String) -> Result<(), FfiError>;
72
73 async fn add_melt_quote(&self, quote: MeltQuote) -> Result<(), FfiError>;
76
77 async fn get_melt_quote(&self, quote_id: String) -> Result<Option<MeltQuote>, FfiError>;
79
80 async fn get_melt_quotes(&self) -> Result<Vec<MeltQuote>, FfiError>;
82
83 async fn remove_melt_quote(&self, quote_id: String) -> Result<(), FfiError>;
85
86 async fn add_keys(&self, keyset: KeySet) -> Result<(), FfiError>;
89
90 async fn get_keys(&self, id: Id) -> Result<Option<Keys>, FfiError>;
92
93 async fn remove_keys(&self, id: Id) -> Result<(), FfiError>;
95
96 async fn update_proofs(
99 &self,
100 added: Vec<ProofInfo>,
101 removed_ys: Vec<PublicKey>,
102 ) -> Result<(), FfiError>;
103
104 async fn get_proofs(
106 &self,
107 mint_url: Option<MintUrl>,
108 unit: Option<CurrencyUnit>,
109 state: Option<Vec<ProofState>>,
110 spending_conditions: Option<Vec<SpendingConditions>>,
111 ) -> Result<Vec<ProofInfo>, FfiError>;
112
113 async fn get_balance(
115 &self,
116 mint_url: Option<MintUrl>,
117 unit: Option<CurrencyUnit>,
118 state: Option<Vec<ProofState>>,
119 ) -> Result<u64, FfiError>;
120
121 async fn update_proofs_state(
123 &self,
124 ys: Vec<PublicKey>,
125 state: ProofState,
126 ) -> Result<(), FfiError>;
127
128 async fn increment_keyset_counter(&self, keyset_id: Id, count: u32) -> Result<u32, FfiError>;
131
132 async fn add_transaction(&self, transaction: Transaction) -> Result<(), FfiError>;
135
136 async fn get_transaction(
138 &self,
139 transaction_id: TransactionId,
140 ) -> Result<Option<Transaction>, FfiError>;
141
142 async fn list_transactions(
144 &self,
145 mint_url: Option<MintUrl>,
146 direction: Option<TransactionDirection>,
147 unit: Option<CurrencyUnit>,
148 ) -> Result<Vec<Transaction>, FfiError>;
149
150 async fn remove_transaction(&self, transaction_id: TransactionId) -> Result<(), FfiError>;
152}
153
154struct WalletDatabaseBridge {
157 ffi_db: Arc<dyn WalletDatabase>,
158}
159
160impl WalletDatabaseBridge {
161 fn new(ffi_db: Arc<dyn WalletDatabase>) -> Self {
162 Self { ffi_db }
163 }
164}
165
166impl std::fmt::Debug for WalletDatabaseBridge {
167 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168 write!(f, "WalletDatabaseBridge")
169 }
170}
171
172#[async_trait::async_trait]
173impl CdkWalletDatabase for WalletDatabaseBridge {
174 type Err = cdk::cdk_database::Error;
175
176 async fn add_mint(
178 &self,
179 mint_url: cdk::mint_url::MintUrl,
180 mint_info: Option<cdk::nuts::MintInfo>,
181 ) -> Result<(), Self::Err> {
182 let ffi_mint_url = mint_url.into();
183 let ffi_mint_info = mint_info.map(Into::into);
184 self.ffi_db
185 .add_mint(ffi_mint_url, ffi_mint_info)
186 .await
187 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
188 }
189
190 async fn remove_mint(&self, mint_url: cdk::mint_url::MintUrl) -> Result<(), Self::Err> {
191 let ffi_mint_url = mint_url.into();
192 self.ffi_db
193 .remove_mint(ffi_mint_url)
194 .await
195 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
196 }
197
198 async fn get_mint(
199 &self,
200 mint_url: cdk::mint_url::MintUrl,
201 ) -> Result<Option<cdk::nuts::MintInfo>, Self::Err> {
202 let ffi_mint_url = mint_url.into();
203 let result = self
204 .ffi_db
205 .get_mint(ffi_mint_url)
206 .await
207 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
208 Ok(result.map(Into::into))
209 }
210
211 async fn get_mints(
212 &self,
213 ) -> Result<HashMap<cdk::mint_url::MintUrl, Option<cdk::nuts::MintInfo>>, Self::Err> {
214 let result = self
215 .ffi_db
216 .get_mints()
217 .await
218 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
219
220 let mut cdk_result = HashMap::new();
221 for (ffi_mint_url, mint_info_opt) in result {
222 let cdk_url = ffi_mint_url
223 .try_into()
224 .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))?;
225 cdk_result.insert(cdk_url, mint_info_opt.map(Into::into));
226 }
227 Ok(cdk_result)
228 }
229
230 async fn update_mint_url(
231 &self,
232 old_mint_url: cdk::mint_url::MintUrl,
233 new_mint_url: cdk::mint_url::MintUrl,
234 ) -> Result<(), Self::Err> {
235 let ffi_old_mint_url = old_mint_url.into();
236 let ffi_new_mint_url = new_mint_url.into();
237 self.ffi_db
238 .update_mint_url(ffi_old_mint_url, ffi_new_mint_url)
239 .await
240 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
241 }
242
243 async fn add_mint_keysets(
245 &self,
246 mint_url: cdk::mint_url::MintUrl,
247 keysets: Vec<cdk::nuts::KeySetInfo>,
248 ) -> Result<(), Self::Err> {
249 let ffi_mint_url = mint_url.into();
250 let ffi_keysets: Vec<KeySetInfo> = keysets.into_iter().map(Into::into).collect();
251
252 self.ffi_db
253 .add_mint_keysets(ffi_mint_url, ffi_keysets)
254 .await
255 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
256 }
257
258 async fn get_mint_keysets(
259 &self,
260 mint_url: cdk::mint_url::MintUrl,
261 ) -> Result<Option<Vec<cdk::nuts::KeySetInfo>>, Self::Err> {
262 let ffi_mint_url = mint_url.into();
263 let result = self
264 .ffi_db
265 .get_mint_keysets(ffi_mint_url)
266 .await
267 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
268 Ok(result.map(|keysets| keysets.into_iter().map(Into::into).collect()))
269 }
270
271 async fn get_keyset_by_id(
272 &self,
273 keyset_id: &cdk::nuts::Id,
274 ) -> Result<Option<cdk::nuts::KeySetInfo>, Self::Err> {
275 let ffi_id = (*keyset_id).into();
276 let result = self
277 .ffi_db
278 .get_keyset_by_id(ffi_id)
279 .await
280 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
281 Ok(result.map(Into::into))
282 }
283
284 async fn add_mint_quote(&self, quote: cdk::wallet::MintQuote) -> Result<(), Self::Err> {
286 let ffi_quote = quote.into();
287 self.ffi_db
288 .add_mint_quote(ffi_quote)
289 .await
290 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
291 }
292
293 async fn get_mint_quote(
294 &self,
295 quote_id: &str,
296 ) -> Result<Option<cdk::wallet::MintQuote>, Self::Err> {
297 let result = self
298 .ffi_db
299 .get_mint_quote(quote_id.to_string())
300 .await
301 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
302 Ok(result
303 .map(|q| {
304 q.try_into()
305 .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))
306 })
307 .transpose()?)
308 }
309
310 async fn get_mint_quotes(&self) -> Result<Vec<cdk::wallet::MintQuote>, Self::Err> {
311 let result = self
312 .ffi_db
313 .get_mint_quotes()
314 .await
315 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
316 Ok(result
317 .into_iter()
318 .map(|q| {
319 q.try_into()
320 .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))
321 })
322 .collect::<Result<Vec<_>, _>>()?)
323 }
324
325 async fn remove_mint_quote(&self, quote_id: &str) -> Result<(), Self::Err> {
326 self.ffi_db
327 .remove_mint_quote(quote_id.to_string())
328 .await
329 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
330 }
331
332 async fn add_melt_quote(&self, quote: cdk::wallet::MeltQuote) -> Result<(), Self::Err> {
334 let ffi_quote = quote.into();
335 self.ffi_db
336 .add_melt_quote(ffi_quote)
337 .await
338 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
339 }
340
341 async fn get_melt_quote(
342 &self,
343 quote_id: &str,
344 ) -> Result<Option<cdk::wallet::MeltQuote>, Self::Err> {
345 let result = self
346 .ffi_db
347 .get_melt_quote(quote_id.to_string())
348 .await
349 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
350 Ok(result
351 .map(|q| {
352 q.try_into()
353 .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))
354 })
355 .transpose()?)
356 }
357
358 async fn get_melt_quotes(&self) -> Result<Vec<cdk::wallet::MeltQuote>, Self::Err> {
359 let result = self
360 .ffi_db
361 .get_melt_quotes()
362 .await
363 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
364 Ok(result
365 .into_iter()
366 .map(|q| {
367 q.try_into()
368 .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))
369 })
370 .collect::<Result<Vec<_>, _>>()?)
371 }
372
373 async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err> {
374 self.ffi_db
375 .remove_melt_quote(quote_id.to_string())
376 .await
377 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
378 }
379
380 async fn add_keys(&self, keyset: cdk::nuts::KeySet) -> Result<(), Self::Err> {
382 let ffi_keyset: KeySet = keyset.into();
383 self.ffi_db
384 .add_keys(ffi_keyset)
385 .await
386 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
387 }
388
389 async fn get_keys(&self, id: &cdk::nuts::Id) -> Result<Option<cdk::nuts::Keys>, Self::Err> {
390 let ffi_id: Id = (*id).into();
391 let result = self
392 .ffi_db
393 .get_keys(ffi_id)
394 .await
395 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
396
397 result
399 .map(|ffi_keys| {
400 ffi_keys
401 .try_into()
402 .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))
403 })
404 .transpose()
405 }
406
407 async fn remove_keys(&self, id: &cdk::nuts::Id) -> Result<(), Self::Err> {
408 let ffi_id = (*id).into();
409 self.ffi_db
410 .remove_keys(ffi_id)
411 .await
412 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
413 }
414
415 async fn update_proofs(
417 &self,
418 added: Vec<cdk::types::ProofInfo>,
419 removed_ys: Vec<cdk::nuts::PublicKey>,
420 ) -> Result<(), Self::Err> {
421 let ffi_added: Vec<ProofInfo> = added.into_iter().map(Into::into).collect();
422 let ffi_removed_ys: Vec<PublicKey> = removed_ys.into_iter().map(Into::into).collect();
423
424 self.ffi_db
425 .update_proofs(ffi_added, ffi_removed_ys)
426 .await
427 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
428 }
429
430 async fn get_proofs(
431 &self,
432 mint_url: Option<cdk::mint_url::MintUrl>,
433 unit: Option<cdk::nuts::CurrencyUnit>,
434 state: Option<Vec<cdk::nuts::State>>,
435 spending_conditions: Option<Vec<cdk::nuts::SpendingConditions>>,
436 ) -> Result<Vec<cdk::types::ProofInfo>, Self::Err> {
437 let ffi_mint_url = mint_url.map(Into::into);
438 let ffi_unit = unit.map(Into::into);
439 let ffi_state = state.map(|s| s.into_iter().map(Into::into).collect());
440 let ffi_spending_conditions =
441 spending_conditions.map(|sc| sc.into_iter().map(Into::into).collect());
442
443 let result = self
444 .ffi_db
445 .get_proofs(ffi_mint_url, ffi_unit, ffi_state, ffi_spending_conditions)
446 .await
447 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
448
449 let cdk_result: Result<Vec<cdk::types::ProofInfo>, cdk::cdk_database::Error> = result
451 .into_iter()
452 .map(|info| {
453 Ok(cdk::types::ProofInfo {
454 proof: info.proof.try_into().map_err(|e: FfiError| {
455 cdk::cdk_database::Error::Database(e.to_string().into())
456 })?,
457 y: info.y.try_into().map_err(|e: FfiError| {
458 cdk::cdk_database::Error::Database(e.to_string().into())
459 })?,
460 mint_url: info.mint_url.try_into().map_err(|e: FfiError| {
461 cdk::cdk_database::Error::Database(e.to_string().into())
462 })?,
463 state: info.state.into(),
464 spending_condition: info
465 .spending_condition
466 .map(|sc| sc.try_into())
467 .transpose()
468 .map_err(|e: FfiError| {
469 cdk::cdk_database::Error::Database(e.to_string().into())
470 })?,
471 unit: info.unit.into(),
472 })
473 })
474 .collect();
475
476 cdk_result
477 }
478
479 async fn get_balance(
480 &self,
481 mint_url: Option<cdk::mint_url::MintUrl>,
482 unit: Option<cdk::nuts::CurrencyUnit>,
483 state: Option<Vec<cdk::nuts::State>>,
484 ) -> Result<u64, Self::Err> {
485 let ffi_mint_url = mint_url.map(Into::into);
486 let ffi_unit = unit.map(Into::into);
487 let ffi_state = state.map(|s| s.into_iter().map(Into::into).collect());
488
489 self.ffi_db
490 .get_balance(ffi_mint_url, ffi_unit, ffi_state)
491 .await
492 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
493 }
494
495 async fn update_proofs_state(
496 &self,
497 ys: Vec<cdk::nuts::PublicKey>,
498 state: cdk::nuts::State,
499 ) -> Result<(), Self::Err> {
500 let ffi_ys: Vec<PublicKey> = ys.into_iter().map(Into::into).collect();
501 let ffi_state = state.into();
502
503 self.ffi_db
504 .update_proofs_state(ffi_ys, ffi_state)
505 .await
506 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
507 }
508
509 async fn increment_keyset_counter(
511 &self,
512 keyset_id: &cdk::nuts::Id,
513 count: u32,
514 ) -> Result<u32, Self::Err> {
515 let ffi_id = (*keyset_id).into();
516 self.ffi_db
517 .increment_keyset_counter(ffi_id, count)
518 .await
519 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
520 }
521
522 async fn add_transaction(
524 &self,
525 transaction: cdk::wallet::types::Transaction,
526 ) -> Result<(), Self::Err> {
527 let ffi_transaction = transaction.into();
528 self.ffi_db
529 .add_transaction(ffi_transaction)
530 .await
531 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
532 }
533
534 async fn get_transaction(
535 &self,
536 transaction_id: cdk::wallet::types::TransactionId,
537 ) -> Result<Option<cdk::wallet::types::Transaction>, Self::Err> {
538 let ffi_id = transaction_id.into();
539 let result = self
540 .ffi_db
541 .get_transaction(ffi_id)
542 .await
543 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
544
545 result
546 .map(|tx| tx.try_into())
547 .transpose()
548 .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))
549 }
550
551 async fn list_transactions(
552 &self,
553 mint_url: Option<cdk::mint_url::MintUrl>,
554 direction: Option<cdk::wallet::types::TransactionDirection>,
555 unit: Option<cdk::nuts::CurrencyUnit>,
556 ) -> Result<Vec<cdk::wallet::types::Transaction>, Self::Err> {
557 let ffi_mint_url = mint_url.map(Into::into);
558 let ffi_direction = direction.map(Into::into);
559 let ffi_unit = unit.map(Into::into);
560
561 let result = self
562 .ffi_db
563 .list_transactions(ffi_mint_url, ffi_direction, ffi_unit)
564 .await
565 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
566
567 result
568 .into_iter()
569 .map(|tx| tx.try_into())
570 .collect::<Result<Vec<_>, FfiError>>()
571 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
572 }
573
574 async fn remove_transaction(
575 &self,
576 transaction_id: cdk::wallet::types::TransactionId,
577 ) -> Result<(), Self::Err> {
578 let ffi_id = transaction_id.into();
579 self.ffi_db
580 .remove_transaction(ffi_id)
581 .await
582 .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
583 }
584}
585
586#[derive(uniffi::Enum)]
588pub enum WalletDbBackend {
589 Sqlite {
590 path: String,
591 },
592 #[cfg(feature = "postgres")]
593 Postgres {
594 url: String,
595 },
596}
597
598#[uniffi::export]
600pub fn create_wallet_db(backend: WalletDbBackend) -> Result<Arc<dyn WalletDatabase>, FfiError> {
601 match backend {
602 WalletDbBackend::Sqlite { path } => {
603 let sqlite = WalletSqliteDatabase::new(path)?;
604 Ok(sqlite as Arc<dyn WalletDatabase>)
605 }
606 #[cfg(feature = "postgres")]
607 WalletDbBackend::Postgres { url } => {
608 let pg = WalletPostgresDatabase::new(url)?;
609 Ok(pg as Arc<dyn WalletDatabase>)
610 }
611 }
612}
613
614pub fn create_cdk_database_from_ffi(
616 ffi_db: Arc<dyn WalletDatabase>,
617) -> Arc<dyn CdkWalletDatabase<Err = cdk::cdk_database::Error> + Send + Sync> {
618 Arc::new(WalletDatabaseBridge::new(ffi_db))
619}