cdk_redb/wallet/
mod.rs

1//! Redb Wallet
2
3use std::cmp::Ordering;
4use std::collections::HashMap;
5use std::path::Path;
6use std::str::FromStr;
7use std::sync::Arc;
8
9use async_trait::async_trait;
10use cdk_common::common::ProofInfo;
11use cdk_common::database::WalletDatabase;
12use cdk_common::mint_url::MintUrl;
13use cdk_common::util::unix_time;
14use cdk_common::wallet::{self, MintQuote, Transaction, TransactionDirection, TransactionId};
15use cdk_common::{
16    database, CurrencyUnit, Id, KeySet, KeySetInfo, Keys, MintInfo, PublicKey, SpendingConditions,
17    State,
18};
19use redb::{Database, MultimapTableDefinition, ReadableTable, TableDefinition};
20use tracing::instrument;
21
22use super::error::Error;
23use crate::migrations::migrate_00_to_01;
24use crate::wallet::migrations::migrate_01_to_02;
25
26mod migrations;
27
28// <Mint_url, Info>
29const MINTS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mints_table");
30// <Mint_Url, Keyset_id>
31const MINT_KEYSETS_TABLE: MultimapTableDefinition<&str, &[u8]> =
32    MultimapTableDefinition::new("mint_keysets");
33// <Keyset_id, KeysetInfo>
34const KEYSETS_TABLE: TableDefinition<&[u8], &str> = TableDefinition::new("keysets");
35// <Quote_id, quote>
36const MINT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_quotes");
37// <Quote_id, quote>
38const MELT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("melt_quotes");
39const MINT_KEYS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_keys");
40// <Y, Proof Info>
41const PROOFS_TABLE: TableDefinition<&[u8], &str> = TableDefinition::new("proofs");
42const CONFIG_TABLE: TableDefinition<&str, &str> = TableDefinition::new("config");
43const KEYSET_COUNTER: TableDefinition<&str, u32> = TableDefinition::new("keyset_counter");
44// <Transaction_id, Transaction>
45const TRANSACTIONS_TABLE: TableDefinition<&[u8], &str> = TableDefinition::new("transactions");
46
47const DATABASE_VERSION: u32 = 2;
48
49/// Wallet Redb Database
50#[derive(Debug, Clone)]
51pub struct WalletRedbDatabase {
52    db: Arc<Database>,
53}
54
55impl WalletRedbDatabase {
56    /// Create new [`WalletRedbDatabase`]
57    pub fn new(path: &Path) -> Result<Self, Error> {
58        {
59            let db = Arc::new(Database::create(path)?);
60
61            let db_version: Option<String>;
62            {
63                // Check database version
64                let read_txn = db.begin_read()?;
65                let table = read_txn.open_table(CONFIG_TABLE);
66
67                db_version = match table {
68                    Ok(table) => table.get("db_version")?.map(|v| v.value().to_string()),
69                    Err(_) => None,
70                };
71            }
72
73            match db_version {
74                Some(db_version) => {
75                    let mut current_file_version = u32::from_str(&db_version)?;
76                    tracing::info!("Current file version {}", current_file_version);
77
78                    match current_file_version.cmp(&DATABASE_VERSION) {
79                        Ordering::Less => {
80                            tracing::info!(
81                                "Database needs to be upgraded at {} current is {}",
82                                current_file_version,
83                                DATABASE_VERSION
84                            );
85                            if current_file_version == 0 {
86                                current_file_version = migrate_00_to_01(Arc::clone(&db))?;
87                            }
88
89                            if current_file_version == 1 {
90                                current_file_version = migrate_01_to_02(Arc::clone(&db))?;
91                            }
92
93                            if current_file_version != DATABASE_VERSION {
94                                tracing::warn!(
95                                    "Database upgrade did not complete at {} current is {}",
96                                    current_file_version,
97                                    DATABASE_VERSION
98                                );
99                                return Err(Error::UnknownDatabaseVersion);
100                            }
101
102                            let write_txn = db.begin_write()?;
103                            {
104                                let mut table = write_txn.open_table(CONFIG_TABLE)?;
105
106                                table
107                                    .insert("db_version", DATABASE_VERSION.to_string().as_str())?;
108                            }
109
110                            write_txn.commit()?;
111                        }
112                        Ordering::Equal => {
113                            tracing::info!("Database is at current version {}", DATABASE_VERSION);
114                        }
115                        Ordering::Greater => {
116                            tracing::warn!(
117                                "Database upgrade did not complete at {} current is {}",
118                                current_file_version,
119                                DATABASE_VERSION
120                            );
121                            return Err(Error::UnknownDatabaseVersion);
122                        }
123                    }
124                }
125                None => {
126                    let write_txn = db.begin_write()?;
127                    {
128                        let mut table = write_txn.open_table(CONFIG_TABLE)?;
129                        // Open all tables to init a new db
130                        let _ = write_txn.open_table(MINTS_TABLE)?;
131                        let _ = write_txn.open_multimap_table(MINT_KEYSETS_TABLE)?;
132                        let _ = write_txn.open_table(KEYSETS_TABLE)?;
133                        let _ = write_txn.open_table(MINT_QUOTES_TABLE)?;
134                        let _ = write_txn.open_table(MELT_QUOTES_TABLE)?;
135                        let _ = write_txn.open_table(MINT_KEYS_TABLE)?;
136                        let _ = write_txn.open_table(PROOFS_TABLE)?;
137                        let _ = write_txn.open_table(KEYSET_COUNTER)?;
138                        let _ = write_txn.open_table(TRANSACTIONS_TABLE)?;
139                        table.insert("db_version", DATABASE_VERSION.to_string().as_str())?;
140                    }
141
142                    write_txn.commit()?;
143                }
144            }
145            drop(db);
146        }
147
148        let db = Database::create(path)?;
149
150        Ok(Self { db: Arc::new(db) })
151    }
152}
153
154#[async_trait]
155impl WalletDatabase for WalletRedbDatabase {
156    type Err = database::Error;
157
158    #[instrument(skip(self))]
159    async fn add_mint(
160        &self,
161        mint_url: MintUrl,
162        mint_info: Option<MintInfo>,
163    ) -> Result<(), Self::Err> {
164        let write_txn = self.db.begin_write().map_err(Error::from)?;
165
166        {
167            let mut table = write_txn.open_table(MINTS_TABLE).map_err(Error::from)?;
168            table
169                .insert(
170                    mint_url.to_string().as_str(),
171                    serde_json::to_string(&mint_info)
172                        .map_err(Error::from)?
173                        .as_str(),
174                )
175                .map_err(Error::from)?;
176        }
177        write_txn.commit().map_err(Error::from)?;
178
179        Ok(())
180    }
181
182    #[instrument(skip(self))]
183    async fn remove_mint(&self, mint_url: MintUrl) -> Result<(), Self::Err> {
184        let write_txn = self.db.begin_write().map_err(Error::from)?;
185
186        {
187            let mut table = write_txn.open_table(MINTS_TABLE).map_err(Error::from)?;
188            table
189                .remove(mint_url.to_string().as_str())
190                .map_err(Error::from)?;
191        }
192        write_txn.commit().map_err(Error::from)?;
193
194        Ok(())
195    }
196
197    #[instrument(skip(self))]
198    async fn get_mint(&self, mint_url: MintUrl) -> Result<Option<MintInfo>, Self::Err> {
199        let read_txn = self.db.begin_read().map_err(Into::<Error>::into)?;
200        let table = read_txn.open_table(MINTS_TABLE).map_err(Error::from)?;
201
202        if let Some(mint_info) = table
203            .get(mint_url.to_string().as_str())
204            .map_err(Error::from)?
205        {
206            return Ok(serde_json::from_str(mint_info.value()).map_err(Error::from)?);
207        }
208
209        Ok(None)
210    }
211
212    #[instrument(skip(self))]
213    async fn get_mints(&self) -> Result<HashMap<MintUrl, Option<MintInfo>>, Self::Err> {
214        let read_txn = self.db.begin_read().map_err(Error::from)?;
215        let table = read_txn.open_table(MINTS_TABLE).map_err(Error::from)?;
216        let mints = table
217            .iter()
218            .map_err(Error::from)?
219            .flatten()
220            .map(|(mint, mint_info)| {
221                (
222                    MintUrl::from_str(mint.value()).unwrap(),
223                    serde_json::from_str(mint_info.value()).ok(),
224                )
225            })
226            .collect();
227
228        Ok(mints)
229    }
230
231    #[instrument(skip(self))]
232    async fn update_mint_url(
233        &self,
234        old_mint_url: MintUrl,
235        new_mint_url: MintUrl,
236    ) -> Result<(), Self::Err> {
237        // Update proofs table
238        {
239            let proofs = self
240                .get_proofs(Some(old_mint_url.clone()), None, None, None)
241                .await
242                .map_err(Error::from)?;
243
244            // Proofs with new url
245            let updated_proofs: Vec<ProofInfo> = proofs
246                .clone()
247                .into_iter()
248                .map(|mut p| {
249                    p.mint_url = new_mint_url.clone();
250                    p
251                })
252                .collect();
253
254            if !updated_proofs.is_empty() {
255                self.update_proofs(updated_proofs, vec![]).await?;
256            }
257        }
258
259        // Update mint quotes
260        {
261            let quotes = self.get_mint_quotes().await?;
262
263            let unix_time = unix_time();
264
265            let quotes: Vec<MintQuote> = quotes
266                .into_iter()
267                .filter_map(|mut q| {
268                    if q.expiry < unix_time {
269                        q.mint_url = new_mint_url.clone();
270                        Some(q)
271                    } else {
272                        None
273                    }
274                })
275                .collect();
276
277            for quote in quotes {
278                self.add_mint_quote(quote).await?;
279            }
280        }
281
282        Ok(())
283    }
284
285    #[instrument(skip(self))]
286    async fn add_mint_keysets(
287        &self,
288        mint_url: MintUrl,
289        keysets: Vec<KeySetInfo>,
290    ) -> Result<(), Self::Err> {
291        let write_txn = self.db.begin_write().map_err(Error::from)?;
292
293        {
294            let mut table = write_txn
295                .open_multimap_table(MINT_KEYSETS_TABLE)
296                .map_err(Error::from)?;
297            let mut keysets_table = write_txn.open_table(KEYSETS_TABLE).map_err(Error::from)?;
298
299            for keyset in keysets {
300                table
301                    .insert(
302                        mint_url.to_string().as_str(),
303                        keyset.id.to_bytes().as_slice(),
304                    )
305                    .map_err(Error::from)?;
306
307                keysets_table
308                    .insert(
309                        keyset.id.to_bytes().as_slice(),
310                        serde_json::to_string(&keyset)
311                            .map_err(Error::from)?
312                            .as_str(),
313                    )
314                    .map_err(Error::from)?;
315            }
316        }
317        write_txn.commit().map_err(Error::from)?;
318
319        Ok(())
320    }
321
322    #[instrument(skip(self))]
323    async fn get_mint_keysets(
324        &self,
325        mint_url: MintUrl,
326    ) -> Result<Option<Vec<KeySetInfo>>, Self::Err> {
327        let read_txn = self.db.begin_read().map_err(Into::<Error>::into)?;
328        let table = read_txn
329            .open_multimap_table(MINT_KEYSETS_TABLE)
330            .map_err(Error::from)?;
331
332        let keyset_ids = table
333            .get(mint_url.to_string().as_str())
334            .map_err(Error::from)?
335            .flatten()
336            .map(|k| Id::from_bytes(k.value()))
337            .collect::<Result<Vec<_>, _>>()?;
338
339        let mut keysets = vec![];
340
341        let keysets_t = read_txn.open_table(KEYSETS_TABLE).map_err(Error::from)?;
342
343        for keyset_id in keyset_ids {
344            if let Some(keyset) = keysets_t
345                .get(keyset_id.to_bytes().as_slice())
346                .map_err(Error::from)?
347            {
348                let keyset = serde_json::from_str(keyset.value()).map_err(Error::from)?;
349
350                keysets.push(keyset);
351            }
352        }
353
354        match keysets.is_empty() {
355            true => Ok(None),
356            false => Ok(Some(keysets)),
357        }
358    }
359
360    #[instrument(skip(self), fields(keyset_id = %keyset_id))]
361    async fn get_keyset_by_id(&self, keyset_id: &Id) -> Result<Option<KeySetInfo>, Self::Err> {
362        let read_txn = self.db.begin_read().map_err(Into::<Error>::into)?;
363        let table = read_txn.open_table(KEYSETS_TABLE).map_err(Error::from)?;
364
365        match table
366            .get(keyset_id.to_bytes().as_slice())
367            .map_err(Error::from)?
368        {
369            Some(keyset) => {
370                let keyset: KeySetInfo =
371                    serde_json::from_str(keyset.value()).map_err(Error::from)?;
372
373                Ok(Some(keyset))
374            }
375            None => Ok(None),
376        }
377    }
378
379    #[instrument(skip_all)]
380    async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), Self::Err> {
381        let write_txn = self.db.begin_write().map_err(Error::from)?;
382
383        {
384            let mut table = write_txn
385                .open_table(MINT_QUOTES_TABLE)
386                .map_err(Error::from)?;
387            table
388                .insert(
389                    quote.id.as_str(),
390                    serde_json::to_string(&quote).map_err(Error::from)?.as_str(),
391                )
392                .map_err(Error::from)?;
393        }
394
395        write_txn.commit().map_err(Error::from)?;
396
397        Ok(())
398    }
399
400    #[instrument(skip_all)]
401    async fn get_mint_quote(&self, quote_id: &str) -> Result<Option<MintQuote>, Self::Err> {
402        let read_txn = self.db.begin_read().map_err(Into::<Error>::into)?;
403        let table = read_txn
404            .open_table(MINT_QUOTES_TABLE)
405            .map_err(Error::from)?;
406
407        if let Some(mint_info) = table.get(quote_id).map_err(Error::from)? {
408            return Ok(serde_json::from_str(mint_info.value()).map_err(Error::from)?);
409        }
410
411        Ok(None)
412    }
413
414    #[instrument(skip_all)]
415    async fn get_mint_quotes(&self) -> Result<Vec<MintQuote>, Self::Err> {
416        let read_txn = self.db.begin_read().map_err(Into::<Error>::into)?;
417        let table = read_txn
418            .open_table(MINT_QUOTES_TABLE)
419            .map_err(Error::from)?;
420
421        Ok(table
422            .iter()
423            .map_err(Error::from)?
424            .flatten()
425            .flat_map(|(_id, quote)| serde_json::from_str(quote.value()))
426            .collect())
427    }
428
429    #[instrument(skip_all)]
430    async fn remove_mint_quote(&self, quote_id: &str) -> Result<(), Self::Err> {
431        let write_txn = self.db.begin_write().map_err(Error::from)?;
432
433        {
434            let mut table = write_txn
435                .open_table(MINT_QUOTES_TABLE)
436                .map_err(Error::from)?;
437            table.remove(quote_id).map_err(Error::from)?;
438        }
439
440        write_txn.commit().map_err(Error::from)?;
441
442        Ok(())
443    }
444
445    #[instrument(skip_all)]
446    async fn add_melt_quote(&self, quote: wallet::MeltQuote) -> Result<(), Self::Err> {
447        let write_txn = self.db.begin_write().map_err(Error::from)?;
448
449        {
450            let mut table = write_txn
451                .open_table(MELT_QUOTES_TABLE)
452                .map_err(Error::from)?;
453            table
454                .insert(
455                    quote.id.as_str(),
456                    serde_json::to_string(&quote).map_err(Error::from)?.as_str(),
457                )
458                .map_err(Error::from)?;
459        }
460
461        write_txn.commit().map_err(Error::from)?;
462
463        Ok(())
464    }
465
466    #[instrument(skip_all)]
467    async fn get_melt_quote(&self, quote_id: &str) -> Result<Option<wallet::MeltQuote>, Self::Err> {
468        let read_txn = self.db.begin_read().map_err(Error::from)?;
469        let table = read_txn
470            .open_table(MELT_QUOTES_TABLE)
471            .map_err(Error::from)?;
472
473        if let Some(mint_info) = table.get(quote_id).map_err(Error::from)? {
474            return Ok(serde_json::from_str(mint_info.value()).map_err(Error::from)?);
475        }
476
477        Ok(None)
478    }
479
480    #[instrument(skip_all)]
481    async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err> {
482        let write_txn = self.db.begin_write().map_err(Error::from)?;
483
484        {
485            let mut table = write_txn
486                .open_table(MELT_QUOTES_TABLE)
487                .map_err(Error::from)?;
488            table.remove(quote_id).map_err(Error::from)?;
489        }
490
491        write_txn.commit().map_err(Error::from)?;
492
493        Ok(())
494    }
495
496    #[instrument(skip_all)]
497    async fn add_keys(&self, keyset: KeySet) -> Result<(), Self::Err> {
498        let write_txn = self.db.begin_write().map_err(Error::from)?;
499
500        keyset.verify_id()?;
501
502        {
503            let mut table = write_txn.open_table(MINT_KEYS_TABLE).map_err(Error::from)?;
504            table
505                .insert(
506                    keyset.id.to_string().as_str(),
507                    serde_json::to_string(&keyset.keys)
508                        .map_err(Error::from)?
509                        .as_str(),
510                )
511                .map_err(Error::from)?;
512        }
513
514        write_txn.commit().map_err(Error::from)?;
515
516        Ok(())
517    }
518
519    #[instrument(skip(self), fields(keyset_id = %keyset_id))]
520    async fn get_keys(&self, keyset_id: &Id) -> Result<Option<Keys>, Self::Err> {
521        let read_txn = self.db.begin_read().map_err(Error::from)?;
522        let table = read_txn.open_table(MINT_KEYS_TABLE).map_err(Error::from)?;
523
524        if let Some(mint_info) = table
525            .get(keyset_id.to_string().as_str())
526            .map_err(Error::from)?
527        {
528            return Ok(serde_json::from_str(mint_info.value()).map_err(Error::from)?);
529        }
530
531        Ok(None)
532    }
533
534    #[instrument(skip(self), fields(keyset_id = %keyset_id))]
535    async fn remove_keys(&self, keyset_id: &Id) -> Result<(), Self::Err> {
536        let write_txn = self.db.begin_write().map_err(Error::from)?;
537
538        {
539            let mut table = write_txn.open_table(MINT_KEYS_TABLE).map_err(Error::from)?;
540
541            table
542                .remove(keyset_id.to_string().as_str())
543                .map_err(Error::from)?;
544        }
545
546        write_txn.commit().map_err(Error::from)?;
547
548        Ok(())
549    }
550
551    #[instrument(skip(self, added, deleted_ys))]
552    async fn update_proofs(
553        &self,
554        added: Vec<ProofInfo>,
555        deleted_ys: Vec<PublicKey>,
556    ) -> Result<(), Self::Err> {
557        let write_txn = self.db.begin_write().map_err(Error::from)?;
558
559        {
560            let mut table = write_txn.open_table(PROOFS_TABLE).map_err(Error::from)?;
561
562            for proof_info in added.iter() {
563                table
564                    .insert(
565                        proof_info.y.to_bytes().as_slice(),
566                        serde_json::to_string(&proof_info)
567                            .map_err(Error::from)?
568                            .as_str(),
569                    )
570                    .map_err(Error::from)?;
571            }
572
573            for y in deleted_ys.iter() {
574                table.remove(y.to_bytes().as_slice()).map_err(Error::from)?;
575            }
576        }
577        write_txn.commit().map_err(Error::from)?;
578
579        Ok(())
580    }
581
582    #[instrument(skip_all)]
583    async fn get_proofs(
584        &self,
585        mint_url: Option<MintUrl>,
586        unit: Option<CurrencyUnit>,
587        state: Option<Vec<State>>,
588        spending_conditions: Option<Vec<SpendingConditions>>,
589    ) -> Result<Vec<ProofInfo>, Self::Err> {
590        let read_txn = self.db.begin_read().map_err(Error::from)?;
591
592        let table = read_txn.open_table(PROOFS_TABLE).map_err(Error::from)?;
593
594        let proofs: Vec<ProofInfo> = table
595            .iter()
596            .map_err(Error::from)?
597            .flatten()
598            .filter_map(|(_k, v)| {
599                let mut proof = None;
600
601                if let Ok(proof_info) = serde_json::from_str::<ProofInfo>(v.value()) {
602                    if proof_info.matches_conditions(&mint_url, &unit, &state, &spending_conditions)
603                    {
604                        proof = Some(proof_info)
605                    }
606                }
607
608                proof
609            })
610            .collect();
611
612        Ok(proofs)
613    }
614
615    async fn update_proofs_state(
616        &self,
617        ys: Vec<PublicKey>,
618        state: State,
619    ) -> Result<(), database::Error> {
620        let read_txn = self.db.begin_read().map_err(Error::from)?;
621        let table = read_txn.open_table(PROOFS_TABLE).map_err(Error::from)?;
622
623        let write_txn = self.db.begin_write().map_err(Error::from)?;
624
625        for y in ys {
626            let y_slice = y.to_bytes();
627            let proof = table
628                .get(y_slice.as_slice())
629                .map_err(Error::from)?
630                .ok_or(Error::UnknownY)?;
631
632            let mut proof_info =
633                serde_json::from_str::<ProofInfo>(proof.value()).map_err(Error::from)?;
634
635            proof_info.state = state;
636
637            {
638                let mut table = write_txn.open_table(PROOFS_TABLE).map_err(Error::from)?;
639                table
640                    .insert(
641                        y_slice.as_slice(),
642                        serde_json::to_string(&proof_info)
643                            .map_err(Error::from)?
644                            .as_str(),
645                    )
646                    .map_err(Error::from)?;
647            }
648        }
649
650        write_txn.commit().map_err(Error::from)?;
651
652        Ok(())
653    }
654
655    #[instrument(skip(self), fields(keyset_id = %keyset_id))]
656    async fn increment_keyset_counter(&self, keyset_id: &Id, count: u32) -> Result<(), Self::Err> {
657        let write_txn = self.db.begin_write().map_err(Error::from)?;
658
659        let current_counter;
660        {
661            let table = write_txn.open_table(KEYSET_COUNTER).map_err(Error::from)?;
662            let counter = table
663                .get(keyset_id.to_string().as_str())
664                .map_err(Error::from)?;
665
666            current_counter = match counter {
667                Some(c) => c.value(),
668                None => 0,
669            };
670        }
671
672        {
673            let mut table = write_txn.open_table(KEYSET_COUNTER).map_err(Error::from)?;
674            let new_counter = current_counter + count;
675
676            table
677                .insert(keyset_id.to_string().as_str(), new_counter)
678                .map_err(Error::from)?;
679        }
680        write_txn.commit().map_err(Error::from)?;
681
682        Ok(())
683    }
684
685    #[instrument(skip(self), fields(keyset_id = %keyset_id))]
686    async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<Option<u32>, Self::Err> {
687        let read_txn = self.db.begin_read().map_err(Error::from)?;
688        let table = read_txn.open_table(KEYSET_COUNTER).map_err(Error::from)?;
689
690        let counter = table
691            .get(keyset_id.to_string().as_str())
692            .map_err(Error::from)?;
693
694        Ok(counter.map(|c| c.value()))
695    }
696
697    #[instrument(skip(self))]
698    async fn add_transaction(&self, transaction: Transaction) -> Result<(), Self::Err> {
699        let write_txn = self.db.begin_write().map_err(Error::from)?;
700
701        {
702            let mut table = write_txn
703                .open_table(TRANSACTIONS_TABLE)
704                .map_err(Error::from)?;
705            table
706                .insert(
707                    transaction.id().as_slice(),
708                    serde_json::to_string(&transaction)
709                        .map_err(Error::from)?
710                        .as_str(),
711                )
712                .map_err(Error::from)?;
713        }
714
715        write_txn.commit().map_err(Error::from)?;
716
717        Ok(())
718    }
719
720    #[instrument(skip(self))]
721    async fn get_transaction(
722        &self,
723        transaction_id: TransactionId,
724    ) -> Result<Option<Transaction>, Self::Err> {
725        let read_txn = self.db.begin_read().map_err(Error::from)?;
726        let table = read_txn
727            .open_table(TRANSACTIONS_TABLE)
728            .map_err(Error::from)?;
729
730        if let Some(transaction) = table.get(transaction_id.as_slice()).map_err(Error::from)? {
731            return Ok(serde_json::from_str(transaction.value()).map_err(Error::from)?);
732        }
733
734        Ok(None)
735    }
736
737    #[instrument(skip(self))]
738    async fn list_transactions(
739        &self,
740        mint_url: Option<MintUrl>,
741        direction: Option<TransactionDirection>,
742        unit: Option<CurrencyUnit>,
743    ) -> Result<Vec<Transaction>, Self::Err> {
744        let read_txn = self.db.begin_read().map_err(Error::from)?;
745
746        let table = read_txn
747            .open_table(TRANSACTIONS_TABLE)
748            .map_err(Error::from)?;
749
750        let transactions: Vec<Transaction> = table
751            .iter()
752            .map_err(Error::from)?
753            .flatten()
754            .filter_map(|(_k, v)| {
755                let mut transaction = None;
756
757                if let Ok(tx) = serde_json::from_str::<Transaction>(v.value()) {
758                    if tx.matches_conditions(&mint_url, &direction, &unit) {
759                        transaction = Some(tx)
760                    }
761                }
762
763                transaction
764            })
765            .collect();
766
767        Ok(transactions)
768    }
769
770    #[instrument(skip(self))]
771    async fn remove_transaction(&self, transaction_id: TransactionId) -> Result<(), Self::Err> {
772        let write_txn = self.db.begin_write().map_err(Error::from)?;
773
774        {
775            let mut table = write_txn
776                .open_table(TRANSACTIONS_TABLE)
777                .map_err(Error::from)?;
778            table
779                .remove(transaction_id.as_slice())
780                .map_err(Error::from)?;
781        }
782
783        write_txn.commit().map_err(Error::from)?;
784
785        Ok(())
786    }
787}