Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 32 additions & 15 deletions 04.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,47 @@ The mint `Bob` responds with a quote that includes some common fields for all me
"quote": <str>, // UUID v7
"request": <str>,
"unit": <str_enum[UNIT]>,
"amount_paid": <int>,
"amount_issued": <int>,
"updated_at": <int>,
"state": <str_enum[STATE]>, // Deprecated, optional

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

State should not be in nut04 that is in nut 23 as it is bolt11 specefic.

// Additional method-specific fields will be included
}
```

Where `quote` is the quote ID string in UUID v7 format, `request` is the payment request for the quote, and `unit` corresponds to the value provided in the request.
Where:

- `quote` is the quote ID in UUID v7 format
- `request` is the payment request for the quote
- `unit` corresponds to the value provided in the request
- `amount_paid` is the total amount that has been paid to the mint for this quote and is eligible for minting, denominated in `unit`
- `amount_issued` is the total amount of ecash that has been issued for this quote, denominated in `unit`
- `updated_at` is a Unix timestamp integer indicating when the quote was last updated
- `state` is a deprecated compatibility field

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
- `state` is a deprecated compatibility field


Mints **MUST** include `amount_paid`, `amount_issued`, and `updated_at` in all mint quote responses. `amount_paid` and `amount_issued` **MUST** be non-negative integers, and `amount_issued` **MUST NOT** exceed `amount_paid`.

The amount currently mintable for a quote is `amount_paid - amount_issued`. Mints **MUST NOT** issue ecash whose total output amount exceeds `amount_paid - amount_issued`. If a wallet mints less than the currently mintable amount, `amount_issued` only increases by the amount that was issued.

`amount_paid` and `amount_issued` are the canonical way to track the state of a mint quote. The deprecated `state` field **SHOULD** be included for single-use quotes for compatibility with older clients. Wallets **SHOULD** use `amount_paid` and `amount_issued` instead of `state` whenever these fields are present.

For single-use quotes, the deprecated finite `state` can be derived from `amount_paid` and `amount_issued`:

- `"UNPAID"` if `amount_paid == 0` and `amount_issued == 0`
- `"PAID"` if `amount_paid > amount_issued`
- `"ISSUED"` if `amount_paid == amount_issued` and `amount_issued > 0`

For reusable quotes, no finite `state` can fully represent whether the quote may receive future payments. Wallets **MUST** use `amount_paid` and `amount_issued` to determine the currently mintable amount.
Comment on lines +76 to +85

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nut23 specefic


Mints **MUST** update `updated_at` whenever `amount_paid` or `amount_issued` changes. Mints **SHOULD** ensure that `updated_at` monotonically increases for each quote, even if multiple updates occur within the same timestamp resolution. Wallets that receive multiple responses for the same quote **MUST NOT** replace locally stored quote data with a response whose `updated_at` is lower than the latest processed value for that quote. Wallets **MUST NOT** decrease locally stored `amount_paid` or `amount_issued` values based on stale responses.

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

TBH I am not sure how to handle this properly. This document is now written in a way that makes updated_at mandatory (e.g. Mints **MUST** include). Including this addition would mean covering off-spec mint behavior. Maybe the new fields should be added as optional an with SHOULD language instead?

@robwoodgate robwoodgate Jun 13, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

On reflection you are right. Handling out of spec older mints is not the protocol's concern.

Ignore my suggestion - leave it as it was.


> [!CAUTION]
>
> `quote` is a **unique and random** id generated by the mint to internally look up the payment state. `quote` **SHOULD** be UUID v7 with all 74 variable bits generated by a CSPRNG and **MUST** remain a secret between user and mint and **MUST NOT** be derivable from the payment request. A third party who knows the `quote` ID can front-run and steal the tokens that this operation mints. To prevent this, use [NUT-20][20] locks to enforce public key authentication during minting.

### Check Mint Quote State
### Check Mint Quote

To check whether a mint quote has been paid, the wallet makes a `GET /v1/mint/quote/{method}/{quote_id}`.
To check the current accounting state of a mint quote, the wallet makes a `GET /v1/mint/quote/{method}/{quote_id}`.

```http
GET https://mint.host:3338/v1/mint/quote/{method}/{quote_id}
Expand All @@ -89,7 +117,7 @@ The wallet includes the following common data in its request:
}
```

with the `quote` being the quote ID from the previous step and `outputs` being `BlindedMessages` (see [NUT-00][00]) that the wallet requests signatures on, whose sum is `amount` as requested in the quote.
with the `quote` being the quote ID from the previous step and `outputs` being `BlindedMessages` (see [NUT-00][00]) that the wallet requests signatures on. The total output amount **MUST NOT** exceed the quote's currently mintable amount, `amount_paid - amount_issued`.

The mint then responds with:

Expand Down Expand Up @@ -146,18 +174,7 @@ The settings for this NUT indicate the supported method-unit pairs for minting.
Upon receiving the `BlindSignatures` from the mint, the wallet unblinds them to generate `Proofs` (using the blinding factor `r` and the mint's public key `K`, see BDHKE [NUT-00][00]). The wallet then stores these `Proofs` in its database.

[00]: 00.md
[01]: 01.md
[02]: 02.md
[03]: 03.md
[04]: 04.md
[05]: 05.md
[06]: 06.md
[07]: 07.md
[08]: 08.md
[09]: 09.md
[10]: 10.md
[11]: 11.md
[12]: 12.md
[20]: 20.md
[23]: 23.md
[25]: 25.md
Expand Down
8 changes: 7 additions & 1 deletion 20.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ The mint `Bob` then responds with a `PostMintQuoteBolt11Response`:
{
"quote": <str>,
"request": <str>,
"state": <str_enum[STATE]>,
"amount_paid": <int>,
"amount_issued": <int>,
"updated_at": <int>,
"state": <str_enum[STATE]>, // Deprecated, optional
Comment thread
thesimplekid marked this conversation as resolved.
"expiry": <int>,
"pubkey": <str|null> // Optional <-- New
}
Expand All @@ -67,6 +70,9 @@ Response of `Bob`:
{
"quote": "9d745270-1405-46de-b5c5-e2762b4f5e00",
"request": "lnbc100n1pj4apw9...",
"amount_paid": 0,
"amount_issued": 0,
"updated_at": 1701704657,
"state": "UNPAID",
"expiry": 1701704757,
"pubkey": "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac"
Expand Down
16 changes: 13 additions & 3 deletions 23.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,24 @@ The mint responds with a `PostMintQuoteBolt11Response`:
"request": <str>, // The bolt11 invoice to pay
"amount": <int>,
"unit": <str_enum[UNIT]>,
"state": <str_enum[STATE]>,
"amount_paid": <int>,
"amount_issued": <int>,
"updated_at": <int>,
"state": <str_enum[STATE]>, // Deprecated, optional
"expiry": <int|null>
}
```

`state` is an enum string field with possible values `"UNPAID"`, `"PAID"`, `"ISSUED"`:
`amount_paid`, `amount_issued`, and `updated_at` are defined in [NUT-04][04].

`state` is a deprecated enum string field with possible values `"UNPAID"`, `"PAID"`, `"ISSUED"`:

- `"UNPAID"` means that the quote's request has not been paid yet.
- `"PAID"` means that the quote's request has been paid but the ecash is not issued yet.
- `"ISSUED"` means that the quote has been paid and the ecash has been issued.

Wallets **SHOULD** use `amount_paid` and `amount_issued` instead of `state` whenever these fields are present.

`expiry` is the Unix timestamp until which the `request` can be paid (i.e. the bolt11 invoice expiry).

### Example
Expand All @@ -57,12 +64,15 @@ Response:
"request": "lnbc100n1pj4apw9...",
"amount": 10,
"unit": "sat",
"amount_paid": 0,
"amount_issued": 0,
"updated_at": 1701704657,
"state": "UNPAID",
"expiry": 1701704757
}
```

Check quote state:
Check mint quote:

```bash
curl -X GET http://localhost:3338/v1/mint/quote/bolt11/019e6d5a-2347-7000-8322-05d51d498303
Expand Down
9 changes: 6 additions & 3 deletions 25.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ The mint responds with a `PostMintQuoteBolt12Response`:
"expiry": <int|null>,
"pubkey": <str>,
"amount_paid": <int>,
"amount_issued": <int>
"amount_issued": <int>,
"updated_at": <int>
}
```

Expand All @@ -47,6 +48,7 @@ Where:
- `expiry` is the Unix timestamp until which the mint quote is valid
- `amount_paid` is the amount that has been paid to the mint via the bolt12 offer
- `amount_issued` is the amount of ecash that has been issued for the given mint quote
- `updated_at` is defined in [NUT-04][04]

### Example

Expand All @@ -69,11 +71,12 @@ curl -X POST http://localhost:3338/v1/mint/quote/bolt12 -d \
"expiry": 1701704757,
"pubkey": "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac",
"amount_paid": 0,
"amount_issued": 0
"amount_issued": 0,
"updated_at": 1701704657
}
```

Check quote state:
Check mint quote:

```bash
curl -X GET http://localhost:3338/v1/mint/quote/bolt12/019e6d5a-2347-7000-8449-370fefb42fed
Expand Down
12 changes: 9 additions & 3 deletions 29.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ This spec describes how a wallet can mint multiple quotes in one batched operati

---

## 1. Batch Checking Quote State
## 1. Batch Checking Mint Quotes

Before minting, the wallet SHOULD verify that each mint quote has been paid. It does this by sending:
Before minting, the wallet SHOULD verify each mint quote's current accounting state. It does this by sending:

```http
POST https://mint.host:3338/v1/mint/quote/{method}/check
Expand All @@ -28,7 +28,7 @@ The wallet includes the following body in its request:

where `quotes` is an array of _unique_ mint quote IDs.

The mint returns a JSON array of mint quotes objects as defined by the payment method's NUT specification. The quotes in this array MUST be in the same order as in the request.
The mint returns a JSON array of mint quote objects as defined by the payment method's NUT specification. The quotes in this array MUST be in the same order as in the request.

#### Example

Expand All @@ -52,6 +52,9 @@ Content-Type: application/json
{
"quote": "019e6d5a-2347-7000-8037-b42dae20f1fe",
"request": "lnbc...",
"amount_paid": 100,
"amount_issued": 0,
"updated_at": 1234567800,
"state": "PAID",
"unit": "sat",
"amount": 100,
Expand All @@ -60,6 +63,9 @@ Content-Type: application/json
{
"quote": "019e6d5a-2347-7000-868a-08e59a1c6716",
"request": "lnbc...",
"amount_paid": 0,
"amount_issued": 0,
"updated_at": 1234567800,
"state": "UNPAID",
"unit": "sat",
"amount": 50,
Expand Down
11 changes: 7 additions & 4 deletions 30.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ The mint responds with a `PostMintQuoteOnchainResponse`:
"expiry": <int|null>,
"pubkey": <str>,
"amount_paid": <int>,
"amount_issued": <int>
"amount_issued": <int>,
"updated_at": <int>
}
```

Expand All @@ -43,6 +44,7 @@ Where:
- `pubkey` is the public key from the request
- `amount_paid` is the total confirmed amount paid to the request in UTXOs that are eligible for minting
- `amount_issued` is the amount of ecash that has been issued for the given mint quote
- `updated_at` is defined in [NUT-04][04]

If `expiry` is not `null`, the wallet **SHOULD NOT** send payments to the request after `expiry`. Mints **MUST** keep monitoring transactions they detected before `expiry` until the transaction reaches the required number of confirmations or is evicted or replaced. Payments first detected by the mint after `expiry` **MUST NOT** increase `amount_paid`.

Expand All @@ -66,19 +68,20 @@ curl -X POST http://localhost:3338/v1/mint/quote/onchain -d \
"expiry": 1701704757,
"pubkey": "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac",
"amount_paid": 0,
"amount_issued": 0
"amount_issued": 0,
"updated_at": 1701704657
}
```

Check quote state:
Check mint quote:

```bash
curl -X GET http://localhost:3338/v1/mint/quote/onchain/019e6d5a-2347-7000-8850-39c85ed1b5d3
```

### Minting Tokens

The quote state will only update to show `amount_paid` once a Bitcoin transaction has reached the minimum number of confirmations specified in the mint's settings.
The quote accounting will only update to show `amount_paid` once a Bitcoin transaction has reached the minimum number of confirmations specified in the mint's settings.

If the `onchain` mint method has a `min_amount` setting, each UTXO paid to the quote address is evaluated independently against `min_amount`. UTXOs with an amount less than `min_amount` **MUST NOT** increase `amount_paid` and **MUST NOT** count towards the mintable balance for the quote. Multiple UTXOs below `min_amount` **MUST NOT** be aggregated to reach `min_amount`.

Expand Down
Loading