Skip to content

NUT-18: Payment request consolidation (and extra fees for non-preferred mints)#381

Open
robwoodgate wants to merge 25 commits into
cashubtc:mainfrom
robwoodgate:payment-request-consolidation
Open

NUT-18: Payment request consolidation (and extra fees for non-preferred mints)#381
robwoodgate wants to merge 25 commits into
cashubtc:mainfrom
robwoodgate:payment-request-consolidation

Conversation

@robwoodgate

@robwoodgate robwoodgate commented May 27, 2026

Copy link
Copy Markdown
Collaborator

Consolidates and Supersedes: #355, #380

Summary

Consolidates the two open NUT-18 payment request proposals around mint preferences, strict mint lists, fee signalling, and required mint payment methods.

This PR keeps the existing m mint list as the single mint-list field, and adds small companion fields to clarify how that list should be interpreted and how the payer compensates the receiver for fees.

Changes

  • Retains sm (supported_method) from feat(nut-18, nut-26): add preferred_mints to payment requests #355 to require that the payer's mint supports at least one listed payment method (e.g. bolt11, bolt12, onchain), now as a list of {mn, mf?} objects. The optional per-method fee mf compensates the receiver for melting out via that method:
    • applies only to payments from a mint outside m, or from any mint if m is not set; payments from a listed mint carry no per-method fee
    • the payer owes the lowest mf among the listed methods their mint supports
  • Adds mp (mint_preferred) to signal whether the m mint list is strict or preferred (this is a flip from ms in feat: preferred mints #380):
    • absent / false: receiver only accepts proofs from listed mints
    • true: listed mints are preferred, but other mints may be accepted
  • Specifies that the requested amount is net of input fees: the payer MUST select proofs such that sum(proofs) - input_fee(proofs) >= amount owed, with the input fee computed from the sending mint's input_fee_ppk (NUT-02). This protects the receiver from pathological proof sets (e.g. 1000 x 1 sat proofs at 250 ppk costs the receiver 250 sats to swap).
  • Updates NUT-26 TLV tags for the new fields
  • Adds test vectors covering the combined behaviour

Note: earlier revisions had a flat fr (fee_required) surcharge for non-preferred mints, then an nf (net_fees) opt-in flag. Review discussion concluded fr was redundant once mf is scoped to non-preferred mints, and nf had no sensible false case (no receiver wants to be underpaid by dust), so net-of-input-fees is now the unconditional rule. Both flags have been dropped and the supported_method sub-TLV uses tag 0x0a.

Rationale

There were overlapping proposals for preferred mints and strict mint lists. Instead of introducing a separate preferred mint list, this keeps the request format minimal:

  • m remains the mint list
  • mp defines whether that list is strict or advisory
  • sm lets receivers express the mint capabilities required for the payment flow, with per-method fees that compensate for melting out of a non-preferred mint
  • the requested amount is net of input fees, so receivers get the amount they asked for regardless of the sender's proof selection

This avoids two competing mint-list fields while covering the desired behaviours.

@github-project-automation github-project-automation Bot moved this to Backlog in nuts May 27, 2026
@robwoodgate robwoodgate changed the title Payment request consolidation Payment request consolidation (and add extr fees for non-preferred mints) May 27, 2026
@robwoodgate robwoodgate changed the title Payment request consolidation (and add extr fees for non-preferred mints) Payment request consolidation (and add extra fees for non-preferred mints) May 27, 2026
@robwoodgate robwoodgate changed the title Payment request consolidation (and add extra fees for non-preferred mints) Payment request consolidation (and extra fees for non-preferred mints) May 27, 2026
robwoodgate added a commit to d4rp4t/cashu-ts that referenced this pull request May 28, 2026
Spec moved from cashubtc/nuts#380 to cashubtc/nuts#381, adding fee_reserve (fr)
and supported_methods (sm) alongside the existing mint-strict flag. Adds an
isMintListStrict resolver for the spec default-to-true semantic.
@robwoodgate

robwoodgate commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

@callebtc , @thesimplekid - we have three implementations, can we consider final review / merge please.

cc @ye0man

@KvngMikey

Copy link
Copy Markdown
Contributor

4 implementations:
cashubtc/nutshell#1059

robwoodgate added a commit to d4rp4t/cashu-ts that referenced this pull request Jun 25, 2026
Spec moved from cashubtc/nuts#380 to cashubtc/nuts#381, adding fee_reserve (fr)
and supported_methods (sm) alongside the existing mint-strict flag. Adds an
isMintListStrict resolver for the spec default-to-true semantic.
@robwoodgate

robwoodgate commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator Author

The only thing I still don't love about this is the mint_strict boolean being true if omitted.

It opens up all kinds of edge case issues as undefined is true but other falsey values are false.

I'd rather default to false on omitted, or if strict is the desirable default, flip the boolean semantic to mint_prefer, where true means the list of mints is a preference and false or omitted means it is strict

What are the thoughts on this?

Comment thread 18.md Outdated
@thesimplekid

Copy link
Copy Markdown
Collaborator

The only thing I still don't love about this is the mint_strict boolean being true if omitted.

It opens up all kinds of edge case issues as undefined is true but other falsey values are false.

I'd rather default to false on omitted, or if strict is the desirable default, flip the boolean semantic to mint_prefer, where true means the list of mints is a preference and false or omitted means it is strict

What are the thoughts on this?

I agree, I think default true is an anti pattern and we should avoid it.

@robwoodgate

Copy link
Copy Markdown
Collaborator Author

I agree, I think default true is an anti pattern and we should avoid it.

Agreed. Would you prefer to keep mint_strict and make the default false, or flip the boolean semantics to mint_prefer / mint_suggest or similar?

@thesimplekid

Copy link
Copy Markdown
Collaborator

I agree, I think default true is an anti pattern and we should avoid it.

Agreed. Would you prefer to keep mint_strict and make the default false, or flip the boolean semantics to mint_prefer / mint_suggest or similar?

I would flip the semantics. mint_preferred / mp: absent or false means the existing strict m behavior, true means the m list is advisory/preferred.

@robwoodgate

Copy link
Copy Markdown
Collaborator Author

I would flip the semantics. mint_preferred / mp: absent or false means the existing strict m behavior, true means the m list is advisory/preferred.

yeah, that's my leaning too. @d4rp4t @a1denvalu3, do you agree?

@KvngMikey

Copy link
Copy Markdown
Contributor

I would flip the semantics. mint_preferred / mp: absent or false means the existing strict m behavior, true means the m list is advisory/preferred.

i agree with this ...

@robwoodgate

Copy link
Copy Markdown
Collaborator Author

ms (mint_strict) now replaced with mp (mint_preferred).

This gives the same semantic, but flipped so an omitted value means false (strict).

@robwoodgate robwoodgate requested a review from thesimplekid June 26, 2026 22:12
@robwoodgate

Copy link
Copy Markdown
Collaborator Author

Cashu-TS updated to use new mp semantic (replacing ms)

@robwoodgate robwoodgate force-pushed the payment-request-consolidation branch from 207e484 to 13e7cc8 Compare June 30, 2026 20:50
- sm entries become {mn, mf?} objects; mf per-method fee stacks additively with top-level fr
- NUT-26 tag 0x0b promoted to sub-TLV (0x01 method, 0x02 fee)
- regenerate + verify test vectors; fix pre-existing basic vector blob/JSON mismatch
@robwoodgate

This comment was marked as outdated.

@robwoodgate robwoodgate requested a review from callebtc July 1, 2026 18:52
@robwoodgate

Copy link
Copy Markdown
Collaborator Author

CTS updated to use new fee per method:

Comment thread 18.md Outdated
Comment on lines +70 to +72
- `mf`: additional fee, in the request unit, the payer MUST add when using this method

The per-method fee (`mf`) is independent of the top-level fee required for non-preferred mints (`fr`): if the payer sends from a non-preferred mint (outside `m`, `mp` = true) **and** relies on an `sm` method that carries a fee, the payer MUST add both the `fr` and `mf` fees.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what is the point of "fr" if there are "mf" fees as well?

@robwoodgate robwoodgate Jul 2, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

fr is the fee for using a non-preferred mint (mint not in the m list, mp=true)
mf is the fee for using a specific payment method.

This allows fee adds for unknown mints, as well as for using expensive methods.

Example:

// preferred list (mp=true), fr=5, and onchain carries mf=10
pr.amountToSend('https://in-list.mint'); // Amount, base only    → 100
pr.amountToSend('https://in-list.mint', 'onchain'); // + mf      → 110
pr.amountToSend('https://other.mint', 'bolt11'); // + fr         → 105
pr.amountToSend('https://other.mint', 'onchain'); // + fr + mf   → 115

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think this is redundant? As presumably the fee for using a non default mint is because they are going to melt out and that fee will be dependent on method so can just include it in the mf? And mf should only apply to mints outside of preferred?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The thing I'm confused about with the whole per-method fee idea is how the sender knows which method the receiver will use to cash out?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this is redundant?

I think so too.

If the mint is in the list, you should accept the ecash (no method fee applies). If it's a foreign mint, you required a method and a certain fee buffer for that method.

@robwoodgate robwoodgate Jul 3, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

So what shape would you want to see? If the sender cannot know which method the receiver will use, it seems that fee per method itself seems redundant.

sm can just be a single method string (your mint must support x) and fr the fee as now.

"sm": "bolt11",
"fr": "2",

Otherwise, given a menu, the sender is likely to just choose the "cheapest" because they cannot know whether the receiver will offload via bolt11, onchain etc

Alternatively, we can go back to the original multi-list sm (your mint must support at least one of these methods) and a single fr for using the non-preferred mint.

"sm": [ "bolt11", "onchain" ],
"fr": "2",

@robwoodgate robwoodgate Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Some thoughts on this, and the wider payment request needs / issues we've discussed over time:

Fee per method

I suppose a sender is rightfully able to send cheapest per method fee if their chosen mint support 2 or more methods.

So if payment request says:

"sm": [
  { "mn": "bolt11" },  // omitted mf = 0
  { "mn": "onchain", "mf": 50 }
]

And the sender mint supports both, they add no extra mf fee because their mint was compliant with request.

But if a sender's mint is onchain only, they have to pay the lowest per method fee their mint supports (50 in example).

Is that how you envisaged it @callebtc ?

And should the mf per mint fee apply in all cases, or just when using a non-preferred mint?

Fee Required (fr)

I'm not sure the fr is totally redundant. We discussed a case where a sender can use a mint they control with high lightning fees. The fr was an extra fee for using a mint the sender doesn't explicitly trust, regardless of the methods they support.

If the mf fee applies only to non-preferred mint, then I agree, fr is redundant.

Include input fees

Nothing we have discussed so far handles the case where a sender sends a pathological set of proofs - example, 1000 x 1 sat proofs from a mint with 250ppk fee = 250 sats. Which is kind of what I imagined the fr fee would help to mitigate. "If you choose to send from your own mint, add this fee".

Maybe the input fee issue could disappear along with fr by replacing it with an "include fees" boolean, or mandating the amount is net of input fees?

@callebtc callebtc changed the title Payment request consolidation (and extra fees for non-preferred mints) NUT-18: Payment request consolidation (and extra fees for non-preferred mints) Jul 4, 2026
…f (method_fee) is for non-preferred mints only
@robwoodgate

robwoodgate commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

Following discussion in the TS Devs meeting, I've updated the spec as follows:

Commit: 7ff7864

  • fr (fee required) dropped
  • mf (method fee) semantics clarified to ONLY apply for non-preferred mints (or when m is unset)
  • amount specified as being net of per-proof input fees

Note: Change of spec, implementations will need to be updated if this new direction is agreeable

Related discussion: #381 (comment)

cc @callebtc , @thesimplekid , @d4rp4t , @KvngMikey

Payers must select proofs so the amount received after input fees
covers the request. Removes the nf flag and its TLV tag; the
supported_method sub-TLV moves to tag 0x0a. Test vectors regenerated.

@thesimplekid thesimplekid left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Updated cdk to current spec in cashubtc/cdk@b5a5eb4

Comment thread 18.md Outdated
KvngMikey added a commit to KvngMikey/nutshell that referenced this pull request Jul 10, 2026
Drop `fr` and replace `sm: List[str]` with `sm: List[SupportedMethod]`
(`mn`, optional `mf`) per cashubtc/nuts#381. NUT-26 tag 0x0a is now a
repeatable supported_method sub-TLV (0x01=method, 0x02=fee u64 BE);
0x0b removed. Also fixes a mis-scoped guard error message (tag 0x01 is
id, not transport). Byte-verified against frozen spec vectors.

@KvngMikey KvngMikey left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nutshell updated to current spec cashubtc/nutshell@91195fd

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

6 participants