1use std::str::FromStr;
4
5use async_trait::async_trait;
6use cdk_common::database::mint::{Acquired, LockedMeltQuotes};
7use cdk_common::database::{
8 self, ConversionError, Error, MintQuotesDatabase, MintQuotesTransaction,
9};
10use cdk_common::mint::{
11 self, IncomingPayment, Issuance, MeltPaymentRequest, MeltQuote, MintQuote, Operation,
12};
13use cdk_common::payment::PaymentIdentifier;
14use cdk_common::quote_id::QuoteId;
15use cdk_common::state::check_melt_quote_state_transition;
16use cdk_common::util::unix_time;
17use cdk_common::{
18 Amount, BlindedMessage, CurrencyUnit, Id, MeltQuoteState, PaymentMethod, PublicKey,
19};
20#[cfg(feature = "prometheus")]
21use cdk_prometheus::METRICS;
22use lightning_invoice::Bolt11Invoice;
23use tracing::instrument;
24
25use super::{SQLMintDatabase, SQLTransaction};
26use crate::database::DatabaseExecutor;
27use crate::pool::DatabasePool;
28use crate::stmt::{query, Column};
29use crate::{
30 column_as_nullable_number, column_as_nullable_string, column_as_number, column_as_string,
31 unpack_into,
32};
33
34async fn get_mint_quote_payments<C>(
35 conn: &C,
36 quote_id: &QuoteId,
37) -> Result<Vec<IncomingPayment>, Error>
38where
39 C: DatabaseExecutor + Send + Sync,
40{
41 query(
43 r#"
44 SELECT
45 p.payment_id,
46 p.timestamp,
47 p.amount,
48 q.unit
49 FROM
50 mint_quote_payments p
51 JOIN mint_quote q ON p.quote_id = q.id
52 WHERE
53 p.quote_id=:quote_id
54 "#,
55 )?
56 .bind("quote_id", quote_id.to_string())
57 .fetch_all(conn)
58 .await?
59 .into_iter()
60 .map(|row| {
61 let amount: u64 = column_as_number!(row[2].clone());
62 let time: u64 = column_as_number!(row[1].clone());
63 let unit = column_as_string!(&row[3], CurrencyUnit::from_str);
64 Ok(IncomingPayment::new(
65 Amount::from(amount).with_unit(unit),
66 column_as_string!(&row[0]),
67 time,
68 ))
69 })
70 .collect()
71}
72
73async fn get_mint_quote_issuance<C>(conn: &C, quote_id: &QuoteId) -> Result<Vec<Issuance>, Error>
74where
75 C: DatabaseExecutor + Send + Sync,
76{
77 query(
79 r#"
80SELECT i.amount, i.timestamp, q.unit
81FROM mint_quote_issued i
82JOIN mint_quote q ON i.quote_id = q.id
83WHERE i.quote_id=:quote_id
84 "#,
85 )?
86 .bind("quote_id", quote_id.to_string())
87 .fetch_all(conn)
88 .await?
89 .into_iter()
90 .map(|row| {
91 let time: u64 = column_as_number!(row[1].clone());
92 let unit = column_as_string!(&row[2], CurrencyUnit::from_str);
93 Ok(Issuance::new(
94 Amount::from_i64(column_as_number!(row[0].clone()))
95 .expect("Is amount when put into db")
96 .with_unit(unit),
97 time,
98 ))
99 })
100 .collect()
101}
102
103pub(super) async fn get_mint_quote_inner<T>(
105 executor: &T,
106 quote_id: &QuoteId,
107 for_update: bool,
108) -> Result<Option<MintQuote>, Error>
109where
110 T: DatabaseExecutor,
111{
112 let payments = get_mint_quote_payments(executor, quote_id).await?;
113 let issuance = get_mint_quote_issuance(executor, quote_id).await?;
114
115 let for_update_clause = if for_update { "FOR UPDATE" } else { "" };
116 let query_str = format!(
117 r#"
118 SELECT
119 id,
120 amount,
121 unit,
122 request,
123 expiry,
124 request_lookup_id,
125 pubkey,
126 created_time,
127 amount_paid,
128 amount_issued,
129 payment_method,
130 request_lookup_id_kind
131 FROM
132 mint_quote
133 WHERE id = :id
134 {for_update_clause}
135 "#
136 );
137
138 query(&query_str)?
139 .bind("id", quote_id.to_string())
140 .fetch_one(executor)
141 .await?
142 .map(|row| sql_row_to_mint_quote(row, payments, issuance))
143 .transpose()
144}
145
146pub(super) async fn get_mint_quote_by_request_inner<T>(
147 executor: &T,
148 request: &str,
149 for_update: bool,
150) -> Result<Option<MintQuote>, Error>
151where
152 T: DatabaseExecutor,
153{
154 let for_update_clause = if for_update { "FOR UPDATE" } else { "" };
155 let query_str = format!(
156 r#"
157 SELECT
158 id,
159 amount,
160 unit,
161 request,
162 expiry,
163 request_lookup_id,
164 pubkey,
165 created_time,
166 amount_paid,
167 amount_issued,
168 payment_method,
169 request_lookup_id_kind
170 FROM
171 mint_quote
172 WHERE request = :request
173 {for_update_clause}
174 "#
175 );
176
177 let mut mint_quote = query(&query_str)?
178 .bind("request", request.to_string())
179 .fetch_one(executor)
180 .await?
181 .map(|row| sql_row_to_mint_quote(row, vec![], vec![]))
182 .transpose()?;
183
184 if let Some(quote) = mint_quote.as_mut() {
185 let payments = get_mint_quote_payments(executor, "e.id).await?;
186 let issuance = get_mint_quote_issuance(executor, "e.id).await?;
187 quote.issuance = issuance;
188 quote.payments = payments;
189 }
190
191 Ok(mint_quote)
192}
193
194pub(super) async fn get_mint_quote_by_request_lookup_id_inner<T>(
195 executor: &T,
196 request_lookup_id: &PaymentIdentifier,
197 for_update: bool,
198) -> Result<Option<MintQuote>, Error>
199where
200 T: DatabaseExecutor,
201{
202 let for_update_clause = if for_update { "FOR UPDATE" } else { "" };
203 let query_str = format!(
204 r#"
205 SELECT
206 id,
207 amount,
208 unit,
209 request,
210 expiry,
211 request_lookup_id,
212 pubkey,
213 created_time,
214 amount_paid,
215 amount_issued,
216 payment_method,
217 request_lookup_id_kind
218 FROM
219 mint_quote
220 WHERE request_lookup_id = :request_lookup_id
221 AND request_lookup_id_kind = :request_lookup_id_kind
222 {for_update_clause}
223 "#
224 );
225
226 let mut mint_quote = query(&query_str)?
227 .bind("request_lookup_id", request_lookup_id.to_string())
228 .bind("request_lookup_id_kind", request_lookup_id.kind())
229 .fetch_one(executor)
230 .await?
231 .map(|row| sql_row_to_mint_quote(row, vec![], vec![]))
232 .transpose()?;
233
234 if let Some(quote) = mint_quote.as_mut() {
235 let payments = get_mint_quote_payments(executor, "e.id).await?;
236 let issuance = get_mint_quote_issuance(executor, "e.id).await?;
237 quote.issuance = issuance;
238 quote.payments = payments;
239 }
240
241 Ok(mint_quote)
242}
243
244pub(super) async fn get_melt_quote_inner<T>(
245 executor: &T,
246 quote_id: &QuoteId,
247 for_update: bool,
248) -> Result<Option<mint::MeltQuote>, Error>
249where
250 T: DatabaseExecutor,
251{
252 let for_update_clause = if for_update { "FOR UPDATE" } else { "" };
253 let query_str = format!(
254 r#"
255 SELECT
256 id,
257 unit,
258 amount,
259 request,
260 fee_reserve,
261 expiry,
262 state,
263 payment_preimage,
264 request_lookup_id,
265 created_time,
266 paid_time,
267 payment_method,
268 options,
269 request_lookup_id_kind
270 FROM
271 melt_quote
272 WHERE
273 id=:id
274 {for_update_clause}
275 "#
276 );
277
278 query(&query_str)?
279 .bind("id", quote_id.to_string())
280 .fetch_one(executor)
281 .await?
282 .map(sql_row_to_melt_quote)
283 .transpose()
284}
285
286pub(super) async fn get_melt_quotes_by_request_lookup_id_inner<T>(
287 executor: &T,
288 request_lookup_id: &PaymentIdentifier,
289 for_update: bool,
290) -> Result<Vec<mint::MeltQuote>, Error>
291where
292 T: DatabaseExecutor,
293{
294 let for_update_clause = if for_update { "FOR UPDATE" } else { "" };
295 let query_str = format!(
296 r#"
297 SELECT
298 id,
299 unit,
300 amount,
301 request,
302 fee_reserve,
303 expiry,
304 state,
305 payment_preimage,
306 request_lookup_id,
307 created_time,
308 paid_time,
309 payment_method,
310 options,
311 request_lookup_id_kind
312 FROM
313 melt_quote
314 WHERE
315 request_lookup_id = :request_lookup_id
316 AND request_lookup_id_kind = :request_lookup_id_kind
317 {for_update_clause}
318 "#
319 );
320
321 query(&query_str)?
322 .bind("request_lookup_id", request_lookup_id.to_string())
323 .bind("request_lookup_id_kind", request_lookup_id.kind())
324 .fetch_all(executor)
325 .await?
326 .into_iter()
327 .map(sql_row_to_melt_quote)
328 .collect::<Result<Vec<_>, _>>()
329}
330
331async fn lock_melt_quote_and_related_inner<T>(
337 executor: &T,
338 quote_id: &QuoteId,
339) -> Result<LockedMeltQuotes, Error>
340where
341 T: DatabaseExecutor,
342{
343 let query_str = r#"
350 SELECT
351 id,
352 unit,
353 amount,
354 request,
355 fee_reserve,
356 expiry,
357 state,
358 payment_preimage,
359 request_lookup_id,
360 created_time,
361 paid_time,
362 payment_method,
363 options,
364 request_lookup_id_kind
365 FROM
366 melt_quote
367 WHERE
368 (
369 request_lookup_id IS NOT NULL
370 AND request_lookup_id = (SELECT request_lookup_id FROM melt_quote WHERE id = :quote_id)
371 AND request_lookup_id_kind = (SELECT request_lookup_id_kind FROM melt_quote WHERE id = :quote_id)
372 )
373 OR
374 (
375 id = :quote_id
376 AND (SELECT request_lookup_id FROM melt_quote WHERE id = :quote_id) IS NULL
377 )
378 ORDER BY id
379 FOR UPDATE
380 "#;
381
382 let all_quotes: Vec<mint::MeltQuote> = query(query_str)?
383 .bind("quote_id", quote_id.to_string())
384 .fetch_all(executor)
385 .await?
386 .into_iter()
387 .map(sql_row_to_melt_quote)
388 .collect::<Result<Vec<_>, _>>()?;
389
390 let target_quote = all_quotes.iter().find(|q| &q.id == quote_id).cloned();
392
393 Ok(LockedMeltQuotes {
394 target: target_quote.map(|q| q.into()),
395 all_related: all_quotes.into_iter().map(|q| q.into()).collect(),
396 })
397}
398
399#[instrument(skip_all)]
400fn sql_row_to_mint_quote(
401 row: Vec<Column>,
402 payments: Vec<IncomingPayment>,
403 issueances: Vec<Issuance>,
404) -> Result<MintQuote, Error> {
405 unpack_into!(
406 let (
407 id, amount, unit, request, expiry, request_lookup_id,
408 pubkey, created_time, amount_paid, amount_issued, payment_method, request_lookup_id_kind
409 ) = row
410 );
411
412 let request_str = column_as_string!(&request);
413 let request_lookup_id = column_as_nullable_string!(&request_lookup_id).unwrap_or_else(|| {
414 Bolt11Invoice::from_str(&request_str)
415 .map(|invoice| invoice.payment_hash().to_string())
416 .unwrap_or_else(|_| request_str.clone())
417 });
418 let request_lookup_id_kind = column_as_string!(request_lookup_id_kind);
419
420 let pubkey = column_as_nullable_string!(&pubkey)
421 .map(|pk| PublicKey::from_hex(&pk))
422 .transpose()?;
423
424 let id = column_as_string!(id);
425 let amount: Option<u64> = column_as_nullable_number!(amount);
426 let amount_paid: u64 = column_as_number!(amount_paid);
427 let amount_issued: u64 = column_as_number!(amount_issued);
428 let payment_method = column_as_string!(payment_method, PaymentMethod::from_str);
429 let unit = column_as_string!(unit, CurrencyUnit::from_str);
430
431 Ok(MintQuote::new(
432 Some(QuoteId::from_str(&id)?),
433 request_str,
434 unit.clone(),
435 amount.map(|a| Amount::from(a).with_unit(unit.clone())),
436 column_as_number!(expiry),
437 PaymentIdentifier::new(&request_lookup_id_kind, &request_lookup_id)
438 .map_err(|_| ConversionError::MissingParameter("Payment id".to_string()))?,
439 pubkey,
440 Amount::from(amount_paid).with_unit(unit.clone()),
441 Amount::from(amount_issued).with_unit(unit),
442 payment_method,
443 column_as_number!(created_time),
444 payments,
445 issueances,
446 None,
447 ))
448}
449
450fn sql_row_to_melt_quote(row: Vec<Column>) -> Result<mint::MeltQuote, Error> {
452 unpack_into!(
453 let (
454 id,
455 unit,
456 amount,
457 request,
458 fee_reserve,
459 expiry,
460 state,
461 payment_preimage,
462 request_lookup_id,
463 created_time,
464 paid_time,
465 payment_method,
466 options,
467 request_lookup_id_kind
468 ) = row
469 );
470
471 let id = column_as_string!(id);
472 let amount: u64 = column_as_number!(amount);
473 let fee_reserve: u64 = column_as_number!(fee_reserve);
474
475 let expiry = column_as_number!(expiry);
476 let payment_preimage = column_as_nullable_string!(payment_preimage);
477 let options = column_as_nullable_string!(options);
478 let options = options.and_then(|o| serde_json::from_str(&o).ok());
479 let created_time: i64 = column_as_number!(created_time);
480 let paid_time = column_as_nullable_number!(paid_time);
481 let payment_method = PaymentMethod::from_str(&column_as_string!(payment_method))?;
482
483 let state =
484 MeltQuoteState::from_str(&column_as_string!(&state)).map_err(ConversionError::from)?;
485
486 let unit = column_as_string!(unit);
487 let request = column_as_string!(request);
488
489 let request_lookup_id_kind = column_as_nullable_string!(request_lookup_id_kind);
490
491 let request_lookup_id = column_as_nullable_string!(&request_lookup_id).or_else(|| {
492 Bolt11Invoice::from_str(&request)
493 .ok()
494 .map(|invoice| invoice.payment_hash().to_string())
495 });
496
497 let request_lookup_id = if let (Some(id_kind), Some(request_lookup_id)) =
498 (request_lookup_id_kind, request_lookup_id)
499 {
500 Some(
501 PaymentIdentifier::new(&id_kind, &request_lookup_id)
502 .map_err(|_| ConversionError::MissingParameter("Payment id".to_string()))?,
503 )
504 } else {
505 None
506 };
507
508 let request = match serde_json::from_str(&request) {
509 Ok(req) => req,
510 Err(err) => {
511 tracing::debug!(
512 "Melt quote from pre migrations defaulting to bolt11 {}.",
513 err
514 );
515 let bolt11 = Bolt11Invoice::from_str(&request)
516 .map_err(|e| Error::Internal(format!("Could not parse invoice: {e}")))?;
517 MeltPaymentRequest::Bolt11 { bolt11 }
518 }
519 };
520
521 let unit = CurrencyUnit::from_str(&unit)?;
522 Ok(MeltQuote::from_db(
523 QuoteId::from_str(&id)?,
524 unit,
525 request,
526 amount,
527 fee_reserve,
528 state,
529 expiry,
530 payment_preimage,
531 request_lookup_id,
532 options,
533 created_time as u64,
534 paid_time,
535 payment_method,
536 ))
537}
538
539#[async_trait]
540impl<RM> MintQuotesTransaction for SQLTransaction<RM>
541where
542 RM: DatabasePool + 'static,
543{
544 type Err = Error;
545
546 async fn add_melt_request(
547 &mut self,
548 quote_id: &QuoteId,
549 inputs_amount: Amount<CurrencyUnit>,
550 inputs_fee: Amount<CurrencyUnit>,
551 ) -> Result<(), Self::Err> {
552 query(
554 r#"
555 INSERT INTO melt_request
556 (quote_id, inputs_amount, inputs_fee)
557 VALUES
558 (:quote_id, :inputs_amount, :inputs_fee)
559 "#,
560 )?
561 .bind("quote_id", quote_id.to_string())
562 .bind("inputs_amount", inputs_amount.to_i64())
563 .bind("inputs_fee", inputs_fee.to_i64())
564 .execute(&self.inner)
565 .await?;
566
567 Ok(())
568 }
569
570 async fn add_blinded_messages(
571 &mut self,
572 quote_id: Option<&QuoteId>,
573 blinded_messages: &[BlindedMessage],
574 operation: &Operation,
575 ) -> Result<(), Self::Err> {
576 let current_time = unix_time();
577
578 for message in blinded_messages {
581 match query(
582 r#"
583 INSERT INTO blind_signature
584 (blinded_message, amount, keyset_id, c, quote_id, created_time, operation_kind, operation_id)
585 VALUES
586 (:blinded_message, :amount, :keyset_id, NULL, :quote_id, :created_time, :operation_kind, :operation_id)
587 "#,
588 )?
589 .bind(
590 "blinded_message",
591 message.blinded_secret.to_bytes().to_vec(),
592 )
593 .bind("amount", message.amount.to_i64())
594 .bind("keyset_id", message.keyset_id.to_string())
595 .bind("quote_id", quote_id.map(|q| q.to_string()))
596 .bind("created_time", current_time as i64)
597 .bind("operation_kind", operation.kind().to_string())
598 .bind("operation_id", operation.id().to_string())
599 .execute(&self.inner)
600 .await
601 {
602 Ok(_) => continue,
603 Err(database::Error::Duplicate) => {
604 return Err(database::Error::Duplicate);
609 }
610 Err(err) => return Err(err),
611 }
612 }
613
614 Ok(())
615 }
616
617 async fn delete_blinded_messages(
618 &mut self,
619 blinded_secrets: &[PublicKey],
620 ) -> Result<(), Self::Err> {
621 if blinded_secrets.is_empty() {
622 return Ok(());
623 }
624
625 query(
628 r#"
629 DELETE FROM blind_signature
630 WHERE blinded_message IN (:blinded_secrets) AND c IS NULL
631 "#,
632 )?
633 .bind_vec(
634 "blinded_secrets",
635 blinded_secrets
636 .iter()
637 .map(|secret| secret.to_bytes().to_vec())
638 .collect(),
639 )
640 .execute(&self.inner)
641 .await?;
642
643 Ok(())
644 }
645
646 async fn get_melt_request_and_blinded_messages(
647 &mut self,
648 quote_id: &QuoteId,
649 ) -> Result<Option<database::mint::MeltRequestInfo>, Self::Err> {
650 let melt_request_row = query(
651 r#"
652 SELECT mr.inputs_amount, mr.inputs_fee, mq.unit
653 FROM melt_request mr
654 JOIN melt_quote mq ON mr.quote_id = mq.id
655 WHERE mr.quote_id = :quote_id
656 FOR UPDATE
657 "#,
658 )?
659 .bind("quote_id", quote_id.to_string())
660 .fetch_one(&self.inner)
661 .await?;
662
663 if let Some(row) = melt_request_row {
664 let inputs_amount: u64 = column_as_number!(row[0].clone());
665 let inputs_fee: u64 = column_as_number!(row[1].clone());
666 let unit_str = column_as_string!(&row[2]);
667 let unit = CurrencyUnit::from_str(&unit_str)?;
668
669 let blinded_messages_rows = query(
671 r#"
672 SELECT blinded_message, keyset_id, amount
673 FROM blind_signature
674 WHERE quote_id = :quote_id AND c IS NULL
675 "#,
676 )?
677 .bind("quote_id", quote_id.to_string())
678 .fetch_all(&self.inner)
679 .await?;
680
681 let blinded_messages: Result<Vec<BlindedMessage>, Error> = blinded_messages_rows
682 .into_iter()
683 .map(|row| -> Result<BlindedMessage, Error> {
684 let blinded_message_key =
685 column_as_string!(&row[0], PublicKey::from_hex, PublicKey::from_slice);
686 let keyset_id = column_as_string!(&row[1], Id::from_str, Id::from_bytes);
687 let amount: u64 = column_as_number!(row[2].clone());
688
689 Ok(BlindedMessage {
690 blinded_secret: blinded_message_key,
691 keyset_id,
692 amount: Amount::from(amount),
693 witness: None, })
695 })
696 .collect();
697 let blinded_messages = blinded_messages?;
698
699 Ok(Some(database::mint::MeltRequestInfo {
700 inputs_amount: Amount::from(inputs_amount).with_unit(unit.clone()),
701 inputs_fee: Amount::from(inputs_fee).with_unit(unit),
702 change_outputs: blinded_messages,
703 }))
704 } else {
705 Ok(None)
706 }
707 }
708
709 async fn delete_melt_request(&mut self, quote_id: &QuoteId) -> Result<(), Self::Err> {
710 query(
712 r#"
713 DELETE FROM melt_request
714 WHERE quote_id = :quote_id
715 "#,
716 )?
717 .bind("quote_id", quote_id.to_string())
718 .execute(&self.inner)
719 .await?;
720
721 query(
723 r#"
724 DELETE FROM blind_signature
725 WHERE quote_id = :quote_id AND c IS NULL
726 "#,
727 )?
728 .bind("quote_id", quote_id.to_string())
729 .execute(&self.inner)
730 .await?;
731
732 Ok(())
733 }
734
735 async fn update_mint_quote(
736 &mut self,
737 quote: &mut Acquired<mint::MintQuote>,
738 ) -> Result<(), Self::Err> {
739 let mut changes = if let Some(changes) = quote.take_changes() {
740 changes
741 } else {
742 return Ok(());
743 };
744
745 if changes.issuances.is_none() && changes.payments.is_none() {
746 return Ok(());
747 }
748
749 for payment in changes.payments.take().unwrap_or_default() {
750 query(
751 r#"
752 INSERT INTO mint_quote_payments
753 (quote_id, payment_id, amount, timestamp)
754 VALUES (:quote_id, :payment_id, :amount, :timestamp)
755 "#,
756 )?
757 .bind("quote_id", quote.id.to_string())
758 .bind("payment_id", payment.payment_id)
759 .bind("amount", payment.amount.to_i64())
760 .bind("timestamp", payment.time as i64)
761 .execute(&self.inner)
762 .await
763 .map_err(|err| {
764 tracing::error!("SQLite could not insert payment ID: {}", err);
765 err
766 })?;
767 }
768
769 let current_time = unix_time();
770
771 for amount_issued in changes.issuances.take().unwrap_or_default() {
772 query(
773 r#"
774 INSERT INTO mint_quote_issued
775 (quote_id, amount, timestamp)
776 VALUES (:quote_id, :amount, :timestamp);
777 "#,
778 )?
779 .bind("quote_id", quote.id.to_string())
780 .bind("amount", amount_issued.to_i64())
781 .bind("timestamp", current_time as i64)
782 .execute(&self.inner)
783 .await?;
784 }
785
786 query(
787 r#"
788 UPDATE
789 mint_quote
790 SET
791 amount_issued = :amount_issued,
792 amount_paid = :amount_paid
793 WHERE
794 id = :quote_id
795 "#,
796 )?
797 .bind("quote_id", quote.id.to_string())
798 .bind("amount_issued", quote.amount_issued().to_i64())
799 .bind("amount_paid", quote.amount_paid().to_i64())
800 .execute(&self.inner)
801 .await
802 .inspect_err(|err| {
803 tracing::error!("SQLite could not update mint quote amount_paid: {}", err);
804 })?;
805
806 Ok(())
807 }
808
809 #[instrument(skip_all)]
810 async fn add_mint_quote(&mut self, quote: MintQuote) -> Result<Acquired<MintQuote>, Self::Err> {
811 query(
812 r#"
813 INSERT INTO mint_quote (
814 id, amount, unit, request, expiry, request_lookup_id, pubkey, created_time, payment_method, request_lookup_id_kind
815 )
816 VALUES (
817 :id, :amount, :unit, :request, :expiry, :request_lookup_id, :pubkey, :created_time, :payment_method, :request_lookup_id_kind
818 )
819 "#,
820 )?
821 .bind("id", quote.id.to_string())
822 .bind("amount", quote.amount.clone().map(|a| a.to_i64()))
823 .bind("unit", quote.unit.to_string())
824 .bind("request", quote.request.clone())
825 .bind("expiry", quote.expiry as i64)
826 .bind(
827 "request_lookup_id",
828 quote.request_lookup_id.to_string(),
829 )
830 .bind("pubkey", quote.pubkey.map(|p| p.to_string()))
831 .bind("created_time", quote.created_time as i64)
832 .bind("payment_method", quote.payment_method.to_string())
833 .bind("request_lookup_id_kind", quote.request_lookup_id.kind())
834 .execute(&self.inner)
835 .await?;
836
837 Ok(quote.into())
838 }
839
840 async fn add_melt_quote(&mut self, quote: mint::MeltQuote) -> Result<(), Self::Err> {
841 query(
843 r#"
844 INSERT INTO melt_quote
845 (
846 id, unit, amount, request, fee_reserve, state,
847 expiry, payment_preimage, request_lookup_id,
848 created_time, paid_time, options, request_lookup_id_kind, payment_method
849 )
850 VALUES
851 (
852 :id, :unit, :amount, :request, :fee_reserve, :state,
853 :expiry, :payment_preimage, :request_lookup_id,
854 :created_time, :paid_time, :options, :request_lookup_id_kind, :payment_method
855 )
856 "#,
857 )?
858 .bind("id", quote.id.to_string())
859 .bind("unit", quote.unit.to_string())
860 .bind("amount", quote.amount().to_i64())
861 .bind("request", serde_json::to_string("e.request)?)
862 .bind("fee_reserve", quote.fee_reserve().to_i64())
863 .bind("state", quote.state.to_string())
864 .bind("expiry", quote.expiry as i64)
865 .bind("payment_preimage", quote.payment_preimage)
866 .bind(
867 "request_lookup_id",
868 quote.request_lookup_id.as_ref().map(|id| id.to_string()),
869 )
870 .bind("created_time", quote.created_time as i64)
871 .bind("paid_time", quote.paid_time.map(|t| t as i64))
872 .bind(
873 "options",
874 quote.options.map(|o| serde_json::to_string(&o).ok()),
875 )
876 .bind(
877 "request_lookup_id_kind",
878 quote.request_lookup_id.map(|id| id.kind()),
879 )
880 .bind("payment_method", quote.payment_method.to_string())
881 .execute(&self.inner)
882 .await?;
883
884 Ok(())
885 }
886
887 async fn update_melt_quote_request_lookup_id(
888 &mut self,
889 quote: &mut Acquired<mint::MeltQuote>,
890 new_request_lookup_id: &PaymentIdentifier,
891 ) -> Result<(), Self::Err> {
892 query(r#"UPDATE melt_quote SET request_lookup_id = :new_req_id, request_lookup_id_kind = :new_kind WHERE id = :id"#)?
893 .bind("new_req_id", new_request_lookup_id.to_string())
894 .bind("new_kind", new_request_lookup_id.kind())
895 .bind("id", quote.id.to_string())
896 .execute(&self.inner)
897 .await?;
898 quote.request_lookup_id = Some(new_request_lookup_id.clone());
899 Ok(())
900 }
901
902 async fn update_melt_quote_state(
903 &mut self,
904 quote: &mut Acquired<mint::MeltQuote>,
905 state: MeltQuoteState,
906 payment_proof: Option<String>,
907 ) -> Result<MeltQuoteState, Self::Err> {
908 let old_state = quote.state;
909
910 check_melt_quote_state_transition(old_state, state)?;
911
912 let rec = if state == MeltQuoteState::Paid {
913 let current_time = unix_time();
914 quote.paid_time = Some(current_time);
915 quote.payment_preimage = payment_proof.clone();
916 query(r#"UPDATE melt_quote SET state = :state, paid_time = :paid_time, payment_preimage = :payment_preimage WHERE id = :id"#)?
917 .bind("state", state.to_string())
918 .bind("paid_time", current_time as i64)
919 .bind("payment_preimage", payment_proof)
920 .bind("id", quote.id.to_string())
921 .execute(&self.inner)
922 .await
923 } else {
924 query(r#"UPDATE melt_quote SET state = :state WHERE id = :id"#)?
925 .bind("state", state.to_string())
926 .bind("id", quote.id.to_string())
927 .execute(&self.inner)
928 .await
929 };
930
931 match rec {
932 Ok(_) => {}
933 Err(err) => {
934 tracing::error!("SQLite Could not update melt quote");
935 return Err(err);
936 }
937 };
938
939 quote.state = state;
940
941 if state == MeltQuoteState::Unpaid || state == MeltQuoteState::Failed {
942 self.delete_melt_request("e.id).await?;
943 }
944
945 Ok(old_state)
946 }
947
948 async fn get_mint_quote(
949 &mut self,
950 quote_id: &QuoteId,
951 ) -> Result<Option<Acquired<MintQuote>>, Self::Err> {
952 get_mint_quote_inner(&self.inner, quote_id, true)
953 .await
954 .map(|quote| quote.map(|inner| inner.into()))
955 }
956
957 async fn get_melt_quote(
958 &mut self,
959 quote_id: &QuoteId,
960 ) -> Result<Option<Acquired<mint::MeltQuote>>, Self::Err> {
961 get_melt_quote_inner(&self.inner, quote_id, true)
962 .await
963 .map(|quote| quote.map(|inner| inner.into()))
964 }
965
966 async fn get_melt_quotes_by_request_lookup_id(
967 &mut self,
968 request_lookup_id: &PaymentIdentifier,
969 ) -> Result<Vec<Acquired<mint::MeltQuote>>, Self::Err> {
970 get_melt_quotes_by_request_lookup_id_inner(&self.inner, request_lookup_id, true)
971 .await
972 .map(|quote| quote.into_iter().map(|inner| inner.into()).collect())
973 }
974
975 async fn lock_melt_quote_and_related(
976 &mut self,
977 quote_id: &QuoteId,
978 ) -> Result<LockedMeltQuotes, Self::Err> {
979 lock_melt_quote_and_related_inner(&self.inner, quote_id).await
980 }
981
982 async fn get_mint_quote_by_request(
983 &mut self,
984 request: &str,
985 ) -> Result<Option<Acquired<MintQuote>>, Self::Err> {
986 get_mint_quote_by_request_inner(&self.inner, request, true)
987 .await
988 .map(|quote| quote.map(|inner| inner.into()))
989 }
990
991 async fn get_mint_quote_by_request_lookup_id(
992 &mut self,
993 request_lookup_id: &PaymentIdentifier,
994 ) -> Result<Option<Acquired<MintQuote>>, Self::Err> {
995 get_mint_quote_by_request_lookup_id_inner(&self.inner, request_lookup_id, true)
996 .await
997 .map(|quote| quote.map(|inner| inner.into()))
998 }
999}
1000
1001#[async_trait]
1002impl<RM> MintQuotesDatabase for SQLMintDatabase<RM>
1003where
1004 RM: DatabasePool + 'static,
1005{
1006 type Err = Error;
1007
1008 async fn get_mint_quote(&self, quote_id: &QuoteId) -> Result<Option<MintQuote>, Self::Err> {
1009 #[cfg(feature = "prometheus")]
1010 METRICS.inc_in_flight_requests("get_mint_quote");
1011
1012 #[cfg(feature = "prometheus")]
1013 let start_time = std::time::Instant::now();
1014 let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
1015
1016 let result = get_mint_quote_inner(&*conn, quote_id, false).await;
1017
1018 #[cfg(feature = "prometheus")]
1019 {
1020 let success = result.is_ok();
1021
1022 METRICS.record_mint_operation("get_mint_quote", success);
1023 METRICS.record_mint_operation_histogram(
1024 "get_mint_quote",
1025 success,
1026 start_time.elapsed().as_secs_f64(),
1027 );
1028 METRICS.dec_in_flight_requests("get_mint_quote");
1029 }
1030
1031 result
1032 }
1033
1034 async fn get_mint_quote_by_request(
1035 &self,
1036 request: &str,
1037 ) -> Result<Option<MintQuote>, Self::Err> {
1038 let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
1039 get_mint_quote_by_request_inner(&*conn, request, false).await
1040 }
1041
1042 async fn get_mint_quote_by_request_lookup_id(
1043 &self,
1044 request_lookup_id: &PaymentIdentifier,
1045 ) -> Result<Option<MintQuote>, Self::Err> {
1046 let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
1047 get_mint_quote_by_request_lookup_id_inner(&*conn, request_lookup_id, false).await
1048 }
1049
1050 async fn get_mint_quotes(&self) -> Result<Vec<MintQuote>, Self::Err> {
1051 let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
1052 let mut mint_quotes = query(
1053 r#"
1054 SELECT
1055 id,
1056 amount,
1057 unit,
1058 request,
1059 expiry,
1060 request_lookup_id,
1061 pubkey,
1062 created_time,
1063 amount_paid,
1064 amount_issued,
1065 payment_method,
1066 request_lookup_id_kind
1067 FROM
1068 mint_quote
1069 "#,
1070 )?
1071 .fetch_all(&*conn)
1072 .await?
1073 .into_iter()
1074 .map(|row| sql_row_to_mint_quote(row, vec![], vec![]))
1075 .collect::<Result<Vec<_>, _>>()?;
1076
1077 for quote in mint_quotes.as_mut_slice() {
1078 let payments = get_mint_quote_payments(&*conn, "e.id).await?;
1079 let issuance = get_mint_quote_issuance(&*conn, "e.id).await?;
1080 quote.issuance = issuance;
1081 quote.payments = payments;
1082 }
1083
1084 Ok(mint_quotes)
1085 }
1086
1087 async fn get_melt_quote(
1088 &self,
1089 quote_id: &QuoteId,
1090 ) -> Result<Option<mint::MeltQuote>, Self::Err> {
1091 #[cfg(feature = "prometheus")]
1092 METRICS.inc_in_flight_requests("get_melt_quote");
1093
1094 #[cfg(feature = "prometheus")]
1095 let start_time = std::time::Instant::now();
1096 let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
1097
1098 let result = get_melt_quote_inner(&*conn, quote_id, false).await;
1099
1100 #[cfg(feature = "prometheus")]
1101 {
1102 let success = result.is_ok();
1103
1104 METRICS.record_mint_operation("get_melt_quote", success);
1105 METRICS.record_mint_operation_histogram(
1106 "get_melt_quote",
1107 success,
1108 start_time.elapsed().as_secs_f64(),
1109 );
1110 METRICS.dec_in_flight_requests("get_melt_quote");
1111 }
1112
1113 result
1114 }
1115
1116 async fn get_melt_quotes(&self) -> Result<Vec<mint::MeltQuote>, Self::Err> {
1117 let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
1118 Ok(query(
1119 r#"
1120 SELECT
1121 id,
1122 unit,
1123 amount,
1124 request,
1125 fee_reserve,
1126 expiry,
1127 state,
1128 payment_preimage,
1129 request_lookup_id,
1130 created_time,
1131 paid_time,
1132 payment_method,
1133 options,
1134 request_lookup_id_kind
1135 FROM
1136 melt_quote
1137 "#,
1138 )?
1139 .fetch_all(&*conn)
1140 .await?
1141 .into_iter()
1142 .map(sql_row_to_melt_quote)
1143 .collect::<Result<Vec<_>, _>>()?)
1144 }
1145}