5b8fef5b23
tests / tests (push) Failing after 5m8s
NIP fixes: - RelayMembers (13534): use NIP-43 `member` tags (not `p`) and set the required NIP-70 `-` protected tag. - Profile (kind 0): remove display-name support entirely (getter, setter, display() fallback, and the search weight). - Comment (1111): A/a tags now carry a real address, not the event id. - BlossomServerList (10063): normalize server URLs with normalizeUrl (HTTP), not normalizeRelayUrl (which forced wss://). - HandlerRecommendation (31989): fix inverted removeRecommendation filter; add setSupportedKind()/supportedKind() for the NIP-89 d-tag. - Report (1984): place the report-type string on the e tag (note reports) or p tag (profile reports); always emit the p tag. Docs/skills: - Add @welshman/domain docs (docs/domain/) and the welshman-domain skill. - Prune @welshman/util docs/skill of the moved Profile/List/Handler/Encryptable helpers; register domain in the sidebar, index, and skills README. - Apply accuracy fixes to the @welshman/app docs/skill. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BsMjvv7krpZeHK1Njeneru
86 lines
3.7 KiB
Markdown
86 lines
3.7 KiB
Markdown
# 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](./readers-and-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.
|
|
|
|
```typescript
|
|
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.
|
|
|
|
```typescript
|
|
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.
|
|
|
|
```typescript
|
|
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.
|
|
|
|
```typescript
|
|
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](./readers-and-builders) — the base `EventReader`/`EventBuilder` pattern.
|