From 35a0a18f684ebc8a36ac630ec443623a5614197b Mon Sep 17 00:00:00 2001 From: ygd58 Date: Thu, 9 Jul 2026 15:33:22 +0200 Subject: [PATCH 1/5] fix: config reset no longer writes undefined value to disk (#305) The reset() method had dead code: - delete config[key] removed the key from the in-memory object - writeConfig(key, undefined) then wrote key=undefined back to disk Replace both lines with removeConfig(key) which correctly deletes the key and persists the change to disk. Fixes #305 --- src/commands/config/getSetReset.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/commands/config/getSetReset.ts b/src/commands/config/getSetReset.ts index b7499316..9a49e1a1 100644 --- a/src/commands/config/getSetReset.ts +++ b/src/commands/config/getSetReset.ts @@ -44,8 +44,7 @@ export class ConfigActions extends BaseAction { return; } - delete config[key]; - this.writeConfig(key, undefined); + this.removeConfig(key); this.succeedSpinner(`Configuration successfully reset`); } } From cb3c37a9e85c715efcc169fca92c5f48b86389c2 Mon Sep 17 00:00:00 2001 From: ygd58 Date: Thu, 9 Jul 2026 15:36:48 +0200 Subject: [PATCH 2/5] fix: setNetwork ignores empty string argument and shows interactive prompt (#306) Previously, passing an empty string to setNetwork() was treated as a valid network name due to the condition: if (networkName || networkName === "") This caused the command to search for a network named "" and always fail instead of falling through to the interactive prompt. Fix: simplify the condition to `if (networkName)` so empty strings are treated the same as undefined, correctly triggering the interactive network selection prompt. Fixes #306 --- src/commands/network/setNetwork.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/network/setNetwork.ts b/src/commands/network/setNetwork.ts index de771cb8..d6c50a5c 100644 --- a/src/commands/network/setNetwork.ts +++ b/src/commands/network/setNetwork.ts @@ -98,7 +98,7 @@ export class NetworkActions extends BaseAction { async setNetwork(networkName?: string): Promise { const entries = this.getNetworkEntries(); - if (networkName || networkName === "") { + if (networkName) { const selectedNetwork = entries.find(n => n.alias === networkName || (n.type === "built-in" && n.name === networkName), ); From 004d158763a36766adcffc8b5c1edc6aaa6a9815 Mon Sep 17 00:00:00 2001 From: ygd58 Date: Thu, 9 Jul 2026 15:39:37 +0200 Subject: [PATCH 3/5] fix: move misplaced import statements to top of BaseAction.ts (#307) Two import statements were incorrectly placed in the middle of the file between the resolveNetwork() function and the BaseAction class definition: import { ethers } from 'ethers'; import { writeFileSync, existsSync, readFileSync } from 'fs'; Move them to the top of the file with all other imports. Fixes #307 --- src/lib/actions/BaseAction.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/actions/BaseAction.ts b/src/lib/actions/BaseAction.ts index 92a0d665..bc9a6735 100644 --- a/src/lib/actions/BaseAction.ts +++ b/src/lib/actions/BaseAction.ts @@ -8,6 +8,8 @@ import {createClient, createAccount} from "genlayer-js"; import {localnet, studionet, testnetAsimov, testnetBradbury} from "genlayer-js/chains"; import type {GenLayerClient, GenLayerChain, Hash, Address, Account} from "genlayer-js/types"; import { +import { ethers } from "ethers"; +import { writeFileSync, existsSync, readFileSync } from "fs"; applyCustomNetworkProfile, CUSTOM_NETWORKS_CONFIG_KEY, normalizeCustomNetworks, @@ -58,8 +60,6 @@ export function resolveNetwork(stored: string | undefined, customNetworks?: Cust throw new Error(`Unknown network: ${stored}`); } } -import { ethers } from "ethers"; -import { writeFileSync, existsSync, readFileSync } from "fs"; export class BaseAction extends ConfigFileManager { private static readonly DEFAULT_ACCOUNT_NAME = "default"; From 0d07e0a7b3b8f90cefaf1b6d01470db83b43ca05 Mon Sep 17 00:00:00 2001 From: ygd58 Date: Thu, 9 Jul 2026 15:42:32 +0200 Subject: [PATCH 4/5] fix: decryptKeystore dead code, infinite recursion and wrong error classification (#302) Three bugs fixed in BaseAction.decryptKeystore(): 1. Dead code: after failSpinner(process.exit), the recursive call was unreachable. Added explicit return after failSpinner. 2. Potential infinite recursion: if failSpinner did not exit, recursion continued without bound. Now returns empty string after max attempts to guarantee termination. 3. Wrong error classification: all errors from fromEncryptedJson were treated as wrong password. Now re-throws non-password errors (corrupted keystore, malformed JSON) immediately with a clear message. Fixes #302 --- src/lib/actions/BaseAction.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/lib/actions/BaseAction.ts b/src/lib/actions/BaseAction.ts index bc9a6735..5ca75c71 100644 --- a/src/lib/actions/BaseAction.ts +++ b/src/lib/actions/BaseAction.ts @@ -90,9 +90,19 @@ export class BaseAction extends ConfigFileManager { const wallet = await ethers.Wallet.fromEncryptedJson(keystoreJson, password); return wallet.privateKey; - } catch (error) { + } catch (error: any) { + // Re-throw non-password errors (corrupted keystore, malformed JSON, etc.) + const isPasswordError = + error?.message?.toLowerCase().includes("password") || + error?.message?.toLowerCase().includes("decrypt") || + error?.code === "INVALID_ARGUMENT"; + if (!isPasswordError) { + throw new Error(`Failed to decrypt keystore: ${error?.message ?? error}`); + } + if (attempt >= BaseAction.MAX_PASSWORD_ATTEMPTS) { this.failSpinner(`Maximum password attempts exceeded (${BaseAction.MAX_PASSWORD_ATTEMPTS}/${BaseAction.MAX_PASSWORD_ATTEMPTS}).`); + return ""; } return await this.decryptKeystore(keystoreJson, attempt + 1); } From d116e5c38f537a4d77281b33c89a3ab3fa4b98b3 Mon Sep 17 00:00:00 2001 From: ygd58 Date: Thu, 9 Jul 2026 15:46:04 +0200 Subject: [PATCH 5/5] fix: check keystore file exists after createKeypairByName to prevent ENOENT crash (#304) getAccount() unconditionally called readFileSync after createKeypairByName even if the latter failed to create the file (e.g. disk full, permissions). This caused an unhandled ENOENT crash instead of a clear error message. Add an existsSync check after createKeypairByName and fail with a descriptive message if the keystore file was not created. Fixes #304 --- src/lib/actions/BaseAction.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/actions/BaseAction.ts b/src/lib/actions/BaseAction.ts index 5ca75c71..943ca49b 100644 --- a/src/lib/actions/BaseAction.ts +++ b/src/lib/actions/BaseAction.ts @@ -159,6 +159,10 @@ export class BaseAction extends ConfigFileManager { if (!existsSync(keystorePath)) { await this.confirmPrompt(`Account '${accountName}' not found. Would you like to create it?`); decryptedPrivateKey = await this.createKeypairByName(accountName, false); + if (!existsSync(keystorePath)) { + this.failSpinner(`Failed to create keystore file for account '${accountName}'. Check disk space and permissions.`); + return; + } } keystoreJson = readFileSync(keystorePath, "utf-8");