Skip to content

custom payment methods#382

Open
asmogo wants to merge 1 commit into
cashubtc:mainfrom
asmogo:generic-payment-method
Open

custom payment methods#382
asmogo wants to merge 1 commit into
cashubtc:mainfrom
asmogo:generic-payment-method

Conversation

@asmogo

@asmogo asmogo commented May 27, 2026

Copy link
Copy Markdown
Contributor

support custom payment methods not defined in a dedicated NUT.

@robwoodgate robwoodgate 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.

I think this is a fantastic step forward for wallets to be able to better support unknown methods - I've added some suggested tweaks to the base structs to make life even better.

Comment thread 04.md

### Websocket Notifications

Custom methods **MAY** support websocket subscriptions via [NUT-17][17] with kind `"{method}_mint_quote"`.

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.

Related: #372

Comment thread 05.md

### Websocket Notifications

Custom methods **MAY** support websocket subscriptions via [NUT-17][17] with kind `"{method}_melt_quote"`.

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.

Related: #372

Comment thread 04.md
Comment on lines 42 to 45
{
"unit": <str_enum[UNIT]>
// Additional method-specific fields may be required
}

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.

To better support custom, I think we should expand the base request something like:

Suggested change
{
"unit": <str_enum[UNIT]>,
"amount": <int>, // Optional
"description": <str>, // Optional
"pubkey": <str> // Optional (NUT-20)
}

Then NUT specific methods can enforce / ignore as needed - eg: NUT-23 makes amount required., NUT-25 makes pubkey required.

Comment thread 04.md
Comment on lines 51 to 56
{
"quote": <str>,
"request": <str>,
"unit": <str_enum[UNIT]>,
// Additional method-specific fields will be included
}

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.

Custom would be better supported if the base reply is more like:

Suggested change
{
"quote": <str>,
"request": <str>,
"unit": <str_enum[UNIT]>,
"expiry": <int|null>,
"pubkey": <str>, // Optional (NUT-20)
"amount_paid": <int>,
"amount_issued": <int>
// Additional method-specific fields will be included
}

Comment thread 05.md
Comment on lines 47 to 51
{
"request": <str>,
"unit": <str_enum[UNIT]>
// Additional method-specific fields will be required
}

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.

Melt base suggestion for better custom handling:

Suggested change
{
"request": <str>,
"unit": <str_enum[UNIT]>,
"amount": <int>, // Optional
// Additional method-specific fields will be required
}

Comment thread 05.md
Comment on lines 57 to 67
{
"quote": <str>,
"amount": <int>,
"unit": <str_enum[UNIT]>,
"state": <str_enum[STATE]>,
"expiry": <int>
// Additional method-specific fields will be included
}
```

Where `quote` is the quote ID, `amount` and `unit` the amount and unit that need to be provided, and `expiry` is the Unix timestamp until which the melt quote is valid.

@robwoodgate robwoodgate May 28, 2026

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.

Melt response base suggestion for better custom method handling:

Suggested change
{
"quote": <str>,
"request": <str>,
"amount": <int>,
"unit": <str_enum[UNIT]>,
"fee_reserve": <int>, // Optional
"state": <str_enum[STATE]>,
"expiry": <int>
// Additional method-specific fields will be included
}
Where `quote` is the quote ID, `amount` and `unit` the amount and unit that need to be provided, and `expiry` is the Unix timestamp until which the melt quote is valid.
`request` contains the method-specific payment routing instructions (eg bolt11 Lightning invoice, Bitcoin onchain address, bank account details, paypal email etc).
`fee_reserve` is the additional fee for using the method. The mint expects the wallet to include `Proofs` of _at least_ `total_amount = amount + fee_reserve + fee` where `fee` is calculated from the keyset's `input_fee_ppk` as described in [NUT-02][02].

@robwoodgate

Copy link
Copy Markdown
Contributor

Sorry @asmogo - I questioned an earlier draft that included method in the quote structs (thinking mint and wallet know by virtue of the endpoint used to request it), but this related issue highlights that for WS, and especially with the suggestion on harmonizing the base structs for custom methods, the distinction is easy to blur/lose.

So maybe we DO need to include method: <str>, in the quote structs for both mint and melt.

@robwoodgate

Copy link
Copy Markdown
Contributor

Related #377

which adds the amount_oaid, amount_issued, updated_at parms to default quote structs

@robwoodgate robwoodgate 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.

Have implemented custom method support in cashu-ts on top of this + #377 + #387 (cashubtc/cashu-ts#693) and it works nicely. Wallets can quote, mint and melt methods they don't fully understand using just the base structs.

A few tweaks below that came out of implementing. Note the suggestions assume #377 merges first.

Related to my base struct suggestions above: expiry really wants to be in the common mint quote response - every method has one, and a wallet showing a quote for an unknown method needs to know when to stop polling. With #377 in, it's the only field from the suggested base response still left as method-specific.

(method in the quote structs is now covered by #387, which also resolves the WS routing question from #378.)

Can also confirm the optional fee_reserve in the melt base works in practice - onchain can't make it required (it uses fee_options), and bolt11/12 tighten it to required in their own NUTs.

Comment thread 04.md

For a custom `{method}`, the wallet sends a request following the common mint quote request format (see [General Flow](#general-flow)). Method-specific fields (e.g., an `amount` of tokens to mint, a `description`, or a `pubkey` for [NUT-20][20] locks) are defined by the method-specific NUT.

The mint responds with the common mint quote response format. The `request` field contains the method-specific payment request (e.g., a payment URL, an on-chain address, an account identifier). Additional method-specific fields (such as `amount` or `expiry`) are defined by the method-specific NUT.

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.

Wallets can derive the accounting fields from state for methods they know, but for an unknown method there's no fallback, so these need to be explicit. Suggest:

Suggested change
The mint responds with the common mint quote response format. The `request` field contains the method-specific payment request (e.g., a payment URL, an on-chain address, an account identifier). Additional method-specific fields (such as `amount` or `expiry`) are defined by the method-specific NUT.
The mint responds with the common mint quote response format, and **MUST** include the `amount_paid`, `amount_issued` and `updated_at` fields. The `request` field contains the method-specific payment request (e.g., a payment URL, an on-chain address, an account identifier). Additional method-specific fields (such as `amount` or `expiry`) are defined by the method-specific NUT.

Comment thread 05.md

For a custom `{method}`, the wallet sends a request following the common melt quote request format (see [General Flow](#general-flow)). The `request` field is the method-specific payment target (e.g., a bank account identifier, an on-chain address, a payment processor reference). `unit` is the unit the wallet would like to pay with.

The mint responds with the common melt quote response format. Method-specific fields are defined by the method-specific NUT.

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.

Wallets can't interpret a custom state machine for a method they don't understand, so custom melts need the standard states. Suggest:

Suggested change
The mint responds with the common melt quote response format. Method-specific fields are defined by the method-specific NUT.
The mint responds with the common melt quote response format, using the standard `state` values (`"UNPAID"`, `"PENDING"`, `"PAID"`). Method-specific fields are defined by the method-specific NUT.

robwoodgate added a commit to robwoodgate/cashu-ts that referenced this pull request Jun 12, 2026
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.
robwoodgate added a commit to robwoodgate/cashu-ts that referenced this pull request Jun 12, 2026
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.
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.

2 participants