feat(nut04/05)!: quote accounting and custom payment method base structs#698
Open
robwoodgate wants to merge 9 commits into
Open
feat(nut04/05)!: quote accounting and custom payment method base structs#698robwoodgate wants to merge 9 commits into
robwoodgate wants to merge 9 commits into
Conversation
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.
This was referenced Jun 18, 2026
robwoodgate
added a commit
that referenced
this pull request
Jun 18, 2026
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. 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. Claude-Session: https://claude.ai/code/session_01WF8XpkeC5uV1aYhpHxw79Q
robwoodgate
added a commit
that referenced
this pull request
Jun 18, 2026
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. 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. Claude-Session: https://claude.ai/code/session_01WF8XpkeC5uV1aYhpHxw79Q
robwoodgate
added a commit
that referenced
this pull request
Jun 18, 2026
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. 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
added a commit
that referenced
this pull request
Jun 18, 2026
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.
normalizeMint/MeltBaseFields used strict !== undefined, so an off-spec null amount_paid/amount_issued/fee_reserve slipped past the guard into Amount.from() and threw AmountError instead of funnelling to the derive path. Loose != null treats null and absent identically, matching how the rest of the file (nullIfUndefined) handles mint inconsistency.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #698 +/- ##
==========================================
- Coverage 92.40% 92.36% -0.04%
==========================================
Files 54 54
Lines 4990 5018 +28
Branches 1224 1237 +13
==========================================
+ Hits 4611 4635 +24
- Misses 160 163 +3
- Partials 219 220 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Matures the NUT-04/05 base payloads so wallets can handle generic and custom payment methods, implementing:
Align NUT-04 mint quote accounting with amount_paid / amount_issued nuts#377 — mint quote accounting:
amount_paid,amount_issuedandupdated_atbecome base mint quote response fields for every payment method;stateis deprecated (but always populated for bolt11).custom payment methods nuts#382 — custom payment methods: base request/response structs widened per the shapes suggested in review, generic quote responses get base normalization + validation, and new
MintQuoteGenericResponse/MeltQuoteGenericResponsepassthrough types are the defaults on the generic quote methods so custom-method fields are reachable without casting.add method field to quote responses, closes #378 nuts#387 — every quote response carries a
methodfield: populated from the request endpoint when the mint omits it (no mint ships it yet), throwing when a reported method disagrees with the endpoint. Once mints implement it, NUT-17 genericmint_quote/melt_quotenotifications become routable by method.Follows up on #672 (nuts#374
method_name), which already merged.Design notes
amount_paid/amount_issuedare derived from the legacy single-usestate+amount(UNPAID → 0/0, PAID → amount/0, ISSUED → amount/amount); conversely a missing bolt11stateis derived from the accounting fields. Underivable responses throw, matching the existing melt-side strictness.updated_atisnumber | null(null when the mint does not report it — it cannot be derived).amount/description;expirymoves into the mint quote response base (normalized tonullwhen unset); melt quote request base gains optionalamount; melt quote response base gains requiredrequestand optionalfee_reserve(bolt methods keep it required, onchain keepsfee_options).validateMintQuoteAvailableAmountloses its bolt12/onchain gate: mintable =amount_paid − amount_issuedis checked whenever the quote object carries accounting fields. Stored quotes without them (pre-accounting) skip the check, as before.^[a-z0-9_-]+$) and the generic/v1/{mint,melt}/quote/{method}plumbing already existed and are unchanged.Breaking changes
MintQuoteBaseResponserequiresamount_paid,amount_issued,updated_at,expiry;MeltQuoteBaseResponserequiresrequest. Type-breaking for code constructing quote literals; runtime consumers are unaffected because normalization guarantees the fields.quote/request/unitor underivable accounting; melt quotes missingrequest) now throwInvalid response from mint.Sequencing
Suggest merging in order:#675 → this.
Test plan
updated_at/expirynormalization, custom-method base validation (mint + melt), unknown-field passthrough, generalized available-amount enforcement.