mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-08 22:47:02 +02:00
NIP-30 allows an optional 4th tag parameter specifying the source emoji set address (e.g. "30030:pubkey:identifier"). This threads that address through the full emoji pipeline so it appears in posts, replies, reactions, and zap requests. - Add local blueprints.ts with patched NoteBlueprint, NoteReplyBlueprint, GroupMessageBlueprint, and ReactionBlueprint that emit the 4th param; marked TODO to revert once applesauce-common supports it upstream - Add address? to EmojiWithAddress, EmojiTag, EmojiSearchResult, and EmojiTag in create-zap-request - Store address in EmojiSearchService.addEmojiSet (30030:pubkey:identifier) - Thread address through both editor serializers (MentionEditor, RichEditor) and the emoji node TipTap attributes - Fix EmojiPickerDialog to pass address when calling onEmojiSelect and when re-indexing context emojis - Update SendMessageOptions.emojiTags and sendReaction customEmoji param to use EmojiTag throughout the adapter chain Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
160 lines
4.7 KiB
TypeScript
160 lines
4.7 KiB
TypeScript
/**
|
|
* Create NIP-57 zap request (kind 9734)
|
|
*/
|
|
|
|
import { EventFactory } from "applesauce-core/event-factory";
|
|
import type { ISigner } from "applesauce-signers";
|
|
import type { NostrEvent } from "@/types/nostr";
|
|
import type { EventPointer, AddressPointer } from "./open-parser";
|
|
import accountManager from "@/services/accounts";
|
|
import { selectZapRelays } from "./zap-relay-selection";
|
|
|
|
export interface EmojiTag {
|
|
shortcode: string;
|
|
url: string;
|
|
/** NIP-30 optional 4th tag: "30030:pubkey:identifier" address of the emoji set */
|
|
address?: string;
|
|
}
|
|
|
|
export interface ZapRequestParams {
|
|
/** Recipient pubkey (who receives the zap) */
|
|
recipientPubkey: string;
|
|
/** Amount in millisatoshis */
|
|
amountMillisats: number;
|
|
/** Optional comment/message */
|
|
comment?: string;
|
|
/** Optional event being zapped (adds e-tag) */
|
|
eventPointer?: EventPointer;
|
|
/** Optional addressable event context (adds a-tag, e.g., live activity) */
|
|
addressPointer?: AddressPointer;
|
|
/** Relays where zap receipt should be published */
|
|
relays?: string[];
|
|
/** LNURL for the recipient */
|
|
lnurl?: string;
|
|
/** NIP-30 custom emoji tags */
|
|
emojiTags?: EmojiTag[];
|
|
/**
|
|
* Custom tags to include in the zap request (beyond standard p/amount/relays)
|
|
* Used for additional protocol-specific tagging
|
|
*/
|
|
customTags?: string[][];
|
|
/** Optional signer for anonymous zaps (overrides account signer) */
|
|
signer?: ISigner;
|
|
}
|
|
|
|
/**
|
|
* Create and sign a zap request event (kind 9734)
|
|
* This event is NOT published to relays - it's sent to the LNURL callback
|
|
*
|
|
* @param params.signer - Optional signer for anonymous zaps. When provided,
|
|
* uses this signer instead of the active account's signer.
|
|
*/
|
|
export async function createZapRequest(
|
|
params: ZapRequestParams,
|
|
): Promise<NostrEvent> {
|
|
// Use provided signer (for anonymous zaps) or fall back to account signer
|
|
let signer = params.signer;
|
|
let senderPubkey: string | undefined;
|
|
|
|
if (signer) {
|
|
// Anonymous zap - use provided signer
|
|
senderPubkey = await signer.getPublicKey();
|
|
} else {
|
|
// Normal zap - use account signer
|
|
const account = accountManager.active;
|
|
|
|
if (!account) {
|
|
throw new Error("No active account. Please log in to send zaps.");
|
|
}
|
|
|
|
signer = account.signer;
|
|
if (!signer) {
|
|
throw new Error("No signer available for active account");
|
|
}
|
|
senderPubkey = account.pubkey;
|
|
}
|
|
|
|
// Get relays for zap receipt publication
|
|
// Priority: explicit relays > recipient's inbox > sender's inbox > fallback aggregators
|
|
const zapRelayResult = await selectZapRelays({
|
|
recipientPubkey: params.recipientPubkey,
|
|
senderPubkey,
|
|
explicitRelays: params.relays,
|
|
});
|
|
const relays = zapRelayResult.relays;
|
|
|
|
// Build tags
|
|
const tags: string[][] = [
|
|
["p", params.recipientPubkey],
|
|
["amount", params.amountMillisats.toString()],
|
|
["relays", ...relays.slice(0, 10)], // Limit to 10 relays
|
|
];
|
|
|
|
// Add lnurl tag if provided
|
|
if (params.lnurl) {
|
|
tags.push(["lnurl", params.lnurl]);
|
|
}
|
|
|
|
// Add event reference if zapping an event (e-tag)
|
|
if (params.eventPointer) {
|
|
const relayHint = params.eventPointer.relays?.[0] || "";
|
|
if (relayHint) {
|
|
tags.push(["e", params.eventPointer.id, relayHint]);
|
|
} else {
|
|
tags.push(["e", params.eventPointer.id]);
|
|
}
|
|
}
|
|
|
|
// Add addressable event reference (a-tag) - for NIP-53 live activities, etc.
|
|
if (params.addressPointer) {
|
|
const coordinate = `${params.addressPointer.kind}:${params.addressPointer.pubkey}:${params.addressPointer.identifier}`;
|
|
const relayHint = params.addressPointer.relays?.[0] || "";
|
|
if (relayHint) {
|
|
tags.push(["a", coordinate, relayHint]);
|
|
} else {
|
|
tags.push(["a", coordinate]);
|
|
}
|
|
}
|
|
|
|
// Add custom tags (protocol-specific like NIP-53 live activity references)
|
|
if (params.customTags) {
|
|
for (const tag of params.customTags) {
|
|
tags.push(tag);
|
|
}
|
|
}
|
|
|
|
// Add NIP-30 emoji tags
|
|
if (params.emojiTags) {
|
|
for (const emoji of params.emojiTags) {
|
|
tags.push(
|
|
emoji.address
|
|
? ["emoji", emoji.shortcode, emoji.url, emoji.address]
|
|
: ["emoji", emoji.shortcode, emoji.url],
|
|
);
|
|
}
|
|
}
|
|
|
|
// Create event template
|
|
const template = {
|
|
kind: 9734,
|
|
content: params.comment || "",
|
|
tags,
|
|
created_at: Math.floor(Date.now() / 1000),
|
|
};
|
|
|
|
// Sign the event
|
|
const factory = new EventFactory({ signer });
|
|
const draft = await factory.build(template);
|
|
const signedEvent = await factory.sign(draft);
|
|
|
|
return signedEvent as NostrEvent;
|
|
}
|
|
|
|
/**
|
|
* Serialize zap request event to JSON string for LNURL callback
|
|
* Note: Do NOT encodeURIComponent here - URLSearchParams.set() will handle encoding
|
|
*/
|
|
export function serializeZapRequest(event: NostrEvent): string {
|
|
return JSON.stringify(event);
|
|
}
|