aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2020-07-20 21:16:16 -0700
committerAdam Soltys <[email protected]>2020-07-20 21:16:16 -0700
commit5a15eb109b6ddd8232abfecaafc7d8ba7ada14ab (patch)
tree04575adc8c837413bb9c743cfb2234ec95d76bbb
parent1bbf30a5ece3b361ac5eb00da052fa63cbc91a2e (diff)
downloadcoinos-server-5a15eb109b6ddd8232abfecaafc7d8ba7ada14ab.tar.gz
coinos-server-5a15eb109b6ddd8232abfecaafc7d8ba7ada14ab.zip
atomic swaps
-rw-r--r--.gitignore2
-rw-r--r--db/index.js6
-rw-r--r--db/models/proposals.js102
-rw-r--r--db/schema.sql34
-rw-r--r--index.js1
-rw-r--r--lib/binance.js6
-rw-r--r--lib/sockets.js8
-rw-r--r--package.json1
-rw-r--r--routes/swaps.js259
-rw-r--r--yarn.lock198
10 files changed, 605 insertions, 12 deletions
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"