diff --git a/README.md b/README.md index d0b913a2..b70cbc51 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,33 @@ -# zkSync CLI tool +
-This CLI tool simplifies the process of developing applications and interacting with zkSync 2.0. +# ‣ zkSync Era CLI -## Requirements +![zksync cli](./zksync-cli-banner.png) -- Node/NPM +This CLI tool simplifies the process of developing applications and interacting with zkSync Era. + +[Report a bug](https://github.com/matter-labs/zksync-cli/issues/new) | [Request a feature](https://github.com/matter-labs/zksync-cli/issues/new) + +[pr-welcome]: https://img.shields.io/static/v1?color=indigo&label=PRs&style=flat&message=welcome + +
+ +## 🛠 Prerequisites + +- Node.js v18.x / NPM - Yarn -## Usage +## 📥 Installation -You can install this program globally with `npm i -g zksync-cli` or run the commands direcly with npx with `npx zksync-cli@latest {COMMAND}`. +You can install this program globally with `npm i -g zksync-cli` or run the commands direcly via NPX with `npx zksync-cli@latest {COMMAND}`. -### Commands +## 💻 Commands - `zksync-cli help`: Provides detailed information about each command. -- `zksync-cli create {PROJECT_NAME}`: creates a new Hardhat 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 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 deposit`: deposits funds from L1 (Goerli testnet) to zkSync 2.0 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 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`: 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. @@ -25,21 +35,21 @@ You can install this program globally with `npm i -g zksync-cli` or run the comm - `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). -> Both deposit and withdraw might take a couple of minutes to complete. +- `zksync-cli --version`: Returns the current version. -### Options (flags) +### ⚙️ Options (flags) - `--zeek`: zeek, the dev cat, will search for an inspirational quote and provide to you at the end of any command. -- `--l1-rpc-url`: override the default Goerli L1 rpc URL when `localnet` is selected as the network. -- `--l2-rpc-url`: override the default zkSync testnet rpc URL when `localnet` is selected as the network. +- `--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://...`. -## Developing new features +## 👩‍💻 Developing new features ### Install and build -Install all dendencies with `npm i`. -This project was build with Typescript. Run `npm run build` to compile code in `/src` into `/bin`. +1. Install all dendencies with `npm i`. +2. This project was build with Typescript. Run `npm run build` to compile code in `/src` into `/bin`. To create a version run: @@ -50,16 +60,18 @@ git push --tags ### Testing +> ⚠️ This project does not have unit tests yet 🤕 + Proper tests will be included soon. For now, you can test new changes locally by installing this package globably with `npm i -g`. -### Tracking +### 📊 Tracking -zkSync-cli tracks its usage for the single purpose of providing data so it can be improved. Data is fully anonymized. If you want to disable the tracking, set the environment variable NO_TRACKING to true. +zkSync-cli tracks its usage for the single purpose of providing data so it can be improved. Data is fully anonymized. If you want to disable the tracking, set the environment variable NO_TRACKING to `true`. -## Official Links +## 🌍 Official Links - [Website](https://zksync.io/) - [GitHub](https://github.com/matter-labs) - [Twitter](https://twitter.com/zksync) -- [Discord](https://discord.gg/nMaPGrDDwk) +- [Discord](https://join.zksync.dev/) diff --git a/package-lock.json b/package-lock.json index a68f7282..df40f1a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "zksync-cli", - "version": "0.1.4", + "version": "0.2.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "zksync-cli", - "version": "0.1.4", + "version": "0.2.3", "license": "MIT", "dependencies": { "@rudderstack/rudder-sdk-node": "^2.0.2", diff --git a/package.json b/package.json index 7bd81109..fcb1353d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zksync-cli", - "version": "0.2.3", + "version": "0.2.4", "description": "CLI tool that simplifies the process of developing applications and interacting with the zkSync Era Network", "main": "bin/index.js", "bin": { diff --git a/src/confirm-withdraw.ts b/src/confirm-withdraw.ts index 3b11b812..11306848 100644 --- a/src/confirm-withdraw.ts +++ b/src/confirm-withdraw.ts @@ -7,7 +7,9 @@ import { track } from "./analytics"; // 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( + "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` @@ -17,12 +19,12 @@ export const help = () => { 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, - l2RpcUrl?: string + l1RpcUrl?: string | undefined, + l2RpcUrl?: string | undefined ) { console.log( chalk.magentaBright("Confirm withdrawal funds from zkSync to Layer 1") @@ -69,10 +71,8 @@ export default async function ( zksyncProviderUrl = "https://testnet.era.zksync.dev"; break; case "localnet": - ethProviderUrl = - l1RpcUrl == undefined ? "http://localhost:8545" : l1RpcUrl; - zksyncProviderUrl = - l2RpcUrl == undefined ? "http://localhost:3050" : l2RpcUrl; + ethProviderUrl = !l1RpcUrl ? "http://localhost:8545" : l1RpcUrl; + zksyncProviderUrl = !l2RpcUrl ? "http://localhost:3050" : l2RpcUrl; break; default: throw `Unsupported network ${results.network}`; diff --git a/src/create.ts b/src/create.ts index 138bf6a2..df2c288d 100644 --- a/src/create.ts +++ b/src/create.ts @@ -25,9 +25,9 @@ export const help = () => { console.log("zksync-cli create \n"); console.log(chalk.bold(`Description:`)); console.log( - `Creates a new Hardhat project in the provided folder. If no folder is specified, it will create the project in the current folder, provided it's empty.\n` + `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 = [ diff --git a/src/deposit.ts b/src/deposit.ts index fe68e4fa..4b0f8c57 100644 --- a/src/deposit.ts +++ b/src/deposit.ts @@ -5,6 +5,8 @@ import chalk from "chalk"; import inquirer, { Answers, QuestionCollection } from "inquirer"; import { track } from "./analytics"; +import { checkBalance } from "./utils"; + // Used for `zksync-cli deposit --help` export const help = () => { console.log(chalk.bold("Usage:")); @@ -13,15 +15,18 @@ export const help = () => { 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:`)); + 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, l2RpcUrl?: string) { +}; +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 = [ @@ -78,10 +83,8 @@ export default async function (zeek?: boolean, l1RpcUrl?: string, l2RpcUrl?: str zkSyncExplorerUrl = "https://goerli.explorer.zksync.io/address/"; break; case "localnet": - ethProviderUrl = - l1RpcUrl == undefined ? "http://localhost:8545" : l1RpcUrl; - zksyncProviderUrl = - l2RpcUrl == undefined ? "http://localhost:3050" : l2RpcUrl; + ethProviderUrl = !l1RpcUrl ? "http://localhost:8545" : l1RpcUrl; + zksyncProviderUrl = !l2RpcUrl ? "http://localhost:3050" : l2RpcUrl; etherScanUrl = "L1 transaction: "; zkSyncExplorerUrl = "L2 address:"; break; @@ -100,6 +103,7 @@ export default async function (zeek?: boolean, l1RpcUrl?: string, l2RpcUrl?: str 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({ @@ -115,11 +119,13 @@ export default async function (zeek?: boolean, l1RpcUrl?: string, l2RpcUrl?: str `Your funds will be available in zkSync in a couple of minutes.` ) ); - console.log( - chalk.magentaBright( - `To check the latest transactions of this wallet on zkSync, visit: ${zkSyncExplorerUrl}${results.to}` - ) - ); + 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 🤕`); diff --git a/src/help.ts b/src/help.ts index c4bd8a5f..57783180 100644 --- a/src/help.ts +++ b/src/help.ts @@ -11,7 +11,7 @@ export default async function () { console.log(chalk.bold(`Commands:\n`)); console.log(chalk.greenBright(`create `)); console.log( - `Creates a new Hardhat project in the provided folder. If no folder is specified, it will create the project in the current folder, provided it's empty.\n` + `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( diff --git a/src/index.ts b/src/index.ts index 2e097b50..c5ff8f68 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,12 +5,15 @@ import chalk from "chalk"; // @ts-ignore // const figlet = require('figlet'); import figlet from "figlet"; +import * as pkg from "../package.json"; // import method to create projects import create, { help as createHelp } from "./create"; import deposit, { help as depositHelp } from "./deposit"; import withdraw, { help as withdrawHelp } from "./withdraw"; -import confirmWithdraw, { help as confirmWithdrawalHelp } from "./confirm-withdraw"; +import confirmWithdraw, { + help as confirmWithdrawalHelp, +} from "./confirm-withdraw"; import zeek from "./zeek"; import help from "./help"; @@ -26,7 +29,18 @@ const availableOptions: string[] = [ const option: string = process.argv[2]; const main = async () => { - const helpFlag = Boolean(process.argv.filter((arg) => ["--help", "-h"].includes(arg))[0]); + 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 ${pkg.version}`)); + process.exit(0); + } + if (!availableOptions.includes(option)) { console.log( `Invalid operation. Available operations are: ${availableOptions}` @@ -43,16 +57,13 @@ const main = async () => { ); const zeekFlag = Boolean(process.argv.filter((arg) => arg === "--zeek")[0]); - const l1RpcUrl = String( - process.argv - .filter((arg) => arg.startsWith("l1-rpc-url")) - .map((arg) => arg.split("=")[1])[0] - ); - const l2RpcUrl = String( - process.argv - .filter((arg) => arg.startsWith("l2-rpc-url")) - .map((arg) => arg.split("=")[1])[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) { diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 00000000..1196f4f7 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,16 @@ +import { ethers, utils } from "ethers"; +import { Provider } from "zksync-web3"; +import { track } from "./analytics"; + +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/withdraw.ts b/src/withdraw.ts index f96307a0..e6039fe0 100644 --- a/src/withdraw.ts +++ b/src/withdraw.ts @@ -4,6 +4,8 @@ import chalk from "chalk"; import inquirer, { Answers, QuestionCollection } from "inquirer"; import { track } from "./analytics"; +import { checkBalance } from "./utils"; + // Used for `zksync-cli withdraw --help` export const help = () => { console.log(chalk.bold("Usage:")); @@ -12,16 +14,19 @@ export const help = () => { 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:`)); + 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, l2RpcUrl?: string) { +}; - console.log(chalk.magentaBright('Withdraw funds from zkSync to Goerli')); +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 = [ { @@ -72,10 +77,8 @@ export default async function (zeek?: boolean, l1RpcUrl?: string, l2RpcUrl?: str zkSyncExplorerUrl = "https://goerli.explorer.zksync.io/"; break; case "localnet": - ethProviderUrl = - l1RpcUrl == undefined ? "http://localhost:8545" : l1RpcUrl; - zksyncProviderUrl = - l2RpcUrl == undefined ? "http://localhost:3050" : l2RpcUrl; + ethProviderUrl = !l1RpcUrl ? "http://localhost:8545" : l1RpcUrl; + zksyncProviderUrl = !l2RpcUrl ? "http://localhost:3050" : l2RpcUrl; zkSyncExplorerUrl = "L2: "; break; default: @@ -91,9 +94,12 @@ export default async function (zeek?: boolean, l1RpcUrl?: string, l2RpcUrl?: str : (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, @@ -110,11 +116,13 @@ export default async function (zeek?: boolean, l1RpcUrl?: string, l2RpcUrl?: str `Your funds will be available in L1 in a couple of minutes.` ) ); - console.log( - chalk.magentaBright( - `To check the latest transactions of this wallet on zkSync, visit: ${zkSyncExplorerUrl}address/${results.to}` - ) - ); + 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) { diff --git a/zksync-cli-banner.png b/zksync-cli-banner.png new file mode 100644 index 00000000..cfa80b12 Binary files /dev/null and b/zksync-cli-banner.png differ