NUT-05: Melting tokens
mandatory
used in: NUT-08, NUT-15, NUT-23
Melting tokens is the opposite of minting tokens (see NUT-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.
Supported methods
Method-specific NUTs describe how to handle different payment methods. The currently specified methods are:
- NUT-23 for bolt11 Lightning invoices
- NUT-25 for bolt12 Lightning offers
- NUT-30 for onchain Bitcoin transactions
General Flow
The melting process follows these steps for all payment methods:
- The wallet requests a melt quote for a
requestit wants paid by the mint, specifying the paymentmethodand theunitthe wallet would like to spend - The mint responds with a quote that includes a
quoteid and anamountdemanded in the requested unit - The wallet sends a melting request including the
quoteid and providesinputsof the required amount - The mint executes the payment and responds with the payment
stateand any method-specific proof of payment
Synchronous vs Asynchronous Processing
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.
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.
Common Request and Response Formats
Requesting a Melt Quote
To 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_-]+.
POST https://mint.host:3338/v1/melt/quote/{method}
Depending on the payment method, the request structure may vary, but all methods will include at minimum:
{
"request": <str>,
"unit": <str_enum[UNIT]>,
"amount": <int> // Optional
// Additional method-specific fields will be required
}
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).
The mint Bob responds with a quote that includes
some common fields for all methods:
{
"quote": <str>, // UUID v7
"request": <str>,
"amount": <int>,
"unit": <str_enum[UNIT]>,
"fee_reserve": <int>, // Optional
"method": <str>,
"state": <str_enum[STATE]>,
"expiry": <int>
// Additional method-specific fields will be included
}
Where
quoteis the quote ID string in UUID v7 format with all 74 variable bits generated by a CSPRNGrequestis the method-specific payment routing targetamountandunitare the amount and unit that need to be providedfee_reserveis the additional fee reserve for using the method (the wallet provides proofs covering at leastamount + fee_reserve + fee, wherefeeis the keyset input fee per NUT-02)methodis the payment method of the quoteexpiryis the Unix timestamp until which the melt quote is valid.
state is an enum string field with possible values
"UNPAID", "PENDING",
"PAID":
"UNPAID"means that the request has not been paid yet."PENDING"means that the request is currently being paid."PAID"means that the request has been paid successfully.
Check Melt Quote State
To check whether a melt quote has been paid, the wallet makes a
GET /v1/melt/quote/{method}/{quote_id}.
GET https://mint.host:3338/v1/melt/quote/{method}/{quote_id}
The mint responds with the same structure as the initial quote response.
Executing a Melt Quote
To execute the melting process, the wallet calls the
POST /v1/melt/{method} endpoint.
POST https://mint.host:3338/v1/melt/{method}
The wallet includes the following common data in its request:
{
"quote": <str>,
"inputs": <Array[Proof]>,
"prefer_async": <bool> // optional: false if omitted
// Additional method-specific fields may be required
}
where 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.
The inputs, and any NUT-08 change outputs,
MUST be of the unit of the melt quote. The mint
MUST reject a melt that mixes units (see Error Codes 11009 and
11010).
Synchronous Processing
[!IMPORTANT] 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!
Asynchronous Processing
If 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.
For other methods, the wallet can set prefer_async
to true in the melt request body to request
asynchronous processing.
When asynchronous processing is used:
- The mint will verify the request (including proof validation)
- If valid, the mint responds immediately with a
200 OKstatus and the quote state set to"PENDING" - The payment processing begins in the background
- The wallet can poll the quote state endpoint or subscribe to websocket notifications to monitor payment progress
If 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.
Synchronous Response
For synchronous processing, the mint responds with a structure that indicates the final payment state and includes any method-specific proof of payment when successful.
Asynchronous Response
For asynchronous processing, the mint responds with:
HTTP/1.1 200 OK
Content-Type: application/json
{
"quote": <str>,
"amount": <int>,
"unit": <str_enum[UNIT]>,
"state": "PENDING",
"expiry": <int>
// Additional method-specific fields may be included
}
The wallet should then either:
- Poll the quote state using
GET /v1/melt/quote/{method}/{quote_id} - Subscribe to websocket notifications for real-time updates (if supported by the mint)
Example Asynchronous Flow
- Request asynchronous melt:
POST https://mint.host:3338/v1/melt/bolt11
Content-Type: application/json
{
"quote": "019e6d5a-2347-7000-89e2-35fe79f92c0e",
"inputs": [...],
"prefer_async": true
}
- Immediate response (200 OK):
HTTP/1.1 200 OK
Content-Type: application/json
{
"quote": "019e6d5a-2347-7000-89e2-35fe79f92c0e",
"amount": 10,
"unit": "sat",
"method": "bolt11",
"state": "PENDING",
"expiry": 1701704757
}
- Poll for status:
GET https://mint.host:3338/v1/melt/quote/bolt11/019e6d5a-2347-7000-89e2-35fe79f92c0e
- Final status response:
{
"quote": "019e6d5a-2347-7000-89e2-35fe79f92c0e",
"request": "lnbc100n1p3kdrv5sp5...",
"amount": 10,
"unit": "sat",
"fee_reserve": 2,
"state": "PAID",
"expiry": 1701704757,
"payment_preimage": "c5a1ae1f639e1f4a3872e81500fd028bece7bedc1152f740cba5c3417b748c1b"
}
Custom Payment Methods
Payment methods not specified in a dedicated NUT can be supported as custom payment methods. See NUT-04 for the custom method identifier definition and general conventions.
Melt Quote
For a custom {method}, the wallet sends a request
following the common melt quote request format (see 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.
The 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.
Adding New Payment Methods
To add a new payment method (e.g., BOLT12), implement the following:
- Define the method-specific request and response structures following the pattern above
- Implement the three required endpoints: quote request, quote check, and melt execution
- Update the settings to include the new method
- Document any method-specific fields or behaviors that differ from the general flow
Settings
The 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):
{
"5": {
"methods": [
<MeltMethodSetting>,
...
],
"disabled": <bool>
}
}
MeltMethodSetting indicates supported
method and unit pairs and additional
settings of the mint. disabled indicates whether
melting is disabled.
MeltMethodSetting is of the form:
{
"method": <str>,
"unit": <str>,
"method_name": <str|null>,
"min_amount": <int|null>,
"max_amount": <int|null>,
"options": <Object|null>
}
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.
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).