Cashu Book of NUTs

NUT-29: Batched Minting

optional

depends on: NUT-04

uses: NUT-20


This spec describes how a wallet can mint multiple quotes in one batched operation by requesting blind signatures for multiple quotes in a single atomic request.

1. Batch Checking Mint Quotes

Before minting, the wallet SHOULD verify each mint quote’s current accounting state. It does this by sending:

POST https://mint.host:3338/v1/mint/quote/{method}/check

The wallet includes the following body in its request:

{
  "quotes": <Array[str]>
}

where quotes is an array of unique mint quote IDs.

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

Below is an example for checking two bolt11 mint quotes.

Request
POST https://mint.host:3338/v1/mint/quote/bolt11/check
Content-Type: application/json

{
  "quotes": [ "019e6d5a-2347-7000-8037-b42dae20f1fe", "019e6d5a-2347-7000-868a-08e59a1c6716" ]
}
Response
[
  {
    "quote": "019e6d5a-2347-7000-8037-b42dae20f1fe",
    "request": "lnbc...",
    "amount_paid": 100,
    "amount_issued": 0,
    "updated_at": 1234567800,
    "state": "PAID",
    "unit": "sat",
    "amount": 100,
    "expiry": 1234567890
  },
  {
    "quote": "019e6d5a-2347-7000-868a-08e59a1c6716",
    "request": "lnbc...",
    "amount_paid": 0,
    "amount_issued": 0,
    "updated_at": 1234567800,
    "state": "UNPAID",
    "unit": "sat",
    "amount": 50,
    "expiry": 1234567890
  }
]
Error Handling

This is a query endpoint that uses all-or-nothing error handling, matching the behavior of the batch mint endpoint:


2. Executing the Batched Mint

Request by wallet

Once all quoted payments are confirmed, the wallet mints the proofs by calling:

POST https://mint.host:3338/v1/mint/{method}/batch

The batch endpoint is method-specific: every quote in the batch MUST be for the same payment method as the {method} in the URL, and a batch that mixes payment methods MUST be rejected by the mint.

The wallet includes the following body in its request:

{
  "quotes": <Array[str]>,
  "quote_amounts": <Array[int]|null>, // Optional
  "outputs": <Array[BlindedMessage]>,
  "signatures": <Array[string]|null> // Optional
}
Response by mint

The mint responds with:

{
  "signatures": <Array[BlindSignature]>
}

Example

Below is an example for minting two NUT-20 locked bolt11 mint quotes.

Request
POST https://mint.host:3338/v1/mint/bolt11/batch
Content-Type: application/json

{
  "quotes": [
    "019e6d5a-2347-7000-8037-b42dae20f1fe",
    "019e6d5a-2347-7000-868a-08e59a1c6716"
  ],
  "quote_amounts": [
    100,
    50
  ],
  "signatures": [
    "d9be080b33179387e504bb6991ea41ae0dd715e28b01ce9f63d57198a095bccc776874914288e6989e97ac9d255ac667c205fa8d90a211184b417b4ffdd24092",
    "f2d97118390195cf5bef21d84c94e505dcdc2760154519536f74ba5e27f886f313b82296610df14db1d91d346e988ed384070bad084aaf06d14ccd7686157f24"
  ],
  "outputs": [
    {
      "amount": 128,
      "id": "009a1f293253e41e",
      "B_": "035015e6d7ade60ba8426cefaf1832bbd27257636e44a76b922d78e79b47cb689d"
    },
    {
      "amount": 16,
      "id": "009a1f293253e41e",
      "B_": "0288d7649652d0a83fc9c966c969fb217f15904431e61a44b14999fabc1b5d9ac6"
    },
    {
      "amount": 4,
      "id": "009a1f293253e41e",
      "B_": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"
    },
    {
      "amount": 2,
      "id": "009a1f293253e41e",
      "B_": "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"
    }
  ]
}
Response
{
  "signatures": [
    "0208657b2917f469f275226cc931e5389451f6eed515d586ad16fa7a700eed4fb6",
    "037dc0b5e712a39ef22f5bba1d49bc65a323ab9e0b771f7281d8c33280e2e58dbc"
  ]
}

Request Validation

The mint MUST validate the following before processing a batch mint request:

  1. Non-empty batch: The quotes array MUST NOT be empty
  2. Unique quotes: All quote IDs in the quotes array MUST be unique (no duplicates) — error code 11016
  3. Valid quote IDs: All quote IDs MUST exist in the mint’s database
  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 a mintable amount for payment methods that allow multiple mint operations like bolt12)
  7. Amount balance: The sum of amounts contained in the outputs MUST equal the sum of quote_amounts (bolt11) or MUST NOT exceed it (bolt12)
  8. Signature validation (NUT-20): The signatures array length MUST match the quotes array length; locked quotes MUST include a valid signature; unlocked quotes MUST NOT include one

Implementations MAY impose additional constraints such as maximum batch size based on their resource limitations. If any validation fails, the mint MUST reject the entire batch and return an appropriate error without minting any quotes.

NUT-20 support

Per NUT-20, quotes can require authentication via signatures. When using batch minting with NUT-20 locked quotes:

Signature Array Structure

Array structure:

Per-quote signatures:

Field requirement:

Signature Message

Each locked quote is signed independently exactly as in NUT-20: signatures[i] is the NUT-20 signature over quotes[i] and the request’s outputs. The batch outputs are a single consolidated set (not partitioned per quote), so each signature is computed over the full outputs array.

Following the NUT-20 message aggregation pattern, the signature message for quotes[i] is computed as:

msg_to_sign = b"Cashu_MintQuoteSig_v1"
              || len32(quotes[i]) || quotes[i]
              || for each output j (in request order):
                   len32(amount_j) || amount_j
                   || len32(B_j)   || B_j

Where:

Signature Validation Failure

If any signature in the batch is invalid, the mint MUST reject the entire batch and return an error. This maintains atomicity: all quotes must be successfully authenticated and minted together, or none at all.

Example

{
  "quotes": [
    "019e6d5a-2347-7000-8005-9428c4fc5edb",
    "019e6d5a-2347-7000-841a-4d222fd00a33",
    "019e6d5a-2347-7000-8d60-d35c1a9e19b3"
  ],
  "outputs": [
    { "amount": 64, "id": "keyset_1", "B_": "..." },
    { "amount": 64, "id": "keyset_1", "B_": "..." },
    { "amount": 22, "id": "keyset_1", "B_": "..." }
  ],
  "signatures": [
    "d9be080b...", // Signature for quote[0], covers ALL 3 outputs
    null, // Quote[1] is unlocked
    "a1c5f7e2..." // Signature for quote[2], covers ALL 3 outputs
  ]
}

Implementation Notes

Batch Size Limits

Mints MAY advertise a maximum batch size through the NUT-06 mint info endpoint. The batch size limit is included in the nuts object under the 29 key:

{
  "nuts": {
    "29": {
      "max_batch_size": 100,
      "methods": ["bolt11", "bolt12"]
    }
  }
}

Fields: