# Zaps The Zaps module provides utilities for working with Lightning Network payments (zaps) in Nostr, following [NIP-57](https://github.com/nostr-protocol/nips/blob/master/57.md). It includes LNURL handling, invoice amount parsing, and zap validation. ## Protocol Overview Zaps enable Lightning Network payments to be associated with Nostr events through a standardized flow: 1. **Zap Request** (kind 9734): Client creates a request specifying the amount and target 2. **Lightning Invoice**: LNURL service generates an invoice with the request embedded 3. **Zap Receipt** (kind 9735): Zapper publishes proof of payment to Nostr ## API ### Types ```typescript // Zapper service information export type Zapper = { lnurl: string; pubkey?: string; callback?: string; minSendable?: number; maxSendable?: number; nostrPubkey?: string; allowsNostr?: boolean; }; // Complete zap with request and receipt export type Zap = { request: TrustedEvent; // kind 9734 (zap request) response: TrustedEvent; // kind 9735 (zap receipt) invoiceAmount: number; // amount in millisatoshis }; ``` ### Lightning Network Utilities ```typescript // Convert human-readable amount to millisatoshis export declare const hrpToMillisat: (hrpString: string) => bigint; // Extract amount from BOLT11 lightning invoice export declare const getInvoiceAmount: (bolt11: string) => number; // Convert lightning address or URL to LNURL export declare const getLnUrl: (address: string) => string | undefined; ``` ### Zap Validation ```typescript // Create validated Zap from zap receipt event export declare const zapFromEvent: (response: TrustedEvent, zapper?: Zapper) => Zap | null; ``` ## Examples ### Converting Lightning Addresses ```typescript import { getLnUrl } from '@welshman/util'; // Lightning address (LUD-16) const lnurl1 = getLnUrl('satoshi@getalby.com'); console.log(lnurl1); // 'lnurl1...' (encoded URL) // Regular URL const lnurl2 = getLnUrl('https://getalby.com/.well-known/lnurlp/satoshi'); console.log(lnurl2); // 'lnurl1...' (encoded URL) // Already encoded LNURL const lnurl3 = getLnUrl('lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttsv9un7um9wdekjmmw84jxywf5x43rvv35xgmr2enrxanr2cfcvsmnwe3jxcukvde48qukgdec89snwde3vfjxvepjxpjnjvtpxd3kvdnxx5crxwpjvyunsephsz36jf'); console.log(lnurl3); // 'lnurl1...' (same as input) // Invalid address const invalid = getLnUrl('not-a-valid-address'); console.log(invalid); // null ``` ### Parsing Invoice Amounts ```typescript import { getInvoiceAmount, hrpToMillisat } from '@welshman/util'; // Extract amount from BOLT11 invoice const invoice = 'lnbc1500n1...'; // 1500 nanosats = 1.5 sats const amount = getInvoiceAmount(invoice); console.log(amount); // 1500 (millisatoshis) // Convert human-readable amounts console.log(hrpToMillisat('1000')); // 100000000000n (1000 BTC in millisats) console.log(hrpToMillisat('1000m')); // 100000000n (1000 mBTC = 1 BTC in millisats) console.log(hrpToMillisat('1000u')); // 100000n (1000 µBTC = 1 mBTC in millisats) console.log(hrpToMillisat('1000n')); // 100n (1000 nBTC = 1000 sats in millisats) console.log(hrpToMillisat('1000p')); // 0.1n (1000 pBTC = 1 msat, but must be divisible by 10) ``` ### Validating Zaps ```typescript import { zapFromEvent, ZAP_RESPONSE } from '@welshman/util'; // Zapper service configuration const zapper: Zapper = { lnurl: 'lnurl1dp68gurn8ghj7mr0vdskc6r0wd6z7mrww4excttsv9un7um9wdekjmmw84jxywf5x43rvv35xgmr2enrxanr2cfcvsmnwe3jxcukvde48qukgdec89snwde3vfjxvepjxpjnjvtpxd3kvdnxx5crxwpjvyunsephsz36jf', nostrPubkey: 'zapper-pubkey-hex', allowsNostr: true, minSendable: 1000, maxSendable: 10000000 }; // Zap receipt event (kind 9735) const zapReceipt = { kind: ZAP_RESPONSE, pubkey: 'zapper-pubkey-hex', tags: [ ['bolt11', 'lnbc1500n1...'], ['description', '{"kind":9734,"pubkey":"sender-pubkey","tags":[["p","recipient-pubkey"],["amount","1500"],["relays","wss://relay.com"]],"content":"Great post!","created_at":1234567890}'], ['p', 'recipient-pubkey'] ], // ... other event fields }; // Validate the zap const validZap = zapFromEvent(zapReceipt, zapper); if (validZap) { console.log('Amount:', validZap.invoiceAmount); // 1500 millisats console.log('Request:', validZap.request.content); // "Great post!" console.log('Recipient:', validZap.request.tags.find(t => t[0] === 'p')?.[1]); } else { console.log('Invalid zap - failed validation'); } ``` ### Complete Zap Flow Example ```typescript import { getLnUrl, zapFromEvent, makeEvent, ZAP_REQUEST } from '@welshman/util'; // Step 1: Get LNURL from lightning address const lightningAddress = 'satoshi@getalby.com'; const lnurl = getLnUrl(lightningAddress); if (!lnurl) { throw new Error('Invalid lightning address'); } // Step 2: Create zap request (kind 9734) const zapRequest = makeEvent(ZAP_REQUEST, { content: 'Amazing content!', tags: [ ['p', 'recipient-pubkey-hex'], // recipient ['amount', '5000'], // 5000 millisats = 5 sats ['lnurl', lnurl], ['relays', 'wss://relay.damus.io', 'wss://relay.snort.social'] ] }); // Step 3: Send to LNURL service (implementation specific) // The service will generate an invoice with the zap request in description // Step 4: Pay the invoice (using Lightning wallet) // Step 5: Validate received zap receipt const zapperInfo = { lnurl, nostrPubkey: 'zapper-service-pubkey', allowsNostr: true }; // When zap receipt arrives (kind 9735) function handleZapReceipt(zapReceipt: TrustedEvent) { const validatedZap = zapFromEvent(zapReceipt, zapperInfo); if (validatedZap) { console.log(`Received ${validatedZap.invoiceAmount} msat zap!`); console.log(`Message: ${validatedZap.request.content}`); return validatedZap; } else { console.log('Invalid zap receipt'); return null; } } ``` ### Zap Validation Rules The `zapFromEvent` function validates several aspects of a zap according to NIP-57: ```typescript import { zapFromEvent } from '@welshman/util'; // Validation checks performed: // 1. Invoice amount matches requested amount (if specified) // 2. Zap request is properly embedded in invoice description // 3. Zapper pubkey matches the expected zapper service // 4. LNURL matches the expected service (if provided in request) // 5. Self-zaps are filtered out (sender != zapper) const zapReceipt = { // ... zap receipt event }; const zapper = { nostrPubkey: 'expected-zapper-pubkey', lnurl: 'expected-lnurl' }; const validZap = zapFromEvent(zapReceipt, zapper); // Returns null if any validation fails: // - Malformed bolt11 invoice // - Amount mismatch // - Wrong zapper pubkey // - LNURL mismatch // - Self-zap detection ```