{"id":"4a4a17a84e9bbd4d03b41cd60c0058a587e1c56c9f8456e15be9932a22375d61","pubkey":"d1678e7ef965374bbea308a1215609a78376dc158277a7d657680f9d5efd5c38","created_at":1780840132,"kind":30817,"tags":[["d","nut-08"],["title","NUT-08: Lightning fee return"],["summary","Blank outputs, which let a mint return the Lightning fees a wallet overpaid when melting."],["s","optional"],["t","cashu"],["t","ecash"],["t","nut"],["alt","A specification: NUT-08: Lightning fee return"],["client","openspecs-import"],["published_at","1678386786"],["proxy","https://github.com/cashubtc/nuts/blob/a845dfc998abae501fc3419592d53dc995d34b12/08.md","web"],["x","8f7c5e5fb0fa60d16431e0221cb47e4c07a8d0af64986b4d73fd24791e174e38"]],"content":"# NUT-08: Lightning fee return\n\n`optional`\n\n`depends on: NUT-05`\n\n---\n\nThis document describes how the overpaid Lightning fees are handled and extends [NUT-05][05] which describes melting tokens (i.e. paying a Lightning invoice). In short, a wallet includes _blank outputs_ when paying a Lightning invoice which can be assigned a value by the mint if the user has overpaid Lightning fees. This can be the case due to the unpredictability of Lightning network fees. To solve this issue, we introduce so-called _blank outputs_ which are blinded messages with an undetermined value.\n\nThe problem is also described in [this gist](https://gist.github.com/callebtc/a6cc0bd2b6f70e081e478147c40fc578).\n\n## Description\n\nBefore requesting a Lightning payment as described in [NUT-05][05], `Alice` produces a number of `BlindedMessage` which are similar to ordinary blinded messages but their value is yet to be determined by the mint `Bob` and are thus called _blank outputs_. The number of necessary blank outputs is `max(ceil(log2(fee_reserve)), 1)` which ensures that there is at least one output if there is any fee. If the `fee_reserve` is `0`, then the number of blank outputs is `0` as well. The blank outputs will contain the overpaid fees that will be returned by the mint to the wallet.\n\nThis code calculates the number of necessary blank outputs in Python:\n\n```python\ndef calculate_number_of_blank_outputs(fee_reserve_sat: int) -> int:\n    assert fee_reserve_sat >= 0, \"Fee reserve can't be negative.\"\n    if fee_reserve_sat == 0:\n        return 0\n    return max(math.ceil(math.log2(fee_reserve_sat)), 1)\n```\n\n## Example\n\nThe wallet wants to pay an invoice with `amount := 100 000 sat` and determines by asking the mint that `fee_reserve` is `1000 sats`. The wallet then provides `101 000 sat` worth of proofs and 10 blank `outputs` to make the payment (since `ceil(log2(1000))=ceil(9.96..)=10`). The mint pays the invoice and determines that the actual fee was `100 sat`, i.e, the overpaid fee to return is `fee_return = 900 sat`. The mint splits the amount `900` into summands of `2^n` which is `4, 128, 256, 512`. The mint inserts these amounts into the blank `outputs` it received form the wallet and generates 4 new promises. The mint then returns these `BlindSignature`s to the wallet together with the successful payment status.\n\n## Wallet flow\n\nThe wallet asks the mint for the `fee_reserve` for paying a specific bolt11 invoice of value `amount` by calling `POST /v1/melt/quote` as described in [NUT-05][05]. The wallet then provides a `PostMeltBolt11Request` to `POST /v1/melt/bolt11` that has (1) proofs of the value `amount+fee+fee_reserve`, (2) the bolt11 invoice to be paid, and finally, as a new entry, (3) a field `outputs` that has `n_blank_outputs` blinded messages that are generated before the payment attempt to receive potential overpaid fees back to her.\n\n## Mint flow\n\nHere we describe how the mint generates `BlindSignature`s for the overpaid fees. The mint `Bob` returns in `PostMeltQuoteBolt11Response` the field `change` **ONLY IF** `Alice` has previously provided `outputs` for the change **AND** if the the `inputs` provided were greater then the `total_amount_paid - fees`.\n\nIf the `overpaid_fees = input_amount - fees - total_paid` is positive, `Bob` decomposes it to amounts supported by the keyset, typically `2^n`, and imprints them into the `blank_outputs` provided by `Alice`.\n\n`Bob` then signs these blank outputs (now with the imprinted amounts) and thus generates `BlindSignature`s. `Bob` then returns a payment status to the wallet, and, in addition, all blind signatures it generated for the overpaid fees.\n\nImportantly, while `Bob` does not necessarily return the same number of blind signatures as it received blank outputs from `Alice` (since some of them may be of value 0), `Bob` **MUST** return the all blank signatures with a value greater than 0 in the same order as the blank outputs were received and should omit all blind signatures with value 0. For example, if `Bob` receives 10 blank outputs but the overpaid fees only occupy 4 blind signatures, `Bob` will only return these 4 blind signatures with the appropriate imprinted amounts and omit the remaining 6 blind signatures with value 0. Due to the well-defined order of the returned blind signatures, `Alice` can map the blind signatures returned from `Bob` to the blank outputs it provided so that she can further apply the correct unblinding operations on them.\n\n## Example\n\n**Request** of `Alice`:\n\n```http\nPOST https://mint.host:3338/v1/melt/bolt11\n```\n\nWith the data being of the form `PostMeltBolt11Request`:\n\n```json\n{\n  \"quote\": <str>,\n  \"inputs\": <Array[Proof]>,\n  \"outputs\": <Array[BlindedMessage]> <-- New\n}\n```\n\nwhere the new `output` field carries the `BlindMessages`.\n\nThe mint `Bob` then responds with a `PostMeltQuoteBolt11Response`:\n\n```json\n{\n  \"quote\": <str>,\n  \"request\": <str>,\n  \"amount\": <int>,\n  \"unit\": <str_enum[UNIT]>,\n  \"fee_reserve\": <int>,\n  \"state\": <str_enum[STATE]>,\n  \"expiry\": <int>,\n  \"payment_preimage\": <str|null>,\n  \"change\": <Array[BlindSignature]> <-- New\n}\n```\n\nwhere the new `change` field carries the returned `BlindSignature`s due to overpaid fees.\n\n## Example\n\nRequest of `Alice` with curl:\n\n```bash\ncurl -X POST https://mint.host:3338/v1/melt/bolt11 -d \\\n'{\n  \"quote\": \"019e6d5a-2347-7000-813e-a11ea6e419e7\",\n  \"inputs\": [\n    {\n      \"amount\": 4,\n      \"id\": \"009a1f293253e41e\",\n      \"secret\": \"429700b812a58436be2629af8731a31a37fce54dbf8cbbe90b3f8553179d23f5\",\n      \"C\": \"03b01869f528337e161a6768b480fcf9f75fd248b649c382f5e352489fd84fd011\",\n    },\n    {\n      \"amount\": 8,\n      \"id\": \"009a1f293253e41e\",\n      \"secret\": \"4f3155acef6481108fcf354f6d06e504ce8b441e617d30c88924991298cdbcad\",\n      \"C\": \"0278ab1c1af35487a5ea903b693e96447b2034d0fd6bac529e753097743bf73ca9\",\n    }\n  ],\n  \"outputs\": [\n    {\n      \"amount\": 1,\n      \"id\": \"009a1f293253e41e\",\n      \"B_\": \"03327fc4fa333909b70f08759e217ce5c94e6bf1fc2382562f3c560c5580fa69f4\"\n    }\n  ]\n}'\n```\n\nEverything here is the same as in [NUT-05][05] except for `outputs`. The `amount` field in the `BlindedMessage`s here are ignored by `Bob` so they can be set to any arbitrary value by `Alice` (they should be set to a value, like `1` so potential JSON validations do not error).\n\nIf the mint has made a successful payment, it will respond the following.\n\n**Response** `PostMeltQuoteBolt11Response` from `Bob`:\n\n```json\n{\n  \"state\": \"PAID\",\n  \"payment_preimage\": \"c5a1ae1f639e1f4a3872e81500fd028bece7bedc1152f740cba5c3417b748c1b\",\n  \"change\": [\n    {\n      \"id\": \"009a1f293253e41e\",\n      \"amount\": 2,\n      \"C_\": \"03c668f551855ddc792e22ea61d32ddfa6a45b1eb659ce66e915bf5127a8657be0\"\n    }\n  ]\n}\n```\n\nThe field `change` is an array of `BlindSignatures` that account for the overpaid fees. Notice that the amount has been changed by the mint. `Alice` must take these and generate `Proofs` by unblinding them as described in [NUT-00][00] and as she does in [NUT-04][04] when minting new tokens. After generating the `Proofs`, `Alice` stores them in her database.\n\n## Mint info setting\n\nThe [NUT-06][06] `MintMethodSetting` indicates support for this feature:\n\n```json\n{\n  \"8\": {\n    \"supported\": true\n  }\n}\n```\n\n[00]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crqqpd6d9\n[01]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crznyve7g\n[02]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cry0t0uzl\n[03]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crxuwwl3j\n[04]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crg74fknc\n[05]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cr2dsg4q4\n[06]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crv3ltsuz\n[07]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crwz62n00\n[08]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crs4q9zck\n[09]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crjx9yptm\n[10]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cnqx5tye7\n[11]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cnz43282n\n[12]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cnyf7fzky\n","sig":"b06cdf83f6adc01f5580709d70a737ec35558ac313efca9cfa75f214debc7bd47412dce094a7a5c0e5010c559c6bd46858d54e535b4d646901a7b924d6a7176d"}