diff options
| author | Adam Soltys <[email protected]> | 2024-08-26 21:17:43 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2024-08-26 21:17:43 -0700 |
| commit | fe62cadd6fb4a7e49f7d59401fc74ae0411e32a7 (patch) | |
| tree | aa8f34e1692bd2d638a2c996eeadb58671d07aea /routes/payments.js | |
| parent | 00e43ddc0bf3d6dd719fc9295d156567fa2fecfb (diff) | |
| download | coinos-server-fe62cadd6fb4a7e49f7d59401fc74ae0411e32a7.tar.gz coinos-server-fe62cadd6fb4a7e49f7d59401fc74ae0411e32a7.zip | |
typescript
Diffstat (limited to 'routes/payments.js')
| -rw-r--r-- | routes/payments.js | 478 |
1 files changed, 0 insertions, 478 deletions
diff --git a/routes/payments.js b/routes/payments.js deleted file mode 100644 index 1ae87916..00000000 --- a/routes/payments.js +++ /dev/null @@ -1,478 +0,0 @@ -import config from "$config"; -import { emit } from "$lib/sockets"; -import { generate } from "$lib/invoices"; -import { v4 } from "uuid"; -import { db, g, s, t } from "$lib/db"; -import { l, err, warn } from "$lib/logging"; -import { bail, fail, getInvoice, btc, sats, SATS, getUser } from "$lib/utils"; -import { requirePin } from "$lib/auth"; -import { - completePayment, - decode, - debit, - credit, - types, - getNode, - build, - sendLightning, - sendOnchain, -} from "$lib/payments"; -import { mqtt1, mqtt2 } from "$lib/mqtt"; -import got from "got"; -import api from "$lib/api"; - -import lq from "$lib/liquid"; -import bc from "$lib/bitcoin"; -import ln from "$lib/ln"; - -export default { - async info({ body, user }, res) { - res.send(await ln.getinfo()); - }, - - async create({ body, user }, res) { - let { amount, hash, maxfee, fund, memo, payreq, rate, username } = body; - let 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)) - fail("Invalid amount"); - } - - await requirePin({ body, user }); - - let p; - - if (payreq) { - p = await sendLightning({ user, pr: payreq, amount, maxfee, memo }); - } - - if (!p) { - if (hash) { - p = await debit({ hash, amount, memo, user }); - await credit(hash, amount, memo, user.id); - } else if (fund) { - p = await debit({ hash, amount, memo: fund, user, type: types.fund }); - await db.incrBy(`fund:${fund}`, amount); - await db.lPush(`fund:${fund}:payments`, p.id); - l("funded fund", fund); - } - } - - res.send(p); - } catch (e) { - warn(user.username, "payment failed", amount, balance, hash, payreq); - err(e.message); - bail(res, e.message); - } - }, - - async list({ user: { id }, query: { start, end, limit, offset } }, res) { - if (limit) limit = parseInt(limit); - offset = parseInt(offset) || 0; - - let payments = (await db.lRange(`${id}:payments`, 0, -1)) || []; - payments = ( - await Promise.all( - payments.map(async (id) => { - let p = await g(`payment:${id}`); - if (!p) { - warn("missing payment", id); - return p; - } - if (p.created < start || p.created > end) return; - if (p.type === types.internal) p.with = await g(`user:${p.ref}`); - return p; - }), - ) - ) - .filter((p) => p) - .sort((a, b) => b.created - a.created); - - let count = payments.length; - - let fn = (a, b) => ({ - ...a, - [b.currency]: { - tips: (a[b.currency] ? a[b.currency].tips : 0) + (b.tip || 0), - fiatTips: ( - parseFloat(a[b.currency] ? a[b.currency].fiatTips : 0) + - ((b.tip || 0) * b.rate) / SATS - ).toFixed(2), - sats: - (a[b.currency] ? a[b.currency].sats : 0) + - (b.amount || 0) + - (b.tip || 0) - - (b.fee || 0) - - (b.ourfee || 0), - fiat: ( - parseFloat(a[b.currency] ? a[b.currency].fiat : 0) + - (((b.amount || 0) + - ((b.amount > 0 ? b.tip : -b.tip) || 0) - - (b.fee || 0) - - (b.ourfee || 0)) * - b.rate) / - SATS - ).toFixed(2), - }, - }); - - let incoming = payments.filter((p) => p.amount > 0).reduce(fn, {}); - let outgoing = payments.filter((p) => p.amount < 0).reduce(fn, {}); - - if (limit) payments = payments.slice(offset, offset + limit); - - res.send({ payments, count, incoming, outgoing }); - }, - - async get({ params: { hash } }, res) { - let p = await g(`payment:${hash}`); - if (typeof p === "string") p = await g(`payment:${p}`); - if (p.type === types.internal) p.with = await g(`user:${p.ref}`); - res.send(p); - }, - - async parse({ body: { payreq }, user }, res) { - try { - let hour = 1000 * 60 * 60; - let nodes = await g("nodes"); - let { last } = nodes || {}; - - if (!last || last > Date.now() - hour) { - ({ nodes } = await ln.listnodes()); - nodes.last = Date.now(); - await s("nodes", nodes); - } - - let twoWeeksAgo = new Date(new Date().setDate(new Date().getDate() - 14)); - 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); - - let 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; - ourfee -= covered; - - res.send({ alias, amount, ourfee }); - } catch (e) { - bail(res, e.message); - } - }, - - async fund({ params: { name } }, res) { - let 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)) || []; - payments = await Promise.all(payments.map((hash) => g(`payment:${hash}`))); - - await Promise.all( - payments.map(async (p) => (p.user = await g(`user:${p.uid}`))), - ); - - payments = payments.filter((p) => p); - res.send({ amount, payments }); - }, - - async withdraw({ params: { name } }, res) { - let maxWithdrawable = await g(`fund:${name}`); - res.send({ - tag: "withdrawRequest", - callback: `${URL}/lnurlw/${name}`, - k1: name, - defaultDescription: `Withdraw from coinos fund ${name}`, - minWithdrawable: 0, - maxWithdrawable, - }); - }, - - async take({ body: { id, amount, invoice: iid }, user }, res) { - try { - amount = parseInt(amount); - if (amount < 0) fail("Invalid amount"); - - await t(`fund:${id}`, async (balance, db) => { - if (balance < amount) fail("Insufficient funds"); - await db.multi().decrBy(`fund:${id}`, amount).exec(); - }); - - if (!iid) { - iid = v4(); - let rates = await g("rates"); - let { currency } = user; - await s(`invoice:${iid}`, { - currency, - id: iid, - hash: iid, - rate: rates[currency], - uid: user.id, - received: 0, - }); - } - - let payment = await credit(iid, amount, id, id, types.fund); - await db.lPush(`fund:${id}:payments`, payment.id); - - res.send({ payment }); - } catch (e) { - warn("problem withdrawing from fund", e.message); - bail(res, e.message); - } - }, - - async confirm({ body: { txid, wallet, type } }, res) { - try { - let node = getNode(type); - - if (wallet === config.bitcoin.wallet || wallet === config.liquid.wallet) { - let { confirmations, details } = await node.getTransaction(txid); - for (let { 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 g(`payment:${txid}`); - if (typeof p === "string") p = await g(`payment:${p}`); - if (!p) continue; - - if (confirmations >= 1) p.confirmed = true; - await s(`payment:${p.id}`, p); - emit(p.uid, "payment", p); - continue; - } - - let p = await g(`payment:${txid}:${vout}`); - if (typeof p === "string") p = await g(`payment:${p}`); - - if (!p) { - await credit(address, sats(amount), "", `${txid}:${vout}`, type); - } else if (confirmations >= 1) { - let id = `payment:${txid}:${vout}`; - let p = await g(id); - if (typeof p === "string") p = await g(`payment:${p}`); - if (!p) return db.sAdd("missed", id); - if (p.confirmed) return; - - let invoice = await getInvoice(address); - let { id: iid } = invoice; - - p.confirmed = true; - invoice.received += parseInt(invoice.pending); - invoice.pending = 0; - - l("confirming", id, p.id, p.amount); - - let r = await db - .multi() - .set(`invoice:${iid}`, JSON.stringify(invoice)) - .set(`payment:${p.id}`, JSON.stringify(p)) - .decrBy(`pending:${p.uid}`, p.amount) - .incrBy(`balance:${p.uid}`, p.amount) - .exec(); - - emit(p.uid, "payment", p); - let user = await g(`user:${p.uid}`); - await completePayment(p, user); - } - } - } - res.send({}); - } catch (e) { - console.log(e); - warn(`problem processing ${txid}`); - bail(res, e.message); - } - }, - - async fee({ body, user }, res) { - try { - res.send(await build({ ...body, user })); - } catch (e) { - warn( - "problem estimating fee", - e.message, - user.username, - body.amount, - body.address, - ); - let msg = e.message; - if (msg.includes("500")) msg = ""; - bail(res, "Failed to prepare transaction " + msg); - } - }, - - async send({ body, user }, res) { - try { - await requirePin({ body, user }); - let { hash: txid } = await sendOnchain({ ...body, user }); - let pid = await g(`payment:${txid}`); - let p = await g(`payment:${pid}`); - - res.send({ txid }); - } catch (e) { - warn(user.username, "payment failed", e.message); - res.code(500).send(e.message); - } - }, - - async freeze({ body: { secret } }, res) { - try { - if (secret !== config.adminpass) fail("unauthorized"); - await s("freeze", true); - res.send("ok"); - } catch (e) { - console.log(e); - bail(res, e.message); - } - }, - - async print({ body: { id }, user }, res) { - try { - let p = await g(`payment:${id}`); - if (p.uid !== user.id) fail("unauthorized"); - emit(user.id, "payment", p); - - let { username } = user; - - mqtt1.publish( - username, - `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}`, - ); - - mqtt2.publish( - username, - `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}`, - ); - - res.send({ ok: true }); - } catch (e) { - bail(res, e.message); - } - }, - - async lnaddress( - { params: { lnaddress, amount, maxfee = 5000 }, body, user }, - res, - ) { - try { - lnaddress = decodeURIComponent(lnaddress); - await requirePin({ body, user }); - - let [username, domain] = lnaddress.split("@"); - let { minSendable, maxSendable, callback, metadata } = await got( - `https://${domain}/.well-known/lnurlp/${username}`, - ).json(); - - let memo = metadata["text/plain"] || ""; - if (amount * 1000 < minSendable || amount * 1000 > maxSendable) - fail("amount out of range"); - - let r = await got(`${callback}?amount=${amount * 1000}`).json(); - if (r.reason) fail(r.rason); - let { pr } = r; - let p = await sendLightning({ user, pr, amount, maxfee, memo }); - - if (!p) { - p = await debit({ hash: pr, amount, memo, user }); - await credit(pr, amount, memo, user.id); - } - - res.send(p); - } catch (e) { - console.log(e); - bail(res, e.message); - } - }, - - async gateway({ body: { short_channel_id, webhook } }, res) { - await s(short_channel_id, webhook); - res.send({ ok: true }); - }, - - async replace({ body: { id }, user }, res) { - try { - let { id: uid } = user; - let 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 = getNode(type); - - let fees = await fetch(`${api[type]}/fees/recommended`).then((r) => - r.json(), - ); - - let outputs = []; - for (let { - scriptPubKey: { address }, - value, - } of tx.vout) { - if (address && !(await node.getAddressInfo(address)).ismine) - outputs.push({ [address]: value }); - } - - let raw = await node.createRawTransaction(tx.vin, outputs); - - let newTx = await node.fundRawTransaction(raw, { - fee_rate: fees.fastestFee + 50, - replaceable: true, - subtractFeeFromOutputs: [], - }); - - let diff = sats(newTx.fee) - p.fee; - if (diff < 0) fail("fee must increase"); - - // let ourfee = Math.round(diff * config.fee) || 0; - // - // ourfee = await db.debit( - // `balance:${uid}`, - // `credit:${type}:${uid}`, - // p.amount || 0, - // 0, - // diff, - // ourfee, - // ); - - 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]); - if (!r[0].allowed) fail(`transaction rejected ${p.hex}`); - warn("bump", user.username, p.hex); - // p.hash = await node.sendRawTransaction(p.hex); - // p.fee = sats(newTx.fee); - // await s(`payment:${id}`, p); - // await s(`payment:${p.hash}`, id); - // emit(uid, "payment", p); - - res.send({ ok: true }); - } catch (e) { - err("failed to bump payment", id, e.message); - bail(res, e.message); - } - }, - - async internal({ body: { username, amount }, user: sender }, res) { - let recipient = await getUser(username); - let inv = await generate({ - invoice: { amount, type: "lightning" }, - user: recipient, - sender, - }); - - let { hash } = inv; - let memo; - - let p = await debit({ hash, amount, memo, user: sender }); - await credit(hash, amount, memo, recipient.id); - - res.send(p); - }, -}; |
