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
- Client calculates a recipient's lnurl pay request url from the
zaptag on the event being zapped (see Appendix G), or by decoding their lud06 or 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
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 NIP-33 event coordinate that allows tipping parameterized replaceable events such as NIP-23 long-form notes.
Example:
{
"kind": 9734,
"content": "Zap!",
"tags": [
["relays", "wss://nostr-pub.wellorder.com", "wss://anotherrelay.example.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:
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 his zap. Here is an example flow in 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:
- 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
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 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. 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 AND optionaletag from thezap requestAND optionalatag from thezap request.- The
zap receiptMUST have abolt11tag containing the description hash bolt11 invoice. - The
zap receiptMUST contain adescriptiontag which is the JSON-encoded invoice description. SHA256(description)MUST 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"],
["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 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
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
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.