Files
2025-09-29 07:21:46 -04:00

254 lines
6.8 KiB
JavaScript

// nip57.ts
import { bech32 } from "@scure/base";
// pure.ts
import { schnorr } from "@noble/curves/secp256k1";
import { bytesToHex as bytesToHex2 } from "@noble/hashes/utils";
// core.ts
var verifiedSymbol = Symbol("verified");
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 i2 = 0; i2 < event.tags.length; i2++) {
let tag = event.tags[i2];
if (!Array.isArray(tag))
return false;
for (let j = 0; j < tag.length; j++) {
if (typeof tag[j] !== "string")
return false;
}
}
return true;
}
// pure.ts
import { sha256 } from "@noble/hashes/sha256";
// utils.ts
import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
var utf8Decoder = new TextDecoder("utf-8");
var utf8Encoder = new TextEncoder();
// pure.ts
var JS = class {
generateSecretKey() {
return schnorr.utils.randomPrivateKey();
}
getPublicKey(secretKey) {
return bytesToHex2(schnorr.getPublicKey(secretKey));
}
finalizeEvent(t, secretKey) {
const event = t;
event.pubkey = bytesToHex2(schnorr.getPublicKey(secretKey));
event.id = getEventHash(event);
event.sig = bytesToHex2(schnorr.sign(getEventHash(event), secretKey));
event[verifiedSymbol] = true;
return event;
}
verifyEvent(event) {
if (typeof event[verifiedSymbol] === "boolean")
return event[verifiedSymbol];
const hash = getEventHash(event);
if (hash !== event.id) {
event[verifiedSymbol] = false;
return false;
}
try {
const valid = schnorr.verify(event.sig, hash, event.pubkey);
event[verifiedSymbol] = valid;
return valid;
} catch (err) {
event[verifiedSymbol] = false;
return false;
}
}
};
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 bytesToHex2(eventHash);
}
var i = new JS();
var generateSecretKey = i.generateSecretKey;
var getPublicKey = i.getPublicKey;
var finalizeEvent = i.finalizeEvent;
var verifyEvent = i.verifyEvent;
// kinds.ts
function isReplaceableKind(kind) {
return [0, 3].includes(kind) || 1e4 <= kind && kind < 2e4;
}
function isAddressableKind(kind) {
return 3e4 <= kind && kind < 4e4;
}
// 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 = new URL(`/.well-known/lnurlp/${name}`, `https://${domain}`).toString();
} 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(params) {
let zr = {
kind: 9734,
created_at: Math.round(Date.now() / 1e3),
content: params.comment || "",
tags: [
["p", "pubkey" in params ? params.pubkey : params.event.pubkey],
["amount", params.amount.toString()],
["relays", ...params.relays]
]
};
if ("event" in params) {
zr.tags.push(["e", params.event.id]);
if (isReplaceableKind(params.event.kind)) {
const a = ["a", `${params.event.kind}:${params.event.pubkey}:`];
zr.tags.push(a);
} else if (isAddressableKind(params.event.kind)) {
let d = params.event.tags.find(([t, v]) => t === "d" && v);
if (!d)
throw new Error("d tag not found or is empty");
const a = ["a", `${params.event.kind}:${params.event.pubkey}:${d[1]}`];
zr.tags.push(a);
}
zr.tags.push(["k", params.event.kind.toString()]);
}
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 (!verifyEvent(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, ["P", zr.pubkey], ["bolt11", bolt11], ["description", zapRequest]]
};
if (preimage) {
zap.tags.push(["preimage", preimage]);
}
return zap;
}
function getSatoshisAmountFromBolt11(bolt11) {
if (bolt11.length < 50) {
return 0;
}
bolt11 = bolt11.substring(0, 50);
const idx = bolt11.lastIndexOf("1");
if (idx === -1) {
return 0;
}
const hrp = bolt11.substring(0, idx);
if (!hrp.startsWith("lnbc")) {
return 0;
}
const amount = hrp.substring(4);
if (amount.length < 1) {
return 0;
}
const char = amount[amount.length - 1];
const digit = char.charCodeAt(0) - "0".charCodeAt(0);
const isDigit = digit >= 0 && digit <= 9;
let cutPoint = amount.length - 1;
if (isDigit) {
cutPoint++;
}
if (cutPoint < 1) {
return 0;
}
const num = parseInt(amount.substring(0, cutPoint));
switch (char) {
case "m":
return num * 1e5;
case "u":
return num * 100;
case "n":
return num / 10;
case "p":
return num / 1e4;
default:
return num * 1e8;
}
}
export {
getSatoshisAmountFromBolt11,
getZapEndpoint,
makeZapReceipt,
makeZapRequest,
useFetchImplementation,
validateZapRequest
};