Explorar el Código

refactor: melt quote and melt to v1 in wallet sdk

thesimplekid hace 1 año
padre
commit
754936d701

+ 6 - 2
bindings/cashu-ffi/src/cashu.udl

@@ -31,8 +31,12 @@ interface Secret {
 	sequence<u8> as_bytes();	
 };
 
-interface MintQuoteInfo {
-	constructor(string id, Amount amount, string unit, Bolt11Invoice? request, boolean paid, u64 boolean);
+interface MintQuote {
+	constructor(string id, Amount amount, string unit, Bolt11Invoice request, boolean paid, u64 boolean);
+};
+
+interface MeltQuote {
+	constructor(string id, Amount amount, string unit, Bolt11Invoice request, Amount fee_reserve, boolean paid, u64 boolean);
 };
 
 // NUT00

+ 1 - 1
bindings/cashu-ffi/src/lib.rs

@@ -29,7 +29,7 @@ mod ffi {
     pub use crate::nuts::nut06::{MintInfo, MintVersion};
     pub use crate::nuts::nut07::{CheckSpendableRequest, CheckSpendableResponse};
     pub use crate::nuts::nut08::{MeltBolt11Request, MeltBolt11Response};
-    pub use crate::types::{Amount, Bolt11Invoice, KeySetInfo, MintQuoteInfo, Secret};
+    pub use crate::types::{Amount, Bolt11Invoice, KeySetInfo, MeltQuote, MintQuote, Secret};
 
     // UDL
     uniffi::include_scaffolding!("cashu");

+ 49 - 0
bindings/cashu-ffi/src/types/melt_quote.rs

@@ -0,0 +1,49 @@
+use std::ops::Deref;
+use std::str::FromStr;
+use std::sync::Arc;
+
+use cashu::nuts::CurrencyUnit;
+use cashu::types::MeltQuote as MeltQuoteSdk;
+
+use crate::{Amount, Bolt11Invoice};
+
+pub struct MeltQuote {
+    inner: MeltQuoteSdk,
+}
+
+impl Deref for MeltQuote {
+    type Target = MeltQuoteSdk;
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl From<MeltQuoteSdk> for MeltQuote {
+    fn from(inner: MeltQuoteSdk) -> MeltQuote {
+        MeltQuote { inner }
+    }
+}
+
+impl MeltQuote {
+    pub fn new(
+        id: String,
+        amount: Arc<Amount>,
+        unit: String,
+        request: Arc<Bolt11Invoice>,
+        fee_reserve: Arc<Amount>,
+        paid: bool,
+        expiry: u64,
+    ) -> Self {
+        Self {
+            inner: MeltQuoteSdk {
+                id,
+                amount: amount.as_ref().deref().clone(),
+                unit: CurrencyUnit::from_str(&unit).unwrap(),
+                request: request.as_ref().deref().clone(),
+                fee_reserve: fee_reserve.as_ref().deref().clone(),
+                paid,
+                expiry,
+            },
+        }
+    }
+}

+ 47 - 0
bindings/cashu-ffi/src/types/mint_quote.rs

@@ -0,0 +1,47 @@
+use std::ops::Deref;
+use std::str::FromStr;
+use std::sync::Arc;
+
+use cashu::nuts::CurrencyUnit;
+use cashu::types::MintQuote as MintQuoteSdk;
+
+use crate::{Amount, Bolt11Invoice};
+
+pub struct MintQuote {
+    inner: MintQuoteSdk,
+}
+
+impl Deref for MintQuote {
+    type Target = MintQuoteSdk;
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl From<MintQuoteSdk> for MintQuote {
+    fn from(inner: MintQuoteSdk) -> MintQuote {
+        MintQuote { inner }
+    }
+}
+
+impl MintQuote {
+    pub fn new(
+        id: String,
+        amount: Arc<Amount>,
+        unit: String,
+        request: Arc<Bolt11Invoice>,
+        paid: bool,
+        expiry: u64,
+    ) -> Self {
+        Self {
+            inner: MintQuoteSdk {
+                id,
+                amount: amount.as_ref().deref().clone(),
+                unit: CurrencyUnit::from_str(&unit).unwrap(),
+                request: request.as_ref().deref().clone(),
+                paid,
+                expiry,
+            },
+        }
+    }
+}

+ 12 - 12
bindings/cashu-ffi/src/types/mint_quote_info.rs

@@ -3,42 +3,42 @@ use std::str::FromStr;
 use std::sync::Arc;
 
 use cashu::nuts::CurrencyUnit;
-use cashu::types::MintQuoteInfo as MintQuoteInfoSdk;
+use cashu::types::MintQuote as MintQuoteSdk;
 
 use crate::{Amount, Bolt11Invoice};
 
-pub struct MintQuoteInfo {
-    inner: MintQuoteInfoSdk,
+pub struct MintQuote {
+    inner: MintQuoteSdk,
 }
 
-impl Deref for MintQuoteInfo {
-    type Target = MintQuoteInfoSdk;
+impl Deref for MintQuote {
+    type Target = MintQuoteSdk;
     fn deref(&self) -> &Self::Target {
         &self.inner
     }
 }
 
-impl From<MintQuoteInfoSdk> for MintQuoteInfo {
-    fn from(inner: MintQuoteInfoSdk) -> MintQuoteInfo {
-        MintQuoteInfo { inner }
+impl From<MintQuoteSdk> for MintQuote {
+    fn from(inner: MintQuoteSdk) -> MintQuote {
+        MintQuote { inner }
     }
 }
 
-impl MintQuoteInfo {
+impl MintQuote {
     pub fn new(
         id: String,
         amount: Arc<Amount>,
         unit: String,
-        request: Option<Arc<Bolt11Invoice>>,
+        request: Arc<Bolt11Invoice>,
         paid: bool,
         expiry: u64,
     ) -> Self {
         Self {
-            inner: MintQuoteInfoSdk {
+            inner: MintQuoteSdk {
                 id,
                 amount: amount.as_ref().deref().clone(),
                 unit: CurrencyUnit::from_str(&unit).unwrap(),
-                request: request.map(|r| r.as_ref().deref().clone()),
+                request: request.as_ref().deref().clone(),
                 paid,
                 expiry,
             },

+ 4 - 2
bindings/cashu-ffi/src/types/mod.rs

@@ -1,11 +1,13 @@
 pub mod amount;
 pub mod bolt11_invoice;
 pub mod keyset_info;
-pub mod mint_quote_info;
+pub mod melt_quote;
+pub mod mint_quote;
 pub mod secret;
 
 pub use amount::Amount;
 pub use bolt11_invoice::Bolt11Invoice;
 pub use keyset_info::KeySetInfo;
-pub use mint_quote_info::MintQuoteInfo;
+pub use melt_quote::MeltQuote;
+pub use mint_quote::MintQuote;
 pub use secret::Secret;

+ 9 - 3
bindings/cashu-sdk-ffi/src/cashu_sdk.udl

@@ -34,8 +34,13 @@ interface Secret {
 	sequence<u8> as_bytes();	
 };
 
-interface MintQuoteInfo {
-	constructor(string id, Amount amount, string unit, Bolt11Invoice? request, boolean paid, u64 boolean);
+interface MintQuote {
+	constructor(string id, Amount amount, string unit, Bolt11Invoice request, boolean paid, u64 boolean);
+};
+
+
+interface MeltQuote {
+	constructor(string id, Amount amount, string unit, Bolt11Invoice request, Amount fee_reserve, boolean paid, u64 boolean);
 };
 
 // NUT00
@@ -299,6 +304,7 @@ interface Melted {
 };
 
 interface Wallet {
+	constructor(string mint_url, Keys mint_keys, sequence<MintQuote> mint_quotes, sequence<MeltQuote> melt_quotes);
 	// [Throws=CashuSdkError]
 	// ProofsStatus check_proofs_spent(sequence<Proof> proofs);
     [Throws=CashuSdkError]
@@ -312,7 +318,7 @@ interface Wallet {
     [Throws=CashuSdkError]
 	SendProofs send(Amount amount, sequence<Proof> proofs);
     [Throws=CashuSdkError]
-	Melted melt(string quote, sequence<Proof> proofs, Amount fee_reserve);
+	Melted melt(string quote_id, sequence<Proof> proofs);
     [Throws=CashuSdkError]
 	string proofs_to_token(sequence<Proof> proof, CurrencyUnit? unit, string? memo);
 };

+ 4 - 5
bindings/cashu-sdk-ffi/src/lib.rs

@@ -7,12 +7,11 @@ mod ffi {
     pub use cashu_ffi::{
         Amount, BlindedMessage, BlindedSignature, Bolt11Invoice, CashuError, CheckSpendableRequest,
         CheckSpendableResponse, CurrencyUnit, Id, InvoiceStatus, KeyPair, KeySet, KeySetInfo,
-        KeySetResponse, Keys, KeysResponse, MeltBolt11Request, MeltBolt11Response,
+        KeySetResponse, Keys, KeysResponse, MeltBolt11Request, MeltBolt11Response, MeltQuote,
         MeltQuoteBolt11Request, MeltQuoteBolt11Response, MintBolt11Request, MintBolt11Response,
-        MintInfo, MintKeySet, MintProof, MintProofs, MintQuoteBolt11Request,
-        MintQuoteBolt11Response, MintQuoteInfo, MintVersion, Nut05MeltBolt11Request,
-        Nut05MeltBolt11Response, PreMintSecrets, Proof, PublicKey, Secret, SecretKey, SwapRequest,
-        SwapResponse, Token,
+        MintInfo, MintKeySet, MintProof, MintProofs, MintQuote, MintQuoteBolt11Request,
+        MintQuoteBolt11Response, MintVersion, Nut05MeltBolt11Request, Nut05MeltBolt11Response,
+        PreMintSecrets, Proof, PublicKey, Secret, SecretKey, SwapRequest, SwapResponse, Token,
     };
 
     pub use crate::error::CashuSdkError;

+ 16 - 11
bindings/cashu-sdk-ffi/src/wallet.rs

@@ -1,7 +1,9 @@
 use std::ops::Deref;
 use std::sync::{Arc, RwLock};
 
-use cashu_ffi::{BlindedSignature, CurrencyUnit, MintQuoteInfo, PreMintSecrets, Proof, Token};
+use cashu_ffi::{
+    BlindedSignature, CurrencyUnit, MeltQuote, MintQuote, PreMintSecrets, Proof, Token,
+};
 use cashu_sdk::client::minreq_client::HttpClient;
 use cashu_sdk::types::ProofsStatus;
 use cashu_sdk::url::UncheckedUrl;
@@ -20,13 +22,22 @@ pub struct Wallet {
 }
 
 impl Wallet {
-    pub fn new(mint_url: &str, mint_keys: Arc<Keys>, quotes: Vec<Arc<MintQuoteInfo>>) -> Self {
+    pub fn new(
+        mint_url: String,
+        mint_keys: Arc<Keys>,
+        mint_quotes: Vec<Arc<MintQuote>>,
+        melt_quotes: Vec<Arc<MeltQuote>>,
+    ) -> Self {
         let client = HttpClient {};
         Self {
             inner: WalletSdk::new(
                 client,
                 UncheckedUrl::new(mint_url),
-                quotes
+                mint_quotes
+                    .into_iter()
+                    .map(|q| q.as_ref().deref().clone())
+                    .collect(),
+                melt_quotes
                     .into_iter()
                     .map(|q| q.as_ref().deref().clone())
                     .collect(),
@@ -111,20 +122,14 @@ impl Wallet {
         Ok(Arc::new(send_proofs.into()))
     }
 
-    pub fn melt(
-        &self,
-        quote: String,
-        proofs: Vec<Arc<Proof>>,
-        fee_reserve: Arc<Amount>,
-    ) -> Result<Arc<Melted>> {
+    pub fn melt(&self, quote_id: String, proofs: Vec<Arc<Proof>>) -> Result<Arc<Melted>> {
         let melted = RUNTIME.block_on(async {
             self.inner
                 .write()
                 .unwrap()
                 .melt(
-                    quote,
+                    &quote_id,
                     proofs.iter().map(|p| p.as_ref().deref().clone()).collect(),
-                    *fee_reserve.as_ref().deref(),
                 )
                 .await
         })?;

+ 9 - 8
bindings/cashu-sdk-js/src/wallet.rs

@@ -40,7 +40,13 @@ impl JsWallet {
         let client = HttpClient {};
 
         JsWallet {
-            inner: Wallet::new(client, mint_url.into(), vec![], mint_keys.deref().clone()),
+            inner: Wallet::new(
+                client,
+                mint_url.into(),
+                vec![],
+                vec![],
+                mint_keys.deref().clone(),
+            ),
         }
     }
 
@@ -123,17 +129,12 @@ impl JsWallet {
 
     /// Melt
     #[wasm_bindgen(js_name = melt)]
-    pub async fn melt(
-        &self,
-        quote: String,
-        proofs: JsValue,
-        fee_reserve: JsAmount,
-    ) -> Result<JsMelted> {
+    pub async fn melt(&self, quote: String, proofs: JsValue) -> Result<JsMelted> {
         let proofs = serde_wasm_bindgen::from_value(proofs).map_err(into_err)?;
 
         Ok(self
             .inner
-            .melt(quote, proofs, *fee_reserve.deref())
+            .melt(&quote, proofs)
             .await
             .map_err(into_err)?
             .into())

+ 29 - 1
crates/cashu-sdk/src/client/gloo_client.rs

@@ -7,7 +7,7 @@ use cashu::nuts::{
 };
 #[cfg(feature = "nut07")]
 use cashu::nuts::{CheckSpendableRequest, CheckSpendableResponse};
-use cashu::Amount;
+use cashu::{Amount, Bolt11Invoice};
 use gloo::net::http::Request;
 use serde_json::Value;
 use url::Url;
@@ -118,6 +118,34 @@ impl Client for HttpClient {
     }
 
     /// Melt [NUT-05]
+    async fn post_melt_quote(
+        &self,
+        mint_url: Url,
+        unit: CurrencyUnit,
+        request: Bolt11Invoice,
+    ) -> Result<MeltQuoteBolt11Response, Error> {
+        let url = join_url(mint_url, &["v1", "melt", "quote", "bolt11"])?;
+
+        let request = MeltQuoteBolt11Request { unit, request };
+        let res = Request::post(url.as_str())
+            .json(&request)
+            .map_err(|err| Error::Gloo(err.to_string()))?
+            .send()
+            .await
+            .map_err(|err| Error::Gloo(err.to_string()))?
+            .json::<Value>()
+            .await
+            .map_err(|err| Error::Gloo(err.to_string()))?;
+
+        let response: Result<MeltQuoteBolt11Response, serde_json::Error> =
+            serde_json::from_value(res.clone());
+
+        match response {
+            Ok(res) => Ok(res),
+            Err(_) => Err(Error::from_json(&res.to_string())?),
+        }
+    }
+
     /// [Nut-08] Lightning fee return if outputs defined
     async fn post_melt(
         &self,

+ 29 - 3
crates/cashu-sdk/src/client/minreq_client.rs

@@ -5,12 +5,13 @@ use std::println;
 use async_trait::async_trait;
 use cashu::nuts::{
     nut00, BlindedMessage, CurrencyUnit, Keys, KeysResponse, KeysetResponse, MeltBolt11Request,
-    MeltBolt11Response, MintBolt11Request, MintBolt11Response, MintInfo, MintQuoteBolt11Request,
-    MintQuoteBolt11Response, PreMintSecrets, Proof, SwapRequest, SwapResponse,
+    MeltBolt11Response, MeltQuoteBolt11Request, MeltQuoteBolt11Response, MintBolt11Request,
+    MintBolt11Response, MintInfo, MintQuoteBolt11Request, MintQuoteBolt11Response, PreMintSecrets,
+    Proof, SwapRequest, SwapResponse,
 };
 #[cfg(feature = "nut07")]
 use cashu::nuts::{CheckSpendableRequest, CheckSpendableResponse};
-use cashu::Amount;
+use cashu::{Amount, Bolt11Invoice};
 use serde_json::Value;
 use tracing::warn;
 use url::Url;
@@ -103,6 +104,31 @@ impl Client for HttpClient {
         }
     }
 
+    /// Melt Quote [NUT-05]
+    async fn post_melt_quote(
+        &self,
+        mint_url: Url,
+        unit: CurrencyUnit,
+        request: Bolt11Invoice,
+    ) -> Result<MeltQuoteBolt11Response, Error> {
+        let url = join_url(mint_url, &["v1", "melt", "quote", "bolt11"])?;
+
+        let request = MeltQuoteBolt11Request { request, unit };
+
+        let value = minreq::post(url)
+            .with_json(&request)?
+            .send()?
+            .json::<Value>()?;
+
+        let response: Result<MeltQuoteBolt11Response, serde_json::Error> =
+            serde_json::from_value(value.clone());
+
+        match response {
+            Ok(res) => Ok(res),
+            Err(_) => Err(Error::from_json(&value.to_string())?),
+        }
+    }
+
     /// Melt [NUT-05]
     /// [Nut-08] Lightning fee return if outputs defined
     async fn post_melt(

+ 10 - 2
crates/cashu-sdk/src/client/mod.rs

@@ -6,8 +6,9 @@ use cashu::nuts::nut00;
 #[cfg(feature = "nut07")]
 use cashu::nuts::CheckSpendableResponse;
 use cashu::nuts::{
-    BlindedMessage, CurrencyUnit, Keys, KeysetResponse, MeltBolt11Response, MintBolt11Response,
-    MintInfo, MintQuoteBolt11Response, PreMintSecrets, Proof, SwapRequest, SwapResponse,
+    BlindedMessage, CurrencyUnit, Keys, KeysetResponse, MeltBolt11Response,
+    MeltQuoteBolt11Response, MintBolt11Response, MintInfo, MintQuoteBolt11Response, PreMintSecrets,
+    Proof, SwapRequest, SwapResponse,
 };
 use cashu::{utils, Amount};
 use serde::{Deserialize, Serialize};
@@ -101,6 +102,13 @@ pub trait Client {
         premint_secrets: PreMintSecrets,
     ) -> Result<MintBolt11Response, Error>;
 
+    async fn post_melt_quote(
+        &self,
+        mint_url: Url,
+        unit: CurrencyUnit,
+        request: Bolt11Invoice,
+    ) -> Result<MeltQuoteBolt11Response, Error>;
+
     async fn post_melt(
         &self,
         mint_url: Url,

+ 7 - 7
crates/cashu-sdk/src/mint.rs

@@ -13,7 +13,7 @@ use cashu::Amount;
 use serde::{Deserialize, Serialize};
 use tracing::{debug, info};
 
-use crate::types::Quote;
+use crate::types::MeltQuote;
 
 pub struct Mint {
     //    pub pubkey: PublicKey
@@ -23,7 +23,7 @@ pub struct Mint {
     pub spent_secrets: HashSet<Secret>,
     pub pending_secrets: HashSet<Secret>,
     pub fee_reserve: FeeReserve,
-    pub quotes: HashMap<String, Quote>,
+    pub melt_quotes: HashMap<String, MeltQuote>,
 }
 
 impl Mint {
@@ -31,7 +31,7 @@ impl Mint {
         secret: &str,
         keysets_info: HashSet<MintKeySetInfo>,
         spent_secrets: HashSet<Secret>,
-        quotes: Vec<Quote>,
+        melt_quotes: Vec<MeltQuote>,
         min_fee_reserve: Amount,
         percent_fee_reserve: f32,
     ) -> Self {
@@ -40,7 +40,7 @@ impl Mint {
 
         let mut active_units: HashSet<String> = HashSet::default();
 
-        let quotes = quotes.into_iter().map(|q| (q.id.clone(), q)).collect();
+        let melt_quotes = melt_quotes.into_iter().map(|q| (q.id.clone(), q)).collect();
 
         // Check that there is only one active keyset per unit
         for keyset_info in keysets_info {
@@ -64,7 +64,7 @@ impl Mint {
         Self {
             _secret: secret.to_string(),
             keysets,
-            quotes,
+            melt_quotes,
             keysets_info: info,
             spent_secrets,
             pending_secrets: HashSet::new(),
@@ -235,14 +235,14 @@ impl Mint {
     }
 
     pub fn verify_melt_request(&mut self, melt_request: &MeltBolt11Request) -> Result<(), Error> {
-        let quote = self.quotes.get(&melt_request.quote).unwrap();
+        let quote = self.melt_quotes.get(&melt_request.quote).unwrap();
         let proofs_total = melt_request.proofs_amount();
 
         let required_total = quote.amount + quote.fee_reserve;
 
         if proofs_total < required_total.into() {
             debug!(
-                "Insufficient Proofs: Got: {:?}, Required: {}",
+                "Insufficient Proofs: Got: {}, Required: {}",
                 proofs_total, required_total
             );
             return Err(Error::Amount);

+ 62 - 20
crates/cashu-sdk/src/wallet.rs

@@ -11,7 +11,7 @@ use cashu::nuts::{
 };
 #[cfg(feature = "nut07")]
 use cashu::types::ProofsStatus;
-use cashu::types::{Melted, MintQuoteInfo, SendProofs};
+use cashu::types::{MeltQuote, Melted, MintQuote, SendProofs};
 use cashu::url::UncheckedUrl;
 use cashu::Amount;
 pub use cashu::Bolt11Invoice;
@@ -45,7 +45,8 @@ pub enum Error {
 pub struct Wallet<C: Client> {
     pub client: C,
     pub mint_url: UncheckedUrl,
-    pub quotes: HashMap<String, MintQuoteInfo>,
+    pub mint_quotes: HashMap<String, MintQuote>,
+    pub melt_quotes: HashMap<String, MeltQuote>,
     pub mint_keys: Keys,
     pub balance: Amount,
 }
@@ -54,14 +55,16 @@ impl<C: Client> Wallet<C> {
     pub fn new(
         client: C,
         mint_url: UncheckedUrl,
-        quotes: Vec<MintQuoteInfo>,
+        mint_quotes: Vec<MintQuote>,
+        melt_quotes: Vec<MeltQuote>,
         mint_keys: Keys,
     ) -> Self {
         Self {
             client,
             mint_url,
             mint_keys,
-            quotes: quotes.into_iter().map(|q| (q.id.clone(), q)).collect(),
+            mint_quotes: mint_quotes.into_iter().map(|q| (q.id.clone(), q)).collect(),
+            melt_quotes: melt_quotes.into_iter().map(|q| (q.id.clone(), q)).collect(),
             balance: Amount::ZERO,
         }
     }
@@ -120,29 +123,30 @@ impl<C: Client> Wallet<C> {
         &mut self,
         amount: Amount,
         unit: CurrencyUnit,
-    ) -> Result<MintQuoteInfo, Error> {
+    ) -> Result<MintQuote, Error> {
         let quote_res = self
             .client
             .post_mint_quote(self.mint_url.clone().try_into()?, amount, unit.clone())
             .await?;
 
-        let quote = MintQuoteInfo {
+        let quote = MintQuote {
             id: quote_res.quote.clone(),
             amount,
             unit: unit.clone(),
-            request: Some(Bolt11Invoice::from_str(&quote_res.request).unwrap()),
+            request: Bolt11Invoice::from_str(&quote_res.request).unwrap(),
             paid: quote_res.paid,
             expiry: quote_res.expiry,
         };
 
-        self.quotes.insert(quote_res.quote.clone(), quote.clone());
+        self.mint_quotes
+            .insert(quote_res.quote.clone(), quote.clone());
 
         Ok(quote)
     }
 
     /// Mint
-    pub async fn mint(&mut self, quote: &str) -> Result<Proofs, Error> {
-        let quote_info = self.quotes.get(quote);
+    pub async fn mint(&mut self, quote_id: &str) -> Result<Proofs, Error> {
+        let quote_info = self.mint_quotes.get(quote_id);
 
         let quote_info = if let Some(quote) = quote_info {
             if quote.expiry.le(&unix_time()) {
@@ -160,7 +164,7 @@ impl<C: Client> Wallet<C> {
             .client
             .post_mint(
                 self.mint_url.clone().try_into()?,
-                quote,
+                quote_id,
                 premint_secrets.clone(),
             )
             .await?;
@@ -172,7 +176,7 @@ impl<C: Client> Wallet<C> {
             &self.mint_keys,
         )?;
 
-        self.quotes.remove(&quote_info.id);
+        self.mint_quotes.remove(&quote_info.id);
 
         Ok(proofs)
     }
@@ -329,19 +333,57 @@ impl<C: Client> Wallet<C> {
         })
     }
 
+    /// Melt Quote
+    pub async fn melt_quote(
+        &mut self,
+        unit: CurrencyUnit,
+        request: Bolt11Invoice,
+    ) -> Result<MeltQuote, Error> {
+        let quote_res = self
+            .client
+            .post_melt_quote(
+                self.mint_url.clone().try_into()?,
+                unit.clone(),
+                request.clone(),
+            )
+            .await?;
+
+        let quote = MeltQuote {
+            id: quote_res.quote,
+            amount: quote_res.amount.into(),
+            request,
+            unit,
+            fee_reserve: quote_res.fee_reserve.into(),
+            paid: quote_res.paid,
+            expiry: quote_res.expiry,
+        };
+
+        self.melt_quotes.insert(quote.id.clone(), quote.clone());
+
+        Ok(quote)
+    }
+
     /// Melt
-    pub async fn melt(
-        &self,
-        quote: String,
-        proofs: Proofs,
-        fee_reserve: Amount,
-    ) -> Result<Melted, Error> {
-        let blinded = PreMintSecrets::blank((&self.mint_keys).into(), fee_reserve)?;
+    pub async fn melt(&self, quote_id: &str, proofs: Proofs) -> Result<Melted, Error> {
+        let quote_info = self.melt_quotes.get(quote_id);
+
+        let quote_info = if let Some(quote) = quote_info {
+            if quote.expiry.le(&unix_time()) {
+                return Err(Error::QuoteExpired);
+            }
+
+            quote.clone()
+        } else {
+            return Err(Error::QuoteUnknown);
+        };
+
+        let blinded = PreMintSecrets::blank((&self.mint_keys).into(), quote_info.fee_reserve)?;
+
         let melt_response = self
             .client
             .post_melt(
                 self.mint_url.clone().try_into()?,
-                quote,
+                quote_id.to_string(),
                 proofs,
                 Some(blinded.blinded_messages()),
             )

+ 8 - 0
crates/cashu/src/amount.rs

@@ -1,3 +1,5 @@
+use std::fmt;
+
 // https://github.com/clarkmoody/cashu-rs
 use serde::{Deserialize, Serialize};
 
@@ -62,6 +64,12 @@ impl std::ops::Sub for Amount {
     }
 }
 
+impl fmt::Display for Amount {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
 impl core::iter::Sum for Amount {
     fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
         let sats: u64 = iter.map(|amt| amt.0).sum();

+ 6 - 6
crates/cashu/src/types.rs

@@ -36,23 +36,23 @@ pub enum InvoiceStatus {
 
 /// Mint Quote Info
 #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
-pub struct MintQuoteInfo {
+pub struct MintQuote {
     pub id: String,
     pub amount: Amount,
     pub unit: CurrencyUnit,
-    pub request: Option<Bolt11Invoice>,
+    pub request: Bolt11Invoice,
     pub paid: bool,
     pub expiry: u64,
 }
 
-/// Quote
+/// Melt Quote Info
 #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
-pub struct Quote {
+pub struct MeltQuote {
     pub id: String,
-    pub amount: u64,
+    pub amount: Amount,
     pub request: Bolt11Invoice,
     pub unit: CurrencyUnit,
-    pub fee_reserve: u64,
+    pub fee_reserve: Amount,
     pub paid: bool,
     pub expiry: u64,
 }