aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/db.js34
-rw-r--r--lib/invoices.js1
-rw-r--r--lib/lightning.js14
-rw-r--r--lib/mail.js14
-rw-r--r--lib/nostr.js88
-rw-r--r--lib/payments.js7
-rw-r--r--lib/register.js1
7 files changed, 146 insertions, 13 deletions
diff --git a/lib/db.js b/lib/db.js
index 197697a0..b790917e 100644
--- a/lib/db.js
+++ b/lib/db.js
@@ -1,7 +1,7 @@
import config from "$config";
-import { sleep } from "$lib/utils";
+import { wait, sleep } from "$lib/utils";
import { createClient, defineScript } from "redis";
-import { warn } from "$lib/logging";
+import { warn, l, err } from "$lib/logging";
let SCRIPT = `
local balanceKey = KEYS[1]
@@ -33,11 +33,35 @@ let debit = defineScript({
transformArguments: (...args) => args.map(a => a.toString())
});
-export const db = createClient({
+export let db = createClient({
url: config.db,
- scripts: { debit }
+ scripts: { debit },
+ socket: {
+ reconnectStrategy: retries => Math.min(retries * 50, 5000)
+ }
+});
+
+
+async function connectWithRetry() {
+ try {
+ await db.connect();
+ } catch (err) {
+ console.error('Failed to connect to Redis, retrying...', err);
+ setTimeout(connectWithRetry, 5000); // Retry after 5 seconds
+ }
+}
+
+connectWithRetry();
+
+db.on("error", e => {
+ if (e.message.startsWith("getaddr")) return;
+ err("Redis error", e.message);
});
-await db.connect();
+
+db.on("end", () => {
+ warn("Redis connection ended");
+});
+
export default db;
export let g = async k => {
diff --git a/lib/invoices.js b/lib/invoices.js
index 8ac49a79..9f2c0dc9 100644
--- a/lib/invoices.js
+++ b/lib/invoices.js
@@ -65,6 +65,7 @@ export let generate = async ({ invoice, user, sender, memo }) => {
currency,
hash,
id,
+ memo,
rate,
pending: 0,
received: 0,
diff --git a/lib/lightning.js b/lib/lightning.js
index 77a429ed..d4664458 100644
--- a/lib/lightning.js
+++ b/lib/lightning.js
@@ -1,4 +1,5 @@
import { emit } from "$lib/sockets";
+import { handleZap } from "$lib/nostr";
import config from "$config";
import { notify } from "$lib/notifications";
import { callWebhook } from "$lib/webhooks";
@@ -8,14 +9,15 @@ import { g, s, t, db } from "$lib/db";
import { credit, types } from "$lib/payments";
export async function listenForLightning() {
- let event = await ln.waitanyinvoice((await g("pay_index")) || 0);
+ let inv = await ln.waitanyinvoice((await g("pay_index")) || 0);
let {
bolt11,
+ description,
pay_index,
status,
amount_received_msat,
payment_preimage: preimage
- } = event;
+ } = inv;
await s("pay_index", pay_index);
setTimeout(listenForLightning);
@@ -31,6 +33,14 @@ export async function listenForLightning() {
let invoice = await g(`invoice:${id}`);
if (!invoice) return warn("received lightning with no invoice", bolt11);
+ if (invoice && invoice.memo) {
+ try {
+ if (JSON.parse(description).kind === 9734) await handleZap(inv);
+ } catch (e) {
+ warn("failed to handle zap", e.message);
+ }
+ }
+
let p = await g(`payment:${bolt11}`);
if (typeof p === "string") p = await g(`payment:${p}`);
diff --git a/lib/mail.js b/lib/mail.js
index b5fda14d..47ef5eac 100644
--- a/lib/mail.js
+++ b/lib/mail.js
@@ -14,12 +14,15 @@ export let templates = {
export let mail = async (user, subject, template, params) => {
try {
+ l("sending mail", user.username, subject);
if (!user.email) return;
- let source = fs.readFileSync(template, "utf8");
- let html = handlebars.compile(source, params);
+ let source = fs.readFileSync(templates[template], "utf8");
+ let html = handlebars.compile(source)(params);
let client = new SESClient({ region: "us-east-2" });
+ return;
+
await client.send(
new SendEmailCommand({
Destination: {
@@ -38,3 +41,10 @@ export let mail = async (user, subject, template, params) => {
err(e.message);
}
};
+
+/*
+mail({ username: "bob", email: "[email protected]" }, "Test", "paymentSent", {
+ username: "bob",
+ link: "https://coinos.io/api/verify/12345"
+});
+*/
diff --git a/lib/nostr.js b/lib/nostr.js
index 604d207d..3a370aec 100644
--- a/lib/nostr.js
+++ b/lib/nostr.js
@@ -1,9 +1,10 @@
import config from "$config";
-import { Relay, RelayPool } from "nostr";
+import { Relay, RelayPool, calculateId, signId, getPublicKey } from "nostr";
import { broadcast, emit } from "$lib/sockets";
import store from "$lib/store";
import { g, s, db } from "$lib/db";
-import { nada, wait } from "$lib/utils";
+import { fail, nada, wait } from "$lib/utils";
+import { nip19 } from "nostr-tools";
export let coinos;
export let pool;
@@ -178,3 +179,86 @@ let getUser = async pubkey => {
}
);
};
+
+function send(ev, url, opts) {
+ let timeout = (opts && opts.timeout != null && opts.timeout) || 1000;
+
+ return new Promise((resolve, reject) => {
+ let relay = Relay(url);
+
+ function timeout_reached() {
+ relay.close();
+ reject(new Error("Request timeout"));
+ }
+
+ let timer = setTimeout(timeout_reached, timeout);
+
+ relay.on("open", () => {
+ clearTimeout(timer);
+ timer = setTimeout(timeout_reached, timeout);
+ relay.send(["EVENT", ev]);
+ });
+
+ relay.on("ok", (evid, ok, msg) => {
+ clearTimeout(timer);
+ relay.close();
+ resolve({ evid, ok, msg });
+ });
+ });
+}
+
+export async function handleZap(invoice) {
+ let { nostrKey: privkey } = config;
+ let pubkey = getPublicKey(privkey);
+ let keypair = { privkey, pubkey };
+ let zapreq = JSON.parse(invoice.description);
+
+ if (!zapreq.tags || zapreq.tags.length === 0) {
+ fail(`No tags found`);
+ }
+
+ let ptags = zapreq.tags.filter(
+ t => t && t.length && t.length >= 2 && t[0] === "p"
+ );
+
+ if (ptags.length !== 1) {
+ fail(`None or multiple p tags found`);
+ }
+
+ let etags = zapreq.tags.filter(
+ t => t && t.length && t.length >= 2 && t[0] === "e"
+ );
+
+ if (!(etags.length === 0 || etags.length === 1)) {
+ fail(`Expected none or 1 e tags`);
+ }
+
+ let relays_tag = zapreq.tags.find(
+ t => t && t.length && t.length >= 2 && t[0] === "relays"
+ );
+
+ if (!relays_tag) {
+ fail(`No relays tag found`);
+ }
+
+ let relays = relays_tag.slice(1).filter(r => r && r.startsWith("ws"));
+ let etag = etags.length > 0 && etags[0];
+ let ptag = ptags[0];
+
+ let kind = 9735;
+ let created_at = invoice.paid_at;
+ let content = zapreq.content;
+
+ let tags = [ptag];
+ if (etag) tags.push(etag);
+
+ tags.push(["bolt11", invoice.bolt11]);
+ tags.push(["description", invoice.description]);
+ tags.push(["preimage", invoice.payment_preimage]);
+
+ let ev = { pubkey, kind, created_at, content, tags };
+ ev.id = await calculateId(ev);
+ ev.sig = await signId(privkey, ev.id);
+
+ await Promise.all(relays.map(r => send(ev, r)));
+}
diff --git a/lib/payments.js b/lib/payments.js
index 9f32f8fb..f2fab1ba 100644
--- a/lib/payments.js
+++ b/lib/payments.js
@@ -113,9 +113,12 @@ export let credit = async (hash, amount, memo, ref, type = types.internal) => {
if (iid && iid.hash) iid = iid.hash;
let invoice = await g(`invoice:${iid}`);
- if (!invoice) return warn("failed to credit", hash, ", invoice not found");
+ if (!invoice) {
+ await db.sAdd("missing", hash);
+ return;
+ }
- let { tip } = invoice;
+ let { tip } = invoice;
tip = parseInt(tip) || 0;
if (!memo) ({ memo } = invoice);
diff --git a/lib/register.js b/lib/register.js
index 798565b9..c52dad03 100644
--- a/lib/register.js
+++ b/lib/register.js
@@ -45,6 +45,7 @@ export default async (user, ip, requireChallenge) => {
user.fiat = false;
user.otpsecret = authenticator.generateSecret();
user.migrated = true;
+ user.locktime = 300;
await s(`user:${id}`, user);
await s(`user:${username}`, id);