Skip to content

Zaps

The Zaps module provides utilities for working with Lightning Network payments (zaps) in Nostr, including LNURL handling, invoice amount parsing, and zap validation.

Zapper Interface

The Zapper interface represents a Lightning Network payment provider that can process zaps:

typescript
interface Zapper {
  // LNURL for payment processing
  lnurl: string

  // User's pubkey on the payment service
  pubkey?: string

  // LNURL callback endpoint
  callback?: string

  // Minimum payment amount in millisatoshis
  minSendable?: number

  // Maximum payment amount in millisatoshis
  maxSendable?: number

  // Pubkey used to sign zap receipts
  nostrPubkey?: string

  // Whether provider supports Nostr zaps
  allowsNostr?: boolean
}

Finding Nostr Zappers

Getting Lightning Info

First, check the user's profile for Lightning addresses:

typescript
function getLightningInfo(profile: Profile) {
  // Check for Lightning Address (NIP-57)
  if (profile.lud16) {
    return {
      type: 'lud16',
      address: profile.lud16
    }
  }

  // Check for LNURL
  if (profile.lud06) {
    return {
      type: 'lud06',
      url: profile.lud06
    }
  }

  return null
}

Fetching LNURL Metadata

Once you have the Lightning address or LNURL, fetch the metadata:

typescript
async function fetchZapper(address: string): Promise<Zapper | null> {
  // Convert Lightning address to LNURL if needed
  const lnurl = getLnUrl(address)
  if (!lnurl) return null

  try {
    // Decode and fetch LNURL metadata
    const url = new URL(bech32.decode(lnurl).data)
    const response = await fetch(url.toString())
    const metadata = await response.json()

    // Extract zapper details
    return {
      lnurl,
      callback: metadata.callback,
      minSendable: metadata.minSendable,
      maxSendable: metadata.maxSendable,
      nostrPubkey: metadata.nostrPubkey,
      allowsNostr: Boolean(metadata.allowsNostr),
    }
  } catch (error) {
    console.error('Failed to fetch zapper:', error)
    return null
  }
}
typescript
// Example Alby zapper configuration
const albyZapper: Zapper = {
  lnurl: "lnurl1...",
  pubkey: "alby_user_pubkey",
  nostrPubkey: "alby_signing_key",
  allowsNostr: true,
  minSendable: 1000,    // 1 sat minimum
  maxSendable: 100000000 // 100k sats maximum
}

// Example LNbits zapper
const lnbitsZapper: Zapper = {
  lnurl: "lnurl1...",
  callback: "https://lnbits.com/callback",
  nostrPubkey: "lnbits_signing_key",
  allowsNostr: true
}

Zap Structure

typescript
interface Zap {
  request: TrustedEvent    // Zap request event kind 9734
  response: TrustedEvent   // Zap receipt/response event kind 9735 sent by the zapper
  invoiceAmount: number    // Amount in millisats
}

Core Functions

Lightning Address Handling

typescript
// Convert address to LNURL
function getLnUrl(address: string): string | null

// Examples:
getLnUrl("user@domain.com")  // => lnurl1...
getLnUrl("https://domain.com/.well-known/lnurlp/user")  // => lnurl1...
getLnUrl("lnurl1...")  // => returns unchanged

Invoice Processing

typescript
// Parse amount from BOLT11 invoice
function getInvoiceAmount(bolt11: string): number

// Convert human readable amount to millisats
function hrpToMillisat(hrpString: string): bigint

Zap Validation

The zapFromEvent function validates a zap receipt event, against an expected zapper.

It returns a Zap object if the zap is valid, or null if not.

typescript
function zapFromEvent(
  response: TrustedEvent,
  zapper: Zapper | undefined
): Zap | null

Usage Examples

Processing Lightning Addresses

typescript
// Get LNURL from various formats
const lnurl1 = getLnUrl("user@getalby.com")
const lnurl2 = getLnUrl("https://getalby.com/.well-known/lnurlp/user")
const lnurl3 = getLnUrl("lnurl1...")

// Check if conversion was successful
if (lnurl1) {
  // Process LNURL
  processLnurl(lnurl1)
}

Invoice Amount Handling

typescript
// Get invoice amount in millisats
const amount = getInvoiceAmount(bolt11Invoice)

// Convert string amount to millisats
const millisats = hrpToMillisat("1000")  // 1000 sats
const millisats = hrpToMillisat("1m")    // 1 million sats

Zap Validation

typescript
// Validate zap event
const zap = zapFromEvent(zapResponse, albyZapper)

if (zap) {
  // Process valid zap
  processZap(zap)
}