Skip to content

feat(nut17)!: method-neutral quote subscriptions - #668

Closed
robwoodgate wants to merge 11 commits into
feat/nut04-05-wideningfrom
fix/nut17-generic-subscription-kinds-main
Closed

feat(nut17)!: method-neutral quote subscriptions#668
robwoodgate wants to merge 11 commits into
feat/nut04-05-wideningfrom
fix/nut17-generic-subscription-kinds-main

Conversation

@robwoodgate

@robwoodgate robwoodgate commented May 21, 2026

Copy link
Copy Markdown
Collaborator

NUT - cashubtc/nuts#372

Summary

Makes NUT-17 quote subscriptions method-neutral. mintQuoteUpdates / meltQuoteUpdates / mintQuotePaid / meltQuotePaid and the once* helpers no longer assume bolt11:

  • Kind selection by fan-out. Subscribe to the generic mint_quote / melt_quote kinds when the mint advertises them, otherwise fan out across every per-method kind it advertises for the unit (bolt11_*, bolt12_*, custom) and merge them into one stream. When both a generic and per-method kinds are advertised, only the generic one is used.
  • Generic payloads. Each helper is generic over its response type, defaulting to MintQuoteGenericResponse / MeltQuoteGenericResponse; callers narrow (e.g. MintQuoteBolt12Response) when they know the method of the quotes they created.
  • Accounting-based "paid". Mint quotes are mintable when amount_paid > amount_issued (falling back to the legacy state for pre-accounting mints); melt quotes use state === PAID.
  • RpcSubKinds enumerates the first-class kinds (generic + bolt11 + bolt12); custom <method>_* kinds are forwarded at runtime but not enumerated.

Motivation

The previous implementation only handled bolt11_* kinds, so bolt12 (NUT-25) quotes — already supported elsewhere in the wallet — could not be watched, and custom payment methods had no path at all. A per-method helper API would explode as methods are added; keeping the helpers method-neutral keeps the public surface stable while covering bolt11, bolt12, and future/custom methods.

The originating wallet already knows each quote's method, so a generic payload with optional T narrowing is enough for the common case. The notification method field (cashubtc/nuts#387) is additive, unlocking runtime discrimination, mixed-method subscriptions, and observers that did not create the quote.

Sequencing

Builds on #698 (quote accounting + custom-method base structs). Suggested merge order: #675#698#668.

@codecov

codecov Bot commented May 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.42%. Comparing base (bbd6bc4) to head (00ad101).
⚠️ Report is 1 commits behind head on feat/nut04-05-widening.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@                    Coverage Diff                     @@
##           feat/nut04-05-widening     #668      +/-   ##
==========================================================
+ Coverage                   92.35%   92.42%   +0.07%     
==========================================================
  Files                          54       54              
  Lines                        5020     5040      +20     
  Branches                     1238     1244       +6     
==========================================================
+ Hits                         4636     4658      +22     
- Misses                        163      165       +2     
+ Partials                      221      217       -4     
Flag Coverage Δ
integration 39.12% <77.77%> (+0.22%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@robwoodgate
robwoodgate force-pushed the fix/nut17-generic-subscription-kinds-main branch 2 times, most recently from efdb5df to 48feb21 Compare May 21, 2026 23:47
@robwoodgate
robwoodgate marked this pull request as ready for review May 21, 2026 23:48
@robwoodgate robwoodgate added the nut hold This PR is held pending the merge of a new or updated NUT label May 22, 2026
@robwoodgate
robwoodgate force-pushed the fix/nut17-generic-subscription-kinds-main branch from b868621 to 04aea5b Compare May 23, 2026 09:00
Implements cashubtc/nuts#377: amount_paid, amount_issued and updated_at
become base mint quote response fields for every payment method. For
mints that predate quote accounting, the amounts are derived from the
legacy single-use state; conversely, a missing bolt11 state is derived
from the accounting fields. updated_at is null when not reported.

Generic mint quote responses now get base normalization (quote, request,
unit, accounting) like melt quotes already did, and the wallet enforces
the mintable amount (paid minus issued) for all methods, not just
bolt12/onchain.

BREAKING CHANGE: MintQuoteBaseResponse requires amount_paid,
amount_issued and updated_at; non-conformant mint quote responses that
previously passed through unvalidated now throw.
Implements the base-shape harmonization proposed on cashubtc/nuts#382:

- MintQuoteBaseRequest gains optional amount and description; method
  NUTs tighten as needed (bolt11 requires amount).
- MintQuoteBaseResponse gains expiry (normalized to null when unset),
  subsuming the per-method expiry fields.
- MeltQuoteBaseRequest gains optional amount (onchain requires it).
- MeltQuoteBaseResponse gains request and optional fee_reserve; bolt
  methods keep fee_reserve required.
- Melt base validation now requires the payment request for every
  method, and normalizes fee_reserve when present.
- New MintQuoteGenericResponse/MeltQuoteGenericResponse passthrough
  types are the defaults on the generic quote methods, so custom-method
  fields are reachable without casting.

BREAKING CHANGE: melt quote responses without a request field now
throw; generic quote methods default to the Generic response types.
…ctivity

The canonical bolt11 flow is create quote -> pay externally -> mint with
the original quote object, so a 0/0 accounting snapshot is
indistinguishable from a stale pre-payment quote. Skip the client-side
mintable-amount check in that case and let the mint decide; once
amount_paid is non-zero the snapshot proves a payment event and the
check applies as before.
Implements cashubtc/nuts#387: every mint and melt quote response
carries a method field. The wallet always knows the method from the
request endpoint, so normalization populates it when the mint omits it
(no mint ships the field yet) and throws when a reported method
disagrees with the endpoint. Once mints implement it, NUT-17 generic
mint_quote/melt_quote notifications become routable by method.
Custom mint example reads quote progress via the accounting fields
instead of state, and both generic examples note the automatic base
normalization and the Generic response type defaults.
@robwoodgate
robwoodgate force-pushed the fix/nut17-generic-subscription-kinds-main branch from fbdb3bb to c72201e Compare June 18, 2026 20:29
@robwoodgate robwoodgate changed the title fix: use generic NUT-17 subscription kinds feat(nut17)!: method-neutral quote subscriptions Jun 18, 2026
@robwoodgate
robwoodgate changed the base branch from main to feat/nut04-05-widening June 18, 2026 20:30
@robwoodgate
robwoodgate force-pushed the fix/nut17-generic-subscription-kinds-main branch 2 times, most recently from 3e8620a to 002d5e9 Compare June 18, 2026 20:47
Subscribe to mint/melt quote updates independent of payment method: prefer the generic
mint_quote/melt_quote kinds when the mint advertises them, else fan out across the per-method
kinds it advertises (bolt11, bolt12, custom) and merge them. Fanned-out kinds isolate errors, so
one kind's subscribe failure no longer tears down the siblings that work. Quote event helpers are
now generic over the response type (default generic), and "paid" is accounting-based (amount_paid
> amount_issued) with a legacy state fallback.

Builds on #698.
@robwoodgate
robwoodgate force-pushed the fix/nut17-generic-subscription-kinds-main branch from 002d5e9 to 8af05b0 Compare June 18, 2026 21:04
@robwoodgate robwoodgate added the stacked hold PR is based on top of another PR, so should be merged in sequence. See comments for details. label Jun 18, 2026
Document the fan-out fallback's intentional tradeoff: with only a quote id the wallet can't map a
quote to the kind that owns it, so a subscribe rejection on the relevant kind is masked while an
idle sibling keeps the alive count above zero. Add a test for that masking and one proving the
once* timeoutMs escape still terminates the wait.
@robwoodgate robwoodgate added this to the v5.0 milestone Jun 20, 2026
@robwoodgate robwoodgate added the experimental release PR is in current @experimental release. Install: npm i @cashu/cashu-ts@experimental label Jul 1, 2026
@robwoodgate
robwoodgate force-pushed the feat/nut04-05-widening branch from e8e17a6 to 6a06679 Compare July 3, 2026 13:36
@robwoodgate robwoodgate closed this Jul 4, 2026
@github-project-automation github-project-automation Bot moved this from Backlog to Done in cashu-ts Jul 4, 2026
@robwoodgate
robwoodgate deleted the fix/nut17-generic-subscription-kinds-main branch July 22, 2026 12:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

experimental release PR is in current @experimental release. Install: npm i @cashu/cashu-ts@experimental nut hold This PR is held pending the merge of a new or updated NUT stacked hold PR is based on top of another PR, so should be merged in sequence. See comments for details.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant