zksync-cli
Version:
CLI tool that simplifies the process of developing applications and interacting with the ZKsync network
199 lines • 8.03 kB
JavaScript
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