cdk_sqlite/mint/
mod.rs

1//! SQLite Mint
2
3use std::collections::{HashMap, HashSet};
4use std::path::Path;
5use std::str::FromStr;
6
7use async_trait::async_trait;
8use bitcoin::bip32::DerivationPath;
9use cdk_common::common::{PaymentProcessorKey, QuoteTTL};
10use cdk_common::database::{self, MintDatabase};
11use cdk_common::mint::{self, MintKeySetInfo, MintQuote};
12use cdk_common::nut00::ProofsMethods;
13use cdk_common::nut05::QuoteState;
14use cdk_common::secret::Secret;
15use cdk_common::{
16    Amount, BlindSignature, BlindSignatureDleq, CurrencyUnit, Id, MeltBolt11Request,
17    MeltQuoteState, MintInfo, MintQuoteState, PaymentMethod, Proof, Proofs, PublicKey, SecretKey,
18    State,
19};
20use error::Error;
21use lightning_invoice::Bolt11Invoice;
22use sqlx::sqlite::SqliteRow;
23use sqlx::{Executor, Pool, Row, Sqlite};
24use uuid::fmt::Hyphenated;
25use uuid::Uuid;
26
27use crate::common::create_sqlite_pool;
28
29#[cfg(feature = "auth")]
30mod auth;
31pub mod error;
32pub mod memory;
33
34#[cfg(feature = "auth")]
35pub use auth::MintSqliteAuthDatabase;
36
37/// Mint SQLite Database
38#[derive(Debug, Clone)]
39pub struct MintSqliteDatabase {
40    pool: Pool<Sqlite>,
41}
42
43impl MintSqliteDatabase {
44    /// Check if any proofs are spent
45    async fn check_for_spent_proofs<'e, 'c: 'e, E>(
46        &self,
47        transaction: E,
48        ys: &[PublicKey],
49    ) -> Result<bool, database::Error>
50    where
51        E: Executor<'c, Database = Sqlite>,
52    {
53        if ys.is_empty() {
54            return Ok(false);
55        }
56
57        let check_sql = format!(
58            "SELECT state FROM proof WHERE y IN ({}) AND state = 'SPENT'",
59            std::iter::repeat("?")
60                .take(ys.len())
61                .collect::<Vec<_>>()
62                .join(",")
63        );
64
65        let spent_count = ys
66            .iter()
67            .fold(sqlx::query(&check_sql), |query, y| {
68                query.bind(y.to_bytes().to_vec())
69            })
70            .fetch_all(transaction)
71            .await
72            .map_err(Error::from)?
73            .len();
74
75        Ok(spent_count > 0)
76    }
77
78    /// Create new [`MintSqliteDatabase`]
79    #[cfg(not(feature = "sqlcipher"))]
80    pub async fn new<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
81        Ok(Self {
82            pool: create_sqlite_pool(path.as_ref().to_str().ok_or(Error::InvalidDbPath)?).await?,
83        })
84    }
85
86    /// Create new [`MintSqliteDatabase`]
87    #[cfg(feature = "sqlcipher")]
88    pub async fn new<P: AsRef<Path>>(path: P, password: String) -> Result<Self, Error> {
89        Ok(Self {
90            pool: create_sqlite_pool(
91                path.as_ref().to_str().ok_or(Error::InvalidDbPath)?,
92                password,
93            )
94            .await?,
95        })
96    }
97
98    /// Migrate [`MintSqliteDatabase`]
99    pub async fn migrate(&self) {
100        sqlx::migrate!("./src/mint/migrations")
101            .run(&self.pool)
102            .await
103            .expect("Could not run migrations");
104    }
105}
106
107#[async_trait]
108impl MintDatabase for MintSqliteDatabase {
109    type Err = database::Error;
110
111    async fn set_active_keyset(&self, unit: CurrencyUnit, id: Id) -> Result<(), Self::Err> {
112        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
113
114        let update_res = sqlx::query(
115            r#"
116UPDATE keyset
117SET active=FALSE
118WHERE unit IS ?;
119        "#,
120        )
121        .bind(unit.to_string())
122        .execute(&mut *transaction)
123        .await;
124
125        match update_res {
126            Ok(_) => (),
127            Err(err) => {
128                tracing::error!("SQLite Could not update keyset");
129                if let Err(err) = transaction.rollback().await {
130                    tracing::error!("Could not rollback sql transaction: {}", err);
131                }
132
133                return Err(Error::from(err).into());
134            }
135        };
136
137        let update_res = sqlx::query(
138            r#"
139UPDATE keyset
140SET active=TRUE
141WHERE unit IS ?
142AND id IS ?;
143        "#,
144        )
145        .bind(unit.to_string())
146        .bind(id.to_string())
147        .execute(&mut *transaction)
148        .await;
149
150        match update_res {
151            Ok(_) => (),
152            Err(err) => {
153                tracing::error!("SQLite Could not update keyset");
154                if let Err(err) = transaction.rollback().await {
155                    tracing::error!("Could not rollback sql transaction: {}", err);
156                }
157
158                return Err(Error::from(err).into());
159            }
160        };
161
162        transaction.commit().await.map_err(Error::from)?;
163
164        Ok(())
165    }
166
167    async fn get_active_keyset_id(&self, unit: &CurrencyUnit) -> Result<Option<Id>, Self::Err> {
168        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
169
170        let rec = sqlx::query(
171            r#"
172SELECT id
173FROM keyset
174WHERE active = 1
175AND unit IS ?
176        "#,
177        )
178        .bind(unit.to_string())
179        .fetch_one(&mut *transaction)
180        .await;
181
182        let rec = match rec {
183            Ok(rec) => {
184                transaction.commit().await.map_err(Error::from)?;
185                rec
186            }
187            Err(err) => match err {
188                sqlx::Error::RowNotFound => {
189                    transaction.commit().await.map_err(Error::from)?;
190                    return Ok(None);
191                }
192                _ => {
193                    return {
194                        if let Err(err) = transaction.rollback().await {
195                            tracing::error!("Could not rollback sql transaction: {}", err);
196                        }
197                        Err(Error::SQLX(err).into())
198                    }
199                }
200            },
201        };
202
203        Ok(Some(
204            Id::from_str(rec.try_get("id").map_err(Error::from)?).map_err(Error::from)?,
205        ))
206    }
207
208    async fn get_active_keysets(&self) -> Result<HashMap<CurrencyUnit, Id>, Self::Err> {
209        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
210
211        let recs = sqlx::query(
212            r#"
213SELECT id, unit
214FROM keyset
215WHERE active = 1
216        "#,
217        )
218        .fetch_all(&mut *transaction)
219        .await;
220
221        match recs {
222            Ok(recs) => {
223                transaction.commit().await.map_err(Error::from)?;
224
225                let keysets = recs
226                    .iter()
227                    .filter_map(|r| match Id::from_str(r.get("id")) {
228                        Ok(id) => Some((
229                            CurrencyUnit::from_str(r.get::<'_, &str, &str>("unit")).unwrap(),
230                            id,
231                        )),
232                        Err(_) => None,
233                    })
234                    .collect();
235                Ok(keysets)
236            }
237            Err(err) => {
238                tracing::error!("SQLite could not get active keyset");
239                if let Err(err) = transaction.rollback().await {
240                    tracing::error!("Could not rollback sql transaction: {}", err);
241                }
242                Err(Error::from(err).into())
243            }
244        }
245    }
246
247    async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), Self::Err> {
248        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
249
250        let res = sqlx::query(
251            r#"
252INSERT INTO mint_quote
253(id, amount, unit, request, state, expiry, request_lookup_id, pubkey)
254VALUES (?, ?, ?, ?, ?, ?, ?, ?)
255ON CONFLICT(id) DO UPDATE SET
256    amount = excluded.amount,
257    unit = excluded.unit,
258    request = excluded.request,
259    state = excluded.state,
260    expiry = excluded.expiry,
261    request_lookup_id = excluded.request_lookup_id
262ON CONFLICT(request_lookup_id) DO UPDATE SET
263    amount = excluded.amount,
264    unit = excluded.unit,
265    request = excluded.request,
266    state = excluded.state,
267    expiry = excluded.expiry,
268    id = excluded.id
269        "#,
270        )
271        .bind(quote.id.to_string())
272        .bind(u64::from(quote.amount) as i64)
273        .bind(quote.unit.to_string())
274        .bind(quote.request)
275        .bind(quote.state.to_string())
276        .bind(quote.expiry as i64)
277        .bind(quote.request_lookup_id)
278        .bind(quote.pubkey.map(|p| p.to_string()))
279        .execute(&mut *transaction)
280        .await;
281
282        match res {
283            Ok(_) => {
284                transaction.commit().await.map_err(Error::from)?;
285                Ok(())
286            }
287            Err(err) => {
288                tracing::error!("SQLite Could not update keyset");
289                if let Err(err) = transaction.rollback().await {
290                    tracing::error!("Could not rollback sql transaction: {}", err);
291                }
292
293                Err(Error::from(err).into())
294            }
295        }
296    }
297
298    async fn get_mint_quote(&self, quote_id: &Uuid) -> Result<Option<MintQuote>, Self::Err> {
299        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
300        let rec = sqlx::query(
301            r#"
302SELECT *
303FROM mint_quote
304WHERE id=?;
305        "#,
306        )
307        .bind(quote_id.as_hyphenated())
308        .fetch_one(&mut *transaction)
309        .await;
310
311        match rec {
312            Ok(rec) => {
313                transaction.commit().await.map_err(Error::from)?;
314                Ok(Some(sqlite_row_to_mint_quote(rec)?))
315            }
316            Err(err) => match err {
317                sqlx::Error::RowNotFound => {
318                    transaction.commit().await.map_err(Error::from)?;
319                    Ok(None)
320                }
321                _ => {
322                    if let Err(err) = transaction.rollback().await {
323                        tracing::error!("Could not rollback sql transaction: {}", err);
324                    }
325                    Err(Error::SQLX(err).into())
326                }
327            },
328        }
329    }
330
331    async fn get_mint_quote_by_request(
332        &self,
333        request: &str,
334    ) -> Result<Option<MintQuote>, Self::Err> {
335        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
336        let rec = sqlx::query(
337            r#"
338SELECT *
339FROM mint_quote
340WHERE request=?;
341        "#,
342        )
343        .bind(request)
344        .fetch_one(&mut *transaction)
345        .await;
346
347        match rec {
348            Ok(rec) => {
349                transaction.commit().await.map_err(Error::from)?;
350                Ok(Some(sqlite_row_to_mint_quote(rec)?))
351            }
352            Err(err) => match err {
353                sqlx::Error::RowNotFound => {
354                    transaction.commit().await.map_err(Error::from)?;
355                    Ok(None)
356                }
357                _ => {
358                    if let Err(err) = transaction.rollback().await {
359                        tracing::error!("Could not rollback sql transaction: {}", err);
360                    }
361                    Err(Error::SQLX(err).into())
362                }
363            },
364        }
365    }
366
367    async fn get_mint_quote_by_request_lookup_id(
368        &self,
369        request_lookup_id: &str,
370    ) -> Result<Option<MintQuote>, Self::Err> {
371        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
372
373        let rec = sqlx::query(
374            r#"
375SELECT *
376FROM mint_quote
377WHERE request_lookup_id=?;
378        "#,
379        )
380        .bind(request_lookup_id)
381        .fetch_one(&mut *transaction)
382        .await;
383
384        match rec {
385            Ok(rec) => {
386                transaction.commit().await.map_err(Error::from)?;
387
388                Ok(Some(sqlite_row_to_mint_quote(rec)?))
389            }
390            Err(err) => match err {
391                sqlx::Error::RowNotFound => {
392                    transaction.commit().await.map_err(Error::from)?;
393                    Ok(None)
394                }
395                _ => {
396                    if let Err(err) = transaction.rollback().await {
397                        tracing::error!("Could not rollback sql transaction: {}", err);
398                    }
399                    Err(Error::SQLX(err).into())
400                }
401            },
402        }
403    }
404
405    async fn update_mint_quote_state(
406        &self,
407        quote_id: &Uuid,
408        state: MintQuoteState,
409    ) -> Result<MintQuoteState, Self::Err> {
410        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
411
412        let rec = sqlx::query(
413            r#"
414SELECT *
415FROM mint_quote
416WHERE id=?;
417        "#,
418        )
419        .bind(quote_id.as_hyphenated())
420        .fetch_one(&mut *transaction)
421        .await;
422        let quote = match rec {
423            Ok(row) => sqlite_row_to_mint_quote(row)?,
424            Err(err) => {
425                tracing::error!("SQLite Could not update keyset");
426                if let Err(err) = transaction.rollback().await {
427                    tracing::error!("Could not rollback sql transaction: {}", err);
428                }
429
430                return Err(Error::from(err).into());
431            }
432        };
433
434        let update = sqlx::query(
435            r#"
436        UPDATE mint_quote SET state = ? WHERE id = ?
437        "#,
438        )
439        .bind(state.to_string())
440        .bind(quote_id.as_hyphenated())
441        .execute(&mut *transaction)
442        .await;
443
444        match update {
445            Ok(_) => {
446                transaction.commit().await.map_err(Error::from)?;
447                Ok(quote.state)
448            }
449            Err(err) => {
450                tracing::error!("SQLite Could not update keyset");
451                if let Err(err) = transaction.rollback().await {
452                    tracing::error!("Could not rollback sql transaction: {}", err);
453                }
454
455                return Err(Error::from(err).into());
456            }
457        }
458    }
459
460    async fn get_mint_quotes(&self) -> Result<Vec<MintQuote>, Self::Err> {
461        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
462        let rec = sqlx::query(
463            r#"
464SELECT *
465FROM mint_quote
466        "#,
467        )
468        .fetch_all(&mut *transaction)
469        .await;
470
471        match rec {
472            Ok(rows) => {
473                transaction.commit().await.map_err(Error::from)?;
474                let mint_quotes = rows
475                    .into_iter()
476                    .map(sqlite_row_to_mint_quote)
477                    .collect::<Result<Vec<MintQuote>, _>>()?;
478
479                Ok(mint_quotes)
480            }
481            Err(err) => {
482                tracing::error!("SQLite get mint quotes");
483                if let Err(err) = transaction.rollback().await {
484                    tracing::error!("Could not rollback sql transaction: {}", err);
485                }
486
487                return Err(Error::from(err).into());
488            }
489        }
490    }
491
492    async fn get_mint_quotes_with_state(
493        &self,
494        state: MintQuoteState,
495    ) -> Result<Vec<MintQuote>, Self::Err> {
496        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
497        let rec = sqlx::query(
498            r#"
499SELECT *
500FROM mint_quote
501WHERE state = ?
502        "#,
503        )
504        .bind(state.to_string())
505        .fetch_all(&mut *transaction)
506        .await;
507
508        match rec {
509            Ok(rows) => {
510                transaction.commit().await.map_err(Error::from)?;
511                let mint_quotes = rows
512                    .into_iter()
513                    .map(sqlite_row_to_mint_quote)
514                    .collect::<Result<Vec<MintQuote>, _>>()?;
515
516                Ok(mint_quotes)
517            }
518            Err(err) => {
519                tracing::error!("SQLite get mint quotes with state");
520                if let Err(err) = transaction.rollback().await {
521                    tracing::error!("Could not rollback sql transaction: {}", err);
522                }
523
524                return Err(Error::from(err).into());
525            }
526        }
527    }
528
529    async fn remove_mint_quote(&self, quote_id: &Uuid) -> Result<(), Self::Err> {
530        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
531
532        let res = sqlx::query(
533            r#"
534DELETE FROM mint_quote
535WHERE id=?
536        "#,
537        )
538        .bind(quote_id.as_hyphenated())
539        .execute(&mut *transaction)
540        .await;
541
542        match res {
543            Ok(_) => {
544                transaction.commit().await.map_err(Error::from)?;
545
546                Ok(())
547            }
548            Err(err) => {
549                tracing::error!("SQLite Could not remove mint quote");
550                if let Err(err) = transaction.rollback().await {
551                    tracing::error!("Could not rollback sql transaction: {}", err);
552                }
553
554                Err(Error::from(err).into())
555            }
556        }
557    }
558
559    async fn add_melt_quote(&self, quote: mint::MeltQuote) -> Result<(), Self::Err> {
560        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
561        let res = sqlx::query(
562            r#"
563INSERT INTO melt_quote
564(id, unit, amount, request, fee_reserve, state, expiry, payment_preimage, request_lookup_id, msat_to_pay)
565VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
566ON CONFLICT(id) DO UPDATE SET
567    unit = excluded.unit,
568    amount = excluded.amount,
569    request = excluded.request,
570    fee_reserve = excluded.fee_reserve,
571    state = excluded.state,
572    expiry = excluded.expiry,
573    payment_preimage = excluded.payment_preimage,
574    request_lookup_id = excluded.request_lookup_id,
575    msat_to_pay = excluded.msat_to_pay
576ON CONFLICT(request_lookup_id) DO UPDATE SET
577    unit = excluded.unit,
578    amount = excluded.amount,
579    request = excluded.request,
580    fee_reserve = excluded.fee_reserve,
581    state = excluded.state,
582    expiry = excluded.expiry,
583    payment_preimage = excluded.payment_preimage,
584    id = excluded.id;
585        "#,
586        )
587        .bind(quote.id.to_string())
588        .bind(quote.unit.to_string())
589        .bind(u64::from(quote.amount) as i64)
590        .bind(quote.request)
591        .bind(u64::from(quote.fee_reserve) as i64)
592        .bind(quote.state.to_string())
593        .bind(quote.expiry as i64)
594        .bind(quote.payment_preimage)
595        .bind(quote.request_lookup_id)
596        .bind(quote.msat_to_pay.map(|a| u64::from(a) as i64))
597        .execute(&mut *transaction)
598        .await;
599
600        match res {
601            Ok(_) => {
602                transaction.commit().await.map_err(Error::from)?;
603
604                Ok(())
605            }
606            Err(err) => {
607                tracing::error!("SQLite Could not remove mint quote");
608                if let Err(err) = transaction.rollback().await {
609                    tracing::error!("Could not rollback sql transaction: {}", err);
610                }
611
612                Err(Error::from(err).into())
613            }
614        }
615    }
616    async fn get_melt_quote(&self, quote_id: &Uuid) -> Result<Option<mint::MeltQuote>, Self::Err> {
617        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
618        let rec = sqlx::query(
619            r#"
620SELECT *
621FROM melt_quote
622WHERE id=?;
623        "#,
624        )
625        .bind(quote_id.as_hyphenated())
626        .fetch_one(&mut *transaction)
627        .await;
628
629        match rec {
630            Ok(rec) => {
631                transaction.commit().await.map_err(Error::from)?;
632
633                Ok(Some(sqlite_row_to_melt_quote(rec)?))
634            }
635            Err(err) => match err {
636                sqlx::Error::RowNotFound => {
637                    transaction.commit().await.map_err(Error::from)?;
638                    Ok(None)
639                }
640                _ => {
641                    if let Err(err) = transaction.rollback().await {
642                        tracing::error!("Could not rollback sql transaction: {}", err);
643                    }
644
645                    Err(Error::SQLX(err).into())
646                }
647            },
648        }
649    }
650
651    async fn get_melt_quotes(&self) -> Result<Vec<mint::MeltQuote>, Self::Err> {
652        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
653        let rec = sqlx::query(
654            r#"
655SELECT *
656FROM melt_quote
657        "#,
658        )
659        .fetch_all(&mut *transaction)
660        .await
661        .map_err(Error::from);
662
663        match rec {
664            Ok(rec) => {
665                let melt_quotes = rec
666                    .into_iter()
667                    .map(sqlite_row_to_melt_quote)
668                    .collect::<Result<Vec<mint::MeltQuote>, _>>()?;
669                Ok(melt_quotes)
670            }
671            Err(err) => {
672                if let Err(err) = transaction.rollback().await {
673                    tracing::error!("Could not rollback sql transaction: {}", err);
674                }
675
676                Err(err.into())
677            }
678        }
679    }
680
681    async fn update_melt_quote_state(
682        &self,
683        quote_id: &Uuid,
684        state: MeltQuoteState,
685    ) -> Result<MeltQuoteState, Self::Err> {
686        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
687
688        let rec = sqlx::query(
689            r#"
690SELECT *
691FROM melt_quote
692WHERE id=?;
693        "#,
694        )
695        .bind(quote_id.as_hyphenated())
696        .fetch_one(&mut *transaction)
697        .await;
698
699        let quote = match rec {
700            Ok(rec) => sqlite_row_to_melt_quote(rec)?,
701            Err(err) => {
702                tracing::error!("SQLite Could not update keyset");
703                if let Err(err) = transaction.rollback().await {
704                    tracing::error!("Could not rollback sql transaction: {}", err);
705                }
706
707                return Err(Error::from(err).into());
708            }
709        };
710
711        let rec = sqlx::query(
712            r#"
713        UPDATE melt_quote SET state = ? WHERE id = ?
714        "#,
715        )
716        .bind(state.to_string())
717        .bind(quote_id.as_hyphenated())
718        .execute(&mut *transaction)
719        .await;
720
721        match rec {
722            Ok(_) => {
723                transaction.commit().await.map_err(Error::from)?;
724            }
725            Err(err) => {
726                tracing::error!("SQLite Could not update melt quote");
727                if let Err(err) = transaction.rollback().await {
728                    tracing::error!("Could not rollback sql transaction: {}", err);
729                }
730
731                return Err(Error::from(err).into());
732            }
733        };
734
735        Ok(quote.state)
736    }
737
738    async fn remove_melt_quote(&self, quote_id: &Uuid) -> Result<(), Self::Err> {
739        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
740        let res = sqlx::query(
741            r#"
742DELETE FROM melt_quote
743WHERE id=?
744        "#,
745        )
746        .bind(quote_id.as_hyphenated())
747        .execute(&mut *transaction)
748        .await;
749
750        match res {
751            Ok(_) => {
752                transaction.commit().await.map_err(Error::from)?;
753                Ok(())
754            }
755            Err(err) => {
756                tracing::error!("SQLite Could not update melt quote");
757                if let Err(err) = transaction.rollback().await {
758                    tracing::error!("Could not rollback sql transaction: {}", err);
759                }
760
761                Err(Error::from(err).into())
762            }
763        }
764    }
765
766    async fn add_keyset_info(&self, keyset: MintKeySetInfo) -> Result<(), Self::Err> {
767        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
768        let res = sqlx::query(
769            r#"
770INSERT INTO keyset
771(id, unit, active, valid_from, valid_to, derivation_path, max_order, input_fee_ppk, derivation_path_index)
772VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
773ON CONFLICT(id) DO UPDATE SET
774    unit = excluded.unit,
775    active = excluded.active,
776    valid_from = excluded.valid_from,
777    valid_to = excluded.valid_to,
778    derivation_path = excluded.derivation_path,
779    max_order = excluded.max_order,
780    input_fee_ppk = excluded.input_fee_ppk,
781    derivation_path_index = excluded.derivation_path_index
782        "#,
783        )
784        .bind(keyset.id.to_string())
785        .bind(keyset.unit.to_string())
786        .bind(keyset.active)
787        .bind(keyset.valid_from as i64)
788        .bind(keyset.valid_to.map(|v| v as i64))
789        .bind(keyset.derivation_path.to_string())
790        .bind(keyset.max_order)
791        .bind(keyset.input_fee_ppk as i64)
792            .bind(keyset.derivation_path_index)
793        .execute(&mut *transaction)
794        .await;
795
796        match res {
797            Ok(_) => {
798                transaction.commit().await.map_err(Error::from)?;
799                Ok(())
800            }
801            Err(err) => {
802                tracing::error!("SQLite could not add keyset info");
803                if let Err(err) = transaction.rollback().await {
804                    tracing::error!("Could not rollback sql transaction: {}", err);
805                }
806
807                Err(Error::from(err).into())
808            }
809        }
810    }
811
812    async fn get_keyset_info(&self, id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err> {
813        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
814        let rec = sqlx::query(
815            r#"
816SELECT *
817FROM keyset
818WHERE id=?;
819        "#,
820        )
821        .bind(id.to_string())
822        .fetch_one(&mut *transaction)
823        .await;
824
825        match rec {
826            Ok(rec) => {
827                transaction.commit().await.map_err(Error::from)?;
828                Ok(Some(sqlite_row_to_keyset_info(rec)?))
829            }
830            Err(err) => match err {
831                sqlx::Error::RowNotFound => {
832                    transaction.commit().await.map_err(Error::from)?;
833                    return Ok(None);
834                }
835                _ => {
836                    tracing::error!("SQLite could not get keyset info");
837                    if let Err(err) = transaction.rollback().await {
838                        tracing::error!("Could not rollback sql transaction: {}", err);
839                    }
840                    return Err(Error::SQLX(err).into());
841                }
842            },
843        }
844    }
845
846    async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err> {
847        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
848        let recs = sqlx::query(
849            r#"
850SELECT *
851FROM keyset;
852        "#,
853        )
854        .fetch_all(&mut *transaction)
855        .await
856        .map_err(Error::from);
857
858        match recs {
859            Ok(recs) => {
860                transaction.commit().await.map_err(Error::from)?;
861                Ok(recs
862                    .into_iter()
863                    .map(sqlite_row_to_keyset_info)
864                    .collect::<Result<_, _>>()?)
865            }
866            Err(err) => {
867                tracing::error!("SQLite could not get keyset info");
868                if let Err(err) = transaction.rollback().await {
869                    tracing::error!("Could not rollback sql transaction: {}", err);
870                }
871                Err(err.into())
872            }
873        }
874    }
875
876    async fn add_proofs(&self, proofs: Proofs, quote_id: Option<Uuid>) -> Result<(), Self::Err> {
877        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
878        for proof in proofs {
879            let result = sqlx::query(
880                r#"
881INSERT OR IGNORE INTO proof
882(y, amount, keyset_id, secret, c, witness, state, quote_id)
883VALUES (?, ?, ?, ?, ?, ?, ?, ?);
884        "#,
885            )
886            .bind(proof.y()?.to_bytes().to_vec())
887            .bind(u64::from(proof.amount) as i64)
888            .bind(proof.keyset_id.to_string())
889            .bind(proof.secret.to_string())
890            .bind(proof.c.to_bytes().to_vec())
891            .bind(proof.witness.map(|w| serde_json::to_string(&w).unwrap()))
892            .bind("UNSPENT")
893            .bind(quote_id.map(|q| q.hyphenated()))
894            .execute(&mut *transaction)
895            .await;
896
897            // We still need to check for foreign key constraint errors
898            if let Err(err) = result {
899                if let sqlx::Error::Database(db_err) = &err {
900                    if db_err.message().contains("FOREIGN KEY constraint failed") {
901                        tracing::error!(
902                            "Foreign key constraint failed when adding proof: {:?}",
903                            err
904                        );
905                        transaction.rollback().await.map_err(Error::from)?;
906                        return Err(database::Error::InvalidKeysetId);
907                    }
908                }
909
910                // For any other error, roll back and return the error
911                tracing::error!("Error adding proof: {:?}", err);
912                transaction.rollback().await.map_err(Error::from)?;
913                return Err(Error::from(err).into());
914            }
915        }
916        transaction.commit().await.map_err(Error::from)?;
917
918        Ok(())
919    }
920
921    async fn remove_proofs(
922        &self,
923        ys: &[PublicKey],
924        _quote_id: Option<Uuid>,
925    ) -> Result<(), Self::Err> {
926        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
927
928        if self.check_for_spent_proofs(&mut *transaction, ys).await? {
929            transaction.rollback().await.map_err(Error::from)?;
930            return Err(Self::Err::AttemptRemoveSpentProof);
931        }
932
933        // If no proofs are spent, proceed with deletion
934        let delete_sql = format!(
935            "DELETE FROM proof WHERE y IN ({})",
936            std::iter::repeat("?")
937                .take(ys.len())
938                .collect::<Vec<_>>()
939                .join(",")
940        );
941
942        ys.iter()
943            .fold(sqlx::query(&delete_sql), |query, y| {
944                query.bind(y.to_bytes().to_vec())
945            })
946            .execute(&mut *transaction)
947            .await
948            .map_err(Error::from)?;
949
950        transaction.commit().await.map_err(Error::from)?;
951        Ok(())
952    }
953
954    async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result<Vec<Option<Proof>>, Self::Err> {
955        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
956
957        let sql = format!(
958            "SELECT * FROM proof WHERE y IN ({})",
959            "?,".repeat(ys.len()).trim_end_matches(',')
960        );
961
962        let mut proofs = ys
963            .iter()
964            .fold(sqlx::query(&sql), |query, y| {
965                query.bind(y.to_bytes().to_vec())
966            })
967            .fetch_all(&mut *transaction)
968            .await
969            .map_err(|err| {
970                tracing::error!("SQLite could not get state of proof: {err:?}");
971                Error::SQLX(err)
972            })?
973            .into_iter()
974            .map(|row| {
975                PublicKey::from_slice(row.get("y"))
976                    .map_err(Error::from)
977                    .and_then(|y| sqlite_row_to_proof(row).map(|proof| (y, proof)))
978            })
979            .collect::<Result<HashMap<_, _>, _>>()?;
980
981        Ok(ys.iter().map(|y| proofs.remove(y)).collect())
982    }
983
984    async fn get_proof_ys_by_quote_id(&self, quote_id: &Uuid) -> Result<Vec<PublicKey>, Self::Err> {
985        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
986
987        let rec = sqlx::query(
988            r#"
989SELECT *
990FROM proof
991WHERE quote_id=?;
992        "#,
993        )
994        .bind(quote_id.as_hyphenated())
995        .fetch_all(&mut *transaction)
996        .await;
997
998        let ys = match rec {
999            Ok(rec) => {
1000                transaction.commit().await.map_err(Error::from)?;
1001
1002                let proofs = rec
1003                    .into_iter()
1004                    .map(sqlite_row_to_proof)
1005                    .collect::<Result<Vec<Proof>, _>>()?;
1006
1007                proofs.ys()?
1008            }
1009            Err(err) => match err {
1010                sqlx::Error::RowNotFound => {
1011                    transaction.commit().await.map_err(Error::from)?;
1012
1013                    vec![]
1014                }
1015                _ => {
1016                    if let Err(err) = transaction.rollback().await {
1017                        tracing::error!("Could not rollback sql transaction: {}", err);
1018                    }
1019                    return Err(Error::SQLX(err).into());
1020                }
1021            },
1022        };
1023
1024        Ok(ys)
1025    }
1026
1027    async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err> {
1028        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1029
1030        let sql = format!(
1031            "SELECT y, state FROM proof WHERE y IN ({})",
1032            "?,".repeat(ys.len()).trim_end_matches(',')
1033        );
1034
1035        let mut current_states = ys
1036            .iter()
1037            .fold(sqlx::query(&sql), |query, y| {
1038                query.bind(y.to_bytes().to_vec())
1039            })
1040            .fetch_all(&mut *transaction)
1041            .await
1042            .map_err(|err| {
1043                tracing::error!("SQLite could not get state of proof: {err:?}");
1044                Error::SQLX(err)
1045            })?
1046            .into_iter()
1047            .map(|row| {
1048                PublicKey::from_slice(row.get("y"))
1049                    .map_err(Error::from)
1050                    .and_then(|y| {
1051                        let state: String = row.get("state");
1052                        State::from_str(&state)
1053                            .map_err(Error::from)
1054                            .map(|state| (y, state))
1055                    })
1056            })
1057            .collect::<Result<HashMap<_, _>, _>>()?;
1058
1059        Ok(ys.iter().map(|y| current_states.remove(y)).collect())
1060    }
1061
1062    async fn get_proofs_by_keyset_id(
1063        &self,
1064        keyset_id: &Id,
1065    ) -> Result<(Proofs, Vec<Option<State>>), Self::Err> {
1066        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1067        let rec = sqlx::query(
1068            r#"
1069SELECT *
1070FROM proof
1071WHERE keyset_id=?;
1072        "#,
1073        )
1074        .bind(keyset_id.to_string())
1075        .fetch_all(&mut *transaction)
1076        .await;
1077
1078        match rec {
1079            Ok(rec) => {
1080                transaction.commit().await.map_err(Error::from)?;
1081                let mut proofs_for_id = vec![];
1082                let mut states = vec![];
1083
1084                for row in rec {
1085                    let (proof, state) = sqlite_row_to_proof_with_state(row)?;
1086
1087                    proofs_for_id.push(proof);
1088                    states.push(state);
1089                }
1090
1091                Ok((proofs_for_id, states))
1092            }
1093            Err(err) => {
1094                tracing::error!("SQLite could not get proofs by keysets id");
1095                if let Err(err) = transaction.rollback().await {
1096                    tracing::error!("Could not rollback sql transaction: {}", err);
1097                }
1098
1099                return Err(Error::from(err).into());
1100            }
1101        }
1102    }
1103
1104    async fn update_proofs_states(
1105        &self,
1106        ys: &[PublicKey],
1107        proofs_state: State,
1108    ) -> Result<Vec<Option<State>>, Self::Err> {
1109        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1110
1111        let sql = format!(
1112            "SELECT y, state FROM proof WHERE y IN ({})",
1113            "?,".repeat(ys.len()).trim_end_matches(',')
1114        );
1115
1116        let rows = ys
1117            .iter()
1118            .fold(sqlx::query(&sql), |query, y| {
1119                query.bind(y.to_bytes().to_vec())
1120            })
1121            .fetch_all(&mut *transaction)
1122            .await
1123            .map_err(|err| {
1124                tracing::error!("SQLite could not get state of proof: {err:?}");
1125                Error::SQLX(err)
1126            })?;
1127
1128        // Check if all proofs exist
1129        if rows.len() != ys.len() {
1130            transaction.rollback().await.map_err(Error::from)?;
1131            tracing::warn!("Attempted to update state of non-existent proof");
1132            return Err(database::Error::ProofNotFound);
1133        }
1134
1135        let mut current_states = rows
1136            .into_iter()
1137            .map(|row| {
1138                PublicKey::from_slice(row.get("y"))
1139                    .map_err(Error::from)
1140                    .and_then(|y| {
1141                        let state: String = row.get("state");
1142                        State::from_str(&state)
1143                            .map_err(Error::from)
1144                            .map(|state| (y, state))
1145                    })
1146            })
1147            .collect::<Result<HashMap<_, _>, _>>()?;
1148
1149        let states = current_states.values().collect::<HashSet<_>>();
1150
1151        if states.contains(&State::Spent) {
1152            transaction.rollback().await.map_err(Error::from)?;
1153            tracing::warn!("Attempted to update state of spent proof");
1154            return Err(database::Error::AttemptUpdateSpentProof);
1155        }
1156
1157        // If no proofs are spent, proceed with update
1158        let update_sql = format!(
1159            "UPDATE proof SET state = ? WHERE y IN ({})",
1160            "?,".repeat(ys.len()).trim_end_matches(',')
1161        );
1162
1163        ys.iter()
1164            .fold(
1165                sqlx::query(&update_sql).bind(proofs_state.to_string()),
1166                |query, y| query.bind(y.to_bytes().to_vec()),
1167            )
1168            .execute(&mut *transaction)
1169            .await
1170            .map_err(|err| {
1171                tracing::error!("SQLite could not update proof state: {err:?}");
1172                Error::SQLX(err)
1173            })?;
1174
1175        transaction.commit().await.map_err(Error::from)?;
1176
1177        Ok(ys.iter().map(|y| current_states.remove(y)).collect())
1178    }
1179
1180    async fn add_blind_signatures(
1181        &self,
1182        blinded_messages: &[PublicKey],
1183        blinded_signatures: &[BlindSignature],
1184        quote_id: Option<Uuid>,
1185    ) -> Result<(), Self::Err> {
1186        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1187        for (message, signature) in blinded_messages.iter().zip(blinded_signatures) {
1188            let res = sqlx::query(
1189                r#"
1190INSERT INTO blind_signature
1191(y, amount, keyset_id, c, quote_id, dleq_e, dleq_s)
1192VALUES (?, ?, ?, ?, ?, ?, ?);
1193        "#,
1194            )
1195            .bind(message.to_bytes().to_vec())
1196            .bind(u64::from(signature.amount) as i64)
1197            .bind(signature.keyset_id.to_string())
1198            .bind(signature.c.to_bytes().to_vec())
1199            .bind(quote_id.map(|q| q.hyphenated()))
1200            .bind(signature.dleq.as_ref().map(|dleq| dleq.e.to_secret_hex()))
1201            .bind(signature.dleq.as_ref().map(|dleq| dleq.s.to_secret_hex()))
1202            .execute(&mut *transaction)
1203            .await;
1204
1205            if let Err(err) = res {
1206                tracing::error!("SQLite could not add blind signature");
1207                if let Err(err) = transaction.rollback().await {
1208                    tracing::error!("Could not rollback sql transaction: {}", err);
1209                }
1210                return Err(Error::SQLX(err).into());
1211            }
1212        }
1213
1214        transaction.commit().await.map_err(Error::from)?;
1215
1216        Ok(())
1217    }
1218
1219    async fn get_blind_signatures(
1220        &self,
1221        blinded_messages: &[PublicKey],
1222    ) -> Result<Vec<Option<BlindSignature>>, Self::Err> {
1223        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1224
1225        let sql = format!(
1226            "SELECT * FROM blind_signature WHERE y IN ({})",
1227            "?,".repeat(blinded_messages.len()).trim_end_matches(',')
1228        );
1229
1230        let mut blinded_signatures = blinded_messages
1231            .iter()
1232            .fold(sqlx::query(&sql), |query, y| {
1233                query.bind(y.to_bytes().to_vec())
1234            })
1235            .fetch_all(&mut *transaction)
1236            .await
1237            .map_err(|err| {
1238                tracing::error!("SQLite could not get state of proof: {err:?}");
1239                Error::SQLX(err)
1240            })?
1241            .into_iter()
1242            .map(|row| {
1243                PublicKey::from_slice(row.get("y"))
1244                    .map_err(Error::from)
1245                    .and_then(|y| sqlite_row_to_blind_signature(row).map(|blinded| (y, blinded)))
1246            })
1247            .collect::<Result<HashMap<_, _>, _>>()?;
1248
1249        Ok(blinded_messages
1250            .iter()
1251            .map(|y| blinded_signatures.remove(y))
1252            .collect())
1253    }
1254
1255    async fn get_blind_signatures_for_keyset(
1256        &self,
1257        keyset_id: &Id,
1258    ) -> Result<Vec<BlindSignature>, Self::Err> {
1259        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1260
1261        let rec = sqlx::query(
1262            r#"
1263SELECT *
1264FROM blind_signature
1265WHERE keyset_id=?;
1266        "#,
1267        )
1268        .bind(keyset_id.to_string())
1269        .fetch_all(&mut *transaction)
1270        .await;
1271
1272        match rec {
1273            Ok(rec) => {
1274                transaction.commit().await.map_err(Error::from)?;
1275                let sigs = rec
1276                    .into_iter()
1277                    .map(sqlite_row_to_blind_signature)
1278                    .collect::<Result<Vec<BlindSignature>, _>>()?;
1279
1280                Ok(sigs)
1281            }
1282            Err(err) => {
1283                tracing::error!("SQLite could not get vlinf signatures for keyset");
1284                if let Err(err) = transaction.rollback().await {
1285                    tracing::error!("Could not rollback sql transaction: {}", err);
1286                }
1287
1288                return Err(Error::from(err).into());
1289            }
1290        }
1291    }
1292
1293    async fn add_melt_request(
1294        &self,
1295        melt_request: MeltBolt11Request<Uuid>,
1296        ln_key: PaymentProcessorKey,
1297    ) -> Result<(), Self::Err> {
1298        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1299
1300        let res = sqlx::query(
1301            r#"
1302INSERT INTO melt_request
1303(id, inputs, outputs, method, unit)
1304VALUES (?, ?, ?, ?, ?)
1305ON CONFLICT(id) DO UPDATE SET
1306    inputs = excluded.inputs,
1307    outputs = excluded.outputs,
1308    method = excluded.method,
1309    unit = excluded.unit
1310        "#,
1311        )
1312        .bind(melt_request.quote)
1313        .bind(serde_json::to_string(&melt_request.inputs)?)
1314        .bind(serde_json::to_string(&melt_request.outputs)?)
1315        .bind(ln_key.method.to_string())
1316        .bind(ln_key.unit.to_string())
1317        .execute(&mut *transaction)
1318        .await;
1319
1320        match res {
1321            Ok(_) => {
1322                transaction.commit().await.map_err(Error::from)?;
1323                Ok(())
1324            }
1325            Err(err) => {
1326                tracing::error!("SQLite Could not update keyset");
1327                if let Err(err) = transaction.rollback().await {
1328                    tracing::error!("Could not rollback sql transaction: {}", err);
1329                }
1330
1331                Err(Error::from(err).into())
1332            }
1333        }
1334    }
1335
1336    async fn get_melt_request(
1337        &self,
1338        quote_id: &Uuid,
1339    ) -> Result<Option<(MeltBolt11Request<Uuid>, PaymentProcessorKey)>, Self::Err> {
1340        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1341
1342        let rec = sqlx::query(
1343            r#"
1344SELECT *
1345FROM melt_request
1346WHERE id=?;
1347        "#,
1348        )
1349        .bind(quote_id.as_hyphenated())
1350        .fetch_one(&mut *transaction)
1351        .await;
1352
1353        match rec {
1354            Ok(rec) => {
1355                transaction.commit().await.map_err(Error::from)?;
1356
1357                let (request, key) = sqlite_row_to_melt_request(rec)?;
1358
1359                Ok(Some((request, key)))
1360            }
1361            Err(err) => match err {
1362                sqlx::Error::RowNotFound => {
1363                    transaction.commit().await.map_err(Error::from)?;
1364                    return Ok(None);
1365                }
1366                _ => {
1367                    return {
1368                        if let Err(err) = transaction.rollback().await {
1369                            tracing::error!("Could not rollback sql transaction: {}", err);
1370                        }
1371                        Err(Error::SQLX(err).into())
1372                    }
1373                }
1374            },
1375        }
1376    }
1377
1378    /// Get [`BlindSignature`]s for quote
1379    async fn get_blind_signatures_for_quote(
1380        &self,
1381        quote_id: &Uuid,
1382    ) -> Result<Vec<BlindSignature>, Self::Err> {
1383        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1384
1385        let recs = sqlx::query(
1386            r#"
1387SELECT *
1388FROM blind_signature
1389WHERE quote_id=?;
1390        "#,
1391        )
1392        .bind(quote_id.as_hyphenated())
1393        .fetch_all(&mut *transaction)
1394        .await;
1395
1396        match recs {
1397            Ok(recs) => {
1398                transaction.commit().await.map_err(Error::from)?;
1399
1400                let keysets = recs
1401                    .into_iter()
1402                    .map(sqlite_row_to_blind_signature)
1403                    .collect::<Result<Vec<_>, _>>()?;
1404                Ok(keysets)
1405            }
1406            Err(err) => {
1407                tracing::error!("SQLite could not get active keyset");
1408                if let Err(err) = transaction.rollback().await {
1409                    tracing::error!("Could not rollback sql transaction: {}", err);
1410                }
1411                Err(Error::from(err).into())
1412            }
1413        }
1414    }
1415
1416    async fn set_mint_info(&self, mint_info: MintInfo) -> Result<(), Self::Err> {
1417        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1418
1419        let res = sqlx::query(
1420            r#"
1421INSERT INTO config
1422(id, value)
1423VALUES (?, ?)
1424ON CONFLICT(id) DO UPDATE SET
1425    value = excluded.value
1426;
1427        "#,
1428        )
1429        .bind("mint_info")
1430        .bind(serde_json::to_string(&mint_info)?)
1431        .execute(&mut *transaction)
1432        .await;
1433
1434        match res {
1435            Ok(_) => {
1436                transaction.commit().await.map_err(Error::from)?;
1437                Ok(())
1438            }
1439            Err(err) => {
1440                tracing::error!("SQLite Could not update mint info");
1441                if let Err(err) = transaction.rollback().await {
1442                    tracing::error!("Could not rollback sql transaction: {}", err);
1443                }
1444
1445                Err(Error::from(err).into())
1446            }
1447        }
1448    }
1449    async fn get_mint_info(&self) -> Result<MintInfo, Self::Err> {
1450        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1451
1452        let rec = sqlx::query(
1453            r#"
1454SELECT *
1455FROM config
1456WHERE id=?;
1457        "#,
1458        )
1459        .bind("mint_info")
1460        .fetch_one(&mut *transaction)
1461        .await;
1462
1463        match rec {
1464            Ok(rec) => {
1465                transaction.commit().await.map_err(Error::from)?;
1466
1467                let value: String = rec.try_get("value").map_err(Error::from)?;
1468
1469                let mint_info = serde_json::from_str(&value)?;
1470
1471                Ok(mint_info)
1472            }
1473            Err(err) => match err {
1474                sqlx::Error::RowNotFound => {
1475                    transaction.commit().await.map_err(Error::from)?;
1476                    return Err(Error::UnknownMintInfo.into());
1477                }
1478                _ => {
1479                    return {
1480                        if let Err(err) = transaction.rollback().await {
1481                            tracing::error!("Could not rollback sql transaction: {}", err);
1482                        }
1483                        Err(Error::SQLX(err).into())
1484                    }
1485                }
1486            },
1487        }
1488    }
1489
1490    async fn set_quote_ttl(&self, quote_ttl: QuoteTTL) -> Result<(), Self::Err> {
1491        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1492
1493        let res = sqlx::query(
1494            r#"
1495INSERT INTO config
1496(id, value)
1497VALUES (?, ?)
1498ON CONFLICT(id) DO UPDATE SET
1499    value = excluded.value
1500;
1501        "#,
1502        )
1503        .bind("quote_ttl")
1504        .bind(serde_json::to_string(&quote_ttl)?)
1505        .execute(&mut *transaction)
1506        .await;
1507
1508        match res {
1509            Ok(_) => {
1510                transaction.commit().await.map_err(Error::from)?;
1511                Ok(())
1512            }
1513            Err(err) => {
1514                tracing::error!("SQLite Could not update mint info");
1515                if let Err(err) = transaction.rollback().await {
1516                    tracing::error!("Could not rollback sql transaction: {}", err);
1517                }
1518
1519                Err(Error::from(err).into())
1520            }
1521        }
1522    }
1523    async fn get_quote_ttl(&self) -> Result<QuoteTTL, Self::Err> {
1524        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
1525
1526        let rec = sqlx::query(
1527            r#"
1528SELECT *
1529FROM config
1530WHERE id=?;
1531        "#,
1532        )
1533        .bind("quote_ttl")
1534        .fetch_one(&mut *transaction)
1535        .await;
1536
1537        match rec {
1538            Ok(rec) => {
1539                transaction.commit().await.map_err(Error::from)?;
1540
1541                let value: String = rec.try_get("value").map_err(Error::from)?;
1542
1543                let quote_ttl = serde_json::from_str(&value)?;
1544
1545                Ok(quote_ttl)
1546            }
1547            Err(err) => match err {
1548                sqlx::Error::RowNotFound => {
1549                    transaction.commit().await.map_err(Error::from)?;
1550                    return Err(Error::UnknownQuoteTTL.into());
1551                }
1552                _ => {
1553                    return {
1554                        if let Err(err) = transaction.rollback().await {
1555                            tracing::error!("Could not rollback sql transaction: {}", err);
1556                        }
1557                        Err(Error::SQLX(err).into())
1558                    }
1559                }
1560            },
1561        }
1562    }
1563}
1564
1565fn sqlite_row_to_keyset_info(row: SqliteRow) -> Result<MintKeySetInfo, Error> {
1566    let row_id: String = row.try_get("id").map_err(Error::from)?;
1567    let row_unit: String = row.try_get("unit").map_err(Error::from)?;
1568    let row_active: bool = row.try_get("active").map_err(Error::from)?;
1569    let row_valid_from: i64 = row.try_get("valid_from").map_err(Error::from)?;
1570    let row_valid_to: Option<i64> = row.try_get("valid_to").map_err(Error::from)?;
1571    let row_derivation_path: String = row.try_get("derivation_path").map_err(Error::from)?;
1572    let row_max_order: u8 = row.try_get("max_order").map_err(Error::from)?;
1573    let row_keyset_ppk: Option<i64> = row.try_get("input_fee_ppk").ok();
1574    let row_derivation_path_index: Option<i64> =
1575        row.try_get("derivation_path_index").map_err(Error::from)?;
1576
1577    Ok(MintKeySetInfo {
1578        id: Id::from_str(&row_id).map_err(Error::from)?,
1579        unit: CurrencyUnit::from_str(&row_unit).map_err(Error::from)?,
1580        active: row_active,
1581        valid_from: row_valid_from as u64,
1582        valid_to: row_valid_to.map(|v| v as u64),
1583        derivation_path: DerivationPath::from_str(&row_derivation_path).map_err(Error::from)?,
1584        derivation_path_index: row_derivation_path_index.map(|d| d as u32),
1585        max_order: row_max_order,
1586        input_fee_ppk: row_keyset_ppk.unwrap_or(0) as u64,
1587    })
1588}
1589
1590fn sqlite_row_to_mint_quote(row: SqliteRow) -> Result<MintQuote, Error> {
1591    let row_id: Hyphenated = row.try_get("id").map_err(Error::from)?;
1592    let row_amount: i64 = row.try_get("amount").map_err(Error::from)?;
1593    let row_unit: String = row.try_get("unit").map_err(Error::from)?;
1594    let row_request: String = row.try_get("request").map_err(Error::from)?;
1595    let row_state: String = row.try_get("state").map_err(Error::from)?;
1596    let row_expiry: i64 = row.try_get("expiry").map_err(Error::from)?;
1597    let row_request_lookup_id: Option<String> =
1598        row.try_get("request_lookup_id").map_err(Error::from)?;
1599    let row_pubkey: Option<String> = row.try_get("pubkey").map_err(Error::from)?;
1600
1601    let request_lookup_id = match row_request_lookup_id {
1602        Some(id) => id,
1603        None => match Bolt11Invoice::from_str(&row_request) {
1604            Ok(invoice) => invoice.payment_hash().to_string(),
1605            Err(_) => row_request.clone(),
1606        },
1607    };
1608
1609    let pubkey = row_pubkey
1610        .map(|key| PublicKey::from_str(&key))
1611        .transpose()?;
1612
1613    Ok(MintQuote {
1614        id: row_id.into_uuid(),
1615        amount: Amount::from(row_amount as u64),
1616        unit: CurrencyUnit::from_str(&row_unit).map_err(Error::from)?,
1617        request: row_request,
1618        state: MintQuoteState::from_str(&row_state).map_err(Error::from)?,
1619        expiry: row_expiry as u64,
1620        request_lookup_id,
1621        pubkey,
1622    })
1623}
1624
1625fn sqlite_row_to_melt_quote(row: SqliteRow) -> Result<mint::MeltQuote, Error> {
1626    let row_id: Hyphenated = row.try_get("id").map_err(Error::from)?;
1627    let row_unit: String = row.try_get("unit").map_err(Error::from)?;
1628    let row_amount: i64 = row.try_get("amount").map_err(Error::from)?;
1629    let row_request: String = row.try_get("request").map_err(Error::from)?;
1630    let row_fee_reserve: i64 = row.try_get("fee_reserve").map_err(Error::from)?;
1631    let row_state: String = row.try_get("state").map_err(Error::from)?;
1632    let row_expiry: i64 = row.try_get("expiry").map_err(Error::from)?;
1633    let row_preimage: Option<String> = row.try_get("payment_preimage").map_err(Error::from)?;
1634    let row_request_lookup: Option<String> =
1635        row.try_get("request_lookup_id").map_err(Error::from)?;
1636
1637    let request_lookup_id = row_request_lookup.unwrap_or(row_request.clone());
1638
1639    let row_msat_to_pay: Option<i64> = row.try_get("msat_to_pay").map_err(Error::from)?;
1640
1641    Ok(mint::MeltQuote {
1642        id: row_id.into_uuid(),
1643        amount: Amount::from(row_amount as u64),
1644        unit: CurrencyUnit::from_str(&row_unit).map_err(Error::from)?,
1645        request: row_request,
1646        fee_reserve: Amount::from(row_fee_reserve as u64),
1647        state: QuoteState::from_str(&row_state)?,
1648        expiry: row_expiry as u64,
1649        payment_preimage: row_preimage,
1650        request_lookup_id,
1651        msat_to_pay: row_msat_to_pay.map(|a| Amount::from(a as u64)),
1652    })
1653}
1654
1655fn sqlite_row_to_proof(row: SqliteRow) -> Result<Proof, Error> {
1656    let row_amount: i64 = row.try_get("amount").map_err(Error::from)?;
1657    let keyset_id: String = row.try_get("keyset_id").map_err(Error::from)?;
1658    let row_secret: String = row.try_get("secret").map_err(Error::from)?;
1659    let row_c: Vec<u8> = row.try_get("c").map_err(Error::from)?;
1660    let row_witness: Option<String> = row.try_get("witness").map_err(Error::from)?;
1661
1662    Ok(Proof {
1663        amount: Amount::from(row_amount as u64),
1664        keyset_id: Id::from_str(&keyset_id)?,
1665        secret: Secret::from_str(&row_secret)?,
1666        c: PublicKey::from_slice(&row_c)?,
1667        witness: row_witness.and_then(|w| serde_json::from_str(&w).ok()),
1668        dleq: None,
1669    })
1670}
1671
1672fn sqlite_row_to_proof_with_state(row: SqliteRow) -> Result<(Proof, Option<State>), Error> {
1673    let row_amount: i64 = row.try_get("amount").map_err(Error::from)?;
1674    let keyset_id: String = row.try_get("keyset_id").map_err(Error::from)?;
1675    let row_secret: String = row.try_get("secret").map_err(Error::from)?;
1676    let row_c: Vec<u8> = row.try_get("c").map_err(Error::from)?;
1677    let row_witness: Option<String> = row.try_get("witness").map_err(Error::from)?;
1678
1679    let row_state: Option<String> = row.try_get("state").map_err(Error::from)?;
1680
1681    let state = row_state.and_then(|s| State::from_str(&s).ok());
1682
1683    Ok((
1684        Proof {
1685            amount: Amount::from(row_amount as u64),
1686            keyset_id: Id::from_str(&keyset_id)?,
1687            secret: Secret::from_str(&row_secret)?,
1688            c: PublicKey::from_slice(&row_c)?,
1689            witness: row_witness.and_then(|w| serde_json::from_str(&w).ok()),
1690            dleq: None,
1691        },
1692        state,
1693    ))
1694}
1695
1696fn sqlite_row_to_blind_signature(row: SqliteRow) -> Result<BlindSignature, Error> {
1697    let row_amount: i64 = row.try_get("amount").map_err(Error::from)?;
1698    let keyset_id: String = row.try_get("keyset_id").map_err(Error::from)?;
1699    let row_c: Vec<u8> = row.try_get("c").map_err(Error::from)?;
1700    let row_dleq_e: Option<String> = row.try_get("dleq_e").map_err(Error::from)?;
1701    let row_dleq_s: Option<String> = row.try_get("dleq_s").map_err(Error::from)?;
1702
1703    let dleq = match (row_dleq_e, row_dleq_s) {
1704        (Some(e), Some(s)) => Some(BlindSignatureDleq {
1705            e: SecretKey::from_hex(e)?,
1706            s: SecretKey::from_hex(s)?,
1707        }),
1708        _ => None,
1709    };
1710
1711    Ok(BlindSignature {
1712        amount: Amount::from(row_amount as u64),
1713        keyset_id: Id::from_str(&keyset_id)?,
1714        c: PublicKey::from_slice(&row_c)?,
1715        dleq,
1716    })
1717}
1718
1719fn sqlite_row_to_melt_request(
1720    row: SqliteRow,
1721) -> Result<(MeltBolt11Request<Uuid>, PaymentProcessorKey), Error> {
1722    let quote_id: Hyphenated = row.try_get("id").map_err(Error::from)?;
1723    let row_inputs: String = row.try_get("inputs").map_err(Error::from)?;
1724    let row_outputs: Option<String> = row.try_get("outputs").map_err(Error::from)?;
1725    let row_method: String = row.try_get("method").map_err(Error::from)?;
1726    let row_unit: String = row.try_get("unit").map_err(Error::from)?;
1727
1728    let melt_request = MeltBolt11Request {
1729        quote: quote_id.into_uuid(),
1730        inputs: serde_json::from_str(&row_inputs)?,
1731        outputs: row_outputs.and_then(|o| serde_json::from_str(&o).ok()),
1732    };
1733
1734    let ln_key = PaymentProcessorKey {
1735        unit: CurrencyUnit::from_str(&row_unit)?,
1736        method: PaymentMethod::from_str(&row_method)?,
1737    };
1738
1739    Ok((melt_request, ln_key))
1740}
1741
1742#[cfg(test)]
1743mod tests {
1744    use cdk_common::mint::MintKeySetInfo;
1745    use cdk_common::Amount;
1746
1747    use super::*;
1748
1749    #[tokio::test]
1750    async fn test_remove_spent_proofs() {
1751        let db = memory::empty().await.unwrap();
1752
1753        // Create a keyset and add it to the database
1754        let keyset_id = Id::from_str("00916bbf7ef91a36").unwrap();
1755        let keyset_info = MintKeySetInfo {
1756            id: keyset_id,
1757            unit: CurrencyUnit::Sat,
1758            active: true,
1759            valid_from: 0,
1760            valid_to: None,
1761            derivation_path: bitcoin::bip32::DerivationPath::from_str("m/0'/0'/0'").unwrap(),
1762            derivation_path_index: Some(0),
1763            max_order: 32,
1764            input_fee_ppk: 0,
1765        };
1766        db.add_keyset_info(keyset_info).await.unwrap();
1767
1768        let proofs = vec![
1769            Proof {
1770                amount: Amount::from(100),
1771                keyset_id,
1772                secret: Secret::generate(),
1773                c: SecretKey::generate().public_key(),
1774                witness: None,
1775                dleq: None,
1776            },
1777            Proof {
1778                amount: Amount::from(200),
1779                keyset_id,
1780                secret: Secret::generate(),
1781                c: SecretKey::generate().public_key(),
1782                witness: None,
1783                dleq: None,
1784            },
1785        ];
1786
1787        // Add proofs to database
1788        db.add_proofs(proofs.clone(), None).await.unwrap();
1789
1790        // Mark one proof as spent
1791        db.update_proofs_states(&[proofs[0].y().unwrap()], State::Spent)
1792            .await
1793            .unwrap();
1794
1795        // Try to remove both proofs - should fail because one is spent
1796        let result = db
1797            .remove_proofs(&[proofs[0].y().unwrap(), proofs[1].y().unwrap()], None)
1798            .await;
1799
1800        assert!(result.is_err());
1801        assert!(matches!(
1802            result.unwrap_err(),
1803            database::Error::AttemptRemoveSpentProof
1804        ));
1805
1806        // Verify both proofs still exist
1807        let states = db
1808            .get_proofs_states(&[proofs[0].y().unwrap(), proofs[1].y().unwrap()])
1809            .await
1810            .unwrap();
1811
1812        assert_eq!(states.len(), 2);
1813        assert_eq!(states[0], Some(State::Spent));
1814        assert_eq!(states[1], Some(State::Unspent));
1815    }
1816
1817    #[tokio::test]
1818    async fn test_update_spent_proofs() {
1819        let db = memory::empty().await.unwrap();
1820
1821        // Create a keyset and add it to the database
1822        let keyset_id = Id::from_str("00916bbf7ef91a36").unwrap();
1823        let keyset_info = MintKeySetInfo {
1824            id: keyset_id,
1825            unit: CurrencyUnit::Sat,
1826            active: true,
1827            valid_from: 0,
1828            valid_to: None,
1829            derivation_path: bitcoin::bip32::DerivationPath::from_str("m/0'/0'/0'").unwrap(),
1830            derivation_path_index: Some(0),
1831            max_order: 32,
1832            input_fee_ppk: 0,
1833        };
1834        db.add_keyset_info(keyset_info).await.unwrap();
1835
1836        let proofs = vec![
1837            Proof {
1838                amount: Amount::from(100),
1839                keyset_id,
1840                secret: Secret::generate(),
1841                c: SecretKey::generate().public_key(),
1842                witness: None,
1843                dleq: None,
1844            },
1845            Proof {
1846                amount: Amount::from(200),
1847                keyset_id,
1848                secret: Secret::generate(),
1849                c: SecretKey::generate().public_key(),
1850                witness: None,
1851                dleq: None,
1852            },
1853        ];
1854
1855        // Add proofs to database
1856        db.add_proofs(proofs.clone(), None).await.unwrap();
1857
1858        // Mark one proof as spent
1859        db.update_proofs_states(&[proofs[0].y().unwrap()], State::Spent)
1860            .await
1861            .unwrap();
1862
1863        // Try to update both proofs - should fail because one is spent
1864        let result = db
1865            .update_proofs_states(
1866                &[proofs[0].y().unwrap(), proofs[1].y().unwrap()],
1867                State::Reserved,
1868            )
1869            .await;
1870
1871        assert!(result.is_err());
1872        assert!(matches!(
1873            result.unwrap_err(),
1874            database::Error::AttemptUpdateSpentProof
1875        ));
1876
1877        // Verify states haven't changed
1878        let states = db
1879            .get_proofs_states(&[proofs[0].y().unwrap(), proofs[1].y().unwrap()])
1880            .await
1881            .unwrap();
1882
1883        assert_eq!(states.len(), 2);
1884        assert_eq!(states[0], Some(State::Spent));
1885        assert_eq!(states[1], Some(State::Unspent));
1886    }
1887}