Skip to main content

cdk_ffi/
database.rs

1//! FFI Database bindings
2
3use std::collections::HashMap;
4use std::sync::Arc;
5
6use cdk_common::database::WalletDatabase as CdkWalletDatabase;
7use cdk_common::wallet::WalletSaga;
8use cdk_sql_common::pool::DatabasePool;
9use cdk_sql_common::SQLWalletDatabase;
10
11use crate::error::FfiError;
12#[cfg(feature = "postgres")]
13use crate::postgres::WalletPostgresDatabase;
14use crate::sqlite::WalletSqliteDatabase;
15use crate::types::*;
16
17/// FFI-compatible wallet database trait with all read and write operations
18/// This trait mirrors the CDK WalletDatabase trait structure
19#[uniffi::export(with_foreign)]
20#[async_trait::async_trait]
21pub trait WalletDatabase: Send + Sync {
22    // ========== Read methods ==========
23
24    /// Get mint from storage
25    async fn get_mint(&self, mint_url: MintUrl) -> Result<Option<MintInfo>, FfiError>;
26
27    /// Get all mints from storage
28    async fn get_mints(&self) -> Result<HashMap<MintUrl, Option<MintInfo>>, FfiError>;
29
30    /// Get mint keysets for mint url
31    async fn get_mint_keysets(
32        &self,
33        mint_url: MintUrl,
34    ) -> Result<Option<Vec<KeySetInfo>>, FfiError>;
35
36    /// Get mint keyset by id
37    async fn get_keyset_by_id(&self, keyset_id: Id) -> Result<Option<KeySetInfo>, FfiError>;
38
39    /// Get mint quote from storage
40    async fn get_mint_quote(&self, quote_id: String) -> Result<Option<MintQuote>, FfiError>;
41
42    /// Get mint quotes from storage
43    async fn get_mint_quotes(&self) -> Result<Vec<MintQuote>, FfiError>;
44
45    /// Get unissued mint quotes from storage
46    /// Returns bolt11 quotes where nothing has been issued yet (amount_issued = 0) and all bolt12 quotes.
47    async fn get_unissued_mint_quotes(&self) -> Result<Vec<MintQuote>, FfiError>;
48
49    /// Get melt quote from storage
50    async fn get_melt_quote(&self, quote_id: String) -> Result<Option<MeltQuote>, FfiError>;
51
52    /// Get melt quotes from storage
53    async fn get_melt_quotes(&self) -> Result<Vec<MeltQuote>, FfiError>;
54
55    /// Get Keys from storage
56    async fn get_keys(&self, id: Id) -> Result<Option<Keys>, FfiError>;
57
58    /// Get proofs from storage
59    async fn get_proofs(
60        &self,
61        mint_url: Option<MintUrl>,
62        unit: Option<CurrencyUnit>,
63        state: Option<Vec<ProofState>>,
64        spending_conditions: Option<Vec<SpendingConditions>>,
65    ) -> Result<Vec<ProofInfo>, FfiError>;
66
67    /// Get proofs by Y values
68    async fn get_proofs_by_ys(&self, ys: Vec<PublicKey>) -> Result<Vec<ProofInfo>, FfiError>;
69
70    /// Get balance efficiently using SQL aggregation
71    async fn get_balance(
72        &self,
73        mint_url: Option<MintUrl>,
74        unit: Option<CurrencyUnit>,
75        state: Option<Vec<ProofState>>,
76    ) -> Result<u64, FfiError>;
77
78    /// Get transaction from storage
79    async fn get_transaction(
80        &self,
81        transaction_id: TransactionId,
82    ) -> Result<Option<Transaction>, FfiError>;
83
84    /// List transactions from storage
85    async fn list_transactions(
86        &self,
87        mint_url: Option<MintUrl>,
88        direction: Option<TransactionDirection>,
89        unit: Option<CurrencyUnit>,
90    ) -> Result<Vec<Transaction>, FfiError>;
91
92    /// Read a value from the KV store
93    async fn kv_read(
94        &self,
95        primary_namespace: String,
96        secondary_namespace: String,
97        key: String,
98    ) -> Result<Option<Vec<u8>>, FfiError>;
99
100    /// List keys in a namespace
101    async fn kv_list(
102        &self,
103        primary_namespace: String,
104        secondary_namespace: String,
105    ) -> Result<Vec<String>, FfiError>;
106
107    /// Write a value to the KV store
108    async fn kv_write(
109        &self,
110        primary_namespace: String,
111        secondary_namespace: String,
112        key: String,
113        value: Vec<u8>,
114    ) -> Result<(), FfiError>;
115
116    /// Remove a value from the KV store
117    async fn kv_remove(
118        &self,
119        primary_namespace: String,
120        secondary_namespace: String,
121        key: String,
122    ) -> Result<(), FfiError>;
123
124    // ========== Write methods ==========
125
126    /// Update the proofs in storage by adding new proofs or removing proofs by their Y value
127    async fn update_proofs(
128        &self,
129        added: Vec<ProofInfo>,
130        removed_ys: Vec<PublicKey>,
131    ) -> Result<(), FfiError>;
132
133    /// Update proofs state in storage
134    async fn update_proofs_state(
135        &self,
136        ys: Vec<PublicKey>,
137        state: ProofState,
138    ) -> Result<(), FfiError>;
139
140    /// Add transaction to storage
141    async fn add_transaction(&self, transaction: Transaction) -> Result<(), FfiError>;
142
143    /// Remove transaction from storage
144    async fn remove_transaction(&self, transaction_id: TransactionId) -> Result<(), FfiError>;
145
146    /// Update mint url
147    async fn update_mint_url(
148        &self,
149        old_mint_url: MintUrl,
150        new_mint_url: MintUrl,
151    ) -> Result<(), FfiError>;
152
153    /// Atomically increment Keyset counter and return new value
154    async fn increment_keyset_counter(&self, keyset_id: Id, count: u32) -> Result<u32, FfiError>;
155
156    /// Add Mint to storage
157    async fn add_mint(
158        &self,
159        mint_url: MintUrl,
160        mint_info: Option<MintInfo>,
161    ) -> Result<(), FfiError>;
162
163    /// Remove Mint from storage
164    async fn remove_mint(&self, mint_url: MintUrl) -> Result<(), FfiError>;
165
166    /// Add mint keyset to storage
167    async fn add_mint_keysets(
168        &self,
169        mint_url: MintUrl,
170        keysets: Vec<KeySetInfo>,
171    ) -> Result<(), FfiError>;
172
173    /// Add mint quote to storage
174    async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), FfiError>;
175
176    /// Remove mint quote from storage
177    async fn remove_mint_quote(&self, quote_id: String) -> Result<(), FfiError>;
178
179    /// Add melt quote to storage
180    async fn add_melt_quote(&self, quote: MeltQuote) -> Result<(), FfiError>;
181
182    /// Remove melt quote from storage
183    async fn remove_melt_quote(&self, quote_id: String) -> Result<(), FfiError>;
184
185    /// Add Keys to storage
186    async fn add_keys(&self, keyset: KeySet) -> Result<(), FfiError>;
187
188    /// Remove Keys from storage
189    async fn remove_keys(&self, id: Id) -> Result<(), FfiError>;
190
191    // ========== Saga management methods ==========
192    // WalletSaga is serialized as JSON for FFI compatibility
193
194    /// Add a wallet saga to storage (JSON serialized)
195    async fn add_saga(&self, saga_json: String) -> Result<(), FfiError>;
196
197    /// Get a wallet saga by ID (returns JSON serialized)
198    async fn get_saga(&self, id: String) -> Result<Option<String>, FfiError>;
199
200    /// Update a wallet saga (JSON serialized) with optimistic locking.
201    ///
202    /// Returns `true` if the update succeeded (version matched),
203    /// `false` if another instance modified the saga first.
204    async fn update_saga(&self, saga_json: String) -> Result<bool, FfiError>;
205
206    /// Delete a wallet saga
207    async fn delete_saga(&self, id: String) -> Result<(), FfiError>;
208
209    /// Get all incomplete sagas (returns JSON serialized sagas)
210    async fn get_incomplete_sagas(&self) -> Result<Vec<String>, FfiError>;
211
212    // ========== Proof reservation methods ==========
213
214    /// Reserve proofs for an operation
215    async fn reserve_proofs(
216        &self,
217        ys: Vec<PublicKey>,
218        operation_id: String,
219    ) -> Result<(), FfiError>;
220
221    /// Release proofs reserved by an operation
222    async fn release_proofs(&self, operation_id: String) -> Result<(), FfiError>;
223
224    /// Get proofs reserved by an operation
225    async fn get_reserved_proofs(&self, operation_id: String) -> Result<Vec<ProofInfo>, FfiError>;
226
227    // ========== Quote reservation methods ==========
228
229    /// Reserve a melt quote for an operation
230    async fn reserve_melt_quote(
231        &self,
232        quote_id: String,
233        operation_id: String,
234    ) -> Result<(), FfiError>;
235
236    /// Release a melt quote reserved by an operation
237    async fn release_melt_quote(&self, operation_id: String) -> Result<(), FfiError>;
238
239    /// Reserve a mint quote for an operation
240    async fn reserve_mint_quote(
241        &self,
242        quote_id: String,
243        operation_id: String,
244    ) -> Result<(), FfiError>;
245
246    /// Release a mint quote reserved by an operation
247    async fn release_mint_quote(&self, operation_id: String) -> Result<(), FfiError>;
248}
249
250/// Internal bridge trait to convert from the FFI trait to the CDK database trait
251/// This allows us to bridge between the UniFFI trait and the CDK's internal database trait
252struct WalletDatabaseBridge {
253    ffi_db: Arc<dyn WalletDatabase>,
254}
255
256impl WalletDatabaseBridge {
257    fn new(ffi_db: Arc<dyn WalletDatabase>) -> Self {
258        Self { ffi_db }
259    }
260}
261
262impl std::fmt::Debug for WalletDatabaseBridge {
263    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
264        write!(f, "WalletDatabaseBridge")
265    }
266}
267
268#[async_trait::async_trait]
269impl CdkWalletDatabase<cdk::cdk_database::Error> for WalletDatabaseBridge {
270    async fn kv_read(
271        &self,
272        primary_namespace: &str,
273        secondary_namespace: &str,
274        key: &str,
275    ) -> Result<Option<Vec<u8>>, cdk::cdk_database::Error> {
276        self.ffi_db
277            .kv_read(
278                primary_namespace.to_string(),
279                secondary_namespace.to_string(),
280                key.to_string(),
281            )
282            .await
283            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
284    }
285
286    async fn kv_list(
287        &self,
288        primary_namespace: &str,
289        secondary_namespace: &str,
290    ) -> Result<Vec<String>, cdk::cdk_database::Error> {
291        self.ffi_db
292            .kv_list(
293                primary_namespace.to_string(),
294                secondary_namespace.to_string(),
295            )
296            .await
297            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
298    }
299
300    // Mint Management
301    async fn get_mint(
302        &self,
303        mint_url: cdk::mint_url::MintUrl,
304    ) -> Result<Option<cdk::nuts::MintInfo>, cdk::cdk_database::Error> {
305        let ffi_mint_url = mint_url.into();
306        let result = self
307            .ffi_db
308            .get_mint(ffi_mint_url)
309            .await
310            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
311        Ok(result.map(Into::into))
312    }
313
314    async fn get_mints(
315        &self,
316    ) -> Result<
317        HashMap<cdk::mint_url::MintUrl, Option<cdk::nuts::MintInfo>>,
318        cdk::cdk_database::Error,
319    > {
320        let result = self
321            .ffi_db
322            .get_mints()
323            .await
324            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
325
326        let mut cdk_result = HashMap::new();
327        for (ffi_mint_url, mint_info_opt) in result {
328            let cdk_url = ffi_mint_url
329                .try_into()
330                .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))?;
331            cdk_result.insert(cdk_url, mint_info_opt.map(Into::into));
332        }
333        Ok(cdk_result)
334    }
335
336    // Keyset Management
337    async fn get_mint_keysets(
338        &self,
339        mint_url: cdk::mint_url::MintUrl,
340    ) -> Result<Option<Vec<cdk::nuts::KeySetInfo>>, cdk::cdk_database::Error> {
341        let ffi_mint_url = mint_url.into();
342        let result = self
343            .ffi_db
344            .get_mint_keysets(ffi_mint_url)
345            .await
346            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
347        Ok(result.map(|keysets| keysets.into_iter().map(Into::into).collect()))
348    }
349
350    async fn get_keyset_by_id(
351        &self,
352        keyset_id: &cdk::nuts::Id,
353    ) -> Result<Option<cdk::nuts::KeySetInfo>, cdk::cdk_database::Error> {
354        let ffi_id = (*keyset_id).into();
355        let result = self
356            .ffi_db
357            .get_keyset_by_id(ffi_id)
358            .await
359            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
360        Ok(result.map(Into::into))
361    }
362
363    // Mint Quote Management
364    async fn get_mint_quote(
365        &self,
366        quote_id: &str,
367    ) -> Result<Option<cdk::wallet::MintQuote>, cdk::cdk_database::Error> {
368        let result = self
369            .ffi_db
370            .get_mint_quote(quote_id.to_string())
371            .await
372            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
373        Ok(result
374            .map(|q| {
375                q.try_into()
376                    .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))
377            })
378            .transpose()?)
379    }
380
381    async fn get_mint_quotes(
382        &self,
383    ) -> Result<Vec<cdk::wallet::MintQuote>, cdk::cdk_database::Error> {
384        let result = self
385            .ffi_db
386            .get_mint_quotes()
387            .await
388            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
389        Ok(result
390            .into_iter()
391            .map(|q| {
392                q.try_into()
393                    .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))
394            })
395            .collect::<Result<Vec<_>, _>>()?)
396    }
397
398    async fn get_unissued_mint_quotes(
399        &self,
400    ) -> Result<Vec<cdk::wallet::MintQuote>, cdk::cdk_database::Error> {
401        let result = self
402            .ffi_db
403            .get_unissued_mint_quotes()
404            .await
405            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
406        Ok(result
407            .into_iter()
408            .map(|q| {
409                q.try_into()
410                    .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))
411            })
412            .collect::<Result<Vec<_>, _>>()?)
413    }
414
415    // Melt Quote Management
416    async fn get_melt_quote(
417        &self,
418        quote_id: &str,
419    ) -> Result<Option<cdk::wallet::MeltQuote>, cdk::cdk_database::Error> {
420        let result = self
421            .ffi_db
422            .get_melt_quote(quote_id.to_string())
423            .await
424            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
425        Ok(result
426            .map(|q| {
427                q.try_into()
428                    .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))
429            })
430            .transpose()?)
431    }
432
433    async fn get_melt_quotes(
434        &self,
435    ) -> Result<Vec<cdk::wallet::MeltQuote>, cdk::cdk_database::Error> {
436        let result = self
437            .ffi_db
438            .get_melt_quotes()
439            .await
440            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
441        Ok(result
442            .into_iter()
443            .map(|q| {
444                q.try_into()
445                    .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))
446            })
447            .collect::<Result<Vec<_>, _>>()?)
448    }
449
450    // Keys Management
451    async fn get_keys(
452        &self,
453        id: &cdk::nuts::Id,
454    ) -> Result<Option<cdk::nuts::Keys>, cdk::cdk_database::Error> {
455        let ffi_id: Id = (*id).into();
456        let result = self
457            .ffi_db
458            .get_keys(ffi_id)
459            .await
460            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
461
462        // Convert FFI Keys back to CDK Keys using TryFrom
463        result
464            .map(|ffi_keys| {
465                ffi_keys
466                    .try_into()
467                    .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))
468            })
469            .transpose()
470    }
471
472    // Proof Management
473    async fn get_proofs(
474        &self,
475        mint_url: Option<cdk::mint_url::MintUrl>,
476        unit: Option<cdk::nuts::CurrencyUnit>,
477        state: Option<Vec<cdk::nuts::State>>,
478        spending_conditions: Option<Vec<cdk::nuts::SpendingConditions>>,
479    ) -> Result<Vec<cdk::types::ProofInfo>, cdk::cdk_database::Error> {
480        let ffi_mint_url = mint_url.map(Into::into);
481        let ffi_unit = unit.map(Into::into);
482        let ffi_state = state.map(|s| s.into_iter().map(Into::into).collect());
483        let ffi_spending_conditions =
484            spending_conditions.map(|sc| sc.into_iter().map(Into::into).collect());
485
486        let result = self
487            .ffi_db
488            .get_proofs(ffi_mint_url, ffi_unit, ffi_state, ffi_spending_conditions)
489            .await
490            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
491
492        // Convert back to CDK ProofInfo
493        let cdk_result: Result<Vec<cdk::types::ProofInfo>, cdk::cdk_database::Error> = result
494            .into_iter()
495            .map(|info| {
496                Ok(cdk::types::ProofInfo {
497                    proof: info.proof.try_into().map_err(|e: FfiError| {
498                        cdk::cdk_database::Error::Database(e.to_string().into())
499                    })?,
500                    y: info.y.try_into().map_err(|e: FfiError| {
501                        cdk::cdk_database::Error::Database(e.to_string().into())
502                    })?,
503                    mint_url: info.mint_url.try_into().map_err(|e: FfiError| {
504                        cdk::cdk_database::Error::Database(e.to_string().into())
505                    })?,
506                    state: info.state.into(),
507                    spending_condition: info
508                        .spending_condition
509                        .map(|sc| sc.try_into())
510                        .transpose()
511                        .map_err(|e: FfiError| {
512                            cdk::cdk_database::Error::Database(e.to_string().into())
513                        })?,
514                    unit: info.unit.into(),
515                    used_by_operation: info
516                        .used_by_operation
517                        .map(|id| uuid::Uuid::parse_str(&id))
518                        .transpose()
519                        .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?,
520                    created_by_operation: info
521                        .created_by_operation
522                        .map(|id| uuid::Uuid::parse_str(&id))
523                        .transpose()
524                        .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?,
525                })
526            })
527            .collect();
528
529        cdk_result
530    }
531
532    async fn get_proofs_by_ys(
533        &self,
534        ys: Vec<cdk::nuts::PublicKey>,
535    ) -> Result<Vec<cdk::types::ProofInfo>, cdk::cdk_database::Error> {
536        let ffi_ys: Vec<PublicKey> = ys.into_iter().map(Into::into).collect();
537
538        let result = self
539            .ffi_db
540            .get_proofs_by_ys(ffi_ys)
541            .await
542            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
543
544        // Convert back to CDK ProofInfo
545        let cdk_result: Result<Vec<cdk::types::ProofInfo>, cdk::cdk_database::Error> = result
546            .into_iter()
547            .map(|info| {
548                Ok(cdk::types::ProofInfo {
549                    proof: info.proof.try_into().map_err(|e: FfiError| {
550                        cdk::cdk_database::Error::Database(e.to_string().into())
551                    })?,
552                    y: info.y.try_into().map_err(|e: FfiError| {
553                        cdk::cdk_database::Error::Database(e.to_string().into())
554                    })?,
555                    mint_url: info.mint_url.try_into().map_err(|e: FfiError| {
556                        cdk::cdk_database::Error::Database(e.to_string().into())
557                    })?,
558                    state: info.state.into(),
559                    spending_condition: info
560                        .spending_condition
561                        .map(|sc| sc.try_into())
562                        .transpose()
563                        .map_err(|e: FfiError| {
564                            cdk::cdk_database::Error::Database(e.to_string().into())
565                        })?,
566                    unit: info.unit.into(),
567                    used_by_operation: info
568                        .used_by_operation
569                        .map(|id| uuid::Uuid::parse_str(&id))
570                        .transpose()
571                        .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?,
572                    created_by_operation: info
573                        .created_by_operation
574                        .map(|id| uuid::Uuid::parse_str(&id))
575                        .transpose()
576                        .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?,
577                })
578            })
579            .collect();
580
581        cdk_result
582    }
583
584    async fn get_balance(
585        &self,
586        mint_url: Option<cdk::mint_url::MintUrl>,
587        unit: Option<cdk::nuts::CurrencyUnit>,
588        state: Option<Vec<cdk::nuts::State>>,
589    ) -> Result<u64, cdk::cdk_database::Error> {
590        let ffi_mint_url = mint_url.map(Into::into);
591        let ffi_unit = unit.map(Into::into);
592        let ffi_state = state.map(|s| s.into_iter().map(Into::into).collect());
593
594        self.ffi_db
595            .get_balance(ffi_mint_url, ffi_unit, ffi_state)
596            .await
597            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
598    }
599
600    // Transaction Management
601    async fn get_transaction(
602        &self,
603        transaction_id: cdk::wallet::types::TransactionId,
604    ) -> Result<Option<cdk::wallet::types::Transaction>, cdk::cdk_database::Error> {
605        let ffi_id = transaction_id.into();
606        let result = self
607            .ffi_db
608            .get_transaction(ffi_id)
609            .await
610            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
611
612        result
613            .map(|tx| tx.try_into())
614            .transpose()
615            .map_err(|e: FfiError| cdk::cdk_database::Error::Database(e.to_string().into()))
616    }
617
618    async fn list_transactions(
619        &self,
620        mint_url: Option<cdk::mint_url::MintUrl>,
621        direction: Option<cdk::wallet::types::TransactionDirection>,
622        unit: Option<cdk::nuts::CurrencyUnit>,
623    ) -> Result<Vec<cdk::wallet::types::Transaction>, cdk::cdk_database::Error> {
624        let ffi_mint_url = mint_url.map(Into::into);
625        let ffi_direction = direction.map(Into::into);
626        let ffi_unit = unit.map(Into::into);
627
628        let result = self
629            .ffi_db
630            .list_transactions(ffi_mint_url, ffi_direction, ffi_unit)
631            .await
632            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
633
634        result
635            .into_iter()
636            .map(|tx| tx.try_into())
637            .collect::<Result<Vec<_>, FfiError>>()
638            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
639    }
640
641    // Write methods (non-transactional)
642
643    async fn update_proofs(
644        &self,
645        added: Vec<cdk::types::ProofInfo>,
646        removed_ys: Vec<cdk::nuts::PublicKey>,
647    ) -> Result<(), cdk::cdk_database::Error> {
648        let ffi_added: Vec<ProofInfo> = added.into_iter().map(Into::into).collect();
649        let ffi_removed_ys: Vec<PublicKey> = removed_ys.into_iter().map(Into::into).collect();
650        self.ffi_db
651            .update_proofs(ffi_added, ffi_removed_ys)
652            .await
653            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
654    }
655
656    async fn update_proofs_state(
657        &self,
658        ys: Vec<cdk::nuts::PublicKey>,
659        state: cdk::nuts::State,
660    ) -> Result<(), cdk::cdk_database::Error> {
661        let ffi_ys: Vec<PublicKey> = ys.into_iter().map(Into::into).collect();
662        let ffi_state = state.into();
663        self.ffi_db
664            .update_proofs_state(ffi_ys, ffi_state)
665            .await
666            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
667    }
668
669    async fn add_transaction(
670        &self,
671        transaction: cdk::wallet::types::Transaction,
672    ) -> Result<(), cdk::cdk_database::Error> {
673        let ffi_transaction = transaction.into();
674        self.ffi_db
675            .add_transaction(ffi_transaction)
676            .await
677            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
678    }
679
680    async fn update_mint_url(
681        &self,
682        old_mint_url: cdk::mint_url::MintUrl,
683        new_mint_url: cdk::mint_url::MintUrl,
684    ) -> Result<(), cdk::cdk_database::Error> {
685        let ffi_old = old_mint_url.into();
686        let ffi_new = new_mint_url.into();
687        self.ffi_db
688            .update_mint_url(ffi_old, ffi_new)
689            .await
690            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
691    }
692
693    async fn increment_keyset_counter(
694        &self,
695        keyset_id: &cdk::nuts::Id,
696        count: u32,
697    ) -> Result<u32, cdk::cdk_database::Error> {
698        let ffi_id = (*keyset_id).into();
699        self.ffi_db
700            .increment_keyset_counter(ffi_id, count)
701            .await
702            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
703    }
704
705    async fn add_mint(
706        &self,
707        mint_url: cdk::mint_url::MintUrl,
708        mint_info: Option<cdk::nuts::MintInfo>,
709    ) -> Result<(), cdk::cdk_database::Error> {
710        let ffi_mint_url = mint_url.into();
711        let ffi_mint_info = mint_info.map(Into::into);
712        self.ffi_db
713            .add_mint(ffi_mint_url, ffi_mint_info)
714            .await
715            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
716    }
717
718    async fn remove_mint(
719        &self,
720        mint_url: cdk::mint_url::MintUrl,
721    ) -> Result<(), cdk::cdk_database::Error> {
722        let ffi_mint_url = mint_url.into();
723        self.ffi_db
724            .remove_mint(ffi_mint_url)
725            .await
726            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
727    }
728
729    async fn add_mint_keysets(
730        &self,
731        mint_url: cdk::mint_url::MintUrl,
732        keysets: Vec<cdk::nuts::KeySetInfo>,
733    ) -> Result<(), cdk::cdk_database::Error> {
734        let ffi_mint_url = mint_url.into();
735        let ffi_keysets: Vec<KeySetInfo> = keysets.into_iter().map(Into::into).collect();
736        self.ffi_db
737            .add_mint_keysets(ffi_mint_url, ffi_keysets)
738            .await
739            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
740    }
741
742    async fn add_mint_quote(
743        &self,
744        quote: cdk::wallet::MintQuote,
745    ) -> Result<(), cdk::cdk_database::Error> {
746        let ffi_quote = quote.into();
747        self.ffi_db
748            .add_mint_quote(ffi_quote)
749            .await
750            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
751    }
752
753    async fn remove_mint_quote(&self, quote_id: &str) -> Result<(), cdk::cdk_database::Error> {
754        self.ffi_db
755            .remove_mint_quote(quote_id.to_string())
756            .await
757            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
758    }
759
760    async fn add_melt_quote(
761        &self,
762        quote: cdk::wallet::MeltQuote,
763    ) -> Result<(), cdk::cdk_database::Error> {
764        let ffi_quote = quote.into();
765        self.ffi_db
766            .add_melt_quote(ffi_quote)
767            .await
768            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
769    }
770
771    async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), cdk::cdk_database::Error> {
772        self.ffi_db
773            .remove_melt_quote(quote_id.to_string())
774            .await
775            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
776    }
777
778    async fn add_keys(&self, keyset: cdk::nuts::KeySet) -> Result<(), cdk::cdk_database::Error> {
779        let ffi_keyset: KeySet = keyset.into();
780        self.ffi_db
781            .add_keys(ffi_keyset)
782            .await
783            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
784    }
785
786    async fn remove_keys(&self, id: &cdk::nuts::Id) -> Result<(), cdk::cdk_database::Error> {
787        let ffi_id = (*id).into();
788        self.ffi_db
789            .remove_keys(ffi_id)
790            .await
791            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
792    }
793
794    async fn remove_transaction(
795        &self,
796        transaction_id: cdk::wallet::types::TransactionId,
797    ) -> Result<(), cdk::cdk_database::Error> {
798        let ffi_id = transaction_id.into();
799        self.ffi_db
800            .remove_transaction(ffi_id)
801            .await
802            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
803    }
804
805    async fn add_saga(&self, saga: WalletSaga) -> Result<(), cdk::cdk_database::Error> {
806        let json = serde_json::to_string(&saga)
807            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
808        self.ffi_db
809            .add_saga(json)
810            .await
811            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
812    }
813
814    async fn get_saga(
815        &self,
816        id: &uuid::Uuid,
817    ) -> Result<Option<WalletSaga>, cdk::cdk_database::Error> {
818        let json_opt = self
819            .ffi_db
820            .get_saga(id.to_string())
821            .await
822            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
823
824        match json_opt {
825            Some(json) => {
826                let saga: WalletSaga = serde_json::from_str(&json)
827                    .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
828                Ok(Some(saga))
829            }
830            None => Ok(None),
831        }
832    }
833
834    async fn update_saga(&self, saga: WalletSaga) -> Result<bool, cdk::cdk_database::Error> {
835        let json = serde_json::to_string(&saga)
836            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
837        self.ffi_db
838            .update_saga(json)
839            .await
840            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
841    }
842
843    async fn delete_saga(&self, id: &uuid::Uuid) -> Result<(), cdk::cdk_database::Error> {
844        self.ffi_db
845            .delete_saga(id.to_string())
846            .await
847            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
848    }
849
850    async fn get_incomplete_sagas(&self) -> Result<Vec<WalletSaga>, cdk::cdk_database::Error> {
851        let json_vec = self
852            .ffi_db
853            .get_incomplete_sagas()
854            .await
855            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
856
857        json_vec
858            .into_iter()
859            .map(|json| {
860                serde_json::from_str(&json)
861                    .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
862            })
863            .collect()
864    }
865
866    async fn reserve_proofs(
867        &self,
868        ys: Vec<cdk::nuts::PublicKey>,
869        operation_id: &uuid::Uuid,
870    ) -> Result<(), cdk::cdk_database::Error> {
871        let ffi_ys: Vec<PublicKey> = ys.into_iter().map(Into::into).collect();
872        self.ffi_db
873            .reserve_proofs(ffi_ys, operation_id.to_string())
874            .await
875            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
876    }
877
878    async fn release_proofs(
879        &self,
880        operation_id: &uuid::Uuid,
881    ) -> Result<(), cdk::cdk_database::Error> {
882        self.ffi_db
883            .release_proofs(operation_id.to_string())
884            .await
885            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
886    }
887
888    async fn get_reserved_proofs(
889        &self,
890        operation_id: &uuid::Uuid,
891    ) -> Result<Vec<cdk::types::ProofInfo>, cdk::cdk_database::Error> {
892        let result = self
893            .ffi_db
894            .get_reserved_proofs(operation_id.to_string())
895            .await
896            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
897
898        result
899            .into_iter()
900            .map(|info| {
901                Ok(cdk::types::ProofInfo {
902                    proof: info.proof.try_into().map_err(|e: FfiError| {
903                        cdk::cdk_database::Error::Database(e.to_string().into())
904                    })?,
905                    y: info.y.try_into().map_err(|e: FfiError| {
906                        cdk::cdk_database::Error::Database(e.to_string().into())
907                    })?,
908                    mint_url: info.mint_url.try_into().map_err(|e: FfiError| {
909                        cdk::cdk_database::Error::Database(e.to_string().into())
910                    })?,
911                    state: info.state.into(),
912                    spending_condition: info
913                        .spending_condition
914                        .map(|sc| sc.try_into())
915                        .transpose()
916                        .map_err(|e: FfiError| {
917                            cdk::cdk_database::Error::Database(e.to_string().into())
918                        })?,
919                    unit: info.unit.into(),
920                    used_by_operation: info
921                        .used_by_operation
922                        .map(|id| uuid::Uuid::parse_str(&id))
923                        .transpose()
924                        .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?,
925                    created_by_operation: info
926                        .created_by_operation
927                        .map(|id| uuid::Uuid::parse_str(&id))
928                        .transpose()
929                        .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?,
930                })
931            })
932            .collect()
933    }
934
935    async fn reserve_melt_quote(
936        &self,
937        quote_id: &str,
938        operation_id: &uuid::Uuid,
939    ) -> Result<(), cdk::cdk_database::Error> {
940        self.ffi_db
941            .reserve_melt_quote(quote_id.to_string(), operation_id.to_string())
942            .await
943            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
944    }
945
946    async fn release_melt_quote(
947        &self,
948        operation_id: &uuid::Uuid,
949    ) -> Result<(), cdk::cdk_database::Error> {
950        self.ffi_db
951            .release_melt_quote(operation_id.to_string())
952            .await
953            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
954    }
955
956    async fn reserve_mint_quote(
957        &self,
958        quote_id: &str,
959        operation_id: &uuid::Uuid,
960    ) -> Result<(), cdk::cdk_database::Error> {
961        self.ffi_db
962            .reserve_mint_quote(quote_id.to_string(), operation_id.to_string())
963            .await
964            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
965    }
966
967    async fn release_mint_quote(
968        &self,
969        operation_id: &uuid::Uuid,
970    ) -> Result<(), cdk::cdk_database::Error> {
971        self.ffi_db
972            .release_mint_quote(operation_id.to_string())
973            .await
974            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
975    }
976
977    async fn kv_write(
978        &self,
979        primary_namespace: &str,
980        secondary_namespace: &str,
981        key: &str,
982        value: &[u8],
983    ) -> Result<(), cdk::cdk_database::Error> {
984        self.ffi_db
985            .kv_write(
986                primary_namespace.to_string(),
987                secondary_namespace.to_string(),
988                key.to_string(),
989                value.to_vec(),
990            )
991            .await
992            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
993    }
994
995    async fn kv_remove(
996        &self,
997        primary_namespace: &str,
998        secondary_namespace: &str,
999        key: &str,
1000    ) -> Result<(), cdk::cdk_database::Error> {
1001        self.ffi_db
1002            .kv_remove(
1003                primary_namespace.to_string(),
1004                secondary_namespace.to_string(),
1005                key.to_string(),
1006            )
1007            .await
1008            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))
1009    }
1010}
1011
1012pub(crate) struct FfiWalletSQLDatabase<RM>
1013where
1014    RM: DatabasePool + 'static,
1015{
1016    inner: SQLWalletDatabase<RM>,
1017}
1018
1019impl<RM> FfiWalletSQLDatabase<RM>
1020where
1021    RM: DatabasePool + 'static,
1022{
1023    /// Creates a new instance
1024    pub fn new(inner: SQLWalletDatabase<RM>) -> Arc<Self> {
1025        Arc::new(Self { inner })
1026    }
1027}
1028
1029// Implement WalletDatabase trait - all read and write methods
1030#[async_trait::async_trait]
1031impl<RM> WalletDatabase for FfiWalletSQLDatabase<RM>
1032where
1033    RM: DatabasePool + 'static,
1034{
1035    // ========== Read methods ==========
1036
1037    async fn get_proofs_by_ys(&self, ys: Vec<PublicKey>) -> Result<Vec<ProofInfo>, FfiError> {
1038        let cdk_ys: Vec<cdk::nuts::PublicKey> = ys
1039            .into_iter()
1040            .map(|y| y.try_into())
1041            .collect::<Result<Vec<_>, FfiError>>()?;
1042
1043        let result = self
1044            .inner
1045            .get_proofs_by_ys(cdk_ys)
1046            .await
1047            .map_err(FfiError::internal)?;
1048
1049        Ok(result.into_iter().map(Into::into).collect())
1050    }
1051
1052    async fn get_mint(&self, mint_url: MintUrl) -> Result<Option<MintInfo>, FfiError> {
1053        let cdk_mint_url = mint_url.try_into()?;
1054        let result = self
1055            .inner
1056            .get_mint(cdk_mint_url)
1057            .await
1058            .map_err(FfiError::internal)?;
1059        Ok(result.map(Into::into))
1060    }
1061
1062    async fn get_mints(&self) -> Result<HashMap<MintUrl, Option<MintInfo>>, FfiError> {
1063        let result = self.inner.get_mints().await.map_err(FfiError::internal)?;
1064        Ok(result
1065            .into_iter()
1066            .map(|(k, v)| (k.into(), v.map(Into::into)))
1067            .collect())
1068    }
1069
1070    async fn get_mint_keysets(
1071        &self,
1072        mint_url: MintUrl,
1073    ) -> Result<Option<Vec<KeySetInfo>>, FfiError> {
1074        let cdk_mint_url = mint_url.try_into()?;
1075        let result = self
1076            .inner
1077            .get_mint_keysets(cdk_mint_url)
1078            .await
1079            .map_err(FfiError::internal)?;
1080        Ok(result.map(|keysets| keysets.into_iter().map(Into::into).collect()))
1081    }
1082
1083    async fn get_keyset_by_id(&self, keyset_id: Id) -> Result<Option<KeySetInfo>, FfiError> {
1084        let cdk_id = keyset_id.into();
1085        let result = self
1086            .inner
1087            .get_keyset_by_id(&cdk_id)
1088            .await
1089            .map_err(FfiError::internal)?;
1090        Ok(result.map(Into::into))
1091    }
1092
1093    async fn get_mint_quote(&self, quote_id: String) -> Result<Option<MintQuote>, FfiError> {
1094        let result = self
1095            .inner
1096            .get_mint_quote(&quote_id)
1097            .await
1098            .map_err(FfiError::internal)?;
1099        Ok(result.map(|q| q.into()))
1100    }
1101
1102    async fn get_mint_quotes(&self) -> Result<Vec<MintQuote>, FfiError> {
1103        let result = self
1104            .inner
1105            .get_mint_quotes()
1106            .await
1107            .map_err(FfiError::internal)?;
1108        Ok(result.into_iter().map(|q| q.into()).collect())
1109    }
1110
1111    async fn get_unissued_mint_quotes(&self) -> Result<Vec<MintQuote>, FfiError> {
1112        let result = self
1113            .inner
1114            .get_unissued_mint_quotes()
1115            .await
1116            .map_err(FfiError::internal)?;
1117        Ok(result.into_iter().map(|q| q.into()).collect())
1118    }
1119
1120    async fn get_melt_quote(&self, quote_id: String) -> Result<Option<MeltQuote>, FfiError> {
1121        let result = self
1122            .inner
1123            .get_melt_quote(&quote_id)
1124            .await
1125            .map_err(FfiError::internal)?;
1126        Ok(result.map(|q| q.into()))
1127    }
1128
1129    async fn get_melt_quotes(&self) -> Result<Vec<MeltQuote>, FfiError> {
1130        let result = self
1131            .inner
1132            .get_melt_quotes()
1133            .await
1134            .map_err(FfiError::internal)?;
1135        Ok(result.into_iter().map(|q| q.into()).collect())
1136    }
1137
1138    async fn get_keys(&self, id: Id) -> Result<Option<Keys>, FfiError> {
1139        let cdk_id = id.into();
1140        let result = self
1141            .inner
1142            .get_keys(&cdk_id)
1143            .await
1144            .map_err(FfiError::internal)?;
1145        Ok(result.map(Into::into))
1146    }
1147
1148    async fn get_proofs(
1149        &self,
1150        mint_url: Option<MintUrl>,
1151        unit: Option<CurrencyUnit>,
1152        state: Option<Vec<ProofState>>,
1153        spending_conditions: Option<Vec<SpendingConditions>>,
1154    ) -> Result<Vec<ProofInfo>, FfiError> {
1155        let cdk_mint_url = mint_url.map(|u| u.try_into()).transpose()?;
1156        let cdk_unit = unit.map(Into::into);
1157        let cdk_state = state.map(|s| s.into_iter().map(Into::into).collect());
1158        let cdk_spending_conditions: Option<Vec<cdk::nuts::SpendingConditions>> =
1159            spending_conditions
1160                .map(|sc| {
1161                    sc.into_iter()
1162                        .map(|c| c.try_into())
1163                        .collect::<Result<Vec<_>, FfiError>>()
1164                })
1165                .transpose()?;
1166
1167        let result = self
1168            .inner
1169            .get_proofs(cdk_mint_url, cdk_unit, cdk_state, cdk_spending_conditions)
1170            .await
1171            .map_err(FfiError::internal)?;
1172
1173        Ok(result.into_iter().map(Into::into).collect())
1174    }
1175
1176    async fn get_balance(
1177        &self,
1178        mint_url: Option<MintUrl>,
1179        unit: Option<CurrencyUnit>,
1180        state: Option<Vec<ProofState>>,
1181    ) -> Result<u64, FfiError> {
1182        let cdk_mint_url = mint_url.map(|u| u.try_into()).transpose()?;
1183        let cdk_unit = unit.map(Into::into);
1184        let cdk_state = state.map(|s| s.into_iter().map(Into::into).collect());
1185
1186        self.inner
1187            .get_balance(cdk_mint_url, cdk_unit, cdk_state)
1188            .await
1189            .map_err(FfiError::internal)
1190    }
1191
1192    async fn get_transaction(
1193        &self,
1194        transaction_id: TransactionId,
1195    ) -> Result<Option<Transaction>, FfiError> {
1196        let cdk_id = transaction_id.try_into()?;
1197        let result = self
1198            .inner
1199            .get_transaction(cdk_id)
1200            .await
1201            .map_err(FfiError::internal)?;
1202        Ok(result.map(Into::into))
1203    }
1204
1205    async fn list_transactions(
1206        &self,
1207        mint_url: Option<MintUrl>,
1208        direction: Option<TransactionDirection>,
1209        unit: Option<CurrencyUnit>,
1210    ) -> Result<Vec<Transaction>, FfiError> {
1211        let cdk_mint_url = mint_url.map(|u| u.try_into()).transpose()?;
1212        let cdk_direction = direction.map(Into::into);
1213        let cdk_unit = unit.map(Into::into);
1214
1215        let result = self
1216            .inner
1217            .list_transactions(cdk_mint_url, cdk_direction, cdk_unit)
1218            .await
1219            .map_err(FfiError::internal)?;
1220
1221        Ok(result.into_iter().map(Into::into).collect())
1222    }
1223
1224    async fn kv_read(
1225        &self,
1226        primary_namespace: String,
1227        secondary_namespace: String,
1228        key: String,
1229    ) -> Result<Option<Vec<u8>>, FfiError> {
1230        self.inner
1231            .kv_read(&primary_namespace, &secondary_namespace, &key)
1232            .await
1233            .map_err(FfiError::internal)
1234    }
1235
1236    async fn kv_list(
1237        &self,
1238        primary_namespace: String,
1239        secondary_namespace: String,
1240    ) -> Result<Vec<String>, FfiError> {
1241        self.inner
1242            .kv_list(&primary_namespace, &secondary_namespace)
1243            .await
1244            .map_err(FfiError::internal)
1245    }
1246
1247    async fn kv_write(
1248        &self,
1249        primary_namespace: String,
1250        secondary_namespace: String,
1251        key: String,
1252        value: Vec<u8>,
1253    ) -> Result<(), FfiError> {
1254        self.inner
1255            .kv_write(&primary_namespace, &secondary_namespace, &key, &value)
1256            .await
1257            .map_err(FfiError::internal)
1258    }
1259
1260    async fn kv_remove(
1261        &self,
1262        primary_namespace: String,
1263        secondary_namespace: String,
1264        key: String,
1265    ) -> Result<(), FfiError> {
1266        self.inner
1267            .kv_remove(&primary_namespace, &secondary_namespace, &key)
1268            .await
1269            .map_err(FfiError::internal)
1270    }
1271
1272    // ========== Write methods ==========
1273
1274    async fn update_proofs(
1275        &self,
1276        added: Vec<ProofInfo>,
1277        removed_ys: Vec<PublicKey>,
1278    ) -> Result<(), FfiError> {
1279        let cdk_added: Result<Vec<cdk::types::ProofInfo>, FfiError> = added
1280            .into_iter()
1281            .map(|info| {
1282                Ok::<cdk::types::ProofInfo, FfiError>(cdk::types::ProofInfo {
1283                    proof: info.proof.try_into()?,
1284                    y: info.y.try_into()?,
1285                    mint_url: info.mint_url.try_into()?,
1286                    state: info.state.into(),
1287                    spending_condition: info
1288                        .spending_condition
1289                        .map(|sc| sc.try_into())
1290                        .transpose()?,
1291                    unit: info.unit.into(),
1292                    used_by_operation: info
1293                        .used_by_operation
1294                        .map(|id| uuid::Uuid::parse_str(&id))
1295                        .transpose()
1296                        .map_err(|e| FfiError::internal(e.to_string()))?,
1297                    created_by_operation: info
1298                        .created_by_operation
1299                        .map(|id| uuid::Uuid::parse_str(&id))
1300                        .transpose()
1301                        .map_err(|e| FfiError::internal(e.to_string()))?,
1302                })
1303            })
1304            .collect();
1305        let cdk_added = cdk_added?;
1306
1307        let cdk_removed_ys: Result<Vec<cdk::nuts::PublicKey>, FfiError> =
1308            removed_ys.into_iter().map(|pk| pk.try_into()).collect();
1309        let cdk_removed_ys = cdk_removed_ys?;
1310
1311        self.inner
1312            .update_proofs(cdk_added, cdk_removed_ys)
1313            .await
1314            .map_err(FfiError::internal)
1315    }
1316
1317    async fn update_proofs_state(
1318        &self,
1319        ys: Vec<PublicKey>,
1320        state: ProofState,
1321    ) -> Result<(), FfiError> {
1322        let cdk_ys: Result<Vec<cdk::nuts::PublicKey>, FfiError> =
1323            ys.into_iter().map(|pk| pk.try_into()).collect();
1324        let cdk_ys = cdk_ys?;
1325        let cdk_state = state.into();
1326
1327        self.inner
1328            .update_proofs_state(cdk_ys, cdk_state)
1329            .await
1330            .map_err(FfiError::internal)
1331    }
1332
1333    async fn add_transaction(&self, transaction: Transaction) -> Result<(), FfiError> {
1334        let cdk_transaction: cdk::wallet::types::Transaction = transaction.try_into()?;
1335        self.inner
1336            .add_transaction(cdk_transaction)
1337            .await
1338            .map_err(FfiError::internal)
1339    }
1340
1341    async fn remove_transaction(&self, transaction_id: TransactionId) -> Result<(), FfiError> {
1342        let cdk_id = transaction_id.try_into()?;
1343        self.inner
1344            .remove_transaction(cdk_id)
1345            .await
1346            .map_err(FfiError::internal)
1347    }
1348
1349    async fn update_mint_url(
1350        &self,
1351        old_mint_url: MintUrl,
1352        new_mint_url: MintUrl,
1353    ) -> Result<(), FfiError> {
1354        let cdk_old = old_mint_url.try_into()?;
1355        let cdk_new = new_mint_url.try_into()?;
1356        self.inner
1357            .update_mint_url(cdk_old, cdk_new)
1358            .await
1359            .map_err(FfiError::internal)
1360    }
1361
1362    async fn increment_keyset_counter(&self, keyset_id: Id, count: u32) -> Result<u32, FfiError> {
1363        let cdk_id = keyset_id.into();
1364        self.inner
1365            .increment_keyset_counter(&cdk_id, count)
1366            .await
1367            .map_err(FfiError::internal)
1368    }
1369
1370    async fn add_mint(
1371        &self,
1372        mint_url: MintUrl,
1373        mint_info: Option<MintInfo>,
1374    ) -> Result<(), FfiError> {
1375        let cdk_mint_url = mint_url.try_into()?;
1376        let cdk_mint_info = mint_info.map(Into::into);
1377        self.inner
1378            .add_mint(cdk_mint_url, cdk_mint_info)
1379            .await
1380            .map_err(FfiError::internal)
1381    }
1382
1383    async fn remove_mint(&self, mint_url: MintUrl) -> Result<(), FfiError> {
1384        let cdk_mint_url = mint_url.try_into()?;
1385        self.inner
1386            .remove_mint(cdk_mint_url)
1387            .await
1388            .map_err(FfiError::internal)
1389    }
1390
1391    async fn add_mint_keysets(
1392        &self,
1393        mint_url: MintUrl,
1394        keysets: Vec<KeySetInfo>,
1395    ) -> Result<(), FfiError> {
1396        let cdk_mint_url = mint_url.try_into()?;
1397        let cdk_keysets: Vec<cdk::nuts::KeySetInfo> = keysets.into_iter().map(Into::into).collect();
1398        self.inner
1399            .add_mint_keysets(cdk_mint_url, cdk_keysets)
1400            .await
1401            .map_err(FfiError::internal)
1402    }
1403
1404    async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), FfiError> {
1405        let cdk_quote = quote.try_into()?;
1406        self.inner
1407            .add_mint_quote(cdk_quote)
1408            .await
1409            .map_err(FfiError::internal)
1410    }
1411
1412    async fn remove_mint_quote(&self, quote_id: String) -> Result<(), FfiError> {
1413        self.inner
1414            .remove_mint_quote(&quote_id)
1415            .await
1416            .map_err(FfiError::internal)
1417    }
1418
1419    async fn add_melt_quote(&self, quote: MeltQuote) -> Result<(), FfiError> {
1420        let cdk_quote = quote.try_into()?;
1421        self.inner
1422            .add_melt_quote(cdk_quote)
1423            .await
1424            .map_err(FfiError::internal)
1425    }
1426
1427    async fn remove_melt_quote(&self, quote_id: String) -> Result<(), FfiError> {
1428        self.inner
1429            .remove_melt_quote(&quote_id)
1430            .await
1431            .map_err(FfiError::internal)
1432    }
1433
1434    async fn add_keys(&self, keyset: KeySet) -> Result<(), FfiError> {
1435        let cdk_keyset: cdk::nuts::KeySet = keyset.try_into()?;
1436        self.inner
1437            .add_keys(cdk_keyset)
1438            .await
1439            .map_err(FfiError::internal)
1440    }
1441
1442    async fn remove_keys(&self, id: Id) -> Result<(), FfiError> {
1443        let cdk_id = id.into();
1444        self.inner
1445            .remove_keys(&cdk_id)
1446            .await
1447            .map_err(FfiError::internal)
1448    }
1449
1450    // ========== Saga management methods ==========
1451
1452    async fn add_saga(&self, saga_json: String) -> Result<(), FfiError> {
1453        let saga: WalletSaga = serde_json::from_str(&saga_json).map_err(FfiError::internal)?;
1454        self.inner.add_saga(saga).await.map_err(FfiError::internal)
1455    }
1456
1457    async fn get_saga(&self, id: String) -> Result<Option<String>, FfiError> {
1458        let id = uuid::Uuid::parse_str(&id).map_err(FfiError::internal)?;
1459        let result = self.inner.get_saga(&id).await.map_err(FfiError::internal)?;
1460
1461        match result {
1462            Some(saga) => {
1463                let json = serde_json::to_string(&saga).map_err(FfiError::internal)?;
1464                Ok(Some(json))
1465            }
1466            None => Ok(None),
1467        }
1468    }
1469
1470    async fn update_saga(&self, saga_json: String) -> Result<bool, FfiError> {
1471        let saga: WalletSaga = serde_json::from_str(&saga_json).map_err(FfiError::internal)?;
1472        self.inner
1473            .update_saga(saga)
1474            .await
1475            .map_err(FfiError::internal)
1476    }
1477
1478    async fn delete_saga(&self, id: String) -> Result<(), FfiError> {
1479        let id = uuid::Uuid::parse_str(&id).map_err(FfiError::internal)?;
1480        self.inner
1481            .delete_saga(&id)
1482            .await
1483            .map_err(FfiError::internal)
1484    }
1485
1486    async fn get_incomplete_sagas(&self) -> Result<Vec<String>, FfiError> {
1487        let result = self
1488            .inner
1489            .get_incomplete_sagas()
1490            .await
1491            .map_err(FfiError::internal)?;
1492
1493        result
1494            .into_iter()
1495            .map(|saga| serde_json::to_string(&saga).map_err(FfiError::internal))
1496            .collect()
1497    }
1498
1499    // ========== Proof reservation methods ==========
1500
1501    async fn reserve_proofs(
1502        &self,
1503        ys: Vec<PublicKey>,
1504        operation_id: String,
1505    ) -> Result<(), FfiError> {
1506        let operation_id = uuid::Uuid::parse_str(&operation_id).map_err(FfiError::internal)?;
1507        let cdk_ys: Result<Vec<cdk::nuts::PublicKey>, FfiError> =
1508            ys.into_iter().map(|pk| pk.try_into()).collect();
1509        let cdk_ys = cdk_ys?;
1510        self.inner
1511            .reserve_proofs(cdk_ys, &operation_id)
1512            .await
1513            .map_err(FfiError::internal)
1514    }
1515
1516    async fn release_proofs(&self, operation_id: String) -> Result<(), FfiError> {
1517        let operation_id = uuid::Uuid::parse_str(&operation_id).map_err(FfiError::internal)?;
1518        self.inner
1519            .release_proofs(&operation_id)
1520            .await
1521            .map_err(FfiError::internal)
1522    }
1523
1524    async fn get_reserved_proofs(&self, operation_id: String) -> Result<Vec<ProofInfo>, FfiError> {
1525        let operation_id = uuid::Uuid::parse_str(&operation_id).map_err(FfiError::internal)?;
1526        let result = self
1527            .inner
1528            .get_reserved_proofs(&operation_id)
1529            .await
1530            .map_err(FfiError::internal)?;
1531
1532        Ok(result.into_iter().map(Into::into).collect())
1533    }
1534
1535    // ========== Quote reservation methods ==========
1536
1537    async fn reserve_melt_quote(
1538        &self,
1539        quote_id: String,
1540        operation_id: String,
1541    ) -> Result<(), FfiError> {
1542        let operation_id = uuid::Uuid::parse_str(&operation_id).map_err(FfiError::internal)?;
1543        self.inner
1544            .reserve_melt_quote(&quote_id, &operation_id)
1545            .await
1546            .map_err(FfiError::internal)
1547    }
1548
1549    async fn release_melt_quote(&self, operation_id: String) -> Result<(), FfiError> {
1550        let operation_id = uuid::Uuid::parse_str(&operation_id).map_err(FfiError::internal)?;
1551        self.inner
1552            .release_melt_quote(&operation_id)
1553            .await
1554            .map_err(FfiError::internal)
1555    }
1556
1557    async fn reserve_mint_quote(
1558        &self,
1559        quote_id: String,
1560        operation_id: String,
1561    ) -> Result<(), FfiError> {
1562        let operation_id = uuid::Uuid::parse_str(&operation_id).map_err(FfiError::internal)?;
1563        self.inner
1564            .reserve_mint_quote(&quote_id, &operation_id)
1565            .await
1566            .map_err(FfiError::internal)
1567    }
1568
1569    async fn release_mint_quote(&self, operation_id: String) -> Result<(), FfiError> {
1570        let operation_id = uuid::Uuid::parse_str(&operation_id).map_err(FfiError::internal)?;
1571        self.inner
1572            .release_mint_quote(&operation_id)
1573            .await
1574            .map_err(FfiError::internal)
1575    }
1576}
1577
1578/// Macro to implement WalletDatabase for wrapper types that delegate to an inner FfiWalletSQLDatabase.
1579/// This eliminates duplication between SQLite and Postgres FFI implementations.
1580///
1581/// Requirements: The following types must be in scope where this macro is invoked:
1582/// - WalletDatabase, FfiError, PublicKey, ProofInfo, MintUrl, MintInfo, KeySetInfo, Id,
1583///   MintQuote, MeltQuote, Keys, CurrencyUnit, ProofState, SpendingConditions, Transaction,
1584///   TransactionId, TransactionDirection, KeySet
1585/// - std::collections::HashMap
1586#[macro_export]
1587macro_rules! impl_ffi_wallet_database {
1588    ($wrapper_type:ty) => {
1589        #[uniffi::export(async_runtime = "tokio")]
1590        #[async_trait::async_trait]
1591        impl WalletDatabase for $wrapper_type {
1592            // ========== Read methods ==========
1593
1594            async fn get_proofs_by_ys(
1595                &self,
1596                ys: Vec<PublicKey>,
1597            ) -> Result<Vec<ProofInfo>, FfiError> {
1598                self.inner.get_proofs_by_ys(ys).await
1599            }
1600
1601            async fn get_mint(&self, mint_url: MintUrl) -> Result<Option<MintInfo>, FfiError> {
1602                self.inner.get_mint(mint_url).await
1603            }
1604
1605            async fn get_mints(
1606                &self,
1607            ) -> Result<std::collections::HashMap<MintUrl, Option<MintInfo>>, FfiError> {
1608                self.inner.get_mints().await
1609            }
1610
1611            async fn get_mint_keysets(
1612                &self,
1613                mint_url: MintUrl,
1614            ) -> Result<Option<Vec<KeySetInfo>>, FfiError> {
1615                self.inner.get_mint_keysets(mint_url).await
1616            }
1617
1618            async fn get_keyset_by_id(
1619                &self,
1620                keyset_id: Id,
1621            ) -> Result<Option<KeySetInfo>, FfiError> {
1622                self.inner.get_keyset_by_id(keyset_id).await
1623            }
1624
1625            async fn get_mint_quote(
1626                &self,
1627                quote_id: String,
1628            ) -> Result<Option<MintQuote>, FfiError> {
1629                self.inner.get_mint_quote(quote_id).await
1630            }
1631
1632            async fn get_mint_quotes(&self) -> Result<Vec<MintQuote>, FfiError> {
1633                self.inner.get_mint_quotes().await
1634            }
1635
1636            async fn get_unissued_mint_quotes(&self) -> Result<Vec<MintQuote>, FfiError> {
1637                self.inner.get_unissued_mint_quotes().await
1638            }
1639
1640            async fn get_melt_quote(
1641                &self,
1642                quote_id: String,
1643            ) -> Result<Option<MeltQuote>, FfiError> {
1644                self.inner.get_melt_quote(quote_id).await
1645            }
1646
1647            async fn get_melt_quotes(&self) -> Result<Vec<MeltQuote>, FfiError> {
1648                self.inner.get_melt_quotes().await
1649            }
1650
1651            async fn get_keys(&self, id: Id) -> Result<Option<Keys>, FfiError> {
1652                self.inner.get_keys(id).await
1653            }
1654
1655            async fn get_proofs(
1656                &self,
1657                mint_url: Option<MintUrl>,
1658                unit: Option<CurrencyUnit>,
1659                state: Option<Vec<ProofState>>,
1660                spending_conditions: Option<Vec<SpendingConditions>>,
1661            ) -> Result<Vec<ProofInfo>, FfiError> {
1662                self.inner
1663                    .get_proofs(mint_url, unit, state, spending_conditions)
1664                    .await
1665            }
1666
1667            async fn get_balance(
1668                &self,
1669                mint_url: Option<MintUrl>,
1670                unit: Option<CurrencyUnit>,
1671                state: Option<Vec<ProofState>>,
1672            ) -> Result<u64, FfiError> {
1673                self.inner.get_balance(mint_url, unit, state).await
1674            }
1675
1676            async fn get_transaction(
1677                &self,
1678                transaction_id: TransactionId,
1679            ) -> Result<Option<Transaction>, FfiError> {
1680                self.inner.get_transaction(transaction_id).await
1681            }
1682
1683            async fn list_transactions(
1684                &self,
1685                mint_url: Option<MintUrl>,
1686                direction: Option<TransactionDirection>,
1687                unit: Option<CurrencyUnit>,
1688            ) -> Result<Vec<Transaction>, FfiError> {
1689                self.inner
1690                    .list_transactions(mint_url, direction, unit)
1691                    .await
1692            }
1693
1694            async fn kv_read(
1695                &self,
1696                primary_namespace: String,
1697                secondary_namespace: String,
1698                key: String,
1699            ) -> Result<Option<Vec<u8>>, FfiError> {
1700                self.inner
1701                    .kv_read(primary_namespace, secondary_namespace, key)
1702                    .await
1703            }
1704
1705            async fn kv_list(
1706                &self,
1707                primary_namespace: String,
1708                secondary_namespace: String,
1709            ) -> Result<Vec<String>, FfiError> {
1710                self.inner
1711                    .kv_list(primary_namespace, secondary_namespace)
1712                    .await
1713            }
1714
1715            async fn kv_write(
1716                &self,
1717                primary_namespace: String,
1718                secondary_namespace: String,
1719                key: String,
1720                value: Vec<u8>,
1721            ) -> Result<(), FfiError> {
1722                self.inner
1723                    .kv_write(primary_namespace, secondary_namespace, key, value)
1724                    .await
1725            }
1726
1727            async fn kv_remove(
1728                &self,
1729                primary_namespace: String,
1730                secondary_namespace: String,
1731                key: String,
1732            ) -> Result<(), FfiError> {
1733                self.inner
1734                    .kv_remove(primary_namespace, secondary_namespace, key)
1735                    .await
1736            }
1737
1738            // ========== Write methods ==========
1739
1740            async fn update_proofs(
1741                &self,
1742                added: Vec<ProofInfo>,
1743                removed_ys: Vec<PublicKey>,
1744            ) -> Result<(), FfiError> {
1745                self.inner.update_proofs(added, removed_ys).await
1746            }
1747
1748            async fn update_proofs_state(
1749                &self,
1750                ys: Vec<PublicKey>,
1751                state: ProofState,
1752            ) -> Result<(), FfiError> {
1753                self.inner.update_proofs_state(ys, state).await
1754            }
1755
1756            async fn add_transaction(&self, transaction: Transaction) -> Result<(), FfiError> {
1757                self.inner.add_transaction(transaction).await
1758            }
1759
1760            async fn remove_transaction(
1761                &self,
1762                transaction_id: TransactionId,
1763            ) -> Result<(), FfiError> {
1764                self.inner.remove_transaction(transaction_id).await
1765            }
1766
1767            async fn update_mint_url(
1768                &self,
1769                old_mint_url: MintUrl,
1770                new_mint_url: MintUrl,
1771            ) -> Result<(), FfiError> {
1772                self.inner.update_mint_url(old_mint_url, new_mint_url).await
1773            }
1774
1775            async fn increment_keyset_counter(
1776                &self,
1777                keyset_id: Id,
1778                count: u32,
1779            ) -> Result<u32, FfiError> {
1780                self.inner.increment_keyset_counter(keyset_id, count).await
1781            }
1782
1783            async fn add_mint(
1784                &self,
1785                mint_url: MintUrl,
1786                mint_info: Option<MintInfo>,
1787            ) -> Result<(), FfiError> {
1788                self.inner.add_mint(mint_url, mint_info).await
1789            }
1790
1791            async fn remove_mint(&self, mint_url: MintUrl) -> Result<(), FfiError> {
1792                self.inner.remove_mint(mint_url).await
1793            }
1794
1795            async fn add_mint_keysets(
1796                &self,
1797                mint_url: MintUrl,
1798                keysets: Vec<KeySetInfo>,
1799            ) -> Result<(), FfiError> {
1800                self.inner.add_mint_keysets(mint_url, keysets).await
1801            }
1802
1803            async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), FfiError> {
1804                self.inner.add_mint_quote(quote).await
1805            }
1806
1807            async fn remove_mint_quote(&self, quote_id: String) -> Result<(), FfiError> {
1808                self.inner.remove_mint_quote(quote_id).await
1809            }
1810
1811            async fn add_melt_quote(&self, quote: MeltQuote) -> Result<(), FfiError> {
1812                self.inner.add_melt_quote(quote).await
1813            }
1814
1815            async fn remove_melt_quote(&self, quote_id: String) -> Result<(), FfiError> {
1816                self.inner.remove_melt_quote(quote_id).await
1817            }
1818
1819            async fn add_keys(&self, keyset: KeySet) -> Result<(), FfiError> {
1820                self.inner.add_keys(keyset).await
1821            }
1822
1823            async fn remove_keys(&self, id: Id) -> Result<(), FfiError> {
1824                self.inner.remove_keys(id).await
1825            }
1826
1827            // ========== Saga management methods ==========
1828
1829            async fn add_saga(&self, saga_json: String) -> Result<(), FfiError> {
1830                self.inner.add_saga(saga_json).await
1831            }
1832
1833            async fn get_saga(&self, id: String) -> Result<Option<String>, FfiError> {
1834                self.inner.get_saga(id).await
1835            }
1836
1837            async fn update_saga(&self, saga_json: String) -> Result<bool, FfiError> {
1838                self.inner.update_saga(saga_json).await
1839            }
1840
1841            async fn delete_saga(&self, id: String) -> Result<(), FfiError> {
1842                self.inner.delete_saga(id).await
1843            }
1844
1845            async fn get_incomplete_sagas(&self) -> Result<Vec<String>, FfiError> {
1846                self.inner.get_incomplete_sagas().await
1847            }
1848
1849            // ========== Proof reservation methods ==========
1850
1851            async fn reserve_proofs(
1852                &self,
1853                ys: Vec<PublicKey>,
1854                operation_id: String,
1855            ) -> Result<(), FfiError> {
1856                self.inner.reserve_proofs(ys, operation_id).await
1857            }
1858
1859            async fn release_proofs(&self, operation_id: String) -> Result<(), FfiError> {
1860                self.inner.release_proofs(operation_id).await
1861            }
1862
1863            async fn get_reserved_proofs(
1864                &self,
1865                operation_id: String,
1866            ) -> Result<Vec<ProofInfo>, FfiError> {
1867                self.inner.get_reserved_proofs(operation_id).await
1868            }
1869
1870            // ========== Quote reservation methods ==========
1871
1872            async fn reserve_melt_quote(
1873                &self,
1874                quote_id: String,
1875                operation_id: String,
1876            ) -> Result<(), FfiError> {
1877                self.inner.reserve_melt_quote(quote_id, operation_id).await
1878            }
1879
1880            async fn release_melt_quote(&self, operation_id: String) -> Result<(), FfiError> {
1881                self.inner.release_melt_quote(operation_id).await
1882            }
1883
1884            async fn reserve_mint_quote(
1885                &self,
1886                quote_id: String,
1887                operation_id: String,
1888            ) -> Result<(), FfiError> {
1889                self.inner.reserve_mint_quote(quote_id, operation_id).await
1890            }
1891
1892            async fn release_mint_quote(&self, operation_id: String) -> Result<(), FfiError> {
1893                self.inner.release_mint_quote(operation_id).await
1894            }
1895        }
1896    };
1897}
1898
1899/// FFI-safe database type enum
1900#[derive(uniffi::Enum, Clone)]
1901pub enum WalletDbBackend {
1902    Sqlite {
1903        path: String,
1904    },
1905    #[cfg(feature = "postgres")]
1906    Postgres {
1907        url: String,
1908    },
1909}
1910
1911/// Factory helpers returning a CDK wallet database behind the FFI trait
1912#[uniffi::export]
1913pub fn create_wallet_db(backend: WalletDbBackend) -> Result<Arc<dyn WalletDatabase>, FfiError> {
1914    match backend {
1915        WalletDbBackend::Sqlite { path } => {
1916            let sqlite = WalletSqliteDatabase::new(path)?;
1917            Ok(sqlite as Arc<dyn WalletDatabase>)
1918        }
1919        #[cfg(feature = "postgres")]
1920        WalletDbBackend::Postgres { url } => {
1921            let pg = WalletPostgresDatabase::new(url)?;
1922            Ok(pg as Arc<dyn WalletDatabase>)
1923        }
1924    }
1925}
1926
1927/// Helper function to create a CDK database from the FFI trait
1928pub fn create_cdk_database_from_ffi(
1929    ffi_db: Arc<dyn WalletDatabase>,
1930) -> Arc<dyn CdkWalletDatabase<cdk::cdk_database::Error> + Send + Sync> {
1931    Arc::new(WalletDatabaseBridge::new(ffi_db))
1932}