refactor: use reqwest client

Using reqwest reduced code duplication since it can be used for both
wasm and non wasm targets.
This commit is contained in:
thesimplekid
2024-04-12 22:00:32 +01:00
parent ce207953a1
commit 391692e332
7 changed files with 114 additions and 505 deletions

View File

@@ -63,6 +63,9 @@ jobs:
build-args:
[
-p cdk,
-p cdk --no-default-features,
-p cdk --no-default-features --features wallet,
-p cdk --no-default-features --features all-nuts,
]
steps:
- name: Checkout

View File

@@ -12,8 +12,7 @@ license.workspace = true
[features]
default = ["mint", "wallet", "all-nuts", "redb"]
mint = ["dep:bip39"]
wallet = ["nut13", "dep:minreq", "dep:bip39"]
gloo = ["dep:gloo"]
wallet = ["nut13", "dep:bip39", "dep:reqwest"]
all-nuts = ["nut13"]
nut13 = ["dep:bip39"]
redb = ["dep:redb"]
@@ -32,13 +31,12 @@ url = "2.3.1"
tracing = { version = "0.1", default-features = false }
thiserror = "1.0.50"
async-trait = "0.1.74"
gloo = { version = "0.11.0", optional = true, features = ["net"] }
http = "1.0.0"
uuid = { version = "1.6", features = ["v4"] }
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls", "socks"], optional = true }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { workspace = true, features = ["rt-multi-thread", "time", "macros", "sync"] }
minreq = { version = "2.7.0", optional = true, features = ["json-using-serde", "https"] }
redb = { version = "2.0.0", optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]

View File

@@ -1,13 +1,8 @@
//! Minreq http Client
use async_trait::async_trait;
use reqwest::Client;
use serde_json::Value;
use tracing::warn;
use url::Url;
use super::join_url;
use crate::client::{Client, Error};
use crate::error::ErrorResponse;
use crate::error::{Error, ErrorResponse};
use crate::nuts::{
BlindedMessage, CheckStateRequest, CheckStateResponse, CurrencyUnit, Id, KeySet, KeysResponse,
KeysetResponse, MeltBolt11Request, MeltBolt11Response, MeltQuoteBolt11Request,
@@ -17,24 +12,61 @@ use crate::nuts::{
};
use crate::{Amount, Bolt11Invoice};
#[derive(Debug, Clone)]
pub struct HttpClient {}
fn join_url(url: Url, paths: &[&str]) -> Result<Url, Error> {
let mut url = url;
for path in paths {
if !url.path().ends_with('/') {
url.path_segments_mut()
.map_err(|_| Error::CustomError("Url Path Segmants".to_string()))?
.push(path);
} else {
url.path_segments_mut()
.map_err(|_| Error::CustomError("Url Path Segmants".to_string()))?
.pop()
.push(path);
}
}
Ok(url)
}
#[derive(Debug, Clone)]
pub struct HttpClient {
inner: Client,
}
impl Default for HttpClient {
fn default() -> Self {
Self::new()
}
}
impl HttpClient {
pub fn new() -> Self {
Self {
inner: Client::new(),
}
}
#[async_trait(?Send)]
impl Client for HttpClient {
/// Get Active Mint Keys [NUT-01]
async fn get_mint_keys(&self, mint_url: Url) -> Result<Vec<KeySet>, Error> {
pub async fn get_mint_keys(&self, mint_url: Url) -> Result<Vec<KeySet>, Error> {
let url = join_url(mint_url, &["v1", "keys"])?;
let keys = minreq::get(url).send()?.json::<Value>()?;
let keys = self.inner.get(url).send().await?.json::<Value>().await?;
let keys: KeysResponse = serde_json::from_value(keys)?;
Ok(keys.keysets)
}
/// Get Keyset Keys [NUT-01]
async fn get_mint_keyset(&self, mint_url: Url, keyset_id: Id) -> Result<KeySet, Error> {
pub async fn get_mint_keyset(&self, mint_url: Url, keyset_id: Id) -> Result<KeySet, Error> {
let url = join_url(mint_url, &["v1", "keys", &keyset_id.to_string()])?;
let keys = minreq::get(url).send()?.json::<KeysResponse>()?;
let keys = self
.inner
.get(url)
.send()
.await?
.json::<KeysResponse>()
.await?;
// let keys: KeysResponse = serde_json::from_value(keys)?; //
// serde_json::from_str(&keys.to_string())?;
@@ -42,9 +74,9 @@ impl Client for HttpClient {
}
/// Get Keysets [NUT-02]
async fn get_mint_keysets(&self, mint_url: Url) -> Result<KeysetResponse, Error> {
pub async fn get_mint_keysets(&self, mint_url: Url) -> Result<KeysetResponse, Error> {
let url = join_url(mint_url, &["v1", "keysets"])?;
let res = minreq::get(url).send()?.json::<Value>()?;
let res = self.inner.get(url).send().await?.json::<Value>().await?;
let response: Result<KeysetResponse, serde_json::Error> =
serde_json::from_value(res.clone());
@@ -56,7 +88,7 @@ impl Client for HttpClient {
}
/// Mint Quote [NUT-04]
async fn post_mint_quote(
pub async fn post_mint_quote(
&self,
mint_url: Url,
amount: Amount,
@@ -66,22 +98,21 @@ impl Client for HttpClient {
let request = MintQuoteBolt11Request { amount, unit };
let res = minreq::post(url).with_json(&request)?.send()?;
let res = self.inner.post(url).json(&request).send().await?;
let status = res.status();
let response: Result<MintQuoteBolt11Response, serde_json::Error> =
serde_json::from_value(res.json()?);
serde_json::from_value(res.json().await?);
match response {
Ok(res) => Ok(res),
Err(_) => {
warn!("Bolt11 Mint Quote Unexpected response: {:?}", res);
Err(ErrorResponse::from_json(&res.status_code.to_string())?.into())
}
Err(_) => Err(ErrorResponse::from_json(&status.to_string())?.into()),
}
}
/// Mint Tokens [NUT-04]
async fn post_mint(
pub async fn post_mint(
&self,
mint_url: Url,
quote: &str,
@@ -94,10 +125,14 @@ impl Client for HttpClient {
outputs: premint_secrets.blinded_messages(),
};
let res = minreq::post(url)
.with_json(&request)?
.send()?
.json::<Value>()?;
let res = self
.inner
.post(url)
.json(&request)
.send()
.await?
.json::<Value>()
.await?;
let response: Result<MintBolt11Response, serde_json::Error> =
serde_json::from_value(res.clone());
@@ -109,7 +144,7 @@ impl Client for HttpClient {
}
/// Melt Quote [NUT-05]
async fn post_melt_quote(
pub async fn post_melt_quote(
&self,
mint_url: Url,
unit: CurrencyUnit,
@@ -119,9 +154,9 @@ impl Client for HttpClient {
let request = MeltQuoteBolt11Request { request, unit };
let value = minreq::post(url).with_json(&request)?.send()?;
let value = self.inner.post(url).json(&request).send().await?;
let value = value.json::<Value>()?;
let value = value.json::<Value>().await?;
let response: Result<MeltQuoteBolt11Response, serde_json::Error> =
serde_json::from_value(value.clone());
@@ -134,7 +169,7 @@ impl Client for HttpClient {
/// Melt [NUT-05]
/// [Nut-08] Lightning fee return if outputs defined
async fn post_melt(
pub async fn post_melt(
&self,
mint_url: Url,
quote: String,
@@ -149,9 +184,9 @@ impl Client for HttpClient {
outputs,
};
let value = minreq::post(url).with_json(&request)?.send()?;
let value = self.inner.post(url).json(&request).send().await?;
let value = value.json::<Value>()?;
let value = value.json::<Value>().await?;
let response: Result<MeltBolt11Response, serde_json::Error> =
serde_json::from_value(value.clone());
@@ -162,16 +197,16 @@ impl Client for HttpClient {
}
/// Split Token [NUT-06]
async fn post_swap(
pub async fn post_swap(
&self,
mint_url: Url,
swap_request: SwapRequest,
) -> Result<SwapResponse, Error> {
let url = join_url(mint_url, &["v1", "swap"])?;
let res = minreq::post(url).with_json(&swap_request)?.send()?;
let res = self.inner.post(url).json(&swap_request).send().await?;
let value = res.json::<Value>()?;
let value = res.json::<Value>().await?;
let response: Result<SwapResponse, serde_json::Error> =
serde_json::from_value(value.clone());
@@ -182,10 +217,10 @@ impl Client for HttpClient {
}
/// Get Mint Info [NUT-06]
async fn get_mint_info(&self, mint_url: Url) -> Result<MintInfo, Error> {
pub async fn get_mint_info(&self, mint_url: Url) -> Result<MintInfo, Error> {
let url = join_url(mint_url, &["v1", "info"])?;
let res = minreq::get(url).send()?.json::<Value>()?;
let res = self.inner.get(url).send().await?.json::<Value>().await?;
let response: Result<MintInfo, serde_json::Error> = serde_json::from_value(res.clone());
@@ -196,7 +231,7 @@ impl Client for HttpClient {
}
/// Spendable check [NUT-07]
async fn post_check_state(
pub async fn post_check_state(
&self,
mint_url: Url,
ys: Vec<PublicKey>,
@@ -204,10 +239,14 @@ impl Client for HttpClient {
let url = join_url(mint_url, &["v1", "checkstate"])?;
let request = CheckStateRequest { ys };
let res = minreq::post(url)
.with_json(&request)?
.send()?
.json::<Value>()?;
let res = self
.inner
.post(url)
.json(&request)
.send()
.await?
.json::<Value>()
.await?;
let response: Result<CheckStateResponse, serde_json::Error> =
serde_json::from_value(res.clone());
@@ -218,17 +257,21 @@ impl Client for HttpClient {
}
}
async fn post_restore(
pub async fn post_restore(
&self,
mint_url: Url,
request: RestoreRequest,
) -> Result<RestoreResponse, Error> {
let url = join_url(mint_url, &["v1", "restore"])?;
let res = minreq::post(url)
.with_json(&request)?
.send()?
.json::<Value>()?;
let res = self
.inner
.post(url)
.json(&request)
.send()
.await?
.json::<Value>()
.await?;
let response: Result<RestoreResponse, serde_json::Error> =
serde_json::from_value(res.clone());

View File

@@ -1,282 +0,0 @@
//! gloo wasm http Client
use async_trait::async_trait;
use gloo::net::http::Request;
use serde_json::Value;
use url::Url;
use super::join_url;
use crate::client::{Client, Error};
use crate::nuts::{
BlindedMessage, CheckSpendableRequest, CheckSpendableResponse, MeltBolt11Request,
MeltBolt11Response, MintBolt11Request, MintBolt11Response, MintInfo, PreMintSecrets, Proof,
PublicKey, RestoreRequest, RestoreResponse, SwapRequest, SwapResponse, *,
};
use crate::{Amount, Bolt11Invoice};
#[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<Vec<KeySet>, Error> {
let url = join_url(mint_url, &["v1", "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: KeysResponse = serde_json::from_str(&keys.to_string())?;
Ok(keys.keysets)
}
/// Get Keysets [NUT-02]
async fn get_mint_keysets(&self, mint_url: Url) -> Result<KeysetResponse, Error> {
let url = join_url(mint_url, &["v1", "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<KeysetResponse, serde_json::Error> =
serde_json::from_value(res.clone());
match response {
Ok(res) => Ok(res),
Err(_) => Err(Error::from_json(&res.to_string())?),
}
}
/// Mint Quote [NUT-04]
async fn post_mint_quote(
&self,
mint_url: Url,
amount: Amount,
unit: CurrencyUnit,
) -> Result<MintQuoteBolt11Response, Error> {
let url = join_url(mint_url, &["v1", "mint", "quote", "bolt11"])?;
let request = MintQuoteBolt11Request { amount, unit };
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<MintQuoteBolt11Response, 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,
quote: &str,
premint_secrets: PreMintSecrets,
) -> Result<MintBolt11Response, Error> {
let url = join_url(mint_url, &["v1", "mint", "bolt11"])?;
let request = MintBolt11Request {
quote: quote.to_string(),
outputs: premint_secrets.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<MintBolt11Response, 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]
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,
mint_url: Url,
quote: String,
inputs: Vec<Proof>,
outputs: Option<Vec<BlindedMessage>>,
) -> Result<MeltBolt11Response, Error> {
let url = join_url(mint_url, &["v1", "melt", "bolt11"])?;
let request = MeltBolt11Request {
quote,
inputs,
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<MeltBolt11Response, 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_swap(
&self,
mint_url: Url,
split_request: SwapRequest,
) -> Result<SwapResponse, Error> {
let url = join_url(mint_url, &["v1", "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<SwapResponse, 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]
async fn post_check_state(
&self,
mint_url: Url,
ys: Vec<PublicKey>,
) -> Result<CheckStateResponse, Error> {
let url = join_url(mint_url, &["v1", "check"])?;
let request = CheckSpendableRequest { ys };
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]
async fn get_mint_info(&self, mint_url: Url) -> Result<MintInfo, Error> {
let url = join_url(mint_url, &["v1", "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())?),
}
}
/// Restore [NUT-09]
async fn post_check_state(
&self,
mint_url: Url,
request: RestoreRequest,
) -> Result<CheckStateResponse, Error> {
let url = join_url(mint_url, &["v1", "check"])?;
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<RestoreRequest, serde_json::Error> =
serde_json::from_value(res.clone());
match response {
Ok(res) => Ok(res),
Err(_) => Err(Error::from_json(&res.to_string())?),
}
}
}

View File

@@ -1,164 +0,0 @@
//! Client to connet to mint
use async_trait::async_trait;
use thiserror::Error;
use url::Url;
use crate::error::ErrorResponse;
use crate::nuts::nut09::{RestoreRequest, RestoreResponse};
use crate::nuts::{
BlindedMessage, CheckStateResponse, CurrencyUnit, Id, KeySet, KeysetResponse,
MeltBolt11Response, MeltQuoteBolt11Response, MintBolt11Response, MintInfo,
MintQuoteBolt11Response, PreMintSecrets, Proof, PublicKey, SwapRequest, SwapResponse,
};
use crate::Amount;
#[cfg(feature = "gloo")]
pub mod gloo_client;
#[cfg(not(target_arch = "wasm32"))]
pub mod minreq_client;
pub use crate::Bolt11Invoice;
#[derive(Debug, Error)]
pub enum Error {
#[error("Invoice not paid")]
InvoiceNotPaid,
#[error("Wallet not responding")]
LightingWalletNotResponding(Option<String>),
/// Parse Url Error
#[error("`{0}`")]
UrlParse(#[from] url::ParseError),
/// Serde Json error
#[error("`{0}`")]
SerdeJson(#[from] serde_json::Error),
/// Cashu Url Error
#[error("`{0}`")]
CashuUrl(#[from] crate::url::Error),
/// Min req error
#[cfg(not(target_arch = "wasm32"))]
#[error("`{0}`")]
MinReq(#[from] minreq::Error),
#[cfg(feature = "gloo")]
#[error("`{0}`")]
Gloo(String),
#[error("Unknown Error response")]
UnknownErrorResponse(crate::error::ErrorResponse),
/// Custom Error
#[error("`{0}`")]
Custom(String),
}
impl From<ErrorResponse> for Error {
fn from(err: ErrorResponse) -> Error {
Self::UnknownErrorResponse(err)
}
}
#[async_trait(?Send)]
pub trait Client {
async fn get_mint_keys(&self, mint_url: Url) -> Result<Vec<KeySet>, Error>;
async fn get_mint_keysets(&self, mint_url: Url) -> Result<KeysetResponse, Error>;
async fn get_mint_keyset(&self, mint_url: Url, keyset_id: Id) -> Result<KeySet, Error>;
async fn post_mint_quote(
&self,
mint_url: Url,
amount: Amount,
unit: CurrencyUnit,
) -> Result<MintQuoteBolt11Response, Error>;
async fn post_mint(
&self,
mint_url: Url,
quote: &str,
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,
quote: String,
inputs: Vec<Proof>,
outputs: Option<Vec<BlindedMessage>>,
) -> Result<MeltBolt11Response, Error>;
// 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_swap(
&self,
mint_url: Url,
split_request: SwapRequest,
) -> Result<SwapResponse, Error>;
async fn post_check_state(
&self,
mint_url: Url,
ys: Vec<PublicKey>,
) -> Result<CheckStateResponse, Error>;
async fn get_mint_info(&self, mint_url: Url) -> Result<MintInfo, Error>;
async fn post_restore(
&self,
mint_url: Url,
restore_request: RestoreRequest,
) -> Result<RestoreResponse, Error>;
}
#[cfg(any(not(target_arch = "wasm32"), feature = "gloo"))]
fn join_url(url: Url, paths: &[&str]) -> Result<Url, Error> {
let mut url = url;
for path in paths {
if !url.path().ends_with('/') {
url.path_segments_mut()
.map_err(|_| Error::Custom("Url Path Segmants".to_string()))?
.push(path);
} else {
url.path_segments_mut()
.map_err(|_| Error::Custom("Url Path Segmants".to_string()))?
.pop()
.push(path);
}
}
Ok(url)
}
#[cfg(test)]
mod tests {
/*
use super::*;
#[test]
fn test_decode_error() {
let err = r#"{"code":0,"error":"Lightning invoice not paid yet."}"#;
let error = Error::from_json(err).unwrap();
match error {
Error::InvoiceNotPaid => {}
_ => panic!("Wrong error"),
}
let err = r#"{"code": 0, "error": "Lightning wallet not responding: Failed to connect to https://legend.lnbits.com due to: All connection attempts failed"}"#;
let error = Error::from_json(err).unwrap();
match error {
Error::LightingWalletNotResponding(mint) => {
assert_eq!(mint, Some("https://legend.lnbits.com".to_string()));
}
_ => panic!("Wrong error"),
}
}
*/
}

View File

@@ -58,6 +58,10 @@ pub enum Error {
/// From hex error
#[error(transparent)]
HexError(#[from] hex::Error),
#[cfg(feature = "wallet")]
/// From hex error
#[error(transparent)]
HReeqwestError(#[from] reqwest::Error),
/// Nut01 error
#[error(transparent)]
NUT01(#[from] crate::nuts::nut01::Error),
@@ -67,6 +71,9 @@ pub enum Error {
/// NUT11 Error
#[error(transparent)]
NUT11(#[from] crate::nuts::nut11::Error),
/// Min req error
#[error("Unknown Error response")]
UnknownErrorResponse(crate::error::ErrorResponse),
/// Custom error
#[error("`{0}`")]
CustomError(String),
@@ -92,3 +99,9 @@ impl ErrorResponse {
}
}
}
impl From<ErrorResponse> for Error {
fn from(err: ErrorResponse) -> Error {
Self::UnknownErrorResponse(err)
}
}

View File

@@ -9,7 +9,7 @@ use localstore::LocalStore;
use thiserror::Error;
use tracing::{debug, warn};
use crate::client::Client;
use crate::client::HttpClient;
use crate::dhke::{construct_proofs, hash_to_curve, unblind_message};
use crate::nuts::{
BlindSignature, CurrencyUnit, Id, KeySet, KeySetInfo, Keys, MintInfo, P2PKConditions,
@@ -48,8 +48,6 @@ pub enum Error {
UnknownKey,
#[error(transparent)]
ParseInt(#[from] ParseIntError),
#[error(transparent)]
Client(#[from] crate::client::Error),
/// Cashu Url Error
#[error(transparent)]
CashuUrl(#[from] crate::url::Error),
@@ -65,14 +63,14 @@ pub enum Error {
#[derive(Clone)]
pub struct Wallet {
pub client: Arc<dyn Client + Send + Sync>,
pub client: HttpClient,
pub localstore: Arc<dyn LocalStore + Send + Sync>,
mnemonic: Option<Mnemonic>,
}
impl Wallet {
pub async fn new(
client: Arc<dyn Client + Sync + Send>,
client: HttpClient,
localstore: Arc<dyn LocalStore + Send + Sync>,
mnemonic: Option<Mnemonic>,
) -> Self {