Browse Source

Refactor: Introduce `cdk-common` internal crate

- Created a new internal crate, `cdk-common`, to centralize shared types and
  trait definitions.
- The goal is to enable CDK extensions without requiring CDK as a direct
  dependency.
- However, this approach currently introduces circular dependencies.

- Refactor NUT types and traits (e.g., databases and other layers) to reside in
  `cdk-common` instead of the main `cdk` crate, resolving the circular dependency issue.
Cesar Rodas 3 months ago
parent
commit
b16a8da8d6
53 changed files with 836 additions and 261 deletions
  1. 27 4
      Cargo.lock
  2. 2 1
      crates/cdk-axum/src/ws/mod.rs
  3. BIN
      crates/cdk-common/.DS_Store
  4. 37 0
      crates/cdk-common/Cargo.toml
  5. BIN
      crates/cdk-common/src/.DS_Store
  6. 470 0
      crates/cdk-common/src/amount.rs
  7. 0 0
      crates/cdk-common/src/dhke.rs
  8. 25 0
      crates/cdk-common/src/lib.rs
  9. 3 4
      crates/cdk-common/src/mint.rs
  10. 0 0
      crates/cdk-common/src/mint_url.rs
  11. 3 9
      crates/cdk-common/src/nuts/mod.rs
  12. 0 1
      crates/cdk-common/src/nuts/nut00/mod.rs
  13. 6 8
      crates/cdk-common/src/nuts/nut00/token.rs
  14. 0 0
      crates/cdk-common/src/nuts/nut01/mod.rs
  15. 0 0
      crates/cdk-common/src/nuts/nut01/public_key.rs
  16. 0 0
      crates/cdk-common/src/nuts/nut01/secret_key.rs
  17. 2 16
      crates/cdk-common/src/nuts/nut02.rs
  18. 0 0
      crates/cdk-common/src/nuts/nut03.rs
  19. 0 4
      crates/cdk-common/src/nuts/nut04.rs
  20. 0 6
      crates/cdk-common/src/nuts/nut05.rs
  21. 0 3
      crates/cdk-common/src/nuts/nut06.rs
  22. 0 0
      crates/cdk-common/src/nuts/nut07.rs
  23. 0 0
      crates/cdk-common/src/nuts/nut08.rs
  24. 0 0
      crates/cdk-common/src/nuts/nut09.rs
  25. 0 0
      crates/cdk-common/src/nuts/nut10.rs
  26. 4 10
      crates/cdk-common/src/nuts/nut11/mod.rs
  27. 0 0
      crates/cdk-common/src/nuts/nut11/serde_p2pk_witness.rs
  28. 0 0
      crates/cdk-common/src/nuts/nut12.rs
  29. 0 0
      crates/cdk-common/src/nuts/nut13.rs
  30. 0 0
      crates/cdk-common/src/nuts/nut14/mod.rs
  31. 0 0
      crates/cdk-common/src/nuts/nut14/serde_htlc_witness.rs
  32. 0 0
      crates/cdk-common/src/nuts/nut15.rs
  33. 45 54
      crates/cdk-common/src/nuts/nut17/mod.rs
  34. 2 1
      crates/cdk-common/src/nuts/nut17/ws.rs
  35. 9 11
      crates/cdk-common/src/nuts/nut18.rs
  36. 0 0
      crates/cdk-common/src/nuts/nut19.rs
  37. 0 0
      crates/cdk-common/src/nuts/nut20.rs
  38. 0 0
      crates/cdk-common/src/pub_sub/index.rs
  39. 77 0
      crates/cdk-common/src/pub_sub/mod.rs
  40. 0 0
      crates/cdk-common/src/secret.rs
  41. 53 0
      crates/cdk-common/src/signatory.rs
  42. 0 23
      crates/cdk-common/src/util/hex.rs
  43. 13 6
      crates/cdk-common/src/util/mod.rs
  44. 5 1
      crates/cdk/Cargo.toml
  45. 1 1
      crates/cdk/src/cdk_database/mint_memory.rs
  46. 6 10
      crates/cdk/src/lib.rs
  47. 20 22
      crates/cdk/src/mint/builder.rs
  48. 1 1
      crates/cdk/src/mint/melt.rs
  49. 4 2
      crates/cdk/src/mint/mod.rs
  50. 3 1
      crates/cdk/src/mint/subscription/manager.rs
  51. 12 0
      crates/cdk/src/mint/subscription/mod.rs
  52. 3 2
      crates/cdk/src/mint/subscription/on_subscription.rs
  53. 3 60
      crates/cdk/src/pub_sub/mod.rs

+ 27 - 4
Cargo.lock

@@ -738,6 +738,7 @@ dependencies = [
  "bip39",
  "bitcoin 0.32.5",
  "cbor-diag",
+ "cdk-common",
  "ciborium",
  "criterion",
  "futures",
@@ -822,6 +823,28 @@ dependencies = [
 ]
 
 [[package]]
+name = "cdk-common"
+version = "0.6.0"
+dependencies = [
+ "async-trait",
+ "bip39",
+ "bitcoin 0.32.5",
+ "cbor-diag",
+ "ciborium",
+ "lightning-invoice",
+ "once_cell",
+ "rand",
+ "serde",
+ "serde_json",
+ "serde_with",
+ "thiserror 2.0.9",
+ "tracing",
+ "url",
+ "utoipa",
+ "uuid",
+]
+
+[[package]]
 name = "cdk-fake-wallet"
 version = "0.6.0"
 dependencies = [
@@ -4065,9 +4088,9 @@ dependencies = [
 
 [[package]]
 name = "serde_with"
-version = "3.11.0"
+version = "3.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817"
+checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa"
 dependencies = [
  "base64 0.22.1",
  "chrono",
@@ -4083,9 +4106,9 @@ dependencies = [
 
 [[package]]
 name = "serde_with_macros"
-version = "3.11.0"
+version = "3.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d"
+checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e"
 dependencies = [
  "darling",
  "proc-macro2",

+ 2 - 1
crates/cdk-axum/src/ws/mod.rs

@@ -4,7 +4,8 @@ use axum::extract::ws::{Message, WebSocket};
 use cdk::nuts::nut17::ws::{
     NotificationInner, WsErrorBody, WsMessageOrResponse, WsMethodRequest, WsRequest,
 };
-use cdk::nuts::nut17::{NotificationPayload, SubId};
+use cdk::nuts::nut17::NotificationPayload;
+use cdk::pub_sub::SubId;
 use futures::StreamExt;
 use tokio::sync::mpsc;
 use uuid::Uuid;

BIN
crates/cdk-common/.DS_Store


+ 37 - 0
crates/cdk-common/Cargo.toml

@@ -0,0 +1,37 @@
+[package]
+name = "cdk-common"
+version = "0.6.0"
+edition = "2021"
+description = "CDK shared types and crypto utilities"
+license.workspace = true
+homepage.workspace = true
+repository.workspace = true
+
+[features]
+swagger = ["dep:utoipa"]
+bench = []
+
+[dependencies]
+async-trait = "0.1"
+bitcoin = { version = "0.32.2", features = [
+    "base64",
+    "serde",
+    "rand",
+    "rand-std",
+] }
+cbor-diag = "0.1.12"
+ciborium = { version = "0.2.2", default-features = false, features = ["std"] }
+once_cell = "1.20.2"
+serde = { version = "1.0.216", features = ["derive"] }
+serde_json = "1.0.134"
+serde_with = "3"
+lightning-invoice = { version = "0.32.0", features = ["serde", "std"] }
+thiserror = "2.0.9"
+tracing = "0.1.41"
+url = "2.3"
+uuid = { version = "1", features = ["v4", "serde"] }
+utoipa = { version = "4", optional = true }
+
+[dev-dependencies]
+rand = "0.8.5"
+bip39 = "2.0"

BIN
crates/cdk-common/src/.DS_Store


+ 470 - 0
crates/cdk-common/src/amount.rs

@@ -0,0 +1,470 @@
+//! CDK Amount
+//!
+//! Is any unit and will be treated as the unit of the wallet
+
+use std::cmp::Ordering;
+use std::fmt;
+use std::str::FromStr;
+
+use serde::{Deserialize, Deserializer, Serialize, Serializer};
+use thiserror::Error;
+
+use crate::nuts::CurrencyUnit;
+
+/// Amount Error
+#[derive(Debug, Error)]
+pub enum Error {
+    /// Split Values must be less then or equal to amount
+    #[error("Split Values must be less then or equal to amount")]
+    SplitValuesGreater,
+    /// Amount overflow
+    #[error("Amount Overflow")]
+    AmountOverflow,
+    /// Cannot convert units
+    #[error("Cannot convert units")]
+    CannotConvertUnits,
+}
+
+/// Amount can be any unit
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
+#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
+#[serde(transparent)]
+pub struct Amount(u64);
+
+impl Amount {
+    /// Amount zero
+    pub const ZERO: Amount = Amount(0);
+
+    /// Split into parts that are powers of two
+    pub fn split(&self) -> Vec<Self> {
+        let sats = self.0;
+        (0_u64..64)
+            .rev()
+            .filter_map(|bit| {
+                let part = 1 << bit;
+                ((sats & part) == part).then_some(Self::from(part))
+            })
+            .collect()
+    }
+
+    /// Split into parts that are powers of two by target
+    pub fn split_targeted(&self, target: &SplitTarget) -> Result<Vec<Self>, Error> {
+        let mut parts = match target {
+            SplitTarget::None => self.split(),
+            SplitTarget::Value(amount) => {
+                if self.le(amount) {
+                    return Ok(self.split());
+                }
+
+                let mut parts_total = Amount::ZERO;
+                let mut parts = Vec::new();
+
+                // The powers of two that are need to create target value
+                let parts_of_value = amount.split();
+
+                while parts_total.lt(self) {
+                    for part in parts_of_value.iter().copied() {
+                        if (part + parts_total).le(self) {
+                            parts.push(part);
+                        } else {
+                            let amount_left = *self - parts_total;
+                            parts.extend(amount_left.split());
+                        }
+
+                        parts_total = Amount::try_sum(parts.clone().iter().copied())?;
+
+                        if parts_total.eq(self) {
+                            break;
+                        }
+                    }
+                }
+
+                parts
+            }
+            SplitTarget::Values(values) => {
+                let values_total: Amount = Amount::try_sum(values.clone().into_iter())?;
+
+                match self.cmp(&values_total) {
+                    Ordering::Equal => values.clone(),
+                    Ordering::Less => {
+                        return Err(Error::SplitValuesGreater);
+                    }
+                    Ordering::Greater => {
+                        let extra = *self - values_total;
+                        let mut extra_amount = extra.split();
+                        let mut values = values.clone();
+
+                        values.append(&mut extra_amount);
+                        values
+                    }
+                }
+            }
+        };
+
+        parts.sort();
+        Ok(parts)
+    }
+
+    /// Checked addition for Amount. Returns None if overflow occurs.
+    pub fn checked_add(self, other: Amount) -> Option<Amount> {
+        self.0.checked_add(other.0).map(Amount)
+    }
+
+    /// Checked subtraction for Amount. Returns None if overflow occurs.
+    pub fn checked_sub(self, other: Amount) -> Option<Amount> {
+        self.0.checked_sub(other.0).map(Amount)
+    }
+
+    /// Try sum to check for overflow
+    pub fn try_sum<I>(iter: I) -> Result<Self, Error>
+    where
+        I: IntoIterator<Item = Self>,
+    {
+        iter.into_iter().try_fold(Amount::ZERO, |acc, x| {
+            acc.checked_add(x).ok_or(Error::AmountOverflow)
+        })
+    }
+}
+
+impl Default for Amount {
+    fn default() -> Self {
+        Amount::ZERO
+    }
+}
+
+impl Default for &Amount {
+    fn default() -> Self {
+        &Amount::ZERO
+    }
+}
+
+impl fmt::Display for Amount {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        if let Some(width) = f.width() {
+            write!(f, "{:width$}", self.0, width = width)
+        } else {
+            write!(f, "{}", self.0)
+        }
+    }
+}
+
+impl From<u64> for Amount {
+    fn from(value: u64) -> Self {
+        Self(value)
+    }
+}
+
+impl From<&u64> for Amount {
+    fn from(value: &u64) -> Self {
+        Self(*value)
+    }
+}
+
+impl From<Amount> for u64 {
+    fn from(value: Amount) -> Self {
+        value.0
+    }
+}
+
+impl AsRef<u64> for Amount {
+    fn as_ref(&self) -> &u64 {
+        &self.0
+    }
+}
+
+impl std::ops::Add for Amount {
+    type Output = Amount;
+
+    fn add(self, rhs: Amount) -> Self::Output {
+        Amount(self.0.checked_add(rhs.0).expect("Addition error"))
+    }
+}
+
+impl std::ops::AddAssign for Amount {
+    fn add_assign(&mut self, rhs: Self) {
+        self.0 = self.0.checked_add(rhs.0).expect("Addition error");
+    }
+}
+
+impl std::ops::Sub for Amount {
+    type Output = Amount;
+
+    fn sub(self, rhs: Amount) -> Self::Output {
+        Amount(self.0 - rhs.0)
+    }
+}
+
+impl std::ops::SubAssign for Amount {
+    fn sub_assign(&mut self, other: Self) {
+        self.0 -= other.0;
+    }
+}
+
+impl std::ops::Mul for Amount {
+    type Output = Self;
+
+    fn mul(self, other: Self) -> Self::Output {
+        Amount(self.0 * other.0)
+    }
+}
+
+impl std::ops::Div for Amount {
+    type Output = Self;
+
+    fn div(self, other: Self) -> Self::Output {
+        Amount(self.0 / other.0)
+    }
+}
+
+/// String wrapper for an [Amount].
+///
+/// It ser-/deserializes the inner [Amount] to a string, while at the same time using the [u64]
+/// value of the [Amount] for comparison and ordering. This helps automatically sort the keys of
+/// a [BTreeMap] when [AmountStr] is used as key.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct AmountStr(Amount);
+
+impl AmountStr {
+    pub(crate) fn from(amt: Amount) -> Self {
+        Self(amt)
+    }
+}
+
+impl PartialOrd<Self> for AmountStr {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for AmountStr {
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.0.cmp(&other.0)
+    }
+}
+
+impl<'de> Deserialize<'de> for AmountStr {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let s = String::deserialize(deserializer)?;
+        u64::from_str(&s)
+            .map(Amount)
+            .map(Self)
+            .map_err(serde::de::Error::custom)
+    }
+}
+
+impl Serialize for AmountStr {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        serializer.serialize_str(&self.0.to_string())
+    }
+}
+
+/// Kinds of targeting that are supported
+#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)]
+pub enum SplitTarget {
+    /// Default target; least amount of proofs
+    #[default]
+    None,
+    /// Target amount for wallet to have most proofs that add up to value
+    Value(Amount),
+    /// Specific amounts to split into **MUST** equal amount being split
+    Values(Vec<Amount>),
+}
+
+/// Msats in sat
+pub const MSAT_IN_SAT: u64 = 1000;
+
+/// Helper function to convert units
+pub fn to_unit<T>(
+    amount: T,
+    current_unit: &CurrencyUnit,
+    target_unit: &CurrencyUnit,
+) -> Result<Amount, Error>
+where
+    T: Into<u64>,
+{
+    let amount = amount.into();
+    match (current_unit, target_unit) {
+        (CurrencyUnit::Sat, CurrencyUnit::Sat) => Ok(amount.into()),
+        (CurrencyUnit::Msat, CurrencyUnit::Msat) => Ok(amount.into()),
+        (CurrencyUnit::Sat, CurrencyUnit::Msat) => Ok((amount * MSAT_IN_SAT).into()),
+        (CurrencyUnit::Msat, CurrencyUnit::Sat) => Ok((amount / MSAT_IN_SAT).into()),
+        (CurrencyUnit::Usd, CurrencyUnit::Usd) => Ok(amount.into()),
+        (CurrencyUnit::Eur, CurrencyUnit::Eur) => Ok(amount.into()),
+        _ => Err(Error::CannotConvertUnits),
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_split_amount() {
+        assert_eq!(Amount::from(1).split(), vec![Amount::from(1)]);
+        assert_eq!(Amount::from(2).split(), vec![Amount::from(2)]);
+        assert_eq!(
+            Amount::from(3).split(),
+            vec![Amount::from(2), Amount::from(1)]
+        );
+        let amounts: Vec<Amount> = [8, 2, 1].iter().map(|a| Amount::from(*a)).collect();
+        assert_eq!(Amount::from(11).split(), amounts);
+        let amounts: Vec<Amount> = [128, 64, 32, 16, 8, 4, 2, 1]
+            .iter()
+            .map(|a| Amount::from(*a))
+            .collect();
+        assert_eq!(Amount::from(255).split(), amounts);
+    }
+
+    #[test]
+    fn test_split_target_amount() {
+        let amount = Amount(65);
+
+        let split = amount
+            .split_targeted(&SplitTarget::Value(Amount(32)))
+            .unwrap();
+        assert_eq!(vec![Amount(1), Amount(32), Amount(32)], split);
+
+        let amount = Amount(150);
+
+        let split = amount
+            .split_targeted(&SplitTarget::Value(Amount::from(50)))
+            .unwrap();
+        assert_eq!(
+            vec![
+                Amount(2),
+                Amount(2),
+                Amount(2),
+                Amount(16),
+                Amount(16),
+                Amount(16),
+                Amount(32),
+                Amount(32),
+                Amount(32)
+            ],
+            split
+        );
+
+        let amount = Amount::from(63);
+
+        let split = amount
+            .split_targeted(&SplitTarget::Value(Amount::from(32)))
+            .unwrap();
+        assert_eq!(
+            vec![
+                Amount(1),
+                Amount(2),
+                Amount(4),
+                Amount(8),
+                Amount(16),
+                Amount(32)
+            ],
+            split
+        );
+    }
+
+    #[test]
+    fn test_split_values() {
+        let amount = Amount(10);
+
+        let target = vec![Amount(2), Amount(4), Amount(4)];
+
+        let split_target = SplitTarget::Values(target.clone());
+
+        let values = amount.split_targeted(&split_target).unwrap();
+
+        assert_eq!(target, values);
+
+        let target = vec![Amount(2), Amount(4), Amount(4)];
+
+        let split_target = SplitTarget::Values(vec![Amount(2), Amount(4)]);
+
+        let values = amount.split_targeted(&split_target).unwrap();
+
+        assert_eq!(target, values);
+
+        let split_target = SplitTarget::Values(vec![Amount(2), Amount(10)]);
+
+        let values = amount.split_targeted(&split_target);
+
+        assert!(values.is_err())
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_amount_addition() {
+        let amount_one: Amount = u64::MAX.into();
+        let amount_two: Amount = 1.into();
+
+        let amounts = vec![amount_one, amount_two];
+
+        let _total: Amount = Amount::try_sum(amounts).unwrap();
+    }
+
+    #[test]
+    fn test_try_amount_addition() {
+        let amount_one: Amount = u64::MAX.into();
+        let amount_two: Amount = 1.into();
+
+        let amounts = vec![amount_one, amount_two];
+
+        let total = Amount::try_sum(amounts);
+
+        assert!(total.is_err());
+        let amount_one: Amount = 10000.into();
+        let amount_two: Amount = 1.into();
+
+        let amounts = vec![amount_one, amount_two];
+        let total = Amount::try_sum(amounts).unwrap();
+
+        assert_eq!(total, 10001.into());
+    }
+
+    #[test]
+    fn test_amount_to_unit() {
+        let amount = Amount::from(1000);
+        let current_unit = CurrencyUnit::Sat;
+        let target_unit = CurrencyUnit::Msat;
+
+        let converted = to_unit(amount, &current_unit, &target_unit).unwrap();
+
+        assert_eq!(converted, 1000000.into());
+
+        let amount = Amount::from(1000);
+        let current_unit = CurrencyUnit::Msat;
+        let target_unit = CurrencyUnit::Sat;
+
+        let converted = to_unit(amount, &current_unit, &target_unit).unwrap();
+
+        assert_eq!(converted, 1.into());
+
+        let amount = Amount::from(1);
+        let current_unit = CurrencyUnit::Usd;
+        let target_unit = CurrencyUnit::Usd;
+
+        let converted = to_unit(amount, &current_unit, &target_unit).unwrap();
+
+        assert_eq!(converted, 1.into());
+
+        let amount = Amount::from(1);
+        let current_unit = CurrencyUnit::Eur;
+        let target_unit = CurrencyUnit::Eur;
+
+        let converted = to_unit(amount, &current_unit, &target_unit).unwrap();
+
+        assert_eq!(converted, 1.into());
+
+        let amount = Amount::from(1);
+        let current_unit = CurrencyUnit::Sat;
+        let target_unit = CurrencyUnit::Eur;
+
+        let converted = to_unit(amount, &current_unit, &target_unit);
+
+        assert!(converted.is_err());
+    }
+}

+ 0 - 0
crates/cdk/src/dhke.rs → crates/cdk-common/src/dhke.rs


+ 25 - 0
crates/cdk-common/src/lib.rs

@@ -0,0 +1,25 @@
+//! CDK shared types and functions.
+//!
+//! This crate is the base foundation to build things that can interact with the CDK (Cashu
+//! Development Kit) and their internal crates.
+//!
+//! This is meant to contain the shared types, traits and common functions that are used across the
+//! internal crates.
+
+pub mod amount;
+pub mod dhke;
+pub mod mint;
+pub mod mint_url;
+pub mod nuts;
+pub mod pub_sub;
+pub mod secret;
+pub mod signatory;
+pub mod util;
+
+// re-exporting external crates
+pub use bitcoin;
+pub use lightning_invoice::{self, Bolt11Invoice};
+
+pub use self::amount::Amount;
+pub use self::nuts::*;
+pub use self::util::SECP256K1;

+ 3 - 4
crates/cdk/src/mint/types.rs → crates/cdk-common/src/mint.rs

@@ -1,12 +1,11 @@
-//! Mint Types
+//! Mint types
 
 use serde::{Deserialize, Serialize};
 use uuid::Uuid;
 
-use super::{CurrencyUnit, PublicKey};
 use crate::mint_url::MintUrl;
 use crate::nuts::{MeltQuoteState, MintQuoteState};
-use crate::Amount;
+use crate::{Amount, CurrencyUnit, PublicKey};
 
 /// Mint Quote Info
 #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
@@ -58,7 +57,7 @@ impl MintQuote {
     }
 }
 
-/// Melt Quote Info
+// Melt Quote Info
 #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
 pub struct MeltQuote {
     /// Quote id

+ 0 - 0
crates/cdk/src/mint_url.rs → crates/cdk-common/src/mint_url.rs


+ 3 - 9
crates/cdk/src/nuts/mod.rs → crates/cdk-common/src/nuts/mod.rs

@@ -28,12 +28,8 @@ pub use nut00::{
     Proofs, Token, TokenV3, TokenV4, Witness,
 };
 pub use nut01::{Keys, KeysResponse, PublicKey, SecretKey};
-#[cfg(feature = "mint")]
-pub use nut02::MintKeySet;
-pub use nut02::{Id, KeySet, KeySetInfo, KeysetResponse};
-#[cfg(feature = "wallet")]
-pub use nut03::PreSwap;
-pub use nut03::{SwapRequest, SwapResponse};
+pub use nut02::{Id, KeySet, KeySetInfo, KeysetResponse, MintKeySet};
+pub use nut03::{PreSwap, SwapRequest, SwapResponse};
 pub use nut04::{
     MintBolt11Request, MintBolt11Response, MintMethodSettings, MintQuoteBolt11Request,
     MintQuoteBolt11Response, QuoteState as MintQuoteState, Settings as NUT04Settings,
@@ -50,7 +46,5 @@ pub use nut11::{Conditions, P2PKWitness, SigFlag, SpendingConditions};
 pub use nut12::{BlindSignatureDleq, ProofDleq};
 pub use nut14::HTLCWitness;
 pub use nut15::{Mpp, MppMethodSettings, Settings as NUT15Settings};
-#[cfg(feature = "mint")]
-pub use nut17::PubSubManager;
-pub use nut17::{NotificationPayload, SupportedSettings as Nut17SupportedSettings};
+pub use nut17::NotificationPayload;
 pub use nut18::{PaymentRequest, PaymentRequestPayload, Transport};

+ 0 - 1
crates/cdk/src/nuts/nut00/mod.rs → crates/cdk-common/src/nuts/nut00/mod.rs

@@ -380,7 +380,6 @@ pub enum CurrencyUnit {
     Custom(String),
 }
 
-#[cfg(feature = "mint")]
 impl CurrencyUnit {
     /// Derivation index mint will use for unit
     pub fn derivation_index(&self) -> Option<u32> {

+ 6 - 8
crates/cdk/src/nuts/nut00/token.rs → crates/cdk-common/src/nuts/nut00/token.rs

@@ -527,11 +527,11 @@ mod tests {
     }
 
     #[test]
-    fn test_token_v4_multi_keyset() -> anyhow::Result<()> {
+    fn test_token_v4_multi_keyset() {
         let token_str_multi_keysets = "cashuBo2F0gqJhaUgA_9SLj17PgGFwgaNhYQFhc3hAYWNjMTI0MzVlN2I4NDg0YzNjZjE4NTAxNDkyMThhZjkwZjcxNmE1MmJmNGE1ZWQzNDdlNDhlY2MxM2Y3NzM4OGFjWCECRFODGd5IXVW-07KaZCvuWHk3WrnnpiDhHki6SCQh88-iYWlIAK0mjE0fWCZhcIKjYWECYXN4QDEzMjNkM2Q0NzA3YTU4YWQyZTIzYWRhNGU5ZjFmNDlmNWE1YjRhYzdiNzA4ZWIwZDYxZjczOGY0ODMwN2U4ZWVhY1ghAjRWqhENhLSsdHrr2Cw7AFrKUL9Ffr1XN6RBT6w659lNo2FhAWFzeEA1NmJjYmNiYjdjYzY0MDZiM2ZhNWQ1N2QyMTc0ZjRlZmY4YjQ0MDJiMTc2OTI2ZDNhNTdkM2MzZGNiYjU5ZDU3YWNYIQJzEpxXGeWZN5qXSmJjY8MzxWyvwObQGr5G1YCCgHicY2FtdWh0dHA6Ly9sb2NhbGhvc3Q6MzMzOGF1Y3NhdA==";
 
         let token = Token::from_str(token_str_multi_keysets).unwrap();
-        let amount = token.value()?;
+        let amount = token.value().expect("valid amount");
 
         assert_eq!(amount, Amount::from(4));
 
@@ -552,21 +552,19 @@ mod tests {
                 assert_eq!("http://localhost:3338", &mint_url.to_string());
             }
             _ => {
-                anyhow::bail!("Token should be a v4 token")
+                panic!("Token should be a v4 token")
             }
         }
-
-        Ok(())
     }
 
     #[test]
-    fn test_tokenv4_from_tokenv3() -> anyhow::Result<()> {
+    fn test_tokenv4_from_tokenv3() {
         let token_v3_str = "cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9";
-        let token_v3 = TokenV3::from_str(token_v3_str)?;
+        let token_v3 =
+            TokenV3::from_str(token_v3_str).expect("TokenV3 should be created from string");
         let token_v4 = TokenV4::try_from(token_v3).expect("TokenV3 should be converted to TokenV4");
         let token_v4_expected = "cashuBpGFtd2h0dHBzOi8vODMzMy5zcGFjZTozMzM4YXVjc2F0YWRqVGhhbmsgeW91LmF0gaJhaUgAmh8pMlPkHmFwgqRhYQJhc3hANDA3OTE1YmMyMTJiZTYxYTc3ZTNlNmQyYWViNGM3Mjc5ODBiZGE1MWNkMDZhNmFmYzI5ZTI4NjE3NjhhNzgzN2FjWCECvJCXmX2Br7LMc0a15DRak0a9KlBut5WFmKcvDPhRY-phZPakYWEIYXN4QGZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmVhY1ghAp6OUFC4kKfWwJaNsWvB1dX6BA6h3ihPbsadYSmfZxBZYWT2";
         assert_eq!(token_v4.to_string(), token_v4_expected);
-        Ok(())
     }
 
     #[test]

+ 0 - 0
crates/cdk/src/nuts/nut01/mod.rs → crates/cdk-common/src/nuts/nut01/mod.rs


+ 0 - 0
crates/cdk/src/nuts/nut01/public_key.rs → crates/cdk-common/src/nuts/nut01/public_key.rs


+ 0 - 0
crates/cdk/src/nuts/nut01/secret_key.rs → crates/cdk-common/src/nuts/nut01/secret_key.rs


+ 2 - 16
crates/cdk/src/nuts/nut02.rs → crates/cdk-common/src/nuts/nut02.rs

@@ -5,30 +5,21 @@
 use core::fmt;
 use core::str::FromStr;
 use std::array::TryFromSliceError;
-#[cfg(feature = "mint")]
 use std::collections::BTreeMap;
 
-#[cfg(feature = "mint")]
-use bitcoin::bip32::DerivationPath;
-#[cfg(feature = "mint")]
-use bitcoin::bip32::{ChildNumber, Xpriv};
+use bitcoin::bip32::{ChildNumber, DerivationPath, Xpriv};
 use bitcoin::hashes::sha256::Hash as Sha256;
 use bitcoin::hashes::Hash;
-#[cfg(feature = "mint")]
 use bitcoin::key::Secp256k1;
-#[cfg(feature = "mint")]
 use bitcoin::secp256k1;
 use serde::{Deserialize, Serialize};
 use serde_with::{serde_as, VecSkipError};
 use thiserror::Error;
 
-use super::nut01::Keys;
-#[cfg(feature = "mint")]
-use super::nut01::{MintKeyPair, MintKeys};
+use super::nut01::{Keys, MintKeyPair, MintKeys};
 use crate::amount::AmountStr;
 use crate::nuts::nut00::CurrencyUnit;
 use crate::util::hex;
-#[cfg(feature = "mint")]
 use crate::Amount;
 
 /// NUT02 Error
@@ -237,7 +228,6 @@ impl KeySet {
     }
 }
 
-#[cfg(feature = "mint")]
 impl From<MintKeySet> for KeySet {
     fn from(keyset: MintKeySet) -> Self {
         Self {
@@ -269,7 +259,6 @@ fn default_input_fee_ppk() -> u64 {
 }
 
 /// MintKeyset
-#[cfg(feature = "mint")]
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 pub struct MintKeySet {
     /// Keyset [`Id`]
@@ -280,7 +269,6 @@ pub struct MintKeySet {
     pub keys: MintKeys,
 }
 
-#[cfg(feature = "mint")]
 impl MintKeySet {
     /// Generate new [`MintKeySet`]
     pub fn generate<C: secp256k1::Signing>(
@@ -355,7 +343,6 @@ impl MintKeySet {
     }
 }
 
-#[cfg(feature = "mint")]
 impl From<MintKeySet> for Id {
     fn from(keyset: MintKeySet) -> Id {
         let keys: super::KeySet = keyset.into();
@@ -364,7 +351,6 @@ impl From<MintKeySet> for Id {
     }
 }
 
-#[cfg(feature = "mint")]
 impl From<&MintKeys> for Id {
     fn from(map: &MintKeys) -> Self {
         let keys: super::Keys = map.clone().into();

+ 0 - 0
crates/cdk/src/nuts/nut03.rs → crates/cdk-common/src/nuts/nut03.rs


+ 0 - 4
crates/cdk/src/nuts/nut04.rs → crates/cdk-common/src/nuts/nut04.rs

@@ -8,7 +8,6 @@ use std::str::FromStr;
 use serde::de::DeserializeOwned;
 use serde::{Deserialize, Serialize};
 use thiserror::Error;
-#[cfg(feature = "mint")]
 use uuid::Uuid;
 
 use super::nut00::{BlindSignature, BlindedMessage, CurrencyUnit, PaymentMethod};
@@ -116,7 +115,6 @@ impl<Q: ToString> MintQuoteBolt11Response<Q> {
     }
 }
 
-#[cfg(feature = "mint")]
 impl From<MintQuoteBolt11Response<Uuid>> for MintQuoteBolt11Response<String> {
     fn from(value: MintQuoteBolt11Response<Uuid>) -> Self {
         Self {
@@ -129,7 +127,6 @@ impl From<MintQuoteBolt11Response<Uuid>> for MintQuoteBolt11Response<String> {
     }
 }
 
-#[cfg(feature = "mint")]
 impl From<crate::mint::MintQuote> for MintQuoteBolt11Response<Uuid> {
     fn from(mint_quote: crate::mint::MintQuote) -> MintQuoteBolt11Response<Uuid> {
         MintQuoteBolt11Response {
@@ -158,7 +155,6 @@ pub struct MintBolt11Request<Q> {
     pub signature: Option<String>,
 }
 
-#[cfg(feature = "mint")]
 impl TryFrom<MintBolt11Request<String>> for MintBolt11Request<Uuid> {
     type Error = uuid::Error;
 

+ 0 - 6
crates/cdk/src/nuts/nut05.rs → crates/cdk-common/src/nuts/nut05.rs

@@ -9,12 +9,10 @@ use serde::de::DeserializeOwned;
 use serde::{Deserialize, Deserializer, Serialize};
 use serde_json::Value;
 use thiserror::Error;
-#[cfg(feature = "mint")]
 use uuid::Uuid;
 
 use super::nut00::{BlindSignature, BlindedMessage, CurrencyUnit, PaymentMethod, Proofs};
 use super::nut15::Mpp;
-#[cfg(feature = "mint")]
 use crate::mint::{self, MeltQuote};
 use crate::nuts::MeltQuoteState;
 use crate::{Amount, Bolt11Invoice};
@@ -132,7 +130,6 @@ impl<Q: ToString> MeltQuoteBolt11Response<Q> {
     }
 }
 
-#[cfg(feature = "mint")]
 impl From<MeltQuoteBolt11Response<Uuid>> for MeltQuoteBolt11Response<String> {
     fn from(value: MeltQuoteBolt11Response<Uuid>) -> Self {
         Self {
@@ -148,7 +145,6 @@ impl From<MeltQuoteBolt11Response<Uuid>> for MeltQuoteBolt11Response<String> {
     }
 }
 
-#[cfg(feature = "mint")]
 impl From<&MeltQuote> for MeltQuoteBolt11Response<Uuid> {
     fn from(melt_quote: &MeltQuote) -> MeltQuoteBolt11Response<Uuid> {
         MeltQuoteBolt11Response {
@@ -248,7 +244,6 @@ impl<'de, Q: DeserializeOwned> Deserialize<'de> for MeltQuoteBolt11Response<Q> {
     }
 }
 
-#[cfg(feature = "mint")]
 impl From<mint::MeltQuote> for MeltQuoteBolt11Response<Uuid> {
     fn from(melt_quote: mint::MeltQuote) -> MeltQuoteBolt11Response<Uuid> {
         let paid = melt_quote.state == QuoteState::Paid;
@@ -280,7 +275,6 @@ pub struct MeltBolt11Request<Q> {
     pub outputs: Option<Vec<BlindedMessage>>,
 }
 
-#[cfg(feature = "mint")]
 impl TryFrom<MeltBolt11Request<String>> for MeltBolt11Request<Uuid> {
     type Error = uuid::Error;
 

+ 0 - 3
crates/cdk/src/nuts/nut06.rs → crates/cdk-common/src/nuts/nut06.rs

@@ -5,7 +5,6 @@
 use serde::{Deserialize, Deserializer, Serialize, Serializer};
 
 use super::nut01::PublicKey;
-#[cfg(feature = "mint")]
 use super::nut17::SupportedMethods;
 use super::nut19::CachedEndpoint;
 use super::{nut04, nut05, nut15, nut19, MppMethodSettings};
@@ -241,7 +240,6 @@ pub struct Nuts {
     /// NUT17 Settings
     #[serde(default)]
     #[serde(rename = "17")]
-    #[cfg(feature = "mint")]
     pub nut17: super::nut17::SupportedSettings,
     /// NUT19 Settings
     #[serde(default)]
@@ -342,7 +340,6 @@ impl Nuts {
     }
 
     /// Nut17 settings
-    #[cfg(feature = "mint")]
     pub fn nut17(self, supported: Vec<SupportedMethods>) -> Self {
         Self {
             nut17: super::nut17::SupportedSettings { supported },

+ 0 - 0
crates/cdk/src/nuts/nut07.rs → crates/cdk-common/src/nuts/nut07.rs


+ 0 - 0
crates/cdk/src/nuts/nut08.rs → crates/cdk-common/src/nuts/nut08.rs


+ 0 - 0
crates/cdk/src/nuts/nut09.rs → crates/cdk-common/src/nuts/nut09.rs


+ 0 - 0
crates/cdk/src/nuts/nut10.rs → crates/cdk-common/src/nuts/nut10.rs


+ 4 - 10
crates/cdk/src/nuts/nut11/mod.rs → crates/cdk-common/src/nuts/nut11/mod.rs

@@ -2,9 +2,7 @@
 //!
 //! <https://github.com/cashubtc/nuts/blob/main/11.md>
 
-use std::collections::HashMap;
-#[cfg(feature = "mint")]
-use std::collections::HashSet;
+use std::collections::{HashMap, HashSet};
 use std::str::FromStr;
 use std::{fmt, vec};
 
@@ -18,9 +16,7 @@ use thiserror::Error;
 
 use super::nut00::Witness;
 use super::nut01::PublicKey;
-#[cfg(feature = "mint")]
-use super::Proofs;
-use super::{Kind, Nut10Secret, Proof, SecretKey};
+use super::{Kind, Nut10Secret, Proof, Proofs, SecretKey};
 use crate::nuts::nut00::BlindedMessage;
 use crate::secret::Secret;
 use crate::util::{hex, unix_time};
@@ -616,10 +612,9 @@ impl FromStr for SigFlag {
     }
 }
 
-#[cfg(feature = "mint")]
 /// Get the signature flag that should be enforced for a set of proofs and the
 /// public keys that signatures are valid for
-pub(crate) fn enforce_sig_flag(proofs: Proofs) -> EnforceSigFlag {
+pub fn enforce_sig_flag(proofs: Proofs) -> EnforceSigFlag {
     let mut sig_flag = SigFlag::SigInputs;
     let mut pubkeys = HashSet::new();
     let mut sigs_required = 1;
@@ -658,10 +653,9 @@ pub(crate) fn enforce_sig_flag(proofs: Proofs) -> EnforceSigFlag {
     }
 }
 
-#[cfg(feature = "mint")]
 /// Enforce Sigflag info
 #[derive(Debug, Clone, PartialEq, Eq)]
-pub(crate) struct EnforceSigFlag {
+pub struct EnforceSigFlag {
     /// Sigflag required for proofs
     pub sig_flag: SigFlag,
     /// Pubkeys that can sign for proofs

+ 0 - 0
crates/cdk/src/nuts/nut11/serde_p2pk_witness.rs → crates/cdk-common/src/nuts/nut11/serde_p2pk_witness.rs


+ 0 - 0
crates/cdk/src/nuts/nut12.rs → crates/cdk-common/src/nuts/nut12.rs


+ 0 - 0
crates/cdk/src/nuts/nut13.rs → crates/cdk-common/src/nuts/nut13.rs


+ 0 - 0
crates/cdk/src/nuts/nut14/mod.rs → crates/cdk-common/src/nuts/nut14/mod.rs


+ 0 - 0
crates/cdk/src/nuts/nut14/serde_htlc_witness.rs → crates/cdk-common/src/nuts/nut14/serde_htlc_witness.rs


+ 0 - 0
crates/cdk/src/nuts/nut15.rs → crates/cdk-common/src/nuts/nut15.rs


+ 45 - 54
crates/cdk/src/nuts/nut17/mod.rs → crates/cdk-common/src/nuts/nut17/mod.rs

@@ -9,18 +9,9 @@ use super::PublicKey;
 use crate::nuts::{
     CurrencyUnit, MeltQuoteBolt11Response, MintQuoteBolt11Response, PaymentMethod, ProofState,
 };
-use crate::pub_sub::{Index, Indexable, SubscriptionGlobalId};
-
-#[cfg(feature = "mint")]
-mod manager;
-#[cfg(feature = "mint")]
-mod on_subscription;
-#[cfg(feature = "mint")]
-pub use manager::PubSubManager;
-#[cfg(feature = "mint")]
-pub use on_subscription::OnSubscription;
-
-pub use crate::pub_sub::SubId;
+use crate::pub_sub::index::{Index, Indexable, SubscriptionGlobalId};
+use crate::pub_sub::SubId;
+
 pub mod ws;
 
 /// Subscription Parameter according to the standard
@@ -35,6 +26,30 @@ pub struct Params {
     pub id: SubId,
 }
 
+impl TryFrom<Params> for Vec<Index<Notification>> {
+    type Error = Error;
+
+    fn try_from(val: Params) -> Result<Self, Self::Error> {
+        let sub_id: SubscriptionGlobalId = Default::default();
+        val.filters
+            .into_iter()
+            .map(|filter| {
+                let idx = match val.kind {
+                    Kind::Bolt11MeltQuote => {
+                        Notification::MeltQuoteBolt11(Uuid::from_str(&filter)?)
+                    }
+                    Kind::Bolt11MintQuote => {
+                        Notification::MintQuoteBolt11(Uuid::from_str(&filter)?)
+                    }
+                    Kind::ProofState => Notification::ProofState(PublicKey::from_str(&filter)?),
+                };
+
+                Ok(Index::from((idx, val.id.clone(), sub_id)))
+            })
+            .collect::<Result<_, _>>()
+    }
+}
+
 /// Check state Settings
 #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 pub struct SupportedSettings {
@@ -91,6 +106,24 @@ pub enum NotificationPayload<T> {
     MintQuoteBolt11Response(MintQuoteBolt11Response<T>),
 }
 
+impl Indexable for NotificationPayload<Uuid> {
+    type Type = Notification;
+
+    fn to_indexes(&self) -> Vec<Index<Self::Type>> {
+        match self {
+            NotificationPayload::ProofState(proof_state) => {
+                vec![Index::from(Notification::ProofState(proof_state.y))]
+            }
+            NotificationPayload::MeltQuoteBolt11Response(melt_quote) => {
+                vec![Index::from(Notification::MeltQuoteBolt11(melt_quote.quote))]
+            }
+            NotificationPayload::MintQuoteBolt11Response(mint_quote) => {
+                vec![Index::from(Notification::MintQuoteBolt11(mint_quote.quote))]
+            }
+        }
+    }
+}
+
 impl<T> From<ProofState> for NotificationPayload<T> {
     fn from(proof_state: ProofState) -> NotificationPayload<T> {
         NotificationPayload::ProofState(proof_state)
@@ -120,24 +153,6 @@ pub enum Notification {
     MintQuoteBolt11(Uuid),
 }
 
-impl Indexable for NotificationPayload<Uuid> {
-    type Type = Notification;
-
-    fn to_indexes(&self) -> Vec<Index<Self::Type>> {
-        match self {
-            NotificationPayload::ProofState(proof_state) => {
-                vec![Index::from(Notification::ProofState(proof_state.y))]
-            }
-            NotificationPayload::MeltQuoteBolt11Response(melt_quote) => {
-                vec![Index::from(Notification::MeltQuoteBolt11(melt_quote.quote))]
-            }
-            NotificationPayload::MintQuoteBolt11Response(mint_quote) => {
-                vec![Index::from(Notification::MintQuoteBolt11(mint_quote.quote))]
-            }
-        }
-    }
-}
-
 /// Kind
 #[derive(Debug, Clone, Copy, Eq, Ord, PartialOrd, PartialEq, Hash, Serialize, Deserialize)]
 #[serde(rename_all = "snake_case")]
@@ -167,27 +182,3 @@ pub enum Error {
     /// PublicKey Error
     PublicKey(#[from] crate::nuts::nut01::Error),
 }
-
-impl TryFrom<Params> for Vec<Index<Notification>> {
-    type Error = Error;
-
-    fn try_from(val: Params) -> Result<Self, Self::Error> {
-        let sub_id: SubscriptionGlobalId = Default::default();
-        val.filters
-            .into_iter()
-            .map(|filter| {
-                let idx = match val.kind {
-                    Kind::Bolt11MeltQuote => {
-                        Notification::MeltQuoteBolt11(Uuid::from_str(&filter)?)
-                    }
-                    Kind::Bolt11MintQuote => {
-                        Notification::MintQuoteBolt11(Uuid::from_str(&filter)?)
-                    }
-                    Kind::ProofState => Notification::ProofState(PublicKey::from_str(&filter)?),
-                };
-
-                Ok(Index::from((idx, val.id.clone(), sub_id)))
-            })
-            .collect::<Result<_, _>>()
-    }
-}

+ 2 - 1
crates/cdk/src/nuts/nut17/ws.rs → crates/cdk-common/src/nuts/nut17/ws.rs

@@ -4,7 +4,8 @@ use serde::de::DeserializeOwned;
 use serde::{Deserialize, Serialize};
 use uuid::Uuid;
 
-use super::{NotificationPayload, Params, SubId};
+use super::{NotificationPayload, Params};
+use crate::pub_sub::SubId;
 
 /// JSON RPC version
 pub const JSON_RPC_VERSION: &str = "2.0";

+ 9 - 11
crates/cdk/src/nuts/nut18.rs → crates/cdk-common/src/nuts/nut18.rs

@@ -147,15 +147,15 @@ mod tests {
     const PAYMENT_REQUEST: &str = "creqApWF0gaNhdGVub3N0cmFheKlucHJvZmlsZTFxeTI4d3VtbjhnaGo3dW45ZDNzaGp0bnl2OWtoMnVld2Q5aHN6OW1od2RlbjV0ZTB3ZmprY2N0ZTljdXJ4dmVuOWVlaHFjdHJ2NWhzenJ0aHdkZW41dGUwZGVoaHh0bnZkYWtxcWd5ZGFxeTdjdXJrNDM5eWtwdGt5c3Y3dWRoZGh1NjhzdWNtMjk1YWtxZWZkZWhrZjBkNDk1Y3d1bmw1YWeBgmFuYjE3YWloYjdhOTAxNzZhYQphdWNzYXRhbYF4Imh0dHBzOi8vbm9mZWVzLnRlc3RudXQuY2FzaHUuc3BhY2U=";
 
     #[test]
-    fn test_decode_payment_req() -> anyhow::Result<()> {
-        let req = PaymentRequest::from_str(PAYMENT_REQUEST)?;
+    fn test_decode_payment_req() {
+        let req = PaymentRequest::from_str(PAYMENT_REQUEST).expect("valid payment request");
 
         assert_eq!(&req.payment_id.unwrap(), "b7a90176");
         assert_eq!(req.amount.unwrap(), 10.into());
         assert_eq!(req.unit.clone().unwrap(), CurrencyUnit::Sat);
         assert_eq!(
             req.mints.unwrap(),
-            vec![MintUrl::from_str("https://nofees.testnut.cashu.space")?]
+            vec![MintUrl::from_str("https://nofees.testnut.cashu.space").expect("valid mint url")]
         );
         assert_eq!(req.unit.unwrap(), CurrencyUnit::Sat);
 
@@ -164,12 +164,10 @@ mod tests {
         let expected_transport = Transport {_type: TransportType::Nostr, target: "nprofile1qy28wumn8ghj7un9d3shjtnyv9kh2uewd9hsz9mhwden5te0wfjkccte9curxven9eehqctrv5hszrthwden5te0dehhxtnvdakqqgydaqy7curk439ykptkysv7udhdhu68sucm295akqefdehkf0d495cwunl5".to_string(), tags: Some(vec![vec!["n".to_string(), "17".to_string()]])};
 
         assert_eq!(transport, &expected_transport);
-
-        Ok(())
     }
 
     #[test]
-    fn test_roundtrip_payment_req() -> anyhow::Result<()> {
+    fn test_roundtrip_payment_req() {
         let transport = Transport {_type: TransportType::Nostr, target: "nprofile1qy28wumn8ghj7un9d3shjtnyv9kh2uewd9hsz9mhwden5te0wfjkccte9curxven9eehqctrv5hszrthwden5te0dehhxtnvdakqqgydaqy7curk439ykptkysv7udhdhu68sucm295akqefdehkf0d495cwunl5".to_string(), tags: Some(vec![vec!["n".to_string(), "17".to_string()]])};
 
         let request = PaymentRequest {
@@ -177,27 +175,27 @@ mod tests {
             amount: Some(10.into()),
             unit: Some(CurrencyUnit::Sat),
             single_use: None,
-            mints: Some(vec!["https://nofees.testnut.cashu.space".parse()?]),
+            mints: Some(vec!["https://nofees.testnut.cashu.space"
+                .parse()
+                .expect("valid mint url")]),
             description: None,
             transports: vec![transport.clone()],
         };
 
         let request_str = request.to_string();
 
-        let req = PaymentRequest::from_str(&request_str)?;
+        let req = PaymentRequest::from_str(&request_str).expect("valid payment request");
 
         assert_eq!(&req.payment_id.unwrap(), "b7a90176");
         assert_eq!(req.amount.unwrap(), 10.into());
         assert_eq!(req.unit.clone().unwrap(), CurrencyUnit::Sat);
         assert_eq!(
             req.mints.unwrap(),
-            vec![MintUrl::from_str("https://nofees.testnut.cashu.space")?]
+            vec![MintUrl::from_str("https://nofees.testnut.cashu.space").expect("valid mint url")]
         );
         assert_eq!(req.unit.unwrap(), CurrencyUnit::Sat);
 
         let t = req.transports.first().unwrap();
         assert_eq!(&transport, t);
-
-        Ok(())
     }
 }

+ 0 - 0
crates/cdk/src/nuts/nut19.rs → crates/cdk-common/src/nuts/nut19.rs


+ 0 - 0
crates/cdk/src/nuts/nut20.rs → crates/cdk-common/src/nuts/nut20.rs


+ 0 - 0
crates/cdk/src/pub_sub/index.rs → crates/cdk-common/src/pub_sub/index.rs


+ 77 - 0
crates/cdk-common/src/pub_sub/mod.rs

@@ -0,0 +1,77 @@
+//! Publish–subscribe pattern.
+//!
+//! This is a generic implementation for
+//! [NUT-17(https://github.com/cashubtc/nuts/blob/main/17.md) with a type
+//! agnostic Publish-subscribe manager.
+//!
+//! The manager has a method for subscribers to subscribe to events with a
+//! generic type that must be converted to a vector of indexes.
+//!
+//! Events are also generic that should implement the `Indexable` trait.
+use std::fmt::Debug;
+use std::ops::Deref;
+use std::str::FromStr;
+
+use serde::{Deserialize, Serialize};
+
+pub mod index;
+
+/// Default size of the remove channel
+pub const DEFAULT_REMOVE_SIZE: usize = 10_000;
+
+/// Default channel size for subscription buffering
+pub const DEFAULT_CHANNEL_SIZE: usize = 10;
+
+#[async_trait::async_trait]
+/// On New Subscription trait
+///
+/// This trait is optional and it is used to notify the application when a new
+/// subscription is created. This is useful when the application needs to send
+/// the initial state to the subscriber upon subscription
+pub trait OnNewSubscription {
+    /// Index type
+    type Index;
+    /// Subscription event type
+    type Event;
+
+    /// Called when a new subscription is created
+    async fn on_new_subscription(
+        &self,
+        request: &[&Self::Index],
+    ) -> Result<Vec<Self::Event>, String>;
+}
+
+/// Subscription Id wrapper
+///
+/// This is the place to add some sane default (like a max length) to the
+/// subscription ID
+#[derive(Debug, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
+pub struct SubId(String);
+
+impl From<&str> for SubId {
+    fn from(s: &str) -> Self {
+        Self(s.to_string())
+    }
+}
+
+impl From<String> for SubId {
+    fn from(s: String) -> Self {
+        Self(s)
+    }
+}
+
+impl FromStr for SubId {
+    type Err = ();
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        Ok(Self(s.to_string()))
+    }
+}
+
+impl Deref for SubId {
+    type Target = String;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}

+ 0 - 0
crates/cdk/src/secret.rs → crates/cdk-common/src/secret.rs


+ 53 - 0
crates/cdk-common/src/signatory.rs

@@ -0,0 +1,53 @@
+//! Signatory
+//!
+//! Types to define new signatory instances and their types
+
+use std::collections::HashMap;
+
+use bitcoin::bip32::DerivationPath;
+
+use crate::nuts::nut00::{BlindSignature, BlindedMessage, Proof};
+use crate::{CurrencyUnit, Id, KeySet, KeysResponse, KeysetResponse};
+
+#[derive(thiserror::Error, Debug)]
+pub enum Error {}
+
+#[async_trait::async_trait]
+/// Signatory trait
+pub trait Signatory {
+    /// Blind sign a message
+    async fn blind_sign<B>(&self, blinded_message: B) -> Result<BlindSignature, Error>
+    where
+        B: Into<BlindedMessage>;
+
+    /// Verify [`Proof`] meets conditions and is signed
+    async fn verify_proof<P>(&self, proof: P) -> Result<(), Error>
+    where
+        P: Into<Proof>;
+
+    /// Retrieve a keyset by id
+    async fn keyset<I>(&self, keyset_id: I) -> Result<Option<KeySet>, Error>
+    where
+        I: Into<Id>;
+
+    /// Retrieve the public keys of a keyset
+    async fn keyset_pubkeys(&self, keyset_id: Id) -> Result<KeysResponse, Error>;
+
+    /// Retrieve the public keys of the active keyset for distribution to wallet
+    /// clients
+    async fn pubkeys(&self) -> Result<KeysResponse, Error>;
+
+    /// Return a list of all supported keysets
+    async fn keysets(&self) -> Result<KeysetResponse, Error>;
+
+    /// Add current keyset to inactive keysets
+    /// Generate new keyset
+    async fn rotate_keyset(
+        &self,
+        unit: CurrencyUnit,
+        derivation_path_index: u32,
+        max_order: u8,
+        input_fee_ppk: u64,
+        custom_paths: HashMap<CurrencyUnit, DerivationPath>,
+    ) -> Result<(), Error>;
+}

+ 0 - 23
crates/cdk/src/util/hex.rs → crates/cdk-common/src/util/hex.rs

@@ -122,26 +122,3 @@ mod tests {
         );
     }
 }
-
-#[cfg(feature = "bench")]
-mod benches {
-    use super::*;
-    use crate::test::{black_box, Bencher};
-
-    const EVENT_JSON: &str = r#"{"content":"uRuvYr585B80L6rSJiHocw==?iv=oh6LVqdsYYol3JfFnXTbPA==","created_at":1640839235,"id":"2be17aa3031bdcb006f0fce80c146dea9c1c0268b0af2398bb673365c6444d45","kind":4,"pubkey":"f86c44a2de95d9149b51c6a29afeabba264c18e2fa7c49de93424a0c56947785","sig":"a5d9290ef9659083c490b303eb7ee41356d8778ff19f2f91776c8dc4443388a64ffcf336e61af4c25c05ac3ae952d1ced889ed655b67790891222aaa15b99fdd","tags":[["p","13adc511de7e1cfcf1c6b7f6365fb5a03442d7bcacf565ea57fa7770912c023d"]]}"#;
-
-    #[bench]
-    pub fn hex_encode(bh: &mut Bencher) {
-        bh.iter(|| {
-            black_box(encode(EVENT_JSON));
-        });
-    }
-
-    #[bench]
-    pub fn hex_decode(bh: &mut Bencher) {
-        let h = "7b22636f6e74656e74223a227552757659723538354238304c3672534a69486f63773d3d3f69763d6f68364c5671647359596f6c334a66466e58546250413d3d222c22637265617465645f6174223a313634303833393233352c226964223a2232626531376161333033316264636230303666306663653830633134366465613963316330323638623061663233393862623637333336356336343434643435222c226b696e64223a342c227075626b6579223a2266383663343461326465393564393134396235316336613239616665616262613236346331386532666137633439646539333432346130633536393437373835222c22736967223a226135643932393065663936353930383363343930623330336562376565343133353664383737386666313966326639313737366338646334343433333838613634666663663333366536316166346332356330356163336165393532643163656438383965643635356236373739303839313232326161613135623939666464222c2274616773223a5b5b2270222c2231336164633531316465376531636663663163366237663633363566623561303334343264376263616366353635656135376661373737303931326330323364225d5d7d";
-        bh.iter(|| {
-            black_box(decode(h)).unwrap();
-        });
-    }
-}

+ 13 - 6
crates/cdk/src/util/mod.rs → crates/cdk-common/src/util/mod.rs

@@ -1,12 +1,7 @@
-//! Util
-
 #[cfg(not(target_arch = "wasm32"))]
 use std::time::{SystemTime, UNIX_EPOCH};
 
-use anyhow::Result;
 use bitcoin::secp256k1::{rand, All, Secp256k1};
-#[cfg(target_arch = "wasm32")]
-use instant::SystemTime;
 use once_cell::sync::Lazy;
 
 pub mod hex;
@@ -30,10 +25,22 @@ pub fn unix_time() -> u64 {
         .as_secs()
 }
 
+#[derive(Debug, thiserror::Error)]
+/// Error type for serialization
+pub enum CborError {
+    /// CBOR serialization error
+    #[error("CBOR serialization error")]
+    Cbor(#[from] ciborium::ser::Error<std::io::Error>),
+
+    /// CBOR diagnostic notation error
+    #[error("CBOR diagnostic notation error: {0}")]
+    CborDiag(#[from] cbor_diag::Error),
+}
+
 /// Serializes a struct to the CBOR diagnostic notation.
 ///
 /// See <https://www.rfc-editor.org/rfc/rfc8949.html#name-diagnostic-notation>
-pub fn serialize_to_cbor_diag<T: serde::Serialize>(data: &T) -> Result<String> {
+pub fn serialize_to_cbor_diag<T: serde::Serialize>(data: &T) -> Result<String, CborError> {
     let mut cbor_buffer = Vec::new();
     ciborium::ser::into_writer(data, &mut cbor_buffer)?;
 

+ 5 - 1
crates/cdk/Cargo.toml

@@ -31,6 +31,7 @@ bitcoin = { version = "0.32.2", features = [
     "rand-std",
 ] }
 ciborium = { version = "0.2.2", default-features = false, features = ["std"] }
+cdk-common = { path = "../cdk-common" }
 cbor-diag = "0.1.12"
 lightning-invoice = { version = "0.32.0", features = ["serde", "std"] }
 once_cell = "1.19"
@@ -40,7 +41,10 @@ reqwest = { version = "0.12", default-features = false, features = [
     "rustls-tls",
     "rustls-tls-native-roots",
     "socks",
-    "zstd", "brotli", "gzip", "deflate"
+    "zstd",
+    "brotli",
+    "gzip",
+    "deflate",
 ], optional = true }
 serde = { version = "1", default-features = false, features = ["derive"] }
 serde_json = "1"

+ 1 - 1
crates/cdk/src/cdk_database/mint_memory.rs

@@ -4,13 +4,13 @@ use std::collections::HashMap;
 use std::sync::Arc;
 
 use async_trait::async_trait;
+use cdk_common::nut00::ProofsMethods;
 use tokio::sync::{Mutex, RwLock};
 use uuid::Uuid;
 
 use super::{Error, MintDatabase};
 use crate::dhke::hash_to_curve;
 use crate::mint::{self, MintKeySetInfo, MintQuote};
-use crate::nuts::nut00::ProofsMethods;
 use crate::nuts::nut07::State;
 use crate::nuts::{
     nut07, BlindSignature, CurrencyUnit, Id, MeltBolt11Request, MeltQuoteState, MintQuoteState,

+ 6 - 10
crates/cdk/src/lib.rs

@@ -3,33 +3,31 @@
 #![warn(missing_docs)]
 #![warn(rustdoc::bare_urls)]
 
-pub mod amount;
 #[cfg(any(feature = "wallet", feature = "mint"))]
 pub mod cdk_database;
 #[cfg(feature = "mint")]
 pub mod cdk_lightning;
-pub mod dhke;
 pub mod error;
 #[cfg(feature = "mint")]
 pub mod mint;
-pub mod mint_url;
-pub mod nuts;
-pub mod secret;
 pub mod types;
-pub mod util;
 #[cfg(feature = "wallet")]
 pub mod wallet;
 
 pub mod pub_sub;
 
+/// Re-export amount type
+#[doc(hidden)]
+pub use cdk_common::{
+    amount, dhke, lightning_invoice, mint_url, nuts, secret, util, Amount, Bolt11Invoice,
+};
+
 pub mod fees;
 
 #[doc(hidden)]
 pub use bitcoin::secp256k1;
 #[doc(hidden)]
 pub use error::Error;
-#[doc(hidden)]
-pub use lightning_invoice::{self, Bolt11Invoice};
 #[cfg(feature = "mint")]
 #[doc(hidden)]
 pub use mint::Mint;
@@ -38,8 +36,6 @@ pub use mint::Mint;
 pub use wallet::{Wallet, WalletSubscription};
 
 #[doc(hidden)]
-pub use self::amount::Amount;
-#[doc(hidden)]
 pub use self::util::SECP256K1;
 #[cfg(feature = "wallet")]
 #[doc(hidden)]

+ 20 - 22
crates/cdk/src/mint/builder.rs

@@ -156,28 +156,26 @@ impl MintBuilder {
             self.mint_info.nuts.nut15 = mpp;
         }
 
-        match method {
-            PaymentMethod::Bolt11 => {
-                let mint_method_settings = MintMethodSettings {
-                    method,
-                    unit: unit.clone(),
-                    min_amount: Some(limits.mint_min),
-                    max_amount: Some(limits.mint_max),
-                    description: settings.invoice_description,
-                };
-
-                self.mint_info.nuts.nut04.methods.push(mint_method_settings);
-                self.mint_info.nuts.nut04.disabled = false;
-
-                let melt_method_settings = MeltMethodSettings {
-                    method,
-                    unit,
-                    min_amount: Some(limits.melt_min),
-                    max_amount: Some(limits.melt_max),
-                };
-                self.mint_info.nuts.nut05.methods.push(melt_method_settings);
-                self.mint_info.nuts.nut05.disabled = false;
-            }
+        if method == PaymentMethod::Bolt11 {
+            let mint_method_settings = MintMethodSettings {
+                method,
+                unit: unit.clone(),
+                min_amount: Some(limits.mint_min),
+                max_amount: Some(limits.mint_max),
+                description: settings.invoice_description,
+            };
+
+            self.mint_info.nuts.nut04.methods.push(mint_method_settings);
+            self.mint_info.nuts.nut04.disabled = false;
+
+            let melt_method_settings = MeltMethodSettings {
+                method,
+                unit,
+                min_amount: Some(limits.melt_min),
+                max_amount: Some(limits.melt_max),
+            };
+            self.mint_info.nuts.nut05.methods.push(melt_method_settings);
+            self.mint_info.nuts.nut05.disabled = false;
         }
 
         ln.insert(ln_key.clone(), ln_backend);

+ 1 - 1
crates/cdk/src/mint/melt.rs

@@ -2,6 +2,7 @@ use std::collections::HashSet;
 use std::str::FromStr;
 
 use anyhow::bail;
+use cdk_common::nut00::ProofsMethods;
 use lightning_invoice::Bolt11Invoice;
 use tracing::instrument;
 use uuid::Uuid;
@@ -13,7 +14,6 @@ use super::{
 use crate::amount::to_unit;
 use crate::cdk_lightning::{MintLightning, PayInvoiceResponse};
 use crate::mint::SigFlag;
-use crate::nuts::nut00::ProofsMethods;
 use crate::nuts::nut11::{enforce_sig_flag, EnforceSigFlag};
 use crate::nuts::{Id, MeltQuoteState};
 use crate::types::LnKey;

+ 4 - 2
crates/cdk/src/mint/mod.rs

@@ -8,6 +8,7 @@ use bitcoin::bip32::{ChildNumber, DerivationPath};
 use config::SwappableConfig;
 use futures::StreamExt;
 use serde::{Deserialize, Serialize};
+use subscription::PubSubManager;
 use tokio::sync::Notify;
 use tokio::task::JoinSet;
 use tracing::instrument;
@@ -31,12 +32,13 @@ mod melt;
 mod mint_nut04;
 mod signatory;
 mod start_up_check;
+pub mod subscription;
 mod swap;
-pub mod types;
+
+pub use cdk_common::mint::{MeltQuote, MintQuote};
 
 pub use self::builder::{MintBuilder, MintMeltLimits};
 pub use self::signatory::*;
-pub use self::types::{MeltQuote, MintQuote};
 
 /// Cashu Mint
 #[derive(Clone)]

+ 3 - 1
crates/cdk/src/nuts/nut17/manager.rs → crates/cdk/src/mint/subscription/manager.rs

@@ -2,9 +2,11 @@
 use std::ops::Deref;
 use std::sync::Arc;
 
+use cdk_common::nut17::Notification;
+use cdk_common::NotificationPayload;
 use uuid::Uuid;
 
-use super::{Notification, NotificationPayload, OnSubscription};
+use super::OnSubscription;
 use crate::cdk_database::{self, MintDatabase};
 use crate::nuts::{
     BlindSignature, MeltQuoteBolt11Response, MeltQuoteState, MintQuoteBolt11Response,

+ 12 - 0
crates/cdk/src/mint/subscription/mod.rs

@@ -0,0 +1,12 @@
+//! Specific Subscription for the cdk crate
+
+#[cfg(feature = "mint")]
+mod manager;
+#[cfg(feature = "mint")]
+mod on_subscription;
+#[cfg(feature = "mint")]
+pub use manager::PubSubManager;
+#[cfg(feature = "mint")]
+pub use on_subscription::OnSubscription;
+
+pub use crate::pub_sub::SubId;

+ 3 - 2
crates/cdk/src/nuts/nut17/on_subscription.rs → crates/cdk/src/mint/subscription/on_subscription.rs

@@ -3,12 +3,13 @@
 //! This module contains the code that is triggered when a new subscription is created.
 use std::sync::Arc;
 
+use cdk_common::nut17::Notification;
+use cdk_common::pub_sub::OnNewSubscription;
+use cdk_common::NotificationPayload;
 use uuid::Uuid;
 
-use super::{Notification, NotificationPayload};
 use crate::cdk_database::{self, MintDatabase};
 use crate::nuts::{MeltQuoteBolt11Response, MintQuoteBolt11Response, ProofState, PublicKey};
-use crate::pub_sub::OnNewSubscription;
 
 #[derive(Default)]
 /// Subscription Init

+ 3 - 60
crates/cdk/src/pub_sub/mod.rs

@@ -12,18 +12,15 @@ use std::cmp::Ordering;
 use std::collections::{BTreeMap, HashSet};
 use std::fmt::Debug;
 use std::ops::{Deref, DerefMut};
-use std::str::FromStr;
 use std::sync::atomic::{self, AtomicUsize};
 use std::sync::Arc;
 
-use serde::{Deserialize, Serialize};
+pub use cdk_common::pub_sub::index::{Index, Indexable, SubscriptionGlobalId};
+use cdk_common::pub_sub::OnNewSubscription;
+pub use cdk_common::pub_sub::SubId;
 use tokio::sync::{mpsc, RwLock};
 use tokio::task::JoinHandle;
 
-mod index;
-
-pub use index::{Index, Indexable, SubscriptionGlobalId};
-
 type IndexTree<T, I> = Arc<RwLock<BTreeMap<Index<I>, mpsc::Sender<(SubId, T)>>>>;
 
 /// Default size of the remove channel
@@ -32,25 +29,6 @@ pub const DEFAULT_REMOVE_SIZE: usize = 10_000;
 /// Default channel size for subscription buffering
 pub const DEFAULT_CHANNEL_SIZE: usize = 10;
 
-#[async_trait::async_trait]
-/// On New Subscription trait
-///
-/// This trait is optional and it is used to notify the application when a new
-/// subscription is created. This is useful when the application needs to send
-/// the initial state to the subscriber upon subscription
-pub trait OnNewSubscription {
-    /// Index type
-    type Index;
-    /// Subscription event type
-    type Event;
-
-    /// Called when a new subscription is created
-    async fn on_new_subscription(
-        &self,
-        request: &[&Self::Index],
-    ) -> Result<Vec<Self::Event>, String>;
-}
-
 /// Subscription manager
 ///
 /// This object keep track of all subscription listener and it is also
@@ -320,41 +298,6 @@ where
     }
 }
 
-/// Subscription Id wrapper
-///
-/// This is the place to add some sane default (like a max length) to the
-/// subscription ID
-#[derive(Debug, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
-pub struct SubId(String);
-
-impl From<&str> for SubId {
-    fn from(s: &str) -> Self {
-        Self(s.to_string())
-    }
-}
-
-impl From<String> for SubId {
-    fn from(s: String) -> Self {
-        Self(s)
-    }
-}
-
-impl FromStr for SubId {
-    type Err = ();
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        Ok(Self(s.to_string()))
-    }
-}
-
-impl Deref for SubId {
-    type Target = String;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-
 #[cfg(test)]
 mod test {
     use tokio::sync::mpsc;