200 lines
5.9 KiB
TypeScript
200 lines
5.9 KiB
TypeScript
import {describe, it, vi, expect, beforeEach} from "vitest"
|
|
import {hrpToMillisat, getInvoiceAmount, getLnUrl, zapFromEvent, Zapper} from "../src/Zaps"
|
|
import type {TrustedEvent} from "../src/Events"
|
|
import {now} from "@welshman/lib"
|
|
|
|
describe("Zaps", () => {
|
|
const recipient = "dd".repeat(32)
|
|
const zapper = "ee".repeat(32)
|
|
// nostrPubkey is the pubkey the ln server will use to sign zap receipt events
|
|
const nostrPubkey = "ff".repeat(32)
|
|
const currentTime = now()
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
describe("hrpToMillisat", () => {
|
|
it("should convert basic amounts", () => {
|
|
expect(hrpToMillisat("100")).toBe(BigInt(10000000000000))
|
|
})
|
|
|
|
it("should handle milli amounts", () => {
|
|
expect(hrpToMillisat("100m")).toBe(BigInt(10000000000))
|
|
})
|
|
|
|
it("should handle micro amounts", () => {
|
|
expect(hrpToMillisat("100u")).toBe(BigInt(10000000))
|
|
})
|
|
|
|
it("should handle nano amounts", () => {
|
|
expect(hrpToMillisat("100n")).toBe(BigInt(10000))
|
|
})
|
|
|
|
it("should handle pico amounts", () => {
|
|
expect(hrpToMillisat("100p")).toBe(BigInt(10))
|
|
})
|
|
|
|
it("should throw on invalid multiplier", () => {
|
|
expect(() => hrpToMillisat("100x")).toThrow("Not a valid multiplier for the amount")
|
|
})
|
|
|
|
it("should throw on invalid amount", () => {
|
|
expect(() => hrpToMillisat("ppp")).toThrow("Not a valid human readable amount")
|
|
})
|
|
|
|
it("should throw on amount outside valid range", () => {
|
|
expect(() => hrpToMillisat("2100000000000000001")).toThrow("Amount is outside of valid range")
|
|
})
|
|
})
|
|
|
|
describe("getInvoiceAmount", () => {
|
|
it("should extract amount from bolt11 invoice", () => {
|
|
const bolt11 = "lnbc100n1..." // Simplified for test
|
|
expect(getInvoiceAmount(bolt11)).toBe(10000)
|
|
})
|
|
})
|
|
|
|
describe("getLnUrl", () => {
|
|
it("should handle lnurl1 addresses", () => {
|
|
const lnurl =
|
|
"lnurl1dp68gurn8ghj7um9wfmxjcm99e3k7mf0v9cxj0m385ekvcenxc6r2c35xvukxefcv5mkvv34x5ekzd3ev56nyd3hxqurzepexejxxepnxscrvwfnv9nxzcn9xq6xyefhvgcxxcmyxymnserxfq5fns"
|
|
expect(getLnUrl(lnurl)).toBe(lnurl)
|
|
})
|
|
|
|
it("should encode regular URLs", () => {
|
|
const url = "https://example.com/.well-known/lnurlp/test"
|
|
const result = getLnUrl(url)
|
|
expect(result?.startsWith("lnurl1")).toBe(true)
|
|
})
|
|
|
|
it("should handle lud16 addresses", () => {
|
|
const address = "user@domain.com"
|
|
const result = getLnUrl(address)
|
|
expect(result?.startsWith("lnurl1")).toBe(true)
|
|
})
|
|
|
|
it("should return null for invalid input", () => {
|
|
expect(getLnUrl("invalid")).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe("zapFromEvent", () => {
|
|
const createZapRequest = (): TrustedEvent => ({
|
|
id: "ff".repeat(32),
|
|
sig: "00".repeat(64),
|
|
kind: 9734,
|
|
pubkey: zapper,
|
|
created_at: currentTime,
|
|
content: "",
|
|
tags: [
|
|
["amount", "100000"],
|
|
["lnurl", "lnurl1..."],
|
|
["p", recipient],
|
|
],
|
|
})
|
|
|
|
const createZapReceipt = (request: TrustedEvent): TrustedEvent => ({
|
|
id: "aa".repeat(32),
|
|
sig: "11".repeat(64),
|
|
kind: 9735,
|
|
pubkey: nostrPubkey,
|
|
created_at: currentTime + 60,
|
|
content: "",
|
|
tags: [
|
|
["bolt11", "lnbc1000n1..."],
|
|
["description", JSON.stringify(request)],
|
|
["p", recipient],
|
|
["P", zapper],
|
|
],
|
|
})
|
|
|
|
const validZapper: Zapper = {
|
|
lnurl: "lnurl1...",
|
|
pubkey: recipient,
|
|
nostrPubkey: nostrPubkey,
|
|
callback: "https://example.com/callback",
|
|
minSendable: 1000,
|
|
maxSendable: 100000000,
|
|
allowsNostr: true,
|
|
}
|
|
|
|
it("should validate a legitimate zap", () => {
|
|
const request = createZapRequest()
|
|
const response = createZapReceipt(request)
|
|
|
|
const result = zapFromEvent(response, validZapper)
|
|
|
|
expect(result).toBeTruthy()
|
|
expect(result?.request).toEqual(request)
|
|
expect(result?.response).toEqual(response)
|
|
expect(result?.invoiceAmount).toBe(100000)
|
|
})
|
|
|
|
it("should reject self-zaps", () => {
|
|
const request = createZapRequest()
|
|
request.pubkey = validZapper.pubkey! // Self-zap
|
|
const response = createZapReceipt(request)
|
|
|
|
const result = zapFromEvent(response, validZapper)
|
|
|
|
expect(result).toBeNull()
|
|
})
|
|
|
|
it("should reject amount mismatch", () => {
|
|
const request = createZapRequest()
|
|
const response = createZapReceipt(request)
|
|
response.tags = response.tags.map(tag =>
|
|
tag[0] === "bolt11" ? ["bolt11", "lnbc200n1..."] : tag,
|
|
)
|
|
|
|
const result = zapFromEvent(response, validZapper)
|
|
|
|
expect(result).toBeNull()
|
|
})
|
|
|
|
it("should reject incorrect zapper pubkey", () => {
|
|
const request = createZapRequest()
|
|
const response = createZapReceipt(request)
|
|
response.pubkey = "deadbeef".repeat(8) // Not the ln server pubkey
|
|
|
|
const result = zapFromEvent(response, validZapper)
|
|
|
|
expect(result).toBeNull()
|
|
})
|
|
|
|
it("should reject incorrect lnurl", () => {
|
|
const request = createZapRequest()
|
|
request.tags = request.tags.map(tag =>
|
|
tag[0] === "lnurl" ? ["lnurl", "different_lnurl"] : tag,
|
|
)
|
|
const response = createZapReceipt(request)
|
|
|
|
const result = zapFromEvent(response, validZapper)
|
|
|
|
expect(result).toBeNull()
|
|
})
|
|
|
|
it("should handle invalid description JSON", () => {
|
|
const response = createZapReceipt(createZapRequest())
|
|
response.tags = response.tags.map(tag =>
|
|
tag[0] === "description" ? ["description", "invalid json"] : tag,
|
|
)
|
|
|
|
const result = zapFromEvent(response, validZapper)
|
|
|
|
expect(result).toBeNull()
|
|
})
|
|
|
|
it("should accept zap when recipient is zapper", () => {
|
|
const request = createZapRequest()
|
|
const response = createZapReceipt(request)
|
|
response.pubkey = recipient // Recipient is zapper
|
|
|
|
const result = zapFromEvent(response, validZapper)
|
|
|
|
expect(result).toBeTruthy()
|
|
})
|
|
})
|
|
})
|