Skip to content

feat: onchain payment method#365

Open
thesimplekid wants to merge 8 commits into
cashubtc:mainfrom
thesimplekid:onchain
Open

feat: onchain payment method#365
thesimplekid wants to merge 8 commits into
cashubtc:mainfrom
thesimplekid:onchain

Conversation

@thesimplekid
Copy link
Copy Markdown
Collaborator

@thesimplekid thesimplekid commented Apr 23, 2026

@github-project-automation github-project-automation Bot moved this to Backlog in nuts Apr 23, 2026
@thesimplekid thesimplekid mentioned this pull request Apr 23, 2026
4 tasks
@thesimplekid thesimplekid added the new nut A new protocol NUT label Apr 23, 2026
Copy link
Copy Markdown
Contributor

@robwoodgate robwoodgate left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One nit suggestion, one clarification question - otherwise LGTM!

Comment thread XX.md Outdated
Comment thread XX.md Outdated
@robwoodgate
Copy link
Copy Markdown
Contributor

robwoodgate commented Apr 25, 2026

Discussed briefly offline:

The multi-quote is quite a significant change to NUT-05. It maybe also adds resource tracking overhead (4-5 quotes per request) and requires new wallet UI to handle quote selection (vs existing methods).

The tradeoff/benefit might be worth it, but perhaps we should consider setting a desired target confirmation speed in the request (eg target_blocks / priority etc) and just returning the closest quote so NUT-05 stays "one request, one quote"

Comment thread XX.md
Comment thread XX.md Outdated
Comment thread XX.md Outdated
Comment thread XX.md Outdated
Comment thread XX.md Outdated
@gudnuf
Copy link
Copy Markdown
Contributor

gudnuf commented Apr 27, 2026

It maybe also adds resource tracking overhead (4-5 quotes per request)

@robwoodgate can you elaborate more on this? My thinking is that after the wallet picks the quote they only have to track that one and forget about the rest

requires new wallet UI to handle quote selection (vs existing methods).

Providing multiple options is pretty standard for onchain UIs. If a wallet doesn't want to offer quote selection to the user, then it can just select the fastest one and leave the UI unchanged.

@thesimplekid
Copy link
Copy Markdown
Collaborator Author

Also, why not support nut-08? Its nice to not have to do a swap first, but I guess that's abusing change.

This will lead to proofs being stuck pending for 10+ min waiting for confirmation. I think this is bad ux so swapping should be forced. It's also much simpler for the mint to not have to deal with change.

@robwoodgate
Copy link
Copy Markdown
Contributor

It maybe also adds resource tracking overhead (4-5 quotes per request)

@robwoodgate can you elaborate more on this? My thinking is that after the wallet picks the quote they only have to track that one and forget about the rest

A mint has to track/hold all quotes it creates. It just seems wasteful on resources to have multiple quotes per request. Could also lead to a DoS vector.

requires new wallet UI to handle quote selection (vs existing methods).

Providing multiple options is pretty standard for onchain UIs. If a wallet doesn't want to offer quote selection to the user, then it can just select the fastest one and leave the UI unchanged.

It's different to the way all other methods are handled. In CTS, for example, I had to write custom handlers vs leaning on the generics. I just don't know as we need it when it's just as simple to send a param with desired confirmation speed up front.

Comment thread XX.md Outdated
@thesimplekid
Copy link
Copy Markdown
Collaborator Author

A mint has to track/hold all quotes it creates. It just seems wasteful on resources to have multiple quotes per request. Could also lead to a DoS vector.

I don't think this is too much of a concern it's really only a couple extra db entry's and there is nothing really to monitor like there is for mint quotes.

It's different to the way all other methods are handled. In CTS, for example, I had to write custom handlers vs leaning on the generics. I just don't know as we need it when it's just as simple to send a param with desired confirmation speed up front.

This though is a valid point and would be the justification for change over the dos point.

Comment thread 04.md Outdated
Comment thread 05.md Outdated
Copy link
Copy Markdown
Contributor

@robwoodgate robwoodgate left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new fee_options works for me - best of both worlds. ACK.

Comment thread XX.md
@robwoodgate
Copy link
Copy Markdown
Contributor

robwoodgate commented May 6, 2026

A thought on SIG_ALL: onchain melt SIG_ALL currently commits to inputs || quote because there are no NUT-08 change outputs. It does not commit to the selected estimated_blocks.

The exact input amount binds the selected fee amount, BUT if two fee_options have the same fee and different estimated_blocks, the same signed proofs can authorize either fee option.

I'm not sure that distinction matters, but if it does, duplicate fee values should also be forbidden... otherwise estimated_blocks would have to be included in the signed message, which is making onchain "special" again :/

@thesimplekid
Copy link
Copy Markdown
Collaborator Author

A thought on SIG_ALL: onchain melt SIG_ALL currently commits to inputs || quote because there are no NUT-08 change outputs. It does not commit to the selected estimated_blocks.

The exact input amount binds the selected fee amount, BUT if two fee_options have the same fee and different estimated_blocks, the same signed proofs can authorize either fee option.

I'm not sure that distinction matters, but if it does, duplicate fee values should also be forbidden... otherwise estimated_blocks would have to be included in the signed message, which is making onchain "special" again :/

Lets just avoid this and forbid two from having the same fee. There is no reason for two to have the same fee.

@thesimplekid
Copy link
Copy Markdown
Collaborator Author

A thought on SIG_ALL: onchain melt SIG_ALL currently commits to inputs || quote because there are no NUT-08 change outputs. It does not commit to the selected estimated_blocks.

The exact input amount binds the selected fee amount, BUT if two fee_options have the same fee and different estimated_blocks, the same signed proofs can authorize either fee option.

I'm not sure that distinction matters, but if it does, duplicate fee values should also be forbidden... otherwise estimated_blocks would have to be included in the signed message, which is making onchain "special" again :/

A thought on SIG_ALL: onchain melt SIG_ALL currently commits to inputs || quote because there are no NUT-08 change outputs. It does not commit to the selected estimated_blocks.

The exact input amount binds the selected fee amount, BUT if two fee_options have the same fee and different estimated_blocks, the same signed proofs can authorize either fee option.

I'm not sure that distinction matters, but if it does, duplicate fee values should also be forbidden... otherwise estimated_blocks would have to be included in the signed message, which is making onchain "special" again :/

Addressed in e62c585

Copy link
Copy Markdown

@TheMhv TheMhv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK e62c585

Copy link
Copy Markdown

@TheMhv TheMhv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re-ACK d76e7e5

@thesimplekid
Copy link
Copy Markdown
Collaborator Author

I am rethinking the use of an absolute fee and wondering if we should allow change.

With no ability to give change the mint must be able to fairly accurately as over estimating too much will cause users to complain of high fees and underestimating will cause tx's to fail at melt time when attempting to construct the final one.

I think this problem is exacerbated in payjoin where onchain inputs could change and thus the fee is changed but with a fixed fee up front there is less flexibility that allowing change would provide.

The issue of change being stuck in pending for 10+ min is still valid but this could be made a wallet problem where they should send no more then amount + fee_reserve not that there is no change given.

After CDK dev call seems like this is a good idea and we should allow change

@SatsAndSports
Copy link
Copy Markdown

SatsAndSports commented May 6, 2026

Resolved. It'll be in the mint-info settings. See responses below

How about including the mint's confirmations number in the PostMintQuoteOnchainResponse, so that the wallet and the end user know how long they might have to wait? A large - but hidden - confirmations number could be a bad UX

(I tried to post this comment in line at XX.md:29, but Github is giving me errors. Hence I'll try this comment)

@SatsAndSports
Copy link
Copy Markdown

TL/DR: I think the only unambiguous way to give any meaning to 'expiry' is to say that the mint must honour any payment which has achieved sufficient confirmations (e.g. 6) within 2,016 blocks of the expiry. So maybe the 'expiry' should be specified as a block height instead of a time?

'evicted' in XX.md:47 might be too ambiguous a term for a NUT. A transaction may be evicted from one node's mempool, but not from another node's mempool, and it could still be confirmed even after being evicted from every Bitcoin Core mempool.

Once a transaction is seen by anyone, the only thing that can stop it being confirmed is if at least one of its inputs is spent in another block (where that block has multiple confirmations). Until that 'double spend' happens, the transaction remains 'confirmable' for the indefinite future.

Ultimately, I don't think we can specify anything related to the mempool.

The only reason to even have an expiry is to free the mint from having to permanently monitor an arbitrarily large set of addresses. Therefore, I think we say that a payment either had sufficient confirmations before a deadline, or it was too late and the mint is free to ignore further payments to that address

(Again, Github is giving me an "Failed to save comment: An internal error occurred, please try again.." error if I try to make the inline comment in the right place)

@thesimplekid
Copy link
Copy Markdown
Collaborator Author

  "amount_issued": <int>,
  "amount_incoming": <int|null>

Its come up a few times that it may benefit wallet ux to have an amount_incoming value that is the amount the mint sees in the mempool or onchain that has not reached the required number of confs.

This would increase complexity of the mint having to mange mempool tx's that could be dropped from the mempool or removed via RBF.

Also some node backends or services may not provide a mempool so the mint does not have this information. For example a mint that is using compact block filters does not have a mempool.

Given this I lean towards not adding this, as we cannot guarantee mints can support it and I think providing it optionally is worse then not at all and it is up to wallets to use a mempool explorer if they want to show pending.

Also having issues with inline comments.

@SatsAndSports
Copy link
Copy Markdown

SatsAndSports commented May 6, 2026

While minting: Are units other than sat and msat meaningful?

If yes, and usd is meaningful, then maybe the mint should be required to return an exchange rate in PostMintQuoteOnchainResponse?

And I guess a similar question apples to melting, but I haven't read that section in detail yet

@thesimplekid
Copy link
Copy Markdown
Collaborator Author

How about including the mint's confirmations number in the PostMintQuoteOnchainResponse, so that the wallet and the end user know how long they might have to wait? A large - but hidden - confirmations number could be a bad UX

(I tried to post this comment in line at XX.md:29, but Github is giving me errors. Hence I'll try this comment)

This is in the mint info, and its up to the wallet how to display this. #365 (comment)

@SatsAndSports
Copy link
Copy Markdown

Nice. Sorry I missed that:

This is in the mint info, and its up to the wallet how to display this. #365 (comment)

How about adding a few words to refer to NUT-04 and make this more explicit? I guess we can assume that mint developers would know this; but wallet devs mightn't know exactly where to check. Something like:

### Example `MintMethodSetting`

See NUT-04 for exactly how the mint can report, via a `MintMethodSetting` in its _mint info_, that it supports `onchain`:

jooray added a commit to jooray/nutshell that referenced this pull request May 10, 2026
NUT-XX onchain payment method (cashubtc/nuts#365) defines
MintMethodSetting.options.confirmations as the minimum block depth
the mint requires before crediting a deposit. We already had
MINT_ZCASH_MIN_CONFIRMATIONS as a setting but did not surface it in
mint info, so wallets had no way to discover it.

- Broaden MintMethodSetting.options from
  Optional[MintMethodBolt11OptionSetting] to Optional[Dict[str, Any]]
  so each method can carry its own options shape.
- bolt11 keeps emitting {"description": bool} via
  MintMethodBolt11OptionSetting.model_dump.
- zcash now emits {"confirmations": MINT_ZCASH_MIN_CONFIRMATIONS}.

Wallet code does not consume MintMethodBolt11OptionSetting as a
typed model anywhere; only features.py builds it. Safe change.

See ZCASH-CDK-COMPATIBILITY.md §6.
jooray added a commit to jooray/nutshell that referenced this pull request May 10, 2026
Aligns the mint's wire format with the unmerged onchain payment-method
NUT (PR cashubtc#365) so wallets implementing it can talk to our mint without
zcash-specific glue.

Method rename:
- backend now registered as Method("onchain") instead of Method("zcash");
  the (onchain, zec) method-unit pair signals native Zcash chain.
- mint info MintMethodSetting/MeltMethodSetting branch keyed on
  method.name == "onchain".
- All internal MINT_ZCASH_* settings keep their names (they configure
  the zwalletd backend; only the spec-facing method changes).

Router:
- router_zcash.py removed; replaced with router_onchain.py serving
  /v1/{mint,melt}/{quote,}/onchain endpoints.
- Mint quote bypasses ledger.mint_quote() so the request can be
  amount-less per NUT-XX. We allocate a deposit address with amount=0
  internally; the running deposit total is polled from the backend on
  each GET /v1/mint/quote/onchain/{quote} and persisted as
  MintQuote.amount, surfaced as `amount_paid`.
- Mint quote response uses NUT-XX shape: no `state` field; emits
  `amount_paid` and `amount_issued` (derived from the underlying
  state machine: amount_issued == amount on ISSUED, amount_paid == amount
  on PAID/PENDING/ISSUED, both zero on UNPAID).
- Mint quote `pubkey` is REQUIRED (Pydantic enforces).
- Melt quote response uses NUT-XX shape: `fee_options[]` array (single
  entry for Zcash's near-flat fee market), `selected_estimated_blocks`
  (echoed from the wallet's melt request), `outpoint` (txid for both
  transparent and shielded — see ZCASH-CDK-COMPATIBILITY.md §6.3 for
  why we don't surface vout).
- Melt request requires `estimated_blocks`, validated against the
  quote's fee_options.

Models:
- New PostMintQuoteOnchainRequest/Response, PostMeltQuoteOnchainRequest/
  Response, PostMeltOnchainRequest, OnchainFeeOption matching XX.md
  on branch thesimplekid/onchain (commit d76e7e5, last ACK 2026-05-06).

Backend:
- ZcashBackend.get_deposit_total(checking_id) helper exposes
  total_confirmed zatoshi for the onchain mint quote accounting.

Tests:
- All 25 backend tests in test_mint_lightning_zcash.py updated to the
  onchain method name and pass green.

Deviations from NUT-XX still in place (tracked in
ZCASH-CDK-COMPATIBILITY.md §6.5):
- one mint per quote (cashu ledger marks the quote ISSUED on first mint)
- one deposit per quote in practice (first deposit is captured as the
  full amount; later deposits to the same address bump amount_paid but
  cannot be minted without lifting the single-mint restriction)
- `change` not yet removed from the wider model (NUT-XX forbids it; the
  decision is still under spec review as of 2026-05-06)
jooray added a commit to jooray/nutshell that referenced this pull request May 10, 2026
Aligns the mint's wire format with the unmerged onchain payment-method
NUT (PR cashubtc#365) so wallets implementing it can talk to our mint without
zcash-specific glue.

Method rename:
- backend now registered as Method("onchain") instead of Method("zcash");
  the (onchain, zec) method-unit pair signals native Zcash chain.
- mint info MintMethodSetting/MeltMethodSetting branch keyed on
  method.name == "onchain".
- All internal MINT_ZCASH_* settings keep their names (they configure
  the zwalletd backend; only the spec-facing method changes).

Router:
- router_zcash.py removed; replaced with router_onchain.py serving
  /v1/{mint,melt}/{quote,}/onchain endpoints.
- Mint quote bypasses ledger.mint_quote() so the request can be
  amount-less per NUT-XX. We allocate a deposit address with amount=0
  internally; the running deposit total is polled from the backend on
  each GET /v1/mint/quote/onchain/{quote} and persisted as
  MintQuote.amount, surfaced as amount_paid.
- Mint quote response uses NUT-XX shape: no state field; emits
  amount_paid and amount_issued (derived from the underlying state
  machine: amount_issued == amount on ISSUED, amount_paid == amount
  on PAID/PENDING/ISSUED, both zero on UNPAID).
- Mint quote pubkey is REQUIRED (Pydantic enforces).
- Melt quote response uses NUT-XX shape: fee_options array (single
  entry for Zcash near-flat fee market), selected_estimated_blocks
  (echoed from the wallet melt request), outpoint (txid for both
  transparent and shielded; we do not surface vout).
- Melt request requires estimated_blocks, validated against the
  quote fee_options.

Models:
- New PostMintQuoteOnchainRequest/Response, PostMeltQuoteOnchainRequest/
  Response, PostMeltOnchainRequest, OnchainFeeOption matching XX.md
  on branch thesimplekid/onchain (commit d76e7e5, last ACK 2026-05-06).

Backend:
- ZcashBackend.get_deposit_total(checking_id) helper exposes
  total_confirmed zatoshi for the onchain mint quote accounting.

Tests:
- All 25 backend tests in test_mint_lightning_zcash.py updated to the
  onchain method name and pass green.

Deviations from NUT-XX still in place (tracked in
ZCASH-CDK-COMPATIBILITY.md §6.5):
- one mint per quote (cashu ledger marks the quote ISSUED on first mint)
- one deposit per quote in practice (first deposit is captured as the
  full amount; later deposits to the same address bump amount_paid but
  cannot be minted without lifting the single-mint restriction)
- change not yet removed from the wider model (NUT-XX forbids it; the
  decision is still under spec review as of 2026-05-06)
Copy link
Copy Markdown
Contributor

@robwoodgate robwoodgate left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK @ 700cf79. LGTM!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new nut A new protocol NUT

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

8 participants