diff --git a/crates/cashu-sdk/Cargo.toml b/crates/cashu-sdk/Cargo.toml index cf2a8346..5855cce6 100644 --- a/crates/cashu-sdk/Cargo.toml +++ b/crates/cashu-sdk/Cargo.toml @@ -28,6 +28,7 @@ tracing = { workspace = true } futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] } once_cell = { version = "1.17", optional = true } thiserror = { workspace = true } +async-trait = "0.1.74" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { workspace = true, features = ["rt-multi-thread", "time", "macros", "sync"] } diff --git a/crates/cashu-sdk/src/client/minreq_client.rs b/crates/cashu-sdk/src/client/minreq_client.rs new file mode 100644 index 00000000..08b7d93d --- /dev/null +++ b/crates/cashu-sdk/src/client/minreq_client.rs @@ -0,0 +1,216 @@ +//! Minreq http Client + +use async_trait::async_trait; +use cashu::nuts::nut00::wallet::BlindedMessages; +use cashu::nuts::nut00::{BlindedMessage, Proof}; +use cashu::nuts::nut01::Keys; +use cashu::nuts::nut03::RequestMintResponse; +use cashu::nuts::nut04::{MintRequest, PostMintResponse}; +use cashu::nuts::nut05::{CheckFeesRequest, CheckFeesResponse}; +use cashu::nuts::nut06::{SplitRequest, SplitResponse}; +#[cfg(feature = "nut07")] +use cashu::nuts::nut07::{CheckSpendableRequest, CheckSpendableResponse}; +use cashu::nuts::nut08::{MeltRequest, MeltResponse}; +#[cfg(feature = "nut09")] +use cashu::nuts::MintInfo; +use cashu::nuts::*; +use cashu::{Amount, Bolt11Invoice}; +#[cfg(target_arch = "wasm32")] +use gloo::net::http::Request; +use serde_json::Value; +use url::Url; + +use crate::client::{Client, Error}; + +#[derive(Debug, Clone)] +pub struct HttpClient {} + +#[async_trait(?Send)] +impl Client for HttpClient { + /// Get Mint Keys [NUT-01] + async fn get_mint_keys(&self, mint_url: &Url) -> Result { + let url = mint_url.join("keys")?; + let keys = minreq::get(url).send()?.json::()?; + + let keys: Keys = serde_json::from_str(&keys.to_string())?; + Ok(keys) + } + + /// Get Keysets [NUT-02] + async fn get_mint_keysets(&self, mint_url: &Url) -> Result { + let url = mint_url.join("keysets")?; + let res = minreq::get(url).send()?.json::()?; + + let response: Result = + serde_json::from_value(res.clone()); + + match response { + Ok(res) => Ok(res), + Err(_) => Err(Error::from_json(&res.to_string())?), + } + } + + /// Request Mint [NUT-03] + async fn get_request_mint( + &self, + mint_url: &Url, + amount: Amount, + ) -> Result { + let mut url = mint_url.join("mint")?; + url.query_pairs_mut() + .append_pair("amount", &amount.to_sat().to_string()); + + let res = minreq::get(url).send()?.json::()?; + + let response: Result = + serde_json::from_value(res.clone()); + + match response { + Ok(res) => Ok(res), + Err(_) => Err(Error::from_json(&res.to_string())?), + } + } + + /// Mint Tokens [NUT-04] + async fn post_mint( + &self, + mint_url: &Url, + blinded_messages: BlindedMessages, + hash: &str, + ) -> Result { + let mut url = mint_url.join("mint")?; + url.query_pairs_mut().append_pair("hash", hash); + + let request = MintRequest { + outputs: blinded_messages.blinded_messages, + }; + + let res = minreq::post(url) + .with_json(&request)? + .send()? + .json::()?; + + let response: Result = + serde_json::from_value(res.clone()); + + match response { + Ok(res) => Ok(res), + Err(_) => Err(Error::from_json(&res.to_string())?), + } + } + + /// Check Max expected fee [NUT-05] + async fn post_check_fees( + &self, + mint_url: &Url, + invoice: Bolt11Invoice, + ) -> Result { + let url = mint_url.join("checkfees")?; + + let request = CheckFeesRequest { pr: invoice }; + + let res = minreq::post(url) + .with_json(&request)? + .send()? + .json::()?; + + let response: Result = + serde_json::from_value(res.clone()); + + match response { + Ok(res) => Ok(res), + Err(_) => Err(Error::from_json(&res.to_string())?), + } + } + + /// Melt [NUT-05] + /// [Nut-08] Lightning fee return if outputs defined + async fn post_melt( + &self, + mint_url: &Url, + proofs: Vec, + invoice: Bolt11Invoice, + outputs: Option>, + ) -> Result { + let url = mint_url.join("melt")?; + + let request = MeltRequest { + proofs, + pr: invoice, + outputs, + }; + + 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())?), + } + } + + /// Split Token [NUT-06] + async fn post_split( + &self, + mint_url: &Url, + split_request: SplitRequest, + ) -> Result { + let url = mint_url.join("split")?; + + let res = minreq::post(url) + .with_json(&split_request)? + .send()? + .json::()?; + + let response: Result = + serde_json::from_value(res.clone()); + + match response { + Ok(res) if res.promises.is_some() => Ok(res), + _ => Err(Error::from_json(&res.to_string())?), + } + } + + /// Spendable check [NUT-07] + #[cfg(feature = "nut07")] + async fn post_check_spendable( + &self, + mint_url: &Url, + proofs: Vec, + ) -> Result { + let url = mint_url.join("check")?; + let request = CheckSpendableRequest { proofs }; + + let res = minreq::post(url) + .with_json(&request)? + .send()? + .json::()?; + + let response: Result = + serde_json::from_value(res.clone()); + + match response { + Ok(res) => Ok(res), + Err(_) => Err(Error::from_json(&res.to_string())?), + } + } + + /// Get Mint Info [NUT-09] + #[cfg(feature = "nut09")] + async fn get_mint_info(&self, mint_url: &Url) -> Result { + let url = mint_url.join("info")?; + let res = minreq::get(url).send()?.json::()?; + + let response: Result = serde_json::from_value(res.clone()); + + match response { + Ok(res) => Ok(res), + Err(_) => Err(Error::from_json(&res.to_string())?), + } + } +} diff --git a/crates/cashu-sdk/src/client/mod.rs b/crates/cashu-sdk/src/client/mod.rs index 4d4e3895..0499e2e4 100644 --- a/crates/cashu-sdk/src/client/mod.rs +++ b/crates/cashu-sdk/src/client/mod.rs @@ -1,30 +1,30 @@ //! Client to connet to mint -use std::str::FromStr; +use async_trait::async_trait; use cashu::nuts::nut00::wallet::BlindedMessages; use cashu::nuts::nut00::{BlindedMessage, Proof}; use cashu::nuts::nut01::Keys; use cashu::nuts::nut03::RequestMintResponse; -use cashu::nuts::nut04::{MintRequest, PostMintResponse}; -use cashu::nuts::nut05::{CheckFeesRequest, CheckFeesResponse}; +use cashu::nuts::nut04::PostMintResponse; +use cashu::nuts::nut05::CheckFeesResponse; use cashu::nuts::nut06::{SplitRequest, SplitResponse}; #[cfg(feature = "nut07")] use cashu::nuts::nut07::{CheckSpendableRequest, CheckSpendableResponse}; -use cashu::nuts::nut08::{MeltRequest, MeltResponse}; +use cashu::nuts::nut08::MeltResponse; #[cfg(feature = "nut09")] -use cashu::nuts::nut09::MintInfo; +use cashu::nuts::MintInfo; use cashu::nuts::*; -use cashu::url::UncheckedUrl; use cashu::{utils, Amount}; #[cfg(target_arch = "wasm32")] use gloo::net::http::Request; use serde::{Deserialize, Serialize}; -use serde_json::Value; use thiserror::Error; use url::Url; #[cfg(feature = "blocking")] pub mod blocking; +#[cfg(not(target_arch = "wasm32"))] +pub mod minreq_client; pub use cashu::Bolt11Invoice; @@ -55,13 +55,6 @@ pub enum Error { Custom(String), } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct MintErrorResponse { - code: u32, - error: Option, - detail: Option, -} - impl Error { pub fn from_json(json: &str) -> Result { let mint_res: MintErrorResponse = serde_json::from_str(json)?; @@ -84,454 +77,69 @@ impl Error { } } -#[derive(Debug, Clone)] -pub struct Client { - pub mint_url: UncheckedUrl, +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct MintErrorResponse { + code: u32, + error: Option, + detail: Option, } -impl Client { - pub fn new(mint_url: &str) -> Result { - let mint_url = UncheckedUrl::from_str(mint_url)?; - let _: Url = (&mint_url).try_into()?; - Ok(Self { mint_url }) - } +#[async_trait(?Send)] +pub trait Client { + async fn get_mint_keys(&self, mint_url: &Url) -> Result; - /// Get Mint Keys [NUT-01] - #[cfg(not(target_arch = "wasm32"))] - pub async fn get_keys(&self) -> Result { - let url = self.mint_url.join("keys")?; - let keys = minreq::get(url).send()?.json::()?; + async fn get_mint_keysets(&self, mint_url: &Url) -> Result; - let keys: Keys = serde_json::from_str(&keys.to_string())?; - Ok(keys) - } - - /// Get Mint Keys [NUT-01] - #[cfg(target_arch = "wasm32")] - pub async fn get_keys(&self) -> Result { - let url = self.mint_url.join("keys")?; - let keys = Request::get(url.as_str()) - .send() - .await - .map_err(|err| Error::Gloo(err.to_string()))? - .json::() - .await - .map_err(|err| Error::Gloo(err.to_string()))?; - - let keys: Keys = serde_json::from_str(&keys.to_string())?; - /* - let keys: BTreeMap = match serde_json::from_value(keys.clone()) { - Ok(keys) => keys, - Err(_err) => { - return Err(Error::CustomError(format!( - "url: {}, {}", - url, - serde_json::to_string(&keys)? - ))) - } - }; - - let mint_keys: BTreeMap = keys - .into_iter() - .filter_map(|(k, v)| { - let key = hex::decode(v).ok()?; - let public_key = PublicKey::from_sec1_bytes(&key).ok()?; - Some((k, public_key)) - }) - .collect(); - */ - Ok(keys) - } - - /// Get Keysets [NUT-02] - #[cfg(not(target_arch = "wasm32"))] - pub async fn get_keysets(&self) -> Result { - let url = self.mint_url.join("keysets")?; - let res = minreq::get(url).send()?.json::()?; - - let response: Result = - serde_json::from_value(res.clone()); - - match response { - Ok(res) => Ok(res), - Err(_) => Err(Error::from_json(&res.to_string())?), - } - } - - /// Get Keysets [NUT-02] - #[cfg(target_arch = "wasm32")] - pub async fn get_keysets(&self) -> Result { - let url = self.mint_url.join("keysets")?; - let res = Request::get(url.as_str()) - .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())?), - } - } - - /// Request Mint [NUT-03] - #[cfg(not(target_arch = "wasm32"))] - pub async fn request_mint(&self, amount: Amount) -> Result { - let mut url = self.mint_url.join("mint")?; - url.query_pairs_mut() - .append_pair("amount", &amount.to_sat().to_string()); - - let res = minreq::get(url).send()?.json::()?; - - let response: Result = - serde_json::from_value(res.clone()); - - match response { - Ok(res) => Ok(res), - Err(_) => Err(Error::from_json(&res.to_string())?), - } - } - - /// Request Mint [NUT-03] - #[cfg(target_arch = "wasm32")] - pub async fn request_mint(&self, amount: Amount) -> Result { - let mut url = self.mint_url.join("mint")?; - url.query_pairs_mut() - .append_pair("amount", &amount.to_sat().to_string()); - - let res = Request::get(url.as_str()) - .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())?), - } - } - - /// Mint Tokens [NUT-04] - #[cfg(not(target_arch = "wasm32"))] - pub async fn mint( + async fn get_request_mint( &self, + mint_url: &Url, + amount: Amount, + ) -> Result; + + // TODO: Hash should have a type + async fn post_mint( + &self, + mint_url: &Url, blinded_messages: BlindedMessages, hash: &str, - ) -> Result { - let mut url = self.mint_url.join("mint")?; - url.query_pairs_mut().append_pair("hash", hash); + ) -> Result; - let request = MintRequest { - outputs: blinded_messages.blinded_messages, - }; - - let res = minreq::post(url) - .with_json(&request)? - .send()? - .json::()?; - - let response: Result = - serde_json::from_value(res.clone()); - - match response { - Ok(res) => Ok(res), - Err(_) => Err(Error::from_json(&res.to_string())?), - } - } - - /// Mint Tokens [NUT-04] - #[cfg(target_arch = "wasm32")] - pub async fn mint( + async fn post_check_fees( &self, - blinded_messages: BlindedMessages, - hash: &str, - ) -> Result { - let mut url = self.mint_url.join("mint")?; - url.query_pairs_mut().append_pair("hash", hash); + mint_url: &Url, + invoice: Bolt11Invoice, + ) -> Result; - let request = MintRequest { - outputs: blinded_messages.blinded_messages, - }; - - 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())?), - } - } - - /// Check Max expected fee [NUT-05] - #[cfg(not(target_arch = "wasm32"))] - pub async fn check_fees(&self, invoice: Bolt11Invoice) -> Result { - let url = self.mint_url.join("checkfees")?; - - let request = CheckFeesRequest { pr: invoice }; - - let res = minreq::post(url) - .with_json(&request)? - .send()? - .json::()?; - - let response: Result = - serde_json::from_value(res.clone()); - - match response { - Ok(res) => Ok(res), - Err(_) => Err(Error::from_json(&res.to_string())?), - } - } - - /// Check Max expected fee [NUT-05] - #[cfg(target_arch = "wasm32")] - pub async fn check_fees(&self, invoice: Bolt11Invoice) -> Result { - let url = self.mint_url.join("checkfees")?; - - let request = CheckFeesRequest { pr: invoice }; - - 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())?), - } - } - - /// Melt [NUT-05] - /// [Nut-08] Lightning fee return if outputs defined - #[cfg(not(target_arch = "wasm32"))] - pub async fn melt( + async fn post_melt( &self, + mint_url: &Url, proofs: Vec, invoice: Bolt11Invoice, outputs: Option>, - ) -> Result { - let url = self.mint_url.join("melt")?; + ) -> Result; - let request = MeltRequest { - proofs, - pr: invoice, - outputs, - }; - - 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 - #[cfg(target_arch = "wasm32")] - pub async fn melt( + // REVIEW: Should be consistent aboue passing in the Request struct or the + // compnatants and making it within the function. Here the struct is passed + // in but in check spendable and melt the compants are passed in + async fn post_split( &self, - proofs: Vec, - invoice: Bolt11Invoice, - outputs: Option>, - ) -> Result { - let url = self.mint_url.join("melt")?; + mint_url: &Url, + split_request: SplitRequest, + ) -> Result; - let request = MeltRequest { - proofs, - pr: invoice, - outputs, - }; - - let value = 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(value.clone()); - - match response { - Ok(res) => Ok(res), - Err(_) => Err(Error::from_json(&value.to_string())?), - } - } - - /// Split Token [NUT-06] - #[cfg(not(target_arch = "wasm32"))] - pub async fn split(&self, split_request: SplitRequest) -> Result { - let url = self.mint_url.join("split")?; - - let res = minreq::post(url) - .with_json(&split_request)? - .send()? - .json::()?; - - let response: Result = - serde_json::from_value(res.clone()); - - match response { - Ok(res) if res.promises.is_some() => Ok(res), - _ => Err(Error::from_json(&res.to_string())?), - } - } - - /// Split Token [NUT-06] - #[cfg(target_arch = "wasm32")] - pub async fn split(&self, split_request: SplitRequest) -> Result { - let url = self.mint_url.join("split")?; - - let res = Request::post(url.as_str()) - .json(&split_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())?), - } - } - - /// Spendable check [NUT-07] - #[cfg(all(not(target_arch = "wasm32"), feature = "nut07"))] - pub async fn check_spendable( + #[cfg(feature = "nut07")] + async fn post_check_spendable( &self, - proofs: &Vec, - ) -> Result { - let url = self.mint_url.join("check")?; - let request = CheckSpendableRequest { - proofs: proofs.to_owned(), - }; + mint_url: &Url, + proofs: Vec, + ) -> Result; - let res = minreq::post(url) - .with_json(&request)? - .send()? - .json::()?; - - let response: Result = - serde_json::from_value(res.clone()); - - match response { - Ok(res) => Ok(res), - Err(_) => Err(Error::from_json(&res.to_string())?), - } - } - - /// Spendable check [NUT-07] - #[cfg(all(target_arch = "wasm32", feature = "nut07"))] - pub async fn check_spendable( - &self, - proofs: &Vec, - ) -> Result { - let url = self.mint_url.join("check")?; - let request = CheckSpendableRequest { - proofs: proofs.to_owned(), - }; - - 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())?), - } - } - - /// Get Mint Info [NUT-09] - #[cfg(all(not(target_arch = "wasm32"), feature = "nut09"))] - pub async fn get_info(&self) -> Result { - let url = self.mint_url.join("info")?; - let res = minreq::get(url).send()?.json::()?; - - let response: Result = serde_json::from_value(res.clone()); - - match response { - Ok(res) => Ok(res), - Err(_) => Err(Error::from_json(&res.to_string())?), - } - } - - /// Get Mint Info [NUT-09] - #[cfg(all(target_arch = "wasm32", feature = "nut09"))] - pub async fn get_info(&self) -> Result { - let url = self.mint_url.join("info")?; - let res = Request::get(url.as_str()) - .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())?), - } - } + #[cfg(feature = "nut09")] + async fn get_mint_info(&self, mint_url: &Url) -> Result; } #[cfg(test)] mod tests { - use super::*; #[test] diff --git a/crates/cashu-sdk/src/wallet.rs b/crates/cashu-sdk/src/wallet.rs index b691abe6..43bf2866 100644 --- a/crates/cashu-sdk/src/wallet.rs +++ b/crates/cashu-sdk/src/wallet.rs @@ -8,6 +8,7 @@ use cashu::nuts::nut01::Keys; use cashu::nuts::nut03::RequestMintResponse; use cashu::nuts::nut06::{SplitPayload, SplitRequest}; use cashu::types::{Melted, SendProofs}; +use cashu::url::UncheckedUrl; use cashu::Amount; pub use cashu::Bolt11Invoice; #[cfg(feature = "nut07")] @@ -15,9 +16,6 @@ use cashu::{nuts::nut00::mint, types::ProofsStatus}; use thiserror::Error; use tracing::warn; -#[cfg(feature = "blocking")] -use crate::client::blocking::Client; -#[cfg(not(feature = "blocking"))] use crate::client::Client; #[derive(Debug, Error)] @@ -29,21 +27,26 @@ pub enum Error { Cashu(#[from] cashu::error::wallet::Error), #[error("`{0}`")] Client(#[from] crate::client::Error), + /// Cashu Url Error + #[error("`{0}`")] + CashuUrl(#[from] cashu::url::Error), #[error("`{0}`")] Custom(String), } #[derive(Clone, Debug)] -pub struct Wallet { - pub client: Client, +pub struct Wallet { + pub client: C, + pub mint_url: UncheckedUrl, pub mint_keys: Keys, pub balance: Amount, } -impl Wallet { - pub fn new(client: Client, mint_keys: Keys) -> Self { +impl Wallet { + pub fn new(client: C, mint_url: UncheckedUrl, mint_keys: Keys) -> Self { Self { client, + mint_url, mint_keys, balance: Amount::ZERO, } @@ -52,26 +55,17 @@ impl Wallet { // TODO: getter method for keys that if it cant get them try again /// Check if a proof is spent - #[cfg(all(not(feature = "blocking"), feature = "nut07"))] - pub async fn check_proofs_spent(&self, proofs: &mint::Proofs) -> Result { - let spendable = self.client.check_spendable(proofs).await?; + #[cfg(feature = "nut07")] + pub async fn check_proofs_spent( + &self, + proofs: Vec, + ) -> Result { + use cashu::types::ProofsStatus; - // Separate proofs in spent and unspent based on mint response - let (spendable, spent): (Vec<_>, Vec<_>) = proofs - .iter() - .zip(spendable.spendable.iter()) - .partition(|(_, &b)| b); - - Ok(ProofsStatus { - spendable: spendable.into_iter().map(|(s, _)| s).cloned().collect(), - spent: spent.into_iter().map(|(s, _)| s).cloned().collect(), - }) - } - - /// Check if a proof is spent - #[cfg(all(feature = "blocking", feature = "nut07"))] - pub fn check_proofs_spent(&self, proofs: &mint::Proofs) -> Result { - let spendable = self.client.check_spendable(proofs)?; + let spendable = self + .client + .post_check_spendable(&self.mint_url.clone().try_into()?, proofs.clone()) + .await?; // Separate proofs in spent and unspent based on mint response let (spendable, spent): (Vec<_>, Vec<_>) = proofs @@ -86,58 +80,32 @@ impl Wallet { } /// Request Token Mint - #[cfg(not(feature = "blocking"))] pub async fn request_mint(&self, amount: Amount) -> Result { - Ok(self.client.request_mint(amount).await?) + Ok(self + .client + .get_request_mint(&self.mint_url.clone().try_into()?, amount) + .await?) } - /// Request Token Mint - #[cfg(feature = "blocking")] - pub fn request_mint(&self, amount: Amount) -> Result { - Ok(self.client.request_mint(amount)?) - } - - /// Mint Token - #[cfg(not(feature = "blocking"))] pub async fn mint_token(&self, amount: Amount, hash: &str) -> Result { let proofs = self.mint(amount, hash).await?; - let token = Token::new(self.client.mint_url.clone(), proofs, None); - Ok(token?) - } - - /// Blocking Mint Token - #[cfg(feature = "blocking")] - pub fn mint_token(&self, amount: Amount, hash: &str) -> Result { - let proofs = self.mint(amount, hash)?; - - let token = Token::new(self.client.client.mint_url.clone(), proofs, None); + let token = Token::new(self.mint_url.clone().into(), proofs, None); Ok(token?) } /// Mint Proofs - #[cfg(not(feature = "blocking"))] pub async fn mint(&self, amount: Amount, hash: &str) -> Result { let blinded_messages = BlindedMessages::random(amount)?; - let mint_res = self.client.mint(blinded_messages.clone(), hash).await?; - - let proofs = construct_proofs( - mint_res.promises, - blinded_messages.rs, - blinded_messages.secrets, - &self.mint_keys, - )?; - - Ok(proofs) - } - - /// Blocking Mint Proofs - #[cfg(feature = "blocking")] - pub fn mint(&self, amount: Amount, hash: &str) -> Result { - let blinded_messages = BlindedMessages::random(amount)?; - - let mint_res = self.client.mint(blinded_messages.clone(), hash)?; + let mint_res = self + .client + .post_mint( + &self.mint_url.clone().try_into()?, + blinded_messages.clone(), + hash, + ) + .await?; let proofs = construct_proofs( mint_res.promises, @@ -150,19 +118,15 @@ impl Wallet { } /// Check fee - #[cfg(not(feature = "blocking"))] pub async fn check_fee(&self, invoice: Bolt11Invoice) -> Result { - Ok(self.client.check_fees(invoice).await?.fee) - } - - /// Check fee - #[cfg(feature = "blocking")] - pub fn check_fee(&self, invoice: Bolt11Invoice) -> Result { - Ok(self.client.check_fees(invoice)?.fee) + Ok(self + .client + .post_check_fees(&self.mint_url.clone().try_into()?, invoice) + .await? + .fee) } /// Receive - #[cfg(not(feature = "blocking"))] pub async fn receive(&self, encoded_token: &str) -> Result { let token_data = Token::from_str(encoded_token)?; @@ -172,12 +136,10 @@ impl Wallet { continue; } - let keys = if token.mint.to_string().eq(&self.client.mint_url.to_string()) { + let keys = if token.mint.to_string().eq(&self.mint_url.to_string()) { self.mint_keys.clone() } else { - Client::new(token.mint.to_string().as_str())? - .get_keys() - .await? + self.client.get_mint_keys(&token.mint.try_into()?).await? }; // Sum amount of all proofs @@ -185,52 +147,13 @@ impl Wallet { let split_payload = self.create_split(token.proofs)?; - let split_response = self.client.split(split_payload.split_payload).await?; - - if let Some(promises) = &split_response.promises { - // Proof to keep - let p = construct_proofs( - promises.to_owned(), - split_payload.blinded_messages.rs, - split_payload.blinded_messages.secrets, - &keys, - )?; - proofs.push(p); - } else { - warn!("Response missing promises"); - return Err(Error::Custom("Split response missing promises".to_string())); - } - } - Ok(proofs.iter().flatten().cloned().collect()) - } - - /// Blocking Receive - #[cfg(feature = "blocking")] - pub fn receive(&self, encoded_token: &str) -> Result { - let token_data = Token::from_str(encoded_token)?; - - let mut proofs: Vec = vec![vec![]]; - for token in token_data.token { - if token.proofs.is_empty() { - continue; - } - - let keys = if token - .mint - .to_string() - .eq(&self.client.client.mint_url.to_string()) - { - self.mint_keys.clone() - } else { - Client::new(&token.mint.to_string())?.get_keys()? - }; - - // Sum amount of all proofs - let _amount: Amount = token.proofs.iter().map(|p| p.amount).sum(); - - let split_payload = self.create_split(token.proofs)?; - - let split_response = self.client.split(split_payload.split_payload)?; + let split_response = self + .client + .post_split( + &self.mint_url.clone().try_into()?, + split_payload.split_payload, + ) + .await?; if let Some(promises) = &split_response.promises { // Proof to keep @@ -302,7 +225,6 @@ impl Wallet { } /// Send - #[cfg(not(feature = "blocking"))] pub async fn send(&self, amount: Amount, proofs: Proofs) -> Result { let mut amount_available = Amount::ZERO; let mut send_proofs = SendProofs::default(); @@ -332,7 +254,13 @@ impl Wallet { let split_payload = self.create_split(send_proofs.send_proofs)?; - let split_response = self.client.split(split_payload.split_payload).await?; + let split_response = self + .client + .post_split( + &self.mint_url.clone().try_into()?, + split_payload.split_payload, + ) + .await?; // If only promises assemble proofs needed for amount let keep_proofs; @@ -363,69 +291,6 @@ impl Wallet { }) } - /// Send - #[cfg(feature = "blocking")] - pub fn send(&self, amount: Amount, proofs: Proofs) -> Result { - let mut amount_available = Amount::ZERO; - let mut send_proofs = SendProofs::default(); - - for proof in proofs { - let proof_value = proof.amount; - if amount_available > amount { - send_proofs.change_proofs.push(proof); - } else { - send_proofs.send_proofs.push(proof); - } - amount_available += proof_value; - } - - if amount_available.lt(&amount) { - println!("Not enough funds"); - return Err(Error::InsufficientFunds); - } - - // If amount available is EQUAL to send amount no need to split - if amount_available.eq(&amount) { - return Ok(send_proofs); - } - - let _amount_to_keep = amount_available - amount; - let amount_to_send = amount; - - let split_payload = self.create_split(send_proofs.send_proofs)?; - - let split_response = self.client.split(split_payload.split_payload)?; - - // If only promises assemble proofs needed for amount - let keep_proofs; - let send_proofs; - - if let Some(promises) = split_response.promises { - let proofs = construct_proofs( - promises, - split_payload.blinded_messages.rs, - split_payload.blinded_messages.secrets, - &self.mint_keys, - )?; - - let split = amount_to_send.split(); - - keep_proofs = proofs[0..split.len()].to_vec(); - send_proofs = proofs[split.len()..].to_vec(); - } else { - return Err(Error::Custom("Invalid split response".to_string())); - } - - // println!("Send Proofs: {:#?}", send_proofs); - // println!("Keep Proofs: {:#?}", keep_proofs); - - Ok(SendProofs { - change_proofs: keep_proofs, - send_proofs, - }) - } - - #[cfg(not(feature = "blocking"))] pub async fn melt( &self, invoice: Bolt11Invoice, @@ -435,7 +300,12 @@ impl Wallet { let blinded = BlindedMessages::blank(fee_reserve)?; let melt_response = self .client - .melt(proofs, invoice, Some(blinded.blinded_messages)) + .post_melt( + &self.mint_url.clone().try_into()?, + proofs, + invoice, + Some(blinded.blinded_messages), + ) .await?; let change_proofs = match melt_response.change { @@ -457,45 +327,8 @@ impl Wallet { Ok(melted) } - #[cfg(feature = "blocking")] - pub fn melt( - &self, - invoice: Bolt11Invoice, - proofs: Proofs, - fee_reserve: Amount, - ) -> Result { - let blinded = BlindedMessages::blank(fee_reserve)?; - let melt_response = self - .client - .melt(proofs, invoice, Some(blinded.blinded_messages))?; - - let change_proofs = match melt_response.change { - Some(change) => Some(construct_proofs( - change, - blinded.rs, - blinded.secrets, - &self.mint_keys, - )?), - None => None, - }; - - let melted = Melted { - paid: true, - preimage: melt_response.preimage, - change: change_proofs, - }; - - Ok(melted) - } - - #[cfg(not(feature = "blocking"))] pub fn proofs_to_token(&self, proofs: Proofs, memo: Option) -> Result { - Ok(Token::new(self.client.mint_url.clone(), proofs, memo)?.convert_to_string()?) - } - - #[cfg(feature = "blocking")] - pub fn proofs_to_token(&self, proofs: Proofs, memo: Option) -> Result { - Ok(Token::new(self.client.client.mint_url.clone(), proofs, memo)?.convert_to_string()?) + Ok(Token::new(self.mint_url.clone(), proofs, memo)?.convert_to_string()?) } } diff --git a/crates/cashu/src/nuts/mod.rs b/crates/cashu/src/nuts/mod.rs index 5401fdb6..ccf76016 100644 --- a/crates/cashu/src/nuts/mod.rs +++ b/crates/cashu/src/nuts/mod.rs @@ -11,3 +11,6 @@ pub mod nut07; pub mod nut08; #[cfg(feature = "nut09")] pub mod nut09; + +#[cfg(feature = "nut09")] +pub use nut09::MintInfo;