diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 019ebdc5..d7e4de4e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,6 +3,8 @@ on: push: branches: - main + - alpha + - beta - '[0-9]+.*' jobs: @@ -19,10 +21,11 @@ jobs: node-version: "18.x" registry-url: "https://registry.npmjs.org" - name: Install dependencies and build - env: - RUDDER_STACK_KEY: ${{ secrets.RUDDER_STACK_KEY }} - RUDDER_STACK_DATAPLANE_URL: ${{ secrets.RUDDER_STACK_DATAPLANE_URL }} - run: npm ci && npm run build && npm run replaceVars + run: npm ci && npm run build + - name: Create .env-public-analytics file + run: | + echo "RUDDER_STACK_KEY=${{ secrets.RUDDER_STACK_KEY }}" > .env-public-analytics + echo "RUDDER_STACK_DATAPLANE_URL=${{ secrets.RUDDER_STACK_DATAPLANE_URL }}" >> .env-public-analytics - name: Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index ac32d116..75477b13 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,3 @@ cache artifacts bin -bin/ diff --git a/.npmignore b/.npmignore index b963d423..973f63fe 100644 --- a/.npmignore +++ b/.npmignore @@ -2,7 +2,8 @@ * # Include specific files -!bin/ +!bin/**/* +!.env-public-analytics !package.json !package-lock.json !LICENSE-MIT diff --git a/README.md b/README.md index 0744b3f6..ca1f1d1e 100644 --- a/README.md +++ b/README.md @@ -23,33 +23,41 @@ You can install this program globally with `npm i -g zksync-cli` or run the comm ## πŸ’» Commands -- `zksync-cli help`: Provides detailed information about each command. +- `zksync-cli deposit`: deposits funds from Ethereum (L1) to zkSync (L2) -- `zksync-cli create {PROJECT_NAME}`: creates a new project in the given project name. If not provided, creates the project in the current folder, although this requires the folder to be empty. +- `zksync-cli withdraw`: withdraws funds from zkSync (L2) to Ethereum (L1) -- `zksync-cli deposit`: deposits funds from L1 to L2 (local, testnet or mainnet). It will ask to enter: network, recipient wallet, amount in ETH (eg 0.1) and the private key of the wallet you're sending the funds from. +- `zksync-cli withdraw-finalize`: finalizes withdrawal of funds from zkSync (L2) to Ethereum (L1) -- `zksync-cli withdraw`: withdraws funds from zkSync 2.0 to L1 (Goerli testnet). It will ask to enter: network, recipient wallet, amount in ETH (eg 0.1) and the private key of the wallet you're sending the funds from. +- `zksync-cli create-project {FOLDER_NAME}`: creates project from template in the specified folder -- `zksync-cli confirm-withdraw`: confirms withdrawal of funds from zkSync 2.0 to L1 (Goerli testnet). It will ask to enter: network, withdrawal transaction address and the private key of the wallet you sent the funds from. +- `zksync-cli help`: Provides information about all supported commands -- `zksync-cli --help`: Provides detailed information about how to use a specific command. Replace with the name of the command you want help with (e.g., create, deposit, withdraw, confirm-withdraw). +- `zksync-cli help `: Provides detailed information about how to use a specific command. Replace with the name of the command you want help with (e.g., create-project, deposit, withdraw, withdraw-finalize) -- `zksync-cli --version`: Returns the current version. +- `zksync-cli --version`: Returns the current version -### βš™οΈ Options (flags) +### πŸ”— Supported chains + +By default zkSync CLI supports Era Testnet and Era Mainnet. You can also use other networks by overwriting L1 and L2 RPC URLs. For example: `zksync-cli deposit --l2-rpc=http://... --l1-rpc=http://...` -- `--l1-rpc-url`: override the default L1 rpc URL when `localnet` is selected as the network. Usage `--l1-rpc-url=http://...`. -- `--l2-rpc-url`: override the default L2 rpc URL when `localnet` is selected as the network. Usage `--l1-rpc-url=http://...`. +If you're using [local setup (dockerized testing node)](https://github.com/matter-labs/local-setup) with default L1 and L2 RPC URLs, you can select `Local Dockerized node` option in the CLI or provide option `--chain local-dockerized`. + +### βš™οΈ Options (flags) - `--zeek`: zeek, the dev cat, will search for an inspirational quote and provide to you at the end of any command. ## πŸ‘©β€πŸ’» Developing new features -### Install and build +### Run in development mode + +1. Install all dependencies with `npm i`. +2. To use CLI in development mode run `NODE_ENV=development npx ts-node --transpile-only src/index.ts`. + +### Building for production 1. Install all dependencies with `npm i`. -2. This project was build with Typescript. Run `npm run build` to compile code in `/src` into `/bin`. +2. This project was build with Typescript. Run `npm run build` to compile the code into `/bin`. 3. You can run your local build with `node ./bin` ### Testing diff --git a/package-lock.json b/package-lock.json index ae21fec1..7e786ebd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,10 +12,13 @@ "@rudderstack/rudder-sdk-node": "^2.0.2", "axios": "^1.3.4", "chalk": "4.1.2", + "commander": "^11.0.0", + "dotenv": "^16.3.1", "ethers": "5.7.2", "figlet": "^1.5.2", "inquirer": "^8.1.4", "node-machine-id": "^1.1.12", + "winston": "^3.10.0", "zksync-web3": "^0.14.3" }, "bin": { @@ -34,12 +37,11 @@ "@types/figlet": "^1.5.6", "@types/inquirer": "^8.0.2", "@types/node": "^18.17.12", - "dotenv": "^16.3.1", "husky": "^8.0.3", "lint-staged": "^14.0.1", "semantic-release": "^18.0.1", "ts-node": "^10.9.1", - "typescript": "^4.8.4" + "typescript": "^4.9.5" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2724,16 +2726,6 @@ "node": ">=0.6" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, "node_modules/bn.js": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", @@ -2785,27 +2777,18 @@ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/bufferutil": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", + "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", + "hasInstallScript": true, + "optional": true, + "peer": true, "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" } }, "node_modules/bull": { @@ -3161,7 +3144,6 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==", - "dev": true, "engines": { "node": ">=16" } @@ -3650,7 +3632,6 @@ "version": "16.3.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", - "dev": true, "engines": { "node": ">=12" }, @@ -5349,6 +5330,95 @@ "node": ">=12.0.0" } }, + "node_modules/inquirer/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/inquirer/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/inquirer/node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/internal-slot": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", @@ -5564,14 +5634,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "engines": { - "node": ">=8" - } - }, "node_modules/is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -5767,17 +5829,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -6325,21 +6376,6 @@ "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", "dev": true }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/log-update": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/log-update/-/log-update-5.0.1.tgz", @@ -6842,6 +6878,18 @@ } } }, + "node_modules/node-gyp-build": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.1.tgz", + "integrity": "sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==", + "optional": true, + "peer": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-gyp-build-optional-packages": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.7.tgz", @@ -7071,16 +7119,19 @@ }, "node_modules/npm/node_modules/@gar/promisify": { "version": "1.1.2", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/@isaacs/string-locale-compare": { "version": "1.1.0", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/@npmcli/arborist": { "version": "2.9.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7126,11 +7177,13 @@ }, "node_modules/npm/node_modules/@npmcli/ci-detect": { "version": "1.3.0", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/@npmcli/config": { "version": "2.3.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7146,6 +7199,7 @@ }, "node_modules/npm/node_modules/@npmcli/disparity-colors": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7157,6 +7211,7 @@ }, "node_modules/npm/node_modules/@npmcli/fs": { "version": "1.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7166,6 +7221,7 @@ }, "node_modules/npm/node_modules/@npmcli/git": { "version": "2.1.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7181,6 +7237,7 @@ }, "node_modules/npm/node_modules/@npmcli/installed-package-contents": { "version": "1.0.7", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7196,6 +7253,7 @@ }, "node_modules/npm/node_modules/@npmcli/map-workspaces": { "version": "1.0.4", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7210,6 +7268,7 @@ }, "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { "version": "1.1.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7220,6 +7279,7 @@ }, "node_modules/npm/node_modules/@npmcli/move-file": { "version": "1.1.2", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7232,16 +7292,19 @@ }, "node_modules/npm/node_modules/@npmcli/name-from-folder": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/@npmcli/node-gyp": { "version": "1.0.2", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/@npmcli/package-json": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7250,6 +7313,7 @@ }, "node_modules/npm/node_modules/@npmcli/promise-spawn": { "version": "1.3.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7258,6 +7322,7 @@ }, "node_modules/npm/node_modules/@npmcli/run-script": { "version": "1.8.6", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7269,6 +7334,7 @@ }, "node_modules/npm/node_modules/@tootallnate/once": { "version": "1.1.2", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7277,11 +7343,13 @@ }, "node_modules/npm/node_modules/abbrev": { "version": "1.1.1", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/agent-base": { "version": "6.0.2", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7293,6 +7361,7 @@ }, "node_modules/npm/node_modules/agentkeepalive": { "version": "4.1.4", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7306,6 +7375,7 @@ }, "node_modules/npm/node_modules/aggregate-error": { "version": "3.1.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7318,6 +7388,7 @@ }, "node_modules/npm/node_modules/ajv": { "version": "6.12.6", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7333,6 +7404,7 @@ }, "node_modules/npm/node_modules/ansi-regex": { "version": "2.1.1", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7341,6 +7413,7 @@ }, "node_modules/npm/node_modules/ansi-styles": { "version": "4.3.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7355,26 +7428,31 @@ }, "node_modules/npm/node_modules/ansicolors": { "version": "0.3.2", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/ansistyles": { "version": "0.1.3", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/aproba": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/archy": { "version": "1.0.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/are-we-there-yet": { "version": "1.1.6", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7387,11 +7465,13 @@ }, "node_modules/npm/node_modules/asap": { "version": "2.0.6", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/asn1": { "version": "0.2.4", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7400,6 +7480,7 @@ }, "node_modules/npm/node_modules/assert-plus": { "version": "1.0.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7408,11 +7489,13 @@ }, "node_modules/npm/node_modules/asynckit": { "version": "0.4.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/aws-sign2": { "version": "0.7.0", + "dev": true, "inBundle": true, "license": "Apache-2.0", "engines": { @@ -7421,16 +7504,19 @@ }, "node_modules/npm/node_modules/aws4": { "version": "1.11.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/balanced-match": { "version": "1.0.2", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/bcrypt-pbkdf": { "version": "1.0.2", + "dev": true, "inBundle": true, "license": "BSD-3-Clause", "dependencies": { @@ -7439,6 +7525,7 @@ }, "node_modules/npm/node_modules/bin-links": { "version": "2.2.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7455,6 +7542,7 @@ }, "node_modules/npm/node_modules/binary-extensions": { "version": "2.2.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7463,6 +7551,7 @@ }, "node_modules/npm/node_modules/brace-expansion": { "version": "1.1.11", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7472,11 +7561,13 @@ }, "node_modules/npm/node_modules/builtins": { "version": "1.0.3", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/cacache": { "version": "15.3.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7505,11 +7596,13 @@ }, "node_modules/npm/node_modules/caseless": { "version": "0.12.0", + "dev": true, "inBundle": true, "license": "Apache-2.0" }, "node_modules/npm/node_modules/chalk": { "version": "4.1.2", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7525,6 +7618,7 @@ }, "node_modules/npm/node_modules/chownr": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -7533,6 +7627,7 @@ }, "node_modules/npm/node_modules/cidr-regex": { "version": "3.1.1", + "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { @@ -7544,6 +7639,7 @@ }, "node_modules/npm/node_modules/clean-stack": { "version": "2.2.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7552,6 +7648,7 @@ }, "node_modules/npm/node_modules/cli-columns": { "version": "3.1.2", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7564,6 +7661,7 @@ }, "node_modules/npm/node_modules/cli-table3": { "version": "0.6.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7579,6 +7677,7 @@ }, "node_modules/npm/node_modules/cli-table3/node_modules/ansi-regex": { "version": "5.0.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7587,6 +7686,7 @@ }, "node_modules/npm/node_modules/cli-table3/node_modules/is-fullwidth-code-point": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7595,6 +7695,7 @@ }, "node_modules/npm/node_modules/cli-table3/node_modules/string-width": { "version": "4.2.2", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7608,6 +7709,7 @@ }, "node_modules/npm/node_modules/cli-table3/node_modules/strip-ansi": { "version": "6.0.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7619,6 +7721,7 @@ }, "node_modules/npm/node_modules/clone": { "version": "1.0.4", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7627,6 +7730,7 @@ }, "node_modules/npm/node_modules/cmd-shim": { "version": "4.1.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7638,6 +7742,7 @@ }, "node_modules/npm/node_modules/code-point-at": { "version": "1.1.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7646,6 +7751,7 @@ }, "node_modules/npm/node_modules/color-convert": { "version": "2.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7657,11 +7763,13 @@ }, "node_modules/npm/node_modules/color-name": { "version": "1.1.4", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/color-support": { "version": "1.1.3", + "dev": true, "inBundle": true, "license": "ISC", "bin": { @@ -7670,14 +7778,17 @@ }, "node_modules/npm/node_modules/colors": { "version": "1.4.0", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "engines": { "node": ">=0.1.90" } }, "node_modules/npm/node_modules/columnify": { "version": "1.5.4", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7687,6 +7798,7 @@ }, "node_modules/npm/node_modules/combined-stream": { "version": "1.0.8", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7698,26 +7810,31 @@ }, "node_modules/npm/node_modules/common-ancestor-path": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/concat-map": { "version": "0.0.1", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/console-control-strings": { "version": "1.1.0", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/core-util-is": { "version": "1.0.2", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/dashdash": { "version": "1.14.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7729,6 +7846,7 @@ }, "node_modules/npm/node_modules/debug": { "version": "4.3.2", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7745,11 +7863,13 @@ }, "node_modules/npm/node_modules/debug/node_modules/ms": { "version": "2.1.2", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/debuglog": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7758,6 +7878,7 @@ }, "node_modules/npm/node_modules/defaults": { "version": "1.0.3", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7766,6 +7887,7 @@ }, "node_modules/npm/node_modules/delayed-stream": { "version": "1.0.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7774,11 +7896,13 @@ }, "node_modules/npm/node_modules/delegates": { "version": "1.0.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/depd": { "version": "1.1.2", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7787,6 +7911,7 @@ }, "node_modules/npm/node_modules/dezalgo": { "version": "1.0.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7796,6 +7921,7 @@ }, "node_modules/npm/node_modules/diff": { "version": "5.0.0", + "dev": true, "inBundle": true, "license": "BSD-3-Clause", "engines": { @@ -7804,6 +7930,7 @@ }, "node_modules/npm/node_modules/ecc-jsbn": { "version": "0.1.2", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7813,19 +7940,23 @@ }, "node_modules/npm/node_modules/emoji-regex": { "version": "8.0.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/encoding": { "version": "0.1.13", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "dependencies": { "iconv-lite": "^0.6.2" } }, "node_modules/npm/node_modules/env-paths": { "version": "2.2.1", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7834,16 +7965,19 @@ }, "node_modules/npm/node_modules/err-code": { "version": "2.0.3", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/extend": { "version": "3.0.2", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/extsprintf": { "version": "1.3.0", + "dev": true, "engines": [ "node >=0.6.0" ], @@ -7852,21 +7986,25 @@ }, "node_modules/npm/node_modules/fast-deep-equal": { "version": "3.1.3", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/fast-json-stable-stringify": { "version": "2.1.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/fastest-levenshtein": { "version": "1.0.12", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/forever-agent": { "version": "0.6.1", + "dev": true, "inBundle": true, "license": "Apache-2.0", "engines": { @@ -7875,6 +8013,7 @@ }, "node_modules/npm/node_modules/fs-minipass": { "version": "2.1.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7886,16 +8025,19 @@ }, "node_modules/npm/node_modules/fs.realpath": { "version": "1.0.0", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/function-bind": { "version": "1.1.1", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/gauge": { "version": "3.0.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7915,6 +8057,7 @@ }, "node_modules/npm/node_modules/getpass": { "version": "0.1.7", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7923,6 +8066,7 @@ }, "node_modules/npm/node_modules/glob": { "version": "7.2.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -7942,11 +8086,13 @@ }, "node_modules/npm/node_modules/graceful-fs": { "version": "4.2.8", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/har-schema": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -7955,6 +8101,7 @@ }, "node_modules/npm/node_modules/har-validator": { "version": "5.1.5", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7967,6 +8114,7 @@ }, "node_modules/npm/node_modules/has": { "version": "1.0.3", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -7978,6 +8126,7 @@ }, "node_modules/npm/node_modules/has-flag": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -7986,11 +8135,13 @@ }, "node_modules/npm/node_modules/has-unicode": { "version": "2.0.1", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/hosted-git-info": { "version": "4.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8002,11 +8153,13 @@ }, "node_modules/npm/node_modules/http-cache-semantics": { "version": "4.1.0", + "dev": true, "inBundle": true, "license": "BSD-2-Clause" }, "node_modules/npm/node_modules/http-proxy-agent": { "version": "4.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8020,6 +8173,7 @@ }, "node_modules/npm/node_modules/http-signature": { "version": "1.2.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8034,6 +8188,7 @@ }, "node_modules/npm/node_modules/https-proxy-agent": { "version": "5.0.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8046,6 +8201,7 @@ }, "node_modules/npm/node_modules/humanize-ms": { "version": "1.2.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8054,8 +8210,10 @@ }, "node_modules/npm/node_modules/iconv-lite": { "version": "0.6.3", + "dev": true, "inBundle": true, "license": "MIT", + "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -8065,6 +8223,7 @@ }, "node_modules/npm/node_modules/ignore-walk": { "version": "3.0.4", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8073,6 +8232,7 @@ }, "node_modules/npm/node_modules/imurmurhash": { "version": "0.1.4", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -8081,6 +8241,7 @@ }, "node_modules/npm/node_modules/indent-string": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -8089,11 +8250,13 @@ }, "node_modules/npm/node_modules/infer-owner": { "version": "1.0.4", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/inflight": { "version": "1.0.6", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8103,11 +8266,13 @@ }, "node_modules/npm/node_modules/inherits": { "version": "2.0.4", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/ini": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC", "engines": { @@ -8116,6 +8281,7 @@ }, "node_modules/npm/node_modules/init-package-json": { "version": "2.0.5", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8133,11 +8299,13 @@ }, "node_modules/npm/node_modules/ip": { "version": "1.1.5", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/ip-regex": { "version": "4.3.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -8146,6 +8314,7 @@ }, "node_modules/npm/node_modules/is-cidr": { "version": "4.0.2", + "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { @@ -8157,6 +8326,7 @@ }, "node_modules/npm/node_modules/is-core-module": { "version": "2.7.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8168,6 +8338,7 @@ }, "node_modules/npm/node_modules/is-fullwidth-code-point": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -8176,45 +8347,54 @@ }, "node_modules/npm/node_modules/is-lambda": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/is-typedarray": { "version": "1.0.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/isexe": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/isstream": { "version": "0.1.2", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/jsbn": { "version": "0.1.1", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/json-parse-even-better-errors": { "version": "2.3.1", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/json-schema": { "version": "0.2.3", + "dev": true, "inBundle": true }, "node_modules/npm/node_modules/json-schema-traverse": { "version": "0.4.1", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/json-stringify-nice": { "version": "1.1.4", + "dev": true, "inBundle": true, "license": "ISC", "funding": { @@ -8223,11 +8403,13 @@ }, "node_modules/npm/node_modules/json-stringify-safe": { "version": "5.0.1", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/jsonparse": { "version": "1.3.1", + "dev": true, "engines": [ "node >= 0.2.0" ], @@ -8236,6 +8418,7 @@ }, "node_modules/npm/node_modules/jsprim": { "version": "1.4.1", + "dev": true, "engines": [ "node >=0.6.0" ], @@ -8250,16 +8433,19 @@ }, "node_modules/npm/node_modules/just-diff": { "version": "3.1.1", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/just-diff-apply": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/libnpmaccess": { "version": "4.0.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8274,6 +8460,7 @@ }, "node_modules/npm/node_modules/libnpmdiff": { "version": "2.0.4", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8292,6 +8479,7 @@ }, "node_modules/npm/node_modules/libnpmexec": { "version": "2.0.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8313,6 +8501,7 @@ }, "node_modules/npm/node_modules/libnpmfund": { "version": "1.1.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8321,6 +8510,7 @@ }, "node_modules/npm/node_modules/libnpmhook": { "version": "6.0.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8333,6 +8523,7 @@ }, "node_modules/npm/node_modules/libnpmorg": { "version": "2.0.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8345,6 +8536,7 @@ }, "node_modules/npm/node_modules/libnpmpack": { "version": "2.0.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8358,6 +8550,7 @@ }, "node_modules/npm/node_modules/libnpmpublish": { "version": "4.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8373,6 +8566,7 @@ }, "node_modules/npm/node_modules/libnpmsearch": { "version": "3.1.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8384,6 +8578,7 @@ }, "node_modules/npm/node_modules/libnpmteam": { "version": "2.0.4", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8396,6 +8591,7 @@ }, "node_modules/npm/node_modules/libnpmversion": { "version": "1.2.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8408,6 +8604,7 @@ }, "node_modules/npm/node_modules/lru-cache": { "version": "6.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8419,6 +8616,7 @@ }, "node_modules/npm/node_modules/make-fetch-happen": { "version": "9.1.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8445,6 +8643,7 @@ }, "node_modules/npm/node_modules/mime-db": { "version": "1.49.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -8453,6 +8652,7 @@ }, "node_modules/npm/node_modules/mime-types": { "version": "2.1.32", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8464,6 +8664,7 @@ }, "node_modules/npm/node_modules/minimatch": { "version": "3.0.4", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8475,6 +8676,7 @@ }, "node_modules/npm/node_modules/minipass": { "version": "3.1.5", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8486,6 +8688,7 @@ }, "node_modules/npm/node_modules/minipass-collect": { "version": "1.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8497,6 +8700,7 @@ }, "node_modules/npm/node_modules/minipass-fetch": { "version": "1.4.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8513,6 +8717,7 @@ }, "node_modules/npm/node_modules/minipass-flush": { "version": "1.0.5", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8524,6 +8729,7 @@ }, "node_modules/npm/node_modules/minipass-json-stream": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8533,6 +8739,7 @@ }, "node_modules/npm/node_modules/minipass-pipeline": { "version": "1.2.4", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8544,6 +8751,7 @@ }, "node_modules/npm/node_modules/minipass-sized": { "version": "1.0.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8555,6 +8763,7 @@ }, "node_modules/npm/node_modules/minizlib": { "version": "2.1.2", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8567,6 +8776,7 @@ }, "node_modules/npm/node_modules/mkdirp": { "version": "1.0.4", + "dev": true, "inBundle": true, "license": "MIT", "bin": { @@ -8578,6 +8788,7 @@ }, "node_modules/npm/node_modules/mkdirp-infer-owner": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8591,16 +8802,19 @@ }, "node_modules/npm/node_modules/ms": { "version": "2.1.3", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/mute-stream": { "version": "0.0.8", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/negotiator": { "version": "0.6.2", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -8609,6 +8823,7 @@ }, "node_modules/npm/node_modules/node-gyp": { "version": "7.1.2", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8632,11 +8847,13 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/aproba": { "version": "1.2.0", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/node-gyp/node_modules/gauge": { "version": "2.7.4", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8652,6 +8869,7 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/is-fullwidth-code-point": { "version": "1.0.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8663,6 +8881,7 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/npmlog": { "version": "4.1.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8674,6 +8893,7 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/string-width": { "version": "1.0.2", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8687,6 +8907,7 @@ }, "node_modules/npm/node_modules/nopt": { "version": "5.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8701,6 +8922,7 @@ }, "node_modules/npm/node_modules/normalize-package-data": { "version": "3.0.3", + "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { @@ -8715,6 +8937,7 @@ }, "node_modules/npm/node_modules/npm-audit-report": { "version": "2.1.5", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8726,6 +8949,7 @@ }, "node_modules/npm/node_modules/npm-bundled": { "version": "1.1.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8734,6 +8958,7 @@ }, "node_modules/npm/node_modules/npm-install-checks": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { @@ -8745,11 +8970,13 @@ }, "node_modules/npm/node_modules/npm-normalize-package-bin": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/npm-package-arg": { "version": "8.1.5", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8763,6 +8990,7 @@ }, "node_modules/npm/node_modules/npm-packlist": { "version": "2.2.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8780,6 +9008,7 @@ }, "node_modules/npm/node_modules/npm-pick-manifest": { "version": "6.1.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8791,6 +9020,7 @@ }, "node_modules/npm/node_modules/npm-profile": { "version": "5.0.4", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8802,6 +9032,7 @@ }, "node_modules/npm/node_modules/npm-registry-fetch": { "version": "11.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8818,11 +9049,13 @@ }, "node_modules/npm/node_modules/npm-user-validate": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "BSD-2-Clause" }, "node_modules/npm/node_modules/npmlog": { "version": "5.0.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8834,6 +9067,7 @@ }, "node_modules/npm/node_modules/npmlog/node_modules/are-we-there-yet": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8846,6 +9080,7 @@ }, "node_modules/npm/node_modules/number-is-nan": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -8854,6 +9089,7 @@ }, "node_modules/npm/node_modules/oauth-sign": { "version": "0.9.0", + "dev": true, "inBundle": true, "license": "Apache-2.0", "engines": { @@ -8862,6 +9098,7 @@ }, "node_modules/npm/node_modules/object-assign": { "version": "4.1.1", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -8870,6 +9107,7 @@ }, "node_modules/npm/node_modules/once": { "version": "1.4.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8878,6 +9116,7 @@ }, "node_modules/npm/node_modules/opener": { "version": "1.5.2", + "dev": true, "inBundle": true, "license": "(WTFPL OR MIT)", "bin": { @@ -8886,6 +9125,7 @@ }, "node_modules/npm/node_modules/p-map": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8900,6 +9140,7 @@ }, "node_modules/npm/node_modules/pacote": { "version": "11.3.5", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8932,6 +9173,7 @@ }, "node_modules/npm/node_modules/parse-conflict-json": { "version": "1.1.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -8942,6 +9184,7 @@ }, "node_modules/npm/node_modules/path-is-absolute": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -8950,16 +9193,19 @@ }, "node_modules/npm/node_modules/performance-now": { "version": "2.1.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/proc-log": { "version": "1.0.0", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/promise-all-reject-late": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "ISC", "funding": { @@ -8968,6 +9214,7 @@ }, "node_modules/npm/node_modules/promise-call-limit": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "ISC", "funding": { @@ -8976,11 +9223,13 @@ }, "node_modules/npm/node_modules/promise-inflight": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/promise-retry": { "version": "2.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -8993,6 +9242,7 @@ }, "node_modules/npm/node_modules/promzard": { "version": "0.3.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -9001,11 +9251,13 @@ }, "node_modules/npm/node_modules/psl": { "version": "1.8.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/punycode": { "version": "2.1.1", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -9014,6 +9266,7 @@ }, "node_modules/npm/node_modules/qrcode-terminal": { "version": "0.12.0", + "dev": true, "inBundle": true, "bin": { "qrcode-terminal": "bin/qrcode-terminal.js" @@ -9021,6 +9274,7 @@ }, "node_modules/npm/node_modules/qs": { "version": "6.5.2", + "dev": true, "inBundle": true, "license": "BSD-3-Clause", "engines": { @@ -9029,6 +9283,7 @@ }, "node_modules/npm/node_modules/read": { "version": "1.0.7", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -9040,11 +9295,13 @@ }, "node_modules/npm/node_modules/read-cmd-shim": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/read-package-json": { "version": "4.1.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -9059,6 +9316,7 @@ }, "node_modules/npm/node_modules/read-package-json-fast": { "version": "2.0.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -9071,6 +9329,7 @@ }, "node_modules/npm/node_modules/readable-stream": { "version": "3.6.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -9084,6 +9343,7 @@ }, "node_modules/npm/node_modules/readdir-scoped-modules": { "version": "1.1.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -9095,6 +9355,7 @@ }, "node_modules/npm/node_modules/request": { "version": "2.88.2", + "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { @@ -9125,6 +9386,7 @@ }, "node_modules/npm/node_modules/request/node_modules/form-data": { "version": "2.3.3", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -9138,6 +9400,7 @@ }, "node_modules/npm/node_modules/request/node_modules/tough-cookie": { "version": "2.5.0", + "dev": true, "inBundle": true, "license": "BSD-3-Clause", "dependencies": { @@ -9150,6 +9413,7 @@ }, "node_modules/npm/node_modules/retry": { "version": "0.12.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -9158,6 +9422,7 @@ }, "node_modules/npm/node_modules/rimraf": { "version": "3.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -9172,6 +9437,7 @@ }, "node_modules/npm/node_modules/safe-buffer": { "version": "5.2.1", + "dev": true, "funding": [ { "type": "github", @@ -9191,11 +9457,13 @@ }, "node_modules/npm/node_modules/safer-buffer": { "version": "2.1.2", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/semver": { "version": "7.3.5", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -9210,16 +9478,19 @@ }, "node_modules/npm/node_modules/set-blocking": { "version": "2.0.0", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/signal-exit": { "version": "3.0.3", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/smart-buffer": { "version": "4.2.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -9229,6 +9500,7 @@ }, "node_modules/npm/node_modules/socks": { "version": "2.6.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -9242,6 +9514,7 @@ }, "node_modules/npm/node_modules/socks-proxy-agent": { "version": "6.1.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -9255,6 +9528,7 @@ }, "node_modules/npm/node_modules/spdx-correct": { "version": "3.1.1", + "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { @@ -9264,11 +9538,13 @@ }, "node_modules/npm/node_modules/spdx-exceptions": { "version": "2.3.0", + "dev": true, "inBundle": true, "license": "CC-BY-3.0" }, "node_modules/npm/node_modules/spdx-expression-parse": { "version": "3.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -9278,11 +9554,13 @@ }, "node_modules/npm/node_modules/spdx-license-ids": { "version": "3.0.10", + "dev": true, "inBundle": true, "license": "CC0-1.0" }, "node_modules/npm/node_modules/sshpk": { "version": "1.16.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -9307,6 +9585,7 @@ }, "node_modules/npm/node_modules/ssri": { "version": "8.0.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -9318,6 +9597,7 @@ }, "node_modules/npm/node_modules/string_decoder": { "version": "1.3.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -9326,6 +9606,7 @@ }, "node_modules/npm/node_modules/string-width": { "version": "2.1.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -9338,6 +9619,7 @@ }, "node_modules/npm/node_modules/string-width/node_modules/ansi-regex": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -9346,6 +9628,7 @@ }, "node_modules/npm/node_modules/string-width/node_modules/strip-ansi": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -9357,11 +9640,13 @@ }, "node_modules/npm/node_modules/stringify-package": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/strip-ansi": { "version": "3.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -9373,6 +9658,7 @@ }, "node_modules/npm/node_modules/supports-color": { "version": "7.2.0", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -9384,6 +9670,7 @@ }, "node_modules/npm/node_modules/tar": { "version": "6.1.11", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -9400,21 +9687,25 @@ }, "node_modules/npm/node_modules/text-table": { "version": "0.2.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/tiny-relative-date": { "version": "1.3.0", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/treeverse": { "version": "1.0.4", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/tunnel-agent": { "version": "0.6.0", + "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { @@ -9426,11 +9717,13 @@ }, "node_modules/npm/node_modules/tweetnacl": { "version": "0.14.5", + "dev": true, "inBundle": true, "license": "Unlicense" }, "node_modules/npm/node_modules/typedarray-to-buffer": { "version": "3.1.5", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -9439,6 +9732,7 @@ }, "node_modules/npm/node_modules/unique-filename": { "version": "1.1.1", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -9447,6 +9741,7 @@ }, "node_modules/npm/node_modules/unique-slug": { "version": "2.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -9455,6 +9750,7 @@ }, "node_modules/npm/node_modules/uri-js": { "version": "4.4.1", + "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { @@ -9463,11 +9759,13 @@ }, "node_modules/npm/node_modules/util-deprecate": { "version": "1.0.2", + "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/uuid": { "version": "3.4.0", + "dev": true, "inBundle": true, "license": "MIT", "bin": { @@ -9476,6 +9774,7 @@ }, "node_modules/npm/node_modules/validate-npm-package-license": { "version": "3.0.4", + "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { @@ -9485,6 +9784,7 @@ }, "node_modules/npm/node_modules/validate-npm-package-name": { "version": "3.0.0", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -9493,6 +9793,7 @@ }, "node_modules/npm/node_modules/verror": { "version": "1.10.0", + "dev": true, "engines": [ "node >=0.6.0" ], @@ -9506,11 +9807,13 @@ }, "node_modules/npm/node_modules/walk-up-path": { "version": "1.0.0", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/wcwidth": { "version": "1.0.1", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -9519,6 +9822,7 @@ }, "node_modules/npm/node_modules/which": { "version": "2.0.2", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -9533,6 +9837,7 @@ }, "node_modules/npm/node_modules/wide-align": { "version": "1.1.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -9541,11 +9846,13 @@ }, "node_modules/npm/node_modules/wrappy": { "version": "1.0.2", + "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/write-file-atomic": { "version": "3.0.3", + "dev": true, "inBundle": true, "license": "ISC", "dependencies": { @@ -9557,6 +9864,7 @@ }, "node_modules/npm/node_modules/yallist": { "version": "4.0.0", + "dev": true, "inBundle": true, "license": "ISC" }, @@ -9716,28 +10024,6 @@ "node": ">= 0.8.0" } }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -11782,6 +12068,20 @@ "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", "dev": true }, + "node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "optional": true, + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -11888,9 +12188,9 @@ } }, "node_modules/winston": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.9.0.tgz", - "integrity": "sha512-jW51iW/X95BCW6MMtZWr2jKQBP4hV5bIDq9QrIjfDk6Q9QuxvTKEAlpUNAzP+HYHFFCeENhph16s0zEunu4uuQ==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.10.0.tgz", + "integrity": "sha512-nT6SIDaE9B7ZRO0u3UvdrimG0HkB7dSTAgInQnNR2SOPJ4bvq5q79+pXLftKmP52lJGW15+H5MCK0nM9D3KB/g==", "dependencies": { "@colors/colors": "1.5.0", "@dabh/diagnostics": "^2.0.2", diff --git a/package.json b/package.json index a56101b0..59d07806 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ }, "scripts": { "build": "tsc -p .", - "replaceVars": "ts-node --transpile-only src/replace.ts bin/analytics.js", "test": "echo \"Error: no test specified\" && exit 1", "lint": "eslint . --ext ./src/* --fix --ignore-path .gitignore --no-error-on-unmatched-pattern --max-warnings=0", "commitlint": "commitlint --edit" @@ -28,10 +27,13 @@ "@rudderstack/rudder-sdk-node": "^2.0.2", "axios": "^1.3.4", "chalk": "4.1.2", + "commander": "^11.0.0", + "dotenv": "^16.3.1", "ethers": "5.7.2", "figlet": "^1.5.2", "inquirer": "^8.1.4", "node-machine-id": "^1.1.12", + "winston": "^3.10.0", "zksync-web3": "^0.14.3" }, "devDependencies": { @@ -47,12 +49,11 @@ "@types/figlet": "^1.5.6", "@types/inquirer": "^8.0.2", "@types/node": "^18.17.12", - "dotenv": "^16.3.1", "husky": "^8.0.3", "lint-staged": "^14.0.1", "semantic-release": "^18.0.1", "ts-node": "^10.9.1", - "typescript": "^4.8.4" + "typescript": "^4.9.5" }, "publishConfig": { "access": "public" diff --git a/src/commands/create-project.ts b/src/commands/create-project.ts new file mode 100644 index 00000000..76c2062d --- /dev/null +++ b/src/commands/create-project.ts @@ -0,0 +1,107 @@ +import { execSync } from "child_process"; +import { Option } from "commander"; +import { prompt } from "inquirer"; +import path from "path"; + +import { zeekOption } from "../common/options"; +import { program } from "../setup"; +import { track } from "../utils/analytics"; +import { optionNameToParam } from "../utils/helpers"; +import Logger from "../utils/logger"; +import zeek from "../utils/zeek"; + +import type { DefaultOptions } from "../common/options"; + +const templates = [ + { + name: "Hardhat + Solidity", + value: "hardhat_solidity", + git: "https://github.com/matter-labs/zksync-hardhat-template", + }, + { + name: "Hardhat + Vyper", + value: "hardhat_vyper", + git: "https://github.com/matter-labs/zksync-hardhat-vyper-template", + }, +]; + +const templateOption = new Option("-t, --template ", "Project template to use").choices( + templates.map((template) => template.value) +); + +const runCommand = (command: string) => { + execSync(`${command}`, { stdio: "inherit" }); +}; + +type CreateOptions = DefaultOptions & { + folderName: string; + template: string; +}; + +program + .command("create-project") + .argument("", "Folder name to create project in") + .description("Creates project from template in the specified folder") + .addOption(templateOption) + .addOption(zeekOption) + .action(async (folderName: string, options: CreateOptions) => { + try { + options = { + ...options, + folderName, + }; + Logger.debug(`Initial create-project options: ${JSON.stringify(options, null, 2)}`); + + const answers: CreateOptions = await prompt( + [ + { + message: templateOption.description, + name: optionNameToParam(templateOption.long!), + type: "list", + choices: templates, + required: true, + }, + ], + options + ); + + options = { + ...options, + ...answers, + }; + + Logger.debug(`Final create-project options: ${JSON.stringify(options, null, 2)}`); + + const template = templates.find((e) => e.value === options.template)!; + + Logger.info(`\nCreating new project from "${template.name}" template at "${path.join(options.folderName, "/")}"`); + runCommand(`git clone ${template.git} ${options.folderName}`); + runCommand(`cd ${options.folderName} && rm -rf -r .git`); // removes .git folder so new repo can be initialized + + Logger.info("\nInstalling dependencies with yarn..."); + runCommand(`cd ${options.folderName} && yarn`); + + Logger.info(`\nAll ready πŸŽ‰πŸŽ‰ + + Run cd ${options.folderName} to enter your project folder. + + Contracts are stored in the /contracts folder. + Deployment scripts go in the /deploy folder. + + - "yarn hardhat compile" to compile your contracts. + - "yarn hardhat deploy-zksync" to deploy your contract (this command accepts a --script option). + + Read the ${path.join(options.folderName, "README.md")} file to learn more. +`); + + track("create", { template: options.template, zeek: options.zeek }); + + if (options.zeek) { + zeek(); + } + } catch (error) { + Logger.error("There was an error while creating new project:"); + Logger.error(error); + track("error", { error }); + } + }); diff --git a/src/commands/deposit.ts b/src/commands/deposit.ts new file mode 100644 index 00000000..efaafd54 --- /dev/null +++ b/src/commands/deposit.ts @@ -0,0 +1,145 @@ +import { prompt } from "inquirer"; + +import { + amountOptionCreate, + chainOption, + l1RpcUrlOption, + l2RpcUrlOption, + privateKeyOption, + recipientOptionCreate, + zeekOption, +} from "../common/options"; +import { l2Chains } from "../data/chains"; +import { program } from "../setup"; +import { track } from "../utils/analytics"; +import { ETH_TOKEN } from "../utils/constants"; +import { bigNumberToDecimal, decimalToBigNumber } from "../utils/formatters"; +import { + getAddressFromPrivateKey, + getL1Provider, + getL2Provider, + getL2Wallet, + optionNameToParam, +} from "../utils/helpers"; +import Logger from "../utils/logger"; +import { isDecimalAmount, isAddress, isPrivateKey } from "../utils/validators"; +import zeek from "../utils/zeek"; + +import type { DefaultTransferOptions } from "../common/options"; + +const amountOption = amountOptionCreate("deposit"); +const recipientOption = recipientOptionCreate("L2"); + +type DepositOptions = DefaultTransferOptions; + +program + .command("deposit") + .description("Deposit ETH from L1 to L2") + .addOption(amountOption) + .addOption(chainOption) + .addOption(recipientOption) + .addOption(l1RpcUrlOption) + .addOption(l2RpcUrlOption) + .addOption(privateKeyOption) + .addOption(zeekOption) + .action(async (options: DepositOptions) => { + try { + Logger.debug( + `Initial deposit options: ${JSON.stringify( + { ...options, ...(options.privateKey ? { privateKey: "" } : {}) }, + null, + 2 + )}` + ); + + const answers: DepositOptions = await prompt( + [ + { + message: chainOption.description, + name: optionNameToParam(chainOption.long!), + type: "list", + choices: l2Chains.filter((e) => e.l1Chain).map((e) => ({ name: e.name, value: e.network })), + required: true, + when(answers: DepositOptions) { + if (answers.l1RpcUrl && answers.l2RpcUrl) { + return false; + } + return true; + }, + }, + { + message: amountOption.description, + name: optionNameToParam(amountOption.long!), + type: "input", + required: true, + validate: (input: string) => isDecimalAmount(input), + }, + { + message: privateKeyOption.description, + name: optionNameToParam(privateKeyOption.long!), + type: "password", + required: true, + validate: (input: string) => isPrivateKey(input), + }, + { + message: recipientOption.description, + name: optionNameToParam(recipientOption.long!), + type: "input", + default: (answers: DepositOptions) => { + return getAddressFromPrivateKey(answers.privateKey); + }, + required: true, + validate: (input: string) => isAddress(input), + }, + ], + options + ); + + options = { + ...options, + ...answers, + }; + + Logger.debug(`Final deposit options: ${JSON.stringify({ ...options, privateKey: "" }, null, 2)}`); + + const fromChain = l2Chains.find((e) => e.network === options.chain)?.l1Chain; + const fromChainLabel = fromChain && !options.l1RpcUrl ? fromChain.name : options.l1RpcUrl ?? "Unknown chain"; + const toChain = l2Chains.find((e) => e.network === options.chain); + const toChainLabel = toChain && !options.l2RpcUrl ? toChain.name : options.l2RpcUrl ?? "Unknown chain"; + + Logger.info("\nDeposit:"); + Logger.info(` From: ${getAddressFromPrivateKey(answers.privateKey)} (${fromChainLabel})`); + Logger.info(` To: ${options.recipient} (${toChainLabel})`); + Logger.info(` Amount: ${bigNumberToDecimal(decimalToBigNumber(options.amount))} ETH`); + + Logger.info("\nSending deposit transaction..."); + + const l1Provider = getL1Provider(options.l1RpcUrl ?? fromChain!.rpcUrl); + const l2Provider = getL2Provider(options.l2RpcUrl ?? toChain!.rpcUrl); + const senderWallet = getL2Wallet(options.privateKey, l2Provider, l1Provider); + + const depositHandle = await senderWallet.deposit({ + to: options.recipient, + token: ETH_TOKEN.l1Address, + amount: decimalToBigNumber(options.amount), + }); + Logger.info("\nDeposit sent:"); + Logger.info(` Transaction hash: ${depositHandle.hash}`); + if (fromChain?.explorerUrl) { + Logger.info(` Transaction link: ${fromChain.explorerUrl}/tx/${depositHandle.hash}`); + } + + track("deposit", { network: toChain?.network ?? "Unknown chain", zeek: options.zeek }); + + const senderBalance = await l1Provider.getBalance(senderWallet.address); + Logger.info(`\nSender L1 balance after transaction: ${bigNumberToDecimal(senderBalance)} ETH`); + + if (options.zeek) { + zeek(); + } + } catch (error) { + Logger.error("There was an error while depositing funds:"); + Logger.error(error); + track("error", { error }); + } + }); diff --git a/src/commands/withdraw-finalize.ts b/src/commands/withdraw-finalize.ts new file mode 100644 index 00000000..7706ae19 --- /dev/null +++ b/src/commands/withdraw-finalize.ts @@ -0,0 +1,138 @@ +import { Option } from "commander"; +import { prompt } from "inquirer"; + +import { l1RpcUrlOption, l2RpcUrlOption, privateKeyOption, zeekOption } from "../common/options"; +import { l2Chains } from "../data/chains"; +import { program } from "../setup"; +import { track } from "../utils/analytics"; +import { bigNumberToDecimal } from "../utils/formatters"; +import { + getAddressFromPrivateKey, + getL1Provider, + getL2Provider, + getL2Wallet, + optionNameToParam, +} from "../utils/helpers"; +import Logger from "../utils/logger"; +import { isPrivateKey, isTransactionHash } from "../utils/validators"; +import zeek from "../utils/zeek"; + +import type { DefaultTransactionOptions } from "../common/options"; + +const chainOption = new Option("-c, --chain ", "Chain to use").choices( + l2Chains.filter((e) => e.l1Chain).map((chain) => chain.network) +); +const transactionHashOption = new Option("--hash ", "L2 withdrawal transaction hash to finalize"); + +type WithdrawFinalizeOptions = DefaultTransactionOptions & { + hash: string; +}; + +program + .command("withdraw-finalize") + .description("Finalizes withdrawal of funds") + .addOption(transactionHashOption) + .addOption(chainOption) + .addOption(l1RpcUrlOption) + .addOption(l2RpcUrlOption) + .addOption(privateKeyOption) + .addOption(zeekOption) + .action(async (options: WithdrawFinalizeOptions) => { + try { + Logger.debug( + `Initial withdraw-finalize options: ${JSON.stringify( + { ...options, ...(options.privateKey ? { privateKey: "" } : {}) }, + null, + 2 + )}` + ); + + const answers: WithdrawFinalizeOptions = await prompt( + [ + { + message: chainOption.description, + name: optionNameToParam(chainOption.long!), + type: "list", + choices: l2Chains.filter((e) => e.l1Chain).map((e) => ({ name: e.name, value: e.network })), + required: true, + when(answers: WithdrawFinalizeOptions) { + if (answers.l1RpcUrl && answers.l2RpcUrl) { + return false; + } + return true; + }, + }, + { + message: transactionHashOption.description, + name: optionNameToParam(transactionHashOption.long!), + type: "input", + required: true, + validate: (input: string) => isTransactionHash(input), + }, + { + message: privateKeyOption.description, + name: optionNameToParam(privateKeyOption.long!), + type: "password", + required: true, + validate: (input: string) => isPrivateKey(input), + }, + ], + options + ); + + options = { + ...options, + ...answers, + }; + + Logger.debug( + `Final withdraw-finalize options: ${JSON.stringify({ ...options, privateKey: "" }, null, 2)}` + ); + + const fromChain = l2Chains.find((e) => e.network === options.chain); + const fromChainLabel = fromChain && !options.l2RpcUrl ? fromChain.name : options.l2RpcUrl ?? "Unknown chain"; + const toChain = l2Chains.find((e) => e.network === options.chain)?.l1Chain; + const toChainLabel = toChain && !options.l1RpcUrl ? toChain.name : options.l1RpcUrl ?? "Unknown chain"; + + Logger.info("\nWithdraw finalize:"); + Logger.info(` From chain: ${fromChainLabel}`); + Logger.info(` To chain: ${toChainLabel}`); + Logger.info(` Withdrawal transaction (L2): ${options.hash}`); + Logger.info(` Finalizer address (L1): ${getAddressFromPrivateKey(answers.privateKey)}`); + + const l1Provider = getL1Provider(options.l1RpcUrl ?? toChain!.rpcUrl); + const l2Provider = getL2Provider(options.l2RpcUrl ?? fromChain!.rpcUrl); + const senderWallet = getL2Wallet(options.privateKey, l2Provider, l1Provider); + + Logger.info("\nChecking status of the transaction..."); + const l2Details = await l2Provider.getTransactionDetails(options.hash); + if (!l2Details.ethExecuteTxHash) { + Logger.info( + `\nTransaction is still being processed on ${fromChainLabel}, please try again when the ethExecuteTxHash has been computed` + ); + Logger.info(`L2 Transaction Details: ${JSON.stringify(l2Details, null, 2)}`); + return; + } + + Logger.info("\nSending finalization transaction..."); + const finalizationHandle = await senderWallet.finalizeWithdrawal(options.hash); + Logger.info("\nWithdrawal finalized:"); + Logger.info(` Finalization transaction hash: ${finalizationHandle.hash}`); + if (fromChain?.explorerUrl) { + Logger.info(` Transaction link: ${fromChain.explorerUrl}/tx/${finalizationHandle.hash}`); + } + + track("confirm-withdraw", { network: toChain?.network ?? "Unknown chain", zeek: options.zeek }); + + const senderBalance = await l1Provider.getBalance(senderWallet.address); + Logger.info(`\nSender L1 balance after transaction: ${bigNumberToDecimal(senderBalance)} ETH`); + + if (options.zeek) { + zeek(); + } + } catch (error) { + Logger.error("There was an error while finalizing withdrawal:"); + Logger.error(error); + track("error", { error }); + } + }); diff --git a/src/commands/withdraw.ts b/src/commands/withdraw.ts new file mode 100644 index 00000000..8354e20e --- /dev/null +++ b/src/commands/withdraw.ts @@ -0,0 +1,145 @@ +import { prompt } from "inquirer"; + +import { + amountOptionCreate, + chainOption, + l1RpcUrlOption, + l2RpcUrlOption, + privateKeyOption, + recipientOptionCreate, + zeekOption, +} from "../common/options"; +import { l2Chains } from "../data/chains"; +import { program } from "../setup"; +import { track } from "../utils/analytics"; +import { ETH_TOKEN } from "../utils/constants"; +import { bigNumberToDecimal, decimalToBigNumber } from "../utils/formatters"; +import { + getAddressFromPrivateKey, + getL1Provider, + getL2Provider, + getL2Wallet, + optionNameToParam, +} from "../utils/helpers"; +import Logger from "../utils/logger"; +import { isDecimalAmount, isAddress, isPrivateKey } from "../utils/validators"; +import zeek from "../utils/zeek"; + +import type { DefaultTransferOptions } from "../common/options"; + +const amountOption = amountOptionCreate("withdraw"); +const recipientOption = recipientOptionCreate("L1"); + +type WithdrawOptions = DefaultTransferOptions; + +program + .command("withdraw") + .description("Withdraw ETH from L2 to L1") + .addOption(amountOption) + .addOption(chainOption) + .addOption(recipientOption) + .addOption(l1RpcUrlOption) + .addOption(l2RpcUrlOption) + .addOption(privateKeyOption) + .addOption(zeekOption) + .action(async (options: WithdrawOptions) => { + try { + Logger.debug( + `Initial withdraw options: ${JSON.stringify( + { ...options, ...(options.privateKey ? { privateKey: "" } : {}) }, + null, + 2 + )}` + ); + + const answers: WithdrawOptions = await prompt( + [ + { + message: chainOption.description, + name: optionNameToParam(chainOption.long!), + type: "list", + choices: l2Chains.filter((e) => e.l1Chain).map((e) => ({ name: e.name, value: e.network })), + required: true, + when(answers: WithdrawOptions) { + if (answers.l1RpcUrl && answers.l2RpcUrl) { + return false; + } + return true; + }, + }, + { + message: amountOption.description, + name: optionNameToParam(amountOption.long!), + type: "input", + required: true, + validate: (input: string) => isDecimalAmount(input), + }, + { + message: privateKeyOption.description, + name: optionNameToParam(privateKeyOption.long!), + type: "password", + required: true, + validate: (input: string) => isPrivateKey(input), + }, + { + message: recipientOption.description, + name: optionNameToParam(recipientOption.long!), + type: "input", + default: (answers: WithdrawOptions) => { + return getAddressFromPrivateKey(answers.privateKey); + }, + required: true, + validate: (input: string) => isAddress(input), + }, + ], + options + ); + + options = { + ...options, + ...answers, + }; + + Logger.debug(`Final withdraw options: ${JSON.stringify({ ...options, privateKey: "" }, null, 2)}`); + + const fromChain = l2Chains.find((e) => e.network === options.chain); + const fromChainLabel = fromChain && !options.l2RpcUrl ? fromChain.name : options.l2RpcUrl ?? "Unknown chain"; + const toChain = l2Chains.find((e) => e.network === options.chain)?.l1Chain; + const toChainLabel = toChain && !options.l1RpcUrl ? toChain.name : options.l1RpcUrl ?? "Unknown chain"; + + Logger.info("\nWithdraw:"); + Logger.info(` From: ${getAddressFromPrivateKey(answers.privateKey)} (${fromChainLabel})`); + Logger.info(` To: ${options.recipient} (${toChainLabel})`); + Logger.info(` Amount: ${bigNumberToDecimal(decimalToBigNumber(options.amount))} ETH`); + + Logger.info("\nSending withdraw transaction..."); + + const l1Provider = getL1Provider(options.l1RpcUrl ?? toChain!.rpcUrl); + const l2Provider = getL2Provider(options.l2RpcUrl ?? fromChain!.rpcUrl); + const senderWallet = getL2Wallet(options.privateKey, l2Provider, l1Provider); + + const withdrawHandle = await senderWallet.withdraw({ + to: options.recipient, + token: ETH_TOKEN.l1Address, + amount: decimalToBigNumber(options.amount), + }); + Logger.info("\nWithdraw sent:"); + Logger.info(` Transaction hash: ${withdrawHandle.hash}`); + if (fromChain?.explorerUrl) { + Logger.info(` Transaction link: ${fromChain.explorerUrl}/tx/${withdrawHandle.hash}`); + } + + track("withdraw", { network: toChain?.network ?? "Unknown chain", zeek: options.zeek }); + + const senderBalance = await l2Provider.getBalance(senderWallet.address); + Logger.info(`\nSender L2 balance after transaction: ${bigNumberToDecimal(senderBalance)} ETH`); + + if (options.zeek) { + zeek(); + } + } catch (error) { + Logger.error("There was an error while withdrawing funds:"); + Logger.error(error); + track("error", { error }); + } + }); diff --git a/src/common/options.ts b/src/common/options.ts new file mode 100644 index 00000000..4bf589eb --- /dev/null +++ b/src/common/options.ts @@ -0,0 +1,32 @@ +import { Option } from "commander"; + +import { l2Chains } from "../data/chains"; + +export const chainOption = new Option("-c, --chain ", "Chain to use").choices( + l2Chains.filter((e) => e.l1Chain).map((chain) => chain.network) +); +export const l1RpcUrlOption = new Option("--l1-rpc, --l1-rpc-url ", "Override L1 RPC URL"); +export const l2RpcUrlOption = new Option("--l2-rpc, --l2-rpc-url ", "Override L2 RPC URL"); +export const privateKeyOption = new Option("-pk, --private-key ", "Private key of the sender"); +export const amountOptionCreate = (action: string) => + new Option("--amount ", `Amount of ETH to ${action} (eg. 0.1)`); +export const recipientOptionCreate = (recipientLocation: string) => + new Option("--recipient
", `Recipient address on ${recipientLocation} (0x address)`); +export const zeekOption = new Option( + "--zeek", + "zeek, the dev cat, will search for an inspirational quote and provide to you at the end of any command" +).hideHelp(); + +export type DefaultOptions = { + zeek?: boolean; +}; +export type DefaultTransactionOptions = DefaultOptions & { + chain?: string; + l1RpcUrl: string; + l2RpcUrl: string; + privateKey: string; +}; +export type DefaultTransferOptions = DefaultTransactionOptions & { + amount: string; + recipient: string; +}; diff --git a/src/confirm-withdraw.ts b/src/confirm-withdraw.ts deleted file mode 100644 index 13f21c9a..00000000 --- a/src/confirm-withdraw.ts +++ /dev/null @@ -1,107 +0,0 @@ -import chalk from "chalk"; -import * as ethers from "ethers"; -import inquirer from "inquirer"; -import { Wallet, Provider } from "zksync-web3"; - -import { track } from "./analytics"; - -import type { Answers, QuestionCollection } from "inquirer"; - -// Used for `zksync-cli confirm-withdraw --help` -export const help = () => { - console.log(chalk.bold("Usage:")); - console.log("zksync-cli confirm-withdraw --l1-rpc-url= --l2-rpc-url=\n"); - console.log(chalk.bold("Description:")); - console.log( - "Confirms the withdrawal of funds from zkSync to L1. The command will ask for the network, the zkSync transaction address, and the sender's private key.\n" - ); - console.log(chalk.bold("Options:")); - console.log(chalk.greenBright("--l1-rpc-url=")); - console.log("The URL of the L1 RPC provider.\n"); - console.log(chalk.greenBright("--l2-rpc-url=")); - console.log("The URL of the L2 RPC provider.\n"); -}; - -export default async function (zeek?: boolean, l1RpcUrl?: string | undefined, l2RpcUrl?: string | undefined) { - console.log(chalk.magentaBright("Confirm withdrawal funds from zkSync to Layer 1")); - - const questions: QuestionCollection = [ - { - message: "Network:", - name: "network", - type: "list", - choices: ["testnet", "mainnet", "localnet"], - default: "testnet", - }, - { - message: "zkSync transaction hash:", - name: "transactionHash", - type: "input", - }, - { - message: "Private key of the sender:", - name: "key", - type: "password", - }, - ]; - - const results: Answers = await inquirer.prompt(questions); - - console.log( - chalk.magentaBright(`Confirming withdrawal of ${results.transactionHash} from zkSync to L1 on ${results.network}`) - ); - - let ethProviderUrl; - let zksyncProviderUrl; - - switch (results.network) { - case "mainnet": - ethProviderUrl = "mainnet"; - zksyncProviderUrl = "https://mainnet.era.zksync.io"; - break; - case "testnet": - ethProviderUrl = "goerli"; - zksyncProviderUrl = "https://testnet.era.zksync.dev"; - break; - case "localnet": - ethProviderUrl = !l1RpcUrl ? "http://localhost:8545" : l1RpcUrl; - zksyncProviderUrl = !l2RpcUrl ? "http://localhost:3050" : l2RpcUrl; - break; - default: - throw `Unsupported network ${results.network}`; - } - - // Init the L1/L2 providers - let L1Provider; - // dynamically change provider class for local or testnet/mainnet - results.network == "localnet" - ? (L1Provider = new ethers.providers.JsonRpcProvider(ethProviderUrl)) - : (L1Provider = ethers.getDefaultProvider(ethProviderUrl)); - - const zkSyncProvider = new Provider(zksyncProviderUrl); - // Initialize the wallet. - const wallet = new Wallet(results.key, zkSyncProvider, L1Provider); - - // Get transaction details. - const l2Details = await zkSyncProvider.getTransactionDetails(results.transactionHash); - if (l2Details.ethExecuteTxHash == undefined || l2Details.ethExecuteTxHash == "") { - console.log( - chalk.magentaBright( - `Transaction ${results.transactionHash} is still being processed, please try again when the ethExecuteTxHash has been computed` - ) - ); - console.log(chalk.magentaBright(`L2 Transaction Details: ${l2Details}`)); - return; - } - - try { - await wallet.finalizeWithdrawal(results.transactionHash); - console.log(chalk.magentaBright("Withdrawal confirmed πŸ’ΈπŸ’ΈπŸ’Έ")); - await track("confirm-withdraw", { zeek, network: results.network }); - } catch (error) { - console.log(chalk.magentaBright("Confirmation of withdrawal unsuccessful")); - await track("error", { error }); - } - - // ends -} diff --git a/src/create.ts b/src/create.ts deleted file mode 100644 index 5b14754a..00000000 --- a/src/create.ts +++ /dev/null @@ -1,104 +0,0 @@ -import chalk from "chalk"; -import { execSync } from "child_process"; -import inquirer from "inquirer"; - -import { track } from "./analytics"; - -import type { Answers, QuestionCollection } from "inquirer"; - -/** - * Runs CLI commands - * @param {*} command String command to run - */ -const runCommand = (command: string) => { - try { - // runs given command and prints its output to console - execSync(`${command}`, { stdio: "inherit" }); - } catch (error) { - console.error("Failed to run command: ", error); - return false; - } - return true; -}; - -// Used for `zksync-cli create --help` -export const help = () => { - console.log(chalk.bold("Usage:")); - console.log("zksync-cli create \n"); - console.log(chalk.bold("Description:")); - console.log( - "Creates a new project in the provided folder. If no folder is specified, it will create the project in the current folder, provided it's empty.\n" - ); -}; - -export default async function (projectName: string, zeek?: boolean) { - const questions: QuestionCollection = [ - { - message: "Project template:", - name: "template", - type: "list", - choices: ["Hardhat + Solidity", "Hardhat + Vyper"], - default: "Hardhat + Solidity", - }, - ]; - - const answers: Answers = await inquirer.prompt(questions); - - let repoUrl; - - switch (answers.template) { - case "Hardhat + Vyper": - repoUrl = "https://github.com/matter-labs/zksync-hardhat-vyper-template"; - break; - default: - repoUrl = "https://github.com/matter-labs/zksync-hardhat-template"; - break; - } - - await track("create", { zeek, template: answers.template }); - - // clones repo inside the given project name folder - const cloneGitTemplate = `git clone ${repoUrl} ${projectName}`; - - // changes dir and installs deps with Yarn - const installDeps = `cd ${projectName} && yarn`; - - // removes .git folder so new repo can be initialised - const cleanup = `cd ${projectName} && rm -f -r .git`; - - console.log(chalk.magentaBright(`Creating a zkSync ${answers.template} project...`)); - - console.log(chalk.magentaBright(`Initialising project with name ${projectName}`)); - - const cloned = runCommand(cloneGitTemplate); - - if (!cloned) process.exit(-1); - // runs cleanup - const cleaned = runCommand(cleanup); - if (!cleaned) process.exit(-1); - - console.log(chalk.magentaBright("Installing dependencies with yarn...")); - - const depsInstalled = runCommand(installDeps); - if (!depsInstalled) process.exit(-1); - - console.log(chalk.magentaBright("Dependencies installed")); - - console.log(`All ready πŸŽ‰πŸŽ‰ - - Run cd ${projectName} to enter your project folder. - - Contracts are stored in the /contracts folder. - Deployment scripts go in the /deploy folder. - - Run ${chalk.magentaBright("yarn hardhat compile")} to compile your contracts. - Run ${chalk.magentaBright( - "yarn hardhat deploy-zksync" - )} to deploy your contract (this command accepts a --script option). - - Run ${chalk.magentaBright("git init")} to initialise a new repository. - - Read the ${chalk.magentaBright("README")} file to learn more. - - `); -} diff --git a/src/data/chains.ts b/src/data/chains.ts new file mode 100644 index 00000000..8f5d3113 --- /dev/null +++ b/src/data/chains.ts @@ -0,0 +1,60 @@ +type Chain = { + id: number; + name: string; + network: string; + rpcUrl: string; + explorerUrl?: string; +}; + +const mainnet: Chain = { + id: 1, + name: "Ethereum Mainnet", + network: "mainnet", + rpcUrl: "https://cloudflare-eth.com", + explorerUrl: "https://etherscan.io", +}; +const goerli: Chain = { + id: 5, + name: "Ethereum Goerli", + network: "goerli", + rpcUrl: "https://rpc.ankr.com/eth_goerli", + explorerUrl: "https://goerli.etherscan.io", +}; + +type L2Chain = Chain & { l1Chain?: Chain }; +export const l2Chains: L2Chain[] = [ + { + id: 280, + name: "zkSync Era Testnet", + network: "era-testnet", + rpcUrl: "https://testnet.era.zksync.dev", + explorerUrl: "https://goerli.explorer.zksync.io", + l1Chain: goerli, + }, + { + id: 324, + name: "zkSync Era Mainnet", + network: "era-mainnet", + rpcUrl: "https://mainnet.era.zksync.io", + explorerUrl: "https://explorer.zksync.io", + l1Chain: mainnet, + }, + { + id: 260, + name: "Local In-memory node", + network: "local-in-memory", + rpcUrl: "http://localhost:8011", + }, + { + id: 270, + name: "Local Dockerized node", + network: "local-dockerized", + rpcUrl: "http://localhost:3050", + l1Chain: { + id: 9, + name: "L1 Local", + network: "l1-local", + rpcUrl: "http://localhost:8545", + }, + }, +]; diff --git a/src/deposit.ts b/src/deposit.ts deleted file mode 100644 index 106c3fba..00000000 --- a/src/deposit.ts +++ /dev/null @@ -1,127 +0,0 @@ -import chalk from "chalk"; -import * as ethers from "ethers"; -import inquirer from "inquirer"; -import { Wallet, Provider, utils } from "zksync-web3"; - -import { track } from "./analytics"; -import { checkBalance } from "./utils"; - -import type { Answers, QuestionCollection } from "inquirer"; -import type { PriorityOpResponse } from "zksync-web3/build/src/types"; - -// Used for `zksync-cli deposit --help` -export const help = () => { - console.log(chalk.bold("Usage:")); - console.log("zksync-cli deposit --l1-rpc-url= --l2-rpc-url=\n"); - console.log(chalk.bold("Description:")); - console.log( - "Deposits funds from L1 to L2. The command will ask for the network, the recipient's address, the amount in ETH, and the sender's private key.\n" - ); - console.log(chalk.bold("Options (ONLY for localnet):")); - console.log(chalk.greenBright("--l1-rpc-url=")); - console.log("The URL of the L1 RPC provider.\n"); - console.log(chalk.greenBright("--l2-rpc-url=")); - console.log("The URL of the L2 RPC provider.\n"); -}; - -export default async function (zeek?: boolean, l1RpcUrl?: string | undefined, l2RpcUrl?: string | undefined) { - console.log(chalk.magentaBright("Deposit funds from L1 to zkSync")); - - const questions: QuestionCollection = [ - { - message: "Network:", - name: "network", - type: "list", - choices: ["testnet", "mainnet", "localnet"], - default: "testnet", - }, - - { - message: "Address to deposit funds to:", - name: "to", - type: "input", - }, - - { - message: "Amount in ETH:", - name: "amount", - type: "input", - }, - { - message: "Private key of the sender:", - name: "key", - type: "password", - }, - ]; - - const results: Answers = await inquirer.prompt(questions); - - console.log(chalk.magentaBright(`Depositing ${results.amount}ETH to ${results.to} on ${results.network}`)); - - let ethProviderUrl; - let zksyncProviderUrl; - let etherScanUrl; - let zkSyncExplorerUrl; - - switch (results.network) { - case "mainnet": - ethProviderUrl = "mainnet"; - zksyncProviderUrl = "https://mainnet.era.zksync.io"; - etherScanUrl = "https://etherscan.io/tx/"; - zkSyncExplorerUrl = "https://explorer.zksync.io/address/"; - break; - case "testnet": - ethProviderUrl = "goerli"; - zksyncProviderUrl = "https://testnet.era.zksync.dev"; - etherScanUrl = "https://goerli.etherscan.io/tx/"; - zkSyncExplorerUrl = "https://goerli.explorer.zksync.io/address/"; - break; - case "localnet": - ethProviderUrl = !l1RpcUrl ? "http://localhost:8545" : l1RpcUrl; - zksyncProviderUrl = !l2RpcUrl ? "http://localhost:3050" : l2RpcUrl; - etherScanUrl = "L1 transaction: "; - zkSyncExplorerUrl = "L2 address:"; - break; - default: - throw `Unsupported network ${results.network}`; - } - - try { - // Init the L1/L2 providers - let L1Provider; - // dynamically change provider class for local or testnet/mainnet - results.network == "localnet" - ? (L1Provider = new ethers.providers.JsonRpcProvider(ethProviderUrl)) - : (L1Provider = ethers.getDefaultProvider(ethProviderUrl)); - - const zkSyncProvider = new Provider(zksyncProviderUrl); - // Initialize the wallet. - const wallet = new Wallet(results.key, zkSyncProvider, L1Provider); - await checkBalance(wallet.address, results.amount, L1Provider); - - // Deposit funds to L2 - const depositHandle: PriorityOpResponse = await wallet.deposit({ - to: results.to, - token: utils.ETH_ADDRESS, - amount: ethers.utils.parseEther(results.amount), - }); - - console.log(chalk.magentaBright("Transaction submitted πŸ’ΈπŸ’ΈπŸ’Έ")); - console.log(chalk.magentaBright(`${etherScanUrl}${depositHandle.hash}`)); - console.log(chalk.magentaBright("Your funds will be available in zkSync in a couple of minutes.")); - if (results.network != "localnet") { - console.log( - chalk.magentaBright( - `To check the latest transactions of this wallet on zkSync, visit: ${zkSyncExplorerUrl}${results.to}` - ) - ); - } - await track("deposit", { zeek, network: results.network }); - } catch (error) { - console.error("Error depositing funds πŸ€•"); - console.log(error); - await track("error", { error }); - } - - // ends -} diff --git a/src/help.ts b/src/help.ts deleted file mode 100644 index d56613f2..00000000 --- a/src/help.ts +++ /dev/null @@ -1,30 +0,0 @@ -import chalk from "chalk"; - -import { getPackageVersion } from "./index"; - -export default async function () { - console.log(chalk.greenBright(`zksync-cli version ${getPackageVersion()}`)); - console.log(chalk.bold("\nUsage:")); - console.log("zksync-cli [...args]\n"); - - console.log(chalk.bold("Commands:\n")); - console.log(chalk.greenBright("create ")); - console.log( - "Creates a new project in the provided folder. If no folder is specified, it will create the project in the current folder, provided it's empty.\n" - ); - console.log(chalk.greenBright("deposit")); - console.log( - "Deposits funds from L1 to zkSync Era. It will prompt for the network (localnet, testnet, mainnet), recipient wallet, amount in ETH (e.g., 0.1), and the private key of the wallet sending funds.\n" - ); - console.log(chalk.greenBright("withdraw")); - console.log( - "Withdraws funds from zkSync Era to L1. It will prompt for the network (localnet, testnet, mainnet), recipient wallet, amount in ETH (e.g., 0.1), and the private key of the wallet sending funds.\n" - ); - console.log(chalk.greenBright("confirm-withdraw")); - console.log( - "Confirms the withdrawal of funds from zkSync to Layer 1. It will prompt for the network (localnet, testnet, mainnet), the transaction address of the withdrawal, and the private key of the wallet initiating the confirmation.\n" - ); - - // Exit the process - process.exit(0); -} diff --git a/src/index.ts b/src/index.ts index 10f7000b..a9519adf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,91 +1,10 @@ -import chalk from "chalk"; -import figlet from "figlet"; +#! /usr/bin/env node -import confirmWithdraw, { help as confirmWithdrawalHelp } from "./confirm-withdraw"; -import create, { help as createHelp } from "./create"; -import deposit, { help as depositHelp } from "./deposit"; -import help from "./help"; -import withdraw, { help as withdrawHelp } from "./withdraw"; -import zeek from "./zeek"; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import * as pkg from "../package.json"; +import { program } from "./setup"; -export const getPackageVersion = () => pkg.version; +import "./commands/deposit"; +import "./commands/withdraw"; +import "./commands/withdraw-finalize"; +import "./commands/create-project"; -const availableOptions: string[] = ["create", "deposit", "withdraw", "confirm-withdraw", "help"]; - -// second argument should be the selected option -const option: string = process.argv[2]; - -const main = async () => { - const helpFlag = Boolean(process.argv.filter((arg) => ["--help", "-h"].includes(arg))[0]); - const versionFlag = Boolean(process.argv.filter((arg) => ["--version", "-v"].includes(arg))[0]); - - if (versionFlag) { - console.log(chalk.magentaBright(`zksync-cli version ${getPackageVersion()}`)); - process.exit(0); - } - - if (!availableOptions.includes(option)) { - console.log(`Invalid operation. Available operations are: ${availableOptions}`); - process.exit(-1); - } - - // Starts CLI - - console.log(chalk.magentaBright(figlet.textSync(`zkSync ${option}`, { horizontalLayout: "full" }))); - - const zeekFlag = Boolean(process.argv.filter((arg) => arg === "--zeek")[0]); - const l1RpcUrl = process.argv.filter((arg) => arg.startsWith("--l1-rpc-url")).map((arg) => arg.split("=")[1])[0]; - - const l2RpcUrl = process.argv.filter((arg) => arg.startsWith("--l2-rpc-url")).map((arg) => arg.split("=")[1])[0]; - - if (helpFlag) { - switch (option) { - case "create": - createHelp(); - break; - case "deposit": - depositHelp(); - break; - case "withdraw": - withdrawHelp(); - break; - case "confirm-withdraw": - confirmWithdrawalHelp(); - break; - default: - help(); - break; - } - } else { - // arg 3 is the project name - const projectName = process.argv[3] || "."; - switch (option) { - case "create": - await create(projectName, zeekFlag); - break; - case "deposit": - await deposit(zeekFlag, l1RpcUrl, l2RpcUrl); - break; - case "withdraw": - await withdraw(zeekFlag, l1RpcUrl, l2RpcUrl); - break; - case "confirm-withdraw": - await confirmWithdraw(zeekFlag, l1RpcUrl, l2RpcUrl); - break; - case "help": - help(); - break; - } - } - - if (zeekFlag) { - await zeek(); - } - - process.exit(0); -}; - -main(); +program.parse(); diff --git a/src/replace.ts b/src/replace.ts deleted file mode 100644 index 39ec8b6e..00000000 --- a/src/replace.ts +++ /dev/null @@ -1,66 +0,0 @@ -import * as dotenv from "dotenv"; -import * as fs from "fs"; -import { EOL } from "os"; -import * as readline from "readline"; - -import type stream from "stream"; - -dotenv.config(); -let filename: string = ""; - -if (process.argv.length === 3) { - filename = process.argv[2]; - console.log("Filename:", filename); - - if (!fs.existsSync(filename)) { - console.log("Error: file does not exists"); - process.exit(1); - } - - if (!process.env.RUDDER_STACK_KEY || !process.env.RUDDER_STACK_DATAPLANE_URL) { - console.log("Error: Missing env variables"); - process.exit(1); - } - - // Create copy of file to use for attempted string replacement - Keep file permissions the same - // Fail if file already exists - fs.copyFileSync(filename, filename + "-new", fs.constants.COPYFILE_EXCL); - - const readInterface = readline.createInterface({ - input: fs.createReadStream(filename), - output: fs.createWriteStream(filename + "-new"), - terminal: false, - }); - - let counter = 0; - - readInterface.on("line", function (line) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const output: stream.Writable = this.output; - if (line) { - const regex = /.*process\.env\.([a-zA-Z0-9_]*).*/; - const envArray = line.match(regex); - if (envArray && envArray.length) { - const envValue = process.env[envArray[1]]; - if (envValue) { - line = line.replace(`process.env.${envArray[1]}`, `"${envValue}"`); - counter++; - } - } - output.write(`${line}${EOL}`); - } else { - output.write(EOL); - } - }); - - readInterface.on("close", function () { - // Rename updated version of file to original overwriting original - fs.renameSync(filename + "-new", filename); - console.log(`Succesfully updated ${filename}`); - console.log(`Replaced ${counter} variables`); - }); -} else { - console.log("Error: must receive single argument containing filename"); - console.log("Usage: 'replace-env '"); -} diff --git a/src/setup.ts b/src/setup.ts new file mode 100644 index 00000000..4010cca8 --- /dev/null +++ b/src/setup.ts @@ -0,0 +1,8 @@ +import { Command } from "commander"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import Package from "../package.json"; + +export const program = new Command(); +program.name(Package.name).description(Package.description).version(Package.version).showHelpAfterError(); diff --git a/src/utils.ts b/src/utils.ts deleted file mode 100644 index 9aa045db..00000000 --- a/src/utils.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { utils } from "ethers"; - -import { track } from "./analytics"; - -import type { ethers } from "ethers"; -import type { Provider } from "zksync-web3"; - -export const checkBalance = async function ( - address: string, - amount: string, - provider: Provider | ethers.providers.BaseProvider -) { - const balance = await provider.getBalance(address); - if (utils.parseEther(amount).gte(balance)) { - console.error("Error: Not enough balance πŸ€•"); - await track("error", { error: "Not enough balance" }); - process.exit(-1); - } -}; diff --git a/src/analytics.ts b/src/utils/analytics.ts similarity index 84% rename from src/analytics.ts rename to src/utils/analytics.ts index 77c622e9..9b882fb7 100644 --- a/src/analytics.ts +++ b/src/utils/analytics.ts @@ -1,8 +1,13 @@ import RudderAnalytics from "@rudderstack/rudder-sdk-node"; +import dotenv from "dotenv"; import { machineId } from "node-machine-id"; +import path from "path"; import type { apiObject } from "@rudderstack/rudder-sdk-node"; +const envPath = path.join(__dirname, "../../", ".env-public-analytics"); +dotenv.config({ path: envPath }); + let client: RudderAnalytics | undefined; try { client = new RudderAnalytics(process.env.RUDDER_STACK_KEY!, { diff --git a/src/utils/constants.ts b/src/utils/constants.ts new file mode 100644 index 00000000..2708ef35 --- /dev/null +++ b/src/utils/constants.ts @@ -0,0 +1,10 @@ +import { getAddress } from "ethers/lib/utils"; +import { L2_ETH_TOKEN_ADDRESS } from "zksync-web3/build/src/utils"; + +export const ETH_TOKEN = { + symbol: "ETH", + name: "Ether", + decimals: 18, + address: getAddress(L2_ETH_TOKEN_ADDRESS), + l1Address: "0x0000000000000000000000000000000000000000", +}; diff --git a/src/utils/formatters.ts b/src/utils/formatters.ts new file mode 100644 index 00000000..5e2d26e5 --- /dev/null +++ b/src/utils/formatters.ts @@ -0,0 +1,17 @@ +import { formatUnits, parseUnits } from "ethers/lib/utils"; + +import { ETH_TOKEN } from "../utils/constants"; + +import type { BigNumberish } from "ethers/lib/ethers"; + +export function decimalToBigNumber(amount: string, decimals = ETH_TOKEN.decimals) { + return parseUnits(amount, decimals); +} + +export function bigNumberToDecimal(amount: BigNumberish, decimals = ETH_TOKEN.decimals): string { + const result = formatUnits(amount.toString(), decimals).toString(); + if (result.endsWith(".0")) { + return result.slice(0, -2); + } + return result; +} diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts new file mode 100644 index 00000000..c5de7345 --- /dev/null +++ b/src/utils/helpers.ts @@ -0,0 +1,29 @@ +import { ethers } from "ethers"; +import { computeAddress } from "ethers/lib/utils"; +import { Wallet, Provider } from "zksync-web3"; + +export function optionNameToParam(input: string): string { + // "--l1-rpc-url" => "l1RpcUrl" + const parts = input.replace(/^--/, "").split("-"); + + for (let i = 1; i < parts.length; i++) { + parts[i] = parts[i].charAt(0).toUpperCase() + parts[i].slice(1); + } + + return parts.join(""); +} + +export const getAddressFromPrivateKey = (privateKey: string): string => { + return computeAddress(privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`); +}; + +export const getL1Provider = (l1RpcUrl: string) => { + return new ethers.providers.JsonRpcProvider(l1RpcUrl); +}; +export const getL2Provider = (l2RpcUrl: string) => { + return new Provider(l2RpcUrl); +}; + +export const getL2Wallet = (privateKey: string, l2Provider: Provider, l1Provider?: ethers.providers.Provider) => { + return new Wallet(privateKey, l2Provider, l1Provider); +}; diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 00000000..743917a2 --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,23 @@ +import chalk from "chalk"; +import { format, createLogger, transports } from "winston"; + +const styleLogs = format.printf((info) => { + if (info.level === "error") { + return chalk.redBright("β“˜ " + info.message); + } else if (info.level === "warn") { + return chalk.yellowBright(info.message); + } else if (info.level === "info") { + return chalk.magentaBright(info.message); + } else if (info.level === "debug") { + return chalk.gray(info.message); + } + return info.message; +}); + +const logger = createLogger({ + level: process.env.NODE_ENV === "development" ? "debug" : "info", + format: format.combine(styleLogs), + transports: [new transports.Console()], +}); + +export default logger; diff --git a/src/utils/validators.ts b/src/utils/validators.ts new file mode 100644 index 00000000..63aed28d --- /dev/null +++ b/src/utils/validators.ts @@ -0,0 +1,43 @@ +import { BigNumber, Wallet } from "ethers"; +import { getAddress } from "ethers/lib/utils"; + +import { ETH_TOKEN } from "./constants"; +import { decimalToBigNumber } from "./formatters"; + +export const isDecimalAmount = (amount: string, decimals = ETH_TOKEN.decimals) => { + try { + if (BigNumber.isBigNumber(decimalToBigNumber(amount, decimals))) { + return true; + } + } catch { + // ignore since we return error message below + } + return "Incorrect amount"; +}; + +export const isAddress = (address: string) => { + try { + return Boolean(getAddress(address)); + } catch (e) { + return "Incorrect address"; + } +}; + +export const isTransactionHash = (s: string) => { + const valid = /^0x([A-Fa-f0-9]{64})$/.test(s); + if (!valid) { + return "Incorrect transaction hash"; + } + return true; +}; + +export const isPrivateKey = (hash: string) => { + try { + if (new Wallet(hash).address) { + return true; + } + } catch { + // ignore since we return error message below + } + return "Incorrect private key"; +}; diff --git a/src/zeek.ts b/src/utils/zeek.ts similarity index 97% rename from src/zeek.ts rename to src/utils/zeek.ts index c560f567..61f0f3ce 100644 --- a/src/zeek.ts +++ b/src/utils/zeek.ts @@ -1,7 +1,9 @@ import axios from "axios"; import chalk from "chalk"; -export default async function () { +import Logger from "./logger"; + +export default async () => { const api_url = "https://zenquotes.io/api/random/"; let quote = ""; try { @@ -52,5 +54,5 @@ export default async function () { ) ); - console.log(chalk.magentaBright(`zeek would like to tell you "${quote}"\n\n`)); -} + Logger.info(`zeek would like to tell you "${quote}"\n\n`); +}; diff --git a/src/withdraw.ts b/src/withdraw.ts deleted file mode 100644 index 43824efd..00000000 --- a/src/withdraw.ts +++ /dev/null @@ -1,121 +0,0 @@ -import chalk from "chalk"; -import * as ethers from "ethers"; -import inquirer from "inquirer"; -import { Wallet, Provider, utils } from "zksync-web3"; - -import { track } from "./analytics"; -import { checkBalance } from "./utils"; - -import type { Answers, QuestionCollection } from "inquirer"; - -// Used for `zksync-cli withdraw --help` -export const help = () => { - console.log(chalk.bold("Usage:")); - console.log("zksync-cli withdraw --l1-rpc-url= --l2-rpc-url=\n"); - console.log(chalk.bold("Description:")); - console.log( - "Withdraws funds from L2 to L1. The command will ask for the network, the recipient's address, the amount in ETH, and the sender's private key.\n" - ); - console.log(chalk.bold("Options (ONLY for localnet):")); - console.log(chalk.greenBright("--l1-rpc-url=")); - console.log("The URL of the L1 RPC provider.\n"); - console.log(chalk.greenBright("--l2-rpc-url=")); - console.log("The URL of the L2 RPC provider.\n"); -}; - -export default async function (zeek?: boolean, l1RpcUrl?: string | undefined, l2RpcUrl?: string | undefined) { - console.log(chalk.magentaBright("Withdraw funds from zkSync to L1")); - - const questions: QuestionCollection = [ - { - message: "Network:", - name: "network", - type: "list", - choices: ["testnet", "mainnet", "localnet"], - default: "testnet", - }, - { - message: "Address to withdraw funds to:", - name: "to", - type: "input", - }, - { - message: "Amount in ETH:", - name: "amount", - type: "input", - }, - { - message: "Private key of the sender:", - name: "key", - type: "password", - }, - ]; - - const results: Answers = await inquirer.prompt(questions); - - console.log(chalk.magentaBright(`Withdrawing ${results.amount}ETH to ${results.to} on ${results.network}`)); - - let ethProviderUrl; - let zksyncProviderUrl; - let zkSyncExplorerUrl; - - switch (results.network) { - case "mainnet": - ethProviderUrl = "mainnet"; - zksyncProviderUrl = "https://mainnet.era.zksync.io"; - zkSyncExplorerUrl = "https://explorer.zksync.io/"; - break; - case "testnet": - ethProviderUrl = "goerli"; - zksyncProviderUrl = "https://testnet.era.zksync.dev"; - zkSyncExplorerUrl = "https://goerli.explorer.zksync.io/"; - break; - case "localnet": - ethProviderUrl = !l1RpcUrl ? "http://localhost:8545" : l1RpcUrl; - zksyncProviderUrl = !l2RpcUrl ? "http://localhost:3050" : l2RpcUrl; - zkSyncExplorerUrl = "L2: "; - break; - default: - throw `Unsupported network ${results.network}`; - } - - try { - // Init the L1/L2 providers - let L1Provider; - // dynamically change provider class for local or testnet/mainnet - results.network == "localnet" - ? (L1Provider = new ethers.providers.JsonRpcProvider(ethProviderUrl)) - : (L1Provider = ethers.getDefaultProvider(ethProviderUrl)); - - const zkSyncProvider = new Provider(zksyncProviderUrl); - - // Initialize the wallet. - const wallet = new Wallet(results.key, zkSyncProvider, L1Provider); - - await checkBalance(wallet.address, results.amount, zkSyncProvider); - - // Withdraw funds to L1 - const withdrawHandle = await wallet.withdraw({ - to: results.to, - token: utils.ETH_ADDRESS, - amount: ethers.utils.parseEther(results.amount), - }); - - console.log(chalk.magentaBright("Transaction submitted πŸ’ΈπŸ’ΈπŸ’Έ")); - console.log(chalk.magentaBright(`${zkSyncExplorerUrl}tx/${withdrawHandle.hash}`)); - console.log(chalk.magentaBright("Your funds will be available in L1 in a couple of minutes.")); - if (results.network != "localnet") { - console.log( - chalk.magentaBright( - `To check the latest transactions of this wallet on zkSync, visit: ${zkSyncExplorerUrl}address/${results.to}` - ) - ); - } - - await track("withdraw", { zeek, network: results.network }); - } catch (error) { - await track("error", { error }); - } - - // ends -}