diff --git a/19.md b/19.md new file mode 100644 index 0000000..74cde56 --- /dev/null +++ b/19.md @@ -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": , + "unit": , + "description": +} +``` + +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": , + "request": , + "state": , + "expiry": +} +``` + +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": , + "outputs": +} +``` + +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": +} +``` + +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": [ + , + ... + ], + "disabled": + } +} +``` + +`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": , + "unit": , + "min_amount": , + "max_amount": , + "description": +} +``` + +`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 diff --git a/20.md b/20.md new file mode 100644 index 0000000..0bb68c0 --- /dev/null +++ b/20.md @@ -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": , + "unit": + "amount": +} +``` + +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": , + "amount": , + "fee_reserve": , + "state": , + "expiry": , + "payment_preimage": +} +``` + +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": , + "inputs": +} +``` + +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": [ + , + ... + ], + "disabled": + } +} +``` + +`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": , + "unit": , + "min_amount": , + "max_amount": +} +``` + +`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 diff --git a/README.md b/README.md index f0b745f..2a3b983 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ 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] | - | +| [19][19] | Mint Bolt12 | [cdk-cli] | [cdk-mintd] | +| [20][20] | Melt Bolt12 | [cdk-cli] | [cdk-mintd] | #### Wallets: @@ -89,3 +91,5 @@ 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