Skip to content

NIP-57

Lightning Zaps - defines the protocol for sending Bitcoin Lightning payments (zaps) to Nostr users and events. Includes kind 9734 zap request events, kind 9735 zap receipt events, and LNURL invoice fetching.

Import

ts
import { nip57 } from 'nostr-core'
// or import individual functions
import {
  createZapRequestEventTemplate,
  createZapRequestEvent,
  parseZapReceipt,
  validateZapReceipt,
  fetchZapInvoice,
  ZapError,
} from 'nostr-core'

ZapRequest Type

ts
type ZapRequest = {
  recipientPubkey: string
  eventId?: string
  address?: string
  amount: number
  relays: string[]
  content?: string
  lnurl?: string
}
FieldTypeDescription
recipientPubkeystringHex pubkey of the zap recipient
eventIdstring (optional)Event ID to zap (if zapping a specific event)
addressstring (optional)Replaceable event address to zap
amountnumberAmount in millisatoshis
relaysstring[]Relays where the zap receipt should be published
contentstring (optional)Zap comment message
lnurlstring (optional)LNURL callback URL

ZapReceipt Type

ts
type ZapReceipt = {
  recipientPubkey: string
  senderPubkey?: string
  eventId?: string
  amount: number
  bolt11: string
  description: string
  preimage?: string
}
FieldTypeDescription
recipientPubkeystringHex pubkey of the zap recipient
senderPubkeystring (optional)Hex pubkey of the zap sender (from embedded zap request)
eventIdstring (optional)Event ID that was zapped
amountnumberAmount in millisatoshis
bolt11stringLightning invoice (BOLT-11)
descriptionstringJSON string of the original zap request event
preimagestring (optional)Payment preimage proof

ZapError Class

ts
class ZapError extends Error {
  code: string
}
FieldTypeDescription
codestringError code: 'ZAP_ERROR', 'INVALID_KIND', 'FETCH_ERROR', 'LNURL_ERROR', or 'MISSING_INVOICE'

nip57.createZapRequestEventTemplate

ts
function createZapRequestEventTemplate(request: ZapRequest): EventTemplate

Creates an unsigned kind 9734 zap request event template.

ParameterTypeDescription
requestZapRequestZap request parameters

Returns: EventTemplate - Unsigned kind 9734 event with p, amount, relays, and optional e, a, lnurl tags.

ts
const template = nip57.createZapRequestEventTemplate({
  recipientPubkey: 'recipient-pubkey-hex',
  amount: 21000,
  relays: ['wss://relay.example.com'],
  content: 'Great post!',
})

const signed = await signer.signEvent(template)

nip57.createZapRequestEvent

ts
function createZapRequestEvent(request: ZapRequest, secretKey: Uint8Array): NostrEvent

Creates and signs a kind 9734 zap request event.

ParameterTypeDescription
requestZapRequestZap request parameters
secretKeyUint8ArraySigner's secret key (32 bytes)

Returns: NostrEvent - Signed kind 9734 zap request event.

ts
const zapRequest = nip57.createZapRequestEvent(
  {
    recipientPubkey: 'recipient-pubkey-hex',
    eventId: 'event-to-zap-id',
    amount: 21000,
    relays: ['wss://relay.example.com'],
    content: 'Great post!',
  },
  secretKey,
)

nip57.parseZapReceipt

ts
function parseZapReceipt(event: NostrEvent): ZapReceipt

Parses a kind 9735 zap receipt event.

ParameterTypeDescription
eventNostrEventA kind 9735 zap receipt event

Returns: ZapReceipt - Parsed receipt with sender, recipient, amount, and invoice details.

Throws: ZapError with code 'INVALID_KIND' if the event is not kind 9735.

ts
const receipt = nip57.parseZapReceipt(zapReceiptEvent)
console.log(receipt)
// {
//   recipientPubkey: 'recipient-hex',
//   senderPubkey: 'sender-hex',
//   eventId: 'zapped-event-id',
//   amount: 21000,
//   bolt11: 'lnbc210n1...',
//   description: '{"kind":9734,...}',
//   preimage: 'abc123...'
// }

nip57.validateZapReceipt

ts
function validateZapReceipt(receipt: NostrEvent, request?: NostrEvent): boolean

Validates a kind 9735 zap receipt event. Checks that the event signature is valid, contains a valid embedded zap request in the description tag, and optionally matches a specific request.

ParameterTypeDescription
receiptNostrEventA kind 9735 zap receipt event
requestNostrEvent (optional)The original zap request to verify against

Returns: boolean - true if the receipt is valid.

ts
const isValid = nip57.validateZapReceipt(receiptEvent)
console.log(isValid) // true

// Validate against a specific request
const matchesRequest = nip57.validateZapReceipt(receiptEvent, originalZapRequest)

nip57.fetchZapInvoice

ts
function fetchZapInvoice(opts: {
  lnurl: string
  zapRequest: NostrEvent
  amount: number
}): Promise<string>

Fetches a Lightning invoice from an LNURL callback, passing the signed zap request event.

ParameterTypeDescription
opts.lnurlstringLNURL callback URL
opts.zapRequestNostrEventSigned kind 9734 zap request event
opts.amountnumberAmount in millisatoshis

Returns: Promise<string> - BOLT-11 Lightning invoice string.

Throws: ZapError with code 'FETCH_ERROR', 'LNURL_ERROR', or 'MISSING_INVOICE'.

ts
const invoice = await nip57.fetchZapInvoice({
  lnurl: 'https://getalby.com/lnurlp/alice/callback',
  zapRequest: signedZapRequest,
  amount: 21000,
})

console.log(invoice) // 'lnbc210n1...'
// Pass this invoice to a Lightning wallet for payment

Full Example

ts
import { generateSecretKey, getPublicKey, nip57, RelayPool } from 'nostr-core'

const sk = generateSecretKey()
const pk = getPublicKey(sk)
const pool = new RelayPool()

// Step 1: Create a zap request
const zapRequest = nip57.createZapRequestEvent(
  {
    recipientPubkey: 'recipient-pubkey-hex',
    eventId: 'note-to-zap-id',
    amount: 21000, // 21 sats in millisatoshis
    relays: ['wss://relay.example.com'],
    content: 'Great note, have some sats!',
  },
  sk,
)

// Step 2: Fetch the Lightning invoice from the LNURL callback
try {
  const invoice = await nip57.fetchZapInvoice({
    lnurl: 'https://wallet.example.com/lnurlp/user/callback',
    zapRequest,
    amount: 21000,
  })

  console.log('Pay this invoice:', invoice)
  // The user's Lightning wallet pays the invoice
} catch (err) {
  if (err instanceof nip57.ZapError) {
    console.error(`Zap error [${err.code}]: ${err.message}`)
  }
}

// Step 3: After payment, the LNURL service publishes a kind 9735 zap receipt
// Fetch zap receipts on an event
const receipts = await pool.querySync(
  ['wss://relay.example.com'],
  { kinds: [9735], '#e': ['note-to-zap-id'] },
)

let totalZapped = 0
for (const event of receipts) {
  // Validate the receipt
  if (!nip57.validateZapReceipt(event)) continue

  const receipt = nip57.parseZapReceipt(event)
  totalZapped += receipt.amount
  console.log(`Zap from ${receipt.senderPubkey}: ${receipt.amount / 1000} sats`)
}

console.log(`Total zapped: ${totalZapped / 1000} sats`)

pool.close()

How It Works

  • Kind 9734 is a zap request event created by the sender and passed to the LNURL service
  • Kind 9735 is a zap receipt event created by the LNURL service after payment confirmation
  • The flow is: create zap request -> fetch invoice from LNURL -> pay invoice -> service publishes receipt
  • The zap request is embedded as JSON in the receipt's description tag for verification
  • The amount tag in the zap request is in millisatoshis (1 sat = 1000 millisatoshis)
  • The relays tag tells the LNURL service where to publish the zap receipt
  • Zap receipts can target a specific event (via e tag) or a user profile (via p tag only)
  • validateZapReceipt verifies the receipt signature and the embedded zap request signature
  • The sender's pubkey is extracted from the embedded zap request, not from the receipt event itself
  • The receipt's pubkey is the LNURL service's key, not the sender's

Released under the MIT License.