mirror of
https://github.com/nostr-protocol/nips.git
synced 2026-08-12 00:47:39 +00:00
NIP-47: simplify core spec and introduce extensions (#2419)
Co-authored-by: fmar <fmar@fmar>
This commit is contained in:
@@ -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<string, unknown>;
|
||||
```
|
||||
|
||||
## 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://...`)
|
||||
|
||||
Reference in New Issue
Block a user