NIP-47 ====== Nostr Wallet Connect -------------------- `draft` `optional` `author:kiwiidb` `author:bumi` `author:semisol` `author:vitorpamplona` ## 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. ## Terms * **client**: Nostr app on any platform that wants to pay Lightning invoices * **wallet service**: Nostr app that typically runs on an always-on computer (eg. in the cloud or on a Raspberry Pi). ## Events There are two event kinds: - `NIP-47 request`: 23194 - `NIP-47 response`: 23195 Both the request and response events SHOULD only 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 content is encrypted with [NIP04](https://github.com/nostr-protocol/nips/blob/master/04.md), and is a JSON object. The content depends on the kind. Request: ```jsonc { "cmd": "pay_invoice", // command, string "data": { // data, object "invoice": "lnbc50n1..." // command-related data } } ``` Response: ```jsonc { "status": "ok", // status, "ok" | "error" "event": "0123456789abcdef...", // event the command is in response to, string "data": { // response data "preimage": "0123456789abcdef..." // command-related data } } ``` The data field SHOULD contain a `message` field with a human readable error message and a `code` field with the error code if the status is `error`. ### Error codes - `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. - `INSUFFICIENT_BALANCE`: The wallet does not have enough funds to cover a fee reserve or the payment amount. - `QUOTA_EXCEEDED`: The wallet has exceeded - `RESTRICTED`: This public key is not allowed to do this operation. - `UNAUTHORIZED`: This public key has no wallet connected. - `INTERNAL`: An internal error. - `OTHER`: Other error. ## Nostr Wallet Connect URI **client** discovers **wallet service** by scanning a QR code, handling a deeplink or pasting in a URI. The **wallet service** generates this connection URI with protocol `nostr+walletconnect:` and base path it's hex-encoded `pubkey` with the following query string parameters: - `relay` Required. URL of the relay where the **wallet service** is connected and will be listening for events. May be more than one. - `secret` Required. 32-byte randomly generated hex encoded string. The **client** should use this to sign events when communicating with the **wallet service**. - Authorization does not require passing keys back and forth. - The user can have different keys for different applications. Keys can be revoked and created at will and have arbitrary constraints (eg. budgets). - The key is harder to leak since it is not shown to the user and backed up. - It improves privacy because the user's main key would not be linked to their payments. The **client** should then store this connection and use it when the user wants to perform actions like paying an invoice. Due to this NIP using ephemeral events, it is recommended to pick relays that do not close connections on inactivity to not drop events. ### Example connection string ```sh nostr+walletconnect:b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4?relay=wss%3A%2F%2Frelay.damus.io&secret=71a8c14c1407c113601079c4302dab36460f0ccd0ad506f1f2dc73b5100e4f3c ``` ## Commands ### `get_info` Description: Get information about the wallet and service. Request: Empty object. Response: ```jsonc { "balance": 100000, // balance in msat, int // this should be capped at the quota allowed for this client // to not report unspendable balance. "implemented_commands": ["get_info", "pay_invoice"] // commands supported, string array // extensions can be specified via command+extension: // get_info+node_info } ``` ### `create_invoice` Description: Requests creation of an invoice. Request: ```jsonc { "amount": 1000, // amount in msat, int // must be a whole number of sats unless // create_invoice+msat_amount is implemented. "description": "memo" // a description, string, optional } ``` Response: ```jsonc { "invoice": "lnbc50n1..." // BOLT11 invoice, string } ``` ### `pay_invoice` Description: Requests payment of an invoice. Request: ```jsonc { "invoice": "lnbc50n1..." // BOLT11 invoice, string } ``` Response: ```jsonc { "preimage": "0123456789abcdef..." // preimage after payment, string } ``` ## Example pay invoice flow 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. 1. **client** sends an event to with **wallet service** service with kind `23194`. The content is a `pay_invoice` request. The private key is the secret from the connection string above. 2. **wallet service** verifies that the author's key is authorized to perform the payment, decrypts the payload and sends the payment. 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. ## 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.