Skip to content

feat(nut04/05)!: quote accounting and custom payment method base structs#693

Closed
robwoodgate wants to merge 8 commits into
cashubtc:mainfrom
robwoodgate:feat/nut04-05-widening
Closed

feat(nut04/05)!: quote accounting and custom payment method base structs#693
robwoodgate wants to merge 8 commits into
cashubtc:mainfrom
robwoodgate:feat/nut04-05-widening

Conversation

@robwoodgate

@robwoodgate robwoodgate commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

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_issued and updated_at become base mint quote response fields for every payment method; state is 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 / MeltQuoteGenericResponse passthrough 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 method field: 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 generic mint_quote/melt_quote notifications become routable by method.

Follows up on #672 (nuts#374 method_name), which already merged.

Design notes

  • Required-but-derived accounting. For mints that predate quote accounting, amount_paid/amount_issued are derived from the legacy single-use state + amount (UNPAID → 0/0, PAID → amount/0, ISSUED → amount/amount); conversely a missing bolt11 state is derived from the accounting fields. Underivable responses throw, matching the existing melt-side strictness. updated_at is number | null (null when the mint does not report it — it cannot be derived).
  • Base shapes per the nuts#382 review suggestions. Mint quote request base gains optional amount/description; expiry moves into the mint quote response base (normalized to null when unset); melt quote request base gains optional amount; melt quote response base gains required request and optional fee_reserve (bolt methods keep it required, onchain keeps fee_options).
  • Mintable amount enforced for all methods. validateMintQuoteAvailableAmount loses its bolt12/onchain gate: mintable = amount_paid − amount_issued is checked whenever the quote object carries accounting fields. Stored quotes without them (pre-accounting) skip the check, as before.
  • Method-id validation (^[a-z0-9_-]+$) and the generic /v1/{mint,melt}/quote/{method} plumbing already existed and are unchanged.

Breaking changes

  • MintQuoteBaseResponse requires amount_paid, amount_issued, updated_at, expiry; MeltQuoteBaseResponse requires request. Type-breaking for code constructing quote literals; runtime consumers are unaffected because normalization guarantees the fields.
  • Non-conformant quote responses that previously passed through unvalidated (mint quotes missing quote/request/unit or underivable accounting; melt quotes missing request) now throw Invalid response from mint.

Sequencing

Suggest merging in order:#675 → this.

Test plan

  • New unit tests: state↔accounting derivation matrix, updated_at/expiry normalization, custom-method base validation (mint + melt), unknown-field passthrough, generalized available-amount enforcement.
  • 1605 node tests pass; tsc clean; api report regenerated.

@codecov

codecov Bot commented Jun 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.45455% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.35%. Comparing base (f568ed3) to head (bbd6bc4).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/mint/Mint.ts 95.23% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #693      +/-   ##
==========================================
- Coverage   92.40%   92.35%   -0.06%     
==========================================
  Files          54       54              
  Lines        4990     5020      +30     
  Branches     1224     1238      +14     
==========================================
+ Hits         4611     4636      +25     
- Misses        160      163       +3     
- Partials      219      221       +2     
Flag Coverage Δ
integration 38.90% <45.45%> (+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.

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 commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #698, which is the same branch reopened on origin (cashubtc/cashu-ts) instead of my fork.

@github-project-automation github-project-automation Bot moved this from Backlog to Done in cashu-ts Jun 18, 2026
@KvngMikey

Copy link
Copy Markdown
Contributor

Superseded by #698, which is the same branch reopened on origin (cashubtc/cashu-ts) instead of my fork.

oh cool, cause i had opened this one to review a couple days ago and was surprised to see it closed today.

@robwoodgate

Copy link
Copy Markdown
Collaborator Author

Superseded by #698, which is the same branch reopened on origin (cashubtc/cashu-ts) instead of my fork.

oh cool, cause i had opened this one to review a couple days ago and was surprised to see it closed today.

Yeah, I needed to build on it, so easier to stack cleanly on the new one

@robwoodgate robwoodgate deleted the feat/nut04-05-widening branch June 19, 2026 07:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

nut hold This PR is held pending the merge of a new or updated NUT

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants