genlayer 0.34.0 → 0.34.3

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.
Files changed (128) hide show
  1. package/dist/index.js +13 -3
  2. package/package.json +11 -2
  3. package/.eslintignore +0 -2
  4. package/.github/workflows/cli-docs.yml +0 -121
  5. package/.github/workflows/publish-beta.yml +0 -41
  6. package/.github/workflows/publish.yml +0 -43
  7. package/.github/workflows/validate-code.yml +0 -47
  8. package/.prettierignore +0 -19
  9. package/.prettierrc +0 -12
  10. package/.release-it.json +0 -64
  11. package/CHANGELOG.md +0 -425
  12. package/CLAUDE.md +0 -55
  13. package/CONTRIBUTING.md +0 -117
  14. package/docker-compose.yml +0 -154
  15. package/docs/delegator-guide.md +0 -203
  16. package/docs/validator-guide.md +0 -329
  17. package/esbuild.config.dev.js +0 -17
  18. package/esbuild.config.js +0 -22
  19. package/esbuild.config.prod.js +0 -17
  20. package/eslint.config.js +0 -60
  21. package/renovate.json +0 -22
  22. package/src/commands/account/create.ts +0 -30
  23. package/src/commands/account/export.ts +0 -106
  24. package/src/commands/account/import.ts +0 -135
  25. package/src/commands/account/index.ts +0 -129
  26. package/src/commands/account/list.ts +0 -34
  27. package/src/commands/account/lock.ts +0 -39
  28. package/src/commands/account/remove.ts +0 -30
  29. package/src/commands/account/send.ts +0 -162
  30. package/src/commands/account/show.ts +0 -74
  31. package/src/commands/account/unlock.ts +0 -56
  32. package/src/commands/account/use.ts +0 -21
  33. package/src/commands/config/getSetReset.ts +0 -51
  34. package/src/commands/config/index.ts +0 -30
  35. package/src/commands/contracts/call.ts +0 -39
  36. package/src/commands/contracts/code.ts +0 -33
  37. package/src/commands/contracts/deploy.ts +0 -157
  38. package/src/commands/contracts/index.ts +0 -86
  39. package/src/commands/contracts/schema.ts +0 -31
  40. package/src/commands/contracts/write.ts +0 -49
  41. package/src/commands/general/index.ts +0 -45
  42. package/src/commands/general/init.ts +0 -179
  43. package/src/commands/general/start.ts +0 -116
  44. package/src/commands/general/stop.ts +0 -26
  45. package/src/commands/localnet/index.ts +0 -100
  46. package/src/commands/localnet/validators.ts +0 -269
  47. package/src/commands/network/index.ts +0 -29
  48. package/src/commands/network/setNetwork.ts +0 -77
  49. package/src/commands/scaffold/index.ts +0 -16
  50. package/src/commands/scaffold/new.ts +0 -34
  51. package/src/commands/staking/StakingAction.ts +0 -279
  52. package/src/commands/staking/delegatorClaim.ts +0 -41
  53. package/src/commands/staking/delegatorExit.ts +0 -56
  54. package/src/commands/staking/delegatorJoin.ts +0 -44
  55. package/src/commands/staking/index.ts +0 -357
  56. package/src/commands/staking/setIdentity.ts +0 -78
  57. package/src/commands/staking/setOperator.ts +0 -46
  58. package/src/commands/staking/stakingInfo.ts +0 -584
  59. package/src/commands/staking/validatorClaim.ts +0 -43
  60. package/src/commands/staking/validatorDeposit.ts +0 -48
  61. package/src/commands/staking/validatorExit.ts +0 -63
  62. package/src/commands/staking/validatorHistory.ts +0 -298
  63. package/src/commands/staking/validatorJoin.ts +0 -47
  64. package/src/commands/staking/validatorPrime.ts +0 -73
  65. package/src/commands/staking/wizard.ts +0 -809
  66. package/src/commands/transactions/appeal.ts +0 -39
  67. package/src/commands/transactions/index.ts +0 -39
  68. package/src/commands/transactions/receipt.ts +0 -90
  69. package/src/commands/update/index.ts +0 -25
  70. package/src/commands/update/ollama.ts +0 -103
  71. package/src/lib/actions/BaseAction.ts +0 -299
  72. package/src/lib/clients/jsonRpcClient.ts +0 -41
  73. package/src/lib/clients/system.ts +0 -73
  74. package/src/lib/config/ConfigFileManager.ts +0 -194
  75. package/src/lib/config/KeychainManager.ts +0 -89
  76. package/src/lib/config/simulator.ts +0 -68
  77. package/src/lib/config/text.ts +0 -2
  78. package/src/lib/errors/missingRequirement.ts +0 -9
  79. package/src/lib/errors/versionRequired.ts +0 -9
  80. package/src/lib/interfaces/ISimulatorService.ts +0 -37
  81. package/src/lib/services/simulator.ts +0 -351
  82. package/src/types/node-fetch.d.ts +0 -1
  83. package/tests/actions/appeal.test.ts +0 -99
  84. package/tests/actions/call.test.ts +0 -94
  85. package/tests/actions/code.test.ts +0 -87
  86. package/tests/actions/create.test.ts +0 -65
  87. package/tests/actions/deploy.test.ts +0 -420
  88. package/tests/actions/getSetReset.test.ts +0 -88
  89. package/tests/actions/init.test.ts +0 -467
  90. package/tests/actions/lock.test.ts +0 -86
  91. package/tests/actions/new.test.ts +0 -80
  92. package/tests/actions/ollama.test.ts +0 -193
  93. package/tests/actions/receipt.test.ts +0 -261
  94. package/tests/actions/schema.test.ts +0 -94
  95. package/tests/actions/setNetwork.test.ts +0 -160
  96. package/tests/actions/staking.test.ts +0 -279
  97. package/tests/actions/start.test.ts +0 -235
  98. package/tests/actions/stop.test.ts +0 -77
  99. package/tests/actions/unlock.test.ts +0 -139
  100. package/tests/actions/validators.test.ts +0 -750
  101. package/tests/actions/write.test.ts +0 -102
  102. package/tests/commands/account.test.ts +0 -146
  103. package/tests/commands/appeal.test.ts +0 -58
  104. package/tests/commands/call.test.ts +0 -78
  105. package/tests/commands/code.test.ts +0 -69
  106. package/tests/commands/config.test.ts +0 -54
  107. package/tests/commands/deploy.test.ts +0 -83
  108. package/tests/commands/init.test.ts +0 -101
  109. package/tests/commands/localnet.test.ts +0 -131
  110. package/tests/commands/network.test.ts +0 -60
  111. package/tests/commands/new.test.ts +0 -68
  112. package/tests/commands/receipt.test.ts +0 -142
  113. package/tests/commands/schema.test.ts +0 -67
  114. package/tests/commands/staking.test.ts +0 -329
  115. package/tests/commands/stop.test.ts +0 -27
  116. package/tests/commands/up.test.ts +0 -105
  117. package/tests/commands/update.test.ts +0 -45
  118. package/tests/commands/write.test.ts +0 -76
  119. package/tests/index.test.ts +0 -56
  120. package/tests/libs/baseAction.test.ts +0 -516
  121. package/tests/libs/configFileManager.test.ts +0 -117
  122. package/tests/libs/jsonRpcClient.test.ts +0 -59
  123. package/tests/libs/keychainManager.test.ts +0 -156
  124. package/tests/libs/system.test.ts +0 -148
  125. package/tests/services/simulator.test.ts +0 -705
  126. package/tests/utils.ts +0 -13
  127. package/tsconfig.json +0 -120
  128. package/vitest.config.ts +0 -12
@@ -1,809 +0,0 @@
1
- import {StakingAction, StakingConfig, BUILT_IN_NETWORKS} from "./StakingAction";
2
- import {CreateAccountAction} from "../account/create";
3
- import {ExportAccountAction} from "../account/export";
4
- import inquirer from "inquirer";
5
- import type {Address} from "genlayer-js/types";
6
- import {formatEther, parseEther} from "viem";
7
- import {createClient} from "genlayer-js";
8
- import {readFileSync, existsSync} from "fs";
9
- import path from "path";
10
-
11
- export interface WizardOptions extends StakingConfig {
12
- skipIdentity?: boolean;
13
- }
14
-
15
- interface WizardState {
16
- accountName: string;
17
- accountAddress: string;
18
- networkAlias: string;
19
- balance: bigint;
20
- minStake: bigint;
21
- operatorAddress?: string;
22
- operatorAccountName?: string; // if operator is a CLI account
23
- operatorKeystorePath?: string;
24
- stakeAmount: string;
25
- validatorWalletAddress?: string; // the validator contract address returned from validatorJoin
26
- identity?: {
27
- moniker: string;
28
- logoUri?: string;
29
- website?: string;
30
- description?: string;
31
- email?: string;
32
- twitter?: string;
33
- telegram?: string;
34
- github?: string;
35
- };
36
- }
37
-
38
- // Ensure address has 0x prefix
39
- function ensureHexPrefix(address: string): string {
40
- if (!address) return address;
41
- return address.startsWith("0x") ? address : `0x${address}`;
42
- }
43
-
44
- export class ValidatorWizardAction extends StakingAction {
45
- constructor() {
46
- super();
47
- }
48
-
49
- async execute(options: WizardOptions): Promise<void> {
50
- console.log("\n========================================");
51
- console.log(" GenLayer Validator Setup Wizard");
52
- console.log("========================================\n");
53
-
54
- const state: Partial<WizardState> = {};
55
-
56
- try {
57
- // Step 1: Account Setup
58
- await this.stepAccountSetup(state, options);
59
-
60
- // Step 2: Network Selection
61
- await this.stepNetworkSelection(state, options);
62
-
63
- // Step 3: Balance Check
64
- await this.stepBalanceCheck(state, options);
65
-
66
- // Step 4: Operator Setup
67
- await this.stepOperatorSetup(state);
68
-
69
- // Step 5: Stake Amount
70
- await this.stepStakeAmount(state);
71
-
72
- // Step 6: Join as Validator
73
- await this.stepJoinValidator(state, options);
74
-
75
- // Step 7: Identity Setup
76
- if (!options.skipIdentity) {
77
- await this.stepIdentitySetup(state, options);
78
- }
79
-
80
- // Step 8: Summary
81
- this.showSummary(state as WizardState);
82
- } catch (error: any) {
83
- if (error.message === "WIZARD_ABORTED") {
84
- this.logError("Wizard aborted.");
85
- return;
86
- }
87
- this.failSpinner("Wizard failed", error.message || error);
88
- }
89
- }
90
-
91
- private async stepAccountSetup(state: Partial<WizardState>, options: WizardOptions): Promise<void> {
92
- console.log("Step 1: Account Setup");
93
- console.log("---------------------\n");
94
-
95
- // Check if account override provided
96
- if (options.account) {
97
- const keystorePath = this.getKeystorePath(options.account);
98
- if (!this.accountExists(options.account)) {
99
- this.failSpinner(`Account '${options.account}' not found.`);
100
- }
101
- state.accountName = options.account;
102
- this.accountOverride = options.account;
103
- const address = await this.getSignerAddress();
104
- state.accountAddress = ensureHexPrefix(address);
105
- console.log(`Using account: ${options.account} (${state.accountAddress})\n`);
106
- return;
107
- }
108
-
109
- const accounts = this.listAccounts();
110
-
111
- if (accounts.length === 0) {
112
- // No accounts exist, create one
113
- console.log("No accounts found. Let's create one.\n");
114
- const {accountName} = await inquirer.prompt([
115
- {
116
- type: "input",
117
- name: "accountName",
118
- message: "Enter a name for your owner account:",
119
- default: "owner",
120
- validate: (input: string) => input.length > 0 || "Name cannot be empty",
121
- },
122
- ]);
123
-
124
- const createAction = new CreateAccountAction();
125
- await createAction.execute({name: accountName, overwrite: false, setActive: true});
126
-
127
- state.accountName = accountName;
128
- this.accountOverride = accountName;
129
- const address = await this.getSignerAddress();
130
- state.accountAddress = ensureHexPrefix(address);
131
- } else {
132
- // Accounts exist, choose or create
133
- const choices = [
134
- ...accounts.map(a => ({
135
- name: `${a.name} (${a.address})`,
136
- value: a.name,
137
- })),
138
- {name: "Create new account", value: "__create_new__"},
139
- ];
140
-
141
- const {selectedAccount} = await inquirer.prompt([
142
- {
143
- type: "list",
144
- name: "selectedAccount",
145
- message: "Select an account that will be the owner of the validator:",
146
- choices,
147
- },
148
- ]);
149
-
150
- if (selectedAccount === "__create_new__") {
151
- const {accountName} = await inquirer.prompt([
152
- {
153
- type: "input",
154
- name: "accountName",
155
- message: "Enter a name for your validator account:",
156
- default: "validator",
157
- validate: (input: string) => {
158
- if (input.length === 0) return "Name cannot be empty";
159
- if (accounts.find(a => a.name === input)) return "Account with this name already exists";
160
- return true;
161
- },
162
- },
163
- ]);
164
-
165
- const createAction = new CreateAccountAction();
166
- await createAction.execute({name: accountName, overwrite: false, setActive: true});
167
-
168
- state.accountName = accountName;
169
- this.accountOverride = accountName;
170
- const address = await this.getSignerAddress();
171
- state.accountAddress = ensureHexPrefix(address);
172
- } else {
173
- state.accountName = selectedAccount;
174
- this.accountOverride = selectedAccount;
175
- this.setActiveAccount(selectedAccount);
176
- const address = await this.getSignerAddress();
177
- state.accountAddress = ensureHexPrefix(address);
178
- console.log(`\nUsing account: ${selectedAccount} (${state.accountAddress})`);
179
- }
180
- }
181
-
182
- console.log("");
183
- }
184
-
185
- private async stepNetworkSelection(state: Partial<WizardState>, options: WizardOptions): Promise<void> {
186
- console.log("Step 2: Network Selection");
187
- console.log("-------------------------\n");
188
-
189
- if (options.network) {
190
- const network = BUILT_IN_NETWORKS[options.network];
191
- if (!network) {
192
- this.failSpinner(`Unknown network: ${options.network}`);
193
- }
194
- state.networkAlias = options.network;
195
- this.writeConfig("network", options.network);
196
- console.log(`Using network: ${network.name}\n`);
197
- return;
198
- }
199
-
200
- const currentNetwork = this.getConfigByKey("network");
201
- // Exclude studionet - not compatible with staking
202
- const excludedNetworks = ["studionet"];
203
- const networks = Object.entries(BUILT_IN_NETWORKS)
204
- .filter(([alias]) => !excludedNetworks.includes(alias))
205
- .map(([alias, chain]) => ({
206
- name: chain.name,
207
- value: alias,
208
- }));
209
-
210
- const {selectedNetwork} = await inquirer.prompt([
211
- {
212
- type: "list",
213
- name: "selectedNetwork",
214
- message: "Select network:",
215
- choices: networks,
216
- default: currentNetwork || "testnet-asimov",
217
- },
218
- ]);
219
-
220
- state.networkAlias = selectedNetwork;
221
- this.writeConfig("network", selectedNetwork);
222
- console.log(`\nNetwork set to: ${BUILT_IN_NETWORKS[selectedNetwork].name}\n`);
223
- }
224
-
225
- private async stepBalanceCheck(state: Partial<WizardState>, options: WizardOptions): Promise<void> {
226
- console.log("Step 3: Balance Check");
227
- console.log("---------------------\n");
228
-
229
- this.startSpinner("Checking balance and staking requirements...");
230
-
231
- const network = BUILT_IN_NETWORKS[state.networkAlias!];
232
- const client = createClient({
233
- chain: network,
234
- account: state.accountAddress as Address,
235
- endpoint: options.rpc,
236
- });
237
-
238
- const [balance, epochInfo] = await Promise.all([
239
- client.getBalance({address: state.accountAddress as Address}),
240
- client.getEpochInfo(),
241
- ]);
242
-
243
- this.stopSpinner();
244
-
245
- const balanceFormatted = formatEther(balance);
246
- const minStakeRaw = epochInfo.validatorMinStakeRaw;
247
- const minStakeFormatted = epochInfo.validatorMinStake;
248
- const currentEpoch = epochInfo.currentEpoch;
249
-
250
- // Minimum gas buffer for transaction fees (~0.01 GEN)
251
- const MIN_GAS_BUFFER = parseEther("0.01");
252
-
253
- console.log(`Balance: ${balanceFormatted} GEN`);
254
- console.log(`Minimum stake required: ${minStakeFormatted}`);
255
- if (currentEpoch === 0n) {
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}`);
258
- }
259
-
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) {
264
- console.log("");
265
- const minFormatted = currentEpoch === 0n ? "0.01 GEN (for gas)" : `${minStakeFormatted} + gas`;
266
- this.failSpinner(
267
- `Insufficient balance. You need at least ${minFormatted} to become a validator.\n` +
268
- `Fund your account (${state.accountAddress}) and run the wizard again.`
269
- );
270
- }
271
-
272
- state.balance = balance;
273
- state.minStake = currentEpoch === 0n ? 0n : minStakeRaw;
274
-
275
- console.log("Balance sufficient!\n");
276
- }
277
-
278
- private async stepOperatorSetup(state: Partial<WizardState>): Promise<void> {
279
- console.log("Step 4: Operator Setup");
280
- console.log("----------------------\n");
281
-
282
- console.log("Using a separate operator address is recommended for security:");
283
- console.log("- Owner account: holds staked funds (keep secure)");
284
- console.log("- Operator account: signs blocks (hot wallet on validator server)\n");
285
-
286
- const {useOperator} = await inquirer.prompt([
287
- {
288
- type: "confirm",
289
- name: "useOperator",
290
- message: "Do you want to use a separate operator address?",
291
- default: true,
292
- },
293
- ]);
294
-
295
- if (!useOperator) {
296
- state.operatorAddress = ensureHexPrefix(state.accountAddress);
297
- state.operatorAccountName = state.accountName;
298
- console.log("\nOperator will be the same as owner address.\n");
299
- return;
300
- }
301
-
302
- const accounts = this.listAccounts();
303
- const otherAccounts = accounts.filter(a => a.name !== state.accountName);
304
-
305
- const choices = [
306
- {name: "Create new operator account", value: "create"},
307
- ...(otherAccounts.length > 0 ? [{name: "Select from my accounts", value: "select"}] : []),
308
- {name: "Enter existing operator address", value: "existing"},
309
- ];
310
-
311
- const {operatorChoice} = await inquirer.prompt([
312
- {
313
- type: "list",
314
- name: "operatorChoice",
315
- message: "How would you like to set up the operator?",
316
- choices,
317
- },
318
- ]);
319
-
320
- if (operatorChoice === "existing") {
321
- const {operatorAddress} = await inquirer.prompt([
322
- {
323
- type: "input",
324
- name: "operatorAddress",
325
- message: "Enter operator address (0x...):",
326
- validate: (input: string) => {
327
- if (!input.match(/^0x[a-fA-F0-9]{40}$/)) {
328
- return "Invalid address format. Expected 0x followed by 40 hex characters.";
329
- }
330
- return true;
331
- },
332
- },
333
- ]);
334
- state.operatorAddress = ensureHexPrefix(operatorAddress);
335
- // No operatorAccountName - external address
336
- console.log("");
337
- return;
338
- }
339
-
340
- if (operatorChoice === "select") {
341
- const {selectedOperator} = await inquirer.prompt([
342
- {
343
- type: "list",
344
- name: "selectedOperator",
345
- message: "Select an account to use as operator:",
346
- choices: otherAccounts.map(a => ({
347
- name: `${a.name} (${a.address})`,
348
- value: a.name,
349
- })),
350
- },
351
- ]);
352
-
353
- const operatorKeystorePath = this.getKeystorePath(selectedOperator);
354
- const operatorData = JSON.parse(readFileSync(operatorKeystorePath, "utf-8"));
355
- state.operatorAddress = ensureHexPrefix(operatorData.address);
356
- state.operatorAccountName = selectedOperator;
357
-
358
- // Export the selected operator keystore
359
- const defaultFilename = `${selectedOperator}-keystore.json`;
360
- const {outputFilename} = await inquirer.prompt([
361
- {
362
- type: "input",
363
- name: "outputFilename",
364
- message: "Export keystore filename:",
365
- default: defaultFilename,
366
- },
367
- ]);
368
-
369
- let outputPath = path.resolve(`./${outputFilename}`);
370
-
371
- // Check if file exists and ask to overwrite
372
- if (existsSync(outputPath)) {
373
- const {overwrite} = await inquirer.prompt([
374
- {
375
- type: "confirm",
376
- name: "overwrite",
377
- message: `File ${outputFilename} already exists. Overwrite?`,
378
- default: false,
379
- },
380
- ]);
381
- if (!overwrite) {
382
- const {newFilename} = await inquirer.prompt([
383
- {
384
- type: "input",
385
- name: "newFilename",
386
- message: "Enter new filename:",
387
- },
388
- ]);
389
- outputPath = path.resolve(`./${newFilename}`);
390
- }
391
- }
392
-
393
- const {exportPassword} = await inquirer.prompt([
394
- {
395
- type: "password",
396
- name: "exportPassword",
397
- message: "Enter password for exported keystore (needed to import in node):",
398
- mask: "*",
399
- validate: (input: string) => input.length >= 8 || "Password must be at least 8 characters",
400
- },
401
- ]);
402
-
403
- const {confirmPassword} = await inquirer.prompt([
404
- {
405
- type: "password",
406
- name: "confirmPassword",
407
- message: "Confirm password:",
408
- mask: "*",
409
- },
410
- ]);
411
-
412
- if (exportPassword !== confirmPassword) {
413
- this.failSpinner("Passwords do not match");
414
- }
415
-
416
- const exportAction = new ExportAccountAction();
417
- await exportAction.execute({
418
- account: selectedOperator,
419
- output: outputPath,
420
- password: exportPassword,
421
- overwrite: true,
422
- });
423
-
424
- state.operatorKeystorePath = outputPath;
425
-
426
- console.log("\n========================================");
427
- console.log(" IMPORTANT: Transfer operator keystore");
428
- console.log("========================================");
429
- console.log(`File: ${outputPath}`);
430
- console.log("Transfer this file to your validator server and import it");
431
- console.log("into your validator node software.");
432
- console.log("========================================\n");
433
- return;
434
- }
435
-
436
- // Create new operator account
437
- const {operatorName} = await inquirer.prompt([
438
- {
439
- type: "input",
440
- name: "operatorName",
441
- message: "Enter a name for the operator account:",
442
- default: "operator",
443
- validate: (input: string) => {
444
- if (input.length === 0) return "Name cannot be empty";
445
- if (accounts.find(a => a.name === input)) return "Account with this name already exists";
446
- return true;
447
- },
448
- },
449
- ]);
450
-
451
- // Create the operator account
452
- console.log("");
453
- const createAction = new CreateAccountAction();
454
- await createAction.execute({name: operatorName, overwrite: false, setActive: false});
455
-
456
- // Get operator address
457
- const operatorKeystorePath = this.getKeystorePath(operatorName);
458
- const operatorData = JSON.parse(readFileSync(operatorKeystorePath, "utf-8"));
459
- state.operatorAddress = ensureHexPrefix(operatorData.address);
460
- state.operatorAccountName = operatorName;
461
-
462
- // Export keystore
463
- const defaultFilename = `${operatorName}-keystore.json`;
464
- const {outputFilename} = await inquirer.prompt([
465
- {
466
- type: "input",
467
- name: "outputFilename",
468
- message: "Export keystore filename:",
469
- default: defaultFilename,
470
- },
471
- ]);
472
-
473
- let outputPath = path.resolve(`./${outputFilename}`);
474
-
475
- // Check if file exists and ask to overwrite
476
- if (existsSync(outputPath)) {
477
- const {overwrite} = await inquirer.prompt([
478
- {
479
- type: "confirm",
480
- name: "overwrite",
481
- message: `File ${outputFilename} already exists. Overwrite?`,
482
- default: false,
483
- },
484
- ]);
485
- if (!overwrite) {
486
- const {newFilename} = await inquirer.prompt([
487
- {
488
- type: "input",
489
- name: "newFilename",
490
- message: "Enter new filename:",
491
- },
492
- ]);
493
- outputPath = path.resolve(`./${newFilename}`);
494
- }
495
- }
496
-
497
- const {exportPassword} = await inquirer.prompt([
498
- {
499
- type: "password",
500
- name: "exportPassword",
501
- message: "Enter password for exported keystore (needed to import in node):",
502
- mask: "*",
503
- validate: (input: string) => input.length >= 8 || "Password must be at least 8 characters",
504
- },
505
- ]);
506
-
507
- const {confirmPassword} = await inquirer.prompt([
508
- {
509
- type: "password",
510
- name: "confirmPassword",
511
- message: "Confirm password:",
512
- mask: "*",
513
- },
514
- ]);
515
-
516
- if (exportPassword !== confirmPassword) {
517
- this.failSpinner("Passwords do not match");
518
- }
519
-
520
- const exportAction = new ExportAccountAction();
521
- await exportAction.execute({
522
- account: operatorName,
523
- output: outputPath,
524
- password: exportPassword,
525
- overwrite: true,
526
- });
527
-
528
- state.operatorKeystorePath = outputPath;
529
-
530
- console.log("\n========================================");
531
- console.log(" IMPORTANT: Transfer operator keystore");
532
- console.log("========================================");
533
- console.log(`File: ${outputPath}`);
534
- console.log("Transfer this file to your validator server and import it");
535
- console.log("into your validator node software.");
536
- console.log("========================================\n");
537
- }
538
-
539
- private async stepStakeAmount(state: Partial<WizardState>): Promise<void> {
540
- console.log("Step 5: Stake Amount");
541
- console.log("--------------------\n");
542
-
543
- const balanceGEN = formatEther(state.balance!);
544
- const minStakeGEN = formatEther(state.minStake!);
545
- const hasMinStake = state.minStake! > 0n;
546
-
547
- const {stakeAmount} = await inquirer.prompt([
548
- {
549
- type: "input",
550
- name: "stakeAmount",
551
- message: hasMinStake
552
- ? `Enter stake amount (min: ${minStakeGEN}, max: ${balanceGEN} GEN):`
553
- : `Enter stake amount (max: ${balanceGEN} GEN):`,
554
- default: hasMinStake ? minStakeGEN : "1",
555
- validate: (input: string) => {
556
- const cleaned = input.toLowerCase().replace("gen", "").trim();
557
- const num = parseFloat(cleaned);
558
- if (isNaN(num) || num <= 0) {
559
- return "Please enter a valid positive number";
560
- }
561
- const amountWei = BigInt(Math.floor(num * 1e18));
562
- if (hasMinStake && amountWei < state.minStake!) {
563
- return `Amount must be at least ${minStakeGEN} GEN`;
564
- }
565
- if (amountWei > state.balance!) {
566
- return `Amount exceeds balance (${balanceGEN} GEN)`;
567
- }
568
- return true;
569
- },
570
- },
571
- ]);
572
-
573
- // Normalize amount to always have "gen" suffix
574
- const normalizedAmount = stakeAmount.toLowerCase().endsWith("gen") ? stakeAmount : `${stakeAmount}gen`;
575
- state.stakeAmount = normalizedAmount;
576
-
577
- const {confirm} = await inquirer.prompt([
578
- {
579
- type: "confirm",
580
- name: "confirm",
581
- message: `You will stake ${stakeAmount}. Continue?`,
582
- default: true,
583
- },
584
- ]);
585
-
586
- if (!confirm) {
587
- throw new Error("WIZARD_ABORTED");
588
- }
589
-
590
- console.log("");
591
- }
592
-
593
- private async stepJoinValidator(state: Partial<WizardState>, options: WizardOptions): Promise<void> {
594
- console.log("Step 6: Join as Validator");
595
- console.log("-------------------------\n");
596
-
597
- this.startSpinner("Creating validator...");
598
-
599
- try {
600
- const client = await this.getStakingClient({
601
- ...options,
602
- account: state.accountName,
603
- network: state.networkAlias,
604
- });
605
-
606
- const amount = this.parseAmount(state.stakeAmount!);
607
-
608
- this.setSpinnerText(`Creating validator with ${this.formatAmount(amount)} stake...`);
609
-
610
- const result = await client.validatorJoin({
611
- amount,
612
- operator: state.operatorAddress as Address,
613
- });
614
-
615
- // Save the validator wallet address
616
- state.validatorWalletAddress = ensureHexPrefix(result.validatorWallet);
617
-
618
- this.succeedSpinner("Validator created successfully!", {
619
- transactionHash: result.transactionHash,
620
- validatorWallet: state.validatorWalletAddress,
621
- amount: result.amount,
622
- operator: result.operator,
623
- blockNumber: result.blockNumber.toString(),
624
- });
625
-
626
- console.log("");
627
- } catch (error: any) {
628
- this.failSpinner("Failed to create validator", error.message || error);
629
- }
630
- }
631
-
632
- private async stepIdentitySetup(state: Partial<WizardState>, options: WizardOptions): Promise<void> {
633
- console.log("Step 7: Identity Setup");
634
- console.log("----------------------\n");
635
-
636
- const {setupIdentity} = await inquirer.prompt([
637
- {
638
- type: "confirm",
639
- name: "setupIdentity",
640
- message: "Would you like to set up your validator identity now?",
641
- default: true,
642
- },
643
- ]);
644
-
645
- if (!setupIdentity) {
646
- console.log("\nYou can set up identity later with: genlayer staking set-identity\n");
647
- return;
648
- }
649
-
650
- // Collect all identity fields
651
- const {moniker} = await inquirer.prompt([
652
- {
653
- type: "input",
654
- name: "moniker",
655
- message: "Enter validator display name (moniker):",
656
- validate: (input: string) => input.length > 0 || "Moniker is required",
657
- },
658
- ]);
659
-
660
- const {logoUri} = await inquirer.prompt([
661
- {
662
- type: "input",
663
- name: "logoUri",
664
- message: "Enter logo URL (optional):",
665
- },
666
- ]);
667
-
668
- const {website} = await inquirer.prompt([
669
- {
670
- type: "input",
671
- name: "website",
672
- message: "Enter website URL (optional):",
673
- },
674
- ]);
675
-
676
- const {description} = await inquirer.prompt([
677
- {
678
- type: "input",
679
- name: "description",
680
- message: "Enter description (optional):",
681
- },
682
- ]);
683
-
684
- const {email} = await inquirer.prompt([
685
- {
686
- type: "input",
687
- name: "email",
688
- message: "Enter contact email (optional):",
689
- },
690
- ]);
691
-
692
- const {twitter} = await inquirer.prompt([
693
- {
694
- type: "input",
695
- name: "twitter",
696
- message: "Enter Twitter handle (optional):",
697
- },
698
- ]);
699
-
700
- const {telegram} = await inquirer.prompt([
701
- {
702
- type: "input",
703
- name: "telegram",
704
- message: "Enter Telegram handle (optional):",
705
- },
706
- ]);
707
-
708
- const {github} = await inquirer.prompt([
709
- {
710
- type: "input",
711
- name: "github",
712
- message: "Enter GitHub handle (optional):",
713
- },
714
- ]);
715
-
716
- state.identity = {
717
- moniker,
718
- logoUri: logoUri || undefined,
719
- website: website || undefined,
720
- description: description || undefined,
721
- email: email || undefined,
722
- twitter: twitter || undefined,
723
- telegram: telegram || undefined,
724
- github: github || undefined,
725
- };
726
-
727
- this.startSpinner("Setting validator identity...");
728
-
729
- try {
730
- const client = await this.getStakingClient({
731
- ...options,
732
- account: state.accountName,
733
- network: state.networkAlias,
734
- });
735
-
736
- // Use the validator wallet address (contract), not owner address
737
- const validatorAddress = state.validatorWalletAddress || state.accountAddress;
738
-
739
- await client.setIdentity({
740
- validator: ensureHexPrefix(validatorAddress) as Address,
741
- moniker,
742
- logoUri: logoUri || undefined,
743
- website: website || undefined,
744
- description: description || undefined,
745
- email: email || undefined,
746
- twitter: twitter || undefined,
747
- telegram: telegram || undefined,
748
- github: github || undefined,
749
- });
750
-
751
- this.succeedSpinner("Validator identity set!");
752
- console.log("");
753
- } catch (error: any) {
754
- this.stopSpinner();
755
- this.logWarning(`Failed to set identity: ${error.message || error}`);
756
- console.log("You can try again later with: genlayer staking set-identity\n");
757
- }
758
- }
759
-
760
- private showSummary(state: WizardState): void {
761
- console.log("\n========================================");
762
- console.log(" Validator Setup Complete!");
763
- console.log("========================================\n");
764
-
765
- // Ensure all addresses have 0x prefix
766
- const validatorWallet = ensureHexPrefix(state.validatorWalletAddress || state.accountAddress);
767
- const ownerAddress = ensureHexPrefix(state.accountAddress);
768
- const operatorAddress = ensureHexPrefix(state.operatorAddress || "");
769
-
770
- console.log("Summary:");
771
- // Validator wallet address first - most important
772
- console.log(` Validator Wallet: ${validatorWallet}`);
773
- console.log(` Owner: ${ownerAddress} (${state.accountName})`);
774
-
775
- // Operator - show account name if it's a CLI account
776
- if (state.operatorAccountName) {
777
- console.log(` Operator: ${operatorAddress} (${state.operatorAccountName})`);
778
- } else {
779
- console.log(` Operator: ${operatorAddress}`);
780
- }
781
-
782
- console.log(` Staked Amount: ${state.stakeAmount}`);
783
- console.log(` Network: ${BUILT_IN_NETWORKS[state.networkAlias].name}`);
784
-
785
- if (state.identity) {
786
- console.log(` Identity:`);
787
- console.log(` Moniker: ${state.identity.moniker}`);
788
- if (state.identity.logoUri) console.log(` Logo: ${state.identity.logoUri}`);
789
- if (state.identity.website) console.log(` Website: ${state.identity.website}`);
790
- if (state.identity.description) console.log(` Description: ${state.identity.description}`);
791
- if (state.identity.email) console.log(` Email: ${state.identity.email}`);
792
- if (state.identity.twitter) console.log(` Twitter: ${state.identity.twitter}`);
793
- if (state.identity.telegram) console.log(` Telegram: ${state.identity.telegram}`);
794
- if (state.identity.github) console.log(` GitHub: ${state.identity.github}`);
795
- }
796
-
797
- console.log("\nNext Steps:");
798
- let step = 1;
799
- if (state.operatorKeystorePath) {
800
- console.log(` ${step++}. Transfer operator keystore to your validator server:`);
801
- console.log(` ${state.operatorKeystorePath}`);
802
- console.log(` ${step++}. Import it into your validator node software`);
803
- }
804
- console.log(` ${step++}. Monitor your validator:`);
805
- console.log(` genlayer staking validator-info --validator ${validatorWallet}`);
806
- console.log(` ${step++}. Lock your account when done: genlayer account lock`);
807
- console.log("\n========================================\n");
808
- }
809
- }