Skip to content

Conversation

@vnprc
Copy link

@vnprc vnprc commented Dec 12, 2025

This document specifies NUT-XX: Batched Mint for the Cashu ecash protocol. It defines how wallets can mint multiple ecash proofs in a single atomic operation instead of individual requests.

Key components:

  1. Quote status checking - endpoint to verify payment status for multiple mint quotes at once
  2. Batch minting - atomic endpoint to mint all quotes together or fail entirely (no partial minting)
  3. Validation rules - strict requirements including:
    • non-empty unique quote arrays
    • consistent payment methods and currency units
    • balanced amounts between quotes and outputs
  4. Error handling - comprehensive error codes and structured error responses for debugging
  5. NUT-20 signature support - handles authentication signatures for locked quotes, with per-quote signatures covering all outputs
  6. Implementation guidance - batch size limits (discoverable via mint info), BOLT12 partial minting support, and compatibility with spending conditions (P2PK, HTLC)

The spec emphasizes atomic processing (all-or-nothing), detailed error reporting, and interoperability with existing Cashu specs (NUT-04, NUT-20, NUT-11, NUT-14, NUT-25).

Egge21M and others added 5 commits July 9, 2025 12:45
This commit introduces a new specification, NUT-XX, for batched mint
operations. It allows wallets to mint multiple proofs in a single
transaction, improving efficiency.
- specify all quotes must share same payment method and currency
- expand nut-20 signature section with detailed structure and validation
- clarify signature array format and per-quote mapping
- add signature message construction specification
- document atomicity requirement for signature validation failures
- add comprehensive mint responsibilities section
- update dependencies to include nut-20
- add validation requirements for empty arrays and duplicate quotes
- clarify batch quote status response format per payment method
- change batch mint endpoint path to include /batch suffix
- add comprehensive request validation section
- add error codes table and structured error response format
- restructure nut-20 signature support section for clarity
- add atomic processing requirement to mint responsibilities
- add implementation notes for batch size limits and bolt12 support
- add note on spending conditions compatibility
- expand error handling documentation with examples
@vnprc
Copy link
Author

vnprc commented Dec 12, 2025

This PR replaces #273

xx.md Outdated

## 1. Checking Quote Status

Before minting, the wallet must verify that each mint quote has been paid.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be a must, a wallet could know the states of the quotes by ws update why must it check again?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be SHOULD. I think its good to mention that this endpoint is not supposed to be a batch-check

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this endpoint is not supposed to be a batch-check

What do you mean? I thought the purpose was to ensure that a batch was ready to mint before attempting to mint it.

Comment on lines 164 to 180
Unknown quote:
```json
{
"code": "UNKNOWN_QUOTE",
"detail": "Quote 'abc-123' does not exist",
"quote": "abc-123"
}
```

Quote not paid:
```json
{
"code": "QUOTE_NOT_PAID",
"detail": "Quote 'xyz-789' is not in PAID state",
"quote": "xyz-789"
}
```
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if multiple quotes are unknown or unpaid? I think it maybe best to not extend out error response and leave it how it is defined in nut00 without a quote id and its up to the wallet to figure it out.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking was that debugging one failing quote ID from a batch of 100 could be a huge pain in the ass. Best effort error reporting could help in some cases. I can see an argument for improving the batch check endpoint for this purpose instead of attempting detailed error messages in the mint endpoint. If you think we shouldn't even try, you're gonna have to convince me.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as discussed in the meeting, we should remove quote ID as a field but still allow quote IDs in the description for human consumption

Comment on lines +230 to +236
```
msg_to_sign = quote_id[i] || B_0 || B_1 || ... || B_(n-1)
```

Where:
- `quote_id[i]` is the UTF-8 encoded quote ID at index `i`
- `B_0 ... B_(n-1)` are **all blinded messages** from the `outputs` array (regardless of amount splitting)
Copy link
Collaborator

@thesimplekid thesimplekid Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to redefine it reference the existing nut.

We should add test vectors for this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I missing something here? I agree that there is no need to re-define it

xx.md Outdated
Comment on lines 311 to 320
### BOLT12 Support

**Partial minting support:**

Mints MAY support partial minting for BOLT12 quotes, where `amount_issued` can be less than `amount_paid`. This allows:
- **Multiple batch operations**: Same BOLT12 quote can be used in multiple mint requests
- **Incremental minting**: Each batch mint increments `amount_issued` by the minted amount
- **State tracking**: Quote remains in PAID state until `amount_issued >= amount_paid`

The mint tracks `amount_paid` and `amount_issued` separately for this purpose.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove this as its defined in the bolt12 nut.

vnprc and others added 2 commits December 15, 2025 23:10
Co-authored-by: tsk <tsk@thesimplekid.com>
Co-authored-by: lollerfirst <43107113+lollerfirst@users.noreply.github.com>
@vnprc
Copy link
Author

vnprc commented Dec 16, 2025

@lollerfirst as discussed in a recent meeting, the wallet can specify the outputs they want to mint so this precludes the possibility of partial success. Atomic batch mint transactions also massively reduce complexity around partial success.

- wallet SHOULD verify paid quotes, not must
- remove redundant requirements and unnecessary statements
- deduplicate request validation and mint responsibilities
- remove http status from error codes
"quotes": [ "quote_id_1", "quote_id_2", … ],
"quote_amounts": [ 50, 50 ],
"outputs": [ BlindedMessage_1, BlindedMessage_2, … ],
"signatures": [signature_1, signature_2, ... ]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if some mint quotes are not locked? maybe: for a quote_id_n that does not require a NUT-20 lock, the signature at the same index should be null.

Copy link
Contributor

@callebtc callebtc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good PR but text still reads AI-sloppy, could probably be compressed to 50% dense human spec.

- **All quotes MUST be from the same payment method** (indicated by `{method}` in the URL path).
- **All quotes MUST use the same currency unit**.
- **quote_amounts**: array of expected mint amounts per quote, in the same order as `quote`.
- REQUIRED for bolt12 batches; OPTIONAL for bolt11.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional quote_amounts and signatures should be annotated in the JSON itself

Comment on lines +79 to +83
{
"quotes": [ "quote_id_1", "quote_id_2", … ],
"quote_amounts": [ 50, 50 ],
"outputs": [ BlindedMessage_1, BlindedMessage_2, … ],
"signatures": [signature_1, signature_2, ... ]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should use the same JSON annotation format that we use in other specs

- For bolt11, each entry MUST equal the quoted amount. For bolt12, each entry MUST NOT exceed the quote's remaining mintable amount. In all cases, the sum of `quote_amounts` MUST equal the sum of `outputs`.
- **outputs**: an array of blinded messages (see [NUT-00][00]).
- The total value represented by all blinded messages must equal the sum of all quote amounts.
- **signatures**: array of signatures for NUT-20 locked quotes. See [NUT-20 Support][nut-20-support]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional

Comment on lines +88 to +91
- Array MUST NOT be empty
- All quote IDs MUST be unique (no duplicates)
- **All quotes MUST be from the same payment method** (indicated by `{method}` in the URL path).
- **All quotes MUST use the same currency unit**.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant here, there's a validation section below

4. **Payment method consistency**: All quotes MUST have the same payment method, matching `{method}` in the URL path
5. **Currency unit consistency**: All quotes MUST use the same currency unit
6. **Quote state**: All quotes MUST be in PAID state (or have mintable amount for BOLT12)
7. **Amount balance**: The sum of output amounts MUST equal the sum of `quote_amounts` (bolt11) or MUST NOT exceed it (bolt12)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

amounts should probably be ignored for bolt11?

| Signature on unlocked quote | `UNEXPECTED_SIGNATURE` |
| Missing required signature | `SIGNATURE_MISSING` |

### Error Response Format
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems too excessive, would remove and use a simpler approach

### Signature Structure

**Array structure:**
- The `signature` field is an array with length equal to `quote.length` (one entry per quote)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's called signatures (plural) above


**Per-quote signatures:**
- **Locked quotes** (with `pubkey`): `signature[i]` contains the signature string
- **Unlocked quotes**: `signature[i]` is `null`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggested null above too, should be defined above.


**Array structure:**
- The `signature` field is an array with length equal to `quote.length` (one entry per quote)
- `signature[i]` corresponds to `quote[i]`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quotes[i]

- Total: 225 sats across 3 outputs
- Both signatures cover all 3 outputs to prevent output tampering

## Mint Responsibilities
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this section is not needed. atomicity should be mentioned very briefly in the first paragraph.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants