From eb77e91b57e56549cc8128b03686edd5889033b9 Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Fri, 3 Feb 2023 22:38:14 +0100 Subject: [PATCH 01/18] feat: zaps --- package.json | 1 + src/Element/HyperText.tsx | 2 +- src/Element/LNURLTip.tsx | 21 ++++- src/Element/NoteFooter.tsx | 9 ++- src/Element/Timeline.tsx | 4 + src/Feed/EventPublisher.ts | 18 +++++ src/Feed/LoginFeed.ts | 2 +- src/Feed/ThreadFeed.ts | 4 +- src/Feed/TimelineFeed.ts | 2 +- src/Feed/ZapsFeed.ts | 17 +++++ src/Nostr/EventKind.ts | 2 + src/Pages/ProfilePage.css | 5 ++ src/Pages/ProfilePage.tsx | 13 +++- src/Util.ts | 9 ++- src/element/Zap.css | 86 +++++++++++++++++++++ src/element/Zap.tsx | 153 +++++++++++++++++++++++++++++++++++++ yarn.lock | 5 ++ 17 files changed, 341 insertions(+), 12 deletions(-) create mode 100644 src/Feed/ZapsFeed.ts create mode 100644 src/element/Zap.css create mode 100644 src/element/Zap.tsx diff --git a/package.json b/package.json index 918f9009..0becb862 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@fortawesome/free-solid-svg-icons": "^6.2.1", "@fortawesome/react-fontawesome": "^0.2.0", "@jukben/emoji-search": "^2.0.1", + "@noble/hashes": "^1.2.0", "@noble/secp256k1": "^1.7.0", "@protobufjs/base64": "^1.1.2", "@reduxjs/toolkit": "^1.9.1", diff --git a/src/Element/HyperText.tsx b/src/Element/HyperText.tsx index 52625ff4..0bb472ef 100644 --- a/src/Element/HyperText.tsx +++ b/src/Element/HyperText.tsx @@ -103,4 +103,4 @@ export default function HyperText({ link, creator }: { link: string, creator: He }, [link]); return render(); -} \ No newline at end of file +} diff --git a/src/Element/LNURLTip.tsx b/src/Element/LNURLTip.tsx index b25bf4a5..91930b48 100644 --- a/src/Element/LNURLTip.tsx +++ b/src/Element/LNURLTip.tsx @@ -1,12 +1,16 @@ import "./LNURLTip.css"; import { useEffect, useMemo, useState } from "react"; import { bech32ToText } from "Util"; +import { HexKey } from "Nostr"; +import useEventPublisher from "Feed/EventPublisher"; import Modal from "Element/Modal"; import QrCode from "Element/QrCode"; import Copy from "Element/Copy"; import useWebln from "Hooks/useWebln"; interface LNURLService { + allowsNostr?: boolean + nostrPubkey?: HexKey minSendable?: number, maxSendable?: number, metadata: string, @@ -31,12 +35,15 @@ export interface LNURLTipProps { invoice?: string, // shortcut to invoice qr tab title?: string, notice?: string + note?: HexKey + author?: HexKey } export default function LNURLTip(props: LNURLTipProps) { const onClose = props.onClose || (() => { }); const service = props.svc; const show = props.show || false; + const { note, author } = props const amounts = [50, 100, 500, 1_000, 5_000, 10_000, 50_000]; const [payService, setPayService] = useState(); const [amount, setAmount] = useState(); @@ -46,6 +53,7 @@ export default function LNURLTip(props: LNURLTipProps) { const [error, setError] = useState(); const [success, setSuccess] = useState(); const webln = useWebln(show); + const publisher = useEventPublisher(); useEffect(() => { if (show && !props.invoice) { @@ -117,7 +125,16 @@ export default function LNURLTip(props: LNURLTipProps) { async function loadInvoice() { if (!amount || !payService) return null; - const url = `${payService.callback}?amount=${Math.floor(amount * 1000)}${comment ? `&comment=${encodeURIComponent(comment)}` : ""}`; + let url = '' + const amountParam = `amount=${Math.floor(amount * 1000)}` + const commentParam = comment ? `&comment=${encodeURIComponent(comment)}` : "" + if (payService.allowsNostr && payService.nostrPubkey && author) { + const ev = await publisher.zap(author, note, comment) + const nostrParam = ev && `&nostr=${encodeURIComponent(JSON.stringify(ev.ToObject()))}` + url = `${payService.callback}?${amountParam}${commentParam}${nostrParam}`; + } else { + url = `${payService.callback}?${amountParam}${commentParam}`; + } try { let rsp = await fetch(url); if (rsp.ok) { @@ -235,4 +252,4 @@ export default function LNURLTip(props: LNURLTipProps) { ) -} \ No newline at end of file +} diff --git a/src/Element/NoteFooter.tsx b/src/Element/NoteFooter.tsx index 86c68a5e..2759d97a 100644 --- a/src/Element/NoteFooter.tsx +++ b/src/Element/NoteFooter.tsx @@ -14,6 +14,7 @@ import useEventPublisher from "Feed/EventPublisher"; import { getReactions, hexToBech32, normalizeReaction, Reaction } from "Util"; import { NoteCreator } from "Element/NoteCreator"; import LNURLTip from "Element/LNURLTip"; +import { parseZap, ZapsSummary } from "Element/Zap"; import { useUserProfile } from "Feed/ProfileFeed"; import { default as NEvent } from "Nostr/Event"; import { RootState } from "State/Store"; @@ -50,6 +51,9 @@ export default function NoteFooter(props: NoteFooterProps) { const langNames = new Intl.DisplayNames([...window.navigator.languages], { type: "language" }); const reactions = useMemo(() => getReactions(related, ev.Id, EventKind.Reaction), [related, ev]); const reposts = useMemo(() => getReactions(related, ev.Id, EventKind.Repost), [related, ev]); + const zaps = useMemo(() => getReactions(related, ev.Id, EventKind.ZapReceipt).map(parseZap).filter(z => z.valid), [related]); + const zapTotal = zaps.reduce((acc, z) => acc + z.amount, 0) + const didZap = zaps.some(a => a.zapper === login); const groupReactions = useMemo(() => { return reactions?.reduce((acc, { content }) => { let r = normalizeReaction(content); @@ -97,10 +101,11 @@ export default function NoteFooter(props: NoteFooterProps) { if (service) { return ( <> -
setTip(true)}> +
setTip(true)}>
+ {zapTotal > 0 && (
{formatShort(zapTotal)}
)}
) @@ -259,7 +264,7 @@ export default function NoteFooter(props: NoteFooterProps) { show={reply} setShow={setReply} /> - setTip(false)} show={tip} /> + setTip(false)} show={tip} author={author?.pubkey} note={ev.Id} />
) } diff --git a/src/Element/Timeline.tsx b/src/Element/Timeline.tsx index b016fad9..2172fbab 100644 --- a/src/Element/Timeline.tsx +++ b/src/Element/Timeline.tsx @@ -7,6 +7,7 @@ import useTimelineFeed, { TimelineSubject } from "Feed/TimelineFeed"; import { TaggedRawEvent } from "Nostr"; import EventKind from "Nostr/EventKind"; import LoadMore from "Element/LoadMore"; +import Zap, { parseZap } from "Element/Zap"; import Note from "Element/Note"; import NoteReaction from "Element/NoteReaction"; import useModeration from "Hooks/useModeration"; @@ -50,6 +51,9 @@ export default function Timeline({ subject, postsOnly = false, method, ignoreMod case EventKind.TextNote: { return } + case EventKind.ZapReceipt: { + return + } case EventKind.Reaction: case EventKind.Repost: { let eRef = e.tags.find(a => a[0] === "e")?.at(1); diff --git a/src/Feed/EventPublisher.ts b/src/Feed/EventPublisher.ts index 2f3b5285..96663e46 100644 --- a/src/Feed/EventPublisher.ts +++ b/src/Feed/EventPublisher.ts @@ -144,6 +144,24 @@ export default function useEventPublisher() { return await signEvent(ev); } }, + zap: async (author: HexKey, note?: HexKey, msg?: string) => { + if (pubKey) { + let ev = NEvent.ForPubKey(pubKey); + ev.Kind = EventKind.ZapRequest; + if (note) { + // @ts-ignore + ev.Tags.push(new Tag(["e", note])) + } + // @ts-ignore + ev.Tags.push(new Tag(["p", author])) + // @ts-ignore + const relayTag = ['relays', ...Object.keys(relays)] + // @ts-ignore + ev.Tags.push(new Tag(relayTag)) + processContent(ev, msg || ''); + return await signEvent(ev); + } + }, /** * Reply to a note */ diff --git a/src/Feed/LoginFeed.ts b/src/Feed/LoginFeed.ts index 6d391485..4d212efc 100644 --- a/src/Feed/LoginFeed.ts +++ b/src/Feed/LoginFeed.ts @@ -41,7 +41,7 @@ export default function useLoginFeed() { let sub = new Subscriptions(); sub.Id = "login:notifications"; - sub.Kinds = new Set([EventKind.TextNote]); + sub.Kinds = new Set([EventKind.TextNote, EventKind.ZapReceipt]); sub.PTags = new Set([pubKey]); sub.Limit = 1; return sub; diff --git a/src/Feed/ThreadFeed.ts b/src/Feed/ThreadFeed.ts index 25d52d1a..b8054e2e 100644 --- a/src/Feed/ThreadFeed.ts +++ b/src/Feed/ThreadFeed.ts @@ -31,7 +31,7 @@ export default function useThreadFeed(id: u256) { // get replies to this event const subRelated = new Subscriptions(); - subRelated.Kinds = new Set(pref.enableReactions ? [EventKind.Reaction, EventKind.TextNote, EventKind.Deletion, EventKind.Repost] : [EventKind.TextNote]); + subRelated.Kinds = new Set(pref.enableReactions ? [EventKind.Reaction, EventKind.TextNote, EventKind.Deletion, EventKind.Repost, EventKind.ZapReceipt] : [EventKind.TextNote]); subRelated.ETags = thisSub.Ids; thisSub.AddSubscription(subRelated); @@ -56,4 +56,4 @@ export default function useThreadFeed(id: u256) { }, [main.store]); return main.store; -} \ No newline at end of file +} diff --git a/src/Feed/TimelineFeed.ts b/src/Feed/TimelineFeed.ts index c1287180..958cf278 100644 --- a/src/Feed/TimelineFeed.ts +++ b/src/Feed/TimelineFeed.ts @@ -107,7 +107,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel if (trackingEvents.length > 0 && pref.enableReactions) { sub = new Subscriptions(); sub.Id = `timeline-related:${subject.type}`; - sub.Kinds = new Set([EventKind.Reaction, EventKind.Deletion]); + sub.Kinds = new Set([EventKind.Reaction, EventKind.Deletion, EventKind.ZapReceipt]); sub.ETags = new Set(trackingEvents); } return sub ?? null; diff --git a/src/Feed/ZapsFeed.ts b/src/Feed/ZapsFeed.ts new file mode 100644 index 00000000..5f5c13af --- /dev/null +++ b/src/Feed/ZapsFeed.ts @@ -0,0 +1,17 @@ +import { useMemo } from "react"; +import { HexKey } from "Nostr"; +import EventKind from "Nostr/EventKind"; +import { Subscriptions } from "Nostr/Subscriptions"; +import useSubscription from "./Subscription"; + +export default function useZapsFeed(pubkey: HexKey) { + const sub = useMemo(() => { + let x = new Subscriptions(); + x.Id = `zaps:${pubkey}`; + x.Kinds = new Set([EventKind.ZapReceipt]); + x.PTags = new Set([pubkey]); + return x; + }, [pubkey]); + + return useSubscription(sub, { leaveOpen: true, cache: true }); +} diff --git a/src/Nostr/EventKind.ts b/src/Nostr/EventKind.ts index 9b69967a..86462c6b 100644 --- a/src/Nostr/EventKind.ts +++ b/src/Nostr/EventKind.ts @@ -10,6 +10,8 @@ const enum EventKind { Reaction = 7, // NIP-25 Auth = 22242, // NIP-42 Lists = 30000, // NIP-51 + ZapRequest = 9734, // NIP tba + ZapReceipt = 9735 // NIP tba }; export default EventKind; diff --git a/src/Pages/ProfilePage.css b/src/Pages/ProfilePage.css index 53c54e1e..be5a58d6 100644 --- a/src/Pages/ProfilePage.css +++ b/src/Pages/ProfilePage.css @@ -213,3 +213,8 @@ .qr-modal .modal-body { width: unset; } + +.profile .zap-amount { + font-weight: normal; + margin-left: 4px; +} diff --git a/src/Pages/ProfilePage.tsx b/src/Pages/ProfilePage.tsx index e3e65f47..a20e501e 100644 --- a/src/Pages/ProfilePage.tsx +++ b/src/Pages/ProfilePage.tsx @@ -4,11 +4,14 @@ import { useEffect, useMemo, useState } from "react"; import { useSelector } from "react-redux"; import { useNavigate, useParams } from "react-router-dom"; +import { formatShort } from "Number"; import Link from "Icons/Link"; import Qr from "Icons/Qr"; import Zap from "Icons/Zap"; import Envelope from "Icons/Envelope"; import { useUserProfile } from "Feed/ProfileFeed"; +import useZapsFeed from "Feed/ZapsFeed"; +import { parseZap } from "Element/Zap"; import FollowButton from "Element/FollowButton"; import { extractLnAddress, parseId, hexToBech32 } from "Util"; import Avatar from "Element/Avatar"; @@ -58,6 +61,11 @@ export default function ProfilePage() { const website_url = (user?.website && !user.website.startsWith("http")) ? "https://" + user.website : user?.website || ""; + const zapFeed = useZapsFeed(id) + const zaps = useMemo(() => { + return zapFeed.store.notes.map(parseZap).filter(z => z.valid && !z.e && z.p === id) + }, [zapFeed.store.notes, id]) + const zapsTotal = zaps.reduce((acc, z) => acc + z.amount, 0) useEffect(() => { setTab(ProfileTab.Notes); @@ -89,7 +97,7 @@ export default function ProfilePage() { )} - setShowLnQr(false)} /> + setShowLnQr(false)} author={id} /> ) } @@ -163,6 +171,9 @@ export default function ProfilePage() { <> setShowLnQr(true)}> + + {zapsTotal > 0 && formatShort(zapsTotal)} + {!loggedOut && ( <> diff --git a/src/Util.ts b/src/Util.ts index abd73dde..f5e9245a 100644 --- a/src/Util.ts +++ b/src/Util.ts @@ -1,8 +1,13 @@ import * as secp from "@noble/secp256k1"; +import { sha256 as hash } from '@noble/hashes/sha256'; import { bech32 } from "bech32"; -import { HexKey, TaggedRawEvent, u256 } from "Nostr"; +import { HexKey, RawEvent, TaggedRawEvent, u256 } from "Nostr"; import EventKind from "Nostr/EventKind"; +export const sha256 = (str: string) => { + return secp.utils.bytesToHex(hash(str)) +} + export async function openFile(): Promise { return new Promise((resolve, reject) => { let elm = document.createElement("input"); @@ -156,4 +161,4 @@ export function unixNow() { export function debounce(timeout: number, fn: () => void) { let t = setTimeout(fn, timeout); return () => clearTimeout(t); -} \ No newline at end of file +} diff --git a/src/element/Zap.css b/src/element/Zap.css new file mode 100644 index 00000000..b70e35f0 --- /dev/null +++ b/src/element/Zap.css @@ -0,0 +1,86 @@ +.zap { + background-color: var(--note-bg); + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + padding: 10px; + border-radius: 16px; + margin-bottom: 12px; +} + +.zap .summary { + display: flex; + align-items: center; + justify-content: space-between; +} + +.zap .body a { + color: var(--highlight); +} + +.amount { + font-size: 18px; +} + +.amount:before { + content: '⚡️ '; +} + +.top-zap .amount:before { + content: ''; +} + +.zaps-summary { + margin-top: 8px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.top-zap { + font-size: 12px; + border: none; + margin: 0; +} + +.top-zap .pfp { + margin-right: .3em; +} + +.top-zap .summary { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.top-zap .avatar { + width: 21px; + height: 21px; +} + +.top-zap .profile-name { + font-size: 12px; +} + +.top-zap .nip05 { + display: none; +} + +.top-zap .amount { + font-size: 12px; +} + +.amount-number { + font-weight: bold; +} + +.rest-zaps { + font-size: 12px; +} + +.rest-zaps:before { + content: ", "; +} diff --git a/src/element/Zap.tsx b/src/element/Zap.tsx new file mode 100644 index 00000000..23f6e558 --- /dev/null +++ b/src/element/Zap.tsx @@ -0,0 +1,153 @@ +import "./Zap.css"; +import { useMemo } from "react"; +// @ts-expect-error +import { decode as invoiceDecode } from "light-bolt11-decoder"; +import { bytesToHex } from "@noble/hashes/utils"; + +import { sha256 } from "Util"; +import { formatShort } from "Number"; +import { HexKey, TaggedRawEvent } from "Nostr"; +import Event from "Nostr/Event"; +import Text from "Element/Text"; +import ProfileImage from "Element/ProfileImage"; + +function findTag(e: TaggedRawEvent, tag: string) { + const maybeTag = e.tags.find((evTag) => { + return evTag[0] === tag + }) + return maybeTag && maybeTag[1] +} + +type Section = { + name: string + value?: any + letters?: string +} + +function getSection(sections: Section[], name: string) { + return sections.find((s) => s.name === name) +} + +function getInvoice(zap: TaggedRawEvent) { + const bolt11 = findTag(zap, 'bolt11') + const decoded = invoiceDecode(bolt11) + + const amount = decoded.sections.find((section: any) => section.name === 'amount')?.value + const hash = decoded.sections.find((section: any) => section.name === 'description_hash')?.value; + + return { amount, hash: hash ? bytesToHex(hash) : undefined }; +} + +function getZapper(zap: TaggedRawEvent, dhash: string) { + const zapRequest = findTag(zap, 'description') + if (zapRequest) { + const rawEvent: TaggedRawEvent = JSON.parse(zapRequest); + if (Array.isArray(rawEvent)) { + // old format, ignored + return; + } + const metaHash = sha256(zapRequest); + const ev = new Event(rawEvent) + return { pubkey: ev.PubKey, valid: metaHash == dhash }; + } +} + +interface ParsedZap { + id: HexKey + e?: HexKey + p: HexKey + amount: number + content: string + zapper?: HexKey + valid: boolean +} + +export function parseZap(zap: TaggedRawEvent): ParsedZap { + const { amount, hash } = getInvoice(zap) + const zapper = hash ? getZapper(zap, hash) : { valid: false, pubkey: undefined }; + const e = findTag(zap, 'e') + const p = findTag(zap, 'p')! + return { + id: zap.id, + e, + p, + amount: Number(amount) / 1000, + zapper: zapper?.pubkey, + content: zap.content, + valid: zapper?.valid ?? false, + } +} + +const Zap = ({ zap }: { zap: ParsedZap }) => { + const { amount, content, zapper, valid } = zap + + return valid ? ( +
+
+ {zapper && } +
+ {formatShort(amount)} sats +
+
+
+ +
+
+ ) : null +} + +interface ZapsSummaryProps { zaps: ParsedZap[] } + +export const ZapsSummary = ({ zaps }: ZapsSummaryProps) => { + const zapTotal = zaps.reduce((acc, z) => acc + z.amount, 0) + + const topZap = zaps.length > 0 && zaps.reduce((acc, z) => { + return z.amount > acc.amount ? z : acc + }) + const restZaps = zaps.filter(z => topZap && z.id !== topZap.id) + const restZapsTotal = restZaps.reduce((acc, z) => acc + z.amount, 0) + const sortedZaps = useMemo(() => { + const s = [...restZaps] + s.sort((a, b) => b.amount - a.amount) + return s + }, [restZaps]) + const { zapper, amount, content, valid } = topZap || {} + + return ( +
+ {amount && valid && zapper && ( +
+
+ +
+ zapped {formatShort(amount)} sats +
+
+
+ {content && ( + + )} +
+
+ )} + {restZapsTotal > 0 && ( +
+ {restZaps.length} other{restZaps.length > 1 ? 's' : ''} zapped  + {formatShort(restZapsTotal)} sats +
+ )} +
+ ) +} + +export default Zap diff --git a/yarn.lock b/yarn.lock index b6ae8765..35386f6a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1569,6 +1569,11 @@ dependencies: eslint-scope "5.1.1" +"@noble/hashes@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12" + integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ== + "@noble/secp256k1@^1.7.0": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" From d0bc7f83fb14ef17bf2d2f336c2ce8657b3f5762 Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Fri, 3 Feb 2023 22:55:03 +0100 Subject: [PATCH 02/18] display zap summary in note footer --- src/Element/NoteFooter.tsx | 5 +++++ src/element/Zap.css | 18 +++--------------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/Element/NoteFooter.tsx b/src/Element/NoteFooter.tsx index 2759d97a..7eeb7be3 100644 --- a/src/Element/NoteFooter.tsx +++ b/src/Element/NoteFooter.tsx @@ -238,6 +238,7 @@ export default function NoteFooter(props: NoteFooterProps) { } return ( + <>
{tipButton()} @@ -266,5 +267,9 @@ export default function NoteFooter(props: NoteFooterProps) { /> setTip(false)} show={tip} author={author?.pubkey} note={ev.Id} />
+
+ +
+ ) } diff --git a/src/element/Zap.css b/src/element/Zap.css index b70e35f0..3b006c3a 100644 --- a/src/element/Zap.css +++ b/src/element/Zap.css @@ -19,11 +19,11 @@ color: var(--highlight); } -.amount { +.zap .amount { font-size: 18px; } -.amount:before { +.zap .amount:before { content: '⚡️ '; } @@ -40,7 +40,7 @@ } .top-zap { - font-size: 12px; + font-size: 14px; border: none; margin: 0; } @@ -61,26 +61,14 @@ height: 21px; } -.top-zap .profile-name { - font-size: 12px; -} - .top-zap .nip05 { display: none; } -.top-zap .amount { - font-size: 12px; -} - .amount-number { font-weight: bold; } -.rest-zaps { - font-size: 12px; -} - .rest-zaps:before { content: ", "; } From a1340868078490a9b29cc9e0780a229118a59102 Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Fri, 3 Feb 2023 22:59:35 +0100 Subject: [PATCH 03/18] fix: number format --- src/Number.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Number.ts b/src/Number.ts index 64460dbd..f19b16fd 100644 --- a/src/Number.ts +++ b/src/Number.ts @@ -7,8 +7,8 @@ export function formatShort(n: number) { if (n < 999) { return n } else if (n < 1e8) { - return `${intl.format(Math.floor(n / 1e3))}K` + return `${intl.format(n / 1e3)}K` } else { - return `${intl.format(Math.floor(n / 1e6))}M` + return `${intl.format(n / 1e6)}M` } } From d0384194015207c6f0e4ad35dace5106fdc5854e Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Fri, 3 Feb 2023 23:10:02 +0100 Subject: [PATCH 04/18] feat: add pubkey to zap button --- src/Element/ZapButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Element/ZapButton.tsx b/src/Element/ZapButton.tsx index 8ab5b1f6..4195e25a 100644 --- a/src/Element/ZapButton.tsx +++ b/src/Element/ZapButton.tsx @@ -19,7 +19,7 @@ const ZapButton = ({ pubkey, svc }: { pubkey?: HexKey, svc?: string }) => {
setZap(true)}>
- setZap(false)} /> + setZap(false)} author={pubkey} /> ) } From a97b1364c5785811dcbb2c95fb81636b836fc180 Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Fri, 3 Feb 2023 23:15:49 +0100 Subject: [PATCH 05/18] rename and add snort pubkey --- src/{element => Element}/Zap.css | 0 src/{element => Element}/Zap.tsx | 0 src/Pages/DonatePage.tsx | 5 ++++- 3 files changed, 4 insertions(+), 1 deletion(-) rename src/{element => Element}/Zap.css (100%) rename src/{element => Element}/Zap.tsx (100%) diff --git a/src/element/Zap.css b/src/Element/Zap.css similarity index 100% rename from src/element/Zap.css rename to src/Element/Zap.css diff --git a/src/element/Zap.tsx b/src/Element/Zap.tsx similarity index 100% rename from src/element/Zap.tsx rename to src/Element/Zap.tsx diff --git a/src/Pages/DonatePage.tsx b/src/Pages/DonatePage.tsx index 802b970f..41ed33cf 100644 --- a/src/Pages/DonatePage.tsx +++ b/src/Pages/DonatePage.tsx @@ -71,7 +71,10 @@ const DonatePage = () => {

Lightning Donation:
- +
{today && (Total today (UTC): {today.donations.toLocaleString()} sats)}

Primary Developers

From e795ea2b12fb8d65fa0c491096bc10a63ba94c68 Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Fri, 3 Feb 2023 23:22:54 +0100 Subject: [PATCH 06/18] fix: limit number of relays for url --- src/Feed/EventPublisher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Feed/EventPublisher.ts b/src/Feed/EventPublisher.ts index 96663e46..7dc88bdf 100644 --- a/src/Feed/EventPublisher.ts +++ b/src/Feed/EventPublisher.ts @@ -155,7 +155,7 @@ export default function useEventPublisher() { // @ts-ignore ev.Tags.push(new Tag(["p", author])) // @ts-ignore - const relayTag = ['relays', ...Object.keys(relays)] + const relayTag = ['relays', ...Object.keys(relays).slice(0, 10)] // @ts-ignore ev.Tags.push(new Tag(relayTag)) processContent(ev, msg || ''); From d08639fffb3694e628637b76585532d3bd81d482 Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Fri, 3 Feb 2023 23:39:53 +0100 Subject: [PATCH 07/18] fix: total zaps in profile --- src/Pages/ProfilePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Pages/ProfilePage.tsx b/src/Pages/ProfilePage.tsx index a20e501e..695626e4 100644 --- a/src/Pages/ProfilePage.tsx +++ b/src/Pages/ProfilePage.tsx @@ -63,7 +63,7 @@ export default function ProfilePage() { : user?.website || ""; const zapFeed = useZapsFeed(id) const zaps = useMemo(() => { - return zapFeed.store.notes.map(parseZap).filter(z => z.valid && !z.e && z.p === id) + return zapFeed.store.notes.map(parseZap).filter(z => z.valid && z.p === id) }, [zapFeed.store.notes, id]) const zapsTotal = zaps.reduce((acc, z) => acc + z.amount, 0) From 1524609a430db467fd817b84bdfb8331b710fcae Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Sat, 4 Feb 2023 00:35:57 +0100 Subject: [PATCH 08/18] feat: zaps in timeline --- src/Element/Zap.css | 21 +++++++++------------ src/Element/Zap.tsx | 7 ++++--- src/Feed/TimelineFeed.ts | 2 +- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/Element/Zap.css b/src/Element/Zap.css index 3b006c3a..7bb74dc3 100644 --- a/src/Element/Zap.css +++ b/src/Element/Zap.css @@ -1,12 +1,5 @@ .zap { - background-color: var(--note-bg); - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - padding: 10px; - border-radius: 16px; - margin-bottom: 12px; + min-height: unset; } .zap .summary { @@ -15,10 +8,6 @@ justify-content: space-between; } -.zap .body a { - color: var(--highlight); -} - .zap .amount { font-size: 18px; } @@ -72,3 +61,11 @@ .rest-zaps:before { content: ", "; } + +.note.zap > .header { + align-items: center; +} + +.note.zap > .body { + margin-bottom: 0; +} diff --git a/src/Element/Zap.tsx b/src/Element/Zap.tsx index 23f6e558..896f2244 100644 --- a/src/Element/Zap.tsx +++ b/src/Element/Zap.tsx @@ -79,12 +79,13 @@ export function parseZap(zap: TaggedRawEvent): ParsedZap { } const Zap = ({ zap }: { zap: ParsedZap }) => { - const { amount, content, zapper, valid } = zap + const { amount, content, zapper, valid, p } = zap return valid ? ( -
-
+
+
{zapper && } +
{formatShort(amount)} sats
diff --git a/src/Feed/TimelineFeed.ts b/src/Feed/TimelineFeed.ts index 958cf278..f37e8007 100644 --- a/src/Feed/TimelineFeed.ts +++ b/src/Feed/TimelineFeed.ts @@ -35,7 +35,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel let sub = new Subscriptions(); sub.Id = `timeline:${subject.type}:${subject.discriminator}`; - sub.Kinds = new Set([EventKind.TextNote, EventKind.Repost]); + sub.Kinds = new Set([EventKind.TextNote, EventKind.Repost, EventKind.ZapReceipt]); switch (subject.type) { case "pubkey": { sub.Authors = new Set(subject.items); From 3304da8a406ab9446a2904f77c68d7bd5b6cf0d3 Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Sat, 4 Feb 2023 00:36:29 +0100 Subject: [PATCH 09/18] note to self --- src/Feed/LoginFeed.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Feed/LoginFeed.ts b/src/Feed/LoginFeed.ts index 4d212efc..1e50bb01 100644 --- a/src/Feed/LoginFeed.ts +++ b/src/Feed/LoginFeed.ts @@ -41,7 +41,8 @@ export default function useLoginFeed() { let sub = new Subscriptions(); sub.Id = "login:notifications"; - sub.Kinds = new Set([EventKind.TextNote, EventKind.ZapReceipt]); + // todo: add zaps + sub.Kinds = new Set([EventKind.TextNote]); sub.PTags = new Set([pubKey]); sub.Limit = 1; return sub; From 5e9d35fb258de1dcf9d80339a6931f08174df9d2 Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Sat, 4 Feb 2023 00:42:02 +0100 Subject: [PATCH 10/18] mobile --- src/Element/Zap.css | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Element/Zap.css b/src/Element/Zap.css index 7bb74dc3..c7b3f2c1 100644 --- a/src/Element/Zap.css +++ b/src/Element/Zap.css @@ -2,6 +2,19 @@ min-height: unset; } +@media (max-width: 520px) { + .zap .header { + flex-direction: column; + } + .zap .header .pfp { + width: 100%; + padding: 4px; + } + .zap .header .amount { + font-size: 32px; + } +} + .zap .summary { display: flex; align-items: center; From 677ca771c84e7c19018b5e6d70997527f6aef54e Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Sat, 4 Feb 2023 00:45:04 +0100 Subject: [PATCH 11/18] don't show yourself as zap target --- src/Element/Zap.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Element/Zap.tsx b/src/Element/Zap.tsx index 896f2244..ffaf012b 100644 --- a/src/Element/Zap.tsx +++ b/src/Element/Zap.tsx @@ -1,5 +1,6 @@ import "./Zap.css"; import { useMemo } from "react"; +import { useSelector } from "react-redux"; // @ts-expect-error import { decode as invoiceDecode } from "light-bolt11-decoder"; import { bytesToHex } from "@noble/hashes/utils"; @@ -10,6 +11,7 @@ import { HexKey, TaggedRawEvent } from "Nostr"; import Event from "Nostr/Event"; import Text from "Element/Text"; import ProfileImage from "Element/ProfileImage"; +import { RootState } from "State/Store"; function findTag(e: TaggedRawEvent, tag: string) { const maybeTag = e.tags.find((evTag) => { @@ -80,12 +82,13 @@ export function parseZap(zap: TaggedRawEvent): ParsedZap { const Zap = ({ zap }: { zap: ParsedZap }) => { const { amount, content, zapper, valid, p } = zap + const pubKey = useSelector((s: RootState) => s.login.publicKey) return valid ? (
{zapper && } - + {p !== pubKey && }
{formatShort(amount)} sats
From 0a056edcad09b054ca7495fd2a64479c9f4440d5 Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Sat, 4 Feb 2023 10:48:21 +0100 Subject: [PATCH 12/18] fix: parse old zaps --- src/Element/Zap.tsx | 15 +++++++++------ src/Pages/ProfilePage.tsx | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Element/Zap.tsx b/src/Element/Zap.tsx index ffaf012b..7756bf1b 100644 --- a/src/Element/Zap.tsx +++ b/src/Element/Zap.tsx @@ -46,11 +46,14 @@ function getZapper(zap: TaggedRawEvent, dhash: string) { const rawEvent: TaggedRawEvent = JSON.parse(zapRequest); if (Array.isArray(rawEvent)) { // old format, ignored - return; + const rawDescriptionTag = rawEvent.find(a => a[0] === 'application/nostr') + const rawDescription = rawDescriptionTag && rawDescriptionTag[1] + const request = typeof rawDescription === 'string' ? JSON.parse(rawDescription) : rawDescription + return request?.pubkey } - const metaHash = sha256(zapRequest); + //const metaHash = sha256(zapRequest); const ev = new Event(rawEvent) - return { pubkey: ev.PubKey, valid: metaHash == dhash }; + return ev.PubKey } } @@ -66,7 +69,7 @@ interface ParsedZap { export function parseZap(zap: TaggedRawEvent): ParsedZap { const { amount, hash } = getInvoice(zap) - const zapper = hash ? getZapper(zap, hash) : { valid: false, pubkey: undefined }; + const zapper = hash && getZapper(zap, hash) const e = findTag(zap, 'e') const p = findTag(zap, 'p')! return { @@ -74,9 +77,9 @@ export function parseZap(zap: TaggedRawEvent): ParsedZap { e, p, amount: Number(amount) / 1000, - zapper: zapper?.pubkey, + zapper, content: zap.content, - valid: zapper?.valid ?? false, + valid: true, } } diff --git a/src/Pages/ProfilePage.tsx b/src/Pages/ProfilePage.tsx index 695626e4..3a048683 100644 --- a/src/Pages/ProfilePage.tsx +++ b/src/Pages/ProfilePage.tsx @@ -63,7 +63,7 @@ export default function ProfilePage() { : user?.website || ""; const zapFeed = useZapsFeed(id) const zaps = useMemo(() => { - return zapFeed.store.notes.map(parseZap).filter(z => z.valid && z.p === id) + return zapFeed.store.notes.map(parseZap).filter(z => z.valid && z.p === id && !z.e) }, [zapFeed.store.notes, id]) const zapsTotal = zaps.reduce((acc, z) => acc + z.amount, 0) From 6fa02bd0df59f18cc3ea8722e5a48ea97f0c4569 Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Sat, 4 Feb 2023 10:59:20 +0100 Subject: [PATCH 13/18] only show profile zaps on timeline --- src/Element/Timeline.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Element/Timeline.tsx b/src/Element/Timeline.tsx index 2172fbab..5fbb2e4b 100644 --- a/src/Element/Timeline.tsx +++ b/src/Element/Timeline.tsx @@ -52,7 +52,8 @@ export default function Timeline({ subject, postsOnly = false, method, ignoreMod return } case EventKind.ZapReceipt: { - return + const zap = parseZap(e) + return zap.e ? null : } case EventKind.Reaction: case EventKind.Repost: { From e7430edb06144086b9d206118b30b9dc825b01ac Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Sat, 4 Feb 2023 11:16:08 +0100 Subject: [PATCH 14/18] show zap list on profile --- src/Element/Zap.tsx | 24 +++--------------------- src/Pages/ProfilePage.tsx | 17 ++++++++++++++--- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/Element/Zap.tsx b/src/Element/Zap.tsx index 7756bf1b..36bbc162 100644 --- a/src/Element/Zap.tsx +++ b/src/Element/Zap.tsx @@ -1,11 +1,10 @@ import "./Zap.css"; -import { useMemo } from "react"; import { useSelector } from "react-redux"; // @ts-expect-error import { decode as invoiceDecode } from "light-bolt11-decoder"; import { bytesToHex } from "@noble/hashes/utils"; -import { sha256 } from "Util"; +//import { sha256 } from "Util"; import { formatShort } from "Number"; import { HexKey, TaggedRawEvent } from "Nostr"; import Event from "Nostr/Event"; @@ -20,16 +19,6 @@ function findTag(e: TaggedRawEvent, tag: string) { return maybeTag && maybeTag[1] } -type Section = { - name: string - value?: any - letters?: string -} - -function getSection(sections: Section[], name: string) { - return sections.find((s) => s.name === name) -} - function getInvoice(zap: TaggedRawEvent) { const bolt11 = findTag(zap, 'bolt11') const decoded = invoiceDecode(bolt11) @@ -83,7 +72,7 @@ export function parseZap(zap: TaggedRawEvent): ParsedZap { } } -const Zap = ({ zap }: { zap: ParsedZap }) => { +const Zap = ({ zap, showZapped = true }: { zap: ParsedZap, showZapped?: boolean }) => { const { amount, content, zapper, valid, p } = zap const pubKey = useSelector((s: RootState) => s.login.publicKey) @@ -91,7 +80,7 @@ const Zap = ({ zap }: { zap: ParsedZap }) => {
{zapper && } - {p !== pubKey && } + {p !== pubKey && showZapped && }
{formatShort(amount)} sats
@@ -111,18 +100,11 @@ const Zap = ({ zap }: { zap: ParsedZap }) => { interface ZapsSummaryProps { zaps: ParsedZap[] } export const ZapsSummary = ({ zaps }: ZapsSummaryProps) => { - const zapTotal = zaps.reduce((acc, z) => acc + z.amount, 0) - const topZap = zaps.length > 0 && zaps.reduce((acc, z) => { return z.amount > acc.amount ? z : acc }) const restZaps = zaps.filter(z => topZap && z.id !== topZap.id) const restZapsTotal = restZaps.reduce((acc, z) => acc + z.amount, 0) - const sortedZaps = useMemo(() => { - const s = [...restZaps] - s.sort((a, b) => b.amount - a.amount) - return s - }, [restZaps]) const { zapper, amount, content, valid } = topZap || {} return ( diff --git a/src/Pages/ProfilePage.tsx b/src/Pages/ProfilePage.tsx index 3a048683..91b2df5b 100644 --- a/src/Pages/ProfilePage.tsx +++ b/src/Pages/ProfilePage.tsx @@ -11,7 +11,7 @@ import Zap from "Icons/Zap"; import Envelope from "Icons/Envelope"; import { useUserProfile } from "Feed/ProfileFeed"; import useZapsFeed from "Feed/ZapsFeed"; -import { parseZap } from "Element/Zap"; +import { default as ZapElement, parseZap } from "Element/Zap"; import FollowButton from "Element/FollowButton"; import { extractLnAddress, parseId, hexToBech32 } from "Util"; import Avatar from "Element/Avatar"; @@ -39,6 +39,7 @@ enum ProfileTab { Reactions = "Reactions", Followers = "Followers", Follows = "Follows", + Zaps = "Zaps", Muted = "Muted", Blocked = "Blocked" }; @@ -63,7 +64,9 @@ export default function ProfilePage() { : user?.website || ""; const zapFeed = useZapsFeed(id) const zaps = useMemo(() => { - return zapFeed.store.notes.map(parseZap).filter(z => z.valid && z.p === id && !z.e) + const profileZaps = zapFeed.store.notes.map(parseZap).filter(z => z.valid && z.p === id && !z.e) + profileZaps.sort((a, b) => b.amount - a.amount) + return profileZaps }, [zapFeed.store.notes, id]) const zapsTotal = zaps.reduce((acc, z) => acc + z.amount, 0) @@ -117,6 +120,14 @@ export default function ProfilePage() { switch (tab) { case ProfileTab.Notes: return ; + case ProfileTab.Zaps: { + return ( +
+ {zaps.map(z => )} +
+ ) + } + case ProfileTab.Follows: { if (isMe) { return ( @@ -216,7 +227,7 @@ export default function ProfilePage() {
- {[ProfileTab.Notes, ProfileTab.Followers, ProfileTab.Follows, ProfileTab.Muted].map(renderTab)} + {[ProfileTab.Notes, ProfileTab.Followers, ProfileTab.Follows, ProfileTab.Zaps, ProfileTab.Muted].map(renderTab)} {isMe && renderTab(ProfileTab.Blocked)}
{tabContent()} From b31b10f686e1de8fba4efdd296efa12ab8c636db Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Sat, 4 Feb 2023 11:28:13 +0100 Subject: [PATCH 15/18] zaps can be anon --- src/Element/Zap.tsx | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/Element/Zap.tsx b/src/Element/Zap.tsx index 36bbc162..e0ca66f5 100644 --- a/src/Element/Zap.tsx +++ b/src/Element/Zap.tsx @@ -1,4 +1,5 @@ import "./Zap.css"; +import { useMemo } from "react"; import { useSelector } from "react-redux"; // @ts-expect-error import { decode as invoiceDecode } from "light-bolt11-decoder"; @@ -79,7 +80,7 @@ const Zap = ({ zap, showZapped = true }: { zap: ParsedZap, showZapped?: boolean return valid ? (
- {zapper && } + {zapper ? :
Anon 
} {p !== pubKey && showZapped && }
{formatShort(amount)} sats @@ -87,7 +88,7 @@ const Zap = ({ zap, showZapped = true }: { zap: ParsedZap, showZapped?: boolean
{ - const topZap = zaps.length > 0 && zaps.reduce((acc, z) => { - return z.amount > acc.amount ? z : acc - }) - const restZaps = zaps.filter(z => topZap && z.id !== topZap.id) + const sortedZaps = useMemo(() => { + const pub = [...zaps.filter(z => z.zapper)] + const priv = [...zaps.filter(z => !z.zapper)] + pub.sort((a, b) => b.amount - a.amount) + return pub.concat(priv) + }, [zaps]) + + if (zaps.length === 0) { + return null + } + + const [topZap, ...restZaps] = sortedZaps const restZapsTotal = restZaps.reduce((acc, z) => acc + z.amount, 0) - const { zapper, amount, content, valid } = topZap || {} + const { zapper, amount, content, valid } = topZap return (
- {amount && valid && zapper && ( + {amount && (
- + {zapper ? :
Anon 
}
zapped {formatShort(amount)} sats
@@ -120,7 +129,7 @@ export const ZapsSummary = ({ zaps }: ZapsSummaryProps) => {
{content && ( Date: Sat, 4 Feb 2023 12:43:05 +0100 Subject: [PATCH 16/18] show small numbers --- src/Number.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Number.ts b/src/Number.ts index f19b16fd..4626d0bb 100644 --- a/src/Number.ts +++ b/src/Number.ts @@ -4,7 +4,7 @@ const intl = new Intl.NumberFormat("en", { }); export function formatShort(n: number) { - if (n < 999) { + if (n < 1e5) { return n } else if (n < 1e8) { return `${intl.format(n / 1e3)}K` From 13176691492476d30901dc9084cf8e0a0213831e Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Sat, 4 Feb 2023 13:07:57 +0100 Subject: [PATCH 17/18] dont count self zaps --- src/Pages/ProfilePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Pages/ProfilePage.tsx b/src/Pages/ProfilePage.tsx index 91b2df5b..a3da4820 100644 --- a/src/Pages/ProfilePage.tsx +++ b/src/Pages/ProfilePage.tsx @@ -64,7 +64,7 @@ export default function ProfilePage() { : user?.website || ""; const zapFeed = useZapsFeed(id) const zaps = useMemo(() => { - const profileZaps = zapFeed.store.notes.map(parseZap).filter(z => z.valid && z.p === id && !z.e) + const profileZaps = zapFeed.store.notes.map(parseZap).filter(z => z.valid && z.p === id && !z.e && z.zapper !== id) profileZaps.sort((a, b) => b.amount - a.amount) return profileZaps }, [zapFeed.store.notes, id]) From 140d87e0717a022d9704dbfc3f6ae50ac2751881 Mon Sep 17 00:00:00 2001 From: Kieran Date: Sat, 4 Feb 2023 13:30:05 +0000 Subject: [PATCH 18/18] verify description hash --- src/Element/Timeline.tsx | 14 ++++++------- src/Element/Zap.tsx | 44 ++++++++++++++++++++++------------------ src/Number.ts | 2 +- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/Element/Timeline.tsx b/src/Element/Timeline.tsx index 5fbb2e4b..dc55b3d9 100644 --- a/src/Element/Timeline.tsx +++ b/src/Element/Timeline.tsx @@ -46,19 +46,19 @@ export default function Timeline({ subject, postsOnly = false, method, ignoreMod function eventElement(e: TaggedRawEvent) { switch (e.kind) { case EventKind.SetMetadata: { - return + return } case EventKind.TextNote: { return } - case EventKind.ZapReceipt: { - const zap = parseZap(e) - return zap.e ? null : + case EventKind.ZapReceipt: { + const zap = parseZap(e) + return zap.e ? null : } case EventKind.Reaction: case EventKind.Repost: { let eRef = e.tags.find(a => a[0] === "e")?.at(1); - return a.id === eRef)}/> + return a.id === eRef)} /> } } } @@ -66,12 +66,12 @@ export default function Timeline({ subject, postsOnly = false, method, ignoreMod return (
{latestFeed.length > 1 && (
showLatest()}> - +   Show latest {latestFeed.length - 1} notes
)} {mainFeed.map(eventElement)} - +
); } diff --git a/src/Element/Zap.tsx b/src/Element/Zap.tsx index e0ca66f5..adfc7288 100644 --- a/src/Element/Zap.tsx +++ b/src/Element/Zap.tsx @@ -4,6 +4,7 @@ import { useSelector } from "react-redux"; // @ts-expect-error import { decode as invoiceDecode } from "light-bolt11-decoder"; import { bytesToHex } from "@noble/hashes/utils"; +import { sha256 } from "Util"; //import { sha256 } from "Util"; import { formatShort } from "Number"; @@ -30,21 +31,24 @@ function getInvoice(zap: TaggedRawEvent) { return { amount, hash: hash ? bytesToHex(hash) : undefined }; } -function getZapper(zap: TaggedRawEvent, dhash: string) { +interface Zapper { + pubkey?: HexKey, + isValid: boolean +} + +function getZapper(zap: TaggedRawEvent, dhash: string): Zapper { const zapRequest = findTag(zap, 'description') if (zapRequest) { const rawEvent: TaggedRawEvent = JSON.parse(zapRequest); if (Array.isArray(rawEvent)) { // old format, ignored - const rawDescriptionTag = rawEvent.find(a => a[0] === 'application/nostr') - const rawDescription = rawDescriptionTag && rawDescriptionTag[1] - const request = typeof rawDescription === 'string' ? JSON.parse(rawDescription) : rawDescription - return request?.pubkey + return { isValid: false }; } - //const metaHash = sha256(zapRequest); + const metaHash = sha256(zapRequest); const ev = new Event(rawEvent) - return ev.PubKey + return { pubkey: ev.PubKey, isValid: dhash === metaHash }; } + return { isValid: false } } interface ParsedZap { @@ -59,7 +63,7 @@ interface ParsedZap { export function parseZap(zap: TaggedRawEvent): ParsedZap { const { amount, hash } = getInvoice(zap) - const zapper = hash && getZapper(zap, hash) + const zapper = hash ? getZapper(zap, hash) : { isValid: false }; const e = findTag(zap, 'e') const p = findTag(zap, 'p')! return { @@ -67,9 +71,9 @@ export function parseZap(zap: TaggedRawEvent): ParsedZap { e, p, amount: Number(amount) / 1000, - zapper, + zapper: zapper.pubkey, content: zap.content, - valid: true, + valid: zapper.isValid, } } @@ -87,12 +91,12 @@ const Zap = ({ zap, showZapped = true }: { zap: ParsedZap, showZapped?: boolean
- +
) : null @@ -102,8 +106,8 @@ interface ZapsSummaryProps { zaps: ParsedZap[] } export const ZapsSummary = ({ zaps }: ZapsSummaryProps) => { const sortedZaps = useMemo(() => { - const pub = [...zaps.filter(z => z.zapper)] - const priv = [...zaps.filter(z => !z.zapper)] + const pub = [...zaps.filter(z => z.zapper)] + const priv = [...zaps.filter(z => !z.zapper)] pub.sort((a, b) => b.amount - a.amount) return pub.concat(priv) }, [zaps]) @@ -127,8 +131,8 @@ export const ZapsSummary = ({ zaps }: ZapsSummaryProps) => {
- {content && ( -