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