aboutsummaryrefslogtreecommitdiffstats
path: root/routes/liquid
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/liquid
parentf7b844883506a8072213245b9b8a2b988694e4f5 (diff)
downloadcoinos-server-dce07aa93b3c2f47aca0c24cea6adde9ec37f1a7.tar.gz
coinos-server-dce07aa93b3c2f47aca0c24cea6adde9ec37f1a7.zip
tech debt jubilee
Diffstat (limited to 'routes/liquid')
-rw-r--r--routes/liquid/broadcast.js58
-rw-r--r--routes/liquid/conversionFee.js12
-rw-r--r--routes/liquid/fee.js89
-rw-r--r--routes/liquid/generate.js5
-rw-r--r--routes/liquid/index.js11
-rw-r--r--routes/liquid/receive.js406
-rw-r--r--routes/liquid/send.js276
7 files changed, 0 insertions, 857 deletions
diff --git a/routes/liquid/broadcast.js b/routes/liquid/broadcast.js
deleted file mode 100644
index 820c0be1..00000000
--- a/routes/liquid/broadcast.js
+++ /dev/null
@@ -1,58 +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 lq.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: "liquid"
- },
- { 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(hash);
- }
- } catch (e) {
- err("problem broadcasting liquid transaction", e);
- res.code(500).send(e.message);
- }
-};
diff --git a/routes/liquid/conversionFee.js b/routes/liquid/conversionFee.js
deleted file mode 100644
index a25c2e7d..00000000
--- a/routes/liquid/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' L-BTC.
- */
-export function computeConversionFee(amount) {
- return Math.floor(amount * conversionFeeMultiplier);
-}
diff --git a/routes/liquid/fee.js b/routes/liquid/fee.js
deleted file mode 100644
index ae6e4393..00000000
--- a/routes/liquid/fee.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import config from "$config";
-import db from "$db";
-import { emit } from "$lib/sockets";
-import buildTx from "$lib/buildliquidtx";
-import { err, l } from "$lib/logging";
-import { SATS } from "$lib/utils";
-import lq from "$lib/liquid";
-
-export const liquidTx = async ({ address, asset, amount, feeRate, replaceable, user }) => {
- let tx, fee;
-
- if (user.account.pubkey) {
- let psbt = await buildTx({
- address,
- asset,
- amount,
- feeRate,
- replaceable,
- user
- });
- return psbt;
- }
-
- let invoice = await db.Invoice.findOne({
- where: { address },
- include: {
- attributes: ["username"],
- model: db.User,
- as: "user"
- }
- });
-
- if (invoice) {
- let { ismine } = await lq.getAddressInfo(address);
- if (ismine) {
- emit(user.username, "to", invoice.user);
- return;
- }
- }
-
- if (config.liquid.walletpass)
- await lq.walletPassphrase(config.liquid.walletpass, 300);
-
- amount = parseInt(amount);
-
- let params = {
- subtractFeeFromOutputs: amount === user.balance ? [0] : []
- };
-
- let value = (amount / SATS).toFixed(8);
- if (feeRate) params.feeRate = (feeRate / SATS).toFixed(8);
-
- let info = await lq.getAddressInfo(address);
-
- tx = await lq.createRawTransaction(
- [],
- [
- {
- [address]: (amount / SATS).toFixed(8),
- asset
- }
- ],
- 0,
- replaceable
- );
-
- l("funding tx for fee estimate", tx);
-
- tx = await lq.fundRawTransaction(tx, params);
-
- let blinded = await lq.blindRawTransaction(tx.hex);
- let signed = await lq.signRawTransactionWithWallet(blinded);
-
- let decoded = await lq.decodeRawTransaction(signed.hex);
- feeRate = Math.round((tx.fee * SATS * 1000) / decoded.vsize);
- l("estimated", asset, feeRate);
-
- return { feeRate, tx };
-};
-
-export default async (req, res) => {
- try {
- let tx = await liquidTx({ ...req.body, user: req.user });
- res.send(tx);
- } catch (e) {
- err("error estimating liquid fee", e.message, e.stack);
- return res.code(500).send(e.message);
- }
-};
diff --git a/routes/liquid/generate.js b/routes/liquid/generate.js
deleted file mode 100644
index 423d8b6a..00000000
--- a/routes/liquid/generate.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export default async (req, res) => {
- let address = await lq.getNewAddress();
- await lq.generateToAddress(1, address);
- res.send({});
-};
diff --git a/routes/liquid/index.js b/routes/liquid/index.js
deleted file mode 100644
index c4c50021..00000000
--- a/routes/liquid/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import broadcast from "./broadcast";
-import generate from "./generate";
-import fee from "./fee";
-import send from "./send";
-
-export default {
- broadcast,
- generate,
- fee,
- send,
-};
diff --git a/routes/liquid/receive.js b/routes/liquid/receive.js
deleted file mode 100644
index 718feba5..00000000
--- a/routes/liquid/receive.js
+++ /dev/null
@@ -1,406 +0,0 @@
-import db from "$db";
-import config from "$config";
-import { getBlockHeight, getUser, getUserById, toSats, SATS } from "$lib/utils";
-import { emit } from "$lib/sockets";
-import store from "$lib/store";
-import { sendLiquid } from "$routes/liquid/send";
-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 liquidJs from "liquidjs-lib";
-import { l, err, warn } from "$lib/logging";
-import lq from "$lib/liquid";
-
-const { Block, networks, Transaction } = liquidJs;
-
-const network =
- networks[
- config.liquid.network === "mainnet" ? "liquid" : config.liquid.network
- ];
-
-import { computeConversionFee } from "./conversionFee";
-
-const getAccount = async (params, transaction) => {
- let account = await db.Account.findOne({
- where: params,
- lock: transaction.LOCK.UPDATE,
- transaction
- });
-
- if (account) {
- l("found account", params, account.asset, account.id);
- return account;
- }
-
- let { asset, pubkey } = params;
- let name = asset.substr(0, 6);
- let domain = "";
- let ticker = asset.substr(0, 3).toUpperCase();
- let precision = 8;
-
- if (pubkey) {
- const nc = await db.Account.findOne({
- where: { pubkey },
- order: [["id", "ASC"]],
- limit: 1,
- lock: transaction.LOCK.UPDATE,
- transaction
- });
-
- params.index = 0;
- params.path = nc.path;
- params.pubkey = nc.pubkey;
- params.privkey = nc.privkey;
- params.seed = nc.seed;
- }
-
- if (store.assets[asset]) {
- ({ domain, ticker, precision, name } = store.assets[asset]);
- } else {
- const existing = await db.Account.findOne({
- where: {
- asset
- },
- order: [["id", "ASC"]],
- limit: 1,
- lock: transaction.LOCK.UPDATE,
- transaction
- });
-
- if (existing) {
- l("existing", existing.id);
- ({ domain, ticker, precision, name } = existing);
- }
- }
-
- params = { ...params, ...{ domain, ticker, precision, name } };
- params.balance = 0;
- params.pending = 0;
- params.network = "liquid";
- return db.Account.create(params, { transaction });
-};
-
-const zmqRawBlock = zmq.socket("sub");
-zmqRawBlock.connect(config.liquid.zmqrawblock);
-zmqRawBlock.subscribe("rawblock");
-
-const zmqRawTx = zmq.socket("sub");
-zmqRawTx.connect(config.liquid.zmqrawtx);
-zmqRawTx.subscribe("rawtx");
-
-const queue = {};
-const seen = [];
-
-zmqRawTx.on("message", async (topic, message, sequence) => {
- try {
- const hex = message.toString("hex");
-
- let unblinded, tx, blinded, txid;
- try {
- txid = Transaction.fromHex(hex).getId();
- if (seen.includes(txid)) return;
- seen.push(txid);
- if (seen.length > 5000) seen.shift();
-
- unblinded = await lq.unblindRawTransaction(hex);
- tx = await lq.decodeRawTransaction(unblinded.hex);
- blinded = await lq.decodeRawTransaction(hex);
- } catch (e) {
- return err("problem decoding liquid tx", e.message);
- }
-
- for (let o of tx.vout) {
- try {
- if (!(o.scriptPubKey && o.scriptPubKey.address)) return;
-
- const { asset } = o;
- const value = toSats(o.value);
- const { address } = o.scriptPubKey;
-
- if (
- Object.keys(store.addresses).includes(address) &&
- !store.change.includes(address)
- ) {
- await db.transaction(async transaction => {
- let user = await getUser(store.addresses[address], transaction);
-
- let invoice = await db.Invoice.findOne({
- where: {
- [Op.or]: {
- unconfidential: address,
- address
- },
- user_id: user.id,
- network: "liquid"
- },
- order: [["id", "DESC"]],
- transaction
- });
-
- if (!invoice) return;
-
- let confirmed = 0;
-
- let account = await db.Account.findOne({
- where: {
- id: invoice.account_id,
- asset
- },
- lock: transaction.LOCK.UPDATE,
- transaction
- });
-
- if (!account) {
- let pubkey = account ? account.pubkey : null;
-
- account = await getAccount(
- {
- user_id: user.id,
- asset,
- pubkey
- },
- transaction
- );
- }
-
- await account.increment({ pending: value }, { transaction });
- await account.reload({ transaction });
-
- if (config.liquid.walletpass)
- await lq.walletPassphrase(config.liquid.walletpass, 300);
-
- await user.save({ transaction });
-
- 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 payment = await db.Payment.create(
- {
- account_id: account.id,
- user_id: user.id,
- hash: blinded.txid,
- amount: value - tip,
- currency,
- memo,
- rate,
- received: true,
- tip,
- confirmed,
- address,
- network: "liquid",
- 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, "payment", payment);
- emit(user.username, "account", payment.account);
- l("liquid detected", address, user.username, asset, value);
- notify(user, `${value} SAT payment detected`);
- callWebhook(invoice, payment);
- });
- }
- } catch (e) {
- console.log(e);
- err("Problem processing transaction", e.message, e.stack);
- }
- }
- } catch (e) {
- console.log(e);
- }
-});
-
-zmqRawBlock.on("message", async (topic, message, sequence) => {
- try {
- const payments = await db.Payment.findAll({
- where: { confirmed: 0 }
- });
-
- let hash, json;
-
- hash = await lq.getBlockHash(getBlockHeight(message));
- json = await lq.getBlock(hash, 2);
-
- for (let tx of json.tx) {
- if (store.issuances[tx.txid]) {
- await db.transaction(async transaction => {
- const {
- user_id,
- asset,
- asset_amount,
- asset_payment_id,
- token,
- token_amount,
- token_payment_id
- } = store.issuances[tx.txid];
-
- const user = await getUserById(user_id);
-
- let account = await db.Account.findOne({
- where: { user_id, asset },
- lock: transaction.LOCK.UPDATE,
- transaction
- });
- account.balance = Math.round(asset_amount * SATS);
- account.pending = 0;
- await account.save({ transaction });
-
- let payment = await db.Payment.findOne({
- where: { id: asset_payment_id },
- include: {
- model: db.Account,
- as: "account"
- },
- lock: transaction.LOCK.UPDATE,
- transaction
- });
-
- payment.confirmed = true;
- await payment.save({ transaction });
- payment = payment.get({ plain: true });
- payment.account = account.get({ plain: true });
-
- emit(user.username, "account", account);
- emit(user.username, "payment", payment);
-
- if (token) {
- account = await db.Account.findOne({
- where: { user_id, asset: token },
- lock: transaction.LOCK.UPDATE,
- transaction
- });
- account.balance = token_amount * SATS;
- account.pending = 0;
- await account.save({ transaction });
-
- payment = await db.Payment.findOne({
- where: { id: token_payment_id },
- include: {
- model: db.Account,
- as: "account"
- },
- lock: transaction.LOCK.UPDATE,
- transaction
- });
-
- payment.confirmed = true;
- await payment.save({ transaction });
-
- payment = payment.get({ plain: true });
- payment.account = account.get({ plain: true });
-
- emit(user.username, "account", account);
- emit(user.username, "payment", payment);
- }
- });
- } else if (payments.find(p => p.hash === tx.txid)) queue[tx.txid] = 1;
- }
- } catch (e) {
- return 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, invoice, address, user, total, p;
- await db.transaction(async transaction => {
- p = await db.Payment.findOne({
- 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
- });
-
- 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 account.increment({ balance: total }, { transaction });
- await account.decrement(
- { pending: Math.min(account.pending, total) },
- { transaction }
- );
- await account.increment(
- { liquid_credits: computeConversionFee(total) },
- { transaction }
- );
- await account.reload({ transaction });
- await invoice.reload({ transaction });
-
- await p.save({ transaction });
-
- p = p.get({ plain: true });
- account = account.get({ plain: true });
- invoice = invoice.get({ plain: true });
-
- emit(user.username, "account", account);
- emit(user.username, "payment", p);
-
- l("liquid confirmed", user.username, account.asset, p.amount, p.tip);
-
- notify(user, `${total} SAT payment confirmed`);
- callWebhook(p.invoice, p);
- } else {
- warn("couldn't find liquid payment", hash);
- }
-
- delete queue[hash];
- });
-
- let c = store.convert[address];
- if (address && c && p.account.asset === network.assetHash) {
- l(
- "liquid detected for conversion request",
- address,
- c.address,
- user.username
- );
-
- user.account = p.account;
-
- sendLiquid({
- address: c.address,
- amount: total - 100,
- user,
- limit: total
- });
- }
- }
- } catch (e) {
- err("problem processing queued liquid transaction", e.message, e.stack);
- }
-}, 1000);
diff --git a/routes/liquid/send.js b/routes/liquid/send.js
deleted file mode 100644
index 83b5d6ca..00000000
--- a/routes/liquid/send.js
+++ /dev/null
@@ -1,276 +0,0 @@
-import db from "$db";
-import lq from "$lib/liquid";
-import { toSats } from "$lib/utils";
-import { emit } from "$lib/sockets";
-import store from "$lib/store";
-import config from "$config";
-import { l, warn, err } from "$lib/logging";
-import { liquidTx } from "$routes/liquid/fee";
-import { Transaction } from 'liquidjs-lib';
-import { requirePin } from "$lib/utils";
-
-import {
- computeConversionFee,
- conversionFeeReceiver
-} from "./conversionFee.js";
-
-const btc = config.liquid.btcasset;
-const lcad = config.liquid.cadasset;
-
-export const sendLiquid = async ({ asset, amount, user, address, memo, tx, limit }) => {
- try {
- l("sending liquid", amount, address);
- if (!tx) {
- ({ tx } = await liquidTx({
- address,
- asset,
- amount,
- feeRate: 200,
- replaceable: false,
- user
- }));
- }
-
- const isChange = async address =>
- (await lq.getAddressInfo(address)).ismine &&
- !Object.keys(store.addresses).includes(address);
-
- let totals = {};
- let change = {};
- let fee = 0;
-
- let { vout } = await lq.decodeRawTransaction(tx.hex);
-
- for (let i = 0; i < vout.length; i++) {
- let {
- asset,
- value,
- scriptPubKey
- } = vout[i];
-
- if (scriptPubKey.type === "fee") fee = toSats(value);
- else {
- if (!totals[asset]) totals[asset] = change[asset] = 0;
- totals[asset] += toSats(value);
-
- if (scriptPubKey.address) {
- if (await isChange(scriptPubKey.address)) {
- change[asset] += toSats(value);
- }
- }
- }
- }
-
- const assets = Object.keys(totals);
- const payments = [];
-
- let main, signed;
- await db.transaction(async transaction => {
- let fee_payment;
- let fee_payment_id = null;
-
- for (let i = 0; i < assets.length; i++) {
- let asset = assets[i];
- let amount = totals[asset];
- if (change[asset]) amount -= change[asset];
- let total = amount;
-
- if (asset === btc) {
- let covered = 0;
- let nonbtc = assets.filter(a => a !== btc);
- if (nonbtc.length === 1) {
- let faucet = await db.Account.findOne({
- where: {
- asset: nonbtc[0],
- user_id: null
- },
- lock: transaction.LOCK.UPDATE,
- transaction
- });
-
- if (faucet) {
- covered = faucet.balance;
- if (covered > fee) covered = fee;
- await faucet.decrement({ balance: covered }, { transaction });
- await faucet.reload({ transaction });
- await faucet.save({ transaction });
- }
- }
-
- total += fee - covered;
- }
-
- var conversionFee = computeConversionFee(amount);
-
- if (limit && total > limit + fee)
- throw new Error("Tx amount exceeds authorized amount");
-
- if (asset !== btc || total) {
- l("creating liquid payment", user.username, asset, total, fee);
-
- let account = await db.Account.findOne({
- where: {
- user_id: user.id,
- asset,
- pubkey: null
- },
- lock: transaction.LOCK.UPDATE,
- order: [["balance", "DESC"]],
- transaction
- });
-
- // use user's credits to reduce fee, if available
- let conversionFeeDeduction = Math.min(
- account.liquid_credits,
- conversionFee
- );
- if (conversionFeeDeduction) {
- await account.decrement(
- { liquid_credits: conversionFeeDeduction },
- { transaction }
- );
- await account.reload({ transaction });
- conversionFee -= conversionFeeDeduction;
- }
-
- if (asset !== btc) conversionFee = 0;
-
- if (total > account.balance) {
- warn("amount exceeds balance", {
- total,
- fee,
- balance: account.balance
- });
- throw new Error(
- `Insufficient funds, need ${total} ${
- account.ticker === "BTC" ? "SAT" : account.ticker
- }, have ${account.balance}`
- );
- } else if (total + conversionFee > account.balance) {
- warn("amount plus conversion fee exceeds balance", {
- total,
- fee,
- conversionFee,
- balance: account.balance
- });
- throw new Error(
- `Insufficient funds, need ${total + conversionFee} ${
- account.ticker === "BTC" ? "SAT" : account.ticker
- }, have ${account.balance}`
- );
- }
-
- 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
- });
-
- if (conversionFee) {
- await receiverAccount.increment(
- { balance: conversionFee },
- { transaction }
- );
- await receiverAccount.reload({ transaction });
- fee_payment = {
- amount: conversionFee,
- fee: 0,
- memo: "Liquid 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"
- };
- fee_payment.account = receiverAccount;
- ({ id: fee_payment_id } = await db.Payment.create(fee_payment, {
- transaction
- }));
- }
-
- let payment = {
- amount: -amount,
- account_id: account.id,
- fee,
- memo,
- user_id: user.id,
- rate: store.rates[user.currency],
- currency: user.currency,
- address,
- confirmed: true,
- received: false,
- network: "liquid",
- fee_payment_id
- };
-
- payment.account = account;
- payments.push(payment);
- }
- }
-
- signed = await lq.signRawTransactionWithWallet(
- await lq.blindRawTransaction(tx.hex)
- );
- let txid = Transaction.fromHex(signed.hex).getId();
-
- for (let i = 0; i < payments.length; i++) {
- let p = payments[i];
- if (p) {
- let { account } = p;
- p.hash = txid;
- p = await db.Payment.create(p, { transaction });
- if (p.user_id === user.id && (account.ticker !== "BTC" || !main)) {
- main = p.get({ plain: true });
- main.fee_payment = fee_payment;
- main.account = account.get({ plain: true });
- }
- }
- }
-
- emit(user.username, "account", main.account);
- emit(user.username, "payment", main);
- });
-
- if (config.liquid.walletpass)
- await lq.walletPassphrase(config.liquid.walletpass, 300);
-
- let txid = await lq.sendRawTransaction(signed.hex);
- l("sent liquid tx", txid, address);
-
- return main;
- } catch (e) {
- err("problem sending liquid", e.message);
- return e;
- }
-};
-
-export default async (req, res) => {
- let { user } = req;
-
- try {
- await requirePin(req);
- let result = await sendLiquid({ ...req.body, user })
- if (result instanceof Error) throw result;
- res.send(result);
- } catch (e) {
- console.log(e)
- err("problem sending liquid", user.username, e.message);
- return res.code(500).send(e);
- }
-};