Skip to content

Commit

Permalink
feat: bolt12
Browse files Browse the repository at this point in the history
  • Loading branch information
thesimplekid committed Nov 18, 2024
1 parent f866a25 commit 3d2f85e
Show file tree
Hide file tree
Showing 3 changed files with 376 additions and 0 deletions.
177 changes: 177 additions & 0 deletions 20.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# NUT-19: Mint tokens via BOLT12

`optional`

---

Similar to [NUT-04][04], which covers minting via BOLT11 invoices, minting via [BOLT12](https://github.com/lightning/bolts/blob/master/12-offer-encoding.md) is also a two-step process: requesting a mint quote and minting 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|null>,
"unit": <str_enum["sat"]>,
"description": <str|null>,
"expiry": <int|null>,
"single_use": <bool>
}
```

The `amount` field is optional and specifies the amount to mint. The `unit` field is mandatory. An optional `description` can be passed if the mint signals support for it in `MintMethodSetting`. If `single_use` is false, the offer can be paid multiple times. The optional `expiry` field specifies the unix timestamp when the offer expires it **MUST** be before the `max_expiry` in the `MintMethodSettings` if one is given.

The mint `Bob` then responds with a `PostMintQuoteBolt12Response`:

```json
{
"quote": <str>,
"request": <str>,
"expiry": <int>,
"amount_paid": <int>,
"amount_issued": <int>
}
```

Where `quote` is the quote ID and `request` is the bolt12 offer. `expiry` is the Unix timestamp until which the mint quote is valid. `amount_paid` is the amount that has been paid to the mint via the bolt12 offer. `amount_issued` is the amount of ecash that has been issued for the given mint quote. `amount_paid` - `amount_issued` represents the amount of ecash a wallet can still mint.

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 ecash 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", "single_use": true}' -H "Content-Type: application/json"
```

Response of `Bob`:

```json
{
"quote": "DSGLX9kevM...",
"request": "lno1qcp...",
"expiry": 1701704757,
"amount_paid": 0,
"amount_issued": 0
}
```

After payment, the wallet continues with the next section.

## Check mint quote

To check whether a mint quote has been paid and has ecash that can be issued, `Alice` makes a `GET /v1/mint/quote/bolt12/{quote_id}`.

```http
GET https://mint.host:3338/v1/mint/quote/bolt12/{quote_id}
```

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 `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]>
}
```

The `quote` is the quote ID from the previous step and `outputs` are `BlindedMessages` (see [NUT-00][00]) that the wallet requests signatures on. The sum of the outputs must equal the amount that can be minted (`amount_paid` - `amount_issued`).

The mint `Bob` then responds with a `PostMintBolt12Response`:

```json
{
"signatures": <Array[BlindSignature]>
}
```

where `signatures` is an array of blind signatures on the outputs.

## Multiple issuances

Unlike BOLT11 invoices, BOLT12 offers can be paid multiple times as long as the `single_use` flag is false. This allows 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 as defined above.

## 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
{
"19": {
"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>,
"max_expiry": <int|null>
}
```

`min_amount` and `max_amount` define the allowed range for transactions using this method-unit pair. `max_expiry` specifies the latest timestamp (in Unix time) until which payments for this offer will be accepted.


Example `MintMethodSetting`:

```json
{
"method": "bolt12",
"unit": "sat",
"min_amount": 0,
"max_amount": 10000,
"description": true,
"max_expiry": 1825076378
}
```

[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
193 changes: 193 additions & 0 deletions 21.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# NUT-19: Melt tokens via BOLT12

`optional`

Similar to [NUT-05][05], 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.

# Melt quote

To request a melt quote, the wallet of `Alice` makes a `POST /v1/melt/quote/bolt12`..

```http
POST https://mint.host:3338/v1/melt/quote/bolt12
```

The wallet `Alice` includes the following `PostMeltQuoteBolt12Request` data in its request:

```json
{
"request": <str>,
"unit": <str_enum["sat"]>
"amount": <int|null>
}
```

Here, `request` is the bolt12 Lightning invoice to be paid and `unit` is the unit the wallet would like to pay with. `amount` is the amount the wallet would like to pay in the request unit. This `amount` can be omitted if the offer has an amount.

The mint `Bob` then responds with a `PostMeltQuoteBolt12Response`:

```json
{
"quote": <str>,
"amount": <int>,
"fee_reserve": <int>,
"state": <str_enum[STATE]>,
"expiry": <int>,
"payment_preimage": <str|null>
}
```

Where `quote` is the quote ID, `amount` the amount that needs to be provided, and `fee_reserve` the additional fee reserve that is required. The mint expects `Alice` to include `Proofs` of _at least_ `total_amount = amount + fee_reserve`. `expiry` is the Unix timestamp until which the melt quote is valid. `payment_preimage` is the payment preimage in case of a successful payment.

`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.

## Example

Request of `Alice` with curl:

```bash
curl -X POST https://mint.host:3338/v1/melt/quote/bolt12 -d \
{
"request": "lno1zrxq8pjw7qjlm68mtp7e3y...",
"unit": "sat"
}
```

Response of `Bob`:

```json
{
"quote": "TRmjduhIsPxd...",
"amount": 10,
"fee_reserve": 2,
"state": "UNPAID",
"expiry": 1701704757
}
```

## Check melt quote state

To check whether a melt quote has been paid, `Alice` makes a `GET /v1/melt/quote/bolt12/{quote_id}`.

```http
GET https://mint.host:3338/v1/melt/quote/bolt12/{quote_id}
```

Like before, the mint `Bob` responds with a `PostMeltQuoteBolt12Response`.

Example request of `Alice` with curl:

```bash
curl -X GET http://localhost:3338/v1/melt/quote/bolt12/TRmjduhIsPxd...
```

# Melting tokens

Now that `Alice` knows what the total amount is (`amount + fee_reserve`) in her requested `unit`, she can proceed to melting tokens for which a payment will be executed by the mint. She calls the `POST /v1/melt/bolt12`.

```http
POST https://mint.host:3338/v1/melt/bolt12
```

⚠️ **Attention:** This call will block until the Lightning payment either succeeds or fails. This can take quite a long time in case the Lightning payment is slow. Make sure to **use no (or a very long) timeout when making this call**!

The wallet of `Alice` includes the following `PostMeltBolt12Request` data in its request

```json
{
"quote": <str>,
"inputs": <Array[Proof]>
}
```

Here, `quote` is the melt quote ID to be paid and `inputs` are the proofs with a total amount of at least `amount + fee_reserve` (see previous melt quote response).

Like before, the mint `Bob` then responds with a `PostMeltQuoteBolt12Response`. If the payment was successful, the `state` field is set to `"PAID"` and the response includes the `payment_preimage` field containing the payment secret of the bolt11 payment.

If `state=="PAID"`, `Alice`'s wallet can delete the `inputs` from her database (or move them to a history). If `state=="UNPAID"`, `Alice` can repeat the same request again until the payment is successful.

## Example

Request of `Alice` with curl:

```bash
curl -X POST https://mint.host:3338/v1/melt/bolt12 -d \
'{
"quote": "od4CN5smMMS3K3QVHkbGGNCTxfcAIyIXeq8IrfhP",
"inputs": [
{
"amount": 4,
"id": "009a1f293253e41e",
"secret": "429700b812a58436be2629af8731a31a37fce54dbf8cbbe90b3f8553179d23f5",
"C": "03b01869f528337e161a6768b480fcf9f75fd248b649c382f5e352489fd84fd011",
},
{
"amount": 8,
"id": "009a1f293253e41e",
"secret": "4f3155acef6481108fcf354f6d06e504ce8b441e617d30c88924991298cdbcad",
"C": "0278ab1c1af35487a5ea903b693e96447b2034d0fd6bac529e753097743bf73ca9",
}
]
}'
```

Response `PostMeltQuoteBolt12Response` of `Bob`:

```json
{
"quote": "TRmjduhIsPxd...",
"amount": 10,
"fee_reserve": 2,
"state": "PAID",
"expiry": 1701704757,
"payment_preimage": "c5a1ae1f639e1f4a3872e81500fd028bece7bedc1152f740cba5c3417b748c1b"
}
```

## 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][06]) which in this case reads

```json
{
"19": {
"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:

```json
{
"method": <str>,
"unit": <str>,
"min_amount": <int|null>,
"max_amount": <int|null>
}
```

`min_amount` and `max_amount` indicate the minimum and maximum amount for an operation of this method-unit pair.

Example `MeltMethodSetting`:

```json
{
"method": "bolt12",
"unit": "sat",
"min_amount": 100,
"max_amount": 10000
}
```

[05]: 05.md
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ Wallets and mints `MUST` implement all mandatory specs and `CAN` implement optio
| [16][16] | Animated QR codes | [Cashu.me][cashume] | - |
| [17][17] | WebSocket subscriptions | [Nutshell][py] | [Nutshell][py] |
| [18][18] | Payment requests | [Cashu.me][cashume], [Boardwalk][bwc], [cdk-cli] | - |
| [19][19] | Sign mint quotes | [cdk-cli] | [cdk-mintd] |
| [20][20] | Mint Bolt12 | [cdk-cli] | [cdk-mintd] |
| [21][21] | Melt Bolt12 | [cdk-cli] | [cdk-mintd] |

#### Wallets:

Expand Down Expand Up @@ -89,3 +92,6 @@ Wallets and mints `MUST` implement all mandatory specs and `CAN` implement optio
[16]: 16.md
[17]: 17.md
[18]: 18.md
[19]: 19.md
[20]: 20.md
[21]: 21.md

0 comments on commit 3d2f85e

Please sign in to comment.