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