-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ea8d7d6
commit 5565d0b
Showing
3 changed files
with
434 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
# NUT-18: Mint tokens via BOLT12 | ||
|
||
`optional` | ||
|
||
--- | ||
|
||
Similar to [NUT-04][04], which covers melting via BOLT11 invoices, melting via [BOLT12](https://github.com/lightning/bolts/blob/master/12-offer-encoding.md) is also a two-step process: requesting a melt quote and melting tokens. This document describes both steps, focusing on BOLT12-specific considerations. | ||
|
||
# Mint quote | ||
|
||
To request a mint quote, the wallet of `Alice` makes a `POST /v1/mint/quote/bolt12`. | ||
|
||
```http | ||
POST https://mint.host:3338/v1/mint/quote/bolt12 | ||
``` | ||
|
||
The wallet of `Alice` includes the following `PostMintQuoteBolt12Request` data in its request: | ||
|
||
```json | ||
{ | ||
"amount": <int>, | ||
"unit": <str_enum["sat"]>, | ||
"description": <str|null> | ||
} | ||
``` | ||
|
||
with the requested `amount` and the `unit`. An optional `description` can be passed if the mint signals support for it in `MintMethodSetting`. | ||
|
||
The mint `Bob` then responds with a `PostMintQuoteBolt12Response`: | ||
|
||
```json | ||
{ | ||
"quote": <str>, | ||
"request": <str>, | ||
"state": <str_enum[STATE]>, | ||
"expiry": <int> | ||
} | ||
``` | ||
|
||
Where `quote` is the quote ID and `request` is the bolt12 offer. The bolt12 offer **MUST** be `single_use` and have the `absolute_expiry` and `amount` set to match the quote. `expiry` is the Unix timestamp until which the mint quote is valid. | ||
|
||
`state` is an enum string field with possible values `"UNPAID"`, `"PAID"`, `"ISSUED"`: | ||
|
||
- `"UNPAID"` means that the quote's request has not been paid yet. | ||
- `"PAID"` means that the request has been paid. | ||
- `"ISSUED"` means that the quote has already been issued. | ||
|
||
Note: `quote` is a **unique and random** id generated by the mint to internally look up the payment state. `quote` **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. | ||
|
||
## Example | ||
|
||
Request of `Alice` with curl: | ||
|
||
```bash | ||
curl -X POST http://localhost:3338/v1/mint/quote/bolt12 -d '{"amount": 10, "unit": "sat"}' -H "Content-Type: application/json" | ||
``` | ||
|
||
Response of `Bob`: | ||
|
||
```json | ||
{ | ||
"quote": "DSGLX9kevM...", | ||
"request": "lnbc100n1pj4apw9...", | ||
"state": "UNPAID", | ||
"expiry": 1701704757 | ||
} | ||
``` | ||
|
||
The wallet **MUST** store the `amount` in the request and the `quote` id in the response in its database so it can later request the tokens after paying the request. After payment, the wallet continues with the next section. | ||
|
||
## Check mint quote state | ||
|
||
To check whether a mint quote has been paid, `Alice` makes a `GET /v1/mint/quote/bolt12/{quote_id}`. | ||
|
||
```http | ||
GET https://mint.host:3338/v1/mint/quote/bolt12/{quote_id} | ||
``` | ||
|
||
Like before, the mint `Bob` responds with a `PostMintQuoteBolt12Response`. | ||
|
||
Example request of `Alice` with curl: | ||
|
||
```bash | ||
curl -X GET http://localhost:3338/v1/mint/quote/bolt12/DSGLX9kevM... | ||
``` | ||
|
||
# Minting tokens | ||
|
||
After requesting a mint quote and paying the request, the wallet proceeds with minting new tokens by calling the `POST /v1/mint/bolt12`. | ||
|
||
```http | ||
POST https://mint.host:3338/v1/mint/bolt12 | ||
``` | ||
|
||
The wallet `Alice` includes the following `PostMintBolt12Request` data in its request | ||
|
||
```json | ||
{ | ||
"quote": <str>, | ||
"outputs": <Array[BlindedMessage]> | ||
} | ||
``` | ||
|
||
with the `quote` being the quote ID from the previous step and `outputs` being `BlindedMessages` (see [NUT-00][00]) that the wallet requests signatures on whose sum is `amount` as requested in the quote. | ||
|
||
The mint `Bob` then responds with a `PostMintBolt12Response`: | ||
|
||
```json | ||
{ | ||
"signatures": <Array[BlindSignature]> | ||
} | ||
``` | ||
|
||
where `signatures` is an array of blind signatures on the outputs. | ||
|
||
## Example | ||
|
||
Request of `Alice` with curl: | ||
|
||
```bash | ||
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 of `Bob`: | ||
|
||
```json | ||
{ | ||
"signatures": [ | ||
{ | ||
"id": "009a1f293253e41e", | ||
"amount": 2, | ||
"C_": "0224f1c4c564230ad3d96c5033efdc425582397a5a7691d600202732edc6d4b1ec" | ||
}, | ||
{ | ||
"id": "009a1f293253e41e", | ||
"amount": 8, | ||
"C_": "0277d1de806ed177007e5b94a8139343b6382e472c752a74e99949d511f7194f6c" | ||
} | ||
] | ||
} | ||
``` | ||
|
||
If the invoice was not paid yet, `Bob` responds with an error. In that case, `Alice` **CAN** repeat the same request until the Lightning invoice is settled. | ||
|
||
## Unblinding signatures | ||
|
||
Upon receiving the `BlindSignatures` from the mint `Bob`, the wallet of `Alice` 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: | ||
|
||
```json | ||
[ | ||
{ | ||
"id": "009a1f293253e41e", | ||
"amount": 2, | ||
"secret": "407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837", | ||
"C": "02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea" | ||
}, | ||
{ | ||
"id": "009a1f293253e41e", | ||
"amount": 8, | ||
"secret": "fe15109314e61d7756b0f8ee0f23a624acaa3f4e042f61433c728c7057b931be", | ||
"C": "029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059" | ||
} | ||
] | ||
``` | ||
|
||
## Settings | ||
|
||
The settings for this nut indicate the supported method-unit pairs for minting and whether minting is disabled or not. They are part of the info response of the mint ([NUT-06][06]) which in this case reads | ||
|
||
```json | ||
{ | ||
"18": { | ||
"methods": [ | ||
<MintMethodSetting>, | ||
... | ||
], | ||
"disabled": <bool> | ||
} | ||
} | ||
``` | ||
|
||
`MintMethodSetting` indicates supported `method` and `unit` pairs and additional settings of the mint. `disabled` indicates whether this minting is disabled. | ||
|
||
`MintMethodSetting` is of the form: | ||
|
||
```json | ||
{ | ||
"method": <str>, | ||
"unit": <str>, | ||
"min_amount": <int|null>, | ||
"max_amount": <int|null>, | ||
"description": <bool|null> | ||
} | ||
``` | ||
|
||
`min_amount` and `max_amount` indicate the minimum and maximum amount for an operation of this method-unit pair. | ||
|
||
Example `MintMethodSetting`: | ||
|
||
```json | ||
{ | ||
"method": "bolt12", | ||
"unit": "sat", | ||
"min_amount": 0, | ||
"max_amount": 10000, | ||
"description": true | ||
} | ||
``` | ||
|
||
[00]: 00.md | ||
[01]: 01.md | ||
[02]: 02.md | ||
[03]: 03.md | ||
[04]: 04.md | ||
[05]: 05.md | ||
[06]: 06.md | ||
[07]: 07.md | ||
[08]: 08.md | ||
[09]: 09.md | ||
[10]: 10.md | ||
[11]: 11.md | ||
[12]: 12.md |
Oops, something went wrong.