mirror of
https://github.com/aljazceru/cdk.git
synced 2025-12-22 07:04:56 +01:00
sdk: add blocking client and wallet as feature
This commit is contained in:
@@ -10,9 +10,17 @@ license.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["mint", "wallet"]
|
||||
mint = []
|
||||
mint = ["cashu/mint"]
|
||||
blocking = ["once_cell"]
|
||||
wallet = ["cashu/wallet", "minreq", "once_cell"]
|
||||
|
||||
|
||||
# Fix: Should be minreq or gloo
|
||||
wallet = ["minreq"]
|
||||
# [target.'cfg(not(target_arch = "wasm32"))'.features]
|
||||
# wallet = ["cashu/wallet", "minreq", "once_cell"]
|
||||
|
||||
# [target.'cfg(target_arch = "wasm32")'.features]
|
||||
# wallet = ["cashu/wallet", "gloo", "once_cell"]
|
||||
|
||||
[dependencies]
|
||||
cashu = { path = "../cashu" }
|
||||
@@ -20,9 +28,16 @@ serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
url = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
|
||||
once_cell = { version = "1.17", optional = true }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
gloo = { version = "0.9.0", features = ["net"]}
|
||||
|
||||
[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"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
tokio = { workspace = true, features = ["rt", "macros", "sync"] }
|
||||
gloo = { version = "0.9.0", features = ["net"]}
|
||||
|
||||
|
||||
|
||||
80
crates/cashu-sdk/src/client/blocking.rs
Normal file
80
crates/cashu-sdk/src/client/blocking.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
use crate::RUNTIME;
|
||||
|
||||
use cashu::{
|
||||
nuts::{
|
||||
nut00::{self, wallet::BlindedMessages, BlindedMessage, Proof},
|
||||
nut01::Keys,
|
||||
nut02,
|
||||
nut03::RequestMintResponse,
|
||||
nut04::PostMintResponse,
|
||||
nut05::CheckFeesResponse,
|
||||
nut06::{SplitRequest, SplitResponse},
|
||||
nut07::CheckSpendableResponse,
|
||||
nut08::MeltResponse,
|
||||
nut09::MintInfo,
|
||||
},
|
||||
Amount, Bolt11Invoice,
|
||||
};
|
||||
|
||||
use super::Error;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Client {
|
||||
pub(crate) client: super::Client,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(mint_url: &str) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
client: super::Client::new(mint_url)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_keys(&self) -> Result<Keys, Error> {
|
||||
RUNTIME.block_on(async { self.client.get_keys().await })
|
||||
}
|
||||
|
||||
pub fn get_keysets(&self) -> Result<nut02::Response, Error> {
|
||||
RUNTIME.block_on(async { self.client.get_keysets().await })
|
||||
}
|
||||
|
||||
pub fn request_mint(&self, amount: Amount) -> Result<RequestMintResponse, Error> {
|
||||
RUNTIME.block_on(async { self.client.request_mint(amount).await })
|
||||
}
|
||||
|
||||
pub fn mint(
|
||||
&self,
|
||||
blinded_mssages: BlindedMessages,
|
||||
hash: &str,
|
||||
) -> Result<PostMintResponse, Error> {
|
||||
RUNTIME.block_on(async { self.client.mint(blinded_mssages, hash).await })
|
||||
}
|
||||
|
||||
pub fn check_fees(&self, invoice: Bolt11Invoice) -> Result<CheckFeesResponse, Error> {
|
||||
RUNTIME.block_on(async { self.client.check_fees(invoice).await })
|
||||
}
|
||||
|
||||
pub fn melt(
|
||||
&self,
|
||||
proofs: Vec<Proof>,
|
||||
invoice: Bolt11Invoice,
|
||||
outputs: Option<Vec<BlindedMessage>>,
|
||||
) -> Result<MeltResponse, Error> {
|
||||
RUNTIME.block_on(async { self.client.melt(proofs, invoice, outputs).await })
|
||||
}
|
||||
|
||||
pub fn split(&self, split_request: SplitRequest) -> Result<SplitResponse, Error> {
|
||||
RUNTIME.block_on(async { self.client.split(split_request).await })
|
||||
}
|
||||
|
||||
pub fn check_spendable(
|
||||
&self,
|
||||
proofs: &Vec<nut00::mint::Proof>,
|
||||
) -> Result<CheckSpendableResponse, Error> {
|
||||
RUNTIME.block_on(async { self.client.check_spendable(proofs).await })
|
||||
}
|
||||
|
||||
pub fn get_info(&self) -> Result<MintInfo, Error> {
|
||||
RUNTIME.block_on(async { self.client.get_info().await })
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,9 @@ use cashu::Amount;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use gloo::net::http::Request;
|
||||
|
||||
#[cfg(feature = "blocking")]
|
||||
pub mod blocking;
|
||||
|
||||
pub use cashu::Bolt11Invoice;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -1,7 +1,28 @@
|
||||
#[cfg(feature = "wallet")]
|
||||
#[cfg(feature = "blocking")]
|
||||
use once_cell::sync::Lazy;
|
||||
#[cfg(feature = "blocking")]
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
#[cfg(feature = "blocking")]
|
||||
use futures_util::Future;
|
||||
|
||||
// #[cfg(feature = "wallet")]
|
||||
pub(crate) mod client;
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
pub mod mint;
|
||||
#[cfg(feature = "wallet")]
|
||||
pub mod wallet;
|
||||
|
||||
pub use cashu::{self, *};
|
||||
|
||||
#[cfg(all(feature = "blocking", feature = "wallet"))]
|
||||
use self::client::blocking;
|
||||
|
||||
#[cfg(feature = "blocking")]
|
||||
static RUNTIME: Lazy<Runtime> = Lazy::new(|| Runtime::new().expect("Can't start Tokio runtime"));
|
||||
|
||||
#[cfg(feature = "blocking")]
|
||||
pub fn block_on<F: Future>(future: F) -> F::Output {
|
||||
RUNTIME.block_on(future)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ use cashu::Amount;
|
||||
pub use cashu::Bolt11Invoice;
|
||||
use tracing::warn;
|
||||
|
||||
#[cfg(feature = "blocking")]
|
||||
use crate::client::blocking::Client;
|
||||
|
||||
#[cfg(not(feature = "blocking"))]
|
||||
use crate::client::Client;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -71,6 +75,7 @@ impl Wallet {
|
||||
// TODO: getter method for keys that if it cant get them try again
|
||||
|
||||
/// Check if a proof is spent
|
||||
#[cfg(not(feature = "blocking"))]
|
||||
pub async fn check_proofs_spent(&self, proofs: &mint::Proofs) -> Result<ProofsStatus, Error> {
|
||||
let spendable = self.client.check_spendable(proofs).await?;
|
||||
|
||||
@@ -86,12 +91,37 @@ impl Wallet {
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if a proof is spent
|
||||
#[cfg(feature = "blocking")]
|
||||
pub fn check_proofs_spent(&self, proofs: &mint::Proofs) -> Result<ProofsStatus, Error> {
|
||||
let spendable = self.client.check_spendable(proofs)?;
|
||||
|
||||
// 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(),
|
||||
})
|
||||
}
|
||||
|
||||
/// 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)?)
|
||||
}
|
||||
|
||||
/// 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?;
|
||||
|
||||
@@ -99,7 +129,17 @@ impl Wallet {
|
||||
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);
|
||||
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)?;
|
||||
|
||||
@@ -115,12 +155,37 @@ impl Wallet {
|
||||
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 proofs = construct_proofs(
|
||||
mint_res.promises,
|
||||
blinded_messages.rs,
|
||||
blinded_messages.secrets,
|
||||
&self.mint_keys,
|
||||
)?;
|
||||
|
||||
Ok(proofs)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// Receive
|
||||
#[cfg(not(feature = "blocking"))]
|
||||
pub async fn receive(&self, encoded_token: &str) -> Result<Proofs, Error> {
|
||||
let token_data = Token::from_str(encoded_token)?;
|
||||
|
||||
@@ -160,6 +225,51 @@ impl Wallet {
|
||||
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.as_str())?.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)?;
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
/// Create Split Payload
|
||||
fn create_split(&self, proofs: Proofs) -> Result<SplitPayload, Error> {
|
||||
let value = proofs.iter().map(|p| p.amount).sum();
|
||||
@@ -217,6 +327,7 @@ 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();
|
||||
@@ -277,6 +388,69 @@ 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::InsufficantFunds);
|
||||
}
|
||||
|
||||
// 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,
|
||||
@@ -308,11 +482,49 @@ 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()?)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -379,3 +591,4 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -30,4 +30,4 @@ url = { workspace = true }
|
||||
regex = "1.8.4"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = {version = "1.27.0", features = ["rt", "macros"] }
|
||||
# tokio = {version = "1.27.0", features = ["rt", "macros"] }
|
||||
|
||||
Reference in New Issue
Block a user