mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-12 08:27:27 +02:00
feat: parse zap requests from invoice description as fallback
Enhance zap request parsing to check multiple sources: - First try transaction.description (primary source) - If not found, decode the Lightning invoice and check its description field - This handles cases where the zap request is embedded in the invoice Changes: - Extract parsing logic into tryParseZapRequestJson() helper - Add invoice field to parseZapRequest() transaction parameter - Import light-bolt11-decoder to decode invoices - Try invoice description as fallback when tx description doesn't contain zap - Maintain applesauce caching pattern on transaction object This ensures zap payments are detected and displayed correctly regardless of where the zap request JSON is stored (tx description vs invoice description).
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
|
||||
import { NostrEvent } from "@/types/nostr";
|
||||
import { getOrComputeCachedValue } from "applesauce-core/helpers";
|
||||
import { decode as decodeBolt11 } from "light-bolt11-decoder";
|
||||
|
||||
export interface ZapRequestInfo {
|
||||
sender: string; // pubkey of the zapper
|
||||
@@ -19,72 +20,102 @@ export interface ZapRequestInfo {
|
||||
// Symbol for caching parsed zap requests on transaction objects
|
||||
const ZapRequestSymbol = Symbol("zapRequest");
|
||||
|
||||
/**
|
||||
* Try to parse a zap request JSON string into a ZapRequestInfo object
|
||||
* @param jsonString - The JSON string to parse
|
||||
* @returns ZapRequestInfo if valid zap request, null otherwise
|
||||
*/
|
||||
function tryParseZapRequestJson(jsonString: string): ZapRequestInfo | null {
|
||||
try {
|
||||
// Try to parse as JSON
|
||||
const parsed = JSON.parse(jsonString);
|
||||
|
||||
// Check if it's a valid zap request (kind 9734)
|
||||
if (
|
||||
!parsed ||
|
||||
typeof parsed !== "object" ||
|
||||
parsed.kind !== 9734 ||
|
||||
!parsed.pubkey ||
|
||||
typeof parsed.pubkey !== "string"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const event = parsed as NostrEvent;
|
||||
|
||||
// Extract zapped event from tags
|
||||
let zappedEventId: string | undefined;
|
||||
let zappedEventAddress: string | undefined;
|
||||
|
||||
if (Array.isArray(event.tags)) {
|
||||
// Look for e tag (event ID)
|
||||
const eTag = event.tags.find(
|
||||
(tag) => Array.isArray(tag) && tag.length >= 2 && tag[0] === "e",
|
||||
);
|
||||
if (eTag && typeof eTag[1] === "string") {
|
||||
zappedEventId = eTag[1];
|
||||
}
|
||||
|
||||
// Look for a tag (address/coordinate)
|
||||
const aTag = event.tags.find(
|
||||
(tag) => Array.isArray(tag) && tag.length >= 2 && tag[0] === "a",
|
||||
);
|
||||
if (aTag && typeof aTag[1] === "string") {
|
||||
zappedEventAddress = aTag[1];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sender: event.pubkey,
|
||||
message: event.content || "",
|
||||
zappedEventId,
|
||||
zappedEventAddress,
|
||||
zapRequestEvent: event,
|
||||
};
|
||||
} catch {
|
||||
// Not JSON or parsing failed - not a zap request
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to parse a zap request from a transaction
|
||||
* Checks both the transaction description and the invoice description
|
||||
* Transaction descriptions for zaps contain a JSON-stringified kind 9734 event
|
||||
* Results are cached on the transaction object using applesauce pattern
|
||||
*
|
||||
* @param transaction - The transaction object with description field
|
||||
* @param transaction - The transaction object with description and/or invoice field
|
||||
* @returns ZapRequestInfo if this is a zap payment, null otherwise
|
||||
*/
|
||||
export function parseZapRequest(transaction: {
|
||||
description?: string;
|
||||
invoice?: string;
|
||||
}): ZapRequestInfo | null {
|
||||
if (!transaction.description) return null;
|
||||
|
||||
// Use applesauce caching pattern - cache result on transaction object
|
||||
return getOrComputeCachedValue(transaction, ZapRequestSymbol, () => {
|
||||
const description = transaction.description!;
|
||||
|
||||
try {
|
||||
// Try to parse as JSON
|
||||
const parsed = JSON.parse(description);
|
||||
|
||||
// Check if it's a valid zap request (kind 9734)
|
||||
if (
|
||||
!parsed ||
|
||||
typeof parsed !== "object" ||
|
||||
parsed.kind !== 9734 ||
|
||||
!parsed.pubkey ||
|
||||
typeof parsed.pubkey !== "string"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const event = parsed as NostrEvent;
|
||||
|
||||
// Extract zapped event from tags
|
||||
let zappedEventId: string | undefined;
|
||||
let zappedEventAddress: string | undefined;
|
||||
|
||||
if (Array.isArray(event.tags)) {
|
||||
// Look for e tag (event ID)
|
||||
const eTag = event.tags.find(
|
||||
(tag) => Array.isArray(tag) && tag.length >= 2 && tag[0] === "e",
|
||||
);
|
||||
if (eTag && typeof eTag[1] === "string") {
|
||||
zappedEventId = eTag[1];
|
||||
}
|
||||
|
||||
// Look for a tag (address/coordinate)
|
||||
const aTag = event.tags.find(
|
||||
(tag) => Array.isArray(tag) && tag.length >= 2 && tag[0] === "a",
|
||||
);
|
||||
if (aTag && typeof aTag[1] === "string") {
|
||||
zappedEventAddress = aTag[1];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sender: event.pubkey,
|
||||
message: event.content || "",
|
||||
zappedEventId,
|
||||
zappedEventAddress,
|
||||
zapRequestEvent: event,
|
||||
};
|
||||
} catch {
|
||||
// Not JSON or parsing failed - not a zap request
|
||||
return null;
|
||||
// Try parsing the transaction description first
|
||||
if (transaction.description) {
|
||||
const result = tryParseZapRequestJson(transaction.description);
|
||||
if (result) return result;
|
||||
}
|
||||
|
||||
// If that didn't work, try decoding the invoice and checking its description
|
||||
if (transaction.invoice) {
|
||||
try {
|
||||
const decoded = decodeBolt11(transaction.invoice);
|
||||
const descSection = decoded.sections.find(
|
||||
(s) => s.name === "description",
|
||||
);
|
||||
|
||||
if (descSection && descSection.value) {
|
||||
const result = tryParseZapRequestJson(descSection.value as string);
|
||||
if (result) return result;
|
||||
}
|
||||
} catch {
|
||||
// Invoice decoding failed, ignore
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user