3.7 KiB
Zaps
The lightning-zap flow (NIP-57) and zap goals (NIP-75) are modeled by three kinds: ZapRequest (what a sender publishes to ask for a zap), ZapReceipt (what the recipient's LN service publishes as proof of payment), and ZapGoal (a fundraising target). All three are plain EventReader / EventBuilder subclasses — see Readers & Builders for the base pattern.
Zap request (kind 9734)
The zap request you send to a recipient's LNURL callback. The comment is the event content; everything else is tags.
import {ZapRequest, ZapRequestBuilder} from "@welshman/domain"
const req = await ZapRequest.fromEvent(event)
req.amount() // millisats as an int, or undefined ("amount" tag)
req.lnurl() // string | undefined
req.recipient() // p-tag value
req.eventId() // e-tag value (the zapped event)
req.urls() // the "relays" tag, sliced past the key
req.comment() // event.content
const template = await new ZapRequestBuilder()
.setAmount(21000) // millisats
.setRecipient(recipientPubkey)
.setLnurl(lnurl)
.setEventId(zappedNoteId)
.setUrls(["wss://relay.example"]) // ["relays", ...urls]
.setComment("great post")
.toTemplate()
buildTags always emits a relays tag (bare if you never called setUrls).
Zap receipt (kind 9735)
The receipt is generated by the recipient's lightning service, so it is effectively read-only in practice. parse decodes the embedded zap request out of the description tag into plain, which the getters read through.
import {ZapReceipt} from "@welshman/domain"
const receipt = await ZapReceipt.fromEvent(event)
receipt.bolt11() // the invoice
receipt.invoiceAmount() // amount parsed from bolt11, or undefined on parse failure
receipt.request() // the embedded zap-request event (TrustedEvent | undefined)
receipt.sender() // request.pubkey
receipt.recipient() // p-tag value
receipt.eventId() // e-tag value
receipt.comment() // the embedded request's content
receipt.preimage() // string | undefined
The important method is verify(zapper), which validates the receipt against a Zapper (the recipient's LNURL zapper info from @welshman/util). It checks that the request is present, that the invoice amount matches the requested amount, that the sender is not the zapper itself, and that the recipient / lnurl / nostr pubkey are consistent. It returns a boolean.
import type {Zapper} from "@welshman/util"
const ok: boolean = receipt.verify(zapper)
A ZapReceiptBuilder exists (setBolt11, setDescription, setRecipient, setEventId, setPreimage) for completeness — e.g. tests or a service generating receipts — but you rarely construct these by hand.
Zap goal (kind 9041)
A fundraising target. The title is the content; the goal amount and relays are tags.
import {ZapGoal, ZapGoalBuilder} from "@welshman/domain"
const goal = await ZapGoal.fromEvent(event)
goal.title() // event.content (or "")
goal.summary() // "summary" tag value
goal.amount() // millisats as an int, default 0
goal.urls() // "relays" tag values
const template = await new ZapGoalBuilder()
.setTitle("Fund the relay")
.setSummary("keeps the lights on")
.setAmount(1000000) // millisats
.setUrls(["wss://relay.example"]) // one ["relays", url] per url
.toTemplate()
validate() requires a title (throws ZapGoal requires a title), and buildTags always emits an amount tag (defaulting to ["amount", "0"]).
See also
- Readers & Builders — the base
EventReader/EventBuilderpattern.