{"id":"be8ad78fc72183decc774753622ace90d775101c5663ca9c29f5875326f6279c","pubkey":"d1678e7ef965374bbea308a1215609a78376dc158277a7d657680f9d5efd5c38","created_at":1783082300,"kind":30817,"tags":[["d","nut-25"],["title","NUT-25: BOLT12"],["summary","Minting and melting with bolt12 Lightning offers."],["s","optional"],["t","cashu"],["t","ecash"],["t","nut"],["alt","A specification: NUT-25: BOLT12"],["client","openspecs-import"],["published_at","1756375483"],["proxy","https://github.com/cashubtc/nuts/blob/a845dfc998abae501fc3419592d53dc995d34b12/25.md","web"],["x","b1c501d74b908c4ce4b2cd04befad17192a0bed8f595e8da11ecb9c42914dabb"]],"content":"# NUT-25: BOLT12\n\n`optional`\n\n`depends on: NUT-04 NUT-05 NUT-20`\n\n---\n\nThis document describes minting and melting ecash with the `bolt12` payment method, which uses Lightning Network offers. 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.\n\n## Mint Quote\n\nFor the `bolt12` method, the wallet includes the following specific `PostMintQuoteBolt12Request` data:\n\n```json\n{\n  \"amount\": <int|null>,\n  \"unit\": <str_enum[UNIT]>,\n  \"description\": <str|null>,\n  \"pubkey\": <str>\n}\n```\n\n> **Note:** While a pubkey is optional as per [NUT-20][20] for [NUT-04][04] it is required in this NUT and the mint **MUST NOT** issue a mint quote if one is not included.\n\n> **Privacy:** To prevent linking multiple mint quotes together, wallets **SHOULD** generate a unique public key for each mint quote request.\n\nThe mint responds with a `PostMintQuoteBolt12Response`:\n\n```json\n{\n  \"quote\": <str>,\n  \"request\": <str>,\n  \"amount\": <int|null>,\n  \"unit\": <str_enum[UNIT]>,\n  \"method\": \"bolt12\",\n  \"expiry\": <int|null>,\n  \"pubkey\": <str>,\n  \"amount_paid\": <int>,\n  \"amount_issued\": <int>,\n  \"updated_at\": <int>\n}\n```\n\nWhere:\n\n- `quote` is the quote ID\n- `request` is the bolt12 offer\n- `expiry` is the Unix timestamp until which the mint quote is valid\n- `amount_paid` is the amount that has been paid to the mint via the bolt12 offer\n- `amount_issued` is the amount of ecash that has been issued for the given mint quote\n- `updated_at` is defined in [NUT-04][04]\n\n### Example\n\n**Request** with curl:\n\n```bash\ncurl -X POST http://localhost:3338/v1/mint/quote/bolt12 -d \\\n'{\"amount\": 10, \"unit\": \"sat\", \"pubkey\": \"03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac\"}' \\\n-H \"Content-Type: application/json\"\n```\n\n**Response**:\n\n```json\n{\n  \"quote\": \"019e6d5a-2347-7000-8449-370fefb42fed\",\n  \"request\": \"lno1qcp...\",\n  \"amount\": 10,\n  \"unit\": \"sat\",\n  \"method\": \"bolt12\",\n  \"expiry\": 1701704757,\n  \"pubkey\": \"03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac\",\n  \"amount_paid\": 0,\n  \"amount_issued\": 0,\n  \"updated_at\": 1701704657\n}\n```\n\nCheck mint quote:\n\n```bash\ncurl -X GET http://localhost:3338/v1/mint/quote/bolt12/019e6d5a-2347-7000-8449-370fefb42fed\n```\n\nMinting tokens:\n\n```bash\ncurl -X POST https://mint.host:3338/v1/mint/bolt12 -H \"Content-Type: application/json\" -d \\\n'{\n  \"quote\": \"019e6d5a-2347-7000-8449-370fefb42fed\",\n  \"outputs\": [\n    {\n      \"amount\": 8,\n      \"id\": \"009a1f293253e41e\",\n      \"B_\": \"035015e6d7ade60ba8426cefaf1832bbd27257636e44a76b922d78e79b47cb689d\"\n    },\n    {\n      \"amount\": 2,\n      \"id\": \"009a1f293253e41e\",\n      \"B_\": \"0288d7649652d0a83fc9c966c969fb217f15904431e61a44b14999fabc1b5d9ac6\"\n    }\n  ]\n}'\n```\n\nResponse:\n\n```json\n{\n  \"signatures\": [\n    {\n      \"id\": \"009a1f293253e41e\",\n      \"amount\": 2,\n      \"C_\": \"0224f1c4c564230ad3d96c5033efdc425582397a5a7691d600202732edc6d4b1ec\"\n    },\n    {\n      \"id\": \"009a1f293253e41e\",\n      \"amount\": 8,\n      \"C_\": \"0277d1de806ed177007e5b94a8139343b6382e472c752a74e99949d511f7194f6c\"\n    }\n  ]\n}\n```\n\n## Multiple Issuances\n\nUnlike BOLT11 invoices, BOLT12 offers can be paid multiple times, allowing the wallet to mint multiple times for one quote. The wallet can call the check bolt12 endpoint, where the mint will return the `PostMintQuoteBolt12Response` including `amount_paid` and `amount_issued`. The difference between these values represents how much the wallet can mint by calling the mint endpoint. Wallets MAY mint any amount up to this available difference; in particular, they can mint less than the amount mintable. Mints MUST accept mint requests whose total output amount is less than or equal to (`amount_paid` - `amount_issued`).\n\n## Mint Settings\n\nA `description` option **SHOULD** be set to indicate whether the `bolt12` payment method backend supports providing an offer description.\n\n### Example `MintMethodSetting`\n\n```json\n{\n  \"method\": \"bolt12\",\n  \"unit\": <str>,\n  \"min_amount\": <int|null>,\n  \"max_amount\": <int|null>,\n  \"options\": {\n    \"description\": true\n  }\n}\n```\n\n## Melt Quote\n\nFor the `bolt12` method, the wallet includes the following specific `PostMeltQuoteBolt12Request` data:\n\n```json\n{\n  \"request\": <str>,\n  \"unit\": <str_enum[UNIT]>,\n  \"options\": { // Optional\n    \"amountless\": {\n      \"amount_msat\": <int>\n    }\n  }\n}\n```\n\nHere, `request` is the bolt12 Offer to be paid and `unit` is the unit the wallet would like to pay with. For amount-less offers, the `options.amountless.amount_msat` field can be used to specify the amount in millisatoshis to pay to the offer. If `options.amountless.amount_msat` is defined and the offer has an amount, they **MUST** be equal.\n\nThe mint responds with a `PostMeltQuoteBolt12Response`:\n\n```json\n{\n  \"quote\": <str>,\n  \"request\": <str>,\n  \"amount\": <int>,\n  \"unit\": <str_enum[UNIT]>,\n  \"method\": \"bolt12\",\n  \"fee_reserve\": <int>,\n  \"state\": <str_enum[STATE]>,\n  \"expiry\": <int>,\n  \"payment_preimage\": <str|null>\n}\n```\n\nWhere `fee_reserve` is the additional fee reserve required for the Lightning payment. The mint expects the wallet to include `Proofs` of _at least_ `total_amount = amount + fee_reserve + fee` where `fee` is calculated from the keyset's `input_fee_ppk` as described in [NUT-02][02].\n\n`state` is an enum string field with possible values `\"UNPAID\"`, `\"PENDING\"`, `\"PAID\"`:\n\n- `\"UNPAID\"` means that the request has not been paid yet.\n- `\"PENDING\"` means that the request is currently being paid.\n- `\"PAID\"` means that the request has been paid successfully.\n\n### Melting Tokens\n\nFor the `bolt12` method, the wallet can include an optional `outputs` field in the melt request to receive change for overpaid Lightning fees (see [NUT-08][08]):\n\n```json\n{\n  \"quote\": <str>,\n  \"inputs\": <Array[Proof]>,\n  \"outputs\": <Array[BlindedMessage]> // Optional\n}\n```\n\nIf the `outputs` field is included and there is excess from the `fee_reserve`, the mint will respond with a `change` field containing blind signatures for the overpaid amount:\n\n```json\n{\n  \"quote\": <str>,\n  \"request\": <str>,\n  \"amount\": <int>,\n  \"unit\": <str_enum[UNIT]>,\n  \"method\": \"bolt12\",\n  \"fee_reserve\": <int>,\n  \"state\": <str_enum[STATE]>,\n  \"expiry\": <int>,\n  \"payment_preimage\": <str>,\n  \"change\": <Array[BlindSignature]> // Present if outputs were included and there's change\n}\n```\n\n### Example\n\n**Melt quote request**:\n\n```bash\ncurl -X POST https://mint.host:3338/v1/melt/quote/bolt12 -d \\\n'{\"request\": \"lno1qcp4256ypqpq86q69t5wv5629arxqurn8cxg9p5qmmqy2e5xq...\", \"unit\": \"sat\"}'\n```\n\n**Melt quote response**:\n\n```json\n{\n  \"quote\": \"019e6d5a-2347-7000-82b3-56e12c3fcdf2\",\n  \"request\": \"lno1qcp4256ypqpq86q69t5wv5629arxqurn8cxg9p5qmmqy2e5xq...\",\n  \"amount\": 10,\n  \"unit\": \"sat\",\n  \"method\": \"bolt12\",\n  \"fee_reserve\": 2,\n  \"state\": \"UNPAID\",\n  \"expiry\": 1701704757\n}\n```\n\nCheck quote state:\n\n```bash\ncurl -X GET http://localhost:3338/v1/melt/quote/bolt12/019e6d5a-2347-7000-82b3-56e12c3fcdf2\n```\n\n**Melt request**:\n\n```bash\ncurl -X POST https://mint.host:3338/v1/melt/bolt12 -d \\\n'{\"quote\": \"019e6d5a-2347-7000-82b3-56e12c3fcdf2\", \"inputs\": [...]}'\n```\n\n**Successful melt response**:\n\n```json\n{\n  \"quote\": \"019e6d5a-2347-7000-82b3-56e12c3fcdf2\",\n  \"request\": \"lno1qcp4256ypqpq86q69t5wv5629arxqurn8cxg9p5qmmqy2e5xq...\",\n  \"amount\": 10,\n  \"unit\": \"sat\",\n  \"method\": \"bolt12\",\n  \"fee_reserve\": 2,\n  \"state\": \"PAID\",\n  \"expiry\": 1701704757,\n  \"payment_preimage\": \"c5a1ae1f639e1f4a3872e81500fd028bece7bedc1152f740cba5c3417b748c1b\"\n}\n```\n\n### Example `MeltMethodSetting`\n\n```json\n{\n  \"method\": \"bolt12\",\n  \"unit\": <str>,\n  \"min_amount\": <int|null>,\n  \"max_amount\": <int|null>\n}\n```\n\n[02]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cry0t0uzl\n[04]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crg74fknc\n[05]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595cr2dsg4q4\n[08]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595crs4q9zck\n[20]: nostr:naddr1qvzqqqrcvypzp5t83el0jefhfwl2xz9py9tqnfurwmwptqnh5lt9w6q0n4006hpcqqrxuat595erqzryd5a\n","sig":"41f2f7212409c7aebbcddf9a1b3496fe157f08938d8348af869dc02d2b635fdaccb03fa562a70dce402b2960060be4a1fac44ea029b1eca2f50cd9cf26055386"}