Merge pull request #1164 from rolznz/feat/nip-47-notifications

NIP-47 notifications
This commit is contained in:
Vitor Pamplona 2024-11-20 15:15:10 -05:00 committed by GitHub
commit 84aeb10d39
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

126
47.md
View File

@ -8,32 +8,42 @@ Nostr Wallet Connect
## Rationale ## Rationale
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. 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.
## Terms ## Terms
* **client**: Nostr app on any platform that wants to pay Lightning invoices. * **client**: Nostr app on any platform that wants to interact with a lightning wallet.
* **user**: The person using the **client**, and want's to connect their wallet app to their **client**. * **user**: The person using the **client**, and wants to connect their wallet to their **client**.
* **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. * **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.
## Theory of Operation ## Theory of Operation
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. 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.
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. 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.
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. 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.
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. 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.
5. The **wallet service** may send encrypted notifications (kind 23196) of wallet events (such as a received payment) to the **client**.
## Events ## Events
There are three event kinds: There are four event kinds:
- `NIP-47 info event`: 13194 - `NIP-47 info event`: 13194
- `NIP-47 request`: 23194 - `NIP-47 request`: 23194
- `NIP-47 response`: 23195 - `NIP-47 response`: 23195
- `NIP-47 notification event`: 23196
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 ### Info Event
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.
The info event should be a replaceable event that is published by the **wallet service** on the relay to indicate which capabilities it supports.
The content should be a plaintext string with the supported capabilities space-separated, eg. `pay_invoice get_balance notifications`.
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`.
### Request and Response Events
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. 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.
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. 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
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. 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.
If the command was successful, the `error` field must be null. If the command was successful, the `error` field must be null.
### Notification Events
The notification event SHOULD contain one `p` tag, the public key of the **user**.
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:
```jsonc
{
"notification_type": "payment_received", //indicates the structure of the notification field
"notification": {
"payment_hash": "0123456789abcdef..." // notification-related data
}
}
```
### Error codes ### Error codes
- `RATE_LIMITED`: The client is sending commands too fast. It should retry in a few seconds. - `RATE_LIMITED`: The client is sending commands too fast. It should retry in a few seconds.
- `NOT_IMPLEMENTED`: The command is not known or is intentionally not implemented. - `NOT_IMPLEMENTED`: The command is not known or is intentionally not implemented.
@ -120,7 +146,8 @@ Response:
{ {
"result_type": "pay_invoice", "result_type": "pay_invoice",
"result": { "result": {
"preimage": "0123456789abcdef..." // preimage of the payment "preimage": "0123456789abcdef...", // preimage of the payment
"fees_paid": 123, // value in msats, optional
} }
} }
``` ```
@ -155,7 +182,8 @@ payment hash of the invoice should be used.
{ {
"result_type": "multi_pay_invoice", "result_type": "multi_pay_invoice",
"result": { "result": {
"preimage": "0123456789abcdef..." // preimage of the payment "preimage": "0123456789abcdef...", // preimage of the payment
"fees_paid": 123, // value in msats, optional
} }
} }
``` ```
@ -189,6 +217,7 @@ Response:
"result_type": "pay_keysend", "result_type": "pay_keysend",
"result": { "result": {
"preimage": "0123456789abcdef...", // preimage of the payment "preimage": "0123456789abcdef...", // preimage of the payment
"fees_paid": 123, // value in msats, optional
} }
} }
``` ```
@ -225,7 +254,8 @@ pubkey should be used.
{ {
"result_type": "multi_pay_keysend", "result_type": "multi_pay_keysend",
"result": { "result": {
"preimage": "0123456789abcdef..." // preimage of the payment "preimage": "0123456789abcdef...", // preimage of the payment
"fees_paid": 123, // value in msats, optional
} }
} }
``` ```
@ -394,6 +424,59 @@ Response:
"block_height": 1, "block_height": 1,
"block_hash": "hex string", "block_hash": "hex string",
"methods": ["pay_invoice", "get_balance", "make_invoice", "lookup_invoice", "list_transactions", "get_info"], // list of supported methods for this connection "methods": ["pay_invoice", "get_balance", "make_invoice", "lookup_invoice", "list_transactions", "get_info"], // list of supported methods for this connection
"notifications": ["payment_received", "payment_sent"], // list of supported notifications for this connection, optional.
}
}
```
## Notifications
### `payment_received`
Description: A payment was successfully received by the wallet.
Notification:
```jsonc
{
"notification_type": "payment_received",
"notification": {
"type": "incoming",
"invoice": "string", // encoded invoice
"description": "string", // invoice's description, optional
"description_hash": "string", // invoice's description hash, optional
"preimage": "string", // payment's preimage
"payment_hash": "string", // Payment hash for the payment
"amount": 123, // value in msats
"fees_paid": 123, // value in msats
"created_at": unixtimestamp, // invoice/payment creation time
"expires_at": unixtimestamp, // invoice expiration time, optional if not applicable
"settled_at": unixtimestamp, // invoice/payment settlement time
"metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc.
}
}
```
### `payment_sent`
Description: A payment was successfully sent by the wallet.
Notification:
```jsonc
{
"notification_type": "payment_sent",
"notification": {
"type": "outgoing",
"invoice": "string", // encoded invoice
"description": "string", // invoice's description, optional
"description_hash": "string", // invoice's description hash, optional
"preimage": "string", // payment's preimage
"payment_hash": "string", // Payment hash for the payment
"amount": 123, // value in msats
"fees_paid": 123, // value in msats
"created_at": unixtimestamp, // invoice/payment creation time
"expires_at": unixtimestamp, // invoice expiration time, optional if not applicable
"settled_at": unixtimestamp, // invoice/payment settlement time
"metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc.
} }
} }
``` ```
@ -407,3 +490,24 @@ Response:
## Using a dedicated relay ## Using a dedicated relay
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. 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.
## Appendix
### Example NIP-47 info event
```jsonc
{
"id": "df467db0a9f9ec77ffe6f561811714ccaa2e26051c20f58f33c3d66d6c2b4d1c",
"pubkey": "c04ccd5c82fc1ea3499b9c6a5c0a7ab627fbe00a0116110d4c750faeaecba1e2",
"created_at": 1713883677,
"kind": 13194,
"tags": [
[
"notifications",
"payment_received payment_sent"
]
],
"content": "pay_invoice pay_keysend get_balance get_info make_invoice lookup_invoice list_transactions multi_pay_invoice multi_pay_keysend sign_message notifications",
"sig": "31f57b369459b5306a5353aa9e03be7fbde169bc881c3233625605dd12f53548179def16b9fe1137e6465d7e4d5bb27ce81fd6e75908c46b06269f4233c845d8"
}
```