From 2cc2f5015af3ff71e52a8e7e4785554f27cb10aa Mon Sep 17 00:00:00 2001 From: AddNad Samuel Date: Tue, 7 Apr 2026 03:48:57 +0100 Subject: [PATCH] feat: Implement get account balance command (#56) - Add GetBalanceAction class that fetches and displays account balance - Register 'balance' subcommand under 'account' command group - Supports --rpc and --account options for flexibility - Displays balance in GEN format with account info --- src/commands/account/balance.ts | 69 +++++++++++++++++++++++++++++++++ src/commands/account/index.ts | 12 ++++++ 2 files changed, 81 insertions(+) create mode 100644 src/commands/account/balance.ts diff --git a/src/commands/account/balance.ts b/src/commands/account/balance.ts new file mode 100644 index 00000000..d73336e2 --- /dev/null +++ b/src/commands/account/balance.ts @@ -0,0 +1,69 @@ +import {BaseAction, resolveNetwork} from "../../lib/actions/BaseAction"; +import {formatEther} from "viem"; +import {createClient} from "genlayer-js"; +import type {GenLayerChain, Address} from "genlayer-js/types"; +import {readFileSync, existsSync} from "fs"; + +export interface GetBalanceOptions { + rpc?: string; + account?: string; +} + +export class GetBalanceAction extends BaseAction { + constructor() { + super(); + } + + private getNetwork(): GenLayerChain { + return resolveNetwork(this.getConfig().network); + } + + async execute(options?: GetBalanceOptions): Promise { + this.startSpinner("Fetching account balance..."); + + try { + if (options?.account) { + this.accountOverride = options.account; + } + + const accountName = this.resolveAccountName(); + const keystorePath = this.getKeystorePath(accountName); + + if (!existsSync(keystorePath)) { + this.failSpinner(`Account '${accountName}' not found. Run 'genlayer account create --name ${accountName}' first.`); + return; + } + + const keystoreData = JSON.parse(readFileSync(keystorePath, "utf-8")); + + if (!this.isValidKeystoreFormat(keystoreData)) { + this.failSpinner("Invalid keystore format."); + return; + } + + const rawAddr = keystoreData.address; + const address = (rawAddr.startsWith("0x") ? rawAddr : `0x${rawAddr}`) as Address; + const network = this.getNetwork(); + + const client = createClient({ + chain: network, + account: address, + endpoint: options?.rpc, + }); + + const balance = await client.getBalance({address}); + const formattedBalance = formatEther(balance); + + const result = { + account: accountName, + address, + balance: `${formattedBalance} GEN`, + network: network.name || "localnet", + }; + + this.succeedSpinner("Account balance", result); + } catch (error: any) { + this.failSpinner("Failed to get account balance", error.message || error); + } + } +} diff --git a/src/commands/account/index.ts b/src/commands/account/index.ts index 3fa23f15..3bc1e22c 100644 --- a/src/commands/account/index.ts +++ b/src/commands/account/index.ts @@ -6,6 +6,8 @@ import {ExportAccountAction, ExportAccountOptions} from "./export"; import {UnlockAccountAction, UnlockAccountOptions} from "./unlock"; import {LockAccountAction, LockAccountOptions} from "./lock"; import {SendAction, SendOptions} from "./send"; +import {GetBalanceAction, GetBalanceOptions} from "./balance"; +import {FundAccountAction, FundAccountOptions} from "./fund"; import {ListAccountsAction} from "./list"; import {UseAccountAction} from "./use"; import {RemoveAccountAction} from "./remove"; @@ -38,6 +40,16 @@ export function initializeAccountCommands(program: Command) { await showAction.execute(options); }); + accountCommand + .command("balance") + .description("Get account balance") + .option("--rpc ", "RPC URL for the network") + .option("--account ", "Account to check balance for") + .action(async (options: GetBalanceOptions) => { + const balanceAction = new GetBalanceAction(); + await balanceAction.execute(options); + }); + accountCommand .command("create") .description("Create a new account with encrypted keystore")