Browse Source

improve: add 'Client' trait, minreq async client

thesimplekid 1 year ago
parent
commit
38318bbc78

+ 1 - 0
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"] }

+ 216 - 0
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<Keys, Error> {
+        let url = mint_url.join("keys")?;
+        let keys = minreq::get(url).send()?.json::<Value>()?;
+
+        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<nut02::Response, Error> {
+        let url = mint_url.join("keysets")?;
+        let res = minreq::get(url).send()?.json::<Value>()?;
+
+        let response: Result<nut02::Response, serde_json::Error> =
+            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<RequestMintResponse, Error> {
+        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::<Value>()?;
+
+        let response: Result<RequestMintResponse, serde_json::Error> =
+            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<PostMintResponse, Error> {
+        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::<Value>()?;
+
+        let response: Result<PostMintResponse, serde_json::Error> =
+            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<CheckFeesResponse, Error> {
+        let url = mint_url.join("checkfees")?;
+
+        let request = CheckFeesRequest { pr: invoice };
+
+        let res = minreq::post(url)
+            .with_json(&request)?
+            .send()?
+            .json::<Value>()?;
+
+        let response: Result<CheckFeesResponse, serde_json::Error> =
+            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<Proof>,
+        invoice: Bolt11Invoice,
+        outputs: Option<Vec<BlindedMessage>>,
+    ) -> Result<MeltResponse, Error> {
+        let url = mint_url.join("melt")?;
+
+        let request = MeltRequest {
+            proofs,
+            pr: invoice,
+            outputs,
+        };
+
+        let value = minreq::post(url)
+            .with_json(&request)?
+            .send()?
+            .json::<Value>()?;
+
+        let response: Result<MeltResponse, serde_json::Error> =
+            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<SplitResponse, Error> {
+        let url = mint_url.join("split")?;
+
+        let res = minreq::post(url)
+            .with_json(&split_request)?
+            .send()?
+            .json::<Value>()?;
+
+        let response: Result<SplitResponse, serde_json::Error> =
+            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<nut00::mint::Proof>,
+    ) -> Result<CheckSpendableResponse, Error> {
+        let url = mint_url.join("check")?;
+        let request = CheckSpendableRequest { proofs };
+
+        let res = minreq::post(url)
+            .with_json(&request)?
+            .send()?
+            .json::<Value>()?;
+
+        let response: Result<CheckSpendableResponse, serde_json::Error> =
+            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<MintInfo, Error> {
+        let url = mint_url.join("info")?;
+        let res = minreq::get(url).send()?.json::<Value>()?;
+
+        let response: Result<MintInfo, serde_json::Error> = serde_json::from_value(res.clone());
+
+        match response {
+            Ok(res) => Ok(res),
+            Err(_) => Err(Error::from_json(&res.to_string())?),
+        }
+    }
+}

+ 44 - 436
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<String>,
-    detail: Option<String>,
-}
-
 impl Error {
     pub fn from_json(json: &str) -> Result<Self, Error> {
         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<String>,
+    detail: Option<String>,
 }
 
-impl Client {
-    pub fn new(mint_url: &str) -> Result<Self, Error> {
-        let mint_url = UncheckedUrl::from_str(mint_url)?;
-        let _: Url = (&mint_url).try_into()?;
-        Ok(Self { mint_url })
-    }
-
-    /// Get Mint Keys [NUT-01]
-    #[cfg(not(target_arch = "wasm32"))]
-    pub async fn get_keys(&self) -> Result<Keys, Error> {
-        let url = self.mint_url.join("keys")?;
-        let keys = minreq::get(url).send()?.json::<Value>()?;
-
-        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<Keys, Error> {
-        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::<Value>()
-            .await
-            .map_err(|err| Error::Gloo(err.to_string()))?;
-
-        let keys: Keys = serde_json::from_str(&keys.to_string())?;
-        /*
-                let keys: BTreeMap<u64, String> = 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<u64, PublicKey> = 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<nut02::Response, Error> {
-        let url = self.mint_url.join("keysets")?;
-        let res = minreq::get(url).send()?.json::<Value>()?;
-
-        let response: Result<nut02::Response, serde_json::Error> =
-            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<nut02::Response, Error> {
-        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::<Value>()
-            .await
-            .map_err(|err| Error::Gloo(err.to_string()))?;
-
-        let response: Result<nut02::Response, serde_json::Error> =
-            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<RequestMintResponse, Error> {
-        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::<Value>()?;
-
-        let response: Result<RequestMintResponse, serde_json::Error> =
-            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<RequestMintResponse, Error> {
-        let mut url = self.mint_url.join("mint")?;
-        url.query_pairs_mut()
-            .append_pair("amount", &amount.to_sat().to_string());
+#[async_trait(?Send)]
+pub trait Client {
+    async fn get_mint_keys(&self, mint_url: &Url) -> Result<Keys, Error>;
 
-        let res = Request::get(url.as_str())
-            .send()
-            .await
-            .map_err(|err| Error::Gloo(err.to_string()))?
-            .json::<Value>()
-            .await
-            .map_err(|err| Error::Gloo(err.to_string()))?;
+    async fn get_mint_keysets(&self, mint_url: &Url) -> Result<nut02::Response, Error>;
 
-        let response: Result<RequestMintResponse, serde_json::Error> =
-            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,
-        blinded_messages: BlindedMessages,
-        hash: &str,
-    ) -> Result<PostMintResponse, Error> {
-        let mut url = self.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::<Value>()?;
-
-        let response: Result<PostMintResponse, serde_json::Error> =
-            serde_json::from_value(res.clone());
+        mint_url: &Url,
+        amount: Amount,
+    ) -> Result<RequestMintResponse, Error>;
 
-        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(
+    // TODO: Hash should have a type
+    async fn post_mint(
         &self,
+        mint_url: &Url,
         blinded_messages: BlindedMessages,
         hash: &str,
-    ) -> Result<PostMintResponse, Error> {
-        let mut url = self.mint_url.join("mint")?;
-        url.query_pairs_mut().append_pair("hash", hash);
-
-        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::<Value>()
-            .await
-            .map_err(|err| Error::Gloo(err.to_string()))?;
-
-        let response: Result<PostMintResponse, serde_json::Error> =
-            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<CheckFeesResponse, Error> {
-        let url = self.mint_url.join("checkfees")?;
-
-        let request = CheckFeesRequest { pr: invoice };
+    ) -> Result<PostMintResponse, Error>;
 
-        let res = minreq::post(url)
-            .with_json(&request)?
-            .send()?
-            .json::<Value>()?;
-
-        let response: Result<CheckFeesResponse, serde_json::Error> =
-            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<CheckFeesResponse, Error> {
-        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::<Value>()
-            .await
-            .map_err(|err| Error::Gloo(err.to_string()))?;
-
-        let response: Result<CheckFeesResponse, serde_json::Error> =
-            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_check_fees(
         &self,
-        proofs: Vec<Proof>,
+        mint_url: &Url,
         invoice: Bolt11Invoice,
-        outputs: Option<Vec<BlindedMessage>>,
-    ) -> Result<MeltResponse, Error> {
-        let url = self.mint_url.join("melt")?;
-
-        let request = MeltRequest {
-            proofs,
-            pr: invoice,
-            outputs,
-        };
-
-        let value = minreq::post(url)
-            .with_json(&request)?
-            .send()?
-            .json::<Value>()?;
-
-        let response: Result<MeltResponse, serde_json::Error> =
-            serde_json::from_value(value.clone());
+    ) -> Result<CheckFeesResponse, Error>;
 
-        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(
+    async fn post_melt(
         &self,
+        mint_url: &Url,
         proofs: Vec<Proof>,
         invoice: Bolt11Invoice,
         outputs: Option<Vec<BlindedMessage>>,
-    ) -> Result<MeltResponse, Error> {
-        let url = self.mint_url.join("melt")?;
-
-        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::<Value>()
-            .await
-            .map_err(|err| Error::Gloo(err.to_string()))?;
-
-        let response: Result<MeltResponse, serde_json::Error> =
-            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<SplitResponse, Error> {
-        let url = self.mint_url.join("split")?;
-
-        let res = minreq::post(url)
-            .with_json(&split_request)?
-            .send()?
-            .json::<Value>()?;
+    ) -> Result<MeltResponse, Error>;
 
-        let response: Result<SplitResponse, serde_json::Error> =
-            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<SplitResponse, Error> {
-        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::<Value>()
-            .await
-            .map_err(|err| Error::Gloo(err.to_string()))?;
-
-        let response: Result<SplitResponse, serde_json::Error> =
-            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(
+    // 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<nut00::mint::Proof>,
-    ) -> Result<CheckSpendableResponse, Error> {
-        let url = self.mint_url.join("check")?;
-        let request = CheckSpendableRequest {
-            proofs: proofs.to_owned(),
-        };
-
-        let res = minreq::post(url)
-            .with_json(&request)?
-            .send()?
-            .json::<Value>()?;
-
-        let response: Result<CheckSpendableResponse, serde_json::Error> =
-            serde_json::from_value(res.clone());
-
-        match response {
-            Ok(res) => Ok(res),
-            Err(_) => Err(Error::from_json(&res.to_string())?),
-        }
-    }
+        mint_url: &Url,
+        split_request: SplitRequest,
+    ) -> Result<SplitResponse, Error>;
 
-    /// Spendable check [NUT-07]
-    #[cfg(all(target_arch = "wasm32", feature = "nut07"))]
-    pub async fn check_spendable(
+    #[cfg(feature = "nut07")]
+    async fn post_check_spendable(
         &self,
-        proofs: &Vec<nut00::mint::Proof>,
-    ) -> Result<CheckSpendableResponse, Error> {
-        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::<Value>()
-            .await
-            .map_err(|err| Error::Gloo(err.to_string()))?;
-
-        let response: Result<CheckSpendableResponse, serde_json::Error> =
-            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<MintInfo, Error> {
-        let url = self.mint_url.join("info")?;
-        let res = minreq::get(url).send()?.json::<Value>()?;
-
-        let response: Result<MintInfo, serde_json::Error> = serde_json::from_value(res.clone());
+        mint_url: &Url,
+        proofs: Vec<nut00::mint::Proof>,
+    ) -> Result<CheckSpendableResponse, Error>;
 
-        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<MintInfo, Error> {
-        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::<Value>()
-            .await
-            .map_err(|err| Error::Gloo(err.to_string()))?;
-
-        let response: Result<MintInfo, serde_json::Error> = 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<MintInfo, Error>;
 }
 
 #[cfg(test)]
 mod tests {
-
     use super::*;
 
     #[test]

+ 61 - 228
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<C: Client> {
+    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<C: Client> Wallet<C> {
+    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<ProofsStatus, Error> {
-        let spendable = self.client.check_spendable(proofs).await?;
-
-        // 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(),
-        })
-    }
+    #[cfg(feature = "nut07")]
+    pub async fn check_proofs_spent(
+        &self,
+        proofs: Vec<cashu::nuts::nut00::mint::Proof>,
+    ) -> Result<ProofsStatus, Error> {
+        use cashu::types::ProofsStatus;
 
-    /// Check if a proof is spent
-    #[cfg(all(feature = "blocking", feature = "nut07"))]
-    pub fn check_proofs_spent(&self, proofs: &mint::Proofs) -> Result<ProofsStatus, Error> {
-        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<RequestMintResponse, Error> {
-        Ok(self.client.request_mint(amount).await?)
-    }
-
-    /// Request Token Mint
-    #[cfg(feature = "blocking")]
-    pub fn request_mint(&self, amount: Amount) -> Result<RequestMintResponse, Error> {
-        Ok(self.client.request_mint(amount)?)
+        Ok(self
+            .client
+            .get_request_mint(&self.mint_url.clone().try_into()?, amount)
+            .await?)
     }
 
-    /// Mint Token
-    #[cfg(not(feature = "blocking"))]
     pub async fn mint_token(&self, amount: Amount, hash: &str) -> Result<Token, Error> {
         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<Token, Error> {
-        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<Proofs, Error> {
         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<Proofs, Error> {
-        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<Amount, Error> {
-        Ok(self.client.check_fees(invoice).await?.fee)
-    }
-
-    /// Check fee
-    #[cfg(feature = "blocking")]
-    pub fn check_fee(&self, invoice: Bolt11Invoice) -> Result<Amount, Error> {
-        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<Proofs, Error> {
         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<Proofs, Error> {
-        let token_data = Token::from_str(encoded_token)?;
-
-        let mut proofs: Vec<Proofs> = 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<SendProofs, Error> {
         let mut amount_available = Amount::ZERO;
         let mut send_proofs = SendProofs::default();
@@ -332,69 +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?;
-
-        // 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,
-        })
-    }
-
-    /// Send
-    #[cfg(feature = "blocking")]
-    pub fn send(&self, amount: Amount, proofs: Proofs) -> Result<SendProofs, Error> {
-        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)?;
+        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;
@@ -425,7 +291,6 @@ impl Wallet {
         })
     }
 
-    #[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<Melted, Error> {
-        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<String>) -> Result<String, Error> {
-        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<String>) -> Result<String, Error> {
-        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()?)
     }
 }
 

+ 3 - 0
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;