diff options
Diffstat (limited to '47.md')
| -rw-r--r-- | 47.md | 288 |
1 files changed, 281 insertions, 7 deletions
| @@ -17,7 +17,7 @@ This NIP describes a way for clients to access a remote Lightning wallet through | |||
| 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 which 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 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. |
| 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** 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. |
| 23 | 23 | ||
| @@ -36,8 +36,9 @@ The info event should be a replaceable event that is published by the **wallet s | |||
| 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. | 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. |
| 37 | 37 | ||
| 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. | 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. |
| 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. | ||
| 39 | 40 | ||
| 40 | The content of requests and responses 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: | 41 | The content of requests and responses is encrypted with [NIP04](04.md), and is a JSON-RPCish object with a semi-fixed structure: |
| 41 | 42 | ||
| 42 | Request: | 43 | Request: |
| 43 | ```jsonc | 44 | ```jsonc |
| @@ -80,7 +81,7 @@ If the command was successful, the `error` field must be null. | |||
| 80 | ## Nostr Wallet Connect URI | 81 | ## Nostr Wallet Connect URI |
| 81 | **client** discovers **wallet service** by scanning a QR code, handling a deeplink or pasting in a URI. | 82 | **client** discovers **wallet service** by scanning a QR code, handling a deeplink or pasting in a URI. |
| 82 | 83 | ||
| 83 | 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: | 84 | 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: |
| 84 | 85 | ||
| 85 | - `relay` Required. URL of the relay where the **wallet service** is connected and will be listening for events. May be more than one. | 86 | - `relay` Required. URL of the relay where the **wallet service** is connected and will be listening for events. May be more than one. |
| 86 | - `secret` Required. 32-byte randomly generated hex encoded string. The **client** MUST use this to sign events and encrypt payloads when communicating with the **wallet service**. | 87 | - `secret` Required. 32-byte randomly generated hex encoded string. The **client** MUST use this to sign events and encrypt payloads when communicating with the **wallet service**. |
| @@ -94,7 +95,7 @@ The **client** should then store this connection and use it when the user wants | |||
| 94 | 95 | ||
| 95 | ### Example connection string | 96 | ### Example connection string |
| 96 | ```sh | 97 | ```sh |
| 97 | nostr+walletconnect:b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4?relay=wss%3A%2F%2Frelay.damus.io&secret=71a8c14c1407c113601079c4302dab36460f0ccd0ad506f1f2dc73b5100e4f3c | 98 | nostr+walletconnect://b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4?relay=wss%3A%2F%2Frelay.damus.io&secret=71a8c14c1407c113601079c4302dab36460f0ccd0ad506f1f2dc73b5100e4f3c |
| 98 | ``` | 99 | ``` |
| 99 | 100 | ||
| 100 | ## Commands | 101 | ## Commands |
| @@ -108,7 +109,8 @@ Request: | |||
| 108 | { | 109 | { |
| 109 | "method": "pay_invoice", | 110 | "method": "pay_invoice", |
| 110 | "params": { | 111 | "params": { |
| 111 | "invoice": "lnbc50n1..." // bolt11 invoice | 112 | "invoice": "lnbc50n1...", // bolt11 invoice |
| 113 | "amount": 123, // invoice amount in msats, optional | ||
| 112 | } | 114 | } |
| 113 | } | 115 | } |
| 114 | ``` | 116 | ``` |
| @@ -117,7 +119,7 @@ Response: | |||
| 117 | ```jsonc | 119 | ```jsonc |
| 118 | { | 120 | { |
| 119 | "result_type": "pay_invoice", | 121 | "result_type": "pay_invoice", |
| 120 | "result": { | 122 | "result": { |
| 121 | "preimage": "0123456789abcdef..." // preimage of the payment | 123 | "preimage": "0123456789abcdef..." // preimage of the payment |
| 122 | } | 124 | } |
| 123 | } | 125 | } |
| @@ -126,9 +128,281 @@ Response: | |||
| 126 | Errors: | 128 | Errors: |
| 127 | - `PAYMENT_FAILED`: The payment failed. This may be due to a timeout, exhausting all routes, insufficient capacity or similar. | 129 | - `PAYMENT_FAILED`: The payment failed. This may be due to a timeout, exhausting all routes, insufficient capacity or similar. |
| 128 | 130 | ||
| 131 | ### `multi_pay_invoice` | ||
| 132 | |||
| 133 | Description: Requests payment of multiple invoices. | ||
| 134 | |||
| 135 | Request: | ||
| 136 | ```jsonc | ||
| 137 | { | ||
| 138 | "method": "multi_pay_invoice", | ||
| 139 | "params": { | ||
| 140 | "invoices": [ | ||
| 141 | {"id":"4da52c32a1", "invoice": "lnbc1...", "amount": 123}, // bolt11 invoice and amount in msats, amount is optional | ||
| 142 | {"id":"3da52c32a1", "invoice": "lnbc50n1..."}, | ||
| 143 | ], | ||
| 144 | } | ||
| 145 | } | ||
| 146 | ``` | ||
| 147 | |||
| 148 | Response: | ||
| 149 | |||
| 150 | For every invoice in the request, a separate response event is sent. To differentiate between the responses, each | ||
| 151 | response event contains an `d` tag with the id of the invoice it is responding to, if no id was given, then the | ||
| 152 | payment hash of the invoice should be used. | ||
| 153 | |||
| 154 | ```jsonc | ||
| 155 | { | ||
| 156 | "result_type": "multi_pay_invoice", | ||
| 157 | "result": { | ||
| 158 | "preimage": "0123456789abcdef..." // preimage of the payment | ||
| 159 | } | ||
| 160 | } | ||
| 161 | ``` | ||
| 162 | |||
| 163 | Errors: | ||
| 164 | - `PAYMENT_FAILED`: The payment failed. This may be due to a timeout, exhausting all routes, insufficient capacity or similar. | ||
| 165 | |||
| 166 | ### `pay_keysend` | ||
| 167 | |||
| 168 | Request: | ||
| 169 | ```jsonc | ||
| 170 | { | ||
| 171 | "method": "pay_keysend", | ||
| 172 | "params": { | ||
| 173 | "amount": 123, // invoice amount in msats, required | ||
| 174 | "pubkey": "03...", // payee pubkey, required | ||
| 175 | "preimage": "0123456789abcdef...", // preimage of the payment, optional | ||
| 176 | "tlv_records: [ // tlv records, optional | ||
| 177 | { | ||
| 178 | "type": 5482373484, // tlv type | ||
| 179 | "value": "0123456789abcdef" // hex encoded tlv value | ||
| 180 | } | ||
| 181 | ] | ||
| 182 | } | ||
| 183 | } | ||
| 184 | ``` | ||
| 185 | |||
| 186 | Response: | ||
| 187 | ```jsonc | ||
| 188 | { | ||
| 189 | "result_type": "pay_keysend", | ||
| 190 | "result": { | ||
| 191 | "preimage": "0123456789abcdef...", // preimage of the payment | ||
| 192 | } | ||
| 193 | } | ||
| 194 | ``` | ||
| 195 | |||
| 196 | Errors: | ||
| 197 | - `PAYMENT_FAILED`: The payment failed. This may be due to a timeout, exhausting all routes, insufficient capacity or similar. | ||
| 198 | |||
| 199 | ### `multi_pay_keysend` | ||
| 200 | |||
| 201 | Description: Requests multiple keysend payments. | ||
| 202 | |||
| 203 | Has an array of keysends, these follow the same semantics as `pay_keysend`, just done in a batch | ||
| 204 | |||
| 205 | Request: | ||
| 206 | ```jsonc | ||
| 207 | { | ||
| 208 | "method": "multi_pay_keysend", | ||
| 209 | "params": { | ||
| 210 | "keysends": [ | ||
| 211 | {"id": "4c5b24a351", pubkey": "03...", "amount": 123}, | ||
| 212 | {"id": "3da52c32a1", "pubkey": "02...", "amount": 567, "preimage": "abc123..", "tlv_records": [{"type": 696969, "value": "77616c5f6872444873305242454d353736"}]}, | ||
| 213 | ], | ||
| 214 | } | ||
| 215 | } | ||
| 216 | ``` | ||
| 217 | |||
| 218 | Response: | ||
| 219 | |||
| 220 | For every keysend in the request, a separate response event is sent. To differentiate between the responses, each | ||
| 221 | response event contains an `d` tag with the id of the keysend it is responding to, if no id was given, then the | ||
| 222 | pubkey should be used. | ||
| 223 | |||
| 224 | ```jsonc | ||
| 225 | { | ||
| 226 | "result_type": "multi_pay_keysend", | ||
| 227 | "result": { | ||
| 228 | "preimage": "0123456789abcdef..." // preimage of the payment | ||
| 229 | } | ||
| 230 | } | ||
| 231 | ``` | ||
| 232 | |||
| 233 | Errors: | ||
| 234 | - `PAYMENT_FAILED`: The payment failed. This may be due to a timeout, exhausting all routes, insufficient capacity or similar. | ||
| 235 | |||
| 236 | ### `make_invoice` | ||
| 237 | |||
| 238 | Request: | ||
| 239 | ```jsonc | ||
| 240 | { | ||
| 241 | "method": "make_invoice", | ||
| 242 | "params": { | ||
| 243 | "amount": 123, // value in msats | ||
| 244 | "description": "string", // invoice's description, optional | ||
| 245 | "description_hash": "string", // invoice's description hash, optional | ||
| 246 | "expiry": 213 // expiry in seconds from time invoice is created, optional | ||
| 247 | } | ||
| 248 | } | ||
| 249 | ``` | ||
| 250 | |||
| 251 | Response: | ||
| 252 | ```jsonc | ||
| 253 | { | ||
| 254 | "result_type": "make_invoice", | ||
| 255 | "result": { | ||
| 256 | "type": "incoming", // "incoming" for invoices, "outgoing" for payments | ||
| 257 | "invoice": "string", // encoded invoice, optional | ||
| 258 | "description": "string", // invoice's description, optional | ||
| 259 | "description_hash": "string", // invoice's description hash, optional | ||
| 260 | "preimage": "string", // payment's preimage, optional if unpaid | ||
| 261 | "payment_hash": "string", // Payment hash for the payment | ||
| 262 | "amount": 123, // value in msats | ||
| 263 | "fees_paid": 123, // value in msats | ||
| 264 | "created_at": unixtimestamp, // invoice/payment creation time | ||
| 265 | "expires_at": unixtimestamp, // invoice expiration time, optional if not applicable | ||
| 266 | "metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc. | ||
| 267 | } | ||
| 268 | } | ||
| 269 | ``` | ||
| 270 | |||
| 271 | ### `lookup_invoice` | ||
| 272 | |||
| 273 | Request: | ||
| 274 | ```jsonc | ||
| 275 | { | ||
| 276 | "method": "lookup_invoice", | ||
| 277 | "params": { | ||
| 278 | "payment_hash": "31afdf1..", // payment hash of the invoice, one of payment_hash or invoice is required | ||
| 279 | "invoice": "lnbc50n1..." // invoice to lookup | ||
| 280 | } | ||
| 281 | } | ||
| 282 | ``` | ||
| 283 | |||
| 284 | Response: | ||
| 285 | ```jsonc | ||
| 286 | { | ||
| 287 | "result_type": "lookup_invoice", | ||
| 288 | "result": { | ||
| 289 | "type": "incoming", // "incoming" for invoices, "outgoing" for payments | ||
| 290 | "invoice": "string", // encoded invoice, optional | ||
| 291 | "description": "string", // invoice's description, optional | ||
| 292 | "description_hash": "string", // invoice's description hash, optional | ||
| 293 | "preimage": "string", // payment's preimage, optional if unpaid | ||
| 294 | "payment_hash": "string", // Payment hash for the payment | ||
| 295 | "amount": 123, // value in msats | ||
| 296 | "fees_paid": 123, // value in msats | ||
| 297 | "created_at": unixtimestamp, // invoice/payment creation time | ||
| 298 | "expires_at": unixtimestamp, // invoice expiration time, optional if not applicable | ||
| 299 | "settled_at": unixtimestamp, // invoice/payment settlement time, optional if unpaid | ||
| 300 | "metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc. | ||
| 301 | } | ||
| 302 | } | ||
| 303 | ``` | ||
| 304 | |||
| 305 | Errors: | ||
| 306 | - `NOT_FOUND`: The invoice could not be found by the given parameters. | ||
| 307 | |||
| 308 | ### `list_transactions` | ||
| 309 | |||
| 310 | Lists invoices and payments. If `type` is not specified, both invoices and payments are returned. | ||
| 311 | The `from` and `until` parameters are timestamps in seconds since epoch. If `from` is not specified, it defaults to 0. | ||
| 312 | If `until` is not specified, it defaults to the current time. Transactions are returned in descending order of creation | ||
| 313 | time. | ||
| 314 | |||
| 315 | Request: | ||
| 316 | ```jsonc | ||
| 317 | { | ||
| 318 | "method": "list_transactions", | ||
| 319 | "params": { | ||
| 320 | "from": 1693876973, // starting timestamp in seconds since epoch (inclusive), optional | ||
| 321 | "until": 1703225078, // ending timestamp in seconds since epoch (inclusive), optional | ||
| 322 | "limit": 10, // maximum number of invoices to return, optional | ||
| 323 | "offset": 0, // offset of the first invoice to return, optional | ||
| 324 | "unpaid": true, // include unpaid invoices, optional, default false | ||
| 325 | "type": "incoming", // "incoming" for invoices, "outgoing" for payments, undefined for both | ||
| 326 | } | ||
| 327 | } | ||
| 328 | ``` | ||
| 329 | |||
| 330 | Response: | ||
| 331 | ```jsonc | ||
| 332 | { | ||
| 333 | "result_type": "list_transactions", | ||
| 334 | "result": { | ||
| 335 | "transactions": [ | ||
| 336 | { | ||
| 337 | "type": "incoming", // "incoming" for invoices, "outgoing" for payments | ||
| 338 | "invoice": "string", // encoded invoice, optional | ||
| 339 | "description": "string", // invoice's description, optional | ||
| 340 | "description_hash": "string", // invoice's description hash, optional | ||
| 341 | "preimage": "string", // payment's preimage, optional if unpaid | ||
| 342 | "payment_hash": "string", // Payment hash for the payment | ||
| 343 | "amount": 123, // value in msats | ||
| 344 | "fees_paid": 123, // value in msats | ||
| 345 | "created_at": unixtimestamp, // invoice/payment creation time | ||
| 346 | "expires_at": unixtimestamp, // invoice expiration time, optional if not applicable | ||
| 347 | "settled_at": unixtimestamp, // invoice/payment settlement time, optional if unpaid | ||
| 348 | "metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc. | ||
| 349 | } | ||
| 350 | ], | ||
| 351 | }, | ||
| 352 | } | ||
| 353 | ``` | ||
| 354 | |||
| 355 | ### `get_balance` | ||
| 356 | |||
| 357 | Request: | ||
| 358 | ```jsonc | ||
| 359 | { | ||
| 360 | "method": "get_balance", | ||
| 361 | "params": { | ||
| 362 | } | ||
| 363 | } | ||
| 364 | ``` | ||
| 365 | |||
| 366 | Response: | ||
| 367 | ```jsonc | ||
| 368 | { | ||
| 369 | "result_type": "get_balance", | ||
| 370 | "result": { | ||
| 371 | "balance": 10000, // user's balance in msats | ||
| 372 | } | ||
| 373 | } | ||
| 374 | ``` | ||
| 375 | |||
| 376 | ### `get_info` | ||
| 377 | |||
| 378 | Request: | ||
| 379 | ```jsonc | ||
| 380 | { | ||
| 381 | "method": "get_info", | ||
| 382 | "params": { | ||
| 383 | } | ||
| 384 | } | ||
| 385 | ``` | ||
| 386 | |||
| 387 | Response: | ||
| 388 | ```jsonc | ||
| 389 | { | ||
| 390 | "result_type": "get_info", | ||
| 391 | "result": { | ||
| 392 | "alias": "string", | ||
| 393 | "color": "hex string", | ||
| 394 | "pubkey": "hex string", | ||
| 395 | "network": "string", // mainnet, testnet, signet, or regtest | ||
| 396 | "block_height": 1, | ||
| 397 | "block_hash": "hex string", | ||
| 398 | "methods": ["pay_invoice", "get_balance", "make_invoice", "lookup_invoice", "list_transactions", "get_info"], // list of supported methods for this connection | ||
| 399 | } | ||
| 400 | } | ||
| 401 | ``` | ||
| 402 | |||
| 129 | ## Example pay invoice flow | 403 | ## Example pay invoice flow |
| 130 | 404 | ||
| 131 | 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. | 405 | 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. |
| 132 | 1. **client** sends an event to the **wallet service** with kind `23194`. The content is a `pay_invoice` request. The private key is the secret from the connection string above. | 406 | 1. **client** sends an event to the **wallet service** with kind `23194`. The content is a `pay_invoice` request. The private key is the secret from the connection string above. |
| 133 | 2. **wallet service** verifies that the author's key is authorized to perform the payment, decrypts the payload and sends the payment. | 407 | 2. **wallet service** verifies that the author's key is authorized to perform the payment, decrypts the payload and sends the payment. |
| 134 | 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. | 408 | 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. |