From 1d5053feb4a5f9b2ea477676f437aef5c7fb7c5d Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sat, 19 Oct 2024 08:09:59 -0700 Subject: subscriptions --- lib/mqtt.ts | 3 +- lib/notifications.ts | 89 +++++++++++++++++++++++++++++++++++++--------------- lib/payments.ts | 74 ++++++------------------------------------- lib/utils.ts | 8 +++++ lib/whitelist.ts | 1 + 5 files changed, 83 insertions(+), 92 deletions(-) (limited to 'lib') diff --git a/lib/mqtt.ts b/lib/mqtt.ts index 636dc3e1..4212989d 100644 --- a/lib/mqtt.ts +++ b/lib/mqtt.ts @@ -1,5 +1,4 @@ import mqtt from "mqtt"; import config from "$config"; -export let mqtt1 = mqtt.connect("mqtt://ln.coinos.io", config.mqtt1); -export let mqtt2 = mqtt.connect("mqtt://mqtt.coinos.io", config.mqtt2); +export default mqtt.connect("mqtt://mqtt.coinos.io", config.mqtt); diff --git a/lib/notifications.ts b/lib/notifications.ts index af47a6d7..023fdeff 100644 --- a/lib/notifications.ts +++ b/lib/notifications.ts @@ -1,38 +1,77 @@ +import { emit } from "$lib/sockets"; +import { err, warn } from "$lib/logging"; +import { db } from "$lib/db"; +import mqtt from "$lib/mqtt"; import config from "$config"; +import { mail, templates } from "$lib/mail"; +import { f, fmt, link, fiat, t } from "$lib/utils"; import webpush from "web-push"; if (config.vapid) { webpush.setVapidDetails( - config.vapid.url, - config.vapid.publicKey, - config.vapid.privateKey, + `mailto:${config.support}`, + config.vapid.pk, + config.vapid.sk, ); } -export const notify = async (user, payload) => { - try { - let { subscriptions } = user; +export const notify = async (p, user, withdrawal) => { + emit(user.id, "payment", p); + let { username } = user; + let { paymentReceived } = t(user); - if (subscriptions) { - let i = 0; - for (let subscription of subscriptions) { - i++; - try { - await webpush.sendNotification(subscription, payload); - } catch (e) { - if ( - e.body && - (e.body.includes("expired") || e.body.includes("No such")) - ) { - user.subscriptions.splice(i, 1); - await user.save(); - } else { - err("problem sending notification", e); - } - } - } + try { + if (user.verified && user.notify) { + mail(user, paymentReceived, templates.paymentReceived, { + ...t(user), + username, + payment: { + amount: fmt(p.amount), + link: link(p.id), + tip: p.tip ? fmt(p.tip) : undefined, + fiat: f(fiat(p.amount, p.rate), p.currency), + fiatTip: p.tip ? f(fiat(p.tip, p.rate), p.currency) : undefined, + memo: p.memo, + items: + p.items && + p.items.map((i) => { + return { + quantity: i.quantity, + name: i.name, + total: i.quantity * i.price, + totalFiat: f(i.quantity * i.price, p.currency), + }; + }), + }, + withdrawal, + }); } } catch (e) { - console.log("problem notifying", e); + err("problem emailing", e.message); + } + + let subscriptions = await db.sMembers(`${user.id}:subscriptions`); + + let payload = { + title: paymentReceived, + body: `${fmt(p.amount)} ${f(fiat(p.amount, p.rate), p.currency)}`, + url: `/payment/${p.id}`, + }; + + for (let s of subscriptions) { + webpush + .sendNotification(JSON.parse(s), JSON.stringify(payload)) + .catch((e) => { + warn("sub failed", e.message); + db.sRem(`${user.id}:subscriptions`, s); + }); + } + + if (config.mqtt) { + if (!mqtt.connected) await mqtt.reconnect(); + mqtt.publish( + username, + `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}:${p.memo}:${p.items}`, + ); } }; diff --git a/lib/payments.ts b/lib/payments.ts index cc55e5ce..aeb16aa3 100644 --- a/lib/payments.ts +++ b/lib/payments.ts @@ -6,10 +6,10 @@ import { notify } from "$lib/notifications"; import { db, g, s } from "$lib/db"; import { l, err, warn } from "$lib/logging"; import { - f, btc, fail, - fiat, + fmt, + link, getInvoice, getPayment, getUser, @@ -21,18 +21,14 @@ import { } from "$lib/utils"; import { callWebhook } from "$lib/webhooks"; import got from "got"; -import { mqtt1, mqtt2 } from "$lib/mqtt"; -import { mail, templates } from "$lib/mail"; import api from "$lib/api"; import { bech32 } from "bech32"; import rpc from "@coinos/rpc"; - import ln from "$lib/ln"; let bc = rpc(config.bitcoin); let lq = rpc(config.liquid); -let { URL } = process.env; let dust = 547; export let types = { @@ -228,49 +224,18 @@ export let credit = async ({ .incrBy(`${balanceKey}:${aid || uid}`, amount) .exec(); - emit(uid, "payment", p); - notify(user, `Received ${amount} sats`); - callWebhook(inv, p); - l(username, "received", type, amount); - - let items; if (inv.items && inv.items.length) { formatReceipt(inv.items, inv.currency); p.items = inv.items; } - if (config.mqtt1) { - if (!mqtt1.connected) await mqtt1.reconnect(); - mqtt1.publish( - username, - `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}:${p.memo}:${items}`, - ); - } - - if (config.mqtt2) { - if (!mqtt2.connected) await mqtt2.reconnect(); - mqtt2.publish( - username, - `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}:${p.memo}:${items}`, - ); - } - - try { - await completePayment(p, user); - } catch (e) { - console.log(e); - } + await completePayment(inv, p, user); return p; }; -let fmt = (sats) => - "⚡️" + - new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(sats); - -export let completePayment = async (p, user) => { +export let completePayment = async (inv, p, user) => { let { id, autowithdraw, threshold, reserve, destination, username } = user; - let link = (id) => `${URL}/payment/${id}`; if (p.confirmed) { let withdrawal; if (autowithdraw) { @@ -292,33 +257,12 @@ export let completePayment = async (p, user) => { } } - let { paymentReceived } = t(user); - if (user.verified && user.notify) { - mail(user, paymentReceived, templates.paymentReceived, { - ...t(user), - username, - payment: { - amount: fmt(p.amount), - link: link(p.id), - tip: p.tip ? fmt(p.tip) : undefined, - fiat: f(fiat(p.amount, p.rate), p.currency), - fiatTip: p.tip ? f(fiat(p.tip, p.rate), p.currency) : undefined, - memo: p.memo, - items: - p.items && - p.items.map((i) => { - return { - quantity: i.quantity, - name: i.name, - total: i.quantity * i.price, - totalFiat: f(i.quantity * i.price, p.currency), - }; - }), - }, - withdrawal, - }); - } + notify(p, user, withdrawal); } + + l(username, "received", p.type, p.amount); + emit(user.id, "payment", p); + callWebhook(inv, p); }; let pay = async ({ aid = undefined, amount, to, user }) => { diff --git a/lib/utils.ts b/lib/utils.ts index b7f55ff1..fa10fb81 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -5,6 +5,8 @@ import { g, s } from "$lib/db"; import config from "$config"; import { getPublicKey } from "nostr"; + let { URL } = process.env; + export let fail = (msg) => { throw new Error(msg); }; @@ -180,3 +182,9 @@ export let time = (() => { console.timeLog("", ++count, s); }; })(); + +export let fmt = (sats) => + "⚡️" + + new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(sats); + +export let link = (id) => `${URL}/payment/${id}`; diff --git a/lib/whitelist.ts b/lib/whitelist.ts index 54d3aafc..b0975d2c 100644 --- a/lib/whitelist.ts +++ b/lib/whitelist.ts @@ -33,6 +33,7 @@ export default [ "profile", "prompt", "pubkey", + "push", "reserve", "salt", "seed", -- cgit v1.2.3 From 985230492f0c2543952bbac01378c033cced7d55 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sat, 19 Oct 2024 21:39:57 -0700 Subject: de-dupe locations --- lib/locations.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/locations.ts b/lib/locations.ts index 560d5259..11981c19 100644 --- a/lib/locations.ts +++ b/lib/locations.ts @@ -1,7 +1,7 @@ import { err } from "$lib/logging"; import got from "got"; import { g, s } from "$lib/db"; -import { fields, pick } from "$lib/utils"; +import { fields, pick, uniq } from "$lib/utils"; export let getLocations = async () => { try { @@ -40,7 +40,10 @@ export let getLocations = async () => { locations.push(...previous); - await s("locations", locations); + await s( + "locations", + uniq(locations, (l) => l.id), + ); await s("locations:since", new Date().toISOString().split(".")[0] + "Z"); } catch (e) { err("problem fetching locations", e); -- cgit v1.2.3 From ab552d882ba9c0d2c99e104c79b2ce854c4d1da8 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sat, 19 Oct 2024 22:02:28 -0700 Subject: fix reverse --- lib/db.ts | 2 +- lib/payments.ts | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/db.ts b/lib/db.ts index 50c78e85..053ecefb 100644 --- a/lib/db.ts +++ b/lib/db.ts @@ -38,7 +38,7 @@ local pid = KEYS[6] local total = tonumber(ARGV[1]) local credit = tonumber(ARGV[2]) -local payment = cjson.decode(redis.call('get', paymentKey)) +local payment = redis.call('get', paymentKey) if payment ~= nil then redis.call('incrby', balanceKey, total) diff --git a/lib/payments.ts b/lib/payments.ts index aeb16aa3..b01c926c 100644 --- a/lib/payments.ts +++ b/lib/payments.ts @@ -1,6 +1,5 @@ import config from "$config"; import { generate } from "$lib/invoices"; -import { emit } from "$lib/sockets"; import { v4 } from "uuid"; import { notify } from "$lib/notifications"; import { db, g, s } from "$lib/db"; @@ -257,11 +256,10 @@ export let completePayment = async (inv, p, user) => { } } - notify(p, user, withdrawal); } + notify(p, user, withdrawal); l(username, "received", p.type, p.amount); - emit(user.id, "payment", p); callWebhook(inv, p); }; @@ -486,7 +484,6 @@ export let sendLightning = async ({ console.log("failed to process payment", e, p); } } catch (e) { - await reverse(p); throw e; } -- cgit v1.2.3 From 5aec744dc98bb73e3d2cf8a2d9d4862269efc195 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sun, 20 Oct 2024 18:40:39 +0000 Subject: move variable declaration --- lib/payments.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/payments.ts b/lib/payments.ts index b01c926c..cb66bd48 100644 --- a/lib/payments.ts +++ b/lib/payments.ts @@ -235,8 +235,8 @@ export let credit = async ({ export let completePayment = async (inv, p, user) => { let { id, autowithdraw, threshold, reserve, destination, username } = user; + let withdrawal; if (p.confirmed) { - let withdrawal; if (autowithdraw) { try { let to = destination.trim(); -- cgit v1.2.3 From bab9ba9bec1d646cc228168d7238c1cd232fd180 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Tue, 22 Oct 2024 11:47:34 -0700 Subject: increase rate limit --- lib/app.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/app.ts b/lib/app.ts index 51b43d05..3b31a923 100644 --- a/lib/app.ts +++ b/lib/app.ts @@ -46,7 +46,7 @@ app.addHook("onResponse", async (req, reply) => { app.register(fastifyRateLimit, { allowList: (req) => req.raw.url?.includes("public"), - max: 100, + max: 500, timeWindow: 2000, keyGenerator: (req) => req.headers["cf-connecting-ip"] as string, errorResponseBuilder: () => { -- cgit v1.2.3 From 4a8fab2201253c53483ef33e6418967129c99463 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Wed, 23 Oct 2024 17:10:09 +0000 Subject: log invalid type --- lib/invoices.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/invoices.ts b/lib/invoices.ts index a17288ca..f2653d74 100644 --- a/lib/invoices.ts +++ b/lib/invoices.ts @@ -91,7 +91,7 @@ export let generate = async ({ invoice, user }) => { } else if (type === types.internal) { hash = id; } else { - fail("unrecognized type"); + fail("unrecognized type", type); } invoice = { -- cgit v1.2.3 From 11a7c49eec78f088c83c5a2ff467d7afe0e55f29 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Wed, 23 Oct 2024 08:27:25 -0700 Subject: lint --- lib/payments.ts | 233 +++++++++++++++++++++++++++-------------------------- lib/utils.ts | 81 ++++++++++--------- routes/payments.ts | 178 ++++++++++++++++++++-------------------- 3 files changed, 247 insertions(+), 245 deletions(-) (limited to 'lib') diff --git a/lib/payments.ts b/lib/payments.ts index cb66bd48..1b865046 100644 --- a/lib/payments.ts +++ b/lib/payments.ts @@ -1,36 +1,36 @@ import config from "$config"; +import api from "$lib/api"; +import { db, g, s } from "$lib/db"; import { generate } from "$lib/invoices"; -import { v4 } from "uuid"; +import ln from "$lib/ln"; +import { err, l, warn } from "$lib/logging"; import { notify } from "$lib/notifications"; -import { db, g, s } from "$lib/db"; -import { l, err, warn } from "$lib/logging"; import { + SATS, btc, fail, fmt, - link, + formatReceipt, getInvoice, getPayment, getUser, - sleep, - SATS, + link, sats, - formatReceipt, + sleep, t, } from "$lib/utils"; import { callWebhook } from "$lib/webhooks"; -import got from "got"; -import api from "$lib/api"; -import { bech32 } from "bech32"; import rpc from "@coinos/rpc"; -import ln from "$lib/ln"; +import { bech32 } from "bech32"; +import got from "got"; +import { v4 } from "uuid"; -let bc = rpc(config.bitcoin); -let lq = rpc(config.liquid); +const bc = rpc(config.bitcoin); +const lq = rpc(config.liquid); -let dust = 547; +const dust = 547; -export let types = { +export const types = { internal: "internal", bitcoin: "bitcoin", lightning: "lightning", @@ -40,7 +40,7 @@ export let types = { reconcile: "reconcile", }; -export let debit = async ({ +export const debit = async ({ aid = undefined, hash, amount, @@ -56,12 +56,12 @@ export let debit = async ({ // if (!user.unlimited && amount > 1000000) // fail(`⚡️${amount} exceeds max withdrawal of ⚡️1,000,000`); let ref; - let { id: uid, currency } = user; + const { id: uid, currency } = user; - let rates = await g("rates"); + const rates = await g("rates"); if (!rate) rate = rates[currency]; - let invoice = await getInvoice(hash); + const invoice = await getInvoice(hash); let iid; if (invoice) { @@ -70,7 +70,7 @@ export let debit = async ({ ref = invoice.uid; - let equivalentRate = + const equivalentRate = invoice.rate * (rates[currency] / rates[invoice.currency]); if (Math.abs(invoice.rate / rates[invoice.currency] - 1) < 0.01) { @@ -80,7 +80,7 @@ export let debit = async ({ } } - let tip = parseInt(invoice?.tip) || null; + const tip = parseInt(invoice?.tip) || null; if (tip < 0) fail("Invalid tip"); if (!amount || amount < 0) fail("Amount must be greater than zero"); @@ -104,8 +104,8 @@ export let debit = async ({ if (ourfee.err) fail(ourfee.err); - let id = v4(); - let p = { + const id = v4(); + const p = { id, aid, amount: -amount, @@ -135,7 +135,7 @@ export let debit = async ({ return p; }; -export let credit = async ({ +export const credit = async ({ hash, amount, memo = "", @@ -145,7 +145,7 @@ export let credit = async ({ }) => { amount = parseInt(amount) || 0; - let inv = await getInvoice(hash); + const inv = await getInvoice(hash); if (!inv) { await db.sAdd("missing", ref.split(":")[0]); return; @@ -159,16 +159,16 @@ export let credit = async ({ if (amount < 0 || tip < 0) fail("Invalid amount"); if (type === types.internal) amount += tip; - let user = await getUser(inv.uid); - let { id: uid, currency, username } = user; + const user = await getUser(inv.uid); + const { id: uid, currency, username } = user; - let rates = await g("rates"); + const rates = await g("rates"); let rate = rates[currency]; if (!rate) await sleep(1000); rate = rates[currency]; - let equivalentRate = inv.rate * (rates[currency] / rates[inv.currency]); + const equivalentRate = inv.rate * (rates[currency] / rates[inv.currency]); if (Math.abs(inv.rate / rates[inv.currency] - 1) < 0.01) { rate = equivalentRate; @@ -176,8 +176,8 @@ export let credit = async ({ // warn("rate slipped", hash, invoice.rate, equivalentRate); } - let id = v4(); - let p = { + const id = v4(); + const p = { aid, id, iid: inv.id, @@ -204,7 +204,7 @@ export let credit = async ({ let balanceKey = "balance"; if ([types.bitcoin, types.liquid].includes(type)) { - let [txid, vout] = ref.split(":").slice(-2); + const [txid, vout] = ref.split(":").slice(-2); p.confirmed = false; balanceKey = "pending"; await s(`payment:${txid}:${vout}`, id); @@ -212,7 +212,7 @@ export let credit = async ({ await s(`payment:${hash}`, id); } - let m = await db.multi(); + const m = await db.multi(); if ([types.bitcoin, types.liquid, types.lightning].includes(type)) m.incrBy(`credit:${type}:${uid}`, Math.round(amount * config.fee)); @@ -223,7 +223,7 @@ export let credit = async ({ .incrBy(`${balanceKey}:${aid || uid}`, amount) .exec(); - if (inv.items && inv.items.length) { + if (inv.items?.length) { formatReceipt(inv.items, inv.currency); p.items = inv.items; } @@ -233,18 +233,18 @@ export let credit = async ({ return p; }; -export let completePayment = async (inv, p, user) => { - let { id, autowithdraw, threshold, reserve, destination, username } = user; +export const completePayment = async (inv, p, user) => { + const { id, autowithdraw, threshold, reserve, destination, username } = user; let withdrawal; if (p.confirmed) { if (autowithdraw) { try { - let to = destination.trim(); - let balance = await g(`balance:${id}`); - let amount = balance - reserve; + const to = destination.trim(); + const balance = await g(`balance:${id}`); + const amount = balance - reserve; if (balance > threshold) { l("initiating autowithdrawal", amount, to, balance, threshold); - let w = await pay({ amount, to, user }); + const w = await pay({ amount, to, user }); withdrawal = { amount: fmt(-w.amount), link: link(w.id), @@ -255,7 +255,6 @@ export let completePayment = async (inv, p, user) => { warn(username, "autowithdraw failed", e.message); } } - } notify(p, user, withdrawal); @@ -263,12 +262,13 @@ export let completePayment = async (inv, p, user) => { callWebhook(inv, p); }; -let pay = async ({ aid = undefined, amount, to, user }) => { +const pay = async ({ aid = undefined, amount, to, user }) => { if (!aid) aid = user.id; amount = parseInt(amount) || 0; - let lnurl, pr; + let lnurl; + let pr; if (to.includes("@") && to.includes(".")) { - let [name, domain] = to.split("@"); + const [name, domain] = to.split("@"); lnurl = `https://${domain}/.well-known/lnurlp/${name}`; } else if (to.startsWith("lnurl")) { lnurl = Buffer.from( @@ -276,10 +276,10 @@ let pay = async ({ aid = undefined, amount, to, user }) => { ).toString(); } - let maxfee = getMaxFee(amount); + const maxfee = getMaxFee(amount); if (lnurl) { amount -= maxfee; - let { callback } = (await got(lnurl).json()) as any; + const { callback } = (await got(lnurl).json()) as any; ({ pr } = (await got(`${callback}?amount=${amount * 1000}`).json()) as any); } else if (to.startsWith("ln")) { amount -= maxfee; @@ -291,8 +291,9 @@ let pay = async ({ aid = undefined, amount, to, user }) => { : await sendOnchain({ aid, amount, address: to, user, subtract: true }); }; -export let decode = async (hex) => { - let type, tx; +export const decode = async (hex) => { + let type; + let tx; try { tx = await bc.decodeRawTransaction(hex); type = types.bitcoin; @@ -309,14 +310,14 @@ export let decode = async (hex) => { return { tx, type }; }; -let inflight = {}; -export let sendOnchain = async (params) => { +const inflight = {}; +export const sendOnchain = async (params) => { let { aid, hex, rate, user, signed } = params; if (!aid) aid = user.id; if (!hex) ({ hex } = await build(params)); - let { tx, type } = await decode(hex); - let node = + const { tx, type } = await decode(hex); + const node = aid === user.id ? rpc(config[type]) : rpc({ ...config[type], wallet: aid }); let { txid } = tx; @@ -335,7 +336,7 @@ export let sendOnchain = async (params) => { ({ txid } = await node.decodeRawTransaction(hex)); - let r = await node.testMempoolAccept([hex]); + const r = await node.testMempoolAccept([hex]); if (!r[0].allowed) fail("transaction rejected"); let total = 0; @@ -343,7 +344,7 @@ export let sendOnchain = async (params) => { let change = 0; if (type === types.liquid) { - for (let { + for (const { asset, scriptPubKey: { address, type }, value, @@ -362,18 +363,18 @@ export let sendOnchain = async (params) => { } } else { let totalIn = 0; - for await (let { txid, vout } of tx.vin) { - let hex = await node.getRawTransaction(txid); - let tx = await node.decodeRawTransaction(hex); + for await (const { txid, vout } of tx.vin) { + const hex = await node.getRawTransaction(txid); + const tx = await node.decodeRawTransaction(hex); totalIn += sats(tx.vout[vout].value); } - for (let { + for (const { scriptPubKey: { address }, value, } of tx.vout) { total += sats(value); - let invoice = await g(`invoice:${address}`); + const invoice = await g(`invoice:${address}`); if (invoice?.aid === aid) fail("Cannot send to internal address"); if ((await node.getAddressInfo(address)).ismine) { @@ -384,9 +385,9 @@ export let sendOnchain = async (params) => { fee = totalIn - total; } - let amount = total - change; + const amount = total - change; - let p = await debit({ + const p = await debit({ aid, hash: txid, amount, @@ -409,7 +410,7 @@ export let sendOnchain = async (params) => { } }; -let getMaxFee = (n) => +const getMaxFee = (n) => Math.round( n < 100 ? n * 5 @@ -424,7 +425,7 @@ let getMaxFee = (n) => : n * 0.01, ); -export let sendLightning = async ({ +export const sendLightning = async ({ user, pr, amount, @@ -439,15 +440,15 @@ export let sendLightning = async ({ } let total = amount; - let decoded = await ln.decode(pr); - let { amount_msat } = decoded; + const decoded = await ln.decode(pr); + const { amount_msat } = decoded; if (amount_msat) total = Math.round(amount_msat / 1000); maxfee = parseInt(maxfee) || getMaxFee(total); if (maxfee < 0) fail("Max fee cannot be negative"); - let { pays } = await ln.listpays(pr); + const { pays } = await ln.listpays(pr); if (pays.find((p) => p.status === "complete")) fail("Invoice has already been paid"); @@ -490,7 +491,7 @@ export let sendLightning = async ({ return p; }; -export let sendInternal = async ({ +export const sendInternal = async ({ amount, invoice = undefined, recipient, @@ -502,15 +503,15 @@ export let sendInternal = async ({ user: recipient, }); - let { hash } = invoice; + const { hash } = invoice; let memo; - let p = await debit({ hash, amount, memo, user: sender }); + const p = await debit({ hash, amount, memo, user: sender }); await credit({ hash, amount, memo, ref: sender.id }); return p; }; -let getAddressType = async (a) => { +const getAddressType = async (a) => { try { await bc.getAddressInfo(a); return types.bitcoin; @@ -524,7 +525,7 @@ let getAddressType = async (a) => { } }; -export let build = async ({ +export const build = async ({ aid, amount, address, @@ -532,15 +533,15 @@ export let build = async ({ subtract, user, }) => { - let type = await getAddressType(address); + const type = await getAddressType(address); if (!aid) aid = user.id; - let node = + const node = aid === user.id ? rpc(config[type]) : rpc({ ...config[type], wallet: aid }); amount = parseInt(amount); if (amount < 0) fail("invalid amount"); - let fees: any = await fetch(`${api[type]}/fees/recommended`).then((r) => + const fees: any = await fetch(`${api[type]}/fees/recommended`).then((r) => r.json(), ); @@ -550,7 +551,7 @@ export let build = async ({ if (feeRate < fees.economyFee) fail("fee rate too low"); - let replaceable = false; + const replaceable = false; let outs = [{ [address]: btc(amount) }]; @@ -575,10 +576,10 @@ export let build = async ({ else throw e; } - let balance = await g(`balance:${aid}`); + const balance = await g(`balance:${aid}`); let ourfee = Math.round(amount * config.fee); - let credit = await g(`credit:${type}:${aid}`); - let covered = Math.min(credit, ourfee); + const credit = await g(`credit:${type}:${aid}`); + const covered = Math.min(credit, ourfee); ourfee -= covered; if (aid) ourfee = 0; @@ -600,16 +601,16 @@ export let build = async ({ fee = sats(tx.fee); } - let inputs = []; - let { vin } = await node.decodeRawTransaction(tx.hex); + const inputs = []; + const { vin } = await node.decodeRawTransaction(tx.hex); - for (let { txid, vout } of vin) { - let rawTx = await node.getRawTransaction(txid); - let tx = await node.decodeRawTransaction(rawTx); - let prevOutput = tx.vout[vout]; - let { address } = prevOutput.scriptPubKey; - let { hdkeypath: path } = await node.getAddressInfo(address); - let witnessUtxo = { + for (const { txid, vout } of vin) { + const rawTx = await node.getRawTransaction(txid); + const tx = await node.decodeRawTransaction(rawTx); + const prevOutput = tx.vout[vout]; + const { address } = prevOutput.scriptPubKey; + const { hdkeypath: path } = await node.getAddressInfo(address); + const witnessUtxo = { amount: Math.round(prevOutput.value * SATS), script: prevOutput.scriptPubKey.hex, }; @@ -619,10 +620,10 @@ export let build = async ({ return { feeRate, ourfee, fee, fees, hex: tx.hex, inputs }; }; -export let catchUp = async () => { +export const catchUp = async () => { try { - let txns = []; - for (let [type, n] of Object.entries({ bitcoin: bc, liquid: lq })) { + const txns = []; + for (const [type, n] of Object.entries({ bitcoin: bc, liquid: lq })) { txns.push( ...(await n.listTransactions("*", 10)).filter((tx) => { tx.type = type; @@ -631,7 +632,7 @@ export let catchUp = async () => { ); } - for (let { txid, type } of txns) { + for (const { txid, type } of txns) { try { if (await db.zScore("seen", txid)) continue; await got.post(`http://localhost:${process.env.PORT || 3119}/confirm`, { @@ -652,33 +653,33 @@ export let catchUp = async () => { setTimeout(catchUp, 10000); }; -export let reconcile = async (account, initial = false) => { +export const reconcile = async (account, initial = false) => { try { - let { descriptors, id, uid, type } = account; - let user = await getUser(uid); - let node = rpc({ ...config[type], wallet: id }); + const { descriptors, id, uid, type } = account; + const user = await getUser(uid); + const node = rpc({ ...config[type], wallet: id }); let total; if (initial) { - let progress = await node.scanTxOutSet("status"); + const progress = await node.scanTxOutSet("status"); if (progress) return setTimeout(() => reconcile(account, initial), 1000); - let { total_amount } = await node.scanTxOutSet("start", descriptors); + const { total_amount } = await node.scanTxOutSet("start", descriptors); total = Math.round(total_amount * SATS); } else { total = Math.round((await node.getBalance({ minconf: 1 })) * SATS); } - let { balanceAdjustment: memo } = t(user); + const { balanceAdjustment: memo } = t(user); - let balance = await g(`balance:${id}`); + const balance = await g(`balance:${id}`); - let amount = Math.abs(total - balance); - let hash = v4(); + const amount = Math.abs(total - balance); + const hash = v4(); if (total > balance) { - let inv = { + const inv = { memo, type: types.reconcile, hash, @@ -711,17 +712,17 @@ export let reconcile = async (account, initial = false) => { } }; -export let check = async () => { +export const check = async () => { try { - let payments = await db.sMembers("pending"); + const payments = await db.sMembers("pending"); - for (let pr of payments) { - let p = await getPayment(pr); + for (const pr of payments) { + const p = await getPayment(pr); if (!p) continue; - let { pays } = await ln.listpays(pr); + const { pays } = await ln.listpays(pr); - let failed = !pays.length || pays.every((p) => p.status === "failed"); - let completed = pays.find((p) => p.status === "complete"); + const failed = !pays.length || pays.every((p) => p.status === "failed"); + const completed = pays.find((p) => p.status === "complete"); if (completed) await finalize(completed, p); else if (failed) await reverse(p); @@ -733,11 +734,11 @@ export let check = async () => { setTimeout(check, 2000); }; -let finalize = async (r, p) => { +const finalize = async (r, p) => { await db.sRem("pending", p.hash); l("payment completed", p.id, r.payment_preimage); - let maxfee = p.fee; + const maxfee = p.fee; p.fee = Math.round((r.amount_sent_msat - r.amount_msat) / 1000); p.ref = r.payment_preimage; @@ -749,12 +750,12 @@ let finalize = async (r, p) => { return p; }; -let reverse = async (p) => { - let total = Math.abs(p.amount) + p.fee; - let ourfee = p.ourfee || 0; - let credit = Math.round(total * config.fee) - ourfee; +const reverse = async (p) => { + const total = Math.abs(p.amount) + p.fee; + const ourfee = p.ourfee || 0; + const credit = Math.round(total * config.fee) - ourfee; - let k = await db.reverse( + const k = await db.reverse( `payment:${p.id}`, `balance:${p.uid}`, `credit:${types.lightning}:${p.uid}`, diff --git a/lib/utils.ts b/lib/utils.ts index fa10fb81..b1ff862b 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,44 +1,44 @@ +import config from "$config"; +import { g, s } from "$lib/db"; import locales from "$lib/locales/index"; -import { randomBytes, bytesToHex } from "@noble/hashes/utils"; import migrate from "$lib/migrate"; -import { g, s } from "$lib/db"; -import config from "$config"; +import { bytesToHex, randomBytes } from "@noble/hashes/utils"; import { getPublicKey } from "nostr"; - let { URL } = process.env; +const { URL } = process.env; -export let fail = (msg) => { +export const fail = (msg) => { throw new Error(msg); }; -export let nada = () => {}; +export const nada = () => {}; -export let sleep = (n) => new Promise((r) => setTimeout(r, n)); -export let wait = async (f, s = 300, n = 50) => { +export const sleep = (n) => new Promise((r) => setTimeout(r, n)); +export const wait = async (f, s = 300, n = 50) => { let i = 0; while (!(await f()) && i < s) (await sleep(n)) && i++; if (i >= s) fail("timeout"); return f(); }; -export let prod = process.env.NODE_ENV === "production"; +export const prod = process.env.NODE_ENV === "production"; -export let bail = (res, msg) => res.code(500).send(msg); +export const bail = (res, msg) => res.code(500).send(msg); -export let SATS = 100000000; -export let sats = (n) => Math.round(n * SATS); -export let btc = (n) => parseFloat((n / SATS).toFixed(8)); -export let fiat = (n, r) => (n * r) / SATS; +export const SATS = 100000000; +export const sats = (n) => Math.round(n * SATS); +export const btc = (n) => parseFloat((n / SATS).toFixed(8)); +export const fiat = (n, r) => (n * r) / SATS; -export let uniq = (a, k) => [...new Map(a.map((x) => [k(x), x])).values()]; -export let pick = (O, K) => +export const uniq = (a, k) => [...new Map(a.map((x) => [k(x), x])).values()]; +export const pick = (O, K) => K.reduce((o, k) => (typeof O[k] !== "undefined" && (o[k] = O[k]), o), {}); -export let bip21 = (address, { amount, memo, tip, type }) => { +export const bip21 = (address, { amount, memo, tip, type }) => { if (!(amount || memo)) return address; - let network = { liquid: "liquidnetwork", bitcoin: "bitcoin" }[type]; - let url = new URLSearchParams(); + const network = { liquid: "liquidnetwork", bitcoin: "bitcoin" }[type]; + const url = new URLSearchParams(); if (amount) { url.append("amount", btc(amount + tip).toFixed(8)); @@ -50,7 +50,7 @@ export let bip21 = (address, { amount, memo, tip, type }) => { return `${network}:${address}?${url.toString()}`; }; -export let fields = [ +export const fields = [ "cipher", "pubkey", "password", @@ -70,9 +70,9 @@ export let fields = [ "banner", ]; -export let getUser = async (username) => { +export const getUser = async (username) => { if (username === "undefined") fail("invalid user"); - let user = await migrate(username); + const user = await migrate(username); if (user && !user.anon && !user.nwc) { user.nwc = bytesToHex(randomBytes(32)); await s(getPublicKey(user.nwc), user.id); @@ -82,20 +82,20 @@ export let getUser = async (username) => { return user; }; -export let getInvoice = async (hash) => { +export const getInvoice = async (hash) => { let iid = await g(`invoice:${hash}`); - if (iid && iid.id) iid = iid.id; - else if (iid && iid.hash) iid = iid.hash; + if (iid?.id) iid = iid.id; + else if (iid?.hash) iid = iid.hash; return await g(`invoice:${iid}`); }; -export let getPayment = async (id) => { +export const getPayment = async (id) => { let p = await g(`payment:${id}`); if (typeof p === "string") p = await g(`payment:${p}`); return p; }; -export let f = (s, currency) => +export const f = (s, currency) => new Intl.NumberFormat("en-US", { style: "currency", currency, @@ -108,14 +108,14 @@ export function formatReceipt(items, currency) { const words = text.split(" "); const lines = []; let currentLine = ""; - words.forEach((word) => { + for (const word of words) { if (currentLine.length + word.length + 1 > maxWidth) { lines.push(currentLine); currentLine = word; } else { currentLine += (currentLine.length > 0 ? " " : "") + word; } - }); + } if (currentLine) lines.push(currentLine); return lines; } @@ -124,7 +124,7 @@ export function formatReceipt(items, currency) { let maxQuantityLength = 0; let maxPriceLength = 0; - items.forEach((item) => { + for (const item of items) { const quantityLength = String(item.quantity).length; if (quantityLength > maxQuantityLength) { maxQuantityLength = quantityLength; @@ -134,7 +134,7 @@ export function formatReceipt(items, currency) { if (priceLength > maxPriceLength) { maxPriceLength = priceLength; } - }); + } // Add padding to the widths for aesthetic spacing return { @@ -156,7 +156,7 @@ export function formatReceipt(items, currency) { const nameLines = wrapText(item.name, nameColumnWidth); // Construct the full line(s) with the first line including the price - let fullLines = [ + const fullLines = [ `${quantityStr}${nameLines[0].padEnd(nameColumnWidth)}${priceStr}`, ]; // Add any additional name lines, properly indented @@ -169,11 +169,11 @@ export function formatReceipt(items, currency) { .join("\n"); } -export let t = ({ language = "en" }) => locales[language]; +export const t = ({ language = "en" }) => locales[language]; -export let time = (() => { - let count = 0, - started = false; +export const time = (() => { + let count = 0; + let started = false; return (s = "") => { if (!started) { console.time(""); @@ -183,8 +183,9 @@ export let time = (() => { }; })(); -export let fmt = (sats) => - "⚡️" + - new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(sats); +export const fmt = (sats) => + `⚡️${new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format( + sats, + )}`; -export let link = (id) => `${URL}/payment/${id}`; +export const link = (id) => `${URL}/payment/${id}`; diff --git a/routes/payments.ts b/routes/payments.ts index f69123ac..4573ac4f 100644 --- a/routes/payments.ts +++ b/routes/payments.ts @@ -1,33 +1,33 @@ import config from "$config"; -import { emit } from "$lib/sockets"; -import { v4 } from "uuid"; +import api from "$lib/api"; +import { requirePin } from "$lib/auth"; import { db, g, s, t } from "$lib/db"; -import { l, err, warn } from "$lib/logging"; +import { err, l, warn } from "$lib/logging"; +import mqtt from "$lib/mqtt"; +import { + build, + completePayment, + credit, + debit, + decode, + sendInternal, + sendLightning, + sendOnchain, + types, +} from "$lib/payments"; +import { emit } from "$lib/sockets"; import { + SATS, bail, fail, getInvoice, getPayment, getUser, sats, - SATS, } from "$lib/utils"; -import { requirePin } from "$lib/auth"; -import { - completePayment, - decode, - debit, - credit, - types, - build, - sendInternal, - sendLightning, - sendOnchain, -} from "$lib/payments"; -import mqtt from "$lib/mqtt"; -import got from "got"; -import api from "$lib/api"; import rpc from "@coinos/rpc"; +import got from "got"; +import { v4 } from "uuid"; import ln from "$lib/ln"; @@ -37,16 +37,16 @@ export default { }, async create(req, res) { - let { body, user } = req; + const { body, user } = req; let { amount, hash, maxfee, fund, memo, payreq } = body; - let balance = await g(`balance:${user.id}`); + const balance = await g(`balance:${user.id}`); try { if (await g("freeze")) fail("Problem sending payment"); if (typeof amount !== "undefined") { amount = parseInt(amount); - if (amount < 0 || amount > SATS || isNaN(amount)) + if (amount < 0 || amount > SATS || Number.isNaN(amount)) fail("Invalid amount"); } @@ -92,7 +92,7 @@ export default { payments = ( await Promise.all( payments.map(async (pid) => { - let p = await g(`payment:${pid}`); + const p = await g(`payment:${pid}`); if (!p) { warn("user", id, "missing payment", pid); await db.lRem(`${aid || id}:payments`, 0, pid); @@ -107,9 +107,9 @@ export default { .filter((p) => p) .sort((a, b) => b.created - a.created); - let count = payments.length; + const count = payments.length; - let fn = (a, b) => ({ + const fn = (a, b) => ({ ...a, [b.currency]: { tips: (a[b.currency] ? a[b.currency].tips : 0) + (b.tip || 0), @@ -135,8 +135,8 @@ export default { }, }); - let incoming = payments.filter((p: any) => p.amount > 0).reduce(fn, {}); - let outgoing = payments.filter((p: any) => p.amount < 0).reduce(fn, {}); + const incoming = payments.filter((p: any) => p.amount > 0).reduce(fn, {}); + const outgoing = payments.filter((p: any) => p.amount < 0).reduce(fn, {}); if (limit) payments = payments.slice(offset, offset + limit); @@ -145,10 +145,10 @@ export default { async get(req, res) { try { - let { + const { params: { hash }, } = req; - let p = await getPayment(hash); + const p = await getPayment(hash); if (p.type === types.internal) p.with = await g(`user:${p.ref}`); res.send(p); } catch (e) { @@ -158,14 +158,14 @@ export default { }, async parse(req, res) { - let { + const { body: { payreq }, user, } = req; try { - let hour = 1000 * 60 * 60; + const hour = 1000 * 60 * 60; let nodes = await g("nodes"); - let { last } = nodes || {}; + const { last } = nodes || {}; if (!last || last > Date.now() - hour) { ({ nodes } = await ln.listnodes()); @@ -173,15 +173,15 @@ export default { await s("nodes", nodes); } - let decoded = await ln.decodepay(payreq); - let { amount_msat, payee } = decoded; - let node = nodes.find((n) => n.nodeid === payee); - let alias = node ? node.alias : payee.substr(0, 12); + const decoded = await ln.decodepay(payreq); + const { amount_msat, payee } = decoded; + const node = nodes.find((n) => n.nodeid === payee); + const alias = node ? node.alias : payee.substr(0, 12); - let amount = Math.round(amount_msat / 1000); + const amount = Math.round(amount_msat / 1000); let ourfee = Math.round(amount * config.fee); - let credit = await g(`credit:lightning:${user.id}`); - let covered = Math.min(credit, ourfee) || 0; + const credit = await g(`credit:lightning:${user.id}`); + const covered = Math.min(credit, ourfee) || 0; ourfee -= covered; res.send({ alias, amount, ourfee }); @@ -191,10 +191,10 @@ export default { }, async fund(req, res) { - let { + const { params: { name }, } = req; - let amount = await g(`fund:${name}`); + const amount = await g(`fund:${name}`); if (typeof amount === "undefined" || amount === null) return bail(res, "fund not found"); let payments = (await db.lRange(`fund:${name}:payments`, 0, -1)) || []; @@ -209,10 +209,10 @@ export default { }, async withdraw(req, res) { - let { + const { params: { name }, } = req; - let maxWithdrawable = await g(`fund:${name}`); + const maxWithdrawable = await g(`fund:${name}`); res.send({ tag: "withdrawRequest", callback: `${URL}/lnurlw/${name}`, @@ -239,8 +239,8 @@ export default { if (!iid) { iid = v4(); - let rates = await g("rates"); - let { currency } = user; + const rates = await g("rates"); + const { currency } = user; await s(`invoice:${iid}`, { currency, id: iid, @@ -251,7 +251,7 @@ export default { }); } - let payment = await credit({ + const payment = await credit({ hash: iid, amount, memo: id, @@ -268,23 +268,23 @@ export default { }, async confirm(req, res) { - let { + const { body: { txid, wallet, type }, } = req; try { - let node = rpc({ ...config[type], wallet }); - let { confirmations, details } = await node.getTransaction(txid); - let hot = wallet === config[type].wallet; + const node = rpc({ ...config[type], wallet }); + const { confirmations, details } = await node.getTransaction(txid); + const hot = wallet === config[type].wallet; let aid; if (!hot) aid = wallet; - for (let { address, amount, asset, category, vout } of details) { + for (const { address, amount, asset, category, vout } of details) { if (!address) continue; if (type === types.liquid && asset !== config.liquid.btc) continue; if (category === "send") { - let p = await getPayment(txid); + const p = await getPayment(txid); if (!p) continue; if (confirmations) { @@ -299,10 +299,10 @@ export default { continue; } - let p = await getPayment(`${txid}:${vout}`); + const p = await getPayment(`${txid}:${vout}`); if (!p) { - let invoice = await getInvoice(address); + const invoice = await getInvoice(address); if (!hot && aid !== invoice?.aid) continue; await credit({ hash: address, @@ -312,13 +312,13 @@ export default { aid, }); } else if (confirmations >= 1) { - let id = `payment:${txid}:${vout}`; - let p = await getPayment(`${txid}:${vout}`); + const id = `payment:${txid}:${vout}`; + const p = await getPayment(`${txid}:${vout}`); if (!p) return db.sAdd("missed", id); if (p.confirmed) return; - let invoice = await getInvoice(address); - let { id: iid } = invoice; + const invoice = await getInvoice(address); + const { id: iid } = invoice; p.confirmed = true; invoice.received += parseInt(invoice.pending); @@ -334,7 +334,7 @@ export default { .incrBy(`balance:${p.aid || p.uid}`, p.amount) .exec(); - let user = await g(`user:${p.uid}`); + const user = await g(`user:${p.uid}`); await completePayment(invoice, p, user); } } @@ -347,7 +347,7 @@ export default { }, async fee(req, res) { - let { body, user } = req; + const { body, user } = req; try { res.send(await build({ ...body, user })); } catch (e) { @@ -365,12 +365,12 @@ export default { }, async send(req, res) { - let { body, user } = req; + const { body, user } = req; try { await requirePin({ body, user }); - let { hash: txid } = await sendOnchain({ ...body, user }); - let pid = await g(`payment:${txid}`); - let p = await g(`payment:${pid}`); + const { hash: txid } = await sendOnchain({ ...body, user }); + const pid = await g(`payment:${txid}`); + const p = await g(`payment:${pid}`); res.send(p); } catch (e) { @@ -381,7 +381,7 @@ export default { }, async freeze(req, res) { - let { + const { body: { secret }, } = req; try { @@ -395,16 +395,16 @@ export default { }, async print(req, res) { - let { + const { body: { id }, user, } = req; try { - let p = await g(`payment:${id}`); + const p = await g(`payment:${id}`); if (p.uid !== user.id) fail("unauthorized"); emit(user.id, "payment", p); - let { username } = user; + const { username } = user; mqtt.publish( username, @@ -427,21 +427,21 @@ export default { lnaddress = decodeURIComponent(lnaddress); await requirePin({ body, user }); - let [username, domain] = lnaddress.split("@"); - let { minSendable, maxSendable, callback, metadata } = (await got( + const [username, domain] = lnaddress.split("@"); + const { minSendable, maxSendable, callback, metadata } = (await got( `https://${domain}/.well-known/lnurlp/${username}`, ).json()) as any; - let memo = metadata["text/plain"] || ""; + const memo = metadata["text/plain"] || ""; if (amount * 1000 < minSendable || amount * 1000 > maxSendable) fail("amount out of range"); - let r: any = await got(`${callback}?amount=${amount * 1000}`).json(); + const r: any = await got(`${callback}?amount=${amount * 1000}`).json(); if (r.reason) fail(r.reason); - let { pr } = r; + const { pr } = r; - let { payee } = await ln.decode(pr); - let { id } = await ln.getinfo(); + const { payee } = await ln.decode(pr); + const { id } = await ln.getinfo(); let p; if (payee === id) { @@ -457,7 +457,7 @@ export default { }, async gateway(req, res) { - let { + const { body: { short_channel_id, webhook }, } = req; @@ -466,24 +466,24 @@ export default { }, async replace(req, res) { - let { + const { body: { id }, user, } = req; try { - let p = await g(`payment:${id}`); + const p = await g(`payment:${id}`); if (!p) fail("Payment not found"); if (p.uid !== user.id) fail("unauthorized"); - let { tx, type } = await decode(p.hex); - let node = rpc(config[type]); + const { tx, type } = await decode(p.hex); + const node = rpc(config[type]); - let fees: any = await fetch(`${api[type]}/fees/recommended`).then((r) => + const fees: any = await fetch(`${api[type]}/fees/recommended`).then((r) => r.json(), ); - let outputs = []; - for (let { + const outputs = []; + for (const { scriptPubKey: { address }, value, } of tx.vout) { @@ -491,21 +491,21 @@ export default { outputs.push({ [address]: value }); } - let raw = await node.createRawTransaction(tx.vin, outputs); + const raw = await node.createRawTransaction(tx.vin, outputs); - let newTx = await node.fundRawTransaction(raw, { + const newTx = await node.fundRawTransaction(raw, { fee_rate: fees.fastestFee + 50, replaceable: true, subtractFeeFromOutputs: [], }); - let diff = sats(newTx.fee) - p.fee; + const diff = sats(newTx.fee) - p.fee; if (diff < 0) fail("fee must increase"); if (config[type].walletpass) await node.walletPassphrase(config[type].walletpass, 300); p.hex = (await node.signRawTransactionWithWallet(newTx.hex)).hex; - let r = await node.testMempoolAccept([p.hex]); + const r = await node.testMempoolAccept([p.hex]); if (!r[0].allowed) fail(`transaction rejected ${p.hex}`); warn("bump", user.username, p.hex); @@ -517,12 +517,12 @@ export default { }, async internal(req, res) { - let { + const { body: { username, amount }, user: sender, } = req; - let recipient = await getUser(username); + const recipient = await getUser(username); res.send(await sendInternal({ amount, sender, recipient })); }, }; -- cgit v1.2.3 From 09496e7eaf40da92bb97b70bb09fa9d6dc115980 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Wed, 23 Oct 2024 10:16:38 -0700 Subject: increase fastest fee by 10% --- lib/payments.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib') diff --git a/lib/payments.ts b/lib/payments.ts index 1b865046..634d3543 100644 --- a/lib/payments.ts +++ b/lib/payments.ts @@ -545,6 +545,8 @@ export const build = async ({ r.json(), ); + fees.fastestFee *= 1.1; + if (!feeRate) { feeRate = fees.halfHourFee; } -- cgit v1.2.3 From ac161f60e08e7e32c46d98aa3288f4f1b3d6e2d8 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Wed, 23 Oct 2024 10:35:04 -0700 Subject: fastest fee rate --- lib/payments.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/payments.ts b/lib/payments.ts index 634d3543..88b9230d 100644 --- a/lib/payments.ts +++ b/lib/payments.ts @@ -545,7 +545,9 @@ export const build = async ({ r.json(), ); - fees.fastestFee *= 1.1; + fees.hourFee = fees.halfHourFee; + fees.halfHourFee = fees.fastestFee; + fees.fastestFee = Math.round(fees.fastestFee * 1.1); if (!feeRate) { feeRate = fees.halfHourFee; -- cgit v1.2.3 From d09712ef8f8ea8218ce229b233fc0013e6c06f7a Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 24 Oct 2024 13:59:34 -0700 Subject: save ecash id --- lib/ecash.ts | 77 ++++++++++++++++++++++++++++++++++++------------------ lib/ln.ts | 8 +++--- routes/ecash.ts | 61 +++++++++++++++++++++--------------------- routes/invoices.ts | 6 ++--- 4 files changed, 87 insertions(+), 65 deletions(-) (limited to 'lib') diff --git a/lib/ecash.ts b/lib/ecash.ts index 8f91e70c..d9d6728e 100644 --- a/lib/ecash.ts +++ b/lib/ecash.ts @@ -1,67 +1,92 @@ import config from "$config"; +import { g, s } from "$lib/db"; +import { lnb } from "$lib/ln"; +import { fail, wait } from "$lib/utils"; import { CashuMint, CashuWallet, + MintQuoteState, getDecodedToken, getEncodedToken, getEncodedTokenV4, } from "@cashu/cashu-ts"; -import { g, s } from "$lib/db"; -import { fail } from "$lib/utils"; -let m = new CashuMint(config.mintUrl); -let w = new CashuWallet(m); +const m = new CashuMint(config.mintUrl); +const w = new CashuWallet(m); -let enc = (proofs, v = 4) => +const enc = (proofs, v = 4) => (v === 4 ? getEncodedTokenV4 : getEncodedToken)({ token: [{ mint: config.mintUrl, proofs }], }); -let dec = (token) => getDecodedToken(token).token[0]; +const dec = (token) => getDecodedToken(token).token[0]; -let ext = async (mint) => { - let issuer = new CashuMint(mint); - let { pubkey: issuerPk } = await issuer.getInfo(); - let { pubkey: ourPk } = await m.getInfo(); +const ext = async (mint) => { + const issuer = new CashuMint(mint); + const { pubkey: issuerPk } = await issuer.getInfo(); + const { pubkey: ourPk } = await m.getInfo(); return issuerPk !== ourPk; }; export async function get(id, v = 4) { - let token = await g(`cash:${id}`); + const token = await g(`cash:${id}`); if (v < 4) return enc(dec(token).proofs, 3); return token; } export async function claim(token) { - let { proofs: current } = dec(await g(`cash`)); - let { mint } = dec(token); + const { proofs: current } = dec(await g("cash")); + const { mint } = dec(token); if (await ext(mint)) fail("Unable to receive from other mints"); - let rcvd = await w.receive(token); + const rcvd = await w.receive(token); - await s(`cash`, enc([...current, ...rcvd])); + await s("cash", enc([...current, ...rcvd])); return rcvd.reduce((a, b) => a + b.amount, 0); } export async function mint(amount, v = 4) { - let { proofs } = dec(await g(`cash`)); - let { send, returnChange } = await w.send(amount, proofs); - let rcvd = await w.receive(enc(send, v)); - let change = enc(returnChange, v); - await s(`cash`, change); + const { proofs } = dec(await g("cash")); + const { send, returnChange } = await w.send(amount, proofs); + const rcvd = await w.receive(enc(send, v)); + const change = enc(returnChange, v); + await s("cash", change); return enc(rcvd, v); } export async function check(token) { - let o = dec(token); - let { mint, proofs } = o; - let total = proofs.reduce((a, b) => a + b.amount, 0); + const o = dec(token); + const { mint, proofs } = o; + const total = proofs.reduce((a, b) => a + b.amount, 0); - let external = await ext(mint); + const external = await ext(mint); - let r = await w.checkProofsSpent(proofs); - let spent = r.reduce((a, b) => a + b.amount, 0); + const r = await w.checkProofsSpent(proofs); + const spent = r.reduce((a, b) => a + b.amount, 0); return { total, spent, mint, external }; } + +export async function init(amount = 10000) { + try { + await new Promise((r) => setTimeout(r, 2000)); + const { quote, request } = await w.createMintQuote(amount); + await lnb.pay(request); + + await wait(async () => { + const { state } = await w.checkMintQuote(quote); + return state === MintQuoteState.PAID; + }); + + const { proofs } = await w.mintTokens(amount, quote); + + const cash = getEncodedTokenV4({ + token: [{ mint: config.mintUrl, proofs }], + }); + + await s("cash", cash); + } catch (e) { + console.log(e); + } +} diff --git a/lib/ln.ts b/lib/ln.ts index c4445b26..fcc6adce 100644 --- a/lib/ln.ts +++ b/lib/ln.ts @@ -1,7 +1,5 @@ import config from "$config"; -let ln; -let { LightningClient } = (await import("clightning-client")).default; -ln = new LightningClient(config.lightning); - -export default ln; +const { LightningClient } = (await import("clightning-client")).default; +export default new LightningClient(config.lightning); +export const lnb = new LightningClient(config.lightningb); diff --git a/routes/ecash.ts b/routes/ecash.ts index 48b03fd3..cc617138 100644 --- a/routes/ecash.ts +++ b/routes/ecash.ts @@ -1,20 +1,20 @@ -import { g, s, db } from "$lib/db"; -import { get, claim, mint, check } from "$lib/ecash"; +import { db, g, s } from "$lib/db"; +import { check, claim, get, mint } from "$lib/ecash"; +import { err, l } from "$lib/logging"; +import { credit, debit, types } from "$lib/payments"; +import { emit } from "$lib/sockets"; import { bail, fail } from "$lib/utils"; -import { debit, credit, types } from "$lib/payments"; import { v4 } from "uuid"; -import { l, err } from "$lib/logging"; -import { emit } from "$lib/sockets"; -let { ecash: type } = types; +const { ecash: type } = types; export default { async save(req, res) { - let { + const { body: { token }, } = req; try { - let id = v4(); + const id = v4(); await s(`cash:${id}`, token); res.send({ id }); } catch (e) { @@ -24,12 +24,12 @@ export default { }, async get(req, res) { - let { + const { params: { id, version }, } = req; try { - let token = await get(id, version); - let status = await check(token); + const token = await get(id, version); + const status = await check(token); res.send({ token, status }); } catch (e) { err(e.message); @@ -38,17 +38,17 @@ export default { }, async claim(req, res) { - let { + const { body: { token }, user, } = req; try { - let amount = await claim(token); + const amount = await claim(token); let memo; - let hash = v4(); - let { currency, id: uid } = user; - let rates = await g("rates"); + const hash = v4(); + const { currency, id: uid } = user; + const rates = await g("rates"); await s(`invoice:${hash}`, { currency, id: hash, @@ -68,18 +68,17 @@ export default { }, async mint(req, res) { - let { + const { body: { amount }, user, } = req; try { - let id = v4(); - let hash = v4(); + const id = v4(); + const hash = v4(); - let memo, rate; - let p = await debit({ hash, amount, user, type, memo, rate }); - let token = await mint(amount); - p.memo = token; + const p = await debit({ hash, amount, user, type }); + const token = await mint(amount); + p.memo = id; await s(`payment:${p.id}`, p); s(`cash:${id}`, token); @@ -97,11 +96,11 @@ export default { } = req; try { amount = Math.round(amount / 1000); - let ref = preimage; - let { lightning: type } = types; + const ref = preimage; + const { lightning: type } = types; if (user.username !== "mint") fail("unauthorized"); - let { id: uid, currency } = user; - let ourfee = await db.debit( + const { id: uid, currency } = user; + const ourfee = await db.debit( `balance:${uid}`, `credit:${type}:${uid}`, amount || 0, @@ -110,13 +109,13 @@ export default { 0, ); - let rates = await g("rates"); - let rate = rates[currency]; + const rates = await g("rates"); + const rate = rates[currency]; if (ourfee.err) fail(ourfee.err); - let id = v4(); - let p = { + const id = v4(); + const p = { id, amount: -amount, hash, diff --git a/routes/invoices.ts b/routes/invoices.ts index acad5ce7..c571f4de 100644 --- a/routes/invoices.ts +++ b/routes/invoices.ts @@ -1,11 +1,11 @@ import { g } from "$lib/db"; import { generate } from "$lib/invoices"; -import { bail, pick } from "$lib/utils"; import { err } from "$lib/logging"; +import { bail, pick } from "$lib/utils"; export default { async get(req, res) { - let { + const { params: { id }, } = req; let invoice = await g(`invoice:${id}`); @@ -31,7 +31,7 @@ export default { async create(req, res) { let { body, user } = req; if (body.user) user = body.user; - let { invoice } = body; + const { invoice } = body; try { res.send(await generate({ invoice, user })); -- cgit v1.2.3