Skip to content
Closed

Onchain #1202

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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,26 @@
- cdk-common: New `Event` enum for payment event handling with `PaymentReceived` variant ([thesimplekid]).
- cdk-common: Added `payment_method` field to `MeltQuote` struct for tracking payment method type ([thesimplekid]).
- cdk-sql-common: Database migration to add `payment_method` column to melt_quote table for SQLite and PostgreSQL ([thesimplekid]).
- cdk-common: New `MintKVStoreDatabase` trait providing generic key-value storage functionality for mint databases ([thesimplekid]).
- cdk-common: Added `KVStoreTransaction` trait for transactional key-value operations with read, write, remove, and list capabilities ([thesimplekid]).
- cdk-common: Added validation functions for KV store namespace and key parameters with ASCII character and length restrictions ([thesimplekid]).
- cdk-common: Added comprehensive test module for KV store functionality with transaction and isolation testing ([thesimplekid]).
- cdk-sql-common: Database migration to add `kv_store` table for generic key-value storage in SQLite and PostgreSQL ([thesimplekid]).
- cdk-sql-common: Implementation of `MintKVStoreDatabase` trait for SQL-based databases with namespace support ([thesimplekid]).

### Changed
- cdk-common: Refactored `MintPayment` trait method `wait_any_incoming_payment` to `wait_payment_event` with event-driven architecture ([thesimplekid]).
- cdk-common: Updated `wait_payment_event` return type to stream `Event` enum instead of `WaitPaymentResponse` directly ([thesimplekid]).
- cdk: Updated mint payment handling to process payment events through new `Event` enum pattern ([thesimplekid]).
<<<<<<< HEAD
- cashu: Updated BOLT12 payment method specification from NUT-24 to NUT-25 ([thesimplekid]).
- cdk: Updated BOLT12 import references from nut24 to nut25 module ([thesimplekid]).

### Fixied
- cdk: Wallet melt track and use payment method from quote for BOLT11/BOLT12 routing ([thesimplekid]).
=======
- cdk: Enhanced melt operations to track and use payment method from quote for BOLT11/BOLT12 routing ([thesimplekid]).
>>>>>>> 5afcc11b (fix: cdk melt quote track payment method)

## [0.12.0](https://github.com/cashubtc/cdk/releases/tag/v0.12.0)

Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ cdk-sqlite = { path = "./crates/cdk-sqlite", default-features = true, version =
cdk-postgres = { path = "./crates/cdk-postgres", default-features = true, version = "=0.12.0" }
cdk-signatory = { path = "./crates/cdk-signatory", version = "=0.12.0", default-features = false }
cdk-mintd = { path = "./crates/cdk-mintd", version = "=0.12.0", default-features = false }
cdk-bdk = { path = "./crates/cdk-bdk", version = "=0.12.0" }
clap = { version = "4.5.31", features = ["derive"] }
ciborium = { version = "0.2.2", default-features = false, features = ["std"] }
cbor-diag = "0.1.12"
Expand Down
56 changes: 53 additions & 3 deletions crates/cashu/src/nuts/auth/nut21.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,18 @@ pub enum RoutePath {
/// Bolt12 Quote
#[serde(rename = "/v1/melt/bolt12")]
MeltBolt12,
/// Onchain Mint Quote
#[serde(rename = "/v1/mint/quote/onchain")]
MintQuoteOnchain,
/// Onchain Mint
#[serde(rename = "/v1/mint/onchain")]
MintOnchain,
/// Onchain Melt Quote
#[serde(rename = "/v1/melt/quote/onchain")]
MeltQuoteOnchain,
/// Onchain Melt
#[serde(rename = "/v1/melt/onchain")]
MeltOnchain,
}

/// Returns [`RoutePath`]s that match regex
Expand Down Expand Up @@ -209,6 +221,12 @@ mod tests {
assert!(paths.contains(&RoutePath::MintBlindAuth));
assert!(paths.contains(&RoutePath::MintQuoteBolt12));
assert!(paths.contains(&RoutePath::MintBolt12));
assert!(paths.contains(&RoutePath::MeltQuoteBolt12));
assert!(paths.contains(&RoutePath::MeltBolt12));
assert!(paths.contains(&RoutePath::MintQuoteOnchain));
assert!(paths.contains(&RoutePath::MintOnchain));
assert!(paths.contains(&RoutePath::MeltQuoteOnchain));
assert!(paths.contains(&RoutePath::MeltOnchain));
}

#[test]
Expand All @@ -217,17 +235,21 @@ mod tests {
let paths = matching_route_paths("^/v1/mint/.*").unwrap();

// Should match only mint paths
assert_eq!(paths.len(), 4);
assert_eq!(paths.len(), 6);
assert!(paths.contains(&RoutePath::MintQuoteBolt11));
assert!(paths.contains(&RoutePath::MintBolt11));
assert!(paths.contains(&RoutePath::MintQuoteBolt12));
assert!(paths.contains(&RoutePath::MintBolt12));
assert!(paths.contains(&RoutePath::MintQuoteOnchain));
assert!(paths.contains(&RoutePath::MintOnchain));

// Should not match other paths
assert!(!paths.contains(&RoutePath::MeltQuoteBolt11));
assert!(!paths.contains(&RoutePath::MeltBolt11));
assert!(!paths.contains(&RoutePath::MeltQuoteBolt12));
assert!(!paths.contains(&RoutePath::MeltBolt12));
assert!(!paths.contains(&RoutePath::MeltQuoteOnchain));
assert!(!paths.contains(&RoutePath::MeltOnchain));
assert!(!paths.contains(&RoutePath::Swap));
}

Expand All @@ -237,15 +259,21 @@ mod tests {
let paths = matching_route_paths(".*/quote/.*").unwrap();

// Should match only quote paths
assert_eq!(paths.len(), 4);
assert_eq!(paths.len(), 6);
assert!(paths.contains(&RoutePath::MintQuoteBolt11));
assert!(paths.contains(&RoutePath::MeltQuoteBolt11));
assert!(paths.contains(&RoutePath::MintQuoteBolt12));
assert!(paths.contains(&RoutePath::MeltQuoteBolt12));
assert!(paths.contains(&RoutePath::MintQuoteOnchain));
assert!(paths.contains(&RoutePath::MeltQuoteOnchain));

// Should not match non-quote paths
assert!(!paths.contains(&RoutePath::MintBolt11));
assert!(!paths.contains(&RoutePath::MeltBolt11));
assert!(!paths.contains(&RoutePath::MintBolt12));
assert!(!paths.contains(&RoutePath::MeltBolt12));
assert!(!paths.contains(&RoutePath::MintOnchain));
assert!(!paths.contains(&RoutePath::MeltOnchain));
}

#[test]
Expand Down Expand Up @@ -294,6 +322,26 @@ mod tests {
assert_eq!(RoutePath::Checkstate.to_string(), "/v1/checkstate");
assert_eq!(RoutePath::Restore.to_string(), "/v1/restore");
assert_eq!(RoutePath::MintBlindAuth.to_string(), "/v1/auth/blind/mint");
assert_eq!(
RoutePath::MintQuoteBolt12.to_string(),
"/v1/mint/quote/bolt12"
);
assert_eq!(RoutePath::MintBolt12.to_string(), "/v1/mint/bolt12");
assert_eq!(
RoutePath::MeltQuoteBolt12.to_string(),
"/v1/melt/quote/bolt12"
);
assert_eq!(RoutePath::MeltBolt12.to_string(), "/v1/melt/bolt12");
assert_eq!(
RoutePath::MintQuoteOnchain.to_string(),
"/v1/mint/quote/onchain"
);
assert_eq!(RoutePath::MintOnchain.to_string(), "/v1/mint/onchain");
assert_eq!(
RoutePath::MeltQuoteOnchain.to_string(),
"/v1/melt/quote/onchain"
);
assert_eq!(RoutePath::MeltOnchain.to_string(), "/v1/melt/onchain");
}

#[test]
Expand Down Expand Up @@ -356,14 +404,16 @@ mod tests {
"https://example.com/.well-known/openid-configuration"
);
assert_eq!(settings.client_id, "client123");
assert_eq!(settings.protected_endpoints.len(), 5); // 3 mint paths + 1 swap path
assert_eq!(settings.protected_endpoints.len(), 7); // 6 mint paths + 1 swap path

let expected_protected: HashSet<ProtectedEndpoint> = HashSet::from_iter(vec![
ProtectedEndpoint::new(Method::Post, RoutePath::Swap),
ProtectedEndpoint::new(Method::Get, RoutePath::MintBolt11),
ProtectedEndpoint::new(Method::Get, RoutePath::MintQuoteBolt11),
ProtectedEndpoint::new(Method::Get, RoutePath::MintQuoteBolt12),
ProtectedEndpoint::new(Method::Get, RoutePath::MintBolt12),
ProtectedEndpoint::new(Method::Get, RoutePath::MintQuoteOnchain),
ProtectedEndpoint::new(Method::Get, RoutePath::MintOnchain),
]);

let deserlized_protected = settings.protected_endpoints.into_iter().collect();
Expand Down
4 changes: 3 additions & 1 deletion crates/cashu/src/nuts/auth/nut22.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,14 +330,16 @@ mod tests {
let settings: Settings = serde_json::from_str(json).unwrap();

assert_eq!(settings.bat_max_mint, 5);
assert_eq!(settings.protected_endpoints.len(), 5); // 4 mint paths + 1 swap path
assert_eq!(settings.protected_endpoints.len(), 7); // 6 mint paths + 1 swap path

let expected_protected: HashSet<ProtectedEndpoint> = HashSet::from_iter(vec![
ProtectedEndpoint::new(Method::Post, RoutePath::Swap),
ProtectedEndpoint::new(Method::Get, RoutePath::MintBolt11),
ProtectedEndpoint::new(Method::Get, RoutePath::MintQuoteBolt11),
ProtectedEndpoint::new(Method::Get, RoutePath::MintQuoteBolt12),
ProtectedEndpoint::new(Method::Get, RoutePath::MintBolt12),
ProtectedEndpoint::new(Method::Get, RoutePath::MintQuoteOnchain),
ProtectedEndpoint::new(Method::Get, RoutePath::MintOnchain),
]);

let deserialized_protected = settings.protected_endpoints.into_iter().collect();
Expand Down
5 changes: 5 additions & 0 deletions crates/cashu/src/nuts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub mod nut19;
pub mod nut20;
pub mod nut23;
pub mod nut25;
pub mod nut26;

#[cfg(feature = "auth")]
mod auth;
Expand Down Expand Up @@ -69,3 +70,7 @@ pub use nut23::{
MintQuoteBolt11Response, QuoteState as MintQuoteState,
};
pub use nut25::{MeltQuoteBolt12Request, MintQuoteBolt12Request, MintQuoteBolt12Response};
pub use nut26::{
MeltQuoteOnchainRequest, MeltQuoteOnchainResponse, MintQuoteOnchainRequest,
MintQuoteOnchainResponse,
};
4 changes: 4 additions & 0 deletions crates/cashu/src/nuts/nut00/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,8 @@ pub enum PaymentMethod {
Bolt11,
/// Bolt12
Bolt12,
/// Onchain
Onchain,
/// Custom
Custom(String),
}
Expand All @@ -659,6 +661,7 @@ impl FromStr for PaymentMethod {
match value.to_lowercase().as_str() {
"bolt11" => Ok(Self::Bolt11),
"bolt12" => Ok(Self::Bolt12),
"onchain" => Ok(Self::Onchain),
c => Ok(Self::Custom(c.to_string())),
}
}
Expand All @@ -669,6 +672,7 @@ impl fmt::Display for PaymentMethod {
match self {
PaymentMethod::Bolt11 => write!(f, "bolt11"),
PaymentMethod::Bolt12 => write!(f, "bolt12"),
PaymentMethod::Onchain => write!(f, "onchain"),
PaymentMethod::Custom(p) => write!(f, "{p}"),
}
}
Expand Down
14 changes: 6 additions & 8 deletions crates/cashu/src/nuts/nut01/public_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,19 +142,17 @@ mod tests {
#[test]
pub fn test_public_key_from_hex() {
// Compressed
assert!(
(PublicKey::from_hex(
"02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104"
)
.is_ok())
);
assert!(PublicKey::from_hex(
"02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104"
)
.is_ok());
}

#[test]
pub fn test_invalid_public_key_from_hex() {
// Uncompressed (is valid but is cashu must be compressed?)
assert!((PublicKey::from_hex("04fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de3625246cb2c27dac965cb7200a5986467eee92eb7d496bbf1453b074e223e481")
.is_err()))
assert!(PublicKey::from_hex("04fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de3625246cb2c27dac965cb7200a5986467eee92eb7d496bbf1453b074e223e481")
.is_err())
}
}

Expand Down
10 changes: 10 additions & 0 deletions crates/cashu/src/nuts/nut08.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use super::nut05::MeltRequest;
use super::nut23::MeltQuoteBolt11Response;
use super::nut26::MeltQuoteOnchainResponse;
use crate::Amount;

impl<Q> MeltRequest<Q> {
Expand All @@ -23,3 +24,12 @@ impl<Q> MeltQuoteBolt11Response<Q> {
.and_then(|o| Amount::try_sum(o.iter().map(|proof| proof.amount)).ok())
}
}

impl<Q> MeltQuoteOnchainResponse<Q> {
/// Total change [`Amount`]
pub fn change_amount(&self) -> Option<Amount> {
self.change
.as_ref()
.and_then(|o| Amount::try_sum(o.iter().map(|proof| proof.amount)).ok())
}
}
41 changes: 36 additions & 5 deletions crates/cashu/src/nuts/nut17/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use serde::{Deserialize, Serialize};
#[cfg(feature = "mint")]
use super::PublicKey;
use crate::nuts::{
CurrencyUnit, MeltQuoteBolt11Response, MintQuoteBolt11Response, PaymentMethod, ProofState,
CurrencyUnit, MeltQuoteBolt11Response, MeltQuoteOnchainResponse, MintQuoteBolt11Response,
MintQuoteOnchainResponse, PaymentMethod, ProofState,
};
#[cfg(feature = "mint")]
use crate::quote_id::{QuoteId, QuoteIdError};
Expand Down Expand Up @@ -107,6 +108,12 @@ pub enum WsCommand {
/// Command to check the state of a proof
#[serde(rename = "proof_state")]
ProofState,
/// Websocket support for Onchain Mint Quote
#[serde(rename = "onchain_mint_quote")]
OnchainMintQuote,
/// Websocket support for Onchain Melt Quote
#[serde(rename = "onchain_melt_quote")]
OnchainMeltQuote,
}

impl<T> From<MintQuoteBolt12Response<T>> for NotificationPayload<T> {
Expand All @@ -128,6 +135,10 @@ pub enum NotificationPayload<T> {
MintQuoteBolt11Response(MintQuoteBolt11Response<T>),
/// Mint Quote Bolt12 Response
MintQuoteBolt12Response(MintQuoteBolt12Response<T>),
/// Mint Quote Onchain Response
MintQuoteOnchainResponse(MintQuoteOnchainResponse<T>),
/// Melt Quote Onchain Response
MeltQuoteOnchainResponse(MeltQuoteOnchainResponse<T>),
}

impl<T> From<ProofState> for NotificationPayload<T> {
Expand All @@ -148,20 +159,36 @@ impl<T> From<MintQuoteBolt11Response<T>> for NotificationPayload<T> {
}
}

impl<T> From<MintQuoteOnchainResponse<T>> for NotificationPayload<T> {
fn from(mint_quote: MintQuoteOnchainResponse<T>) -> NotificationPayload<T> {
NotificationPayload::MintQuoteOnchainResponse(mint_quote)
}
}

impl<T> From<MeltQuoteOnchainResponse<T>> for NotificationPayload<T> {
fn from(melt_quote: MeltQuoteOnchainResponse<T>) -> NotificationPayload<T> {
NotificationPayload::MeltQuoteOnchainResponse(melt_quote)
}
}

#[cfg(feature = "mint")]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
/// A parsed notification
pub enum Notification {
/// ProofState id is a Pubkey
ProofState(PublicKey),
/// MeltQuote id is an QuoteId
/// MeltQuote id is an Uuid
MeltQuoteBolt11(QuoteId),
/// MintQuote id is an QuoteId
/// MintQuote id is an Uuid
MintQuoteBolt11(QuoteId),
/// MintQuote id is an QuoteId
/// MintQuote id is an Uuid
MintQuoteBolt12(QuoteId),
/// MintQuote id is an QuoteId
/// MintQuote id is an Uuid
MeltQuoteBolt12(QuoteId),
/// MintQuote id is an Uuid
MintQuoteOnchain(QuoteId),
/// MeltQuote id is an Uuid
MeltQuoteOnchain(QuoteId),
}

/// Kind
Expand All @@ -176,6 +203,10 @@ pub enum Kind {
ProofState,
/// Bolt 12 Mint Quote
Bolt12MintQuote,
/// Onchain Mint Quote
OnchainMintQuote,
/// Onchain Melt Quote
OnchainMeltQuote,
}

impl<I> AsRef<I> for Params<I> {
Expand Down
Loading