UNPKG

zksync-cli

Version:

CLI tool that simplifies the process of developing applications and interacting with the ZKsync network

199 lines 8.03 kB
import chalk from "chalk"; import { Option } from "commander"; import { ethers } from "ethers"; import inquirer from "inquirer"; import ora from "ora"; import Program from "./command.js"; import { abiOption, argumentsOption, contractOption, dataOption, methodOption, showTransactionInfoOption, } from "./common/options.js"; import { encodeData, formatArgs, getFragmentFromSignature, getInputsFromSignature } from "./utils/formatters.js"; import { checkIfMethodExists, 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"; const valueOption = new Option("--value <Ether amount>", "Ether value to send with transaction (e.g. 0.1)"); // ---------------- // prompts // ---------------- const askMethod = async (contractInfo, options) => { if (options.method) { return; } const methodByAbi = await askAbiMethod(contractInfo, "write"); if (methodByAbi !== "manual") { const fullMethodName = methodByAbi.format("full"); options.method = formatMethodString(fullMethodName); return; } const answers = await inquirer.prompt([ { message: "Enter method to call", name: optionNameToParam(methodOption.long), type: "input", validate: (input) => { try { getFragmentFromSignature(input); // throws if invalid return true; } catch { return `Invalid method signature. Example: ${chalk.blueBright("balanceOf(address)")}`; } }, }, ], options); options.method = answers.method; }; const askArguments = async (method, options) => { if (options.arguments) { return; } const inputs = getInputsFromSignature(method); if (!inputs.length) { options.arguments = []; return; } Logger.info(chalk.green("?") + chalk.bold(" Provide method arguments:")); const prompts = []; inputs.forEach((input, index) => { let name = chalk.gray(`[${index + 1}/${inputs.length}]`); if (input.name) { name += ` ${input.name}`; name += chalk.gray(` (${input.type})`); } else { name += ` ${input.type}`; } prompts.push({ message: name, name: index.toString(), type: "input", }); }); const answers = await inquirer.prompt(prompts); options.arguments = Object.values(answers); }; // ---------------- // request handler // ---------------- export const handler = async (options, context) => { try { const chains = [...l2Chains, ...getChains()]; const answers = 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) => !answers.rpc, }, { message: contractOption.description, name: optionNameToParam(contractOption.long), type: "input", required: true, validate: (input) => isAddress(input), }, ], options); options.chain = answers.chain; options.contract = answers.contract; 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); if (contractInfo.implementation) { Logger.info(`${chalk.green("✔")} ${chalk.bold("Contract implementation address")} ${chalk.cyan(contractInfo.implementation.address)}`); } if (!options.data) { if (options.abi) { contractInfo.abi = readAbiFromFile(options.abi); Logger.info(chalk.gray("Using provided ABI file")); } await askMethod(contractInfo, options); } if (options.method) { checkIfMethodExists(contractInfo, options.method); } if (!options.data) { await askArguments(options.method, options); } options.arguments = formatArgs(options.method, options.arguments); const { privateKey } = await inquirer.prompt([ { message: "Private key of the wallet to sign transaction", name: "privateKey", type: "input", required: true, validate: (input) => { return isPrivateKey(input); }, transformer: (input) => { return input.replace(/./g, "*"); }, }, ], options); const senderWallet = getL2Wallet(options.privateKey || privateKey, provider); const transaction = { from: senderWallet.address, to: contractInfo.address, data: options.data || encodeData(options.method, options.arguments), value: options.value ? ethers.parseEther(options.value) : undefined, }; Logger.info(""); if (options.showInfo) { 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)}`); if (options.showInfo) { Logger.info(chalk.gray("Transaction response: " + JSON.stringify(response, null, 2))); } const receiptSpinner = ora("Waiting for transaction to be processed...").start(); try { const receipt = await response.wait(); if (receipt.status) { receiptSpinner.succeed("Transaction processed successfully." + (!options.rpc && selectedChain?.explorerUrl ? ` Transaction link: ${selectedChain.explorerUrl}/tx/${response.hash}` : "")); } else { receiptSpinner.fail("Transaction failed"); } if (options.showInfo) { Logger.info(chalk.gray("Transaction receipt: " + JSON.stringify(receipt, null, 2))); } } catch (error) { receiptSpinner.fail("Transaction failed"); throw error; } logFullCommandFromOptions(options, context, { emptyLine: true }); } catch (error) { spinner.stop(); throw error; } } catch (error) { Logger.error("There was an error while performing method call"); Logger.error(error); } }; Program.command("write") .addOption(chainOption) .addOption(l2RpcUrlOption) .addOption(contractOption) .addOption(methodOption) .addOption(argumentsOption) .addOption(valueOption) .addOption(dataOption) .addOption(privateKeyOption) .addOption(abiOption) .addOption(showTransactionInfoOption) .description("Write contract method") .action(handler); //# sourceMappingURL=write.js.map