diff options
| author | Adam Soltys <[email protected]> | 2025-02-11 20:07:21 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2025-02-11 20:07:21 -0800 |
| commit | eb3c2aad4cef9b86294bc9a13a36da4918529209 (patch) | |
| tree | 33c58529ee50bf875620a5a33d97e8ab57b673ae | |
| parent | 004ac3ccbfb6a12227a81d6e0dc0beaf9a740827 (diff) | |
| download | coinos-server-eb3c2aad4cef9b86294bc9a13a36da4918529209.tar.gz coinos-server-eb3c2aad4cef9b86294bc9a13a36da4918529209.zip | |
nwc notifications
| -rw-r--r-- | lib/nostr.ts | 8 | ||||
| -rw-r--r-- | lib/notifications.ts | 47 | ||||
| -rw-r--r-- | lib/nwc.ts | 26 | ||||
| -rw-r--r-- | lib/payments.ts | 5 |
4 files changed, 70 insertions, 16 deletions
diff --git a/lib/nostr.ts b/lib/nostr.ts index 9f5a2698..4c28ebc9 100644 --- a/lib/nostr.ts +++ b/lib/nostr.ts @@ -3,6 +3,7 @@ import { db, g, s } from "$lib/db"; import { l, warn } from "$lib/logging"; import { scan } from "$lib/strfry"; import { fail, fields, getUser, pick } from "$lib/utils"; +import { bytesToHex } from "@noble/hashes/utils"; import { finalizeEvent, getPublicKey, nip19, verifyEvent } from "nostr-tools"; import type { Event } from "nostr-tools"; import { AbstractSimplePool } from "nostr-tools/abstract-pool"; @@ -11,10 +12,12 @@ import { Relay } from "nostr-tools/relay"; export const EX = 60 * 60 * 24; const coinos = await Relay.connect(config.nostr); -export const serverPubkey = getPublicKey( +export const serverSecret = bytesToHex( nip19.decode(config.nostrKey).data as Uint8Array, ); +export const serverPubkey = getPublicKey(serverSecret); + const alwaysTrue: any = (t: Event) => { t[Symbol("verified")] = true; return true; @@ -40,7 +43,6 @@ export async function publish(ev, url = config.nostr) { export async function handleZap(invoice, sender = undefined) { try { const pubkey = serverPubkey; - const sk = nip19.decode(config.nostrKey).data as Uint8Array; const zapreq = JSON.parse(invoice.description); if (!zapreq.tags || zapreq.tags.length === 0) { @@ -93,7 +95,7 @@ export async function handleZap(invoice, sender = undefined) { tags.push(["preimage", invoice.payment_preimage]); const ev = { pubkey, kind, created_at, content, tags }; - const signed = await finalizeEvent(ev, sk); + const signed = await finalizeEvent(ev, serverSecret); l("sending receipt"); diff --git a/lib/notifications.ts b/lib/notifications.ts index 8891d875..e988a728 100644 --- a/lib/notifications.ts +++ b/lib/notifications.ts @@ -1,10 +1,13 @@ import config from "$config"; import { db } from "$lib/db"; +import ln from "$lib/ln"; import { err, warn } from "$lib/logging"; import { mail, templates } from "$lib/mail"; import mqtt from "$lib/mqtt"; +import { publish, serverSecret } from "$lib/nostr"; import { emit } from "$lib/sockets"; -import { f, fiat, fmt, link, t } from "$lib/utils"; +import { f, fiat, fmt, getUser, link, nada, t } from "$lib/utils"; +import { finalizeEvent } from "nostr-tools"; import webpush from "web-push"; if (config.vapid) { @@ -74,3 +77,45 @@ export const notify = async (p, user, withdrawal) => { ); } }; + +export const nwcNotify = async (p) => { + try { + const user = await getUser(p.uid); + const pubkeys = await db.sMembers(`${user.id}:apps`); + if (pubkeys.length) { + let payment_hash = ""; + if (p.type === "lightning") ({ payment_hash } = await ln.decode(p.hash)); + for (const pubkey of pubkeys) { + const notification = { + type: p.amount > 0 ? "incoming" : "outgoing", + invoice: p.hash, + description: p.memo, + preimage: p.ref, + payment_hash: payment_hash, + amount: Math.abs(p.amount) * 1000, + fees_paid: (parseInt(p.fee) || 0) * 1000, + created_at: p.created, + settled_at: p.created, + }; + + const content = JSON.stringify({ + notification_type: p.amount > 0 ? "payment_received" : "payment_sent", + notification, + }); + + const unsigned = { + content, + tags: [["p", pubkey]], + kind: 23196, + created_at: Math.floor(Date.now() / 1000), + }; + + const event = finalizeEvent(unsigned, serverSecret); + + publish(event).catch(nada); + } + } + } catch (e) { + warn("nwc notification failed", e.message); + } +}; @@ -22,6 +22,7 @@ const methods = [ "make_invoice", "lookup_invoice", "list_transactions", + "notifications", ]; const week = 7 * 24 * 60 * 60; @@ -31,14 +32,19 @@ export default () => { r.on("open", async (_) => { r.subscribe("nwc", { kinds: [23194], "#p": [serverPubkey] }); - let info: UnsignedEvent = { - created_at: Math.floor(Date.now() / 1000), - kind: 13194, - tags: [["p", serverPubkey]], - pubkey: serverPubkey, - content: methods.join(" "), - }; - info = await finalizeEvent(info, sk); + const info = await finalizeEvent( + { + created_at: Math.floor(Date.now() / 1000), + kind: 13194, + tags: [ + ["p", serverPubkey], + ["notifications", "payment_received payment_sent"], + ], + pubkey: serverPubkey, + content: methods.join(" "), + }, + sk, + ); r.send(["EVENT", info]); }); @@ -314,9 +320,7 @@ const handle = (method, params, ev, app, user) => async list_transactions() { const { from, until, limit = 10, offset = 0, type } = params; - const { pubkey } = app; - const k = pubkey === user.pubkey ? user.id : pubkey; - const payments = await db.lRange(`${k}:payments`, 0, -1); + const payments = await db.lRange(`${user.id}:payments`, 0, -1); let transactions = []; for (const pid of payments) { diff --git a/lib/payments.ts b/lib/payments.ts index c35c3795..a150e50c 100644 --- a/lib/payments.ts +++ b/lib/payments.ts @@ -5,7 +5,7 @@ import { generate } from "$lib/invoices"; import ln from "$lib/ln"; import { err, l, warn } from "$lib/logging"; import { handleZap } from "$lib/nostr"; -import { notify } from "$lib/notifications"; +import { notify, nwcNotify } from "$lib/notifications"; import { SATS, btc, @@ -144,6 +144,7 @@ export const debit = async ({ l(user.username, "sent", type, amount); //emit(user.id, "payment", p); + if (![types.lightning, types.bolt12].includes(type)) nwcNotify(p, user); return p; }; @@ -283,6 +284,7 @@ export const completePayment = async (inv, p, user) => { } } + nwcNotify(p); notify(p, user, withdrawal); l(username, "received", p.type, p.amount); callWebhook(inv, p); @@ -821,6 +823,7 @@ const finalize = async (r, p) => { await db.sRem("pending", p.hash); l("payment completed", p.id, r.payment_preimage); + nwcNotify(p); const maxfee = p.fee; const { amount_msat } = await ln.decode(p.hash); |
