genlayer 0.12.4 → 0.13.0

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 (73) hide show
  1. package/.env.example +4 -0
  2. package/CHANGELOG.md +8 -0
  3. package/dist/index.js +17970 -17628
  4. package/esbuild.config.dev.js +1 -2
  5. package/esbuild.config.prod.js +1 -1
  6. package/eslint.config.js +2 -1
  7. package/package.json +5 -3
  8. package/src/commands/contracts/call.ts +23 -33
  9. package/src/commands/contracts/deploy.ts +109 -33
  10. package/src/commands/contracts/index.ts +14 -3
  11. package/src/commands/general/init.ts +0 -1
  12. package/src/commands/keygen/create.ts +5 -25
  13. package/src/commands/scaffold/index.ts +16 -0
  14. package/src/commands/scaffold/new.ts +34 -0
  15. package/src/index.ts +2 -0
  16. package/src/lib/accounts/KeypairManager.ts +43 -0
  17. package/src/lib/actions/BaseAction.ts +34 -7
  18. package/src/lib/config/simulator.ts +2 -2
  19. package/templates/default/LICENSE +21 -0
  20. package/templates/default/README.md +101 -0
  21. package/templates/default/__init__.py +0 -0
  22. package/templates/default/app/.env.example +2 -0
  23. package/templates/default/app/.vscode/extensions.json +3 -0
  24. package/templates/default/app/README.md +5 -0
  25. package/templates/default/app/index.html +17 -0
  26. package/templates/default/app/package-lock.json +4920 -0
  27. package/templates/default/app/package.json +23 -0
  28. package/templates/default/app/postcss.config.js +6 -0
  29. package/templates/default/app/public/favicon.png +0 -0
  30. package/templates/default/app/src/App.vue +16 -0
  31. package/templates/default/app/src/components/Address.vue +38 -0
  32. package/templates/default/app/src/components/BetsScreen.vue +329 -0
  33. package/templates/default/app/src/logic/FootballBets.js +100 -0
  34. package/templates/default/app/src/main.js +5 -0
  35. package/templates/default/app/src/services/genlayer.js +19 -0
  36. package/templates/default/app/src/style.css +3 -0
  37. package/templates/default/app/tailwind.config.js +8 -0
  38. package/templates/default/app/vite.config.js +7 -0
  39. package/templates/default/config/__init__.py +0 -0
  40. package/templates/default/config/genlayer_config.py +14 -0
  41. package/templates/default/contracts/__init__.py +0 -0
  42. package/templates/default/contracts/football_bets.py +119 -0
  43. package/templates/default/deploy/deployScript.ts +31 -0
  44. package/templates/default/package-lock.json +3231 -0
  45. package/templates/default/package.json +7 -0
  46. package/templates/default/requirements.txt +6 -0
  47. package/templates/default/test/__init__.py +0 -0
  48. package/templates/default/test/football_bets_get_contract_schema_for_code.py +124 -0
  49. package/templates/default/test/test_football_bet_success_draw.py +108 -0
  50. package/templates/default/test/test_football_bet_success_win.py +106 -0
  51. package/templates/default/test/test_football_bet_unsuccess.py +107 -0
  52. package/templates/default/tools/__init__.py +0 -0
  53. package/templates/default/tools/accounts.py +5 -0
  54. package/templates/default/tools/calldata.py +224 -0
  55. package/templates/default/tools/request.py +134 -0
  56. package/templates/default/tools/response.py +52 -0
  57. package/templates/default/tools/structure.py +39 -0
  58. package/templates/default/tools/transactions.py +28 -0
  59. package/templates/default/tools/types.py +214 -0
  60. package/templates/default/tsconfig.json +7 -0
  61. package/tests/actions/call.test.ts +39 -79
  62. package/tests/actions/create.test.ts +11 -72
  63. package/tests/actions/deploy.test.ts +201 -33
  64. package/tests/actions/new.test.ts +80 -0
  65. package/tests/commands/call.test.ts +6 -1
  66. package/tests/commands/deploy.test.ts +12 -1
  67. package/tests/commands/new.test.ts +68 -0
  68. package/tests/index.test.ts +4 -0
  69. package/tests/libs/accounts/KeypairManager.test.ts +110 -0
  70. package/tests/libs/baseAction.test.ts +40 -0
  71. package/vitest.config.ts +1 -1
  72. package/src/lib/accounts/getPrivateKey.ts +0 -21
  73. package/tests/libs/getPrivateKey.test.ts +0 -96
@@ -11,8 +11,7 @@ export default {
11
11
  banner: {
12
12
  js: `const _importMetaUrl = new URL(import.meta.url).pathname;`,
13
13
  },
14
- external: ["commander", "dockerode", "dotenv", "ethers", "inquirer", "update-check", "ssh2"]
15
-
14
+ external: ["commander", "dockerode", "dotenv", "ethers", "inquirer", "update-check", "ssh2", "fs-extra", "esbuild"]
16
15
  },
17
16
  watch: true,
18
17
  };
@@ -11,7 +11,7 @@ export default {
11
11
  banner: {
12
12
  js: `const _importMetaUrl = new URL(import.meta.url).pathname;`,
13
13
  },
14
- external: ["commander", "dockerode", "dotenv", "ethers", "inquirer", "update-check", "ssh2"]
14
+ external: ["commander", "dockerode", "dotenv", "ethers", "inquirer", "update-check", "ssh2", "fs-extra", "esbuild"]
15
15
  },
16
16
  watch: false,
17
17
  };
package/eslint.config.js CHANGED
@@ -24,7 +24,8 @@ export default [
24
24
  "eslint.config.js",
25
25
  "Config.js",
26
26
  "commitLint.config.ts",
27
- "scripts/postinstall.js"
27
+ "scripts/postinstall.js",
28
+ "templates/**/*"
28
29
  ],
29
30
  plugins: {
30
31
  "@typescript-eslint": tseslint,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genlayer",
3
- "version": "0.12.4",
3
+ "version": "0.13.0",
4
4
  "description": "GenLayer Command Line Tool",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -37,6 +37,7 @@
37
37
  "devDependencies": {
38
38
  "@release-it/conventional-changelog": "^10.0.0",
39
39
  "@types/dockerode": "^3.3.31",
40
+ "@types/fs-extra": "^11.0.4",
40
41
  "@types/inquirer": "^9.0.7",
41
42
  "@types/node": "^22.0.0",
42
43
  "@types/sinon": "^17.0.3",
@@ -45,7 +46,7 @@
45
46
  "@typescript-eslint/parser": "^8.0.0",
46
47
  "@vitest/coverage-v8": "^2.1.4",
47
48
  "cross-env": "^7.0.3",
48
- "esbuild": "^0.25.0",
49
+ "esbuild": ">=0.25.0",
49
50
  "eslint": "^9.0.0",
50
51
  "eslint-config-prettier": "^10.0.0",
51
52
  "eslint-import-resolver-typescript": "^3.6.1",
@@ -62,7 +63,8 @@
62
63
  "dockerode": "^4.0.2",
63
64
  "dotenv": "^16.4.5",
64
65
  "ethers": "^6.13.4",
65
- "genlayer-js": "^0.6.0",
66
+ "fs-extra": "^11.3.0",
67
+ "genlayer-js": "^0.9.0",
66
68
  "inquirer": "^12.0.0",
67
69
  "node-fetch": "^3.0.0",
68
70
  "open": "^10.1.0",
@@ -1,21 +1,14 @@
1
- import { createClient, createAccount } from "genlayer-js";
2
1
  import { simulator } from "genlayer-js/chains";
3
2
  import type { GenLayerClient } from "genlayer-js/types";
4
- import { getPrivateKey } from "../../lib/accounts/getPrivateKey";
3
+ import { BaseAction } from "../../lib/actions/BaseAction";
5
4
 
6
5
  export interface CallOptions {
7
6
  args: any[];
8
7
  }
9
8
 
10
- export class CallAction {
11
- private genlayerClient: GenLayerClient<typeof simulator>;
12
-
9
+ export class CallAction extends BaseAction{
13
10
  constructor() {
14
- this.genlayerClient = createClient({
15
- chain: simulator,
16
- endpoint: process.env.VITE_JSON_RPC_SERVER_URL,
17
- account: createAccount(getPrivateKey() as any),
18
- });
11
+ super();
19
12
  }
20
13
 
21
14
  async call({
@@ -27,61 +20,58 @@ export class CallAction {
27
20
  method: string;
28
21
  args: any[];
29
22
  }): Promise<void> {
30
- console.log(`Calling method ${method} on contract at ${contractAddress}...`);
23
+ const client = await this.getClient();
24
+ this.startSpinner(`Calling method ${method} on contract at ${contractAddress}...`);
31
25
 
32
- const contractSchema = await this.genlayerClient.getContractSchema(contractAddress);
26
+ const contractSchema = await client.getContractSchema(contractAddress);
33
27
 
34
28
  if(!contractSchema.methods.hasOwnProperty(method)){
35
- console.error(`method ${method} not found.`);
36
- process.exit(1);
29
+ this.failSpinner(`method ${method} not found.`);
30
+ return
37
31
  }
38
32
 
39
33
  const readonly = contractSchema.methods[method as any].readonly;
40
34
 
41
- try {
42
- if (readonly) {
43
- await this.executeRead(contractAddress, method, args);
44
- } else {
45
- await this.executeWrite(contractAddress, method, args);
46
- }
47
- } catch (error) {
48
- console.error("Error calling contract method:", error);
49
- throw error;
35
+ if (readonly) {
36
+ await this.executeRead(contractAddress, method, args);
37
+ return
50
38
  }
39
+
40
+ await this.executeWrite(contractAddress, method, args);
51
41
  }
52
42
 
53
43
  private async executeRead(contractAddress: string, method: string, args: any[]): Promise<void> {
54
44
  try {
55
- const result = await this.genlayerClient.readContract({
45
+ const client = await this.getClient();
46
+ const result = await client.readContract({
56
47
  address: contractAddress as any,
57
48
  functionName: method,
58
49
  args,
59
50
  });
60
- console.log("Read result:", result);
51
+ this.succeedSpinner("Read operation successfully executed", result);
61
52
  } catch (error) {
62
- console.error("Error during read operation:", error);
63
- throw error;
53
+ this.failSpinner("Error during read operation", error);
64
54
  }
65
55
  }
66
56
 
67
57
  private async executeWrite(contractAddress: string, method: string, args: any[]): Promise<void> {
68
58
  try {
69
- const hash = await this.genlayerClient.writeContract({
59
+ const client = await this.getClient();
60
+ const hash = await client.writeContract({
70
61
  address: contractAddress as any,
71
62
  functionName: method,
72
63
  args,
73
64
  value: 0n,
74
65
  });
75
- const result = await this.genlayerClient.waitForTransactionReceipt({
66
+ const result = await client.waitForTransactionReceipt({
76
67
  hash,
77
68
  retries: 15,
78
69
  interval: 2000,
79
70
  });
80
- console.log("Write transaction hash:", hash);
81
- console.log("Result:", result);
71
+ this.log("Write transaction hash:", hash);
72
+ this.succeedSpinner("Write operation successfully executed", result);
82
73
  } catch (error) {
83
- console.error("Error during write operation:", error);
84
- throw error;
74
+ this.failSpinner("Error during write operation", error);
85
75
  }
86
76
  }
87
77
  }
@@ -1,25 +1,22 @@
1
1
  import fs from "fs";
2
- import { createClient, createAccount } from "genlayer-js";
2
+ import path from "path";
3
3
  import { simulator } from "genlayer-js/chains";
4
4
  import type { GenLayerClient } from "genlayer-js/types";
5
- import { getPrivateKey } from "../../lib/accounts/getPrivateKey";
5
+ import { BaseAction } from "../../lib/actions/BaseAction";
6
+ import { pathToFileURL } from "url";
7
+ import { TransactionStatus } from "genlayer-js/types";
8
+ import { buildSync } from "esbuild";
6
9
 
7
10
  export interface DeployOptions {
8
11
  contract?: string;
9
- // network: string;
10
12
  args?: any[];
11
- kwargs?: string;
12
13
  }
13
14
 
14
- export class DeployAction {
15
- private genlayerClient: GenLayerClient<typeof simulator>;
15
+ export class DeployAction extends BaseAction {
16
+ private readonly deployDir = path.resolve(process.cwd(), "deploy");
16
17
 
17
18
  constructor() {
18
- this.genlayerClient = createClient({
19
- chain: simulator,
20
- endpoint: process.env.VITE_JSON_RPC_SERVER_URL,
21
- account: createAccount(getPrivateKey() as any),
22
- });
19
+ super();
23
20
  }
24
21
 
25
22
  private readContractCode(contractPath: string): string {
@@ -29,44 +26,123 @@ export class DeployAction {
29
26
  return fs.readFileSync(contractPath, "utf-8");
30
27
  }
31
28
 
32
- async deploy(options: DeployOptions): Promise<void> {
33
-
34
- const argsUsed = options.args && options.args.length > 0;
35
- const kwargsUsed = options.kwargs && options.kwargs.trim() !== "";
29
+ private async executeTsScript(filePath: string): Promise<void> {
30
+ const outFile = filePath.replace(/\.ts$/, ".compiled.js");
31
+ this.startSpinner(`Transpiling TypeScript file: ${filePath}`);
32
+ try {
33
+ buildSync({
34
+ entryPoints: [filePath],
35
+ outfile: outFile,
36
+ bundle: false,
37
+ platform: "node",
38
+ format: "esm",
39
+ target: "es2020",
40
+ sourcemap: false,
41
+ });
42
+ await this.executeJsScript(filePath, outFile);
43
+ } catch (error) {
44
+ this.failSpinner(`Error executing: ${filePath}`, error);
45
+ } finally {
46
+ fs.unlinkSync(outFile);
47
+ }
48
+ }
36
49
 
37
- if (argsUsed && kwargsUsed) {
38
- throw new Error("Invalid usage: Please specify either `args` or `kwargs`, but not both.");
50
+ private async executeJsScript(filePath: string, transpiledFilePath?: string): Promise<void> {
51
+ this.startSpinner(`Executing file: ${filePath}`);
52
+ try {
53
+ const module = await import(pathToFileURL(transpiledFilePath || filePath).href);
54
+ if (!module.default || typeof module.default !== "function") {
55
+ this.failSpinner(`No "default" function found in: ${filePath}`);
56
+ return
57
+ }
58
+ const client = await this.getClient();
59
+ await module.default(client);
60
+ this.succeedSpinner(`Successfully executed: ${filePath}`);
61
+ } catch (error) {
62
+ this.failSpinner(`Error executing: ${filePath}`, error);
39
63
  }
64
+ }
40
65
 
41
- if (!options.contract) {
42
- console.error("No contract specified for deployment.");
66
+ async deployScripts() {
67
+ this.startSpinner("Searching for deploy scripts...");
68
+ if (!fs.existsSync(this.deployDir)) {
69
+ this.failSpinner("No deploy folder found.");
43
70
  return;
44
71
  }
72
+ const files = fs.readdirSync(this.deployDir)
73
+ .filter(file => file.endsWith(".ts") || file.endsWith(".js"))
74
+ .sort((a, b) => {
75
+ const numA = parseInt(a.split("_")[0]);
76
+ const numB = parseInt(b.split("_")[0]);
45
77
 
46
- const contractCode = this.readContractCode(options.contract);
78
+ if (!isNaN(numA) && !isNaN(numB)) return numA - numB;
79
+ if (!isNaN(numA)) return -1;
80
+ if (!isNaN(numB)) return 1;
81
+ return a.localeCompare(b);
82
+ });
47
83
 
48
- if (!contractCode) {
49
- console.error("Contract code is empty.");
84
+ if (files.length === 0) {
85
+ this.failSpinner("No deploy scripts found.");
50
86
  return;
51
87
  }
52
88
 
53
- const leaderOnly = false;
54
- let deployParams: any = { code: contractCode, args: options.args, leaderOnly };
89
+ this.setSpinnerText(`Found ${files.length} deploy scripts. Executing...`);
55
90
 
56
- console.log("Starting contract deployment...");
57
- console.log("Deployment Parameters:", deployParams);
91
+ for (const file of files) {
92
+ const filePath = path.resolve(this.deployDir, file);
93
+ this.setSpinnerText(`Executing script: ${filePath}`);
94
+ try {
95
+ if (file.endsWith(".ts")) {
96
+ await this.executeTsScript(filePath);
97
+ } else {
98
+ await this.executeJsScript(filePath);
99
+ }
100
+ } catch (error) {
101
+ this.failSpinner(`Error executing script: ${filePath}`, error);
102
+ }
103
+ }
104
+ }
58
105
 
106
+ async deploy(options: DeployOptions): Promise<void> {
59
107
  try {
60
- const hash = await this.genlayerClient.deployContract(deployParams) as any;
108
+ const client = await this.getClient();
109
+ this.startSpinner("Setting up the deployment environment...");
110
+ await client.initializeConsensusSmartContract();
111
+
112
+ if (!options.contract) {
113
+ this.failSpinner("No contract specified for deployment.");
114
+ return;
115
+ }
116
+ this.setSpinnerText("Reading contract code...");
117
+ const contractCode = this.readContractCode(options.contract);
118
+
119
+ if (!contractCode) {
120
+ this.failSpinner("Contract code is empty.");
121
+ return;
122
+ }
123
+
124
+ const leaderOnly = false;
125
+ const deployParams: any = { code: contractCode, args: options.args, leaderOnly };
126
+
127
+ this.setSpinnerText("Starting contract deployment...");
128
+ this.log("Deployment Parameters:", deployParams);
129
+
130
+ const hash = (await client.deployContract(deployParams)) as any;
131
+ const result = await client.waitForTransactionReceipt({
132
+ hash,
133
+ retries: 15,
134
+ interval: 2000,
135
+ status: TransactionStatus.ACCEPTED,
136
+ });
61
137
 
62
- const result = await this.genlayerClient.waitForTransactionReceipt({hash, retries: 15, interval: 2000})
138
+ this.log("Deployment Receipt:", result);
63
139
 
64
- console.log("Contract deployed successfully.");
65
- console.log("Transaction Hash:", hash);
66
- console.log("Contract Address:", result.data?.contract_address);
140
+ this.succeedSpinner("Contract deployed successfully.", {
141
+ "Transaction Hash": hash,
142
+ "Contract Address": result.data?.contract_address,
143
+ });
67
144
  } catch (error) {
68
- console.error("Error deploying contract:", error);
69
- throw new Error("Contract deployment failed.");
145
+ this.failSpinner("Error deploying contract", error);
70
146
  }
71
147
  }
72
148
  }
@@ -2,6 +2,13 @@ import { Command } from "commander";
2
2
  import { DeployAction, DeployOptions } from "./deploy";
3
3
  import { CallAction, CallOptions } from "./call";
4
4
 
5
+ function parseArg(value: string, previous: any[] = []): any[] {
6
+ if (value === "true") return [...previous, true];
7
+ if (value === "false") return [...previous, false];
8
+ if (!isNaN(Number(value))) return [...previous, Number(value)];
9
+ return [...previous, value];
10
+ }
11
+
5
12
  export function initializeContractsCommands(program: Command) {
6
13
 
7
14
  program
@@ -9,16 +16,20 @@ export function initializeContractsCommands(program: Command) {
9
16
  .description("Deploy intelligent contracts")
10
17
  .option("--contract <contractPath>", "Path to the smart contract to deploy")
11
18
  // .option("--network <networkName>", "Specify the network (e.g., testnet)", "localnet")
12
- .option("--args <args...>", "Positional arguments for the contract (space-separated, use quotes for multi-word arguments)", [])
19
+ .option("--args <args...>", "Positional arguments for the contract (space-separated, use quotes for multi-word arguments)", parseArg, [])
13
20
  .action(async (options: DeployOptions) => {
14
21
  const deployer = new DeployAction();
15
- await deployer.deploy(options);
22
+ if(options.contract){
23
+ await deployer.deploy(options);
24
+ }else {
25
+ await deployer.deployScripts();
26
+ }
16
27
  });
17
28
 
18
29
  program
19
30
  .command("call <contractAddress> <method>")
20
31
  .description("Call a contract method")
21
- .option("--args <args...>", "Positional arguments for the method (space-separated, use quotes for multi-word arguments)", [])
32
+ .option("--args <args...>", "Positional arguments for the method (space-separated, use quotes for multi-word arguments)", parseArg, [])
22
33
  .action(async (contractAddress: string, method: string, options: CallOptions) => {
23
34
  const caller = new CallAction();
24
35
  await caller.call({ contractAddress, method, ...options });
@@ -94,7 +94,6 @@ export class InitAction extends BaseAction {
94
94
  const selectedLlmProviders = llmProvidersAnswer.selectedLlmProviders as AiProviders[];
95
95
 
96
96
  let defaultOllamaModel = this.getConfig().defaultOllamaModel;
97
- AI_PROVIDERS_CONFIG.ollama.hint = `(This will download and run a local instance of ${defaultOllamaModel})`;
98
97
  const aiProvidersEnvVars: Record<string, string> = {};
99
98
  const configurableAiProviders = selectedLlmProviders.filter(
100
99
  (provider: AiProviders) => AI_PROVIDERS_CONFIG[provider].envVar
@@ -1,5 +1,3 @@
1
- import { writeFileSync, existsSync } from "fs";
2
- import { ethers } from "ethers";
3
1
  import { BaseAction } from "../../lib/actions/BaseAction";
4
2
 
5
3
  export interface CreateKeypairOptions {
@@ -7,36 +5,18 @@ export interface CreateKeypairOptions {
7
5
  overwrite: boolean;
8
6
  }
9
7
 
10
- export class KeypairCreator extends BaseAction{
11
-
8
+ export class KeypairCreator extends BaseAction {
12
9
  constructor() {
13
- super()
10
+ super();
14
11
  }
15
12
 
16
13
  createKeypairAction(options: CreateKeypairOptions) {
17
14
  try {
18
15
  this.startSpinner(`Creating keypair...`);
19
- const outputPath = this.getFilePath(options.output);
20
-
21
- if(existsSync(outputPath) && !options.overwrite) {
22
- this.failSpinner(
23
- `The file at ${outputPath} already exists. Use the '--overwrite' option to replace it.`
24
- );
25
- return;
26
- }
27
-
28
- const wallet = ethers.Wallet.createRandom();
29
- const keypairData = {
30
- address: wallet.address,
31
- privateKey: wallet.privateKey,
32
- };
33
-
34
- writeFileSync(outputPath, JSON.stringify(keypairData, null, 2));
35
-
36
- this.writeConfig('keyPairPath', outputPath);
37
- this.succeedSpinner(`Keypair successfully created and saved to: ${outputPath}`);
16
+ this.keypairManager.createKeypair(options.output, options.overwrite);
17
+ this.succeedSpinner(`Keypair successfully created and saved to: ${options.output}`);
38
18
  } catch (error) {
39
- this.failSpinner("Failed to generate keypair:", error);
19
+ this.failSpinner("Failed to generate keypair", error);
40
20
  }
41
21
  }
42
22
  }
@@ -0,0 +1,16 @@
1
+ import { Command } from "commander";
2
+ import { NewAction } from "./new";
3
+
4
+ export function initializeScaffoldCommands(program: Command) {
5
+ program
6
+ .command("new <projectName>")
7
+ .description("Create a new GenLayer project using the default template")
8
+ .option("--path <directory>", "Specify the directory for the new project", ".")
9
+ .option("--overwrite", "Overwrite existing directory if it exists", false)
10
+ .action(async (projectName, options) => {
11
+ const newProjectAction = new NewAction();
12
+ await newProjectAction.createProject(projectName, options);
13
+ });
14
+
15
+ return program;
16
+ }
@@ -0,0 +1,34 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { BaseAction } from "../../lib/actions/BaseAction";
5
+
6
+ export class NewAction extends BaseAction {
7
+ private templatePath: string;
8
+
9
+ constructor() {
10
+ super();
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const basePath = path.resolve(path.dirname(__filename), "..");
13
+ this.templatePath = path.join(basePath, "templates", "default");
14
+ }
15
+
16
+ async createProject(projectName: string, options: { path: string; overwrite: boolean }) {
17
+ const targetPath = path.resolve(options.path, projectName);
18
+
19
+ if (fs.existsSync(targetPath) && !options.overwrite) {
20
+ return this.failSpinner(
21
+ `Project directory "${targetPath}" already exists. Use --overwrite to replace it.`
22
+ );
23
+ }
24
+
25
+ this.startSpinner(`Creating new GenLayer project: ${projectName}`);
26
+
27
+ try {
28
+ fs.copySync(this.templatePath, targetPath);
29
+ this.succeedSpinner(`Project "${projectName}" created successfully at ${targetPath}`);
30
+ } catch (error) {
31
+ this.failSpinner(`Error creating project "${projectName}"`, error);
32
+ }
33
+ }
34
+ }
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ import { initializeContractsCommands } from "../src/commands/contracts";
8
8
  import { initializeConfigCommands } from "../src/commands/config";
9
9
  import {initializeValidatorCommands} from "../src/commands/validators";
10
10
  import { initializeUpdateCommands } from "../src/commands/update";
11
+ import { initializeScaffoldCommands } from "../src/commands/scaffold";
11
12
 
12
13
  export function initializeCLI() {
13
14
  program.version(version).description(CLI_DESCRIPTION);
@@ -17,6 +18,7 @@ export function initializeCLI() {
17
18
  initializeConfigCommands(program);
18
19
  initializeUpdateCommands(program)
19
20
  initializeValidatorCommands(program);
21
+ initializeScaffoldCommands(program);
20
22
  program.parse(process.argv);
21
23
  }
22
24
 
@@ -0,0 +1,43 @@
1
+ import { writeFileSync, existsSync, readFileSync } from "fs";
2
+ import { ethers } from "ethers";
3
+ import path from "path";
4
+ import { ConfigFileManager } from "../config/ConfigFileManager";
5
+
6
+ export class KeypairManager extends ConfigFileManager {
7
+ constructor() {
8
+ super();
9
+ }
10
+
11
+ getPrivateKey(): string | undefined {
12
+ const keypairPath = this.getConfigByKey("keyPairPath");
13
+
14
+ if (!keypairPath || !existsSync(keypairPath)) {
15
+ return ""
16
+ }
17
+
18
+ const keypairData = JSON.parse(readFileSync(keypairPath, "utf-8"));
19
+
20
+ if (!keypairData.privateKey) {
21
+ return ""
22
+ }
23
+
24
+ return keypairData.privateKey;
25
+ }
26
+
27
+ createKeypair(outputPath = "./keypair.json", overwrite: boolean = false): void {
28
+ const finalOutputPath = this.getFilePath(outputPath);
29
+
30
+ if(existsSync(finalOutputPath) && !overwrite) {
31
+ throw new Error(`The file at ${finalOutputPath} already exists. Use the '--overwrite' option to replace it.`);
32
+ }
33
+
34
+ const wallet = ethers.Wallet.createRandom();
35
+ const keypairData = {
36
+ address: wallet.address,
37
+ privateKey: wallet.privateKey,
38
+ };
39
+
40
+ writeFileSync(finalOutputPath, JSON.stringify(keypairData, null, 2));
41
+ this.writeConfig('keyPairPath', finalOutputPath);
42
+ }
43
+ }
@@ -2,14 +2,41 @@ import { ConfigFileManager } from "../../lib/config/ConfigFileManager";
2
2
  import ora, { Ora } from "ora";
3
3
  import chalk from "chalk";
4
4
  import inquirer from "inquirer";
5
-
5
+ import { KeypairManager } from "../accounts/KeypairManager";
6
+ import { createClient, createAccount } from "genlayer-js";
7
+ import { localnet } from "genlayer-js/chains";
8
+ import type { GenLayerClient } from "genlayer-js/types";
6
9
 
7
10
  export class BaseAction extends ConfigFileManager {
11
+ protected keypairManager: KeypairManager;
8
12
  private spinner: Ora;
13
+ private _genlayerClient: GenLayerClient<typeof localnet> | null = null;
9
14
 
10
15
  constructor() {
11
16
  super()
12
17
  this.spinner = ora({ text: "", spinner: "dots" });
18
+ this.keypairManager = new KeypairManager();
19
+ }
20
+
21
+ protected async getClient(): Promise<GenLayerClient<typeof localnet>> {
22
+ if (!this._genlayerClient) {
23
+ this._genlayerClient = createClient({
24
+ chain: localnet,
25
+ endpoint: process.env.VITE_JSON_RPC_SERVER_URL,
26
+ account: createAccount(await this.getPrivateKey() as any),
27
+ });
28
+ }
29
+ return this._genlayerClient;
30
+ }
31
+
32
+ protected async getPrivateKey() {
33
+ const privateKey = this.keypairManager.getPrivateKey();
34
+ if (privateKey) {
35
+ return privateKey;
36
+ }
37
+ await this.confirmPrompt("Keypair file not found. Would you like to create a new keypair?");
38
+ this.keypairManager.createKeypair();
39
+ return this.keypairManager.getPrivateKey();
13
40
  }
14
41
 
15
42
  protected async confirmPrompt(message: string): Promise<void> {
@@ -47,27 +74,27 @@ export class BaseAction extends ConfigFileManager {
47
74
 
48
75
  protected log(message: string, data?: any): void {
49
76
  console.log(chalk.white(`\n${message}`));
50
- if (data) console.log(this.formatOutput(data));
77
+ if (data !== undefined) console.log(this.formatOutput(data));
51
78
  }
52
79
 
53
80
  protected logSuccess(message: string, data?: any): void {
54
81
  console.log(chalk.green(`\n✔ ${message}`));
55
- if (data) console.log(chalk.green(this.formatOutput(data)));
82
+ if (data !== undefined) console.log(chalk.green(this.formatOutput(data)));
56
83
  }
57
84
 
58
85
  protected logInfo(message: string, data?: any): void {
59
86
  console.log(chalk.blue(`\nℹ ${message}`));
60
- if (data) console.log(chalk.blue(this.formatOutput(data)));
87
+ if (data !== undefined) console.log(chalk.blue(this.formatOutput(data)));
61
88
  }
62
89
 
63
90
  protected logWarning(message: string, data?: any): void {
64
91
  console.log(chalk.yellow(`\n⚠ ${message}`));
65
- if (data) console.log(chalk.yellow(this.formatOutput(data)));
92
+ if (data !== undefined) console.log(chalk.yellow(this.formatOutput(data)));
66
93
  }
67
94
 
68
95
  protected logError(message: string, error?: any): void {
69
96
  console.error(chalk.red(`\n✖ ${message}`));
70
- if (error) console.error(chalk.red(this.formatOutput(error)));
97
+ if (error !== undefined) console.error(chalk.red(this.formatOutput(error)));
71
98
  }
72
99
 
73
100
  protected startSpinner(message: string) {
@@ -76,7 +103,7 @@ export class BaseAction extends ConfigFileManager {
76
103
  }
77
104
 
78
105
  protected succeedSpinner(message: string, data?: any): void {
79
- if (data) this.log('Result:', data);
106
+ if (data !== undefined) this.log('Result:', data);
80
107
  this.spinner.succeed(chalk.green(message));
81
108
  }
82
109
 
@@ -1,4 +1,4 @@
1
- export const localnetCompatibleVersion = "v0.42.0";
1
+ export const localnetCompatibleVersion = "v0.51.0";
2
2
  export const DEFAULT_JSON_RPC_URL = "http://localhost:4000/api";
3
3
  export const CONTAINERS_NAME_PREFIX = "/genlayer-";
4
4
  export const IMAGES_NAME_PREFIX = "yeagerai";
@@ -32,7 +32,7 @@ export type AiProvidersConfigType = {
32
32
  export const AI_PROVIDERS_CONFIG: AiProvidersConfigType = {
33
33
  ollama: {
34
34
  name: "Ollama",
35
- hint: "(This will download and run a local instance of Llama 3)",
35
+ hint: "(By default, this will download and run a local instance of Llama 3)",
36
36
  cliOptionValue: "ollama",
37
37
  },
38
38
  openai: {