NUT-25: BOLT12¶
optional
depends on: NUT-04 NUT-05 NUT-20
This document describes minting and melting ecash with the bolt12
payment method, which uses Lightning Network offers. It is an extension of NUT-04 and NUT-05 which cover the protocol steps of minting and melting ecash shared by any supported payment method.
Mint Quote¶
For the bolt12
method, the wallet includes the following specific PostMintQuoteBolt12Request
data:
{
"amount": <int|null>,
"unit": <str_enum[UNIT]>,
"description": <str|null>,
"pubkey": <str>
}
Note: While a pubkey is optional as per NUT-20 for NUT-04 it is required in this NUT and the mint MUST NOT issue a mint quote if one is not included.
Privacy: To prevent linking multiple mint quotes together, wallets SHOULD generate a unique public key for each mint quote request.
The mint responds with a PostMintQuoteBolt12Response
:
{
"quote": <str>,
"request": <str>,
"amount": <int|null>,
"unit": <str_enum[UNIT]>,
"expiry": <int|null>,
"pubkey": <str>,
"amount_paid": <int>,
"amount_issued": <int>
}
Where:
quote
is the quote IDrequest
is the bolt12 offerexpiry
is the Unix timestamp until which the mint quote is validamount_paid
is the amount that has been paid to the mint via the bolt12 offeramount_issued
is the amount of ecash that has been issued for the given mint quote
Example¶
Request with curl:
curl -X POST http://localhost:3338/v1/mint/quote/bolt12 -d \
'{"amount": 10, "unit": "sat", "pubkey": "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac"}' \
-H "Content-Type: application/json"
Response:
{
"quote": "DSGLX9kevM...",
"request": "lno1qcp...",
"amount": 10,
"unit": "sat",
"expiry": 1701704757,
"pubkey": "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac",
"amount_paid": 0,
"amount_issued": 0
}
Check quote state:
curl -X GET http://localhost:3338/v1/mint/quote/bolt12/DSGLX9kevM...
Minting tokens:
curl -X POST https://mint.host:3338/v1/mint/bolt12 -H "Content-Type: application/json" -d \
'{
"quote": "DSGLX9kevM...",
"outputs": [
{
"amount": 8,
"id": "009a1f293253e41e",
"B_": "035015e6d7ade60ba8426cefaf1832bbd27257636e44a76b922d78e79b47cb689d"
},
{
"amount": 2,
"id": "009a1f293253e41e",
"B_": "0288d7649652d0a83fc9c966c969fb217f15904431e61a44b14999fabc1b5d9ac6"
}
]
}'
Response:
{
"signatures": [
{
"id": "009a1f293253e41e",
"amount": 2,
"C_": "0224f1c4c564230ad3d96c5033efdc425582397a5a7691d600202732edc6d4b1ec"
},
{
"id": "009a1f293253e41e",
"amount": 8,
"C_": "0277d1de806ed177007e5b94a8139343b6382e472c752a74e99949d511f7194f6c"
}
]
}
Multiple Issuances¶
Unlike BOLT11 invoices, BOLT12 offers can be paid multiple times, allowing the wallet to mint multiple times for one quote. The wallet can call the check bolt12 endpoint, where the mint will return the PostMintQuoteBolt12Response
including amount_paid
and amount_issued
. The difference between these values represents how much the wallet can mint by calling the mint endpoint. Wallets MAY mint any amount up to this available difference; in particular, they can mint less than the amount mintable. Mints MUST accept mint requests whose total output amount is less than or equal to (amount_paid
- amount_issued
).
Mint Settings¶
A description
option SHOULD be set to indicate whether the bolt12
payment method backend supports providing an offer description.
Example MintMethodSetting
¶
{
"method": "bolt12",
"unit": <str>,
"min_amount": <int|null>,
"max_amount": <int|null>,
"options": {
"description": true
}
}
Melt Quote¶
For the bolt12
method, the wallet includes the following specific PostMeltQuoteBolt12Request
data:
{
"request": <str>,
"unit": <str_enum[UNIT]>,
"options": { // Optional
"amountless": {
"amount_msat": <int>
}
}
}
Here, request
is the bolt12 Offer to be paid and unit
is the unit the wallet would like to pay with. For amount-less offers, the options.amountless.amount_msat
field can be used to specify the amount in millisatoshis to pay to the offer. If options.amountless.amount_msat
is defined and the offer has an amount, they MUST be equal.
The mint responds with a PostMeltQuoteBolt12Response
:
{
"quote": <str>,
"request": <str>,
"amount": <int>,
"unit": <str_enum[UNIT]>,
"fee_reserve": <int>,
"state": <str_enum[STATE]>,
"expiry": <int>,
"payment_preimage": <str|null>
}
Where fee_reserve
is the additional fee reserve required for the Lightning payment. The mint expects the wallet to include Proofs
of at least total_amount = amount + fee_reserve + fee
where fee
is calculated from the keyset's input_fee_ppk
as described in NUT-02.
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.
Melting Tokens¶
For the bolt12
method, the wallet can include an optional outputs
field in the melt request to receive change for overpaid Lightning fees (see NUT-08):
{
"quote": <str>,
"inputs": <Array[Proof]>,
"outputs": <Array[BlindedMessage]> // Optional
}
If the outputs
field is included and there is excess from the fee_reserve
, the mint will respond with a change
field containing blind signatures for the overpaid amount:
{
"quote": <str>,
"request": <str>,
"amount": <int>,
"unit": <str_enum[UNIT]>,
"fee_reserve": <int>,
"state": <str_enum[STATE]>,
"expiry": <int>,
"payment_preimage": <str>,
"change": <Array[BlindSignature]> // Present if outputs were included and there's change
}
Example¶
Melt quote request:
curl -X POST https://mint.host:3338/v1/melt/quote/bolt12 -d \
'{"request": "lno1qcp4256ypqpq86q69t5wv5629arxqurn8cxg9p5qmmqy2e5xq...", "unit": "sat"}'
Melt quote response:
{
"quote": "TRmjduhIsPxd...",
"request": "lno1qcp4256ypqpq86q69t5wv5629arxqurn8cxg9p5qmmqy2e5xq...",
"amount": 10,
"unit": "sat",
"fee_reserve": 2,
"state": "UNPAID",
"expiry": 1701704757
}
Check quote state:
curl -X GET http://localhost:3338/v1/melt/quote/bolt12/TRmjduhIsPxd...
Melt request:
curl -X POST https://mint.host:3338/v1/melt/bolt12 -d \
'{"quote": "TRmjduhIsPxd...", "inputs": [...]}'
Successful melt response:
{
"quote": "TRmjduhIsPxd...",
"request": "lno1qcp4256ypqpq86q69t5wv5629arxqurn8cxg9p5qmmqy2e5xq...",
"amount": 10,
"unit": "sat",
"fee_reserve": 2,
"state": "PAID",
"expiry": 1701704757,
"payment_preimage": "c5a1ae1f639e1f4a3872e81500fd028bece7bedc1152f740cba5c3417b748c1b"
}
Example MeltMethodSetting
¶
{
"method": "bolt12",
"unit": <str>,
"min_amount": <int|null>,
"max_amount": <int|null>
}