From 754936d701b02e7684605a4f0dae8061bc4da7bf Mon Sep 17 00:00:00 2001 From: thesimplekid Date: Fri, 29 Dec 2023 07:02:12 +0000 Subject: [PATCH] refactor: melt quote and melt to v1 in wallet sdk --- bindings/cashu-ffi/src/cashu.udl | 8 +- bindings/cashu-ffi/src/lib.rs | 2 +- bindings/cashu-ffi/src/types/melt_quote.rs | 49 +++++++++++ bindings/cashu-ffi/src/types/mint_quote.rs | 47 +++++++++++ .../cashu-ffi/src/types/mint_quote_info.rs | 24 +++--- bindings/cashu-ffi/src/types/mod.rs | 6 +- bindings/cashu-sdk-ffi/src/cashu_sdk.udl | 12 ++- bindings/cashu-sdk-ffi/src/lib.rs | 9 +- bindings/cashu-sdk-ffi/src/wallet.rs | 27 +++--- bindings/cashu-sdk-js/src/wallet.rs | 17 ++-- crates/cashu-sdk/src/client/gloo_client.rs | 30 ++++++- crates/cashu-sdk/src/client/minreq_client.rs | 32 +++++++- crates/cashu-sdk/src/client/mod.rs | 12 ++- crates/cashu-sdk/src/mint.rs | 14 ++-- crates/cashu-sdk/src/wallet.rs | 82 ++++++++++++++----- crates/cashu/src/amount.rs | 8 ++ crates/cashu/src/types.rs | 12 +-- 17 files changed, 308 insertions(+), 83 deletions(-) create mode 100644 bindings/cashu-ffi/src/types/melt_quote.rs create mode 100644 bindings/cashu-ffi/src/types/mint_quote.rs diff --git a/bindings/cashu-ffi/src/cashu.udl b/bindings/cashu-ffi/src/cashu.udl index 660762b7..ac8ddea2 100644 --- a/bindings/cashu-ffi/src/cashu.udl +++ b/bindings/cashu-ffi/src/cashu.udl @@ -31,8 +31,12 @@ interface Secret { sequence 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 diff --git a/bindings/cashu-ffi/src/lib.rs b/bindings/cashu-ffi/src/lib.rs index c19a6118..e0be90db 100644 --- a/bindings/cashu-ffi/src/lib.rs +++ b/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"); diff --git a/bindings/cashu-ffi/src/types/melt_quote.rs b/bindings/cashu-ffi/src/types/melt_quote.rs new file mode 100644 index 00000000..052cbd99 --- /dev/null +++ b/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 for MeltQuote { + fn from(inner: MeltQuoteSdk) -> MeltQuote { + MeltQuote { inner } + } +} + +impl MeltQuote { + pub fn new( + id: String, + amount: Arc, + unit: String, + request: Arc, + fee_reserve: Arc, + 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, + }, + } + } +} diff --git a/bindings/cashu-ffi/src/types/mint_quote.rs b/bindings/cashu-ffi/src/types/mint_quote.rs new file mode 100644 index 00000000..4d59b2e9 --- /dev/null +++ b/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 for MintQuote { + fn from(inner: MintQuoteSdk) -> MintQuote { + MintQuote { inner } + } +} + +impl MintQuote { + pub fn new( + id: String, + amount: Arc, + unit: String, + request: Arc, + 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, + }, + } + } +} diff --git a/bindings/cashu-ffi/src/types/mint_quote_info.rs b/bindings/cashu-ffi/src/types/mint_quote_info.rs index 838e3071..4d59b2e9 100644 --- a/bindings/cashu-ffi/src/types/mint_quote_info.rs +++ b/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 for MintQuoteInfo { - fn from(inner: MintQuoteInfoSdk) -> MintQuoteInfo { - MintQuoteInfo { inner } +impl From for MintQuote { + fn from(inner: MintQuoteSdk) -> MintQuote { + MintQuote { inner } } } -impl MintQuoteInfo { +impl MintQuote { pub fn new( id: String, amount: Arc, unit: String, - request: Option>, + request: Arc, 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, }, diff --git a/bindings/cashu-ffi/src/types/mod.rs b/bindings/cashu-ffi/src/types/mod.rs index 0bb5acbf..3240231d 100644 --- a/bindings/cashu-ffi/src/types/mod.rs +++ b/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; diff --git a/bindings/cashu-sdk-ffi/src/cashu_sdk.udl b/bindings/cashu-sdk-ffi/src/cashu_sdk.udl index c331952b..dae52d71 100644 --- a/bindings/cashu-sdk-ffi/src/cashu_sdk.udl +++ b/bindings/cashu-sdk-ffi/src/cashu_sdk.udl @@ -34,8 +34,13 @@ interface Secret { sequence 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 mint_quotes, sequence melt_quotes); // [Throws=CashuSdkError] // ProofsStatus check_proofs_spent(sequence proofs); [Throws=CashuSdkError] @@ -312,7 +318,7 @@ interface Wallet { [Throws=CashuSdkError] SendProofs send(Amount amount, sequence proofs); [Throws=CashuSdkError] - Melted melt(string quote, sequence proofs, Amount fee_reserve); + Melted melt(string quote_id, sequence proofs); [Throws=CashuSdkError] string proofs_to_token(sequence proof, CurrencyUnit? unit, string? memo); }; diff --git a/bindings/cashu-sdk-ffi/src/lib.rs b/bindings/cashu-sdk-ffi/src/lib.rs index 18893f16..08946483 100644 --- a/bindings/cashu-sdk-ffi/src/lib.rs +++ b/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; diff --git a/bindings/cashu-sdk-ffi/src/wallet.rs b/bindings/cashu-sdk-ffi/src/wallet.rs index 0391a56c..e6a442b6 100644 --- a/bindings/cashu-sdk-ffi/src/wallet.rs +++ b/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, quotes: Vec>) -> Self { + pub fn new( + mint_url: String, + mint_keys: Arc, + mint_quotes: Vec>, + melt_quotes: Vec>, + ) -> 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>, - fee_reserve: Arc, - ) -> Result> { + pub fn melt(&self, quote_id: String, proofs: Vec>) -> Result> { let melted = RUNTIME.block_on(async { self.inner .write() .unwrap() .melt( - quote, + "e_id, proofs.iter().map(|p| p.as_ref().deref().clone()).collect(), - *fee_reserve.as_ref().deref(), ) .await })?; diff --git a/bindings/cashu-sdk-js/src/wallet.rs b/bindings/cashu-sdk-js/src/wallet.rs index 3bff2524..30df3929 100644 --- a/bindings/cashu-sdk-js/src/wallet.rs +++ b/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 { + pub async fn melt(&self, quote: String, proofs: JsValue) -> Result { let proofs = serde_wasm_bindgen::from_value(proofs).map_err(into_err)?; Ok(self .inner - .melt(quote, proofs, *fee_reserve.deref()) + .melt("e, proofs) .await .map_err(into_err)? .into()) diff --git a/crates/cashu-sdk/src/client/gloo_client.rs b/crates/cashu-sdk/src/client/gloo_client.rs index 4b7f4dc6..c0d67578 100644 --- a/crates/cashu-sdk/src/client/gloo_client.rs +++ b/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 { + 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::() + .await + .map_err(|err| Error::Gloo(err.to_string()))?; + + let response: Result = + 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, diff --git a/crates/cashu-sdk/src/client/minreq_client.rs b/crates/cashu-sdk/src/client/minreq_client.rs index c93432d3..b39fedc7 100644 --- a/crates/cashu-sdk/src/client/minreq_client.rs +++ b/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 { + 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::()?; + + let response: Result = + 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( diff --git a/crates/cashu-sdk/src/client/mod.rs b/crates/cashu-sdk/src/client/mod.rs index 05a25b91..38b3993d 100644 --- a/crates/cashu-sdk/src/client/mod.rs +++ b/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; + async fn post_melt_quote( + &self, + mint_url: Url, + unit: CurrencyUnit, + request: Bolt11Invoice, + ) -> Result; + async fn post_melt( &self, mint_url: Url, diff --git a/crates/cashu-sdk/src/mint.rs b/crates/cashu-sdk/src/mint.rs index faaacd6e..e7eac397 100644 --- a/crates/cashu-sdk/src/mint.rs +++ b/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, pub pending_secrets: HashSet, pub fee_reserve: FeeReserve, - pub quotes: HashMap, + pub melt_quotes: HashMap, } impl Mint { @@ -31,7 +31,7 @@ impl Mint { secret: &str, keysets_info: HashSet, spent_secrets: HashSet, - quotes: Vec, + melt_quotes: Vec, min_fee_reserve: Amount, percent_fee_reserve: f32, ) -> Self { @@ -40,7 +40,7 @@ impl Mint { let mut active_units: HashSet = 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); diff --git a/crates/cashu-sdk/src/wallet.rs b/crates/cashu-sdk/src/wallet.rs index 74505aee..8c4d542e 100644 --- a/crates/cashu-sdk/src/wallet.rs +++ b/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 { pub client: C, pub mint_url: UncheckedUrl, - pub quotes: HashMap, + pub mint_quotes: HashMap, + pub melt_quotes: HashMap, pub mint_keys: Keys, pub balance: Amount, } @@ -54,14 +55,16 @@ impl Wallet { pub fn new( client: C, mint_url: UncheckedUrl, - quotes: Vec, + mint_quotes: Vec, + melt_quotes: Vec, 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 Wallet { &mut self, amount: Amount, unit: CurrencyUnit, - ) -> Result { + ) -> Result { 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("e_res.request).unwrap()), + request: Bolt11Invoice::from_str("e_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 { - let quote_info = self.quotes.get(quote); + pub async fn mint(&mut self, quote_id: &str) -> Result { + 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 Wallet { .client .post_mint( self.mint_url.clone().try_into()?, - quote, + quote_id, premint_secrets.clone(), ) .await?; @@ -172,7 +176,7 @@ impl Wallet { &self.mint_keys, )?; - self.quotes.remove("e_info.id); + self.mint_quotes.remove("e_info.id); Ok(proofs) } @@ -329,19 +333,57 @@ impl Wallet { }) } + /// Melt Quote + pub async fn melt_quote( + &mut self, + unit: CurrencyUnit, + request: Bolt11Invoice, + ) -> Result { + 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 { - let blinded = PreMintSecrets::blank((&self.mint_keys).into(), fee_reserve)?; + pub async fn melt(&self, quote_id: &str, proofs: Proofs) -> Result { + 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()), ) diff --git a/crates/cashu/src/amount.rs b/crates/cashu/src/amount.rs index 13a0e871..cd5a1e52 100644 --- a/crates/cashu/src/amount.rs +++ b/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>(iter: I) -> Self { let sats: u64 = iter.map(|amt| amt.0).sum(); diff --git a/crates/cashu/src/types.rs b/crates/cashu/src/types.rs index 041ce6aa..fbb8cdc5 100644 --- a/crates/cashu/src/types.rs +++ b/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, + 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, }