diff options
| author | Vitor Pamplona <vitor@vitorpamplona.com> | 2024-11-20 15:15:10 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-11-20 15:15:10 -0500 |
| commit | 84aeb10d395d0b9a3379c81aacf5b1ff4d675499 (patch) | |
| tree | 75e84d13e6efe69482a1d51a91876ed3bc0a9bcd /47.md | |
| parent | 36fa8bb66fce728520c3705971a75221e9ba06f4 (diff) | |
| parent | 32b9f3f40f2b7842fbcad08a041d9a2205b23de8 (diff) | |
Merge pull request #1164 from rolznz/feat/nip-47-notifications
NIP-47 notifications
Diffstat (limited to '47.md')
| -rw-r--r-- | 47.md | 128 |
1 files changed, 116 insertions, 12 deletions
| @@ -8,32 +8,42 @@ Nostr Wallet Connect | |||
| 8 | 8 | ||
| 9 | ## Rationale | 9 | ## Rationale |
| 10 | 10 | ||
| 11 | This NIP describes a way for clients to access a remote Lightning wallet through a standardized protocol. Custodians may implement this, or the user may run a bridge that bridges their wallet/node and the Nostr Wallet Connect protocol. | 11 | This NIP describes a way for clients to access a remote lightning wallet through a standardized protocol. Custodians may implement this, or the user may run a bridge that bridges their wallet/node and the Nostr Wallet Connect protocol. |
| 12 | 12 | ||
| 13 | ## Terms | 13 | ## Terms |
| 14 | 14 | ||
| 15 | * **client**: Nostr app on any platform that wants to pay Lightning invoices. | 15 | * **client**: Nostr app on any platform that wants to interact with a lightning wallet. |
| 16 | * **user**: The person using the **client**, and want's to connect their wallet app to their **client**. | 16 | * **user**: The person using the **client**, and wants to connect their wallet to their **client**. |
| 17 | * **wallet service**: Nostr app that typically runs on an always-on computer (eg. in the cloud or on a Raspberry Pi). This app has access to the APIs of the wallets it serves. | 17 | * **wallet service**: Nostr app that typically runs on an always-on computer (eg. in the cloud or on a Raspberry Pi). This app has access to the APIs of the wallets it serves. |
| 18 | 18 | ||
| 19 | ## Theory of Operation | 19 | ## Theory of Operation |
| 20 | 1. **Users** who wish to use this NIP to send lightning payments to other nostr users must first acquire a special "connection" URI from their NIP-47 compliant wallet application. The wallet application may provide this URI using a QR screen, or a pasteable string, or some other means. | 20 | 1. **Users** who wish to use this NIP to allow **client(s)** to interact with their wallet must first acquire a special "connection" URI from their NIP-47 compliant wallet application. The wallet application may provide this URI using a QR screen, or a pasteable string, or some other means. |
| 21 | 21 | ||
| 22 | 2. The **user** should then copy this URI into their **client(s)** by pasting, or scanning the QR, etc. The **client(s)** should save this URI and use it later whenever the **user** makes a payment. The **client** should then request an `info` (13194) event from the relay(s) specified in the URI. The **wallet service** will have sent that event to those relays earlier, and the relays will hold it as a replaceable event. | 22 | 2. The **user** should then copy this URI into their **client(s)** by pasting, or scanning the QR, etc. The **client(s)** should save this URI and use it later whenever the **user** (or the **client** on the user's behalf) wants to interact with the wallet. The **client** should then request an `info` (13194) event from the relay(s) specified in the URI. The **wallet service** will have sent that event to those relays earlier, and the relays will hold it as a replaceable event. |
| 23 | 23 | ||
| 24 | 3. When the **user** initiates a payment their nostr **client** create a `pay_invoice` request, encrypts it using a token from the URI, and sends it (kind 23194) to the relay(s) specified in the connection URI. The **wallet service** will be listening on those relays and will decrypt the request and then contact the **user's** wallet application to send the payment. The **wallet service** will know how to talk to the wallet application because the connection URI specified relay(s) that have access to the wallet app API. | 24 | 3. When the **user** initiates a payment their nostr **client** create a `pay_invoice` request, encrypts it using a token from the URI, and sends it (kind 23194) to the relay(s) specified in the connection URI. The **wallet service** will be listening on those relays and will decrypt the request and then contact the **user's** wallet application to send the payment. The **wallet service** will know how to talk to the wallet application because the connection URI specified relay(s) that have access to the wallet app API. |
| 25 | 25 | ||
| 26 | 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. | 26 | 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. |
| 27 | |||
| 28 | 5. The **wallet service** may send encrypted notifications (kind 23196) of wallet events (such as a received payment) to the **client**. | ||
| 27 | 29 | ||
| 28 | ## Events | 30 | ## Events |
| 29 | 31 | ||
| 30 | There are three event kinds: | 32 | There are four event kinds: |
| 31 | - `NIP-47 info event`: 13194 | 33 | - `NIP-47 info event`: 13194 |
| 32 | - `NIP-47 request`: 23194 | 34 | - `NIP-47 request`: 23194 |
| 33 | - `NIP-47 response`: 23195 | 35 | - `NIP-47 response`: 23195 |
| 36 | - `NIP-47 notification event`: 23196 | ||
| 37 | |||
| 38 | ### Info Event | ||
| 39 | |||
| 40 | The info event should be a replaceable event that is published by the **wallet service** on the relay to indicate which capabilities it supports. | ||
| 41 | |||
| 42 | The content should be a plaintext string with the supported capabilities space-separated, eg. `pay_invoice get_balance notifications`. | ||
| 34 | 43 | ||
| 35 | The info event should be a replaceable event that is published by the **wallet service** on the relay to indicate which commands it supports. The content should be | 44 | 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`. |
| 36 | a plaintext string with the supported commands, space-separated, eg. `pay_invoice get_balance`. Only the `pay_invoice` command is described in this NIP, but other commands might be defined in different NIPs. | 45 | |
| 46 | ### Request and Response Events | ||
| 37 | 47 | ||
| 38 | 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 **user** if this is a response. The response event SHOULD contain an `e` tag with the id of the request event it is responding to. | 48 | 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 **user** if this is a response. The response event SHOULD contain an `e` tag with the id of the request event it is responding to. |
| 39 | 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. | 49 | 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,6 +78,22 @@ The `result_type` field MUST contain the name of the method that this event is r | |||
| 68 | The `error` field MUST contain a `message` field with a human readable error message and a `code` field with the error code if the command was not successful. | 78 | The `error` field MUST contain a `message` field with a human readable error message and a `code` field with the error code if the command was not successful. |
| 69 | If the command was successful, the `error` field must be null. | 79 | If the command was successful, the `error` field must be null. |
| 70 | 80 | ||
| 81 | ### Notification Events | ||
| 82 | |||
| 83 | The notification event SHOULD contain one `p` tag, the public key of the **user**. | ||
| 84 | |||
| 85 | The content of notifications is encrypted with [NIP04](https://github.com/nostr-protocol/nips/blob/master/04.md), and is a JSON-RPCish object with a semi-fixed structure: | ||
| 86 | |||
| 87 | ```jsonc | ||
| 88 | { | ||
| 89 | "notification_type": "payment_received", //indicates the structure of the notification field | ||
| 90 | "notification": { | ||
| 91 | "payment_hash": "0123456789abcdef..." // notification-related data | ||
| 92 | } | ||
| 93 | } | ||
| 94 | ``` | ||
| 95 | |||
| 96 | |||
| 71 | ### Error codes | 97 | ### Error codes |
| 72 | - `RATE_LIMITED`: The client is sending commands too fast. It should retry in a few seconds. | 98 | - `RATE_LIMITED`: The client is sending commands too fast. It should retry in a few seconds. |
| 73 | - `NOT_IMPLEMENTED`: The command is not known or is intentionally not implemented. | 99 | - `NOT_IMPLEMENTED`: The command is not known or is intentionally not implemented. |
| @@ -120,7 +146,8 @@ Response: | |||
| 120 | { | 146 | { |
| 121 | "result_type": "pay_invoice", | 147 | "result_type": "pay_invoice", |
| 122 | "result": { | 148 | "result": { |
| 123 | "preimage": "0123456789abcdef..." // preimage of the payment | 149 | "preimage": "0123456789abcdef...", // preimage of the payment |
| 150 | "fees_paid": 123, // value in msats, optional | ||
| 124 | } | 151 | } |
| 125 | } | 152 | } |
| 126 | ``` | 153 | ``` |
| @@ -155,7 +182,8 @@ payment hash of the invoice should be used. | |||
| 155 | { | 182 | { |
| 156 | "result_type": "multi_pay_invoice", | 183 | "result_type": "multi_pay_invoice", |
| 157 | "result": { | 184 | "result": { |
| 158 | "preimage": "0123456789abcdef..." // preimage of the payment | 185 | "preimage": "0123456789abcdef...", // preimage of the payment |
| 186 | "fees_paid": 123, // value in msats, optional | ||
| 159 | } | 187 | } |
| 160 | } | 188 | } |
| 161 | ``` | 189 | ``` |
| @@ -189,6 +217,7 @@ Response: | |||
| 189 | "result_type": "pay_keysend", | 217 | "result_type": "pay_keysend", |
| 190 | "result": { | 218 | "result": { |
| 191 | "preimage": "0123456789abcdef...", // preimage of the payment | 219 | "preimage": "0123456789abcdef...", // preimage of the payment |
| 220 | "fees_paid": 123, // value in msats, optional | ||
| 192 | } | 221 | } |
| 193 | } | 222 | } |
| 194 | ``` | 223 | ``` |
| @@ -225,7 +254,8 @@ pubkey should be used. | |||
| 225 | { | 254 | { |
| 226 | "result_type": "multi_pay_keysend", | 255 | "result_type": "multi_pay_keysend", |
| 227 | "result": { | 256 | "result": { |
| 228 | "preimage": "0123456789abcdef..." // preimage of the payment | 257 | "preimage": "0123456789abcdef...", // preimage of the payment |
| 258 | "fees_paid": 123, // value in msats, optional | ||
| 229 | } | 259 | } |
| 230 | } | 260 | } |
| 231 | ``` | 261 | ``` |
| @@ -394,6 +424,59 @@ Response: | |||
| 394 | "block_height": 1, | 424 | "block_height": 1, |
| 395 | "block_hash": "hex string", | 425 | "block_hash": "hex string", |
| 396 | "methods": ["pay_invoice", "get_balance", "make_invoice", "lookup_invoice", "list_transactions", "get_info"], // list of supported methods for this connection | 426 | "methods": ["pay_invoice", "get_balance", "make_invoice", "lookup_invoice", "list_transactions", "get_info"], // list of supported methods for this connection |
| 427 | "notifications": ["payment_received", "payment_sent"], // list of supported notifications for this connection, optional. | ||
| 428 | } | ||
| 429 | } | ||
| 430 | ``` | ||
| 431 | |||
| 432 | ## Notifications | ||
| 433 | |||
| 434 | ### `payment_received` | ||
| 435 | |||
| 436 | Description: A payment was successfully received by the wallet. | ||
| 437 | |||
| 438 | Notification: | ||
| 439 | ```jsonc | ||
| 440 | { | ||
| 441 | "notification_type": "payment_received", | ||
| 442 | "notification": { | ||
| 443 | "type": "incoming", | ||
| 444 | "invoice": "string", // encoded invoice | ||
| 445 | "description": "string", // invoice's description, optional | ||
| 446 | "description_hash": "string", // invoice's description hash, optional | ||
| 447 | "preimage": "string", // payment's preimage | ||
| 448 | "payment_hash": "string", // Payment hash for the payment | ||
| 449 | "amount": 123, // value in msats | ||
| 450 | "fees_paid": 123, // value in msats | ||
| 451 | "created_at": unixtimestamp, // invoice/payment creation time | ||
| 452 | "expires_at": unixtimestamp, // invoice expiration time, optional if not applicable | ||
| 453 | "settled_at": unixtimestamp, // invoice/payment settlement time | ||
| 454 | "metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc. | ||
| 455 | } | ||
| 456 | } | ||
| 457 | ``` | ||
| 458 | |||
| 459 | ### `payment_sent` | ||
| 460 | |||
| 461 | Description: A payment was successfully sent by the wallet. | ||
| 462 | |||
| 463 | Notification: | ||
| 464 | ```jsonc | ||
| 465 | { | ||
| 466 | "notification_type": "payment_sent", | ||
| 467 | "notification": { | ||
| 468 | "type": "outgoing", | ||
| 469 | "invoice": "string", // encoded invoice | ||
| 470 | "description": "string", // invoice's description, optional | ||
| 471 | "description_hash": "string", // invoice's description hash, optional | ||
| 472 | "preimage": "string", // payment's preimage | ||
| 473 | "payment_hash": "string", // Payment hash for the payment | ||
| 474 | "amount": 123, // value in msats | ||
| 475 | "fees_paid": 123, // value in msats | ||
| 476 | "created_at": unixtimestamp, // invoice/payment creation time | ||
| 477 | "expires_at": unixtimestamp, // invoice expiration time, optional if not applicable | ||
| 478 | "settled_at": unixtimestamp, // invoice/payment settlement time | ||
| 479 | "metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc. | ||
| 397 | } | 480 | } |
| 398 | } | 481 | } |
| 399 | ``` | 482 | ``` |
| @@ -407,3 +490,24 @@ Response: | |||
| 407 | 490 | ||
| 408 | ## Using a dedicated relay | 491 | ## Using a dedicated relay |
| 409 | 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. | 492 | 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. |
| 493 | |||
| 494 | ## Appendix | ||
| 495 | |||
| 496 | ### Example NIP-47 info event | ||
| 497 | |||
| 498 | ```jsonc | ||
| 499 | { | ||
| 500 | "id": "df467db0a9f9ec77ffe6f561811714ccaa2e26051c20f58f33c3d66d6c2b4d1c", | ||
| 501 | "pubkey": "c04ccd5c82fc1ea3499b9c6a5c0a7ab627fbe00a0116110d4c750faeaecba1e2", | ||
| 502 | "created_at": 1713883677, | ||
| 503 | "kind": 13194, | ||
| 504 | "tags": [ | ||
| 505 | [ | ||
| 506 | "notifications", | ||
| 507 | "payment_received payment_sent" | ||
| 508 | ] | ||
| 509 | ], | ||
| 510 | "content": "pay_invoice pay_keysend get_balance get_info make_invoice lookup_invoice list_transactions multi_pay_invoice multi_pay_keysend sign_message notifications", | ||
| 511 | "sig": "31f57b369459b5306a5353aa9e03be7fbde169bc881c3233625605dd12f53548179def16b9fe1137e6465d7e4d5bb27ce81fd6e75908c46b06269f4233c845d8" | ||
| 512 | } | ||
| 513 | ``` | ||