@rsksmart/rsk-cli
Version:
CLI tool for Rootstock network using Viem
269 lines (268 loc) • 11.3 kB
JavaScript
import precompiled from "@rsksmart/rsk-precompiled-abis";
import chalk from "chalk";
import { formatBridgeFragments } from "../utils/index.js";
import inquirer from "inquirer";
import { ALLOWED_BRIDGE_METHODS } from "../utils/constants.js";
import ViemProvider from "../utils/viemProvider.js";
import ora from "ora";
import { getConfig } from "./config.js";
var FunctionType;
(function (FunctionType) {
FunctionType["READ"] = "read";
FunctionType["WRITE"] = "write";
})(FunctionType || (FunctionType = {}));
function logMessage(params, message, color = chalk.white) {
if (!params.isExternal) {
console.log(color(message));
}
}
function logInfo(params, message) {
logMessage(params, message, chalk.blue);
}
function logError(params, message) {
logMessage(params, `❌ ${message}`, chalk.red);
}
function logSuccess(params, message) {
logMessage(params, message, chalk.green);
}
function startSpinner(params, spinner, message) {
if (!params.isExternal) {
spinner.start(message);
}
}
function stopSpinner(params, spinner) {
if (!params.isExternal) {
spinner.stop();
}
}
function succeedSpinner(params, spinner, message) {
if (!params.isExternal) {
spinner.succeed(message);
}
}
export async function bridgeCommand(params) {
try {
let config;
try {
config = getConfig();
if (!config || !config.defaultNetwork || !config.displayPreferences) {
logError(params, "Invalid configuration detected. Please run 'rsk-cli config' to set up your configuration.");
return {
error: "Invalid configuration",
success: false,
};
}
}
catch (error) {
logError(params, `Failed to load configuration: ${error.message}`);
return {
error: "Configuration loading failed",
success: false,
};
}
const isTestnet = params.testnet === undefined ? (config.defaultNetwork === 'testnet') : params.testnet;
if (!params.isExternal) {
logInfo(params, `Using network: ${isTestnet ? 'testnet' : 'mainnet'} (${params.testnet === undefined ? 'from config' : 'from parameter'})`);
}
const spinner = params.isExternal ? ora({ isEnabled: false }) : ora();
if (!config.displayPreferences.compactMode) {
logInfo(params, `🔧 Initializing bridge for ${isTestnet ? "testnet" : "mainnet"}...`);
}
const bridge = {
address: precompiled.bridge.address,
abi: formatBridgeFragments(precompiled.bridge.abi),
};
let finalSelectedType = params.selectedType;
if (!params.isExternal) {
try {
const readOrWriteQuestion = [
{
type: "list",
name: "selectedType",
message: "Select the type of function you want to call:",
choices: [FunctionType.READ, FunctionType.WRITE],
},
];
const { selectedType } = await inquirer.prompt(readOrWriteQuestion);
finalSelectedType = selectedType;
}
catch (error) {
if (error.isTtyError) {
logError(params, "Interactive prompts are not supported in this environment. Please use the --help flag to see available options.");
return {
error: "Interactive prompts not supported",
success: false,
};
}
throw error;
}
}
if (!finalSelectedType) {
logError(params, "Selected type is required.");
return {
error: "Selected type is required.",
success: false,
};
}
const provider = new ViemProvider(isTestnet);
const publicClient = await provider.getPublicClient();
const explorerUrl = isTestnet
? `https://explorer.testnet.rootstock.io/address/${bridge.address}`
: `https://explorer.rootstock.io/address/${bridge.address}`;
const functions = ALLOWED_BRIDGE_METHODS[finalSelectedType];
let finalSelectedFunction = params.functionName;
if (!params.isExternal) {
try {
const functionQuestion = [
{
type: "list",
name: "selectedFunction",
message: `Select a ${finalSelectedType} function to call:`,
choices: [...functions.map((item) => item)],
},
];
const { selectedFunction } = await inquirer.prompt(functionQuestion);
finalSelectedFunction = selectedFunction;
}
catch (error) {
if (error.isTtyError) {
logError(params, "Interactive prompts are not supported in this environment. Please use the --help flag to see available options.");
return {
error: "Interactive prompts not supported",
success: false,
};
}
throw error;
}
}
if (!finalSelectedFunction) {
logError(params, "Selected function is required.");
return {
error: "Selected function is required.",
success: false,
};
}
const selectedAbiFunction = bridge.abi.find((item) => item.name === finalSelectedFunction);
if (!selectedAbiFunction) {
logError(params, "Selected function is not available.");
return {
error: "Selected function is not available.",
success: false,
};
}
let args = params.args || [];
if (!params.isExternal &&
selectedAbiFunction.inputs &&
selectedAbiFunction.inputs.length > 0) {
try {
const argQuestions = selectedAbiFunction.inputs.map((input) => ({
type: "input",
name: input.name,
message: `Enter the value for argument ${chalk.yellow(input.name)} (${chalk.yellow(input.type)}):`,
}));
const answers = await inquirer.prompt(argQuestions);
args = selectedAbiFunction.inputs.map((input) => answers[input.name]);
}
catch (error) {
if (error.isTtyError) {
logError(params, "Interactive prompts are not supported in this environment. Please provide arguments via command line options.");
return {
error: "Interactive prompts not supported",
success: false,
};
}
throw error;
}
}
try {
if (finalSelectedType === FunctionType.READ) {
startSpinner(params, spinner, `⏳ Calling ${finalSelectedFunction} function...`);
const data = await publicClient.readContract({
address: bridge.address,
abi: bridge.abi,
functionName: finalSelectedFunction,
args,
});
stopSpinner(params, spinner);
if (data) {
logSuccess(params, `✅ Function ${finalSelectedFunction} called successfully!`);
if (config.displayPreferences.compactMode) {
succeedSpinner(params, spinner, chalk.green(`${data}`));
}
else {
succeedSpinner(params, spinner, chalk.white(`🔧 Result: `) + chalk.green(data));
}
return {
success: true,
result: data,
};
}
}
let finalWalletClient;
if (finalSelectedType === FunctionType.WRITE) {
if (params.isExternal) {
if (!params.name || !params.password || !params.walletsData) {
logError(params, "Wallet name, password and wallets data are required.");
return {
error: "Wallet name, password and wallets data are required.",
success: false,
};
}
finalWalletClient = await provider.getWalletClientExternal(params.walletsData, params.name, params.password, provider);
}
else {
finalWalletClient = await provider.getWalletClient(params.name);
}
if (!finalWalletClient) {
logError(params, "Failed to get wallet client.");
return {
error: "Failed to get wallet client.",
success: false,
};
}
const account = finalWalletClient.account;
if (!config.displayPreferences.compactMode) {
logInfo(params, `🔑 Wallet account: ${account?.address}`);
}
startSpinner(params, spinner, `⏳ Calling ${finalSelectedFunction} function...`);
const gasConfig = {
gas: config.defaultGasLimit ? BigInt(config.defaultGasLimit) : undefined
};
if (config.defaultGasPrice && config.defaultGasPrice > 0) {
gasConfig.maxFeePerGas = BigInt(config.defaultGasPrice);
}
const contractParams = {
account,
address: bridge.address,
abi: bridge.abi,
functionName: finalSelectedFunction,
args,
...gasConfig
};
const hash = await finalWalletClient.writeContract(contractParams);
stopSpinner(params, spinner);
logSuccess(params, `✅ Function ${finalSelectedFunction} called successfully!`);
if (config.displayPreferences.compactMode) {
succeedSpinner(params, spinner, chalk.green(`Transaction Hash: ${hash}`));
}
else {
succeedSpinner(params, spinner, chalk.white(`🔧 Transaction Hash: `) + chalk.green(hash));
}
return {
success: true,
result: hash,
};
}
if (!config.displayPreferences.compactMode && config.displayPreferences.showExplorerLinks) {
logInfo(params, `🔗 View on Explorer: ${explorerUrl}`);
}
}
catch (error) {
stopSpinner(params, spinner);
throw error;
}
}
catch (error) {
console.error(chalk.red("❌ Error interacting with the bridge: "), chalk.yellow(error.message || error));
}
}