UNPKG

zksync-cli

Version:

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

195 lines 7.94 kB
import chalk from "chalk"; import { Option } from "commander"; 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 { decodeData, encodeData, formatArgs, getFragmentFromSignature, getInputValues, getInputsFromSignature, } from "./utils/formatters.js"; import { checkIfMethodExists, 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"; const outputsOption = new Option("--output, --outputTypes <output types...>", "Output types"); const fromOption = new Option("--from <0x address>", "Read on behalf of specific address"); const decodeSkipOption = new Option("--decode-skip", "Skip decoding response"); // ---------------- // prompts // ---------------- const askMethod = async (contractInfo, options) => { if (options.method) { return; } const methodByAbi = await askAbiMethod(contractInfo, "read"); if (methodByAbi !== "manual") { const fullMethodName = methodByAbi.format("full"); options.method = formatMethodString(fullMethodName); if (methodByAbi.outputs) { options.outputTypes = methodByAbi.outputs.map((output) => output.type); } 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); }; const askOutputTypes = async (rawCallResponse, options) => { if (!options.outputTypes) { Logger.info(chalk.gray("Provide output types to decode the response (optional)")); } const answers = await inquirer.prompt([ { message: outputsOption.description, name: optionNameToParam(outputsOption.long), type: "input", validate: (input) => { try { decodeData(getInputValues(input), rawCallResponse); // throws if invalid return true; } catch (error) { return `${chalk.redBright("Failed to decode response with provided types: " + (error instanceof Error ? error.message : error))}\nInput example: ${chalk.blueBright("string,uint256")}`; } }, }, ], options); options.outputTypes = options.outputTypes || getInputValues(answers.outputTypes); if (!options.outputTypes.length) return; const decodedOutput = decodeData(options.outputTypes, rawCallResponse); Logger.info(`${chalk.green("✔")} Decoded method response: ${chalk.cyanBright(decodedOutput)}`); }; // ---------------- // 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) { await checkIfMethodExists(contractInfo, options.method); } if (!options.data) { await askArguments(options.method, options); } options.arguments = formatArgs(options.method, options.arguments); const transaction = { to: contractInfo.address, data: options.data || encodeData(options.method, options.arguments), from: options.from, }; 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 provider.call(transaction); const isEmptyResponse = response === "0x"; spinner[isEmptyResponse ? "warn" : "succeed"](`Method response (raw): ${chalk.cyanBright(response)}`); if (!isEmptyResponse && !options.decodeSkip) { await askOutputTypes(response, options); } 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("read") .addOption(chainOption) .addOption(l2RpcUrlOption) .addOption(contractOption) .addOption(methodOption) .addOption(argumentsOption) .addOption(dataOption) .addOption(outputsOption) .addOption(fromOption) .addOption(abiOption) .addOption(decodeSkipOption) .addOption(showTransactionInfoOption) .description("Call contract method and decode response") .action(handler); //# sourceMappingURL=read.js.map