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/26.md b/26.md new file mode 100644 index 00000000..35b54aa1 --- /dev/null +++ b/26.md @@ -0,0 +1,339 @@ +# 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:** 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. + +The mint responds with a `PostMintQuoteOnchainResponse`: + +```json +{ + "quote": , + "request": , + "unit": , + "expiry": , + "pubkey": , + "amount_paid": , + "amount_issued": , +} +``` + +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 request +- `amount_issued` is the amount of ecash that has been issued for the given mint quote + +### Example + +**1. Create mint quote:** + +```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 +} +``` + +**2. Send payment to the Bitcoin address and check quote state:** + +```bash +curl -X GET http://localhost:3338/v1/mint/quote/onchain/DSGLX9kevM... +``` + +**Response after payment reaches the required number of 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 +{ + "quote": "DSGLX9kevM...", + "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", + "unit": "sat", + "expiry": 1701704757, + "pubkey": "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac", + "amount_paid": 100000, + "amount_issued": 0 +} +``` + +**3. Mint tokens using the confirmed quote:** + +```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_": "0224f1c4c564230ad3d96c5033efdc425582a5a7691d600202732edc6d4b1ec" + }, + { + "id": "009a1f293253e41e", + "amount": 50000, + "C_": "0277d1de806ed177007e5b94a8139343b6382e472c752a74e99949d511f7194f6c" + } + ] +} +``` + +## Multiple Deposits + +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 blockchain 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": +} +``` + +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 an array of `PostMeltQuoteOnchainResponse`: + +```json +[ + { + "quote": , + "request": , + "amount": , + "unit": , + "fee": , + "estimated_blocks": , + "state": , + "expiry": , + "outpoint": + } +] +``` + +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` 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"`: + +- `"UNPAID"` means that the transaction has not been broadcast yet. +- `"PENDING"` means that the transaction is being processed by the mint but not yet mined. +- `"PAID"` means that the transaction has been mined. + +`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 + +> **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 + +**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": 5000, + "estimated_blocks": 1, + "state": "UNPAID", + "expiry": 1701704757 + }, + { + "quote": "OewtRaqeXmzK...", + "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", + "amount": 100000, + "unit": "sat", + "fee": 2000, + "estimated_blocks": 6, + "state": "UNPAID", + "expiry": 1701704757 + }, + { + "quote": "KfPqNghzLvtY...", + "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", + "amount": 100000, + "unit": "sat", + "fee": 800, + "estimated_blocks": 144, + "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**: + +```json +{ + "quote": "TRmjduhIsPxd...", + "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", + "amount": 100000, + "unit": "sat", + "fee": 5000, + "estimated_blocks": 1, + "state": "PENDING", + "expiry": 1701704757, + "outpoint": "3b7f3b85c5f1a3c4d2b8e9f6a7c5d8e9f1a2b3c4d5e6f7a8b9c1d2e3f4a5b6c7:2" +} +``` + +**Final confirmed state**: + +```json +{ + "quote": "TRmjduhIsPxd...", + "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", + "amount": 100000, + "unit": "sat", + "fee": 5000, + "estimated_blocks": 1, + "state": "PAID", + "expiry": 1701704757, + "outpoint": "3b7f3b85c5f1a3c4d2b8e9f6a7c5d8e9f1a2b3c4d5e6f7a8b9c1d2e3f4a5b6c7:2" +} +``` + +### 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 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