diff options
Diffstat (limited to '47.md')
| -rw-r--r-- | 47.md | 380 |
1 files changed, 283 insertions, 97 deletions
| @@ -28,15 +28,16 @@ Fundamentally NWC is communication between a **client** and **wallet service** b | |||
| 28 | 28 | ||
| 29 | 4. Once the payment is complete the **wallet service** will send an encrypted `response` (kind 23195) to the **user** over the relay(s) in the URI. | 29 | 4. Once the payment is complete the **wallet service** will send an encrypted `response` (kind 23195) to the **user** over the relay(s) in the URI. |
| 30 | 30 | ||
| 31 | 5. The **wallet service** may send encrypted notifications (kind 23196) of wallet events (such as a received payment) to the **client**. | 31 | 5. The **wallet service** may send encrypted notifications (kind 23197) of wallet events (such as a received payment) to the **client**. |
| 32 | 32 | ||
| 33 | ## Events | 33 | ## Events |
| 34 | 34 | ||
| 35 | There are four event kinds: | 35 | There are four event kinds: |
| 36 | |||
| 36 | - `NIP-47 info event`: 13194 | 37 | - `NIP-47 info event`: 13194 |
| 37 | - `NIP-47 request`: 23194 | 38 | - `NIP-47 request`: 23194 |
| 38 | - `NIP-47 response`: 23195 | 39 | - `NIP-47 response`: 23195 |
| 39 | - `NIP-47 notification event`: 23196 | 40 | - `NIP-47 notification event`: 23197 (23196 for backwards compatibility with NIP-04) |
| 40 | 41 | ||
| 41 | ### Info Event | 42 | ### Info Event |
| 42 | 43 | ||
| @@ -46,34 +47,71 @@ The content should be a plaintext string with the supported capabilities space-s | |||
| 46 | 47 | ||
| 47 | If the **wallet service** supports notifications, the info event SHOULD contain a `notifications` tag with the supported notification types space-separated, eg. `payment_received payment_sent`. | 48 | If the **wallet service** supports notifications, the info event SHOULD contain a `notifications` tag with the supported notification types space-separated, eg. `payment_received payment_sent`. |
| 48 | 49 | ||
| 50 | It should also contain supported encryption modes as described in the [Encryption](#encryption) section. For example: | ||
| 51 | |||
| 52 | ```jsonc | ||
| 53 | { | ||
| 54 | "kind": 13194, | ||
| 55 | "tags": [ | ||
| 56 | ["encryption", "nip44_v2 nip04"], // List of supported encryption schemes as described in the Encryption section. | ||
| 57 | ["notifications", "payment_received payment_sent"] | ||
| 58 | // ... | ||
| 59 | ], | ||
| 60 | "content": "pay_invoice get_balance make_invoice lookup_invoice list_transactions get_info notifications", | ||
| 61 | // ... | ||
| 62 | } | ||
| 63 | ``` | ||
| 64 | |||
| 49 | ### Request and Response Events | 65 | ### Request and Response Events |
| 50 | 66 | ||
| 51 | Both the request and response events SHOULD contain one `p` tag, containing the public key of the **wallet service** if this is a request, and the public key of the **client** if this is a response. The response event SHOULD contain an `e` tag with the id of the request event it is responding to. | 67 | Both the request and response events SHOULD contain one `p` tag, containing the public key of the **wallet service** if this is a request, and the public key of the **client** if this is a response. The response event SHOULD contain an `e` tag with the id of the request event it is responding to. |
| 52 | Optionally, a request can have an `expiration` tag that has a unix timestamp in seconds. If the request is received after this timestamp, it should be ignored. | 68 | Optionally, a request can have an `expiration` tag that has a unix timestamp in seconds. If the request is received after this timestamp, it should be ignored. |
| 53 | 69 | ||
| 54 | The content of requests and responses is encrypted with [NIP04](04.md), and is a JSON-RPCish object with a semi-fixed structure: | 70 | The content of requests and responses is encrypted with [NIP44](44.md), and is a JSON-RPCish object with a semi-fixed structure. |
| 55 | 71 | ||
| 56 | Request: | 72 | **Important note for backwards-compatibility:** The initial version of the protocol used [NIP04](04.md). If a **wallet service** or client app does not include the `encryption` tag in the |
| 57 | ```jsonc | 73 | `info` or request events, it should be assumed that the connection is using NIP04 for encryption. See the [Encryption](#encryption) section for more information. |
| 74 | |||
| 75 | Example request: | ||
| 76 | |||
| 77 | ```js | ||
| 58 | { | 78 | { |
| 59 | "method": "pay_invoice", // method, string | 79 | "kind" 23194, |
| 60 | "params": { // params, object | 80 | "tags": [ |
| 61 | "invoice": "lnbc50n1..." // command-related data | 81 | ["encryption", "nip44_v2"], |
| 62 | } | 82 | ["p", "03..." ] // public key of the wallet service. |
| 83 | // ... | ||
| 84 | ], | ||
| 85 | "content": nip44_encrypt({ // Encryption type corresponds to the `encryption` tag. | ||
| 86 | "method": "pay_invoice", // method, string | ||
| 87 | "params": { // params, object | ||
| 88 | "invoice": "lnbc50n1..." // command-related data | ||
| 89 | } | ||
| 90 | }), | ||
| 63 | } | 91 | } |
| 64 | ``` | 92 | ``` |
| 65 | 93 | ||
| 66 | Response: | 94 | Example response: |
| 67 | ```jsonc | 95 | |
| 96 | ```js | ||
| 68 | { | 97 | { |
| 69 | "result_type": "pay_invoice", //indicates the structure of the result field | 98 | "kind" 23195, |
| 70 | "error": { //object, non-null in case of error | 99 | "tags": [ |
| 71 | "code": "UNAUTHORIZED", //string error code, see below | 100 | ["p", "03..." ] // public key of the requesting client app |
| 72 | "message": "human readable error message" | 101 | ["e", "1234"] // id of the request event this is responding to |
| 73 | }, | 102 | // ... |
| 74 | "result": { // result, object. null in case of error. | 103 | ], |
| 75 | "preimage": "0123456789abcdef..." // command-related data | 104 | "content": nip44_encrypt({ // Encrypted using the scheme requested by the client. |
| 76 | } | 105 | "result_type": "pay_invoice", //indicates the structure of the result field |
| 106 | "error": { //object, non-null in case of error | ||
| 107 | "code": "UNAUTHORIZED", //string error code, see below | ||
| 108 | "message": "human readable error message" | ||
| 109 | }, | ||
| 110 | "result": { // result, object. null in case of error. | ||
| 111 | "preimage": "0123456789abcdef..." // command-related data | ||
| 112 | } | ||
| 113 | }) | ||
| 114 | // ... | ||
| 77 | } | 115 | } |
| 78 | ``` | 116 | ``` |
| 79 | 117 | ||
| @@ -83,9 +121,9 @@ If the command was successful, the `error` field must be null. | |||
| 83 | 121 | ||
| 84 | ### Notification Events | 122 | ### Notification Events |
| 85 | 123 | ||
| 86 | The notification event SHOULD contain one `p` tag, the public key of the **client**. | 124 | The notification event is a kind 23197 event SHOULD contain one `p` tag, the public key of the **client**. |
| 87 | 125 | ||
| 88 | The content of notifications is encrypted with [NIP04](04.md), and is a JSON-RPCish object with a semi-fixed structure: | 126 | The content of notifications is encrypted with [NIP44](44.md) (or NIP-04 for legacy client apps), and is a JSON-RPCish object with a semi-fixed structure: |
| 89 | 127 | ||
| 90 | ```jsonc | 128 | ```jsonc |
| 91 | { | 129 | { |
| @@ -96,7 +134,6 @@ The content of notifications is encrypted with [NIP04](04.md), and is a JSON-RPC | |||
| 96 | } | 134 | } |
| 97 | ``` | 135 | ``` |
| 98 | 136 | ||
| 99 | |||
| 100 | ### Error codes | 137 | ### Error codes |
| 101 | - `RATE_LIMITED`: The client is sending commands too fast. It should retry in a few seconds. | 138 | - `RATE_LIMITED`: The client is sending commands too fast. It should retry in a few seconds. |
| 102 | - `NOT_IMPLEMENTED`: The command is not known or is intentionally not implemented. | 139 | - `NOT_IMPLEMENTED`: The command is not known or is intentionally not implemented. |
| @@ -105,6 +142,7 @@ The content of notifications is encrypted with [NIP04](04.md), and is a JSON-RPC | |||
| 105 | - `RESTRICTED`: This public key is not allowed to do this operation. | 142 | - `RESTRICTED`: This public key is not allowed to do this operation. |
| 106 | - `UNAUTHORIZED`: This public key has no wallet connected. | 143 | - `UNAUTHORIZED`: This public key has no wallet connected. |
| 107 | - `INTERNAL`: An internal error. | 144 | - `INTERNAL`: An internal error. |
| 145 | - `UNSUPPORTED_ENCRYPTION`: The encryption type of the request is not supported by the wallet service. | ||
| 108 | - `OTHER`: Other error. | 146 | - `OTHER`: Other error. |
| 109 | 147 | ||
| 110 | ## Nostr Wallet Connect URI | 148 | ## Nostr Wallet Connect URI |
| @@ -148,6 +186,7 @@ Request: | |||
| 148 | "params": { | 186 | "params": { |
| 149 | "invoice": "lnbc50n1...", // bolt11 invoice | 187 | "invoice": "lnbc50n1...", // bolt11 invoice |
| 150 | "amount": 123, // invoice amount in msats, optional | 188 | "amount": 123, // invoice amount in msats, optional |
| 189 | "metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc, optional | ||
| 151 | } | 190 | } |
| 152 | } | 191 | } |
| 153 | ``` | 192 | ``` |
| @@ -166,42 +205,6 @@ Response: | |||
| 166 | Errors: | 205 | Errors: |
| 167 | - `PAYMENT_FAILED`: The payment failed. This may be due to a timeout, exhausting all routes, insufficient capacity or similar. | 206 | - `PAYMENT_FAILED`: The payment failed. This may be due to a timeout, exhausting all routes, insufficient capacity or similar. |
| 168 | 207 | ||
| 169 | ### `multi_pay_invoice` | ||
| 170 | |||
| 171 | Description: Requests payment of multiple invoices. | ||
| 172 | |||
| 173 | Request: | ||
| 174 | ```jsonc | ||
| 175 | { | ||
| 176 | "method": "multi_pay_invoice", | ||
| 177 | "params": { | ||
| 178 | "invoices": [ | ||
| 179 | {"id":"4da52c32a1", "invoice": "lnbc1...", "amount": 123}, // bolt11 invoice and amount in msats, amount is optional | ||
| 180 | {"id":"3da52c32a1", "invoice": "lnbc50n1..."}, | ||
| 181 | ], | ||
| 182 | } | ||
| 183 | } | ||
| 184 | ``` | ||
| 185 | |||
| 186 | Response: | ||
| 187 | |||
| 188 | For every invoice in the request, a separate response event is sent. To differentiate between the responses, each | ||
| 189 | response event contains a `d` tag with the id of the invoice it is responding to; if no id was given, then the | ||
| 190 | payment hash of the invoice should be used. | ||
| 191 | |||
| 192 | ```jsonc | ||
| 193 | { | ||
| 194 | "result_type": "multi_pay_invoice", | ||
| 195 | "result": { | ||
| 196 | "preimage": "0123456789abcdef...", // preimage of the payment | ||
| 197 | "fees_paid": 123, // value in msats, optional | ||
| 198 | } | ||
| 199 | } | ||
| 200 | ``` | ||
| 201 | |||
| 202 | Errors: | ||
| 203 | - `PAYMENT_FAILED`: The payment failed. This may be due to a timeout, exhausting all routes, insufficient capacity or similar. | ||
| 204 | |||
| 205 | ### `pay_keysend` | 208 | ### `pay_keysend` |
| 206 | 209 | ||
| 207 | Request: | 210 | Request: |
| @@ -236,44 +239,6 @@ Response: | |||
| 236 | Errors: | 239 | Errors: |
| 237 | - `PAYMENT_FAILED`: The payment failed. This may be due to a timeout, exhausting all routes, insufficient capacity or similar. | 240 | - `PAYMENT_FAILED`: The payment failed. This may be due to a timeout, exhausting all routes, insufficient capacity or similar. |
| 238 | 241 | ||
| 239 | ### `multi_pay_keysend` | ||
| 240 | |||
| 241 | Description: Requests multiple keysend payments. | ||
| 242 | |||
| 243 | Has an array of keysends, these follow the same semantics as `pay_keysend`, just done in a batch | ||
| 244 | |||
| 245 | Request: | ||
| 246 | ```jsonc | ||
| 247 | { | ||
| 248 | "method": "multi_pay_keysend", | ||
| 249 | "params": { | ||
| 250 | "keysends": [ | ||
| 251 | {"id": "4c5b24a351", "pubkey": "03...", "amount": 123}, | ||
| 252 | {"id": "3da52c32a1", "pubkey": "02...", "amount": 567, "preimage": "abc123..", "tlv_records": [{"type": 696969, "value": "77616c5f6872444873305242454d353736"}]}, | ||
| 253 | ], | ||
| 254 | } | ||
| 255 | } | ||
| 256 | ``` | ||
| 257 | |||
| 258 | Response: | ||
| 259 | |||
| 260 | For every keysend in the request, a separate response event is sent. To differentiate between the responses, each | ||
| 261 | response event contains a `d` tag with the id of the keysend it is responding to; if no id was given, then the | ||
| 262 | pubkey should be used. | ||
| 263 | |||
| 264 | ```jsonc | ||
| 265 | { | ||
| 266 | "result_type": "multi_pay_keysend", | ||
| 267 | "result": { | ||
| 268 | "preimage": "0123456789abcdef...", // preimage of the payment | ||
| 269 | "fees_paid": 123, // value in msats, optional | ||
| 270 | } | ||
| 271 | } | ||
| 272 | ``` | ||
| 273 | |||
| 274 | Errors: | ||
| 275 | - `PAYMENT_FAILED`: The payment failed. This may be due to a timeout, exhausting all routes, insufficient capacity or similar. | ||
| 276 | |||
| 277 | ### `make_invoice` | 242 | ### `make_invoice` |
| 278 | 243 | ||
| 279 | Request: | 244 | Request: |
| @@ -284,7 +249,8 @@ Request: | |||
| 284 | "amount": 123, // value in msats | 249 | "amount": 123, // value in msats |
| 285 | "description": "string", // invoice's description, optional | 250 | "description": "string", // invoice's description, optional |
| 286 | "description_hash": "string", // invoice's description hash, optional | 251 | "description_hash": "string", // invoice's description hash, optional |
| 287 | "expiry": 213 // expiry in seconds from time invoice is created, optional | 252 | "expiry": 213, // expiry in seconds from time invoice is created, optional |
| 253 | "metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc, optional | ||
| 288 | } | 254 | } |
| 289 | } | 255 | } |
| 290 | ``` | 256 | ``` |
| @@ -295,6 +261,7 @@ Response: | |||
| 295 | "result_type": "make_invoice", | 261 | "result_type": "make_invoice", |
| 296 | "result": { | 262 | "result": { |
| 297 | "type": "incoming", // "incoming" for invoices, "outgoing" for payments | 263 | "type": "incoming", // "incoming" for invoices, "outgoing" for payments |
| 264 | "state": "pending", // optional | ||
| 298 | "invoice": "string", // encoded invoice, optional | 265 | "invoice": "string", // encoded invoice, optional |
| 299 | "description": "string", // invoice's description, optional | 266 | "description": "string", // invoice's description, optional |
| 300 | "description_hash": "string", // invoice's description hash, optional | 267 | "description_hash": "string", // invoice's description hash, optional |
| @@ -328,6 +295,7 @@ Response: | |||
| 328 | "result_type": "lookup_invoice", | 295 | "result_type": "lookup_invoice", |
| 329 | "result": { | 296 | "result": { |
| 330 | "type": "incoming", // "incoming" for invoices, "outgoing" for payments | 297 | "type": "incoming", // "incoming" for invoices, "outgoing" for payments |
| 298 | "state": "pending", // can be "pending", "settled", "accepted" (for hold invoices), "expired" (for invoices) or "failed" (for payments), optional | ||
| 331 | "invoice": "string", // encoded invoice, optional | 299 | "invoice": "string", // encoded invoice, optional |
| 332 | "description": "string", // invoice's description, optional | 300 | "description": "string", // invoice's description, optional |
| 333 | "description_hash": "string", // invoice's description hash, optional | 301 | "description_hash": "string", // invoice's description hash, optional |
| @@ -376,6 +344,7 @@ Response: | |||
| 376 | "transactions": [ | 344 | "transactions": [ |
| 377 | { | 345 | { |
| 378 | "type": "incoming", // "incoming" for invoices, "outgoing" for payments | 346 | "type": "incoming", // "incoming" for invoices, "outgoing" for payments |
| 347 | "state": "pending", // can be "pending", "settled", "accepted" (for hold invoices), "expired" (for invoices) or "failed" (for payments), optional | ||
| 379 | "invoice": "string", // encoded invoice, optional | 348 | "invoice": "string", // encoded invoice, optional |
| 380 | "description": "string", // invoice's description, optional | 349 | "description": "string", // invoice's description, optional |
| 381 | "description_hash": "string", // invoice's description hash, optional | 350 | "description_hash": "string", // invoice's description hash, optional |
| @@ -440,6 +409,89 @@ Response: | |||
| 440 | } | 409 | } |
| 441 | ``` | 410 | ``` |
| 442 | 411 | ||
| 412 | ### `make_hold_invoice` | ||
| 413 | |||
| 414 | Creates a hold invoice using a pre-generated preimage. | ||
| 415 | |||
| 416 | Request: | ||
| 417 | ```jsonc | ||
| 418 | { | ||
| 419 | "method": "make_hold_invoice", | ||
| 420 | "params": { | ||
| 421 | "amount": 123, // value in msats | ||
| 422 | "description": "string", // invoice's description, optional | ||
| 423 | "description_hash": "string", // invoice's description hash, optional | ||
| 424 | "expiry": 213 // expiry in seconds from time invoice is created for a payment to be initiated, optional. This does not determine how long a payment can be held (see `settle_deadline`) | ||
| 425 | "payment_hash": "string" // Payment hash for the payment generated from the preimage | ||
| 426 | "min_cltv_expiry_delta": 144 // The minimum CLTV delta to use for the final hop, optional | ||
| 427 | } | ||
| 428 | } | ||
| 429 | ``` | ||
| 430 | |||
| 431 | Response: | ||
| 432 | ```jsonc | ||
| 433 | { | ||
| 434 | "result_type": "make_hold_invoice", | ||
| 435 | "result": { | ||
| 436 | "type": "incoming", // "incoming" for invoices, "outgoing" for payments | ||
| 437 | "invoice": "string", // encoded invoice, optional | ||
| 438 | "description": "string", // invoice's description, optional | ||
| 439 | "description_hash": "string", // invoice's description hash, optional | ||
| 440 | "payment_hash": "string", // Payment hash for the payment | ||
| 441 | "amount": 123, // value in msats | ||
| 442 | "created_at": unixtimestamp, // invoice/payment creation time | ||
| 443 | "expires_at": unixtimestamp, // invoice expiration time, optional if not applicable | ||
| 444 | "metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc. | ||
| 445 | } | ||
| 446 | } | ||
| 447 | ``` | ||
| 448 | |||
| 449 | ### `cancel_hold_invoice` | ||
| 450 | |||
| 451 | Cancels a hold invoice using the payment hash | ||
| 452 | |||
| 453 | Request: | ||
| 454 | ```jsonc | ||
| 455 | { | ||
| 456 | "method": "cancel_hold_invoice", | ||
| 457 | "params": { | ||
| 458 | "payment_hash": "string" // Payment hash for the payment generated from the preimage | ||
| 459 | } | ||
| 460 | } | ||
| 461 | ``` | ||
| 462 | |||
| 463 | Response: | ||
| 464 | ```jsonc | ||
| 465 | { | ||
| 466 | "result_type": "cancel_hold_invoice", | ||
| 467 | "result": {} | ||
| 468 | } | ||
| 469 | ``` | ||
| 470 | |||
| 471 | ### `settle_hold_invoice` | ||
| 472 | |||
| 473 | Settles a hold invoice using the preimage | ||
| 474 | |||
| 475 | |||
| 476 | Request: | ||
| 477 | ```jsonc | ||
| 478 | { | ||
| 479 | "method": "settle_hold_invoice", | ||
| 480 | "params": { | ||
| 481 | "preimage": "string" // preimage for the payment | ||
| 482 | } | ||
| 483 | } | ||
| 484 | ``` | ||
| 485 | |||
| 486 | Response: | ||
| 487 | ```jsonc | ||
| 488 | { | ||
| 489 | "result_type": "settle_hold_invoice", | ||
| 490 | "result": {} | ||
| 491 | } | ||
| 492 | ``` | ||
| 493 | |||
| 494 | |||
| 443 | ## Notifications | 495 | ## Notifications |
| 444 | 496 | ||
| 445 | ### `payment_received` | 497 | ### `payment_received` |
| @@ -452,6 +504,7 @@ Notification: | |||
| 452 | "notification_type": "payment_received", | 504 | "notification_type": "payment_received", |
| 453 | "notification": { | 505 | "notification": { |
| 454 | "type": "incoming", | 506 | "type": "incoming", |
| 507 | "state": "settled", // optional | ||
| 455 | "invoice": "string", // encoded invoice | 508 | "invoice": "string", // encoded invoice |
| 456 | "description": "string", // invoice's description, optional | 509 | "description": "string", // invoice's description, optional |
| 457 | "description_hash": "string", // invoice's description hash, optional | 510 | "description_hash": "string", // invoice's description hash, optional |
| @@ -477,6 +530,7 @@ Notification: | |||
| 477 | "notification_type": "payment_sent", | 530 | "notification_type": "payment_sent", |
| 478 | "notification": { | 531 | "notification": { |
| 479 | "type": "outgoing", | 532 | "type": "outgoing", |
| 533 | "state": "settled", // optional | ||
| 480 | "invoice": "string", // encoded invoice | 534 | "invoice": "string", // encoded invoice |
| 481 | "description": "string", // invoice's description, optional | 535 | "description": "string", // invoice's description, optional |
| 482 | "description_hash": "string", // invoice's description hash, optional | 536 | "description_hash": "string", // invoice's description hash, optional |
| @@ -492,6 +546,30 @@ Notification: | |||
| 492 | } | 546 | } |
| 493 | ``` | 547 | ``` |
| 494 | 548 | ||
| 549 | ### `hold_invoice_accepted` | ||
| 550 | |||
| 551 | Description: Sent when a payer accepts (locks in) a hold invoice. To avoid locking up funds in channels the hold invoice SHOULD be settled or canceled within a few minutes of receiving this event. | ||
| 552 | |||
| 553 | Notification: | ||
| 554 | ```jsonc | ||
| 555 | { | ||
| 556 | "notification_type": "hold_invoice_accepted", | ||
| 557 | "notification": { | ||
| 558 | "type": "incoming", | ||
| 559 | "state": "accepted", // optional | ||
| 560 | "invoice": "string", // encoded invoice | ||
| 561 | "description": "string", // invoice's description, optional | ||
| 562 | "description_hash": "string", // invoice's description hash, optional | ||
| 563 | "payment_hash": "string", // Payment hash for the payment | ||
| 564 | "amount": 123, // value in msats | ||
| 565 | "created_at": unixtimestamp, // invoice/payment creation time | ||
| 566 | "expires_at": unixtimestamp, // invoice expiration time | ||
| 567 | "settle_deadline": blocknumber, // invoice can only be safely settled or canceled before this block number. | ||
| 568 | "metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc. | ||
| 569 | } | ||
| 570 | } | ||
| 571 | ``` | ||
| 572 | |||
| 495 | ## Example pay invoice flow | 573 | ## Example pay invoice flow |
| 496 | 574 | ||
| 497 | 0. The user scans the QR code generated by the **wallet service** with their **client** application, they follow a `nostr+walletconnect://` deeplink or configure the connection details manually. | 575 | 0. The user scans the QR code generated by the **wallet service** with their **client** application, they follow a `nostr+walletconnect://` deeplink or configure the connection details manually. |
| @@ -499,9 +577,89 @@ Notification: | |||
| 499 | 2. **wallet service** verifies that the author's key is authorized to perform the payment, decrypts the payload and sends the payment. | 577 | 2. **wallet service** verifies that the author's key is authorized to perform the payment, decrypts the payload and sends the payment. |
| 500 | 3. **wallet service** responds to the event by sending an event with kind `23195` and content being a response either containing an error message or a preimage. | 578 | 3. **wallet service** responds to the event by sending an event with kind `23195` and content being a response either containing an error message or a preimage. |
| 501 | 579 | ||
| 580 | ## Encryption | ||
| 581 | |||
| 582 | The initial version of NWC used [NIP-04](04.md) for encryption which has been deprecated and replaced by [NIP-44](44.md). NIP-44 should always be preferred for encryption, but there may be legacy cases | ||
| 583 | where the **wallet service** or **client** has not yet migrated to NIP-44. The **wallet service** and **client** should negotiate the encryption method to use based on the `encryption` tag in the `info` event. | ||
| 584 | |||
| 585 | The encryption tag can contain either `nip44_v2` or `nip04`. The absence of this tag implies that the wallet only supports `nip04`. | ||
| 586 | |||
| 587 | | Encryption code | Use | Notes | | ||
| 588 | |-----------------|----------------------|---------------------------------------------------------| | ||
| 589 | | `nip44_v2` | NIP-44 | Required | | ||
| 590 | | `nip04` | NIP-04 | Deprecated and only here for backward compatibility | | ||
| 591 | | `<not present>` | NIP-04 | Deprecated and only here for backward compatibility | | ||
| 592 | |||
| 593 | The negotiation works as follows. | ||
| 594 | |||
| 595 | 1. The **wallet service** includes an `encryption` tag in the `info` event. This tag contains a space-separated list of encryption schemes that the **wallet service** supports (eg. `nip44_v2 nip04`) | ||
| 596 | 2. The **client application** includes an `encryption` tag in each request event. This tag contains the encryption scheme which should be used for the request. The **client application** should always prefer nip44 if supported by the **wallet service**. | ||
| 597 | |||
| 598 | When a **client application** establishes a connection, it should read the info event and look for the `encryption` tag. | ||
| 599 | |||
| 600 | **Absence of this tag implies that the wallet only supports nip04.** | ||
| 601 | |||
| 602 | If the `encryption` tag is present, the **client application** will choose optimal encryption supported by both itself, and the **wallet service**, which should always be nip44 if possible. | ||
| 603 | |||
| 604 | ### Request events | ||
| 605 | |||
| 606 | When a **client application** sends a request event, it should include a `encryption` tag with the encryption scheme it is using. The scheme MUST be supported by the **wallet service** as indicated by the info event. | ||
| 607 | For example, if the client application supports nip44, the request event might look like: | ||
| 608 | |||
| 609 | ```jsonc | ||
| 610 | { | ||
| 611 | "kind": 23194, | ||
| 612 | "tags": [ | ||
| 613 | ["encryption", "nip44_v2"], | ||
| 614 | // ... | ||
| 615 | ], | ||
| 616 | // ... | ||
| 617 | } | ||
| 618 | ``` | ||
| 619 | |||
| 620 | If the **wallet service** does not support the specified encryption scheme, it will return an `UNSUPPORTED_ENCRYPTION` error. Absence of the `encryption` tag indicates use of nip04 for encryption. | ||
| 621 | |||
| 622 | ### Notification events | ||
| 623 | |||
| 624 | If a **wallet service** supports both nip04 and nip44, it should publish two notification events for each notification - kind 23196 encrypted with NIP-04, and kind 23197 encrypted with NIP-44. If the **wallet service** only supports nip44, it should only publish kind 23197 events. | ||
| 625 | |||
| 626 | The **client** should check the `encryption` tag in the `info` event to determine which encryption schemes the **wallet service** supports, and listen to the appropriate notification event. | ||
| 627 | |||
| 502 | ## Using a dedicated relay | 628 | ## Using a dedicated relay |
| 503 | This NIP does not specify any requirements on the type of relays used. However, if the user is using a custodial service it might make sense to use a relay that is hosted by the custodial service. The relay may then enforce authentication to prevent metadata leaks. Not depending on a 3rd party relay would also improve reliability in this case. | 629 | This NIP does not specify any requirements on the type of relays used. However, if the user is using a custodial service it might make sense to use a relay that is hosted by the custodial service. The relay may then enforce authentication to prevent metadata leaks. Not depending on a 3rd party relay would also improve reliability in this case. |
| 504 | 630 | ||
| 631 | ## Metadata | ||
| 632 | Metadata MAY be stored by the **wallet service** alongside invoices and payments. The metadata MUST be no more than 4096 characters, otherwise MUST be dropped. This is to ensure transactions do not get too large to be relayed. | ||
| 633 | |||
| 634 | NWC relays SHOULD allow at least a payload size of 64KB and **clients** SHOULD fetch small page sizes (maximum of 20 transactions per page) otherwise there is risk of `list_transactions` responses being rejected. | ||
| 635 | |||
| 636 | Here are some properties that are recognized by some NWC clients: | ||
| 637 | |||
| 638 | ```jsonc | ||
| 639 | { | ||
| 640 | "comment": "string", // LUD-12 comment | ||
| 641 | "payer_data": { | ||
| 642 | "email": "string", | ||
| 643 | "name": "string", | ||
| 644 | "pubkey": "string", | ||
| 645 | }, // LUD-18 payer data | ||
| 646 | "recipient_data": { | ||
| 647 | "identifier": "string" | ||
| 648 | }, // similar to LUD-18 payer data, but to record recipient data e.g. the lightning address of the recipient | ||
| 649 | "nostr": { | ||
| 650 | "pubkey": "string", | ||
| 651 | "tags": [], | ||
| 652 | // ... rest of zap request event | ||
| 653 | }, // NIP-57 Zap Request event (kind 9734) | ||
| 654 | "tlv_records": [ | ||
| 655 | { | ||
| 656 | "type": 5482373484, // tlv type | ||
| 657 | "value": "0123456789abcdef" // hex encoded tlv value | ||
| 658 | } | ||
| 659 | ] // keysend TLV records (e.g. for podcasting 2.0 boostagrams) | ||
| 660 | } & Record<string, unknown>; | ||
| 661 | ``` | ||
| 662 | |||
| 505 | ## Appendix | 663 | ## Appendix |
| 506 | 664 | ||
| 507 | ### Example NIP-47 info event | 665 | ### Example NIP-47 info event |
| @@ -513,6 +671,7 @@ This NIP does not specify any requirements on the type of relays used. However, | |||
| 513 | "created_at": 1713883677, | 671 | "created_at": 1713883677, |
| 514 | "kind": 13194, | 672 | "kind": 13194, |
| 515 | "tags": [ | 673 | "tags": [ |
| 674 | [ "encryption", "nip44_v2 nip04" ], | ||
| 516 | [ | 675 | [ |
| 517 | "notifications", | 676 | "notifications", |
| 518 | "payment_received payment_sent" | 677 | "payment_received payment_sent" |
| @@ -522,3 +681,30 @@ This NIP does not specify any requirements on the type of relays used. However, | |||
| 522 | "sig": "31f57b369459b5306a5353aa9e03be7fbde169bc881c3233625605dd12f53548179def16b9fe1137e6465d7e4d5bb27ce81fd6e75908c46b06269f4233c845d8" | 681 | "sig": "31f57b369459b5306a5353aa9e03be7fbde169bc881c3233625605dd12f53548179def16b9fe1137e6465d7e4d5bb27ce81fd6e75908c46b06269f4233c845d8" |
| 523 | } | 682 | } |
| 524 | ``` | 683 | ``` |
| 684 | |||
| 685 | ### Example Hold Invoice Support Flow | ||
| 686 | |||
| 687 | 1. Client generates a 32-byte hex-encoded preimage. | ||
| 688 | 2. Computes SHA-256 to derive payment hash. | ||
| 689 | 3. Sends `make_hold_invoice` with payment hash and desired parameters. | ||
| 690 | 4. Waits for `hold_invoice_accepted` notification. | ||
| 691 | 5. Upon receiving notification, either: | ||
| 692 | |||
| 693 | * Calls `settle_hold_invoice` with the original preimage to release funds, or | ||
| 694 | * Calls `cancel_hold_invoice` with payment hash to abort. | ||
| 695 | |||
| 696 | ### Deep-links | ||
| 697 | |||
| 698 | Wallet applications can register deeplinks in mobile systems to make it possible to create a linking UX that doesn't require the user scanning a QR code or pasting some code. | ||
| 699 | |||
| 700 | `nostrnwc://connect` and `nostrnwc+{app_name}://connect` can be registered by wallet apps and queried by apps that want to receive an NWC pairing code. | ||
| 701 | |||
| 702 | All URI parameters, MUST be URI-encoded. | ||
| 703 | |||
| 704 | URI parameters: | ||
| 705 | * `appicon` -- URL to an icon of the client that wants to create a connection. | ||
| 706 | * `appname` -- Name of the client that wants to create a connection. | ||
| 707 | * `callback` -- URI schema the wallet should open with the connection string | ||
| 708 | |||
| 709 | Once a connection has been created by the wallet, it should be returned to the client by opening the callback with the following parameters | ||
| 710 | * `value` -- NWC pairing code (e.g. `nostr+walletconnect://...`) | ||