{"id":"7ba091a77be7c15b26347904691b6669e9a64402aba27202e5dce1faf1f1b526","pubkey":"d1678e7ef965374bbea308a1215609a78376dc158277a7d657680f9d5efd5c38","created_at":1784648067,"kind":30817,"tags":[["d","nut-05"],["title","NUT-05: Melting tokens"],["summary","Melting: the two-step flow where a wallet asks the mint to pay a request and spends ecash to cover it."],["s","mandatory"],["t","cashu"],["t","ecash"],["t","nut"],["alt","A specification: NUT-05: Melting tokens"],["client","openspecs-import"],["published_at","1674849737"],["proxy","https://github.com/cashubtc/nuts/blob/a845dfc998abae501fc3419592d53dc995d34b12/05.md","web"],["x","e6fb19f8633c010fe6979f7b11593c75942592da5731d0de41e527587eea370d"]],"content":"# NUT-05: Melting tokens\n\n`mandatory`\n\n`used in: NUT-08, NUT-15, NUT-23`\n\n---\n\nMelting tokens is the opposite of minting tokens (see [NUT-04][04]). Like minting tokens, melting is a two-step process: requesting a melt quote and melting tokens. This document describes the general flow that applies to all payment methods, with specifics for each supported payment method provided in dedicated method-specific 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 melting process follows these steps for all payment methods:\n\n1. The wallet requests a melt quote for a `request` it wants paid by the mint, specifying the payment `method` and the `unit` the wallet would like to spend\n2. The mint responds with a quote that includes a `quote` id and an `amount` demanded in the requested unit\n3. The wallet sends a melting request including the `quote` id and provides `inputs` of the required amount\n4. The mint executes the payment and responds with the payment `state` and any method-specific proof of payment\n\n### Synchronous vs Asynchronous Processing\n\n**Synchronous (Default):** Unless the payment method requires asynchronous execution, the melt request blocks until payment completion. This ensures immediate finality but may result in long request times for slow payment methods.\n\n**Asynchronous:** A method-specific NUT can require asynchronous execution for a payment method. For other methods, the wallet can request asynchronous execution with `prefer_async: true` in the melt request body if it is supported by the mint. In both cases, the request returns immediately after validation with a `\"PENDING\"` state. The wallet must then monitor the payment progress through polling or websocket notifications.\n\n## Common Request and Response Formats\n\n### Requesting a Melt Quote\n\nTo request a melt quote, the wallet of `Alice` makes a `POST /v1/melt/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/melt/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  \"request\": <str>,\n  \"unit\": <str_enum[UNIT]>,\n  \"amount\": <int>   // Optional\n  // Additional method-specific fields will be required\n}\n```\n\n`amount` is a common optional field; method-specific NUTs make it required or ignore it as needed (e.g. NUT-30 requires `amount` for onchain melts).\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  \"amount\": <int>,\n  \"unit\": <str_enum[UNIT]>,\n  \"fee_reserve\": <int>,     // Optional\n  \"method\": <str>,\n  \"state\": <str_enum[STATE]>,\n  \"expiry\": <int>\n  // Additional method-specific fields will be included\n}\n```\n\nWhere\n\n- `quote` is the quote ID string in UUID v7 format with all 74 variable bits generated by a CSPRNG\n- `request` is the method-specific payment routing target\n- `amount` and `unit` the amount and unit that need to be provided\n- `fee_reserve` is the additional fee reserve for using the method (the wallet provides proofs covering at least `amount + fee_reserve + fee`, where `fee` is the keyset input fee per [NUT-02][02])\n- `method` is the payment method of the quote\n- `expiry` is the Unix timestamp until which the melt quote is valid.\n\n`state` is an enum string field with possible values `\"UNPAID\"`, `\"PENDING\"`, `\"PAID\"`:\n\n- `\"UNPAID\"` means that the request has not been paid yet.\n- `\"PENDING\"` means that the request is currently being paid.\n- `\"PAID\"` means that the request has been paid successfully.\n\n### Check Melt Quote State\n\nTo check whether a melt quote has been paid, the wallet makes a `GET /v1/melt/quote/{method}/{quote_id}`.\n\n```http\nGET https://mint.host:3338/v1/melt/quote/{method}/{quote_id}\n```\n\nThe mint responds with the same structure as the initial quote response.\n\n### Executing a Melt Quote\n\nTo execute the melting process, the wallet calls the `POST /v1/melt/{method}` endpoint.\n\n```http\nPOST https://mint.host:3338/v1/melt/{method}\n```\n\nThe wallet includes the following common data in its request:\n\n```json\n{\n  \"quote\": <str>,\n  \"inputs\": <Array[Proof]>,\n  \"prefer_async\": <bool> // optional: false if omitted\n  // Additional method-specific fields may be required\n}\n```\n\nwhere `quote` is the melt quote ID, `inputs` are the proofs with a total amount sufficient to cover the requested amount plus any fees, and `prefer_async` requests asynchronous processing when set to `true`.\n\n#### Synchronous Processing\n\n> [!IMPORTANT]\n> For methods that involve external payments (like Lightning), a synchronous call will block until the payment either succeeds or fails. This can take a long time. Make sure to **use no (or a very long) timeout when making this call**!\n\n#### Asynchronous Processing\n\nIf the method-specific NUT requires asynchronous execution, the mint **MUST** process melt requests for that method asynchronously. The wallet does not need to set `prefer_async`.\n\nFor other methods, the wallet can set `prefer_async` to `true` in the melt request body to request asynchronous processing.\n\nWhen asynchronous processing is used:\n\n1. The mint will verify the request (including proof validation)\n2. If valid, the mint responds immediately with a `200 OK` status and the quote state set to `\"PENDING\"`\n3. The payment processing begins in the background\n4. The wallet can poll the quote state endpoint or subscribe to websocket notifications to monitor payment progress\n\nIf the method-specific NUT does not require asynchronous execution and the mint does not support asynchronous processing for the method, the mint ignores `prefer_async` and processes the request synchronously. The response follows the standard synchronous flow with final payment state.\n\n#### Synchronous Response\n\nFor synchronous processing, the mint responds with a structure that indicates the final payment state and includes any method-specific proof of payment when successful.\n\n#### Asynchronous Response\n\nFor asynchronous processing, the mint responds with:\n\n```http\nHTTP/1.1 200 OK\nContent-Type: application/json\n```\n\n```json\n{\n  \"quote\": <str>,\n  \"amount\": <int>,\n  \"unit\": <str_enum[UNIT]>,\n  \"state\": \"PENDING\",\n  \"expiry\": <int>\n  // Additional method-specific fields may be included\n}\n```\n\nThe wallet should then either:\n\n- Poll the quote state using `GET /v1/melt/quote/{method}/{quote_id}`\n- Subscribe to websocket notifications for real-time updates (if supported by the mint)\n\n### Example Asynchronous Flow\n\n1. **Request asynchronous melt:**\n\n```http\nPOST https://mint.host:3338/v1/melt/bolt11\nContent-Type: application/json\n\n{\n  \"quote\": \"019e6d5a-2347-7000-89e2-35fe79f92c0e\",\n  \"inputs\": [...],\n  \"prefer_async\": true\n}\n```\n\n2. **Immediate response (200 OK):**\n\n```http\nHTTP/1.1 200 OK\nContent-Type: application/json\n\n{\n  \"quote\": \"019e6d5a-2347-7000-89e2-35fe79f92c0e\",\n  \"amount\": 10,\n  \"unit\": \"sat\",\n  \"method\": \"bolt11\",\n  \"state\": \"PENDING\",\n  \"expiry\": 1701704757\n}\n```\n\n3. **Poll for status:**\n\n```http\nGET https://mint.host:3338/v1/melt/quote/bolt11/019e6d5a-2347-7000-89e2-35fe79f92c0e\n```\n\n4. **Final status response:**\n\n```json\n{\n  \"quote\": \"019e6d5a-2347-7000-89e2-35fe79f92c0e\",\n  \"request\": \"lnbc100n1p3kdrv5sp5...\",\n  \"amount\": 10,\n  \"unit\": \"sat\",\n  \"fee_reserve\": 2,\n  \"state\": \"PAID\",\n  \"expiry\": 1701704757,\n  \"payment_preimage\": \"c5a1ae1f639e1f4a3872e81500fd028bece7bedc1152f740cba5c3417b748c1b\"\n}\n```\n\n## Custom Payment Methods\n\nPayment methods not specified in a dedicated NUT can be supported as custom payment methods. See [NUT-04][04] for the custom method identifier definition and general conventions.\n\n### Melt Quote\n\nFor 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.\n\nThe 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.\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 melt execution\n3. Update the settings to include the new method\n4. Document any method-specific fields or behaviors that differ from the general flow\n\n## Settings\n\nThe mint's settings for this NUT indicate the supported method-unit pairs for melting. They are part of the info response of the mint ([NUT-06][06]):\n\n```json\n{\n  \"5\": {\n    \"methods\": [\n      <MeltMethodSetting>,\n      ...\n    ],\n    \"disabled\": <bool>\n  }\n}\n```\n\n`MeltMethodSetting` indicates supported `method` and `unit` pairs and additional settings of the mint. `disabled` indicates whether melting is disabled.\n\n`MeltMethodSetting` 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[00]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crqqpd6d9\n[01]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crznyve7g\n[02]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cry0t0uzl\n[03]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crxuwwl3j\n[04]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crg74fknc\n[05]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cr2dsg4q4\n[06]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crv3ltsuz\n[07]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crwz62n00\n[08]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crs4q9zck\n[09]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crjx9yptm\n[10]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cnqx5tye7\n[11]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cnz43282n\n[12]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cnyf7fzky\n[17]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cnwy0vdm5\n[19]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cnjqszllq\n[23]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595erx7v8gg2\n[25]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595er20jpzed\n[30]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595enqykznqx\n","sig":"5a3a53f5cc0dff8aae9fd9897d956c7e10e1058b52a08fcdef5aafb940e97789f9e8bc361bf7eb0ebdbbeef8ccfb5668263388ca4e23020a62de541473ea1e2b"}