diff options
| author | Adam Soltys <[email protected]> | 2024-01-27 13:58:06 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2024-01-27 13:58:06 -0800 |
| commit | 96f865cd9c9e70205d062e0cda86e0114afb5fbc (patch) | |
| tree | 7e15e5e0a178374a5f03d02008b6d866df6fe1ac | |
| parent | 1b14654bc2d4766353d3f054a7c7902d560bef59 (diff) | |
| download | coinos-server-96f865cd9c9e70205d062e0cda86e0114afb5fbc.tar.gz coinos-server-96f865cd9c9e70205d062e0cda86e0114afb5fbc.zip | |
autowithdrawals
| -rw-r--r-- | lib/payments.js | 188 | ||||
| -rw-r--r-- | lib/whitelist.js | 17 | ||||
| -rw-r--r-- | routes/invoices.js | 16 | ||||
| -rw-r--r-- | routes/payments.js | 103 | ||||
| -rw-r--r-- | routes/users.js | 1 |
5 files changed, 193 insertions, 132 deletions
diff --git a/lib/payments.js b/lib/payments.js index c924dd2b..c7cac619 100644 --- a/lib/payments.js +++ b/lib/payments.js @@ -8,11 +8,6 @@ import { l, err, warn } from "$lib/logging"; import { btc, fail, getInvoice, sleep, wait, SATS, sats } from "$lib/utils"; import { callWebhook } from "$lib/webhooks"; import got from "got"; -import { spawn } from "child_process"; -import ffmpeg from "fluent-ffmpeg"; -import fs from "fs"; -import EventEmitter from "events"; -import crc32 from "buffer-crc32"; import { mqtt1, mqtt2 } from "$lib/mqtt"; import { mail, templates } from "$lib/mail"; @@ -45,7 +40,7 @@ export let debit = async ({ }) => { let ref; let { id: uid, currency, username } = user; - if (!rate) rate = store.rates[currency]; + if (!rate) rate = store.rates[currency]; let invoice = await getInvoice(hash); let iid; @@ -112,6 +107,7 @@ export let debit = async ({ }; export let credit = async (hash, amount, memo, ref, type = types.internal) => { + console.log("crediting"); amount = parseInt(amount) || 0; let inv = await getInvoice(hash); @@ -187,6 +183,7 @@ export let credit = async (hash, amount, memo, ref, type = types.internal) => { emit(uid, "payment", p); notify(user, `Received ${amount} sats`); callWebhook(inv, p); + l(username, "received", type, amount); if (user.verified && user.notify) { mail(user, "Payment received", templates.paymentReceived, { @@ -200,20 +197,159 @@ export let credit = async (hash, amount, memo, ref, type = types.internal) => { }); } - mqtt1.publish( - username, - `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}`, - ); + if (config.mqtt1) { + 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}`, - ); + if (config.mqtt2) { + mqtt2.publish( + username, + `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}`, + ); + } + + autowithdraw(user); + console.log("done crediting"); return p; }; -export let payLightning = async (user, payreq, amount, maxfee, memo) => { +let autowithdraw = async (user) => { + let { autowithdraw, id, destination, threshold, withdrawal } = user; + destination = destination.trim(); + if (autowithdraw) { + let balance = await g(`balance:${id}`); + if (balance > threshold) + await pay({ amount: withdrawal, to: destination, user }); + } +}; + +let pay = async ({ amount, to, user }) => { + amount = parseInt(amount) || 0; + let lnurl; + if (to.includes("@") && to.includes(".")) { + let [name, domain] = to.split("@"); + lnurl = `https://${domain}/.well-known/lnurlp/${name}`; + } else if (to.startsWith("lnurl")) { + lnurl = Buffer.from( + bech32.fromWords(bech32.decode(text, 20000).words), + ).toString(); + } + + if (lnurl) { + let { callback } = await got(lnurl).json(); + let { pr } = await got(`${callback}?amount=${amount}`).json(); + await sendLightning(user, pr, amount); + } + + await sendOnchain({ amount, address: to, user }); +}; + +let decode = async (hex) => { + let type, tx; + try { + tx = await bc.decodeRawTransaction(hex); + type = types.bitcoin; + } catch (e) { + try { + tx = await lq.decodeRawTransaction(hex); + type = types.liquid; + } catch (e) { + fail("unrecognized tx"); + } + } + + console.log("TYPE", type); + + return { tx, type }; +}; + +export let sendOnchain = async (params) => { + let { hex, rate, user } = params; + if (!hex) ({ hex } = await build(params)); + + let { tx, type } = await decode(hex); + let node = getNode(type); + let { txid } = tx; + + console.log("YO"); + + if (config[type].walletpass) + await node.walletPassphrase(config[type].walletpass, 300); + + let { hex: signed } = await node.signRawTransactionWithWallet( + type === types.liquid ? await node.blindRawTransaction(hex) : hex, + ); + + ({ txid } = await node.decodeRawTransaction(signed)); + + let r = await node.testMempoolAccept([signed]); + if (!r[0].allowed) fail("transaction rejected"); + + let total = 0; + let fee = 0; + let change = 0; + + if (type === types.liquid) { + for (let { + asset, + scriptPubKey: { address, type }, + value, + } of tx.vout) { + if (asset !== config.liquid.btc) fail("only L-BTC supported"); + if (type === "fee") fee = sats(value); + else { + total += sats(value); + + if (address) { + if ((await node.getAddressInfo(address)).ismine) { + change += sats(value); + } + } + } + } + } else { + let totalIn = 0; + for await (let { txid, vout } of tx.vin) { + let hex = await node.getRawTransaction(txid); + let tx = await node.decodeRawTransaction(hex); + totalIn += sats(tx.vout[vout].value); + } + + for (let { + scriptPubKey: { address }, + value, + } of tx.vout) { + total += sats(value); + if (await g(`invoice:${address}`)) + fail("Cannot send to internal address"); + + if ((await node.getAddressInfo(address)).ismine) change += sats(value); + } + + fee = totalIn - total; + } + + let amount = total - change; + + console.log("HERE", amount, signed.length); + + await debit({ + hash: txid, + amount, + fee, + rate, + user, + type, + }); + + return node.sendRawTransaction(signed); +}; + +export let sendLightning = async (user, payreq, amount, maxfee, memo) => { let p; let hash = payreq; @@ -325,25 +461,31 @@ export let getNode = (type) => { else fail("unrecognized transaction type"); }; -export let build = async ({ amount, address, feeRate }, user) => { - let type; - +let getAddressType = async (a) => { + console.log("A", a); try { - await bc.getAddressInfo(address); - type = types.bitcoin; + await bc.getAddressInfo(a); + return types.bitcoin; } catch (e) { try { - await lq.getAddressInfo(address); - type = types.liquid; + await lq.getAddressInfo(a); + return types.liquid; } catch (e) { fail("unrecognized address"); } } +}; + +export let build = async ({ amount, address, feeRate, user }) => { + let type = await getAddressType(address); + console.log("type", type, amount); let node = getNode(type); amount = parseInt(amount); - let fees = await fetch(`${api[type]}/fees/recommended`).then((r) => r.json()); + let fees = { halfHourFee: 50 }; + + // let fees = await fetch(`${api[type]}/fees/recommended`).then((r) => r.json()); if (!feeRate) { feeRate = fees.halfHourFee; diff --git a/lib/whitelist.js b/lib/whitelist.js index 3c99777c..9ef1ceca 100644 --- a/lib/whitelist.js +++ b/lib/whitelist.js @@ -1,10 +1,9 @@ export default [ - "autowithdraw", - "notify", - "anon", - "balance", "address", "admin", + "anon", + "autowithdraw", + "balance", "banner", "cipher", "currencies", @@ -13,28 +12,30 @@ export default [ "display", "email", "fiat", - "follows", "followers", + "follows", "haspin", "hasprinter", "id", "index", + "keys", "language", "linked", - "keys", "locktime", "nip5", + "notify", "payments", "profile", "prompt", "pubkey", "salt", "seed", + "shopifyStore", + "shopifyToken", "theme", "threshold", "twofa", "username", "verified", - "shopifyStore", - "shopifyToken", + "withdrawal", ]; diff --git a/routes/invoices.js b/routes/invoices.js index 898938dd..85c9166f 100644 --- a/routes/invoices.js +++ b/routes/invoices.js @@ -22,13 +22,15 @@ export default { "pubkey", ]); } else if (config.classic) { - invoice = await got(`${config.classic}/invoice/${id}`).json(); - if (invoice) { - invoice.id = invoice.uuid; - invoice.classic = true; - invoice.user.id = invoice.user.uuid; - invoice.user.username += "@classic"; - } + try { + invoice = await got(`${config.classic}/invoice/${id}`).json(); + if (invoice) { + invoice.id = invoice.uuid; + invoice.classic = true; + invoice.user.id = invoice.user.uuid; + invoice.user.username += "@classic"; + } + } catch (e) {} } if (invoice) res.send(invoice); diff --git a/routes/payments.js b/routes/payments.js index 3dba0385..13ef20c2 100644 --- a/routes/payments.js +++ b/routes/payments.js @@ -12,7 +12,8 @@ import { types, getNode, build, - payLightning, + sendLightning, + sendOnchain, } from "$lib/payments"; import { mqtt1, mqtt2 } from "$lib/mqtt"; import got from "got"; @@ -62,7 +63,7 @@ export default { }) .json(); } else if (payreq) { - p = await payLightning(user, payreq, amount, maxfee, memo); + p = await sendLightning(user, payreq, amount, maxfee, memo); } if (!p) { @@ -79,7 +80,7 @@ export default { res.send(p); } catch (e) { - warn(user.username, "payment failed", amount, balance, hash, payreq); + warn(user.username, "payment failed", amount, balance, hash, payreq); err(e.message); bail(res, e.message); } @@ -282,8 +283,9 @@ export default { async fee({ body, user }, res) { try { - res.send(await build(body, user)); + res.send(await build({ ...body, user })); } catch (e) { + console.log(e) warn( "problem estimating fee", e.message, @@ -300,98 +302,11 @@ export default { async send({ body, user }, res) { try { await requirePin({ body, user }); - - let { memo, hex, rate } = body; - if (!hex) ({ hex } = await build(body, user)); - - let type, tx; - try { - tx = await bc.decodeRawTransaction(hex); - type = types.bitcoin; - } catch (e) { - try { - tx = await lq.decodeRawTransaction(hex); - type = types.liquid; - } catch (e) { - fail("unrecognized tx"); - } - } - - let node = getNode(type); - let { txid } = tx; - - if (config[type].walletpass) - await node.walletPassphrase(config[type].walletpass, 300); - - let { hex: signed } = await node.signRawTransactionWithWallet( - type === types.liquid ? await node.blindRawTransaction(hex) : hex, - ); - - ({ txid } = await node.decodeRawTransaction(signed)); - - let r = await node.testMempoolAccept([signed]); - if (!r[0].allowed) fail("transaction rejected"); - - let total = 0; - let fee = 0; - let change = 0; - - if (type === types.liquid) { - for (let { - asset, - scriptPubKey: { address, type }, - value, - } of tx.vout) { - if (asset !== config.liquid.btc) fail("only L-BTC supported"); - if (type === "fee") fee = sats(value); - else { - total += sats(value); - - if (address) { - if ((await node.getAddressInfo(address)).ismine) { - change += sats(value); - } - } - } - } - } else { - let totalIn = 0; - for await (let { txid, vout } of tx.vin) { - let hex = await node.getRawTransaction(txid); - let tx = await node.decodeRawTransaction(hex); - totalIn += sats(tx.vout[vout].value); - } - - for (let { - scriptPubKey: { address }, - value, - } of tx.vout) { - total += sats(value); - if (await g(`invoice:${address}`)) - fail("Cannot send to internal address"); - - if ((await node.getAddressInfo(address)).ismine) - change += sats(value); - } - - fee = totalIn - total; - } - - let amount = total - change; - - await debit({ - hash: txid, - amount, - fee, - rate, - user, - type, - }); - - await node.sendRawTransaction(signed); + let txid = await sendOnchain({ ...body, user }); res.send({ txid }); } catch (e) { + console.log(e) warn("payment failed", e.message); res.code(500).send(e.message); } @@ -450,7 +365,7 @@ export default { let r = await got(`${callback}?amount=${amount * 1000}`).json(); if (r.reason) fail(r.rason); let { pr } = r; - let p = await payLightning(user, pr, amount, maxfee, memo); + let p = await sendLightning(user, pr, amount, maxfee, memo); if (!p) { p = await debit({ hash: pr, amount, memo, user }); diff --git a/routes/users.js b/routes/users.js index 230d46a1..dff3f7bc 100644 --- a/routes/users.js +++ b/routes/users.js @@ -244,6 +244,7 @@ export default { "twofa", "shopifyToken", "shopifyStore", + "withdrawal", ]; for (let a of attributes) { |
