From 46209ae0fe535b011a739f07f93033ca29413445 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Jan 2026 11:05:54 +0000 Subject: [PATCH] feat: add NIP-89 app address to client tag - Add GRIMOIRE_APP_ADDRESS and GRIMOIRE_CLIENT_TAG constants - Update all client tag usages to include the 31990 app definition address - Update tests to verify the app address is included - Update spell.ts documentation --- src/actions/publish-spellbook.test.ts | 2 ++ src/actions/publish-spellbook.ts | 3 ++- src/constants/app.ts | 20 ++++++++++++++++++++ src/lib/spell-conversion.test.ts | 20 ++++++++------------ src/lib/spell-conversion.ts | 3 ++- src/lib/spellbook-manager.test.ts | 3 ++- src/lib/spellbook-manager.ts | 3 ++- src/types/spell.ts | 2 +- 8 files changed, 39 insertions(+), 17 deletions(-) create mode 100644 src/constants/app.ts diff --git a/src/actions/publish-spellbook.test.ts b/src/actions/publish-spellbook.test.ts index e4302f6..7ba63b3 100644 --- a/src/actions/publish-spellbook.test.ts +++ b/src/actions/publish-spellbook.test.ts @@ -3,6 +3,7 @@ import { PublishSpellbook } from "./publish-spellbook"; import type { ActionContext } from "applesauce-actions"; import type { GrimoireState } from "@/types/app"; import type { NostrEvent } from "nostr-tools/core"; +import { GRIMOIRE_APP_ADDRESS } from "@/constants/app"; // Mock accountManager vi.mock("@/services/accounts", () => ({ @@ -192,6 +193,7 @@ describe("PublishSpellbook action", () => { expect(descTag?.[1]).toBe("Test description"); expect(clientTag).toBeDefined(); expect(clientTag?.[1]).toBe("grimoire"); + expect(clientTag?.[2]).toBe(GRIMOIRE_APP_ADDRESS); expect(altTag).toBeDefined(); expect(altTag?.[1]).toBe("Grimoire Spellbook: Test Spellbook"); }); diff --git a/src/actions/publish-spellbook.ts b/src/actions/publish-spellbook.ts index 7a07f54..6079572 100644 --- a/src/actions/publish-spellbook.ts +++ b/src/actions/publish-spellbook.ts @@ -2,6 +2,7 @@ import { createSpellbook, slugify } from "@/lib/spellbook-manager"; import { SpellbookEvent } from "@/types/spell"; import { GrimoireState } from "@/types/app"; import { SpellbookContent } from "@/types/spell"; +import { GRIMOIRE_CLIENT_TAG } from "@/constants/app"; import accountManager from "@/services/accounts"; import type { ActionContext } from "applesauce-actions"; @@ -74,7 +75,7 @@ export function PublishSpellbook(options: PublishSpellbookOptions) { tags: [ ["d", slugify(title)], ["title", title], - ["client", "grimoire"], + GRIMOIRE_CLIENT_TAG, ] as [string, string, ...string[]][], }; if (description) { diff --git a/src/constants/app.ts b/src/constants/app.ts new file mode 100644 index 0000000..93ea396 --- /dev/null +++ b/src/constants/app.ts @@ -0,0 +1,20 @@ +/** + * Grimoire app constants + */ + +/** + * Grimoire NIP-89 app definition address (kind 31990) + * Format: "kind:pubkey:identifier" + */ +export const GRIMOIRE_APP_ADDRESS = + "31990:7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194:k50nvf8d85"; + +/** + * Client tag for events published by Grimoire + * Format: ["client", "", "<31990:pubkey:d-tag>"] + */ +export const GRIMOIRE_CLIENT_TAG: [string, string, string] = [ + "client", + "grimoire", + GRIMOIRE_APP_ADDRESS, +]; diff --git a/src/lib/spell-conversion.test.ts b/src/lib/spell-conversion.test.ts index 66e3974..ca12c9a 100644 --- a/src/lib/spell-conversion.test.ts +++ b/src/lib/spell-conversion.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect } from "vitest"; import { encodeSpell, decodeSpell } from "./spell-conversion"; import type { SpellEvent } from "@/types/spell"; +import { GRIMOIRE_CLIENT_TAG } from "@/constants/app"; describe("Spell Conversion", () => { describe("encodeSpell", () => { @@ -11,7 +12,7 @@ describe("Spell Conversion", () => { }); expect(result.tags).toContainEqual(["cmd", "REQ"]); - expect(result.tags).toContainEqual(["client", "grimoire"]); + expect(result.tags).toContainEqual(GRIMOIRE_CLIENT_TAG); expect(result.tags).toContainEqual(["k", "1"]); expect(result.tags).toContainEqual(["k", "3"]); expect(result.tags).toContainEqual(["k", "7"]); @@ -253,12 +254,7 @@ describe("Spell Conversion", () => { pubkey: "test-pubkey", created_at: 1234567890, kind: 777, - tags: [ - ["cmd", "REQ"], - ["client", "grimoire"], - ["k", "1"], - ["k", "3"], - ], + tags: [["cmd", "REQ"], GRIMOIRE_CLIENT_TAG, ["k", "1"], ["k", "3"]], content: "Test spell", sig: "test-sig", }; @@ -360,7 +356,7 @@ describe("Spell Conversion", () => { kind: 777, tags: [ ["cmd", "REQ"], - ["client", "grimoire"], + GRIMOIRE_CLIENT_TAG, ["k", "1"], ["authors", "abc123", "def456"], ], @@ -382,7 +378,7 @@ describe("Spell Conversion", () => { kind: 777, tags: [ ["cmd", "REQ"], - ["client", "grimoire"], + GRIMOIRE_CLIENT_TAG, ["k", "1"], ["tag", "t", "bitcoin", "nostr"], ["tag", "p", "abc123"], @@ -410,7 +406,7 @@ describe("Spell Conversion", () => { kind: 777, tags: [ ["cmd", "REQ"], - ["client", "grimoire"], + GRIMOIRE_CLIENT_TAG, ["k", "1"], ["since", "7d"], ["until", "now"], @@ -433,7 +429,7 @@ describe("Spell Conversion", () => { kind: 777, tags: [ ["cmd", "REQ"], - ["client", "grimoire"], + GRIMOIRE_CLIENT_TAG, ["k", "1"], ["t", "bitcoin"], ["t", "news"], @@ -456,7 +452,7 @@ describe("Spell Conversion", () => { kind: 777, tags: [ ["cmd", "REQ"], - ["client", "grimoire"], + GRIMOIRE_CLIENT_TAG, ["k", "1"], ["e", "abc123def456"], ], diff --git a/src/lib/spell-conversion.ts b/src/lib/spell-conversion.ts index 8fc1157..aefb9a6 100644 --- a/src/lib/spell-conversion.ts +++ b/src/lib/spell-conversion.ts @@ -6,6 +6,7 @@ import type { SpellEvent, } from "@/types/spell"; import type { NostrFilter } from "@/types/nostr"; +import { GRIMOIRE_CLIENT_TAG } from "@/constants/app"; /** * Simple tokenization that doesn't expand shell variables @@ -116,7 +117,7 @@ export function encodeSpell(options: CreateSpellOptions): EncodedSpell { // Start with required tags const tags: [string, string, ...string[]][] = [ ["cmd", cmdType], - ["client", "grimoire"], + GRIMOIRE_CLIENT_TAG, ]; // Add name tag if provided diff --git a/src/lib/spellbook-manager.test.ts b/src/lib/spellbook-manager.test.ts index 3acbd75..699a16a 100644 --- a/src/lib/spellbook-manager.test.ts +++ b/src/lib/spellbook-manager.test.ts @@ -7,6 +7,7 @@ import { } from "./spellbook-manager"; import { GrimoireState, WindowInstance, Workspace } from "@/types/app"; import { SPELLBOOK_KIND, SpellbookEvent } from "@/types/spell"; +import { GRIMOIRE_CLIENT_TAG } from "@/constants/app"; // Mock Data const mockWindow1: WindowInstance = { @@ -138,7 +139,7 @@ describe("Spellbook Manager", () => { "description", "Test description", ]); - expect(eventProps.tags).toContainEqual(["client", "grimoire"]); + expect(eventProps.tags).toContainEqual(GRIMOIRE_CLIENT_TAG); // Check referenced spells (e tags) expect(referencedSpells).toContain("spell-1"); diff --git a/src/lib/spellbook-manager.ts b/src/lib/spellbook-manager.ts index b1d2a49..89773eb 100644 --- a/src/lib/spellbook-manager.ts +++ b/src/lib/spellbook-manager.ts @@ -2,6 +2,7 @@ import { v4 as uuidv4 } from "uuid"; import type { MosaicNode } from "react-mosaic-component"; import type { GrimoireState, WindowInstance, Workspace } from "@/types/app"; import { SPELLBOOK_KIND } from "@/constants/kinds"; +import { GRIMOIRE_CLIENT_TAG } from "@/constants/app"; import { type SpellbookContent, type SpellbookEvent, @@ -127,7 +128,7 @@ export function createSpellbook( const tags: [string, string, ...string[]][] = [ ["d", slugify(title)], ["title", title], - ["client", "grimoire"], + GRIMOIRE_CLIENT_TAG, ]; if (description) { diff --git a/src/types/spell.ts b/src/types/spell.ts index 1e71706..9f85c7e 100644 --- a/src/types/spell.ts +++ b/src/types/spell.ts @@ -13,7 +13,7 @@ export { SPELL_KIND, SPELLBOOK_KIND }; * - ["cmd", "REQ"] - Command type * * METADATA: - * - ["client", "grimoire"] - Client identifier + * - ["client", "grimoire", "<31990:pubkey:d-tag>"] - Client identifier with NIP-89 app address * - ["alt", "description"] - NIP-31 human-readable description * - ["name", "My Spell"] - Optional spell name (metadata only, not unique identifier) * - ["t", "bitcoin"], ["t", "news"] - Topic tags for categorization