UNPKG

zksync-cli

Version:

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

188 lines 7.3 kB
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"; export const formatMethodString = (method) => { // remove "function " prefix and return type // e.g. "greet() view returns (string)" -> "greet()" return method.substring("function ".length).replace(/\).+$/, ")"); }; export const getMethodsFromAbi = (abi, type) => { const getReadMethods = () => { const readMethods = abi.filter((fragment) => fragment.type === "function" && (fragment.stateMutability === "view" || fragment.stateMutability === "pure")); const contractInterface = new ethers.utils.Interface(readMethods); return contractInterface.fragments; }; const getWriteMethods = () => { const writeMethods = abi.filter((fragment) => fragment.type === "function" && (fragment.stateMutability === "nonpayable" || fragment.stateMutability === "payable")); const contractInterface = new ethers.utils.Interface(writeMethods); return contractInterface.fragments; }; if (type === "read") { return getReadMethods(); } else if (type === "write") { return getWriteMethods(); } return [...getReadMethods(), ...getWriteMethods()]; }; export const checkIfMethodExists = (contractInfo, method) => { 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"); } else if (!contractInfo.implementation.bytecode.includes(methodId)) { Logger.warn("Provided method is not part of the provided contract nor its implementation"); } } }; export const getContractABI = async (chain, contractAddress) => { if (!chain.verificationApiUrl) return; const response = await fetch(`${chain.verificationApiUrl}/contract_verification/info/${contractAddress}`); const decoded = await response.json(); return decoded.artifacts.abi; }; export const readAbiFromFile = (abiFilePath) => { if (!fileOrDirExists(abiFilePath)) { throw new Error(`ABI not found at specified location: ${abiFilePath}`); } const contents = fs.readFileSync(abiFilePath, "utf-8"); try { const data = JSON.parse(contents); if (Array.isArray(data)) { return data; } else if (data?.abi) { return data.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}`); } }; export const getContractInformation = async (chain, provider, contractAddress, options) => { const [bytecode, abi] = await Promise.all([ provider.getCode(contractAddress), chain ? getContractABI(chain, contractAddress).catch(() => undefined) : undefined, ]); const contractInfo = { address: contractAddress, bytecode, abi, }; if (options?.fetchImplementation) { const implementationAddress = await getProxyImplementation(contractAddress, provider).catch(() => undefined); if (implementationAddress) { const implementation = await getContractInformation(chain, provider, implementationAddress); contractInfo.implementation = implementation; } } return contractInfo; }; export const getContractInfoWithLoader = async (chain, provider, contractAddress) => { const spinner = ora("Fetching contract information...").start(); try { const contractInfo = await getContractInformation(chain, provider, contractAddress, { fetchImplementation: true }); if (contractInfo.bytecode === "0x") { throw new Error("Provided address is not a contract"); } return contractInfo; } finally { spinner.stop(); } }; export const askAbiMethod = async (contractInfo, type = "any") => { if (!contractInfo.abi && !contractInfo.implementation?.abi) { return "manual"; } const formatFragment = (fragment) => { 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 } return { name: name.substring("function ".length), value: fragment, }; }; const choices = []; const separators = { noReadMethods: { type: "separator", line: chalk.white("No read methods found") }, noWriteMethods: { type: "separator", line: chalk.white("No write methods found") }, noMethods: { type: "separator", line: chalk.white("No methods found") }, contractNotVerified: { type: "separator", line: chalk.white("Contract is not verified") }, }; choices.push(formatSeparator("Provided contract")); if (contractInfo.abi) { const methods = getMethodsFromAbi(contractInfo.abi, type); if (methods.length) { choices.push(...methods.map(formatFragment)); } else { if (type === "read") { choices.push(separators.noReadMethods); } else if (type === "write") { choices.push(separators.noWriteMethods); } else { choices.push(separators.noMethods); } } } else { choices.push(separators.contractNotVerified); } if (contractInfo?.implementation) { if (contractInfo.implementation.abi) { choices.push(formatSeparator("Resolved implementation")); const implementationMethods = getMethodsFromAbi(contractInfo.implementation.abi, type); if (implementationMethods.length) { choices.push(...implementationMethods.map(formatFragment)); } else { if (type === "read") { choices.push(separators.noReadMethods); } else if (type === "write") { choices.push(separators.noWriteMethods); } else { choices.push(separators.noMethods); } } } else { choices.push(separators.contractNotVerified); } } choices.push(formatSeparator("")); choices.push({ name: "Type method manually", value: "manual", }); const { method } = await inquirer.prompt([ { message: "Contract method to call", name: "method", type: "list", choices, required: true, pageSize: 10, loop: false, }, ]); return method; }; //# sourceMappingURL=helpers.js.map