cdk_ffi/
sqlite.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3
4use cdk_sqlite::wallet::WalletSqliteDatabase as CdkWalletSqliteDatabase;
5
6use crate::{
7    CurrencyUnit, FfiError, Id, KeySet, KeySetInfo, Keys, MeltQuote, MintInfo, MintQuote, MintUrl,
8    ProofInfo, ProofState, PublicKey, SpendingConditions, Transaction, TransactionDirection,
9    TransactionId, WalletDatabase,
10};
11
12/// FFI-compatible WalletSqliteDatabase implementation that implements the WalletDatabase trait
13#[derive(uniffi::Object)]
14pub struct WalletSqliteDatabase {
15    inner: Arc<CdkWalletSqliteDatabase>,
16}
17use cdk::cdk_database::WalletDatabase as CdkWalletDatabase;
18
19impl WalletSqliteDatabase {
20    // No additional methods needed beyond the trait implementation
21}
22
23#[uniffi::export]
24impl WalletSqliteDatabase {
25    /// Create a new WalletSqliteDatabase with the given work directory
26    #[uniffi::constructor]
27    pub fn new(file_path: String) -> Result<Arc<Self>, FfiError> {
28        let db = match tokio::runtime::Handle::try_current() {
29            Ok(handle) => tokio::task::block_in_place(|| {
30                handle
31                    .block_on(async move { CdkWalletSqliteDatabase::new(file_path.as_str()).await })
32            }),
33            Err(_) => {
34                // No current runtime, create a new one
35                tokio::runtime::Runtime::new()
36                    .map_err(|e| FfiError::Database {
37                        msg: format!("Failed to create runtime: {}", e),
38                    })?
39                    .block_on(async move { CdkWalletSqliteDatabase::new(file_path.as_str()).await })
40            }
41        }
42        .map_err(|e| FfiError::Database { msg: e.to_string() })?;
43        Ok(Arc::new(Self {
44            inner: Arc::new(db),
45        }))
46    }
47
48    /// Create an in-memory database
49    #[uniffi::constructor]
50    pub fn new_in_memory() -> Result<Arc<Self>, FfiError> {
51        let db = match tokio::runtime::Handle::try_current() {
52            Ok(handle) => tokio::task::block_in_place(|| {
53                handle.block_on(async move { cdk_sqlite::wallet::memory::empty().await })
54            }),
55            Err(_) => {
56                // No current runtime, create a new one
57                tokio::runtime::Runtime::new()
58                    .map_err(|e| FfiError::Database {
59                        msg: format!("Failed to create runtime: {}", e),
60                    })?
61                    .block_on(async move { cdk_sqlite::wallet::memory::empty().await })
62            }
63        }
64        .map_err(|e| FfiError::Database { msg: e.to_string() })?;
65        Ok(Arc::new(Self {
66            inner: Arc::new(db),
67        }))
68    }
69}
70
71#[uniffi::export(async_runtime = "tokio")]
72#[async_trait::async_trait]
73impl WalletDatabase for WalletSqliteDatabase {
74    // Mint Management
75    async fn add_mint(
76        &self,
77        mint_url: MintUrl,
78        mint_info: Option<MintInfo>,
79    ) -> Result<(), FfiError> {
80        let cdk_mint_url = mint_url.try_into()?;
81        let cdk_mint_info = mint_info.map(Into::into);
82        self.inner
83            .add_mint(cdk_mint_url, cdk_mint_info)
84            .await
85            .map_err(|e| FfiError::Database { msg: e.to_string() })
86    }
87
88    async fn remove_mint(&self, mint_url: MintUrl) -> Result<(), FfiError> {
89        let cdk_mint_url = mint_url.try_into()?;
90        self.inner
91            .remove_mint(cdk_mint_url)
92            .await
93            .map_err(|e| FfiError::Database { msg: e.to_string() })
94    }
95
96    async fn get_mint(&self, mint_url: MintUrl) -> Result<Option<MintInfo>, FfiError> {
97        let cdk_mint_url = mint_url.try_into()?;
98        let result = self
99            .inner
100            .get_mint(cdk_mint_url)
101            .await
102            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
103        Ok(result.map(Into::into))
104    }
105
106    async fn get_mints(&self) -> Result<HashMap<MintUrl, Option<MintInfo>>, FfiError> {
107        let result = self
108            .inner
109            .get_mints()
110            .await
111            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
112        Ok(result
113            .into_iter()
114            .map(|(k, v)| (k.into(), v.map(Into::into)))
115            .collect())
116    }
117
118    async fn update_mint_url(
119        &self,
120        old_mint_url: MintUrl,
121        new_mint_url: MintUrl,
122    ) -> Result<(), FfiError> {
123        let cdk_old_mint_url = old_mint_url.try_into()?;
124        let cdk_new_mint_url = new_mint_url.try_into()?;
125        self.inner
126            .update_mint_url(cdk_old_mint_url, cdk_new_mint_url)
127            .await
128            .map_err(|e| FfiError::Database { msg: e.to_string() })
129    }
130
131    // Keyset Management
132    async fn add_mint_keysets(
133        &self,
134        mint_url: MintUrl,
135        keysets: Vec<KeySetInfo>,
136    ) -> Result<(), FfiError> {
137        let cdk_mint_url = mint_url.try_into()?;
138        let cdk_keysets: Vec<cdk::nuts::KeySetInfo> = keysets.into_iter().map(Into::into).collect();
139        self.inner
140            .add_mint_keysets(cdk_mint_url, cdk_keysets)
141            .await
142            .map_err(|e| FfiError::Database { msg: e.to_string() })
143    }
144
145    async fn get_mint_keysets(
146        &self,
147        mint_url: MintUrl,
148    ) -> Result<Option<Vec<KeySetInfo>>, FfiError> {
149        let cdk_mint_url = mint_url.try_into()?;
150        let result = self
151            .inner
152            .get_mint_keysets(cdk_mint_url)
153            .await
154            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
155        Ok(result.map(|keysets| keysets.into_iter().map(Into::into).collect()))
156    }
157
158    async fn get_keyset_by_id(&self, keyset_id: Id) -> Result<Option<KeySetInfo>, FfiError> {
159        let cdk_id = keyset_id.into();
160        let result = self
161            .inner
162            .get_keyset_by_id(&cdk_id)
163            .await
164            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
165        Ok(result.map(Into::into))
166    }
167
168    // Mint Quote Management
169    async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), FfiError> {
170        let cdk_quote = quote.try_into()?;
171        self.inner
172            .add_mint_quote(cdk_quote)
173            .await
174            .map_err(|e| FfiError::Database { msg: e.to_string() })
175    }
176
177    async fn get_mint_quote(&self, quote_id: String) -> Result<Option<MintQuote>, FfiError> {
178        let result = self
179            .inner
180            .get_mint_quote(&quote_id)
181            .await
182            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
183        Ok(result.map(|q| q.into()))
184    }
185
186    async fn get_mint_quotes(&self) -> Result<Vec<MintQuote>, FfiError> {
187        let result = self
188            .inner
189            .get_mint_quotes()
190            .await
191            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
192        Ok(result.into_iter().map(|q| q.into()).collect())
193    }
194
195    async fn remove_mint_quote(&self, quote_id: String) -> Result<(), FfiError> {
196        self.inner
197            .remove_mint_quote(&quote_id)
198            .await
199            .map_err(|e| FfiError::Database { msg: e.to_string() })
200    }
201
202    // Melt Quote Management
203    async fn add_melt_quote(&self, quote: MeltQuote) -> Result<(), FfiError> {
204        let cdk_quote = quote.try_into()?;
205        self.inner
206            .add_melt_quote(cdk_quote)
207            .await
208            .map_err(|e| FfiError::Database { msg: e.to_string() })
209    }
210
211    async fn get_melt_quote(&self, quote_id: String) -> Result<Option<MeltQuote>, FfiError> {
212        let result = self
213            .inner
214            .get_melt_quote(&quote_id)
215            .await
216            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
217        Ok(result.map(|q| q.into()))
218    }
219
220    async fn get_melt_quotes(&self) -> Result<Vec<MeltQuote>, FfiError> {
221        let result = self
222            .inner
223            .get_melt_quotes()
224            .await
225            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
226        Ok(result.into_iter().map(|q| q.into()).collect())
227    }
228
229    async fn remove_melt_quote(&self, quote_id: String) -> Result<(), FfiError> {
230        self.inner
231            .remove_melt_quote(&quote_id)
232            .await
233            .map_err(|e| FfiError::Database { msg: e.to_string() })
234    }
235
236    // Keys Management
237    async fn add_keys(&self, keyset: KeySet) -> Result<(), FfiError> {
238        // Convert FFI KeySet to cdk::nuts::KeySet
239        let cdk_keyset: cdk::nuts::KeySet = keyset.try_into()?;
240        self.inner
241            .add_keys(cdk_keyset)
242            .await
243            .map_err(|e| FfiError::Database { msg: e.to_string() })
244    }
245
246    async fn get_keys(&self, id: Id) -> Result<Option<Keys>, FfiError> {
247        let cdk_id = id.into();
248        let result = self
249            .inner
250            .get_keys(&cdk_id)
251            .await
252            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
253        Ok(result.map(Into::into))
254    }
255
256    async fn remove_keys(&self, id: Id) -> Result<(), FfiError> {
257        let cdk_id = id.into();
258        self.inner
259            .remove_keys(&cdk_id)
260            .await
261            .map_err(|e| FfiError::Database { msg: e.to_string() })
262    }
263
264    // Proof Management
265    async fn update_proofs(
266        &self,
267        added: Vec<ProofInfo>,
268        removed_ys: Vec<PublicKey>,
269    ) -> Result<(), FfiError> {
270        // Convert FFI types to CDK types
271        let cdk_added: Result<Vec<cdk::types::ProofInfo>, FfiError> = added
272            .into_iter()
273            .map(|info| {
274                Ok::<cdk::types::ProofInfo, FfiError>(cdk::types::ProofInfo {
275                    proof: info.proof.try_into()?,
276                    y: info.y.try_into()?,
277                    mint_url: info.mint_url.try_into()?,
278                    state: info.state.into(),
279                    spending_condition: info
280                        .spending_condition
281                        .map(|sc| sc.try_into())
282                        .transpose()?,
283                    unit: info.unit.into(),
284                })
285            })
286            .collect();
287        let cdk_added = cdk_added?;
288
289        let cdk_removed_ys: Result<Vec<cdk::nuts::PublicKey>, FfiError> =
290            removed_ys.into_iter().map(|pk| pk.try_into()).collect();
291        let cdk_removed_ys = cdk_removed_ys?;
292
293        self.inner
294            .update_proofs(cdk_added, cdk_removed_ys)
295            .await
296            .map_err(|e| FfiError::Database { msg: e.to_string() })
297    }
298
299    async fn get_proofs(
300        &self,
301        mint_url: Option<MintUrl>,
302        unit: Option<CurrencyUnit>,
303        state: Option<Vec<ProofState>>,
304        spending_conditions: Option<Vec<SpendingConditions>>,
305    ) -> Result<Vec<ProofInfo>, FfiError> {
306        let cdk_mint_url = mint_url.map(|u| u.try_into()).transpose()?;
307        let cdk_unit = unit.map(Into::into);
308        let cdk_state = state.map(|s| s.into_iter().map(Into::into).collect());
309        let cdk_spending_conditions: Option<Vec<cdk::nuts::SpendingConditions>> =
310            spending_conditions
311                .map(|sc| {
312                    sc.into_iter()
313                        .map(|c| c.try_into())
314                        .collect::<Result<Vec<_>, FfiError>>()
315                })
316                .transpose()?;
317
318        let result = self
319            .inner
320            .get_proofs(cdk_mint_url, cdk_unit, cdk_state, cdk_spending_conditions)
321            .await
322            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
323
324        Ok(result.into_iter().map(Into::into).collect())
325    }
326
327    async fn get_balance(
328        &self,
329        mint_url: Option<MintUrl>,
330        unit: Option<CurrencyUnit>,
331        state: Option<Vec<ProofState>>,
332    ) -> Result<u64, FfiError> {
333        let cdk_mint_url = mint_url.map(|u| u.try_into()).transpose()?;
334        let cdk_unit = unit.map(Into::into);
335        let cdk_state = state.map(|s| s.into_iter().map(Into::into).collect());
336
337        self.inner
338            .get_balance(cdk_mint_url, cdk_unit, cdk_state)
339            .await
340            .map_err(|e| FfiError::Database { msg: e.to_string() })
341    }
342
343    async fn update_proofs_state(
344        &self,
345        ys: Vec<PublicKey>,
346        state: ProofState,
347    ) -> Result<(), FfiError> {
348        let cdk_ys: Result<Vec<cdk::nuts::PublicKey>, FfiError> =
349            ys.into_iter().map(|pk| pk.try_into()).collect();
350        let cdk_ys = cdk_ys?;
351        let cdk_state = state.into();
352
353        self.inner
354            .update_proofs_state(cdk_ys, cdk_state)
355            .await
356            .map_err(|e| FfiError::Database { msg: e.to_string() })
357    }
358
359    // Keyset Counter Management
360    async fn increment_keyset_counter(&self, keyset_id: Id, count: u32) -> Result<u32, FfiError> {
361        let cdk_id = keyset_id.into();
362        self.inner
363            .increment_keyset_counter(&cdk_id, count)
364            .await
365            .map_err(|e| FfiError::Database { msg: e.to_string() })
366    }
367
368    // Transaction Management
369    async fn add_transaction(&self, transaction: Transaction) -> Result<(), FfiError> {
370        // Convert FFI Transaction to CDK Transaction using TryFrom
371        let cdk_transaction: cdk::wallet::types::Transaction = transaction.try_into()?;
372
373        self.inner
374            .add_transaction(cdk_transaction)
375            .await
376            .map_err(|e| FfiError::Database { msg: e.to_string() })
377    }
378
379    async fn get_transaction(
380        &self,
381        transaction_id: TransactionId,
382    ) -> Result<Option<Transaction>, FfiError> {
383        let cdk_id = transaction_id.try_into()?;
384        let result = self
385            .inner
386            .get_transaction(cdk_id)
387            .await
388            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
389        Ok(result.map(Into::into))
390    }
391
392    async fn list_transactions(
393        &self,
394        mint_url: Option<MintUrl>,
395        direction: Option<TransactionDirection>,
396        unit: Option<CurrencyUnit>,
397    ) -> Result<Vec<Transaction>, FfiError> {
398        let cdk_mint_url = mint_url.map(|u| u.try_into()).transpose()?;
399        let cdk_direction = direction.map(Into::into);
400        let cdk_unit = unit.map(Into::into);
401
402        let result = self
403            .inner
404            .list_transactions(cdk_mint_url, cdk_direction, cdk_unit)
405            .await
406            .map_err(|e| FfiError::Database { msg: e.to_string() })?;
407
408        Ok(result.into_iter().map(Into::into).collect())
409    }
410
411    async fn remove_transaction(&self, transaction_id: TransactionId) -> Result<(), FfiError> {
412        let cdk_id = transaction_id.try_into()?;
413        self.inner
414            .remove_transaction(cdk_id)
415            .await
416            .map_err(|e| FfiError::Database { msg: e.to_string() })
417    }
418}