├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .nvmrc ├── LICENSE ├── README.md ├── docs └── README.md ├── lib ├── chainclient.js ├── hnscan.js ├── hnscandb.js ├── http.js ├── index.js ├── indexer.js ├── layout.js ├── plugin.js ├── types.js └── util.js ├── package.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "es6": true 5 | }, 6 | "parserOptions": { 7 | "ecmaVersion": 2017 8 | }, 9 | "extends": ["plugin:prettier/recommended"] 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | node_modules/* 3 | bench/.hsd/ 4 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v10.9.0 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright <2018-2019> 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hnscan Backend 2 | 3 | This is the plugin to HSD that backs the open source block explorer HNScan. 4 | 5 | ## Installation 6 | 7 | Hnscan can currently be installed as an HSD plugin. 8 | 9 | Install hnscan by running: 10 | 11 | npm install hnscan 12 | 13 | 14 | ## Usage 15 | 16 | In order to enable Hnscan with your Handshake Daemon, add the flag: 17 | 18 | --plugins=hnscan 19 | 20 | to your HSD startup script. Ensure that hnscan is installed in the repository from which 21 | you are running your daemon. 22 | 23 | To access the frontend of HNScan, follow the intructions here: https://github.com/HandshakeAlliance/HNScan 24 | 25 | 26 | ## License 27 | 28 | This project is licensed under [MIT License](/LICENSE). 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | Welcome to the HNScan Docs! 2 | -------------------------------------------------------------------------------- /lib/chainclient.js: -------------------------------------------------------------------------------- 1 | /* 2 | * chainclient.js - chain client for hnscan 3 | * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). 4 | * Copyright (c) 2018-2019, Handshake Alliance Developers (MIT License). 5 | * https://github.com/handshakealliance/hnscan-backend 6 | */ 7 | 8 | "use strict"; 9 | 10 | const assert = require("bsert"); 11 | const AsyncEmitter = require("bevent"); 12 | const CoinView = require("hsd").coins.CoinView; 13 | //TODO revamp this. 14 | 15 | /** 16 | * Node Client 17 | * @alias module:hnscan.ChainClient 18 | */ 19 | 20 | class ChainClient extends AsyncEmitter { 21 | /** 22 | * Create a chain client. 23 | * @constructor 24 | */ 25 | 26 | constructor(chain) { 27 | super(); 28 | 29 | this.chain = chain; 30 | this.network = chain.network; 31 | this.opened = false; 32 | 33 | this.init(); 34 | } 35 | 36 | /** 37 | * Initialize the client. 38 | */ 39 | 40 | init() { 41 | this.chain.on("connect", (entry, block, view) => { 42 | if (!this.opened) return; 43 | 44 | this.emit("block connect", entry, block, view); 45 | }); 46 | 47 | this.chain.on("disconnect", (entry, block, view) => { 48 | if (!this.opened) return; 49 | 50 | this.emit("block disconnect", entry, block, view); 51 | }); 52 | 53 | this.chain.on("reset", tip => { 54 | if (!this.opened) return; 55 | 56 | this.emit("chain reset", tip); 57 | }); 58 | } 59 | 60 | /** 61 | * Open the client. 62 | * @returns {Promise} 63 | */ 64 | 65 | async open(options) { 66 | assert(!this.opened, "ChainClient is already open."); 67 | this.opened = true; 68 | setImmediate(() => this.emit("connect")); 69 | } 70 | 71 | /** 72 | * Close the client. 73 | * @returns {Promise} 74 | */ 75 | 76 | async close() { 77 | assert(this.opened, "ChainClient is not open."); 78 | this.opened = false; 79 | setImmediate(() => this.emit("disconnect")); 80 | } 81 | 82 | /** 83 | * Add a listener. 84 | * @param {String} type 85 | * @param {Function} handler 86 | */ 87 | 88 | bind(type, handler) { 89 | return this.on(type, handler); 90 | } 91 | 92 | /** 93 | * Add a listener. 94 | * @param {String} type 95 | * @param {Function} handler 96 | */ 97 | 98 | hook(type, handler) { 99 | return this.on(type, handler); 100 | } 101 | 102 | /** 103 | * Get chain tip. 104 | * @returns {Promise} 105 | */ 106 | 107 | getTip() { 108 | return this.chain.tip; 109 | } 110 | 111 | /** 112 | * Get hash range. 113 | * @param {Number} start 114 | * @param {Number} end 115 | * @returns {Promise} 116 | */ 117 | 118 | async getHashes(start = -1, end = -1) { 119 | return this.chain.getHashes(start, end); 120 | } 121 | 122 | /** 123 | * Rescan for any missed transactions. 124 | * @param {Number|Hash} start - Start block. 125 | * @param {Bloom} filter 126 | * @param {Function} iter - Iterator. 127 | * @returns {Promise} 128 | */ 129 | 130 | async rescan(start) { 131 | return this.chain.scan(start, this.filter, (entry, txs) => { 132 | return this.emitAsync("block rescan", entry, txs); 133 | }); 134 | } 135 | 136 | /** 137 | * Get chain entry. 138 | * @param {Hash/Number} hash 139 | * @returns {Promise} 140 | */ 141 | 142 | async getEntry(hash) { 143 | const entry = await this.chain.getEntry(hash); 144 | 145 | if (!entry) return null; 146 | 147 | if (!(await this.chain.isMainChain(entry))) return null; 148 | 149 | return entry; 150 | } 151 | 152 | /** 153 | * Get block 154 | * @param {Hash} hash 155 | * @returns {Promise} 156 | */ 157 | 158 | async getBlock(hash) { 159 | const block = await this.chain.getBlock(hash); 160 | 161 | if (!block) return null; 162 | 163 | return block; 164 | } 165 | 166 | /** 167 | * Get tx 168 | * @param {Hash} hash 169 | * @returns {Promise} 170 | */ 171 | 172 | async getTX(hash) { 173 | const tx = await this.chain.getTX(hash); 174 | 175 | if (!tx) return null; 176 | 177 | return tx; 178 | } 179 | 180 | /** 181 | * Get previous entry. 182 | * @param {ChainEntry} entry 183 | * @returns {Promise} - Returns ChainEntry. 184 | */ 185 | 186 | getPrevious(entry) { 187 | return this.chain.getEntryByHash(entry.prevBlock); 188 | } 189 | 190 | /** 191 | * Get a historical block coin viewpoint. 192 | * @param {Block} hash 193 | * @returns {Promise} - Returns {@link CoinView}. 194 | */ 195 | 196 | async getBlockView(block) { 197 | const prev = await this.getPrevious(block); 198 | const view = await this.updateInputs(block, prev); 199 | return view; 200 | } 201 | 202 | /** 203 | * Spend and update inputs. 204 | * @private 205 | * @param {Block} block 206 | * @param {ChainEntry} prev 207 | * @returns {Promise} - Returns {@link CoinView}. 208 | */ 209 | 210 | async updateInputs(block, prev) { 211 | const view = new CoinView(); 212 | const height = prev ? prev.height + 1 : 0; 213 | const cb = block.txs[0]; 214 | 215 | const cbSpent = await this.chain.getSpentView(cb); 216 | view.addTX(cb, height); 217 | 218 | for (const entry of cbSpent.map) { 219 | for (const mapEntry of entry[1].outputs) { 220 | const coins = view.ensure(entry[0]); 221 | coins.addOutput(mapEntry[0], mapEntry[1].output); 222 | } 223 | } 224 | 225 | for (let i = 1; i < block.txs.length; i++) { 226 | const tx = block.txs[i]; 227 | const spent = await this.chain.getSpentView(tx); 228 | 229 | view.addTX(tx, height); 230 | for (const entry of spent.map) { 231 | for (const mapEntry of entry[1].outputs) { 232 | const coins = view.ensure(entry[0]); 233 | coins.addOutput(mapEntry[0], mapEntry[1].output); 234 | } 235 | } 236 | } 237 | 238 | return view; 239 | } 240 | } 241 | 242 | /* 243 | * Expose 244 | */ 245 | 246 | module.exports = ChainClient; 247 | -------------------------------------------------------------------------------- /lib/hnscan.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * hnscan.js - hnscan api 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * Copyright (c) 2018-2019, Handshake Alliance Developers (MIT License). 5 | * https://github.com/handshakealliance/hnscan-backend 6 | */ 7 | 8 | "use strict"; 9 | 10 | const EventEmitter = require("events"); 11 | const path = require("path"); 12 | const { Network, Address, Covenant, Script, Coin } = require("hsd"); 13 | const consensus = require("hsd/lib/protocol/consensus"); 14 | const Logger = require("blgr"); 15 | const assert = require("bsert"); 16 | const bdb = require("bdb"); 17 | const layout = require("./layout"); 18 | const { Lock } = require("bmutex"); 19 | const bio = require("bufio"); 20 | const blake2b = require("bcrypto/lib/blake2b"); 21 | const rules = require("hsd/lib/covenants/rules"); 22 | const geoip = require("geoip-lite"); 23 | const util = require("./util"); 24 | 25 | /** 26 | * Hnscan 27 | * @alias module:hnscan.hnscanDB 28 | * @extends EventEmitter 29 | */ 30 | 31 | class Hnscan extends EventEmitter { 32 | /** 33 | * Create a hnscan . 34 | * @constructor 35 | * @param {Object} options 36 | */ 37 | 38 | constructor(options) { 39 | super(); 40 | this.options = new HnscanOptions(options); 41 | 42 | this.network = this.options.network; 43 | this.logger = this.options.logger.context("hnscan"); 44 | this.client = this.options.client; 45 | this.chain = this.options.chain; 46 | this.hdb = this.options.hdb; 47 | this.node = this.options.node; 48 | 49 | this.nameCount = 0; 50 | this.nameCountBlock = 0; 51 | } 52 | 53 | //Calls all of the resource heavy API calls, to cache them for faster response times. 54 | //@todo make sure to add this to unindex block when that is implemented. 55 | async memorize(height) { 56 | this.logger.info("HNScan memorizing values..."); 57 | await this.getNameCount(height); 58 | } 59 | 60 | async getNameCount(height) { 61 | if (height === this.nameCountBlock) { 62 | return this.nameCount; 63 | } 64 | 65 | let txn = this.chain.db.txn; 66 | 67 | let iter = txn.iterator(); 68 | let nameCount = 0; 69 | while (await iter.next()) { 70 | const { key, value } = iter; 71 | nameCount++; 72 | } 73 | 74 | this.nameCount = nameCount; 75 | this.nameCountBlock = height; 76 | 77 | return this.nameCount; 78 | } 79 | 80 | async getTransactions(limit = 25) { 81 | let txs = []; 82 | 83 | for (let i = this.chain.height; i > -1; i--) { 84 | const block = await this.chain.getBlock(i); 85 | const entry = await this.chain.getEntryByHeight(i); 86 | 87 | for (let tx of block.txs) { 88 | let json = await this.getTransaction(tx.hash()); 89 | 90 | txs.push(json); 91 | 92 | if (txs.length === limit) { 93 | return txs; 94 | } 95 | } 96 | } 97 | } 98 | 99 | //@todo make this standard for all list APIs, return [data, total] 100 | async getTransactionsByHeight(height, offset = 0, limit = 25) { 101 | const block = await this.chain.getBlock(height); 102 | const entry = await this.chain.getEntryByHeight(height); 103 | let txs = []; 104 | 105 | let list = block.txs.slice(offset, offset + limit); 106 | 107 | for (const tx of list) { 108 | const json = await this.getTransaction(tx.hash()); 109 | txs.push(json); 110 | } 111 | 112 | return [txs, block.txs.length]; 113 | } 114 | 115 | //Expects a Address object NOT a hash or string 116 | async getTransactionsByAddress(addr, offset = 0, limit = 25) { 117 | //@todo this may be pretty slow as we increase the # of transactions for users. See what we acn do about this. 118 | //That said, the transaction numbers we are seeing on the testnet are probably pretty unrealistic. 119 | const raw = await this.hdb.addressHistory(addr); 120 | 121 | const list = raw.slice(offset, offset + limit); 122 | 123 | let txs = []; 124 | 125 | for (let i = 0; i < list.length; i++) { 126 | let tx = await this.getTransaction(Buffer.from(list[i].tx_hash, "hex")); 127 | txs.push(tx); 128 | } 129 | 130 | return [txs, raw.length]; 131 | } 132 | 133 | async getAddress(addr) { 134 | let address = await this.hdb.addressBalance(addr); 135 | 136 | address.hash = addr.toString(this.network.type); 137 | 138 | return address; 139 | } 140 | 141 | async getBlock(height, details = true) { 142 | const block = await this.chain.getBlock(height); 143 | 144 | const view = await this.chain.getBlockView(block); 145 | 146 | if (!view) { 147 | return; 148 | } 149 | 150 | const depth = this.chain.height - height + 1; 151 | const entry = await this.chain.getEntryByHeight(height); 152 | 153 | if (!entry) { 154 | return; 155 | } 156 | 157 | const mtp = await this.chain.getMedianTime(entry); 158 | const next = await this.chain.getNextHash(entry.hash); 159 | 160 | let cbOutput = block.txs[0].outputs[0].value; 161 | 162 | //Need to get reward here. 163 | let reward = consensus.getReward(height, this.network.halvingInterval); 164 | 165 | let fees = cbOutput - reward; 166 | 167 | let miner = block.txs[0].outputs[0].address.toString(this.network.type); 168 | 169 | let txs = []; 170 | 171 | //@todo kill this if it's entirely covered in the above API call. 172 | if (details) { 173 | for (const tx of block.txs) { 174 | // const json = await this.txToJSON(tx, entry); 175 | const json = await this.getTransaction(tx.hash()); 176 | txs.push(json); 177 | continue; 178 | } 179 | } 180 | 181 | return { 182 | hash: entry.hash.toString("hex"), 183 | confirmations: this.chain.height - entry.height + 1, 184 | strippedSize: block.getBaseSize(), 185 | size: block.getSize(), 186 | weight: block.getWeight(), 187 | height: entry.height, 188 | version: entry.version, 189 | merkleRoot: entry.merkleRoot.toString("hex"), 190 | witnessRoot: entry.witnessRoot.toString("hex"), 191 | treeRoot: entry.treeRoot.toString("hex"), 192 | mask: entry.mask.toString("hex"), 193 | reservedRoot: entry.reservedRoot.toString("hex"), 194 | coinbase: details ? block.txs[0].inputs[0].witness.toJSON() : undefined, 195 | tx: details ? txs : undefined, 196 | txs: block.txs.length, 197 | fees, 198 | miner, 199 | averageFee: fees / block.txs.length, 200 | time: entry.time, 201 | medianTime: mtp, 202 | bits: entry.bits, 203 | difficulty: util.toDifficulty(entry.bits), 204 | chainwork: entry.chainwork.toString("hex", 64), 205 | prevBlock: !entry.prevBlock.equals(consensus.ZERO_HASH) 206 | ? entry.prevBlock.toString("hex") 207 | : null, 208 | nextHash: next ? next.toString("hex") : null, 209 | nonce: entry.nonce 210 | }; 211 | } 212 | 213 | async getTransaction(hash) { 214 | //@todo add value to Transaction. 215 | const meta = await this.node.getMeta(hash); 216 | if (!meta) { 217 | return; 218 | } 219 | 220 | const view = await this.node.getMetaView(meta); 221 | let tx = meta.getJSON(this.network.type, view, this.chain.height); 222 | 223 | //Might want index in both inputs and outputs 224 | const inputs = []; 225 | 226 | let index = 0; 227 | for (const input of tx.inputs) { 228 | const json = { 229 | airdrop: undefined, 230 | coinbase: undefined, 231 | value: undefined, 232 | address: undefined 233 | }; 234 | 235 | if (!input.coin) { 236 | if (index === 0) { 237 | json.value = consensus.getReward( 238 | tx.height, 239 | this.network.halvingInterval 240 | ); 241 | json.coinbase = true; 242 | } else { 243 | json.airdrop = true; 244 | } 245 | } else { 246 | json.value = input.coin.value; 247 | json.address = input.coin.address; 248 | } 249 | 250 | inputs.push(json); 251 | index++; 252 | } 253 | 254 | //@todo break this into it's own function, along with the input one. 255 | const outputs = []; 256 | 257 | for (let i = 0; i < tx.outputs.length; i++) { 258 | const output = tx.outputs[i]; 259 | // output.covenant = output.covenant.getJSON(); 260 | 261 | const json = { 262 | action: output.covenant.action, 263 | address: output.address, 264 | value: undefined, 265 | name: undefined, 266 | nameHash: undefined 267 | }; 268 | 269 | switch (json.action) { 270 | case "NONE": 271 | json.value = output.value; 272 | break; 273 | 274 | case "OPEN": 275 | json.name = Buffer.from(output.covenant.items[2], "hex").toString(); 276 | break; 277 | 278 | case "BID": 279 | //@todo Need to decode start height 280 | // newOutput.startHeight = items[1]; 281 | json.name = Buffer.from(output.covenant.items[2], "hex").toString(); 282 | json.value = output.value; 283 | break; 284 | 285 | case "REVEAL": 286 | //@todo Need to decode start height 287 | // newOutput.startHeight = items[1]; 288 | json.nonce = output.covenant.items[2]; 289 | json.value = output.value; 290 | break; 291 | 292 | case "REDEEM": 293 | break; 294 | 295 | // case "REGISTER": 296 | } 297 | 298 | if (json.action != "NONE") { 299 | json.nameHash = output.covenant.items[0]; 300 | if (!json.name) { 301 | const ns = await this.chain.db.getNameState( 302 | Buffer.from(json.nameHash, "hex") 303 | ); 304 | if (ns) { 305 | json.name = ns.name.toString("binary"); 306 | } 307 | } 308 | } 309 | outputs.push(json); 310 | } 311 | 312 | tx.inputs = inputs; 313 | tx.outputs = outputs; 314 | 315 | return tx; 316 | } 317 | 318 | //@todo build out the name over these function calls. 319 | async getName(name) { 320 | const height = this.chain.height; 321 | const nameHash = rules.hashName(name); 322 | const reserved = rules.isReserved(nameHash, height + 1, this.network); 323 | const [start, week] = rules.getRollout(nameHash, this.network); 324 | const ns = await this.chain.db.getNameState(nameHash); 325 | 326 | let info = null; 327 | 328 | if (ns) { 329 | if (!ns.isExpired(height, this.network)) 330 | info = ns.getJSON(height, this.network.type); 331 | } 332 | 333 | let data = {}; 334 | data.name = name; 335 | data.reserved = reserved; 336 | data.hash = nameHash.toString("hex"); 337 | data.release = start; 338 | //@todo perfect, go off of this. 339 | data.state = info ? info.state : "INACTIVE"; 340 | 341 | //Advanced 342 | data.height = start; 343 | data.value = info ? info.value : 0; 344 | data.renewal = info ? info.renewal : 0; 345 | data.renewals = info ? info.renewals : 0; 346 | //@todo 347 | data.weak = info ? info.weak : false; 348 | data.transfer = info ? info.transfer : 0; 349 | data.highest = info ? info.highest : 0; 350 | data.revoked = info ? info.revoked : 0; 351 | data.blocksUntil = info ? Object.values(info.stats)[2] : null; 352 | 353 | //@todo pull this into it's own function. 354 | switch (data.state) { 355 | case "OPENING": 356 | data.nextState = "BIDDING"; 357 | break; 358 | case "BIDDING": 359 | data.nextState = "REVEAL"; 360 | break; 361 | case "REVEAL": 362 | data.nextState = "CLOSED"; 363 | break; 364 | case "CLOSED": 365 | data.nextState = "RENEWAL"; 366 | default: 367 | data.nextState = "OPENING"; 368 | } 369 | 370 | let res = null; 371 | if (ns) { 372 | try { 373 | res = Resource.decode(ns.data); 374 | // return res.toJSON(); 375 | } catch (e) {} 376 | } 377 | 378 | //Offset, limit 379 | // let history = await this.nameHistory(nameHash, 0, 25); 380 | 381 | //@todo show owner if it's not empty hash. 382 | //@todo have renewals link to a page on the name -> /name/sean/renewals 383 | //@todo use expired, and have that show names that were closed but then expired. 384 | // return { 385 | // name, 386 | // hash: nameHash.toString("hex"), 387 | // height: info.height, 388 | // highest: info.highest, 389 | // value: info.value, 390 | // renewal: info.renewal, 391 | // renewals: info.renewals, 392 | // weak: info.weak, 393 | // transfer: info.transfer, 394 | // revoked: info.revoked, 395 | // release: start, 396 | // reserved, 397 | // state: info.state, 398 | // nextState, 399 | // records: res, 400 | // blocksUntil: Object.values(info.stats)[2] 401 | // }; 402 | return data; 403 | } 404 | 405 | addrToJSON(addr) { 406 | return { 407 | version: addr.version, 408 | hash: addr.hash.toString("hex") 409 | }; 410 | } 411 | 412 | async getPeers(offset = 0, limit = 10) { 413 | let peers = []; 414 | 415 | for (let peer = this.node.pool.peers.head(); peer; peer = peer.next) { 416 | const offset = this.network.time.known.get(peer.hostname()) || 0; 417 | const hashes = []; 418 | 419 | for (const hash in peer.blockMap.keys()) 420 | hashes.push(hash.toString("hex")); 421 | 422 | peer.getName(); 423 | 424 | peers.push({ 425 | id: peer.id, 426 | addr: peer.hostname(), 427 | name: peer.name || undefined, 428 | services: hex32(peer.services), 429 | relaytxes: !peer.noRelay, 430 | lastsend: (peer.lastSend / 1000) | 0, 431 | lastrecv: (peer.lastRecv / 1000) | 0, 432 | bytessent: peer.socket.bytesWritten, 433 | bytesrecv: peer.socket.bytesRead, 434 | conntime: peer.time !== 0 ? ((Date.now() - peer.time) / 1000) | 0 : 0, 435 | timeoffset: offset, 436 | pingtime: 437 | peer.lastPong !== -1 ? (peer.lastPong - peer.lastPing) / 1000 : -1, 438 | minping: peer.minPing !== -1 ? peer.minPing / 1000 : -1, 439 | version: peer.version, 440 | subver: peer.agent, 441 | inbound: !peer.outbound, 442 | startingheight: peer.height, 443 | besthash: peer.bestHash.toString("hex"), 444 | bestheight: peer.bestHeight, 445 | banscore: peer.banScore, 446 | inflight: hashes, 447 | whitelisted: false 448 | }); 449 | } 450 | 451 | //@todo optimize both of these by doing them before pushing all the data above. 452 | let total = peers.length; 453 | 454 | peers = peers.slice(offset, offset + limit); 455 | 456 | return [peers, total]; 457 | } 458 | 459 | async getPeersLocation(offset = 0) { 460 | let peers = []; 461 | 462 | for (let peer = this.node.pool.peers.head(); peer; peer = peer.next) { 463 | let ip = peer.address.host; 464 | 465 | // GeoIP information 466 | peers.push(geoip.lookup(ip)); 467 | } 468 | 469 | return peers; 470 | } 471 | 472 | /** 473 | * Return the funding outputs for an address, confirmed and unconfirmed. 474 | * @param addr - {Address} 475 | * @returns {Promise} -> {[confirmed: Number, unconfirmed: Number]} 476 | */ 477 | async addressFunding(addr) { 478 | try { 479 | let confirmed = await this._addressFunding(addr); 480 | let unconfirmed = await this._addressFundingUnconfirmed(addr); 481 | 482 | return [confirmed, unconfirmed]; 483 | } catch (e) { 484 | console.log(e); 485 | return; 486 | } 487 | } 488 | 489 | async _addressFunding(addr) { 490 | let hash = addr.getHash(); 491 | let funding = []; 492 | 493 | const iter = this.db.iterator({ 494 | gte: layout.o.min(hash), 495 | lte: layout.o.max(hash), 496 | values: true 497 | }); 498 | 499 | await iter.each(async (key, raw) => { 500 | const [userHash, txid] = layout.o.decode(key); 501 | 502 | let tx = { 503 | userHash, 504 | tx_hash: txid.toString("hex"), 505 | height: toU32(raw) 506 | }; 507 | 508 | let newtx = await this.client.getTX(txid); 509 | 510 | let outputIndex; 511 | let value; 512 | 513 | for (let i = 0; i < newtx.outputs.length; i++) { 514 | if (userHash.equals(newtx.outputs[i].address.getHash())) { 515 | outputIndex = i; 516 | value = newtx.outputs[i].value; 517 | break; 518 | } 519 | } 520 | 521 | let output = { 522 | tx_hash: txid.toString("hex"), 523 | height: toU32(raw), 524 | output_index: outputIndex, 525 | value 526 | }; 527 | 528 | funding.push(output); 529 | }); 530 | 531 | return funding; 532 | } 533 | 534 | //TODO 535 | async _addressFundingUnconfirmed(hash) { 536 | return []; 537 | } 538 | 539 | async addressSpent(hash, funding) { 540 | let confirmed = await this._addressSpent(hash, funding); 541 | let unconfirmed = await this._addressSpentUnconfirmed(hash, funding); 542 | 543 | return [confirmed, unconfirmed]; 544 | } 545 | 546 | async _addressSpent(hash, funding) { 547 | let spents = []; 548 | for (let o of funding) { 549 | let txPrefix = Buffer.from(o.tx_hash, "hex").slice(0, 8); 550 | 551 | const txHashX = await this.db.get( 552 | layout.i.encode(txPrefix, o.output_index) 553 | ); 554 | 555 | if (txHashX) { 556 | // let newtx = await this.client.getTX(txHashX); 557 | let height = await this.db.get(layout.t.encode(txHashX)); 558 | height = toU32(height); 559 | 560 | // let found = false; 561 | 562 | // for (let i = 0; i < newtx.inputs.length; i++) { 563 | // let outpoint = newtx.inputs[i].prevout; 564 | // if ( 565 | // outpoint.hash.toString("hex") === o.tx_hash && 566 | // outpoint.index === o.output_index 567 | // ) { 568 | // found = true; 569 | 570 | // break; 571 | // } 572 | // } 573 | 574 | // if (found) { 575 | let spent = { 576 | tx_hash: txHashX.toString("hex"), 577 | height: height, 578 | funding_output: [o.tx_hash, o.output_index], 579 | value: o.value 580 | }; 581 | spents.push(spent); 582 | // } 583 | } 584 | } 585 | 586 | return spents; 587 | } 588 | 589 | async _addressSpentUnconfirmed(hash, funding) { 590 | return []; 591 | } 592 | 593 | //Calculate Balance for an address 594 | //TODO might actually be faster to have 1 function for both unconfirmed. 595 | //Instead of in each subfunction. 596 | async addressBalance(addr) { 597 | let [fConfirmed, fUnconfirmed] = await this.addressFunding(addr); 598 | let [sConfirmed, sUnconfirmed] = await this.addressSpent(addr, fConfirmed); 599 | 600 | let totalFunded = 0; 601 | for (let f of fConfirmed) { 602 | totalFunded += f.value; 603 | } 604 | 605 | let totalSpent = 0; 606 | for (let s of sConfirmed) { 607 | totalSpent += s.value; 608 | } 609 | 610 | let balance = { 611 | confirmed: totalFunded - totalSpent, 612 | unconfirmed: totalFunded - totalSpent, 613 | received: totalFunded, 614 | spent: totalSpent 615 | }; 616 | 617 | return balance; 618 | } 619 | 620 | async addressHistory(addr) { 621 | let [fConfirmed, fUnconfirmed] = await this.addressFunding(addr); 622 | let [sConfirmed, sUnconfirmed] = await this.addressSpent(addr, fConfirmed); 623 | 624 | let txs = []; 625 | 626 | for (let f of fConfirmed) { 627 | let newtx = { 628 | tx_hash: f.tx_hash, 629 | height: f.height 630 | }; 631 | txs.push(newtx); 632 | } 633 | 634 | for (let s of sConfirmed) { 635 | let newtx = { 636 | tx_hash: s.tx_hash, 637 | height: s.height 638 | }; 639 | txs.push(newtx); 640 | } 641 | 642 | //TODO implement mempool txs. 643 | //Probably implement this through the client. 644 | 645 | return txs; 646 | } 647 | 648 | async addressUnspent(addr) { 649 | let [fConfirmed, fUnconfirmed] = await this.addressFunding(addr); 650 | let [sConfirmed, sUnconfirmed] = await this.addressSpent(addr, fConfirmed); 651 | 652 | let txs = []; 653 | 654 | for (let f of fConfirmed) { 655 | let newtx = { 656 | tx_hash: f.tx_hash, 657 | height: f.height, 658 | tx_pos: f.output_index, 659 | value: f.value 660 | }; 661 | txs.push(newtx); 662 | } 663 | 664 | for (let s of sConfirmed) { 665 | txs = txs.filter(tx => tx.tx_hash !== s.funding_output[0]); 666 | } 667 | 668 | return txs; 669 | } 670 | 671 | //@todo Not all bids and reveals are returning a value. help. 672 | async getNameHistory(name, offset = 0, limit = 25) { 673 | const nameHash = rules.hashName(name); 674 | let entireList = await this.hdb.nameHistory(nameHash); 675 | //Sort in descending order. 676 | entireList.sort((a, b) => (a.height < b.height ? 1 : -1)); 677 | 678 | let total = entireList.length; 679 | 680 | let list = entireList.slice(offset, offset + limit); 681 | 682 | let history = []; 683 | for (let i = 0; i < list.length; i++) { 684 | // let tx = await this.getTransaction(Buffer.from(list[i].tx_hash, "hex")); 685 | let meta = await this.node.getMeta(Buffer.from(list[i].tx_hash, "hex")); 686 | let tx = meta.tx; 687 | let j = 0; 688 | for (let o of meta.tx.outputs) { 689 | let newtx = {}; 690 | let cov = o.covenant; 691 | // let cov = new Covenant(o.covenant.type, o.covenant.items); 692 | // let cov = new Covenant(o.covenant.type, []); 693 | // cov = cov.fromString(o.covenant.items); 694 | 695 | if (cov.isName()) { 696 | if (cov.get(0).toString("hex") === nameHash.toString("hex")) { 697 | if (cov.isOpen()) { 698 | newtx.action = "Opened"; 699 | } 700 | 701 | if (cov.isBid()) { 702 | newtx.action = "Bid"; 703 | newtx.value = o.value; 704 | } 705 | 706 | if (cov.isReveal()) { 707 | //See if we can connect Reveals to Bids using the nonce. 708 | newtx.action = "Reveal"; 709 | newtx.value = o.value; 710 | } 711 | 712 | //XXX Add owner information to this. 713 | //See if this is called on a transfer as well. 714 | if (cov.isRegister()) { 715 | //Link to the data on a new page. 716 | newtx.action = "Register"; 717 | } 718 | 719 | if (cov.isRedeem()) { 720 | //Redeem non winning bids? 721 | //Possibly also connect these to reveals and bids. 722 | newtx.action = "Redeem"; 723 | newtx.value = o.value; 724 | } 725 | 726 | if (cov.isUpdate()) { 727 | //Link data on new page 728 | newtx.action = "Update"; 729 | } 730 | 731 | if (cov.isRenew()) { 732 | newtx.action = "Renew"; 733 | } 734 | 735 | newtx.time = meta.mtime; 736 | newtx.height = list[i].height; 737 | newtx.txid = list[i].tx_hash; 738 | newtx.index = j; 739 | history.push(newtx); 740 | } 741 | } 742 | j++; 743 | } 744 | } 745 | return [history, total]; 746 | } 747 | } 748 | 749 | class HnscanOptions { 750 | /** 751 | * Create hnscan options. 752 | * @constructor 753 | * @param {Object} options 754 | */ 755 | 756 | constructor(options) { 757 | this.network = Network.primary; 758 | this.logger = Logger.global; 759 | this.client = null; 760 | this.prefix = null; 761 | this.location = null; 762 | this.memory = true; 763 | this.maxFiles = 64; 764 | this.cacheSize = 16 << 20; 765 | this.compression = true; 766 | 767 | if (options) this._fromOptions(options); 768 | } 769 | 770 | /** 771 | * Inject properties from object. 772 | * @private 773 | * @param {Object} options 774 | * @returns {HnscanOptions} 775 | */ 776 | 777 | _fromOptions(options) { 778 | if (options.network != null) this.network = Network.get(options.network); 779 | 780 | if (options.logger != null) { 781 | assert(typeof options.logger === "object"); 782 | this.logger = options.logger; 783 | } 784 | 785 | if (options.client != null) { 786 | assert(typeof options.client === "object"); 787 | this.client = options.client; 788 | } 789 | 790 | if (options.chain != null) { 791 | assert(typeof options.chain === "object"); 792 | this.chain = options.chain; 793 | } 794 | 795 | if (options.hdb != null) { 796 | assert(typeof options.hdb === "object"); 797 | this.hdb = options.hdb; 798 | } 799 | 800 | if (options.node != null) { 801 | assert(typeof options.node === "object"); 802 | this.node = options.node; 803 | } 804 | 805 | assert(this.client); 806 | 807 | return this; 808 | } 809 | 810 | /** 811 | * Instantiate chain options from object. 812 | * @param {Object} options 813 | * @returns {HnscanOptions} 814 | */ 815 | 816 | static fromOptions(options) { 817 | return new this()._fromOptions(options); 818 | } 819 | } 820 | 821 | /* 822 | * Helpers 823 | */ 824 | 825 | function fromU32(num) { 826 | const data = Buffer.allocUnsafe(4); 827 | data.writeUInt32LE(num, 0, true); 828 | return data; 829 | } 830 | 831 | function toU32(buf) { 832 | const num = buf.readUInt32LE(0, true); 833 | return num; 834 | } 835 | 836 | function hex32(num) { 837 | assert(num >= 0); 838 | 839 | num = num.toString(16); 840 | 841 | assert(num.length <= 8); 842 | 843 | while (num.length < 8) num = "0" + num; 844 | 845 | return num; 846 | } 847 | 848 | module.exports = Hnscan; 849 | -------------------------------------------------------------------------------- /lib/hnscandb.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * hnscandb.js - hnscan database 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * Copyright (c) 2018-2019, Handshake Alliance Developers (MIT License). 5 | * https://github.com/handshakealliance/hnscan-backend 6 | */ 7 | 8 | "use strict"; 9 | 10 | const EventEmitter = require("events"); 11 | const path = require("path"); 12 | const { Network, Address, Covenant, Script, Coin } = require("hsd"); 13 | const Logger = require("blgr"); 14 | const assert = require("bsert"); 15 | const bdb = require("bdb"); 16 | const layout = require("./layout"); 17 | const { Lock } = require("bmutex"); 18 | const bio = require("bufio"); 19 | const blake2b = require("bcrypto/lib/blake2b"); 20 | const { ChartData, ChainState } = require("./types"); 21 | const rules = require("hsd/lib/covenants/rules"); 22 | const { types } = rules; 23 | const Amount = require("hsd/lib/ui/amount"); 24 | 25 | /** 26 | * HnscanDB 27 | * @alias module:hnscan.hnscanDB 28 | * @extends EventEmitter 29 | */ 30 | 31 | class HnscanDB extends EventEmitter { 32 | /** 33 | * Create a hnscan db. 34 | * @constructor 35 | * @param {Object} options 36 | */ 37 | 38 | constructor(options) { 39 | super(); 40 | this.options = new HnscanDBOptions(options); 41 | 42 | this.network = this.options.network; 43 | this.logger = this.options.logger.context("hnscan"); 44 | this.db = bdb.create(this.options); 45 | this.client = this.options.client; 46 | this.state = new ChainState(); 47 | this.pending = new ChainState(); 48 | 49 | this.chartDataCurrentDate = 0; 50 | this.chartDataCurrent = []; 51 | } 52 | 53 | /** 54 | * Open the hnscandb, wait for the database to load. 55 | * @returns {Promise} 56 | */ 57 | 58 | async open() { 59 | await this.db.open(); 60 | 61 | await this.db.verify(layout.V.encode(), "hnscan", 0); 62 | 63 | const state = await this.getState(); 64 | 65 | if (state) { 66 | this.state = state; 67 | } 68 | } 69 | 70 | /** 71 | * Return header from the database. 72 | * @returns {Promise} 73 | */ 74 | 75 | async getHeaders(height) { 76 | return await this.db.get(layout.h.encode(height)); 77 | } 78 | 79 | async getHashByHeight(height) { 80 | let header = await this.db.get(layout.h.encode(height)); 81 | 82 | return blake2b.digest(header); 83 | } 84 | 85 | /** 86 | * Verify network. 87 | * @returns {Promise} 88 | */ 89 | 90 | async verifyNetwork() { 91 | const raw = await this.db.get(layout.O.encode()); 92 | 93 | if (!raw) { 94 | const b = this.db.batch(); 95 | b.put(layout.O.encode(), fromU32(this.network.magic)); 96 | return b.write(); 97 | } 98 | 99 | const magic = raw.readUInt32LE(0, true); 100 | 101 | if (magic !== this.network.magic) 102 | throw new Error("Network mismatch for HnscanDB."); 103 | 104 | return undefined; 105 | } 106 | 107 | /** 108 | * Close the hnscandb, wait for the database to close. 109 | * @returns {Promise} 110 | */ 111 | 112 | async close() { 113 | return this.db.close(); 114 | } 115 | 116 | batch() { 117 | return this.db.batch(); 118 | } 119 | 120 | async saveEntry(entry, block, view) { 121 | this.pending = this.state.clone(); 122 | const hash = block.hash(); 123 | this.pending.connect(block); 124 | 125 | // Update chain state value. 126 | for (let i = 0; i < block.txs.length; i++) { 127 | const tx = block.txs[i]; 128 | 129 | if (i > 0) { 130 | for (const { prevout } of tx.inputs) { 131 | this.pending.spend(view.getOutput(prevout)); 132 | } 133 | } 134 | 135 | for (let i = 0; i < tx.outputs.length; i++) { 136 | const output = tx.outputs[i]; 137 | 138 | if (output.isUnspendable()) continue; 139 | 140 | // Registers are burned. 141 | if (output.covenant.isRegister()) { 142 | this.pending.burn(output); 143 | continue; 144 | } 145 | 146 | if ( 147 | output.covenant.type >= types.UPDATE && 148 | output.covenant.type <= types.REVOKE 149 | ) { 150 | continue; 151 | } 152 | 153 | this.pending.add(output); 154 | } 155 | } 156 | 157 | this.pending.commit(hash); 158 | this.state = this.pending; 159 | this.pending = null; 160 | } 161 | 162 | async saveState() { 163 | this.db.put(layout.s.encode(), this.state.encode()); 164 | } 165 | 166 | //Need to edit this function - add more error checking 167 | async setHeight(height) { 168 | this.height = height; 169 | 170 | //Insert into DB. 171 | await this.db.put(layout.H.encode(), fromU32(height)); 172 | 173 | return; 174 | } 175 | 176 | async getHeight() { 177 | let height = await this.db.get(layout.H.encode()); 178 | 179 | if (height == null) { 180 | height = 0; 181 | } else { 182 | height = toU32(height); 183 | } 184 | 185 | return height; 186 | } 187 | 188 | //For each block difficulty within the day, 189 | //Would be great to only set 1 of these a day. 190 | async setChartData(data) { 191 | let date = Math.floor(data.time / (3600 * 24)); 192 | 193 | if (this.chartDataCurrentDate === 0) { 194 | //initialization 195 | this.chartDataCurrentDate = date; 196 | this.chartDataCurrent.push(data); 197 | } else if (date > this.chartDataCurrentDate) { 198 | let avg = ChartData.fromArray(this.chartDataCurrent); 199 | 200 | await this.db.put(layout.d.encode(date * 3600 * 24), avg.encode()); 201 | 202 | this.chartDataCurrent = []; 203 | this.chartDataCurrent.push(data); 204 | this.chartDataCurrentDate = date; 205 | return; 206 | } 207 | 208 | this.chartDataCurrent.push(data); 209 | } 210 | 211 | async getDifficultySeries(startTime, endTime) { 212 | const iter = this.db.iterator({ 213 | gte: layout.d.min(startTime), 214 | lte: layout.d.max(endTime), 215 | values: true 216 | }); 217 | 218 | let tickData = []; 219 | 220 | await iter.each(async (key, rawValue) => { 221 | const [time] = layout.d.decode(key); 222 | const chartData = ChartData.decode(rawValue); 223 | 224 | let tick = { 225 | date: time * 1000, 226 | value: chartData.difficulty 227 | }; 228 | 229 | tickData.push(tick); 230 | }); 231 | 232 | return tickData; 233 | } 234 | 235 | async getTransactionSeries(startTime, endTime) { 236 | const iter = this.db.iterator({ 237 | gte: layout.d.min(startTime), 238 | lte: layout.d.max(endTime), 239 | values: true 240 | }); 241 | 242 | let tickData = []; 243 | 244 | await iter.each(async (key, rawValue) => { 245 | const [time] = layout.d.decode(key); 246 | const chartData = ChartData.decode(rawValue); 247 | 248 | let tick = { 249 | date: time * 1000, 250 | value: chartData.transactions 251 | }; 252 | 253 | tickData.push(tick); 254 | }); 255 | 256 | return tickData; 257 | } 258 | 259 | //@todo revamp all these and put them into 1 call w/ dynamic data fetching. 260 | async getTotalTransactionSeries(startTime, endTime) { 261 | const iter = this.db.iterator({ 262 | gte: layout.d.min(startTime), 263 | lte: layout.d.max(endTime), 264 | values: true 265 | }); 266 | 267 | let tickData = []; 268 | 269 | await iter.each(async (key, rawValue) => { 270 | const [time] = layout.d.decode(key); 271 | const chartData = ChartData.decode(rawValue); 272 | 273 | let tick = { 274 | date: time * 1000, 275 | value: chartData.totalTx 276 | }; 277 | 278 | tickData.push(tick); 279 | }); 280 | 281 | return tickData; 282 | } 283 | 284 | async getSupplySeries(startTime, endTime) { 285 | const iter = this.db.iterator({ 286 | gte: layout.d.min(startTime), 287 | lte: layout.d.max(endTime), 288 | values: true 289 | }); 290 | 291 | let tickData = []; 292 | 293 | await iter.each(async (key, rawValue) => { 294 | const [time] = layout.d.decode(key); 295 | const chartData = ChartData.decode(rawValue); 296 | 297 | let tick = { 298 | date: time * 1000, 299 | value: Amount.coin(chartData.supply) 300 | }; 301 | 302 | tickData.push(tick); 303 | }); 304 | 305 | return tickData; 306 | } 307 | 308 | async getBurnedSeries(startTime, endTime) { 309 | const iter = this.db.iterator({ 310 | gte: layout.d.min(startTime), 311 | lte: layout.d.max(endTime), 312 | values: true 313 | }); 314 | 315 | let tickData = []; 316 | 317 | await iter.each(async (key, rawValue) => { 318 | const [time] = layout.d.decode(key); 319 | const chartData = ChartData.decode(rawValue); 320 | 321 | let tick = { 322 | date: time * 1000, 323 | value: Amount.coin(chartData.burned) 324 | }; 325 | 326 | tickData.push(tick); 327 | }); 328 | 329 | return tickData; 330 | } 331 | 332 | /** 333 | * Return the funding outputs for an address, confirmed and unconfirmed. 334 | * @param addr - {Address} 335 | * @returns {Promise} -> {[confirmed: Number, unconfirmed: Number]} 336 | */ 337 | async addressFunding(addr) { 338 | try { 339 | let confirmed = await this._addressFunding(addr); 340 | let unconfirmed = await this._addressFundingUnconfirmed(addr); 341 | 342 | return [confirmed, unconfirmed]; 343 | } catch (e) { 344 | console.log(e); 345 | return; 346 | } 347 | } 348 | 349 | async _addressFunding(addr) { 350 | let hash = addr.getHash(); 351 | let funding = []; 352 | 353 | const iter = this.db.iterator({ 354 | gte: layout.o.min(hash), 355 | lte: layout.o.max(hash), 356 | values: true 357 | }); 358 | 359 | await iter.each(async (key, raw) => { 360 | const [userHash, txid] = layout.o.decode(key); 361 | 362 | let tx = { 363 | userHash, 364 | tx_hash: txid.toString("hex"), 365 | height: toU32(raw) 366 | }; 367 | 368 | let newtx = await this.client.getTX(txid); 369 | 370 | let outputIndex; 371 | let value; 372 | 373 | for (let i = 0; i < newtx.outputs.length; i++) { 374 | if (userHash.equals(newtx.outputs[i].address.getHash())) { 375 | outputIndex = i; 376 | value = newtx.outputs[i].value; 377 | break; 378 | } 379 | } 380 | 381 | let output = { 382 | tx_hash: txid.toString("hex"), 383 | height: toU32(raw), 384 | output_index: outputIndex, 385 | value 386 | }; 387 | 388 | funding.push(output); 389 | }); 390 | 391 | return funding; 392 | } 393 | 394 | //TODO 395 | async _addressFundingUnconfirmed(hash) { 396 | return []; 397 | } 398 | 399 | async addressSpent(hash, funding) { 400 | let confirmed = await this._addressSpent(hash, funding); 401 | let unconfirmed = await this._addressSpentUnconfirmed(hash, funding); 402 | 403 | return [confirmed, unconfirmed]; 404 | } 405 | 406 | async _addressSpent(hash, funding) { 407 | let spents = []; 408 | for (let o of funding) { 409 | let txPrefix = Buffer.from(o.tx_hash, "hex").slice(0, 8); 410 | 411 | const txHashX = await this.db.get( 412 | layout.i.encode(txPrefix, o.output_index) 413 | ); 414 | 415 | if (txHashX) { 416 | // let newtx = await this.client.getTX(txHashX); 417 | let height = await this.db.get(layout.t.encode(txHashX)); 418 | height = toU32(height); 419 | 420 | // let found = false; 421 | 422 | // for (let i = 0; i < newtx.inputs.length; i++) { 423 | // let outpoint = newtx.inputs[i].prevout; 424 | // if ( 425 | // outpoint.hash.toString("hex") === o.tx_hash && 426 | // outpoint.index === o.output_index 427 | // ) { 428 | // found = true; 429 | 430 | // break; 431 | // } 432 | // } 433 | 434 | // if (found) { 435 | let spent = { 436 | tx_hash: txHashX.toString("hex"), 437 | height: height, 438 | funding_output: [o.tx_hash, o.output_index], 439 | value: o.value 440 | }; 441 | spents.push(spent); 442 | // } 443 | } 444 | } 445 | 446 | return spents; 447 | } 448 | 449 | async _addressSpentUnconfirmed(hash, funding) { 450 | return []; 451 | } 452 | 453 | //Calculate Balance for an address 454 | //TODO might actually be faster to have 1 function for both unconfirmed. 455 | //Instead of in each subfunction. 456 | async addressBalance(addr) { 457 | let [fConfirmed, fUnconfirmed] = await this.addressFunding(addr); 458 | let [sConfirmed, sUnconfirmed] = await this.addressSpent(addr, fConfirmed); 459 | 460 | let totalFunded = 0; 461 | for (let f of fConfirmed) { 462 | totalFunded += f.value; 463 | } 464 | 465 | let totalSpent = 0; 466 | for (let s of sConfirmed) { 467 | totalSpent += s.value; 468 | } 469 | 470 | let balance = { 471 | confirmed: totalFunded - totalSpent, 472 | unconfirmed: totalFunded - totalSpent, 473 | received: totalFunded, 474 | spent: totalSpent 475 | }; 476 | 477 | return balance; 478 | } 479 | 480 | async addressHistory(addr) { 481 | let [fConfirmed, fUnconfirmed] = await this.addressFunding(addr); 482 | let [sConfirmed, sUnconfirmed] = await this.addressSpent(addr, fConfirmed); 483 | 484 | let txs = []; 485 | 486 | for (let f of fConfirmed) { 487 | let newtx = { 488 | tx_hash: f.tx_hash, 489 | height: f.height 490 | }; 491 | txs.push(newtx); 492 | } 493 | 494 | for (let s of sConfirmed) { 495 | let newtx = { 496 | tx_hash: s.tx_hash, 497 | height: s.height 498 | }; 499 | txs.push(newtx); 500 | } 501 | 502 | //TODO implement mempool txs. 503 | //Probably implement this through the client. 504 | 505 | return txs; 506 | } 507 | 508 | async addressUnspent(addr) { 509 | let [fConfirmed, fUnconfirmed] = await this.addressFunding(addr); 510 | let [sConfirmed, sUnconfirmed] = await this.addressSpent(addr, fConfirmed); 511 | 512 | let txs = []; 513 | 514 | for (let f of fConfirmed) { 515 | let newtx = { 516 | tx_hash: f.tx_hash, 517 | height: f.height, 518 | tx_pos: f.output_index, 519 | value: f.value 520 | }; 521 | txs.push(newtx); 522 | } 523 | 524 | for (let s of sConfirmed) { 525 | txs = txs.filter(tx => tx.tx_hash !== s.funding_output[0]); 526 | } 527 | 528 | return txs; 529 | } 530 | 531 | async nameHistory(nameHash) { 532 | let auctionList = []; 533 | 534 | const iter = this.db.iterator({ 535 | gte: layout.n.min(nameHash), 536 | lte: layout.n.max(nameHash), 537 | values: true 538 | }); 539 | 540 | await iter.each(async (key, raw) => { 541 | const [, txid] = layout.n.decode(key); 542 | 543 | let tx = { 544 | tx_hash: txid.toString("hex"), 545 | height: toU32(raw) 546 | }; 547 | 548 | auctionList.push(tx); 549 | }); 550 | 551 | return auctionList; 552 | } 553 | 554 | async getState() { 555 | const data = await this.db.get(layout.s.encode()); 556 | 557 | if (!data) return null; 558 | 559 | return ChainState.decode(data); 560 | } 561 | } 562 | 563 | class HnscanDBOptions { 564 | /** 565 | * Create hnscandb options. 566 | * @constructor 567 | * @param {Object} options 568 | */ 569 | 570 | constructor(options) { 571 | this.network = Network.primary; 572 | this.logger = Logger.global; 573 | this.client = null; 574 | this.prefix = null; 575 | this.location = null; 576 | this.memory = true; 577 | this.maxFiles = 64; 578 | this.cacheSize = 16 << 20; 579 | this.compression = true; 580 | 581 | if (options) this._fromOptions(options); 582 | } 583 | 584 | /** 585 | * Inject properties from object. 586 | * @private 587 | * @param {Object} options 588 | * @returns {HnscanDBOptions} 589 | */ 590 | 591 | _fromOptions(options) { 592 | if (options.network != null) this.network = Network.get(options.network); 593 | 594 | if (options.logger != null) { 595 | assert(typeof options.logger === "object"); 596 | this.logger = options.logger; 597 | } 598 | 599 | if (options.client != null) { 600 | assert(typeof options.client === "object"); 601 | this.client = options.client; 602 | } 603 | 604 | assert(this.client); 605 | 606 | if (options.prefix != null) { 607 | assert(typeof options.prefix === "string"); 608 | this.prefix = options.prefix; 609 | this.location = path.join(this.prefix, "hnscan"); 610 | } 611 | 612 | if (options.location != null) { 613 | assert(typeof options.location === "string"); 614 | this.location = options.location; 615 | } 616 | 617 | if (options.memory != null) { 618 | assert(typeof options.memory === "boolean"); 619 | this.memory = options.memory; 620 | } 621 | 622 | if (options.maxFiles != null) { 623 | assert(options.maxFiles >>> 0 === options.maxFiles); 624 | this.maxFiles = options.maxFiles; 625 | } 626 | 627 | if (options.cacheSize != null) { 628 | assert(Number.isSafeInteger(options.cacheSize) && options.cacheSize >= 0); 629 | this.cacheSize = options.cacheSize; 630 | } 631 | 632 | if (options.compression != null) { 633 | assert(typeof options.compression === "boolean"); 634 | this.compression = options.compression; 635 | } 636 | 637 | return this; 638 | } 639 | 640 | /** 641 | * Instantiate chain options from object. 642 | * @param {Object} options 643 | * @returns {HnscanDBOptions} 644 | */ 645 | 646 | static fromOptions(options) { 647 | return new this()._fromOptions(options); 648 | } 649 | } 650 | 651 | /* 652 | * Helpers 653 | */ 654 | 655 | function fromU32(num) { 656 | const data = Buffer.allocUnsafe(4); 657 | data.writeUInt32LE(num, 0, true); 658 | return data; 659 | } 660 | 661 | function toU32(buf) { 662 | const num = buf.readUInt32LE(0, true); 663 | return num; 664 | } 665 | 666 | module.exports = HnscanDB; 667 | -------------------------------------------------------------------------------- /lib/http.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * http.js - http server for hnscan 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * Copyright (c) 2018-2019, Handshake Alliance Developers (MIT License). 5 | * https://github.com/handshakealliance/hnscan-backend 6 | */ 7 | 8 | "use strict"; 9 | 10 | const { Server } = require("bweb"); 11 | const Network = require("hsd").protocol.Network; 12 | const Validator = require("bval"); 13 | const { base58 } = require("bstring"); 14 | const random = require("bcrypto/lib/random"); 15 | const sha256 = require("bcrypto/lib/sha256"); 16 | const assert = require("bsert"); 17 | const version = require("../package.json").version; 18 | const protocol = require("../package.json").protocol; 19 | const bio = require("bufio"); 20 | const util = require("./util.js"); 21 | const rules = require("hsd/lib/covenants/rules"); 22 | const Amount = require("hsd/lib/ui/amount"); 23 | const NameState = require("hsd/lib/covenants/namestate"); 24 | const pkg = require("hsd").pkg; 25 | const geoip = require("geoip-lite"); 26 | 27 | const { Address, TX } = require("hsd"); 28 | 29 | /** 30 | * HTTP 31 | * @alias module:hnscan.HTTP 32 | */ 33 | 34 | class HTTP extends Server { 35 | /** 36 | * Create an http server. 37 | * @constructor 38 | * @param {Object} options 39 | */ 40 | 41 | constructor(options) { 42 | super(new HTTPOptions(options)); 43 | 44 | this.network = this.options.network; 45 | this.logger = this.options.logger.context("hnscan-http"); 46 | this.hdb = this.options.hdb; 47 | this.client = this.options.client; 48 | this.host = this.options.host; 49 | this.port = this.options.port; 50 | this.ssl = this.options.ssl; 51 | this.node = this.options.node; 52 | this.chain = this.node.chain; 53 | this.fees = this.node.fees; 54 | this.mempool = this.node.mempool; 55 | // TODO: remove rpc call for names 56 | this.rpc = this.node.rpc; 57 | this.hnscan = this.options.hnscan; 58 | 59 | this.init(); 60 | } 61 | 62 | /** 63 | * Initialize http server. 64 | * @private 65 | */ 66 | 67 | init() { 68 | this.on("request", (req, res) => { 69 | if (req.method === "POST" && req.pathname === "/") return; 70 | 71 | this.logger.debug( 72 | "Request for method=%s path=%s (%s).", 73 | req.method, 74 | req.pathname, 75 | req.socket.remoteAddress 76 | ); 77 | }); 78 | 79 | this.on("listening", address => { 80 | this.logger.info( 81 | "Hnscan HTTP server listening on %s (port=%d).", 82 | address.address, 83 | address.port 84 | ); 85 | }); 86 | 87 | this.initRouter(); 88 | } 89 | 90 | /** 91 | * Initialize routes. 92 | * @private 93 | */ 94 | 95 | initRouter() { 96 | if (this.options.cors) this.use(this.cors()); 97 | 98 | if (!this.options.noAuth) { 99 | this.use( 100 | this.basicAuth({ 101 | hash: sha256.digest, 102 | password: this.options.apiKey, 103 | realm: "hnscan" 104 | }) 105 | ); 106 | } 107 | 108 | this.use( 109 | this.bodyParser({ 110 | type: "json" 111 | }) 112 | ); 113 | 114 | this.use(this.jsonRPC()); 115 | this.use(this.router()); 116 | 117 | this.error((err, req, res) => { 118 | const code = err.statusCode || 500; 119 | res.json(code, { 120 | error: { 121 | type: err.type, 122 | code: err.code, 123 | message: err.message 124 | } 125 | }); 126 | }); 127 | 128 | this.get("/summary", async (req, res) => { 129 | const totalTX = this.mempool ? this.mempool.map.size : 0; 130 | const size = this.mempool ? this.mempool.getSize() : 0; 131 | // const registeredNames = sortArr(await this.rpc.getNames([]), "close"); 132 | const start = process.hrtime(); 133 | 134 | const nameCount = await this.hnscan.getNameCount(this.chain.height); 135 | 136 | const diff = process.hrtime(start); 137 | 138 | const NS_PER_SEC = 1e9; 139 | 140 | console.log( 141 | `Benchmark took ${diff[0] * NS_PER_SEC + diff[1]} nanoseconds` 142 | ); 143 | 144 | res.json(200, { 145 | network: this.network.type, 146 | chainWork: this.chain.tip.chainwork.toString("hex", 64), 147 | difficulty: toDifficulty(this.chain.tip.bits), 148 | hashrate: await this.getHashRate(120), 149 | unconfirmed: totalTX, 150 | unconfirmedSize: size, 151 | // Getting names doubles response time - any way to speed this up? 152 | // @todo make this activeNames 153 | registeredNames: nameCount 154 | }); 155 | }); 156 | 157 | this.get("/status", async (req, res) => { 158 | let addr = this.node.pool.hosts.getLocal(); 159 | 160 | if (!addr) addr = this.node.pool.hosts.address; 161 | 162 | let sent = 0; 163 | let recv = 0; 164 | 165 | for (let peer = this.node.pool.peers.head(); peer; peer = peer.next) { 166 | sent += peer.socket.bytesWritten; 167 | recv += peer.socket.bytesRead; 168 | } 169 | 170 | res.json(200, { 171 | host: addr.host, 172 | port: addr.port, 173 | key: addr.getKey("base32"), 174 | network: this.network.type, 175 | progress: this.chain.getProgress(), 176 | version: pkg.version, 177 | agent: this.node.pool.options.agent, 178 | connections: this.node.pool.peers.size(), 179 | height: this.chain.height, 180 | difficulty: toDifficulty(this.chain.tip.bits), 181 | uptime: this.node.uptime(), 182 | totalBytesRecv: recv, 183 | totalBytesSent: sent 184 | }); 185 | }); 186 | 187 | // Blocks in bulk by specified number 188 | // Default = 10; Max = 50 189 | this.get("/blocks", async (req, res) => { 190 | const valid = Validator.fromRequest(req); 191 | const tip = this.chain.height; 192 | const limit = valid.uint("limit", 25); 193 | const offset = valid.uint("offset", 0); 194 | const start = tip - offset; 195 | 196 | enforce(limit <= 50, "Too many blocks requested. Max of 50."); 197 | enforce(start >= 0, "Offset too large."); 198 | enforce(!this.chain.options.spv, "Cannot get block in SPV mode."); 199 | 200 | let end = start - limit; 201 | 202 | if (end < 0) { 203 | end = -1; 204 | } 205 | 206 | let blocks = []; 207 | for (let i = start; i > end; i--) { 208 | //@todo performance - parrallize this. 209 | const block = await this.hnscan.getBlock(i); 210 | 211 | blocks.push(block); 212 | } 213 | 214 | res.json(200, { 215 | total: tip + 1, 216 | offset, 217 | limit, 218 | result: blocks 219 | }); 220 | }); 221 | 222 | this.get("/blocks/:height", async (req, res) => { 223 | const valid = Validator.fromRequest(req); 224 | const height = valid.u32("height"); 225 | 226 | enforce(height != null, "height required."); 227 | 228 | const block = await this.hnscan.getBlock(height); 229 | 230 | if (!block) { 231 | res.json(404); 232 | return; 233 | } 234 | 235 | res.json(200, block); 236 | }); 237 | 238 | this.get("/txs", async (req, res) => { 239 | const valid = Validator.fromRequest(req); 240 | const height = valid.u32("height"); 241 | const address = valid.str("address"); 242 | //@todo remove the offset 243 | const offset = valid.u32("offset", 0); 244 | const page = valid.u32("p", 1); 245 | const limit = valid.u32("limit", 25); 246 | 247 | // TODO: Needs Fixing 248 | // enforce(height != null || address != null, "height required."); 249 | 250 | //@todo this block can definitely be cleaned up considering they are both returning the same variable names. 251 | if (height) { 252 | const [txs, total] = await this.hnscan.getTransactionsByHeight( 253 | height, 254 | offset, 255 | limit 256 | ); 257 | 258 | res.json(200, { 259 | total, 260 | result: txs 261 | }); 262 | } else if (address) { 263 | //@todo catch this and return 404. 264 | let addr = Address.fromString(address, this.network.type); 265 | const [txs, total] = await this.hnscan.getTransactionsByAddress( 266 | addr, 267 | offset, 268 | limit 269 | ); 270 | res.json(200, { 271 | total, 272 | offset, 273 | limit, 274 | result: txs 275 | }); 276 | } else { 277 | const txs = await this.hnscan.getTransactions(limit); 278 | // console.log("txs:", txs); 279 | res.json(200, { result: txs }); 280 | } 281 | }); 282 | 283 | this.get("/txs/:hash", async (req, res) => { 284 | const valid = Validator.fromRequest(req); 285 | const hash = valid.bhash("hash"); 286 | 287 | enforce(hash != null, "tx hash required."); 288 | 289 | const tx = await this.hnscan.getTransaction(hash); 290 | 291 | if (!tx) { 292 | res.json(404); 293 | return; 294 | } 295 | 296 | res.json(200, tx); 297 | }); 298 | 299 | //@todo filters for name status 300 | this.get("/names", async (req, res) => { 301 | const valid = Validator.fromRequest(req); 302 | const limit = valid.uint("limit", 25); 303 | const offset = valid.uint("offset", 0); 304 | 305 | enforce(limit <= 50, "Too many names requested. Max of 50."); 306 | //@todo not sure if this is true. 307 | enforce(!this.chain.options.spv, "Cannot get names in SPV mode."); 308 | 309 | // for (let i = start; i > end; i--) { 310 | // const block = await this.hnscan.getBlock(i); 311 | 312 | // blocks.push(block); 313 | // } 314 | 315 | const height = this.chain.height; 316 | const txn = this.chain.db.txn; 317 | const items = []; 318 | 319 | //@todo would be a lot more efficient to implement something like Lodash memorize here considering this will only change once every 36 blocks. 320 | const iter = txn.iterator(); 321 | 322 | let length = iter.length; 323 | while (await iter.next()) { 324 | const { key, value } = iter; 325 | const ns = NameState.decode(value); 326 | ns.nameHash = key; 327 | 328 | const info = ns.getJSON(height, this.network); 329 | //@todo it may actually be more efficient to implement something like Binary Insert here. 330 | //Ref: https://machinesaredigging.com/2014/04/27/binary-insert-how-to-keep-an-array-sorted-as-you-insert-data-in-it/ 331 | //For now, we are just going to push then sort. 332 | items.push(info); 333 | } 334 | 335 | items.sort(function(a, b) { 336 | if (b.height !== a.height) { 337 | return b.height - a.height; 338 | } 339 | if (a.name === b.name) { 340 | return 0; 341 | } 342 | return a.name > b.name ? 1 : -1; 343 | }); 344 | 345 | const names = items.slice(offset, offset + limit); 346 | 347 | res.json(200, { 348 | total: items.length, 349 | offset, 350 | limit, 351 | result: names 352 | }); 353 | }); 354 | 355 | this.get("/names/:name", async (req, res) => { 356 | const valid = Validator.fromRequest(req); 357 | const name = valid.str("name"); 358 | 359 | enforce(name != null, "name required."); 360 | 361 | const nameData = await this.hnscan.getName(name); 362 | 363 | if (!nameData) { 364 | res.json(404); 365 | return; 366 | } 367 | 368 | res.json(200, nameData); 369 | }); 370 | 371 | this.get("/names/:name/history", async (req, res) => { 372 | const valid = Validator.fromRequest(req); 373 | const name = valid.str("name"); 374 | 375 | enforce(name != null, "name required."); 376 | 377 | //@todo implement offset and limit here. 378 | const [history, total] = await this.hnscan.getNameHistory(name, 0, 100); 379 | 380 | if (!history) { 381 | res.json(404); 382 | return; 383 | } 384 | 385 | res.json(200, { 386 | total, 387 | offset: 0, 388 | limit: 25, 389 | result: history 390 | }); 391 | }); 392 | 393 | this.get("/addresses/:hash", async (req, res) => { 394 | const valid = Validator.fromRequest(req); 395 | //@todo I believe there is a valid.hash 396 | const hash = valid.str("hash"); 397 | 398 | enforce(hash != null, "address required."); 399 | 400 | //@todo catch this error and return 404. 401 | let addr = Address.fromString(hash, this.network.type); 402 | 403 | const balance = await this.hnscan.getAddress(addr); 404 | 405 | if (!balance) { 406 | res.json(404); 407 | return; 408 | } 409 | 410 | res.json(200, balance); 411 | }); 412 | 413 | //@todo allow for filtering of peers by services, etc. 414 | this.get("/peers", async (req, res) => { 415 | const valid = Validator.fromRequest(req); 416 | const page = valid.uint("page", 1); 417 | const limit = valid.uint("limit", 10); 418 | // const offset = valid.uint("offset", 0); 419 | const offset = (page - 1) * limit; 420 | 421 | // enforce(limit <= 50, "Too many names requested. Max of 50."); 422 | 423 | const [peers, total] = await this.hnscan.getPeers(offset, limit); 424 | 425 | res.json(200, { 426 | total, 427 | result: peers 428 | }); 429 | }); 430 | 431 | //@todo allow for filtering of peers by services, etc. 432 | this.get("/mapdata", async (req, res) => { 433 | const peers = await this.hnscan.getPeersLocation(); 434 | const data = []; 435 | 436 | for (let i = 0; i < peers.length; i++) { 437 | data.push({ 438 | latitude: peers[i].ll[0], 439 | longitude: peers[i].ll[1], 440 | title: peers[i].city 441 | }); 442 | } 443 | 444 | res.json(200, { data: data }); 445 | }); 446 | 447 | /* 448 | * 449 | * Address HTTP Functions 450 | * 451 | */ 452 | this.get("/address/:hash/mempool", async (req, res) => { 453 | const valid = Validator.fromRequest(req); 454 | 455 | let hash = valid.str("hash"); 456 | 457 | //Check if is valid, if not return error - enforce 458 | let addr = Address.fromString(hash, this.network); 459 | 460 | let txs = this.mempool.getTXByAddress(addr); 461 | 462 | let history = this.mempool.getHistory(); 463 | 464 | //@todo 465 | console.log(history); 466 | 467 | console.log(txs); 468 | 469 | res.json(200, {}); 470 | }); 471 | 472 | this.get("/address/:hash/unspent", async (req, res) => { 473 | const valid = Validator.fromRequest(req); 474 | 475 | let hash = valid.str("hash"); 476 | let limit = valid.u32("limit", 25); 477 | let offset = valid.u32("offset", 0); 478 | 479 | let end = offset + limit; 480 | 481 | //Check if is valid, if not return error - enforce 482 | let addr = Address.fromString(hash, this.network); 483 | 484 | let txs = await this.ndb.addressUnspent(addr); 485 | 486 | txs = util.sortTXs(txs); 487 | 488 | let total = txs.length; 489 | 490 | let result = txs.slice(offset, end); 491 | 492 | res.json(200, { total, offset, limit, result }); 493 | }); 494 | 495 | // Address Tx History 496 | this.get("/address/:hash/history", async (req, res) => { 497 | const valid = Validator.fromRequest(req); 498 | 499 | let hash = valid.str("hash"); 500 | let limit = valid.u32("limit", 10); 501 | let offset = valid.u32("offset", 0); 502 | 503 | let end = offset + limit; 504 | 505 | let txs; 506 | 507 | let addr = Address.fromString(hash, this.network); 508 | 509 | try { 510 | txs = await this.ndb.addressHistory(addr); 511 | } catch (e) { 512 | res.json(400); 513 | return; 514 | } 515 | 516 | //Return out of range if start is beyond array length. 517 | if (offset > txs.length) { 518 | res.json(416); 519 | return; 520 | } 521 | 522 | txs = util.sortTXs(txs); 523 | 524 | let total = txs.length; 525 | 526 | let result = txs.slice(offset, end); 527 | 528 | res.json(200, { total, offset, limit, result }); 529 | 530 | return; 531 | }); 532 | 533 | //@todo this is probably an "epic", but build this search in such a way that we can actually send the data for the next page if needed. So if someone searches just for a TX and that's the only result, then push that data to the React app so that another API request doesn't have to be made when the user is redirected to that page. I think the way we would do that is send it up in an object inside of the JSON called data, and then in the React app, during the search bar redirect we pass Data if it's available. Then in each component, we check to see if data exists *before* making the useResource call. 534 | this.get("/search", async (req, res) => { 535 | const valid = Validator.fromRequest(req); 536 | 537 | const query = valid.str("q"); 538 | const txHash = RegExp("^[a-fA-F0-9]{64}$"); 539 | 540 | let results = []; 541 | 542 | // Checks if the query is a number. 543 | if (!isNaN(query)) { 544 | //Converts search string to int 545 | let height = +query; 546 | 547 | let tip = this.chain.height; 548 | 549 | //If it's a feasible block height, redirect to block page. 550 | if (height <= tip && height >= 0) { 551 | let result = { type: "Block", url: `/block/${height}` }; 552 | 553 | results.push(result); 554 | } 555 | } 556 | //@todo switch to hasTX 557 | 558 | if (query.length === 64) { 559 | if (await this.chain.db.getTX(Buffer.from(query, "hex"))) { 560 | let result = { type: "Transaction", url: `/tx/${query}` }; 561 | results.push(result); 562 | } 563 | 564 | let height = await this.chain.db.getHeight(Buffer.from(query, "hex")); 565 | 566 | if (height >= 0) { 567 | let result = { type: "Block", url: `/block/${height}` }; 568 | 569 | results.push(result); 570 | } 571 | } 572 | 573 | //Do we want to look up the transaction here, or just test it? 574 | //@todo actually test the tx here. 575 | // if (txHash.test(search)) { 576 | // let result = { type: "Transaction", url: `/tx/${search}` }; 577 | // results.push(result); 578 | // } 579 | 580 | //@todo test for blockhash 581 | //this.chain.db.getHeight(hash) //Todo await. 582 | //this.chain.db.getBlock(hash) probably better. 583 | 584 | let address; 585 | 586 | try { 587 | address = new Address(query); 588 | } catch (e) { 589 | //Do nothing. 590 | } 591 | 592 | if (address) { 593 | if (address.isValid()) { 594 | let result = { type: "Address", url: `/address/${query}` }; 595 | results.push(result); 596 | } 597 | } 598 | 599 | let name = query.toLowerCase(); 600 | if (rules.verifyString(name)) { 601 | let result = { type: "Name", url: `/name/${name}` }; 602 | results.push(result); 603 | } 604 | 605 | return res.json(200, results); 606 | }); 607 | 608 | // Name History 609 | this.get("/name/:name/history", async (req, res) => { 610 | const valid = Validator.fromRequest(req); 611 | 612 | let name = valid.str("name"); 613 | let limit = valid.u32("limit", 10); 614 | let offset = valid.u32("offset", 0); 615 | let full = valid.bool("full", false); 616 | 617 | let end = offset + limit; 618 | 619 | let txs; 620 | 621 | let nameHash = rules.hashName(name); 622 | 623 | //Do namechecks here, and return accordingly 624 | 625 | try { 626 | txs = await this.ndb.nameHistory(nameHash); 627 | } catch (e) { 628 | res.json(400); 629 | return; 630 | } 631 | 632 | //Return out of range if start is beyond array length. 633 | if (offset > txs.length) { 634 | res.json(416); 635 | return; 636 | } 637 | 638 | txs = util.sortTXs(txs); 639 | 640 | let total = txs.length; 641 | 642 | let result = txs.slice(offset, end); 643 | 644 | res.json(200, { total, offset, limit, result }); 645 | 646 | return; 647 | }); 648 | 649 | //Address Balance 650 | this.get("/address/:hash/balance", async (req, res) => { 651 | const valid = Validator.fromRequest(req); 652 | 653 | let hash = valid.str("hash"); 654 | 655 | let addr = Address.fromString(hash, this.network); 656 | 657 | let balance; 658 | 659 | try { 660 | balance = await this.ndb.addressBalance(addr); 661 | } catch (e) { 662 | res.json(400); 663 | return; 664 | } 665 | 666 | res.json(200, balance); 667 | 668 | return; 669 | }); 670 | 671 | this.get("/charts/difficulty", async (req, res) => { 672 | const valid = Validator.fromRequest(req); 673 | let startTime = valid.u32("startTime"); 674 | let endTime = valid.u32("endTime"); 675 | let chartData; 676 | 677 | try { 678 | //@todo move this to hnscan file. 679 | chartData = await this.hdb.getDifficultySeries(startTime, endTime); 680 | } catch (e) { 681 | console.log(e); 682 | res.json(400); 683 | return; 684 | } 685 | 686 | res.json(200, chartData); 687 | 688 | return; 689 | }); 690 | 691 | this.get("/charts/dailyTransactions", async (req, res) => { 692 | const valid = Validator.fromRequest(req); 693 | let startTime = valid.u32("startTime"); 694 | let endTime = valid.u32("endTime"); 695 | let chartData; 696 | 697 | try { 698 | //@todo move this to hnscan file. 699 | chartData = await this.hdb.getTransactionSeries(startTime, endTime); 700 | } catch (e) { 701 | res.json(400); 702 | return; 703 | } 704 | 705 | res.json(200, chartData); 706 | 707 | return; 708 | }); 709 | 710 | this.get("/charts/dailyTotalTransactions", async (req, res) => { 711 | const valid = Validator.fromRequest(req); 712 | let startTime = valid.u32("startTime"); 713 | let endTime = valid.u32("endTime"); 714 | let chartData; 715 | 716 | try { 717 | //@todo move this to hnscan file. 718 | chartData = await this.hdb.getTotalTransactionSeries( 719 | startTime, 720 | endTime 721 | ); 722 | } catch (e) { 723 | res.json(400); 724 | return; 725 | } 726 | 727 | res.json(200, chartData); 728 | 729 | return; 730 | }); 731 | 732 | this.get("/charts/supply", async (req, res) => { 733 | const valid = Validator.fromRequest(req); 734 | let startTime = valid.u32("startTime"); 735 | let endTime = valid.u32("endTime"); 736 | let chartData; 737 | 738 | try { 739 | //@todo move this to hnscan file. 740 | chartData = await this.hdb.getSupplySeries(startTime, endTime); 741 | } catch (e) { 742 | res.json(400); 743 | return; 744 | } 745 | 746 | res.json(200, chartData); 747 | 748 | return; 749 | }); 750 | 751 | this.get("/charts/burned", async (req, res) => { 752 | const valid = Validator.fromRequest(req); 753 | let startTime = valid.u32("startTime"); 754 | let endTime = valid.u32("endTime"); 755 | let chartData; 756 | 757 | try { 758 | //@todo move this to hnscan file. 759 | chartData = await this.hdb.getBurnedSeries(startTime, endTime); 760 | } catch (e) { 761 | res.json(400); 762 | return; 763 | } 764 | 765 | res.json(200, chartData); 766 | 767 | return; 768 | }); 769 | } 770 | 771 | //TODO move these to util or somewhere else. 772 | txToJSON(tx, entry) { 773 | let height = -1; 774 | let time = 0; 775 | let hash = null; 776 | let conf = 0; 777 | 778 | if (entry) { 779 | height = entry.height; 780 | time = entry.time; 781 | hash = entry.hash; 782 | conf = this.client.getTip().height - height + 1; 783 | } 784 | 785 | const vin = []; 786 | 787 | for (const input of tx.inputs) { 788 | const json = { 789 | coinbase: undefined, 790 | txid: undefined, 791 | vout: undefined, 792 | txinwitness: undefined, 793 | sequence: input.sequence, 794 | link: input.link 795 | }; 796 | 797 | json.coinbase = tx.isCoinbase(); 798 | json.txid = input.prevout.txid(); 799 | json.vout = input.prevout.index; 800 | json.txinwitness = input.witness.toJSON(); 801 | 802 | vin.push(json); 803 | } 804 | 805 | const vout = []; 806 | 807 | for (let i = 0; i < tx.outputs.length; i++) { 808 | const output = tx.outputs[i]; 809 | vout.push({ 810 | value: Amount.coin(output.value, true), 811 | n: i, 812 | address: this.addrToJSON(output.address), 813 | covenant: output.covenant.toJSON() 814 | }); 815 | } 816 | 817 | return { 818 | txid: tx.txid(), 819 | hash: tx.wtxid(), 820 | size: tx.getSize(), 821 | vsize: tx.getVirtualSize(), 822 | version: tx.version, 823 | locktime: tx.locktime, 824 | vin: vin, 825 | vout: vout, 826 | blockhash: hash ? hash.toString("hex") : null, 827 | confirmations: conf, 828 | time: time, 829 | blocktime: time, 830 | hex: undefined 831 | }; 832 | } 833 | 834 | //TODO move these to util or somewhere else. 835 | addrToJSON(addr) { 836 | return { 837 | version: addr.version, 838 | hash: addr.hash.toString("hex") 839 | }; 840 | } 841 | 842 | async getHashRate(lookup, height) { 843 | let tip = this.chain.tip; 844 | 845 | if (height != null) { 846 | tip = await this.chain.getEntry(height); 847 | } 848 | 849 | if (!tip) { 850 | return 0; 851 | } 852 | 853 | assert(typeof lookup === "number"); 854 | assert(lookup >= 0); 855 | 856 | if (lookup === 0) { 857 | lookup = (tip.height % this.network.pow.targetWindow) + 1; 858 | } 859 | 860 | if (lookup > tip.height) { 861 | lookup = tip.height; 862 | } 863 | 864 | let min = tip.time; 865 | let max = min; 866 | let entry = tip; 867 | 868 | for (let i = 0; i < lookup; i++) { 869 | entry = await this.chain.getPrevious(entry); 870 | 871 | if (!entry) { 872 | throw new RPCError(errs.DATABASE_ERROR, "Not found."); 873 | } 874 | 875 | min = Math.min(entry.time, min); 876 | max = Math.max(entry.time, max); 877 | } 878 | 879 | const diff = max - min; 880 | 881 | if (diff === 0) { 882 | return 0; 883 | } 884 | 885 | const work = tip.chainwork.sub(entry.chainwork); 886 | 887 | return Number(work.toString()) / diff; 888 | } 889 | } 890 | 891 | class HTTPOptions { 892 | /** 893 | * HTTPOptions 894 | * @alias module:http.HTTPOptions 895 | * @constructor 896 | * @param {Object} options 897 | */ 898 | 899 | constructor(options) { 900 | this.network = Network.primary; 901 | this.logger = null; 902 | this.node = null; 903 | this.apiKey = base58.encode(random.randomBytes(20)); 904 | this.apiHash = sha256.digest(Buffer.from(this.apiKey, "ascii")); 905 | this.adminToken = random.randomBytes(32); 906 | this.serviceHash = this.apiHash; 907 | this.noAuth = false; 908 | this.cors = false; 909 | this.walletAuth = false; 910 | 911 | this.prefix = null; 912 | this.host = "127.0.0.1"; 913 | this.port = 8080; 914 | this.ssl = false; 915 | this.keyFile = null; 916 | this.certFile = null; 917 | 918 | this.fromOptions(options); 919 | } 920 | 921 | /** 922 | * Inject properties from object. 923 | * @private 924 | * @param {Object} options 925 | * @returns {HTTPOptions} 926 | */ 927 | 928 | fromOptions(options) { 929 | assert(options); 930 | assert( 931 | options.node && typeof options.node === "object", 932 | "HTTP Server requires a node." 933 | ); 934 | 935 | this.node = options.node; 936 | 937 | if (options.network != null) this.network = Network.get(options.network); 938 | 939 | if (options.logger != null) { 940 | assert(typeof options.logger === "object"); 941 | this.logger = options.logger; 942 | } 943 | 944 | if (options.hdb != null) { 945 | assert(typeof options.hdb === "object"); 946 | this.hdb = options.hdb; 947 | } 948 | 949 | if (options.hnscan != null) { 950 | assert(typeof options.hnscan === "object"); 951 | this.hnscan = options.hnscan; 952 | } 953 | 954 | if (options.client != null) { 955 | assert(typeof options.client === "object"); 956 | this.client = options.client; 957 | } 958 | 959 | if (options.logger != null) { 960 | assert(typeof options.logger === "object"); 961 | this.logger = options.logger; 962 | } 963 | 964 | if (options.apiKey != null) { 965 | assert(typeof options.apiKey === "string", "API key must be a string."); 966 | assert(options.apiKey.length <= 255, "API key must be under 255 bytes."); 967 | this.apiKey = options.apiKey; 968 | this.apiHash = sha256.digest(Buffer.from(this.apiKey, "ascii")); 969 | } 970 | 971 | if (options.noAuth != null) { 972 | assert(typeof options.noAuth === "boolean"); 973 | this.noAuth = options.noAuth; 974 | } 975 | 976 | if (options.cors != null) { 977 | assert(typeof options.cors === "boolean"); 978 | this.cors = options.cors; 979 | } 980 | 981 | if (options.prefix != null) { 982 | assert(typeof options.prefix === "string"); 983 | this.prefix = options.prefix; 984 | this.keyFile = path.join(this.prefix, "key.pem"); 985 | this.certFile = path.join(this.prefix, "cert.pem"); 986 | } 987 | 988 | if (options.host != null) { 989 | assert(typeof options.host === "string"); 990 | this.host = options.host; 991 | } 992 | 993 | if (options.port != null) { 994 | assert( 995 | (options.port & 0xffff) === options.port, 996 | "Port must be a number." 997 | ); 998 | this.port = options.port; 999 | } 1000 | 1001 | if (options.ssl != null) { 1002 | assert(typeof options.ssl === "boolean"); 1003 | this.ssl = options.ssl; 1004 | } 1005 | 1006 | if (options.keyFile != null) { 1007 | assert(typeof options.keyFile === "string"); 1008 | this.keyFile = options.keyFile; 1009 | } 1010 | 1011 | if (options.certFile != null) { 1012 | assert(typeof options.certFile === "string"); 1013 | this.certFile = options.certFile; 1014 | } 1015 | 1016 | // Allow no-auth implicitly 1017 | // if we're listening locally. 1018 | if (!options.apiKey) { 1019 | if (this.host === "127.0.0.1" || this.host === "::1") this.noAuth = true; 1020 | } 1021 | 1022 | return this; 1023 | } 1024 | 1025 | /** 1026 | * Instantiate http options from object. 1027 | * @param {Object} options 1028 | * @returns {HTTPOptions} 1029 | */ 1030 | 1031 | static fromOptions(options) { 1032 | return new HTTPOptions().fromOptions(options); 1033 | } 1034 | } 1035 | 1036 | /* 1037 | * Helpers 1038 | */ 1039 | 1040 | function enforce(value, msg) { 1041 | if (!value) { 1042 | const err = new Error(msg); 1043 | err.statusCode = 400; 1044 | throw err; 1045 | } 1046 | } 1047 | 1048 | function toDifficulty(bits) { 1049 | let shift = (bits >>> 24) & 0xff; 1050 | let diff = 0x0000ffff / (bits & 0x00ffffff); 1051 | 1052 | while (shift < 29) { 1053 | diff *= 256.0; 1054 | shift++; 1055 | } 1056 | 1057 | while (shift > 29) { 1058 | diff /= 256.0; 1059 | shift--; 1060 | } 1061 | 1062 | return diff; 1063 | } 1064 | 1065 | function sortArr(arr, type) { 1066 | let names = []; 1067 | 1068 | for (let i = 0; i < arr.length; i++) { 1069 | if (type === "close" && arr[i].state === "CLOSED") { 1070 | names.push(arr[i]); 1071 | } else if (type === "open" && arr[i].state === "OPENING") { 1072 | names.push(arr[i]); 1073 | } else if (type === "bid" && arr[i].state === "BIDDING") { 1074 | names.push(arr[i]); 1075 | } else if (type === "reveal" && arr[i].state === "REVEAL") { 1076 | names.push(arr[i]); 1077 | } else if (type === "all") { 1078 | names.push(arr[i]); 1079 | } 1080 | } 1081 | 1082 | return names.sort(function(a, b) { 1083 | if (b.height !== a.height) { 1084 | return b.height - a.height; 1085 | } 1086 | if (a.name === b.name) { 1087 | return 0; 1088 | } 1089 | return a.name > b.name ? 1 : -1; 1090 | }); 1091 | } 1092 | 1093 | /* 1094 | * Expose 1095 | */ 1096 | 1097 | module.exports = HTTP; 1098 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * index.js - hncan plugin for hsd 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * Copyright (c) 2018-2019, Handshake Alliance Developers (MIT License). 5 | * https://github.com/handshakealliance/hnscan-backend 6 | */ 7 | 8 | "use strict"; 9 | 10 | const plugin = require("./plugin"); 11 | 12 | /** 13 | * @module hnscan 14 | */ 15 | 16 | exports = plugin; 17 | 18 | exports.plugin = plugin; 19 | 20 | module.exports = exports; 21 | -------------------------------------------------------------------------------- /lib/indexer.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * indexer.js - indexer for hnscan 3 | * Copyright (c) 2018-2019, Handshake Alliance Developers (MIT License). 4 | * https://github.com/handshakealliance/hnscan-backend 5 | */ 6 | 7 | "use strict"; 8 | 9 | const EventEmitter = require("events"); 10 | const { Network } = require("hsd"); 11 | const Logger = require("blgr"); 12 | const assert = require("bsert"); 13 | const { Lock } = require("bmutex"); 14 | const layout = require("./layout.js"); 15 | const util = require("./util.js"); 16 | const { ChartData, ChainState } = require("./types"); 17 | 18 | /** 19 | * Indexer 20 | * @alias module:hnscan.indexer 21 | * @extends EventEmitter 22 | */ 23 | 24 | class Indexer extends EventEmitter { 25 | /** 26 | * Create an indexer. 27 | * @constructor 28 | * @param {Object} options 29 | */ 30 | 31 | constructor(options) { 32 | super(); 33 | 34 | this.options = new IndexerOptions(options); 35 | 36 | this.network = this.options.network; 37 | this.logger = this.options.logger.context("hnscan"); 38 | this.client = this.options.client; 39 | this.hnscan = this.options.hnscan; 40 | this.chain = this.options.chain; 41 | //@todo see if necessary 42 | // this.client = this.options.client || new NullClient(this); 43 | this.hdb = this.options.hdb; 44 | this.height = 0; 45 | this.lock = new Lock(); 46 | 47 | this.init(); 48 | } 49 | 50 | /** 51 | * Initialize the indexer. 52 | * @private 53 | */ 54 | 55 | init() { 56 | this._bind(); 57 | } 58 | 59 | /** 60 | * Bind to node events. 61 | * @private 62 | */ 63 | 64 | _bind() { 65 | this.client.on("error", e => { 66 | this.emit("error", e); 67 | }); 68 | 69 | this.client.on("connect", async () => { 70 | try { 71 | await this.syncNode(); 72 | } catch (e) { 73 | this.emit("error", e); 74 | } 75 | }); 76 | 77 | this.client.bind("block connect", async (entry, block, view) => { 78 | try { 79 | await this.indexBlock(entry, block, view); 80 | } catch (e) { 81 | this.emit("error", e); 82 | } 83 | }); 84 | 85 | this.client.bind("block disconnect", async (entry, block, view) => { 86 | try { 87 | //@todo 88 | await this.unindexBlock(entry, block, view); 89 | } catch (e) { 90 | this.emit("error", e); 91 | } 92 | }); 93 | 94 | this.client.bind("chain reset", async tip => { 95 | try { 96 | await this.rollback(tip.height); 97 | } catch (e) { 98 | this.emit("error", e); 99 | } 100 | }); 101 | } 102 | 103 | /** 104 | * Open the indexer. 105 | * @returns {Promise} 106 | */ 107 | async open() { 108 | await this.hdb.verifyNetwork(); 109 | 110 | //Get tip of chain when starting 111 | let tip = await this.client.getTip(); 112 | 113 | //Height of internal database. 114 | this.height = await this.hdb.getHeight(); 115 | 116 | this.logger.info( 117 | "Hnscan initialized at height: %d, and chain tip: %d", 118 | this.height, 119 | tip.height 120 | ); 121 | 122 | //Connect to the daemon. 123 | await this.connect(); 124 | } 125 | 126 | /** 127 | * Placeholder 128 | * @returns {Promise} 129 | */ 130 | async close() { 131 | await this.disconnect(); 132 | return; 133 | } 134 | 135 | /** 136 | * Connect to the node server (client required). 137 | * @returns {Promise} 138 | */ 139 | 140 | async connect() { 141 | return this.client.open(); 142 | } 143 | 144 | /** 145 | * Disconnect from chain server (client required). 146 | * @returns {Promise} 147 | */ 148 | 149 | async disconnect() { 150 | return this.client.close(); 151 | } 152 | 153 | /** 154 | * Sync state with server on every connect. 155 | * @returns {Promise} 156 | */ 157 | 158 | async syncNode() { 159 | const unlock = await this.lock.lock(); 160 | let start = process.hrtime(); 161 | try { 162 | this.logger.info("Resyncing from server..."); 163 | await this.syncChain(); 164 | } finally { 165 | // Add time here 166 | let end = process.hrtime(start); 167 | this.logger.info("Hnscan fully synced in %d seconds", end[0]); 168 | await this.hdb.saveState(); 169 | await this.hnscan.memorize(this.height); 170 | unlock(); 171 | } 172 | } 173 | 174 | /** 175 | * Connect and sync with the chain server. 176 | * @private 177 | * @returns {Promise} 178 | */ 179 | 180 | async syncChain() { 181 | return this.scan(); 182 | } 183 | 184 | /** 185 | * Rescan blockchain from a given height. 186 | * @private 187 | * @param {Number?} height 188 | * @returns {Promise} 189 | */ 190 | 191 | async scan(height) { 192 | if (height == null) height = this.height; 193 | 194 | assert(height >>> 0 === height, "Hnscan: Must pass in a height."); 195 | 196 | const tip = await this.client.getTip(); 197 | 198 | if (tip.height < height) { 199 | height = tip.height; 200 | } 201 | 202 | await this.rollback(height); 203 | 204 | this.logger.info("Hnscan is scanning %d blocks.", tip.height - height + 1); 205 | 206 | for (let i = height; i <= tip.height; i++) { 207 | const entry = await this.client.getEntry(i); 208 | assert(entry); 209 | 210 | const block = await this.client.getBlock(entry.hash); 211 | assert(block); 212 | 213 | const view = await this.client.getBlockView(block); 214 | assert(view); 215 | 216 | await this._indexBlock(entry, block, view); 217 | } 218 | } 219 | 220 | /** 221 | * Sync with chain height. 222 | * @param {Number} height 223 | * @returns {Promise} 224 | */ 225 | //@todo Untested. 226 | async rollback(height) { 227 | const tip = this.client.getTip(); 228 | 229 | if (height > tip.height) 230 | throw new Error("Hnscan: Cannot rollback to the future."); 231 | 232 | if (height === tip.height) { 233 | this.logger.info("Rolled back to same height (%d).", height); 234 | return; 235 | } 236 | 237 | this.logger.info( 238 | "Rolling back %d HnscanDB blocks to height %d.", 239 | this.height - height, 240 | height 241 | ); 242 | 243 | const entry = await this.client.getEntry(height); 244 | 245 | assert(entry); 246 | 247 | //@todo 248 | // await this.revert(entry.height); 249 | await this.setHeight(entry.height); 250 | } 251 | 252 | // /** 253 | // * Revert a block. 254 | // * @param {Number} height 255 | // * @returns {Promise} 256 | // */ 257 | 258 | //TODO implement this function 259 | // async revert(height) { 260 | // const block = await this.getBlock(height); 261 | 262 | // if (!block) 263 | // return 0; 264 | 265 | // const hashes = block.toArray(); 266 | 267 | // for (let i = hashes.length - 1; i >= 0; i--) { 268 | // const hash = hashes[i]; 269 | // await this.unconfirm(hash); 270 | // } 271 | 272 | // return hashes.length; 273 | // } 274 | 275 | /** 276 | * Set internal indexer height. 277 | * @param {Number} height 278 | * @returns {Promise} 279 | */ 280 | async setHeight(height) { 281 | this.height = height; 282 | 283 | //Insert into DB. 284 | await this.hdb.setHeight(height); 285 | 286 | return; 287 | } 288 | 289 | /** 290 | * Index a block with a lock 291 | * @param (ChainEntry) entry 292 | * @param (Block) block 293 | * @param (CoinView) view 294 | * @returns {Promise} 295 | */ 296 | 297 | async indexBlock(entry, block, view) { 298 | const unlock = await this.lock.lock(); 299 | try { 300 | this.logger.info("Adding block: %d.", entry.height); 301 | return await this._indexBlock(entry, block, view); 302 | } finally { 303 | await this.hdb.saveState(); 304 | await this.hnscan.memorize(this.height); 305 | unlock(); 306 | } 307 | } 308 | 309 | /** 310 | * Index a block 311 | * @param (ChainEntry) entry 312 | * @param (Block) block 313 | * @param (CoinView) view 314 | * @returns {Promise} 315 | */ 316 | 317 | async _indexBlock(entry, block, view) { 318 | if (entry.height < this.height) { 319 | this.logger.warning( 320 | "Hnscan is connecting low blocks (%d).", 321 | entry.height 322 | ); 323 | return; 324 | } 325 | 326 | // if (entry.height >= this.network.block.slowHeight) 327 | // this.logger.debug("Adding block: %d.", entry.height); 328 | 329 | //TODO review this code from wallet. 330 | ////We may want to adjust this. 331 | ////Right now it's running on every height, but I'm wondering if we want height to be +1 332 | //if (block.height === this.height) { 333 | // // We let blocks of the same height 334 | // // through specifically for rescans: 335 | // // we always want to rescan the last 336 | // // block since the state may have 337 | // // updated before the block was fully 338 | // // processed (in the case of a crash). 339 | // this.logger.warning("Already saw Hnscan block (%d).", block.height); 340 | //} else if (block.height !== this.height + 1) { 341 | // await this.scan(this.height); 342 | // return 0; 343 | //} 344 | 345 | //TODO implement, and check if necessary 346 | // if (this.options.checkpoints && !this.state.marked) { 347 | // if (block.height <= this.network.lastCheckpoint) return 0; 348 | // } 349 | // 350 | 351 | //if (this.standalone) { 352 | // //if we are standalone we want to save the block headers 353 | //} 354 | 355 | await this.indexTX(entry, block, view); 356 | 357 | await this.hdb.saveEntry(entry, block, view); 358 | let state = await this.hdb.state; 359 | 360 | let chartData = ChartData.fromBlockData(entry, block, state); 361 | 362 | //Index the chart data 363 | await this.hdb.setChartData(chartData); 364 | 365 | // Sync the new tip. 366 | await this.setHeight(entry.height); 367 | } 368 | 369 | /** 370 | * Index a transaction by txid. 371 | * @private 372 | * @param (ChainEntry) entry 373 | * @param (Block) block 374 | * @param (CoinView) view 375 | */ 376 | async indexTX(entry, block, view) { 377 | const b = this.hdb.batch(); 378 | 379 | for (let tx of block.txs) { 380 | let txid = Buffer.from(tx.txid(), "hex"); 381 | 382 | for (let input of tx.inputs) { 383 | if (input.isCoinbase()) { 384 | continue; 385 | } 386 | 387 | let previousHashPrefix = Buffer.from(input.prevout.txid(), "hex").slice( 388 | 0, 389 | 8 390 | ); 391 | let previousIndex = input.prevout.index; 392 | 393 | b.put(layout.i.encode(previousHashPrefix, previousIndex), txid); 394 | } 395 | 396 | //TODO see if parallizing the address indexing, and the name indexing will speed things up. 397 | for (let output of tx.outputs) { 398 | let address = Buffer.from(output.address.getHash(), "hex"); 399 | 400 | if (output.covenant.isName()) { 401 | b.put( 402 | layout.n.encode(output.covenant.getHash(0), txid), 403 | util.fromU32(entry.height) 404 | ); 405 | } 406 | 407 | b.put(layout.o.encode(address, txid), util.fromU32(entry.height)); 408 | } 409 | 410 | b.put(layout.t.encode(txid), util.fromU32(entry.height)); 411 | } 412 | 413 | await b.write(); 414 | 415 | return; 416 | } 417 | } 418 | 419 | class IndexerOptions { 420 | /** 421 | * Create indexer options. 422 | * @constructor 423 | * @param {Object} options 424 | */ 425 | 426 | constructor(options) { 427 | //TODO review these to see if they are all needed. 428 | this.network = Network.primary; 429 | this.logger = Logger.global; 430 | this.client = null; 431 | this.chain = null; 432 | this.prefix = null; 433 | this.location = null; 434 | this.memory = true; 435 | this.maxFiles = 64; 436 | this.cacheSize = 16 << 20; 437 | this.compression = true; 438 | 439 | if (options) this._fromOptions(options); 440 | } 441 | 442 | _fromOptions(options) { 443 | if (options.network != null) this.network = Network.get(options.network); 444 | 445 | if (options.logger != null) { 446 | assert(typeof options.logger === "object"); 447 | this.logger = options.logger; 448 | } 449 | 450 | if (options.client != null) { 451 | assert(typeof options.client === "object"); 452 | this.client = options.client; 453 | } 454 | 455 | // if (options.chain != null) { 456 | // assert(typeof options.chain === "object"); 457 | // this.client = new ChainClient(options.chain); 458 | // } 459 | 460 | assert(this.client); 461 | 462 | if (options.hdb != null) { 463 | assert(typeof options.hdb === "object"); 464 | this.hdb = options.hdb; 465 | } 466 | 467 | if (options.chain != null) { 468 | assert(typeof options.chain === "object"); 469 | this.chain = options.chain; 470 | } 471 | 472 | assert(this.hdb); 473 | 474 | if (options.hnscan != null) { 475 | assert(typeof options.hnscan === "object"); 476 | this.hnscan = options.hnscan; 477 | } 478 | 479 | if (options.prefix != null) { 480 | assert(typeof options.prefix === "string"); 481 | this.prefix = options.prefix; 482 | this.location = path.join(this.prefix, "hnscan"); 483 | } 484 | 485 | if (options.location != null) { 486 | assert(typeof options.location === "string"); 487 | this.location = options.location; 488 | } 489 | 490 | if (options.memory != null) { 491 | assert(typeof options.memory === "boolean"); 492 | this.memory = options.memory; 493 | } 494 | 495 | if (options.maxFiles != null) { 496 | assert(options.maxFiles >>> 0 === options.maxFiles); 497 | this.maxFiles = options.maxFiles; 498 | } 499 | 500 | if (options.cacheSize != null) { 501 | assert(Number.isSafeInteger(options.cacheSize) && options.cacheSize >= 0); 502 | this.cacheSize = options.cacheSize; 503 | } 504 | 505 | if (options.compression != null) { 506 | assert(typeof options.compression === "boolean"); 507 | this.compression = options.compression; 508 | } 509 | 510 | return this; 511 | } 512 | 513 | /** 514 | * Instantiate chain options from object. 515 | * @param {Object} options 516 | * @returns {IndexerOptions} 517 | */ 518 | 519 | static fromOptions(options) { 520 | return new this()._fromOptions(options); 521 | } 522 | } 523 | 524 | module.exports = Indexer; 525 | -------------------------------------------------------------------------------- /lib/layout.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * layout.js - key layouts for leveldb database. 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * Copyright (c) 2018-2019, Handshake Alliance Developers (MIT License). 5 | * https://github.com/handshakealliance/ 6 | */ 7 | 8 | "use strict"; 9 | 10 | const bdb = require("bdb"); 11 | 12 | //@todo remove the key for saving headers 13 | //TODO finish documenting this. 14 | /* 15 | * V -> db version 16 | * O -> flags 17 | * H -> Last Sync Height 18 | * 19 | * Transactions Output's Index 20 | * o[SHA256(hash)(:8)] -> [txid(:8)] 21 | * Code: o, Address Hash Prefix: hash(:8) -> Funding TxID Prefix: txid[:8] 22 | * 23 | * Transactions Input Index 24 | * i[txid(:8)][uint16][txid(:8)] -> Transaction inputs row. 25 | * Code: i, Funding TxID Prefix: txid(8), Funding Output Index: uint, Spending TxID Prefix: txid(8) 26 | * 27 | * Full Transaction IDs 28 | * t[txid][uint32] 29 | * 30 | * NameHash Transaction Index 31 | * 32 | * 33 | * Block Difficulty Historical Data 34 | * d[block_timestamp] -> hash rate 35 | * 36 | */ 37 | 38 | const layout = { 39 | V: bdb.key("V"), 40 | O: bdb.key("O"), 41 | H: bdb.key("H"), 42 | h: bdb.key("h", ["uint32"]), 43 | o: bdb.key("o", ["hash", "hash"]), 44 | i: bdb.key("i", ["hash", "uint32"]), 45 | t: bdb.key("t", ["hash"]), 46 | n: bdb.key("n", ["hash", "hash"]), 47 | d: bdb.key("d", ["uint32"]), 48 | s: bdb.key("s") 49 | }; 50 | 51 | module.exports = layout; 52 | -------------------------------------------------------------------------------- /lib/plugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * plugin.js - hnscan plugin for hsd 3 | * Copyright (c) 2017-2019, Christopher Jeffrey (MIT License). 4 | * Copyright (c) 2018-2019, Handshake Alliance Developers (MIT License). 5 | * https://github.com/handshakealliance/hnscan-backend 6 | */ 7 | 8 | "use strict"; 9 | 10 | const EventEmitter = require("events"); 11 | const ChainClient = require("./chainclient"); 12 | const HnscanDB = require("./hnscandb.js"); 13 | const Indexer = require("./indexer.js"); 14 | const HTTP = require("./http"); 15 | const { Network } = require("hsd"); 16 | const Hnscan = require("./hnscan.js"); 17 | 18 | /** 19 | * @exports hnscan/plugin 20 | */ 21 | 22 | const plugin = exports; 23 | 24 | /** 25 | * Plugin 26 | * @extends EventEmitter 27 | */ 28 | 29 | class Plugin extends EventEmitter { 30 | constructor(node) { 31 | super(); 32 | 33 | this.config = node.config.filter("hnscan"); 34 | this.config.open("hnscan.conf"); 35 | 36 | this.network = Network.get(node.network.type); 37 | this.logger = node.logger; 38 | this.prefix = node.config.prefix; 39 | 40 | this.client = new ChainClient(node.chain); 41 | 42 | this.httpEnabled = this.config.bool("http-enabled", true); 43 | 44 | console.log("connecting to: %s", node.network); 45 | 46 | //Init DB here 47 | this.hdb = new HnscanDB({ 48 | network: this.network, 49 | logger: this.logger, 50 | client: this.client, 51 | memory: this.config.bool("memory", node.memory), 52 | prefix: this.prefix, 53 | maxFiles: this.config.uint("max-files"), 54 | cacheSize: this.config.mb("cache-size") 55 | }); 56 | 57 | this.hnscan = new Hnscan({ 58 | network: this.network, 59 | logger: this.logger, 60 | hdb: this.hdb, 61 | chain: node.chain, 62 | //@todo I don't think we ever need client. 63 | client: this.client, 64 | node: node 65 | }); 66 | 67 | this.indexer = new Indexer({ 68 | network: this.network, 69 | logger: this.logger, 70 | client: this.client, 71 | chain: node.chain, 72 | hdb: this.hdb, 73 | hnscan: this.hnscan 74 | }); 75 | 76 | this.http = new HTTP({ 77 | network: this.network, 78 | logger: this.logger, 79 | hdb: this.hdb, 80 | client: this.client, 81 | node: node, 82 | hnscan: this.hnscan, 83 | // prefix: this.prefix, 84 | ssl: this.config.bool("ssl"), 85 | keyFile: this.config.path("ssl-key"), 86 | certFile: this.config.path("ssl-cert"), 87 | host: this.config.str("http-host"), 88 | port: this.config.uint("http-port"), 89 | apiKey: this.config.str("api-key", node.config.str("api-key")), 90 | noAuth: this.config.bool("no-auth"), 91 | cors: this.config.bool("cors", true) 92 | }); 93 | 94 | this.init(); 95 | } 96 | 97 | init() { 98 | this.hdb.on("error", err => this.emit("error", err)); 99 | this.indexer.on("error", err => this.emit("error", err)); 100 | this.http.on("error", err => this.emit("error", err)); 101 | } 102 | 103 | //Going to open the http server here and the database 104 | async open() { 105 | await this.hdb.open(); 106 | 107 | await this.indexer.open(); 108 | 109 | await this.http.open(); 110 | } 111 | 112 | //Close the db and the http server. 113 | async close() {} 114 | } 115 | 116 | /** 117 | * Plugin name. 118 | * @const {String} 119 | */ 120 | 121 | plugin.id = "hnscan"; 122 | 123 | /** 124 | * Plugin initialization. 125 | * @param {Node} node 126 | * @returns {Hnscan} 127 | */ 128 | 129 | plugin.init = function init(node) { 130 | return new Plugin(node); 131 | }; 132 | -------------------------------------------------------------------------------- /lib/types.js: -------------------------------------------------------------------------------- 1 | const bio = require("bufio"); 2 | const util = require("./util"); 3 | const consensus = require("hsd/lib/protocol/consensus"); 4 | const assert = require("bsert"); 5 | 6 | //Create a constructor that sets all of the data, and then we can call that in the indexer. 7 | // 8 | // 9 | //Chart data currently available. 10 | //1. Average Daily difficulty. 11 | //2. Transactions per Day. 12 | //3. Total supply per day. 13 | //4. Total burned 14 | class ChartData extends bio.Struct { 15 | //move this to from blockData, and then make the contrustor empty 16 | constructor(entry, block, chainState) { 17 | super(); 18 | //Time of data. 19 | this.time = 0; 20 | //Double 21 | this.difficulty = 0; 22 | this.transactions = 0; 23 | this.supply = 0; 24 | this.burned = 0; 25 | this.totalTx = 0; 26 | } 27 | 28 | fromBlockData(entry, block, chainState) { 29 | this.time = entry.time || 0; 30 | //Double 31 | this.difficulty = util.toDifficulty(entry.bits) || 0; 32 | this.transactions = block.txs.length || 0; 33 | this.supply = chainState.value || 0; 34 | this.burned = chainState.burned || 0; 35 | this.totalTx = chainState.tx || 0; 36 | return this; 37 | } 38 | 39 | write(bw) { 40 | bw.writeDouble(this.difficulty); 41 | bw.writeU32(this.transactions); 42 | bw.writeU64(this.supply); 43 | bw.writeU64(this.burned); 44 | bw.writeU64(this.totalTx); 45 | return bw; 46 | } 47 | 48 | read(br) { 49 | this.time = 0; 50 | this.difficulty = br.readDouble(); 51 | this.transactions = br.readU32(); 52 | this.supply = br.readU64(); 53 | this.burned = br.readU64(); 54 | this.totalTx = br.readU64(); 55 | return this; 56 | } 57 | 58 | fromObject(data) { 59 | this.time = data.time || 0; 60 | this.difficulty = data.difficulty || 0; 61 | this.transactions = data.transactions || 0; 62 | this.supply = data.supply || 0; 63 | this.burned = data.burned || 0; 64 | this.totalTx = data.totalTx || 0; 65 | return this; 66 | } 67 | 68 | //creates average chart data from an array of chart data elements. 69 | static fromArray(data) { 70 | let sums = data.reduce(function(a, b) { 71 | return { 72 | difficulty: a.difficulty + b.difficulty, 73 | transactions: a.transactions + b.transactions, 74 | supply: Math.max(a.supply, b.supply), 75 | burned: Math.max(a.burned, b.burned), 76 | totalTx: Math.max(a.totalTx, b.totalTx) 77 | }; 78 | }); 79 | 80 | //We want average difficulty. 81 | sums.difficulty /= data.length; 82 | //make the time the UTC midnight of that day. 83 | sums.time = Math.floor(data[0].time / (3600 * 24)); 84 | 85 | //@todo return new this().fromObject(sums); 86 | // return sums; 87 | return new this().fromObject(sums); 88 | } 89 | 90 | static fromBlockData(entry, block, state) { 91 | return new this().fromBlockData(entry, block, state); 92 | } 93 | } 94 | 95 | /** 96 | * Chain State 97 | */ 98 | 99 | class ChainState extends bio.Struct { 100 | /** 101 | * Create chain state. 102 | * @alias module:blockchain.ChainState 103 | * @constructor 104 | */ 105 | 106 | constructor() { 107 | super(); 108 | this.tip = consensus.ZERO_HASH; 109 | this.tx = 0; 110 | this.coin = 0; 111 | this.value = 0; 112 | this.burned = 0; 113 | this.committed = false; 114 | } 115 | 116 | inject(state) { 117 | this.tip = state.tip; 118 | this.tx = state.tx; 119 | this.coin = state.coin; 120 | this.value = state.value; 121 | this.burned = state.burned; 122 | return this; 123 | } 124 | 125 | connect(block) { 126 | this.tx += block.txs.length; 127 | } 128 | 129 | disconnect(block) { 130 | this.tx -= block.txs.length; 131 | } 132 | 133 | add(coin) { 134 | this.coin += 1; 135 | this.value += coin.value; 136 | } 137 | 138 | spend(coin) { 139 | this.coin -= 1; 140 | this.value -= coin.value; 141 | } 142 | 143 | burn(coin) { 144 | this.coin += 1; 145 | this.burned += coin.value; 146 | } 147 | 148 | unburn(coin) { 149 | this.coin -= 1; 150 | this.burned -= coin.value; 151 | } 152 | 153 | commit(hash) { 154 | assert(Buffer.isBuffer(hash)); 155 | this.tip = hash; 156 | this.committed = true; 157 | return this.encode(); 158 | } 159 | 160 | getSize() { 161 | return 64; 162 | } 163 | 164 | write(bw) { 165 | bw.writeHash(this.tip); 166 | bw.writeU64(this.tx); 167 | bw.writeU64(this.coin); 168 | bw.writeU64(this.value); 169 | bw.writeU64(this.burned); 170 | return bw; 171 | } 172 | 173 | read(br) { 174 | this.tip = br.readHash(); 175 | this.tx = br.readU64(); 176 | this.coin = br.readU64(); 177 | this.value = br.readU64(); 178 | this.burned = br.readU64(); 179 | return this; 180 | } 181 | } 182 | 183 | module.exports.ChartData = ChartData; 184 | module.exports.ChainState = ChainState; 185 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * util.js - utils for hnscan 3 | * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License). 4 | * Copyright (c) 2018-2019, Handshake Alliance Developers (MIT License). 5 | * https://github.com/handshakealliance/hnscan-backend 6 | */ 7 | 8 | "use strict"; 9 | 10 | /** 11 | * @exports util 12 | */ 13 | 14 | const util = exports; 15 | 16 | util.now = function now() { 17 | return Math.floor(Date.now() / 1000); 18 | }; 19 | 20 | util.fromU32 = function fromU32(num) { 21 | const data = Buffer.allocUnsafe(4); 22 | data.writeUInt32LE(num, 0, true); 23 | return data; 24 | }; 25 | 26 | util.toU32 = function toU32(buf) { 27 | const num = buf.readUInt32LE(0, true); 28 | return num; 29 | }; 30 | 31 | /** 32 | * Sorts transactions in ascending order. 33 | */ 34 | util.sortTXs = function sortTXs(txs) { 35 | //Not sure how we can do this exactly to ensure that things are sorted especially if they 36 | // are the same block - XXX 37 | // For now will sort just in block order. 38 | //Also let's pass in some parameters here as right now 39 | // We are going to default to descending. 40 | 41 | txs.sort(function(a, b) { 42 | return b.height - a.height; 43 | }); 44 | 45 | return txs; 46 | }; 47 | 48 | util.toDifficulty = function toDifficulty(bits) { 49 | let shift = (bits >>> 24) & 0xff; 50 | let diff = 0x0000ffff / (bits & 0x00ffffff); 51 | 52 | while (shift < 29) { 53 | diff *= 256.0; 54 | shift++; 55 | } 56 | 57 | while (shift > 29) { 58 | diff /= 256.0; 59 | shift--; 60 | } 61 | 62 | return diff; 63 | }; 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hnscan", 3 | "version": "0.0.5", 4 | "description": "A hnscan backend instance designed to be run as a HSD plugin", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Handshake Alliance Contributors", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "eslint": "^5.9.0", 13 | "eslint-config-prettier": "^3.3.0", 14 | "eslint-plugin-prettier": "^3.0.0", 15 | "prettier": "^1.15.2" 16 | }, 17 | "dependencies": { 18 | "bcrypto": "^3.0.1", 19 | "bdb": "^1.1.2", 20 | "bevent": "^0.1.2", 21 | "blgr": "^0.1.2", 22 | "bmutex": "^0.1.5", 23 | "bsert": "0.0.5", 24 | "bstring": "^0.3.4", 25 | "bufio": "^1.0.3", 26 | "bval": "^0.1.4", 27 | "bweb": "^0.1.4", 28 | "geoip-lite": "^1.3.8", 29 | "hsd": "github:handshake-org/hsd", 30 | "path": "^0.12.7" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@babel/code-frame@^7.0.0": 6 | version "7.5.5" 7 | resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" 8 | integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw== 9 | dependencies: 10 | "@babel/highlight" "^7.0.0" 11 | 12 | "@babel/highlight@^7.0.0": 13 | version "7.5.0" 14 | resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540" 15 | integrity sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ== 16 | dependencies: 17 | chalk "^2.0.0" 18 | esutils "^2.0.2" 19 | js-tokens "^4.0.0" 20 | 21 | acorn-jsx@^5.0.0: 22 | version "5.1.0" 23 | resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.1.0.tgz#294adb71b57398b0680015f0a38c563ee1db5384" 24 | integrity sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw== 25 | 26 | acorn@^6.0.7: 27 | version "6.3.0" 28 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.3.0.tgz#0087509119ffa4fc0a0041d1e93a417e68cb856e" 29 | integrity sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA== 30 | 31 | ajv@^6.10.2, ajv@^6.9.1: 32 | version "6.10.2" 33 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" 34 | integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw== 35 | dependencies: 36 | fast-deep-equal "^2.0.1" 37 | fast-json-stable-stringify "^2.0.0" 38 | json-schema-traverse "^0.4.1" 39 | uri-js "^4.2.2" 40 | 41 | ansi-escapes@^3.2.0: 42 | version "3.2.0" 43 | resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" 44 | integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== 45 | 46 | ansi-regex@^3.0.0: 47 | version "3.0.0" 48 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" 49 | integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= 50 | 51 | ansi-regex@^4.1.0: 52 | version "4.1.0" 53 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" 54 | integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== 55 | 56 | ansi-styles@^3.2.0, ansi-styles@^3.2.1: 57 | version "3.2.1" 58 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 59 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 60 | dependencies: 61 | color-convert "^1.9.0" 62 | 63 | argparse@^1.0.7: 64 | version "1.0.10" 65 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" 66 | integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== 67 | dependencies: 68 | sprintf-js "~1.0.2" 69 | 70 | astral-regex@^1.0.0: 71 | version "1.0.0" 72 | resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" 73 | integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== 74 | 75 | async@^2.1.1: 76 | version "2.6.3" 77 | resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" 78 | integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== 79 | dependencies: 80 | lodash "^4.17.14" 81 | 82 | balanced-match@^1.0.0: 83 | version "1.0.0" 84 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 85 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 86 | 87 | bcfg@~0.1.6: 88 | version "0.1.6" 89 | resolved "https://registry.yarnpkg.com/bcfg/-/bcfg-0.1.6.tgz#f77a6323bddef14f3886222e7ef8ccc0bc2143ec" 90 | integrity sha512-BR2vwQZwu24aRm588XHOnPVjjQtbK8sF0RopRFgMuke63/REJMWnePTa2YHKDBefuBYiVdgkowuB1/e4K7Ue3g== 91 | dependencies: 92 | bsert "~0.0.10" 93 | 94 | bcrypto@^3.0.1: 95 | version "3.1.11" 96 | resolved "https://registry.yarnpkg.com/bcrypto/-/bcrypto-3.1.11.tgz#c76a4fda965d83e968455fd1bcfdadaff337f291" 97 | integrity sha512-XHsle+v0aYjCyHZSwd3Insnu8GUe4cuf3+f07Z/mO+GLq60YqUyEIvpaSv4LAAJ4uf8UNYMSSQ0LslsV0r4tug== 98 | dependencies: 99 | bsert "~0.0.10" 100 | bufio "~1.0.6" 101 | loady "~0.0.1" 102 | nan "^2.13.2" 103 | 104 | bcrypto@~4.2.3, bcrypto@~4.2.8: 105 | version "4.2.9" 106 | resolved "https://registry.yarnpkg.com/bcrypto/-/bcrypto-4.2.9.tgz#d57794196c1c230d20bb12319387f733452a6a53" 107 | integrity sha512-upd5xGkB3REZwOOM2wGgUrc29HFQcm+mbXYLN8fKI2GcnWiCXmDU7f0wzr/ye2o1t0smhX3ddSzAqXIYEXehIw== 108 | dependencies: 109 | bsert "~0.0.10" 110 | bufio "~1.0.6" 111 | loady "~0.0.1" 112 | nan "^2.13.2" 113 | 114 | bcurl@~0.1.6: 115 | version "0.1.6" 116 | resolved "https://registry.yarnpkg.com/bcurl/-/bcurl-0.1.6.tgz#97df6a12a119e30f4948a73ff9173410f73cd308" 117 | integrity sha512-noeDhfqFiUcNOUuZKErkXcbZfxBjn6duTfYPEfA4hAYXGr7gwVxkAWvIerxl3ZLLyn8jzh7Oi0nHlrLm1ST9Fw== 118 | dependencies: 119 | brq "~0.1.7" 120 | bsert "~0.0.10" 121 | bsock "~0.1.8" 122 | 123 | bdb@^1.1.2, bdb@~1.1.7: 124 | version "1.1.7" 125 | resolved "https://registry.yarnpkg.com/bdb/-/bdb-1.1.7.tgz#c02f4e1514f4c349e3ca8a389a2dd290ef749094" 126 | integrity sha512-jtBnWEyDDK08dBbKL9LJeO2huZyBmbjBQMMVjU9RI1liJo6YDbv86uDHoaD4gniefd9pfxqOM1w6rYuwLVCXlQ== 127 | dependencies: 128 | bsert "~0.0.10" 129 | loady "~0.0.1" 130 | 131 | bdns@~0.1.5: 132 | version "0.1.5" 133 | resolved "https://registry.yarnpkg.com/bdns/-/bdns-0.1.5.tgz#2c1dc10acd987048e0a5d12c7cbd29db00f45f1f" 134 | integrity sha512-LNVkfM7ynlAD0CvPvO9cKxW8YXt1KOCRQZlRsGZWeMyymUWVdHQpZudAzH9chaFAz6HiwAnQxwDemCKDPy6Mag== 135 | dependencies: 136 | bsert "~0.0.10" 137 | 138 | bevent@^0.1.2, bevent@~0.1.5: 139 | version "0.1.5" 140 | resolved "https://registry.yarnpkg.com/bevent/-/bevent-0.1.5.tgz#c43a8d48a7037a97209c92c5fcf5a11f35a746b1" 141 | integrity sha512-hs6T3BjndibrAmPSoKTHmKa3tz/c6Qgjv9iZw+tAoxuP6izfTCkzfltBQrW7SuK5xnY22gv9jCEf51+mRH+Qvg== 142 | dependencies: 143 | bsert "~0.0.10" 144 | 145 | bfile@~0.2.1: 146 | version "0.2.1" 147 | resolved "https://registry.yarnpkg.com/bfile/-/bfile-0.2.1.tgz#2d19ac27f355fef861d5d1a4cd0e4a7f27bb4151" 148 | integrity sha512-du2QjKNkqZ1YJweWEQeq3CEqd+nQD/WOnmAMfs52ok5ujBBagWYLZC5ORDuqfV2fuF88of44PZdsnAVfxoH31g== 149 | 150 | bfilter@~1.0.5: 151 | version "1.0.5" 152 | resolved "https://registry.yarnpkg.com/bfilter/-/bfilter-1.0.5.tgz#c1592e427f9c1f8be11b13bddd5f74fc2da56049" 153 | integrity sha512-GupIidtCvLbKhXnA1sxvrwa+gh95qbjafy7P1U1x/2DHxNabXq4nGW0x3rmgzlJMYlVl+c8fMxoMRIwpKYlgcQ== 154 | dependencies: 155 | bsert "~0.0.10" 156 | bufio "~1.0.6" 157 | mrmr "~0.1.6" 158 | 159 | bheep@~0.1.5: 160 | version "0.1.5" 161 | resolved "https://registry.yarnpkg.com/bheep/-/bheep-0.1.5.tgz#ed6a3da7857c8ebe71d71de9571ddd8f42340889" 162 | integrity sha512-0KR5Zi8hgJBKL35+aYzndCTtgSGakOMxrYw2uszd5UmXTIfx3+drPGoETlVbQ6arTdAzSoQYA1j35vbaWpQXBg== 163 | dependencies: 164 | bsert "~0.0.10" 165 | 166 | binet@~0.3.5: 167 | version "0.3.5" 168 | resolved "https://registry.yarnpkg.com/binet/-/binet-0.3.5.tgz#bf4f2a1859d08bb5de55fce9faa34274b18cce0b" 169 | integrity sha512-suogkqXrt63Z76ABvwJjI28w+LWrRE53nwItDPz1VwNjHEuh1+rxudGYtoewFmMhkJ9pW/VGGnjoxP+AGzHgLQ== 170 | dependencies: 171 | bs32 "~0.1.5" 172 | bsert "~0.0.10" 173 | 174 | blgr@^0.1.2, blgr@~0.1.7: 175 | version "0.1.7" 176 | resolved "https://registry.yarnpkg.com/blgr/-/blgr-0.1.7.tgz#69ed054282e5d3be7b6b19b03962a397227ab498" 177 | integrity sha512-+jSaU2jnYqF+ec9e8nzjfjU7QhZIntkDNG8/AEwoxW7J3mmwvmUfAI5lm9BFYs1OzT+1UgIZTFHa2qWjTBURfw== 178 | dependencies: 179 | bsert "~0.0.10" 180 | 181 | blru@~0.1.6: 182 | version "0.1.6" 183 | resolved "https://registry.yarnpkg.com/blru/-/blru-0.1.6.tgz#fb091f76e269366513143709659069c952a5bde6" 184 | integrity sha512-34+xZ2u4ys/aUzWCU9m6Eee4nVuN1ywdxbi8b3Z2WULU6qvnfeHvCWEdGzlVfRbbhimG2xxJX6R77GD2cuVO6w== 185 | dependencies: 186 | bsert "~0.0.10" 187 | 188 | blst@~0.1.5: 189 | version "0.1.5" 190 | resolved "https://registry.yarnpkg.com/blst/-/blst-0.1.5.tgz#257232ca9c267e7c16ee14a692e526a1948d02b7" 191 | integrity sha512-TPl04Cx3CHdPFAJ2x9Xx1Z1FOfpAzmNPfHkfo+pGAaNH4uLhS58ExvamVkZh3jadF+B7V5sMtqvrqdf9mHINYA== 192 | dependencies: 193 | bsert "~0.0.10" 194 | 195 | bmutex@^0.1.5, bmutex@~0.1.6: 196 | version "0.1.6" 197 | resolved "https://registry.yarnpkg.com/bmutex/-/bmutex-0.1.6.tgz#28d721316c95c9deda77b3e50b71ee356215b415" 198 | integrity sha512-nXWOXtQHbfPaMl6jyEF/rmRMrcemj2qn+OCAI/uZYurjfx7Dg3baoXdPzHOL0U8Cfvn8CWxKcnM/rgxL7DR4zw== 199 | dependencies: 200 | bsert "~0.0.10" 201 | 202 | bns@~0.8.1: 203 | version "0.8.1" 204 | resolved "https://registry.yarnpkg.com/bns/-/bns-0.8.1.tgz#aee02187f1515613699aa83be1819a0028c4a06e" 205 | integrity sha512-xAi6R0FkwfKP87iC/S7qxhLOzDSd9sX4qgH+1ppYmyMVOlAiNYZku8UxF+WogMEGlNJJwWvn8sWdIBshED1QLg== 206 | dependencies: 207 | bcrypto "~4.2.3" 208 | bfile "~0.2.1" 209 | bheep "~0.1.5" 210 | binet "~0.3.5" 211 | bs32 "~0.1.6" 212 | bsert "~0.0.10" 213 | btcp "~0.1.5" 214 | budp "~0.1.6" 215 | bufio "~1.0.6" 216 | optionalDependencies: 217 | unbound "~0.3.5" 218 | 219 | brace-expansion@^1.1.7: 220 | version "1.1.11" 221 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 222 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 223 | dependencies: 224 | balanced-match "^1.0.0" 225 | concat-map "0.0.1" 226 | 227 | brq@~0.1.7: 228 | version "0.1.8" 229 | resolved "https://registry.yarnpkg.com/brq/-/brq-0.1.8.tgz#35c68bf48e0aef19e397dcd1a1ba7a0cfb3baca2" 230 | integrity sha512-6SDY1lJMKXgt5TZ6voJQMH2zV1XPWWtm203PSkx3DSg9AYNYuRfOPFSBDkNemabzgpzFW9/neR4YhTvyJml8rQ== 231 | dependencies: 232 | bsert "~0.0.10" 233 | 234 | bs32@~0.1.5, bs32@~0.1.6: 235 | version "0.1.6" 236 | resolved "https://registry.yarnpkg.com/bs32/-/bs32-0.1.6.tgz#2710cb70da3f55447138181fa645e2c54b1ef6d0" 237 | integrity sha512-usjDesQqZ8ihHXOnOEQuAdymBHnJEfSd+aELFSg1jN/V3iAf12HrylHlRJwIt6DTMmXpBDQ+YBg3Q3DIYdhRgQ== 238 | dependencies: 239 | bsert "~0.0.10" 240 | 241 | bsert@0.0.5: 242 | version "0.0.5" 243 | resolved "https://registry.yarnpkg.com/bsert/-/bsert-0.0.5.tgz#0c7792d9414182a8c72c8a46e5f67e3d2f9a3b69" 244 | integrity sha512-prgSa82RHcsdEM7SKYl1S7j0tKeiE3SF8C3B39MyUrpbP66diJK16RIkbFC2pXvQx4xitN0xMgBHKBAgBldX1w== 245 | 246 | bsert@~0.0.10: 247 | version "0.0.10" 248 | resolved "https://registry.yarnpkg.com/bsert/-/bsert-0.0.10.tgz#231ac82873a1418c6ade301ab5cd9ae385895597" 249 | integrity sha512-NHNwlac+WPy4t2LoNh8pXk8uaIGH3NSaIUbTTRXGpE2WEbq0te/tDykYHkFK57YKLPjv/aGHmbqvnGeVWDz57Q== 250 | 251 | bsip@~0.1.9: 252 | version "0.1.9" 253 | resolved "https://registry.yarnpkg.com/bsip/-/bsip-0.1.9.tgz#5fc7667ada17eb5c285b5634acbf5e6dc45eebda" 254 | integrity sha512-i7cVEfCthASPB3BYKS/pZN/D4kh4vToIlSAFcVBLNjzYl1UirT3E2PIGSfNnYR2qZ3UW1qnDavrWEHhLeSKt2A== 255 | dependencies: 256 | bsert "~0.0.10" 257 | loady "~0.0.1" 258 | nan "^2.13.1" 259 | 260 | bsock@~0.1.8, bsock@~0.1.9: 261 | version "0.1.9" 262 | resolved "https://registry.yarnpkg.com/bsock/-/bsock-0.1.9.tgz#6aa14b8e4bda730e0f60ec73eb52a8a888ac22c8" 263 | integrity sha512-/l9Kg/c5o+n/0AqreMxh2jpzDMl1ikl4gUxT7RFNe3A3YRIyZkiREhwcjmqxiymJSRI/Qhew357xGn1SLw/xEw== 264 | dependencies: 265 | bsert "~0.0.10" 266 | 267 | bsocks@~0.2.5: 268 | version "0.2.5" 269 | resolved "https://registry.yarnpkg.com/bsocks/-/bsocks-0.2.5.tgz#b89f76a61bf8414a6a866d2f5edd2ce9996f8209" 270 | integrity sha512-w1yG8JmfKPIaTDLuR9TIxJM2Ma6nAiInRpLNZ43g3qPnPHjawCC4SV6Bdy84bEJQX1zJWYTgdod/BnQlDhq4Gg== 271 | dependencies: 272 | binet "~0.3.5" 273 | bsert "~0.0.10" 274 | 275 | bstring@^0.3.4, bstring@~0.3.9: 276 | version "0.3.9" 277 | resolved "https://registry.yarnpkg.com/bstring/-/bstring-0.3.9.tgz#dc50294b54e9e767c07ca5592795dcc59ef1bf2e" 278 | integrity sha512-D95flI7SXL+UsQi9mW+hH+AK2AFfafIJi+3GbbyTAWMe2FqwR9keBxsjGiGd/JM+77Y9WsC+M4EhZVNVcym9jw== 279 | dependencies: 280 | bsert "~0.0.10" 281 | loady "~0.0.1" 282 | nan "^2.13.1" 283 | 284 | btcp@~0.1.5: 285 | version "0.1.5" 286 | resolved "https://registry.yarnpkg.com/btcp/-/btcp-0.1.5.tgz#f76262415a0f6eaa592cdbb91c8b18386530ac28" 287 | integrity sha512-tkrtMDxeJorn5p0KxaLXELneT8AbfZMpOFeoKYZ5qCCMMSluNuwut7pGccLC5YOJqmuk0DR774vNVQLC9sNq/A== 288 | 289 | budp@~0.1.6: 290 | version "0.1.6" 291 | resolved "https://registry.yarnpkg.com/budp/-/budp-0.1.6.tgz#bbe166cc4842cf8d96754ee29afb1af56f6757c8" 292 | integrity sha512-o+a8NPq3DhV91j4nInjht2md6mbU1XL+7ciPltP66rw5uD3KP1m5r8lA94LZVaPKcFdJ0l2HVVzRNxnY26Pefg== 293 | 294 | buffer-crc32@~0.2.3: 295 | version "0.2.13" 296 | resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" 297 | integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= 298 | 299 | buffer-map@~0.0.7: 300 | version "0.0.7" 301 | resolved "https://registry.yarnpkg.com/buffer-map/-/buffer-map-0.0.7.tgz#5c2db65f7b3a723a2d9dff8e896fada3d2dc1c5d" 302 | integrity sha512-95try3p/vMRkIAAnJDaGkFhGpT/65NoeW6XelEPjAomWYR58RQtW4khn0SwKj34kZoE7uxL7w2koZSwbnszvQQ== 303 | 304 | bufio@^1.0.3, bufio@~1.0.6: 305 | version "1.0.6" 306 | resolved "https://registry.yarnpkg.com/bufio/-/bufio-1.0.6.tgz#e0eb6d70b2efcc997b6f8872173540967f90fa4d" 307 | integrity sha512-mjYZFRHmI9bk3Oeexu0rWjHFY+w6hGLabdmwSFzq+EFr4MHHsNOYduDVdYl71NG5pTPL7GGzUCMk9cYuV34/Qw== 308 | 309 | bupnp@~0.2.6: 310 | version "0.2.6" 311 | resolved "https://registry.yarnpkg.com/bupnp/-/bupnp-0.2.6.tgz#fbe1843aaa336594057dc568c37c5629fd9a6ff7" 312 | integrity sha512-J6ykzJhZMxXKN78K+1NzFi3v/51X2Mvzp2hW42BWwmxIVfau6PaN99gyABZ8x05e8MObWbsAis23gShhj9qpbw== 313 | dependencies: 314 | binet "~0.3.5" 315 | brq "~0.1.7" 316 | bsert "~0.0.10" 317 | 318 | bval@^0.1.4, bval@~0.1.6: 319 | version "0.1.6" 320 | resolved "https://registry.yarnpkg.com/bval/-/bval-0.1.6.tgz#e4efd4a32c563da054a5a1f055080b6089356a9f" 321 | integrity sha512-jxNH9gSx7g749hQtS+nTxXYz/bLxwr4We1RHFkCYalNYcj12RfbW6qYWsKu0RYiKAdFcbNoZRHmWrIuXIyhiQQ== 322 | dependencies: 323 | bsert "~0.0.10" 324 | 325 | bweb@^0.1.4, bweb@~0.1.10: 326 | version "0.1.10" 327 | resolved "https://registry.yarnpkg.com/bweb/-/bweb-0.1.10.tgz#7b28c905e09fd4d4d7114c0f70a8bdc076f1ed08" 328 | integrity sha512-3Kkz/rfsyAWUS+8DV5XYhwcgVN4DfDewrP+iFTcpQfdZzcF6+OypAq7dHOtXV0sW7U/3msA/sEEqz0MHZ9ERWg== 329 | dependencies: 330 | bsert "~0.0.10" 331 | bsock "~0.1.8" 332 | 333 | callsites@^3.0.0: 334 | version "3.1.0" 335 | resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" 336 | integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== 337 | 338 | chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.2: 339 | version "2.4.2" 340 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 341 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 342 | dependencies: 343 | ansi-styles "^3.2.1" 344 | escape-string-regexp "^1.0.5" 345 | supports-color "^5.3.0" 346 | 347 | chardet@^0.7.0: 348 | version "0.7.0" 349 | resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" 350 | integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== 351 | 352 | cli-cursor@^2.1.0: 353 | version "2.1.0" 354 | resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" 355 | integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= 356 | dependencies: 357 | restore-cursor "^2.0.0" 358 | 359 | cli-width@^2.0.0: 360 | version "2.2.0" 361 | resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" 362 | integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= 363 | 364 | color-convert@^1.9.0: 365 | version "1.9.3" 366 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 367 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 368 | dependencies: 369 | color-name "1.1.3" 370 | 371 | color-name@1.1.3: 372 | version "1.1.3" 373 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 374 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 375 | 376 | colors@^1.1.2: 377 | version "1.4.0" 378 | resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" 379 | integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== 380 | 381 | concat-map@0.0.1: 382 | version "0.0.1" 383 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 384 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 385 | 386 | cross-spawn@^6.0.5: 387 | version "6.0.5" 388 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" 389 | integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== 390 | dependencies: 391 | nice-try "^1.0.4" 392 | path-key "^2.0.1" 393 | semver "^5.5.0" 394 | shebang-command "^1.2.0" 395 | which "^1.2.9" 396 | 397 | debug@^4.0.1: 398 | version "4.1.1" 399 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" 400 | integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== 401 | dependencies: 402 | ms "^2.1.1" 403 | 404 | deep-is@~0.1.3: 405 | version "0.1.3" 406 | resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" 407 | integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= 408 | 409 | doctrine@^3.0.0: 410 | version "3.0.0" 411 | resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" 412 | integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== 413 | dependencies: 414 | esutils "^2.0.2" 415 | 416 | emoji-regex@^7.0.1: 417 | version "7.0.3" 418 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" 419 | integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== 420 | 421 | escape-string-regexp@^1.0.5: 422 | version "1.0.5" 423 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 424 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 425 | 426 | eslint-config-prettier@^3.3.0: 427 | version "3.6.0" 428 | resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-3.6.0.tgz#8ca3ffac4bd6eeef623a0651f9d754900e3ec217" 429 | integrity sha512-ixJ4U3uTLXwJts4rmSVW/lMXjlGwCijhBJHk8iVqKKSifeI0qgFEfWl8L63isfc8Od7EiBALF6BX3jKLluf/jQ== 430 | dependencies: 431 | get-stdin "^6.0.0" 432 | 433 | eslint-plugin-prettier@^3.0.0: 434 | version "3.1.1" 435 | resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.1.tgz#507b8562410d02a03f0ddc949c616f877852f2ba" 436 | integrity sha512-A+TZuHZ0KU0cnn56/9mfR7/KjUJ9QNVXUhwvRFSR7PGPe0zQR6PTkmyqg1AtUUEOzTqeRsUwyKFh0oVZKVCrtA== 437 | dependencies: 438 | prettier-linter-helpers "^1.0.0" 439 | 440 | eslint-scope@^4.0.3: 441 | version "4.0.3" 442 | resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" 443 | integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== 444 | dependencies: 445 | esrecurse "^4.1.0" 446 | estraverse "^4.1.1" 447 | 448 | eslint-utils@^1.3.1: 449 | version "1.4.3" 450 | resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" 451 | integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== 452 | dependencies: 453 | eslint-visitor-keys "^1.1.0" 454 | 455 | eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: 456 | version "1.1.0" 457 | resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" 458 | integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A== 459 | 460 | eslint@^5.9.0: 461 | version "5.16.0" 462 | resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea" 463 | integrity sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg== 464 | dependencies: 465 | "@babel/code-frame" "^7.0.0" 466 | ajv "^6.9.1" 467 | chalk "^2.1.0" 468 | cross-spawn "^6.0.5" 469 | debug "^4.0.1" 470 | doctrine "^3.0.0" 471 | eslint-scope "^4.0.3" 472 | eslint-utils "^1.3.1" 473 | eslint-visitor-keys "^1.0.0" 474 | espree "^5.0.1" 475 | esquery "^1.0.1" 476 | esutils "^2.0.2" 477 | file-entry-cache "^5.0.1" 478 | functional-red-black-tree "^1.0.1" 479 | glob "^7.1.2" 480 | globals "^11.7.0" 481 | ignore "^4.0.6" 482 | import-fresh "^3.0.0" 483 | imurmurhash "^0.1.4" 484 | inquirer "^6.2.2" 485 | js-yaml "^3.13.0" 486 | json-stable-stringify-without-jsonify "^1.0.1" 487 | levn "^0.3.0" 488 | lodash "^4.17.11" 489 | minimatch "^3.0.4" 490 | mkdirp "^0.5.1" 491 | natural-compare "^1.4.0" 492 | optionator "^0.8.2" 493 | path-is-inside "^1.0.2" 494 | progress "^2.0.0" 495 | regexpp "^2.0.1" 496 | semver "^5.5.1" 497 | strip-ansi "^4.0.0" 498 | strip-json-comments "^2.0.1" 499 | table "^5.2.3" 500 | text-table "^0.2.0" 501 | 502 | espree@^5.0.1: 503 | version "5.0.1" 504 | resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a" 505 | integrity sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A== 506 | dependencies: 507 | acorn "^6.0.7" 508 | acorn-jsx "^5.0.0" 509 | eslint-visitor-keys "^1.0.0" 510 | 511 | esprima@^4.0.0: 512 | version "4.0.1" 513 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" 514 | integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== 515 | 516 | esquery@^1.0.1: 517 | version "1.0.1" 518 | resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" 519 | integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA== 520 | dependencies: 521 | estraverse "^4.0.0" 522 | 523 | esrecurse@^4.1.0: 524 | version "4.2.1" 525 | resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" 526 | integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== 527 | dependencies: 528 | estraverse "^4.1.0" 529 | 530 | estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1: 531 | version "4.3.0" 532 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" 533 | integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== 534 | 535 | esutils@^2.0.2: 536 | version "2.0.3" 537 | resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" 538 | integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== 539 | 540 | external-editor@^3.0.3: 541 | version "3.1.0" 542 | resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" 543 | integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== 544 | dependencies: 545 | chardet "^0.7.0" 546 | iconv-lite "^0.4.24" 547 | tmp "^0.0.33" 548 | 549 | fast-deep-equal@^2.0.1: 550 | version "2.0.1" 551 | resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" 552 | integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= 553 | 554 | fast-diff@^1.1.2: 555 | version "1.2.0" 556 | resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" 557 | integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== 558 | 559 | fast-json-stable-stringify@^2.0.0: 560 | version "2.0.0" 561 | resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" 562 | integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= 563 | 564 | fast-levenshtein@~2.0.6: 565 | version "2.0.6" 566 | resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" 567 | integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= 568 | 569 | fd-slicer@~1.1.0: 570 | version "1.1.0" 571 | resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" 572 | integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= 573 | dependencies: 574 | pend "~1.2.0" 575 | 576 | figures@^2.0.0: 577 | version "2.0.0" 578 | resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" 579 | integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= 580 | dependencies: 581 | escape-string-regexp "^1.0.5" 582 | 583 | file-entry-cache@^5.0.1: 584 | version "5.0.1" 585 | resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" 586 | integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== 587 | dependencies: 588 | flat-cache "^2.0.1" 589 | 590 | flat-cache@^2.0.1: 591 | version "2.0.1" 592 | resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" 593 | integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== 594 | dependencies: 595 | flatted "^2.0.0" 596 | rimraf "2.6.3" 597 | write "1.0.3" 598 | 599 | flatted@^2.0.0: 600 | version "2.0.1" 601 | resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08" 602 | integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg== 603 | 604 | fs.realpath@^1.0.0: 605 | version "1.0.0" 606 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 607 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 608 | 609 | functional-red-black-tree@^1.0.1: 610 | version "1.0.1" 611 | resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" 612 | integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= 613 | 614 | geoip-lite@^1.3.8: 615 | version "1.3.8" 616 | resolved "https://registry.yarnpkg.com/geoip-lite/-/geoip-lite-1.3.8.tgz#f065424f338faaf85e0016ec93c25fd1bb97f611" 617 | integrity sha512-K0YNaQlHRjdLymVfDr47UEy+NTw40WLVmaAKy8lCzIrwWvuS764ZeIDlDofdApFWVbwU3HgJoU4oSIJvsA09bg== 618 | dependencies: 619 | async "^2.1.1" 620 | colors "^1.1.2" 621 | iconv-lite "^0.4.13" 622 | ip-address "^5.8.9" 623 | lazy "^1.0.11" 624 | rimraf "^2.5.2" 625 | yauzl "^2.9.2" 626 | 627 | get-stdin@^6.0.0: 628 | version "6.0.0" 629 | resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" 630 | integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== 631 | 632 | glob@^7.1.2, glob@^7.1.3: 633 | version "7.1.6" 634 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" 635 | integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== 636 | dependencies: 637 | fs.realpath "^1.0.0" 638 | inflight "^1.0.4" 639 | inherits "2" 640 | minimatch "^3.0.4" 641 | once "^1.3.0" 642 | path-is-absolute "^1.0.0" 643 | 644 | globals@^11.7.0: 645 | version "11.12.0" 646 | resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" 647 | integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== 648 | 649 | goosig@~0.3.0: 650 | version "0.3.0" 651 | resolved "https://registry.yarnpkg.com/goosig/-/goosig-0.3.0.tgz#97b68c9636e7d2ea61e697ff3caf6c56fca1f91b" 652 | integrity sha512-GGYiPgT+MCiWdnf/b4l0e54u2kAzE3+hBQVO0A/UuyLPkeoCa65wCOy6QW/e93HSCRsVc+tUO+WT/nqpeByBtw== 653 | dependencies: 654 | bcrypto "~4.2.3" 655 | bsert "~0.0.10" 656 | loady "~0.0.1" 657 | nan "^2.13.1" 658 | 659 | has-flag@^3.0.0: 660 | version "3.0.0" 661 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 662 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 663 | 664 | hs-client@~0.0.6: 665 | version "0.0.6" 666 | resolved "https://registry.yarnpkg.com/hs-client/-/hs-client-0.0.6.tgz#9997dbe5f2f204598252e9cdac7f98d52c0906b9" 667 | integrity sha512-RcuUH/cvVas/tXqkaK0yOovf5Dzp6FteQ/vYZJC8EvRQga/vPRjBRKFoG/gFOeUZ58Jrt0c0eWbg/7NAAgFEdw== 668 | dependencies: 669 | bcfg "~0.1.6" 670 | bcurl "~0.1.6" 671 | bsert "~0.0.10" 672 | 673 | "hsd@github:handshake-org/hsd": 674 | version "0.0.0" 675 | resolved "https://codeload.github.com/handshake-org/hsd/tar.gz/27c99adda0ff7f91d3ccfa0af5806cc4871d1b00" 676 | dependencies: 677 | bcfg "~0.1.6" 678 | bcrypto "~4.2.8" 679 | bdb "~1.1.7" 680 | bdns "~0.1.5" 681 | bevent "~0.1.5" 682 | bfile "~0.2.1" 683 | bfilter "~1.0.5" 684 | bheep "~0.1.5" 685 | binet "~0.3.5" 686 | blgr "~0.1.7" 687 | blru "~0.1.6" 688 | blst "~0.1.5" 689 | bmutex "~0.1.6" 690 | bns "~0.8.1" 691 | bs32 "~0.1.6" 692 | bsert "~0.0.10" 693 | bsip "~0.1.9" 694 | bsock "~0.1.9" 695 | bsocks "~0.2.5" 696 | bstring "~0.3.9" 697 | btcp "~0.1.5" 698 | buffer-map "~0.0.7" 699 | bufio "~1.0.6" 700 | bupnp "~0.2.6" 701 | bval "~0.1.6" 702 | bweb "~0.1.10" 703 | goosig "~0.3.0" 704 | hs-client "~0.0.6" 705 | mrmr "~0.1.8" 706 | n64 "~0.2.10" 707 | urkel "~0.6.3" 708 | 709 | iconv-lite@^0.4.13, iconv-lite@^0.4.24: 710 | version "0.4.24" 711 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 712 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 713 | dependencies: 714 | safer-buffer ">= 2.1.2 < 3" 715 | 716 | ignore@^4.0.6: 717 | version "4.0.6" 718 | resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" 719 | integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== 720 | 721 | import-fresh@^3.0.0: 722 | version "3.2.1" 723 | resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" 724 | integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== 725 | dependencies: 726 | parent-module "^1.0.0" 727 | resolve-from "^4.0.0" 728 | 729 | imurmurhash@^0.1.4: 730 | version "0.1.4" 731 | resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" 732 | integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= 733 | 734 | inflight@^1.0.4: 735 | version "1.0.6" 736 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 737 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 738 | dependencies: 739 | once "^1.3.0" 740 | wrappy "1" 741 | 742 | inherits@2: 743 | version "2.0.4" 744 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 745 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 746 | 747 | inherits@2.0.3: 748 | version "2.0.3" 749 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 750 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 751 | 752 | inquirer@^6.2.2: 753 | version "6.5.2" 754 | resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" 755 | integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== 756 | dependencies: 757 | ansi-escapes "^3.2.0" 758 | chalk "^2.4.2" 759 | cli-cursor "^2.1.0" 760 | cli-width "^2.0.0" 761 | external-editor "^3.0.3" 762 | figures "^2.0.0" 763 | lodash "^4.17.12" 764 | mute-stream "0.0.7" 765 | run-async "^2.2.0" 766 | rxjs "^6.4.0" 767 | string-width "^2.1.0" 768 | strip-ansi "^5.1.0" 769 | through "^2.3.6" 770 | 771 | ip-address@^5.8.9: 772 | version "5.9.4" 773 | resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-5.9.4.tgz#4660ac261ad61bd397a860a007f7e98e4eaee386" 774 | integrity sha512-dHkI3/YNJq4b/qQaz+c8LuarD3pY24JqZWfjB8aZx1gtpc2MDILu9L9jpZe1sHpzo/yWFweQVn+U//FhazUxmw== 775 | dependencies: 776 | jsbn "1.1.0" 777 | lodash "^4.17.15" 778 | sprintf-js "1.1.2" 779 | 780 | is-fullwidth-code-point@^2.0.0: 781 | version "2.0.0" 782 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 783 | integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= 784 | 785 | is-promise@^2.1.0: 786 | version "2.1.0" 787 | resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" 788 | integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= 789 | 790 | isexe@^2.0.0: 791 | version "2.0.0" 792 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 793 | integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= 794 | 795 | js-tokens@^4.0.0: 796 | version "4.0.0" 797 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" 798 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 799 | 800 | js-yaml@^3.13.0: 801 | version "3.13.1" 802 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" 803 | integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== 804 | dependencies: 805 | argparse "^1.0.7" 806 | esprima "^4.0.0" 807 | 808 | jsbn@1.1.0: 809 | version "1.1.0" 810 | resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" 811 | integrity sha1-sBMHyym2GKHtJux56RH4A8TaAEA= 812 | 813 | json-schema-traverse@^0.4.1: 814 | version "0.4.1" 815 | resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" 816 | integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== 817 | 818 | json-stable-stringify-without-jsonify@^1.0.1: 819 | version "1.0.1" 820 | resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" 821 | integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= 822 | 823 | lazy@^1.0.11: 824 | version "1.0.11" 825 | resolved "https://registry.yarnpkg.com/lazy/-/lazy-1.0.11.tgz#daa068206282542c088288e975c297c1ae77b690" 826 | integrity sha1-2qBoIGKCVCwIgojpdcKXwa53tpA= 827 | 828 | levn@^0.3.0, levn@~0.3.0: 829 | version "0.3.0" 830 | resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" 831 | integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= 832 | dependencies: 833 | prelude-ls "~1.1.2" 834 | type-check "~0.3.2" 835 | 836 | loady@~0.0.1: 837 | version "0.0.1" 838 | resolved "https://registry.yarnpkg.com/loady/-/loady-0.0.1.tgz#24a99c14cfed9cd0bffed365b1836035303f7e5d" 839 | integrity sha512-PW5Z13Jd0v6ZcA1P6ZVUc3EV8BJwQuAiwUvvT6VQGHoaZ1d/tu7r1QZctuKfQqwy9SFBWeAGfcIdLxhp7ZW3Rw== 840 | 841 | lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15: 842 | version "4.17.15" 843 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" 844 | integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== 845 | 846 | mimic-fn@^1.0.0: 847 | version "1.2.0" 848 | resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" 849 | integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== 850 | 851 | minimatch@^3.0.4: 852 | version "3.0.4" 853 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 854 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 855 | dependencies: 856 | brace-expansion "^1.1.7" 857 | 858 | minimist@0.0.8: 859 | version "0.0.8" 860 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 861 | integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= 862 | 863 | mkdirp@^0.5.1: 864 | version "0.5.1" 865 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 866 | integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= 867 | dependencies: 868 | minimist "0.0.8" 869 | 870 | mrmr@~0.1.6, mrmr@~0.1.8: 871 | version "0.1.8" 872 | resolved "https://registry.yarnpkg.com/mrmr/-/mrmr-0.1.8.tgz#206b7975157543d2cffe762eec23966000b3fd12" 873 | integrity sha512-lNav10EJsPZvlMqlBOYQ5atIO9jrlvbJ4/7asqunXY89dHooN5c+W6AV7jtvBRw4wFDR7TpEWfmBQgRGRVwBzA== 874 | dependencies: 875 | bsert "~0.0.10" 876 | loady "~0.0.1" 877 | nan "^2.13.1" 878 | 879 | ms@^2.1.1: 880 | version "2.1.2" 881 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 882 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 883 | 884 | mute-stream@0.0.7: 885 | version "0.0.7" 886 | resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" 887 | integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= 888 | 889 | n64@~0.2.10: 890 | version "0.2.10" 891 | resolved "https://registry.yarnpkg.com/n64/-/n64-0.2.10.tgz#e5831073dd527e6934b880231a43f3a8e36c096a" 892 | integrity sha512-uH9geV4+roR1tohsrrqSOLCJ9Mh1iFcDI+9vUuydDlDxUS1UCAWUfuGb06p3dj3flzywquJNrGsQ7lHP8+4RVQ== 893 | 894 | nan@^2.13.1, nan@^2.13.2: 895 | version "2.14.0" 896 | resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" 897 | integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== 898 | 899 | natural-compare@^1.4.0: 900 | version "1.4.0" 901 | resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" 902 | integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= 903 | 904 | nice-try@^1.0.4: 905 | version "1.0.5" 906 | resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" 907 | integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== 908 | 909 | once@^1.3.0: 910 | version "1.4.0" 911 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 912 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 913 | dependencies: 914 | wrappy "1" 915 | 916 | onetime@^2.0.0: 917 | version "2.0.1" 918 | resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" 919 | integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= 920 | dependencies: 921 | mimic-fn "^1.0.0" 922 | 923 | optionator@^0.8.2: 924 | version "0.8.3" 925 | resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" 926 | integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== 927 | dependencies: 928 | deep-is "~0.1.3" 929 | fast-levenshtein "~2.0.6" 930 | levn "~0.3.0" 931 | prelude-ls "~1.1.2" 932 | type-check "~0.3.2" 933 | word-wrap "~1.2.3" 934 | 935 | os-tmpdir@~1.0.2: 936 | version "1.0.2" 937 | resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" 938 | integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= 939 | 940 | parent-module@^1.0.0: 941 | version "1.0.1" 942 | resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" 943 | integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== 944 | dependencies: 945 | callsites "^3.0.0" 946 | 947 | path-is-absolute@^1.0.0: 948 | version "1.0.1" 949 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 950 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 951 | 952 | path-is-inside@^1.0.2: 953 | version "1.0.2" 954 | resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" 955 | integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= 956 | 957 | path-key@^2.0.1: 958 | version "2.0.1" 959 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" 960 | integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= 961 | 962 | path@^0.12.7: 963 | version "0.12.7" 964 | resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f" 965 | integrity sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8= 966 | dependencies: 967 | process "^0.11.1" 968 | util "^0.10.3" 969 | 970 | pend@~1.2.0: 971 | version "1.2.0" 972 | resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" 973 | integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= 974 | 975 | prelude-ls@~1.1.2: 976 | version "1.1.2" 977 | resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" 978 | integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= 979 | 980 | prettier-linter-helpers@^1.0.0: 981 | version "1.0.0" 982 | resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" 983 | integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== 984 | dependencies: 985 | fast-diff "^1.1.2" 986 | 987 | prettier@^1.15.2: 988 | version "1.19.1" 989 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" 990 | integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== 991 | 992 | process@^0.11.1: 993 | version "0.11.10" 994 | resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" 995 | integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= 996 | 997 | progress@^2.0.0: 998 | version "2.0.3" 999 | resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" 1000 | integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== 1001 | 1002 | punycode@^2.1.0: 1003 | version "2.1.1" 1004 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" 1005 | integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== 1006 | 1007 | regexpp@^2.0.1: 1008 | version "2.0.1" 1009 | resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" 1010 | integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== 1011 | 1012 | resolve-from@^4.0.0: 1013 | version "4.0.0" 1014 | resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" 1015 | integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== 1016 | 1017 | restore-cursor@^2.0.0: 1018 | version "2.0.0" 1019 | resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" 1020 | integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= 1021 | dependencies: 1022 | onetime "^2.0.0" 1023 | signal-exit "^3.0.2" 1024 | 1025 | rimraf@2.6.3: 1026 | version "2.6.3" 1027 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" 1028 | integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== 1029 | dependencies: 1030 | glob "^7.1.3" 1031 | 1032 | rimraf@^2.5.2: 1033 | version "2.7.1" 1034 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" 1035 | integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== 1036 | dependencies: 1037 | glob "^7.1.3" 1038 | 1039 | run-async@^2.2.0: 1040 | version "2.3.0" 1041 | resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" 1042 | integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA= 1043 | dependencies: 1044 | is-promise "^2.1.0" 1045 | 1046 | rxjs@^6.4.0: 1047 | version "6.5.3" 1048 | resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.3.tgz#510e26317f4db91a7eb1de77d9dd9ba0a4899a3a" 1049 | integrity sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA== 1050 | dependencies: 1051 | tslib "^1.9.0" 1052 | 1053 | "safer-buffer@>= 2.1.2 < 3": 1054 | version "2.1.2" 1055 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 1056 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 1057 | 1058 | semver@^5.5.0, semver@^5.5.1: 1059 | version "5.7.1" 1060 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 1061 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 1062 | 1063 | shebang-command@^1.2.0: 1064 | version "1.2.0" 1065 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" 1066 | integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= 1067 | dependencies: 1068 | shebang-regex "^1.0.0" 1069 | 1070 | shebang-regex@^1.0.0: 1071 | version "1.0.0" 1072 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" 1073 | integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= 1074 | 1075 | signal-exit@^3.0.2: 1076 | version "3.0.2" 1077 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" 1078 | integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= 1079 | 1080 | slice-ansi@^2.1.0: 1081 | version "2.1.0" 1082 | resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" 1083 | integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== 1084 | dependencies: 1085 | ansi-styles "^3.2.0" 1086 | astral-regex "^1.0.0" 1087 | is-fullwidth-code-point "^2.0.0" 1088 | 1089 | sprintf-js@1.1.2: 1090 | version "1.1.2" 1091 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" 1092 | integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== 1093 | 1094 | sprintf-js@~1.0.2: 1095 | version "1.0.3" 1096 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 1097 | integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= 1098 | 1099 | string-width@^2.1.0: 1100 | version "2.1.1" 1101 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" 1102 | integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== 1103 | dependencies: 1104 | is-fullwidth-code-point "^2.0.0" 1105 | strip-ansi "^4.0.0" 1106 | 1107 | string-width@^3.0.0: 1108 | version "3.1.0" 1109 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" 1110 | integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== 1111 | dependencies: 1112 | emoji-regex "^7.0.1" 1113 | is-fullwidth-code-point "^2.0.0" 1114 | strip-ansi "^5.1.0" 1115 | 1116 | strip-ansi@^4.0.0: 1117 | version "4.0.0" 1118 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" 1119 | integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= 1120 | dependencies: 1121 | ansi-regex "^3.0.0" 1122 | 1123 | strip-ansi@^5.1.0: 1124 | version "5.2.0" 1125 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" 1126 | integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== 1127 | dependencies: 1128 | ansi-regex "^4.1.0" 1129 | 1130 | strip-json-comments@^2.0.1: 1131 | version "2.0.1" 1132 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 1133 | integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= 1134 | 1135 | supports-color@^5.3.0: 1136 | version "5.5.0" 1137 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 1138 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 1139 | dependencies: 1140 | has-flag "^3.0.0" 1141 | 1142 | table@^5.2.3: 1143 | version "5.4.6" 1144 | resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" 1145 | integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== 1146 | dependencies: 1147 | ajv "^6.10.2" 1148 | lodash "^4.17.14" 1149 | slice-ansi "^2.1.0" 1150 | string-width "^3.0.0" 1151 | 1152 | text-table@^0.2.0: 1153 | version "0.2.0" 1154 | resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" 1155 | integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= 1156 | 1157 | through@^2.3.6: 1158 | version "2.3.8" 1159 | resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" 1160 | integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= 1161 | 1162 | tmp@^0.0.33: 1163 | version "0.0.33" 1164 | resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" 1165 | integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== 1166 | dependencies: 1167 | os-tmpdir "~1.0.2" 1168 | 1169 | tslib@^1.9.0: 1170 | version "1.10.0" 1171 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" 1172 | integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== 1173 | 1174 | type-check@~0.3.2: 1175 | version "0.3.2" 1176 | resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" 1177 | integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= 1178 | dependencies: 1179 | prelude-ls "~1.1.2" 1180 | 1181 | unbound@~0.3.5: 1182 | version "0.3.5" 1183 | resolved "https://registry.yarnpkg.com/unbound/-/unbound-0.3.5.tgz#9f91cfc740b8394562b5accf23a7b5f28bfb21e2" 1184 | integrity sha512-vnIkZGQswi7SVEnGI+25+oN9hLA7rc40zWi+xnzEwktNIZFtAC8PB/e5FMWgFoXUC4p2Jb/ZRNsZDcv3vivH+g== 1185 | dependencies: 1186 | loady "~0.0.1" 1187 | nan "^2.13.1" 1188 | 1189 | uri-js@^4.2.2: 1190 | version "4.2.2" 1191 | resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" 1192 | integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== 1193 | dependencies: 1194 | punycode "^2.1.0" 1195 | 1196 | urkel@~0.6.3: 1197 | version "0.6.3" 1198 | resolved "https://registry.yarnpkg.com/urkel/-/urkel-0.6.3.tgz#0fe573cd2a6dae30dfdc28dc54c453eea8ea1d29" 1199 | integrity sha512-aY/u3cpCRRwa3XjUGG8DcLcQJSNmQ0+qXDeT0igDytKhrK+yiusZ7mYdPRzmtbQ2CPozHhwsmVxCVBkPBp9jDQ== 1200 | dependencies: 1201 | bfile "~0.2.1" 1202 | bmutex "~0.1.6" 1203 | bsert "~0.0.10" 1204 | 1205 | util@^0.10.3: 1206 | version "0.10.4" 1207 | resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" 1208 | integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== 1209 | dependencies: 1210 | inherits "2.0.3" 1211 | 1212 | which@^1.2.9: 1213 | version "1.3.1" 1214 | resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" 1215 | integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== 1216 | dependencies: 1217 | isexe "^2.0.0" 1218 | 1219 | word-wrap@~1.2.3: 1220 | version "1.2.3" 1221 | resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" 1222 | integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== 1223 | 1224 | wrappy@1: 1225 | version "1.0.2" 1226 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 1227 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 1228 | 1229 | write@1.0.3: 1230 | version "1.0.3" 1231 | resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" 1232 | integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== 1233 | dependencies: 1234 | mkdirp "^0.5.1" 1235 | 1236 | yauzl@^2.9.2: 1237 | version "2.10.0" 1238 | resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" 1239 | integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= 1240 | dependencies: 1241 | buffer-crc32 "~0.2.3" 1242 | fd-slicer "~1.1.0" 1243 | --------------------------------------------------------------------------------