From c706e367e9504022fa49e795c2020ab5bbb93b51 Mon Sep 17 00:00:00 2001 From: thesimplekid Date: Mon, 11 Dec 2023 22:35:47 +0000 Subject: [PATCH] refactor: v1 MeltRequest TODO: ffi bindings --- bindings/cashu-ffi/src/cashu.udl | 62 +++---- bindings/cashu-ffi/src/lib.rs | 8 +- .../src/nuts/nut00/blinded_signature.rs | 7 + bindings/cashu-ffi/src/nuts/nut00/token.rs | 45 +++++- bindings/cashu-ffi/src/nuts/nut05/mod.rs | 152 +++++++++++------- bindings/cashu-ffi/src/nuts/nut08/mod.rs | 68 ++++---- bindings/cashu-js/src/nuts/nut00/token.rs | 10 +- bindings/cashu-js/src/nuts/nut05.rs | 78 --------- bindings/cashu-js/src/nuts/nut08.rs | 56 +++---- bindings/cashu-sdk-ffi/src/cashu_sdk.udl | 88 +++++----- bindings/cashu-sdk-js/src/mint.rs | 4 + bindings/cashu-sdk-js/src/wallet.rs | 33 ++-- crates/cashu-sdk/src/client/gloo_client.rs | 50 ++---- crates/cashu-sdk/src/client/minreq_client.rs | 45 ++---- crates/cashu-sdk/src/client/mod.rs | 16 +- crates/cashu-sdk/src/mint.rs | 48 +++--- crates/cashu-sdk/src/wallet.rs | 27 ++-- crates/cashu/src/nuts/mod.rs | 8 +- crates/cashu/src/nuts/nut00.rs | 38 ++++- crates/cashu/src/nuts/nut05.rs | 56 ++++--- crates/cashu/src/nuts/nut08.rs | 42 +++-- crates/cashu/src/types.rs | 15 +- 22 files changed, 458 insertions(+), 498 deletions(-) diff --git a/bindings/cashu-ffi/src/cashu.udl b/bindings/cashu-ffi/src/cashu.udl index b063d018..0210327d 100644 --- a/bindings/cashu-ffi/src/cashu.udl +++ b/bindings/cashu-ffi/src/cashu.udl @@ -6,6 +6,12 @@ interface CashuError { }; // Types + +[Enum] +interface CurrencyUnit { + Sat(); + Custom(string unit); +}; interface Bolt11Invoice { [Throws=CashuError] @@ -88,11 +94,11 @@ interface MintProofs { interface Token { [Throws=CashuError] - constructor(string mint, sequence token, string? unit, string? memo); + constructor(string mint, sequence token, string? memo, string? unit); sequence token(); string? memo(); - [Throws=CashuError] - string as_string(); + string? unit(); + string to_string(); [Throws=CashuError, Name=from_string] constructor(string token); @@ -144,6 +150,32 @@ interface KeySetResponse { sequence keysets(); }; +// NUT-05 + +interface MeltQuoteBolt11Response { + [Throws=CashuError] + constructor(string quote, u64 amount, u64 fee_reserve, boolean paid, u64 expiry); + string quote(); + u64 amount(); + u64 fee_reserve(); + boolean paid(); + u64 expiry(); +}; + +interface MeltQuoteBolt11Request { + [Throws=CashuError] + constructor(string request, string unit); + string request(); + string unit(); +}; + +interface MeltBolt11Request { + [Throws=CashuError] + constructor(sequence inputs, string quote); + sequence inputs(); + string quote(); +}; + interface RequestMintResponse { [Throws=CashuError] constructor(string invoice, string hash); @@ -162,30 +194,6 @@ interface PostMintResponse { sequence promises(); }; -interface CheckFeesRequest { - [Throws=CashuError] - constructor(string invoice); - string invoice(); -}; - -interface CheckFeesResponse { - constructor(Amount amount); - Amount amount(); -}; - -interface Nut05MeltRequest { - [Throws=CashuError] - constructor(sequence proofs, string Invoice); - sequence proofs(); - string invoice(); -}; - -interface Nut05MeltResponse { - constructor(boolean paid, string? preimage); - boolean paid(); - string? preimage(); -}; - interface SplitRequest { constructor(sequence proofs, sequence outputs); sequence proofs(); diff --git a/bindings/cashu-ffi/src/lib.rs b/bindings/cashu-ffi/src/lib.rs index 0ff687e0..a6548996 100644 --- a/bindings/cashu-ffi/src/lib.rs +++ b/bindings/cashu-ffi/src/lib.rs @@ -12,7 +12,7 @@ mod ffi { pub use crate::nuts::nut00::premint_secrets::PreMintSecrets; pub use crate::nuts::nut00::proof::mint::Proof as MintProof; pub use crate::nuts::nut00::proof::Proof; - pub use crate::nuts::nut00::token::Token; + pub use crate::nuts::nut00::token::{CurrencyUnit, Token}; pub use crate::nuts::nut01::key_pair::KeyPair; pub use crate::nuts::nut01::keys::{Keys, KeysResponse}; pub use crate::nuts::nut01::public_key::PublicKey; @@ -21,11 +21,11 @@ mod ffi { pub use crate::nuts::nut03::{RequestMintResponse, SplitRequest, SplitResponse}; pub use crate::nuts::nut04::{MintRequest, PostMintResponse}; pub use crate::nuts::nut05::{ - CheckFeesRequest, CheckFeesResponse, MeltRequest as Nut05MeltRequest, - MeltResponse as Nut05MeltResponse, + MeltBolt11Request as Nut05MeltBolt11Request, MeltBolt11Response as Nut05MeltBolt11Response, + MeltQuoteBolt11Request, }; pub use crate::nuts::nut07::{CheckSpendableRequest, CheckSpendableResponse}; - pub use crate::nuts::nut08::{MeltRequest, MeltResponse}; + pub use crate::nuts::nut08::{MeltBolt11Request, MeltBolt11Response}; pub use crate::nuts::nut09::{MintInfo, MintVersion}; pub use crate::types::amount::Amount; pub use crate::types::{Bolt11Invoice, KeySetInfo, Secret}; diff --git a/bindings/cashu-ffi/src/nuts/nut00/blinded_signature.rs b/bindings/cashu-ffi/src/nuts/nut00/blinded_signature.rs index c8dd2ab6..5fbc96a8 100644 --- a/bindings/cashu-ffi/src/nuts/nut00/blinded_signature.rs +++ b/bindings/cashu-ffi/src/nuts/nut00/blinded_signature.rs @@ -9,6 +9,13 @@ pub struct BlindedSignature { inner: BlindedSignatureSdk, } +impl Deref for BlindedSignature { + type Target = BlindedSignatureSdk; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + impl BlindedSignature { pub fn new(id: Arc, amount: Arc, c: Arc) -> Self { Self { diff --git a/bindings/cashu-ffi/src/nuts/nut00/token.rs b/bindings/cashu-ffi/src/nuts/nut00/token.rs index 83c2077e..d3d63924 100644 --- a/bindings/cashu-ffi/src/nuts/nut00/token.rs +++ b/bindings/cashu-ffi/src/nuts/nut00/token.rs @@ -1,12 +1,37 @@ +use std::fmt; use std::str::FromStr; use std::sync::Arc; use cashu::nuts::nut00::wallet::Token as TokenSdk; +use cashu::nuts::CurrencyUnit as CurrencyUnitSdk; use cashu::url::UncheckedUrl; use crate::error::Result; use crate::{MintProofs, Proof}; +pub enum CurrencyUnit { + Sat, + Custom { unit: String }, +} + +impl From<&CurrencyUnit> for CurrencyUnitSdk { + fn from(unit: &CurrencyUnit) -> CurrencyUnitSdk { + match unit { + CurrencyUnit::Sat => CurrencyUnitSdk::Sat, + CurrencyUnit::Custom { unit } => CurrencyUnitSdk::Custom(unit.clone()), + } + } +} + +impl From for CurrencyUnit { + fn from(unit: CurrencyUnitSdk) -> CurrencyUnit { + match unit { + CurrencyUnitSdk::Sat => CurrencyUnit::Sat, + CurrencyUnitSdk::Custom(unit) => CurrencyUnit::Custom { unit: unit.clone() }, + } + } +} + pub struct Token { inner: TokenSdk, } @@ -15,13 +40,16 @@ impl Token { pub fn new( mint: String, proofs: Vec>, - unit: Option, memo: Option, + unit: Option, ) -> Result { let mint = UncheckedUrl::from_str(&mint)?; let proofs = proofs.into_iter().map(|p| p.as_ref().into()).collect(); + + let unit = unit.map(|u| CurrencyUnitSdk::from_str(&u).unwrap_or_default().into()); + Ok(Self { - inner: TokenSdk::new(mint, proofs, unit, memo)?, + inner: TokenSdk::new(mint, proofs, memo, unit)?, }) } @@ -38,14 +66,23 @@ impl Token { self.inner.memo.clone() } + pub fn unit(&self) -> Option { + self.inner + .unit + .clone() + .map(|u| Into::::into(u).to_string()) + } + pub fn from_string(token: String) -> Result { Ok(Self { inner: TokenSdk::from_str(&token)?, }) } +} - pub fn as_string(&self) -> Result { - Ok(self.inner.to_string()) +impl fmt::Display for Token { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.inner.to_string()) } } diff --git a/bindings/cashu-ffi/src/nuts/nut05/mod.rs b/bindings/cashu-ffi/src/nuts/nut05/mod.rs index 98e5ee14..f64eae5d 100644 --- a/bindings/cashu-ffi/src/nuts/nut05/mod.rs +++ b/bindings/cashu-ffi/src/nuts/nut05/mod.rs @@ -2,100 +2,130 @@ use std::ops::Deref; use std::str::FromStr; use std::sync::Arc; -use cashu::nuts::nut05::{ - CheckFeesRequest as CheckFeesRequestSdk, CheckFeesResponse as CheckFeesResponseSdk, - MeltRequest as MeltRequestSdk, MeltResponse as MeltResponseSdk, +use cashu::nuts::{ + CurrencyUnit, MeltBolt11Request as MeltBolt11RequestSdk, + MeltBolt11Response as MeltBolt11ResponseSdk, + MeltQuoteBolt11Request as MeltQuoteBolt11RequestSdk, + MeltQuoteBolt11Response as MeltQuoteBolt11ResponseSdk, }; use cashu::Bolt11Invoice; use crate::error::Result; -use crate::{Amount, Proof}; +use crate::{BlindedMessage, BlindedSignature, Proof}; -pub struct CheckFeesRequest { - inner: CheckFeesRequestSdk, +pub struct MeltQuoteBolt11Response { + inner: MeltQuoteBolt11ResponseSdk, } -impl CheckFeesRequest { - pub fn new(invoice: String) -> Result { +impl MeltQuoteBolt11Response { + pub fn new( + quote: String, + amount: u64, + fee_reserve: u64, + paid: bool, + expiry: u64, + ) -> Result { Ok(Self { - inner: CheckFeesRequestSdk { - pr: Bolt11Invoice::from_str(&invoice)?, + inner: MeltQuoteBolt11ResponseSdk { + quote, + amount, + fee_reserve, + paid, + expiry, }, }) } - pub fn invoice(&self) -> String { - self.inner.pr.to_string() + pub fn quote(&self) -> String { + self.inner.quote.clone() + } + + pub fn amount(&self) -> u64 { + self.inner.amount + } + + pub fn fee_reserve(&self) -> u64 { + self.inner.fee_reserve + } + + pub fn paid(&self) -> bool { + self.inner.paid + } + + pub fn expiry(&self) -> u64 { + self.inner.expiry } } -pub struct CheckFeesResponse { - inner: CheckFeesResponseSdk, +pub struct MeltQuoteBolt11Request { + inner: MeltQuoteBolt11RequestSdk, } -impl CheckFeesResponse { - pub fn new(amount: Arc) -> Self { - Self { - inner: CheckFeesResponseSdk { - fee: *amount.as_ref().deref(), - }, - } - } - - pub fn amount(&self) -> Arc { - Arc::new(self.inner.fee.into()) - } -} - -impl From for CheckFeesResponse { - fn from(inner: cashu::nuts::nut05::CheckFeesResponse) -> CheckFeesResponse { - Self { inner } - } -} - -impl From for cashu::nuts::nut05::CheckFeesResponse { - fn from(res: CheckFeesResponse) -> cashu::nuts::nut05::CheckFeesResponse { - res.inner - } -} - -pub struct MeltRequest { - inner: MeltRequestSdk, -} - -impl MeltRequest { - pub fn new(proofs: Vec>, invoice: String) -> Result { - let pr = Bolt11Invoice::from_str(&invoice)?; +impl MeltQuoteBolt11Request { + pub fn new(request: String, unit: String) -> Result { Ok(Self { - inner: MeltRequestSdk { - pr, - proofs: proofs.into_iter().map(|p| p.as_ref().into()).collect(), + inner: MeltQuoteBolt11RequestSdk { + request: Bolt11Invoice::from_str(&request)?, + unit: CurrencyUnit::from_str(&unit)?, }, }) } - pub fn proofs(&self) -> Vec> { + pub fn request(&self) -> String { + self.inner.request.to_string() + } + + pub fn unit(&self) -> String { + self.inner.unit.to_string() + } +} + +pub struct MeltBolt11Request { + inner: MeltBolt11RequestSdk, +} + +impl MeltBolt11Request { + pub fn new( + quote: String, + inputs: Vec>, + outputs: Option>>, + ) -> Result { + Ok(Self { + inner: MeltBolt11RequestSdk { + quote, + inputs: inputs.into_iter().map(|p| p.as_ref().into()).collect(), + outputs: outputs + .map(|o| o.into_iter().map(|p| p.as_ref().deref().clone()).collect()), + }, + }) + } + + pub fn inputs(&self) -> Vec> { self.inner - .proofs + .inputs .clone() .into_iter() .map(|p| Arc::new(p.into())) .collect() } - pub fn invoice(&self) -> String { - self.inner.pr.to_string() + pub fn quote(&self) -> String { + self.inner.quote } } -pub struct MeltResponse { - inner: MeltResponseSdk, +pub struct MeltBolt11Response { + inner: MeltBolt11ResponseSdk, } -impl MeltResponse { - pub fn new(paid: bool, preimage: Option) -> Self { +impl MeltBolt11Response { + pub fn new(paid: bool, proof: String, change: Option>>) -> Self { Self { - inner: MeltResponseSdk { paid, preimage }, + inner: MeltBolt11ResponseSdk { + paid, + proof, + change: change.map(|c| c.into_iter().map(|b| b.as_ref().deref().clone()).collect()), + }, } } @@ -103,7 +133,7 @@ impl MeltResponse { self.inner.paid } - pub fn preimage(&self) -> Option { - self.inner.preimage.clone() + pub fn proof(&self) -> String { + self.inner.proof.clone() } } diff --git a/bindings/cashu-ffi/src/nuts/nut08/mod.rs b/bindings/cashu-ffi/src/nuts/nut08/mod.rs index 7bdd33ec..8390a1ff 100644 --- a/bindings/cashu-ffi/src/nuts/nut08/mod.rs +++ b/bindings/cashu-ffi/src/nuts/nut08/mod.rs @@ -1,53 +1,51 @@ use std::ops::Deref; -use std::str::FromStr; use std::sync::Arc; -use cashu::nuts::nut08::{MeltRequest as MeltRequestSdk, MeltResponse as MeltResponseSdk}; -use cashu::Bolt11Invoice; +use cashu::nuts::nut08::{ + MeltBolt11Request as MeltBolt11RequestSdk, MeltBolt11Response as MeltBolt11ResponseSdk, +}; use crate::error::Result; use crate::{BlindedMessage, BlindedSignature, Proof}; -pub struct MeltRequest { - inner: MeltRequestSdk, +pub struct MeltBolt11Request { + inner: MeltBolt11RequestSdk, } -impl Deref for MeltRequest { - type Target = MeltRequestSdk; +impl Deref for MeltBolt11Request { + type Target = MeltBolt11RequestSdk; fn deref(&self) -> &Self::Target { &self.inner } } -impl MeltRequest { +impl MeltBolt11Request { pub fn new( + quote: String, proofs: Vec>, - invoice: String, outputs: Option>>, ) -> Result { - let pr = Bolt11Invoice::from_str(&invoice)?; - Ok(Self { - inner: MeltRequestSdk { - proofs: proofs.iter().map(|p| p.as_ref().into()).collect(), - pr, + inner: MeltBolt11RequestSdk { + quote, + inputs: proofs.iter().map(|p| p.as_ref().into()).collect(), outputs: outputs .map(|outputs| outputs.into_iter().map(|o| o.as_ref().into()).collect()), }, }) } - pub fn proofs(&self) -> Vec> { + pub fn inputs(&self) -> Vec> { self.inner - .proofs + .inputs .clone() .into_iter() .map(|o| Arc::new(o.into())) .collect() } - pub fn invoice(&self) -> String { - self.inner.pr.to_string() + pub fn quote(&self) -> String { + self.inner.quote.clone() } pub fn outputs(&self) -> Option>> { @@ -58,39 +56,35 @@ impl MeltRequest { } } -pub struct MeltResponse { - inner: MeltResponseSdk, +pub struct MeltBolt11Response { + inner: MeltBolt11ResponseSdk, } -impl Deref for MeltResponse { - type Target = MeltResponseSdk; +impl Deref for MeltBolt11Response { + type Target = MeltBolt11ResponseSdk; fn deref(&self) -> &Self::Target { &self.inner } } -impl From for MeltResponse { - fn from(inner: cashu::nuts::nut08::MeltResponse) -> MeltResponse { - MeltResponse { inner } +impl From for MeltBolt11Response { + fn from(inner: cashu::nuts::nut08::MeltBolt11Response) -> MeltBolt11Response { + MeltBolt11Response { inner } } } -impl From for cashu::nuts::nut08::MeltResponse { - fn from(res: MeltResponse) -> cashu::nuts::nut08::MeltResponse { +impl From for cashu::nuts::nut08::MeltBolt11Response { + fn from(res: MeltBolt11Response) -> cashu::nuts::nut08::MeltBolt11Response { res.inner } } -impl MeltResponse { - pub fn new( - paid: bool, - preimage: Option, - change: Option>>, - ) -> Self { +impl MeltBolt11Response { + pub fn new(paid: bool, proof: String, change: Option>>) -> Self { Self { - inner: MeltResponseSdk { + inner: MeltBolt11ResponseSdk { paid, - preimage, + proof, change: change .map(|change| change.into_iter().map(|bs| bs.as_ref().into()).collect()), }, @@ -101,8 +95,8 @@ impl MeltResponse { self.inner.paid } - pub fn preimage(&self) -> Option { - self.inner.preimage.clone() + pub fn proof(&self) -> String { + self.inner.proof.clone() } pub fn change(&self) -> Option>> { diff --git a/bindings/cashu-js/src/nuts/nut00/token.rs b/bindings/cashu-js/src/nuts/nut00/token.rs index 141fa6fc..0de0a44a 100644 --- a/bindings/cashu-js/src/nuts/nut00/token.rs +++ b/bindings/cashu-js/src/nuts/nut00/token.rs @@ -2,6 +2,7 @@ use std::ops::Deref; use std::str::FromStr; use cashu::nuts::nut00::wallet::Token; +use cashu::nuts::CurrencyUnit; use cashu::url::UncheckedUrl; use wasm_bindgen::prelude::*; @@ -31,13 +32,18 @@ impl JsToken { pub fn new( mint: String, proofs: JsValue, - unit: Option, memo: Option, + unit: Option, ) -> Result { let mint = UncheckedUrl::from_str(&mint).map_err(into_err)?; let proofs = serde_wasm_bindgen::from_value(proofs).map_err(into_err)?; + let unit = unit.map(|u| { + CurrencyUnit::from_str(&u) + .map_err(into_err) + .unwrap_or_default() + }); Ok(Self { - inner: Token::new(mint, proofs, unit, memo).map_err(into_err)?, + inner: Token::new(mint, proofs, memo, unit).map_err(into_err)?, }) } diff --git a/bindings/cashu-js/src/nuts/nut05.rs b/bindings/cashu-js/src/nuts/nut05.rs index 33a1fdd0..8b137891 100644 --- a/bindings/cashu-js/src/nuts/nut05.rs +++ b/bindings/cashu-js/src/nuts/nut05.rs @@ -1,79 +1 @@ -use std::ops::Deref; -use cashu::nuts::nut05::{CheckFeesRequest, CheckFeesResponse}; -use wasm_bindgen::prelude::*; - -use crate::error::Result; -use crate::types::{JsAmount, JsBolt11Invoice}; - -#[wasm_bindgen(js_name = CheckFeesRequest)] -pub struct JsCheckFeesRequest { - inner: CheckFeesRequest, -} - -impl Deref for JsCheckFeesRequest { - type Target = CheckFeesRequest; - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl From for JsCheckFeesRequest { - fn from(inner: CheckFeesRequest) -> JsCheckFeesRequest { - JsCheckFeesRequest { inner } - } -} - -#[wasm_bindgen(js_class = CheckFeesRequest)] -impl JsCheckFeesRequest { - #[wasm_bindgen(constructor)] - pub fn new(invoice: JsBolt11Invoice) -> Result { - Ok(JsCheckFeesRequest { - inner: CheckFeesRequest { - pr: invoice.clone(), - }, - }) - } - - /// Get Amount - #[wasm_bindgen(getter)] - pub fn invoice(&self) -> JsBolt11Invoice { - self.inner.pr.clone().into() - } -} - -#[wasm_bindgen(js_name = CheckFeesResponse)] -pub struct JsCheckFeesResponse { - inner: CheckFeesResponse, -} - -impl Deref for JsCheckFeesResponse { - type Target = CheckFeesResponse; - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl From for JsCheckFeesResponse { - fn from(inner: CheckFeesResponse) -> JsCheckFeesResponse { - JsCheckFeesResponse { inner } - } -} - -#[wasm_bindgen(js_class = CheckFeesResponse)] -impl JsCheckFeesResponse { - #[wasm_bindgen(constructor)] - pub fn new(amount: JsAmount) -> Result { - Ok(JsCheckFeesResponse { - inner: CheckFeesResponse { - fee: *amount.deref(), - }, - }) - } - - /// Get Amount - #[wasm_bindgen(getter)] - pub fn amount(&self) -> JsAmount { - self.inner.fee.into() - } -} diff --git a/bindings/cashu-js/src/nuts/nut08.rs b/bindings/cashu-js/src/nuts/nut08.rs index 4f7142f1..bd210eea 100644 --- a/bindings/cashu-js/src/nuts/nut08.rs +++ b/bindings/cashu-js/src/nuts/nut08.rs @@ -1,26 +1,26 @@ use std::ops::Deref; use cashu::nuts::nut00::{BlindedMessage, BlindedSignature, Proof}; -use cashu::nuts::nut08::{MeltRequest, MeltResponse}; +use cashu::nuts::nut08::{MeltBolt11Request, MeltBolt11Response}; use wasm_bindgen::prelude::*; use crate::error::{into_err, Result}; -use crate::types::{JsAmount, JsBolt11Invoice}; +use crate::types::JsAmount; #[wasm_bindgen(js_name = MeltRequest)] pub struct JsMeltRequest { - inner: MeltRequest, + inner: MeltBolt11Request, } impl Deref for JsMeltRequest { - type Target = MeltRequest; + type Target = MeltBolt11Request; fn deref(&self) -> &Self::Target { &self.inner } } -impl From for JsMeltRequest { - fn from(inner: MeltRequest) -> JsMeltRequest { +impl From for JsMeltRequest { + fn from(inner: MeltBolt11Request) -> JsMeltRequest { JsMeltRequest { inner } } } @@ -28,12 +28,8 @@ impl From for JsMeltRequest { #[wasm_bindgen(js_class = MeltRequest)] impl JsMeltRequest { #[wasm_bindgen(constructor)] - pub fn new( - proofs: JsValue, - invoice: JsBolt11Invoice, - outputs: JsValue, - ) -> Result { - let proofs: Vec = serde_wasm_bindgen::from_value(proofs).map_err(into_err)?; + pub fn new(quote: String, inputs: JsValue, outputs: JsValue) -> Result { + let inputs: Vec = serde_wasm_bindgen::from_value(inputs).map_err(into_err)?; let outputs: Option> = if !outputs.is_null() { Some(serde_wasm_bindgen::from_value(outputs).map_err(into_err)?) } else { @@ -41,9 +37,9 @@ impl JsMeltRequest { }; Ok(JsMeltRequest { - inner: MeltRequest { - proofs, - pr: invoice.deref().clone(), + inner: MeltBolt11Request { + quote, + inputs, outputs, }, }) @@ -51,14 +47,14 @@ impl JsMeltRequest { /// Get Proofs #[wasm_bindgen(getter)] - pub fn proofs(&self) -> Result { - serde_wasm_bindgen::to_value(&self.inner.proofs).map_err(into_err) + pub fn inputs(&self) -> Result { + serde_wasm_bindgen::to_value(&self.inner.inputs).map_err(into_err) } /// Get Invoice #[wasm_bindgen(getter)] - pub fn invoice(&self) -> JsBolt11Invoice { - self.inner.pr.clone().into() + pub fn quote(&self) -> String { + self.inner.quote.clone() } /// Get outputs @@ -70,18 +66,18 @@ impl JsMeltRequest { #[wasm_bindgen(js_name = MeltResponse)] pub struct JsMeltResponse { - inner: MeltResponse, + inner: MeltBolt11Response, } impl Deref for JsMeltResponse { - type Target = MeltResponse; + type Target = MeltBolt11Response; fn deref(&self) -> &Self::Target { &self.inner } } -impl From for JsMeltResponse { - fn from(inner: MeltResponse) -> JsMeltResponse { +impl From for JsMeltResponse { + fn from(inner: MeltBolt11Response) -> JsMeltResponse { JsMeltResponse { inner } } } @@ -89,7 +85,7 @@ impl From for JsMeltResponse { #[wasm_bindgen(js_class = MeltResponse)] impl JsMeltResponse { #[wasm_bindgen(constructor)] - pub fn new(paid: bool, preimage: Option, change: JsValue) -> Result { + pub fn new(paid: bool, proof: String, change: JsValue) -> Result { let change: Option> = if change.is_null() { Some(serde_wasm_bindgen::from_value(change).map_err(into_err)?) } else { @@ -97,9 +93,9 @@ impl JsMeltResponse { }; Ok(JsMeltResponse { - inner: MeltResponse { + inner: MeltBolt11Response { + proof, paid, - preimage, change, }, }) @@ -113,8 +109,8 @@ impl JsMeltResponse { /// Get Preimage #[wasm_bindgen(getter)] - pub fn preimage(&self) -> Option { - self.inner.preimage.clone() + pub fn proof(&self) -> String { + self.inner.proof.clone() } /// Get Change @@ -125,7 +121,7 @@ impl JsMeltResponse { /// Change Amount #[wasm_bindgen(js_name = "changeAmount")] - pub fn change_amount(&self) -> JsAmount { - self.inner.change_amount().into() + pub fn change_amount(&self) -> Option { + self.inner.change_amount().map(|a| a.into()) } } diff --git a/bindings/cashu-sdk-ffi/src/cashu_sdk.udl b/bindings/cashu-sdk-ffi/src/cashu_sdk.udl index 8dc65f7e..47ab48ca 100644 --- a/bindings/cashu-sdk-ffi/src/cashu_sdk.udl +++ b/bindings/cashu-sdk-ffi/src/cashu_sdk.udl @@ -10,6 +10,13 @@ interface CashuError { // Types + +[Enum] +interface CurrencyUnit { + Sat(); + Custom(string unit); +}; + interface Bolt11Invoice { [Throws=CashuError] constructor(string bolt11); @@ -91,11 +98,11 @@ interface MintProofs { interface Token { [Throws=CashuError] - constructor(string mint, sequence token, string? unit, string? memo); + constructor(string mint, sequence token, string? memo, CurrencyUnit? unit); sequence token(); string? memo(); - [Throws=CashuError] - string as_string(); + CurrencyUnit? unit(); + string to_string(); [Throws=CashuError, Name=from_string] constructor(string token); @@ -148,6 +155,32 @@ interface KeySetResponse { sequence keysets(); }; +// NUT-05 + +interface MeltQuoteBolt11Response { + [Throws=CashuError] + constructor(string quote, u64 amount, u64 fee_reserve, boolean paid, u64 expiry); + string quote(); + u64 amount(); + u64 fee_reserve(); + boolean paid(); + u64 expiry(); +}; + +interface MeltQuoteBolt11Request { + [Throws=CashuError] + constructor(string request, string unit); + string request(); + string unit(); +}; + +interface MeltBolt11Request { + [Throws=CashuError] + constructor(sequence inputs, string quote); + sequence inputs(); + string quote(); +}; + interface RequestMintResponse { [Throws=CashuError] constructor(string invoice, string hash); @@ -166,30 +199,6 @@ interface PostMintResponse { sequence promises(); }; -interface CheckFeesRequest { - [Throws=CashuError] - constructor(string invoice); - string invoice(); -}; - -interface CheckFeesResponse { - constructor(Amount amount); - Amount amount(); -}; - -interface Nut05MeltRequest { - [Throws=CashuError] - constructor(sequence proofs, string Invoice); - sequence proofs(); - string invoice(); -}; - -interface Nut05MeltResponse { - constructor(boolean paid, string? preimage); - boolean paid(); - string? preimage(); -}; - interface SplitRequest { constructor(sequence proofs, sequence outputs); sequence proofs(); @@ -205,31 +214,6 @@ interface SplitResponse { }; -interface CheckSpendableRequest { - constructor(sequence proofs); - sequence proofs(); -}; - -interface CheckSpendableResponse { - constructor(sequence spendable, sequence pending); - sequence spendable(); - sequence pending(); -}; - -interface MeltRequest { - [Throws=CashuError] - constructor(sequence proofs, string Invoice, sequence? outputs); - sequence proofs(); - string invoice(); - sequence? outputs(); -}; - -interface MeltResponse { - constructor(boolean paid, string? preimage, sequence? change); - boolean paid(); - string? preimage(); - sequence? change(); -}; interface MintVersion { constructor(string name, string version); diff --git a/bindings/cashu-sdk-js/src/mint.rs b/bindings/cashu-sdk-js/src/mint.rs index f824ce23..5d62700b 100644 --- a/bindings/cashu-sdk-js/src/mint.rs +++ b/bindings/cashu-sdk-js/src/mint.rs @@ -38,16 +38,20 @@ impl JsMint { secret: String, keyset_info: JsValue, spent_secrets: JsValue, + quotes: JsValue, min_fee_reserve: JsAmount, percent_fee_reserve: f32, ) -> Result { let keyset_info = serde_wasm_bindgen::from_value(keyset_info).map_err(into_err)?; let spent_secrets = serde_wasm_bindgen::from_value(spent_secrets).map_err(into_err)?; + + let quotes = serde_wasm_bindgen::from_value(quotes).map_err(into_err)?; Ok(JsMint { inner: Mint::new( &secret, keyset_info, spent_secrets, + quotes, *min_fee_reserve.deref(), percent_fee_reserve, ), diff --git a/bindings/cashu-sdk-js/src/wallet.rs b/bindings/cashu-sdk-js/src/wallet.rs index 66d3aebc..57c398ed 100644 --- a/bindings/cashu-sdk-js/src/wallet.rs +++ b/bindings/cashu-sdk-js/src/wallet.rs @@ -1,12 +1,14 @@ use std::ops::Deref; +use std::str::FromStr; use cashu_js::nuts::nut00::{JsBlindedMessages, JsToken}; use cashu_js::nuts::nut01::JsKeys; use cashu_js::nuts::nut03::JsRequestMintResponse; +use cashu_js::JsAmount; #[cfg(feature = "nut07")] use cashu_js::JsProofsStatus; -use cashu_js::{JsAmount, JsBolt11Invoice}; use cashu_sdk::client::gloo_client::HttpClient; +use cashu_sdk::nuts::CurrencyUnit; use cashu_sdk::wallet::Wallet; use wasm_bindgen::prelude::*; @@ -72,12 +74,14 @@ impl JsWallet { &self, amount: JsAmount, hash: String, - unit: Option, memo: Option, + unit: Option, ) -> Result { + let unit = unit.map(|u| CurrencyUnit::from_str(&u).unwrap_or_default()); + Ok(self .inner - .mint_token(*amount.deref(), &hash, unit, memo) + .mint_token(*amount.deref(), &hash, memo, unit) .await .map_err(into_err)? .into()) @@ -96,17 +100,6 @@ impl JsWallet { .map_err(into_err) } - /// Check Fee - #[wasm_bindgen(js_name = checkFee)] - pub async fn check_fee(&self, invoice: JsBolt11Invoice) -> Result { - Ok(self - .inner - .check_fee(invoice.deref().clone()) - .await - .map_err(into_err)? - .into()) - } - /// Receive #[wasm_bindgen(js_name = receive)] pub async fn receive(&self, token: String) -> Result { @@ -149,7 +142,7 @@ impl JsWallet { #[wasm_bindgen(js_name = melt)] pub async fn melt( &self, - invoice: JsBolt11Invoice, + quote: String, proofs: JsValue, fee_reserve: JsAmount, ) -> Result { @@ -157,7 +150,7 @@ impl JsWallet { Ok(self .inner - .melt(invoice.deref().clone(), proofs, *fee_reserve.deref()) + .melt(quote, proofs, *fee_reserve.deref()) .await .map_err(into_err)? .into()) @@ -173,8 +166,14 @@ impl JsWallet { ) -> Result { let proofs = serde_wasm_bindgen::from_value(proofs).map_err(into_err)?; + let unit = unit.map(|u| { + CurrencyUnit::from_str(&u) + .map_err(into_err) + .unwrap_or_default() + }); + self.inner - .proofs_to_token(proofs, unit, memo) + .proofs_to_token(proofs, memo, unit) .map_err(into_err) } } diff --git a/crates/cashu-sdk/src/client/gloo_client.rs b/crates/cashu-sdk/src/client/gloo_client.rs index ed3450a9..b21b238d 100644 --- a/crates/cashu-sdk/src/client/gloo_client.rs +++ b/crates/cashu-sdk/src/client/gloo_client.rs @@ -4,13 +4,12 @@ use async_trait::async_trait; #[cfg(feature = "nut09")] use cashu::nuts::MintInfo; use cashu::nuts::{ - BlindedMessage, CheckFeesRequest, CheckFeesResponse, Keys, MeltRequest, MeltResponse, - MintRequest, PostMintResponse, PreMintSecrets, Proof, RequestMintResponse, SplitRequest, - SplitResponse, *, + BlindedMessage, Keys, MeltBolt11Request, MeltBolt11Response, MintRequest, PostMintResponse, + PreMintSecrets, Proof, RequestMintResponse, SplitRequest, SplitResponse, *, }; #[cfg(feature = "nut07")] use cashu::nuts::{CheckSpendableRequest, CheckSpendableResponse}; -use cashu::{Amount, Bolt11Invoice}; +use cashu::Amount; use gloo::net::http::Request; use serde_json::Value; use url::Url; @@ -118,49 +117,20 @@ impl Client for HttpClient { } } - /// Check Max expected fee [NUT-05] - async fn post_check_fees( - &self, - mint_url: Url, - invoice: Bolt11Invoice, - ) -> Result { - let url = join_url(mint_url, "checkfees")?; - - let request = CheckFeesRequest { pr: invoice }; - - let res = Request::post(url.as_str()) - .json(&request) - .map_err(|err| Error::Gloo(err.to_string()))? - .send() - .await - .map_err(|err| Error::Gloo(err.to_string()))? - .json::() - .await - .map_err(|err| Error::Gloo(err.to_string()))?; - - let response: Result = - serde_json::from_value(res.clone()); - - match response { - Ok(res) => Ok(res), - Err(_) => Err(Error::from_json(&res.to_string())?), - } - } - /// Melt [NUT-05] /// [Nut-08] Lightning fee return if outputs defined async fn post_melt( &self, mint_url: Url, - proofs: Vec, - invoice: Bolt11Invoice, + quote: String, + inputs: Vec, outputs: Option>, - ) -> Result { + ) -> Result { let url = join_url(mint_url, "melt")?; - let request = MeltRequest { - proofs, - pr: invoice, + let request = MeltBolt11Request { + quote, + inputs, outputs, }; @@ -174,7 +144,7 @@ impl Client for HttpClient { .await .map_err(|err| Error::Gloo(err.to_string()))?; - let response: Result = + let response: Result = serde_json::from_value(value.clone()); match response { diff --git a/crates/cashu-sdk/src/client/minreq_client.rs b/crates/cashu-sdk/src/client/minreq_client.rs index 75315ee5..28501f80 100644 --- a/crates/cashu-sdk/src/client/minreq_client.rs +++ b/crates/cashu-sdk/src/client/minreq_client.rs @@ -6,13 +6,12 @@ use async_trait::async_trait; #[cfg(feature = "nut09")] use cashu::nuts::MintInfo; use cashu::nuts::{ - BlindedMessage, CheckFeesRequest, CheckFeesResponse, Keys, MeltRequest, MeltResponse, - MintRequest, PostMintResponse, PreMintSecrets, Proof, RequestMintResponse, SplitRequest, - SplitResponse, *, + BlindedMessage, Keys, MeltBolt11Request, MeltBolt11Response, MintRequest, PostMintResponse, + PreMintSecrets, Proof, RequestMintResponse, SplitRequest, SplitResponse, *, }; #[cfg(feature = "nut07")] use cashu::nuts::{CheckSpendableRequest, CheckSpendableResponse}; -use cashu::{Amount, Bolt11Invoice}; +use cashu::Amount; use serde_json::Value; use url::Url; @@ -96,44 +95,20 @@ impl Client for HttpClient { } } - /// Check Max expected fee [NUT-05] - async fn post_check_fees( - &self, - mint_url: Url, - invoice: Bolt11Invoice, - ) -> Result { - let url = join_url(mint_url, "checkfees")?; - - let request = CheckFeesRequest { pr: invoice }; - - let res = minreq::post(url) - .with_json(&request)? - .send()? - .json::()?; - - let response: Result = - serde_json::from_value(res.clone()); - - match response { - Ok(res) => Ok(res), - Err(_) => Err(Error::from_json(&res.to_string())?), - } - } - /// Melt [NUT-05] /// [Nut-08] Lightning fee return if outputs defined async fn post_melt( &self, mint_url: Url, - proofs: Vec, - invoice: Bolt11Invoice, + quote: String, + inputs: Vec, outputs: Option>, - ) -> Result { + ) -> Result { let url = join_url(mint_url, "melt")?; - let request = MeltRequest { - proofs, - pr: invoice, + let request = MeltBolt11Request { + quote, + inputs, outputs, }; @@ -142,7 +117,7 @@ impl Client for HttpClient { .send()? .json::()?; - let response: Result = + let response: Result = serde_json::from_value(value.clone()); match response { diff --git a/crates/cashu-sdk/src/client/mod.rs b/crates/cashu-sdk/src/client/mod.rs index 0699da65..b5d6837f 100644 --- a/crates/cashu-sdk/src/client/mod.rs +++ b/crates/cashu-sdk/src/client/mod.rs @@ -8,8 +8,8 @@ use cashu::nuts::CheckSpendableResponse; #[cfg(feature = "nut09")] use cashu::nuts::MintInfo; use cashu::nuts::{ - BlindedMessage, CheckFeesResponse, Keys, KeysetResponse, MeltResponse, PostMintResponse, - PreMintSecrets, Proof, RequestMintResponse, SplitRequest, SplitResponse, + BlindedMessage, Keys, KeysetResponse, MeltBolt11Response, PostMintResponse, PreMintSecrets, + Proof, RequestMintResponse, SplitRequest, SplitResponse, }; use cashu::{utils, Amount}; use serde::{Deserialize, Serialize}; @@ -103,19 +103,13 @@ pub trait Client { hash: &str, ) -> Result; - async fn post_check_fees( - &self, - mint_url: Url, - invoice: Bolt11Invoice, - ) -> Result; - async fn post_melt( &self, mint_url: Url, - proofs: Vec, - invoice: Bolt11Invoice, + quote: String, + inputs: Vec, outputs: Option>, - ) -> Result; + ) -> Result; // REVIEW: Should be consistent aboue passing in the Request struct or the // compnatants and making it within the function. Here the struct is passed diff --git a/crates/cashu-sdk/src/mint.rs b/crates/cashu-sdk/src/mint.rs index e454706a..57a9ab6f 100644 --- a/crates/cashu-sdk/src/mint.rs +++ b/crates/cashu-sdk/src/mint.rs @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet}; use cashu::dhke::{sign_message, verify_message}; pub use cashu::error::mint::Error; use cashu::nuts::{ - BlindedMessage, BlindedSignature, MeltRequest, MeltResponse, Proof, SplitRequest, + BlindedMessage, BlindedSignature, MeltBolt11Request, MeltBolt11Response, Proof, SplitRequest, SplitResponse, *, }; #[cfg(feature = "nut07")] @@ -13,6 +13,8 @@ use cashu::Amount; use serde::{Deserialize, Serialize}; use tracing::{debug, info}; +use crate::types::Quote; + pub struct Mint { // pub pubkey: PublicKey secret: String, @@ -21,6 +23,7 @@ pub struct Mint { pub spent_secrets: HashSet, pub pending_secrets: HashSet, pub fee_reserve: FeeReserve, + pub quotes: HashMap, } impl Mint { @@ -28,6 +31,7 @@ impl Mint { secret: &str, keysets_info: HashSet, spent_secrets: HashSet, + quotes: Vec, min_fee_reserve: Amount, percent_fee_reserve: f32, ) -> Self { @@ -36,6 +40,8 @@ impl Mint { let mut active_units: HashSet = HashSet::default(); + let quotes = quotes.into_iter().map(|q| (q.id.clone(), q)).collect(); + // Check that there is only one active keyset per unit for keyset_info in keysets_info { if keyset_info.active && !active_units.insert(keyset_info.unit.clone()) { @@ -58,6 +64,7 @@ impl Mint { Self { secret: secret.to_string(), keysets, + quotes, keysets_info: info, spent_secrets, pending_secrets: HashSet::new(), @@ -222,41 +229,28 @@ impl Mint { Ok(CheckSpendableResponse { spendable, pending }) } - pub fn verify_melt_request(&mut self, melt_request: &MeltRequest) -> Result<(), Error> { - let proofs_total = melt_request.proofs_amount(); + pub fn verify_melt_request(&mut self, melt_request: &MeltBolt11Request) -> Result<(), Error> { + let quote = self.quotes.get(&melt_request.quote).unwrap(); + let proofs_total = melt_request.proofs_amount().to_sat(); - let percent_fee_reserve = Amount::from_sat( - (proofs_total.to_sat() as f32 * self.fee_reserve.percent_fee_reserve) as u64, - ); - - let fee_reserve = if percent_fee_reserve > self.fee_reserve.min_fee_reserve { - percent_fee_reserve - } else { - self.fee_reserve.min_fee_reserve - }; - - let required_total = melt_request - .invoice_amount() - .map_err(|_| Error::InvoiceAmountUndefined)? - + fee_reserve; + let required_total = quote.amount + quote.fee_reserve; if proofs_total < required_total { debug!( "Insufficient Proofs: Got: {}, Required: {}", - proofs_total.to_sat().to_string(), - required_total.to_sat().to_string() + proofs_total, required_total ); return Err(Error::Amount); } - let secrets: HashSet<&Secret> = melt_request.proofs.iter().map(|p| &p.secret).collect(); + let secrets: HashSet<&Secret> = melt_request.inputs.iter().map(|p| &p.secret).collect(); // Ensure proofs are unique and not being double spent - if melt_request.proofs.len().ne(&secrets.len()) { + if melt_request.inputs.len().ne(&secrets.len()) { return Err(Error::DuplicateProofs); } - for proof in &melt_request.proofs { + for proof in &melt_request.inputs { self.verify_proof(proof)? } @@ -265,13 +259,13 @@ impl Mint { pub fn process_melt_request( &mut self, - melt_request: &MeltRequest, + melt_request: &MeltBolt11Request, preimage: &str, total_spent: Amount, - ) -> Result { + ) -> Result { self.verify_melt_request(melt_request)?; - let secrets = Vec::with_capacity(melt_request.proofs.len()); + let secrets = Vec::with_capacity(melt_request.inputs.len()); for secret in secrets { self.spent_secrets.insert(secret); } @@ -312,9 +306,9 @@ impl Mint { ); } - Ok(MeltResponse { + Ok(MeltBolt11Response { paid: true, - preimage: Some(preimage.to_string()), + proof: preimage.to_string(), change, }) } diff --git a/crates/cashu-sdk/src/wallet.rs b/crates/cashu-sdk/src/wallet.rs index f0488b4f..5085720b 100644 --- a/crates/cashu-sdk/src/wallet.rs +++ b/crates/cashu-sdk/src/wallet.rs @@ -5,8 +5,8 @@ use cashu::dhke::{construct_proofs, unblind_message}; #[cfg(feature = "nut07")] use cashu::nuts::nut00::mint; use cashu::nuts::{ - BlindedSignature, Keys, PreMintSecrets, PreSplit, Proof, Proofs, RequestMintResponse, - SplitRequest, Token, + BlindedSignature, CurrencyUnit, Keys, PreMintSecrets, PreSplit, Proof, Proofs, + RequestMintResponse, SplitRequest, Token, }; #[cfg(feature = "nut07")] use cashu::types::ProofsStatus; @@ -95,12 +95,12 @@ impl Wallet { &self, amount: Amount, hash: &str, - unit: Option, memo: Option, + unit: Option, ) -> Result { let proofs = self.mint(amount, hash).await?; - let token = Token::new(self.mint_url.clone(), proofs, unit, memo); + let token = Token::new(self.mint_url.clone(), proofs, memo, unit); Ok(token?) } @@ -127,15 +127,6 @@ impl Wallet { Ok(proofs) } - /// Check fee - pub async fn check_fee(&self, invoice: Bolt11Invoice) -> Result { - Ok(self - .client - .post_check_fees(self.mint_url.clone().try_into()?, invoice) - .await? - .fee) - } - /// Receive pub async fn receive(&self, encoded_token: &str) -> Result { let token_data = Token::from_str(encoded_token)?; @@ -300,7 +291,7 @@ impl Wallet { pub async fn melt( &self, - invoice: Bolt11Invoice, + quote: String, proofs: Proofs, fee_reserve: Amount, ) -> Result { @@ -309,8 +300,8 @@ impl Wallet { .client .post_melt( self.mint_url.clone().try_into()?, + quote, proofs, - invoice, Some(blinded.blinded_messages()), ) .await?; @@ -327,7 +318,7 @@ impl Wallet { let melted = Melted { paid: true, - preimage: melt_response.preimage, + preimage: Some(melt_response.proof), change: change_proofs, }; @@ -337,10 +328,10 @@ impl Wallet { pub fn proofs_to_token( &self, proofs: Proofs, - unit: Option, memo: Option, + unit: Option, ) -> Result { - Ok(Token::new(self.mint_url.clone(), proofs, unit, memo)?.to_string()) + Ok(Token::new(self.mint_url.clone(), proofs, memo, unit)?.to_string()) } } diff --git a/crates/cashu/src/nuts/mod.rs b/crates/cashu/src/nuts/mod.rs index 9e96d67f..a61e4259 100644 --- a/crates/cashu/src/nuts/mod.rs +++ b/crates/cashu/src/nuts/mod.rs @@ -13,7 +13,7 @@ pub mod nut09; #[cfg(feature = "wallet")] pub use nut00::wallet::{PreMint, PreMintSecrets, Token}; -pub use nut00::{BlindedMessage, BlindedSignature, Proof}; +pub use nut00::{BlindedMessage, BlindedSignature, CurrencyUnit, Proof}; pub use nut01::{Keys, KeysResponse, PublicKey, SecretKey}; pub use nut02::mint::KeySet as MintKeySet; pub use nut02::{Id, KeySet, KeySetInfo, KeysetResponse}; @@ -21,14 +21,14 @@ pub use nut02::{Id, KeySet, KeySetInfo, KeysetResponse}; pub use nut03::PreSplit; pub use nut03::{RequestMintResponse, SplitRequest, SplitResponse}; pub use nut04::{MintRequest, PostMintResponse}; -pub use nut05::{CheckFeesRequest, CheckFeesResponse}; #[cfg(not(feature = "nut08"))] -pub use nut05::{MeltRequest, MeltResponse}; +pub use nut05::{MeltBolt11Request, MeltBolt11Response}; +pub use nut05::{MeltQuoteBolt11Request, MeltQuoteBolt11Response}; #[cfg(feature = "wallet")] #[cfg(feature = "nut07")] pub use nut07::{CheckSpendableRequest, CheckSpendableResponse}; #[cfg(feature = "nut08")] -pub use nut08::{MeltRequest, MeltResponse}; +pub use nut08::{MeltBolt11Request, MeltBolt11Response}; #[cfg(feature = "nut09")] pub use nut09::MintInfo; diff --git a/crates/cashu/src/nuts/nut00.rs b/crates/cashu/src/nuts/nut00.rs index 4e957f39..dab8e682 100644 --- a/crates/cashu/src/nuts/nut00.rs +++ b/crates/cashu/src/nuts/nut00.rs @@ -1,9 +1,13 @@ //! Notation and Models // https://github.com/cashubtc/nuts/blob/main/00.md +use std::fmt; +use std::str::FromStr; + use serde::{Deserialize, Serialize}; use super::{Id, Proofs, PublicKey}; +use crate::error::Error; use crate::secret::Secret; use crate::url::UncheckedUrl; use crate::Amount; @@ -20,6 +24,33 @@ pub struct BlindedMessage { pub keyset_id: Id, } +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CurrencyUnit { + #[default] + Sat, + Custom(String), +} + +impl FromStr for CurrencyUnit { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "sat" => Ok(Self::Sat), + _ => Ok(Self::Custom(s.to_string())), + } + } +} + +impl fmt::Display for CurrencyUnit { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CurrencyUnit::Sat => write!(f, "sat"), + CurrencyUnit::Custom(unit) => write!(f, "{}", unit), + } + } +} + #[cfg(feature = "wallet")] pub mod wallet { use std::cmp::Ordering; @@ -31,7 +62,7 @@ pub mod wallet { use serde::{Deserialize, Serialize}; use url::Url; - use super::MintProofs; + use super::{CurrencyUnit, MintProofs}; use crate::dhke::blind_message; use crate::error::wallet; use crate::nuts::{BlindedMessage, Id, Proofs, SecretKey}; @@ -201,8 +232,9 @@ pub mod wallet { /// Memo for token #[serde(skip_serializing_if = "Option::is_none")] pub memo: Option, + /// Token Unit #[serde(skip_serializing_if = "Option::is_none")] - pub unit: Option, + pub unit: Option, } impl Token { @@ -210,7 +242,7 @@ pub mod wallet { mint_url: UncheckedUrl, proofs: Proofs, memo: Option, - unit: Option, + unit: Option, ) -> Result { if proofs.is_empty() { return Err(wallet::Error::ProofsRequired); diff --git a/crates/cashu/src/nuts/nut05.rs b/crates/cashu/src/nuts/nut05.rs index bc5da8bd..fffd1270 100644 --- a/crates/cashu/src/nuts/nut05.rs +++ b/crates/cashu/src/nuts/nut05.rs @@ -3,48 +3,54 @@ use serde::{Deserialize, Serialize}; -use crate::error::Error; +use super::CurrencyUnit; use crate::nuts::Proofs; use crate::{Amount, Bolt11Invoice}; -/// Check Fees Response [NUT-05] +/// Melt quote request [NUT-05] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct CheckFeesResponse { - /// Expected Mac Fee in satoshis - pub fee: Amount, +pub struct MeltQuoteBolt11Request { + /// Bolt11 invoice to be paid + pub request: Bolt11Invoice, + /// Unit wallet would like to pay with + pub unit: CurrencyUnit, } -/// Check Fees request [NUT-05] +/// Melt quote response [NUT-05] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct CheckFeesRequest { - /// Lighting Invoice - pub pr: Bolt11Invoice, +pub struct MeltQuoteBolt11Response { + /// Quote Id + pub quote: String, + /// The amount that needs to be provided + pub amount: u64, + /// The fee reserve that is required + pub fee_reserve: u64, + /// Whether the the request haas be paid + pub paid: bool, + /// Unix timestamp until the quote is valid + pub expiry: u64, } -/// Melt Request [NUT-05] +/// Melt Bolt11 Request [NUT-05] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct MeltRequest { - pub proofs: Proofs, - /// bollt11 - pub pr: Bolt11Invoice, +pub struct MeltBolt11Request { + /// Quote ID + pub quote: String, + /// Proofs + pub inputs: Proofs, } -impl MeltRequest { +impl MeltBolt11Request { pub fn proofs_amount(&self) -> Amount { - self.proofs.iter().map(|proof| proof.amount).sum() - } - - pub fn invoice_amount(&self) -> Result { - match self.pr.amount_milli_satoshis() { - Some(value) => Ok(Amount::from_sat(value)), - None => Err(Error::InvoiceAmountUndefined), - } + self.inputs.iter().map(|proof| proof.amount).sum() } } /// Melt Response [NUT-05] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct MeltResponse { +pub struct MeltBolt11Response { + /// Indicate if payment was successful pub paid: bool, - pub preimage: Option, + /// Bolt11 preimage + pub payment_preimage: String, } diff --git a/crates/cashu/src/nuts/nut08.rs b/crates/cashu/src/nuts/nut08.rs index e326eedc..1b63d401 100644 --- a/crates/cashu/src/nuts/nut08.rs +++ b/crates/cashu/src/nuts/nut08.rs @@ -1,51 +1,49 @@ //! Lightning fee return // https://github.com/cashubtc/nuts/blob/main/08.md -use lightning_invoice::Bolt11Invoice; use serde::{Deserialize, Serialize}; use super::{BlindedMessage, BlindedSignature, Proofs}; -use crate::error::Error; use crate::Amount; -/// Melt Request [NUT-08] +/// Melt Bolt11 Request [NUT-08] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct MeltRequest { - pub proofs: Proofs, - /// bollt11 - pub pr: Bolt11Invoice, +pub struct MeltBolt11Request { + /// Quote ID + pub quote: String, + /// Proofs + pub inputs: Proofs, /// Blinded Message that can be used to return change [NUT-08] /// Amount field of blindedMessages `SHOULD` be set to zero pub outputs: Option>, } -impl MeltRequest { +impl MeltBolt11Request { pub fn proofs_amount(&self) -> Amount { - self.proofs.iter().map(|proof| proof.amount).sum() + self.inputs.iter().map(|proof| proof.amount).sum() } - pub fn invoice_amount(&self) -> Result { - match self.pr.amount_milli_satoshis() { - Some(value) => Ok(Amount::from_msat(value)), - None => Err(Error::InvoiceAmountUndefined), - } + pub fn output_amount(&self) -> Option { + self.outputs + .as_ref() + .map(|o| o.iter().map(|proof| proof.amount).sum()) } } /// Melt Response [NUT-08] /// Lightning fee return [NUT-08] if change is defined #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct MeltResponse { +pub struct MeltBolt11Response { pub paid: bool, - pub preimage: Option, + // REVIEW: https://github.com/cashubtc/nuts/pull/55#discussion_r1419991818 + pub proof: String, pub change: Option>, } -impl MeltResponse { - pub fn change_amount(&self) -> Amount { - match &self.change { - Some(change) => change.iter().map(|c| c.amount).sum(), - None => Amount::ZERO, - } +impl MeltBolt11Response { + pub fn change_amount(&self) -> Option { + self.change + .as_ref() + .map(|c| c.iter().map(|b| b.amount).sum()) } } diff --git a/crates/cashu/src/types.rs b/crates/cashu/src/types.rs index 49580696..403ff74f 100644 --- a/crates/cashu/src/types.rs +++ b/crates/cashu/src/types.rs @@ -2,7 +2,8 @@ use serde::{Deserialize, Serialize}; -use crate::nuts::{Id, KeySetInfo, Proofs}; +use crate::nuts::{CurrencyUnit, Proofs}; +use crate::Bolt11Invoice; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ProofsStatus { @@ -32,3 +33,15 @@ pub enum InvoiceStatus { Expired, InFlight, } + +/// Quote +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct Quote { + pub id: String, + pub amount: u64, + pub request: Bolt11Invoice, + pub unit: CurrencyUnit, + pub fee_reserve: u64, + pub paid: bool, + pub expiry: u64, +}