mirror of
https://github.com/nostr-protocol/nips.git
synced 2024-09-20 23:05:49 -04:00
182 lines
12 KiB
Markdown
182 lines
12 KiB
Markdown
NIP-57
|
|
======
|
|
|
|
Lightning Zaps
|
|
--------------
|
|
|
|
`draft` `optional` `author:jb55` `author:kieran`
|
|
|
|
This NIP defines two new event types for recording lightning payments between users. `9734` is a `zap request`, representing a payer's request to a recipient's lightning wallet for an invoice. `9735` is a `zap receipt`, representing the confirmation by the recipient's lightning wallet that the invoice issued in response to a `zap request` has been paid.
|
|
|
|
Having lightning receipts on nostr allows clients to display lightning payments from entities on the network. These can be used for fun or for spam deterrence.
|
|
|
|
## Protocol flow
|
|
|
|
1. Client calculates a recipient's lnurl pay request url from the `zap` tag on the event being zapped (see Appendix G), or by decoding their lud06 or lud16 field on their profile according to the [lnurl specifications](https://github.com/lnurl/luds). The client MUST send a GET request to this url and parse the response. If `allowsNostr` exists and it is `true`, and if `nostrPubkey` exists and is a valid BIP 340 public key in hex, the client should associate this information with the user, along with the response's `callback`, `minSendable`, and `maxSendable` values.
|
|
2. Clients may choose to display a lightning zap button on each post or on a user's profile. If the user's lnurl pay request endpoint supports nostr, the client SHOULD use this NIP to request a `zap receipt` rather than a normal lnurl invoice.
|
|
3. When a user (the "sender") indicates they want to send a zap to another user (the "recipient"), the client should create a `zap request` event as described in Appendix A of this NIP and sign it.
|
|
4. Instead of publishing the `zap request`, the `9734` event should instead be sent to the `callback` url received from the lnurl pay endpoint for the recipient using a GET request. See Appendix B for details and an example.
|
|
5. The recipient's lnurl server will receive this `zap request` and validate it. See Appendix C for details on how to properly configure an lnurl server to support zaps, and Appendix D for details on how to validate the `nostr` query parameter.
|
|
6. If the `zap request` is valid, the server should fetch an invoice which will be returned in the response according to [LUD06](https://github.com/lnurl/luds/blob/luds/06.md).
|
|
7. On receiving the invoice, the client MAY pay it or pass it to an app that can pay the invoice.
|
|
8. Once the invoice is paid, the recipient's lnurl server MUST generate a `zap receipt` as described in Appendix E, and publish it to the `relays` specified in the `zap request`.
|
|
9. Clients MAY fetch `zap receipt`s on posts and profiles, but MUST authorize their validity as described in Appendix F. If the `zap request` note contains a non-empty `content`, it may display a zap comment. Generally clients should show users the `zap request` note, and use the `zap receipt` to show "zap authorized by ..." but this is optional.
|
|
|
|
## Reference and examples
|
|
|
|
### Appendix A: Zap Request Event
|
|
|
|
A `zap request` is an event of kind `9734` that is _not_ published to relays, but is instead sent to a recipient's lnurl pay `callback` url. This event's `content` MAY be an optional message to send along with the payment. The event MUST include the following tags:
|
|
|
|
- `relays` is a list of relays the recipient's wallet should publish its `zap receipt` to. Note that relays should not be nested in an additional list, but should be included as shown in the example below.
|
|
- `amount` is the amount in _millisats_ the sender intends to pay, formatted as a string. This is recommended, but optional.
|
|
- `lnurl` is the lnurl pay url of the recipient, encoded using bech32 with the prefix `lnurl`. This is recommended, but optional.
|
|
- `p` is the hex-encoded pubkey of the recipient.
|
|
|
|
In addition, the event MAY include the following tags:
|
|
|
|
- `e` is an optional hex-encoded event id. Clients MUST include this if zapping an event rather than a person.
|
|
- `a` is an optional NIP-33 event coordinate that allows tipping parameterized replaceable events such as NIP-23 long-form notes.
|
|
|
|
Example:
|
|
|
|
```json
|
|
{
|
|
"kind": 9734,
|
|
"content": "Zap!",
|
|
"tags": [
|
|
["relays", "wss://nostr-pub.wellorder.com"],
|
|
["amount", "21000"],
|
|
["lnurl", "lnurl1dp68gurn8ghj7um5v93kketj9ehx2amn9uh8wetvdskkkmn0wahz7mrww4excup0dajx2mrv92x9xp"],
|
|
["p", "04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9"],
|
|
["e", "9ae37aa68f48645127299e9453eb5d908a0cbb6058ff340d528ed4d37c8994fb"]
|
|
],
|
|
"pubkey": "97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322",
|
|
"created_at": 1679673265,
|
|
"id": "30efed56a035b2549fcaeec0bf2c1595f9a9b3bb4b1a38abaf8ee9041c4b7d93",
|
|
"sig": "f2cb581a84ed10e4dc84937bd98e27acac71ab057255f6aa8dfa561808c981fe8870f4a03c1e3666784d82a9c802d3704e174371aa13d63e2aeaf24ff5374d9d"
|
|
}
|
|
```
|
|
|
|
### Appendix B: Zap Request HTTP Request
|
|
|
|
A signed `zap request` event is not published, but is instead sent using a HTTP GET request to the recipient's `callback` url, which was provided by the recipient's lnurl pay endpoint. This request should have the following query parameters defined:
|
|
|
|
- `amount` is the amount in _millisats_ the sender intends to pay
|
|
- `nostr` is the `9734` `zap request` event, JSON encoded then URI encoded
|
|
- `lnurl` is the lnurl pay url of the recipient, encoded using bech32 with the prefix `lnurl`
|
|
|
|
This request should return a JSON response with a `pr` key, which is the invoice the sender must pay to finalize his zap. Here is an example flow:
|
|
|
|
```javascript
|
|
const senderPubkey // The sender's pubkey
|
|
const recipientPubkey = // The recipient's pubkey
|
|
const callback = // The callback received from the recipients lnurl pay endpoint
|
|
const lnurl = // The recipient's lightning address, encoded as a lnurl
|
|
const sats = 21
|
|
|
|
const amount = sats * 1000
|
|
const relays = ['wss://nostr-pub.wellorder.net']
|
|
const event = encodeURI(JSON.stringify(await signEvent({
|
|
kind: [9734],
|
|
content: "",
|
|
pubkey: senderPubkey,
|
|
created_at: Math.round(Date.now() / 1000),
|
|
tags: [
|
|
["relays", ...relays],
|
|
["amount", amount.toString()],
|
|
["lnurl", lnurl],
|
|
["p", recipientPubkey],
|
|
],
|
|
})))
|
|
|
|
const {pr: invoice} = await fetchJson(`${callback}?amount=${amount}&nostr=${event}&lnurl=${lnurl}`)
|
|
```
|
|
|
|
### Appendix C: LNURL Server Configuration
|
|
|
|
The lnurl server will need some additional pieces of information so that clients can know that zap invoices are supported:
|
|
|
|
1. Add a `nostrPubkey` to the lnurl-pay static endpoint `/.well-known/lnurlp/<user>`, where `nostrPubkey` is the nostr pubkey your server will use to sign `zap receipt` events. Clients will use this to validate `zap receipt`s.
|
|
2. Add an `allowsNostr` field and set it to true.
|
|
|
|
### Appendix D: LNURL Server Zap Request Validation
|
|
|
|
When a client sends a `zap request` event to a server's lnurl-pay callback URL, there will be a `nostr` query parameter whose value is that event which is URI- and JSON-encoded. If present, the `zap request` event must be validated in the following ways:
|
|
|
|
1. It MUST have a valid nostr signature
|
|
2. It MUST have tags
|
|
3. It MUST have only one `p` tag
|
|
4. It MUST have 0 or 1 `e` tags
|
|
5. There should be a `relays` tag with the relays to send the `zap receipt` to.
|
|
6. If there is an `amount` tag, it MUST be equal to the `amount` query parameter.
|
|
7. If there is an `a` tag, it MUST be a valid NIP-33 event coordinate
|
|
|
|
The event MUST then be stored for use later, when the invoice is paid.
|
|
|
|
### Appendix E: Zap Receipt Event
|
|
|
|
A `zap receipt` is created by a lightning node when an invoice generated by a `zap request` is paid.
|
|
|
|
When receiving a payment, the following steps are executed:
|
|
|
|
1. Parse the bolt11 description as a JSON nostr event. This SHOULD be validated based on the requirements in Appendix D, either when it is received, or before the invoice is paid.
|
|
2. Create a nostr event of kind `9735` as described below, and publish it to the `relays` declared in the `zap request`.
|
|
|
|
The following should be true of the `zap receipt` event:
|
|
|
|
- The content SHOULD be empty.
|
|
- The `created_at` date SHOULD be set to the invoice `paid_at` date for idempotency.
|
|
- `tags` MUST include the `p` tag AND optional `e` tag from the `zap request`.
|
|
- The `zap receipt` MUST have a `bolt11` tag containing the bolt11 invoice.
|
|
- The `zap receipt` MUST contain a `description` tag which is the JSON-encoded `zap request`.
|
|
- The `zap receipt` MAY contain a `preimage` tag to match against the payment hash of the bolt11 invoice. This isn't really a payment proof, there is no real way to prove that the invoice is real or has been paid. You are trusting the author of the `zap receipt` for the legitimacy of the payment.
|
|
|
|
The `zap receipt` is not a proof of payment, all it proves is that some nostr user fetched an invoice. The existence of the `zap receipt` implies the invoice as paid, but it could be a lie given a rogue implementation.
|
|
|
|
A reference implementation for a zap-enabled lnurl server can be found [here](https://github.com/jb55/cln-nostr-zapper).
|
|
|
|
Example `zap receipt`:
|
|
|
|
```json
|
|
{
|
|
"id": "67b48a14fb66c60c8f9070bdeb37afdfcc3d08ad01989460448e4081eddda446",
|
|
"pubkey": "9630f464cca6a5147aa8a35f0bcdd3ce485324e732fd39e09233b1d848238f31",
|
|
"created_at": 1674164545,
|
|
"kind": 9735,
|
|
"tags": [
|
|
["p", "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"],
|
|
["e", "3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8"],
|
|
["bolt11", "lnbc10u1p3unwfusp5t9r3yymhpfqculx78u027lxspgxcr2n2987mx2j55nnfs95nxnzqpp5jmrh92pfld78spqs78v9euf2385t83uvpwk9ldrlvf6ch7tpascqhp5zvkrmemgth3tufcvflmzjzfvjt023nazlhljz2n9hattj4f8jq8qxqyjw5qcqpjrzjqtc4fc44feggv7065fqe5m4ytjarg3repr5j9el35xhmtfexc42yczarjuqqfzqqqqqqqqlgqqqqqqgq9q9qxpqysgq079nkq507a5tw7xgttmj4u990j7wfggtrasah5gd4ywfr2pjcn29383tphp4t48gquelz9z78p4cq7ml3nrrphw5w6eckhjwmhezhnqpy6gyf0"],
|
|
["description", "{\"pubkey\":\"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245\",\"content\":\"\",\"id\":\"d9cc14d50fcb8c27539aacf776882942c1a11ea4472f8cdec1dea82fab66279d\",\"created_at\":1674164539,\"sig\":\"77127f636577e9029276be060332ea565deaf89ff215a494ccff16ae3f757065e2bc59b2e8c113dd407917a010b3abd36c8d7ad84c0e3ab7dab3a0b0caa9835d\",\"kind\":9734,\"tags\":[[\"e\",\"3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8\"],[\"p\",\"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245\"],[\"relays\",\"wss://relay.damus.io\",\"wss://nostr-relay.wlvs.space\",\"wss://nostr.fmt.wiz.biz\",\"wss://relay.nostr.bg\",\"wss://nostr.oxtr.dev\",\"wss://nostr.v0l.io\",\"wss://brb.io\",\"wss://nostr.bitcoiner.social\",\"ws://monad.jb55.com:8080\",\"wss://relay.snort.social\"]]}"],
|
|
["preimage", "5d006d2cf1e73c7148e7519a4c68adc81642ce0e25a432b2434c99f97344c15f"]
|
|
],
|
|
"content": "",
|
|
"sig": "b0a3c5c984ceb777ac455b2f659505df51585d5fd97a0ec1fdb5f3347d392080d4b420240434a3afd909207195dac1e2f7e3df26ba862a45afd8bfe101c2b1cc"
|
|
}
|
|
```
|
|
|
|
### Appendix F: Validating Zap Receipts
|
|
|
|
A client can retrieve `zap receipt`s on events and pubkeys using a NIP-01 filter, for example `{"kinds": [9735], "#e": [...]}`. Zaps MUST be validated using the following steps:
|
|
|
|
- The `zap receipt` event's `pubkey` MUST be the same as the recipient's lnurl provider's `nostrPubkey` (retrieved in step 1 of the protocol flow).
|
|
- The `invoiceAmount` contained in the `bolt11` tag of the `zap receipt` MUST equal the `amount` tag of the `zap request` (if present).
|
|
- The `lnurl` tag of the `zap request` (if present) SHOULD equal the recipient's `lnurl`.
|
|
|
|
### Appendix G: `zap` tag on zapped event
|
|
|
|
When an event includes a `zap` tag, clients SHOULD calculate the lnurl pay request based on its value instead of the profile's field. An optional third argument on the tag specifies the type of value, either `lud06` or `lud16`.
|
|
|
|
```json
|
|
{
|
|
"tags": [
|
|
[ "zap", "pablo@f7z.io", "lud16" ]
|
|
]
|
|
}
|
|
```
|
|
|
|
## Future Work
|
|
|
|
Zaps can be extended to be more private by encrypting `zap request` notes to the target user, but for simplicity it has been left out of this initial draft.
|