From da4bb253542ef98e257a4345913bb9913c15ccc5 Mon Sep 17 00:00:00 2001 From: gudnuf Date: Mon, 25 Aug 2025 16:25:14 -0700 Subject: [PATCH 01/10] onchain bitcoin payment method --- 26.md | 322 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 26.md diff --git a/26.md b/26.md new file mode 100644 index 00000000..6cc11a0a --- /dev/null +++ b/26.md @@ -0,0 +1,322 @@ +# NUT-26: Onchain + +`optional` + +`depends on: NUT-04 NUT-05 NUT-20` + +--- + +This document describes minting and melting ecash with the `onchain` payment method, which uses Bitcoin onchain transactions. It is an extension of [NUT-04][04] and [NUT-05][05] which cover the protocol steps of minting and melting ecash shared by any supported payment method. + +## Mint Quote + +For the `onchain` method, the wallet includes the following specific `PostMintQuoteOnchainRequest` data: + +```json +{ + "unit": , + "pubkey": +} +``` + +> **Note:** While a pubkey is optional as per [NUT-20][20] for [NUT-04][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 `PostMintQuoteOnchainResponse`: + +```json +{ + "quote": , + "request": , + "unit": , + "expiry": , + "pubkey": , + "amount_paid": , + "amount_issued": , + "amount_unconfirmed": +} +``` + +Where: + +- `quote` is the quote ID +- `request` is the Bitcoin address to send funds to +- `expiry` is the Unix timestamp until which the mint quote is valid +- `pubkey` is the public key from the request +- `amount_paid` is the total confirmed amount paid to the mint via the onchain address +- `amount_issued` is the amount of ecash that has been issued for the given mint quote +- `amount_unconfirmed` is the amount that has been received but is waiting for sufficient confirmations + +### Example + +**Request** with curl: + +```bash +curl -X POST http://localhost:3338/v1/mint/quote/onchain -d \ +'{"unit": "sat", "pubkey": "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac"}' \ +-H "Content-Type: application/json" +``` + +**Response**: + +```json +{ + "quote": "DSGLX9kevM...", + "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", + "unit": "sat", + "expiry": 1701704757, + "pubkey": "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac", + "amount_paid": 0, + "amount_issued": 0, + "amount_unconfirmed": 0 +} +``` + +Check quote state: + +```bash +curl -X GET http://localhost:3338/v1/mint/quote/onchain/DSGLX9kevM... +``` + +After sending ₿100,000 to the address (transaction detected but unconfirmed): + +```json +{ + "quote": "DSGLX9kevM...", + "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", + "unit": "sat", + "expiry": 1701704757, + "pubkey": "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac", + "amount_paid": 0, + "amount_issued": 0, + "amount_unconfirmed": 100000 +} +``` + +Once the transaction has sufficient confirmations: + +```json +{ + "quote": "DSGLX9kevM...", + "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", + "unit": "sat", + "expiry": 1701704757, + "pubkey": "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac", + "amount_paid": 100000, + "amount_issued": 0, + "amount_unconfirmed": 0 +} +``` + +Minting tokens: + +```bash +curl -X POST https://mint.host:3338/v1/mint/onchain -H "Content-Type: application/json" -d \ +'{ + "quote": "DSGLX9kevM...", + "outputs": [ + { + "amount": 50000, + "id": "009a1f293253e41e", + "B_": "035015e6d7ade60ba8426cefaf1832bbd27257636e44a76b922d78e79b47cb689d" + }, + { + "amount": 50000, + "id": "009a1f293253e41e", + "B_": "0288d7649652d0a83fc9c966c969fb217f15904431e61a44b14999fabc1b5d9ac6" + } + ] +}' +``` + +Response: + +```json +{ + "signatures": [ + { + "id": "009a1f293253e41e", + "amount": 50000, + "C_": "0224f1c4c564230ad3d96c5033efdc425582397a5a7691d600202732edc6d4b1ec" + }, + { + "id": "009a1f293253e41e", + "amount": 50000, + "C_": "0277d1de806ed177007e5b94a8139343b6382e472c752a74e99949d511f7194f6c" + } + ] +} +``` + +## Multiple Deposits + +Unlike lightning payments, onchain addresses can receive multiple payments, allowing the wallet to make multiple deposits for one quote. The wallet can call the check onchain endpoint, where the mint will return the `PostMintQuoteOnchainResponse` including `amount_paid`, `amount_issued`, and `amount_unconfirmed`. The difference between `amount_paid` and `amount_issued` represents how much the wallet can mint by calling the mint endpoint. + +## Mint Settings + +A `confirmations` option **SHOULD** be set to indicate the minimum depth in the blockahin for a transaction to be considered confirmed. + +### Example `MintMethodSetting` + +```json +{ + "method": "onchain", + "unit": , + "min_amount": , + "max_amount": , + "options": { + "confirmations": + } +} +``` + +## Melt Quote + +For the `onchain` method, the wallet includes the following specific `PostMeltQuoteOnchainRequest` data: + +```json +{ + "request": , + "unit": , + "amount": +} +``` + +Here, `request` is the Bitcoin address to send to, `unit` is the unit the wallet would like to pay with, and `amount` is the amount to send in the specified unit. + +The mint responds with a `PostMeltQuoteOnchainResponse`: + +```json +{ + "quote": , + "request": , + "amount": , + "unit": , + "fee_reserve": , + "state": , + "expiry": , + "transaction_id": +} +``` + +Where `fee_reserve` is the additional fee reserve required to broadcast the transaction. 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][02]. + +`state` is an enum string field with possible values `"UNPAID"`, `"PENDING"`, `"PAID"`: + +- `"UNPAID"` means that the transaction has not been broadcast yet. +- `"PENDING"` means that the transaction has been broadcast but not yet confirmed. +- `"PAID"` means that the transaction has been confirmed. + +`transaction_id` is the Bitcoin transaction ID, present once the transaction has been broadcast. + +### Melting Tokens + +For the `onchain` method, the wallet can include an optional `outputs` field in the melt request to receive change for overpaid transaction fees (see [NUT-08][08]): + +```json +{ + "quote": , + "inputs": , + "outputs": // 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: + +```json +{ + "quote": , + "request": , + "amount": , + "unit": , + "fee_reserve": , + "state": , + "expiry": , + "transaction_id": , + "change": // Present if outputs were included and there's change +} +``` + +### Example + +**Melt quote request**: + +```bash +curl -X POST https://mint.host:3338/v1/melt/quote/onchain -d \ +'{"request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", "unit": "sat", "amount": 100000}' +``` + +**Melt quote response**: + +```json +{ + "quote": "TRmjduhIsPxd...", + "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", + "amount": 100000, + "unit": "sat", + "fee_reserve": 2000, + "state": "UNPAID", + "expiry": 1701704757 +} +``` + +Check quote state: + +```bash +curl -X GET http://localhost:3338/v1/melt/quote/onchain/TRmjduhIsPxd... +``` + +**Melt request**: + +```bash +curl -X POST https://mint.host:3338/v1/melt/onchain -d \ +'{"quote": "TRmjduhIsPxd...", "inputs": [...]}' +``` + +**Successful melt response** (transaction broadcast): + +```json +{ + "quote": "TRmjduhIsPxd...", + "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", + "amount": 100000, + "unit": "sat", + "fee_reserve": 2000, + "state": "PENDING", + "expiry": 1701704757, + "transaction_id": "3b7f3b85c5f1a3c4d2b8e9f6a7c5d8e9f1a2b3c4d5e6f7a8b9c1d2e3f4a5b6c7" +} +``` + +**Final confirmed state**: + +```json +{ + "quote": "TRmjduhIsPxd...", + "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", + "amount": 100000, + "unit": "sat", + "fee_reserve": 2000, + "state": "PAID", + "expiry": 1701704757, + "transaction_id": "3b7f3b85c5f1a3c4d2b8e9f6a7c5d8e9f1a2b3c4d5e6f7a8b9c1d2e3f4a5b6c7" +} +``` + +### Example `MeltMethodSetting` + +```json +{ + "method": "onchain", + "unit": , + "min_amount": , + "max_amount": , +} +``` + +[02]: 02.md +[04]: 04.md +[05]: 05.md +[08]: 08.md +[20]: 20.md From 437a796c207d5b31c7852d3cfa0a7f5158da0cbd Mon Sep 17 00:00:00 2001 From: gudnuf <108303703+gudnuf@users.noreply.github.com> Date: Thu, 28 Aug 2025 13:00:34 -0700 Subject: [PATCH 02/10] Update 26.md (add comma) Co-authored-by: asmo --- 26.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/26.md b/26.md index 6cc11a0a..666198d0 100644 --- a/26.md +++ b/26.md @@ -19,7 +19,7 @@ For the `onchain` method, the wallet includes the following specific `PostMintQu } ``` -> **Note:** While a pubkey is optional as per [NUT-20][20] for [NUT-04][04] it is required in this NUT and the mint **MUST NOT** issue a mint quote if one is not included. +> **Note:** While a pubkey is optional as per [NUT-20][20] for [NUT-04][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. From f8bf489cd56a603b4549fc91c9a469d0200cfa53 Mon Sep 17 00:00:00 2001 From: gudnuf <108303703+gudnuf@users.noreply.github.com> Date: Thu, 28 Aug 2025 13:02:52 -0700 Subject: [PATCH 03/10] make `amount_unconfirmed` optional Co-authored-by: asmo --- 26.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/26.md b/26.md index 666198d0..29fbc7eb 100644 --- a/26.md +++ b/26.md @@ -34,7 +34,7 @@ The mint responds with a `PostMintQuoteOnchainResponse`: "pubkey": , "amount_paid": , "amount_issued": , - "amount_unconfirmed": + "amount_unconfirmed": } ``` From aaabef4b7833122b234e4dfb186512aed2aea7ee Mon Sep 17 00:00:00 2001 From: gudnuf Date: Fri, 29 Aug 2025 14:28:49 -0700 Subject: [PATCH 04/10] remove amount_unconfirmed from PostMintQuoteOnchainRequest and nits --- 26.md | 75 ++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/26.md b/26.md index 29fbc7eb..b1d863ca 100644 --- a/26.md +++ b/26.md @@ -19,7 +19,7 @@ For the `onchain` method, the wallet includes the following specific `PostMintQu } ``` -> **Note:** While a pubkey is optional as per [NUT-20][20] for [NUT-04][04], it is required in this NUT and the mint **MUST NOT** issue a mint quote if one is not included. +> **Note:** A [NUT-20][20] `pubkey` 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. @@ -34,7 +34,6 @@ The mint responds with a `PostMintQuoteOnchainResponse`: "pubkey": , "amount_paid": , "amount_issued": , - "amount_unconfirmed": } ``` @@ -44,13 +43,12 @@ Where: - `request` is the Bitcoin address to send funds to - `expiry` is the Unix timestamp until which the mint quote is valid - `pubkey` is the public key from the request -- `amount_paid` is the total confirmed amount paid to the mint via the onchain address +- `amount_paid` is the total confirmed amount paid to the request - `amount_issued` is the amount of ecash that has been issued for the given mint quote -- `amount_unconfirmed` is the amount that has been received but is waiting for sufficient confirmations ### Example -**Request** with curl: +**1. Create mint quote:** ```bash curl -X POST http://localhost:3338/v1/mint/quote/onchain -d \ @@ -58,7 +56,7 @@ curl -X POST http://localhost:3338/v1/mint/quote/onchain -d \ -H "Content-Type: application/json" ``` -**Response**: +**Response:** ```json { @@ -68,33 +66,19 @@ curl -X POST http://localhost:3338/v1/mint/quote/onchain -d \ "expiry": 1701704757, "pubkey": "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac", "amount_paid": 0, - "amount_issued": 0, - "amount_unconfirmed": 0 + "amount_issued": 0 } ``` -Check quote state: +**2. Send payment to the Bitcoin address and check quote state:** ```bash curl -X GET http://localhost:3338/v1/mint/quote/onchain/DSGLX9kevM... ``` -After sending ₿100,000 to the address (transaction detected but unconfirmed): - -```json -{ - "quote": "DSGLX9kevM...", - "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", - "unit": "sat", - "expiry": 1701704757, - "pubkey": "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac", - "amount_paid": 0, - "amount_issued": 0, - "amount_unconfirmed": 100000 -} -``` +**Response after payment reaches the required number of confirmations:** -Once the transaction has sufficient confirmations: +> **Note:** The quote state will only update to show `amount_paid` once the Bitcoin transaction has reached the minimum number of confirmations specified in the mint's settings. ```json { @@ -104,12 +88,11 @@ Once the transaction has sufficient confirmations: "expiry": 1701704757, "pubkey": "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac", "amount_paid": 100000, - "amount_issued": 0, - "amount_unconfirmed": 0 + "amount_issued": 0 } ``` -Minting tokens: +**3. Mint tokens using the confirmed quote:** ```bash curl -X POST https://mint.host:3338/v1/mint/onchain -H "Content-Type: application/json" -d \ @@ -130,7 +113,7 @@ curl -X POST https://mint.host:3338/v1/mint/onchain -H "Content-Type: applicatio }' ``` -Response: +**Response:** ```json { @@ -138,7 +121,7 @@ Response: { "id": "009a1f293253e41e", "amount": 50000, - "C_": "0224f1c4c564230ad3d96c5033efdc425582397a5a7691d600202732edc6d4b1ec" + "C_": "0224f1c4c564230ad3d96c5033efdc425582a5a7691d600202732edc6d4b1ec" }, { "id": "009a1f293253e41e", @@ -151,11 +134,35 @@ Response: ## Multiple Deposits -Unlike lightning payments, onchain addresses can receive multiple payments, allowing the wallet to make multiple deposits for one quote. The wallet can call the check onchain endpoint, where the mint will return the `PostMintQuoteOnchainResponse` including `amount_paid`, `amount_issued`, and `amount_unconfirmed`. The difference between `amount_paid` and `amount_issued` represents how much the wallet can mint by calling the mint endpoint. +Onchain addresses can receive multiple payments, allowing the wallet to make multiple deposits for one quote. The wallet can call the check onchain endpoint, where the mint will return the `PostMintQuoteOnchainResponse` including `amount_paid` and `amount_issued`. The difference between `amount_paid` and `amount_issued` represents how much the wallet can mint by calling the mint endpoint. + +### Example - Additional Payment + +Continuing the previous example, if another payment is sent to the same address `bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh` and reaches the required confirmations: + +```bash +curl -X GET http://localhost:3338/v1/mint/quote/onchain/DSGLX9kevM... +``` + +**Response after second payment is confirmed:** + +```json +{ + "quote": "DSGLX9kevM...", + "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", + "unit": "sat", + "expiry": 1701704757, + "pubkey": "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac", + "amount_paid": 150000, + "amount_issued": 100000 +} +``` + +The wallet can now mint an additional ₿50,000 worth of ecash. ## Mint Settings -A `confirmations` option **SHOULD** be set to indicate the minimum depth in the blockahin for a transaction to be considered confirmed. +A `confirmations` option **SHOULD** be set to indicate the minimum depth in the blockchain for a transaction to be considered confirmed. ### Example `MintMethodSetting` @@ -183,7 +190,11 @@ For the `onchain` method, the wallet includes the following specific `PostMeltQu } ``` -Here, `request` is the Bitcoin address to send to, `unit` is the unit the wallet would like to pay with, and `amount` is the amount to send in the specified unit. +Where: + +- `request` is the Bitcoin address to send to +- `unit` is the unit the wallet would like to pay with +- `amount` is the amount to send in the specified unit The mint responds with a `PostMeltQuoteOnchainResponse`: From 03678943dc9fe3fca44f8818f0fc0ed2f0e8e41a Mon Sep 17 00:00:00 2001 From: gudnuf <108303703+gudnuf@users.noreply.github.com> Date: Sat, 30 Aug 2025 19:29:53 -0700 Subject: [PATCH 05/10] change transaction_id to txid:vout --- 26.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/26.md b/26.md index b1d863ca..9fdcacd5 100644 --- a/26.md +++ b/26.md @@ -207,7 +207,7 @@ The mint responds with a `PostMeltQuoteOnchainResponse`: "fee_reserve": , "state": , "expiry": , - "transaction_id": + "txid:vout": } ``` @@ -219,7 +219,7 @@ Where `fee_reserve` is the additional fee reserve required to broadcast the tran - `"PENDING"` means that the transaction has been broadcast but not yet confirmed. - `"PAID"` means that the transaction has been confirmed. -`transaction_id` is the Bitcoin transaction ID, present once the transaction has been broadcast. +`txid:vout` is the outpoint of the payment, present once the transaction has been broadcast. ### Melting Tokens @@ -244,7 +244,7 @@ If the `outputs` field is included and there is excess from the `fee_reserve`, t "fee_reserve": , "state": , "expiry": , - "transaction_id": , + "txid:vout": , "change": // Present if outputs were included and there's change } ``` @@ -296,7 +296,7 @@ curl -X POST https://mint.host:3338/v1/melt/onchain -d \ "fee_reserve": 2000, "state": "PENDING", "expiry": 1701704757, - "transaction_id": "3b7f3b85c5f1a3c4d2b8e9f6a7c5d8e9f1a2b3c4d5e6f7a8b9c1d2e3f4a5b6c7" + "txid:vout": "3b7f3b85c5f1a3c4d2b8e9f6a7c5d8e9f1a2b3c4d5e6f7a8b9c1d2e3f4a5b6c7:2" } ``` @@ -311,7 +311,7 @@ curl -X POST https://mint.host:3338/v1/melt/onchain -d \ "fee_reserve": 2000, "state": "PAID", "expiry": 1701704757, - "transaction_id": "3b7f3b85c5f1a3c4d2b8e9f6a7c5d8e9f1a2b3c4d5e6f7a8b9c1d2e3f4a5b6c7" + "txid:vout": "3b7f3b85c5f1a3c4d2b8e9f6a7c5d8e9f1a2b3c4d5e6f7a8b9c1d2e3f4a5b6c7:2" } ``` From 73f0e510eb5dbb848b4cd9ab1dc88e910ce2b3a5 Mon Sep 17 00:00:00 2001 From: gudnuf Date: Sat, 30 Aug 2025 19:48:31 -0700 Subject: [PATCH 06/10] update melting to return an array of PostMeltQuoteOnchainResponse --- 26.md | 77 +++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 23 deletions(-) diff --git a/26.md b/26.md index 9fdcacd5..606d023e 100644 --- a/26.md +++ b/26.md @@ -196,22 +196,27 @@ Where: - `unit` is the unit the wallet would like to pay with - `amount` is the amount to send in the specified unit -The mint responds with a `PostMeltQuoteOnchainResponse`: +The mint responds with an array of `PostMeltQuoteOnchainResponse`: ```json -{ - "quote": , - "request": , - "amount": , - "unit": , - "fee_reserve": , - "state": , - "expiry": , - "txid:vout": -} +[ + { + "quote": , + "request": , + "amount": , + "unit": , + "fee_reserve": , + "estimated_blocks": , + "state": , + "expiry": , + "txid:vout": + } +] ``` -Where `fee_reserve` is the additional fee reserve required to broadcast the transaction. 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][02]. +The mint can return multiple `PostMeltQuoteOnchainResponse` with different `fee_reserve` amounts and `estimated_blocks`. The wallet can choose which quote to use for melting, and the other quotes will expire. + +Where `fee_reserve` is the additional fee reserve required to broadcast the transaction, and `estimated_blocks` is the estimated number of blocks until confirmation. 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][02]. `state` is an enum string field with possible values `"UNPAID"`, `"PENDING"`, `"PAID"`: @@ -242,6 +247,7 @@ If the `outputs` field is included and there is excess from the `fee_reserve`, t "amount": , "unit": , "fee_reserve": , + "estimated_blocks": , "state": , "expiry": , "txid:vout": , @@ -261,15 +267,38 @@ curl -X POST https://mint.host:3338/v1/melt/quote/onchain -d \ **Melt quote response**: ```json -{ - "quote": "TRmjduhIsPxd...", - "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", - "amount": 100000, - "unit": "sat", - "fee_reserve": 2000, - "state": "UNPAID", - "expiry": 1701704757 -} +[ + { + "quote": "TRmjduhIsPxd...", + "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", + "amount": 100000, + "unit": "sat", + "fee_reserve": 5000, + "estimated_blocks": 1, + "state": "UNPAID", + "expiry": 1701704757 + }, + { + "quote": "OewtRaqeXmzK...", + "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", + "amount": 100000, + "unit": "sat", + "fee_reserve": 2000, + "estimated_blocks": 6, + "state": "UNPAID", + "expiry": 1701704757 + }, + { + "quote": "KfPqNghzLvtY...", + "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", + "amount": 100000, + "unit": "sat", + "fee_reserve": 800, + "estimated_blocks": 144, + "state": "UNPAID", + "expiry": 1701704757 + } +] ``` Check quote state: @@ -293,7 +322,8 @@ curl -X POST https://mint.host:3338/v1/melt/onchain -d \ "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", "amount": 100000, "unit": "sat", - "fee_reserve": 2000, + "fee_reserve": 5000, + "estimated_blocks": 1, "state": "PENDING", "expiry": 1701704757, "txid:vout": "3b7f3b85c5f1a3c4d2b8e9f6a7c5d8e9f1a2b3c4d5e6f7a8b9c1d2e3f4a5b6c7:2" @@ -308,7 +338,8 @@ curl -X POST https://mint.host:3338/v1/melt/onchain -d \ "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", "amount": 100000, "unit": "sat", - "fee_reserve": 2000, + "fee_reserve": 5000, + "estimated_blocks": 1, "state": "PAID", "expiry": 1701704757, "txid:vout": "3b7f3b85c5f1a3c4d2b8e9f6a7c5d8e9f1a2b3c4d5e6f7a8b9c1d2e3f4a5b6c7:2" From 10c8665bdcefdbe044da695373035b47b0919c3b Mon Sep 17 00:00:00 2001 From: gudnuf Date: Sat, 30 Aug 2025 19:55:22 -0700 Subject: [PATCH 07/10] change fee_reserve to fee and remove NUT-08 from melt response --- 26.md | 43 +++++++++---------------------------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/26.md b/26.md index 606d023e..850ad795 100644 --- a/26.md +++ b/26.md @@ -205,7 +205,7 @@ The mint responds with an array of `PostMeltQuoteOnchainResponse`: "request": , "amount": , "unit": , - "fee_reserve": , + "fee": , "estimated_blocks": , "state": , "expiry": , @@ -214,9 +214,9 @@ The mint responds with an array of `PostMeltQuoteOnchainResponse`: ] ``` -The mint can return multiple `PostMeltQuoteOnchainResponse` with different `fee_reserve` amounts and `estimated_blocks`. The wallet can choose which quote to use for melting, and the other quotes will expire. +The mint can return multiple `PostMeltQuoteOnchainResponse` with different `fee` amounts and `estimated_blocks`. The wallet can choose which quote to use for melting, and the other quotes will expire. -Where `fee_reserve` is the additional fee reserve required to broadcast the transaction, and `estimated_blocks` is the estimated number of blocks until confirmation. 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][02]. +Where `fee` is the additional fee reserve required to broadcast the transaction, and `estimated_blocks` is the estimated number of blocks until confirmation. The mint expects the wallet to include `Proofs` of _at least_ `total_amount = amount + fee + input_fee` where `input_fee` is calculated from the keyset's `input_fee_ppk` as described in [NUT-02][02]. `state` is an enum string field with possible values `"UNPAID"`, `"PENDING"`, `"PAID"`: @@ -228,32 +228,7 @@ Where `fee_reserve` is the additional fee reserve required to broadcast the tran ### Melting Tokens -For the `onchain` method, the wallet can include an optional `outputs` field in the melt request to receive change for overpaid transaction fees (see [NUT-08][08]): - -```json -{ - "quote": , - "inputs": , - "outputs": // 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: - -```json -{ - "quote": , - "request": , - "amount": , - "unit": , - "fee_reserve": , - "estimated_blocks": , - "state": , - "expiry": , - "txid:vout": , - "change": // Present if outputs were included and there's change -} -``` +> **NOTE**: For the `onchain` method, `fee` is the absolute fee of the melt quote and the mint will not return [NUT-08][08] change outputs. ### Example @@ -273,7 +248,7 @@ curl -X POST https://mint.host:3338/v1/melt/quote/onchain -d \ "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", "amount": 100000, "unit": "sat", - "fee_reserve": 5000, + "fee": 5000, "estimated_blocks": 1, "state": "UNPAID", "expiry": 1701704757 @@ -283,7 +258,7 @@ curl -X POST https://mint.host:3338/v1/melt/quote/onchain -d \ "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", "amount": 100000, "unit": "sat", - "fee_reserve": 2000, + "fee": 2000, "estimated_blocks": 6, "state": "UNPAID", "expiry": 1701704757 @@ -293,7 +268,7 @@ curl -X POST https://mint.host:3338/v1/melt/quote/onchain -d \ "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", "amount": 100000, "unit": "sat", - "fee_reserve": 800, + "fee": 800, "estimated_blocks": 144, "state": "UNPAID", "expiry": 1701704757 @@ -322,7 +297,7 @@ curl -X POST https://mint.host:3338/v1/melt/onchain -d \ "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", "amount": 100000, "unit": "sat", - "fee_reserve": 5000, + "fee": 5000, "estimated_blocks": 1, "state": "PENDING", "expiry": 1701704757, @@ -338,7 +313,7 @@ curl -X POST https://mint.host:3338/v1/melt/onchain -d \ "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", "amount": 100000, "unit": "sat", - "fee_reserve": 5000, + "fee": 5000, "estimated_blocks": 1, "state": "PAID", "expiry": 1701704757, From a9776d1f07ff8d2449da94372969e30abe58f4ee Mon Sep 17 00:00:00 2001 From: gudnuf Date: Sat, 30 Aug 2025 20:04:21 -0700 Subject: [PATCH 08/10] tweak PENDING to be more inclusive --- 26.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/26.md b/26.md index 850ad795..85416d20 100644 --- a/26.md +++ b/26.md @@ -221,8 +221,8 @@ Where `fee` is the additional fee reserve required to broadcast the transaction, `state` is an enum string field with possible values `"UNPAID"`, `"PENDING"`, `"PAID"`: - `"UNPAID"` means that the transaction has not been broadcast yet. -- `"PENDING"` means that the transaction has been broadcast but not yet confirmed. -- `"PAID"` means that the transaction has been confirmed. +- `"PENDING"` means that the transaction is being processed by the mint but not yet mined. +- `"PAID"` means that the transaction has been mined. `txid:vout` is the outpoint of the payment, present once the transaction has been broadcast. @@ -289,7 +289,7 @@ curl -X POST https://mint.host:3338/v1/melt/onchain -d \ '{"quote": "TRmjduhIsPxd...", "inputs": [...]}' ``` -**Successful melt response** (transaction broadcast): +**Successful melt response**: ```json { From 4d8e59f42d876178ea492f902e211a3fd6210cee Mon Sep 17 00:00:00 2001 From: gudnuf Date: Tue, 2 Sep 2025 16:27:52 -0700 Subject: [PATCH 09/10] change txid:vout to outpoint --- 26.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/26.md b/26.md index 85416d20..35b54aa1 100644 --- a/26.md +++ b/26.md @@ -209,7 +209,7 @@ The mint responds with an array of `PostMeltQuoteOnchainResponse`: "estimated_blocks": , "state": , "expiry": , - "txid:vout": + "outpoint": } ] ``` @@ -224,7 +224,7 @@ Where `fee` is the additional fee reserve required to broadcast the transaction, - `"PENDING"` means that the transaction is being processed by the mint but not yet mined. - `"PAID"` means that the transaction has been mined. -`txid:vout` is the outpoint of the payment, present once the transaction has been broadcast. +`outpoint` is the transaction ID and output index of the payment in the format `txid:vout`, present once the transaction has been broadcast. ### Melting Tokens @@ -301,7 +301,7 @@ curl -X POST https://mint.host:3338/v1/melt/onchain -d \ "estimated_blocks": 1, "state": "PENDING", "expiry": 1701704757, - "txid:vout": "3b7f3b85c5f1a3c4d2b8e9f6a7c5d8e9f1a2b3c4d5e6f7a8b9c1d2e3f4a5b6c7:2" + "outpoint": "3b7f3b85c5f1a3c4d2b8e9f6a7c5d8e9f1a2b3c4d5e6f7a8b9c1d2e3f4a5b6c7:2" } ``` @@ -317,7 +317,7 @@ curl -X POST https://mint.host:3338/v1/melt/onchain -d \ "estimated_blocks": 1, "state": "PAID", "expiry": 1701704757, - "txid:vout": "3b7f3b85c5f1a3c4d2b8e9f6a7c5d8e9f1a2b3c4d5e6f7a8b9c1d2e3f4a5b6c7:2" + "outpoint": "3b7f3b85c5f1a3c4d2b8e9f6a7c5d8e9f1a2b3c4d5e6f7a8b9c1d2e3f4a5b6c7:2" } ``` From af046ed02f8eef2d618b33b4f569cb5780acc8c0 Mon Sep 17 00:00:00 2001 From: gudnuf Date: Tue, 2 Sep 2025 16:40:03 -0700 Subject: [PATCH 10/10] nut 04/05 and README references --- 04.md | 2 ++ 05.md | 2 ++ README.md | 2 ++ 3 files changed, 6 insertions(+) diff --git a/04.md b/04.md index 2353daaf..ea67e73c 100644 --- a/04.md +++ b/04.md @@ -14,6 +14,7 @@ Method-specific NUTs describe how to handle different payment methods. The curre - [NUT-23][23] for bolt11 Lightning invoices - [NUT-25][25] for bolt12 Lightning offers +- [NUT-26][26] for onchain Bitcoin transactions ## General Flow @@ -160,3 +161,4 @@ Upon receiving the `BlindSignatures` from the mint, the wallet unblinds them to [20]: 20.md [23]: 23.md [25]: 25.md +[26]: 26.md diff --git a/05.md b/05.md index 2e5d9b58..a2ca9f07 100644 --- a/05.md +++ b/05.md @@ -14,6 +14,7 @@ Method-specific NUTs describe how to handle different payment methods. The curre - [NUT-23][23] for bolt11 Lightning invoices - [NUT-25][25] for bolt12 Lightning offers +- [NUT-26][26] for onchain Bitcoin transactions ## General Flow @@ -156,3 +157,4 @@ The mint's settings for this NUT indicate the supported method-unit pairs for me [12]: 12.md [23]: 23.md [25]: 25.md +[26]: 26.md diff --git a/README.md b/README.md index 48216bef..7b2c4bfb 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Wallets and mints `MUST` implement all mandatory specs and `CAN` implement optio | [23][23] | Payment Method: BOLT11 | [Nutshell][py], [cdk-cli] | [Nutshell][py], [cdk-mintd], [nutmix] | | [24][24] | HTTP 402 Payment Required | - | - | | [25][25] | Payment Method: BOLT12 | [cdk-cli], [cashu-ts][ts] | [cdk-mintd] | +| [26][26] | Payment Method: Onchain | [cdk-cli], [cashu-ts][ts] | [cdk-mintd] | #### Wallets: @@ -99,3 +100,4 @@ Wallets and mints `MUST` implement all mandatory specs and `CAN` implement optio [23]: 23.md [24]: 24.md [25]: 25.md +[26]: 26.md