Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 106 additions & 13 deletions crates/cashu/src/nuts/nut17/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ impl SupportedMethods {
/// Create [`SupportedMethods`] for Bolt11 with all supported commands
pub fn default_bolt11(unit: CurrencyUnit) -> Self {
let commands = vec![
WsCommand::Bolt11MintQuote,
WsCommand::Bolt11MeltQuote,
WsCommand::MintQuote,
WsCommand::MeltQuote,
WsCommand::ProofState,
];

Expand All @@ -75,8 +75,8 @@ impl SupportedMethods {
/// Create [`SupportedMethods`] for Bolt12 with all supported commands
pub fn default_bolt12(unit: CurrencyUnit) -> Self {
let commands = vec![
WsCommand::Bolt12MintQuote,
WsCommand::Bolt12MeltQuote,
WsCommand::MintQuote,
WsCommand::MeltQuote,
WsCommand::ProofState,
];

Expand All @@ -89,10 +89,9 @@ impl SupportedMethods {

/// Create [`SupportedMethods`] for custom payment method with all supported commands
pub fn default_custom(method: PaymentMethod, unit: CurrencyUnit) -> Self {
let method_name = method.to_string();
let commands = vec![
WsCommand::Custom(format!("{}_mint_quote", method_name)),
WsCommand::Custom(format!("{}_melt_quote", method_name)),
WsCommand::MintQuote,
WsCommand::MeltQuote,
WsCommand::ProofState,
];

Expand All @@ -105,27 +104,43 @@ impl SupportedMethods {
}

impl WsCommand {
/// Create a custom mint quote command for a payment method
pub fn custom_mint_quote(method: &str) -> Self {
WsCommand::Custom(format!("{}_mint_quote", method))
/// Create the generic mint quote command for any payment method
pub fn custom_mint_quote(_method: &str) -> Self {
Self::MintQuote
}

/// Create a custom melt quote command for a payment method
pub fn custom_melt_quote(method: &str) -> Self {
WsCommand::Custom(format!("{}_melt_quote", method))
/// Create the generic melt quote command for any payment method
pub fn custom_melt_quote(_method: &str) -> Self {
Self::MeltQuote
}
}

/// WebSocket commands supported by the Cashu mint
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
pub enum WsCommand {
/// Command to subscribe to mint quote updates
MintQuote,
/// Command to subscribe to melt quote updates
MeltQuote,
#[deprecated(
note = "Temporary backwards compatibility for legacy NUT-17 websocket commands. TODO: remove once old quote command strings are dropped."
)]
/// Command to request a Lightning invoice for minting tokens
Bolt11MintQuote,
#[deprecated(
note = "Temporary backwards compatibility for legacy NUT-17 websocket commands. TODO: remove once old quote command strings are dropped."
)]
/// Command to request a Lightning payment for melting tokens
Bolt11MeltQuote,
#[deprecated(
note = "Temporary backwards compatibility for legacy NUT-17 websocket commands. TODO: remove once old quote command strings are dropped."
)]
/// Websocket support for Bolt12 Mint Quote
Bolt12MintQuote,
#[deprecated(
note = "Temporary backwards compatibility for legacy NUT-17 websocket commands. TODO: remove once old quote command strings are dropped."
)]
/// Websocket support for Bolt12 Melt Quote
Bolt12MeltQuote,
/// Command to check the state of a proof
Expand All @@ -135,11 +150,14 @@ pub enum WsCommand {
}

impl Serialize for WsCommand {
#[allow(deprecated)]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let s = match self {
WsCommand::MintQuote => "mint_quote",
WsCommand::MeltQuote => "melt_quote",
WsCommand::Bolt11MintQuote => "bolt11_mint_quote",
WsCommand::Bolt11MeltQuote => "bolt11_melt_quote",
WsCommand::Bolt12MintQuote => "bolt12_mint_quote",
Expand All @@ -152,12 +170,15 @@ impl Serialize for WsCommand {
}

impl<'de> Deserialize<'de> for WsCommand {
#[allow(deprecated)]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(match s.as_str() {
"mint_quote" => WsCommand::MintQuote,
"melt_quote" => WsCommand::MeltQuote,
"bolt11_mint_quote" => WsCommand::Bolt11MintQuote,
"bolt11_melt_quote" => WsCommand::Bolt11MeltQuote,
"bolt12_mint_quote" => WsCommand::Bolt12MintQuote,
Expand Down Expand Up @@ -219,6 +240,10 @@ where
{
/// ProofState id is a Pubkey
ProofState(PublicKey),
/// MintQuote id is a QuoteId, regardless of payment method
MintQuote(T),
/// MeltQuote id is a QuoteId, regardless of payment method
MeltQuote(T),
/// MeltQuote id is an QuoteId
MeltQuoteBolt11(T),
/// MintQuote id is an QuoteId
Expand All @@ -236,26 +261,45 @@ where
/// Kind
#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq, Hash)]
pub enum Kind {
/// Generic mint quote subscription kind
MintQuote,
/// Generic melt quote subscription kind
MeltQuote,
#[deprecated(
note = "Temporary backwards compatibility for legacy NUT-17 websocket kinds. TODO: remove once old quote kind strings are dropped."
)]
/// Bolt 11 Melt Quote
Bolt11MeltQuote,
#[deprecated(
note = "Temporary backwards compatibility for legacy NUT-17 websocket kinds. TODO: remove once old quote kind strings are dropped."
)]
/// Bolt 11 Mint Quote
Bolt11MintQuote,
/// Proof State
ProofState,
#[deprecated(
note = "Temporary backwards compatibility for legacy NUT-17 websocket kinds. TODO: remove once old quote kind strings are dropped."
)]
/// Bolt 12 Mint Quote
Bolt12MintQuote,
#[deprecated(
note = "Temporary backwards compatibility for legacy NUT-17 websocket kinds. TODO: remove once old quote kind strings are dropped."
)]
/// Bolt 12 Melt Quote
Bolt12MeltQuote,
/// Custom
Custom(String),
}

impl Serialize for Kind {
#[allow(deprecated)]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let s = match self {
Kind::MintQuote => "mint_quote",
Kind::MeltQuote => "melt_quote",
Kind::Bolt11MintQuote => "bolt11_mint_quote",
Kind::Bolt11MeltQuote => "bolt11_melt_quote",
Kind::Bolt12MintQuote => "bolt12_mint_quote",
Expand All @@ -268,12 +312,15 @@ impl Serialize for Kind {
}

impl<'de> Deserialize<'de> for Kind {
#[allow(deprecated)]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(match s.as_str() {
"mint_quote" => Kind::MintQuote,
"melt_quote" => Kind::MeltQuote,
"bolt11_mint_quote" => Kind::Bolt11MintQuote,
"bolt11_melt_quote" => Kind::Bolt11MeltQuote,
"bolt12_mint_quote" => Kind::Bolt12MintQuote,
Expand All @@ -284,6 +331,52 @@ impl<'de> Deserialize<'de> for Kind {
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn serialize_generic_ws_commands() {
assert_eq!(
serde_json::to_string(&WsCommand::MintQuote).unwrap(),
"\"mint_quote\""
);
assert_eq!(
serde_json::to_string(&WsCommand::MeltQuote).unwrap(),
"\"melt_quote\""
);
assert_eq!(
serde_json::to_string(&Kind::MintQuote).unwrap(),
"\"mint_quote\""
);
assert_eq!(
serde_json::to_string(&Kind::MeltQuote).unwrap(),
"\"melt_quote\""
);
}

#[test]
#[allow(deprecated)]
fn deserialize_legacy_quote_kinds_for_compatibility() {
assert_eq!(
serde_json::from_str::<WsCommand>("\"bolt11_mint_quote\"").unwrap(),
WsCommand::Bolt11MintQuote
);
assert_eq!(
serde_json::from_str::<WsCommand>("\"bolt12_melt_quote\"").unwrap(),
WsCommand::Bolt12MeltQuote
);
assert_eq!(
serde_json::from_str::<Kind>("\"bolt11_mint_quote\"").unwrap(),
Kind::Bolt11MintQuote
);
assert_eq!(
serde_json::from_str::<Kind>("\"bolt12_melt_quote\"").unwrap(),
Kind::Bolt12MeltQuote
);
}
}

impl<I> AsRef<I> for Params<I> {
fn as_ref(&self) -> &I {
&self.id
Expand Down
2 changes: 1 addition & 1 deletion crates/cdk-axum/src/ws/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ mod tests {
// the channel closes immediately and the ActiveSubscription is dropped
// before the test can observe the active_subscribers count.
Params {
kind: cdk::nuts::nut17::Kind::Bolt11MintQuote,
kind: cdk::nuts::nut17::Kind::MintQuote,
filters: vec![QuoteId::new_uuid().to_string()],
id: Arc::new(SubId::from(sub_id)),
}
Expand Down
14 changes: 14 additions & 0 deletions crates/cdk-common/src/subscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,17 @@ impl SubscriptionRequest for Params {
self.id.clone()
}

#[allow(deprecated)]
fn try_get_topics(&self) -> Result<Vec<Self::Topic>, Error> {
self.filters
.iter()
.map(|filter| match self.kind {
Kind::MintQuote => QuoteId::from_str(filter)
.map(NotificationId::MintQuote)
.map_err(|_| Error::ParsingError(filter.to_owned())),
Kind::MeltQuote => QuoteId::from_str(filter)
.map(NotificationId::MeltQuote)
.map_err(|_| Error::ParsingError(filter.to_owned())),
Kind::Bolt11MeltQuote => QuoteId::from_str(filter)
.map(NotificationId::MeltQuoteBolt11)
.map_err(|_| Error::ParsingError(filter.to_owned())),
Expand All @@ -45,6 +52,8 @@ impl SubscriptionRequest for Params {
.map(NotificationId::MeltQuoteBolt12)
.map_err(|_| Error::ParsingError(filter.to_owned())),
Kind::Custom(ref s) => {
// TODO: Remove this legacy custom-kind compatibility once old
// websocket quote kind strings are no longer accepted by mints.
if let Some(method) = s.strip_suffix("_mint_quote") {
QuoteId::from_str(filter)
.map(|id| NotificationId::MintQuoteCustom(method.to_string(), id))
Expand Down Expand Up @@ -77,11 +86,14 @@ impl SubscriptionRequest for WalletParams {
self.id.clone()
}

#[allow(deprecated)]
fn try_get_topics(&self) -> Result<Vec<Self::Topic>, Error> {
self.filters
.iter()
.map(|filter| {
Ok(match self.kind {
Kind::MintQuote => NotificationId::MintQuote(filter.to_owned()),
Kind::MeltQuote => NotificationId::MeltQuote(filter.to_owned()),
Kind::Bolt11MeltQuote => NotificationId::MeltQuoteBolt11(filter.to_owned()),
Kind::Bolt11MintQuote => NotificationId::MintQuoteBolt11(filter.to_owned()),
Kind::ProofState => PublicKey::from_str(filter)
Expand All @@ -91,6 +103,8 @@ impl SubscriptionRequest for WalletParams {
Kind::Bolt12MintQuote => NotificationId::MintQuoteBolt12(filter.to_owned()),
Kind::Bolt12MeltQuote => NotificationId::MeltQuoteBolt12(filter.to_owned()),
Kind::Custom(ref s) => {
// TODO: Remove this legacy custom-kind compatibility once old
// websocket quote kind strings are no longer accepted by wallets.
if let Some(method) = s.strip_suffix("_mint_quote") {
NotificationId::MintQuoteCustom(method.to_string(), filter.to_owned())
} else if let Some(method) = s.strip_suffix("_melt_quote") {
Expand Down
30 changes: 26 additions & 4 deletions crates/cdk-ffi/src/types/subscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,55 @@ use crate::error::FfiError;
/// FFI-compatible SubscriptionKind
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
pub enum SubscriptionKind {
/// Generic mint quote subscription
MintQuote,
/// Generic melt quote subscription
MeltQuote,
#[deprecated(
note = "Temporary backwards compatibility for legacy NUT-17 FFI subscription kinds. TODO: remove once downstream clients migrate to generic kinds."
)]
/// Bolt 11 Melt Quote
Bolt11MeltQuote,
#[deprecated(
note = "Temporary backwards compatibility for legacy NUT-17 FFI subscription kinds. TODO: remove once downstream clients migrate to generic kinds."
)]
/// Bolt 11 Mint Quote
Bolt11MintQuote,
#[deprecated(
note = "Temporary backwards compatibility for legacy NUT-17 FFI subscription kinds. TODO: remove once downstream clients migrate to generic kinds."
)]
/// Bolt 12 Mint Quote
Bolt12MintQuote,
#[deprecated(
note = "Temporary backwards compatibility for legacy NUT-17 FFI subscription kinds. TODO: remove once downstream clients migrate to generic kinds."
)]
/// Bolt 12 Melt Quote
Bolt12MeltQuote,
/// Proof State
ProofState,
}

impl From<SubscriptionKind> for cdk::nuts::nut17::Kind {
#[allow(deprecated)]
fn from(kind: SubscriptionKind) -> Self {
match kind {
SubscriptionKind::Bolt11MeltQuote => cdk::nuts::nut17::Kind::Bolt11MeltQuote,
SubscriptionKind::Bolt11MintQuote => cdk::nuts::nut17::Kind::Bolt11MintQuote,
SubscriptionKind::Bolt12MintQuote => cdk::nuts::nut17::Kind::Bolt12MintQuote,
SubscriptionKind::Bolt12MeltQuote => cdk::nuts::nut17::Kind::Bolt12MeltQuote,
SubscriptionKind::MintQuote => cdk::nuts::nut17::Kind::MintQuote,
SubscriptionKind::MeltQuote => cdk::nuts::nut17::Kind::MeltQuote,
SubscriptionKind::Bolt11MeltQuote => cdk::nuts::nut17::Kind::MeltQuote,
SubscriptionKind::Bolt11MintQuote => cdk::nuts::nut17::Kind::MintQuote,
SubscriptionKind::Bolt12MintQuote => cdk::nuts::nut17::Kind::MintQuote,
SubscriptionKind::Bolt12MeltQuote => cdk::nuts::nut17::Kind::MeltQuote,
SubscriptionKind::ProofState => cdk::nuts::nut17::Kind::ProofState,
}
}
}

impl From<cdk::nuts::nut17::Kind> for SubscriptionKind {
#[allow(deprecated)]
fn from(kind: cdk::nuts::nut17::Kind) -> Self {
match kind {
cdk::nuts::nut17::Kind::MintQuote => SubscriptionKind::MintQuote,
cdk::nuts::nut17::Kind::MeltQuote => SubscriptionKind::MeltQuote,
cdk::nuts::nut17::Kind::Bolt11MeltQuote => SubscriptionKind::Bolt11MeltQuote,
cdk::nuts::nut17::Kind::Bolt11MintQuote => SubscriptionKind::Bolt11MintQuote,
cdk::nuts::nut17::Kind::Bolt12MintQuote => SubscriptionKind::Bolt12MintQuote,
Expand Down
Loading
Loading