genlayer 0.32.1 → 0.32.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.32.2 (2025-12-04)
4
+
3
5
  ## 0.32.1 (2025-12-04)
4
6
 
5
7
  ## 0.32.0 (2025-12-03)
package/dist/index.js CHANGED
@@ -18249,7 +18249,7 @@ var require_semver2 = __commonJS({
18249
18249
  import { program } from "commander";
18250
18250
 
18251
18251
  // package.json
18252
- var version = "0.32.1";
18252
+ var version = "0.32.2";
18253
18253
  var package_default = {
18254
18254
  name: "genlayer",
18255
18255
  version,
@@ -19922,7 +19922,19 @@ var ConfigFileManager = class {
19922
19922
  };
19923
19923
 
19924
19924
  // src/lib/config/KeychainManager.ts
19925
- import { default as keytar } from "keytar";
19925
+ var keytarModule = null;
19926
+ var keytarLoadAttempted = false;
19927
+ async function getKeytar() {
19928
+ if (keytarLoadAttempted) return keytarModule;
19929
+ keytarLoadAttempted = true;
19930
+ try {
19931
+ const mod3 = await import("keytar");
19932
+ keytarModule = mod3.default ?? mod3;
19933
+ return keytarModule;
19934
+ } catch {
19935
+ return null;
19936
+ }
19937
+ }
19926
19938
  var _KeychainManager = class _KeychainManager {
19927
19939
  constructor() {
19928
19940
  }
@@ -19931,6 +19943,8 @@ var _KeychainManager = class _KeychainManager {
19931
19943
  }
19932
19944
  async isKeychainAvailable() {
19933
19945
  try {
19946
+ const keytar = await getKeytar();
19947
+ if (!keytar) return false;
19934
19948
  await keytar.findCredentials("test-service");
19935
19949
  return true;
19936
19950
  } catch {
@@ -19938,17 +19952,44 @@ var _KeychainManager = class _KeychainManager {
19938
19952
  }
19939
19953
  }
19940
19954
  async storePrivateKey(accountName, privateKey) {
19941
- return await keytar.setPassword(_KeychainManager.SERVICE, this.getKeychainAccount(accountName), privateKey);
19955
+ const keytar = await getKeytar();
19956
+ if (!keytar) throw new Error("Keychain not available. Install libsecret-1-dev on Linux.");
19957
+ try {
19958
+ return await keytar.setPassword(_KeychainManager.SERVICE, this.getKeychainAccount(accountName), privateKey);
19959
+ } catch (error) {
19960
+ if (error?.message?.includes("org.freedesktop.secrets")) {
19961
+ throw new Error("Keychain service not running. Install and start gnome-keyring or another secrets service.");
19962
+ }
19963
+ throw error;
19964
+ }
19942
19965
  }
19943
19966
  async getPrivateKey(accountName) {
19944
- return await keytar.getPassword(_KeychainManager.SERVICE, this.getKeychainAccount(accountName));
19967
+ const keytar = await getKeytar();
19968
+ if (!keytar) return null;
19969
+ try {
19970
+ return await keytar.getPassword(_KeychainManager.SERVICE, this.getKeychainAccount(accountName));
19971
+ } catch {
19972
+ return null;
19973
+ }
19945
19974
  }
19946
19975
  async removePrivateKey(accountName) {
19947
- return await keytar.deletePassword(_KeychainManager.SERVICE, this.getKeychainAccount(accountName));
19976
+ const keytar = await getKeytar();
19977
+ if (!keytar) return false;
19978
+ try {
19979
+ return await keytar.deletePassword(_KeychainManager.SERVICE, this.getKeychainAccount(accountName));
19980
+ } catch {
19981
+ return false;
19982
+ }
19948
19983
  }
19949
19984
  async listUnlockedAccounts() {
19950
- const credentials = await keytar.findCredentials(_KeychainManager.SERVICE);
19951
- return credentials.map((c) => c.account).filter((a) => a.startsWith("account:")).map((a) => a.replace("account:", ""));
19985
+ const keytar = await getKeytar();
19986
+ if (!keytar) return [];
19987
+ try {
19988
+ const credentials = await keytar.findCredentials(_KeychainManager.SERVICE);
19989
+ return credentials.map((c) => c.account).filter((a) => a.startsWith("account:")).map((a) => a.replace("account:", ""));
19990
+ } catch {
19991
+ return [];
19992
+ }
19952
19993
  }
19953
19994
  async isAccountUnlocked(accountName) {
19954
19995
  const key = await this.getPrivateKey(accountName);
@@ -49906,15 +49947,19 @@ Network set to: ${BUILT_IN_NETWORKS[selectedNetwork].name}
49906
49947
  const minStakeRaw = epochInfo.validatorMinStakeRaw;
49907
49948
  const minStakeFormatted = epochInfo.validatorMinStake;
49908
49949
  const currentEpoch = epochInfo.currentEpoch;
49950
+ const MIN_GAS_BUFFER = parseEther("0.01");
49909
49951
  console.log(`Balance: ${balanceFormatted} GEN`);
49910
49952
  console.log(`Minimum stake required: ${minStakeFormatted}`);
49911
49953
  if (currentEpoch === 0n) {
49912
- console.log("(Epoch 0: minimum stake not enforced)");
49954
+ console.log("(Epoch 0: minimum stake not enforced, but gas fees still required)");
49955
+ console.log(`Note: Validator won't become active until self-stake reaches ${minStakeFormatted}`);
49913
49956
  }
49914
- if (currentEpoch !== 0n && balance < minStakeRaw) {
49957
+ const minRequired = currentEpoch === 0n ? MIN_GAS_BUFFER : minStakeRaw + MIN_GAS_BUFFER;
49958
+ if (balance < minRequired) {
49915
49959
  console.log("");
49960
+ const minFormatted = currentEpoch === 0n ? "0.01 GEN (for gas)" : `${minStakeFormatted} + gas`;
49916
49961
  this.failSpinner(
49917
- `Insufficient balance. You need at least ${minStakeFormatted} to become a validator.
49962
+ `Insufficient balance. You need at least ${minFormatted} to become a validator.
49918
49963
  Fund your account (${state.accountAddress}) and run the wizard again.`
49919
49964
  );
49920
49965
  }
@@ -2,6 +2,8 @@
2
2
 
3
3
  This guide walks you through becoming a validator on the GenLayer testnet using the CLI.
4
4
 
5
+ For a deeper understanding of how staking works in GenLayer, see the [Staking documentation](/understand-genlayer-protocol/core-concepts/optimistic-democracy/staking).
6
+
5
7
  ## Prerequisites
6
8
 
7
9
  - Node.js installed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genlayer",
3
- "version": "0.32.1",
3
+ "version": "0.32.2",
4
4
  "description": "GenLayer Command Line Tool",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -3,7 +3,7 @@ import {CreateAccountAction} from "../account/create";
3
3
  import {ExportAccountAction} from "../account/export";
4
4
  import inquirer from "inquirer";
5
5
  import type {Address} from "genlayer-js/types";
6
- import {formatEther} from "viem";
6
+ import {formatEther, parseEther} from "viem";
7
7
  import {createClient} from "genlayer-js";
8
8
  import {readFileSync, existsSync} from "fs";
9
9
  import path from "path";
@@ -247,17 +247,24 @@ export class ValidatorWizardAction extends StakingAction {
247
247
  const minStakeFormatted = epochInfo.validatorMinStake;
248
248
  const currentEpoch = epochInfo.currentEpoch;
249
249
 
250
+ // Minimum gas buffer for transaction fees (~0.01 GEN)
251
+ const MIN_GAS_BUFFER = parseEther("0.01");
252
+
250
253
  console.log(`Balance: ${balanceFormatted} GEN`);
251
254
  console.log(`Minimum stake required: ${minStakeFormatted}`);
252
255
  if (currentEpoch === 0n) {
253
- console.log("(Epoch 0: minimum stake not enforced)");
256
+ console.log("(Epoch 0: minimum stake not enforced, but gas fees still required)");
257
+ console.log(`Note: Validator won't become active until self-stake reaches ${minStakeFormatted}`);
254
258
  }
255
259
 
256
- // Epoch 0 doesn't enforce min stake, so only check for non-zero epochs
257
- if (currentEpoch !== 0n && balance < minStakeRaw) {
260
+ // Always need gas, plus stake requirement after Epoch 0
261
+ const minRequired = currentEpoch === 0n ? MIN_GAS_BUFFER : minStakeRaw + MIN_GAS_BUFFER;
262
+
263
+ if (balance < minRequired) {
258
264
  console.log("");
265
+ const minFormatted = currentEpoch === 0n ? "0.01 GEN (for gas)" : `${minStakeFormatted} + gas`;
259
266
  this.failSpinner(
260
- `Insufficient balance. You need at least ${minStakeFormatted} to become a validator.\n` +
267
+ `Insufficient balance. You need at least ${minFormatted} to become a validator.\n` +
261
268
  `Fund your account (${state.accountAddress}) and run the wizard again.`
262
269
  );
263
270
  }
@@ -1,4 +1,19 @@
1
- import {default as keytar} from 'keytar';
1
+ type Keytar = typeof import('keytar').default;
2
+
3
+ let keytarModule: Keytar | null = null;
4
+ let keytarLoadAttempted = false;
5
+
6
+ async function getKeytar(): Promise<Keytar | null> {
7
+ if (keytarLoadAttempted) return keytarModule;
8
+ keytarLoadAttempted = true;
9
+ try {
10
+ const mod = await import('keytar');
11
+ keytarModule = mod.default ?? mod;
12
+ return keytarModule;
13
+ } catch {
14
+ return null;
15
+ }
16
+ }
2
17
 
3
18
  export class KeychainManager {
4
19
  private static readonly SERVICE = 'genlayer-cli';
@@ -11,6 +26,8 @@ export class KeychainManager {
11
26
 
12
27
  async isKeychainAvailable(): Promise<boolean> {
13
28
  try {
29
+ const keytar = await getKeytar();
30
+ if (!keytar) return false;
14
31
  await keytar.findCredentials('test-service');
15
32
  return true;
16
33
  } catch {
@@ -19,23 +36,50 @@ export class KeychainManager {
19
36
  }
20
37
 
21
38
  async storePrivateKey(accountName: string, privateKey: string): Promise<void> {
22
- return await keytar.setPassword(KeychainManager.SERVICE, this.getKeychainAccount(accountName), privateKey);
39
+ const keytar = await getKeytar();
40
+ if (!keytar) throw new Error('Keychain not available. Install libsecret-1-dev on Linux.');
41
+ try {
42
+ return await keytar.setPassword(KeychainManager.SERVICE, this.getKeychainAccount(accountName), privateKey);
43
+ } catch (error: any) {
44
+ if (error?.message?.includes('org.freedesktop.secrets')) {
45
+ throw new Error('Keychain service not running. Install and start gnome-keyring or another secrets service.');
46
+ }
47
+ throw error;
48
+ }
23
49
  }
24
50
 
25
51
  async getPrivateKey(accountName: string): Promise<string | null> {
26
- return await keytar.getPassword(KeychainManager.SERVICE, this.getKeychainAccount(accountName));
52
+ const keytar = await getKeytar();
53
+ if (!keytar) return null;
54
+ try {
55
+ return await keytar.getPassword(KeychainManager.SERVICE, this.getKeychainAccount(accountName));
56
+ } catch {
57
+ return null;
58
+ }
27
59
  }
28
60
 
29
61
  async removePrivateKey(accountName: string): Promise<boolean> {
30
- return await keytar.deletePassword(KeychainManager.SERVICE, this.getKeychainAccount(accountName));
62
+ const keytar = await getKeytar();
63
+ if (!keytar) return false;
64
+ try {
65
+ return await keytar.deletePassword(KeychainManager.SERVICE, this.getKeychainAccount(accountName));
66
+ } catch {
67
+ return false;
68
+ }
31
69
  }
32
70
 
33
71
  async listUnlockedAccounts(): Promise<string[]> {
34
- const credentials = await keytar.findCredentials(KeychainManager.SERVICE);
35
- return credentials
36
- .map(c => c.account)
37
- .filter(a => a.startsWith('account:'))
38
- .map(a => a.replace('account:', ''));
72
+ const keytar = await getKeytar();
73
+ if (!keytar) return [];
74
+ try {
75
+ const credentials = await keytar.findCredentials(KeychainManager.SERVICE);
76
+ return credentials
77
+ .map(c => c.account)
78
+ .filter(a => a.startsWith('account:'))
79
+ .map(a => a.replace('account:', ''));
80
+ } catch {
81
+ return [];
82
+ }
39
83
  }
40
84
 
41
85
  async isAccountUnlocked(accountName: string): Promise<boolean> {
@@ -75,10 +75,12 @@ describe("KeychainManager", () => {
75
75
  expect(keytar.getPassword).toHaveBeenCalledWith("genlayer-cli", "account:main");
76
76
  });
77
77
 
78
- test("handles retrieval error", async () => {
78
+ test("returns null on retrieval error", async () => {
79
79
  vi.mocked(keytar.getPassword).mockRejectedValue(new Error("Retrieval failed"));
80
80
 
81
- await expect(keychainManager.getPrivateKey("main")).rejects.toThrow("Retrieval failed");
81
+ const result = await keychainManager.getPrivateKey("main");
82
+
83
+ expect(result).toBeNull();
82
84
  expect(keytar.getPassword).toHaveBeenCalledWith("genlayer-cli", "account:main");
83
85
  });
84
86
  });
@@ -102,10 +104,12 @@ describe("KeychainManager", () => {
102
104
  expect(keytar.deletePassword).toHaveBeenCalledWith("genlayer-cli", "account:main");
103
105
  });
104
106
 
105
- test("handles removal error", async () => {
107
+ test("returns false on removal error", async () => {
106
108
  vi.mocked(keytar.deletePassword).mockRejectedValue(new Error("Removal failed"));
107
109
 
108
- await expect(keychainManager.removePrivateKey("main")).rejects.toThrow("Removal failed");
110
+ const result = await keychainManager.removePrivateKey("main");
111
+
112
+ expect(result).toBe(false);
109
113
  expect(keytar.deletePassword).toHaveBeenCalledWith("genlayer-cli", "account:main");
110
114
  });
111
115
  });