diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | lib/passport.js | 4 | ||||
| -rw-r--r-- | routes/assets.js | 13 | ||||
| -rw-r--r-- | routes/liquid/receive.js | 9 | ||||
| -rw-r--r-- | routes/send.js | 1 | ||||
| -rw-r--r-- | routes/swaps.js | 277 |
6 files changed, 216 insertions, 89 deletions
@@ -10,3 +10,4 @@ yarn-error.log tls.* proposal.txt leveldb +swaps diff --git a/lib/passport.js b/lib/passport.js index 8f87d42d..660233ca 100644 --- a/lib/passport.js +++ b/lib/passport.js @@ -2,7 +2,9 @@ const Passport = require("passport"); const jwt = require("passport-jwt"); function cookieExtractor(req) { - if (req && req.cookies) return req.cookies["token"]; + if (req && req.cookies) { + return req.cookies["token"]; + } return null; } diff --git a/routes/assets.js b/routes/assets.js index 25ed8ea6..27643c0d 100644 --- a/routes/assets.js +++ b/routes/assets.js @@ -2,10 +2,17 @@ const axios = require("axios"); const crypto = require("crypto"); app.get("/assets", async (req, res) => { - let assets; try { - assets = await axios.get("https://assets.blockstream.info/"); - res.send(assets.data); + const { data: assets } = await axios.get("https://assets.blockstream.info/"); + const accounts = await db.Account.findAll({ + group: ['asset'], + }); + + accounts.map(a => { + if (!assets[a.asset]) assets[a.asset] = a; + }); + + res.send(assets); } catch (e) { l.error("error fetching assets", e); res.status(500).send("error fetching assets"); diff --git a/routes/liquid/receive.js b/routes/liquid/receive.js index 45419ecb..447a031f 100644 --- a/routes/liquid/receive.js +++ b/routes/liquid/receive.js @@ -100,7 +100,7 @@ zmqRawTx.on("message", async (topic, message, sequence) => { const currency = invoice ? invoice.currency : user.currency; const rate = invoice ? invoice.rate : app.get("rates")[user.currency]; const tip = invoice ? invoice.tip : 0; - const memo = invoice ? invoice.memo : ''; + const memo = invoice ? invoice.memo : ""; let payment = await db.Payment.create({ account_id: account.id, @@ -122,6 +122,7 @@ zmqRawTx.on("message", async (topic, message, sequence) => { payment.account = account.get({ plain: true }); emit(user.username, "payment", payment); + emit(user.username, "account", account); l.info("liquid detected", user.username, asset, value); notify(user, `${value} SAT payment detected`); } @@ -176,6 +177,9 @@ zmqRawBlock.on("message", async (topic, message, sequence) => { 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); @@ -202,6 +206,9 @@ zmqRawBlock.on("message", async (topic, message, sequence) => { 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); } diff --git a/routes/send.js b/routes/send.js index d2ba3e5d..f774e852 100644 --- a/routes/send.js +++ b/routes/send.js @@ -49,6 +49,7 @@ module.exports = async (req, res) => { l.info("sent internal", user.username, -payment.amount); emit(user.username, "payment", payment); + emit(user.username, "account", account); emit(user.username, "user", user); res.send(payment); diff --git a/routes/swaps.js b/routes/swaps.js index 3a77ec19..b604710f 100644 --- a/routes/swaps.js +++ b/routes/swaps.js @@ -1,4 +1,5 @@ const axios = require("axios"); +const crypto = require("crypto"); const level = require("level"); const fs = require("fs"); const { spawn } = require("child_process"); @@ -9,6 +10,11 @@ const assets = { "4eebe36eb0819e6daa5dd3c97733251ff4eb728c810d949365d6dacaad5ef6e8": "tether", }; +const swapsdir = 'swaps/'; +if (!fs.existsSync(swapsdir)){ + fs.mkdirSync(swapsdir); +} + const cli = (...args) => { const params = ["--regtest", "-c", config.liquid.conf, ...args]; if (config.liquid.network !== "regtest") params.shift(); @@ -28,7 +34,7 @@ const createProposal = (a1, v1, a2, v2) => const proc = cli("propose", a1, v1, a2, v2); proc.stdout.on("data", (data) => { - fs.writeFile("proposal.txt", data.toString(), function (err) { + fs.writeFile(swapsdir + "proposal.txt", data.toString(), function (err) { if (err) { return l.error(err); } @@ -45,7 +51,7 @@ const createProposal = (a1, v1, a2, v2) => const getInfo = (filename = "proposal.txt") => new Promise((resolve, reject) => { - const proc = cli("info", filename); + const proc = cli("info", swapsdir + filename); proc.stdout.on("data", (data) => { resolve(data.toString()); @@ -82,7 +88,7 @@ app.get("/proposal", auth, async (req, res) => { assets["b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23"] = "bitcoin"; - if (!(assets[a1] && assets[a2])) throw new Error("unsupported assets"); + if (!assets[a1]) throw new Error("unsupported asset"); if (v1 > b[assets[a1]]) throw new Error("insufficient server funds"); const account = await db.Account.findOne({ where: { @@ -125,7 +131,6 @@ app.get("/proposal", auth, async (req, res) => { app.post("/accept", optionalAuth, async (req, res) => { try { const { id, text } = req.body; - l.info("id", id); const proposal = await db.Proposal.findOne({ where: { @@ -142,100 +147,83 @@ app.post("/accept", optionalAuth, async (req, res) => { const filename = `proposal-${id}.txt`; - fs.writeFileSync(filename, proposal.text); - const info = JSON.parse(await getInfo(filename)); + fs.writeFileSync(swapsdir + filename, proposal.text); + let info = JSON.parse(await getInfo(filename)); const { user } = req; + if (user) { + let [fee, rate, asset] = parse(info); const leg1 = info.legs.find((leg) => !leg.incoming && leg.funded); const leg2 = info.legs.find((leg) => leg.incoming && !leg.funded); await db.transaction(async (transaction) => { - const l1a1 = await db.Account.findOne({ - where: { - user_id: user.id, - asset: leg1.asset, - }, - lock: transaction.LOCK.UPDATE, - transaction, - }); - - if (!l1a1) throw new Error("Outgoing asset not found"); - - const l1a2 = await db.Account.findOne({ - where: { - user_id: proposal.user_id, - asset: leg1.asset, - }, - lock: transaction.LOCK.UPDATE, - transaction, - }); + const l1a1 = await getAccount(leg1.asset, user); + const l1a2 = await getAccount(leg1.asset, proposal.user); let amount = Math.round(leg1.amount * SATS); if (amount > l1a2.balance) - throw new Error("Proposer has insufficient funds"); + throw new Error(`Proposer has insufficient funds: ${amount}, ${l1a2.balance}`); l1a1.balance += amount; l1a2.balance -= amount; - let l2a1 = await db.Account.findOne({ - where: { - user_id: user.id, - asset: leg2.asset, - }, - lock: transaction.LOCK.UPDATE, - transaction, + let l1a1p = await db.Payment.create({ + hash: "Internal Transfer", + amount, + account_id: l1a1.id, + memo: "Atomic Swap", + user_id: user.id, + rate, + confirmed: true, + received: true, + network: "COINOS", }); - if (!l2a1) { - let { asset } = leg2; - let params = { - user_id: user.id, - asset, - }; - let name = asset.substr(0, 6); - let ticker = asset.substr(0, 3).toUpperCase(); - let precision = 8; - - const assets = (await axios.get("https://assets.blockstream.info/")) - .data; - - if (assets[asset]) { - ({ ticker, precision, name } = assets[asset]); - } else { - const existing = await db.Account.findOne({ - where: { - asset, - }, - order: [["id", "ASC"]], - limit: 1, - }); - - if (existing) { - ({ ticker, precision, name } = existing); - } - } - - params = { ...params, ...{ ticker, precision, name } }; - params.balance = 0; - params.pending = 0; - l2a1 = await db.Account.create(params, { transaction }); - } - - const l2a2 = await db.Account.findOne({ - where: { - user_id: proposal.user_id, - asset: leg2.asset, - }, - lock: transaction.LOCK.UPDATE, - transaction, + let l1a2p = await db.Payment.create({ + hash: "Internal Transfer", + amount: -amount, + account_id: l1a2.id, + memo: "Atomic Swap", + user_id: proposal.user_id, + rate, + confirmed: true, + received: false, + network: "COINOS", }); + const l2a1 = await getAccount(leg2.asset, user); + const l2a2 = await getAccount(leg2.asset, proposal.user); + amount = Math.round(leg2.amount * SATS); if (amount > l2a1.balance) throw new Error("Insufficient funds"); l2a1.balance -= amount; l2a2.balance += amount; + let l2a1p = await db.Payment.create({ + hash: "Internal Transfer", + amount: -amount, + account_id: l2a1.id, + memo: "Atomic Swap", + user_id: user.id, + rate, + confirmed: true, + received: false, + network: "COINOS", + }); + + let l2a2p = await db.Payment.create({ + hash: "Internal Transfer", + amount, + account_id: l2a2.id, + memo: "Atomic Swap", + user_id: proposal.user_id, + rate, + confirmed: true, + received: true, + network: "COINOS", + }); + await l1a1.save({ transaction }); await l1a2.save({ transaction }); await l2a1.save({ transaction }); @@ -244,21 +232,100 @@ app.post("/accept", optionalAuth, async (req, res) => { proposal.accepted = true; await proposal.save({ transaction }); + l1a1p.account = l1a1.get({ plain: true }); + l1a2p.account = l1a2.get({ plain: true }); + l2a1p.account = l2a1.get({ plain: true }); + l2a2p.account = l2a2.get({ plain: true }); + emit(user.username, "account", l1a1); emit(user.username, "account", l2a1); emit(proposal.user.username, "account", l1a2); emit(proposal.user.username, "account", l2a2); + emit(user.username, "payment", l1a1p); + emit(user.username, "payment", l2a1p); + emit(proposal.user.username, "payment", l1a2p); + emit(proposal.user.username, "payment", l2a2p); emit(user.username, "proposal", proposal); emit(proposal.user.username, "proposal", proposal); }); } else if (text) { const filename = `acceptance-${id}.txt`; - fs.writeFileSync(filename, text); - const info = JSON.parse(await getInfo()); - l.info("info", info); + fs.writeFileSync(swapsdir + filename, text); + + info = JSON.parse(await getInfo(filename)); + let [fee, rate, asset] = parse(info); + let { tx, u_address_p, u_address_r } = JSON.parse( + Buffer.from(text, "base64").toString() + ); + const sha256 = crypto.createHash("sha256"); + sha256.update(tx); + const hash = sha256.digest("hex"); + + const leg1 = info.legs.find((leg) => !leg.incoming); + const leg2 = info.legs.find((leg) => leg.incoming); + + const l1a2 = await getAccount(leg1.asset, proposal.user); + const btc = await getAccount(config.liquid.btcasset, proposal.user); + + await db.transaction(async (transaction) => { + + let amount = Math.round(leg1.amount * SATS); + fee = Math.round(fee * SATS); + + if (amount > l1a2.balance) + throw new Error(`Proposer has insufficient funds: ${amount}, ${l1a2.balance}`); + + if (fee > btc.balance) + throw new Error(`Proposer has insufficient funds for fee: ${fee}, ${btc.balance}`); + + l1a2.balance -= amount; + btc.balance -= fee; + + const payment = await db.Payment.create({ + hash, + amount: -amount, + account_id: l1a2.id, + fee, + memo: "Atomic Swap", + user_id: proposal.user_id, + rate, + address: u_address_r, + confirmed: true, + received: false, + network: "LBTC", + }); + + payment.account = l1a2.get({ plain: true }); + amount = Math.round(leg2.amount * SATS); + + await db.Invoice.create({ + user_id: proposal.user_id, + text: u_address_p, + currency: proposal.user.currency, + memo: "Atomic Swap", + rate, + amount, + tip: 0, + network: "LBTC", + }); + + addresses[u_address_p] = proposal.user.username; + + await finalize(filename); + await l1a2.save({ transaction }); + await btc.save({ transaction }); + + proposal.accepted = true; + await proposal.save({ transaction }); + + emit(proposal.user.username, "payment", payment); + emit(proposal.user.username, "account", l1a2); + emit(proposal.user.username, "account", btc); + emit(proposal.user.username, "proposal", proposal); + }); } else { throw new Error("no acceptance provided"); - } + } res.send(info); } catch (e) { @@ -271,7 +338,7 @@ app.post("/acceptance", async (req, res) => { const { acceptance: text } = req.body; l.info("acceptance received"); - fs.writeFile("accepted.txt", text, async (err) => { + fs.writeFile(swapsdir + "accepted.txt", text, async (err) => { if (err) { return res.status(500).send(err); } @@ -279,7 +346,7 @@ app.post("/acceptance", async (req, res) => { try { let info = JSON.parse( await new Promise((resolve, reject) => { - const proc = cli("info", "accepted.txt"); + const proc = cli("info", swapsdir + "accepted.txt"); proc.stdout.on("data", (data) => { resolve(data.toString()); @@ -387,11 +454,11 @@ const parse = (info) => { ]; }; -const finalize = (text) => { - fs.writeFileSync("accepted.txt", text); +const finalize = (filename = "finalized.txt", text) => { + if (text) fs.writeFileSync(swapsdir + filename, text); return new Promise((resolve, reject) => { - const proc = cli("finalize", "accepted.txt", "-s"); + const proc = cli("finalize", swapsdir + filename, "-s"); proc.stdout.on("data", (data) => { resolve(data.toString()); @@ -436,3 +503,45 @@ app.post("/publish", auth, async (req, res) => { res.status(500).send(e.message); } }); + +const getAccount = async (asset, user) => { + const account = await db.Account.findOne({ + where: { + user_id: user.id, + asset, + }, + }); + + if (account) return account; + + let params = { + user_id: user.id, + asset, + }; + let name = asset.substr(0, 6); + let ticker = asset.substr(0, 3).toUpperCase(); + let precision = 8; + + const assets = (await axios.get("https://assets.blockstream.info/")).data; + + if (assets[asset]) { + ({ ticker, precision, name } = assets[asset]); + } else { + const existing = await db.Account.findOne({ + where: { + asset, + }, + order: [["id", "ASC"]], + limit: 1, + }); + + if (existing) { + ({ ticker, precision, name } = existing); + } + } + + params = { ...params, ...{ ticker, precision, name } }; + params.balance = 0; + params.pending = 0; + return db.Account.create(params); +}; |
