refactor: melt quote and melt to v1 in wallet sdk

This commit is contained in:
thesimplekid
2023-12-29 07:02:12 +00:00
parent dcc35092f4
commit 754936d701
17 changed files with 308 additions and 83 deletions

View File

@@ -31,8 +31,12 @@ interface Secret {
sequence<u8> as_bytes();
};
interface MintQuoteInfo {
constructor(string id, Amount amount, string unit, Bolt11Invoice? request, boolean paid, u64 boolean);
interface MintQuote {
constructor(string id, Amount amount, string unit, Bolt11Invoice request, boolean paid, u64 boolean);
};
interface MeltQuote {
constructor(string id, Amount amount, string unit, Bolt11Invoice request, Amount fee_reserve, boolean paid, u64 boolean);
};
// NUT00

View File

@@ -29,7 +29,7 @@ mod ffi {
pub use crate::nuts::nut06::{MintInfo, MintVersion};
pub use crate::nuts::nut07::{CheckSpendableRequest, CheckSpendableResponse};
pub use crate::nuts::nut08::{MeltBolt11Request, MeltBolt11Response};
pub use crate::types::{Amount, Bolt11Invoice, KeySetInfo, MintQuoteInfo, Secret};
pub use crate::types::{Amount, Bolt11Invoice, KeySetInfo, MeltQuote, MintQuote, Secret};
// UDL
uniffi::include_scaffolding!("cashu");

View File

@@ -0,0 +1,49 @@
use std::ops::Deref;
use std::str::FromStr;
use std::sync::Arc;
use cashu::nuts::CurrencyUnit;
use cashu::types::MeltQuote as MeltQuoteSdk;
use crate::{Amount, Bolt11Invoice};
pub struct MeltQuote {
inner: MeltQuoteSdk,
}
impl Deref for MeltQuote {
type Target = MeltQuoteSdk;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl From<MeltQuoteSdk> for MeltQuote {
fn from(inner: MeltQuoteSdk) -> MeltQuote {
MeltQuote { inner }
}
}
impl MeltQuote {
pub fn new(
id: String,
amount: Arc<Amount>,
unit: String,
request: Arc<Bolt11Invoice>,
fee_reserve: Arc<Amount>,
paid: bool,
expiry: u64,
) -> Self {
Self {
inner: MeltQuoteSdk {
id,
amount: amount.as_ref().deref().clone(),
unit: CurrencyUnit::from_str(&unit).unwrap(),
request: request.as_ref().deref().clone(),
fee_reserve: fee_reserve.as_ref().deref().clone(),
paid,
expiry,
},
}
}
}

View File

@@ -0,0 +1,47 @@
use std::ops::Deref;
use std::str::FromStr;
use std::sync::Arc;
use cashu::nuts::CurrencyUnit;
use cashu::types::MintQuote as MintQuoteSdk;
use crate::{Amount, Bolt11Invoice};
pub struct MintQuote {
inner: MintQuoteSdk,
}
impl Deref for MintQuote {
type Target = MintQuoteSdk;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl From<MintQuoteSdk> for MintQuote {
fn from(inner: MintQuoteSdk) -> MintQuote {
MintQuote { inner }
}
}
impl MintQuote {
pub fn new(
id: String,
amount: Arc<Amount>,
unit: String,
request: Arc<Bolt11Invoice>,
paid: bool,
expiry: u64,
) -> Self {
Self {
inner: MintQuoteSdk {
id,
amount: amount.as_ref().deref().clone(),
unit: CurrencyUnit::from_str(&unit).unwrap(),
request: request.as_ref().deref().clone(),
paid,
expiry,
},
}
}
}

View File

@@ -3,42 +3,42 @@ use std::str::FromStr;
use std::sync::Arc;
use cashu::nuts::CurrencyUnit;
use cashu::types::MintQuoteInfo as MintQuoteInfoSdk;
use cashu::types::MintQuote as MintQuoteSdk;
use crate::{Amount, Bolt11Invoice};
pub struct MintQuoteInfo {
inner: MintQuoteInfoSdk,
pub struct MintQuote {
inner: MintQuoteSdk,
}
impl Deref for MintQuoteInfo {
type Target = MintQuoteInfoSdk;
impl Deref for MintQuote {
type Target = MintQuoteSdk;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl From<MintQuoteInfoSdk> for MintQuoteInfo {
fn from(inner: MintQuoteInfoSdk) -> MintQuoteInfo {
MintQuoteInfo { inner }
impl From<MintQuoteSdk> for MintQuote {
fn from(inner: MintQuoteSdk) -> MintQuote {
MintQuote { inner }
}
}
impl MintQuoteInfo {
impl MintQuote {
pub fn new(
id: String,
amount: Arc<Amount>,
unit: String,
request: Option<Arc<Bolt11Invoice>>,
request: Arc<Bolt11Invoice>,
paid: bool,
expiry: u64,
) -> Self {
Self {
inner: MintQuoteInfoSdk {
inner: MintQuoteSdk {
id,
amount: amount.as_ref().deref().clone(),
unit: CurrencyUnit::from_str(&unit).unwrap(),
request: request.map(|r| r.as_ref().deref().clone()),
request: request.as_ref().deref().clone(),
paid,
expiry,
},

View File

@@ -1,11 +1,13 @@
pub mod amount;
pub mod bolt11_invoice;
pub mod keyset_info;
pub mod mint_quote_info;
pub mod melt_quote;
pub mod mint_quote;
pub mod secret;
pub use amount::Amount;
pub use bolt11_invoice::Bolt11Invoice;
pub use keyset_info::KeySetInfo;
pub use mint_quote_info::MintQuoteInfo;
pub use melt_quote::MeltQuote;
pub use mint_quote::MintQuote;
pub use secret::Secret;

View File

@@ -34,8 +34,13 @@ interface Secret {
sequence<u8> as_bytes();
};
interface MintQuoteInfo {
constructor(string id, Amount amount, string unit, Bolt11Invoice? request, boolean paid, u64 boolean);
interface MintQuote {
constructor(string id, Amount amount, string unit, Bolt11Invoice request, boolean paid, u64 boolean);
};
interface MeltQuote {
constructor(string id, Amount amount, string unit, Bolt11Invoice request, Amount fee_reserve, boolean paid, u64 boolean);
};
// NUT00
@@ -299,6 +304,7 @@ interface Melted {
};
interface Wallet {
constructor(string mint_url, Keys mint_keys, sequence<MintQuote> mint_quotes, sequence<MeltQuote> melt_quotes);
// [Throws=CashuSdkError]
// ProofsStatus check_proofs_spent(sequence<Proof> proofs);
[Throws=CashuSdkError]
@@ -312,7 +318,7 @@ interface Wallet {
[Throws=CashuSdkError]
SendProofs send(Amount amount, sequence<Proof> proofs);
[Throws=CashuSdkError]
Melted melt(string quote, sequence<Proof> proofs, Amount fee_reserve);
Melted melt(string quote_id, sequence<Proof> proofs);
[Throws=CashuSdkError]
string proofs_to_token(sequence<Proof> proof, CurrencyUnit? unit, string? memo);
};

View File

@@ -7,12 +7,11 @@ mod ffi {
pub use cashu_ffi::{
Amount, BlindedMessage, BlindedSignature, Bolt11Invoice, CashuError, CheckSpendableRequest,
CheckSpendableResponse, CurrencyUnit, Id, InvoiceStatus, KeyPair, KeySet, KeySetInfo,
KeySetResponse, Keys, KeysResponse, MeltBolt11Request, MeltBolt11Response,
KeySetResponse, Keys, KeysResponse, MeltBolt11Request, MeltBolt11Response, MeltQuote,
MeltQuoteBolt11Request, MeltQuoteBolt11Response, MintBolt11Request, MintBolt11Response,
MintInfo, MintKeySet, MintProof, MintProofs, MintQuoteBolt11Request,
MintQuoteBolt11Response, MintQuoteInfo, MintVersion, Nut05MeltBolt11Request,
Nut05MeltBolt11Response, PreMintSecrets, Proof, PublicKey, Secret, SecretKey, SwapRequest,
SwapResponse, Token,
MintInfo, MintKeySet, MintProof, MintProofs, MintQuote, MintQuoteBolt11Request,
MintQuoteBolt11Response, MintVersion, Nut05MeltBolt11Request, Nut05MeltBolt11Response,
PreMintSecrets, Proof, PublicKey, Secret, SecretKey, SwapRequest, SwapResponse, Token,
};
pub use crate::error::CashuSdkError;

View File

@@ -1,7 +1,9 @@
use std::ops::Deref;
use std::sync::{Arc, RwLock};
use cashu_ffi::{BlindedSignature, CurrencyUnit, MintQuoteInfo, PreMintSecrets, Proof, Token};
use cashu_ffi::{
BlindedSignature, CurrencyUnit, MeltQuote, MintQuote, PreMintSecrets, Proof, Token,
};
use cashu_sdk::client::minreq_client::HttpClient;
use cashu_sdk::types::ProofsStatus;
use cashu_sdk::url::UncheckedUrl;
@@ -20,13 +22,22 @@ pub struct Wallet {
}
impl Wallet {
pub fn new(mint_url: &str, mint_keys: Arc<Keys>, quotes: Vec<Arc<MintQuoteInfo>>) -> Self {
pub fn new(
mint_url: String,
mint_keys: Arc<Keys>,
mint_quotes: Vec<Arc<MintQuote>>,
melt_quotes: Vec<Arc<MeltQuote>>,
) -> Self {
let client = HttpClient {};
Self {
inner: WalletSdk::new(
client,
UncheckedUrl::new(mint_url),
quotes
mint_quotes
.into_iter()
.map(|q| q.as_ref().deref().clone())
.collect(),
melt_quotes
.into_iter()
.map(|q| q.as_ref().deref().clone())
.collect(),
@@ -111,20 +122,14 @@ impl Wallet {
Ok(Arc::new(send_proofs.into()))
}
pub fn melt(
&self,
quote: String,
proofs: Vec<Arc<Proof>>,
fee_reserve: Arc<Amount>,
) -> Result<Arc<Melted>> {
pub fn melt(&self, quote_id: String, proofs: Vec<Arc<Proof>>) -> Result<Arc<Melted>> {
let melted = RUNTIME.block_on(async {
self.inner
.write()
.unwrap()
.melt(
quote,
&quote_id,
proofs.iter().map(|p| p.as_ref().deref().clone()).collect(),
*fee_reserve.as_ref().deref(),
)
.await
})?;

View File

@@ -40,7 +40,13 @@ impl JsWallet {
let client = HttpClient {};
JsWallet {
inner: Wallet::new(client, mint_url.into(), vec![], mint_keys.deref().clone()),
inner: Wallet::new(
client,
mint_url.into(),
vec![],
vec![],
mint_keys.deref().clone(),
),
}
}
@@ -123,17 +129,12 @@ impl JsWallet {
/// Melt
#[wasm_bindgen(js_name = melt)]
pub async fn melt(
&self,
quote: String,
proofs: JsValue,
fee_reserve: JsAmount,
) -> Result<JsMelted> {
pub async fn melt(&self, quote: String, proofs: JsValue) -> Result<JsMelted> {
let proofs = serde_wasm_bindgen::from_value(proofs).map_err(into_err)?;
Ok(self
.inner
.melt(quote, proofs, *fee_reserve.deref())
.melt(&quote, proofs)
.await
.map_err(into_err)?
.into())

View File

@@ -7,7 +7,7 @@ use cashu::nuts::{
};
#[cfg(feature = "nut07")]
use cashu::nuts::{CheckSpendableRequest, CheckSpendableResponse};
use cashu::Amount;
use cashu::{Amount, Bolt11Invoice};
use gloo::net::http::Request;
use serde_json::Value;
use url::Url;
@@ -118,6 +118,34 @@ impl Client for HttpClient {
}
/// Melt [NUT-05]
async fn post_melt_quote(
&self,
mint_url: Url,
unit: CurrencyUnit,
request: Bolt11Invoice,
) -> Result<MeltQuoteBolt11Response, Error> {
let url = join_url(mint_url, &["v1", "melt", "quote", "bolt11"])?;
let request = MeltQuoteBolt11Request { unit, request };
let res = Request::post(url.as_str())
.json(&request)
.map_err(|err| Error::Gloo(err.to_string()))?
.send()
.await
.map_err(|err| Error::Gloo(err.to_string()))?
.json::<Value>()
.await
.map_err(|err| Error::Gloo(err.to_string()))?;
let response: Result<MeltQuoteBolt11Response, serde_json::Error> =
serde_json::from_value(res.clone());
match response {
Ok(res) => Ok(res),
Err(_) => Err(Error::from_json(&res.to_string())?),
}
}
/// [Nut-08] Lightning fee return if outputs defined
async fn post_melt(
&self,

View File

@@ -5,12 +5,13 @@ use std::println;
use async_trait::async_trait;
use cashu::nuts::{
nut00, BlindedMessage, CurrencyUnit, Keys, KeysResponse, KeysetResponse, MeltBolt11Request,
MeltBolt11Response, MintBolt11Request, MintBolt11Response, MintInfo, MintQuoteBolt11Request,
MintQuoteBolt11Response, PreMintSecrets, Proof, SwapRequest, SwapResponse,
MeltBolt11Response, MeltQuoteBolt11Request, MeltQuoteBolt11Response, MintBolt11Request,
MintBolt11Response, MintInfo, MintQuoteBolt11Request, MintQuoteBolt11Response, PreMintSecrets,
Proof, SwapRequest, SwapResponse,
};
#[cfg(feature = "nut07")]
use cashu::nuts::{CheckSpendableRequest, CheckSpendableResponse};
use cashu::Amount;
use cashu::{Amount, Bolt11Invoice};
use serde_json::Value;
use tracing::warn;
use url::Url;
@@ -103,6 +104,31 @@ impl Client for HttpClient {
}
}
/// Melt Quote [NUT-05]
async fn post_melt_quote(
&self,
mint_url: Url,
unit: CurrencyUnit,
request: Bolt11Invoice,
) -> Result<MeltQuoteBolt11Response, Error> {
let url = join_url(mint_url, &["v1", "melt", "quote", "bolt11"])?;
let request = MeltQuoteBolt11Request { request, unit };
let value = minreq::post(url)
.with_json(&request)?
.send()?
.json::<Value>()?;
let response: Result<MeltQuoteBolt11Response, 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
async fn post_melt(

View File

@@ -6,8 +6,9 @@ use cashu::nuts::nut00;
#[cfg(feature = "nut07")]
use cashu::nuts::CheckSpendableResponse;
use cashu::nuts::{
BlindedMessage, CurrencyUnit, Keys, KeysetResponse, MeltBolt11Response, MintBolt11Response,
MintInfo, MintQuoteBolt11Response, PreMintSecrets, Proof, SwapRequest, SwapResponse,
BlindedMessage, CurrencyUnit, Keys, KeysetResponse, MeltBolt11Response,
MeltQuoteBolt11Response, MintBolt11Response, MintInfo, MintQuoteBolt11Response, PreMintSecrets,
Proof, SwapRequest, SwapResponse,
};
use cashu::{utils, Amount};
use serde::{Deserialize, Serialize};
@@ -101,6 +102,13 @@ pub trait Client {
premint_secrets: PreMintSecrets,
) -> Result<MintBolt11Response, Error>;
async fn post_melt_quote(
&self,
mint_url: Url,
unit: CurrencyUnit,
request: Bolt11Invoice,
) -> Result<MeltQuoteBolt11Response, Error>;
async fn post_melt(
&self,
mint_url: Url,

View File

@@ -13,7 +13,7 @@ use cashu::Amount;
use serde::{Deserialize, Serialize};
use tracing::{debug, info};
use crate::types::Quote;
use crate::types::MeltQuote;
pub struct Mint {
// pub pubkey: PublicKey
@@ -23,7 +23,7 @@ pub struct Mint {
pub spent_secrets: HashSet<Secret>,
pub pending_secrets: HashSet<Secret>,
pub fee_reserve: FeeReserve,
pub quotes: HashMap<String, Quote>,
pub melt_quotes: HashMap<String, MeltQuote>,
}
impl Mint {
@@ -31,7 +31,7 @@ impl Mint {
secret: &str,
keysets_info: HashSet<MintKeySetInfo>,
spent_secrets: HashSet<Secret>,
quotes: Vec<Quote>,
melt_quotes: Vec<MeltQuote>,
min_fee_reserve: Amount,
percent_fee_reserve: f32,
) -> Self {
@@ -40,7 +40,7 @@ impl Mint {
let mut active_units: HashSet<String> = HashSet::default();
let quotes = quotes.into_iter().map(|q| (q.id.clone(), q)).collect();
let melt_quotes = melt_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 {
@@ -64,7 +64,7 @@ impl Mint {
Self {
_secret: secret.to_string(),
keysets,
quotes,
melt_quotes,
keysets_info: info,
spent_secrets,
pending_secrets: HashSet::new(),
@@ -235,14 +235,14 @@ impl Mint {
}
pub fn verify_melt_request(&mut self, melt_request: &MeltBolt11Request) -> Result<(), Error> {
let quote = self.quotes.get(&melt_request.quote).unwrap();
let quote = self.melt_quotes.get(&melt_request.quote).unwrap();
let proofs_total = melt_request.proofs_amount();
let required_total = quote.amount + quote.fee_reserve;
if proofs_total < required_total.into() {
debug!(
"Insufficient Proofs: Got: {:?}, Required: {}",
"Insufficient Proofs: Got: {}, Required: {}",
proofs_total, required_total
);
return Err(Error::Amount);

View File

@@ -11,7 +11,7 @@ use cashu::nuts::{
};
#[cfg(feature = "nut07")]
use cashu::types::ProofsStatus;
use cashu::types::{Melted, MintQuoteInfo, SendProofs};
use cashu::types::{MeltQuote, Melted, MintQuote, SendProofs};
use cashu::url::UncheckedUrl;
use cashu::Amount;
pub use cashu::Bolt11Invoice;
@@ -45,7 +45,8 @@ pub enum Error {
pub struct Wallet<C: Client> {
pub client: C,
pub mint_url: UncheckedUrl,
pub quotes: HashMap<String, MintQuoteInfo>,
pub mint_quotes: HashMap<String, MintQuote>,
pub melt_quotes: HashMap<String, MeltQuote>,
pub mint_keys: Keys,
pub balance: Amount,
}
@@ -54,14 +55,16 @@ impl<C: Client> Wallet<C> {
pub fn new(
client: C,
mint_url: UncheckedUrl,
quotes: Vec<MintQuoteInfo>,
mint_quotes: Vec<MintQuote>,
melt_quotes: Vec<MeltQuote>,
mint_keys: Keys,
) -> Self {
Self {
client,
mint_url,
mint_keys,
quotes: quotes.into_iter().map(|q| (q.id.clone(), q)).collect(),
mint_quotes: mint_quotes.into_iter().map(|q| (q.id.clone(), q)).collect(),
melt_quotes: melt_quotes.into_iter().map(|q| (q.id.clone(), q)).collect(),
balance: Amount::ZERO,
}
}
@@ -120,29 +123,30 @@ impl<C: Client> Wallet<C> {
&mut self,
amount: Amount,
unit: CurrencyUnit,
) -> Result<MintQuoteInfo, Error> {
) -> Result<MintQuote, Error> {
let quote_res = self
.client
.post_mint_quote(self.mint_url.clone().try_into()?, amount, unit.clone())
.await?;
let quote = MintQuoteInfo {
let quote = MintQuote {
id: quote_res.quote.clone(),
amount,
unit: unit.clone(),
request: Some(Bolt11Invoice::from_str(&quote_res.request).unwrap()),
request: Bolt11Invoice::from_str(&quote_res.request).unwrap(),
paid: quote_res.paid,
expiry: quote_res.expiry,
};
self.quotes.insert(quote_res.quote.clone(), quote.clone());
self.mint_quotes
.insert(quote_res.quote.clone(), quote.clone());
Ok(quote)
}
/// Mint
pub async fn mint(&mut self, quote: &str) -> Result<Proofs, Error> {
let quote_info = self.quotes.get(quote);
pub async fn mint(&mut self, quote_id: &str) -> Result<Proofs, Error> {
let quote_info = self.mint_quotes.get(quote_id);
let quote_info = if let Some(quote) = quote_info {
if quote.expiry.le(&unix_time()) {
@@ -160,7 +164,7 @@ impl<C: Client> Wallet<C> {
.client
.post_mint(
self.mint_url.clone().try_into()?,
quote,
quote_id,
premint_secrets.clone(),
)
.await?;
@@ -172,7 +176,7 @@ impl<C: Client> Wallet<C> {
&self.mint_keys,
)?;
self.quotes.remove(&quote_info.id);
self.mint_quotes.remove(&quote_info.id);
Ok(proofs)
}
@@ -329,19 +333,57 @@ impl<C: Client> Wallet<C> {
})
}
/// Melt Quote
pub async fn melt_quote(
&mut self,
unit: CurrencyUnit,
request: Bolt11Invoice,
) -> Result<MeltQuote, Error> {
let quote_res = self
.client
.post_melt_quote(
self.mint_url.clone().try_into()?,
unit.clone(),
request.clone(),
)
.await?;
let quote = MeltQuote {
id: quote_res.quote,
amount: quote_res.amount.into(),
request,
unit,
fee_reserve: quote_res.fee_reserve.into(),
paid: quote_res.paid,
expiry: quote_res.expiry,
};
self.melt_quotes.insert(quote.id.clone(), quote.clone());
Ok(quote)
}
/// Melt
pub async fn melt(
&self,
quote: String,
proofs: Proofs,
fee_reserve: Amount,
) -> Result<Melted, Error> {
let blinded = PreMintSecrets::blank((&self.mint_keys).into(), fee_reserve)?;
pub async fn melt(&self, quote_id: &str, proofs: Proofs) -> Result<Melted, Error> {
let quote_info = self.melt_quotes.get(quote_id);
let quote_info = if let Some(quote) = quote_info {
if quote.expiry.le(&unix_time()) {
return Err(Error::QuoteExpired);
}
quote.clone()
} else {
return Err(Error::QuoteUnknown);
};
let blinded = PreMintSecrets::blank((&self.mint_keys).into(), quote_info.fee_reserve)?;
let melt_response = self
.client
.post_melt(
self.mint_url.clone().try_into()?,
quote,
quote_id.to_string(),
proofs,
Some(blinded.blinded_messages()),
)

View File

@@ -1,3 +1,5 @@
use std::fmt;
// https://github.com/clarkmoody/cashu-rs
use serde::{Deserialize, Serialize};
@@ -62,6 +64,12 @@ impl std::ops::Sub for Amount {
}
}
impl fmt::Display for Amount {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl core::iter::Sum for Amount {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
let sats: u64 = iter.map(|amt| amt.0).sum();

View File

@@ -36,23 +36,23 @@ pub enum InvoiceStatus {
/// Mint Quote Info
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct MintQuoteInfo {
pub struct MintQuote {
pub id: String,
pub amount: Amount,
pub unit: CurrencyUnit,
pub request: Option<Bolt11Invoice>,
pub request: Bolt11Invoice,
pub paid: bool,
pub expiry: u64,
}
/// Quote
/// Melt Quote Info
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Quote {
pub struct MeltQuote {
pub id: String,
pub amount: u64,
pub amount: Amount,
pub request: Bolt11Invoice,
pub unit: CurrencyUnit,
pub fee_reserve: u64,
pub fee_reserve: Amount,
pub paid: bool,
pub expiry: u64,
}