NIP-57: Lightning Zaps
Lightning Zaps
Section titled âLightning Zapsâdraft optional
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
Section titled âProtocol flowâ- Client calculates a recipientâs lnurl pay request url from the
zaptag on the event being zapped (see Appendix G), or by decoding their lud16 field on their profile according to the lnurl specifications. The client MUST send a GET request to this url and parse the response. IfallowsNostrexists and it istrue, and ifnostrPubkeyexists and is a valid BIP 340 public key in hex, the client should associate this information with the user, along with the responseâscallback,minSendable, andmaxSendablevalues. - 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 receiptrather than a normal lnurl invoice. - When a user (the âsenderâ) indicates they want to send a zap to another user (the ârecipientâ), the client should create a
zap requestevent as described in Appendix A of this NIP and sign it. - Instead of publishing the
zap request, the9734event should instead be sent to thecallbackurl received from the lnurl pay endpoint for the recipient using a GET request. See Appendix B for details and an example. - The recipientâs lnurl server will receive this
zap requestand 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 thenostrquery parameter. - If the
zap requestis valid, the server should fetch a description hash invoice where the description is thiszap requestnote and this note only. No additional lnurl metadata is included in the description. This will be returned in the response according to LUD06. - On receiving the invoice, the client MAY pay it or pass it to an app that can pay the invoice.
- Once the invoice is paid, the recipientâs lnurl server MUST generate a
zap receiptas described in Appendix E, and publish it to therelaysspecified in thezap request. - Clients MAY fetch
zap receipts on posts and profiles, but MUST authorize their validity as described in Appendix F. If thezap requestnote contains a non-emptycontent, it may display a zap comment. Generally clients should show users thezap requestnote, and use thezap receiptto show âzap authorized by âŚâ but this is optional.
Reference and examples
Section titled âReference and examplesâAppendix A: Zap Request Event
Section titled â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:
relaysis a list of relays the recipientâs wallet should publish itszap receiptto. Note that relays should not be nested in an additional list, but should be included as shown in the example below.amountis the amount in millisats the sender intends to pay, formatted as a string. This is recommended, but optional.lnurlis the lnurl pay url of the recipient, encoded using bech32 with the prefixlnurl. This is recommended, but optional.pis the hex-encoded pubkey of the recipient.
In addition, the event MAY include the following tags:
eis an optional hex-encoded event id. Clients MUST include this if zapping an event rather than a person.ais an optional event coordinate that allows tipping addressable events such as NIP-23 long-form notes.kis the stringified kind of the target event.
Example:
{ "kind": 9734, "content": "Zap!", "tags": [ ["relays", "wss://nostr-pub.wellorder.com", "wss://anotherrelay.example.com"], ["amount", "21000"], ["lnurl", "lnurl1dp68gurn8ghj7um5v93kketj9ehx2amn9uh8wetvdskkkmn0wahz7mrww4excup0dajx2mrv92x9xp"], ["p", "04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9"], ["e", "9ae37aa68f48645127299e9453eb5d908a0cbb6058ff340d528ed4d37c8994fb"], ["k", "1"] ], "pubkey": "97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322", "created_at": 1679673265, "id": "30efed56a035b2549fcaeec0bf2c1595f9a9b3bb4b1a38abaf8ee9041c4b7d93", "sig": "f2cb581a84ed10e4dc84937bd98e27acac71ab057255f6aa8dfa561808c981fe8870f4a03c1e3666784d82a9c802d3704e174371aa13d63e2aeaf24ff5374d9d"}Appendix B: Zap Request HTTP Request
Section titled â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:
amountis the amount in millisats the sender intends to paynostris the9734zap requestevent, JSON encoded then URI encodedlnurlis the lnurl pay url of the recipient, encoded using bech32 with the prefixlnurl
This request should return a JSON response with a pr key, which is the invoice the sender must pay to finalize their zap. Here is an example flow in javascript:
const senderPubkey // The sender's pubkeyconst recipientPubkey = // The recipient's pubkeyconst callback = // The callback received from the recipients lnurl pay endpointconst lnurl = // The recipient's lightning address, encoded as a lnurlconst sats = 21
const amount = sats * 1000const 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
Section titled â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:
- Add a
nostrPubkeyto the lnurl-pay static endpoint/.well-known/lnurlp/<user>, wherenostrPubkeyis the nostr pubkey your server will use to signzap receiptevents. Clients will use this to validatezap receipts. - Add an
allowsNostrfield and set it to true.
Appendix D: LNURL Server Zap Request Validation
Section titled â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:
- It MUST have a valid nostr signature
- It MUST have tags
- It MUST have only one
ptag - It MUST have 0 or 1
etags - There should be a
relaystag with the relays to send thezap receiptto. - If there is an
amounttag, it MUST be equal to theamountquery parameter. - If there is an
atag, it MUST be a valid event coordinate - There MUST be 0 or 1
Ptags. If there is one, it MUST be equal to thezap receiptâspubkey.
The event MUST then be stored for use later, when the invoice is paid.
Appendix E: Zap Receipt Event
Section titled âAppendix E: Zap Receipt EventâA zap receipt is created by a lightning node when an invoice generated by a zap request is paid. Zap receipts are only created when the invoice description (committed to the description hash) contains a zap request note.
When receiving a payment, the following steps are executed:
- Get the description for the invoice. This needs to be saved somewhere during the generation of the description hash invoice. It is saved automatically for you with CLN, which is the reference implementation used here.
- 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.
- Create a nostr event of kind
9735as described below, and publish it to therelaysdeclared in thezap request.
The following should be true of the zap receipt event:
- The
contentSHOULD be empty. - The
created_atdate SHOULD be set to the invoicepaid_atdate for idempotency. tagsMUST include theptag (zap recipient) AND optionaletag from thezap requestAND optionalatag from thezap requestAND optionalPtag from the pubkey of the zap request (zap sender).- The
zap receiptMUST have abolt11tag containing the description hash bolt11 invoice. - The
zap receiptMUST contain adescriptiontag which is the JSON-encoded zap request. SHA256(description)SHOULD match the description hash in the bolt11 invoice.- The
zap receiptMAY contain apreimagetag 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 thezap receiptfor 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.
Example zap receipt:
{ "id": "67b48a14fb66c60c8f9070bdeb37afdfcc3d08ad01989460448e4081eddda446", "pubkey": "9630f464cca6a5147aa8a35f0bcdd3ce485324e732fd39e09233b1d848238f31", "created_at": 1674164545, "kind": 9735, "tags": [ ["p", "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"], ["P", "97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322"], ["e", "3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8"], ["k", "1"], ["bolt11", "lnbc10u1p3unwfusp5t9r3yymhpfqculx78u027lxspgxcr2n2987mx2j55nnfs95nxnzqpp5jmrh92pfld78spqs78v9euf2385t83uvpwk9ldrlvf6ch7tpascqhp5zvkrmemgth3tufcvflmzjzfvjt023nazlhljz2n9hattj4f8jq8qxqyjw5qcqpjrzjqtc4fc44feggv7065fqe5m4ytjarg3repr5j9el35xhmtfexc42yczarjuqqfzqqqqqqqqlgqqqqqqgq9q9qxpqysgq079nkq507a5tw7xgttmj4u990j7wfggtrasah5gd4ywfr2pjcn29383tphp4t48gquelz9z78p4cq7ml3nrrphw5w6eckhjwmhezhnqpy6gyf0"], ["description", "{\"pubkey\":\"97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322\",\"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": "", }Appendix F: Validating Zap Receipts
Section titled âAppendix F: Validating Zap ReceiptsâA client can retrieve zap receipts on events and pubkeys using a NIP-01 filter, for example {"kinds": [9735], "#e": [...]}. Zaps MUST be validated using the following steps:
- The
zap receipteventâspubkeyMUST be the same as the recipientâs lnurl providerâsnostrPubkey(retrieved in step 1 of the protocol flow). - The
invoiceAmountcontained in thebolt11tag of thezap receiptMUST equal theamounttag of thezap request(if present). - The
lnurltag of thezap request(if present) SHOULD equal the recipientâslnurl.
Appendix G: zap tag on other events
Section titled âAppendix G: zap tag on other eventsâWhen an event includes one or more zap tags, clients wishing to zap it SHOULD calculate the lnurl pay request based on the tags value instead of the event authorâs profile field. The tagâs second argument is the hex string of the receiverâs pub key and the third argument is the relay to download the receiverâs metadata (Kind-0). An optional fourth parameter specifies the weight (a generalization of a percentage) assigned to the respective receiver. Clients should parse all weights, calculate a sum, and then a percentage to each receiver. If weights are not present, CLIENTS should equally divide the zap amount to all receivers. If weights are only partially present, receivers without a weight should not be zapped (weight = 0).
{ "tags": [ [ "zap", "82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2", "wss://nostr.oxtr.dev", "1" ], // 25% [ "zap", "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52", "wss://nostr.wine/", "1" ], // 25% [ "zap", "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c", "wss://nos.lol/", "2" ] // 50% ]}Clients MAY display the zap split configuration in the note.
Future Work
Section titled â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.