feat(nut18): pay and validate payment requests end to end - #787
Merged
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #787 +/- ##
==========================================
- Coverage 95.54% 95.52% -0.02%
==========================================
Files 55 55
Lines 5428 5473 +45
Branches 1345 1359 +14
==========================================
+ Hits 5186 5228 +42
- Misses 107 108 +1
- Partials 135 137 +2
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:
|
robwoodgate
marked this pull request as ready for review
July 14, 2026 19:34
42 tasks
robwoodgate
force-pushed
the
feat/payment-request-builder
branch
from
July 21, 2026 20:31
c864ea5 to
9cb626a
Compare
wallet.ops.sendToRequest(pr, proofs, amount?) builds a send that enforces the payer-side MUSTs in one step: strict mint list, unit rule, NUT-05 melt-method support for the request unit, lowest applicable mf, the request's nut10 lock (throws on kinds it cannot honour), and net-of-input-fees selection via includeFees(true). Returns the normal SendBuilder for chaining. wallet.isPaymentRequestSatisfied(pr, proofs, expectedAmount?) is the receiver-side check: sum(proofs) - input fees >= amount + mf, with input fees and melt methods resolved from the wallet's own state. PaymentRequest.includesMint(url) resolves mint-list membership with URL normalization; both new paths and the docs use it.
…terms Restructures the usage doc: payer story (decode, pay, deliver) and payee story (create, validate) first, the rule-by-rule material moved under a marked Manual control section. Sender/receiver wording was ambiguous (request vs payment) and is now payer/payee throughout the doc, TSDocs and comments.
robwoodgate
force-pushed
the
feat/pay-payment-request
branch
from
July 21, 2026 20:40
693d4d1 to
236de85
Compare
69 tasks
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
Closes the gap between documenting the NUT-18 payer/receiver rules and enforcing them. Until now the docs choreographed four separate steps (mint-list check, melt-method check,
amountToSend,includeFees(true)); a wallet that skipped one could underpay the receiver or violate a spec MUST silently.Payer side:
wallet.ops.sendToRequest(pr, proofs, amount?)builds a send that enforces the request in one step:MintInfo(callers never pass method strings, so the mint-vs-melt and unit-binding mistakes are unrepresentable)mf), waived for listed mintsnut10lock viaasP2PK, throwing on kinds it cannot express (an exotic lock would otherwise send unlocked)includeFees(true))It returns the normal
SendBuilder, so output types, keysets and offline modes chain as usual. Amountless requests take the payer-chosen amount as the third argument.Receiver side:
wallet.isPaymentRequestSatisfied(pr, proofs, expectedAmount?)checkssum(proofs) - inputFees >= amount + mfbefore a payment is treated as settled, with input fees from the wallet's keysets andmfpriced from its melt methods. Scope is the amount only; DLEQ/lock validation stays separate.PaymentRequest.includesMint(url)is the shared primitive: mint-list membership compared after URL normalization (unparsable foreign entries fall back to a raw comparison). The docs' previouspr.mints?.includes(myMint)example had the normalization footgun this removes.Design notes
Wallet/WalletOpsbecause that is where the state lives (mint URL, unit, keyset fees, melt methods). An earlier draft put the settlement check onPaymentRequestand needed three wallet-state options passed in; review moved it.sendToRequestreturns the builder rather than running it, so.includeFees(false)after the fact remains possible: deliberate opt-out, not a hole, and it keeps the builder contract uniform.feesFor,amountToSend,isMintListStrict) are unchanged and remain the manual-control path; docs now lead with the one-step call and keep the manual recipe below it.Tests
sendToRequest: fee quadrants (listed/unlisted, with/without list), URL-normalized strict-list matching, unit mismatch, unit-bound melt rejection, amountless handling, lock honour/reject.isPaymentRequestSatisfied: net-of-fees formula against a 1000 ppk keyset (the spec's dust scenario),mfinside/outside the mint list, unit mismatch, amountless expectation.includesMint: case/trailing-slash normalization, unparsable entries, absent list.