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
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
19951
|
-
|
|
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
|
-
|
|
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 ${
|
|
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
|
}
|
package/docs/validator-guide.md
CHANGED
|
@@ -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
|
@@ -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
|
-
//
|
|
257
|
-
|
|
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 ${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
35
|
-
return
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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("
|
|
78
|
+
test("returns null on retrieval error", async () => {
|
|
79
79
|
vi.mocked(keytar.getPassword).mockRejectedValue(new Error("Retrieval failed"));
|
|
80
80
|
|
|
81
|
-
await
|
|
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("
|
|
107
|
+
test("returns false on removal error", async () => {
|
|
106
108
|
vi.mocked(keytar.deletePassword).mockRejectedValue(new Error("Removal failed"));
|
|
107
109
|
|
|
108
|
-
await
|
|
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
|
});
|