Wallet: Check Pending Melt Quotes (#895)

* Add transaction for pending melt

* Check pending melt quotes

* Fix imports
This commit is contained in:
David Caseria
2025-07-17 03:37:38 -04:00
committed by GitHub
parent 0b88c990a9
commit bd2fbb13f9
6 changed files with 134 additions and 0 deletions

View File

@@ -69,6 +69,8 @@ pub trait Database: Debug {
async fn add_melt_quote(&self, quote: wallet::MeltQuote) -> Result<(), Self::Err>; async fn add_melt_quote(&self, quote: wallet::MeltQuote) -> Result<(), Self::Err>;
/// Get melt quote from storage /// Get melt quote from storage
async fn get_melt_quote(&self, quote_id: &str) -> Result<Option<wallet::MeltQuote>, Self::Err>; async fn get_melt_quote(&self, quote_id: &str) -> Result<Option<wallet::MeltQuote>, Self::Err>;
/// Get melt quotes from storage
async fn get_melt_quotes(&self) -> Result<Vec<wallet::MeltQuote>, Self::Err>;
/// Remove melt quote from storage /// Remove melt quote from storage
async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err>; async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err>;

View File

@@ -477,6 +477,21 @@ impl WalletDatabase for WalletRedbDatabase {
Ok(None) Ok(None)
} }
#[instrument(skip_all)]
async fn get_melt_quotes(&self) -> Result<Vec<wallet::MeltQuote>, Self::Err> {
let read_txn = self.db.begin_read().map_err(Error::from)?;
let table = read_txn
.open_table(MELT_QUOTES_TABLE)
.map_err(Error::from)?;
Ok(table
.iter()
.map_err(Error::from)?
.flatten()
.flat_map(|(_id, quote)| serde_json::from_str(quote.value()))
.collect())
}
#[instrument(skip_all)] #[instrument(skip_all)]
async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err> { async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err> {
let write_txn = self.db.begin_write().map_err(Error::from)?; let write_txn = self.db.begin_write().map_err(Error::from)?;

View File

@@ -530,6 +530,30 @@ ON CONFLICT(id) DO UPDATE SET
.transpose()?) .transpose()?)
} }
#[instrument(skip(self))]
async fn get_melt_quotes(&self) -> Result<Vec<wallet::MeltQuote>, Self::Err> {
Ok(Statement::new(
r#"
SELECT
id,
unit,
amount,
request,
fee_reserve,
state,
expiry,
payment_preimage
FROM
melt_quote
"#,
)
.fetch_all(&self.pool.get().map_err(Error::Pool)?)
.map_err(Error::Sqlite)?
.into_iter()
.map(sqlite_row_to_melt_quote)
.collect::<Result<_, _>>()?)
}
#[instrument(skip(self))] #[instrument(skip(self))]
async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err> { async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err> {
Statement::new(r#"DELETE FROM melt_quote WHERE id=:id"#) Statement::new(r#"DELETE FROM melt_quote WHERE id=:id"#)

View File

@@ -104,6 +104,13 @@ impl Wallet {
Some(quote) => { Some(quote) => {
let mut quote = quote; let mut quote = quote;
if let Err(e) = self
.add_transaction_for_pending_melt(&quote, &response)
.await
{
tracing::error!("Failed to add transaction for pending melt: {}", e);
}
quote.state = response.state; quote.state = response.state;
self.localstore.add_melt_quote(quote).await?; self.localstore.add_melt_quote(quote).await?;
} }

View File

@@ -76,6 +76,13 @@ impl Wallet {
Some(quote) => { Some(quote) => {
let mut quote = quote; let mut quote = quote;
if let Err(e) = self
.add_transaction_for_pending_melt(&quote, &response)
.await
{
tracing::error!("Failed to add transaction for pending melt: {}", e);
}
quote.state = response.state; quote.state = response.state;
self.localstore.add_melt_quote(quote).await?; self.localstore.add_melt_quote(quote).await?;
} }

View File

@@ -1,2 +1,81 @@
use std::collections::HashMap;
use cdk_common::util::unix_time;
use cdk_common::wallet::{MeltQuote, Transaction, TransactionDirection};
use cdk_common::{Error, MeltQuoteBolt11Response, MeltQuoteState, ProofsMethods};
use tracing::instrument;
use crate::Wallet;
mod melt_bolt11; mod melt_bolt11;
mod melt_bolt12; mod melt_bolt12;
impl Wallet {
/// Check pending melt quotes
#[instrument(skip_all)]
pub async fn check_pending_melt_quotes(&self) -> Result<(), Error> {
let quotes = self.get_pending_melt_quotes().await?;
for quote in quotes {
self.melt_quote_status(&quote.id).await?;
}
Ok(())
}
/// Get all active melt quotes from the wallet
pub async fn get_active_melt_quotes(&self) -> Result<Vec<MeltQuote>, Error> {
let quotes = self.localstore.get_melt_quotes().await?;
Ok(quotes
.into_iter()
.filter(|q| {
q.state == MeltQuoteState::Pending
|| (q.state == MeltQuoteState::Unpaid && q.expiry > unix_time())
})
.collect())
}
/// Get pending melt quotes
pub async fn get_pending_melt_quotes(&self) -> Result<Vec<MeltQuote>, Error> {
let quotes = self.localstore.get_melt_quotes().await?;
Ok(quotes
.into_iter()
.filter(|q| q.state == MeltQuoteState::Pending)
.collect())
}
pub(crate) async fn add_transaction_for_pending_melt(
&self,
quote: &MeltQuote,
response: &MeltQuoteBolt11Response<String>,
) -> Result<(), Error> {
if quote.state != response.state {
tracing::info!(
"Quote melt {} state changed from {} to {}",
quote.id,
quote.state,
response.state
);
if response.state == MeltQuoteState::Paid {
let pending_proofs = self.get_pending_proofs().await?;
let proofs_total = pending_proofs.total_amount().unwrap_or_default();
let change_total = response.change_amount().unwrap_or_default();
self.localstore
.add_transaction(Transaction {
mint_url: self.mint_url.clone(),
direction: TransactionDirection::Outgoing,
amount: response.amount,
fee: proofs_total
.checked_sub(response.amount)
.and_then(|amt| amt.checked_sub(change_total))
.unwrap_or_default(),
unit: quote.unit.clone(),
ys: pending_proofs.ys()?,
timestamp: unix_time(),
memo: None,
metadata: HashMap::new(),
})
.await?;
}
}
Ok(())
}
}