diff options
| author | Adam Soltys <[email protected]> | 2020-03-11 15:10:40 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2020-03-11 15:10:40 -0700 |
| commit | c0888fab024cc9f7c99812bfabf9d7774312dbf7 (patch) | |
| tree | cad95683af97432f6092a2c2bae10fbb7a588db5 /routes/liquid | |
| parent | bf2b51f0c2ae9b8f0f330dde6411e45fc0da8a25 (diff) | |
| download | coinos-server-c0888fab024cc9f7c99812bfabf9d7774312dbf7.tar.gz coinos-server-c0888fab024cc9f7c99812bfabf9d7774312dbf7.zip | |
refactor
Diffstat (limited to 'routes/liquid')
| -rw-r--r-- | routes/liquid/fee.js | 25 | ||||
| -rw-r--r-- | routes/liquid/receive.js | 137 | ||||
| -rw-r--r-- | routes/liquid/send.js | 97 |
3 files changed, 259 insertions, 0 deletions
diff --git a/routes/liquid/fee.js b/routes/liquid/fee.js new file mode 100644 index 00000000..402c0b2a --- /dev/null +++ b/routes/liquid/fee.js @@ -0,0 +1,25 @@ +module.exports = async (req, res) => { + let { user } = req; + let { address, amount, feeRate } = req.body; + let tx, fee; + + try { + amount = parseInt(amount); + + tx = await lq.createRawTransaction([], { + [address]: (amount / SATS).toFixed(8) + }); + + let params = { + subtractFeeFromOutputs: amount === user.balance ? [0] : [] + } + + if (feeRate) params["feeRate"] = (parseInt(feeRate) / SATS * 1000).toFixed(8); + + tx = await lq.fundRawTransaction(tx, params); + res.send({ tx }); + } catch (e) { + l.error("error estimating liquid fee", e); + return res.status(500).send(e.message); + } +}; diff --git a/routes/liquid/receive.js b/routes/liquid/receive.js new file mode 100644 index 00000000..2e622aad --- /dev/null +++ b/routes/liquid/receive.js @@ -0,0 +1,137 @@ +const reverse = require("buffer-reverse"); +const zmq = require("zeromq"); +const { Op } = require("sequelize"); +const bitcoin = require("elementsjs-lib"); + +const zmqRawBlock = zmq.socket("sub"); +zmqRawBlock.connect(config.liquid.zmqrawblock); +zmqRawBlock.subscribe("rawblock"); + +const zmqRawTx = zmq.socket("sub"); +zmqRawTx.connect("tcp://127.0.0.1:18603"); +zmqRawTx.subscribe("rawtx"); + +let NETWORK = bitcoin.networks[config.bitcoin.network === "mainnet" ? "bitcoin" : config.bitcoin.network]; + +zmqRawTx.on("message", async (topic, message, sequence) => { + const hex = message.toString("hex"); + const unblinded = await lq.unblindRawTransaction(hex); + const tx = await lq.decodeRawTransaction(unblinded.hex); + const blinded = await lq.decodeRawTransaction(hex); + if (payments.includes(blinded.txid)) return; + + Promise.all( + tx.vout.map(async o => { + if (!(o.scriptPubKey && o.scriptPubKey.addresses)) return; + const value = toSats(o.value); + const address = o.scriptPubKey.addresses[0]; + + if (Object.keys(addresses).includes(address)) { + let user = await db.User.findOne({ + where: { + username: addresses[address] + } + }); + + let invoices = await db.Payment.findAll({ + limit: 1, + where: { + address, + received: null, + amount: { + [Op.gt]: 0 + } + }, + order: [["id", "DESC"]] + }); + + let tip = null; + if (invoices.length) tip = invoices[0].tip; + + let confirmed = 0; + + user.pending += value; + user.confidential = await lq.getNewAddress(); + user.liquid = (await lq.getAddressInfo(user.confidential)).unconfidential; + + await user.save(); + emit(user.username, "user", user); + + addresses[user.liquid] = user.username; + + const payment = await db.Payment.create({ + user_id: user.id, + hash: blinded.txid, + amount: value, + currency: user.currency, + rate: app.get("rates")[user.currency], + received: true, + tip, + confirmed, + address, + asset: 'LBTC', + }); + payments.push(blinded.txid); + + l.info("liquid detected", user.username, value); + emit(user.username, "payment", payment); + } + }) + ); +}); + +let queue = {}; + +zmqRawBlock.on("message", async (topic, message, sequence) => { + const payments = await db.Payment.findAll({ + where: { confirmed: 0 } + }); + + const hashes = payments.map(p => p.hash); + + const block = bitcoin.Block.fromHex(message.toString("hex"), true); + const hash = await lq.getBlockHash(block.height); + const json = await lq.getBlock(hash, 2); + + json.tx.map(tx => { + if (hashes.includes(tx.txid)) queue[tx.txid] = 1; + }); +}); + +setInterval(async () => { + let arr = Object.keys(queue); + + for (let i = 0; i < arr.length; i++) { + let hash = arr[i]; + + let p = await db.Payment.findOne({ + include: [{ model: db.User, as: "user" }], + where: { hash, confirmed: 0, received: 1 } + }) + + p.confirmed = 1; + + let user = await p.getUser(); + user.balance += p.amount; + user.pending -= Math.min(user.pending, p.amount); + l.info("liquid confirmed", user.username, p.amount); + emit(user.username, "user", user); + + await user.save(); + await p.save(); + + let payments = await db.Payment.findAll({ + where: { + user_id: user.id, + received: { + [Op.ne]: null + }, + }, + order: [['id', 'DESC']], + limit: 12 + }); + + emit(user.username, "payments", payments); + delete queue[hash]; + } +}, 1000); diff --git a/routes/liquid/send.js b/routes/liquid/send.js new file mode 100644 index 00000000..aaced4a9 --- /dev/null +++ b/routes/liquid/send.js @@ -0,0 +1,97 @@ +const reverse = require("buffer-reverse"); + +module.exports = async (req, res) => { + let { user } = req; + let { + address, + tx: { hex } + } = req.body; + + const isChange = async address => + !((await bc.getAddressInfo(address)).ismine) || + !Object.keys(addresses).includes(address) || + address === user.liquid; + + const unblinded = await bc.unblindRawTransaction(hex); + tx = await bc.decodeRawTransaction(unblinded.hex); + + let total = 0; + let change = 0; + let fee = 0; + + for (let i = 0; i < tx.vout.length; i++) { + let o = tx.vout[i]; + total += toSats(o.value); + if (o.scriptPubKey.type === 'fee') fee = toSats(o.value); + + if (o.scriptPubKey.addresses) { + if ((await isChange(o.scriptPubKey.addresses[0]))) { + change += toSats(o.value); + } + } + } + + total = total - change; + let amount = total - fee; + + try { + await db.transaction(async transaction => { + let { balance } = await db.User.findOne({ + where: { + username: user.username + }, + lock: transaction.LOCK.UPDATE, + transaction + }); + + if (amount !== balance && total > balance) { + l.warn("amount exceeds balance", { amount, fee, balance }); + throw new Error("insufficient funds"); + } + + req.user.balance -= total; + await user.save({ transaction }); + }); + } catch (e) { + l.warn("insufficient funds for liquid payment", e); + return res.status(500).send("Not enough satoshis"); + } + + try { + if (config.liquid.walletpass) + await bc.walletPassphrase(config.liquid.walletpass, 300); + + hex = await bc.blindRawTransaction(hex); + rawtx = (await bc.signRawTransactionWithWallet(hex)).hex; + let txid = await bc.sendRawTransaction(rawtx); + + await db.transaction(async transaction => { + await user.save({ transaction }); + emit(user.username, "user", user); + l.info("total", total); + + const payment = await db.Payment.create( + { + amount: -total, + fee, + user_id: user.id, + hash: txid, + rate: app.get("rates")[user.currency], + currency: user.currency, + address, + confirmed: true, + received: false, + asset: 'LBTC' + }, + { transaction } + ); + + l.info("sent liquid", user.username, total); + emit(user.username, "payment", payment); + res.send(payment); + }); + } catch (e) { + l.error("liquid send failed", e); + return res.status(500).send(e.message); + } +}; |
