{"id":"de346a2dfe6440fb68909cd1d3f14a58f7a381ae902c37787ecc31c3bfea9f3a","pubkey":"d1678e7ef965374bbea308a1215609a78376dc158277a7d657680f9d5efd5c38","created_at":1784648067,"kind":30817,"tags":[["d","nut-04"],["title","NUT-04: Mint tokens"],["summary","Minting: the two-step flow where a wallet requests a quote, pays it, and then asks the mint to issue ecash."],["s","mandatory"],["t","cashu"],["t","ecash"],["t","nut"],["alt","A specification: NUT-04: Mint tokens"],["client","openspecs-import"],["published_at","1674849737"],["proxy","https://github.com/cashubtc/nuts/blob/a845dfc998abae501fc3419592d53dc995d34b12/04.md","web"],["x","75c872649ac46123a6485aa1748cf59501233f985582b76368c823a844f7012a"]],"content":"# NUT-04: Mint tokens\n\n`mandatory`\n\n`used in: NUT-20, NUT-23`\n\n---\n\nMinting tokens is a two-step process: requesting a mint quote and minting new tokens. This document describes the general flow that applies to all payment methods, with specifics for each supported payment method provided in dedicated NUTs.\n\n## Supported methods\n\nMethod-specific NUTs describe how to handle different payment methods. The currently specified models are:\n\n- [NUT-23][23] for bolt11 Lightning invoices\n- [NUT-25][25] for bolt12 Lightning offers\n- [NUT-30][30] for onchain Bitcoin transactions\n\n## General Flow\n\nThe minting process follows these steps for all payment methods:\n\n1. The wallet requests a mint quote for the `unit` to mint, specifying the payment `method`.\n2. The mint responds with a quote that includes a `quote` id and a payment `request`.\n3. The user pays the `request` using the specified payment method.\n4. The wallet then requests minting of new tokens with the mint, including the `quote` id and new `outputs`.\n5. The mint verifies payment and returns blind signatures.\n\n## Common Request and Response Formats\n\n### Requesting a Mint Quote\n\nTo request a mint quote, the wallet of `Alice` makes a `POST /v1/mint/quote/{method}` request where `method` is the payment method requested (e.g., `bolt11`, `bolt12`, etc.). `method` **MUST** match `[a-z0-9_-]+`.\n\n```http\nPOST https://mint.host:3338/v1/mint/quote/{method}\n```\n\nDepending on the payment method, the request structure may vary, but all methods will include at minimum:\n\n```json\n{\n  \"unit\": <str_enum[UNIT]>,\n  \"amount\": <int>,        // Optional\n  \"description\": <str>,   // Optional\n  \"pubkey\": <str>         // Optional, NUT-20\n  // Additional method-specific fields may be required\n}\n```\n\n`amount`, `description` and `pubkey` are common optional fields; method-specific NUTs make them required or ignore them as needed (e.g. NUT-23 requires `amount`, NUT-20 defines `pubkey`).\n\nThe mint `Bob` responds with a quote that includes some common fields for all methods:\n\n```json\n{\n  \"quote\": <str>, // UUID v7\n  \"request\": <str>,\n  \"unit\":  <str_enum[UNIT]>,\n  \"expiry\": <int|null>,\n  \"pubkey\": <str>,             // Optional\n  \"method\": <str>,\n  \"amount_paid\": <int>,\n  \"amount_issued\": <int>,\n  \"updated_at\": <int>,\n  // Additional method-specific fields will be included\n}\n```\n\nWhere:\n\n- `quote` is the quote ID in [UUIDv7](https://www.rfc-editor.org/rfc/rfc9562) format\n- `request` is the payment request for the quote\n- `unit` corresponds to the value provided in the request\n- `expiry` is the Unix timestamp until which the quote is valid (`null` if it does not expire)\n- `method` is the payment method of the quote\n- `amount_paid` is the total amount that has been paid to the mint for this quote, denominated in `unit`\n- `amount_issued` is the total amount of ecash that has been issued for this quote, denominated in `unit`\n- `updated_at` is a Unix timestamp integer indicating when the quote was last updated\n\nMints **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`.\n\nThe 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.\n\nMints **MUST** update `updated_at` whenever `amount_paid` or `amount_issued` changes. Mints **MUST** 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.\n\n> [!CAUTION]\n>\n> `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.\n\n### Check Mint Quote\n\nTo check the current accounting data of a mint quote, the wallet makes a `GET /v1/mint/quote/{method}/{quote_id}`.\n\n```http\nGET https://mint.host:3338/v1/mint/quote/{method}/{quote_id}\n```\n\nThe mint responds with the same structure as the initial quote response.\n\n### Executing a Mint Quote\n\nAfter requesting a mint quote and paying the request, the wallet proceeds with minting new tokens by calling the `POST /v1/mint/{method}` endpoint.\n\n```http\nPOST https://mint.host:3338/v1/mint/{method}\n```\n\nThe wallet includes the following common data in its request:\n\n```json\n{\n  \"quote\": <str>,\n  \"outputs\": <Array[BlindedMessage]>\n}\n```\n\nwith 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`.\n\nThe mint then responds with:\n\n```json\n{\n  \"signatures\": <Array[BlindSignature]>\n}\n```\n\nwhere `signatures` is an array of blind signatures on the outputs.\n\n## Custom Payment Methods\n\nPayment methods not specified in a dedicated NUT can be supported as custom payment methods. A custom payment method is identified by a lowercase string `{method}` (e.g., `paypal`, `stripe`, `onchain-payment-processor`). The `{method}` string **MUST** contain only ASCII alphanumeric characters, hyphens (`-`), and underscores (`_`), and **MUST** be non-empty.\n\n### Mint Quote\n\nFor 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.\n\nThe mint responds with the common mint quote response format and **MUST** include the `amount_paid`, `amount_issued` and `updated_at` accounting 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 are defined by the method-specific NUT.\n\n### Method-Specific Fields\n\nCustom payment methods **MAY** include additional fields in requests and responses. Implementations **MUST** ignore unrecognized fields to preserve forward compatibility. When a custom method gains widespread adoption, its fields **MAY** be formalized in a dedicated NUT.\n\nCustom payment methods **MAY** include extra fields that the mint forwards to a third party payment processor without validation.\n\n## Adding New Payment Methods\n\nTo add a new payment method (e.g., BOLT12), implement the following:\n\n1. Define the method-specific request and response structures following the pattern above\n2. Implement the three required endpoints: quote request, quote check, and mint execution\n3. Update the settings to include the new method\n\n## Settings\n\nThe settings for this NUT indicate the supported method-unit pairs for minting. They are part of the info response of the mint ([NUT-06][06]) which reads:\n\n```json\n{\n  \"4\": {\n    \"methods\": [\n      <MintMethodSetting>,\n      ...\n    ],\n    \"disabled\": <bool>\n  }\n}\n```\n\n`MintMethodSetting` indicates supported `method` and `unit` pairs and additional settings of the mint. `disabled` indicates whether minting is disabled.\n\n`MintMethodSetting` is of the form:\n\n```json\n{\n  \"method\": <str>,\n  \"unit\": <str>,\n  \"method_name\": <str|null>,\n  \"min_amount\": <int|null>,\n  \"max_amount\": <int|null>,\n  \"options\": <Object|null>\n}\n```\n\n`min_amount` and `max_amount` indicate the minimum and maximum amount for an operation of this method-unit pair. `options` are method-specific and can be defined in method-specific NUTs.\n\n`method_name` is a human-readable name for the payment method. If `null` or omitted, wallets **SHOULD** derive it from `method` by replacing `_` and `-` with spaces and title-casing each word (e.g. `bolt11` -> `Bolt11`, `apple-pay` -> `Apple Pay`).\n\n## Unblinding Signatures\n\nUpon 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.\n\n[00]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crqqpd6d9\n[06]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crv3ltsuz\n[17]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cnwy0vdm5\n[19]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cnjqszllq\n[20]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595erqzryd5a\n[23]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595erx7v8gg2\n[25]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595er20jpzed\n[30]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595enqykznqx\n","sig":"1975e08e26d9f62e0f4f5d23d41d91e99c8a9a4c62a6c9649c922b95f009d84e66177fe32d5b7d68114fc1968f717b8444ee99dca89bd7971c83e8a7a77eddc6"}