From 67a0de3ea5c66242002018fb72bd23c0c55aaf03 Mon Sep 17 00:00:00 2001 From: ekzyis <27162016+ekzyis@users.noreply.github.com> Date: Tue, 8 Aug 2023 20:19:31 +0200 Subject: [PATCH] Notifications with nostr info (#368) * Show zap message and pubkey in notifications + show zap request event in invoice view * enhance ui --------- Co-authored-by: ekzyis Co-authored-by: keyan --- api/resolvers/wallet.js | 5 +++++ api/typeDefs/wallet.js | 1 + components/invoice.js | 27 +++++++++++++++++++++++++- components/lightning-auth.module.css | 2 +- components/notifications.js | 29 +++++++++++++++++++++++++++- fragments/notifications.js | 1 + fragments/wallet.js | 1 + lib/nostr.js | 24 +++++++++++++++++++++++ package-lock.json | 2 +- styles/globals.scss | 4 ++++ 10 files changed, 92 insertions(+), 4 deletions(-) diff --git a/api/resolvers/wallet.js b/api/resolvers/wallet.js index 2936f8ff..d005bdd1 100644 --- a/api/resolvers/wallet.js +++ b/api/resolvers/wallet.js @@ -26,6 +26,11 @@ export async function getInvoice (parent, { id }, { me, models }) { throw new GraphQLError('not ur invoice', { extensions: { code: 'FORBIDDEN' } }) } + try { + inv.nostr = JSON.parse(inv.desc) + } catch (err) { + } + return inv } diff --git a/api/typeDefs/wallet.js b/api/typeDefs/wallet.js index dc894e9b..068a33c3 100644 --- a/api/typeDefs/wallet.js +++ b/api/typeDefs/wallet.js @@ -22,6 +22,7 @@ export default gql` cancelled: Boolean! confirmedAt: Date satsReceived: Int + nostr: JSONObject } type Withdrawl { diff --git a/components/invoice.js b/components/invoice.js index 5e398714..3c81d53a 100644 --- a/components/invoice.js +++ b/components/invoice.js @@ -1,18 +1,43 @@ +import AccordianItem from './accordian-item' import Qr from './qr' export function Invoice ({ invoice }) { let variant = 'default' let status = 'waiting for you' + let webLn = true if (invoice.confirmedAt) { variant = 'confirmed' status = `${invoice.satsReceived} sats deposited` + webLn = false } else if (invoice.cancelled) { variant = 'failed' status = 'cancelled' + webLn = false } else if (invoice.expiresAt <= new Date()) { variant = 'failed' status = 'expired' + webLn = false } - return + const { nostr } = invoice + + return ( + <> + +
+ {nostr + ? + + {JSON.stringify(nostr, null, 2)} + + + } + /> + : null} +
+ + ) } diff --git a/components/lightning-auth.module.css b/components/lightning-auth.module.css index e4eb344b..8cce8ce2 100644 --- a/components/lightning-auth.module.css +++ b/components/lightning-auth.module.css @@ -1,5 +1,5 @@ .login { - justify-content: start; + justify-content: flex-start; align-items: center; display: flex; flex-direction: column; diff --git a/components/notifications.js b/components/notifications.js index 37484712..cd943854 100644 --- a/components/notifications.js +++ b/components/notifications.js @@ -21,6 +21,9 @@ import { useServiceWorker } from './serviceworker' import { Checkbox, Form } from './form' import { useRouter } from 'next/router' import { useData } from './use-data' +import { nostrZapDetails } from '../lib/nostr' +import Text from './text' +import NostrIcon from '../svgs/nostr.svg' function Notification ({ n, fresh }) { const type = n.__typename @@ -30,7 +33,7 @@ function Notification ({ n, fresh }) { { (type === 'Earn' && ) || (type === 'Invitification' && ) || - (type === 'InvoicePaid' && ) || + (type === 'InvoicePaid' && (n.invoice.nostr ? : )) || (type === 'Referral' && ) || (type === 'Streak' && ) || (type === 'Votification' && ) || @@ -187,6 +190,30 @@ function Invitification ({ n }) { ) } +function NostrZap ({ n }) { + const { nostr } = n.invoice + const { npub, content, note } = nostrZapDetails(nostr) + + return ( + <> +
+ {n.earnedSats} sats zap from + + {npub.slice(0, 10)}... + + on {note + ? ( + + {note.slice(0, 12)}... + ) + : 'nostr'} + {timeSince(new Date(n.sortTime))} + {content && {content}} +
+ + ) +} + function InvoicePaid ({ n }) { return (
diff --git a/fragments/notifications.js b/fragments/notifications.js index a8bbfe3b..5416422d 100644 --- a/fragments/notifications.js +++ b/fragments/notifications.js @@ -80,6 +80,7 @@ export const NOTIFICATIONS = gql` earnedSats invoice { id + nostr } } } diff --git a/fragments/wallet.js b/fragments/wallet.js index 62f55d1c..67475c2d 100644 --- a/fragments/wallet.js +++ b/fragments/wallet.js @@ -10,6 +10,7 @@ export const INVOICE = gql` cancelled confirmedAt expiresAt + nostr } }` diff --git a/lib/nostr.js b/lib/nostr.js index e6793d3b..e5cf8995 100644 --- a/lib/nostr.js +++ b/lib/nostr.js @@ -1,3 +1,27 @@ +import { bech32 } from 'bech32' + export const NOSTR_PUBKEY_HEX = /^[0-9a-fA-F]{64}$/ export const NOSTR_PUBKEY_BECH32 = /^npub1[02-9ac-hj-np-z]+$/ export const NOSTR_MAX_RELAY_NUM = 20 +export const NOSTR_ZAPPLE_PAY_NPUB = 'npub1wxl6njlcgygduct7jkgzrvyvd9fylj4pqvll6p32h59wyetm5fxqjchcan' + +export function hexToBech32 (hex, prefix = 'npub') { + return bech32.encode(prefix, bech32.toWords(Buffer.from(hex, 'hex'))) +} + +export function nostrZapDetails (zap) { + let { pubkey, content, tags } = zap + let npub = hexToBech32(pubkey) + if (npub === NOSTR_ZAPPLE_PAY_NPUB) { + const znpub = content.match(/^From: nostr:(npub1[02-9ac-hj-np-z]+)$/)?.[1] + if (znpub) { + npub = znpub + // zapple pay does not support user content + content = null + } + } + const event = tags.filter(t => t?.length >= 2 && t[0] === 'e')?.[0]?.[1] + const note = event ? hexToBech32(event, 'note') : null + + return { npub, content, note } +} diff --git a/package-lock.json b/package-lock.json index 426c0174..8fcfc28c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -74,8 +74,8 @@ "remark-gfm": "^3.0.1", "remove-markdown": "^0.5.0", "sass": "^1.64.1", - "tldts": "^6.0.13", "serviceworker-storage": "^0.1.0", + "tldts": "^6.0.13", "typescript": "^5.1.6", "unist-util-visit": "^5.0.0", "url-unshort": "^6.1.0", diff --git a/styles/globals.scss b/styles/globals.scss index 5c4fc873..9650e5f0 100644 --- a/styles/globals.scss +++ b/styles/globals.scss @@ -608,6 +608,10 @@ div[contenteditable]:focus, fill: var(--theme-grey); } +.fill-nostr { + fill: var(--bs-nostr); +} + .fill-lgrey { fill: #a5a5a5; }