diff --git a/src/commands/bridge/command.ts b/src/commands/bridge/command.ts index 52de1941..3863583e 100644 --- a/src/commands/bridge/command.ts +++ b/src/commands/bridge/command.ts @@ -1,3 +1,5 @@ import Program from "../../program.js"; -export default Program.command("bridge").description("Bridge operations (e.g. deposit, withdraw)"); +export default Program.command("bridge").description( + "Bridge operations (e.g. deposit, withdraw)" +); diff --git a/src/commands/bridge/deposit.ts b/src/commands/bridge/deposit.ts index 3ba9e4a1..2601ce2f 100644 --- a/src/commands/bridge/deposit.ts +++ b/src/commands/bridge/deposit.ts @@ -1,7 +1,6 @@ import inquirer from "inquirer"; import ora from "ora"; -import Program from "./command.js"; import { amountOptionCreate, chainWithL1Option, @@ -24,9 +23,14 @@ import { } from "../../utils/helpers.js"; import Logger from "../../utils/logger.js"; import { getBalance, getTokenInfo } from "../../utils/token.js"; -import { isDecimalAmount, isAddress, isPrivateKey } from "../../utils/validators.js"; +import { + isAddress, + isDecimalAmount, + isPrivateKey, +} from "../../utils/validators.js"; import zeek from "../../utils/zeek.js"; import { getChains } from "../config/chains.js"; +import Program from "./command.js"; import type { DefaultTransferOptions } from "../../common/options.js"; @@ -39,7 +43,10 @@ export const handler = async (options: DepositOptions) => { try { Logger.debug( `Initial deposit options: ${JSON.stringify( - { ...options, ...(options.privateKey ? { privateKey: "" } : {}) }, + { + ...options, + ...(options.privateKey ? { privateKey: "" } : {}), + }, null, 2 )}` @@ -52,7 +59,9 @@ export const handler = async (options: DepositOptions) => { message: chainWithL1Option.description, name: optionNameToParam(chainWithL1Option.long!), type: "list", - choices: chains.filter((e) => e.l1Chain).map((e) => ({ name: e.name, value: e.network })), + choices: chains + .filter((e) => e.l1Chain) + .map((e) => ({ name: e.name, value: e.network })), required: true, when(answers: DepositOptions) { if (answers.l1Rpc && answers.rpc) { @@ -94,24 +103,42 @@ export const handler = async (options: DepositOptions) => { ...answers, }; - Logger.debug(`Final deposit options: ${JSON.stringify({ ...options, privateKey: "" }, null, 2)}`); + Logger.debug( + `Final deposit options: ${JSON.stringify({ ...options, privateKey: "" }, null, 2)}` + ); const fromChain = chains.find((e) => e.network === options.chain)?.l1Chain; - const fromChainLabel = fromChain && !options.l1Rpc ? fromChain.name : (options.l1Rpc ?? "Unknown chain"); + const fromChainLabel = + fromChain && !options.l1Rpc + ? fromChain.name + : (options.l1Rpc ?? "Unknown chain"); const toChain = chains.find((e) => e.network === options.chain); - const toChainLabel = toChain && !options.rpc ? toChain.name : (options.rpc ?? "Unknown chain"); + const toChainLabel = + toChain && !options.rpc ? toChain.name : (options.rpc ?? "Unknown chain"); const l1Provider = getL1Provider(options.l1Rpc ?? fromChain!.rpcUrl); const l2Provider = getL2Provider(options.rpc ?? toChain!.rpcUrl); - const senderWallet = getL2Wallet(options.privateKey, l2Provider, l1Provider); - const token = options.token ? await getTokenInfo(options.token!, l2Provider, l1Provider) : ETH_TOKEN; - const { decimalToBigNumber, bigNumberToDecimal } = useDecimals(token.decimals); + const senderWallet = getL2Wallet( + options.privateKey, + l2Provider, + l1Provider + ); + const token = options.token + ? await getTokenInfo(options.token!, l2Provider, l1Provider) + : ETH_TOKEN; + const { decimalToBigNumber, bigNumberToDecimal } = useDecimals( + token.decimals + ); if (!token.l1Address) { - throw new Error(`Token ${token.symbol} doesn't exist on ${fromChainLabel} therefore it cannot be deposited`); + throw new Error( + `Token ${token.symbol} doesn't exist on ${fromChainLabel} therefore it cannot be deposited` + ); } Logger.info("\nDeposit:"); - Logger.info(` From: ${getAddressFromPrivateKey(answers.privateKey)} (${fromChainLabel})`); + Logger.info( + ` From: ${getAddressFromPrivateKey(answers.privateKey)} (${fromChainLabel})` + ); Logger.info(` To: ${options.recipient} (${toChainLabel})`); Logger.info( ` Amount: ${bigNumberToDecimal(decimalToBigNumber(options.amount))} ${token.symbol} ${ @@ -133,10 +160,16 @@ export const handler = async (options: DepositOptions) => { Logger.info("\nDeposit sent:"); Logger.info(` Transaction hash: ${depositHandle.hash}`); if (fromChain?.explorerUrl) { - Logger.info(` Transaction link: ${fromChain.explorerUrl}/tx/${depositHandle.hash}`); + Logger.info( + ` Transaction link: ${fromChain.explorerUrl}/tx/${depositHandle.hash}` + ); } - const senderBalance = await getBalance(token.l1Address, senderWallet.address, l1Provider); + const senderBalance = await getBalance( + token.l1Address, + senderWallet.address, + l1Provider + ); Logger.info( `\nSender L1 balance after transaction: ${bigNumberToDecimal(senderBalance)} ${token.symbol} ${ token.name ? `(${token.name})` : "" diff --git a/src/commands/bridge/index.ts b/src/commands/bridge/index.ts index d66ea062..2f1103d8 100644 --- a/src/commands/bridge/index.ts +++ b/src/commands/bridge/index.ts @@ -1,5 +1,4 @@ import "./deposit.js"; import "./withdraw.js"; import "./withdraw-finalize.js"; - import "./command.js"; // registers all the commands above diff --git a/src/commands/bridge/withdraw-finalize.ts b/src/commands/bridge/withdraw-finalize.ts index 28745d56..8c67f0e2 100644 --- a/src/commands/bridge/withdraw-finalize.ts +++ b/src/commands/bridge/withdraw-finalize.ts @@ -1,7 +1,6 @@ import { Option } from "commander"; import inquirer from "inquirer"; -import Program from "./command.js"; import { chainWithL1Option, l1RpcUrlOption, @@ -24,10 +23,14 @@ import { getBalance } from "../../utils/token.js"; import { isPrivateKey, isTransactionHash } from "../../utils/validators.js"; import zeek from "../../utils/zeek.js"; import { getChains } from "../config/chains.js"; +import Program from "./command.js"; import type { DefaultTransactionOptions } from "../../common/options.js"; -const transactionHashOption = new Option("--hash ", "L2 withdrawal transaction hash to finalize"); +const transactionHashOption = new Option( + "--hash ", + "L2 withdrawal transaction hash to finalize" +); type WithdrawFinalizeOptions = DefaultTransactionOptions & { hash: string; @@ -37,7 +40,10 @@ export const handler = async (options: WithdrawFinalizeOptions) => { try { Logger.debug( `Initial withdraw-finalize options: ${JSON.stringify( - { ...options, ...(options.privateKey ? { privateKey: "" } : {}) }, + { + ...options, + ...(options.privateKey ? { privateKey: "" } : {}), + }, null, 2 )}` @@ -50,7 +56,9 @@ export const handler = async (options: WithdrawFinalizeOptions) => { message: chainWithL1Option.description, name: optionNameToParam(chainWithL1Option.long!), type: "list", - choices: chains.filter((e) => e.l1Chain).map((e) => ({ name: e.name, value: e.network })), + choices: chains + .filter((e) => e.l1Chain) + .map((e) => ({ name: e.name, value: e.network })), required: true, when(answers: WithdrawFinalizeOptions) { if (answers.l1Rpc && answers.rpc) { @@ -82,22 +90,36 @@ export const handler = async (options: WithdrawFinalizeOptions) => { ...answers, }; - Logger.debug(`Final withdraw-finalize options: ${JSON.stringify({ ...options, privateKey: "" }, null, 2)}`); + Logger.debug( + `Final withdraw-finalize options: ${JSON.stringify({ ...options, privateKey: "" }, null, 2)}` + ); const fromChain = chains.find((e) => e.network === options.chain); - const fromChainLabel = fromChain && !options.rpc ? fromChain.name : (options.rpc ?? "Unknown chain"); + const fromChainLabel = + fromChain && !options.rpc + ? fromChain.name + : (options.rpc ?? "Unknown chain"); const toChain = chains.find((e) => e.network === options.chain)?.l1Chain; - const toChainLabel = toChain && !options.l1Rpc ? toChain.name : (options.l1Rpc ?? "Unknown chain"); + const toChainLabel = + toChain && !options.l1Rpc + ? toChain.name + : (options.l1Rpc ?? "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)}`); + Logger.info( + ` Finalizer address (L1): ${getAddressFromPrivateKey(answers.privateKey)}` + ); const l1Provider = getL1Provider(options.l1Rpc ?? toChain!.rpcUrl); const l2Provider = getL2Provider(options.rpc ?? fromChain!.rpcUrl); - const senderWallet = getL2Wallet(options.privateKey, l2Provider, l1Provider); + const senderWallet = getL2Wallet( + options.privateKey, + l2Provider, + l1Provider + ); Logger.info("\nChecking status of the transaction..."); const l2Details = await l2Provider.getTransactionDetails(options.hash); @@ -109,26 +131,38 @@ export const handler = async (options: WithdrawFinalizeOptions) => { Logger.error( `\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)}`); + Logger.info( + `L2 Transaction Details: ${JSON.stringify(l2Details, null, 2)}` + ); return; } Logger.info("Transaction is ready to be finalized"); Logger.info("\nSending finalization transaction..."); - const finalizationHandle = await senderWallet.finalizeWithdrawal(options.hash); + const finalizationHandle = await senderWallet.finalizeWithdrawal( + options.hash + ); Logger.info("\nWithdrawal finalized:"); Logger.info(` Finalization transaction hash: ${finalizationHandle.hash}`); if (toChain?.explorerUrl) { - Logger.info(` Transaction link: ${toChain.explorerUrl}/tx/${finalizationHandle.hash}`); + Logger.info( + ` Transaction link: ${toChain.explorerUrl}/tx/${finalizationHandle.hash}` + ); } Logger.info("\nWaiting for finalization transaction to be mined..."); const receipt = await finalizationHandle.wait(); - Logger.info(` Finalization transaction was mined in block ${receipt.blockNumber}`); + Logger.info( + ` Finalization transaction was mined in block ${receipt.blockNumber}` + ); const token = ETH_TOKEN; const { bigNumberToDecimal } = useDecimals(token.decimals); - const senderBalance = await getBalance(token.l1Address, senderWallet.address, l1Provider); + const senderBalance = await getBalance( + token.l1Address, + senderWallet.address, + l1Provider + ); Logger.info( `\nSender L1 balance after transaction: ${bigNumberToDecimal(senderBalance)} ${token.symbol} ${ token.name ? `(${token.name})` : "" diff --git a/src/commands/bridge/withdraw.ts b/src/commands/bridge/withdraw.ts index 20c6b52f..a613cf65 100644 --- a/src/commands/bridge/withdraw.ts +++ b/src/commands/bridge/withdraw.ts @@ -1,7 +1,6 @@ import inquirer from "inquirer"; import ora from "ora"; -import Program from "./command.js"; import { amountOptionCreate, chainWithL1Option, @@ -24,9 +23,14 @@ import { } from "../../utils/helpers.js"; import Logger from "../../utils/logger.js"; import { getBalance, getTokenInfo } from "../../utils/token.js"; -import { isDecimalAmount, isAddress, isPrivateKey } from "../../utils/validators.js"; +import { + isAddress, + isDecimalAmount, + isPrivateKey, +} from "../../utils/validators.js"; import zeek from "../../utils/zeek.js"; import { getChains } from "../config/chains.js"; +import Program from "./command.js"; import type { DefaultTransferOptions } from "../../common/options.js"; @@ -39,7 +43,10 @@ export const handler = async (options: WithdrawOptions) => { try { Logger.debug( `Initial withdraw options: ${JSON.stringify( - { ...options, ...(options.privateKey ? { privateKey: "" } : {}) }, + { + ...options, + ...(options.privateKey ? { privateKey: "" } : {}), + }, null, 2 )}` @@ -52,7 +59,9 @@ export const handler = async (options: WithdrawOptions) => { message: chainWithL1Option.description, name: optionNameToParam(chainWithL1Option.long!), type: "list", - choices: chains.filter((e) => e.l1Chain).map((e) => ({ name: e.name, value: e.network })), + choices: chains + .filter((e) => e.l1Chain) + .map((e) => ({ name: e.name, value: e.network })), required: true, when(answers: WithdrawOptions) { if (answers.l1Rpc && answers.rpc) { @@ -94,27 +103,49 @@ export const handler = async (options: WithdrawOptions) => { ...answers, }; - Logger.debug(`Final withdraw options: ${JSON.stringify({ ...options, privateKey: "" }, null, 2)}`); + Logger.debug( + `Final withdraw options: ${JSON.stringify({ ...options, privateKey: "" }, null, 2)}` + ); const fromChain = chains.find((e) => e.network === options.chain); - const fromChainLabel = fromChain && !options.rpc ? fromChain.name : (options.rpc ?? "Unknown chain"); + const fromChainLabel = + fromChain && !options.rpc + ? fromChain.name + : (options.rpc ?? "Unknown chain"); const toChain = chains.find((e) => e.network === options.chain)?.l1Chain; - const toChainLabel = toChain && !options.l1Rpc ? toChain.name : (options.l1Rpc ?? "Unknown chain"); + const toChainLabel = + toChain && !options.l1Rpc + ? toChain.name + : (options.l1Rpc ?? "Unknown chain"); const l1Provider = getL1Provider(options.l1Rpc ?? toChain!.rpcUrl); const l2Provider = getL2Provider(options.rpc ?? fromChain!.rpcUrl); - const senderWallet = getL2Wallet(options.privateKey, l2Provider, l1Provider); - const token = options.token ? await getTokenInfo(options.token!, l2Provider, l1Provider) : ETH_TOKEN; - const { decimalToBigNumber, bigNumberToDecimal } = useDecimals(token.decimals); + const senderWallet = getL2Wallet( + options.privateKey, + l2Provider, + l1Provider + ); + const token = options.token + ? await getTokenInfo(options.token!, l2Provider, l1Provider) + : ETH_TOKEN; + const { decimalToBigNumber, bigNumberToDecimal } = useDecimals( + token.decimals + ); if (!token.l1Address) { - throw new Error(`Token ${token.symbol} doesn't exist on ${toChainLabel} therefore it cannot be withdrawn`); + throw new Error( + `Token ${token.symbol} doesn't exist on ${toChainLabel} therefore it cannot be withdrawn` + ); } if (!token.address) { - throw new Error(`Token ${token.symbol} does not exist on ${fromChain?.name}`); + throw new Error( + `Token ${token.symbol} does not exist on ${fromChain?.name}` + ); } Logger.info("\nWithdraw:"); - Logger.info(` From: ${getAddressFromPrivateKey(answers.privateKey)} (${fromChainLabel})`); + Logger.info( + ` From: ${getAddressFromPrivateKey(answers.privateKey)} (${fromChainLabel})` + ); Logger.info(` To: ${options.recipient} (${toChainLabel})`); Logger.info( ` Amount: ${bigNumberToDecimal(decimalToBigNumber(options.amount))} ${token.symbol} ${ @@ -126,7 +157,10 @@ export const handler = async (options: WithdrawOptions) => { try { const withdrawHandle = await senderWallet.withdraw({ to: options.recipient, - token: token.address === ETH_TOKEN.address ? token.l1Address : token.address!, + token: + token.address === ETH_TOKEN.address + ? token.l1Address + : token.address!, amount: decimalToBigNumber(options.amount), }); await withdrawHandle.wait(); @@ -134,10 +168,16 @@ export const handler = async (options: WithdrawOptions) => { Logger.info("\nWithdraw sent:"); Logger.info(` Transaction hash: ${withdrawHandle.hash}`); if (fromChain?.explorerUrl) { - Logger.info(` Transaction link: ${fromChain.explorerUrl}/tx/${withdrawHandle.hash}`); + Logger.info( + ` Transaction link: ${fromChain.explorerUrl}/tx/${withdrawHandle.hash}` + ); } - const senderBalance = await getBalance(token.address, senderWallet.address, l2Provider); + const senderBalance = await getBalance( + token.address, + senderWallet.address, + l2Provider + ); Logger.info( `\nSender L2 balance after transaction: ${bigNumberToDecimal(senderBalance)} ${token.symbol} ${ token.name ? `(${token.name})` : "" diff --git a/src/commands/config/chains.ts b/src/commands/config/chains.ts index a1d316bf..080413db 100644 --- a/src/commands/config/chains.ts +++ b/src/commands/config/chains.ts @@ -2,11 +2,11 @@ import chalk from "chalk"; import inquirer from "inquirer"; import slugify from "slugify"; -import Program from "./command.js"; import { configHandler } from "../../common/ConfigHandler.js"; import { l2Chains } from "../../data/chains.js"; import Logger from "../../utils/logger.js"; import { isUrl } from "../../utils/validators.js"; +import Program from "./command.js"; import type { Chain, L2Chain } from "../../data/chains.js"; @@ -54,14 +54,20 @@ export const promptAddNewChain = async (defaults?: L2Chain) => { if (defaults?.name === value) { return true; } - if ([...getChains(), ...l2Chains].find((chain) => chain.name === value)) { + if ( + [...getChains(), ...l2Chains].find((chain) => chain.name === value) + ) { return "Chain name already exists"; } return true; }, }, ]); - const { network, rpcUrl, explorerUrl }: { network: string; rpcUrl: string; explorerUrl: string } = + const { + network, + rpcUrl, + explorerUrl, + }: { network: string; rpcUrl: string; explorerUrl: string } = await inquirer.prompt([ { message: "Chain key", @@ -82,7 +88,11 @@ export const promptAddNewChain = async (defaults?: L2Chain) => { if (defaults?.network === value) { return true; } - if ([...getChains(), ...l2Chains].find((chain) => chain.network === value)) { + if ( + [...getChains(), ...l2Chains].find( + (chain) => chain.network === value + ) + ) { return "Chain key already exists"; } return true; @@ -139,30 +149,35 @@ export const promptAddNewChain = async (defaults?: L2Chain) => { }, ]); if (hasL1Chain === "yes") { - const { l1_id, l1_name }: { l1_id: string; l1_name: string } = await inquirer.prompt([ - { - message: "L1 Chain id", - name: "l1_id", - type: "input", - default: defaults?.l1Chain?.id, - required: true, - validate: validateChainId, - }, - { - message: "L1 Chain name", - name: "l1_name", - type: "input", - default: defaults?.l1Chain?.name, - required: true, - validate: (value: string) => { - if (!value) { - return "Chain name is required"; - } - return true; + const { l1_id, l1_name }: { l1_id: string; l1_name: string } = + await inquirer.prompt([ + { + message: "L1 Chain id", + name: "l1_id", + type: "input", + default: defaults?.l1Chain?.id, + required: true, + validate: validateChainId, }, - }, - ]); - const { l1_network, l1_rpcUrl, l1_explorerUrl }: { l1_network: string; l1_rpcUrl: string; l1_explorerUrl: string } = + { + message: "L1 Chain name", + name: "l1_name", + type: "input", + default: defaults?.l1Chain?.name, + required: true, + validate: (value: string) => { + if (!value) { + return "Chain name is required"; + } + return true; + }, + }, + ]); + const { + l1_network, + l1_rpcUrl, + l1_explorerUrl, + }: { l1_network: string; l1_rpcUrl: string; l1_explorerUrl: string } = await inquirer.prompt([ { message: "L1 Chain key", @@ -215,7 +230,10 @@ export const promptAddNewChain = async (defaults?: L2Chain) => { newChain.l1Chain = l1Chain; } - saveChains([newChain, ...chains.filter((e) => e.network !== defaults?.network)]); + saveChains([ + newChain, + ...chains.filter((e) => e.network !== defaults?.network), + ]); Logger.info(`${chalk.greenBright("✔")} Chain "${name}" saved`); return newChain; }; @@ -263,27 +281,28 @@ const promptAskChainAction = async (chain: L2Chain) => { export const handler = async () => { try { const chains = getChains(); - const { chain }: { chain: "add-new-chain" | string } = await inquirer.prompt([ - { - message: "Select a chain", - name: "chain", - type: "list", - choices: [ - ...chains.map((chain) => ({ - name: chain.name + chalk.gray(` - ${chain.network}`), - short: chain.network, - value: chain.network, - })), - ...(chains.length > 0 ? [{ type: "separator" }] : []), - { - name: chalk.greenBright("+") + " Add new chain", - short: "Add new chain", - value: "add-new-chain", - }, - ], - required: true, - }, - ]); + const { chain }: { chain: "add-new-chain" | string } = + await inquirer.prompt([ + { + message: "Select a chain", + name: "chain", + type: "list", + choices: [ + ...chains.map((chain) => ({ + name: chain.name + chalk.gray(` - ${chain.network}`), + short: chain.network, + value: chain.network, + })), + ...(chains.length > 0 ? [{ type: "separator" }] : []), + { + name: chalk.greenBright("+") + " Add new chain", + short: "Add new chain", + value: "add-new-chain", + }, + ], + required: true, + }, + ]); if (chain === "add-new-chain") { await promptAddNewChain(); } else { @@ -296,4 +315,6 @@ export const handler = async () => { } }; -Program.command("chains").description("Add or edit available CLI chains").action(handler); +Program.command("chains") + .description("Add or edit available CLI chains") + .action(handler); diff --git a/src/commands/config/command.ts b/src/commands/config/command.ts index 4c468232..07c75e56 100644 --- a/src/commands/config/command.ts +++ b/src/commands/config/command.ts @@ -1,3 +1,5 @@ import Program from "../../program.js"; -export default Program.command("config").description("Configure the CLI (e.g. available chains)"); +export default Program.command("config").description( + "Configure the CLI (e.g. available chains)" +); diff --git a/src/commands/config/index.ts b/src/commands/config/index.ts index 25a9a31f..28cc0090 100644 --- a/src/commands/config/index.ts +++ b/src/commands/config/index.ts @@ -1,3 +1,2 @@ import "./chains.js"; - import "./command.js"; // registers all the commands above diff --git a/src/commands/contract/command.ts b/src/commands/contract/command.ts index 8769bb59..6594e18f 100644 --- a/src/commands/contract/command.ts +++ b/src/commands/contract/command.ts @@ -1,3 +1,5 @@ import Program from "../../program.js"; -export default Program.command("contract").description("Interact with the contracts"); +export default Program.command("contract").description( + "Interact with the contracts" +); diff --git a/src/commands/contract/encode.ts b/src/commands/contract/encode.ts index 6d1f43cf..a424c9e1 100644 --- a/src/commands/contract/encode.ts +++ b/src/commands/contract/encode.ts @@ -2,16 +2,28 @@ import chalk from "chalk"; import { ethers } from "ethers"; import inquirer from "inquirer"; +import { + logFullCommandFromOptions, + optionNameToParam, +} from "../../utils/helpers.js"; +import Logger from "../../utils/logger.js"; import Program from "./command.js"; import { abiOption, argumentsOption, methodOption } from "./common/options.js"; -import { encodeData, formatArgs, getFragmentFromSignature, getInputsFromSignature } from "./utils/formatters.js"; -import { readAbiFromFile, askAbiMethod, formatMethodString } from "./utils/helpers.js"; -import { logFullCommandFromOptions, optionNameToParam } from "../../utils/helpers.js"; -import Logger from "../../utils/logger.js"; +import { + encodeData, + formatArgs, + getFragmentFromSignature, + getInputsFromSignature, +} from "./utils/formatters.js"; +import { + askAbiMethod, + formatMethodString, + readAbiFromFile, +} from "./utils/helpers.js"; -import type { ABI } from "./utils/helpers.js"; import type { Command } from "commander"; import type { DistinctQuestion } from "inquirer"; +import type { ABI } from "./utils/helpers.js"; type EncodeOptions = { method?: string; @@ -23,7 +35,10 @@ type EncodeOptions = { // prompts // ---------------- -const askMethod = async (contractAbi: ABI | undefined, options: EncodeOptions) => { +const askMethod = async ( + contractAbi: ABI | undefined, + options: EncodeOptions +) => { if (options.method) { return; } @@ -118,5 +133,7 @@ Program.command("encode") .addOption(methodOption) .addOption(argumentsOption) .addOption(abiOption) - .description("Get calldata (e.g. 0x1234) from contract method signature and arguments") + .description( + "Get calldata (e.g. 0x1234) from contract method signature and arguments" + ) .action(handler); diff --git a/src/commands/contract/index.ts b/src/commands/contract/index.ts index deed9050..aac478c1 100644 --- a/src/commands/contract/index.ts +++ b/src/commands/contract/index.ts @@ -1,5 +1,4 @@ import "./read.js"; import "./write.js"; import "./encode.js"; - import "./command.js"; // registers all the commands above diff --git a/src/commands/contract/read.ts b/src/commands/contract/read.ts index 2b7913c4..712adb5a 100644 --- a/src/commands/contract/read.ts +++ b/src/commands/contract/read.ts @@ -4,6 +4,16 @@ import { ethers } from "ethers"; import inquirer from "inquirer"; import ora from "ora"; +import { chainOption, l2RpcUrlOption } from "../../common/options.js"; +import { l2Chains } from "../../data/chains.js"; +import { + getL2Provider, + logFullCommandFromOptions, + optionNameToParam, +} from "../../utils/helpers.js"; +import Logger from "../../utils/logger.js"; +import { isAddress } from "../../utils/validators.js"; +import { getChains } from "../config/chains.js"; import Program from "./command.js"; import { abiOption, @@ -18,31 +28,31 @@ import { encodeData, formatArgs, getFragmentFromSignature, - getInputValues, getInputsFromSignature, + getInputValues, } from "./utils/formatters.js"; import { + askAbiMethod, checkIfMethodExists, + formatMethodString, getContractInfoWithLoader, readAbiFromFile, - askAbiMethod, - formatMethodString, } from "./utils/helpers.js"; -import { chainOption, l2RpcUrlOption } from "../../common/options.js"; -import { l2Chains } from "../../data/chains.js"; -import { getL2Provider, logFullCommandFromOptions, optionNameToParam } from "../../utils/helpers.js"; -import Logger from "../../utils/logger.js"; -import { isAddress } from "../../utils/validators.js"; -import { getChains } from "../config/chains.js"; -import type { ContractInfo } from "./utils/helpers.js"; -import type { DefaultTransactionOptions } from "../../common/options.js"; import type { TransactionRequest } from "@ethersproject/abstract-provider"; import type { Command } from "commander"; import type { DistinctQuestion } from "inquirer"; +import type { DefaultTransactionOptions } from "../../common/options.js"; +import type { ContractInfo } from "./utils/helpers.js"; -const outputsOption = new Option("--output, --outputTypes ", "Output types"); -const fromOption = new Option("--from <0x address>", "Read on behalf of specific address"); +const outputsOption = new Option( + "--output, --outputTypes ", + "Output types" +); +const fromOption = new Option( + "--from <0x address>", + "Read on behalf of specific address" +); const decodeSkipOption = new Option("--decode-skip", "Skip decoding response"); type CallOptions = DefaultTransactionOptions & { @@ -130,9 +140,14 @@ const askArguments = async (method: string, options: CallOptions) => { options.arguments = Object.values(answers); }; -const askOutputTypes = async (rawCallResponse: string, options: CallOptions) => { +const askOutputTypes = async ( + rawCallResponse: string, + options: CallOptions +) => { if (!options.outputTypes) { - Logger.info(chalk.gray("Provide output types to decode the response (optional)")); + Logger.info( + chalk.gray("Provide output types to decode the response (optional)") + ); } const answers: Pick = await inquirer.prompt( [ @@ -146,7 +161,8 @@ const askOutputTypes = async (rawCallResponse: string, options: CallOptions) => return true; } catch (error) { return `${chalk.redBright( - "Failed to decode response with provided types: " + (error instanceof Error ? error.message : error) + "Failed to decode response with provided types: " + + (error instanceof Error ? error.message : error) )}\nInput example: ${chalk.blueBright("string,uint256")}`; } }, @@ -155,12 +171,16 @@ const askOutputTypes = async (rawCallResponse: string, options: CallOptions) => options ); - options.outputTypes = options.outputTypes || getInputValues(answers.outputTypes as unknown as string); + options.outputTypes = + options.outputTypes || + getInputValues(answers.outputTypes as unknown as string); if (!options.outputTypes.length) return; const decodedOutput = decodeData(options.outputTypes, rawCallResponse); - Logger.info(`${chalk.green("✔")} Decoded method response: ${chalk.cyanBright(decodedOutput)}`); + Logger.info( + `${chalk.green("✔")} Decoded method response: ${chalk.cyanBright(decodedOutput)}` + ); }; // ---------------- @@ -170,34 +190,41 @@ const askOutputTypes = async (rawCallResponse: string, options: CallOptions) => export const handler = async (options: CallOptions, context: Command) => { try { const chains = [...l2Chains, ...getChains()]; - const answers: Pick = await inquirer.prompt( - [ - { - message: chainOption.description, - name: optionNameToParam(chainOption.long!), - type: "list", - choices: chains.map((e) => ({ name: e.name, value: e.network })), - required: true, - when: (answers: CallOptions) => !answers.rpc, - }, - { - message: contractOption.description, - name: optionNameToParam(contractOption.long!), - type: "input", - required: true, - validate: (input: string) => isAddress(input), - }, - ], - options - ); + const answers: Pick = + await inquirer.prompt( + [ + { + message: chainOption.description, + name: optionNameToParam(chainOption.long!), + type: "list", + choices: chains.map((e) => ({ name: e.name, value: e.network })), + required: true, + when: (answers: CallOptions) => !answers.rpc, + }, + { + message: contractOption.description, + name: optionNameToParam(contractOption.long!), + type: "input", + required: true, + validate: (input: string) => isAddress(input), + }, + ], + options + ); options.chain = answers.chain; options.contract = answers.contract; - const selectedChain = options.rpc ? undefined : chains.find((e) => e.network === options.chain); + const selectedChain = options.rpc + ? undefined + : chains.find((e) => e.network === options.chain); const provider = getL2Provider(options.rpc || selectedChain!.rpcUrl); - const contractInfo = await getContractInfoWithLoader(selectedChain, provider, options.contract!); + const contractInfo = await getContractInfoWithLoader( + selectedChain, + provider, + options.contract! + ); if (contractInfo.implementation) { Logger.info( `${chalk.green("✔")} ${chalk.bold("Contract implementation address")} ${chalk.cyan( @@ -226,18 +253,26 @@ export const handler = async (options: CallOptions, context: Command) => { to: contractInfo.address, data: options.data || encodeData(options.method!, options.arguments!), from: options.from, - nonce: options.from ? await provider.getTransactionCount(options.from) : undefined, + nonce: options.from + ? await provider.getTransactionCount(options.from) + : undefined, }; Logger.info(""); if (options.showInfo) { - Logger.info(chalk.gray("Transaction request: " + JSON.stringify(transaction, null, 2))); + Logger.info( + chalk.gray( + "Transaction request: " + JSON.stringify(transaction, null, 2) + ) + ); } const spinner = ora("Calling contract method...").start(); try { const response = await provider.call(transaction); const isEmptyResponse = response === "0x"; - spinner[isEmptyResponse ? "warn" : "succeed"](`Method response (raw): ${chalk.cyanBright(response)}`); + spinner[isEmptyResponse ? "warn" : "succeed"]( + `Method response (raw): ${chalk.cyanBright(response)}` + ); if (!isEmptyResponse && !options.decodeSkip) { await askOutputTypes(response, options); diff --git a/src/commands/contract/utils/formatters.ts b/src/commands/contract/utils/formatters.ts index 5ff7458b..a2a52887 100644 --- a/src/commands/contract/utils/formatters.ts +++ b/src/commands/contract/utils/formatters.ts @@ -36,15 +36,22 @@ export const getInputValues = (inputsString: string): string[] => { }; export const getMethodId = (method: string) => { - const methodSignature = getFragmentFromSignature(method).format(ethers.utils.FormatTypes.sighash); + const methodSignature = getFragmentFromSignature(method).format( + ethers.utils.FormatTypes.sighash + ); return ethers.utils.id(methodSignature).substring(2, 10); // remove 0x and take first 4 bytes }; -export const getMethodsFromAbi = (abi: ABI, type: "read" | "write"): ethers.utils.FunctionFragment[] => { +export const getMethodsFromAbi = ( + abi: ABI, + type: "read" | "write" +): ethers.utils.FunctionFragment[] => { if (type === "read") { const readMethods = abi.filter( (fragment) => - fragment.type === "function" && (fragment.stateMutability === "view" || fragment.stateMutability === "pure") + fragment.type === "function" && + (fragment.stateMutability === "view" || + fragment.stateMutability === "pure") ); const contractInterface = new ethers.utils.Interface(readMethods); return contractInterface.fragments as ethers.utils.FunctionFragment[]; @@ -52,7 +59,8 @@ export const getMethodsFromAbi = (abi: ABI, type: "read" | "write"): ethers.util const writeMethods = abi.filter( (fragment) => fragment.type === "function" && - (fragment.stateMutability === "nonpayable" || fragment.stateMutability === "payable") + (fragment.stateMutability === "nonpayable" || + fragment.stateMutability === "payable") ); const contractInterface = new ethers.utils.Interface(writeMethods); return contractInterface.fragments as ethers.utils.FunctionFragment[]; diff --git a/src/commands/contract/utils/helpers.ts b/src/commands/contract/utils/helpers.ts index 373e8839..d8808183 100644 --- a/src/commands/contract/utils/helpers.ts +++ b/src/commands/contract/utils/helpers.ts @@ -1,18 +1,18 @@ +import fs from "fs"; import chalk from "chalk"; import { ethers } from "ethers"; -import fs from "fs"; import inquirer from "inquirer"; import ora from "ora"; -import { getMethodId } from "./formatters.js"; -import { getProxyImplementation } from "./proxy.js"; import { fileOrDirExists } from "../../../utils/files.js"; import { formatSeparator } from "../../../utils/formatters.js"; import Logger from "../../../utils/logger.js"; +import { getMethodId } from "./formatters.js"; +import { getProxyImplementation } from "./proxy.js"; -import type { L2Chain } from "../../../data/chains.js"; import type { AsyncDynamicQuestionProperty, DistinctChoice } from "inquirer"; import type { Provider } from "zksync-ethers"; +import type { L2Chain } from "../../../data/chains.js"; export type ABI = Record[]; export type ContractInfo = { @@ -28,11 +28,16 @@ export const formatMethodString = (method: string): string => { return method.substring("function ".length).replace(/\).+$/, ")"); }; -export const getMethodsFromAbi = (abi: ABI, type: "read" | "write" | "any"): ethers.utils.FunctionFragment[] => { +export const getMethodsFromAbi = ( + abi: ABI, + type: "read" | "write" | "any" +): ethers.utils.FunctionFragment[] => { const getReadMethods = () => { const readMethods = abi.filter( (fragment) => - fragment.type === "function" && (fragment.stateMutability === "view" || fragment.stateMutability === "pure") + fragment.type === "function" && + (fragment.stateMutability === "view" || + fragment.stateMutability === "pure") ); const contractInterface = new ethers.utils.Interface(readMethods); return contractInterface.fragments as ethers.utils.FunctionFragment[]; @@ -41,7 +46,8 @@ export const getMethodsFromAbi = (abi: ABI, type: "read" | "write" | "any"): eth const writeMethods = abi.filter( (fragment) => fragment.type === "function" && - (fragment.stateMutability === "nonpayable" || fragment.stateMutability === "payable") + (fragment.stateMutability === "nonpayable" || + fragment.stateMutability === "payable") ); const contractInterface = new ethers.utils.Interface(writeMethods); return contractInterface.fragments as ethers.utils.FunctionFragment[]; @@ -54,21 +60,34 @@ export const getMethodsFromAbi = (abi: ABI, type: "read" | "write" | "any"): eth return [...getReadMethods(), ...getWriteMethods()]; }; -export const checkIfMethodExists = (contractInfo: ContractInfo, method: string) => { +export const checkIfMethodExists = ( + contractInfo: ContractInfo, + method: string +) => { const methodId = getMethodId(method); if (!contractInfo.bytecode.includes(methodId)) { if (!contractInfo.implementation) { - Logger.warn("Provided method is not part of the contract and will only work if provided contract is a proxy"); + Logger.warn( + "Provided method is not part of the contract and will only work if provided contract is a proxy" + ); } else if (!contractInfo.implementation.bytecode.includes(methodId)) { - Logger.warn("Provided method is not part of the provided contract nor its implementation"); + Logger.warn( + "Provided method is not part of the provided contract nor its implementation" + ); } } }; -export const getContractABI = async (chain: L2Chain, contractAddress: string) => { +export const getContractABI = async ( + chain: L2Chain, + contractAddress: string +) => { if (!chain.verificationApiUrl) return; - const response = await fetch(`${chain.verificationApiUrl}/contract_verification/info/${contractAddress}`); - const decoded: { artifacts: { abi: Record[] } } = await response.json(); + const response = await fetch( + `${chain.verificationApiUrl}/contract_verification/info/${contractAddress}` + ); + const decoded: { artifacts: { abi: Record[] } } = + await response.json(); return decoded.artifacts.abi; }; @@ -86,7 +105,9 @@ export const readAbiFromFile = (abiFilePath: string): ABI => { } throw new Error("ABI wasn't found in the provided file"); } catch (error) { - throw new Error(`Failed to parse ABI file: ${error instanceof Error ? error.message : error}`); + throw new Error( + `Failed to parse ABI file: ${error instanceof Error ? error.message : error}` + ); } }; @@ -98,7 +119,9 @@ export const getContractInformation = async ( ): Promise => { const [bytecode, abi] = await Promise.all([ provider.getCode(contractAddress), - chain ? getContractABI(chain, contractAddress).catch(() => undefined) : undefined, + chain + ? getContractABI(chain, contractAddress).catch(() => undefined) + : undefined, ]); const contractInfo: ContractInfo = { address: contractAddress, @@ -107,9 +130,16 @@ export const getContractInformation = async ( }; if (options?.fetchImplementation) { - const implementationAddress = await getProxyImplementation(contractAddress, provider).catch(() => undefined); + const implementationAddress = await getProxyImplementation( + contractAddress, + provider + ).catch(() => undefined); if (implementationAddress) { - const implementation = await getContractInformation(chain, provider, implementationAddress); + const implementation = await getContractInformation( + chain, + provider, + implementationAddress + ); contractInfo.implementation = implementation; } } @@ -124,7 +154,12 @@ export const getContractInfoWithLoader = async ( ): Promise => { const spinner = ora("Fetching contract information...").start(); try { - const contractInfo = await getContractInformation(chain, provider, contractAddress, { fetchImplementation: true }); + const contractInfo = await getContractInformation( + chain, + provider, + contractAddress, + { fetchImplementation: true } + ); if (contractInfo.bytecode === "0x") { throw new Error("Provided address is not a contract"); } @@ -145,7 +180,9 @@ export const askAbiMethod = async ( return "manual"; } - const formatFragment = (fragment: ethers.utils.FunctionFragment): DistinctChoice => { + const formatFragment = ( + fragment: ethers.utils.FunctionFragment + ): DistinctChoice => { let name = fragment.format(ethers.utils.FormatTypes.full); if ((type === "write" || type === "any") && name.includes(" returns ")) { name = name.substring(0, name.indexOf(" returns ")); // remove return type for write methods @@ -158,10 +195,22 @@ export const askAbiMethod = async ( const choices: AsyncDynamicQuestionProperty = []; const separators = { - noReadMethods: { type: "separator", line: chalk.white("No read methods found") } as DistinctChoice, - noWriteMethods: { type: "separator", line: chalk.white("No write methods found") } as DistinctChoice, - noMethods: { type: "separator", line: chalk.white("No methods found") } as DistinctChoice, - contractNotVerified: { type: "separator", line: chalk.white("Contract is not verified") } as DistinctChoice, + noReadMethods: { + type: "separator", + line: chalk.white("No read methods found"), + } as DistinctChoice, + noWriteMethods: { + type: "separator", + line: chalk.white("No write methods found"), + } as DistinctChoice, + noMethods: { + type: "separator", + line: chalk.white("No methods found"), + } as DistinctChoice, + contractNotVerified: { + type: "separator", + line: chalk.white("Contract is not verified"), + } as DistinctChoice, }; choices.push(formatSeparator("Provided contract") as DistinctChoice); if (contractInfo.abi) { @@ -182,8 +231,13 @@ export const askAbiMethod = async ( } if (contractInfo?.implementation) { if (contractInfo.implementation.abi) { - choices.push(formatSeparator("Resolved implementation") as DistinctChoice); - const implementationMethods = getMethodsFromAbi(contractInfo.implementation.abi, type); + choices.push( + formatSeparator("Resolved implementation") as DistinctChoice + ); + const implementationMethods = getMethodsFromAbi( + contractInfo.implementation.abi, + type + ); if (implementationMethods.length) { choices.push(...implementationMethods.map(formatFragment)); } else { @@ -206,17 +260,18 @@ export const askAbiMethod = async ( value: "manual", }); - const { method }: { method: ethers.utils.FunctionFragment | "manual" } = await inquirer.prompt([ - { - message: "Contract method to call", - name: "method", - type: "list", - choices, - required: true, - pageSize: 10, - loop: false, - }, - ]); + const { method }: { method: ethers.utils.FunctionFragment | "manual" } = + await inquirer.prompt([ + { + message: "Contract method to call", + name: "method", + type: "list", + choices, + required: true, + pageSize: 10, + loop: false, + }, + ]); return method; }; diff --git a/src/commands/contract/utils/proxy.ts b/src/commands/contract/utils/proxy.ts index 9e1112b9..79066034 100644 --- a/src/commands/contract/utils/proxy.ts +++ b/src/commands/contract/utils/proxy.ts @@ -21,7 +21,9 @@ const PROXY_CONTRACT_IMPLEMENTATION_ABI = [ ]; const EIP1967_PROXY_IMPLEMENTATION_SLOT = BigNumber.from( - ethers.utils.keccak256(ethers.utils.toUtf8Bytes("eip1967.proxy.implementation")) + ethers.utils.keccak256( + ethers.utils.toUtf8Bytes("eip1967.proxy.implementation") + ) ) .sub(1) .toHexString(); @@ -30,7 +32,9 @@ const EIP1967_PROXY_BEACON_SLOT = BigNumber.from( ) .sub(1) .toHexString(); -const EIP1822_PROXY_IMPLEMENTATION_SLOT = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("PROXIABLE")); +const EIP1822_PROXY_IMPLEMENTATION_SLOT = ethers.utils.keccak256( + ethers.utils.toUtf8Bytes("PROXIABLE") +); const getAddressSafe = async (getAddressFn: () => Promise) => { try { @@ -49,12 +53,33 @@ export const getProxyImplementation = async ( proxyContractAddress: string, provider: Provider ): Promise => { - const proxyContract = new ethers.Contract(proxyContractAddress, PROXY_CONTRACT_IMPLEMENTATION_ABI, provider); - const [implementation, eip1967Implementation, eip1967Beacon, eip1822Implementation] = await Promise.all([ + const proxyContract = new ethers.Contract( + proxyContractAddress, + PROXY_CONTRACT_IMPLEMENTATION_ABI, + provider + ); + const [ + implementation, + eip1967Implementation, + eip1967Beacon, + eip1822Implementation, + ] = await Promise.all([ getAddressSafe(() => proxyContract.implementation()), - getAddressSafe(() => provider.getStorageAt(proxyContractAddress, EIP1967_PROXY_IMPLEMENTATION_SLOT)), - getAddressSafe(() => provider.getStorageAt(proxyContractAddress, EIP1967_PROXY_BEACON_SLOT)), - getAddressSafe(() => provider.getStorageAt(proxyContractAddress, EIP1822_PROXY_IMPLEMENTATION_SLOT)), + getAddressSafe(() => + provider.getStorageAt( + proxyContractAddress, + EIP1967_PROXY_IMPLEMENTATION_SLOT + ) + ), + getAddressSafe(() => + provider.getStorageAt(proxyContractAddress, EIP1967_PROXY_BEACON_SLOT) + ), + getAddressSafe(() => + provider.getStorageAt( + proxyContractAddress, + EIP1822_PROXY_IMPLEMENTATION_SLOT + ) + ), ]); if (implementation) { return implementation; @@ -66,7 +91,11 @@ export const getProxyImplementation = async ( return eip1822Implementation; } if (eip1967Beacon) { - const beaconContract = new ethers.Contract(eip1967Beacon, PROXY_CONTRACT_IMPLEMENTATION_ABI, provider); + const beaconContract = new ethers.Contract( + eip1967Beacon, + PROXY_CONTRACT_IMPLEMENTATION_ABI, + provider + ); return getAddressSafe(() => beaconContract.implementation()); } }; diff --git a/src/commands/contract/write.ts b/src/commands/contract/write.ts index 56526735..6b5b6f1f 100644 --- a/src/commands/contract/write.ts +++ b/src/commands/contract/write.ts @@ -4,6 +4,21 @@ import { ethers } from "ethers"; import inquirer from "inquirer"; import ora from "ora"; +import { + chainOption, + l2RpcUrlOption, + privateKeyOption, +} from "../../common/options.js"; +import { l2Chains } from "../../data/chains.js"; +import { + getL2Provider, + getL2Wallet, + logFullCommandFromOptions, + optionNameToParam, +} from "../../utils/helpers.js"; +import Logger from "../../utils/logger.js"; +import { isAddress, isPrivateKey } from "../../utils/validators.js"; +import { getChains } from "../config/chains.js"; import Program from "./command.js"; import { abiOption, @@ -13,28 +28,30 @@ import { methodOption, showTransactionInfoOption, } from "./common/options.js"; -import { encodeData, formatArgs, getFragmentFromSignature, getInputsFromSignature } from "./utils/formatters.js"; import { + encodeData, + formatArgs, + getFragmentFromSignature, + getInputsFromSignature, +} from "./utils/formatters.js"; +import { + askAbiMethod, checkIfMethodExists, + formatMethodString, getContractInfoWithLoader, readAbiFromFile, - askAbiMethod, - formatMethodString, } from "./utils/helpers.js"; -import { chainOption, l2RpcUrlOption, privateKeyOption } from "../../common/options.js"; -import { l2Chains } from "../../data/chains.js"; -import { getL2Provider, getL2Wallet, logFullCommandFromOptions, optionNameToParam } from "../../utils/helpers.js"; -import Logger from "../../utils/logger.js"; -import { isAddress, isPrivateKey } from "../../utils/validators.js"; -import { getChains } from "../config/chains.js"; -import type { ContractInfo } from "./utils/helpers.js"; -import type { DefaultTransactionOptions } from "../../common/options.js"; import type { TransactionRequest } from "@ethersproject/abstract-provider"; import type { Command } from "commander"; import type { DistinctQuestion } from "inquirer"; +import type { DefaultTransactionOptions } from "../../common/options.js"; +import type { ContractInfo } from "./utils/helpers.js"; -const valueOption = new Option("--value ", "Ether value to send with transaction (e.g. 0.1)"); +const valueOption = new Option( + "--value ", + "Ether value to send with transaction (e.g. 0.1)" +); type WriteOptions = DefaultTransactionOptions & { contract?: string; @@ -123,34 +140,41 @@ const askArguments = async (method: string, options: WriteOptions) => { export const handler = async (options: WriteOptions, context: Command) => { try { const chains = [...l2Chains, ...getChains()]; - const answers: Pick = await inquirer.prompt( - [ - { - message: chainOption.description, - name: optionNameToParam(chainOption.long!), - type: "list", - choices: chains.map((e) => ({ name: e.name, value: e.network })), - required: true, - when: (answers: WriteOptions) => !answers.rpc, - }, - { - message: contractOption.description, - name: optionNameToParam(contractOption.long!), - type: "input", - required: true, - validate: (input: string) => isAddress(input), - }, - ], - options - ); + const answers: Pick = + await inquirer.prompt( + [ + { + message: chainOption.description, + name: optionNameToParam(chainOption.long!), + type: "list", + choices: chains.map((e) => ({ name: e.name, value: e.network })), + required: true, + when: (answers: WriteOptions) => !answers.rpc, + }, + { + message: contractOption.description, + name: optionNameToParam(contractOption.long!), + type: "input", + required: true, + validate: (input: string) => isAddress(input), + }, + ], + options + ); options.chain = answers.chain; options.contract = answers.contract; - const selectedChain = options.rpc ? undefined : chains.find((e) => e.network === options.chain); + const selectedChain = options.rpc + ? undefined + : chains.find((e) => e.network === options.chain); const provider = getL2Provider(options.rpc || selectedChain!.rpcUrl); - const contractInfo = await getContractInfoWithLoader(selectedChain, provider, options.contract!); + const contractInfo = await getContractInfoWithLoader( + selectedChain, + provider, + options.contract! + ); if (contractInfo.implementation) { Logger.info( `${chalk.green("✔")} ${chalk.bold("Contract implementation address")} ${chalk.cyan( @@ -192,7 +216,10 @@ export const handler = async (options: WriteOptions, context: Command) => { ], options ); - const senderWallet = getL2Wallet(options.privateKey || privateKey, provider); + const senderWallet = getL2Wallet( + options.privateKey || privateKey, + provider + ); const transaction: TransactionRequest = { from: senderWallet.address, to: contractInfo.address, @@ -202,17 +229,29 @@ export const handler = async (options: WriteOptions, context: Command) => { Logger.info(""); if (options.showInfo) { - Logger.info(chalk.gray("Transaction request: " + JSON.stringify(transaction, null, 2))); + Logger.info( + chalk.gray( + "Transaction request: " + JSON.stringify(transaction, null, 2) + ) + ); } const spinner = ora("Calling contract method...").start(); try { const response = await senderWallet.sendTransaction(transaction); - spinner.succeed(`Transaction submitted. Transaction hash: ${chalk.cyanBright(response.hash)}`); + spinner.succeed( + `Transaction submitted. Transaction hash: ${chalk.cyanBright(response.hash)}` + ); if (options.showInfo) { - Logger.info(chalk.gray("Transaction response: " + JSON.stringify(response, null, 2))); + Logger.info( + chalk.gray( + "Transaction response: " + JSON.stringify(response, null, 2) + ) + ); } - const receiptSpinner = ora("Waiting for transaction to be processed...").start(); + const receiptSpinner = ora( + "Waiting for transaction to be processed..." + ).start(); try { const receipt = await response.wait(); if (receipt.status) { @@ -226,7 +265,11 @@ export const handler = async (options: WriteOptions, context: Command) => { receiptSpinner.fail("Transaction failed"); } if (options.showInfo) { - Logger.info(chalk.gray("Transaction receipt: " + JSON.stringify(receipt, null, 2))); + Logger.info( + chalk.gray( + "Transaction receipt: " + JSON.stringify(receipt, null, 2) + ) + ); } } catch (error) { receiptSpinner.fail("Transaction failed"); diff --git a/src/commands/create/groups/contracts.ts b/src/commands/create/groups/contracts.ts index 961c73a6..9fbf612b 100644 --- a/src/commands/create/groups/contracts.ts +++ b/src/commands/create/groups/contracts.ts @@ -4,7 +4,13 @@ import inquirer from "inquirer"; import Logger from "../../../utils/logger.js"; import { packageManagers } from "../../../utils/packageManager.js"; import { isPrivateKey } from "../../../utils/validators.js"; -import { askForTemplate, setupTemplate, askForPackageManager, successfulMessage, getUniqueValues } from "../utils.js"; +import { + askForPackageManager, + askForTemplate, + getUniqueValues, + setupTemplate, + successfulMessage, +} from "../utils.js"; import type { GenericTemplate } from "../index.js"; @@ -35,20 +41,33 @@ export const templates: Template[] = [ }, ]; -export default async (folderLocation: string, folderRelativePath: string, templateKey?: string) => { +export default async ( + folderLocation: string, + folderRelativePath: string, + templateKey?: string +) => { let env: Record = {}; let template: Template; if (!templateKey) { - const { ethereumFramework }: { ethereumFramework: Template["ethereumFramework"] } = await inquirer.prompt([ - { - message: "Ethereum framework", - name: "ethereumFramework", - type: "list", - choices: getUniqueValues(templates.map((template) => template.ethereumFramework)), - required: true, - }, - ]); - template = await askForTemplate(templates.filter((template) => template.ethereumFramework === ethereumFramework)); + const { + ethereumFramework, + }: { ethereumFramework: Template["ethereumFramework"] } = + await inquirer.prompt([ + { + message: "Ethereum framework", + name: "ethereumFramework", + type: "list", + choices: getUniqueValues( + templates.map((template) => template.ethereumFramework) + ), + required: true, + }, + ]); + template = await askForTemplate( + templates.filter( + (template) => template.ethereumFramework === ethereumFramework + ) + ); } else { template = templates.find((e) => e.value === templateKey)!; Logger.info(`Using ${chalk.magentaBright(template.name)} template`); diff --git a/src/commands/create/groups/frontend.ts b/src/commands/create/groups/frontend.ts index 2b0d5ce0..4f5120c0 100644 --- a/src/commands/create/groups/frontend.ts +++ b/src/commands/create/groups/frontend.ts @@ -3,7 +3,13 @@ import inquirer from "inquirer"; import Logger from "../../../utils/logger.js"; import { packageManagers } from "../../../utils/packageManager.js"; -import { askForTemplate, setupTemplate, getUniqueValues, askForPackageManager, successfulMessage } from "../utils.js"; +import { + askForPackageManager, + askForTemplate, + getUniqueValues, + setupTemplate, + successfulMessage, +} from "../utils.js"; import type { GenericTemplate } from "../index.js"; @@ -192,30 +198,42 @@ export const templates: Template[] = [ }, ]; -export default async (folderLocation: string, folderRelativePath: string, templateKey?: string) => { +export default async ( + folderLocation: string, + folderRelativePath: string, + templateKey?: string +) => { let env: Record = {}; let template: Template; if (!templateKey) { - const { framework }: { framework: Template["framework"] } = await inquirer.prompt([ - { - message: "Frontend framework", - name: "framework", - type: "list", - choices: getUniqueValues(templates.map((template) => template.framework)), - required: true, - }, - ]); - const { ethereumFramework }: { ethereumFramework: Template["ethereumFramework"] } = await inquirer.prompt([ - { - message: "Ethereum framework", - name: "ethereumFramework", - type: "list", - choices: getUniqueValues( - templates.filter((template) => template.framework === framework).map((template) => template.ethereumFramework) - ), - required: true, - }, - ]); + const { framework }: { framework: Template["framework"] } = + await inquirer.prompt([ + { + message: "Frontend framework", + name: "framework", + type: "list", + choices: getUniqueValues( + templates.map((template) => template.framework) + ), + required: true, + }, + ]); + const { + ethereumFramework, + }: { ethereumFramework: Template["ethereumFramework"] } = + await inquirer.prompt([ + { + message: "Ethereum framework", + name: "ethereumFramework", + type: "list", + choices: getUniqueValues( + templates + .filter((template) => template.framework === framework) + .map((template) => template.ethereumFramework) + ), + required: true, + }, + ]); template = await askForTemplate( templates .filter((template) => template.framework === framework) @@ -223,18 +241,21 @@ export default async (folderLocation: string, folderRelativePath: string, templa ); } else { template = templates.find((e) => e.value === templateKey)!; - Logger.info(`Using ${chalk.magentaBright(`${template.name} - ${template.framework}`)} template`); + Logger.info( + `Using ${chalk.magentaBright(`${template.name} - ${template.framework}`)} template` + ); } if (template.requiresWalletConnectProjectId) { - const { walletConnectProjectId }: { walletConnectProjectId: string } = await inquirer.prompt([ - { - message: "WalletConnect Project ID", - name: "walletConnectProjectId", - suffix: chalk.gray(" Find it at https://cloud.walletconnect.com/app"), - type: "input", - required: true, - }, - ]); + const { walletConnectProjectId }: { walletConnectProjectId: string } = + await inquirer.prompt([ + { + message: "WalletConnect Project ID", + name: "walletConnectProjectId", + suffix: chalk.gray(" Find it at https://cloud.walletconnect.com/app"), + type: "input", + required: true, + }, + ]); env = { ...env, WALLET_CONNECT_PROJECT_ID: walletConnectProjectId, diff --git a/src/commands/create/groups/quickstart.ts b/src/commands/create/groups/quickstart.ts index 6c718512..d037e736 100644 --- a/src/commands/create/groups/quickstart.ts +++ b/src/commands/create/groups/quickstart.ts @@ -4,7 +4,13 @@ import inquirer from "inquirer"; import Logger from "../../../utils/logger.js"; import { packageManagers } from "../../../utils/packageManager.js"; import { isPrivateKey } from "../../../utils/validators.js"; -import { askForTemplate, setupTemplate, askForPackageManager, successfulMessage, getUniqueValues } from "../utils.js"; +import { + askForPackageManager, + askForTemplate, + getUniqueValues, + setupTemplate, + successfulMessage, +} from "../utils.js"; import type { PackageManagerType } from "../../../utils/packageManager.js"; import type { GenericTemplate } from "../index.js"; @@ -94,7 +100,9 @@ const logFoundryInfo = () => { const contractsDir = "/src"; const deploymentScriptsDir = "/script"; const tipMessage = - "- Tip: You can use the " + chalk.blueBright("--rpc-url") + " option to specify the network to deploy to."; + "- Tip: You can use the " + + chalk.blueBright("--rpc-url") + + " option to specify the network to deploy to."; const deployCommand = `- Deploy your contract: ${chalk.blueBright("forge script [OPTIONS] [ARGS] --zksync")}`; const directoryOverview = `${chalk.magentaBright("Directory Overview:")} - Contracts: ${contractsDir} @@ -111,7 +119,9 @@ const logHardhatInfo = (packageManager: PackageManagerType) => { const contractsDir = "/contracts"; const deploymentScriptsDir = "/deploy"; const tipMessage = - "- Tip: You can use the " + chalk.blueBright("--network") + " option to specify the network to deploy to."; + "- Tip: You can use the " + + chalk.blueBright("--network") + + " option to specify the network to deploy to."; const deployCommand = `- Deploy your contract: ${chalk.blueBright(packageManagers[packageManager].run("deploy"))}`; const directoryOverview = `${chalk.magentaBright("Directory Overview:")} - Contracts: ${contractsDir} @@ -124,20 +134,33 @@ const logHardhatInfo = (packageManager: PackageManagerType) => { Logger.info(`${directoryOverview}\n\n${commandsOverview}`); }; -export default async (folderLocation: string, folderRelativePath: string, templateKey?: string) => { +export default async ( + folderLocation: string, + folderRelativePath: string, + templateKey?: string +) => { let env: Record = {}; let template: Template; if (!templateKey) { - const { ethereumFramework }: { ethereumFramework: Template["ethereumFramework"] } = await inquirer.prompt([ - { - message: "Ethereum framework", - name: "ethereumFramework", - type: "list", - choices: getUniqueValues(templates.map((template) => template.ethereumFramework)), - required: true, - }, - ]); - template = await askForTemplate(templates.filter((template) => template.ethereumFramework === ethereumFramework)); + const { + ethereumFramework, + }: { ethereumFramework: Template["ethereumFramework"] } = + await inquirer.prompt([ + { + message: "Ethereum framework", + name: "ethereumFramework", + type: "list", + choices: getUniqueValues( + templates.map((template) => template.ethereumFramework) + ), + required: true, + }, + ]); + template = await askForTemplate( + templates.filter( + (template) => template.ethereumFramework === ethereumFramework + ) + ); } else { template = templates.find((e) => e.value === templateKey)!; Logger.info(`Using ${chalk.magentaBright(template.name)} template`); diff --git a/src/commands/create/groups/scripting.ts b/src/commands/create/groups/scripting.ts index 025d64cb..c5466db6 100644 --- a/src/commands/create/groups/scripting.ts +++ b/src/commands/create/groups/scripting.ts @@ -2,7 +2,12 @@ import chalk from "chalk"; import Logger from "../../../utils/logger.js"; import { packageManagers } from "../../../utils/packageManager.js"; -import { askForTemplate, setupTemplate, askForPackageManager, successfulMessage } from "../utils.js"; +import { + askForPackageManager, + askForTemplate, + setupTemplate, + successfulMessage, +} from "../utils.js"; import type { GenericTemplate } from "../index.js"; @@ -38,8 +43,14 @@ export const templates: Template[] = [ }, ]; -export default async (folderLocation: string, folderRelativePath: string, templateKey?: string) => { - const template = templateKey ? templates.find((e) => e.value === templateKey)! : await askForTemplate(templates); +export default async ( + folderLocation: string, + folderRelativePath: string, + templateKey?: string +) => { + const template = templateKey + ? templates.find((e) => e.value === templateKey)! + : await askForTemplate(templates); if (templateKey) { Logger.info(`Using ${chalk.magentaBright(template.name)} template`); } diff --git a/src/commands/create/index.ts b/src/commands/create/index.ts index 9f6b26c1..e9dbd45d 100644 --- a/src/commands/create/index.ts +++ b/src/commands/create/index.ts @@ -1,17 +1,25 @@ +import path from "path"; import chalk from "chalk"; import { Option } from "commander"; import inquirer from "inquirer"; -import path from "path"; -import useContractTemplates, { templates as contractTemplates } from "./groups/contracts.js"; -import useFrontendTemplates, { templates as frontendTemplates } from "./groups/frontend.js"; -import useQuickstartTemplates, { templates as quickstartTemplates } from "./groups/quickstart.js"; -import useScriptingTemplates, { templates as scriptingTemplates } from "./groups/scripting.js"; import { zeekOption } from "../../common/options.js"; import Program from "../../program.js"; import { fileOrDirExists } from "../../utils/files.js"; import Logger from "../../utils/logger.js"; import zeek from "../../utils/zeek.js"; +import useContractTemplates, { + templates as contractTemplates, +} from "./groups/contracts.js"; +import useFrontendTemplates, { + templates as frontendTemplates, +} from "./groups/frontend.js"; +import useQuickstartTemplates, { + templates as quickstartTemplates, +} from "./groups/quickstart.js"; +import useScriptingTemplates, { + templates as scriptingTemplates, +} from "./groups/scripting.js"; import type { DefaultOptions } from "../../common/options.js"; @@ -23,16 +31,21 @@ export type GenericTemplate = { }; type ProjectType = "frontend" | "contracts" | "scripting" | "quickstart"; -const templateOption = new Option("--template ", "Project template to use").choices( - [...contractTemplates, ...frontendTemplates, ...scriptingTemplates, ...quickstartTemplates].map( - (template) => template.value - ) +const templateOption = new Option( + "--template ", + "Project template to use" +).choices( + [ + ...contractTemplates, + ...frontendTemplates, + ...scriptingTemplates, + ...quickstartTemplates, + ].map((template) => template.value) ); -const projectTypeOption = new Option("--project ", "Project type to select templates from").choices([ - "contracts", - "frontend", - "scripting", -]); +const projectTypeOption = new Option( + "--project ", + "Project type to select templates from" +).choices(["contracts", "frontend", "scripting"]); type CreateOptions = DefaultOptions & { folderName?: string; @@ -40,13 +53,18 @@ type CreateOptions = DefaultOptions & { template: string; }; -export const handler = async (predefinedFolderName: string | undefined, options: CreateOptions) => { +export const handler = async ( + predefinedFolderName: string | undefined, + options: CreateOptions +) => { try { options = { ...options, folderName: predefinedFolderName, }; - Logger.debug(`Initial create project options: ${JSON.stringify(options, null, 2)}`); + Logger.debug( + `Initial create project options: ${JSON.stringify(options, null, 2)}` + ); if (!options.folderName) { const transformer = (input: string) => { @@ -61,7 +79,10 @@ export const handler = async (predefinedFolderName: string | undefined, options: validate: (input) => { const formattedInput = input.trim().replace(/\s+/g, "-"); const isValid = /^[a-zA-Z0-9-_]+$/.test(formattedInput); - return isValid || "Folder name can only contain letters, numbers, hyphens, and underscores."; + return ( + isValid || + "Folder name can only contain letters, numbers, hyphens, and underscores." + ); }, transformer, }, @@ -70,11 +91,17 @@ export const handler = async (predefinedFolderName: string | undefined, options: } const folderLocation = path.join(process.cwd(), options.folderName!); if (fileOrDirExists(folderLocation)) { - throw new Error(`Folder at ${folderLocation} already exists. Try a different project name or remove the folder.`); + throw new Error( + `Folder at ${folderLocation} already exists. Try a different project name or remove the folder.` + ); } const templates: { - [key in ProjectType]: (folder: string, folderRelativePath: string, templateKey?: string) => Promise; + [key in ProjectType]: ( + folder: string, + folderRelativePath: string, + templateKey?: string + ) => Promise; } = { contracts: useContractTemplates, frontend: useFrontendTemplates, @@ -83,48 +110,72 @@ export const handler = async (predefinedFolderName: string | undefined, options: }; if (!options.template) { - const { projectType }: { projectType: ProjectType } = await inquirer.prompt([ - { - message: "What type of project do you want to create?", - name: "projectType", - type: "list", - choices: [ - { - name: `Contracts ${chalk.gray("- quick contract deployment and testing")}`, - short: "Contracts", - value: "contracts", - }, - { - name: `Frontend ${chalk.gray("- rapid UI development and integration")}`, - short: "Frontend", - value: "frontend", - }, - { - name: `Scripting ${chalk.gray("- automated interactions and advanced ZKsync operations")}`, - short: "Scripting", - value: "scripting", - }, - ], - required: true, - }, - ]); + const { projectType }: { projectType: ProjectType } = + await inquirer.prompt([ + { + message: "What type of project do you want to create?", + name: "projectType", + type: "list", + choices: [ + { + name: `Contracts ${chalk.gray("- quick contract deployment and testing")}`, + short: "Contracts", + value: "contracts", + }, + { + name: `Frontend ${chalk.gray("- rapid UI development and integration")}`, + short: "Frontend", + value: "frontend", + }, + { + name: `Scripting ${chalk.gray("- automated interactions and advanced ZKsync operations")}`, + short: "Scripting", + value: "scripting", + }, + ], + required: true, + }, + ]); await templates[projectType](folderLocation, options.folderName); } else { // find project type by template value let projectType: ProjectType | undefined; - if (contractTemplates.some((template) => template.value === options.template)) { + if ( + contractTemplates.some( + (template) => template.value === options.template + ) + ) { projectType = "contracts"; - } else if (frontendTemplates.some((template) => template.value === options.template)) { + } else if ( + frontendTemplates.some( + (template) => template.value === options.template + ) + ) { projectType = "frontend"; - } else if (scriptingTemplates.some((template) => template.value === options.template)) { + } else if ( + scriptingTemplates.some( + (template) => template.value === options.template + ) + ) { projectType = "scripting"; - } else if (quickstartTemplates.some((template) => template.value === options.template)) { + } else if ( + quickstartTemplates.some( + (template) => template.value === options.template + ) + ) { projectType = "quickstart"; } - if (!projectType) throw new Error(`Could not find project type for template ${options.template}`); + if (!projectType) + throw new Error( + `Could not find project type for template ${options.template}` + ); - await templates[projectType](folderLocation, options.folderName, options.template); + await templates[projectType]( + folderLocation, + options.folderName, + options.template + ); } if (options.zeek) { diff --git a/src/commands/create/utils.ts b/src/commands/create/utils.ts index e5c7886d..cd1f0b3a 100644 --- a/src/commands/create/utils.ts +++ b/src/commands/create/utils.ts @@ -1,8 +1,8 @@ -import chalk from "chalk"; import fs from "fs"; +import path from "path"; +import chalk from "chalk"; import inquirer from "inquirer"; import ora from "ora"; -import path from "path"; import { copyRecursiveSync, fileOrDirExists } from "../../utils/files.js"; import { cloneRepo } from "../../utils/git.js"; @@ -10,15 +10,17 @@ import { executeCommand } from "../../utils/helpers.js"; import Logger from "../../utils/logger.js"; import { packageManagers } from "../../utils/packageManager.js"; +import type { PackageManagerType } from "../../utils/packageManager.js"; import type { Template } from "./groups/quickstart.js"; import type { GenericTemplate } from "./index.js"; -import type { PackageManagerType } from "../../utils/packageManager.js"; export const getUniqueValues = (arr: T[]) => { return Array.from(new Set(arr)); }; -export const askForTemplate = async (templates: T[]) => { +export const askForTemplate = async ( + templates: T[] +) => { const { name: templateValue }: { name: T["name"] } = await inquirer.prompt([ { message: "Template", @@ -34,15 +36,16 @@ export const askForTemplate = async (templates: T[]) }; export const askForPackageManager = async () => { - const { packageManager }: { packageManager: PackageManagerType } = await inquirer.prompt([ - { - message: "Package manager", - name: "packageManager", - type: "list", - choices: ["npm", "pnpm", "yarn", "bun"], - required: true, - }, - ]); + const { packageManager }: { packageManager: PackageManagerType } = + await inquirer.prompt([ + { + message: "Package manager", + name: "packageManager", + type: "list", + choices: ["npm", "pnpm", "yarn", "bun"], + required: true, + }, + ]); return packageManager; }; @@ -51,7 +54,9 @@ const setupEnv = (folderLocation: string, env: Record) => { const envPath = path.join(folderLocation, ".env"); // Initialize finalEnvContent with the content from .env if it exists or an empty string - let finalEnvContent = fs.existsSync(envPath) ? fs.readFileSync(envPath, "utf8") : ""; + let finalEnvContent = fs.existsSync(envPath) + ? fs.readFileSync(envPath, "utf8") + : ""; // Keep track of what keys we've seen in the final .env content const seenKeys: Record = {}; @@ -99,7 +104,9 @@ export const setupTemplate = async ( env: Record, packageManager?: PackageManagerType ) => { - Logger.info(`\nSetting up template in ${chalk.magentaBright(folderLocation)}...`); + Logger.info( + `\nSetting up template in ${chalk.magentaBright(folderLocation)}...` + ); const typedTemplate = template as Template; if (typedTemplate.framework === "Foundry") { await setupFoundryProject(template, folderLocation); @@ -119,11 +126,16 @@ export const setupTemplate = async ( }; // Sets up a foundry project by initializing it with the specified template. // Primarily only used for foundry quickstart templates. -const setupFoundryProject = async (template: GenericTemplate, folderLocation: string) => { +const setupFoundryProject = async ( + template: GenericTemplate, + folderLocation: string +) => { const spinner = ora("Initializing foundry project...").start(); try { - const isInstalled = await executeCommand("forge --version", { silent: true }); + const isInstalled = await executeCommand("forge --version", { + silent: true, + }); // TODO: https://github.com/matter-labs/zksync-cli/issues/137 if (!isInstalled) { spinner.fail( @@ -133,11 +145,16 @@ const setupFoundryProject = async (template: GenericTemplate, folderLocation: st } const cloneTempPath = path.join(folderLocation, "___temp"); - await executeCommand(`forge init --template ${template.git} ${cloneTempPath}`, { silent: true }); + await executeCommand( + `forge init --template ${template.git} ${cloneTempPath}`, + { silent: true } + ); const templatePath = path.join(cloneTempPath, template.path || ""); if (!fileOrDirExists(templatePath)) { - throw new Error(`The specified template path does not exist: ${templatePath}`); + throw new Error( + `The specified template path does not exist: ${templatePath}` + ); } copyRecursiveSync(templatePath, folderLocation); @@ -151,7 +168,10 @@ const setupFoundryProject = async (template: GenericTemplate, folderLocation: st }; // Sets up a Hardhat project by cloning the specified template and copying it to the specified folder location. -const setupHardhatProject = async (template: GenericTemplate, folderLocation: string) => { +const setupHardhatProject = async ( + template: GenericTemplate, + folderLocation: string +) => { if (!template.path) { const spinner = ora("Cloning template...").start(); try { @@ -159,7 +179,9 @@ const setupHardhatProject = async (template: GenericTemplate, folderLocation: st try { fs.rmSync(path.join(folderLocation, ".git"), { recursive: true }); } catch { - Logger.warn("Failed to remove .git folder. Make sure to remove it manually before pushing to a new repo."); + Logger.warn( + "Failed to remove .git folder. Make sure to remove it manually before pushing to a new repo." + ); } try { const githubFolderLocation = path.join(folderLocation, ".github"); @@ -167,7 +189,9 @@ const setupHardhatProject = async (template: GenericTemplate, folderLocation: st fs.rmSync(githubFolderLocation, { recursive: true }); } } catch { - Logger.warn("Failed to remove .github folder. Make sure to remove it manually before pushing to a new repo."); + Logger.warn( + "Failed to remove .github folder. Make sure to remove it manually before pushing to a new repo." + ); } spinner.succeed("Cloned template"); } catch (error) { @@ -191,7 +215,9 @@ const setupHardhatProject = async (template: GenericTemplate, folderLocation: st throw new Error("An error occurred while copying the template"); } } else { - throw new Error(`The specified template path does not exist: ${templatePath}`); + throw new Error( + `The specified template path does not exist: ${templatePath}` + ); } spinner.succeed("Cloned template"); } catch (error) { @@ -201,7 +227,10 @@ const setupHardhatProject = async (template: GenericTemplate, folderLocation: st } }; // Sets up environment variables in the specified folder location. -const setupEnvironmentVariables = async (folderLocation: string, env: Record) => { +const setupEnvironmentVariables = async ( + folderLocation: string, + env: Record +) => { const spinner = ora("Setting up environment variables...").start(); try { setupEnv(folderLocation, env); @@ -212,13 +241,19 @@ const setupEnvironmentVariables = async (folderLocation: string, env: Record { +const installDependencies = async ( + packageManager: PackageManagerType, + folderLocation: string +) => { const spinner = ora( `Installing dependencies with ${chalk.bold(packageManager)}... This may take a couple minutes.` ).start(); if (await packageManagers[packageManager].isInstalled()) { try { - await executeCommand(packageManagers[packageManager].install(), { cwd: folderLocation, silent: true }); + await executeCommand(packageManagers[packageManager].install(), { + cwd: folderLocation, + silent: true, + }); } catch (error) { spinner.fail("Failed to install dependencies"); throw error; @@ -237,7 +272,9 @@ export const successfulMessage = { start: (folderName: string) => { Logger.info(`\n${chalk.green("🎉 All set up! 🎉")}\n`); Logger.info("--------------------------\n", { noFormat: true }); - Logger.info(`${chalk.magentaBright("Navigate to your project:")} ${chalk.blueBright(`cd ${folderName}`)}\n`); + Logger.info( + `${chalk.magentaBright("Navigate to your project:")} ${chalk.blueBright(`cd ${folderName}`)}\n` + ); }, end: (folderName: string) => { Logger.info(`${chalk.magentaBright("\nFurther Reading:")} diff --git a/src/commands/dev/ModulesConfigHandler.ts b/src/commands/dev/ModulesConfigHandler.ts index 231ed057..56685ae2 100644 --- a/src/commands/dev/ModulesConfigHandler.ts +++ b/src/commands/dev/ModulesConfigHandler.ts @@ -1,7 +1,7 @@ -import { ModuleCategory } from "./modules/Module.js"; -import { getModulePackages } from "./modules/utils/packages.js"; import { configHandler } from "../../common/ConfigHandler.js"; import { getChains } from "../config/chains.js"; +import { ModuleCategory } from "./modules/Module.js"; +import { getModulePackages } from "./modules/utils/packages.js"; import type { ModuleNode, NodeInfo } from "./modules/Module.js"; import type Module from "./modules/Module.js"; @@ -24,8 +24,11 @@ class ConfigHandlerClass { } /* Returns modules selected in the config */ async getConfigModules() { - const selectedModules = configHandler.getConfigValue("modules") || []; - return (await this.getAllModules()).filter((module) => selectedModules.includes(module.package.name)); + const selectedModules = + configHandler.getConfigValue("modules") || []; + return (await this.getAllModules()).filter((module) => + selectedModules.includes(module.package.name) + ); } async setConfigModules(modules: string[]) { configHandler.setConfigValue("modules", modules); @@ -44,7 +47,9 @@ class ConfigHandlerClass { return chain; } const modules = await this.getConfigModules(); - const node = modules.find((module) => module.category === ModuleCategory.Node); + const node = modules.find( + (module) => module.category === ModuleCategory.Node + ); if (!node) { throw new Error("No node module found"); } diff --git a/src/commands/dev/clean.ts b/src/commands/dev/clean.ts index 9b509355..8ba0ceea 100644 --- a/src/commands/dev/clean.ts +++ b/src/commands/dev/clean.ts @@ -1,6 +1,6 @@ +import Logger from "../../utils/logger.js"; import Program from "./command.js"; import { modulesConfigHandler } from "./ModulesConfigHandler.js"; -import Logger from "../../utils/logger.js"; import type Module from "./modules/Module.js"; @@ -34,7 +34,9 @@ export const handler = async (modulePackageNames: string[]) => { const configModules = await modulesConfigHandler.getConfigModules(); modules.push(...configModules); } - Logger.info(`Cleaning: ${modules.map((module) => module.name).join(", ")}...`); + Logger.info( + `Cleaning: ${modules.map((module) => module.name).join(", ")}...` + ); await Promise.all(modules.map((module) => cleanModule(module))); } catch (error) { Logger.error("There was an error while cleaning the testing environment:"); diff --git a/src/commands/dev/command.ts b/src/commands/dev/command.ts index b5f35fb0..c540168f 100644 --- a/src/commands/dev/command.ts +++ b/src/commands/dev/command.ts @@ -1,3 +1,5 @@ import Program from "../../program.js"; -export default Program.command("dev").description("Manage local ZKsync development environment"); +export default Program.command("dev").description( + "Manage local ZKsync development environment" +); diff --git a/src/commands/dev/config.ts b/src/commands/dev/config.ts index 3984c8ab..ea6def8f 100644 --- a/src/commands/dev/config.ts +++ b/src/commands/dev/config.ts @@ -1,16 +1,16 @@ import chalk from "chalk"; import inquirer from "inquirer"; -import Program from "./command.js"; -import { ModuleCategory } from "./modules/Module.js"; -import { modulesConfigHandler } from "./ModulesConfigHandler.js"; import { formatSeparator } from "../../utils/formatters.js"; import Logger from "../../utils/logger.js"; import { getChains, promptAddNewChain } from "../config/chains.js"; +import Program from "./command.js"; +import { ModuleCategory } from "./modules/Module.js"; +import { modulesConfigHandler } from "./ModulesConfigHandler.js"; +import type { L2Chain } from "../../data/chains.js"; import type Module from "./modules/Module.js"; import type { ModuleNode, NodeInfo } from "./modules/Module.js"; -import type { L2Chain } from "../../data/chains.js"; type LocalConfigOptions = { node?: string; @@ -30,11 +30,15 @@ export const setupConfig = async (options: LocalConfigOptions = {}) => { const modules = await modulesConfigHandler.getAllModules(); if (!modules.length) { Logger.error("No installed modules were found"); - Logger.error("Run `npx zksync-cli dev install [module-name...]` to install modules."); + Logger.error( + "Run `npx zksync-cli dev install [module-name...]` to install modules." + ); return; } - const nodes = modules.filter((module) => module.category === ModuleCategory.Node); + const nodes = modules.filter( + (module) => module.category === ModuleCategory.Node + ); const chains = getChains(); const nodeAnswers: LocalConfigOptions = await inquirer.prompt( @@ -87,18 +91,24 @@ export const setupConfig = async (options: LocalConfigOptions = {}) => { nodeInfo = chain; modulesConfigHandler.setCustomChain(chain.network); } else { - selectedNode = modules.find((module) => module.package.name === options.node)! as ModuleNode; + selectedNode = modules.find( + (module) => module.package.name === options.node + )! as ModuleNode; nodeInfo = selectedNode.nodeInfo; modulesConfigHandler.setCustomChain(undefined); } - const potentialModules = modules.filter((module) => module.category !== ModuleCategory.Node); + const potentialModules = modules.filter( + (module) => module.category !== ModuleCategory.Node + ); const modulesWithSupportInfo = await Promise.all( potentialModules.map(async (module) => { try { return { instance: module, - unsupported: (await module.isNodeSupported(nodeInfo)) ? false : "Module doesn't support selected node", + unsupported: (await module.isNodeSupported(nodeInfo)) + ? false + : "Module doesn't support selected node", }; } catch { return { @@ -149,7 +159,9 @@ export const setupConfig = async (options: LocalConfigOptions = {}) => { Logger.debug("Saving configuration to dev config file..."); - const selectedAdditionalModules = options.modules!.map((module) => modules.find((m) => m.package.name === module)!); + const selectedAdditionalModules = options.modules!.map( + (module) => modules.find((m) => m.package.name === module)! + ); modulesConfigHandler.setConfigModules([ ...(selectedNode ? [selectedNode.package.name] : []), @@ -159,16 +171,24 @@ export const setupConfig = async (options: LocalConfigOptions = {}) => { export const handler = async (options: LocalConfigOptions = {}) => { try { - Logger.debug(`Initial dev config options: ${JSON.stringify(options, null, 2)}`); + Logger.debug( + `Initial dev config options: ${JSON.stringify(options, null, 2)}` + ); await setupConfig(options); Logger.info("\nConfiguration saved successfully!", { noFormat: true }); - Logger.info(`Start configured environment with \`${chalk.magentaBright("npx zksync-cli dev start")}\``); + Logger.info( + `Start configured environment with \`${chalk.magentaBright("npx zksync-cli dev start")}\`` + ); } catch (error) { - Logger.error("There was an error while configuring the testing environment:"); + Logger.error( + "There was an error while configuring the testing environment:" + ); Logger.error(error); } }; -Program.command("config").description("Select modules to run in local development environment").action(handler); +Program.command("config") + .description("Select modules to run in local development environment") + .action(handler); diff --git a/src/commands/dev/index.ts b/src/commands/dev/index.ts index 928c466a..aa3ae6c9 100644 --- a/src/commands/dev/index.ts +++ b/src/commands/dev/index.ts @@ -4,10 +4,8 @@ import "./restart.js"; import "./logs.js"; import "./clean.js"; import "./config.js"; - import "./install.js"; import "./update.js"; import "./uninstall.js"; import "./modules/index.js"; - import "./command.js"; // registers all the commands above diff --git a/src/commands/dev/install.ts b/src/commands/dev/install.ts index 43b59565..c2fa3670 100644 --- a/src/commands/dev/install.ts +++ b/src/commands/dev/install.ts @@ -1,14 +1,20 @@ import chalk from "chalk"; import { Option } from "commander"; -import Program from "./command.js"; -import { createModulesFolder, modulesPath } from "./modules/Module.js"; import { executeCommand } from "../../utils/helpers.js"; import Logger from "../../utils/logger.js"; +import Program from "./command.js"; +import { createModulesFolder, modulesPath } from "./modules/Module.js"; -const linkOption = new Option("--link", "Use `npm link` instead of `npm install` (useful during module development)"); +const linkOption = new Option( + "--link", + "Use `npm link` instead of `npm install` (useful during module development)" +); -export const handler = async (moduleNames: string[], options: { link: boolean }) => { +export const handler = async ( + moduleNames: string[], + options: { link: boolean } +) => { try { createModulesFolder(); diff --git a/src/commands/dev/logs.ts b/src/commands/dev/logs.ts index 17b8ffcb..9b6bcec0 100644 --- a/src/commands/dev/logs.ts +++ b/src/commands/dev/logs.ts @@ -1,8 +1,8 @@ import chalk from "chalk"; +import Logger from "../../utils/logger.js"; import Program from "./command.js"; import { modulesConfigHandler } from "./ModulesConfigHandler.js"; -import Logger from "../../utils/logger.js"; export const handler = async () => { try { @@ -27,7 +27,9 @@ export const handler = async () => { Logger.info(chalk.gray("No logs to display")); } } catch (error) { - Logger.error(`There was an error displaying logs: ${error?.toString()}`); + Logger.error( + `There was an error displaying logs: ${error?.toString()}` + ); } } } catch (error) { @@ -36,4 +38,6 @@ export const handler = async () => { } }; -Program.command("logs").description("Show logs for configured modules").action(handler); +Program.command("logs") + .description("Show logs for configured modules") + .action(handler); diff --git a/src/commands/dev/modules/Module.ts b/src/commands/dev/modules/Module.ts index e10ce749..3609b4fe 100644 --- a/src/commands/dev/modules/Module.ts +++ b/src/commands/dev/modules/Module.ts @@ -1,7 +1,11 @@ import fs from "fs"; import path from "path"; -import { fileOrDirExists, getLocalPath, writeFile } from "../../../utils/files.js"; +import { + fileOrDirExists, + getLocalPath, + writeFile, +} from "../../../utils/files.js"; import Logger from "../../../utils/logger.js"; import type { L2Chain } from "../../../data/chains.js"; @@ -85,9 +89,13 @@ abstract class Module { return {} as TModuleConfig; } else { try { - return JSON.parse(fs.readFileSync(this.configPath, { encoding: "utf-8" })); + return JSON.parse( + fs.readFileSync(this.configPath, { encoding: "utf-8" }) + ); } catch { - Logger.error(`There was an error while reading config file for module "${this.name}":`); + Logger.error( + `There was an error while reading config file for module "${this.name}":` + ); return {} as TModuleConfig; } } @@ -111,10 +119,15 @@ abstract class Module { export default Module; export type NodeInfo = L2Chain; -export abstract class ModuleNode extends Module { +export abstract class ModuleNode< + TModuleConfig = ModuleConfigDefault, +> extends Module { abstract get nodeInfo(): NodeInfo; - constructor(data: Omit, modulesConfigHandler: ConfigHandler) { + constructor( + data: Omit, + modulesConfigHandler: ConfigHandler + ) { super({ ...data, category: ModuleCategory.Node }, modulesConfigHandler); } } diff --git a/src/commands/dev/modules/index.ts b/src/commands/dev/modules/index.ts index 329e57d3..3723e098 100644 --- a/src/commands/dev/modules/index.ts +++ b/src/commands/dev/modules/index.ts @@ -9,7 +9,9 @@ export const handler = async () => { const modules = await modulesConfigHandler.getAllModules(); if (!modules.length) { Logger.warn("There are no modules installed"); - Logger.info("You can install modules with: `npx zksync-cli dev install [module-name...]"); + Logger.info( + "You can install modules with: `npx zksync-cli dev install [module-name...]" + ); return; } @@ -20,7 +22,9 @@ export const handler = async () => { if (moduleVersion) { logStr += ` ${moduleVersion}`; } - logStr += chalk.blueBright(` - ${module.package.name}${chalk.gray("@" + module.package.version)}`); + logStr += chalk.blueBright( + ` - ${module.package.name}${chalk.gray("@" + module.package.version)}` + ); if (module.package.symlinked) { logStr += chalk.blueBright(" (installed via --link)"); } @@ -32,4 +36,6 @@ export const handler = async () => { } }; -Program.command("modules").description("List currently installed modules").action(handler); +Program.command("modules") + .description("List currently installed modules") + .action(handler); diff --git a/src/commands/dev/modules/utils/packages.ts b/src/commands/dev/modules/utils/packages.ts index 1a9182cf..9e015d4f 100644 --- a/src/commands/dev/modules/utils/packages.ts +++ b/src/commands/dev/modules/utils/packages.ts @@ -30,7 +30,8 @@ const requireModule = async (modulePath: string): Promise => { const getPackageByPath = async (modulePath: string): Promise => { const modulePackagePath = path.join(modulePath, "package.json"); const packageContent = fs.readFileSync(modulePackagePath, "utf-8"); - const { name, version, main }: Package & { main: string } = JSON.parse(packageContent); + const { name, version, main }: Package & { main: string } = + JSON.parse(packageContent); return { module: await requireModule(path.join(modulePath, main)), name, @@ -75,7 +76,9 @@ const findInstalledModules = async (): Promise => { } const packageContent = fs.readFileSync(modulePackagePath, "utf-8"); - const modulesPackage: { dependencies?: Record } = JSON.parse(packageContent); + const modulesPackage: { + dependencies?: Record; + } = JSON.parse(packageContent); if (!modulesPackage.dependencies) { return []; } @@ -100,9 +103,12 @@ export const findDefaultModules = async (): Promise => { type PackageJSON = { name: string; version: string }; const require = createRequire(import.meta.url); const packages = { - "zkcli-in-memory-node": require("zkcli-in-memory-node/package.json") as PackageJSON, - "zkcli-dockerized-node": require("zkcli-dockerized-node/package.json") as PackageJSON, - "zkcli-block-explorer": require("zkcli-block-explorer/package.json") as PackageJSON, + "zkcli-in-memory-node": + require("zkcli-in-memory-node/package.json") as PackageJSON, + "zkcli-dockerized-node": + require("zkcli-dockerized-node/package.json") as PackageJSON, + "zkcli-block-explorer": + require("zkcli-block-explorer/package.json") as PackageJSON, "zkcli-portal": require("zkcli-portal/package.json") as PackageJSON, } as const; diff --git a/src/commands/dev/modules/utils/updates.ts b/src/commands/dev/modules/utils/updates.ts index c032b9c1..b3f16ef6 100644 --- a/src/commands/dev/modules/utils/updates.ts +++ b/src/commands/dev/modules/utils/updates.ts @@ -29,7 +29,9 @@ const getModuleUpdatesInfo = async (): Promise => { } }; -const checkModulesForUpdates = async (modules: Module[]): Promise => { +const checkModulesForUpdates = async ( + modules: Module[] +): Promise => { const moduleUpdatesInfo = await getModuleUpdatesInfo(); let hadChanges = false; @@ -38,7 +40,9 @@ const checkModulesForUpdates = async (modules: Module[]): Promise { - const installedModules = await Promise.all(modules.filter((module) => module.isInstalled())); + const installedModules = await Promise.all( + modules.filter((module) => module.isInstalled()) + ); const updateInfo = await checkModulesForUpdates(installedModules); return installedModules @@ -82,7 +93,8 @@ export const getModulesRequiringUpdates = async (modules: Module[]) => { module, currentVersion: module.version, latestVersion: updateInfo[module.package.name]?.latest, - requiresUpdate: module.version !== updateInfo[module.package.name]?.latest, + requiresUpdate: + module.version !== updateInfo[module.package.name]?.latest, })) .filter((e) => e.requiresUpdate); }; diff --git a/src/commands/dev/restart.ts b/src/commands/dev/restart.ts index 39c557c6..d670bd49 100644 --- a/src/commands/dev/restart.ts +++ b/src/commands/dev/restart.ts @@ -1,14 +1,16 @@ +import Logger from "../../utils/logger.js"; import Program from "./command.js"; import { handler as start } from "./start.js"; import { handler as stop } from "./stop.js"; -import Logger from "../../utils/logger.js"; export const handler = async (modulePackageNames: string[]) => { try { await stop(modulePackageNames); await start(); } catch (error) { - Logger.error("There was an error while restarting the testing environment:"); + Logger.error( + "There was an error while restarting the testing environment:" + ); Logger.error(error); } }; diff --git a/src/commands/dev/start.ts b/src/commands/dev/start.ts index 06d97c42..7a770e9f 100644 --- a/src/commands/dev/start.ts +++ b/src/commands/dev/start.ts @@ -1,13 +1,13 @@ import chalk from "chalk"; import ora from "ora"; +import { formatLogs } from "../../utils/formatters.js"; +import Logger from "../../utils/logger.js"; import Program from "./command.js"; import { setupConfig } from "./config.js"; import { ModuleCategory } from "./modules/Module.js"; import { getModulesRequiringUpdates } from "./modules/utils/updates.js"; import { modulesConfigHandler } from "./ModulesConfigHandler.js"; -import { formatLogs } from "../../utils/formatters.js"; -import Logger from "../../utils/logger.js"; import type Module from "./modules/Module.js"; @@ -65,7 +65,10 @@ const waitForCustomChainStart = async () => { spinner.succeed(`"${nodeInfo.name}" is alive!`); resolve(); } else { - Logger.debug("Received unexpected data from eth_blockNumber:", data); + Logger.debug( + "Received unexpected data from eth_blockNumber:", + data + ); } } else { updateSpinner(); @@ -89,7 +92,9 @@ const startModules = async (modules: Module[]) => { const stopOtherNodes = async (currentModules: Module[]) => { const modules = await modulesConfigHandler.getAllModules(); - const currentNodeKeys = currentModules.filter((e) => e.category === ModuleCategory.Node).map((m) => m.package.name); + const currentNodeKeys = currentModules + .filter((e) => e.category === ModuleCategory.Node) + .map((m) => m.package.name); for (const module of modules) { if ( @@ -111,7 +116,11 @@ const checkForUpdates = async (modules: Module[]) => { } Logger.info(chalk.yellow("\nModule updates available:")); - for (const { module, currentVersion, latestVersion } of modulesRequiringUpdates) { + for (const { + module, + currentVersion, + latestVersion, + } of modulesRequiringUpdates) { let str = `${module.name}: ${latestVersion}`; if (currentVersion) { str += chalk.gray(` (current: ${currentVersion})`); @@ -153,19 +162,27 @@ export const handler = async () => { try { if (!(await modulesConfigHandler.getConfigModules()).length) { await setupConfig(); - Logger.info(`You can change the config later with ${chalk.blueBright("`npx zksync-cli dev config`")}\n`, { - noFormat: true, - }); + Logger.info( + `You can change the config later with ${chalk.blueBright("`npx zksync-cli dev config`")}\n`, + { + noFormat: true, + } + ); } const modules = await modulesConfigHandler.getConfigModules(); if (!modules.length) { Logger.warn("Config does not contain any installed modules."); - Logger.warn("Run `npx zksync-cli dev config` to select which modules to use."); + Logger.warn( + "Run `npx zksync-cli dev config` to select which modules to use." + ); return; } - const sortedModules = [...modules.filter((e) => !e.startAfterNode), ...modules.filter((e) => e.startAfterNode)]; + const sortedModules = [ + ...modules.filter((e) => !e.startAfterNode), + ...modules.filter((e) => e.startAfterNode), + ]; await installModules(sortedModules); await stopOtherNodes(sortedModules); await startModules(sortedModules); @@ -178,4 +195,6 @@ export const handler = async () => { } }; -Program.command("start").description("Start local ZKsync environment and modules").action(handler); +Program.command("start") + .description("Start local ZKsync environment and modules") + .action(handler); diff --git a/src/commands/dev/stop.ts b/src/commands/dev/stop.ts index d13748e3..a668bcb1 100644 --- a/src/commands/dev/stop.ts +++ b/src/commands/dev/stop.ts @@ -1,6 +1,6 @@ +import Logger from "../../utils/logger.js"; import Program from "./command.js"; import { modulesConfigHandler } from "./ModulesConfigHandler.js"; -import Logger from "../../utils/logger.js"; export const handler = async (modulePackageNames: string[]) => { try { @@ -19,7 +19,11 @@ export const handler = async (modulePackageNames: string[]) => { modules.push(...configModules); } Logger.info(`Stopping: ${modules.map((m) => m.name).join(", ")}...`); - await Promise.all(modules.map((m) => m.isInstalled().then((installed) => (installed ? m.stop() : undefined)))); + await Promise.all( + modules.map((m) => + m.isInstalled().then((installed) => (installed ? m.stop() : undefined)) + ) + ); } catch (error) { Logger.error("There was an error while stopping the testing environment:"); Logger.error(error); diff --git a/src/commands/dev/uninstall.ts b/src/commands/dev/uninstall.ts index 736e7e38..eff854f8 100644 --- a/src/commands/dev/uninstall.ts +++ b/src/commands/dev/uninstall.ts @@ -1,19 +1,22 @@ import { Option } from "commander"; +import { executeCommand } from "../../utils/helpers.js"; +import Logger from "../../utils/logger.js"; import { cleanModule } from "./clean.js"; import Program from "./command.js"; import { createModulesFolder, modulesPath } from "./modules/Module.js"; import { findDefaultModules } from "./modules/utils/packages.js"; import { modulesConfigHandler } from "./ModulesConfigHandler.js"; -import { executeCommand } from "../../utils/helpers.js"; -import Logger from "../../utils/logger.js"; const unlinkOption = new Option( "--unlink", "Use `npm unlink` instead of `npm uninstall` (useful during module development)" ); -export const handler = async (moduleNames: string[], options: { unlink: boolean }) => { +export const handler = async ( + moduleNames: string[], + options: { unlink: boolean } +) => { try { if (!options.unlink) { const defaultModules = await findDefaultModules(); @@ -26,7 +29,11 @@ export const handler = async (moduleNames: string[], options: { unlink: boolean } const modules = await modulesConfigHandler.getAllModules(); - await Promise.all(modules.filter((e) => moduleNames.includes(e.package.name)).map((module) => cleanModule(module))); + await Promise.all( + modules + .filter((e) => moduleNames.includes(e.package.name)) + .map((module) => cleanModule(module)) + ); createModulesFolder(); diff --git a/src/commands/dev/update.ts b/src/commands/dev/update.ts index 07d97c29..5389b5b4 100644 --- a/src/commands/dev/update.ts +++ b/src/commands/dev/update.ts @@ -1,21 +1,30 @@ import chalk from "chalk"; import { Option } from "commander"; +import { executeCommand } from "../../utils/helpers.js"; +import Logger from "../../utils/logger.js"; import Program from "./command.js"; import { createModulesFolder, modulesPath } from "./modules/Module.js"; import { modulesConfigHandler } from "./ModulesConfigHandler.js"; -import { executeCommand } from "../../utils/helpers.js"; -import Logger from "../../utils/logger.js"; -const packageOption = new Option("--package", "Update NPM package instead of module"); -const forceOption = new Option("--force", "Force update module (skip version check)"); +const packageOption = new Option( + "--package", + "Update NPM package instead of module" +); +const forceOption = new Option( + "--force", + "Force update module (skip version check)" +); type ModuleUpdateOptions = { force?: boolean; package?: boolean; }; -export const handler = async (moduleNames: string[], options: ModuleUpdateOptions = {}) => { +export const handler = async ( + moduleNames: string[], + options: ModuleUpdateOptions = {} +) => { try { if (options.package) { createModulesFolder(); @@ -31,7 +40,9 @@ export const handler = async (moduleNames: string[], options: ModuleUpdateOption const modules = await modulesConfigHandler.getAllModules(); for (const moduleName of moduleNames) { Logger.info(""); - const module = modules.find((module) => module.package.name === moduleName); + const module = modules.find( + (module) => module.package.name === moduleName + ); if (!module) { Logger.error(`Module "${moduleName}" is not installed`); continue; @@ -48,7 +59,9 @@ export const handler = async (moduleNames: string[], options: ModuleUpdateOption } } if (!latestVersion) { - Logger.error(`Latest version wasn't found for module "${moduleName}"`); + Logger.error( + `Latest version wasn't found for module "${moduleName}"` + ); continue; } @@ -57,14 +70,18 @@ export const handler = async (moduleNames: string[], options: ModuleUpdateOption ); await module.update(); } catch (error) { - Logger.error(`There was an error while updating module "${moduleName}":`); + Logger.error( + `There was an error while updating module "${moduleName}":` + ); Logger.error(error); continue; } } } - Logger.info(`\nTo make sure changes are applied use: \`${chalk.magentaBright("npx zksync-cli dev start")}\``); + Logger.info( + `\nTo make sure changes are applied use: \`${chalk.magentaBright("npx zksync-cli dev start")}\`` + ); } catch (error) { Logger.error("There was an error while updating module:"); Logger.error(error); diff --git a/src/commands/transaction/command.ts b/src/commands/transaction/command.ts index f7f70dec..fb9d1537 100644 --- a/src/commands/transaction/command.ts +++ b/src/commands/transaction/command.ts @@ -1,3 +1,5 @@ import Program from "../../program.js"; -export default Program.command("transaction").description("Transactions related functionality"); +export default Program.command("transaction").description( + "Transactions related functionality" +); diff --git a/src/commands/transaction/index.ts b/src/commands/transaction/index.ts index e0256463..28144d6b 100644 --- a/src/commands/transaction/index.ts +++ b/src/commands/transaction/index.ts @@ -1,3 +1,2 @@ import "./info.js"; - import "./command.js"; // registers all the commands above diff --git a/src/commands/transaction/info.ts b/src/commands/transaction/info.ts index a6b6684d..4074941c 100644 --- a/src/commands/transaction/info.ts +++ b/src/commands/transaction/info.ts @@ -5,20 +5,28 @@ import inquirer from "inquirer"; import ora from "ora"; import { utils } from "zksync-ethers"; -import Program from "./command.js"; import { chainOption, l2RpcUrlOption } from "../../common/options.js"; import { promptChain } from "../../common/prompts.js"; import { ETH_TOKEN } from "../../utils/constants.js"; -import { useDecimals, convertBigNumbersToStrings, formatSeparator, getTimeAgo } from "../../utils/formatters.js"; +import { + convertBigNumbersToStrings, + formatSeparator, + getTimeAgo, + useDecimals, +} from "../../utils/formatters.js"; import { getL2Provider, optionNameToParam } from "../../utils/helpers.js"; import Logger from "../../utils/logger.js"; import { isTransactionHash } from "../../utils/validators.js"; import { abiOption } from "../contract/common/options.js"; -import { getContractInformation, readAbiFromFile } from "../contract/utils/helpers.js"; +import { + getContractInformation, + readAbiFromFile, +} from "../contract/utils/helpers.js"; +import Program from "./command.js"; -import type { L2Chain } from "../../data/chains.js"; import type { Provider } from "zksync-ethers"; import type { TransactionReceipt } from "zksync-ethers/src/types.js"; +import type { L2Chain } from "../../data/chains.js"; type TransactionInfoOptions = { chain?: string; @@ -29,7 +37,10 @@ type TransactionInfoOptions = { abi?: string; }; -const transactionHashOption = new Option("--tx, --transaction ", "Transaction hash"); +const transactionHashOption = new Option( + "--tx, --transaction ", + "Transaction hash" +); const fullOption = new Option("--full", "Show all available data"); const rawOption = new Option("--raw", "Show raw JSON response"); @@ -38,7 +49,11 @@ export const handler = async (options: TransactionInfoOptions) => { const transfers: { amount: BigNumber; from: string; to: string }[] = []; receipt.logs.forEach((log) => { try { - const parsed = utils.IERC20.decodeEventLog("Transfer", log.data, log.topics); + const parsed = utils.IERC20.decodeEventLog( + "Transfer", + log.data, + log.topics + ); transfers.push({ from: parsed.from, to: parsed.to, @@ -61,30 +76,43 @@ export const handler = async (options: TransactionInfoOptions) => { totalFee, paidByPaymaster: !transfers.length || - receipt.from !== transfers.find((transfer) => transfer.from === utils.BOOTLOADER_FORMAL_ADDRESS)?.to, + receipt.from !== + transfers.find( + (transfer) => transfer.from === utils.BOOTLOADER_FORMAL_ADDRESS + )?.to, }; }; const getDecodedMethodSignature = async (hexSignature: string) => { if (hexSignature === "0x") { return; } - return await fetch(`https://www.4byte.directory/api/v1/signatures/?format=json&hex_signature=${hexSignature}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }) + return await fetch( + `https://www.4byte.directory/api/v1/signatures/?format=json&hex_signature=${hexSignature}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + } + ) .then((res) => res.json()) .then((data) => data?.results?.[0]?.text_signature) .catch(() => undefined); }; - const getAddressAndMethodInfo = async (address: string, calldata: string, provider: Provider, chain: L2Chain) => { - let contractInfo = await getContractInformation(chain, provider, address, { fetchImplementation: true }).catch( - () => undefined - ); + const getAddressAndMethodInfo = async ( + address: string, + calldata: string, + provider: Provider, + chain: L2Chain + ) => { + let contractInfo = await getContractInformation(chain, provider, address, { + fetchImplementation: true, + }).catch(() => undefined); const hexSignature = calldata.slice(0, 10); let decodedSignature: string | undefined; - let decodedArgs: { name?: string; type: string; value: string }[] | undefined; + let decodedArgs: + | { name?: string; type: string; value: string }[] + | undefined; if (options.abi) { if (!contractInfo) { contractInfo = { @@ -97,10 +125,15 @@ export const handler = async (options: TransactionInfoOptions) => { } } if (contractInfo?.abi || contractInfo?.implementation?.abi) { - const initialAddressInterface = new ethers.utils.Interface(contractInfo?.abi || []); - const implementationInterface = new ethers.utils.Interface(contractInfo?.implementation?.abi || []); + const initialAddressInterface = new ethers.utils.Interface( + contractInfo?.abi || [] + ); + const implementationInterface = new ethers.utils.Interface( + contractInfo?.implementation?.abi || [] + ); const matchedMethod = - initialAddressInterface.getFunction(hexSignature) || implementationInterface.getFunction(hexSignature); + initialAddressInterface.getFunction(hexSignature) || + implementationInterface.getFunction(hexSignature); if (matchedMethod) { decodedSignature = matchedMethod.format(ethers.utils.FormatTypes.full); if (decodedSignature.startsWith("function")) { @@ -115,10 +148,15 @@ export const handler = async (options: TransactionInfoOptions) => { if (decodedSignature) { try { - const contractInterface = new ethers.utils.Interface([`function ${decodedSignature}`]); + const contractInterface = new ethers.utils.Interface([ + `function ${decodedSignature}`, + ]); const inputs = contractInterface.getFunction(hexSignature).inputs; const encodedArgs = calldata.slice(10); - const decoded = ethers.utils.defaultAbiCoder.decode(inputs, `0x${encodedArgs}`); + const decoded = ethers.utils.defaultAbiCoder.decode( + inputs, + `0x${encodedArgs}` + ); decodedArgs = inputs.map((input, index) => { return { name: input.name, @@ -169,11 +207,12 @@ export const handler = async (options: TransactionInfoOptions) => { const l2Provider = getL2Provider(options.rpc ?? chain!.rpcUrl); const spinner = ora("Looking for transaction...").start(); try { - const [transactionData, transactionDetails, transactionReceipt] = await Promise.all([ - l2Provider.getTransaction(options.transaction!), - l2Provider.getTransactionDetails(options.transaction!), - l2Provider.getTransactionReceipt(options.transaction!), - ]); + const [transactionData, transactionDetails, transactionReceipt] = + await Promise.all([ + l2Provider.getTransaction(options.transaction!), + l2Provider.getTransactionDetails(options.transaction!), + l2Provider.getTransactionReceipt(options.transaction!), + ]); if (!transactionData) { throw new Error("Transaction not found"); } @@ -182,12 +221,19 @@ export const handler = async (options: TransactionInfoOptions) => { hexSignature: methodHexSignature, decodedSignature: methodDecodedSignature, decodedArgs: methodDecodedArgs, - } = await getAddressAndMethodInfo(transactionData.to!, transactionData.data, l2Provider, chain); + } = await getAddressAndMethodInfo( + transactionData.to!, + transactionData.data, + l2Provider, + chain + ); spinner.stop(); if (options.raw) { Logger.info( JSON.stringify( - convertBigNumbersToStrings(transactionReceipt || transactionDetails || transactionData), + convertBigNumbersToStrings( + transactionReceipt || transactionDetails || transactionData + ), null, 2 ), @@ -207,7 +253,10 @@ export const handler = async (options: TransactionInfoOptions) => { logString += "\nStatus: "; if (transactionDetails?.status === "failed") { logString += chalk.redBright("failed"); - } else if (transactionDetails?.status === "included" || transactionDetails?.status === "verified") { + } else if ( + transactionDetails?.status === "included" || + transactionDetails?.status === "verified" + ) { logString += chalk.greenBright("completed"); } else { logString += transactionDetails?.status || chalk.gray("N/A"); @@ -216,20 +265,30 @@ export const handler = async (options: TransactionInfoOptions) => { logString += `\nTo: ${transactionData.to}`; if (contractInfo?.implementation) { logString += chalk.gray(" |"); - logString += chalk.gray(` Implementation: ${contractInfo.implementation.address}`); + logString += chalk.gray( + ` Implementation: ${contractInfo.implementation.address}` + ); } logString += `\nValue: ${bigNumberToDecimal(transactionData.value)} ETH`; - const initialFee = transactionData.gasLimit.mul(transactionData.gasPrice!); - const feeData = transactionReceipt ? getTransactionFeeData(transactionReceipt) : undefined; + const initialFee = transactionData.gasLimit.mul( + transactionData.gasPrice! + ); + const feeData = transactionReceipt + ? getTransactionFeeData(transactionReceipt) + : undefined; logString += `\nFee: ${bigNumberToDecimal(feeData?.totalFee || initialFee)} ETH`; if (feeData?.paidByPaymaster) { logString += chalk.gray(" (paid by paymaster)"); } if (feeData) { logString += chalk.gray(" |"); - logString += chalk.gray(` Initial: ${bigNumberToDecimal(initialFee)} ETH`); - logString += chalk.gray(` Refunded: ${bigNumberToDecimal(feeData.refunded)} ETH`); + logString += chalk.gray( + ` Initial: ${bigNumberToDecimal(initialFee)} ETH` + ); + logString += chalk.gray( + ` Refunded: ${bigNumberToDecimal(feeData.refunded)} ETH` + ); } logString += "\nMethod: "; @@ -245,7 +304,9 @@ export const handler = async (options: TransactionInfoOptions) => { logString = ""; if (methodDecodedArgs) { - Logger.info(`\n${formatSeparator("Method arguments").line}`, { noFormat: true }); + Logger.info(`\n${formatSeparator("Method arguments").line}`, { + noFormat: true, + }); methodDecodedArgs.forEach((arg, index) => { if (index !== 0) { logString += "\n"; diff --git a/src/commands/wallet/balance.ts b/src/commands/wallet/balance.ts index 38e4d8fc..87f874f6 100644 --- a/src/commands/wallet/balance.ts +++ b/src/commands/wallet/balance.ts @@ -1,8 +1,13 @@ import chalk from "chalk"; import inquirer from "inquirer"; -import Program from "./command.js"; -import { accountOption, tokenOption, chainOption, l2RpcUrlOption, zeekOption } from "../../common/options.js"; +import { + accountOption, + chainOption, + l2RpcUrlOption, + tokenOption, + zeekOption, +} from "../../common/options.js"; import { l2Chains } from "../../data/chains.js"; import { ETH_TOKEN } from "../../utils/constants.js"; import { useDecimals } from "../../utils/formatters.js"; @@ -12,6 +17,7 @@ import { getBalance, getTokenInfo } from "../../utils/token.js"; import { isAddress } from "../../utils/validators.js"; import zeek from "../../utils/zeek.js"; import { getChains } from "../config/chains.js"; +import Program from "./command.js"; import type { DefaultOptions } from "../../common/options.js"; @@ -58,14 +64,22 @@ export const handler = async (options: BalanceOptions) => { const selectedChain = chains.find((e) => e.network === options.chain); const l2Provider = getL2Provider(options.rpc ?? selectedChain!.rpcUrl); - const token = options.token ? await getTokenInfo(options.token!, l2Provider) : ETH_TOKEN; + const token = options.token + ? await getTokenInfo(options.token!, l2Provider) + : ETH_TOKEN; if (!token.address) { - throw new Error(`Token ${token.symbol} does not exist on ${selectedChain?.name}`); + throw new Error( + `Token ${token.symbol} does not exist on ${selectedChain?.name}` + ); } const { bigNumberToDecimal } = useDecimals(token.decimals); - const balance = await getBalance(token.address, options.address!, l2Provider); + const balance = await getBalance( + token.address, + options.address!, + l2Provider + ); Logger.info( `\n${selectedChain?.name} Balance: ${bigNumberToDecimal(balance)} ${token.symbol} ${ token.name ? chalk.gray(`(${token.name})`) : "" @@ -76,7 +90,9 @@ export const handler = async (options: BalanceOptions) => { zeek(); } } catch (error) { - Logger.error("There was an error while fetching balance for the specified address:"); + Logger.error( + "There was an error while fetching balance for the specified address:" + ); Logger.error(error); } }; diff --git a/src/commands/wallet/command.ts b/src/commands/wallet/command.ts index 19461f51..fd10b4cd 100644 --- a/src/commands/wallet/command.ts +++ b/src/commands/wallet/command.ts @@ -1,3 +1,5 @@ import Program from "../../program.js"; -export default Program.command("wallet").description("Manage wallet related features"); +export default Program.command("wallet").description( + "Manage wallet related features" +); diff --git a/src/commands/wallet/index.ts b/src/commands/wallet/index.ts index ca096fc4..09381b5b 100644 --- a/src/commands/wallet/index.ts +++ b/src/commands/wallet/index.ts @@ -1,4 +1,3 @@ import "./transfer.js"; import "./balance.js"; - import "./command.js"; // registers all the commands above diff --git a/src/commands/wallet/transfer.ts b/src/commands/wallet/transfer.ts index 83dc5f73..7b57236c 100644 --- a/src/commands/wallet/transfer.ts +++ b/src/commands/wallet/transfer.ts @@ -2,25 +2,33 @@ import chalk from "chalk"; import inquirer from "inquirer"; import ora from "ora"; -import Program from "./command.js"; import { + amountOptionCreate, chainOption, - zeekOption, + l2RpcUrlOption, privateKeyOption, recipientOptionCreate, - amountOptionCreate, - l2RpcUrlOption, tokenOption, + zeekOption, } from "../../common/options.js"; import { l2Chains } from "../../data/chains.js"; import { ETH_TOKEN } from "../../utils/constants.js"; import { useDecimals } from "../../utils/formatters.js"; -import { getL2Provider, getL2Wallet, optionNameToParam } from "../../utils/helpers.js"; +import { + getL2Provider, + getL2Wallet, + optionNameToParam, +} from "../../utils/helpers.js"; import Logger from "../../utils/logger.js"; import { getBalance, getTokenInfo } from "../../utils/token.js"; -import { isDecimalAmount, isAddress, isPrivateKey } from "../../utils/validators.js"; +import { + isAddress, + isDecimalAmount, + isPrivateKey, +} from "../../utils/validators.js"; import zeek from "../../utils/zeek.js"; import { getChains } from "../config/chains.js"; +import Program from "./command.js"; import type { DefaultTransferOptions } from "../../common/options.js"; @@ -80,10 +88,16 @@ export const handler = async (options: TransferOptions) => { const selectedChain = chains.find((e) => e.network === options.chain); const l2Provider = getL2Provider(options.rpc ?? selectedChain!.rpcUrl); const senderWallet = getL2Wallet(options.privateKey, l2Provider); - const token = options.token ? await getTokenInfo(options.token!, l2Provider) : ETH_TOKEN; - const { decimalToBigNumber, bigNumberToDecimal } = useDecimals(token.decimals); + const token = options.token + ? await getTokenInfo(options.token!, l2Provider) + : ETH_TOKEN; + const { decimalToBigNumber, bigNumberToDecimal } = useDecimals( + token.decimals + ); if (!token.address) { - throw new Error(`Token ${token.symbol} does not exist on ${selectedChain?.name}`); + throw new Error( + `Token ${token.symbol} does not exist on ${selectedChain?.name}` + ); } const spinner = ora("Sending transfer...").start(); @@ -98,10 +112,16 @@ export const handler = async (options: TransferOptions) => { Logger.info("\nTransfer sent:"); Logger.info(` Transaction hash: ${transferReceipt.transactionHash}`); if (selectedChain?.explorerUrl) { - Logger.info(` Transaction link: ${selectedChain.explorerUrl}/tx/${transferReceipt.transactionHash}`); + Logger.info( + ` Transaction link: ${selectedChain.explorerUrl}/tx/${transferReceipt.transactionHash}` + ); } - const senderBalance = await getBalance(token.address, senderWallet.address, l2Provider); + const senderBalance = await getBalance( + token.address, + senderWallet.address, + l2Provider + ); Logger.info( `\nSender L2 balance after transaction: ${bigNumberToDecimal(senderBalance)} ${token.symbol} ${ token.name ? chalk.gray(`(${token.name})`) : "" diff --git a/src/common/ConfigHandler.ts b/src/common/ConfigHandler.ts index 22d64b75..7701b2a2 100644 --- a/src/common/ConfigHandler.ts +++ b/src/common/ConfigHandler.ts @@ -14,7 +14,9 @@ class ConfigHandlerClass { private loadConfig() { if (this.configExists) { try { - this.internalConfig = JSON.parse(readFileSync(this.configPath, "utf-8")); + this.internalConfig = JSON.parse( + readFileSync(this.configPath, "utf-8") + ); } catch (error) { Logger.error(`Error while reading config file: ${error}`); } @@ -30,7 +32,10 @@ class ConfigHandlerClass { return fileOrDirExists(this.configPath); } - private accessNestedProperty(path: string, createIfNotExist: boolean = false) { + private accessNestedProperty( + path: string, + createIfNotExist: boolean = false + ) { const keys = path.split("."); let current = this.internalConfig; for (let i = 0; i < keys.length - 1; i++) { diff --git a/src/common/options.ts b/src/common/options.ts index 5dac6d2c..d8ca408a 100644 --- a/src/common/options.ts +++ b/src/common/options.ts @@ -4,18 +4,38 @@ import { getChains } from "../commands/config/chains.js"; import { l2Chains } from "../data/chains.js"; const chains = [...l2Chains, ...getChains()]; -export const chainOption = new Option("--chain ", "Chain to use").choices(chains.map((chain) => chain.network)); -export const chainWithL1Option = new Option("--chain ", "Chain to use").choices( - chains.filter((e) => e.l1Chain).map((chain) => chain.network) +export const chainOption = new Option( + "--chain ", + "Chain to use" +).choices(chains.map((chain) => chain.network)); +export const chainWithL1Option = new Option( + "--chain ", + "Chain to use" +).choices(chains.filter((e) => e.l1Chain).map((chain) => chain.network)); +export const l1RpcUrlOption = new Option( + "--l1-rpc ", + "Override L1 RPC URL" ); -export const l1RpcUrlOption = new Option("--l1-rpc ", "Override L1 RPC URL"); export const l2RpcUrlOption = new Option("--rpc ", "Override L2 RPC URL"); -export const tokenOption = new Option("--token <0x address>", "ERC-20 token address"); -export const accountOption = new Option("--address <0x address>", "Account address"); -export const privateKeyOption = new Option("--pk, --private-key ", "Private key of the sender"); -export const amountOptionCreate = (action: string) => new Option("--amount <0.1>", `Amount to ${action}`); +export const tokenOption = new Option( + "--token <0x address>", + "ERC-20 token address" +); +export const accountOption = new Option( + "--address <0x address>", + "Account address" +); +export const privateKeyOption = new Option( + "--pk, --private-key ", + "Private key of the sender" +); +export const amountOptionCreate = (action: string) => + new Option("--amount <0.1>", `Amount to ${action}`); export const recipientOptionCreate = (recipientLocation: string) => - new Option("--to, --recipient <0x address>", `Recipient address on ${recipientLocation}`); + new Option( + "--to, --recipient <0x address>", + `Recipient address on ${recipientLocation}` + ); 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" diff --git a/src/common/prompts.ts b/src/common/prompts.ts index ec6f8f30..453bf539 100644 --- a/src/common/prompts.ts +++ b/src/common/prompts.ts @@ -7,7 +7,9 @@ import { formatSeparator } from "../utils/formatters.js"; import type { L2Chain } from "../data/chains.js"; -export const promptChain = async | undefined>( +export const promptChain = async < + T extends Record | undefined, +>( prompt: { message: string; name: string }, chains?: { filter?: (chain: L2Chain) => boolean }, options?: T @@ -16,7 +18,9 @@ export const promptChain = async | undefined> const allChains = [...l2Chains, ...customChains]; if (options?.[prompt.name]) { - const chain = allChains.find((chain) => chain.network === options[prompt.name]); + const chain = allChains.find( + (chain) => chain.network === options[prompt.name] + ); if (chain) { return chain; } else { @@ -37,10 +41,12 @@ export const promptChain = async | undefined> value: chain.network, })), formatSeparator("Custom chains"), - ...customChains.filter(chains?.filter || (() => true)).map((chain) => ({ - name: chain.name + chalk.gray(` - ${chain.network}`), - value: chain.network, - })), + ...customChains + .filter(chains?.filter || (() => true)) + .map((chain) => ({ + name: chain.name + chalk.gray(` - ${chain.network}`), + value: chain.network, + })), { name: chalk.greenBright("+") + " Add new chain", short: "Add new chain", diff --git a/src/data/chains.ts b/src/data/chains.ts index a6da023c..8d767d85 100644 --- a/src/data/chains.ts +++ b/src/data/chains.ts @@ -21,7 +21,11 @@ const sepolia: Chain = { explorerUrl: "https://sepolia.etherscan.io", }; -export type L2Chain = Chain & { l1Chain?: Chain; blockExplorerApiUrl?: string; verificationApiUrl?: string }; +export type L2Chain = Chain & { + l1Chain?: Chain; + blockExplorerApiUrl?: string; + verificationApiUrl?: string; +}; export const l2Chains: L2Chain[] = [ { id: 300, diff --git a/src/index.ts b/src/index.ts index 539b0b22..33895c50 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,15 +2,11 @@ import Program from "./program.js"; import "./commands/dev/index.js"; - import "./commands/contract/index.js"; import "./commands/transaction/index.js"; - import "./commands/create/index.js"; - import "./commands/wallet/index.js"; import "./commands/bridge/index.js"; - import "./commands/config/index.js"; Program.parse(); diff --git a/src/lib/index.ts b/src/lib/index.ts index c4ae6a09..ddbbfc07 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,13 +1,28 @@ -import Module, { ModuleCategory, ModuleNode } from "../commands/dev/modules/Module.js"; +import Module, { + ModuleCategory, + ModuleNode, +} from "../commands/dev/modules/Module.js"; import * as docker from "../utils/docker.js"; import * as files from "../utils/files.js"; import * as git from "../utils/git.js"; import * as helpers from "../utils/helpers.js"; import Logger from "../utils/logger.js"; -import type { DefaultModuleFields, NodeInfo } from "../commands/dev/modules/Module.js"; +import type { + DefaultModuleFields, + NodeInfo, +} from "../commands/dev/modules/Module.js"; import type { ConfigHandler } from "../commands/dev/ModulesConfigHandler.js"; import type { LogEntry } from "../utils/formatters.js"; -export { Module, ModuleNode, ModuleCategory, Logger, docker, git, files, helpers }; +export { + Module, + ModuleNode, + ModuleCategory, + Logger, + docker, + git, + files, + helpers, +}; export type { ConfigHandler, NodeInfo, DefaultModuleFields, LogEntry }; diff --git a/src/program.ts b/src/program.ts index d6609b10..678b6c07 100644 --- a/src/program.ts +++ b/src/program.ts @@ -3,10 +3,14 @@ import { compare } from "compare-versions"; import Logger from "./utils/logger.js"; import { getNodeVersion } from "./utils/node.js"; -import { Package, checkForUpdates } from "./utils/package.js"; +import { checkForUpdates, Package } from "./utils/package.js"; const program = new Command(); -program.name(Package.name).description(Package.description).version(Package.version).showHelpAfterError(); +program + .name(Package.name) + .description(Package.description) + .version(Package.version) + .showHelpAfterError(); program.hook("preAction", async () => { const nodeVersion = await getNodeVersion(); const minimumNodeVersion = "18.0.0"; @@ -17,7 +21,9 @@ program.hook("preAction", async () => { process.exit(1); } } catch { - Logger.warn(`Failed to check Node.js version. Make sure you are using version ${minimumNodeVersion} or higher`); + Logger.warn( + `Failed to check Node.js version. Make sure you are using version ${minimumNodeVersion} or higher` + ); } await checkForUpdates(); diff --git a/src/utils/docker.ts b/src/utils/docker.ts index faa4a375..c553980a 100644 --- a/src/utils/docker.ts +++ b/src/utils/docker.ts @@ -11,15 +11,25 @@ const checkDockerInstallation = async () => { await executeCommand("docker --version", { silent: true }); dockerInstalled = true; } catch { - throw new Error("Docker is not installed. Download: https://www.docker.com/get-started/"); + throw new Error( + "Docker is not installed. Download: https://www.docker.com/get-started/" + ); } }; -const getComposeCommandBase = (dockerComposePath: string, projectDir?: string) => { +const getComposeCommandBase = ( + dockerComposePath: string, + projectDir?: string +) => { return `docker compose -f ${dockerComposePath} --project-directory ${projectDir ?? path.dirname(dockerComposePath)}`; }; const createComposeCommand = - (action: string) => async (dockerComposePath: string, projectDir?: string, additionalArgs?: string[]) => { + (action: string) => + async ( + dockerComposePath: string, + projectDir?: string, + additionalArgs?: string[] + ) => { await checkDockerInstallation(); const baseCommand = getComposeCommandBase(dockerComposePath, projectDir); const args = additionalArgs ? `${additionalArgs.join(" ")}` : ""; @@ -39,12 +49,18 @@ interface ContainerInfo { name: string; isRunning: boolean; } -export const composeStatus = async (dockerComposePath: string, projectDir?: string): Promise => { +export const composeStatus = async ( + dockerComposePath: string, + projectDir?: string +): Promise => { await checkDockerInstallation(); let statusJson = ( - await executeCommand(`${getComposeCommandBase(dockerComposePath, projectDir)} ps --format json --all`, { - silent: true, - }) + await executeCommand( + `${getComposeCommandBase(dockerComposePath, projectDir)} ps --format json --all`, + { + silent: true, + } + ) ).trim(); // trim to remove leading and trailing whitespace // if no containers are mounted, docker compose returns an empty string @@ -61,10 +77,14 @@ export const composeStatus = async (dockerComposePath: string, projectDir?: stri return containers.map((container) => ({ name: container.Name, - isRunning: container.State === ContainerStatus.Running || container.State === ContainerStatus.Restarting, + isRunning: + container.State === ContainerStatus.Running || + container.State === ContainerStatus.Restarting, })); } catch (error) { - Logger.debug(`Failed to JSON.parse compose status ${dockerComposePath}: ${error?.toString()}`); + Logger.debug( + `Failed to JSON.parse compose status ${dockerComposePath}: ${error?.toString()}` + ); Logger.debug(statusJson); return []; } @@ -76,15 +96,20 @@ export const composeLogs = async ( ): Promise => { await checkDockerInstallation(); const response = ( - await executeCommand(`${getComposeCommandBase(dockerComposePath, projectDir)} logs --tail=${totalLines}`, { - silent: true, - }) + await executeCommand( + `${getComposeCommandBase(dockerComposePath, projectDir)} logs --tail=${totalLines}`, + { + silent: true, + } + ) ).trim(); // trim to remove leading and trailing whitespace try { return response.split("\n"); } catch (error) { - Logger.debug(`Failed to split compose logs ${dockerComposePath}: ${error?.toString()}`); + Logger.debug( + `Failed to split compose logs ${dockerComposePath}: ${error?.toString()}` + ); return []; } }; diff --git a/src/utils/files.ts b/src/utils/files.ts index e667dafc..c4b04090 100644 --- a/src/utils/files.ts +++ b/src/utils/files.ts @@ -7,7 +7,8 @@ const getUserDirectory = () => { // From the XDG Base Directory Specification: // `$XDG_STATE_HOME` defines the base directory relative to which user-specific state files should be stored. // If `$XDG_STATE_HOME` is either not set or empty, a default equal to `$HOME/.local/state/` should be used. - const xdgStateHome = process.env.XDG_STATE_HOME || path.join(homedir(), ".local/state/"); + const xdgStateHome = + process.env.XDG_STATE_HOME || path.join(homedir(), ".local/state/"); return path.join(xdgStateHome, "zksync-cli/"); }; @@ -24,7 +25,10 @@ export const getDirPath = (filePath: string) => { return path.dirname(filename); }; -export const writeFile = (filePath: string, data: string | NodeJS.ArrayBufferView) => { +export const writeFile = ( + filePath: string, + data: string | NodeJS.ArrayBufferView +) => { // Create directory if it doesn't exist const directory = path.dirname(filePath); if (!fileOrDirExists(directory)) { @@ -35,7 +39,11 @@ export const writeFile = (filePath: string, data: string | NodeJS.ArrayBufferVie fs.writeFileSync(filePath, data, "utf-8"); }; -export const createSymlink = (targetPath: string, linkPath: string, type: "file" | "dir" | "junction" = "file") => { +export const createSymlink = ( + targetPath: string, + linkPath: string, + type: "file" | "dir" | "junction" = "file" +) => { if (fileOrDirExists(linkPath)) { throw new Error(`${type} already exists at ${linkPath}`); } @@ -48,7 +56,10 @@ export const copyRecursiveSync = (src: string, dest: string) => { if (isDirectory) { fs.mkdirSync(dest, { recursive: true }); fs.readdirSync(src).forEach((childItemName) => { - copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName)); + copyRecursiveSync( + path.join(src, childItemName), + path.join(dest, childItemName) + ); }); } else { fs.copyFileSync(src, dest); diff --git a/src/utils/formatters.ts b/src/utils/formatters.ts index d5660d64..705705b4 100644 --- a/src/utils/formatters.ts +++ b/src/utils/formatters.ts @@ -17,8 +17,10 @@ export function useDecimals(decimals: number): { bigNumberToDecimal: (amount: BigNumberish) => string; } { return { - decimalToBigNumber: (amount: string) => decimalToBigNumber(amount, decimals), - bigNumberToDecimal: (amount: BigNumberish) => bigNumberToDecimal(amount, decimals), + decimalToBigNumber: (amount: string) => + decimalToBigNumber(amount, decimals), + bigNumberToDecimal: (amount: BigNumberish) => + bigNumberToDecimal(amount, decimals), }; } @@ -41,7 +43,11 @@ export type LogEntry = list?: LogEntry[]; }; -const formatLogEntry = (entry: LogEntry, indentation = "", defaultColor = chalk.blueBright): string => { +const formatLogEntry = ( + entry: LogEntry, + indentation = "", + defaultColor = chalk.blueBright +): string => { function formatString(text: string): string { if (!text.trimStart().startsWith("-")) { text = `- ${text}`; @@ -55,7 +61,9 @@ const formatLogEntry = (entry: LogEntry, indentation = "", defaultColor = chalk. const { text, list } = entry; const formattedText = formatString(text); if (list && list.length > 0) { - const subEntries = list.map((item) => formatLogEntry(item, indentation + " ", defaultColor)).join("\n"); + const subEntries = list + .map((item) => formatLogEntry(item, indentation + " ", defaultColor)) + .join("\n"); return `${formattedText}\n${subEntries}`; } else { return formattedText; @@ -63,8 +71,14 @@ const formatLogEntry = (entry: LogEntry, indentation = "", defaultColor = chalk. } }; -export const formatLogs = (logs: LogEntry[], indentation = "", defaultColor = chalk.blueBright): string => { - return logs.map((entry) => formatLogEntry(entry, indentation, defaultColor)).join("\n"); +export const formatLogs = ( + logs: LogEntry[], + indentation = "", + defaultColor = chalk.blueBright +): string => { + return logs + .map((entry) => formatLogEntry(entry, indentation, defaultColor)) + .join("\n"); }; export const formatSeparator = (text: string) => { diff --git a/src/utils/git.ts b/src/utils/git.ts index abf2008d..4269ea92 100644 --- a/src/utils/git.ts +++ b/src/utils/git.ts @@ -16,7 +16,11 @@ const checkGitInstallation = async () => { } }; -export const cloneRepo = async (repoUrl: string, destination: string, options?: ExecuteOptions) => { +export const cloneRepo = async ( + repoUrl: string, + destination: string, + options?: ExecuteOptions +) => { if (fileOrDirExists(destination)) { Logger.debug(`${repoUrl} repository is already cloned. Skipping...`); return; @@ -29,21 +33,29 @@ export const cloneRepo = async (repoUrl: string, destination: string, options?: await executeCommand(command, options); }; -export const getLatestReleaseVersion = async (repo: string): Promise => { +export const getLatestReleaseVersion = async ( + repo: string +): Promise => { const apiUrl = `https://api.github.com/repos/${repo}/releases/latest`; try { const response = await fetch(apiUrl); if (!response.ok) { - throw new Error(`GitHub API request failed with status: ${response.status}`); + throw new Error( + `GitHub API request failed with status: ${response.status}` + ); } const releaseInfo = await response.json(); if (typeof releaseInfo?.tag_name !== "string") { - throw new Error(`Failed to parse the latest release version: ${JSON.stringify(releaseInfo)}`); + throw new Error( + `Failed to parse the latest release version: ${JSON.stringify(releaseInfo)}` + ); } return releaseInfo.tag_name; } catch (error) { if (error instanceof Error) { - throw new Error(`Failed to fetch the latest release version: ${error.message}`); + throw new Error( + `Failed to fetch the latest release version: ${error.message}` + ); } throw error; } diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 355da6de..516d070b 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -1,8 +1,8 @@ -import chalk from "chalk"; import { spawn } from "child_process"; +import chalk from "chalk"; import { ethers } from "ethers"; import { computeAddress } from "ethers/lib/utils.js"; -import { Wallet, Provider } from "zksync-ethers"; +import { Provider, Wallet } from "zksync-ethers"; import { Logger } from "../lib/index.js"; @@ -20,7 +20,9 @@ export const optionNameToParam = (input: string): string => { }; export const getAddressFromPrivateKey = (privateKey: string): string => { - return computeAddress(privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`); + return computeAddress( + privateKey.startsWith("0x") ? privateKey : `0x${privateKey}` + ); }; export const getL1Provider = (l1RpcUrl: string) => { @@ -30,7 +32,11 @@ export const getL2Provider = (rpc: string) => { return new Provider(rpc); }; -export const getL2Wallet = (privateKey: string, l2Provider: Provider, l1Provider?: ethers.providers.Provider) => { +export const getL2Wallet = ( + privateKey: string, + l2Provider: Provider, + l1Provider?: ethers.providers.Provider +) => { return new Wallet(privateKey, l2Provider, l1Provider); }; @@ -38,14 +44,21 @@ export interface ExecuteOptions { silent?: boolean; cwd?: string; } -export const executeCommand = (command: string, options: ExecuteOptions = {}): Promise => { +export const executeCommand = ( + command: string, + options: ExecuteOptions = {} +): Promise => { return new Promise((resolve, reject) => { const [cmd, ...args] = command.split(" "); - const child = spawn(cmd === "npm" ? (/^win/.test(process.platform) ? "npm.cmd" : "npm") : cmd, args, { - stdio: options.silent ? "pipe" : "inherit", - cwd: options.cwd, - }); + const child = spawn( + cmd === "npm" ? (/^win/.test(process.platform) ? "npm.cmd" : "npm") : cmd, + args, + { + stdio: options.silent ? "pipe" : "inherit", + cwd: options.cwd, + } + ); let output = ""; let errorOutput = ""; diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 6e0d77b8..9536d856 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,5 +1,5 @@ import chalk from "chalk"; -import { format, createLogger, transports } from "winston"; +import { createLogger, format, transports } from "winston"; import { hasColor } from "./helpers.js"; diff --git a/src/utils/node.ts b/src/utils/node.ts index fc2690eb..108f0284 100644 --- a/src/utils/node.ts +++ b/src/utils/node.ts @@ -5,7 +5,9 @@ let nodeVersion: string | undefined; export const getNodeVersion = async () => { if (nodeVersion) return nodeVersion; try { - const version = (await executeCommand("node --version", { silent: true })).trim(); + const version = ( + await executeCommand("node --version", { silent: true }) + ).trim(); const vIndex = version.indexOf("v"); if (vIndex === -1) { return version; diff --git a/src/utils/package.ts b/src/utils/package.ts index bf875c99..28b6db5a 100644 --- a/src/utils/package.ts +++ b/src/utils/package.ts @@ -1,6 +1,6 @@ -import chalk from "chalk"; import { readFileSync } from "fs"; import path from "path"; +import chalk from "chalk"; import updateNotifier from "update-notifier"; import { getDirPath } from "./files.js"; @@ -11,7 +11,12 @@ export const Package: { name: string; description: string; version: string; -} = JSON.parse(readFileSync(path.join(getDirPath(import.meta.url), "../../package.json"), "utf-8")); +} = JSON.parse( + readFileSync( + path.join(getDirPath(import.meta.url), "../../package.json"), + "utf-8" + ) +); export const checkForUpdates = async () => { if (Package.version === "0.0.0-development") return; diff --git a/src/utils/packageManager.ts b/src/utils/packageManager.ts index 174f5ccc..f24df225 100644 --- a/src/utils/packageManager.ts +++ b/src/utils/packageManager.ts @@ -12,7 +12,10 @@ interface PackageManagerMethods { } // The package manager implementations -export const packageManagers: Record = { +export const packageManagers: Record< + PackageManagerType, + PackageManagerMethods +> = { npm: { install(packages?: string): string { return `npm install${packages ? ` ${packages}` : ""} --force`; diff --git a/src/utils/token.ts b/src/utils/token.ts index 89d13c15..9865b578 100644 --- a/src/utils/token.ts +++ b/src/utils/token.ts @@ -50,11 +50,18 @@ export const getTokenInfo = async ( l2Provider: Provider, l1Provider?: ethers.providers.JsonRpcProvider ): Promise & { address?: string }> => { - if (tokenAddress === ETH_TOKEN.address || tokenAddress === ETH_TOKEN.l1Address) { + if ( + tokenAddress === ETH_TOKEN.address || + tokenAddress === ETH_TOKEN.l1Address + ) { return ETH_TOKEN; } - const { address, l1Address } = await getTokenAddresses(tokenAddress, l2Provider, l1Provider); + const { address, l1Address } = await getTokenAddresses( + tokenAddress, + l2Provider, + l1Provider + ); if (!address && !l1Provider) { throw new Error("Token with specified address was not found"); } @@ -88,7 +95,10 @@ export const getBalance = async ( address: string, provider: Provider | ethers.providers.JsonRpcProvider ): Promise => { - if (tokenAddress === ETH_TOKEN.address || tokenAddress === ETH_TOKEN.l1Address) { + if ( + tokenAddress === ETH_TOKEN.address || + tokenAddress === ETH_TOKEN.l1Address + ) { return provider.getBalance(address); } const balanceAbi = "balanceOf(address)"; diff --git a/src/utils/validators.ts b/src/utils/validators.ts index 88383266..82c7a578 100644 --- a/src/utils/validators.ts +++ b/src/utils/validators.ts @@ -4,7 +4,10 @@ import { getAddress } from "ethers/lib/utils.js"; import { ETH_TOKEN } from "./constants.js"; import { useDecimals } from "./formatters.js"; -export const isDecimalAmount = (amount: string, decimals = ETH_TOKEN.decimals) => { +export const isDecimalAmount = ( + amount: string, + decimals = ETH_TOKEN.decimals +) => { try { const { decimalToBigNumber } = useDecimals(decimals); if (BigNumber.isBigNumber(decimalToBigNumber(amount))) {