Add fees to NUT-02 #126
No reviewers
Labels
No labels
breaking change
bug
documentation
enhancement
needs discussion
needs implementation
new nut
ready
wallet-only
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
forgejo-admin/nuts!126
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "input-fees"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Proposing to add fees to the mandatory part of the spec as I expect most public mints will require fees at some point. If the fees are not set, we should assume it as being 0.
Moved around some existing text. The new part is:
Fees (parts per thousand)
Keysets indicate the fee
input_fee_ppkthat is charged when aProofof that keyset is spent as an input to a transaction. The fee is given in parts per thousand (ppk) per input measured in theunitof the keyset and the sum is rounded up to the next larger integer.As an example, we construct a transaction spending 3 inputs (
Proofs) from a keyset with unitsatandinput_fee_ppkof100. A fee of100 ppkmeans0.1 satper input. The sum of the fees would be 300 ppk for this transaction and the mint would charge1 satin fees (ceil(0.3) == 1). The fees for spending 1-10 inputs is 1 sat, 11-20 inputs is 2 sat and so on....
Wallet input and output construction
When constructing a transaction with ecash inputs (example:
/v1/swapor/v1/melt), wallets MUST add fees to the inputs (or subtract from the outputs) if they spent ecash from a keyset with fees. The mint checks the following equation:The
feesare calculated for each input individually (by summing the fee from the keyset they are from) and then rounded up to the next integer.Tracking progress
Mints:
Wallets
@ -38,0 +47,4 @@sum_fees = 0for proof in inputs:sum_fees += keysets[proof.id].input_fee_ppkreturn (sum_fees + 999) // 1000any reason for including melts? I thought mints could use the
fee_reservefield in melts to charge fees@ -38,0 +47,4 @@sum_fees = 0for proof in inputs:sum_fees += keysets[proof.id].input_fee_ppkreturn (sum_fees + 999) // 1000The
fee_reserveis the estimation of the routing fees in the case of Bolt11, this is calculated at the time of the quote when the mint does not know the number of inputs nor thekeysetsof theProofsthe wallet will send when it actually goes tomeltthus it does not know what the fee will be. So the wallet must calculate the fee to the mint when selecting proofs for the melt. And send enoughProofsto pay theamount+fee_reserve+mint fee.@ -38,0 +47,4 @@sum_fees = 0for proof in inputs:sum_fees += keysets[proof.id].input_fee_ppkreturn (sum_fees + 999) // 1000You just explained it :) I thought about this a lot but I think
fee_reservedoes not fulfill the requirements here. The mint does not know how many inputs will be spent for a melt, so it can't use that as an estimate for the fee_reserve. In the worst case, someone could pay a 1000 sat invoice with 1000x1sat tokens. With that, the whole fee system could be circumvented by users simply using internal invoices instead of sending around ecash (which would cost fees).unintended side effect: this has a negative effect on privacy measures and offline spending
@ -38,0 +47,4 @@sum_fees = 0for proof in inputs:sum_fees += keysets[proof.id].input_fee_ppkreturn (sum_fees + 999) // 1000Ok, I think I see part of it. The mint does not know the number of inputs so it can't calculate the fee per input. But it seems to me that the mint can still charge fees in melts although not in the same way as with
input_fee_ppk. I see most mint implementations have some setting likeLIGHTNING_FEE_PERCENTwhich is the percent they are currently using for thefee_reserve(or if not, let me know), so mints could add to that their desired fees for the melt. So that field will beLIGHTNING_FEE_PERCENT+MELT_FEE_PERCENT. It's by percent and not by input but still a way to charge fee.Maybe I'm missing something but I think mints today have ways to charge fees in both mints and melts requests to avoid this. If a mint receives a request for a mint quote for 1000 sats and it wants to charge a fee, it can return an invoice for 1002 sats (2 sats in fees). Similar in melt, if a wallet user wants a 1000 sat invoice paid then the mint can ask 1002 sat worth of proofs (a fee accounting for both lightning and melt).
@ -38,0 +47,4 @@sum_fees = 0for proof in inputs:sum_fees += keysets[proof.id].input_fee_ppkreturn (sum_fees + 999) // 1000That's true but consider the example above with someone paying a 1000 sat invoice with 1000 1 sat ecash tokens. The mint would have to add the expected maximum input fee possible (might be on the order of the lightning fee reserve or even higher) to the fee_reserve and deduct it later depending on how many inputs were spent.
This way it seems cleaner: the mint checks is the provided inputs are amount + fee + fee_reserve and can deduct the overpaid fee_reserve like they do now
@ -38,0 +47,4 @@sum_fees = 0for proof in inputs:sum_fees += keysets[proof.id].input_fee_ppkreturn (sum_fees + 999) // 1000Using only the
fee_reservewould also mean that everyone has to reserve the maximum possible fee, so most users who won't use 1000x1 sat inputs would have to reserve as much as those who do.Regarding the check in the mint, what I do in nutshell is for a
/mintis:@ -38,0 +47,4 @@sum_fees = 0for proof in inputs:sum_fees += keysets[proof.id].input_fee_ppkreturn (sum_fees + 999) // 1000ok, prob a misunderstanding as I was proposing that this change does not affect melt operations and only do the input fee calculation for swaps.
I was thinking about this and agree that this could be cleaner to know the type of fee. But you could also end up with weird flows like paying fees in one way and the possibility of asking for back fees in another way
@ -1,5 +1,4 @@NUT-02: Keysets and keyset ID==========================# NUT-02: Keysets and feesThis implies wallets must use floating point operations to compute fees, which is a non-deterministic procedure. Wallets would need to be robust against off-by-one errors. I ran into this problem while working on fees for #128.
If it were me, i'd define the fees with integer arithmetic only so that every implementation can agree.
@ -1,5 +1,4 @@NUT-02: Keysets and keyset ID==========================# NUT-02: Keysets and feesI had this concern too and thought that using an intermediary float for the last step would not include any ambiguity but thinking about it now, it might.
Using integer division
//however will round down and not up, so I'm not sure whether your suggestion would produce the same result as usingceil(). Do you agree?@ -1,5 +1,4 @@NUT-02: Keysets and keyset ID==========================# NUT-02: Keysets and feesOh sorry, i missed that. if you want to round up with integer division, try this:
@ -1,5 +1,4 @@NUT-02: Keysets and keyset ID==========================# NUT-02: Keysets and feesNoticed another issue here: the fees do not depend on
proof.amountbut only on the number of input proofs.@ -1,5 +1,4 @@NUT-02: Keysets and keyset ID==========================# NUT-02: Keysets and feesAddressed in b7d64fd.
@ -38,0 +47,4 @@sum_fees = 0for proof in inputs:sum_fees += keysets[proof.id].input_fee_ppkreturn (sum_fees + 999) // 1000Could you make an example?
@ -93,1 +58,4 @@#### Keyset ID versionKeyset IDs have a version byte (two hexadecimal characters). The currently used version byte is `00`.Still missing
proof.amounthere:@ -38,0 +47,4 @@sum_fees = 0for proof in inputs:sum_fees += keysets[proof.id].input_fee_ppkreturn (sum_fees + 999) // 1000I was hung up with having 2 different fee fields (input fees and lightning fees) so it was that scenario but seems that should be the way forward in order to be able to charge by input.
@ -93,1 +58,4 @@#### Keyset ID versionKeyset IDs have a version byte (two hexadecimal characters). The currently used version byte is `00`.The fees are not amount-dependent.
merged in nutmix.
https://github.com/lescuer97/nutmix/pull/74