From 9305fb74117be357330541b027313ac766b6b820 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Jan 2026 13:38:50 +0000 Subject: [PATCH] fix: improve zap/nutzap rendering in chat - Add mb-1 margin bottom to zap messages for spacing - Show inline reply preview for zaps that target specific messages - Fix nutzap amount extraction to handle multiple proof tags - Extract replyTo from e-tag for nutzaps - Pass nutzap event to RichText for custom emoji rendering --- src/components/ChatViewer.tsx | 20 +++++++++++--- src/lib/chat/adapters/nip-29-adapter.ts | 36 +++++++++++++++---------- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/components/ChatViewer.tsx b/src/components/ChatViewer.tsx index f2b2061..3ffc805 100644 --- a/src/components/ChatViewer.tsx +++ b/src/components/ChatViewer.tsx @@ -181,9 +181,15 @@ const MessageItem = memo(function MessageItem({ // Zap messages have special styling with gradient border if (message.type === "zap") { const zapRequest = message.event ? getZapRequest(message.event) : null; + // For NIP-57 zaps, reply target is in the zap request's e-tag + // For NIP-61 nutzaps, reply target is already in message.replyTo + const zapReplyTo = + message.replyTo || + zapRequest?.tags.find((t) => t[0] === "e")?.[1] || + undefined; return ( -
+
+ {zapReplyTo && ( + + )} {message.content && ( )} diff --git a/src/lib/chat/adapters/nip-29-adapter.ts b/src/lib/chat/adapters/nip-29-adapter.ts index f576fef..1bd24f5 100644 --- a/src/lib/chat/adapters/nip-29-adapter.ts +++ b/src/lib/chat/adapters/nip-29-adapter.ts @@ -676,22 +676,29 @@ export class Nip29Adapter extends ChatProtocolAdapter { const pTag = event.tags.find((t) => t[0] === "p"); const recipient = pTag?.[1] || ""; - // Amount is sum of proof amounts from the proof tag - // proof tag format: ["proof", ""] - const proofTag = event.tags.find((t) => t[0] === "proof"); + // Reply target is the e-tag (the event being nutzapped) + const eTag = event.tags.find((t) => t[0] === "e"); + const replyTo = eTag?.[1]; + + // Amount is sum of proof amounts from all proof tags + // NIP-61 allows multiple proof tags, each containing a JSON-encoded Cashu proof let amount = 0; - if (proofTag?.[1]) { - try { - const proofs = JSON.parse(proofTag[1]); - if (Array.isArray(proofs)) { - amount = proofs.reduce( - (sum: number, proof: { amount?: number }) => - sum + (proof.amount || 0), - 0, - ); + for (const tag of event.tags) { + if (tag[0] === "proof" && tag[1]) { + try { + const proof = JSON.parse(tag[1]); + // Proof can be a single object or an array of proofs + if (Array.isArray(proof)) { + amount += proof.reduce( + (sum: number, p: { amount?: number }) => sum + (p.amount || 0), + 0, + ); + } else if (typeof proof === "object" && proof.amount) { + amount += proof.amount; + } + } catch { + // Invalid proof JSON, skip this tag } - } catch { - // Invalid proof JSON, amount stays 0 } } @@ -709,6 +716,7 @@ export class Nip29Adapter extends ChatProtocolAdapter { content: comment, timestamp: event.created_at, type: "zap", // Render the same as zaps + replyTo, protocol: "nip-29", metadata: { encrypted: false,