aboutsummaryrefslogtreecommitdiffstats
path: root/routes/liquid
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2020-03-11 15:10:40 -0700
committerAdam Soltys <[email protected]>2020-03-11 15:10:40 -0700
commitc0888fab024cc9f7c99812bfabf9d7774312dbf7 (patch)
treecad95683af97432f6092a2c2bae10fbb7a588db5 /routes/liquid
parentbf2b51f0c2ae9b8f0f330dde6411e45fc0da8a25 (diff)
downloadcoinos-server-c0888fab024cc9f7c99812bfabf9d7774312dbf7.tar.gz
coinos-server-c0888fab024cc9f7c99812bfabf9d7774312dbf7.zip
refactor
Diffstat (limited to 'routes/liquid')
-rw-r--r--routes/liquid/fee.js25
-rw-r--r--routes/liquid/receive.js137
-rw-r--r--routes/liquid/send.js97
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);
+ }
+};