genlayer 0.38.9 → 0.38.10

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 (205) hide show
  1. package/.eslintignore +2 -0
  2. package/.github/workflows/cli-docs.yml +124 -0
  3. package/.github/workflows/publish.yml +55 -0
  4. package/.github/workflows/smoke.yml +27 -0
  5. package/.github/workflows/validate-code.yml +51 -0
  6. package/.prettierignore +19 -0
  7. package/.prettierrc +12 -0
  8. package/.release-it.json +66 -0
  9. package/CHANGELOG.md +545 -0
  10. package/CLAUDE.md +55 -0
  11. package/CONTRIBUTING.md +117 -0
  12. package/dist/index.js +221 -62
  13. package/docs/api-references/_meta.json +9 -0
  14. package/docs/api-references/accounts/_meta.json +3 -0
  15. package/docs/api-references/accounts/account/create.mdx +19 -0
  16. package/docs/api-references/accounts/account/export.mdx +19 -0
  17. package/docs/api-references/accounts/account/import.mdx +22 -0
  18. package/docs/api-references/accounts/account/list.mdx +15 -0
  19. package/docs/api-references/accounts/account/lock.mdx +16 -0
  20. package/docs/api-references/accounts/account/remove.mdx +20 -0
  21. package/docs/api-references/accounts/account/send.mdx +24 -0
  22. package/docs/api-references/accounts/account/show.mdx +17 -0
  23. package/docs/api-references/accounts/account/unlock.mdx +17 -0
  24. package/docs/api-references/accounts/account/use.mdx +19 -0
  25. package/docs/api-references/accounts/account.mdx +32 -0
  26. package/docs/api-references/configuration/_meta.json +4 -0
  27. package/docs/api-references/configuration/config/get.mdx +21 -0
  28. package/docs/api-references/configuration/config/reset.mdx +21 -0
  29. package/docs/api-references/configuration/config/set.mdx +21 -0
  30. package/docs/api-references/configuration/config.mdx +25 -0
  31. package/docs/api-references/configuration/network/info.mdx +15 -0
  32. package/docs/api-references/configuration/network/list.mdx +15 -0
  33. package/docs/api-references/configuration/network/set.mdx +21 -0
  34. package/docs/api-references/configuration/network.mdx +25 -0
  35. package/docs/api-references/contracts/_meta.json +7 -0
  36. package/docs/api-references/contracts/call.mdx +21 -0
  37. package/docs/api-references/contracts/code.mdx +20 -0
  38. package/docs/api-references/contracts/deploy.mdx +17 -0
  39. package/docs/api-references/contracts/schema.mdx +20 -0
  40. package/docs/api-references/contracts/write.mdx +21 -0
  41. package/docs/api-references/environment/_meta.json +7 -0
  42. package/docs/api-references/environment/init.mdx +20 -0
  43. package/docs/api-references/environment/new.mdx +21 -0
  44. package/docs/api-references/environment/stop.mdx +15 -0
  45. package/docs/api-references/environment/up.mdx +20 -0
  46. package/docs/api-references/environment/update/ollama.mdx +16 -0
  47. package/docs/api-references/environment/update.mdx +23 -0
  48. package/docs/api-references/index.mdx +35 -0
  49. package/docs/api-references/localnet/_meta.json +3 -0
  50. package/docs/api-references/localnet/localnet/validators/count.mdx +15 -0
  51. package/docs/api-references/localnet/localnet/validators/create-random.mdx +16 -0
  52. package/docs/api-references/localnet/localnet/validators/create.mdx +19 -0
  53. package/docs/api-references/localnet/localnet/validators/delete.mdx +16 -0
  54. package/docs/api-references/localnet/localnet/validators/get.mdx +16 -0
  55. package/docs/api-references/localnet/localnet/validators/update.mdx +23 -0
  56. package/docs/api-references/localnet/localnet/validators.mdx +28 -0
  57. package/docs/api-references/localnet/localnet.mdx +23 -0
  58. package/docs/api-references/staking/_meta.json +3 -0
  59. package/docs/api-references/staking/staking/active-validators.mdx +18 -0
  60. package/docs/api-references/staking/staking/banned-validators.mdx +18 -0
  61. package/docs/api-references/staking/staking/delegation-info.mdx +25 -0
  62. package/docs/api-references/staking/staking/delegator-claim.mdx +26 -0
  63. package/docs/api-references/staking/staking/delegator-exit.mdx +26 -0
  64. package/docs/api-references/staking/staking/delegator-join.mdx +26 -0
  65. package/docs/api-references/staking/staking/epoch-info.mdx +19 -0
  66. package/docs/api-references/staking/staking/prime-all.mdx +20 -0
  67. package/docs/api-references/staking/staking/quarantined-validators.mdx +18 -0
  68. package/docs/api-references/staking/staking/set-identity.mdx +33 -0
  69. package/docs/api-references/staking/staking/set-operator.mdx +26 -0
  70. package/docs/api-references/staking/staking/validator-claim.mdx +24 -0
  71. package/docs/api-references/staking/staking/validator-deposit.mdx +25 -0
  72. package/docs/api-references/staking/staking/validator-exit.mdx +25 -0
  73. package/docs/api-references/staking/staking/validator-history.mdx +29 -0
  74. package/docs/api-references/staking/staking/validator-info.mdx +25 -0
  75. package/docs/api-references/staking/staking/validator-join.mdx +22 -0
  76. package/docs/api-references/staking/staking/validator-prime.mdx +25 -0
  77. package/docs/api-references/staking/staking/validators.mdx +19 -0
  78. package/docs/api-references/staking/staking/wizard.mdx +20 -0
  79. package/docs/api-references/staking/staking.mdx +42 -0
  80. package/docs/api-references/transactions/_meta.json +6 -0
  81. package/docs/api-references/transactions/appeal-bond.mdx +20 -0
  82. package/docs/api-references/transactions/appeal.mdx +21 -0
  83. package/docs/api-references/transactions/receipt.mdx +25 -0
  84. package/docs/api-references/transactions/trace.mdx +21 -0
  85. package/docs/delegator-guide.md +203 -0
  86. package/docs/validator-guide.md +329 -0
  87. package/esbuild.config.dev.js +17 -0
  88. package/esbuild.config.js +22 -0
  89. package/esbuild.config.prod.js +17 -0
  90. package/eslint.config.js +60 -0
  91. package/package.json +2 -11
  92. package/renovate.json +22 -0
  93. package/scripts/generate-cli-docs.mjs +68 -5
  94. package/src/commands/account/create.ts +30 -0
  95. package/src/commands/account/export.ts +106 -0
  96. package/src/commands/account/import.ts +135 -0
  97. package/src/commands/account/index.ts +129 -0
  98. package/src/commands/account/list.ts +34 -0
  99. package/src/commands/account/lock.ts +39 -0
  100. package/src/commands/account/remove.ts +30 -0
  101. package/src/commands/account/send.ts +162 -0
  102. package/src/commands/account/show.ts +74 -0
  103. package/src/commands/account/unlock.ts +56 -0
  104. package/src/commands/account/use.ts +21 -0
  105. package/src/commands/config/getSetReset.ts +51 -0
  106. package/src/commands/config/index.ts +30 -0
  107. package/src/commands/contracts/call.ts +39 -0
  108. package/src/commands/contracts/code.ts +33 -0
  109. package/src/commands/contracts/deploy.ts +161 -0
  110. package/src/commands/contracts/index.ts +150 -0
  111. package/src/commands/contracts/schema.ts +31 -0
  112. package/src/commands/contracts/write.ts +49 -0
  113. package/src/commands/general/index.ts +45 -0
  114. package/src/commands/general/init.ts +180 -0
  115. package/src/commands/general/start.ts +128 -0
  116. package/src/commands/general/stop.ts +26 -0
  117. package/src/commands/localnet/index.ts +100 -0
  118. package/src/commands/localnet/validators.ts +269 -0
  119. package/src/commands/network/index.ts +29 -0
  120. package/src/commands/network/setNetwork.ts +77 -0
  121. package/src/commands/scaffold/index.ts +16 -0
  122. package/src/commands/scaffold/new.ts +34 -0
  123. package/src/commands/staking/StakingAction.ts +279 -0
  124. package/src/commands/staking/delegatorClaim.ts +41 -0
  125. package/src/commands/staking/delegatorExit.ts +56 -0
  126. package/src/commands/staking/delegatorJoin.ts +44 -0
  127. package/src/commands/staking/index.ts +357 -0
  128. package/src/commands/staking/setIdentity.ts +78 -0
  129. package/src/commands/staking/setOperator.ts +46 -0
  130. package/src/commands/staking/stakingInfo.ts +584 -0
  131. package/src/commands/staking/validatorClaim.ts +43 -0
  132. package/src/commands/staking/validatorDeposit.ts +48 -0
  133. package/src/commands/staking/validatorExit.ts +63 -0
  134. package/src/commands/staking/validatorHistory.ts +300 -0
  135. package/src/commands/staking/validatorJoin.ts +47 -0
  136. package/src/commands/staking/validatorPrime.ts +73 -0
  137. package/src/commands/staking/wizard.ts +809 -0
  138. package/src/commands/transactions/appeal.ts +83 -0
  139. package/src/commands/transactions/index.ts +60 -0
  140. package/src/commands/transactions/receipt.ts +90 -0
  141. package/src/commands/transactions/trace.ts +42 -0
  142. package/src/commands/update/index.ts +25 -0
  143. package/src/commands/update/ollama.ts +103 -0
  144. package/src/lib/actions/BaseAction.ts +301 -0
  145. package/src/lib/clients/jsonRpcClient.ts +41 -0
  146. package/src/lib/clients/system.ts +73 -0
  147. package/src/lib/config/ConfigFileManager.ts +194 -0
  148. package/src/lib/config/KeychainManager.ts +89 -0
  149. package/src/lib/config/simulator.ts +68 -0
  150. package/src/lib/config/text.ts +2 -0
  151. package/src/lib/errors/missingRequirement.ts +9 -0
  152. package/src/lib/errors/versionRequired.ts +9 -0
  153. package/src/lib/interfaces/ISimulatorService.ts +39 -0
  154. package/src/lib/services/simulator.ts +386 -0
  155. package/src/types/node-fetch.d.ts +1 -0
  156. package/tests/actions/appeal.test.ts +141 -0
  157. package/tests/actions/call.test.ts +94 -0
  158. package/tests/actions/code.test.ts +87 -0
  159. package/tests/actions/create.test.ts +65 -0
  160. package/tests/actions/deploy.test.ts +420 -0
  161. package/tests/actions/getSetReset.test.ts +88 -0
  162. package/tests/actions/init.test.ts +483 -0
  163. package/tests/actions/lock.test.ts +86 -0
  164. package/tests/actions/new.test.ts +80 -0
  165. package/tests/actions/ollama.test.ts +193 -0
  166. package/tests/actions/receipt.test.ts +261 -0
  167. package/tests/actions/schema.test.ts +94 -0
  168. package/tests/actions/setNetwork.test.ts +161 -0
  169. package/tests/actions/staking.test.ts +280 -0
  170. package/tests/actions/start.test.ts +257 -0
  171. package/tests/actions/stop.test.ts +77 -0
  172. package/tests/actions/unlock.test.ts +139 -0
  173. package/tests/actions/validators.test.ts +750 -0
  174. package/tests/actions/write.test.ts +102 -0
  175. package/tests/commands/account.test.ts +146 -0
  176. package/tests/commands/appeal.test.ts +97 -0
  177. package/tests/commands/call.test.ts +78 -0
  178. package/tests/commands/code.test.ts +69 -0
  179. package/tests/commands/config.test.ts +54 -0
  180. package/tests/commands/deploy.test.ts +83 -0
  181. package/tests/commands/init.test.ts +101 -0
  182. package/tests/commands/localnet.test.ts +131 -0
  183. package/tests/commands/network.test.ts +60 -0
  184. package/tests/commands/new.test.ts +68 -0
  185. package/tests/commands/parseArg.test.ts +156 -0
  186. package/tests/commands/receipt.test.ts +142 -0
  187. package/tests/commands/schema.test.ts +67 -0
  188. package/tests/commands/staking.test.ts +329 -0
  189. package/tests/commands/stop.test.ts +27 -0
  190. package/tests/commands/up.test.ts +105 -0
  191. package/tests/commands/update.test.ts +45 -0
  192. package/tests/commands/write.test.ts +76 -0
  193. package/tests/index.test.ts +56 -0
  194. package/tests/libs/baseAction.test.ts +535 -0
  195. package/tests/libs/configFileManager.test.ts +118 -0
  196. package/tests/libs/jsonRpcClient.test.ts +59 -0
  197. package/tests/libs/keychainManager.test.ts +156 -0
  198. package/tests/libs/platformCommands.test.ts +78 -0
  199. package/tests/libs/system.test.ts +148 -0
  200. package/tests/services/simulator.test.ts +789 -0
  201. package/tests/smoke.test.ts +134 -0
  202. package/tests/utils.ts +13 -0
  203. package/tsconfig.json +120 -0
  204. package/vitest.config.ts +13 -0
  205. package/vitest.smoke.config.ts +17 -0
@@ -0,0 +1,22 @@
1
+ /* eslint-disable no-undef -- Allow process and console in ignored file */
2
+
3
+ import esbuild from "esbuild";
4
+
5
+ const isProduction = process.env.NODE_ENV === "production";
6
+ const config = isProduction
7
+ ? await import("./esbuild.config.prod.js")
8
+ : await import("./esbuild.config.dev.js");
9
+
10
+ const run = async () => {
11
+ if (config.default.watch) {
12
+ const context = await esbuild.context(config.default.context);
13
+ await context.watch();
14
+ } else {
15
+ await esbuild.build(config.default.context);
16
+ }
17
+ };
18
+
19
+ run().catch(e => {
20
+ console.error(e);
21
+ process.exit(1);
22
+ });
@@ -0,0 +1,17 @@
1
+ export default {
2
+ context: {
3
+ tsconfig: "./tsconfig.json",
4
+ entryPoints: ["src/index.ts"],
5
+ bundle: true,
6
+ outfile: "dist/index.js",
7
+ platform: "node",
8
+ target: "es2020",
9
+ format: "esm",
10
+ define: { "import.meta.url": "import.meta.url" },
11
+ banner: {
12
+ js: `const _importMetaUrl = new URL(import.meta.url).pathname;`,
13
+ },
14
+ external: ["commander", "dockerode", "dotenv", "ethers", "inquirer", "update-check", "ssh2", "fs-extra", "esbuild", "keytar"]
15
+ },
16
+ watch: false,
17
+ };
@@ -0,0 +1,60 @@
1
+ import js from "@eslint/js";
2
+ import tseslint from "@typescript-eslint/eslint-plugin";
3
+ import tsparser from "@typescript-eslint/parser";
4
+ import prettier from "eslint-config-prettier";
5
+ import importPlugin from "eslint-plugin-import";
6
+
7
+ export default [
8
+ js.configs.recommended,
9
+ prettier,
10
+ {
11
+ languageOptions: {
12
+ parser: tsparser,
13
+ parserOptions: {
14
+ project: ["./tsconfig.json"],
15
+ sourceType: "module",
16
+ },
17
+ },
18
+ ignores: [
19
+ "**/dist/**/*",
20
+ "esbuild.config.js",
21
+ "esbuild.config.dev.js",
22
+ "esbuild.config.prod.js",
23
+ "jest.config.js",
24
+ "eslint.config.js",
25
+ "Config.js",
26
+ "commitLint.config.ts",
27
+ "scripts/postinstall.js",
28
+ "templates/**/*"
29
+ ],
30
+ plugins: {
31
+ "@typescript-eslint": tseslint,
32
+ import: importPlugin,
33
+ },
34
+ rules: {
35
+ "import/namespace": "off",
36
+ "@typescript-eslint/no-empty-function": "off",
37
+ "@typescript-eslint/no-empty-interface": "off",
38
+ "no-constant-condition": "off",
39
+ "@typescript-eslint/no-explicit-any": "off",
40
+ "prefer-const": "off",
41
+ "@typescript-eslint/no-non-null-assertion": "off",
42
+ "@typescript-eslint/ban-ts-ignore": "off",
43
+ "@typescript-eslint/no-loss-of-precision": "off",
44
+ "@typescript-eslint/ban-types": "off",
45
+ "@typescript-eslint/ban-ts-comment": "off",
46
+ "@typescript-eslint/no-non-null-asserted-optional-chain": "off",
47
+ "@typescript-eslint/no-var-requires": "off",
48
+ "import/export": "off",
49
+ "no-fallthrough": "off",
50
+ "@typescript-eslint/explicit-module-boundary-types": "off",
51
+ "@typescript-eslint/no-floating-promises": ["error"],
52
+ "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
53
+ },
54
+ settings: {
55
+ "import/resolver": {
56
+ typescript: {},
57
+ },
58
+ },
59
+ },
60
+ ];
package/package.json CHANGED
@@ -1,21 +1,12 @@
1
1
  {
2
2
  "name": "genlayer",
3
- "version": "0.38.9",
3
+ "version": "0.38.10",
4
4
  "description": "GenLayer Command Line Tool",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "genlayer": "./dist/index.js"
9
9
  },
10
- "files": [
11
- "dist",
12
- "scripts",
13
- "templates",
14
- ".env.example",
15
- "docker-compose.yml",
16
- "README.md",
17
- "LICENSE"
18
- ],
19
10
  "scripts": {
20
11
  "test": "vitest",
21
12
  "test:watch": "vitest --watch",
@@ -76,7 +67,7 @@
76
67
  "dotenv": "^17.0.0",
77
68
  "ethers": "^6.13.4",
78
69
  "fs-extra": "^11.3.0",
79
- "genlayer-js": "^0.23.0",
70
+ "genlayer-js": "^0.27.9",
80
71
  "inquirer": "^12.0.0",
81
72
  "keytar": "^7.9.0",
82
73
  "node-fetch": "^3.0.0",
package/renovate.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": [
4
+ "config:recommended"
5
+ ],
6
+ "ignorePaths": [
7
+ "templates/**"
8
+ ],
9
+ "packageRules": [
10
+ {
11
+ "groupName": "all non-major dependencies",
12
+ "groupSlug": "all-minor-patch",
13
+ "matchPackageNames": [
14
+ "*"
15
+ ],
16
+ "matchUpdateTypes": [
17
+ "minor",
18
+ "patch"
19
+ ]
20
+ }
21
+ ]
22
+ }
@@ -17,9 +17,44 @@ function toSlug(text) {
17
17
  return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
18
18
  }
19
19
 
20
+ const COMMAND_GROUPS = {
21
+ 'init': 'environment',
22
+ 'up': 'environment',
23
+ 'stop': 'environment',
24
+ 'new': 'environment',
25
+ 'update': 'environment',
26
+ 'config': 'configuration',
27
+ 'network': 'configuration',
28
+ 'deploy': 'contracts',
29
+ 'call': 'contracts',
30
+ 'write': 'contracts',
31
+ 'schema': 'contracts',
32
+ 'code': 'contracts',
33
+ 'receipt': 'transactions',
34
+ 'trace': 'transactions',
35
+ 'appeal': 'transactions',
36
+ 'appeal-bond': 'transactions',
37
+ 'account': 'accounts',
38
+ 'staking': 'staking',
39
+ 'localnet': 'localnet',
40
+ };
41
+
20
42
  function makeCommandFilepath(commandPath) {
21
43
  const parts = commandPath.split(' ');
22
- if (parts.length === 1) return { relDir: '', filename: `${toSlug(parts[0])}.mdx` };
44
+ const topCommand = toSlug(parts[0]);
45
+ const group = COMMAND_GROUPS[topCommand];
46
+
47
+ if (parts.length === 1 && group) {
48
+ return { relDir: group, filename: `${topCommand}.mdx` };
49
+ }
50
+ if (parts.length === 1) {
51
+ return { relDir: '', filename: `${topCommand}.mdx` };
52
+ }
53
+ // Subcommands go under group/parent/sub.mdx
54
+ const parentGroup = COMMAND_GROUPS[topCommand];
55
+ if (parentGroup) {
56
+ return { relDir: [parentGroup, topCommand, ...parts.slice(1, -1)].map(toSlug).join('/'), filename: `${toSlug(parts[parts.length - 1])}.mdx` };
57
+ }
23
58
  return { relDir: parts.slice(0, -1).map(toSlug).join('/'), filename: `${toSlug(parts[parts.length - 1])}.mdx` };
24
59
  }
25
60
 
@@ -246,12 +281,40 @@ async function main() {
246
281
 
247
282
  const defaultOut = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'docs', 'api-references');
248
283
  const rootOut = outputDirFromEnv ? outputDirFromEnv : defaultOut;
249
- if (clean) await rmrf(rootOut);
284
+ await rmrf(rootOut);
250
285
  await writePages(rootOut, outputs);
251
286
 
252
- const meta = {};
253
- for (const c of (rootHelp.subcommands || []).map((c) => toSlug(c.name))) meta[c] = c;
254
- await fs.writeFile(path.join(rootOut, '_meta.json'), JSON.stringify(meta, null, 2), 'utf8');
287
+ // Write root _meta.json with groups
288
+ const GROUP_LABELS = {
289
+ 'environment': 'Environment',
290
+ 'configuration': 'Configuration',
291
+ 'contracts': 'Contracts',
292
+ 'transactions': 'Transactions',
293
+ 'accounts': 'Accounts',
294
+ 'staking': 'Staking',
295
+ 'localnet': 'Localnet',
296
+ };
297
+ const rootMeta = {};
298
+ for (const [key, label] of Object.entries(GROUP_LABELS)) {
299
+ rootMeta[key] = label;
300
+ }
301
+ await fs.writeFile(path.join(rootOut, '_meta.json'), JSON.stringify(rootMeta, null, 2), 'utf8');
302
+
303
+ // Write _meta.json for each group subdirectory
304
+ const groupCommands = {};
305
+ for (const [cmd, group] of Object.entries(COMMAND_GROUPS)) {
306
+ if (!groupCommands[group]) groupCommands[group] = [];
307
+ groupCommands[group].push(cmd);
308
+ }
309
+ for (const [group, cmds] of Object.entries(groupCommands)) {
310
+ const groupDir = path.join(rootOut, group);
311
+ await ensureDir(groupDir);
312
+ const groupMeta = {};
313
+ for (const cmd of cmds) {
314
+ groupMeta[toSlug(cmd)] = cmd;
315
+ }
316
+ await fs.writeFile(path.join(groupDir, '_meta.json'), JSON.stringify(groupMeta, null, 2), 'utf8');
317
+ }
255
318
 
256
319
  console.log(`Generated ${outputs.length} pages at ${rootOut}`);
257
320
  }
@@ -0,0 +1,30 @@
1
+ import {BaseAction} from "../../lib/actions/BaseAction";
2
+
3
+ export interface CreateAccountOptions {
4
+ name: string;
5
+ overwrite: boolean;
6
+ setActive?: boolean;
7
+ password?: string;
8
+ }
9
+
10
+ export class CreateAccountAction extends BaseAction {
11
+ constructor() {
12
+ super();
13
+ }
14
+
15
+ async execute(options: CreateAccountOptions): Promise<void> {
16
+ try {
17
+ this.startSpinner(`Creating account '${options.name}'...`);
18
+ await this.createKeypairByName(options.name, options.overwrite, options.password);
19
+
20
+ if (options.setActive !== false) {
21
+ this.setActiveAccount(options.name);
22
+ }
23
+
24
+ const keystorePath = this.getKeystorePath(options.name);
25
+ this.succeedSpinner(`Account '${options.name}' created at: ${keystorePath}`);
26
+ } catch (error) {
27
+ this.failSpinner("Failed to create account", error);
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,106 @@
1
+ import {BaseAction} from "../../lib/actions/BaseAction";
2
+ import {ethers} from "ethers";
3
+ import {writeFileSync, existsSync, readFileSync} from "fs";
4
+ import path from "path";
5
+
6
+ export interface ExportAccountOptions {
7
+ account?: string;
8
+ output: string;
9
+ password?: string;
10
+ sourcePassword?: string;
11
+ overwrite?: boolean;
12
+ }
13
+
14
+ export class ExportAccountAction extends BaseAction {
15
+ constructor() {
16
+ super();
17
+ }
18
+
19
+ async execute(options: ExportAccountOptions): Promise<void> {
20
+ try {
21
+ if (options.account) {
22
+ this.accountOverride = options.account;
23
+ }
24
+
25
+ const accountName = this.resolveAccountName();
26
+ const keystorePath = this.getKeystorePath(accountName);
27
+
28
+ if (!existsSync(keystorePath)) {
29
+ this.failSpinner(`Account '${accountName}' not found.`);
30
+ }
31
+
32
+ const outputPath = path.resolve(options.output);
33
+
34
+ if (existsSync(outputPath) && !options.overwrite) {
35
+ this.failSpinner(`Output file already exists: ${outputPath}`);
36
+ }
37
+
38
+ // Get the private key
39
+ const privateKey = await this.getPrivateKeyForExport(accountName, keystorePath, options.sourcePassword);
40
+
41
+ // Get password for the exported keystore
42
+ let password: string;
43
+ if (options.password) {
44
+ password = options.password;
45
+ } else {
46
+ password = await this.promptPassword("Enter password for exported keystore (minimum 8 characters):");
47
+ const confirmPassword = await this.promptPassword("Confirm password:");
48
+ if (password !== confirmPassword) {
49
+ this.failSpinner("Passwords do not match");
50
+ }
51
+ }
52
+
53
+ if (password.length < BaseAction.MIN_PASSWORD_LENGTH) {
54
+ this.failSpinner(`Password must be at least ${BaseAction.MIN_PASSWORD_LENGTH} characters long`);
55
+ }
56
+
57
+ this.startSpinner(`Exporting account '${accountName}'...`);
58
+
59
+ const wallet = new ethers.Wallet(privateKey);
60
+ const encryptedJson = await wallet.encrypt(password);
61
+
62
+ // Write standard web3 keystore format (compatible with geth, foundry, etc.)
63
+ writeFileSync(outputPath, encryptedJson);
64
+
65
+ this.succeedSpinner(`Account '${accountName}' exported to: ${outputPath}`);
66
+ this.logInfo(`Address: ${wallet.address}`);
67
+ } catch (error) {
68
+ this.failSpinner("Failed to export account", error);
69
+ }
70
+ }
71
+
72
+ private async getPrivateKeyForExport(
73
+ accountName: string,
74
+ keystorePath: string,
75
+ sourcePassword?: string
76
+ ): Promise<string> {
77
+ // First check if key is cached in keychain
78
+ const isAvailable = await this.keychainManager.isKeychainAvailable();
79
+ if (isAvailable) {
80
+ const cachedKey = await this.keychainManager.getPrivateKey(accountName);
81
+ if (cachedKey) {
82
+ return cachedKey;
83
+ }
84
+ }
85
+
86
+ // Need to decrypt the keystore
87
+ const fileContent = readFileSync(keystorePath, "utf-8");
88
+ const parsed = JSON.parse(fileContent);
89
+
90
+ const encryptedJson = parsed.encrypted || fileContent;
91
+
92
+ const password = sourcePassword || await this.promptPassword(`Enter password to unlock '${accountName}':`);
93
+
94
+ this.startSpinner("Decrypting keystore...");
95
+
96
+ try {
97
+ const wallet = await ethers.Wallet.fromEncryptedJson(encryptedJson, password);
98
+ this.stopSpinner();
99
+ return wallet.privateKey;
100
+ } catch {
101
+ this.failSpinner("Failed to decrypt keystore. Wrong password?");
102
+ }
103
+
104
+ throw new Error("Unreachable");
105
+ }
106
+ }
@@ -0,0 +1,135 @@
1
+ import {BaseAction} from "../../lib/actions/BaseAction";
2
+ import {ethers} from "ethers";
3
+ import {writeFileSync, existsSync, readFileSync} from "fs";
4
+
5
+ export interface ImportAccountOptions {
6
+ privateKey?: string;
7
+ keystore?: string;
8
+ name: string;
9
+ overwrite: boolean;
10
+ setActive?: boolean;
11
+ password?: string;
12
+ sourcePassword?: string;
13
+ }
14
+
15
+ export class ImportAccountAction extends BaseAction {
16
+ constructor() {
17
+ super();
18
+ }
19
+
20
+ async execute(options: ImportAccountOptions): Promise<void> {
21
+ try {
22
+ const keystorePath = this.getKeystorePath(options.name);
23
+
24
+ if (existsSync(keystorePath) && !options.overwrite) {
25
+ this.failSpinner(`Account '${options.name}' already exists. Use '--overwrite' to replace.`);
26
+ }
27
+
28
+ let privateKey: string;
29
+
30
+ if (options.keystore) {
31
+ privateKey = await this.importFromKeystore(options.keystore, options.sourcePassword);
32
+ } else if (options.privateKey) {
33
+ const normalizedKey = this.normalizePrivateKey(options.privateKey);
34
+ this.validatePrivateKey(normalizedKey);
35
+ privateKey = normalizedKey;
36
+ } else {
37
+ const inputKey = await this.promptPrivateKey();
38
+ const normalizedKey = this.normalizePrivateKey(inputKey);
39
+ this.validatePrivateKey(normalizedKey);
40
+ privateKey = normalizedKey;
41
+ }
42
+
43
+ const wallet = new ethers.Wallet(privateKey);
44
+
45
+ let password: string;
46
+ if (options.password) {
47
+ password = options.password;
48
+ } else {
49
+ password = await this.promptPassword("Enter a password to encrypt your keystore (minimum 8 characters):");
50
+ const confirmPassword = await this.promptPassword("Confirm password:");
51
+ if (password !== confirmPassword) {
52
+ this.failSpinner("Passwords do not match");
53
+ }
54
+ }
55
+
56
+ if (password.length < BaseAction.MIN_PASSWORD_LENGTH) {
57
+ this.failSpinner(`Password must be at least ${BaseAction.MIN_PASSWORD_LENGTH} characters long`);
58
+ }
59
+
60
+ this.startSpinner(`Importing account '${options.name}'...`);
61
+
62
+ const encryptedJson = await wallet.encrypt(password);
63
+
64
+ // Write standard web3 keystore format directly
65
+ writeFileSync(keystorePath, encryptedJson);
66
+
67
+ if (options.setActive !== false) {
68
+ this.setActiveAccount(options.name);
69
+ }
70
+
71
+ await this.keychainManager.removePrivateKey(options.name);
72
+
73
+ this.succeedSpinner(`Account '${options.name}' imported to: ${keystorePath}`);
74
+ this.logInfo(`Address: ${wallet.address}`);
75
+ } catch (error) {
76
+ this.failSpinner("Failed to import account", error);
77
+ }
78
+ }
79
+
80
+ private async importFromKeystore(keystorePath: string, sourcePassword?: string): Promise<string> {
81
+ if (!existsSync(keystorePath)) {
82
+ this.failSpinner(`Keystore file not found: ${keystorePath}`);
83
+ }
84
+
85
+ const fileContent = readFileSync(keystorePath, "utf-8");
86
+ let encryptedJson: string;
87
+
88
+ try {
89
+ const parsed = JSON.parse(fileContent);
90
+
91
+ // Check if it's our format (with 'encrypted' field) or standard web3 keystore
92
+ if (parsed.encrypted) {
93
+ // Our format
94
+ encryptedJson = parsed.encrypted;
95
+ } else if (parsed.crypto || parsed.Crypto) {
96
+ // Standard web3 keystore format (geth, foundry, etc.)
97
+ encryptedJson = fileContent;
98
+ } else {
99
+ this.failSpinner("Invalid keystore format. Expected encrypted keystore file.");
100
+ }
101
+ } catch {
102
+ this.failSpinner("Invalid keystore file. Could not parse JSON.");
103
+ }
104
+
105
+ const password = sourcePassword || await this.promptPassword("Enter password to decrypt keystore:");
106
+
107
+ this.startSpinner("Decrypting keystore...");
108
+
109
+ try {
110
+ const wallet = await ethers.Wallet.fromEncryptedJson(encryptedJson!, password);
111
+ this.stopSpinner();
112
+ return wallet.privateKey;
113
+ } catch {
114
+ this.failSpinner("Failed to decrypt keystore. Wrong password?");
115
+ }
116
+
117
+ // This line is unreachable but TypeScript needs it
118
+ throw new Error("Unreachable");
119
+ }
120
+
121
+ private async promptPrivateKey(): Promise<string> {
122
+ return this.promptPassword("Enter private key to import:");
123
+ }
124
+
125
+ private normalizePrivateKey(key: string): string {
126
+ const trimmed = key.trim();
127
+ return trimmed.startsWith("0x") ? trimmed : `0x${trimmed}`;
128
+ }
129
+
130
+ private validatePrivateKey(key: string): void {
131
+ if (!/^0x[0-9a-fA-F]{64}$/.test(key)) {
132
+ this.failSpinner("Invalid private key format. Expected 64 hex characters (with or without 0x prefix).");
133
+ }
134
+ }
135
+ }
@@ -0,0 +1,129 @@
1
+ import {Command} from "commander";
2
+ import {ShowAccountAction, ShowAccountOptions} from "./show";
3
+ import {CreateAccountAction, CreateAccountOptions} from "./create";
4
+ import {ImportAccountAction, ImportAccountOptions} from "./import";
5
+ import {ExportAccountAction, ExportAccountOptions} from "./export";
6
+ import {UnlockAccountAction, UnlockAccountOptions} from "./unlock";
7
+ import {LockAccountAction, LockAccountOptions} from "./lock";
8
+ import {SendAction, SendOptions} from "./send";
9
+ import {ListAccountsAction} from "./list";
10
+ import {UseAccountAction} from "./use";
11
+ import {RemoveAccountAction} from "./remove";
12
+
13
+ export function initializeAccountCommands(program: Command) {
14
+ const accountCommand = program
15
+ .command("account")
16
+ .description("Manage your accounts (address, balance, keys)")
17
+ .action(async () => {
18
+ // Default action: show account info (use 'account show' for options)
19
+ const showAction = new ShowAccountAction();
20
+ await showAction.execute({});
21
+ });
22
+
23
+ accountCommand
24
+ .command("list")
25
+ .description("List all accounts")
26
+ .action(async () => {
27
+ const listAction = new ListAccountsAction();
28
+ await listAction.execute();
29
+ });
30
+
31
+ accountCommand
32
+ .command("show")
33
+ .description("Show account details (address, balance)")
34
+ .option("--rpc <rpcUrl>", "RPC URL for the network")
35
+ .option("--account <name>", "Account to show")
36
+ .action(async (options: ShowAccountOptions) => {
37
+ const showAction = new ShowAccountAction();
38
+ await showAction.execute(options);
39
+ });
40
+
41
+ accountCommand
42
+ .command("create")
43
+ .description("Create a new account with encrypted keystore")
44
+ .requiredOption("--name <name>", "Name for the account")
45
+ .option("--password <password>", "Password for the keystore (skips interactive prompt)")
46
+ .option("--overwrite", "Overwrite existing account", false)
47
+ .option("--no-set-active", "Do not set as active account")
48
+ .action(async (options: CreateAccountOptions) => {
49
+ const createAction = new CreateAccountAction();
50
+ await createAction.execute(options);
51
+ });
52
+
53
+ accountCommand
54
+ .command("import")
55
+ .description("Import an account from a private key or keystore file")
56
+ .requiredOption("--name <name>", "Name for the account")
57
+ .option("--private-key <key>", "Private key to import")
58
+ .option("--keystore <path>", "Path to keystore file to import (geth, foundry, etc.)")
59
+ .option("--password <password>", "Password for the new keystore (skips confirmation prompt)")
60
+ .option("--source-password <password>", "Password to decrypt source keystore (with --keystore)")
61
+ .option("--overwrite", "Overwrite existing account", false)
62
+ .option("--no-set-active", "Do not set as active account")
63
+ .action(async (options: ImportAccountOptions) => {
64
+ const importAction = new ImportAccountAction();
65
+ await importAction.execute(options);
66
+ });
67
+
68
+ accountCommand
69
+ .command("export")
70
+ .description("Export an account to a keystore file (web3/geth/foundry compatible)")
71
+ .requiredOption("--output <path>", "Output path for the keystore file")
72
+ .option("--account <name>", "Account to export (defaults to active account)")
73
+ .option("--password <password>", "Password for exported keystore (skips confirmation)")
74
+ .option("--source-password <password>", "Password to decrypt account (if not unlocked)")
75
+ .action(async (options: ExportAccountOptions) => {
76
+ const exportAction = new ExportAccountAction();
77
+ await exportAction.execute(options);
78
+ });
79
+
80
+ accountCommand
81
+ .command("use <name>")
82
+ .description("Set the active account")
83
+ .action(async (name: string) => {
84
+ const useAction = new UseAccountAction();
85
+ await useAction.execute(name);
86
+ });
87
+
88
+ accountCommand
89
+ .command("remove <name>")
90
+ .description("Remove an account")
91
+ .option("--force", "Skip confirmation prompt", false)
92
+ .action(async (name: string, options: {force?: boolean}) => {
93
+ const removeAction = new RemoveAccountAction();
94
+ await removeAction.execute(name, options);
95
+ });
96
+
97
+ accountCommand
98
+ .command("send <to> <amount>")
99
+ .description("Send GEN to an address")
100
+ .option("--rpc <rpcUrl>", "RPC URL for the network")
101
+ .option("--network <network>", "Network to use (localnet, testnet-asimov)")
102
+ .option("--account <name>", "Account to send from")
103
+ .option("--password <password>", "Password to unlock account (skips interactive prompt)")
104
+ .action(async (to: string, amount: string, options: {rpc?: string; network?: string; account?: string; password?: string}) => {
105
+ const sendAction = new SendAction();
106
+ await sendAction.execute({to, amount, rpc: options.rpc, network: options.network, account: options.account, password: options.password});
107
+ });
108
+
109
+ accountCommand
110
+ .command("unlock")
111
+ .description("Unlock account by caching private key in OS keychain")
112
+ .option("--account <name>", "Account to unlock")
113
+ .option("--password <password>", "Password to unlock account (skips interactive prompt)")
114
+ .action(async (options: UnlockAccountOptions) => {
115
+ const unlockAction = new UnlockAccountAction();
116
+ await unlockAction.execute(options);
117
+ });
118
+
119
+ accountCommand
120
+ .command("lock")
121
+ .description("Lock account by removing private key from OS keychain")
122
+ .option("--account <name>", "Account to lock")
123
+ .action(async (options: LockAccountOptions) => {
124
+ const lockAction = new LockAccountAction();
125
+ await lockAction.execute(options);
126
+ });
127
+
128
+ return program;
129
+ }
@@ -0,0 +1,34 @@
1
+ import {BaseAction} from "../../lib/actions/BaseAction";
2
+
3
+ export class ListAccountsAction extends BaseAction {
4
+ constructor() {
5
+ super();
6
+ }
7
+
8
+ async execute(): Promise<void> {
9
+ try {
10
+ const accounts = this.listAccounts();
11
+ const activeAccount = this.getActiveAccount();
12
+ const unlockedAccounts = await this.keychainManager.listUnlockedAccounts();
13
+
14
+ if (accounts.length === 0) {
15
+ this.logInfo("No accounts found. Run 'genlayer account create --name <name>' to create one.");
16
+ return;
17
+ }
18
+
19
+ console.log("");
20
+ for (const account of accounts) {
21
+ const isActive = account.name === activeAccount;
22
+ const isUnlocked = unlockedAccounts.includes(account.name);
23
+ const marker = isActive ? "*" : " ";
24
+ const status = isUnlocked ? "(unlocked)" : "";
25
+ const activeLabel = isActive ? "(active)" : "";
26
+
27
+ console.log(`${marker} ${account.name.padEnd(16)} ${account.address} ${activeLabel} ${status}`.trim());
28
+ }
29
+ console.log("");
30
+ } catch (error) {
31
+ this.failSpinner("Failed to list accounts", error);
32
+ }
33
+ }
34
+ }