From c53877571f96eb423661fc23c620d629d37b8f19 Mon Sep 17 00:00:00 2001 From: frnandu Date: Sat, 1 Aug 2026 16:53:23 +0200 Subject: [PATCH] NIP-47: simplify core spec and introduce extensions (#2419) Co-authored-by: fmar --- 47.md | 351 ++++------------------------------------------------------ 1 file changed, 19 insertions(+), 332 deletions(-) diff --git a/47.md b/47.md index 708fb469..a00445f5 100644 --- a/47.md +++ b/47.md @@ -10,6 +10,8 @@ Nostr Wallet Connect (NWC) 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 defines the core Nostr Wallet Connect protocol. More granular authorization models and extensions for advanced wallet functionality may be defined in separate specifications in the NWC repository at `https://github.com/nostr-wallet-connect/nwc`. + ## Terms * **client**: Nostr app on any platform that wants to interact with a lightning wallet. @@ -28,24 +30,25 @@ Fundamentally NWC is communication between a **client** and **wallet service** b 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 23197) of wallet events (such as a received payment) to the **client**. + 5. Optional extension specifications may define additional wallet-to-client events. ## Events -There are four event kinds: +There are three core event kinds: - `NIP-47 info event`: 13194 - `NIP-47 request`: 23194 - `NIP-47 response`: 23195 -- `NIP-47 notification event`: 23197 (23196 for backwards compatibility with NIP-04) ### Info Event 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`. +The content should be a plaintext string with the supported capabilities space-separated, eg. `pay_invoice get_balance`. -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`. +Any additional methods supported through optional NWC extensions advertised in the `extensions` tag SHOULD also be included in the content of the info event. + +If the **wallet service** supports optional NWC extensions, the info event SHOULD contain an `extensions` tag with the supported extension identifiers space-separated, eg. `02 03 04`. It should also contain supported encryption modes as described in the [Encryption](#encryption) section. For example: @@ -54,10 +57,10 @@ It should also contain supported encryption modes as described in the [Encryptio "kind": 13194, "tags": [ ["encryption", "nip44_v2 nip04"], // List of supported encryption schemes as described in the Encryption section. - ["notifications", "payment_received payment_sent"] + ["extensions", "02 03 04"] // ... ], - "content": "pay_invoice get_balance make_invoice lookup_invoice list_transactions get_info notifications", + "content": "pay_invoice get_balance make_invoice lookup_invoice get_info", // ... } ``` @@ -119,21 +122,6 @@ 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. If the command was successful, the `error` field must be null. -### Notification Events - -The notification event is a kind 23197 event SHOULD contain one `p` tag, the public key of the **client**. - -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: - -```yaml -{ - "notification_type": "payment_received", //indicates the structure of the notification field - "notification": { - "payment_hash": "0123456789abcdef..." // notification-related data - } -} -``` - ### 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. @@ -205,40 +193,6 @@ Response: Errors: - `PAYMENT_FAILED`: The payment failed. This may be due to a timeout, exhausting all routes, insufficient capacity or similar. -### `pay_keysend` - -Request: -```yaml -{ - "method": "pay_keysend", - "params": { - "amount": 123, // invoice amount in msats, required - "pubkey": "03...", // payee pubkey, required - "preimage": "0123456789abcdef...", // preimage of the payment, optional - "tlv_records": [ // tlv records, optional - { - "type": 5482373484, // tlv type - "value": "0123456789abcdef" // hex encoded tlv value - } - ] - } -} -``` - -Response: -```yaml -{ - "result_type": "pay_keysend", - "result": { - "preimage": "0123456789abcdef...", // preimage of the payment - "fees_paid": 123, // value in msats, optional - } -} -``` - -Errors: -- `PAYMENT_FAILED`: The payment failed. This may be due to a timeout, exhausting all routes, insufficient capacity or similar. - ### `make_invoice` Request: @@ -314,54 +268,6 @@ Response: Errors: - `NOT_FOUND`: The invoice could not be found by the given parameters. -### `list_transactions` - -Lists invoices and payments. If `type` is not specified, both invoices and payments are returned. -The `from` and `until` parameters are timestamps in seconds since epoch. If `from` is not specified, it defaults to 0. -If `until` is not specified, it defaults to the current time. Transactions are returned in descending order of creation -time. - -Request: -```yaml -{ - "method": "list_transactions", - "params": { - "from": 1693876973, // starting timestamp in seconds since epoch (inclusive), optional - "until": 1703225078, // ending timestamp in seconds since epoch (inclusive), optional - "limit": 10, // maximum number of invoices to return, optional - "offset": 0, // offset of the first invoice to return, optional - "unpaid": true, // include unpaid invoices, optional, default false - "type": "incoming", // "incoming" for invoices, "outgoing" for payments, undefined for both - } -} -``` - -Response: -```yaml -{ - "result_type": "list_transactions", - "result": { - "transactions": [ - { - "type": "incoming", // "incoming" for invoices, "outgoing" for payments - "state": "pending", // can be "pending", "settled", "accepted" (for hold invoices), "expired" (for invoices) or "failed" (for payments), optional - "invoice": "string", // encoded invoice, optional - "description": "string", // invoice's description, optional - "description_hash": "string", // invoice's description hash, optional - "preimage": "string", // payment's preimage, optional if unpaid - "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, optional if unpaid - "metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc. - } - ], - }, -} -``` - ### `get_balance` Request: @@ -403,172 +309,21 @@ Response: "network": "string", // mainnet, testnet, signet, or regtest "block_height": 1, "block_hash": "hex string", - "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. + "methods": ["pay_invoice", "get_balance", "make_invoice", "lookup_invoice", "get_info"], // list of supported methods for this connection + "extensions": ["02", "03", "04", "06", "07", "08"], // list of supported optional NWC extension specs for this connection, optional } } ``` -### `make_hold_invoice` +## Extensions -Creates a hold invoice using a pre-generated preimage. +This NIP defines the core protocol and a small common command set. Additional methods, metadata conventions, pairing flows, and more granular authorization behavior MAY be defined by optional NWC extension specifications. -Request: -```yaml -{ - "method": "make_hold_invoice", - "params": { - "amount": 123, // value in msats - "description": "string", // invoice's description, optional - "description_hash": "string", // invoice's description hash, optional - "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`) - "payment_hash": "string" // Payment hash for the payment generated from the preimage - "min_cltv_expiry_delta": 144 // The minimum CLTV delta to use for the final hop, optional - } -} -``` +Additional optional NWC specifications are maintained in the dedicated NWC repository: -Response: -```yaml -{ - "result_type": "make_hold_invoice", - "result": { - "type": "incoming", // "incoming" for invoices, "outgoing" for payments - "invoice": "string", // encoded invoice, optional - "description": "string", // invoice's description, optional - "description_hash": "string", // invoice's description hash, optional - "payment_hash": "string", // Payment hash for the payment - "amount": 123, // value in msats - "created_at": unixtimestamp, // invoice/payment creation time - "expires_at": unixtimestamp, // invoice expiration time, optional if not applicable - "metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc. - } -} -``` +- `https://github.com/nostr-wallet-connect/nwc` -### `cancel_hold_invoice` - -Cancels a hold invoice using the payment hash - -Request: -```yaml -{ - "method": "cancel_hold_invoice", - "params": { - "payment_hash": "string" // Payment hash for the payment generated from the preimage - } -} -``` - -Response: -```yaml -{ - "result_type": "cancel_hold_invoice", - "result": {} -} -``` - -### `settle_hold_invoice` - -Settles a hold invoice using the preimage - - -Request: -```yaml -{ - "method": "settle_hold_invoice", - "params": { - "preimage": "string" // preimage for the payment - } -} -``` - -Response: -```yaml -{ - "result_type": "settle_hold_invoice", - "result": {} -} -``` - - -## Notifications - -### `payment_received` - -Description: A payment was successfully received by the wallet. - -Notification: -```yaml -{ - "notification_type": "payment_received", - "notification": { - "type": "incoming", - "state": "settled", // optional - "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: -```yaml -{ - "notification_type": "payment_sent", - "notification": { - "type": "outgoing", - "state": "settled", // optional - "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. - } -} -``` - -### `hold_invoice_accepted` - -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. - -Notification: -```yaml -{ - "notification_type": "hold_invoice_accepted", - "notification": { - "type": "incoming", - "state": "accepted", // optional - "invoice": "string", // encoded invoice - "description": "string", // invoice's description, optional - "description_hash": "string", // invoice's description hash, optional - "payment_hash": "string", // Payment hash for the payment - "amount": 123, // value in msats - "created_at": unixtimestamp, // invoice/payment creation time - "expires_at": unixtimestamp, // invoice expiration time - "settle_deadline": blocknumber, // invoice can only be safely settled or canceled before this block number. - "metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc. - } -} -``` +That repository reserves `01.md` for the NWC core specification corresponding to this simplified NIP-47, and defines optional features in separate specs starting with `02.md`. ## Example pay invoice flow @@ -619,47 +374,9 @@ For example, if the client application supports nip44, the request event might l 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. -### Notification events - -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. - -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. - ## 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. -## Metadata -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. - -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. - -Here are some properties that are recognized by some NWC clients: - -```yaml -{ - "comment": "string", // LUD-12 comment - "payer_data": { - "email": "string", - "name": "string", - "pubkey": "string", - }, // LUD-18 payer data - "recipient_data": { - "identifier": "string" - }, // similar to LUD-18 payer data, but to record recipient data e.g. the lightning address of the recipient - "nostr": { - "pubkey": "string", - "tags": [], - // ... rest of zap request event - }, // NIP-57 Zap Request event (kind 9734) - "tlv_records": [ - { - "type": 5482373484, // tlv type - "value": "0123456789abcdef" // hex encoded tlv value - } - ] // keysend TLV records (e.g. for podcasting 2.0 boostagrams) -} & Record; -``` - ## Appendix ### Example NIP-47 info event @@ -672,39 +389,9 @@ Here are some properties that are recognized by some NWC clients: "kind": 13194, "tags": [ [ "encryption", "nip44_v2 nip04" ], - [ - "notifications", - "payment_received payment_sent" - ] + [ "extensions", "02 03 04 06 07 08" ] ], - "content": "pay_invoice pay_keysend get_balance get_info make_invoice lookup_invoice list_transactions multi_pay_invoice multi_pay_keysend sign_message notifications", + "content": "pay_invoice get_balance get_info make_invoice lookup_invoice", "sig": "31f57b369459b5306a5353aa9e03be7fbde169bc881c3233625605dd12f53548179def16b9fe1137e6465d7e4d5bb27ce81fd6e75908c46b06269f4233c845d8" } ``` - -### Example Hold Invoice Support Flow - -1. Client generates a 32-byte hex-encoded preimage. -2. Computes SHA-256 to derive payment hash. -3. Sends `make_hold_invoice` with payment hash and desired parameters. -4. Waits for `hold_invoice_accepted` notification. -5. Upon receiving notification, either: - - * Calls `settle_hold_invoice` with the original preimage to release funds, or - * Calls `cancel_hold_invoice` with payment hash to abort. - -### Deep-links - -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. - -`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. - -All URI parameters, MUST be URI-encoded. - -URI parameters: - * `appicon` -- URL to an icon of the client that wants to create a connection. - * `appname` -- Name of the client that wants to create a connection. - * `callback` -- URI schema the wallet should open with the connection string - -Once a connection has been created by the wallet, it should be returned to the client by opening the callback with the following parameters - * `value` -- NWC pairing code (e.g. `nostr+walletconnect://...`)