@rsksmart/rsk-cli
Version:
CLI tool for Rootstock network using Viem
299 lines (298 loc) • 12.5 kB
JavaScript
import ViemProvider from "../utils/viemProvider.js";
import chalk from "chalk";
import ora from "ora";
import fs from "fs";
import { walletFilePath } from "../utils/constants.js";
import { getTokenInfo, isERC20Contract } from "../utils/tokenHelper.js";
function logMessage(params, message, color = chalk.white) {
if (!params.isExternal) {
console.log(color(message));
}
}
function logError(params, message) {
logMessage(params, `❌ ${message}`, chalk.red);
}
function logSuccess(params, message) {
logMessage(params, message, chalk.green);
}
function logInfo(params, message) {
logMessage(params, message, chalk.blue);
}
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 transferCommand(params) {
try {
let walletsData;
if (params.isExternal && params.walletsData) {
walletsData = params.walletsData;
}
else {
if (!fs.existsSync(walletFilePath)) {
const errorMessage = "No saved wallet found. Please create a wallet first.";
logError(params, errorMessage);
return {
error: errorMessage,
success: false,
};
}
walletsData = JSON.parse(fs.readFileSync(walletFilePath, "utf8"));
}
if (!walletsData.currentWallet || !walletsData.wallets) {
const errorMessage = "No valid wallet found. Please create or import a wallet first.";
logError(params, errorMessage);
return {
error: errorMessage,
success: false,
};
}
const { currentWallet, wallets } = walletsData;
let wallet = wallets[currentWallet];
if (params.name) {
if (!wallets[params.name]) {
const errorMessage = "Wallet with the provided name does not exist.";
logError(params, errorMessage);
return {
error: errorMessage,
success: false,
};
}
else {
wallet = wallets[params.name];
}
}
const { address: walletAddress } = wallet;
if (!walletAddress) {
const errorMessage = "No valid address found in the saved wallet.";
logError(params, errorMessage);
return {
error: errorMessage,
success: false,
};
}
const provider = new ViemProvider(params.testnet);
const publicClient = await provider.getPublicClient();
let walletClient;
if (params.isExternal) {
if (!params.name || !params.password || !params.walletsData) {
const errorMessage = "Wallet name, password and wallets data are required.";
logError(params, errorMessage);
return {
error: errorMessage,
success: false,
};
}
walletClient = await provider.getWalletClientExternal(params.walletsData, params.name, params.password, provider);
}
else {
walletClient = await provider.getWalletClient(params.name);
}
if (!walletClient) {
const errorMessage = "Failed to get wallet client.";
logError(params, errorMessage);
return {
error: errorMessage,
success: false,
};
}
const account = walletClient.account;
if (!account) {
const errorMessage = "Failed to retrieve the account. Please ensure your wallet is correctly set up.";
logError(params, errorMessage);
return {
error: errorMessage,
success: false,
};
}
logInfo(params, `🔑 Wallet account: ${account.address}`);
if (params.tokenAddress) {
// Handle ERC20 token transfer
const isERC20 = await isERC20Contract(publicClient, params.tokenAddress);
if (!isERC20) {
const errorMessage = "The provided address is not a valid ERC20 token contract.";
logError(params, errorMessage);
return {
error: errorMessage,
success: false,
};
}
// Get token information
const tokenName = await publicClient.readContract({
address: params.tokenAddress,
abi: [{
name: "name",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ type: "string" }]
}],
functionName: "name"
});
const tokenSymbol = await publicClient.readContract({
address: params.tokenAddress,
abi: [{
name: "symbol",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ type: "string" }]
}],
functionName: "symbol"
});
// Display token and transfer information
logInfo(params, `📄 Token Information:`);
logInfo(params, ` Name: ${tokenName}`);
logInfo(params, ` Symbol: ${tokenSymbol}`);
logInfo(params, ` Contract: ${params.tokenAddress}`);
logInfo(params, `🎯 To Address: ${params.toAddress}`);
logInfo(params, `💵 Amount to Transfer: ${params.value} ${tokenSymbol}`);
// Check balance and proceed with transfer
const { balance } = await getTokenInfo(publicClient, params.tokenAddress, walletAddress);
const formattedBalance = Number(balance) / 10 ** 18;
if (formattedBalance < params.value) {
const errorMessage = `Insufficient balance to transfer ${params.value} tokens.`;
logError(params, errorMessage);
return {
error: errorMessage,
success: false,
};
}
const spinner = params.isExternal ? ora({ isEnabled: false }) : ora();
startSpinner(params, spinner, "⏳ Simulating token transfer...");
const { request } = await publicClient.simulateContract({
account,
address: params.tokenAddress,
abi: [{
name: "transfer",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "recipient", type: "address" },
{ name: "amount", type: "uint256" }
],
outputs: [{ type: "bool" }]
}],
functionName: "transfer",
args: [params.toAddress, BigInt(params.value * (10 ** 18))]
});
succeedSpinner(params, spinner, "✅ Simulation successful, proceeding with transfer...");
const txHash = await walletClient.writeContract(request);
logSuccess(params, `🔄 Transaction initiated. TxHash: ${txHash}`);
startSpinner(params, spinner, "⏳ Waiting for confirmation...");
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
stopSpinner(params, spinner);
const explorerUrl = params.testnet
? `https://explorer.testnet.rootstock.io/tx/${txHash}`
: `https://explorer.rootstock.io/tx/${txHash}`;
if (receipt.status === "success") {
logSuccess(params, "✅ Transfer completed successfully!");
logInfo(params, `📦 Block Number: ${receipt.blockNumber}`);
logInfo(params, `⛽ Gas Used: ${receipt.gasUsed}`);
logInfo(params, `🔗 View on Explorer: ${explorerUrl}`);
return {
success: true,
data: {
transactionHash: txHash,
from: walletAddress,
to: params.toAddress,
amount: `${params.value}`,
token: tokenSymbol,
network: params.testnet ? "Rootstock Testnet" : "Rootstock Mainnet",
explorerUrl,
gasUsed: receipt.gasUsed.toString(),
blockNumber: receipt.blockNumber.toString(),
},
};
}
else {
const errorMessage = "Transfer failed.";
logError(params, errorMessage);
return {
error: errorMessage,
success: false,
};
}
}
else {
// Handle RBTC transfer
const balance = await publicClient.getBalance({ address: walletAddress });
const rbtcBalance = Number(balance) / 10 ** 18;
logInfo(params, `📄 Wallet Address: ${walletAddress}`);
logInfo(params, `🎯 Recipient Address: ${params.toAddress}`);
logInfo(params, `💵 Amount to Transfer: ${params.value} RBTC`);
logInfo(params, `💰 Current Balance: ${rbtcBalance} RBTC`);
if (rbtcBalance < params.value) {
const errorMessage = `Insufficient balance to transfer ${params.value} RBTC.`;
logError(params, errorMessage);
return {
error: errorMessage,
success: false,
};
}
const txHash = await walletClient.sendTransaction({
account: account,
chain: provider.chain,
to: params.toAddress,
value: BigInt(params.value * 10 ** 18),
});
logSuccess(params, `🔄 Transaction initiated. TxHash: ${txHash}`);
const spinner = params.isExternal ? ora({ isEnabled: false }) : ora();
startSpinner(params, spinner, "⏳ Waiting for confirmation...");
const receipt = await publicClient.waitForTransactionReceipt({
hash: txHash,
});
stopSpinner(params, spinner);
const explorerUrl = params.testnet
? `https://explorer.testnet.rootstock.io/tx/${txHash}`
: `https://explorer.rootstock.io/tx/${txHash}`;
if (receipt.status === "success") {
logSuccess(params, "✅ Transaction confirmed successfully!");
logInfo(params, `📦 Block Number: ${receipt.blockNumber}`);
logInfo(params, `⛽ Gas Used: ${receipt.gasUsed.toString()}`);
logInfo(params, `🔗 View on Explorer: ${explorerUrl}`);
return {
success: true,
data: {
transactionHash: txHash,
from: walletAddress,
to: params.toAddress,
amount: `${params.value}`,
token: "RBTC",
network: params.testnet ? "Rootstock Testnet" : "Rootstock Mainnet",
explorerUrl,
gasUsed: receipt.gasUsed.toString(),
blockNumber: receipt.blockNumber.toString(),
},
};
}
else {
const errorMessage = "Transaction failed.";
logError(params, errorMessage);
return {
error: errorMessage,
success: false,
};
}
}
}
catch (error) {
const errorMessage = "Error during transfer, please check the transfer details.";
logError(params, errorMessage);
return {
error: errorMessage,
success: false,
};
}
}