// nip57.ts import { bech32 } from "@scure/base"; // event.ts import { schnorr } from "@noble/curves/secp256k1"; import { sha256 } from "@noble/hashes/sha256"; import { bytesToHex } from "@noble/hashes/utils"; // utils.ts var utf8Decoder = new TextDecoder("utf-8"); var utf8Encoder = new TextEncoder(); // event.ts var verifiedSymbol = Symbol("verified"); function serializeEvent(evt) { if (!validateEvent(evt)) throw new Error("can't serialize event with wrong or missing properties"); return JSON.stringify([0, evt.pubkey, evt.created_at, evt.kind, evt.tags, evt.content]); } function getEventHash(event) { let eventHash = sha256(utf8Encoder.encode(serializeEvent(event))); return bytesToHex(eventHash); } var isRecord = (obj) => obj instanceof Object; function validateEvent(event) { if (!isRecord(event)) return false; if (typeof event.kind !== "number") return false; if (typeof event.content !== "string") return false; if (typeof event.created_at !== "number") return false; if (typeof event.pubkey !== "string") return false; if (!event.pubkey.match(/^[a-f0-9]{64}$/)) return false; if (!Array.isArray(event.tags)) return false; for (let i = 0; i < event.tags.length; i++) { let tag = event.tags[i]; if (!Array.isArray(tag)) return false; for (let j = 0; j < tag.length; j++) { if (typeof tag[j] === "object") return false; } } return true; } function verifySignature(event) { if (typeof event[verifiedSymbol] === "boolean") return event[verifiedSymbol]; const hash = getEventHash(event); if (hash !== event.id) { return event[verifiedSymbol] = false; } try { return event[verifiedSymbol] = schnorr.verify(event.sig, hash, event.pubkey); } catch (err) { return event[verifiedSymbol] = false; } } // nip57.ts var _fetch; try { _fetch = fetch; } catch { } function useFetchImplementation(fetchImplementation) { _fetch = fetchImplementation; } async function getZapEndpoint(metadata) { try { let lnurl = ""; let { lud06, lud16 } = JSON.parse(metadata.content); if (lud06) { let { words } = bech32.decode(lud06, 1e3); let data = bech32.fromWords(words); lnurl = utf8Decoder.decode(data); } else if (lud16) { let [name, domain] = lud16.split("@"); lnurl = `https://${domain}/.well-known/lnurlp/${name}`; } else { return null; } let res = await _fetch(lnurl); let body = await res.json(); if (body.allowsNostr && body.nostrPubkey) { return body.callback; } } catch (err) { } return null; } function makeZapRequest({ profile, event, amount, relays, comment = "" }) { if (!amount) throw new Error("amount not given"); if (!profile) throw new Error("profile not given"); let zr = { kind: 9734, created_at: Math.round(Date.now() / 1e3), content: comment, tags: [ ["p", profile], ["amount", amount.toString()], ["relays", ...relays] ] }; if (event) { zr.tags.push(["e", event]); } return zr; } function validateZapRequest(zapRequestString) { let zapRequest; try { zapRequest = JSON.parse(zapRequestString); } catch (err) { return "Invalid zap request JSON."; } if (!validateEvent(zapRequest)) return "Zap request is not a valid Nostr event."; if (!verifySignature(zapRequest)) return "Invalid signature on zap request."; let p = zapRequest.tags.find(([t, v]) => t === "p" && v); if (!p) return "Zap request doesn't have a 'p' tag."; if (!p[1].match(/^[a-f0-9]{64}$/)) return "Zap request 'p' tag is not valid hex."; let e = zapRequest.tags.find(([t, v]) => t === "e" && v); if (e && !e[1].match(/^[a-f0-9]{64}$/)) return "Zap request 'e' tag is not valid hex."; let relays = zapRequest.tags.find(([t, v]) => t === "relays" && v); if (!relays) return "Zap request doesn't have a 'relays' tag."; return null; } function makeZapReceipt({ zapRequest, preimage, bolt11, paidAt }) { let zr = JSON.parse(zapRequest); let tagsFromZapRequest = zr.tags.filter(([t]) => t === "e" || t === "p" || t === "a"); let zap = { kind: 9735, created_at: Math.round(paidAt.getTime() / 1e3), content: "", tags: [...tagsFromZapRequest, ["bolt11", bolt11], ["description", zapRequest]] }; if (preimage) { zap.tags.push(["preimage", preimage]); } return zap; } export { getZapEndpoint, makeZapReceipt, makeZapRequest, useFetchImplementation, validateZapRequest };