From 7d963e345a686b6aff99ba218f0f8e8e067038fa Mon Sep 17 00:00:00 2001 From: Edgars Date: Tue, 21 Apr 2026 15:57:15 +0100 Subject: [PATCH 1/5] fix(account): send parses plain integer as wei, not GEN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old parseAmount had a bias: integers below 10^12 wei were silently interpreted as GEN, integers above as wei. That makes `account send 1000` attempt a 1000 GEN transfer, not 1000 wei — a footgun that's inconsistent with how parseStakingAmount and the SDK's parseStakingAmount handle the same units. Align with the symmetric convention used elsewhere in the codebase: - 'Ngen' / 'N gen' suffix → N GEN via parseEther - plain integer → wei (BigInt, no conversion) - plain decimal without suffix → rejected (ambiguous, was silently GEN for small values and wei for large) --- src/commands/account/send.ts | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/commands/account/send.ts b/src/commands/account/send.ts index 66188bc8..716d577b 100644 --- a/src/commands/account/send.ts +++ b/src/commands/account/send.ts @@ -31,18 +31,20 @@ export class SendAction extends BaseAction { } private parseAmount(amount: string): bigint { - // Support "10gen" or "10" (assumes gen) or wei values - const lowerAmount = amount.toLowerCase(); - if (lowerAmount.endsWith("gen")) { - const value = lowerAmount.slice(0, -3); - return parseEther(value); + // Symmetric with genlayer-js `parseStakingAmount`: + // "Ngen" → N GEN (parseEther) + // integer → wei (as-is) + // decimal without suffix → rejected (ambiguous; was previously silently + // interpreted as GEN for small values and as wei for large — a footgun + // that made `send 1000` attempt to transfer 1000 GEN, not 1000 wei). + const trimmed = amount.trim(); + const lower = trimmed.toLowerCase(); + if (lower.endsWith("gen")) { + return parseEther(lower.slice(0, -3).trim()); } - // If it's a large number (likely wei), use as-is - if (BigInt(amount) > 1_000_000_000_000n) { - return BigInt(amount); - } - // Otherwise assume it's in GEN - return parseEther(amount); + // Plain integer → wei. BigInt() throws on decimals, which is the intended + // failure mode for ambiguous input like "1.5". + return BigInt(trimmed); } async execute(options: SendOptions): Promise { From 35686f307e62d07abe184f443bd6b34a8e662496 Mon Sep 17 00:00:00 2001 From: Edgars Date: Wed, 22 Apr 2026 15:00:58 +0100 Subject: [PATCH 2/5] chore(deps): bump genlayer-js to ^1.0.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Picks up the validatorDeposit/Exit routing fix (genlayer-js#155) which routes those two calls through the ValidatorWallet so Staking sees the wallet as msg.sender (previously reverted). The CLI's own staking subcommands already route through VALIDATOR_WALLET_ABI directly (walletClient.writeContract on the validator address), so no call-site changes are needed — this is just keeping the bundled SDK current. --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4fab6514..b642ca1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "dotenv": "^17.0.0", "ethers": "^6.13.4", "fs-extra": "^11.3.0", - "genlayer-js": "^0.28.5", + "genlayer-js": "^1.0.0", "inquirer": "^12.0.0", "keytar": "^7.9.0", "node-fetch": "^3.0.0", @@ -5581,9 +5581,9 @@ } }, "node_modules/genlayer-js": { - "version": "0.28.5", - "resolved": "https://registry.npmjs.org/genlayer-js/-/genlayer-js-0.28.5.tgz", - "integrity": "sha512-Wj4tsqvywV9EZtJgp5MeDQcFzc18FXK3x+2ihAGqkY9SB8g0BC39ihj+ysQNHXUCiFmTK4hy3DwRzeWiZf1jRg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/genlayer-js/-/genlayer-js-1.0.0.tgz", + "integrity": "sha512-372szMXY95jI32u7xOTrqHOXk3Wf4WdsrmXSzgXywv7TBSRwj/W3W4s9oBBpzMIxUzS06Bv80rTCrblvuH78ug==", "license": "MIT", "dependencies": { "eslint-plugin-import": "^2.30.0", diff --git a/package.json b/package.json index d0356190..958b05e3 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "dotenv": "^17.0.0", "ethers": "^6.13.4", "fs-extra": "^11.3.0", - "genlayer-js": "^0.28.5", + "genlayer-js": "^1.0.0", "inquirer": "^12.0.0", "keytar": "^7.9.0", "node-fetch": "^3.0.0", From 44c033b72432e196c122c754f88aca3fe4a14741 Mon Sep 17 00:00:00 2001 From: Edgars Date: Wed, 22 Apr 2026 15:13:14 +0100 Subject: [PATCH 3/5] ci(smoke): build the CLI before running smoke tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The smoke suite shells out to `node dist/index.js` to exercise the compiled CLI end-to-end (the `genlayer staking validators` assertion in tests/smoke.test.ts). Without a build step that entry point doesn't exist and the test fails with MODULE_NOT_FOUND before it can hit the network. Pre-existing bug — every PR has been red on this check; it's only green on main because the scheduled run happens after a release built dist/. --- .github/workflows/smoke.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index d4a85d80..f122ffd5 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -23,5 +23,12 @@ jobs: - run: npm ci + # Smoke tests shell out to `node dist/index.js` to exercise the + # compiled CLI — without a build first, that file doesn't exist + # and the `genlayer staking validators` assertion fails with + # MODULE_NOT_FOUND before it can hit the network at all. + - name: Build CLI + run: npm run build + - name: Run smoke tests run: npm run test:smoke From e7e45ed3ae60b196c75a163a0890b1613c93a922 Mon Sep 17 00:00:00 2001 From: Edgars Date: Wed, 22 Apr 2026 15:21:58 +0100 Subject: [PATCH 4/5] ci(smoke): create default keystore before running subprocess tests --- .github/workflows/smoke.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index f122ffd5..13650c28 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -30,5 +30,14 @@ jobs: - name: Build CLI run: npm run build + # The staking subcommands need a keystore for the default account + # even for read-only listings (BaseAction loads the account to + # report "your stake"). Create a throwaway one with a known + # password so the subprocess test in tests/smoke.test.ts can run. + - name: Create default keystore for CLI subprocess tests + run: node dist/index.js account create --name default --password ci-smoke-test + env: + CI: true + - name: Run smoke tests run: npm run test:smoke From 86aece97d5ed9790da441b5392d617a725c57e36 Mon Sep 17 00:00:00 2001 From: Edgars Date: Wed, 22 Apr 2026 15:27:46 +0100 Subject: [PATCH 5/5] =?UTF-8?q?ci(smoke):=20bump=20test=20timeout=2030s=20?= =?UTF-8?q?=E2=86=92=2090s=20for=20slow=20validator-list=20fetch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/smoke.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/smoke.test.ts b/tests/smoke.test.ts index f9dfb7d3..56808871 100644 --- a/tests/smoke.test.ts +++ b/tests/smoke.test.ts @@ -7,7 +7,10 @@ import type {Address, GenLayerChain} from "genlayer-js/types"; const CLI = path.resolve(__dirname, "../dist/index.js"); -const TIMEOUT = 30_000; +// Testnet validator-list fetches ALL validators + per-validator detail in +// batches; on bradbury/asimov that routinely passes 30s. 90s gives headroom +// without hiding real hangs. +const TIMEOUT = 90_000; const testnets: {name: string; chain: GenLayerChain}[] = [ {name: "Asimov", chain: testnetAsimov},