UNPKG

@nostr-dev-kit/ndk

Version:

NDK - Nostr Development Kit. Includes AI Guardrails to catch common mistakes during development.

341 lines (289 loc) 12.6 kB
import { beforeAll, describe, expect, it } from "vitest"; import { mockNutzap, mockProof } from "../../../../test"; import { NDK } from "../../../ndk"; import { NDKPrivateKeySigner } from "../../../signers/private-key"; import { NDKNutzap } from "./index.js"; import { NutzapValidationCode, NutzapValidationSeverity } from "./validation.js"; describe("NIP-61 Nutzap Validation", () => { let ndk: NDK; let senderSigner: NDKPrivateKeySigner; let recipientPubkey: string; let senderPubkey: string; beforeAll(async () => { ndk = new NDK(); senderSigner = NDKPrivateKeySigner.generate(); const recipientSigner = NDKPrivateKeySigner.generate(); senderPubkey = (await senderSigner.user()).pubkey; recipientPubkey = (await recipientSigner.user()).pubkey; }); describe("Valid nutzaps", () => { it("should validate a nutzap with e and P tags", async () => { const eventId = "event123"; const nutzap = await mockNutzap("https://mint.example.com", 100, ndk, { senderPk: senderSigner, recipientPubkey, eventId, }); const result = nutzap.validateNIP61(); expect(result.valid).toBe(true); expect(result.issues).toHaveLength(0); }); it("should validate a nutzap without e tag (backwards compatibility)", async () => { const nutzap = await mockNutzap("https://mint.example.com", 100, ndk, { senderPk: senderSigner, recipientPubkey, }); const result = nutzap.validateNIP61(); // Should be valid but have warnings expect(result.valid).toBe(true); expect(result.issues.length).toBeGreaterThan(0); expect(result.issues.every((i) => i.severity === NutzapValidationSeverity.WARNING)).toBe(true); }); }); describe("Critical errors", () => { it("should fail validation with no proofs", () => { const nutzap = new NDKNutzap(ndk); nutzap.mint = "https://mint.example.com"; nutzap.recipientPubkey = recipientPubkey; nutzap.proofs = []; const result = nutzap.validateNIP61(); expect(result.valid).toBe(false); expect(result.issues).toContainEqual( expect.objectContaining({ code: NutzapValidationCode.NO_PROOFS, severity: NutzapValidationSeverity.ERROR, }), ); }); it("should fail validation with no recipient", async () => { const nutzap = new NDKNutzap(ndk); nutzap.mint = "https://mint.example.com"; nutzap.proofs = [mockProof("https://mint.example.com", 100)]; const result = nutzap.validateNIP61(); expect(result.valid).toBe(false); expect(result.issues).toContainEqual( expect.objectContaining({ code: NutzapValidationCode.NO_RECIPIENT, severity: NutzapValidationSeverity.ERROR, }), ); }); it("should fail validation with multiple recipients", async () => { const nutzap = new NDKNutzap(ndk); nutzap.mint = "https://mint.example.com"; nutzap.proofs = [mockProof("https://mint.example.com", 100)]; nutzap.tags = [ ["p", recipientPubkey], ["p", "another_pubkey"], ]; const result = nutzap.validateNIP61(); expect(result.valid).toBe(false); expect(result.issues).toContainEqual( expect.objectContaining({ code: NutzapValidationCode.MULTIPLE_RECIPIENTS, severity: NutzapValidationSeverity.ERROR, }), ); }); it("should fail validation with no mint", async () => { const nutzap = new NDKNutzap(ndk); nutzap.recipientPubkey = recipientPubkey; nutzap.proofs = [mockProof("https://mint.example.com", 100)]; const result = nutzap.validateNIP61(); expect(result.valid).toBe(false); expect(result.issues).toContainEqual( expect.objectContaining({ code: NutzapValidationCode.NO_MINT, severity: NutzapValidationSeverity.ERROR, }), ); }); it("should fail validation with multiple mints", async () => { const nutzap = new NDKNutzap(ndk); nutzap.recipientPubkey = recipientPubkey; nutzap.proofs = [mockProof("https://mint.example.com", 100)]; nutzap.tags = [ ["p", recipientPubkey], ["u", "https://mint1.example.com"], ["u", "https://mint2.example.com"], ]; const result = nutzap.validateNIP61(); expect(result.valid).toBe(false); expect(result.issues).toContainEqual( expect.objectContaining({ code: NutzapValidationCode.MULTIPLE_MINTS, severity: NutzapValidationSeverity.ERROR, }), ); }); it("should fail validation with multiple event tags", async () => { const nutzap = await mockNutzap("https://mint.example.com", 100, ndk, { senderPk: senderSigner, recipientPubkey, eventId: "event1", }); nutzap.tags.push(["e", "event2"]); const result = nutzap.validateNIP61(); expect(result.valid).toBe(false); expect(result.issues).toContainEqual( expect.objectContaining({ code: NutzapValidationCode.MULTIPLE_EVENT_TAGS, severity: NutzapValidationSeverity.ERROR, }), ); }); it("should fail validation with malformed proof secret", () => { const nutzap = new NDKNutzap(ndk); nutzap.mint = "https://mint.example.com"; nutzap.recipientPubkey = recipientPubkey; nutzap.proofs = [ { id: "test", amount: 100, C: "test", secret: "not valid json", }, ]; const result = nutzap.validateNIP61(); expect(result.valid).toBe(false); expect(result.issues).toContainEqual( expect.objectContaining({ code: NutzapValidationCode.MALFORMED_PROOF_SECRET, severity: NutzapValidationSeverity.ERROR, proofIndex: 0, }), ); }); }); describe("Warnings (non-critical)", () => { it("should warn when event has e tag but proof is missing e tag", async () => { const eventId = "event123"; const nutzap = new NDKNutzap(ndk); nutzap.mint = "https://mint.example.com"; nutzap.recipientPubkey = recipientPubkey; nutzap.tags.push(["e", eventId]); // Create proof without e tag nutzap.proofs = [mockProof("mint", 100, recipientPubkey, [["P", senderPubkey]])]; await nutzap.sign(senderSigner); const result = nutzap.validateNIP61(); // Should be valid but have warning expect(result.valid).toBe(true); expect(result.issues).toContainEqual( expect.objectContaining({ code: NutzapValidationCode.MISSING_EVENT_TAG_IN_PROOF, severity: NutzapValidationSeverity.WARNING, proofIndex: 0, }), ); }); it("should warn when proof e tag doesn't match event e tag", async () => { const nutzap = new NDKNutzap(ndk); nutzap.mint = "https://mint.example.com"; nutzap.recipientPubkey = recipientPubkey; nutzap.tags.push(["e", "event1"]); // Create proof with different e tag nutzap.proofs = [ mockProof("mint", 100, recipientPubkey, [ ["e", "event2"], ["P", senderPubkey], ]), ]; await nutzap.sign(senderSigner); const result = nutzap.validateNIP61(); // Should be valid but have warning expect(result.valid).toBe(true); expect(result.issues).toContainEqual( expect.objectContaining({ code: NutzapValidationCode.MISMATCHED_EVENT_TAG_IN_PROOF, severity: NutzapValidationSeverity.WARNING, proofIndex: 0, }), ); }); it("should warn when proof is missing P tag", async () => { const eventId = "event123"; const nutzap = new NDKNutzap(ndk); nutzap.mint = "https://mint.example.com"; nutzap.recipientPubkey = recipientPubkey; nutzap.tags.push(["e", eventId]); // Create proof without P tag nutzap.proofs = [mockProof("mint", 100, recipientPubkey, [["e", eventId]])]; await nutzap.sign(senderSigner); const result = nutzap.validateNIP61(); // Should be valid but have warning expect(result.valid).toBe(true); expect(result.issues).toContainEqual( expect.objectContaining({ code: NutzapValidationCode.MISSING_SENDER_TAG_IN_PROOF, severity: NutzapValidationSeverity.WARNING, proofIndex: 0, }), ); }); it("should warn when proof P tag doesn't match sender pubkey", async () => { const eventId = "event123"; const nutzap = new NDKNutzap(ndk); nutzap.mint = "https://mint.example.com"; nutzap.recipientPubkey = recipientPubkey; nutzap.tags.push(["e", eventId]); // Create proof with wrong P tag nutzap.proofs = [ mockProof("mint", 100, recipientPubkey, [ ["e", eventId], ["P", "wrong_pubkey"], ]), ]; await nutzap.sign(senderSigner); const result = nutzap.validateNIP61(); // Should be valid but have warning expect(result.valid).toBe(true); expect(result.issues).toContainEqual( expect.objectContaining({ code: NutzapValidationCode.MISMATCHED_SENDER_TAG_IN_PROOF, severity: NutzapValidationSeverity.WARNING, proofIndex: 0, }), ); }); it("should warn when event is missing e tag", async () => { const nutzap = await mockNutzap("https://mint.example.com", 100, ndk, { senderPk: senderSigner, recipientPubkey, // No eventId }); const result = nutzap.validateNIP61(); // Should be valid but have warning expect(result.valid).toBe(true); expect(result.issues).toContainEqual( expect.objectContaining({ code: NutzapValidationCode.NO_EVENT_TAG_IN_EVENT, severity: NutzapValidationSeverity.WARNING, }), ); }); }); describe("isValid getter", () => { it("should return true for valid nutzaps", async () => { const eventId = "event123"; const nutzap = await mockNutzap("https://mint.example.com", 100, ndk, { senderPk: senderSigner, recipientPubkey, eventId, }); expect(nutzap.isValid).toBe(true); }); it("should return false for invalid nutzaps", () => { const nutzap = new NDKNutzap(ndk); nutzap.proofs = []; expect(nutzap.isValid).toBe(false); }); it("should return true even with warnings", async () => { const nutzap = await mockNutzap("https://mint.example.com", 100, ndk, { senderPk: senderSigner, recipientPubkey, // No eventId - will generate warnings }); expect(nutzap.isValid).toBe(true); }); }); });