From b5561226bd824fb52bf55ef3621d6f1d03fb1edc Mon Sep 17 00:00:00 2001 From: David Caseria Date: Fri, 13 Dec 2024 14:05:19 -0500 Subject: [PATCH] Payment Method Refactor First Draft --- 04.md | 161 +++++++++++++++++++++++++++--------------------- 05.md | 183 +++++++++++++++++++++++++++++++++---------------------- 08.md | 138 ++++++++++++++++++++--------------------- 20.md | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 463 insertions(+), 211 deletions(-) create mode 100644 20.md diff --git a/04.md b/04.md index e3f7a28..992665f 100644 --- a/04.md +++ b/04.md @@ -1,65 +1,82 @@ -# NUT-04: Mint tokens +# NUT-04: Minting tokens `mandatory` --- -Minting tokens is a two-step process: requesting a mint quote and minting new tokens. Here, we describe both steps. +Minting tokens is a two-step process: requesting a mint quote and minting new tokens. -In the first request the wallet asks the mint for a quote for a specific `amount` and `unit` to mint, and the payment `method` to pay. The mint responds with a quote that includes a `quote` id and a payment `request`. The user pays the `request` and, if successful, requests minting of new tokens with the mint in a second request. The wallet includes the `quote` id and new `outputs` in the second request. +1. The wallet **MUST** first request a mint quote by specifying the `amount`, `unit`, and payment `method`. + - The mint **MUST** respond with a `quote` ID and a payment `request` for valid requests. +2. After payment, the wallet **MUST** initiate the minting process by sending the `quote` ID and `outputs` in a second request. -We limit this document to mint quotes of `unit="sat"` and `method="bolt11"` which requests a bolt11 Lightning invoice (typically generated by the mint to add Bitcoin to its reserves) to mint ecash denominated in Satoshis. +--- + +## Mint Quote -# Mint quote +To request a mint quote, the wallet **MUST** send a `POST /v1/mint/quote/{method}` request. The `method` specifies the payment method (e.g., `bolt11`). -To request a mint quote, the wallet of `Alice` makes a `POST /v1/mint/quote/{method}` request where `method` is the payment method requested (here `bolt11`). +### Request Endpoint ```http -POST https://mint.host:3338/v1/mint/quote/bolt11 +POST https://mint.host:3338/v1/mint/quote/{method} ``` -The wallet of `Alice` includes the following `PostMintQuoteBolt11Request` data in its request: +### Request Body + +The request body **MUST** include the following fields: ```json { "amount": , - "unit": , - "description": + "unit": , + "opts": } ``` -with the requested `amount` and the `unit`. An optional `description` can be passed if the mint signals support for it in `MintMethodSetting`. +- `amount`: The requested amount to mint. +- `unit`: The unit of the amount. +- `opts`: Options specific to the payment method. + +### Response -The mint `Bob` then responds with a `PostMintQuoteBolt11Response`: +The mint **MUST** respond with a `PostMintQuoteResponse`: ```json { "quote": , "request": , "state": , - "expiry": + "expiry": } ``` -Where `quote` is the quote ID and `request` is the payment request to fulfill. `expiry` is the Unix timestamp until which the mint quote is valid. +- `quote`: A unique ID for the quote. +- `request`: The payment request. +- `state`: The current state of the quote (`UNPAID`, `PAID`, or `ISSUED`). +- `expiry`: The expiration timestamp for the quote (or `null` for quotes without expiration). -`state` is an enum string field with possible values `"UNPAID"`, `"PAID"`, `"ISSUED"`: +The wallet **MUST** store the `amount` and `quote` ID in its database for use during token minting after payment. -- `"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. +--- + +### Security Note -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. +The `quote` ID **MUST** be unique and secret. It **MUST NOT** be derivable from the payment request. Leaking this ID allows third parties to steal tokens. + +--- -## Example +### Example: Requesting a Quote -Request of `Alice` with curl: +**Request:** ```bash -curl -X POST http://localhost:3338/v1/mint/quote/bolt11 -d '{"amount": 10, "unit": "sat"}' -H "Content-Type: application/json" +curl -X POST https://mint.host:3338/v1/mint/quote/{method} \ +-d '{"amount": 10, "unit": "sat"}' \ +-H "Content-Type: application/json" ``` -Response of `Bob`: +**Response:** ```json { @@ -70,33 +87,46 @@ Response of `Bob`: } ``` -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 +## Checking Mint Quote State -To check whether a mint quote has been paid, `Alice` makes a `GET /v1/mint/quote/bolt11/{quote_id}`. +To check whether a mint quote has been paid, the wallet **MUST** send a `GET /v1/mint/quote/{method}/{quote_id}` request. -```http -GET https://mint.host:3338/v1/mint/quote/bolt11/{quote_id} -``` +### Example: Checking Quote State -Like before, the mint `Bob` responds with a `PostMintQuoteBolt11Response`. - -Example request of `Alice` with curl: +**Request:** ```bash -curl -X GET http://localhost:3338/v1/mint/quote/bolt11/DSGLX9kevM... +curl -X GET https://mint.host:3338/v1/mint/quote/{method}/{quote_id} ``` -# Minting tokens +**Response:** -After requesting a mint quote and paying the request, the wallet proceeds with minting new tokens by calling the `POST /v1/mint/{method}` endpoint where `method` is the payment method requested (here `bolt11`). +```json +{ + "quote": "DSGLX9kevM...", + "request": "lnbc100n1pj4apw9...", + "state": "UNPAID", + "expiry": 1701704757 +} +``` + +--- + +## Minting Tokens + +After completing the payment, the wallet **MUST** send a `POST /v1/mint/{method}` request to mint tokens. + +### Request Endpoint ```http -POST https://mint.host:3338/v1/mint/bolt11 +POST https://mint.host:3338/v1/mint/{method} ``` -The wallet `Alice` includes the following `PostMintBolt11Request` data in its request +### Request Body + +The request body **MUST** include: ```json { @@ -105,9 +135,12 @@ The wallet `Alice` includes the following `PostMintBolt11Request` data in its re } ``` -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. +- `quote`: The quote ID. +- `outputs`: Blinded messages (see [NUT-00](00.md)) for the requested amount. -The mint `Bob` then responds with a `PostMintBolt11Response`: +### Response + +The mint **MUST** respond with a `PostMintResponse`: ```json { @@ -115,15 +148,18 @@ The mint `Bob` then responds with a `PostMintBolt11Response`: } ``` -where `signatures` is an array of blind signatures on the outputs. +- `signatures`: Blind signatures for the outputs. + +--- -## Example +### Example: Minting Tokens -Request of `Alice` with curl: +**Request:** ```bash -curl -X POST https://mint.host:3338/v1/mint/bolt11 -H "Content-Type: application/json" -d \ -'{ +curl -X POST https://mint.host:3338/v1/mint/{method} \ +-H "Content-Type: application/json" \ +-d '{ "quote": "DSGLX9kevM...", "outputs": [ { @@ -140,7 +176,7 @@ curl -X POST https://mint.host:3338/v1/mint/bolt11 -H "Content-Type: application }' ``` -Response of `Bob`: +**Response:** ```json { @@ -159,11 +195,15 @@ Response of `Bob`: } ``` -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. +If the payment is incomplete, the mint **MUST** return an error. The wallet **SHOULD** retry until the payment is completed. + +--- + +### Unblinding Signatures -## Unblinding signatures +After receiving blind signatures, the wallet **MUST** unblind them to generate `Proofs`. This involves the blinding factor `r` and the mint's public key `K` (see BDHKE in [NUT-00](00.md)). -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: +The wallet **MUST** store the `Proofs` in its database: ```json [ @@ -184,7 +224,7 @@ Upon receiving the `BlindSignatures` from the mint `Bob`, the wallet of `Alice` ## 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 +The mint's [NUT-06](06.md) settings **MUST** indicate supported method-unit pairs for minting. It **MUST** also specify whether minting is disabled. ```json { @@ -198,9 +238,7 @@ The settings for this nut indicate the supported method-unit pairs for minting a } ``` -`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: +`MintMethodSetting` indicates supported `method` and `unit` pairs. The `disabled` field indicates whether this minting is disabled. The `min_amount` and `max_amount` fields indicate the minimum and maximum amount for an operation of this method-unit pair. ```json { @@ -208,12 +246,9 @@ The settings for this nut indicate the supported method-unit pairs for minting a "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 @@ -222,20 +257,8 @@ Example `MintMethodSetting`: "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 +# Well-Known Payment Methods +- [bolt11](20.md) diff --git a/05.md b/05.md index 5cf215d..4695c70 100644 --- a/05.md +++ b/05.md @@ -2,71 +2,83 @@ `mandatory` -`used in: NUT-08, NUT-15` - --- -Melting tokens is the opposite of minting tokens (see [NUT-04][04]). Like minting tokens, melting is a two-step process: requesting a melt quote and melting tokens. Here, we describe both steps. +Melting tokens allows wallets to convert ecash into external payments executed by the mint. Similar to minting tokens, melting is a two-step process: requesting a melt quote and performing the melt. + +1. **Requesting a Melt Quote** + The wallet **MUST** request a melt quote by specifying the `request` to be paid and the `unit` to be used for payment. The mint responds with a `quote` ID, the required `amount`, and, for specific methods like `bolt11`, a `fee_reserve` for payment fees. -In the first request the wallet asks the mint for a quote for a `request` it wants paid by the mint and the `unit` the wallet would like to spend as inputs. The mint responds with a quote that includes a `quote` id and an `amount` the mint demands in the requested unit. For the method `bolt11`, the mint includes a `fee_reserve` field indicating the reserve fee for a Lightning payment. +2. **Performing the Melt** + After obtaining the quote, the wallet **MUST** submit the `quote` ID and `inputs` that meet or exceed the required total (`amount + fee_reserve`). If applicable, the wallet may include `outputs` to handle overpaid fees. The mint responds with the payment `state`, and if successful, includes a `payment_preimage`. -In the second request, the wallet includes the `quote` id and provides `inputs` that sum up to `amount+fee_reserve` in the first response. For the method `bolt11`, the wallet can also include `outputs` in order for the mint to return overpaid Lightning fees (see [NUT-08][08]). The mint responds with a payment `state`. If the `state` is `"PAID"` the response includes a `payment_preimage` as a proof of payment. If the request included `outputs`, the mint may respond with `change` for the overpaid fees (see [NUT-08][08]). +--- -We limit this document to mint quotes of `unit="sat"` and `method="bolt11"` which requests a bolt11 Lightning payment (typically paid by the mint from its Bitcoin reserves) using ecash denominated in Satoshis. +## Melt Quote -# Melt quote +To request a melt quote, the wallet sends a `POST /v1/melt/quote/{method}` request. The `method` specifies the payment method (e.g., `bolt11`). -To request a melt quote, the wallet of `Alice` makes a `POST /v1/melt/quote/{method}` request where `method` is the payment method requested (here `bolt11`). +### Request Endpoint ```http -POST https://mint.host:3338/v1/melt/quote/bolt11 +POST https://mint.host:3338/v1/melt/quote/{method} ``` -The wallet `Alice` includes the following `PostMeltQuoteBolt11Request` data in its request: +### Request Body + +The wallet **MUST** include: ```json { "request": , - "unit": + "unit": , + "opts": } ``` -Here, `request` is the bolt11 Lightning invoice to be paid and `unit` is the unit the wallet would like to pay with. +- `request`: The payment request to be fulfilled by the mint. +- `unit`: The unit for the payment (e.g., "sat"). +- `opts`: Options specific to the payment method. + +### Response -The mint `Bob` then responds with a `PostMeltQuoteBolt11Response`: +The mint **MUST** respond with: ```json { "quote": , "amount": , - "fee_reserve": , + "fee_reserve": , "state": , - "expiry": , - "payment_preimage": + "expiry": , + "opts": } ``` -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 bolt11 payment preimage in case of a successful payment. +- `quote`: A unique ID for the quote. +- `amount`: The amount required for payment. +- `fee_reserve`: An additional reserve for fees (if applicable). +- `state`: Current state of the quote (`UNPAID`, `PENDING`, or `PAID`). +- `expiry`: Expiration timestamp for the quote (or `null` if it does not expire). +- `opts`: Options specific to the payment method. -`state` is an enum string field with possible values `"UNPAID"`, `"PENDING"`, `"PAID"`: +The wallet **MUST** store the `quote` ID and the required `amount` for use in subsequent melt operations. -- `"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 +### Example: Requesting a Melt Quote -Request of `Alice` with curl: +**Request:** ```bash -curl -X POST https://mint.host:3338/v1/melt/quote/bolt11 -d \ -{ +curl -X POST https://mint.host:3338/v1/melt/quote/{method} -d \ +'{ "request": "lnbc100n1p3kdrv5sp5lpdxzghe5j67q...", "unit": "sat" -} +}' ``` -Response of `Bob`: +**Response:** ```json { @@ -78,33 +90,61 @@ Response of `Bob`: } ``` -## Check melt quote state +--- + +## Checking Melt Quote State -To check whether a melt quote has been paid, `Alice` makes a `GET /v1/melt/quote/bolt11/{quote_id}`. +To check the state of a melt quote, the wallet **MUST** send a `GET /v1/melt/quote/{method}/{quote_id}` request. + +### Request Endpoint ```http -GET https://mint.host:3338/v1/melt/quote/bolt11/{quote_id} +GET https://mint.host:3338/v1/melt/quote/{method}/{quote_id} ``` -Like before, the mint `Bob` responds with a `PostMeltQuoteBolt11Response`. +### Response + +The mint **MUST** return the same fields as in the `PostMeltQuoteResponse`. + +--- -Example request of `Alice` with curl: +### Example: Checking Melt Quote State + +**Request:** ```bash -curl -X GET http://localhost:3338/v1/melt/quote/bolt11/TRmjduhIsPxd... +curl -X GET https://mint.host:3338/v1/melt/quote/{method}/{quote_id} +``` + +**Response:** + +```json +{ + "quote": "TRmjduhIsPxd...", + "amount": 10, + "fee_reserve": 2, + "state": "UNPAID", + "expiry": 1701704757 +} ``` -# 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/{method}` endpoint where `method` is the payment method requested (here `bolt11`). +## Melting Tokens + +After obtaining a valid melt quote, the wallet **MUST** call `POST /v1/melt/{method}` to perform the melt. + +### Request Endpoint ```http -POST https://mint.host:3338/v1/melt/bolt11 +POST https://mint.host:3338/v1/melt/{method} ``` -⚠️ **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**! +⚠️ **Note:** This call blocks until the payment succeeds or fails. Ensure appropriate timeout handling when making this call. + +### Request Body -The wallet of `Alice` includes the following `PostMeltBolt11Request` data in its request +The wallet **MUST** include: ```json { @@ -113,53 +153,66 @@ The wallet of `Alice` includes the following `PostMeltBolt11Request` data in its } ``` -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). +- `quote`: The melt quote ID. +- `inputs`: Proofs with a total value meeting or exceeding `amount + fee_reserve`. + +### Response + +The mint **MUST** respond with: + +```json +{ + "quote": , + "state": , + "opts": +} +``` -Like before, the mint `Bob` then responds with a `PostMeltQuoteBolt11Response`. 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. +- `quote`: The quote ID. +- `state`: The current state (`PAID` indicates success). +- `opts`: Options specific to the payment method. -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 +### Example: Performing a Melt -Request of `Alice` with curl: +**Request:** ```bash -curl -X POST https://mint.host:3338/v1/melt/bolt11 -d \ +curl -X POST https://mint.host:3338/v1/melt/{method} -d \ '{ - "quote": "od4CN5smMMS3K3QVHkbGGNCTxfcAIyIXeq8IrfhP", + "quote": "TRmjduhIsPxd...", "inputs": [ { "amount": 4, "id": "009a1f293253e41e", "secret": "429700b812a58436be2629af8731a31a37fce54dbf8cbbe90b3f8553179d23f5", - "C": "03b01869f528337e161a6768b480fcf9f75fd248b649c382f5e352489fd84fd011", + "C": "03b01869f528337e161a6768b480fcf9f75fd248b649c382f5e352489fd84fd011" }, { "amount": 8, "id": "009a1f293253e41e", "secret": "4f3155acef6481108fcf354f6d06e504ce8b441e617d30c88924991298cdbcad", - "C": "0278ab1c1af35487a5ea903b693e96447b2034d0fd6bac529e753097743bf73ca9", + "C": "0278ab1c1af35487a5ea903b693e96447b2034d0fd6bac529e753097743bf73ca9" } ] }' ``` -Response `PostMeltQuoteBolt11Response` of `Bob`: +**Response:** ```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 +The mint's [NUT-06](06.md) settings **MUST** define supported method-unit pairs and additional constraints: ```json { @@ -173,9 +226,7 @@ The mint's settings for this nut indicate the supported method-unit pairs for me } ``` -`MeltMethodSetting` indicates supported `method` and `unit` pairs and additional settings of the mint. `disabled` indicates whether melting is disabled. - -`MeltMethodSetting` is of the form: +`MeltMethodSetting` specifies the supported payment methods and units, along with optional `min_amount` and `max_amount`: ```json { @@ -186,9 +237,7 @@ The mint's settings for this nut indicate the supported method-unit pairs for me } ``` -`min_amount` and `max_amount` indicate the minimum and maximum amount for an operation of this method-unit pair. - -Example `MeltMethodSetting`: +### Example: Melt Method Setting ```json { @@ -197,18 +246,4 @@ Example `MeltMethodSetting`: "min_amount": 100, "max_amount": 10000 } -``` - -[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 +``` \ No newline at end of file diff --git a/08.md b/08.md index d03ffcd..ca1b181 100644 --- a/08.md +++ b/08.md @@ -1,103 +1,121 @@ -# NUT-08: Lightning fee return +# NUT-08: Fee Return for Overpayment `optional` `depends on: NUT-05` ---- - -This document describes how the overpaid Lightning fees are handled and extends [NUT-05][05] which describes melting tokens (i.e. paying a Lightning invoice). In short, a wallet includes _blank outputs_ when paying a Lightning invoice which can be assigned a value by the mint if the user has overpaid Lightning fees. This can be the case due to the unpredictability of Lightning network fees. To solve this issue, we introduce so-called _blank outputs_ which are blinded messages with an undetermined value. +This document describes how overpaid fees are handled during payments and extends [NUT-05](05.md), which describes the process of melting tokens (i.e., making a payment). Due to the unpredictability of payment network fees (e.g., Lightning network), a wallet may overpay fees. To address this, the wallet provides _blank outputs_ that allow the mint to return overpaid fees. These blank outputs are blinded messages with an undetermined value, which the mint assigns after calculating the overpaid amount. -The problem is also described in [this gist](https://gist.github.com/callebtc/a6cc0bd2b6f70e081e478147c40fc578). +--- ## Description -Before requesting a Lightning payment as described in [NUT-05][05], `Alice` produces a number of `BlindedMessage` which are similar to ordinary blinded messages but their value is yet to be determined by the mint `Bob` and are thus called _blank outputs_. The number of necessary blank outputs is `max(ceil(log2(fee_reserve)), 1)` which ensures that there is at least one output if there is any fee. If the `fee_reserve` is `0`, then the number of blank outputs is `0` as well. The blank outputs will contain the overpaid fees that will be returned by the mint to the wallet. - -This code calculates the number of necessary blank outputs in Python: +Before initiating a melt, the wallet generates a number of _blank outputs_. These are similar to blinded messages but do not yet carry a value. The number of required blank outputs is calculated as: ```python -def calculate_number_of_blank_outputs(fee_reserve_sat: int) -> int: - assert fee_reserve_sat >= 0, "Fee reserve can't be negative." - if fee_reserve_sat == 0: +def calculate_blank_outputs(fee_reserve: int) -> int: + assert fee_reserve >= 0, "Fee reserve must be non-negative." + if fee_reserve == 0: return 0 - return max(math.ceil(math.log2(fee_reserve_sat)), 1) + return max(math.ceil(math.log2(fee_reserve)), 1) ``` -## Example +The blank outputs ensure the mint can efficiently return overpaid fees by assigning values derived from powers of two (as described in [NUT-00](00.md)). If no fees are overpaid (`fee_reserve = actual_fees`), the mint does not return any blank outputs. + +--- + +### Wallet Workflow + +1. **Request Fee Reserve** + The wallet requests a melt quote using `POST /v1/melt/quote/{method}` (see [NUT-05](05.md)). This quote includes the `fee_reserve` required for the payment. + +2. **Generate Blank Outputs** + The wallet calculates the number of blank outputs based on the `fee_reserve` and generates blinded messages to include in the payment request. -The wallet wants to pay an invoice with `amount := 100 000 sat` and determines by asking the mint that `fee_reserve` is `1000 sats`. The wallet then provides `101 000 sat` worth of proofs and 10 blank `outputs` to make the payment (since `ceil(log2(1000))=ceil(9.96..)=10`). The mint pays the invoice and determines that the actual fee was `100 sat`, i.e, the overpaid fee to return is `fee_return = 900 sat`. The mint splits the amount `900` into summands of `2^n` which is `4, 128, 256, 512`. The mint inserts these amounts into the blank `outputs` it received form the wallet and generates 4 new promises. The mint then returns these `BlindSignature`s to the wallet together with the successful payment status. +3. **Perform Payment** + The wallet submits the payment request, including: + - `inputs` that satisfy `amount + fee_reserve` + - The original `quote` + - The blank `outputs` for potential overpaid fees. -## Wallet flow +4. **Receive Fee Return** + If fees are overpaid (`fee_reserve > actual_fees`), the mint assigns values to the blank outputs and returns them as `BlindSignatures` in the response. -The wallet asks the mint for the `fee_reserve` for paying a specific bolt11 invoice of value `amount` by calling `POST /v1/melt/quote` as described in [NUT-05][05]. The wallet then provides a `PostMeltBolt11Request` to `POST /v1/melt/bolt11` that has (1) proofs of the value `amount+fee_reserve`, (2) the bolt11 invoice to be paid, and finally, as a new entry, (3) a field `outputs` that has `n_blank_outputs` blinded messages that are generated before the payment attempt to receive potential overpaid fees back to her. +--- + +### Mint Workflow -## Mint flow +1. **Calculate Overpaid Fees** + Upon receiving a payment request with blank outputs, the mint calculates the overpaid fees (`fee_reserve - actual_fees`). -Here we describe how the mint generates `BlindSignature`s for the overpaid fees. The mint `Bob` returns in `PostMeltQuoteBolt11Response` the field `change` **ONLY IF** `Alice` has previously provided `outputs` for the change **AND** if the Lightning `actual_fees` were smaller than the `fee_reserve`. +2. **Assign Values to Blank Outputs** + The mint decomposes the overpaid amount into powers of two and assigns these values to the blank outputs. Only outputs with non-zero values are retained. -If the `overpaid_fees = fee_reserve - actual_fees` is positive, `Bob` decomposes it to values of `2^n` (as in [NUT-00][00]) and then imprints them into the `blank_outputs` provided by `Alice`. +3. **Return Blind Signatures** + The mint generates `BlindSignatures` for the assigned values and returns them to the wallet in the response. -`Bob` then signs these blank outputs (now with the imprinted amounts) and thus generates `BlindSignature`s. `Bob` then returns a payment status to the wallet, and, in addition, all blind signatures it generated for the overpaid fees. +--- -Importantly, while `Bob` does not necessarily return the same number of blind signatures as it received blank outputs from `Alice` (since some of them may be of value 0), `Bob` **MUST** return the all blank signatures with a value greater than 0 in the same order as the blank outputs were received and should omit all blind signatures with value 0. For example, if `Bob` receives 10 blank outputs but the overpaid fees only occupy 4 blind signatures, `Bob` will only return these 4 blind signatures with the appropriate imprinted amounts and omit the remaining 6 blind signatures with value 0. Due to the well-defined order of the returned blind signatures, `Alice` can map the blind signatures returned from `Bob` to the blank outputs it provided so that she can further apply the correct unblinding operations on them. +## API Changes -## Example +### Payment Request -**Request** of `Alice`: +The wallet includes blank outputs in the melt request: ```http -POST https://mint.host:3338/v1/melt/bolt11 +POST https://mint.host:3338/v1/melt/{method} ``` -With the data being of the form `PostMeltBolt11Request`: +#### Request Body ```json { "quote": , "inputs": , - "outputs": <-- New + "outputs": } ``` -where the new `output` field carries the `BlindMessages`. +- `quote`: The melt quote ID. +- `inputs`: Proofs for `amount + fee_reserve`. +- `outputs`: Blank outputs for overpaid fees. -The mint `Bob` then responds with a `PostMeltQuoteBolt11Response`: +--- + +### Payment Response + +The mint returns any overpaid fees as part of the response: ```json { "quote": , - "amount": , - "fee_reserve": , "state": , - "expiry": , "payment_preimage": , - "change": <-- New + "change": } ``` -where the new `change` field carries the returned `BlindSignature`s due to overpaid fees. +- `quote`: The quote ID. +- `state`: The payment state (`PAID`, `PENDING`, `UNPAID`). +- `payment_preimage`: Payment preimage (if applicable). +- `change`: Blind signatures for overpaid fees. -## Example +--- + +## Example: Wallet Request -Request of `Alice` with curl: +The wallet submits a melt request: ```bash -curl -X POST https://mint.host:3338/v1/melt/bolt11 -d \ +curl -X POST https://mint.host:3338/v1/melt/{method} -d \ '{ "quote": "od4CN5smMMS3K3QVHkbGGNCTxfcAIyIXeq8IrfhP", "inputs": [ { - "amount": 4, + "amount": 1000, "id": "009a1f293253e41e", "secret": "429700b812a58436be2629af8731a31a37fce54dbf8cbbe90b3f8553179d23f5", - "C": "03b01869f528337e161a6768b480fcf9f75fd248b649c382f5e352489fd84fd011", - }, - { - "amount": 8, - "id": "009a1f293253e41e", - "secret": "4f3155acef6481108fcf354f6d06e504ce8b441e617d30c88924991298cdbcad", - "C": "0278ab1c1af35487a5ea903b693e96447b2034d0fd6bac529e753097743bf73ca9", + "C": "03b01869f528337e161a6768b480fcf9f75fd248b649c382f5e352489fd84fd011" } ], "outputs": [ @@ -110,31 +128,29 @@ curl -X POST https://mint.host:3338/v1/melt/bolt11 -d \ }' ``` -Everything here is the same as in [NUT-05][05] except for `outputs`. The `amount` field in the `BlindedMessage`s here are ignored by `Bob` so they can be set to any arbitrary value by `Alice` (they should be set to a value, like `1` so potential JSON validations do not error). +--- -If the mint has made a successful payment, it will respond the following. +## Example: Mint Response -**Response** `PostMeltQuoteBolt11Response` from `Bob`: +The mint responds with assigned values for overpaid fees: ```json { + "quote": "od4CN5smMMS3K3QVHkbGGNCTxfcAIyIXeq8IrfhP", "state": "PAID", - "payment_preimage": "c5a1ae1f639e1f4a3872e81500fd028bece7bedc1152f740cba5c3417b748c1b", "change": [ { "id": "009a1f293253e41e", - "amount": 2, + "amount": 512, "C_": "03c668f551855ddc792e22ea61d32ddfa6a45b1eb659ce66e915bf5127a8657be0" } ] } ``` -The field `change` is an array of `BlindSignatures` that account for the overpaid fees. Notice that the amount has been changed by the mint. `Alice` must take these and generate `Proofs` by unblinding them as described in [NUT-00][00] and as she does in [NUT-04][04] when minting new tokens. After generating the `Proofs`, `Alice` stores them in her database. +## Mint Info Setting -## Mint info setting - -The [NUT-06][06] `MintMethodSetting` indicates support for this feature: +The [NUT-06][06.md] `MintMethodSetting` indicates support for this feature: ```json { @@ -142,18 +158,4 @@ The [NUT-06][06] `MintMethodSetting` indicates support for this feature: "supported": 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 +``` \ No newline at end of file diff --git a/20.md b/20.md new file mode 100644 index 0000000..049c56e --- /dev/null +++ b/20.md @@ -0,0 +1,192 @@ +# NUT-20: Bolt11 Payment Method + +`optional` + +--- + +**Method URI Name:** `bolt11` + +This specification defines how wallets and mints **MUST** interact when using the `bolt11` payment method for minting and melting tokens. The `bolt11` method relies on [Bolt11](https://github.com/lightning/bolts/blob/master/11-payment-encoding.md) invoices for Lightning Network payment processing. + +--- + +## Overview + +The `bolt11` payment method allows wallets to interact with mints for: + +1. **Minting Tokens** + The wallet requests a mint quote specifying the desired amount in a supported unit (e.g., `sat`). The mint responds with a `bolt11` invoice in the `request` field of the quote, which the wallet must pay to proceed with token minting. + +2. **Melting Tokens** + The wallet requests a melt quote specifying a `bolt11` invoice to be paid by the mint. The mint responds with the required amount and fee reserve. After payment is completed, the mint updates the payment state and optionally returns any overpaid fees (see [NUT-08](08.md)). + +--- + +## Minting Tokens + +When a wallet requests a mint quote with the `bolt11` payment method, the mint **MUST** respond with: + +- A unique `quote` ID. +- A `bolt11` invoice for the requested amount. +- The quote `state` and `expiry`. + +### Example: Mint Quote Request + +**Request:** + +```http +POST https://mint.host:3338/v1/mint/quote/bolt11 +``` + +**Request Body:** + +```json +{ + "amount": 10, + "unit": "sat", + "opts": { + "description": "bolt11 example" + } +} +``` + +**Response:** + +```json +{ + "quote": "DSGLX9kevM...", + "request": "lnbc10n1pj4apw9pp5e3j...", + "state": "UNPAID", + "expiry": 1701704757 +} +``` + +--- + +### Payment Workflow + +1. **Wallet Request:** + The wallet submits a mint quote request specifying the desired `amount`, `unit`, and optional `description`. + +2. **Mint Response:** + The mint generates a `bolt11` invoice for the specified amount and returns it in the `request` field of the response. + +3. **Invoice Payment:** + The wallet **MUST** pay the invoice using the Lightning Network and then submit a minting request (see [NUT-04](04.md)). + +--- + +## Melting Tokens + +When a wallet requests a melt quote with the `bolt11` payment method, the mint **MUST**: + +1. Provide a `quote` ID and calculate the required `amount` and `fee_reserve` for the payment. +2. Respond with the state of the melt quote (`UNPAID`, `PENDING`, `PAID`). +3. After receiving sufficient `inputs`, execute the payment and update the quote state. + +--- + +### Example: Melt Quote Request + +**Request:** + +```http +POST https://mint.host:3338/v1/melt/quote/bolt11 +``` + +**Request Body:** + +```json +{ + "request": "lnbc100n1pj3k...", + "unit": "sat" +} +``` + +**Response:** + +```json +{ + "quote": "TRmjduhIsPxd...", + "amount": 100000, + "fee_reserve": 2000, + "state": "UNPAID", + "expiry": 1701704757 +} +``` + +--- + +### Example: Performing a Melt + +**Request:** + +```http +POST https://mint.host:3338/v1/melt/bolt11 +``` + +**Request Body:** + +```json +{ + "quote": "TRmjduhIsPxd...", + "inputs": [ + { + "amount": 102000, + "id": "009a1f293253e41e", + "secret": "429700b812a58436be2629af8731a31a37fce54dbf8cbbe90b3f8553179d23f5", + "C": "03b01869f528337e161a6768b480fcf9f75fd248b649c382f5e352489fd84fd011" + } + ], + "outputs": [ + { + "amount": 1, + "id": "blank_output_id_1", + "B_": "03327fc4fa333909b70f08759e217ce5c94e6bf1fc2382562f3c560c5580fa69f4" + } + ] +} +``` + +**Response:** + +```json +{ + "quote": "TRmjduhIsPxd...", + "state": "PAID", + "payment_preimage": "c5a1ae1f639e1f4a3872e81500fd028bece7bedc1152f740cba5c3417b748c1b", + "change": [ + { + "id": "009a1f293253e41e", + "amount": 512, + "C_": "03c668f551855ddc792e22ea61d32ddfa6a45b1eb659ce66e915bf5127a8657be0" + } + ] +} +``` + +--- + +## Handling Fee Overpayments + +- The mint **SHOULD** support returning overpaid fees using blank outputs as described in [NUT-08](08.md). + +--- + +### Mint Info Settings + +The mint **MUST** advertise its support for `bolt11` in its settings (see [NUT-06](06.md)): + +```json +{ + "methods": [ + { + "method": "bolt11", + "unit": "sat", + "min_amount": 1000, + "max_amount": 1000000 + } + ], + "disabled": false +} +```