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