mirror of
https://github.com/aljazceru/cdk.git
synced 2026-02-05 05:06:14 +01:00
improve: add 'Client' trait, minreq async client
This commit is contained in:
@@ -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
crates/cashu-sdk/src/client/minreq_client.rs
Normal file
216
crates/cashu-sdk/src/client/minreq_client.rs
Normal file
@@ -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())?),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 })
|
||||
}
|
||||
#[async_trait(?Send)]
|
||||
pub trait Client {
|
||||
async fn get_mint_keys(&self, mint_url: &Url) -> Result<Keys, Error>;
|
||||
|
||||
/// 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>()?;
|
||||
async fn get_mint_keysets(&self, mint_url: &Url) -> Result<nut02::Response, Error>;
|
||||
|
||||
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());
|
||||
|
||||
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<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,
|
||||
mint_url: &Url,
|
||||
amount: Amount,
|
||||
) -> Result<RequestMintResponse, Error>;
|
||||
|
||||
// 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);
|
||||
) -> Result<PostMintResponse, Error>;
|
||||
|
||||
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())?),
|
||||
}
|
||||
}
|
||||
|
||||
/// Mint Tokens [NUT-04]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub async fn mint(
|
||||
async fn post_check_fees(
|
||||
&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);
|
||||
mint_url: &Url,
|
||||
invoice: Bolt11Invoice,
|
||||
) -> Result<CheckFeesResponse, Error>;
|
||||
|
||||
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 };
|
||||
|
||||
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_melt(
|
||||
&self,
|
||||
mint_url: &Url,
|
||||
proofs: Vec<Proof>,
|
||||
invoice: Bolt11Invoice,
|
||||
outputs: Option<Vec<BlindedMessage>>,
|
||||
) -> Result<MeltResponse, Error> {
|
||||
let url = self.mint_url.join("melt")?;
|
||||
) -> Result<MeltResponse, Error>;
|
||||
|
||||
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())?),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<Proof>,
|
||||
invoice: Bolt11Invoice,
|
||||
outputs: Option<Vec<BlindedMessage>>,
|
||||
) -> Result<MeltResponse, Error> {
|
||||
let url = self.mint_url.join("melt")?;
|
||||
mint_url: &Url,
|
||||
split_request: SplitRequest,
|
||||
) -> Result<SplitResponse, Error>;
|
||||
|
||||
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>()?;
|
||||
|
||||
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(
|
||||
#[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(),
|
||||
};
|
||||
mint_url: &Url,
|
||||
proofs: Vec<nut00::mint::Proof>,
|
||||
) -> Result<CheckSpendableResponse, Error>;
|
||||
|
||||
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())?),
|
||||
}
|
||||
}
|
||||
|
||||
/// Spendable check [NUT-07]
|
||||
#[cfg(all(target_arch = "wasm32", feature = "nut07"))]
|
||||
pub async fn 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());
|
||||
|
||||
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]
|
||||
|
||||
@@ -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?;
|
||||
#[cfg(feature = "nut07")]
|
||||
pub async fn check_proofs_spent(
|
||||
&self,
|
||||
proofs: Vec<cashu::nuts::nut00::mint::Proof>,
|
||||
) -> Result<ProofsStatus, Error> {
|
||||
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<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?)
|
||||
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<RequestMintResponse, Error> {
|
||||
Ok(self.client.request_mint(amount)?)
|
||||
}
|
||||
|
||||
/// 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,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<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)?;
|
||||
|
||||
// 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<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()?)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,3 +11,6 @@ pub mod nut07;
|
||||
pub mod nut08;
|
||||
#[cfg(feature = "nut09")]
|
||||
pub mod nut09;
|
||||
|
||||
#[cfg(feature = "nut09")]
|
||||
pub use nut09::MintInfo;
|
||||
|
||||
Reference in New Issue
Block a user