diff --git a/CHANGELOG.md b/CHANGELOG.md index a2545c14d..9f382f274 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ The changelog format is based on [Keep a Changelog](https://keepachangelog.com/e | ---------------- | ------- | ------------------------------------------------------------------ | | Symbol Bootstrap | v1.0.8 | [symbol-bootstrap](https://www.npmjs.com/package/symbol-bootstrap) | +- Added `wizard` command. +- Added `pack` command. ## [1.0.7] - June-22-2021 diff --git a/README.md b/README.md index 22161b4c8..a99217a89 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ Symbol CLI tool that allows you creating, configuring and running Symbol's c * [Concepts](#concepts) * [Requirements](#requirements) * [Usage](#usage) +* [General Usage](#general-usage) +* [Wizard](#wizard) * [E2E Testing support](#e2e-testing-support) * [Development](#development) * [Commands](#commands) @@ -143,6 +145,8 @@ USAGE ``` +# General Usage + The general usage would be: ``` @@ -163,6 +167,14 @@ If you need to start fresh, you many need to sudo remove the target folder (dock sudo rm -rf ./target ``` +# Wizard + +If this is your first time creating a node, it's recommended to use the Wizard. Just follow the instructions: + +``` +symbol-bootstrap wizard +``` + # E2E Testing support One use case of this CLI is client E2E testing support. If you are coding a Symbol client, you (Travis or Jenkins) can run e2e tests like: @@ -272,6 +284,7 @@ General users should install this tool like any other node module. * [`symbol-bootstrap healthCheck`](docs/healthCheck.md) - It checks if the services created with docker compose are up and running. * [`symbol-bootstrap help`](docs/help.md) - display help for symbol-bootstrap * [`symbol-bootstrap link`](docs/link.md) - It announces VRF and Voting Link transactions to the network for each node with 'Peer' or 'Voting' roles. This command finalizes the node registration to an existing network. +* [`symbol-bootstrap pack`](docs/pack.md) - It configures and packages your node into a zip file that can be uploaded to the final node machine. * [`symbol-bootstrap report`](docs/report.md) - it generates reStructuredText (.rst) reports describing the configuration of each node. * [`symbol-bootstrap resetData`](docs/resetData.md) - It removes the data keeping the generated configuration, certificates, keys and block 1. * [`symbol-bootstrap run`](docs/run.md) - It boots the network via docker using the generated `docker-compose.yml` file and configuration. The config and compose methods/commands need to be called before this method. This is just a wrapper for the `docker-compose up` bash call. @@ -279,6 +292,7 @@ General users should install this tool like any other node module. * [`symbol-bootstrap stop`](docs/stop.md) - It stops the docker-compose network if running (symbol-bootstrap started with --detached). This is just a wrapper for the `docker-compose down` bash call. * [`symbol-bootstrap updateVotingKeys`](docs/updateVotingKeys.md) - It updates the voting files containing the voting keys when required. * [`symbol-bootstrap verify`](docs/verify.md) - It tests the installed software in the current computer reporting if there is any missing dependency, invalid version, or software related issue. +* [`symbol-bootstrap wizard`](docs/wizard.md) - An utility command that will help you configuring node! diff --git a/docs/pack.md b/docs/pack.md new file mode 100644 index 000000000..c777ce2f6 --- /dev/null +++ b/docs/pack.md @@ -0,0 +1,62 @@ +`symbol-bootstrap pack` +======================= + +It configures and packages your node into a zip file that can be uploaded to the final node machine. + +* [`symbol-bootstrap pack`](#symbol-bootstrap-pack) + +## `symbol-bootstrap pack` + +It configures and packages your node into a zip file that can be uploaded to the final node machine. + +``` +USAGE + $ symbol-bootstrap pack + +OPTIONS + -a, --assembly=assembly (required) The assembly, example "dual" for testnet. + + -c, --customPreset=customPreset (required) External preset file. Values in this file will override the + provided presets + + -h, --help It shows the help of this command. + + -p, --preset=(bootstrap|testnet|mainnet) (required) The network preset, can be provided via custom preset or cli + parameter. + + -r, --reset It resets the configuration generating a new one + + -t, --target=target [default: target] The target folder where the symbol-bootstrap network is + generated + + -u, --user=user [default: current] User used to run docker images when creating + configuration files like certificates or nemesis block. "current" means the + current user. + + --noPassword When provided, Bootstrap will not use a password, so private keys will be + stored in plain text. Use with caution. + + --password=password A password used to encrypt and decrypt private keys in preset files like + addresses.yml and preset.yml. Bootstrap prompts for a password by default, + can be provided in the command line (--password=XXXX) or disabled in the + command line (--noPassword). + + --ready If --ready is provided, the command will not ask offline confirmation. + + --report It generates reStructuredText (.rst) reports describing the configuration of + each node. + + --upgrade It regenerates the configuration reusing the previous keys. Use this flag + when upgrading the version of bootstrap to keep your node up to date without + dropping the local data. The original preset (-t), assembly (-a), and custom + preset (-a) must be used. Backup the target folder before upgrading. + +EXAMPLES + $ symbol-bootstrap pack + $ symbol-bootstrap pack -p bootstrap -c custom-preset.yml + $ symbol-bootstrap pack -p testnet -a dual -c custom-preset.yml + $ symbol-bootstrap pack -p mainnet -a dual --password 1234 -c custom-preset.yml + $ echo "$MY_ENV_VAR_PASSWORD" | symbol-bootstrap pack -p mainnet -a dual -c custom-preset.yml +``` + +_See code: [src/commands/pack.ts](https://github.com/nemtech/symbol-bootstrap/blob/v1.0.8/src/commands/pack.ts)_ diff --git a/docs/wizard.md b/docs/wizard.md new file mode 100644 index 000000000..351de636a --- /dev/null +++ b/docs/wizard.md @@ -0,0 +1,39 @@ +`symbol-bootstrap wizard` +========================= + +An utility command that will help you configuring node! + +* [`symbol-bootstrap wizard`](#symbol-bootstrap-wizard) + +## `symbol-bootstrap wizard` + +An utility command that will help you configuring node! + +``` +USAGE + $ symbol-bootstrap wizard + +OPTIONS + -c, --customPreset=customPreset [default: custom-preset.yml] The custom preset to be created. + -h, --help It shows the help of this command. + + -t, --target=target [default: target] The target folder where the symbol-bootstrap network is + generated + + --network=mainnet|testnet|privateNetwork The node or network you want to create + + --noPassword When provided, Bootstrap will not use a password, so private keys will be + stored in plain text. Use with caution. + + --password=password A password used to encrypt and decrypt private keys in preset files like + addresses.yml and preset.yml. Bootstrap prompts for a password by default, + can be provided in the command line (--password=XXXX) or disabled in the + command line (--noPassword). + + --ready If --ready is provided, the command will not ask offline confirmation. + +EXAMPLE + $ symbol-bootstrap wizard +``` + +_See code: [src/commands/wizard.ts](https://github.com/nemtech/symbol-bootstrap/blob/v1.0.8/src/commands/wizard.ts)_ diff --git a/package-lock.json b/package-lock.json index 08d1042e3..1d35db6c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,13 +5,14 @@ "requires": true, "packages": { "": { - "version": "1.0.7", + "version": "1.0.8", "license": "Apache-2.0", "dependencies": { "@oclif/command": "^1.7.0", "@oclif/config": "^1.16.0", "@oclif/plugin-autocomplete": "^0.3.0", "@oclif/plugin-help": "^3.1.0", + "archiver": "^5.2.0", "figlet": "^1.2.4", "handlebars": "^4.7.7", "inquirer": "^7.3.3", @@ -20,10 +21,12 @@ "memorystream": "^0.3.1", "noble-ed25519": "^1.0.3", "node-forge": "^0.10.0", + "node-stream-zip": "^1.12.0", "rxjs": "^6.6.3", "semver": "^7.3.5", "shx": "^0.3.2", "sshpk": "^1.16.1", + "symbol-hd-wallets": "^0.14.1-alpha-202103052158", "symbol-sdk": "^1.0.1", "tslib": "^1.13.0", "utf8": "^2.1.2", @@ -35,6 +38,8 @@ "devDependencies": { "@oclif/dev-cli": "^1.22.2", "@oclif/test": "^1.2.8", + "@types/archiver": "^5.1.0", + "@types/bip32": "^1.0.2", "@types/chai": "^4.2.12", "@types/figlet": "^1.2.0", "@types/handlebars": "^4.1.0", @@ -60,6 +65,7 @@ "marked": ">=2.0.0", "mocha": "^8.3.2", "mocha-lcov-reporter": "^1.3.0", + "mock-stdin": "^1.0.0", "nyc": "^15.1.0", "prettier": "^2.0.5", "prettier-plugin-organize-imports": "^1.1.1", @@ -997,6 +1003,24 @@ "node": ">=8.0.0" } }, + "node_modules/@types/archiver": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-5.1.0.tgz", + "integrity": "sha512-baFOhanb/hxmcOd1Uey2TfFg43kTSmM6py1Eo7Rjbv/ivcl7PXLhY0QgXGf50Hx/eskGCFqPfhs/7IZLb15C5g==", + "dev": true, + "dependencies": { + "@types/glob": "*" + } + }, + "node_modules/@types/bip32": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/bip32/-/bip32-1.0.2.tgz", + "integrity": "sha512-gqkz3Jq2OA3s5QOHe7de9tUzW7Xjoct6ImCbt0KQnF0ParqDSvLo5Fu+mKQykFF1fgIcdzEmm0CO6HN+xG2SAA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/chai": { "version": "4.2.12", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.12.tgz", @@ -1368,6 +1392,70 @@ "node": ">=8" } }, + "node_modules/archiver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz", + "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.0", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.0.0", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", @@ -1469,11 +1557,18 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "node_modules/base-x": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz", + "integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/base64-js": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", - "dev": true + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", @@ -1505,11 +1600,47 @@ "node": ">=8" } }, + "node_modules/bip32": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/bip32/-/bip32-1.0.4.tgz", + "integrity": "sha512-8T21eLWylZETolyqCPgia+MNp+kY37zFr7PTFDTPObHeNi9JlfG4qGIh8WzerIJidtwoK+NsWq2I5i66YfHoIw==", + "dependencies": { + "bs58check": "^2.1.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "tiny-secp256k1": "^1.0.0", + "typeforce": "^1.11.5", + "wif": "^2.0.6" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bip39": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.4.tgz", + "integrity": "sha512-YZKQlb752TrUWqHWj7XAwCSjYEgGAk+/Aas3V7NyjQeZYsztO8JnQUaCWhcnL4T+jL8nvB8typ2jRPzTlgugNw==", + "dependencies": { + "@types/node": "11.11.6", + "create-hash": "^1.1.0", + "pbkdf2": "^3.0.9", + "randombytes": "^2.0.1" + } + }, + "node_modules/bip39/node_modules/@types/node": { + "version": "11.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", + "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==" + }, + "node_modules/bip44-constants": { + "version": "8.0.103", + "resolved": "https://registry.npmjs.org/bip44-constants/-/bip44-constants-8.0.103.tgz", + "integrity": "sha512-wuGsY9IKUS9GC5Sf/Acb5jLJZI3Z8qsMoQHWldnQyoVUlij4y8e5srh28Iqul1HwK+elPsAYGNYKtYhovjvNxA==" + }, "node_modules/bl": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", - "dev": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -1576,16 +1707,41 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dependencies": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, "node_modules/buffer": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", - "dev": true, "dependencies": { "base64-js": "^1.0.2", "ieee754": "^1.1.4" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "engines": { + "node": "*" + } + }, "node_modules/buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -1767,6 +1923,15 @@ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/clean-stack": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.0.tgz", @@ -2041,6 +2206,20 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "node_modules/compress-commons": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", + "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2094,6 +2273,58 @@ "node": ">=6" } }, + "node_modules/crc-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", + "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", + "dependencies": { + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" + }, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", + "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, "node_modules/create-ts-index": { "version": "1.13.6", "resolved": "https://registry.npmjs.org/create-ts-index/-/create-ts-index-1.13.6.tgz", @@ -2336,7 +2567,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "dependencies": { "once": "^1.4.0" } @@ -2708,6 +2938,14 @@ "node": ">=4" } }, + "node_modules/exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -3059,8 +3297,7 @@ "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "node_modules/fs-extra": { "version": "7.0.1", @@ -3489,8 +3726,7 @@ "node_modules/ieee754": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", - "dev": true + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, "node_modules/ignore": { "version": "5.1.8", @@ -4079,6 +4315,44 @@ "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, + "node_modules/lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/lcov-parse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", @@ -4168,12 +4442,32 @@ "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, "node_modules/lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, "node_modules/lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", @@ -4197,6 +4491,11 @@ "lodash._reinterpolate": "^3.0.0" } }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" + }, "node_modules/log-driver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", @@ -4323,6 +4622,16 @@ "node": ">= 8.16.2" } }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -4819,6 +5128,18 @@ "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==", "dev": true }, + "node_modules/node-stream-zip": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.13.5.tgz", + "integrity": "sha512-Lfi9xhSNvnJU35+4ZFlECXKJ70etAgJYWAVCdcEpksPnMrgwNqwkCJqdunoViVoFFV38C7AIodYE+2apuoK9Gw==", + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/antelle" + } + }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -4844,7 +5165,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -5190,6 +5510,21 @@ "node": "*" } }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -5255,6 +5590,17 @@ "integrity": "sha512-rFA1lnek1FYkMGthm4xBKME41qUKItTovuo24bCGZu/Vu1n3gW71UPLAkIdwewwkZCe29gRVweSOPXvAdckFuw==", "dev": true }, + "node_modules/printj": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", + "bin": { + "printj": "bin/printj.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -5419,6 +5765,14 @@ "node": ">= 6" } }, + "node_modules/readdir-glob": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz", + "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", + "dependencies": { + "minimatch": "^3.0.4" + } + }, "node_modules/readdirp": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", @@ -5660,6 +6014,18 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -6093,6 +6459,23 @@ "node": ">=0.10.0" } }, + "node_modules/symbol-hd-wallets": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/symbol-hd-wallets/-/symbol-hd-wallets-0.14.1.tgz", + "integrity": "sha512-jMYbVHlGtB9IiufME4nGDKEvYXz7e6YfBK0eulGhetMH/0TZ+qIomCmNaMU2EfJEeywo0Kb6TIMIN4AEq9ObBA==", + "dependencies": { + "bip32": "^1.0.4", + "bip39": "^3.0.2", + "bip44-constants": "^8.0.5", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "crypto-js": "^4.0.0", + "js-sha3": "^0.8.0", + "tiny-secp256k1": "^1.1.3", + "tweetnacl": "^1.0.3" + } + }, "node_modules/symbol-openapi-typescript-fetch-client": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/symbol-openapi-typescript-fetch-client/-/symbol-openapi-typescript-fetch-client-0.11.2.tgz", @@ -6211,12 +6594,11 @@ } }, "node_modules/tar-stream": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.3.tgz", - "integrity": "sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA==", - "dev": true, + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dependencies": { - "bl": "^4.0.1", + "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", @@ -6264,6 +6646,22 @@ "node": ">=0.10.0" } }, + "node_modules/tiny-secp256k1": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz", + "integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.3.0", + "bn.js": "^4.11.8", + "create-hmac": "^1.1.7", + "elliptic": "^6.4.0", + "nan": "^2.13.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/tmp": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", @@ -6495,6 +6893,11 @@ "node": ">= 10.0.0" } }, + "node_modules/typeforce": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", + "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" + }, "node_modules/typescript": { "version": "3.9.7", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", @@ -6744,6 +7147,14 @@ "node": ">=4" } }, + "node_modules/wif": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", + "integrity": "sha1-CNP1IFbGZnkplyb63g1DKudLRwQ=", + "dependencies": { + "bs58check": "<3.0.0" + } + }, "node_modules/winston": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz", @@ -7035,6 +7446,19 @@ "engines": { "node": ">=10" } + }, + "node_modules/zip-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", + "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", + "dependencies": { + "archiver-utils": "^2.1.0", + "compress-commons": "^4.1.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } } }, "dependencies": { @@ -7850,6 +8274,24 @@ "fancy-test": "^1.4.3" } }, + "@types/archiver": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-5.1.0.tgz", + "integrity": "sha512-baFOhanb/hxmcOd1Uey2TfFg43kTSmM6py1Eo7Rjbv/ivcl7PXLhY0QgXGf50Hx/eskGCFqPfhs/7IZLb15C5g==", + "dev": true, + "requires": { + "@types/glob": "*" + } + }, + "@types/bip32": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/bip32/-/bip32-1.0.2.tgz", + "integrity": "sha512-gqkz3Jq2OA3s5QOHe7de9tUzW7Xjoct6ImCbt0KQnF0ParqDSvLo5Fu+mKQykFF1fgIcdzEmm0CO6HN+xG2SAA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/chai": { "version": "4.2.12", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.12.tgz", @@ -8175,6 +8617,66 @@ "default-require-extensions": "^3.0.0" } }, + "archiver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz", + "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", + "requires": { + "archiver-utils": "^2.1.0", + "async": "^3.2.0", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.0.0", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + } + }, + "archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "requires": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", @@ -8258,11 +8760,18 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "base-x": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz", + "integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "base64-js": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", - "dev": true + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" }, "bcrypt-pbkdf": { "version": "1.0.2", @@ -8290,11 +8799,46 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, + "bip32": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/bip32/-/bip32-1.0.4.tgz", + "integrity": "sha512-8T21eLWylZETolyqCPgia+MNp+kY37zFr7PTFDTPObHeNi9JlfG4qGIh8WzerIJidtwoK+NsWq2I5i66YfHoIw==", + "requires": { + "bs58check": "^2.1.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "tiny-secp256k1": "^1.0.0", + "typeforce": "^1.11.5", + "wif": "^2.0.6" + } + }, + "bip39": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.4.tgz", + "integrity": "sha512-YZKQlb752TrUWqHWj7XAwCSjYEgGAk+/Aas3V7NyjQeZYsztO8JnQUaCWhcnL4T+jL8nvB8typ2jRPzTlgugNw==", + "requires": { + "@types/node": "11.11.6", + "create-hash": "^1.1.0", + "pbkdf2": "^3.0.9", + "randombytes": "^2.0.1" + }, + "dependencies": { + "@types/node": { + "version": "11.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", + "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==" + } + } + }, + "bip44-constants": { + "version": "8.0.103", + "resolved": "https://registry.npmjs.org/bip44-constants/-/bip44-constants-8.0.103.tgz", + "integrity": "sha512-wuGsY9IKUS9GC5Sf/Acb5jLJZI3Z8qsMoQHWldnQyoVUlij4y8e5srh28Iqul1HwK+elPsAYGNYKtYhovjvNxA==" + }, "bl": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", - "dev": true, "requires": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -8352,16 +8896,38 @@ "node-releases": "^1.1.71" } }, + "bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "requires": { + "base-x": "^3.0.2" + } + }, + "bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "requires": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, "buffer": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", - "dev": true, "requires": { "base64-js": "^1.0.2", "ieee754": "^1.1.4" } }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -8513,6 +9079,15 @@ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "clean-stack": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.0.tgz", @@ -8745,6 +9320,17 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "compress-commons": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", + "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", + "requires": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -8791,6 +9377,49 @@ "request": "^2.88.2" } }, + "crc-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", + "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", + "requires": { + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" + } + }, + "crc32-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", + "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", + "requires": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, "create-ts-index": { "version": "1.13.6", "resolved": "https://registry.npmjs.org/create-ts-index/-/create-ts-index-1.13.6.tgz", @@ -8990,7 +9619,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "requires": { "once": "^1.4.0" } @@ -9284,6 +9912,11 @@ } } }, + "exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==" + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -9568,8 +10201,7 @@ "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "fs-extra": { "version": "7.0.1", @@ -9909,8 +10541,7 @@ "ieee754": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", - "dev": true + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, "ignore": { "version": "5.1.8", @@ -10373,6 +11004,43 @@ "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "requires": { + "readable-stream": "^2.0.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "lcov-parse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", @@ -10446,12 +11114,32 @@ "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, "lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, "lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", @@ -10475,6 +11163,11 @@ "lodash._reinterpolate": "^3.0.0" } }, + "lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" + }, "log-driver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", @@ -10578,6 +11271,16 @@ "integrity": "sha512-NqRSh2+LlN2NInpqTQnS614Y/3NkVMFFU6sJlRFEpxJ/LHuK/qJECH7/fXZjk4VZstPW/Pevjil/VtSONsLc7Q==", "dev": true }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, "memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -10979,6 +11682,11 @@ "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==", "dev": true }, + "node-stream-zip": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.13.5.tgz", + "integrity": "sha512-Lfi9xhSNvnJU35+4ZFlECXKJ70etAgJYWAVCdcEpksPnMrgwNqwkCJqdunoViVoFFV38C7AIodYE+2apuoK9Gw==" + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -11002,8 +11710,7 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "npm-run-path": { "version": "2.0.2", @@ -11273,6 +11980,18 @@ "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", "dev": true }, + "pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -11320,6 +12039,11 @@ "integrity": "sha512-rFA1lnek1FYkMGthm4xBKME41qUKItTovuo24bCGZu/Vu1n3gW71UPLAkIdwewwkZCe29gRVweSOPXvAdckFuw==", "dev": true }, + "printj": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==" + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -11456,6 +12180,14 @@ "util-deprecate": "^1.0.1" } }, + "readdir-glob": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.1.tgz", + "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", + "requires": { + "minimatch": "^3.0.4" + } + }, "readdirp": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", @@ -11653,6 +12385,15 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -12002,6 +12743,23 @@ } } }, + "symbol-hd-wallets": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/symbol-hd-wallets/-/symbol-hd-wallets-0.14.1.tgz", + "integrity": "sha512-jMYbVHlGtB9IiufME4nGDKEvYXz7e6YfBK0eulGhetMH/0TZ+qIomCmNaMU2EfJEeywo0Kb6TIMIN4AEq9ObBA==", + "requires": { + "bip32": "^1.0.4", + "bip39": "^3.0.2", + "bip44-constants": "^8.0.5", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "crypto-js": "^4.0.0", + "js-sha3": "^0.8.0", + "tiny-secp256k1": "^1.1.3", + "tweetnacl": "^1.0.3" + } + }, "symbol-openapi-typescript-fetch-client": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/symbol-openapi-typescript-fetch-client/-/symbol-openapi-typescript-fetch-client-0.11.2.tgz", @@ -12106,12 +12864,11 @@ } }, "tar-stream": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.3.tgz", - "integrity": "sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA==", - "dev": true, + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "requires": { - "bl": "^4.0.1", + "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", @@ -12150,6 +12907,18 @@ "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" }, + "tiny-secp256k1": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz", + "integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==", + "requires": { + "bindings": "^1.3.0", + "bn.js": "^4.11.8", + "create-hmac": "^1.1.7", + "elliptic": "^6.4.0", + "nan": "^2.13.2" + } + }, "tmp": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", @@ -12322,6 +13091,11 @@ "integrity": "sha512-fIS001cAYHkyQPidWXmHuhs8usjP5XVJjWB8oZGqkTowZaz3v7g3KDZeeqE82FBrmkAnIBOY3jgy7lnPnqATbA==", "dev": true }, + "typeforce": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", + "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" + }, "typescript": { "version": "3.9.7", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", @@ -12522,6 +13296,14 @@ } } }, + "wif": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", + "integrity": "sha1-CNP1IFbGZnkplyb63g1DKudLRwQ=", + "requires": { + "bs58check": "<3.0.0" + } + }, "winston": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz", @@ -12771,6 +13553,16 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true + }, + "zip-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", + "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", + "requires": { + "archiver-utils": "^2.1.0", + "compress-commons": "^4.1.0", + "readable-stream": "^3.6.0" + } } } } diff --git a/package.json b/package.json index 8648a263f..14ef1a0b6 100644 --- a/package.json +++ b/package.json @@ -16,14 +16,17 @@ "handlebars": "^4.7.7", "inquirer": "^7.3.3", "js-yaml": "^3.14.0", + "archiver": "^5.2.0", "lodash": "^4.17.21", "memorystream": "^0.3.1", "noble-ed25519": "^1.0.3", "node-forge": "^0.10.0", + "node-stream-zip": "^1.12.0", "rxjs": "^6.6.3", "semver": "^7.3.5", "shx": "^0.3.2", "sshpk": "^1.16.1", + "symbol-hd-wallets": "^0.14.1-alpha-202103052158", "symbol-sdk": "^1.0.1", "tslib": "^1.13.0", "utf8": "^2.1.2", @@ -32,6 +35,8 @@ "devDependencies": { "@oclif/dev-cli": "^1.22.2", "@oclif/test": "^1.2.8", + "@types/archiver": "^5.1.0", + "@types/bip32": "^1.0.2", "@types/chai": "^4.2.12", "@types/figlet": "^1.2.0", "@types/handlebars": "^4.1.0", @@ -57,6 +62,7 @@ "marked": ">=2.0.0", "mocha": "^8.3.2", "mocha-lcov-reporter": "^1.3.0", + "mock-stdin": "^1.0.0", "nyc": "^15.1.0", "prettier": "^2.0.5", "prettier-plugin-organize-imports": "^1.1.1", diff --git a/src/commands/clean.ts b/src/commands/clean.ts index 7fec7dbde..eb6aa901b 100644 --- a/src/commands/clean.ts +++ b/src/commands/clean.ts @@ -15,8 +15,7 @@ */ import { Command } from '@oclif/command'; -import { BootstrapUtils } from '../service'; -import { CommandUtils } from '../service/CommandUtils'; +import { BootstrapUtils, CommandUtils } from '../service'; export default class Clean extends Command { static description = 'It removes the target folder deleting the generated configuration and data'; diff --git a/src/commands/compose.ts b/src/commands/compose.ts index 9b21ca1a5..8f9b56e68 100644 --- a/src/commands/compose.ts +++ b/src/commands/compose.ts @@ -15,8 +15,7 @@ */ import { Command, flags } from '@oclif/command'; -import { BootstrapService, BootstrapUtils, ComposeService } from '../service'; -import { CommandUtils } from '../service/CommandUtils'; +import { BootstrapService, BootstrapUtils, CommandUtils, ComposeService } from '../service'; export default class Compose extends Command { static description = 'It generates the `docker-compose.yml` file from the configured network.'; diff --git a/src/commands/pack.ts b/src/commands/pack.ts new file mode 100644 index 000000000..7a4b30901 --- /dev/null +++ b/src/commands/pack.ts @@ -0,0 +1,118 @@ +/* + * Copyright 2021 NEM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Command, flags } from '@oclif/command'; +import { existsSync } from 'fs'; +import { prompt } from 'inquirer'; +import { dirname, join } from 'path'; +import { BootstrapService, BootstrapUtils, CommandUtils, CryptoUtils, ZipItem, ZipUtils } from '../service'; +import Clean from './clean'; +import Compose from './compose'; +import Config from './config'; + +export default class Pack extends Command { + static description = 'It configures and packages your node into a zip file that can be uploaded to the final node machine.'; + + static examples = [ + `$ symbol-bootstrap pack`, + `$ symbol-bootstrap pack -p bootstrap -c custom-preset.yml`, + `$ symbol-bootstrap pack -p testnet -a dual -c custom-preset.yml`, + `$ symbol-bootstrap pack -p mainnet -a dual --password 1234 -c custom-preset.yml`, + `$ echo "$MY_ENV_VAR_PASSWORD" | symbol-bootstrap pack -p mainnet -a dual -c custom-preset.yml`, + ]; + + static flags = { + ...Compose.flags, + ...Clean.flags, + ...Config.resolveFlags(true), + ready: flags.boolean({ + description: 'If --ready is provided, the command will not ask offline confirmation.', + }), + }; + + public async run(): Promise { + const { flags } = this.parse(Pack); + BootstrapUtils.showBanner(); + const preset = flags.preset; + const assembly = flags.assembly; + const targetZip = join(dirname(flags.target), `${preset}-${assembly || 'default'}-node.zip`); + + if (existsSync(targetZip)) { + throw new Error( + `The target zip file ${targetZip} already exist. Do you want to delete it before repackaging your target folder?`, + ); + } + console.log(); + console.log(); + if ( + !flags.ready && + !( + await prompt([ + { + name: 'offlineNow', + message: `Symbol Bootstrap is about to start working with sensitive information (certificates and voting file generation) so it is highly recommended that you disconnect from the network before continuing. Say YES if you are offline or if you don't care.`, + type: 'confirm', + default: true, + }, + ]) + ).offlineNow + ) { + console.log('Come back when you are offline...'); + return; + } + + flags.password = await CommandUtils.resolvePassword( + flags.password, + flags.noPassword, + CommandUtils.passwordPromptDefaultMessage, + true, + ); + const service = await new BootstrapService(this.config.root); + const configOnlyCustomPresetFileName = 'config-only-custom-preset.yml'; + const configResult = await service.config(flags); + await service.compose(flags, configResult.presetData); + + const noPrivateKeyTempFile = 'custom-preset-temp.temp'; + + if (flags.customPreset) { + await BootstrapUtils.writeYaml( + noPrivateKeyTempFile, + CryptoUtils.removePrivateKeys(BootstrapUtils.loadYaml(flags.customPreset, flags.password)), + flags.password, + ); + } else { + await BootstrapUtils.writeYaml(noPrivateKeyTempFile, {}, flags.password); + } + const zipItems: ZipItem[] = [ + { + from: flags.target, + to: 'target', + directory: true, + }, + { + from: noPrivateKeyTempFile, + to: configOnlyCustomPresetFileName, + directory: false, + }, + ]; + + await ZipUtils.zip(targetZip, zipItems); + await BootstrapUtils.deleteFile(noPrivateKeyTempFile); + console.log(); + console.log(`Zip file ${targetZip} has been created. You can unzip it in your node's machine and run:`); + console.log(`$ symbol-bootstrap start -p ${preset}${assembly ? ` -a ${assembly}` : ''} -c ${configOnlyCustomPresetFileName}`); + } +} diff --git a/src/commands/wizard.ts b/src/commands/wizard.ts new file mode 100644 index 000000000..036259b2a --- /dev/null +++ b/src/commands/wizard.ts @@ -0,0 +1,743 @@ +/* + * Copyright 2021 NEM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Command, flags } from '@oclif/command'; +import { IOptionFlag } from '@oclif/command/lib/flags'; +import { existsSync } from 'fs'; +import { prompt } from 'inquirer'; +import { ExtendedKey, MnemonicPassPhrase, Network as SeedNetwork, Wallet } from 'symbol-hd-wallets'; +import { Account, NetworkType, PublicAccount } from 'symbol-sdk'; +import { CustomPreset, PrivateKeySecurityMode } from '../model'; +import { BootstrapService, BootstrapUtils, CommandUtils, ConfigLoader, ConfigService, KeyName, Preset, RewardProgram } from '../service'; + +export const assemblies: Record = { + [Preset.bootstrap]: [ + { value: '', description: 'Default: A network with 2 peers, a api, a broker, a mongo db, and a Rest Gateway' }, + { value: 'full', description: 'Full: A complete network with a private Explorer, Faucet and Wallet' }, + { value: 'light', description: 'Light: A light network with a dual, a mongo dn and a rest gateway' }, + ], + [Preset.mainnet]: [ + { value: 'dual', description: 'Dual Node' }, + { value: 'peer', description: 'Peer Node' }, + { value: 'api', description: 'Api Node' }, + ], + [Preset.testnet]: [ + { value: 'dual', description: 'Dual Node' }, + { value: 'peer', description: 'Peer Node' }, + { value: 'api', description: 'Api Node' }, + ], +}; +export enum Network { + mainnet = 'mainnet', + testnet = 'testnet', + privateNetwork = 'privateNetwork', +} + +export enum ImportType { + PRIVATE_KEYS = 'privateKeys', + OPTIN_PAPER_WALLET = 'optinPaperWallet', + SYMBOL_PAPER_WALLET = 'symbolPaperWallet', +} + +export interface DerivedAccount { + optinMode: boolean; + networkType: NetworkType; + account: Account; + accountIndex: number; + changeIndex: number; + mnemonicPassPhrase: MnemonicPassPhrase; +} +export interface ProvidedAccounts { + seeded: boolean; + main: Account; + remote: Account; + vrf: Account; + transport: Account; + agent?: Account; +} + +export const networkToPreset: Record = { + [Network.privateNetwork]: Preset.bootstrap, + [Network.mainnet]: Preset.mainnet, + [Network.testnet]: Preset.testnet, +}; +export default class Wizard extends Command { + static description = 'An utility command that will help you configuring node!'; + + static examples = [`$ symbol-bootstrap wizard`]; + + static flags = { + help: CommandUtils.helpFlag, + target: CommandUtils.targetFlag, + password: CommandUtils.passwordFlag, + noPassword: CommandUtils.noPasswordFlag, + network: Wizard.getNetworkIdFlag(), + customPreset: Wizard.getCustomPresetFile(), + ready: flags.boolean({ + description: 'If --ready is provided, the command will not ask offline confirmation.', + }), + }; + + public async run(): Promise { + const flags = this.parse(Wizard).flags; + return Wizard.execute(this.config.root, flags); + } + + public static async execute( + root: string, + flags: { + noPassword: boolean; + skipPull?: boolean; + target: string; + password: string | undefined; + network: Network | undefined; + customPreset: string; + ready?: boolean; + }, + ): Promise { + BootstrapUtils.showBanner(); + console.log('Welcome to the Symbol Bootstrap wizard! This command will:'); + console.log(' - Guide you through the configuration process.'); + console.log(' - Import Paper Wallet seeds.'); + console.log(` - Create a custom preset and show you the way to launch your node!`); + console.log(); + const target = flags.target; + + const customPresetFile = flags.customPreset; + if (existsSync(customPresetFile)) { + throw new Error(`${customPresetFile} already exist!!! You should move the file somewhere else before overwriting it!`); + } + if (existsSync(target)) { + throw new Error( + 'There is currently a ./target folder here!!!! Have you executed bootstrap already? You should move the folder somewhere else before overwriting it!', + ); + } + + const network = await Wizard.resolveNetwork(flags.network); + const preset = networkToPreset[network]; + const assembly = await Wizard.resolveAssembly(preset); + if (network == Network.privateNetwork) { + console.log('For a private network, just run: '); + console.log(''); + console.log(`$ symbol-bootstrap start -b ${preset}${assembly ? ` -a ${assembly}` : ''}`); + return; + } + + if (!flags.skipPull) { + const service = await new BootstrapService(); + console.log(); + console.log('Pulling catapult tools image before asking to go offline...'); + console.log(); + ConfigLoader.presetInfoLogged = true; + await BootstrapUtils.pullImage( + service.resolveConfigPreset({ + ...ConfigService.defaultParams, + preset: preset, + assembly: assembly, + target: target, + }).symbolServerImage, + ); + } + console.log(); + console.log(); + if ( + !flags.ready && + !( + await prompt([ + { + name: 'offlineNow', + message: `Symbol Bootstrap is about to start working with sensitive information (private keys or mnemonic phrases) so it is highly recommended that you disconnect from the network before continuing. Say YES if you are offline or if you don't care.`, + type: 'confirm', + default: true, + }, + ]) + ).offlineNow + ) { + console.log('Come back when you are offline...'); + return; + } + + console.log(); + console.log( + 'Symbol bootstrap needs to provide the node with a number of key pairs (Read more at https://docs.symbolplatform.com/concepts/cryptography.html#symbol-keys).', + ); + console.log(`If you don't know what a key is used for, let Symbol Bootstrap generate a new one for you.`); + + const password = await CommandUtils.resolvePassword( + flags.password, + flags.noPassword, + CommandUtils.passwordPromptDefaultMessage, + false, + ); + + const rewardProgram = assembly === 'dual' ? await Wizard.resolveRewardProgram() : undefined; + + const networkType = network === Network.mainnet ? NetworkType.MAIN_NET : NetworkType.TEST_NET; + const accounts = await Wizard.resolveAccounts(networkType, rewardProgram); + + console.log(); + console.log(`These are your node's accounts:`); + Wizard.logAccount(accounts.main, KeyName.Main, false); + Wizard.logAccount(accounts.vrf, KeyName.VRF, false); + Wizard.logAccount(accounts.remote, KeyName.Remote, false); + Wizard.logAccount(accounts.transport, KeyName.Transport, false); + Wizard.logAccount(accounts.agent, KeyName.Agent, false); + console.log(); + console.log(); + + const symbolHostRequired = !!rewardProgram; + const host = await Wizard.resolveHost( + `Enter the public hostname or IP of your future node. ${ + symbolHostRequired ? 'This value is required when you are in a reward program!' : '' + }`, + symbolHostRequired, + ); + const friendlyName = await Wizard.resolveFriendlyName(host || accounts.main.publicKey.substr(0, 7)); + const privateKeySecurityMode = await Wizard.resolvePrivateKeySecurityMode(); + const voting = await Wizard.isVoting(); + const presetContent: CustomPreset = { + assembly: assembly, + preset: preset, + privateKeySecurityMode: privateKeySecurityMode, + nodes: [ + { + host: host, + voting: voting, + friendlyName: friendlyName, + rewardProgram: rewardProgram, + mainPrivateKey: accounts.main.privateKey, + vrfPrivateKey: accounts.vrf.privateKey, + remotePrivateKey: accounts.remote.privateKey, + transportPrivateKey: accounts.transport.privateKey, + agentPrivateKey: accounts.agent?.privateKey, + }, + ], + }; + const defaultParams = ConfigService.defaultParams; + await BootstrapUtils.writeYaml(customPresetFile, presetContent, password); + console.log(); + console.log(); + console.log(`The Symbol Bootstrap preset file '${customPresetFile}' has been created!!!. Keep this safe!`); + console.log(); + console.log(`To decrypt the node's private key, run: `); + console.log(); + console.log(`$ symbol-bootstrap decrypt --source ${customPresetFile} --destination plain-custom-preset.yml`); + console.log(); + console.log('Remember to delete the plain-custom-preset.yml file after used!!!'); + + console.log( + `You can edit this file to further customize it. Read more https://github.com/nemtech/symbol-bootstrap/blob/main/docs/presetGuides.md`, + ); + console.log(); + console.log(`Once you have finished the custom preset customization, You can use the 'start' to run the node in this machine:`); + console.log(); + console.log( + `$ symbol-bootstrap start -p ${network} -a ${assembly} -c ${customPresetFile} ${ + target !== defaultParams.target ? `-t ${target}` : '' + }`, + ); + + console.log(); + console.log(`Alternatively, to create a zip file that can be deployed in your node machine you can use the 'pack' command:`); + console.log(); + console.log( + `$ symbol-bootstrap pack -p ${network} -a ${assembly} -c ${customPresetFile} ${ + target !== defaultParams.target ? `-t ${target}` : '' + }`, + ); + console.log(); + console.log( + `Once the target folder is created, Bootstrap will use the protected and encrypted addresses.yml, and preset.yml in inside the target folder.`, + ); + console.log( + 'To upgrade your node version or configuration, use the --upgrade parameter in config, compose, start and/or pack. Remember to backup the node`s target folder!', + ); + console.log( + 'Hint: You can change the configuration of an already created node by proving a new custom preset. This is an experimental feature, backup the target folder before!', + ); + console.log(); + console.log('To complete the registration, you need to link your keys (online):'); + console.log(); + console.log(`$ symbol-bootstrap link --useKnownRestGateways -c ${customPresetFile}`); + if (rewardProgram == RewardProgram.SuperNode) { + console.log(); + console.log('To enroll to the supernode program, run (online):'); + console.log(); + console.log(`$ symbol-bootstrap enrollRewardProgram --useKnownRestGateways -c ${customPresetFile}`); + } + console.log(); + } + public static logAccount(account: T, keyName: KeyName, showPrivateKeys: boolean): T { + if (account === undefined) { + return account; + } + const privateKeyText = showPrivateKeys && account instanceof Account ? `\n\tPrivate Key: ${account.privateKey}` : ''; + console.log(` - ${keyName}:\n\tAddress: ${account.address.plain()}\n\tPublic Key: ${account.publicKey}${privateKeyText}`); + return account as T; + } + + private static async resolveAccounts(networkType: NetworkType, rewardProgram: RewardProgram | undefined): Promise { + while (true) { + const importMode = await Wizard.resolveImportMode(); + if (importMode === ImportType.OPTIN_PAPER_WALLET) { + { + const derivedAccount = await Wizard.resolveDerivedAccount(networkType, true); + if (derivedAccount) { + return await Wizard.resolveAllAccount(networkType, derivedAccount, rewardProgram); + } + } + } else if (importMode === ImportType.SYMBOL_PAPER_WALLET) { + { + const derivedAccount = await Wizard.resolveDerivedAccount(networkType, false); + if (derivedAccount) { + return await Wizard.resolveAllAccount(networkType, derivedAccount, rewardProgram); + } + } + } else if (importMode === ImportType.PRIVATE_KEYS) { + { + return await Wizard.resolveAllAccount(networkType, undefined, rewardProgram); + } + } + } + } + + private static async resolveAllAccount( + networkType: NetworkType, + derivedAccount: DerivedAccount | undefined, + rewardProgram: RewardProgram | undefined, + ): Promise { + console.log(); + return { + seeded: true, + main: derivedAccount + ? derivedAccount.account + : await this.resolveAccountFromSelection( + networkType, + derivedAccount, + KeyName.Main, + 0, + 'It holds the tokens required to give the node its importance.', + ), + transport: await this.resolveAccountFromSelection( + networkType, + derivedAccount, + KeyName.Transport, + 1, + 'It is used by nodes for secure transport over TLS.', + ), + vrf: await this.resolveAccountFromSelection(networkType, derivedAccount, KeyName.VRF, 2, 'It is required for harvesting.'), + remote: await this.resolveAccountFromSelection( + networkType, + derivedAccount, + KeyName.Remote, + 3, + 'It is used to harvest and collect the rewards on behalf of the main account in remote harvesting.', + ), + agent: rewardProgram + ? await this.resolveAccountFromSelection( + networkType, + derivedAccount, + KeyName.Remote, + 4, + 'It is used to create TLS certificates request for the Controller to Agent communication.', + ) + : undefined, + }; + } + + public static async resolveAccountFromSelection( + networkType: NetworkType, + derivedAccount: DerivedAccount | undefined, + keyName: KeyName, + changeIndex: number, + keyDescription: string, + ): Promise { + console.log(`${keyName} Key Pair: ${keyDescription}`); + while (true) { + const keyCreationChoices = []; + keyCreationChoices.push({ name: 'Generating a new account', value: 'generate' }); + keyCreationChoices.push({ name: 'Entering a private key', value: 'manual' }); + if (derivedAccount && !derivedAccount.optinMode) { + keyCreationChoices.push({ name: 'Deriving it from Symbol Paper Wallet seed', value: 'seed' }); + } + const { keyCreationMode } = await prompt([ + { + name: 'keyCreationMode', + message: `How do you want to create the ${keyName} account:`, + type: 'list', + default: keyCreationChoices[0].name, + choices: keyCreationChoices, + }, + ]); + const log = (account: Account, message: string): Account => { + console.log(); + console.log(`Using account ${account.address.plain()} for ${keyName} key. ${message}`); + console.log(); + return account; + }; + if (keyCreationMode == 'generate') { + return log(Account.generateNewAccount(networkType), 'It will be stored in your custom preset. Keep file safe!'); + } + if (keyCreationMode == 'seed' && derivedAccount) { + const seedAccount = Wizard.toAccountFromMnemonicPhrase( + derivedAccount.mnemonicPassPhrase, + networkType, + derivedAccount.optinMode, + derivedAccount.accountIndex, + changeIndex, + ); + return log( + Account.createFromPrivateKey(seedAccount.privateKey, networkType), + 'It will be stored in your custom preset. Can be derived from the paper wallet if lost!', + ); + } + // manual + const account = await Wizard.resolveAccount(networkType, keyName); + if (account) { + return log( + account, + 'It will be stored in your custom preset. You can recreate the account by providing the private key again!', + ); + } + } + } + + public static async resolveAccount(networkType: NetworkType, keyName: KeyName): Promise { + while (true) { + const { privateKey } = await prompt([ + { + name: 'privateKey', + message: `Enter the 64 HEX private key of the ${keyName} account (or press enter to select the option again).`, + type: 'password', + mask: '*', + validate: (value) => { + if (!value) { + return true; + } + return CommandUtils.isValidPrivateKey(value); + }, + }, + ]); + if (!privateKey) { + return undefined; + } else { + const enteredAccount = Account.createFromPrivateKey(privateKey, networkType); + const { ok } = await prompt([ + { + name: 'ok', + message: `Is this the expected address ${enteredAccount.address.plain()} to used as ${keyName} account? `, + type: 'confirm', + default: false, + }, + ]); + if (ok) { + return enteredAccount; + } + } + } + } + + public static async resolveNetwork(providedNetwork: Network | undefined): Promise { + if (!providedNetwork) { + console.log('Select type node or network you want to run:\n'); + const responses = await prompt([ + { + name: 'network', + message: 'Select a network:', + type: 'list', + default: Network.mainnet, + choices: [ + { name: 'Mainnet Node', value: Network.mainnet }, + { name: 'Testnet Node', value: Network.testnet }, + { name: 'Private Network', value: Network.privateNetwork }, + ], + }, + ]); + return responses.network; + } + return providedNetwork; + } + + public static async resolvePrivateKeySecurityMode(): Promise { + const { mode } = await prompt([ + { + name: 'mode', + message: 'Select the type of security you want to use:', + type: 'list', + default: PrivateKeySecurityMode.PROMPT_MAIN_TRANSPORT, + choices: [ + { + name: + 'PROMPT_MAIN: Bootstrap may ask for the Main private key when doing certificates upgrades. Other keys are encrypted. Recommended for Supernodes.', + value: PrivateKeySecurityMode.PROMPT_MAIN, + }, + { + name: + 'PROMPT_MAIN_TRANSPORT: Bootstrap may ask for the Main and Transport private keys when regenerating certificates and agent configuration. Other keys are encrypted. Recommended for regular nodes', + value: PrivateKeySecurityMode.PROMPT_MAIN_TRANSPORT, + }, + { name: 'ENCRYPT: All keys are encrypted, only password would be asked', value: PrivateKeySecurityMode.ENCRYPT }, + ], + }, + ]); + return mode; + } + + public static async resolveAssembly(preset: Preset): Promise { + console.log('Select the assembly to be created:\n'); + const responses = await prompt([ + { + name: 'assembly', + message: 'Select an assembly:', + type: 'list', + default: assemblies[preset][0].value, + choices: assemblies[preset].map(({ value, description }) => ({ + value: value, + name: description, + })), + }, + ]); + return responses.assembly; + } + + public static async resolveImportMode(): Promise { + const responses = await prompt([ + { + name: 'mode', + message: 'How do you want to import your accounts?', + type: 'list', + default: ImportType.PRIVATE_KEYS, + choices: [ + { + value: ImportType.PRIVATE_KEYS, + name: 'Private Keys: The private Keys will be generated or entered.', + }, + { + value: ImportType.OPTIN_PAPER_WALLET, + name: 'OptIn Paper Wallet (Pre-Launch): Only the main private key can be restored. Other keys will be generated.', + }, + { + value: ImportType.SYMBOL_PAPER_WALLET, + name: 'Symbol Paper Wallet (Post-Launch): The main and secondary keys can be restored from the paper.', + }, + ], + }, + ]); + return responses.mode; + } + private static async isVoting(): Promise { + console.log( + 'Select whether your Symbol node should be a Voting node. Note: A Voting node requires the main account to hold at least 3 million XYMs. ', + ); + console.log('If your node does not have enough XYMs its Voting key may not be included. '); + const { voting } = await prompt([ + { + name: 'voting', + message: 'Are you creating a Voting node?', + type: 'confirm', + default: false, + }, + ]); + return voting; + } + + public static getNetworkIdFlag(): IOptionFlag { + return flags.string({ + description: 'The node or network you want to create', + options: [Network.mainnet, Network.testnet, Network.privateNetwork], + }) as IOptionFlag; + } + + public static getCustomPresetFile(): IOptionFlag { + return flags.string({ char: 'c', description: 'The custom preset to be created.', default: 'custom-preset.yml' }); + } + + public static isValidPhrase(input: string): boolean | string { + if (!input) { + return true; + } + const words = input.trim().split(' ').length; + return words === 24 ? true : `Invalid phrase. It must have 24 words but got ${words}.`; + } + + public static async resolveDerivedAccount(networkType: NetworkType, optinMode: boolean): Promise { + try { + console.log( + 'To generate the keys, enter the 24 words of the mnemonic phrase created when you opted in.\nYou can find them in the Paper Wallet. They will not be stored anywhere by this tool.\n', + ); + let lastEnteredPhrase = ''; + while (true) { + const phraseResponse = await prompt([ + { + name: 'value', + message: `Enter the mnemonic phrase for you Main account (or press enter to select the option again).`, + type: 'input', + default: lastEnteredPhrase || undefined, + validate: (input) => Wizard.isValidPhrase(input), + }, + ]); + if (!phraseResponse.value) { + return undefined; + } + lastEnteredPhrase = phraseResponse.value.trim(); + const mnemonicPassPhrase = new MnemonicPassPhrase(lastEnteredPhrase); + const derivedAccounts = Wizard.findDerivedAccounts(mnemonicPassPhrase, networkType, optinMode, undefined, 0); + + const choices = derivedAccounts.map((derivedAccounts) => ({ + value: derivedAccounts.account.address.plain(), + name: derivedAccounts.account.address.plain(), + })); + choices.push({ + value: 'none', + name: 'None of the above. Re-enter phrase', + }); + const accountResponse = await prompt([ + { + name: 'accountAddress', + message: 'Select an account:', + type: 'list', + default: choices[0].value, + choices: choices, + }, + ]); + const derivedAccount = derivedAccounts.find( + (derivedAccount) => derivedAccount.account.address.plain() == accountResponse.accountAddress, + ); + if (derivedAccount) { + return derivedAccount; + } + } + } catch (e) { + throw new Error(`Symbol account cannot be created from phrase: ${e.message}`); + } + } + + private static findDerivedAccounts( + mnemonicPassPhrase: MnemonicPassPhrase, + networkType: NetworkType, + optinMode: boolean, + expectedAccountIndex: number | undefined, + expectedChangeIndex: number | undefined, + ): DerivedAccount[] { + const accountIndexes: number[] = expectedAccountIndex === undefined ? Array.from(Array(20).keys()) : [expectedAccountIndex]; + const changeIndexes: number[] = expectedChangeIndex === undefined ? Array.from(Array(20).keys()) : [expectedChangeIndex]; + + const derivedAccounts: DerivedAccount[] = []; + for (const accountIndex of accountIndexes) { + for (const changeIndex of changeIndexes) { + const account = Wizard.toAccountFromMnemonicPhrase(mnemonicPassPhrase, networkType, optinMode, accountIndex, changeIndex); + derivedAccounts.push({ networkType, optinMode, account, accountIndex, changeIndex, mnemonicPassPhrase }); + } + } + return derivedAccounts; + } + + public static toAccountFromMnemonicPhrase( + mnemonicPassPhrase: MnemonicPassPhrase, + networkType: NetworkType, + optinMode: boolean, + accountIndex: number, + changeIndex: number, + ): Account { + const coinIndex = networkType === NetworkType.MAIN_NET ? '4343' : '1'; + const mnemonicSeed = mnemonicPassPhrase.toSeed().toString('hex'); + const seedNetwork = optinMode ? SeedNetwork.BITCOIN : SeedNetwork.SYMBOL; + const extendedKey = ExtendedKey.createFromSeed(mnemonicSeed, seedNetwork); + const wallet = new Wallet(extendedKey); + const path = `m/44'/${coinIndex}'/${accountIndex}'/${changeIndex}'/0'`; + const privateKey = wallet.getChildAccountPrivateKey(path); + return Account.createFromPrivateKey(privateKey, networkType); + } + + public static async resolveRewardProgram(): Promise { + const { value } = await prompt([ + { + name: 'value', + message: 'Select your Symbol Reward Program:', + type: 'list', + default: 'None', + choices: [ + { name: 'None. Just a standard node.', value: 'None' }, + { name: RewardProgram.SuperNode, value: RewardProgram.SuperNode }, + { + name: RewardProgram.EarlyAdoption + ' (only if you have pre-enrolled using the Nis1 Transfer transaction)', + value: RewardProgram.EarlyAdoption, + }, + { + name: RewardProgram.Ecosystem + ' (only if you have pre-enrolled using the Nis1 Transfer transaction)', + value: RewardProgram.Ecosystem, + }, + ], + }, + ]); + if (value === 'None') { + return undefined; + } + return value; + } + + public static async resolveHost(message: string, required: boolean): Promise { + const { host } = await prompt([ + { + name: 'host', + message: message, + type: 'input', + validate: (value) => { + if (!required && !value) { + return true; + } + return Wizard.isValidHost(value); + }, + }, + ]); + return host || undefined; + } + + public static isValidHost(input: string): boolean | string { + if (input.trim() == '') { + return 'Host is required.'; + } + if (input.length > 50) { + return `Input (${input.length}) is larger than 50`; + } + const valid = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$|^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$|^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/.test( + input, + ); + return valid ? true : `It's not a valid IP or hostname`; + } + + public static isValidFriendlyName(input: string): boolean | string { + if (input.trim() == '') { + return 'Friendly name is required.'; + } + if (input.length > 30) { + return `Input (${input.length}) is larger than 30`; + } + return true; + } + public static async resolveFriendlyName(defaultFriendlyName: string): Promise { + const { friendlyName } = await prompt([ + { + name: 'friendlyName', + message: `Enter the friendly name of your node.`, + type: 'input', + default: defaultFriendlyName, + validate: Wizard.isValidFriendlyName, + }, + ]); + return friendlyName; + } +} diff --git a/src/service/BootstrapService.ts b/src/service/BootstrapService.ts index 62e535d10..35e13e2ea 100644 --- a/src/service/BootstrapService.ts +++ b/src/service/BootstrapService.ts @@ -40,6 +40,15 @@ export class BootstrapService { return new ConfigService(this.root, config).run(); } + /** + * It resolves the preset used for preventive configuration. + * + * @param config the params of the config command. + */ + public resolveConfigPreset(config: ConfigParams = ConfigService.defaultParams): ConfigPreset { + return new ConfigService(this.root, config).resolveConfigPreset(false); + } + /** * It generates the docker-compose.yaml file from the previously generated configuration. * diff --git a/src/service/BootstrapUtils.ts b/src/service/BootstrapUtils.ts index a02bb43cd..bfe98bcd6 100644 --- a/src/service/BootstrapUtils.ts +++ b/src/service/BootstrapUtils.ts @@ -31,7 +31,7 @@ import { import * as Handlebars from 'handlebars'; import { get } from 'https'; import * as _ from 'lodash'; -import { platform, totalmem } from 'os'; +import { totalmem } from 'os'; import { basename, dirname, join, resolve } from 'path'; import { Convert, DtoMapping, NetworkType } from 'symbol-sdk'; import * as util from 'util'; @@ -183,7 +183,7 @@ export class BootstrapUtils { } public static logSameLineMessage(message: string): void { - process.stdout.write(platform() == 'win32' ? '\\033[0G' : '\r'); + process.stdout.write(BootstrapUtils.isWindows() ? '\x1b[0G' : '\r'); process.stdout.write(message); } diff --git a/src/service/ConfigService.ts b/src/service/ConfigService.ts index 50555f1e8..043a8c976 100644 --- a/src/service/ConfigService.ts +++ b/src/service/ConfigService.ts @@ -102,6 +102,16 @@ export class ConfigService { this.configLoader = new ConfigLoader(); } + public resolveConfigPreset(password: Password): ConfigPreset { + const target = this.params.target; + const presetLocation = this.configLoader.getGeneratedPresetLocation(target); + if (fs.existsSync(presetLocation) && !this.params.upgrade) { + return this.configLoader.loadExistingPresetData(target, password); + } + const oldPresetData = this.configLoader.loadExistingPresetDataIfPreset(target, password); + return this.resolveCurrentPresetData(oldPresetData, password); + } + public async run(): Promise { const target = this.params.target; try { diff --git a/src/service/ZipUtils.ts b/src/service/ZipUtils.ts new file mode 100644 index 000000000..21e5da987 --- /dev/null +++ b/src/service/ZipUtils.ts @@ -0,0 +1,128 @@ +/* + * Copyright 2021 NEM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as archiver from 'archiver'; +import { createWriteStream } from 'fs'; +import * as StreamZip from 'node-stream-zip'; +import { LogType } from '../logger'; +import Logger from '../logger/Logger'; +import LoggerFactory from '../logger/LoggerFactory'; +import { BootstrapUtils } from './BootstrapUtils'; + +export interface ZipItem { + from: string; + directory: boolean; + to: string; + blacklist?: string[]; +} +const logger: Logger = LoggerFactory.getLogger(LogType.System); + +export class ZipUtils { + public static async zip(destination: string, items: ZipItem[]): Promise { + const output = createWriteStream(destination); + const archive = archiver('zip', { + zlib: { level: 9 }, // Sets the compression level. + }); + archive.pipe(output); + return new Promise(async (resolve, reject) => { + output.on('close', () => { + console.log(''); + console.info(`Zip file ${destination} size ${Math.floor(archive.pointer() / 1024)} MB has been created.`); + resolve(); + }); + + output.on('end', () => { + console.log(''); + console.log('Data has been drained'); + }); + + // good practice to catch warnings (ie stat failures and other non-blocking errors) + archive.on('warning', (err: any) => { + console.log(''); + if (err.code === 'ENOENT') { + // log warning + console.log(`There has been an warning creating ZIP file '${destination}' ${err.message || err}`); + } else { + // throw error + console.log(`There has been an error creating ZIP file '${destination}' ${err.message || err}`); + reject(err); + } + }); + + // good practice to catch this error explicitly + archive.on('error', function (err: any) { + console.log(`There has been an error creating ZIP file '${destination}' ${err.message || err}`); + reject(err); + }); + + for (const item of items) { + if (item.directory) { + archive.directory(item.from, item.to || false, (entry) => { + if (item.blacklist?.find((s) => entry.name === s)) { + return false; + } + return entry; + }); + } else { + archive.file(item.from, { name: item.to }); + } + } + archive.on('progress', (progress) => { + const message = `${progress.entries.processed} entries zipped!`; + BootstrapUtils.logSameLineMessage(message); + }); + await archive.finalize(); + }); + } + + public static unzip(zipFile: string, innerFolder: string, targetFolder: string): Promise { + const zip = new StreamZip({ + file: zipFile, + storeEntries: true, + }); + logger.info(`Unzipping Backup Sync's '${innerFolder}' into '${targetFolder}'. This could take a while!`); + let totalFiles = 0; + let process = 0; + return new Promise((resolve, reject) => { + zip.on('entry', (entry) => { + if (!entry.isDirectory && totalFiles) { + process++; + const percentage = ((process * 100) / totalFiles).toFixed(2); + const message = `${percentage}% | ${process} files unzipped out of ${totalFiles}`; + BootstrapUtils.logSameLineMessage(message); + } + if (BootstrapUtils.stopProcess) { + zip.close(); + console.log(); + reject(new Error('Process cancelled!')); + } + }); + zip.on('ready', () => { + totalFiles = zip.entriesCount; + zip.extract(innerFolder, targetFolder, (err) => { + zip.close(); + if (err) { + console.log(); + reject(err); + } else { + console.log(); + logger.info(`Unzipped '${targetFolder}' created`); + resolve(); + } + }); + }); + }); + } +} diff --git a/src/service/index.ts b/src/service/index.ts index dcd0c07fa..4032214c3 100644 --- a/src/service/index.ts +++ b/src/service/index.ts @@ -22,3 +22,4 @@ export * from './SshpkService'; export * from './VerifyService'; export * from './VotingService'; export * from './VotingUtils'; +export * from './ZipUtils'; diff --git a/test/commands/Wizard.test.ts b/test/commands/Wizard.test.ts new file mode 100644 index 000000000..bb02c263e --- /dev/null +++ b/test/commands/Wizard.test.ts @@ -0,0 +1,185 @@ +/* + * Copyright 2021 NEM + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from '@oclif/test'; +import { stdin } from 'mock-stdin'; +import Wizard, { Network } from '../../src/commands/wizard'; +import { CustomPreset, PrivateKeySecurityMode } from '../../src/model'; +import { BootstrapUtils, Preset, RewardProgram } from '../../src/service'; + +export const StdUtils = { + keys: Object.freeze({ + up: '\u001b[A', + down: '\u001b[B', + left: '\u001b[D', + right: '\u001b[C', + }), + in: (responses: string[]) => { + let k = 0; + + const s = stdin(); + function sendAnswer() { + setTimeout(function () { + const text = responses[k]; + if (typeof text !== 'string') { + throw new Error('Should give only text responses ' + JSON.stringify(responses, null, 2)); + } + s.send(text); + k += 1; + if (k < responses.length) { + sendAnswer(); + } + }, 0); + } + + sendAnswer(); + }, +}; + +describe('Wizard', () => { + const testFolder = 'target/wizardTest'; + beforeEach(async () => { + BootstrapUtils.deleteFolder(testFolder); + }); + it('Provide private keys', async () => { + // assembly + StdUtils.in([ + '\n', + '\n', + StdUtils.keys.down, + '\n', + '\n', + StdUtils.keys.down, + '\n', + 'AAA3F0EF0AB368B8D7AC194D52A8CCFA2D5050B80B9C76E4D2F4D4BF2CD461C1\n', + 'y\n', + StdUtils.keys.down, + '\n', + 'BBB3F0EF0AB368B8D7AC194D52A8CCFA2D5050B80B9C76E4D2F4D4BF2CD461C1\n', + 'y\n', + StdUtils.keys.down, + '\n', + 'CCC3F0EF0AB368B8D7AC194D52A8CCFA2D5050B80B9C76E4D2F4D4BF2CD461C1\n', + 'y\n', + StdUtils.keys.down, + '\n', + 'DDD3F0EF0AB368B8D7AC194D52A8CCFA2D5050B80B9C76E4D2F4D4BF2CD461C1\n', + 'y\n', + StdUtils.keys.down, + '\n', + 'EEE3F0EF0AB368B8D7AC194D52A8CCFA2D5050B80B9C76E4D2F4D4BF2CD461C1\n', + 'y\n', + 'myhostname\n', + 'myfriendlyname\n', + '\n', + 'y\n', //Voting! + '\n', + 'n\n', + 'n\n', + ]); + + const password = '11111'; + const customPresetFile = `${testFolder}/wizard-custom.yml`; + await Wizard.execute(BootstrapUtils.resolveRootFolder(), { + customPreset: customPresetFile, + network: Network.mainnet, + noPassword: false, + skipPull: true, + target: `${testFolder}/target`, + password: password, + }); + const expectedCustomPreset: CustomPreset = { + assembly: 'dual', + nodes: [ + { + friendlyName: 'myfriendlyname', + host: 'myhostname', + voting: true, + mainPrivateKey: 'AAA3F0EF0AB368B8D7AC194D52A8CCFA2D5050B80B9C76E4D2F4D4BF2CD461C1', + remotePrivateKey: 'DDD3F0EF0AB368B8D7AC194D52A8CCFA2D5050B80B9C76E4D2F4D4BF2CD461C1', + transportPrivateKey: 'BBB3F0EF0AB368B8D7AC194D52A8CCFA2D5050B80B9C76E4D2F4D4BF2CD461C1', + vrfPrivateKey: 'CCC3F0EF0AB368B8D7AC194D52A8CCFA2D5050B80B9C76E4D2F4D4BF2CD461C1', + agentPrivateKey: 'EEE3F0EF0AB368B8D7AC194D52A8CCFA2D5050B80B9C76E4D2F4D4BF2CD461C1', + rewardProgram: RewardProgram.SuperNode, + }, + ], + preset: Preset.mainnet, + privateKeySecurityMode: PrivateKeySecurityMode.PROMPT_MAIN_TRANSPORT, + }; + const customPreset = BootstrapUtils.loadYaml(customPresetFile, password); + expect(customPreset).deep.eq(expectedCustomPreset); + }); + + it('Generate use seed keys voting', async () => { + // assembly + StdUtils.in([ + '\n', + 'y\n', //Are you offline. + '\n', + StdUtils.keys.down, + StdUtils.keys.down, + '\n', + 'dragon situate error grid farm obtain speak mail creek ridge arrange grid crew box sugar play cram ranch evoke include creek breeze shadow critic', + '\n', // accept seed + '\n', // address selection + StdUtils.keys.down, + StdUtils.keys.down, + '\n', + StdUtils.keys.down, + StdUtils.keys.down, + '\n', + StdUtils.keys.down, + StdUtils.keys.down, + '\n', + 'myhostname\n', + 'myfriendlyname\n', + '\n', + 'y\n', + // '\n', + // '\n', + // 'y\n', + ]); + + const customPresetFile = `${testFolder}/wizard-custom.yml`; + const password = '11111'; + await Wizard.execute(BootstrapUtils.resolveRootFolder(), { + customPreset: customPresetFile, + network: Network.mainnet, + noPassword: false, + skipPull: true, + target: `${testFolder}/target`, + password: password, + }); + const expectedCustomPreset: CustomPreset = { + assembly: 'dual', + nodes: [ + { + friendlyName: 'myfriendlyname', + host: 'myhostname', + mainPrivateKey: '91D8B10CC8F67FB4F42806DAAC59E57A074A999A8FC11F68230AD138AFC2E056', + remotePrivateKey: 'B4CA246A890EA7AF48A0D869398ECD042DDD9C6443F1D29CAB6638D1741F27A2', + transportPrivateKey: '5D487EB256C5D5B5C47E6884B9C22FAE4C8208B3742331369DCD4BE0423A29DF', + voting: true, + vrfPrivateKey: 'B47E0FD09B0D8566EA9058218E6CC57C0431913A9ED41E5D4FE131C54C8D306E', + }, + ], + preset: Preset.mainnet, + privateKeySecurityMode: PrivateKeySecurityMode.PROMPT_MAIN_TRANSPORT, + }; + const customPreset = BootstrapUtils.loadYaml(customPresetFile, password); + expect(customPreset).deep.eq(expectedCustomPreset); + }); +});