aboutsummaryrefslogtreecommitdiffstats
path: root/routes/bitcoin
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2023-01-08 21:05:34 -0800
committerAdam Soltys <[email protected]>2023-01-08 21:05:34 -0800
commitdce07aa93b3c2f47aca0c24cea6adde9ec37f1a7 (patch)
tree6c6b8930ebe9c2cc5b8d1f759e61260f509fdd7a /routes/bitcoin
parentf7b844883506a8072213245b9b8a2b988694e4f5 (diff)
downloadcoinos-server-dce07aa93b3c2f47aca0c24cea6adde9ec37f1a7.tar.gz
coinos-server-dce07aa93b3c2f47aca0c24cea6adde9ec37f1a7.zip
tech debt jubilee
Diffstat (limited to 'routes/bitcoin')
-rw-r--r--routes/bitcoin/broadcast.js56
-rw-r--r--routes/bitcoin/conversionFee.js12
-rw-r--r--routes/bitcoin/fee.js73
-rw-r--r--routes/bitcoin/generate.js5
-rw-r--r--routes/bitcoin/index.js13
-rw-r--r--routes/bitcoin/receive.js281
-rw-r--r--routes/bitcoin/send.js192
-rw-r--r--routes/bitcoin/sweep.js87
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);
- }
-};