-
Notifications
You must be signed in to change notification settings - Fork 75
onchain bitcoin payment method #283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
da4bb25
437a796
f8bf489
aaabef4
0367894
73f0e51
10c8665
a9776d1
4d8e59f
af046ed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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": <str_enum[UNIT]>, | ||
| "pubkey": <str> | ||
| } | ||
| ``` | ||
|
|
||
| > **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": <str>, | ||
| "request": <str>, | ||
| "unit": <str_enum[UNIT]>, | ||
| "expiry": <int|null>, | ||
| "pubkey": <str>, | ||
| "amount_paid": <int>, | ||
| "amount_issued": <int>, | ||
| } | ||
| ``` | ||
|
|
||
| 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": <str>, | ||
| "min_amount": <int|null>, | ||
| "max_amount": <int|null>, | ||
| "options": { | ||
| "confirmations": <int> | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Melt Quote | ||
|
|
||
| For the `onchain` method, the wallet includes the following specific `PostMeltQuoteOnchainRequest` data: | ||
|
|
||
| ```json | ||
| { | ||
| "request": <str>, | ||
| "unit": <str_enum[UNIT]>, | ||
| "amount": <int> | ||
| } | ||
| ``` | ||
|
|
||
| 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": <str>, | ||
| "request": <str>, | ||
| "amount": <int>, | ||
| "unit": <str_enum[UNIT]>, | ||
| "fee": <int>, | ||
| "estimated_blocks": <int>, | ||
| "state": <str_enum[STATE]>, | ||
| "expiry": <int>, | ||
| "outpoint": <str|null> | ||
| } | ||
| ] | ||
| ``` | ||
|
|
||
| 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 \ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we need to revist this for onchain and we should just do it for all of them while were at it. #37. But for onchain specifically it make no sense to wait for the melt to confirm and request should return okay once it is received.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we are going to say that a melt quote does not complete until the transaction is confirmed, then yeah. If a completed melt quote means the transaction was broadcast, then it seems fine to wait. Was that the consensus that the quote stays pending until the transaction is confirmed?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
What if a mint wants to batch then it doesn't work for broadcast even.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Is there a change you suggest I make here? In NUT-05 it says: "For methods that involve external payments (like Lightning), this call may block until the payment either succeeds or fails. This can take a long time. Make sure to use no (or a very long) timeout when making this call!" The way this nut is now seems fine as it shows that a "successful melt response" returns the quote as PENDING and NUT-05 is pretty open-ended in that it says, "this call may block". |
||
| '{"quote": "TRmjduhIsPxd...", "inputs": [...]}' | ||
| ``` | ||
|
|
||
| **Successful melt response**: | ||
|
|
||
| ```json | ||
| { | ||
| "quote": "TRmjduhIsPxd...", | ||
| "request": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", | ||
| "amount": 100000, | ||
| "unit": "sat", | ||
| "fee": 5000, | ||
| "estimated_blocks": 1, | ||
| "state": "PENDING", | ||
gudnuf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "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": <str>, | ||
| "min_amount": <int|null>, | ||
| "max_amount": <int|null>, | ||
| } | ||
| ``` | ||
|
|
||
| [02]: 02.md | ||
| [04]: 04.md | ||
| [05]: 05.md | ||
| [08]: 08.md | ||
| [20]: 20.md | ||
Uh oh!
There was an error while loading. Please reload this page.