Ver código fonte

Wallet: Check Pending Melt Quotes (#895)

* Add transaction for pending melt

* Check pending melt quotes

* Fix imports
David Caseria 2 semanas atrás
pai
commit
bd2fbb13f9

+ 2 - 0
crates/cdk-common/src/database/wallet.rs

@@ -69,6 +69,8 @@ pub trait Database: Debug {
     async fn add_melt_quote(&self, quote: wallet::MeltQuote) -> Result<(), Self::Err>;
     /// Get melt quote from storage
     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
     async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err>;
 

+ 15 - 0
crates/cdk-redb/src/wallet/mod.rs

@@ -478,6 +478,21 @@ impl WalletDatabase for WalletRedbDatabase {
     }
 
     #[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)]
     async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err> {
         let write_txn = self.db.begin_write().map_err(Error::from)?;
 

+ 24 - 0
crates/cdk-sqlite/src/wallet/mod.rs

@@ -531,6 +531,30 @@ ON CONFLICT(id) DO UPDATE SET
     }
 
     #[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))]
     async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err> {
         Statement::new(r#"DELETE FROM melt_quote WHERE id=:id"#)
             .bind(":id", quote_id.to_owned())

+ 7 - 0
crates/cdk/src/wallet/melt/melt_bolt11.rs

@@ -104,6 +104,13 @@ impl Wallet {
             Some(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;
                 self.localstore.add_melt_quote(quote).await?;
             }

+ 7 - 0
crates/cdk/src/wallet/melt/melt_bolt12.rs

@@ -76,6 +76,13 @@ impl Wallet {
             Some(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;
                 self.localstore.add_melt_quote(quote).await?;
             }

+ 79 - 0
crates/cdk/src/wallet/melt/mod.rs

@@ -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_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(())
+    }
+}