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/liquid | |
| parent | f7b844883506a8072213245b9b8a2b988694e4f5 (diff) | |
| download | coinos-server-dce07aa93b3c2f47aca0c24cea6adde9ec37f1a7.tar.gz coinos-server-dce07aa93b3c2f47aca0c24cea6adde9ec37f1a7.zip | |
tech debt jubilee
Diffstat (limited to 'routes/liquid')
| -rw-r--r-- | routes/liquid/broadcast.js | 58 | ||||
| -rw-r--r-- | routes/liquid/conversionFee.js | 12 | ||||
| -rw-r--r-- | routes/liquid/fee.js | 89 | ||||
| -rw-r--r-- | routes/liquid/generate.js | 5 | ||||
| -rw-r--r-- | routes/liquid/index.js | 11 | ||||
| -rw-r--r-- | routes/liquid/receive.js | 406 | ||||
| -rw-r--r-- | routes/liquid/send.js | 276 |
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); - } -}; |
