diff options
| author | Adam Soltys <[email protected]> | 2023-01-08 21:05:34 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2023-01-08 21:05:34 -0800 |
| commit | dce07aa93b3c2f47aca0c24cea6adde9ec37f1a7 (patch) | |
| tree | 6c6b8930ebe9c2cc5b8d1f759e61260f509fdd7a /routes/bitcoin | |
| parent | f7b844883506a8072213245b9b8a2b988694e4f5 (diff) | |
| download | coinos-server-dce07aa93b3c2f47aca0c24cea6adde9ec37f1a7.tar.gz coinos-server-dce07aa93b3c2f47aca0c24cea6adde9ec37f1a7.zip | |
tech debt jubilee
Diffstat (limited to 'routes/bitcoin')
| -rw-r--r-- | routes/bitcoin/broadcast.js | 56 | ||||
| -rw-r--r-- | routes/bitcoin/conversionFee.js | 12 | ||||
| -rw-r--r-- | routes/bitcoin/fee.js | 73 | ||||
| -rw-r--r-- | routes/bitcoin/generate.js | 5 | ||||
| -rw-r--r-- | routes/bitcoin/index.js | 13 | ||||
| -rw-r--r-- | routes/bitcoin/receive.js | 281 | ||||
| -rw-r--r-- | routes/bitcoin/send.js | 192 | ||||
| -rw-r--r-- | routes/bitcoin/sweep.js | 87 |
8 files changed, 0 insertions, 719 deletions
diff --git a/routes/bitcoin/broadcast.js b/routes/bitcoin/broadcast.js deleted file mode 100644 index 85c8cf66..00000000 --- a/routes/bitcoin/broadcast.js +++ /dev/null @@ -1,56 +0,0 @@ -import { emit } from "$lib/sockets"; -import store from "$lib/store"; -import { requirePin } from "$lib/utils"; - -export default async (req, res) => { - try { - await requirePin(req); - - const { user } = req; - let hash = await bc.sendRawTransaction(req.body.tx); - - if (user) { - await db.transaction(async transaction => { - let account = await db.Account.findOne({ - where: { - id: user.account_id - }, - lock: transaction.LOCK.UPDATE, - transaction - }); - let payment = await db.Payment.create( - { - ...req.body.payment, - hash, - amount: -req.body.payment.amount, - tip: 0, - account_id: account.id, - user_id: user.id, - rate: store.rates[user.currency], - currency: user.currency, - confirmed: true, - received: false, - network: "bitcoin" - }, - { transaction } - ); - - await account.increment( - { balance: payment.amount - payment.fee }, - { transaction } - ); - await account.reload({ transaction }); - - payment = payment.get({ plain: true }); - payment.account = account.get({ plain: true }); - emit(user.username, "payment", payment); - emit(user.username, "account", payment.account); - res.send(payment); - }); - } else { - res.send(result); - } - } catch (e) { - res.code(500).send(e.message); - } -}; diff --git a/routes/bitcoin/conversionFee.js b/routes/bitcoin/conversionFee.js deleted file mode 100644 index 7e305659..00000000 --- a/routes/bitcoin/conversionFee.js +++ /dev/null @@ -1,12 +0,0 @@ -// Code for the conversion fee -// This is in a separate file so that the same function can be used by ./send and ./receive - -const conversionFeeMultiplier = 0.001; -export const conversionFeeReceiver = "coinosfees"; // account that receives the fees - -/** - * Returns the fee you have to pay for withdrawing 'amount' bitcoin. - */ -export function computeConversionFee(amount) { - return Math.floor(amount * conversionFeeMultiplier); -} diff --git a/routes/bitcoin/fee.js b/routes/bitcoin/fee.js deleted file mode 100644 index 74c26a92..00000000 --- a/routes/bitcoin/fee.js +++ /dev/null @@ -1,73 +0,0 @@ -import config from "$config"; -import db from "$db"; -import bc from "$lib/bitcoin"; -import { err } from "$lib/logging"; -import { emit } from "$lib/sockets"; -import buildTx from "$lib/buildtx"; -import { SATS } from "$lib/utils"; - -export default async (req, res) => { - let { user } = req; - let { address, amount, feeRate, replaceable = true } = req.body; - let tx, fee; - - if (user.account.pubkey) { - try { - let psbt = await buildTx({ address, amount, feeRate, replaceable, user }); - return res.send(psbt); - } catch (e) { - return res.code(500).send(e.message); - } - } - - let invoice = await db.Invoice.findOne({ - where: { address }, - include: { - attributes: ["username"], - model: db.User, - as: "user" - } - }); - - if (invoice) { - let { ismine } = await bc.getAddressInfo(address); - if (ismine) { - emit(user.username, "to", invoice.user); - return res.send({}); - } - } - - try { - amount = parseInt(amount); - - let partial = await bc.createRawTransaction( - [], - { - [address]: (amount / SATS).toFixed(8) - }, - 0, - replaceable - ); - - let params = { - subtractFeeFromOutputs: amount === user.balance ? [0] : [], - replaceable - }; - - if (feeRate) params.feeRate = (feeRate / SATS).toFixed(8); - - tx = await bc.fundRawTransaction(partial, params); - - if (config.bitcoin.walletpass) - await bc.walletPassphrase(config.bitcoin.walletpass, 300); - - let signed = await bc.signRawTransactionWithWallet(tx.hex); - let decoded = await bc.decodeRawTransaction(signed.hex); - feeRate = Math.round((tx.fee * SATS * 1000) / decoded.vsize); - - res.send({ feeRate, tx }); - } catch (e) { - err("bitcoin fee estimation error", e); - return res.code(500).send(e.message); - } -}; diff --git a/routes/bitcoin/generate.js b/routes/bitcoin/generate.js deleted file mode 100644 index 6285492d..00000000 --- a/routes/bitcoin/generate.js +++ /dev/null @@ -1,5 +0,0 @@ -export default async (req, res) => { - let address = await bc.getNewAddress(); - await bc.generateToAddress(1, address); - res.send({}); -}; diff --git a/routes/bitcoin/index.js b/routes/bitcoin/index.js deleted file mode 100644 index 58b0d193..00000000 --- a/routes/bitcoin/index.js +++ /dev/null @@ -1,13 +0,0 @@ -import broadcast from "./broadcast"; -import generate from "./generate"; -import sweep from "./sweep"; -import fee from "./fee"; -import send from "./send"; - -export default { - broadcast, - generate, - sweep, - fee, - send -}; diff --git a/routes/bitcoin/receive.js b/routes/bitcoin/receive.js deleted file mode 100644 index 10193ceb..00000000 --- a/routes/bitcoin/receive.js +++ /dev/null @@ -1,281 +0,0 @@ -import config from "$config"; -import db from "$db"; -import bc from "$lib/bitcoin"; -import { emit } from "$lib/sockets"; -import store from "$lib/store"; -import { notify } from "$lib/notifications"; -import { callWebhook } from "$lib/webhooks"; -import reverse from "buffer-reverse"; -import zmq from "zeromq/v5-compat"; -import { Op } from "@sequelize/core"; -import { fromBase58 } from "bip32"; -import bitcoin from "bitcoinjs-lib"; -import { sendLiquid } from "$routes/liquid/send"; -import { computeConversionFee } from "./conversionFee"; -import { l, warn, err } from "$lib/logging"; - -const zmqRawBlock = zmq.socket("sub"); -zmqRawBlock.connect(config.bitcoin.zmqrawblock); -zmqRawBlock.subscribe("rawblock"); - -const zmqRawTx = zmq.socket("sub"); -zmqRawTx.connect(config.bitcoin.zmqrawtx); -zmqRawTx.subscribe("rawtx"); - -const network = - bitcoin.networks[ - config.bitcoin.network === "mainnet" ? "bitcoin" : config.bitcoin.network - ]; - -const queue = {}; -const seen = []; - -console.warn = () => {}; - -zmqRawTx.on("message", async (topic, message, sequence) => { - try { - const hex = message.toString("hex"); - let tx = bitcoin.Transaction.fromHex(message); - - let hash = reverse(tx.getHash()).toString("hex"); - - if (seen.includes(hash)) return; - seen.push(hash); - if (seen.length > 5000) seen.shift(); - - Promise.all( - tx.outs.map(async o => { - try { - const { value } = o; - - let address; - try { - address = bitcoin.address.fromOutputScript(o.script, network); - } catch (e) { - return; - } - - if ( - Object.keys(store.addresses).includes(address) && - !store.change.includes(address) - ) { - await db.transaction(async transaction => { - let user = await db.User.findOne({ - where: { - username: store.addresses[address] - }, - transaction - }); - - let invoice = await db.Invoice.findOne({ - where: { - address, - user_id: user.id, - network: "bitcoin" - }, - order: [["id", "DESC"]], - include: { - model: db.Account, - as: "account" - }, - transaction - }); - - if (!invoice) return; - - const currency = invoice ? invoice.currency : user.currency; - const rate = invoice ? invoice.rate : store.rates[user.currency]; - const tip = invoice ? invoice.tip : 0; - const memo = invoice ? invoice.memo : ""; - - let confirmed = false; - - let { account } = invoice; - - if (account.asset !== config.liquid.btcasset) { - account = await db.Account.findOne({ - where: { - user_id: user.id, - asset: config.liquid.btcasset, - pubkey: null - }, - transaction - }); - } - - await invoice.increment({ pending: value }, { transaction }); - await invoice.reload({ transaction }); - - await account.increment({ pending: value }, { transaction }); - await account.reload({ transaction }); - - if (config.bitcoin.walletpass) - await bc.walletPassphrase(config.bitcoin.walletpass, 300); - - let totalOutputs = tx.outs.reduce((a, b) => a + b.value, 0); - let totalInputs = 0; - for (let i = 0; i < tx.ins.length; i++) { - let { hash, index } = tx.ins[i]; - hash = reverse(hash).toString("hex"); - let hex = await bc.getRawTransaction(hash); - let inputTx = bitcoin.Transaction.fromHex(hex); - totalInputs += inputTx.outs[index].value; - } - let fee = totalInputs - totalOutputs; - - let payment = await db.Payment.create( - { - account_id: account.id, - user_id: user.id, - hash, - fee, - memo, - amount: value - tip, - currency, - rate, - received: true, - tip, - confirmed, - address, - network: "bitcoin", - invoice_id: invoice.id - }, - { transaction } - ); - - payment = payment.get({ plain: true }); - payment.account = account.get({ plain: true }); - payment.invoice = invoice.get({ plain: true }); - - emit(user.username, "account", account); - emit(user.username, "payment", payment); - l("bitcoin detected", user.username, value); - notify(user, `${value} SAT payment detected`); - callWebhook(invoice, payment); - }); - } - } catch (e) { - console.log("problem parsing bitcoin tx", e); - } - }) - ); - } catch (e) { - console.log("problem receiving bitcoin tx", e); - } -}); - -zmqRawBlock.on("message", async (topic, message, sequence) => { - try { - const payments = await db.Payment.findAll({ - where: { confirmed: false } - }); - - let block = bitcoin.Block.fromHex(message.toString("hex")); - block.transactions.map(tx => { - let hash = reverse(tx.getHash()).toString("hex"); - if (payments.find(p => p.hash === hash)) queue[hash] = 1; - }); - } catch (e) { - console.log(e); - } -}); - -setInterval(async () => { - try { - const arr = Object.keys(queue); - for (let i = 0; i < arr.length; i++) { - const hash = arr[i]; - - let account, address, invoice, user, total; - await db.transaction(async transaction => { - let payments = await db.Payment.findAll({ - where: { hash, confirmed: 0, received: 1 }, - include: [ - { - model: db.Account, - as: "account" - }, - { - model: db.Invoice, - as: "invoice" - }, - { - model: db.User, - as: "user" - } - ], - lock: transaction.LOCK.UPDATE, - transaction - }); - - for (let i = 0; i < payments.length; i++) { - let p = payments[i]; - if (p && p.address) address = p.address; - if (p && p.user) user = p.user; - - if (p && p.account) { - ({ account, invoice } = p); - total = p.amount + p.tip; - - p.confirmed = 1; - await account.save({ transaction }); - - await invoice.increment({ received: total }, { transaction }); - await invoice.decrement( - { pending: Math.min(invoice.pending, total) }, - { transaction } - ); - - await account.increment({ balance: total }, { transaction }); - await account.decrement( - { pending: Math.min(account.pending, total) }, - { transaction } - ); - // get the # of fee credits you would need to pay off this amount of bitcoin - await account.increment( - { btc_credits: computeConversionFee(total) }, - { transaction } - ); - await account.reload({ transaction }); - await invoice.reload({ transaction }); - await p.save({ transaction }); - - p = p.get({ plain: true }); - p.account = account.get({ plain: true }); - p.invoice = invoice.get({ plain: true }); - - emit(user.username, "account", account); - emit(user.username, "payment", p); - l("bitcoin confirmed", user.username, p.amount, p.tip); - notify(user, `${total} SAT payment confirmed`); - callWebhook(p.invoice, p); - - let c = store.convert[address]; - if (address && c) { - l( - "bitcoin detected to conversion address", - address, - c.address, - user.username - ); - user.account = account; - - sendLiquid({ - address: c.address, - amount: total - 100, - user, - limit: total - }); - } - } else { - warn("couldn't find bitcoin payment", hash); - } - } - - delete queue[hash]; - }); - } - } catch (e) { - err("problem processing queued bitcoin transaction", e.message, e.stack); - } -}, 1000); diff --git a/routes/bitcoin/send.js b/routes/bitcoin/send.js deleted file mode 100644 index 3e030413..00000000 --- a/routes/bitcoin/send.js +++ /dev/null @@ -1,192 +0,0 @@ -import bc from "$lib/bitcoin"; -import { emit } from "$lib/sockets"; -import db from "$db"; -import { toSats } from "$lib/utils"; -import store from "$lib/store"; -import config from "$config"; -import { l, warn, err } from "$lib/logging"; -import { requirePin } from "$lib/utils"; - -const btc = config.liquid.btcasset; -import { - computeConversionFee, - conversionFeeReceiver, -} from "./conversionFee.js"; - -export default async (req, res) => { - try { - await requirePin(req); - - let { user } = req; - let { address, memo, tx } = req.body; - let { hex } = tx; - - let fee = toSats(tx.fee); - if (fee < 0) throw new Error("fee cannot be negative"); - - const isChange = async ({ address }) => - (await bc.getAddressInfo(address)).ismine && - !Object.keys(store.addresses).includes(address); - - tx = await bc.decodeRawTransaction(hex); - - let total = 0; - let change = 0; - - for (let i = 0; i < tx.vout.length; i++) { - let o = tx.vout[i]; - total += toSats(o.value); - - if (await isChange(o.scriptPubKey)) { - change += toSats(o.value); - } - } - - total = total - change + fee; - let amount = total - fee; - - // get conversion fee - // 'total' refers to the total before the conversion fee - // (i.e. the total bitcoin that leaves this server) - let conversionFee = computeConversionFee(amount); - - // withdraw bitcoin - await db.transaction(async (transaction) => { - let { account } = user; - if (account.asset !== btc) { - account = await db.Account.findOne({ - where: { - user_id: user.id, - asset: btc, - pubkey: null, - }, - lock: transaction.LOCK.UPDATE, - transaction, - }); - } - - // use user's credits to reduce fee, if available - let conversionFeeDeduction = Math.min(account.btc_credits, conversionFee); - if (conversionFeeDeduction) { - await account.decrement( - { btc_credits: conversionFeeDeduction }, - { transaction } - ); - await account.reload({ transaction }); - conversionFee -= conversionFeeDeduction; - } - - l( - "attempting to send bitcoin", - user.username, - account.id, - address, - total, - conversionFee, - conversionFeeDeduction - ); - - if (total > account.balance) { - err("amount exceeds balance", amount, fee, account.balance); - throw new Error("low"); - } else if (total + conversionFee > account.balance) { - err( - "total (after conversion fee) exceeds balance", - amount, - fee, - account.balance - ); - throw new Error("low"); - } - - await account.decrement( - { balance: total + conversionFee }, - { transaction } - ); - await account.reload({ transaction }); - - let receiverAccount = await db.Account.findOne({ - where: { - "$user.username$": conversionFeeReceiver, - }, - include: [ - { - model: db.User, - as: "user", - }, - ], - lock: transaction.LOCK.UPDATE, - transaction, - }); - - let fee_payment; - let fee_payment_id = null; - if (conversionFee) { - await receiverAccount.increment( - { balance: conversionFee }, - { transaction } - ); - await receiverAccount.reload({ transaction }); - fee_payment = await db.Payment.create( - { - amount: conversionFee, - fee: 0, - memo: "Bitcoin conversion fee", - account_id: receiverAccount.id, - user_id: receiverAccount.user_id, - rate: store.rates[receiverAccount.user.currency], - currency: receiverAccount.user.currency, - confirmed: true, - received: true, - network: "COINOS", - }, - { transaction } - ); - fee_payment_id = fee_payment.id; - } - - // record the external bitcoin transaction - let params = { - amount: -amount, - fee, - memo, - account_id: account.id, - user_id: user.id, - rate: store.rates[user.currency], - currency: user.currency, - address, - confirmed: true, - received: false, - network: "bitcoin", - fee_payment_id: fee_payment_id, - }; - - if (config.bitcoin.walletpass) - await bc.walletPassphrase(config.bitcoin.walletpass, 300); - - hex = (await bc.signRawTransactionWithWallet(hex)).hex; - params.hash = await bc.sendRawTransaction(hex); - - let payment = await db.Payment.create(params, { transaction }); - - payment = payment.get({ plain: true }); - payment.account = account.get({ plain: true }); - payment.fee_payment = fee_payment && fee_payment.get({ plain: true }); - - emit(user.username, "payment", payment); - res.send(payment); - - store.payments.push(params.hash); - l("sent bitcoin", user.username, total); - }); - } catch (e) { - if (e.message.includes("Insufficient")) - e.message = - "Our server has insufficient funds, try again later"; - - if (e.message === "low") e.message = "Insufficient funds in account" - - err("error sending bitcoin", e.message); - return res.code(500).send(e.message); - } -}; diff --git a/routes/bitcoin/sweep.js b/routes/bitcoin/sweep.js deleted file mode 100644 index 49fb1f04..00000000 --- a/routes/bitcoin/sweep.js +++ /dev/null @@ -1,87 +0,0 @@ -import config from "$config"; -import { prod } from "$lib/utils"; -import axios from "axios"; -import bitcoin from "bitcoinjs-lib"; -import coinselect from "coinselect"; -import split from "coinselect/split"; - -const api = prod ? "https://blockstream.info/api" : config.bitcoin.electrs; -const SATS = 100000000; -const network = prod - ? bitcoin.networks["bitcoin"] - : bitcoin.networks["regtest"]; - -export default async (req, res) => { - let { address: from, amount, feeRate, target } = req.body; - let utxos; - if (!feeRate) feeRate = (await bc.estimateSmartFee(6)).feerate * SATS; - feeRate = Math.round(feeRate / 1000); - l("sweeping", req.user.username, from, amount, feeRate); - - try { - let { - data: { - chain_stats: { funded_txo_sum: funded, spent_txo_sum: spent } - } - } = await axios.get(`${api}/address/${from}`); - let balance = funded - spent; - console.log("boom"); - - try { - console.log("fetching utxo"); - ({ data: utxos } = await axios.get(`${api}/address/${from}/utxo`)); - console.log("got utxos", utxos); - } catch (e) { - err("problem fetching utxos", e.message); - } - - let targets = [ - { - address: target, - value: amount - } - ]; - - let { inputs, outputs, fee } = coinselect(utxos, targets, feeRate); - - if (balance === amount) { - delete targets[0].value; - ({ inputs, outputs, fee } = split(utxos, targets, feeRate)); - l("split", inputs, outputs, fee); - } - - if (!inputs || !outputs) - return res - .code(500) - .send("Unable to construct sweep transaction, try a lower fee rate?"); - - let psbt = new bitcoin.Psbt({ network }); - let total = 0; - - outputs.map(({ address, value }) => { - total += value; - psbt = psbt.addOutput({ - address: address || from, - value - }); - }); - - for (let i = 0; i < inputs.length; i++) { - let { txid, vout } = inputs[i]; - let nonWitnessUtxo = Buffer.from(await bc.getRawTransaction(txid), "hex"); - psbt = psbt.addInput({ - hash: txid, - index: vout, - nonWitnessUtxo - }); - } - - feeRate = Math.round(feeRate * 1000); - psbt = psbt.toBase64(); - - if (balance !== amount) total = 0; - res.send({ feeRate, psbt, total }); - } catch (e) { - err("problem getting address stats", from, e.message); - } -}; |
