From 5a15eb109b6ddd8232abfecaafc7d8ba7ada14ab Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 20 Jul 2020 21:16:16 -0700 Subject: atomic swaps --- .gitignore | 2 + db/index.js | 6 ++ db/models/proposals.js | 102 +++++++++++++++++++ db/schema.sql | 34 +++++-- index.js | 1 + lib/binance.js | 6 +- lib/sockets.js | 8 +- package.json | 1 + routes/swaps.js | 259 +++++++++++++++++++++++++++++++++++++++++++++++++ yarn.lock | 198 ++++++++++++++++++++++++++++++++++++- 10 files changed, 605 insertions(+), 12 deletions(-) create mode 100644 db/models/proposals.js create mode 100644 routes/swaps.js diff --git a/.gitignore b/.gitignore index 28a82f66..f18358f2 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ yarn-error.log .pnp* .yarn* tls.* +proposal.txt +leveldb diff --git a/db/index.js b/db/index.js index 1b4cc0e4..7f011c9c 100644 --- a/db/index.js +++ b/db/index.js @@ -13,6 +13,7 @@ require("./models/codes.js"); require("./models/invoices.js"); require("./models/keys.js"); require("./models/payments.js"); +require("./models/proposals.js"); require("./models/users.js"); db["User"].hasMany(db["Account"], { @@ -64,3 +65,8 @@ db["Payment"].belongsTo(db["User"], { as: "user", foreignKey: "user_id" }); + +db["Proposal"].belongsTo(db["User"], { + as: "user", + foreignKey: "user_id" +}); diff --git a/db/models/proposals.js b/db/models/proposals.js new file mode 100644 index 00000000..350bdab8 --- /dev/null +++ b/db/models/proposals.js @@ -0,0 +1,102 @@ +const { DataTypes } = require('sequelize'); + +const attributes = { + id: { + type: DataTypes.INTEGER(11), + allowNull: false, + defaultValue: null, + primaryKey: true, + autoIncrement: true, + comment: null, + field: "id" + }, + user_id: { + type: DataTypes.INTEGER(11), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "user_id" + }, + createdAt: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "createdAt" + }, + updatedAt: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "updatedAt" + }, + a1: { + type: DataTypes.STRING(255), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "a1" + }, + a2: { + type: DataTypes.STRING(255), + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "a2" + }, + v1: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "v1" + }, + v2: { + type: DataTypes.INTEGER, + allowNull: true, + defaultValue: null, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "v2" + }, + text: { + type: DataTypes.TEXT, + allowNull: false, + defaultValue: false, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "text" + }, + accepted: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + primaryKey: false, + autoIncrement: false, + comment: null, + field: "accepted" + }, +}; + +const options = { + tableName: "proposals", + comment: "", + indexes: [] +}; + +db["Proposal"] = db.define("proposals_model", attributes, options); diff --git a/db/schema.sql b/db/schema.sql index 940a3490..ff272a43 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -57,7 +57,7 @@ CREATE TABLE `accounts` ( `ticker` varchar(255) DEFAULT NULL, `precision` int(11) DEFAULT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=3730 DEFAULT CHARSET=latin1; +) ENGINE=InnoDB AUTO_INCREMENT=3731 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -98,7 +98,7 @@ CREATE TABLE `invoices` ( `uuid` varchar(255) DEFAULT NULL, `memo` text DEFAULT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=4626 DEFAULT CHARSET=latin1; +) ENGINE=InnoDB AUTO_INCREMENT=4644 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -115,7 +115,7 @@ CREATE TABLE `linkingkeys` ( `createdAt` datetime NOT NULL, `updatedAt` datetime NOT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=4495 DEFAULT CHARSET=latin1; +) ENGINE=InnoDB AUTO_INCREMENT=4497 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -144,7 +144,29 @@ CREATE TABLE `payments` ( `preimage` varchar(255) DEFAULT NULL, `memo` text DEFAULT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=4458 DEFAULT CHARSET=latin1; +) ENGINE=InnoDB AUTO_INCREMENT=4459 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `proposals` +-- + +DROP TABLE IF EXISTS `proposals`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `proposals` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `a1` varchar(255) DEFAULT NULL, + `a2` varchar(255) DEFAULT NULL, + `v1` int(11) DEFAULT NULL, + `v2` int(11) DEFAULT NULL, + `user_id` int(11) DEFAULT NULL, + `createdAt` datetime NOT NULL, + `updatedAt` datetime NOT NULL, + `accepted` tinyint(1) NOT NULL DEFAULT 0, + `text` text DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -192,7 +214,7 @@ CREATE TABLE `users` ( `fiat` tinyint(1) NOT NULL DEFAULT 0, PRIMARY KEY (`id`), KEY `ip` (`ip`) -) ENGINE=InnoDB AUTO_INCREMENT=633 DEFAULT CHARSET=latin1; +) ENGINE=InnoDB AUTO_INCREMENT=634 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; @@ -204,4 +226,4 @@ CREATE TABLE `users` ( /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2020-07-17 20:55:55 +-- Dump completed on 2020-07-20 21:16:00 diff --git a/index.js b/index.js index b40fa99d..82ae8db5 100644 --- a/index.js +++ b/index.js @@ -46,6 +46,7 @@ require("./routes/assets"); require("./routes/invoices"); require("./routes/payments"); require("./routes/info"); +require("./routes/swaps"); require("./routes/users"); if (config.lnurl) { diff --git a/lib/binance.js b/lib/binance.js index 1951d820..aa6364c8 100644 --- a/lib/binance.js +++ b/lib/binance.js @@ -15,9 +15,13 @@ module.exports = () => { rates[symbol] = msg.c * fx[symbol]; }); + app.set("ask", msg.a); + app.set("last", msg.l); + app.set("balance", await lq.getBalance()); app.set("rates", rates); + checkQueue(); } catch (e) { - l.error("binance socket error", e); + l.error("binance socket error", e.message); } }; diff --git a/lib/sockets.js b/lib/sockets.js index 2dae9e93..87317a16 100644 --- a/lib/sockets.js +++ b/lib/sockets.js @@ -114,10 +114,10 @@ wss.on("connection", function (ws, req) { user_id: ws.user.id, id: data.id, }, - include: { - model: db.Account, - as: "account", - }, + include: { + model: db.Account, + as: "account", + }, }); if (payment) { payment.memo = data.memo; diff --git a/package.json b/package.json index 49cdffa6..84d270ec 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "http-proxy-middleware": "^0.20.0", "jsonwebtoken": "^8.5.1", "knex": "^0.21.1", + "level": "^6.0.1", "lnurl": "https://github.com/asoltys/lnurl-node", "long": "^4.0.0", "mailgun-js": "^0.22.0", diff --git a/routes/swaps.js b/routes/swaps.js new file mode 100644 index 00000000..8edf4863 --- /dev/null +++ b/routes/swaps.js @@ -0,0 +1,259 @@ +const level = require("level"); +const fs = require("fs"); +const { spawn } = require("child_process"); +const leveldb = level("leveldb"); + +const assets = { + b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23: "bitcoin", + "4eebe36eb0819e6daa5dd3c97733251ff4eb728c810d949365d6dacaad5ef6e8": "tether", +}; + +const createProposal = (a1, v1, a2, v2) => + new Promise((resolve, reject) => { + l.info("running liquid-cli tool %s %s %s %s %s", config.liquid.conf, a1, v1, a2, v2); + const proc = spawn("liquidswap-cli", [ + "--regtest", + "-c", + config.liquid.conf, + "propose", + a1, + v1, + a2, + v2, + ]); + + proc.stdout.on("data", (data) => { + fs.writeFile("proposal.txt", data.toString(), function (err) { + if (err) { + return l.error(err); + } + l.info("proposal.txt file saved"); + }); + resolve(data.toString()); + }); + + proc.stderr.on("error", (err) => { + l.error("proposal error", err.toString()); + reject(err.toString()); + }); + }); + +const getInfo = () => + new Promise((resolve, reject) => { + const spawn = require("child_process").spawn; + const proc = spawn("liquidswap-cli", [ + "--regtest", + "-c", + config.liquid.conf, + "info", + "proposal.txt", + ]); + + proc.stdout.on("data", (data) => { + resolve(data.toString()); + }); + + proc.stderr.on("data", (err) => { + l.error("info error", err.toString()); + reject(err.toString()); + }); + + setTimeout(() => reject("timeout"), 2000); + }); + +app.get("/proposal", auth, async (req, res) => { + try { + const { user } = req; + const { a1, v1, a2, v2 } = req.query; + const b = app.get("balance"); + + Object.keys(b).map((asset) => { + assets[asset] = asset; + }); + assets["b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23"] = + "bitcoin"; + + if (!(assets[a1] && assets[a2])) throw new Error("unsupported assets"); + if (v1 > b[assets[a1]]) throw new Error("not enough funds"); + + l.info( + `proposal requested to swap ${v1} ${assets[a1]} for ${v2} ${assets[a2]}` + ); + + let proposal = await createProposal(a1, v1, a2, v2); + proposal = proposal.replace(/\s+/g, "").trim(); + + const info = JSON.parse(await getInfo()); + const [fee, rate, asset] = parse(info); + + if (!rate) throw new Error("invalid asset pair"); + if (rate < 0) + throw new Error(`${assets[a1]} amount must be greater than ${fee}`); + + await db.Proposal.create({ + a1, + a2, + v1: Math.round(v1 * SATS), + v2: Math.round(v2 * SATS), + user_id: user.id, + text: proposal, + }); + + res.send({ proposal, info, rate, asset }); + } catch (e) { + l.error(e); + res.status(500).send({ error: e.message }); + } +}); + +app.post("/acceptance", async (req, res) => { + const { acceptance: text } = req.body; + l.info("acceptance received"); + + fs.writeFile("accepted.txt", text, async (err) => { + if (err) { + return res.status(500).send(err); + } + + try { + let info = JSON.parse( + await new Promise((resolve, reject) => { + const spawn = require("child_process").spawn; + const proc = spawn("liquidswap-cli", [ + "--regtest", + "-c", + config.liquid.conf, + "info", + "accepted.txt", + ]); + + proc.stdout.on("data", (data) => { + resolve(data.toString()); + }); + + proc.stderr.on("data", (err) => { + reject(err.toString()); + }); + + setTimeout(() => reject("timeout", proc), 2000); + }) + ); + + const time = Math.floor(new Date()).toString(); + const [fee, rate, asset] = parse(info); + if (!asset) throw new Error("unsupported asset"); + l.info("accepted", info, rate, asset); + + leveldb.put(time, JSON.stringify({ text, info, rate, asset })); + l.info({ text, info, rate, asset }); + + res.send({ info, rate }); + } catch (e) { + l.error(e); + res.status(500).send({ error: e }); + } + }); +}); + +checkQueue = async () => { + const ws = app.get("ws"); + const txs = []; + + leveldb.createReadStream() + .on("data", async function (data) { + console.log("pushing", data.key); + txs.push({ ...JSON.parse(data.value), key: data.key }); + }) + .on("end", async function (data) { + const rate = (a, b) => a.rate - b.rate; + const time = (a, b) => b.time - a.time; + + const pending = txs.filter((tx) => !tx.id); + const completed = txs.filter((tx) => tx.id).sort(time); + + const bitcoin = pending + .filter((tx) => tx.asset === "bitcoin") + .sort(rate) + .reverse(); + const tether = pending + .filter((tx) => tx.asset === "tether") + .sort(rate) + .reverse(); + + const strip = (a) => + a.map(({ id, key, info, rate, time }) => ({ + id, + key, + info, + rate, + time, + })); + + ws && + ws.send( + JSON.stringify({ + completed: strip(completed.slice(0, 3)), + bitcoin: strip(bitcoin.slice(0, 3)), + tether: strip(tether.slice(0, 3)), + }) + ); + + let b, t, tx; + if (bitcoin[0]) b = bitcoin[0].rate - app.get("ask"); + if (tether[0]) t = tether[0].rate - 1 / app.get("bid"); + + if (b > 0 || t > 0) { + if ((b && !t) || b > t) tx = bitcoin[0]; + if ((t && !b) || t > b) tx = tether[0]; + } + + if (tx) { + try { + tx.id = JSON.parse(await finalize(tx.text)).txid; + tx.time = Date.now(); + delete tx.text; + leveldb.put(tx.key, JSON.stringify(tx)); + } catch (e) { + l.info(e); + if (e.includes("Unexpected fees")) leveldb.del(tx.key); + if (e.includes("unsigned inputs")) leveldb.del(tx.key); + if (e.includes("insufficient fee")) leveldb.del(tx.key); + } + } + }); +}; + +const parse = (info) => { + const fee = info.legs[0].fee; + return [ + fee, + (info.legs[1].amount / (info.legs[0].amount + fee)).toFixed(8), + assets[info.legs[0].asset], + ]; +}; + +const finalize = (text) => { + fs.writeFileSync("accepted.txt", text); + + return new Promise((resolve, reject) => { + const spawn = require("child_process").spawn; + const proc = spawn("liquidswap-cli", [ + "--regtest", + "-c", + config.liquid.conf, + "finalize", + "accepted.txt", + "--send", + ]); + + proc.stdout.on("data", (data) => { + resolve(data.toString()); + }); + + proc.stderr.on("data", (err) => { + reject(err.toString()); + }); + + setTimeout(() => reject("timeout"), 2000); + }); +}; diff --git a/yarn.lock b/yarn.lock index 013df81a..4e80e257 100644 --- a/yarn.lock +++ b/yarn.lock @@ -379,6 +379,19 @@ __metadata: languageName: node linkType: hard +"abstract-leveldown@npm:^6.2.1, abstract-leveldown@npm:~6.2.1, abstract-leveldown@npm:~6.2.3": + version: 6.2.3 + resolution: "abstract-leveldown@npm:6.2.3" + dependencies: + buffer: ^5.5.0 + immediate: ^3.2.3 + level-concat-iterator: ~2.0.0 + level-supports: ~1.0.0 + xtend: ~4.0.0 + checksum: 193fd90d2110b63bf46aa761f18b3cb8c6e774d5b969c4b5ff8230d215b8982eac9edb8e10dcda7d9ccef361a845bcaa99e6a510d4ba7215a709b3d3ffffee8a + languageName: node + linkType: hard + "accepts@npm:~1.3.5, accepts@npm:~1.3.7": version: 1.3.7 resolution: "accepts@npm:1.3.7" @@ -1180,7 +1193,7 @@ __metadata: languageName: node linkType: hard -"buffer@npm:^5.5.0": +"buffer@npm:^5.5.0, buffer@npm:^5.6.0": version: 5.6.0 resolution: "buffer@npm:5.6.0" dependencies: @@ -1554,6 +1567,7 @@ __metadata: http-proxy-middleware: ^0.20.0 jsonwebtoken: ^8.5.1 knex: ^0.21.1 + level: ^6.0.1 lnurl: "https://github.com/asoltys/lnurl-node" long: ^4.0.0 mailgun-js: ^0.22.0 @@ -1992,6 +2006,16 @@ __metadata: languageName: node linkType: hard +"deferred-leveldown@npm:~5.3.0": + version: 5.3.0 + resolution: "deferred-leveldown@npm:5.3.0" + dependencies: + abstract-leveldown: ~6.2.1 + inherits: ^2.0.3 + checksum: 2d2dbff3db68635a872218145b6c88f37358d26e31cf9e1efda229a68a56b420f82278794915c4ceb7129a651facfad4e44c8b6740d43733da6f808d981ac014 + languageName: node + linkType: hard + "define-properties@npm:^1.1.2, define-properties@npm:^1.1.3": version: 1.1.3 resolution: "define-properties@npm:1.1.3" @@ -2260,6 +2284,18 @@ __metadata: languageName: node linkType: hard +"encoding-down@npm:^6.3.0": + version: 6.3.0 + resolution: "encoding-down@npm:6.3.0" + dependencies: + abstract-leveldown: ^6.2.1 + inherits: ^2.0.3 + level-codec: ^9.0.0 + level-errors: ^2.0.0 + checksum: 002e025e623647585f410e9184840bbdec3451e7473b27dba75d89e7c031b8aef51b16aa21bd3955ef2f5f57ead57a477715d7234b677885c14f5f3af69a8873 + languageName: node + linkType: hard + "end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1": version: 1.4.4 resolution: "end-of-stream@npm:1.4.4" @@ -2285,6 +2321,17 @@ __metadata: languageName: node linkType: hard +"errno@npm:~0.1.1": + version: 0.1.7 + resolution: "errno@npm:0.1.7" + dependencies: + prr: ~1.0.1 + bin: + errno: ./cli.js + checksum: 3d2da6fa1e3826dead7e06476cb4219555e8492c4ba8e0c40b2dc333e9b52e33223a414a394d7b9f18f82740aa69861c5fcef5b80798f08ff903c7c78916ce14 + languageName: node + linkType: hard + "error@npm:^7.2.0": version: 7.2.1 resolution: "error@npm:7.2.1" @@ -3449,6 +3496,13 @@ __metadata: languageName: node linkType: hard +"immediate@npm:^3.2.3": + version: 3.3.0 + resolution: "immediate@npm:3.3.0" + checksum: 782748e9f02525960109b59738f123d0960bd857131f20ed851d27f664cec5b1528d10d145683652487df9b723c9fa6a01dc796c721e93b74362a722356649b7 + languageName: node + linkType: hard + "import-lazy@npm:^2.1.0": version: 2.1.0 resolution: "import-lazy@npm:2.1.0" @@ -4135,6 +4189,109 @@ __metadata: languageName: node linkType: hard +"level-codec@npm:^9.0.0": + version: 9.0.2 + resolution: "level-codec@npm:9.0.2" + dependencies: + buffer: ^5.6.0 + checksum: 3474cadde6f6221caff2e464c8ffd50761ee05dbb9d3d1259eca9bcca2a733f1de839cf2ea3ed7e2dbc5b01436096a6fc5606b9df2a0b796f96f53dce27b9105 + languageName: node + linkType: hard + +"level-concat-iterator@npm:~2.0.0": + version: 2.0.1 + resolution: "level-concat-iterator@npm:2.0.1" + checksum: d5ac362a950bcf63bc19c4e8d397055c9bbd335eea2c56f7de372d38c66147d6db2430596025861d99820f890a9881aef9a192ee62b109ccf0885c0b5c5c2586 + languageName: node + linkType: hard + +"level-errors@npm:^2.0.0, level-errors@npm:~2.0.0": + version: 2.0.1 + resolution: "level-errors@npm:2.0.1" + dependencies: + errno: ~0.1.1 + checksum: 74dbf642313f4ccd8736092daad482c133bfba2a1ded8e622be5b75c8d358627daab2f7684c404ded7147b16caa8bbc2509772c95f86b2fddcaa75651e0ac665 + languageName: node + linkType: hard + +"level-iterator-stream@npm:~4.0.0": + version: 4.0.2 + resolution: "level-iterator-stream@npm:4.0.2" + dependencies: + inherits: ^2.0.4 + readable-stream: ^3.4.0 + xtend: ^4.0.2 + checksum: 795cdbbf856b58fffaf19e0d0b294f65fd4d8feb22bdee8563408f739107ce88b03b8b36ecc60be95c736ea676d611f95dabfbbb15346a9005ed627ae644dfb5 + languageName: node + linkType: hard + +"level-js@npm:^5.0.0": + version: 5.0.2 + resolution: "level-js@npm:5.0.2" + dependencies: + abstract-leveldown: ~6.2.3 + buffer: ^5.5.0 + inherits: ^2.0.3 + ltgt: ^2.1.2 + checksum: 48d6e58bdfb248f6e25e84d6a447fa5df715d570268244efcee457f48815ec377fb474e0f10ff70f98896a6599c9d3310e95a7524a1fbc20b46443054cf7041e + languageName: node + linkType: hard + +"level-packager@npm:^5.1.0": + version: 5.1.1 + resolution: "level-packager@npm:5.1.1" + dependencies: + encoding-down: ^6.3.0 + levelup: ^4.3.2 + checksum: bf36e98c6dcb1f35c6e05b6fee9e505feb0ca5b88d88206e96bfd328da5ed6c2e52d8b0c5ba53d283f74d8a9e9ab634697c4f7de69339ad26a4e302d8299b98e + languageName: node + linkType: hard + +"level-supports@npm:~1.0.0": + version: 1.0.1 + resolution: "level-supports@npm:1.0.1" + dependencies: + xtend: ^4.0.2 + checksum: 73c845921516d38ea57d6574908e86fedbdf3a069938f14f72e221e3f9dc04adc93b0b484fc9d7591c3baab7405f33292d90250b4239a75c6afc7f37bc5b3540 + languageName: node + linkType: hard + +"level@npm:^6.0.1": + version: 6.0.1 + resolution: "level@npm:6.0.1" + dependencies: + level-js: ^5.0.0 + level-packager: ^5.1.0 + leveldown: ^5.4.0 + checksum: 02e073708819256ab03cc68ec9d2768f4c836c8a6d448f3334651799d6c836dcff60968f836018a26cb3a42a3b2c242fde4364f3dda4a2e6fcd0ddeaa4eafcdd + languageName: node + linkType: hard + +"leveldown@npm:^5.4.0": + version: 5.6.0 + resolution: "leveldown@npm:5.6.0" + dependencies: + abstract-leveldown: ~6.2.1 + napi-macros: ~2.0.0 + node-gyp: latest + node-gyp-build: ~4.1.0 + checksum: 3c7fc9455e7d3232a50658b7ee94f043e7649800e6ff671d32fc5305390359bc9ba5a76bba692c79e9d5ccf99baafd882b6408fb58fb944c9b01074745b2d9dc + languageName: node + linkType: hard + +"levelup@npm:^4.3.2": + version: 4.4.0 + resolution: "levelup@npm:4.4.0" + dependencies: + deferred-leveldown: ~5.3.0 + level-errors: ~2.0.0 + level-iterator-stream: ~4.0.0 + level-supports: ~1.0.0 + xtend: ~4.0.0 + checksum: 7cc5b84c29baeae52ee81c5d6f56a03557ec0eed4b95b1a7f22c82d8a0ebe4cea537166d2823e0cf45125dff86f119c058ccd824b5c5f2c2c8041df745b6dd39 + languageName: node + linkType: hard + "leven@npm:2.1.0": version: 2.1.0 resolution: "leven@npm:2.1.0" @@ -4331,6 +4488,13 @@ __metadata: languageName: node linkType: hard +"ltgt@npm:^2.1.2": + version: 2.2.1 + resolution: "ltgt@npm:2.2.1" + checksum: 680494cb8f5ff63432f9802bac743cf6798b494dc3ea46b704e69c0c0655018eb83f0c25d76220f0877bda8aabf6f3d107f173597c98f22c6977422bb29fbc93 + languageName: node + linkType: hard + "mailgun-js@npm:^0.22.0": version: 0.22.0 resolution: "mailgun-js@npm:0.22.0" @@ -4808,6 +4972,13 @@ __metadata: languageName: node linkType: hard +"napi-macros@npm:~2.0.0": + version: 2.0.0 + resolution: "napi-macros@npm:2.0.0" + checksum: 1b67e5271e6688d7801a5bfa49c50c097ead038ed552af5f4b1e849255f711581b389cbeb8d746ee14803612dbdf205c086e75606f8ff7d354bc9d4b5863912c + languageName: node + linkType: hard + "ncp@npm:~2.0.0": version: 2.0.0 resolution: "ncp@npm:2.0.0" @@ -4876,6 +5047,17 @@ __metadata: languageName: node linkType: hard +"node-gyp-build@npm:~4.1.0": + version: 4.1.1 + resolution: "node-gyp-build@npm:4.1.1" + bin: + node-gyp-build: ./bin.js + node-gyp-build-optional: ./optional.js + node-gyp-build-test: ./build-test.js + checksum: b63416498b98ac05b297cc6582744a76ff24e0e549dd619563e0edacd07526a27ded68e6ba11730aa0a7601bf04881a74d1f326475403cebc437b3767bafb531 + languageName: node + linkType: hard + "node-gyp@npm:latest": version: 7.0.0 resolution: "node-gyp@npm:7.0.0" @@ -5794,6 +5976,13 @@ __metadata: languageName: node linkType: hard +"prr@npm:~1.0.1": + version: 1.0.1 + resolution: "prr@npm:1.0.1" + checksum: ac5c0986b46390140b920b8e7f6b56e769a00620af02b6bbdfc6658e8a36b876569c8f174a7c209843f5b9af3d13cbf847c2a9dded4d965b01afbfa5ea8d0761 + languageName: node + linkType: hard + "ps-list@npm:6.3.0": version: 6.3.0 resolution: "ps-list@npm:6.3.0" @@ -7644,6 +7833,13 @@ __metadata: languageName: node linkType: hard +"xtend@npm:^4.0.2, xtend@npm:~4.0.0": + version: 4.0.2 + resolution: "xtend@npm:4.0.2" + checksum: 37ee522a3e9fb9b143a400c30b21dc122aa8c9c9411c6afae1005a4617dc20a21765c114d544e37a6bb60c2733dd8ee0a44ed9e80d884ac78cccd30b5e0ab0da + languageName: node + linkType: hard + "y18n@npm:^3.2.0": version: 3.2.1 resolution: "y18n@npm:3.2.1" -- cgit v1.2.3