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
21 changes: 21 additions & 0 deletions crates/cashu/examples/payment_request_encoding_benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ fn minimal_comparison() -> Result<(), Box<dyn std::error::Error>> {
unit: None,
single_use: None,
mints: vec![MintUrl::from_str("https://mint.example.com")?],
mints_strict: None,
fee_reserve: None,
supported_methods: vec![],
description: None,
transports: vec![],
nut10: None,
Expand All @@ -110,6 +113,9 @@ fn amount_unit_comparison() -> Result<(), Box<dyn std::error::Error>> {
unit: Some(CurrencyUnit::Sat),
single_use: None,
mints: vec![MintUrl::from_str("https://mint.example.com")?],
mints_strict: None,
fee_reserve: None,
supported_methods: vec![],
description: None,
transports: vec![],
nut10: None,
Expand All @@ -131,6 +137,9 @@ fn multiple_mints_comparison() -> Result<(), Box<dyn std::error::Error>> {
MintUrl::from_str("https://mint3.example.com")?,
MintUrl::from_str("https://backup-mint.cashu.space")?,
],
mints_strict: None,
fee_reserve: None,
supported_methods: vec![],
description: Some("Payment with multiple mint options".to_string()),
transports: vec![],
nut10: None,
Expand All @@ -156,6 +165,9 @@ fn transport_comparison() -> Result<(), Box<dyn std::error::Error>> {
unit: Some(CurrencyUnit::Sat),
single_use: Some(true),
mints: vec![MintUrl::from_str("https://mint.example.com")?],
mints_strict: None,
fee_reserve: None,
supported_methods: vec![],
description: Some("Payment with callback transport".to_string()),
transports: vec![transport],
nut10: None,
Expand Down Expand Up @@ -193,6 +205,9 @@ fn complete_with_nut10_comparison() -> Result<(), Box<dyn std::error::Error>> {
MintUrl::from_str("https://mint1.example.com")?,
MintUrl::from_str("https://mint2.example.com")?,
],
mints_strict: None,
fee_reserve: None,
supported_methods: vec![],
description: Some("Complete payment with P2PK locking and refund key".to_string()),
transports: vec![transport],
nut10: Some(nut10),
Expand Down Expand Up @@ -245,6 +260,9 @@ fn very_complex_comparison() -> Result<(), Box<dyn std::error::Error>> {
MintUrl::from_str("https://backup-mint-2.example.net")?,
MintUrl::from_str("https://emergency-mint.example.io")?,
],
mints_strict: None,
fee_reserve: None,
supported_methods: vec![],
description: Some("Complex payment with multiple mints and transports".to_string()),
transports: vec![transport1, transport2],
nut10: Some(nut10),
Expand Down Expand Up @@ -503,6 +521,9 @@ mod tests {
unit: Some(CurrencyUnit::Sat),
single_use: None,
mints: vec![MintUrl::from_str("https://mint.example.com").unwrap()],
mints_strict: None,
fee_reserve: None,
supported_methods: vec![],
description: Some("Test".to_string()),
transports: vec![],
nut10: None,
Expand Down
45 changes: 45 additions & 0 deletions crates/cashu/src/nuts/nut18/payment_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ pub struct PaymentRequest {
#[serde(rename = "m")]
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub mints: Vec<MintUrl>,
/// Mints strict flag
#[serde(rename = "ms")]
#[serde(skip_serializing_if = "Option::is_none")]
pub mints_strict: Option<bool>,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Honor non-strict mint lists when selecting a mint

When ms is Some(false), the mint list is only a suggestion, but WalletRepository::pay_request still rejects a specified mint and skips auto-selected wallets whenever payment_request.mints is non-empty. A payer with funds only at another mint will fail a non-strict request instead of using that mint, so the selection filters should be gated on mints_strict.unwrap_or(true).

Useful? React with 👍 / 👎.

/// Additional fee reserve for payments from non-preferred mints
#[serde(rename = "fr")]
#[serde(skip_serializing_if = "Option::is_none")]
pub fee_reserve: Option<Amount>,
Comment on lines +43 to +46

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove the unassigned fee reserve request field

The referenced NUT-18/NUT-26 change only adds ms/tag 0x09; fr is not defined for payment requests. Because this field and its builder method are serialized into CBOR and NUT-26 tag 0x0a when set, callers can emit non-standard requests that may collide with future tag assignments, so this should be removed until the NUT assigns it.

Useful? React with 👍 / 👎.

/// Supported payment methods the mint must support
#[serde(rename = "sm")]
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub supported_methods: Vec<String>,
Comment on lines +47 to +50

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove the unassigned supported methods field

The preferred-mints spec change does not define an sm payment-request field or NUT-26 tag 0x0b. If a caller sets supported_methods, this implementation serializes a non-standard field in both encodings, which other implementations may ignore and which can conflict with a future NUT assignment.

Useful? React with 👍 / 👎.

/// Description
#[serde(rename = "d")]
pub description: Option<String>,
Expand Down Expand Up @@ -102,6 +114,9 @@ pub struct PaymentRequestBuilder {
unit: Option<CurrencyUnit>,
single_use: Option<bool>,
mints: Vec<MintUrl>,
mints_strict: Option<bool>,
fee_reserve: Option<Amount>,
supported_methods: Vec<String>,
description: Option<String>,
transports: Vec<Transport>,
nut10: Option<Nut10SecretRequest>,
Expand Down Expand Up @@ -150,6 +165,27 @@ impl PaymentRequestBuilder {
self
}

/// Set mints strict flag
pub fn mints_strict(mut self, mints_strict: bool) -> Self {
self.mints_strict = Some(mints_strict);
self
}

/// Set fee reserve for payments from non-preferred mints
pub fn fee_reserve<A>(mut self, fee_reserve: A) -> Self
where
A: Into<Amount>,
{
self.fee_reserve = Some(fee_reserve.into());
self
}

/// Set supported payment methods
pub fn supported_methods(mut self, methods: Vec<String>) -> Self {
self.supported_methods = methods;
self
}

/// Set description
pub fn description<S: Into<String>>(mut self, description: S) -> Self {
self.description = Some(description.into());
Expand Down Expand Up @@ -182,6 +218,9 @@ impl PaymentRequestBuilder {
unit: self.unit,
single_use: self.single_use,
mints: self.mints,
mints_strict: self.mints_strict,
fee_reserve: self.fee_reserve,
supported_methods: self.supported_methods,
description: self.description,
transports: self.transports,
nut10: self.nut10,
Expand Down Expand Up @@ -249,6 +288,9 @@ mod tests {
mints: vec!["https://nofees.testnut.cashu.space"
.parse()
.expect("valid mint url")],
mints_strict: None,
fee_reserve: None,
supported_methods: vec![],
description: None,
transports: vec![transport.clone()],
nut10: None,
Expand Down Expand Up @@ -693,6 +735,9 @@ mod tests {
unit: Some(CurrencyUnit::Sat),
single_use: None,
mints: vec![MintUrl::from_str("https://mint.example.com").unwrap()],
mints_strict: None,
fee_reserve: None,
supported_methods: vec![],
description: Some("Test both formats".to_string()),
transports: vec![],
nut10: None,
Expand Down
Loading
Loading