@semaphore-protocol/cli 3.5.0 → 3.6.0-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * @module @semaphore-protocol/cli
5
- * @version 3.5.0
5
+ * @version 3.6.0-1
6
6
  * @file A command line tool to set up your Semaphore project and get group data.
7
7
  * @copyright Ethereum Foundation 2022
8
8
  * @license MIT
@@ -12,11 +12,10 @@
12
12
  import { SemaphoreSubgraph, SemaphoreEthers } from '@semaphore-protocol/data';
13
13
  import chalk from 'chalk';
14
14
  import { program } from 'commander';
15
- import download from 'download';
16
15
  import figlet from 'figlet';
17
- import { readFileSync, existsSync, renameSync } from 'fs';
18
- import inquirer from 'inquirer';
16
+ import { readFileSync, existsSync } from 'fs';
19
17
  import logSymbols from 'log-symbols';
18
+ import pacote from 'pacote';
20
19
  import { dirname } from 'path';
21
20
  import { fileURLToPath } from 'url';
22
21
  import axios from 'axios';
@@ -24,6 +23,7 @@ import boxen from 'boxen';
24
23
  import { execSync } from 'child_process';
25
24
  import { lt } from 'semver';
26
25
  import ora from 'ora';
26
+ import inquirer from 'inquirer';
27
27
 
28
28
  const cliRegistryURL = "https://registry.npmjs.org/-/package/@semaphore-protocol/cli/dist-tags";
29
29
  /**
@@ -70,9 +70,98 @@ class Spinner {
70
70
  }
71
71
  }
72
72
 
73
+ /**
74
+ * Gets all group ids on the specified network
75
+ * @param network The specified network
76
+ */
77
+ async function getGroupIds(network) {
78
+ let groupIds;
79
+ const spinner = new Spinner("Fetching groups");
80
+ spinner.start();
81
+ try {
82
+ const semaphoreSubgraph = new SemaphoreSubgraph(network);
83
+ groupIds = await semaphoreSubgraph.getGroupIds();
84
+ spinner.stop();
85
+ }
86
+ catch {
87
+ try {
88
+ const semaphoreEthers = new SemaphoreEthers(network);
89
+ groupIds = await semaphoreEthers.getGroupIds();
90
+ spinner.stop();
91
+ }
92
+ catch {
93
+ spinner.stop();
94
+ console.info(`\n ${logSymbols.error}`, "error: unexpected error with the SemaphoreEthers package");
95
+ return null;
96
+ }
97
+ }
98
+ if (groupIds.length === 0) {
99
+ console.info(`\n ${logSymbols.info}`, "info: there are no groups in this network\n");
100
+ return null;
101
+ }
102
+ return groupIds;
103
+ }
104
+
105
+ async function getProjectName() {
106
+ const { projectName } = await inquirer.prompt({
107
+ name: "projectName",
108
+ type: "input",
109
+ message: "What is your project name?",
110
+ default: "my-app"
111
+ });
112
+ return projectName;
113
+ }
114
+ async function getSupportedTemplate(supportedTemplates) {
115
+ const { selectedTemplate } = await inquirer.prompt({
116
+ name: "selectedTemplate",
117
+ type: "list",
118
+ message: "Select one of the supported templates:",
119
+ default: 0,
120
+ choices: supportedTemplates.map((template) => ({
121
+ value: template.value,
122
+ name: `${template.value} (${template.name})`
123
+ }))
124
+ });
125
+ return selectedTemplate;
126
+ }
127
+ async function getSupportedNetwork(supportedNetworks) {
128
+ const { selectedNetwork } = await inquirer.prompt({
129
+ name: "selectedNetwork",
130
+ type: "list",
131
+ message: "Select one of the supported networks:",
132
+ default: 0,
133
+ choices: supportedNetworks
134
+ });
135
+ return selectedNetwork;
136
+ }
137
+ async function getGroupId(groupIds) {
138
+ const { selectedGroupId } = await inquirer.prompt({
139
+ name: "selectedGroupId",
140
+ type: "list",
141
+ message: "Select one of the following existing group ids:",
142
+ default: 0,
143
+ choices: groupIds
144
+ });
145
+ return selectedGroupId;
146
+ }
147
+
73
148
  const packagePath = `${dirname(fileURLToPath(import.meta.url))}/..`;
74
149
  const { description, version } = JSON.parse(readFileSync(`${packagePath}/package.json`, "utf8"));
75
150
  const supportedNetworks = ["sepolia", "goerli", "mumbai", "optimism-goerli", "arbitrum", "arbitrum-goerli"];
151
+ const supportedTemplates = [
152
+ {
153
+ value: "contracts-hardhat",
154
+ name: "Hardhat"
155
+ },
156
+ {
157
+ value: "monorepo-ethers",
158
+ name: "Hardhat + Next.js + SemaphoreEthers"
159
+ },
160
+ {
161
+ value: "monorepo-subgraph",
162
+ name: "Hardhat + Next.js + SemaphoreSubgraph"
163
+ }
164
+ ];
76
165
  program
77
166
  .name("semaphore")
78
167
  .description(description)
@@ -90,29 +179,28 @@ program
90
179
  .command("create")
91
180
  .description("Create a Semaphore project with a supported template.")
92
181
  .argument("[project-directory]", "Directory of the project.")
93
- // .option("-t, --template <template-name>", "Supported Semaphore template.", "hardhat")
182
+ .option("-t, --template <template-name>", "Supported Semaphore template.")
94
183
  .allowExcessArguments(false)
95
- .action(async (projectDirectory) => {
184
+ .action(async (projectDirectory, { template }) => {
96
185
  if (!projectDirectory) {
97
- const { projectName } = await inquirer.prompt({
98
- name: "projectName",
99
- type: "input",
100
- message: "What is your project name?",
101
- default: "my-app"
102
- });
103
- projectDirectory = projectName;
186
+ projectDirectory = await getProjectName();
187
+ }
188
+ if (!template) {
189
+ template = await getSupportedTemplate(supportedTemplates);
190
+ }
191
+ if (!supportedTemplates.some((t) => t.value === template)) {
192
+ console.info(`\n ${logSymbols.error}`, `error: the template '${template}' is not supported\n`);
193
+ return;
104
194
  }
105
195
  const currentDirectory = process.cwd();
106
196
  const spinner = new Spinner(`Creating your project in ${chalk.green(`./${projectDirectory}`)}`);
107
- const templateURL = `https://registry.npmjs.org/@semaphore-protocol/cli-template-hardhat/-/cli-template-hardhat-${version}.tgz`;
108
197
  if (existsSync(projectDirectory)) {
109
198
  console.info(`\n ${logSymbols.error}`, `error: the '${projectDirectory}' folder already exists\n`);
110
199
  return;
111
200
  }
112
201
  spinner.start();
113
202
  await checkLatestVersion(version);
114
- await download(templateURL, currentDirectory, { extract: true });
115
- renameSync(`${currentDirectory}/package`, `${currentDirectory}/${projectDirectory}`);
203
+ await pacote.extract(`@semaphore-protocol/cli-template-${template}@${version}`, `${currentDirectory}/${projectDirectory}`);
116
204
  spinner.stop();
117
205
  console.info(`\n ${logSymbols.success}`, `Your project is ready!\n`);
118
206
  console.info(` Please, install your dependencies by running:\n`);
@@ -134,137 +222,153 @@ program
134
222
  .allowExcessArguments(false)
135
223
  .action(async ({ network }) => {
136
224
  if (!network) {
137
- const { selectedNetwork } = await inquirer.prompt({
138
- name: "selectedNetwork",
139
- type: "list",
140
- message: "Select one of our supported networks:",
141
- default: supportedNetworks[0],
142
- choices: supportedNetworks
143
- });
144
- network = selectedNetwork;
225
+ network = await getSupportedNetwork(supportedNetworks);
145
226
  }
146
227
  if (!supportedNetworks.includes(network)) {
147
228
  console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`);
148
229
  return;
149
230
  }
150
- let groupIds;
151
- const spinner = new Spinner("Fetching groups");
231
+ const groupIds = await getGroupIds(network);
232
+ if (groupIds === null)
233
+ return;
234
+ const content = `${chalk.bold("Groups")} (${groupIds.length}): \n${groupIds
235
+ .map((id) => ` - ${id}`)
236
+ .join("\n")}`;
237
+ console.info(`\n${content}\n`);
238
+ });
239
+ program
240
+ .command("get-group")
241
+ .description("Get the data of a group from a supported network (e.g. goerli or arbitrum).")
242
+ .argument("[group-id]", "Identifier of the group.")
243
+ .option("-n, --network <network-name>", "Supported Ethereum network.")
244
+ .allowExcessArguments(false)
245
+ .action(async (groupId, { network }) => {
246
+ if (!network) {
247
+ network = await getSupportedNetwork(supportedNetworks);
248
+ }
249
+ if (!supportedNetworks.includes(network)) {
250
+ console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`);
251
+ return;
252
+ }
253
+ if (!groupId) {
254
+ const groupIds = await getGroupIds(network);
255
+ if (groupIds === null)
256
+ return;
257
+ groupId = await getGroupId(groupIds);
258
+ }
259
+ let group;
260
+ const spinner = new Spinner(`Fetching group ${groupId}`);
152
261
  spinner.start();
153
262
  try {
154
263
  const semaphoreSubgraph = new SemaphoreSubgraph(network);
155
- groupIds = await semaphoreSubgraph.getGroupIds();
264
+ group = await semaphoreSubgraph.getGroup(groupId);
156
265
  spinner.stop();
157
266
  }
158
267
  catch {
159
268
  try {
160
269
  const semaphoreEthers = new SemaphoreEthers(network);
161
- groupIds = await semaphoreEthers.getGroupIds();
270
+ group = await semaphoreEthers.getGroup(groupId);
271
+ group.admin = await semaphoreEthers.getGroupAdmin(groupId);
162
272
  spinner.stop();
163
273
  }
164
274
  catch {
165
275
  spinner.stop();
166
- console.info(`\n ${logSymbols.error}`, "error: unexpected error with the SemaphoreEthers package");
276
+ console.info(`\n ${logSymbols.error}`, "error: the group does not exist\n");
167
277
  return;
168
278
  }
169
279
  }
170
- if (groupIds.length === 0) {
171
- console.info(`\n ${logSymbols.info}`, "info: there are no groups in this network\n");
172
- return;
173
- }
174
- const content = `\n${groupIds.map((id) => ` - ${id}`).join("\n")}`;
175
- console.info(`${content}\n`);
280
+ let content = ` ${chalk.bold("Id")}: ${group.id}\n`;
281
+ content += ` ${chalk.bold("Admin")}: ${group.admin}\n`;
282
+ content += ` ${chalk.bold("Merkle tree")}:\n`;
283
+ content += ` Root: ${group.merkleTree.root}\n`;
284
+ content += ` Depth: ${group.merkleTree.depth}\n`;
285
+ content += ` Zero value: ${group.merkleTree.zeroValue}\n`;
286
+ content += ` Number of leaves: ${group.merkleTree.numberOfLeaves}`;
287
+ console.info(`\n${content}\n`);
176
288
  });
177
289
  program
178
- .command("get-group")
179
- .description("Get the data of a group from a supported network (e.g. goerli or arbitrum).")
290
+ .command("get-members")
291
+ .description("Get the members of a group from a supported network (e.g. goerli or arbitrum).")
180
292
  .argument("[group-id]", "Identifier of the group.")
181
293
  .option("-n, --network <network-name>", "Supported Ethereum network.")
182
- .option("-m, --members", "Show group members.")
183
- .option("-s, --signals", "Show group signals.")
184
294
  .allowExcessArguments(false)
185
- .action(async (groupId, { network, members, signals }) => {
295
+ .action(async (groupId, { network }) => {
186
296
  if (!network) {
187
- const { selectedNetwork } = await inquirer.prompt({
188
- name: "selectedNetwork",
189
- type: "list",
190
- message: "Select one of our supported networks:",
191
- default: supportedNetworks[0],
192
- choices: supportedNetworks
193
- });
194
- network = selectedNetwork;
297
+ network = await getSupportedNetwork(supportedNetworks);
195
298
  }
196
299
  if (!supportedNetworks.includes(network)) {
197
300
  console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`);
198
301
  return;
199
302
  }
200
303
  if (!groupId) {
201
- let groupIds;
202
- const spinnerGroups = new Spinner("Fetching groups");
203
- spinnerGroups.start();
304
+ const groupIds = await getGroupIds(network);
305
+ if (groupIds === null)
306
+ return;
307
+ groupId = await getGroupId(groupIds);
308
+ }
309
+ let groupMembers;
310
+ const spinner = new Spinner(`Fetching members of group ${groupId}`);
311
+ spinner.start();
312
+ try {
313
+ const semaphoreSubgraph = new SemaphoreSubgraph(network);
314
+ const group = await semaphoreSubgraph.getGroup(groupId, { members: true });
315
+ groupMembers = group.members;
316
+ spinner.stop();
317
+ }
318
+ catch {
204
319
  try {
205
- const semaphoreSubgraphGroups = new SemaphoreSubgraph(network);
206
- groupIds = await semaphoreSubgraphGroups.getGroupIds();
207
- spinnerGroups.stop();
320
+ const semaphoreEthers = new SemaphoreEthers(network);
321
+ groupMembers = await semaphoreEthers.getGroupMembers(groupId);
322
+ spinner.stop();
208
323
  }
209
324
  catch {
210
- try {
211
- const semaphoreEthersGroups = new SemaphoreEthers(network);
212
- groupIds = await semaphoreEthersGroups.getGroupIds();
213
- spinnerGroups.stop();
214
- }
215
- catch {
216
- spinnerGroups.stop();
217
- console.info(`\n ${logSymbols.error}`, "error: unexpected error with the SemaphoreEthers package");
218
- return;
219
- }
220
- }
221
- if (groupIds.length === 0) {
222
- console.info(`\n ${logSymbols.info}`, "info: there are no groups in this network\n");
325
+ spinner.stop();
326
+ console.info(`\n ${logSymbols.error}`, "error: the group does not exist\n");
223
327
  return;
224
328
  }
225
- const { selectedGroupId } = await inquirer.prompt({
226
- name: "selectedGroupId",
227
- type: "list",
228
- message: "Select one of the following existing group ids:",
229
- choices: groupIds
230
- });
231
- groupId = selectedGroupId;
232
- }
233
- if (!members && !signals) {
234
- const { showMembers } = await inquirer.prompt({
235
- name: "showMembers",
236
- type: "confirm",
237
- message: "Do you want to show members?",
238
- default: false
239
- });
240
- members = showMembers;
241
- const { showSignals } = await inquirer.prompt({
242
- name: "showSignals",
243
- type: "confirm",
244
- message: "Do you want to show signals?",
245
- default: false
246
- });
247
- signals = showSignals;
248
329
  }
249
- let group;
250
- const spinner = new Spinner(`Fetching group ${groupId}`);
330
+ if (groupMembers.length === 0) {
331
+ console.info(`\n ${logSymbols.info}`, "info: there are no members in this group\n");
332
+ return;
333
+ }
334
+ const content = `${chalk.bold("Members")} (${groupMembers.length}): \n${groupMembers
335
+ .map((member, i) => ` ${i}. ${member}`)
336
+ .join("\n")}`;
337
+ console.info(`\n${content}\n`);
338
+ });
339
+ program
340
+ .command("get-proofs")
341
+ .description("Get the proofs from a supported network (e.g. goerli or arbitrum).")
342
+ .argument("[group-id]", "Identifier of the group.")
343
+ .option("-n, --network <network-name>", "Supported Ethereum network.")
344
+ .allowExcessArguments(false)
345
+ .action(async (groupId, { network }) => {
346
+ if (!network) {
347
+ network = await getSupportedNetwork(supportedNetworks);
348
+ }
349
+ if (!supportedNetworks.includes(network)) {
350
+ console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`);
351
+ return;
352
+ }
353
+ if (!groupId) {
354
+ const groupIds = await getGroupIds(network);
355
+ if (groupIds === null)
356
+ return;
357
+ groupId = await getGroupId(groupIds);
358
+ }
359
+ let verifiedProofs;
360
+ const spinner = new Spinner(`Fetching proofs of group ${groupId}`);
251
361
  spinner.start();
252
362
  try {
253
363
  const semaphoreSubgraph = new SemaphoreSubgraph(network);
254
- group = await semaphoreSubgraph.getGroup(groupId, { members, verifiedProofs: signals });
364
+ const group = await semaphoreSubgraph.getGroup(groupId, { verifiedProofs: true });
365
+ verifiedProofs = group.verifiedProofs;
255
366
  spinner.stop();
256
367
  }
257
368
  catch {
258
369
  try {
259
370
  const semaphoreEthers = new SemaphoreEthers(network);
260
- group = await semaphoreEthers.getGroup(groupId);
261
- if (members) {
262
- group.members = await semaphoreEthers.getGroupMembers(groupId);
263
- }
264
- if (signals) {
265
- group.verifiedProofs = await semaphoreEthers.getGroupVerifiedProofs(groupId);
266
- }
267
- group.admin = await semaphoreEthers.getGroupAdmin(groupId);
371
+ verifiedProofs = await semaphoreEthers.getGroupVerifiedProofs(groupId);
268
372
  spinner.stop();
269
373
  }
270
374
  catch {
@@ -273,23 +377,13 @@ program
273
377
  return;
274
378
  }
275
379
  }
276
- let content = ` ${chalk.bold("Id")}: ${group.id}\n`;
277
- content += ` ${chalk.bold("Admin")}: ${group.admin}\n`;
278
- content += ` ${chalk.bold("Merkle tree")}:\n`;
279
- content += ` Root: ${group.merkleTree.root}\n`;
280
- content += ` Depth: ${group.merkleTree.depth}\n`;
281
- content += ` Zero value: ${group.merkleTree.zeroValue}\n`;
282
- content += ` Number of leaves: ${group.merkleTree.numberOfLeaves}`;
283
- if (members) {
284
- content += `\n\n ${chalk.bold("Members")}: \n${group.members
285
- .map((member, i) => ` ${i}. ${member}`)
286
- .join("\n")}`;
287
- }
288
- if (signals) {
289
- content += `\n\n ${chalk.bold("Signals")}: \n${group.verifiedProofs
290
- .map(({ signal }) => ` - ${signal}`)
291
- .join("\n")}`;
380
+ if (verifiedProofs.length === 0) {
381
+ console.info(`\n ${logSymbols.info}`, "info: there are no proofs in this group\n");
382
+ return;
292
383
  }
384
+ const content = `${chalk.bold("Proofs")} (${verifiedProofs.length}): \n${verifiedProofs
385
+ .map(({ signal, merkleTreeRoot, externalNullifier, nullifierHash }, i) => ` ${i}. signal: ${signal} \n merkleTreeRoot: ${merkleTreeRoot} \n externalNullifier: ${externalNullifier} \n nullifierHash: ${nullifierHash}`)
386
+ .join("\n")}`;
293
387
  console.info(`\n${content}\n`);
294
388
  });
295
389
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@semaphore-protocol/cli",
3
3
  "type": "module",
4
- "version": "3.5.0",
4
+ "version": "3.6.0-1",
5
5
  "description": "A command line tool to set up your Semaphore project and get group data.",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -34,6 +34,8 @@
34
34
  "devDependencies": {
35
35
  "@types/clear": "^0.1.2",
36
36
  "@types/figlet": "^1.5.5",
37
+ "@types/inquirer": "^9.0.3",
38
+ "@types/pacote": "^11.1.5",
37
39
  "@types/semver": "^7.3.13",
38
40
  "rollup-plugin-cleanup": "^3.2.1",
39
41
  "rollup-plugin-typescript2": "^0.31.2",
@@ -41,16 +43,17 @@
41
43
  "typedoc": "^0.22.11"
42
44
  },
43
45
  "dependencies": {
44
- "@semaphore-protocol/data": "3.5.0",
46
+ "@semaphore-protocol/data": "3.6.0-1",
45
47
  "axios": "^1.3.2",
46
48
  "boxen": "^7.0.1",
47
49
  "chalk": "^5.1.2",
48
50
  "commander": "^9.4.1",
49
- "download": "^8.0.0",
50
51
  "figlet": "^1.5.2",
51
52
  "inquirer": "^9.1.4",
52
53
  "log-symbols": "^5.1.0",
53
54
  "ora": "^6.1.2",
55
+ "pacote": "^15.1.1",
54
56
  "semver": "^7.3.8"
55
- }
57
+ },
58
+ "stableVersion": "3.5.0"
56
59
  }
@@ -0,0 +1,42 @@
1
+ import { SemaphoreSubgraph, SemaphoreEthers } from "@semaphore-protocol/data"
2
+ import logSymbols from "log-symbols"
3
+ import Spinner from "./spinner.js"
4
+
5
+ /**
6
+ * Gets all group ids on the specified network
7
+ * @param network The specified network
8
+ */
9
+ export default async function getGroupIds(network): Promise<string[]> {
10
+ let groupIds: string[]
11
+
12
+ const spinner = new Spinner("Fetching groups")
13
+
14
+ spinner.start()
15
+
16
+ try {
17
+ const semaphoreSubgraph = new SemaphoreSubgraph(network)
18
+
19
+ groupIds = await semaphoreSubgraph.getGroupIds()
20
+
21
+ spinner.stop()
22
+ } catch {
23
+ try {
24
+ const semaphoreEthers = new SemaphoreEthers(network)
25
+
26
+ groupIds = await semaphoreEthers.getGroupIds()
27
+
28
+ spinner.stop()
29
+ } catch {
30
+ spinner.stop()
31
+
32
+ console.info(`\n ${logSymbols.error}`, "error: unexpected error with the SemaphoreEthers package")
33
+
34
+ return null
35
+ }
36
+ }
37
+ if (groupIds.length === 0) {
38
+ console.info(`\n ${logSymbols.info}`, "info: there are no groups in this network\n")
39
+ return null
40
+ }
41
+ return groupIds
42
+ }
package/src/index.ts CHANGED
@@ -1,14 +1,15 @@
1
- import { SemaphoreSubgraph, SemaphoreEthers, GroupResponse } from "@semaphore-protocol/data"
1
+ import { GroupResponse, SemaphoreEthers, SemaphoreSubgraph } from "@semaphore-protocol/data"
2
2
  import chalk from "chalk"
3
3
  import { program } from "commander"
4
- import download from "download"
5
4
  import figlet from "figlet"
6
- import { existsSync, readFileSync, renameSync } from "fs"
7
- import inquirer from "inquirer"
5
+ import { existsSync, readFileSync } from "fs"
8
6
  import logSymbols from "log-symbols"
7
+ import pacote from "pacote"
9
8
  import { dirname } from "path"
10
9
  import { fileURLToPath } from "url"
11
10
  import checkLatestVersion from "./checkLatestVersion.js"
11
+ import getGroupIds from "./getGroupIds.js"
12
+ import { getGroupId, getProjectName, getSupportedNetwork, getSupportedTemplate } from "./inquirerPrompts.js"
12
13
  import Spinner from "./spinner.js"
13
14
 
14
15
  const packagePath = `${dirname(fileURLToPath(import.meta.url))}/..`
@@ -16,6 +17,21 @@ const { description, version } = JSON.parse(readFileSync(`${packagePath}/package
16
17
 
17
18
  const supportedNetworks = ["sepolia", "goerli", "mumbai", "optimism-goerli", "arbitrum", "arbitrum-goerli"]
18
19
 
20
+ const supportedTemplates = [
21
+ {
22
+ value: "contracts-hardhat",
23
+ name: "Hardhat"
24
+ },
25
+ {
26
+ value: "monorepo-ethers",
27
+ name: "Hardhat + Next.js + SemaphoreEthers"
28
+ },
29
+ {
30
+ value: "monorepo-subgraph",
31
+ name: "Hardhat + Next.js + SemaphoreSubgraph"
32
+ }
33
+ ]
34
+
19
35
  program
20
36
  .name("semaphore")
21
37
  .description(description)
@@ -34,26 +50,27 @@ program
34
50
  .command("create")
35
51
  .description("Create a Semaphore project with a supported template.")
36
52
  .argument("[project-directory]", "Directory of the project.")
37
- // .option("-t, --template <template-name>", "Supported Semaphore template.", "hardhat")
53
+ .option("-t, --template <template-name>", "Supported Semaphore template.")
38
54
  .allowExcessArguments(false)
39
- .action(async (projectDirectory) => {
55
+ .action(async (projectDirectory, { template }) => {
40
56
  if (!projectDirectory) {
41
- const { projectName } = await inquirer.prompt({
42
- name: "projectName",
43
- type: "input",
44
- message: "What is your project name?",
45
- default: "my-app"
46
- })
47
- projectDirectory = projectName
57
+ projectDirectory = await getProjectName()
58
+ }
59
+
60
+ if (!template) {
61
+ template = await getSupportedTemplate(supportedTemplates)
62
+ }
63
+
64
+ if (!supportedTemplates.some((t) => t.value === template)) {
65
+ console.info(`\n ${logSymbols.error}`, `error: the template '${template}' is not supported\n`)
66
+ return
48
67
  }
49
68
 
50
69
  const currentDirectory = process.cwd()
51
70
  const spinner = new Spinner(`Creating your project in ${chalk.green(`./${projectDirectory}`)}`)
52
- const templateURL = `https://registry.npmjs.org/@semaphore-protocol/cli-template-hardhat/-/cli-template-hardhat-${version}.tgz`
53
71
 
54
72
  if (existsSync(projectDirectory)) {
55
73
  console.info(`\n ${logSymbols.error}`, `error: the '${projectDirectory}' folder already exists\n`)
56
-
57
74
  return
58
75
  }
59
76
 
@@ -61,9 +78,10 @@ program
61
78
 
62
79
  await checkLatestVersion(version)
63
80
 
64
- await download(templateURL, currentDirectory, { extract: true })
65
-
66
- renameSync(`${currentDirectory}/package`, `${currentDirectory}/${projectDirectory}`)
81
+ await pacote.extract(
82
+ `@semaphore-protocol/cli-template-${template}@${version}`,
83
+ `${currentDirectory}/${projectDirectory}`
84
+ )
67
85
 
68
86
  spinner.stop()
69
87
 
@@ -94,209 +112,213 @@ program
94
112
  .allowExcessArguments(false)
95
113
  .action(async ({ network }) => {
96
114
  if (!network) {
97
- const { selectedNetwork } = await inquirer.prompt({
98
- name: "selectedNetwork",
99
- type: "list",
100
- message: "Select one of our supported networks:",
101
- default: supportedNetworks[0],
102
- choices: supportedNetworks
103
- })
104
- network = selectedNetwork
115
+ network = await getSupportedNetwork(supportedNetworks)
105
116
  }
106
117
 
107
118
  if (!supportedNetworks.includes(network)) {
108
119
  console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`)
120
+ return
121
+ }
122
+
123
+ const groupIds = await getGroupIds(network)
109
124
 
125
+ if (groupIds === null) return
126
+
127
+ const content = `${chalk.bold("Groups")} (${groupIds.length}): \n${groupIds
128
+ .map((id: any) => ` - ${id}`)
129
+ .join("\n")}`
130
+
131
+ console.info(`\n${content}\n`)
132
+ })
133
+
134
+ program
135
+ .command("get-group")
136
+ .description("Get the data of a group from a supported network (e.g. goerli or arbitrum).")
137
+ .argument("[group-id]", "Identifier of the group.")
138
+ .option("-n, --network <network-name>", "Supported Ethereum network.")
139
+ .allowExcessArguments(false)
140
+ .action(async (groupId, { network }) => {
141
+ if (!network) {
142
+ network = await getSupportedNetwork(supportedNetworks)
143
+ }
144
+
145
+ if (!supportedNetworks.includes(network)) {
146
+ console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`)
110
147
  return
111
148
  }
112
149
 
113
- let groupIds: string[]
150
+ if (!groupId) {
151
+ const groupIds = await getGroupIds(network)
152
+
153
+ if (groupIds === null) return
154
+
155
+ groupId = await getGroupId(groupIds)
156
+ }
114
157
 
115
- const spinner = new Spinner("Fetching groups")
158
+ let group: GroupResponse
159
+
160
+ const spinner = new Spinner(`Fetching group ${groupId}`)
116
161
 
117
162
  spinner.start()
118
163
 
119
164
  try {
120
165
  const semaphoreSubgraph = new SemaphoreSubgraph(network)
121
166
 
122
- groupIds = await semaphoreSubgraph.getGroupIds()
167
+ group = await semaphoreSubgraph.getGroup(groupId)
123
168
 
124
169
  spinner.stop()
125
170
  } catch {
126
171
  try {
127
172
  const semaphoreEthers = new SemaphoreEthers(network)
128
173
 
129
- groupIds = await semaphoreEthers.getGroupIds()
174
+ group = await semaphoreEthers.getGroup(groupId)
175
+
176
+ group.admin = await semaphoreEthers.getGroupAdmin(groupId)
130
177
 
131
178
  spinner.stop()
132
179
  } catch {
133
180
  spinner.stop()
134
-
135
- console.info(`\n ${logSymbols.error}`, "error: unexpected error with the SemaphoreEthers package")
136
-
181
+ console.info(`\n ${logSymbols.error}`, "error: the group does not exist\n")
137
182
  return
138
183
  }
139
184
  }
140
- if (groupIds.length === 0) {
141
- console.info(`\n ${logSymbols.info}`, "info: there are no groups in this network\n")
142
185
 
143
- return
144
- }
145
-
146
- const content = `\n${groupIds.map((id: any) => ` - ${id}`).join("\n")}`
186
+ let content = ` ${chalk.bold("Id")}: ${group.id}\n`
187
+ content += ` ${chalk.bold("Admin")}: ${group.admin}\n`
188
+ content += ` ${chalk.bold("Merkle tree")}:\n`
189
+ content += ` Root: ${group.merkleTree.root}\n`
190
+ content += ` Depth: ${group.merkleTree.depth}\n`
191
+ content += ` Zero value: ${group.merkleTree.zeroValue}\n`
192
+ content += ` Number of leaves: ${group.merkleTree.numberOfLeaves}`
147
193
 
148
- console.info(`${content}\n`)
194
+ console.info(`\n${content}\n`)
149
195
  })
150
196
 
151
197
  program
152
- .command("get-group")
153
- .description("Get the data of a group from a supported network (e.g. goerli or arbitrum).")
198
+ .command("get-members")
199
+ .description("Get the members of a group from a supported network (e.g. goerli or arbitrum).")
154
200
  .argument("[group-id]", "Identifier of the group.")
155
201
  .option("-n, --network <network-name>", "Supported Ethereum network.")
156
- .option("-m, --members", "Show group members.")
157
- .option("-s, --signals", "Show group signals.")
158
202
  .allowExcessArguments(false)
159
- .action(async (groupId, { network, members, signals }) => {
203
+ .action(async (groupId, { network }) => {
160
204
  if (!network) {
161
- const { selectedNetwork } = await inquirer.prompt({
162
- name: "selectedNetwork",
163
- type: "list",
164
- message: "Select one of our supported networks:",
165
- default: supportedNetworks[0],
166
- choices: supportedNetworks
167
- })
168
-
169
- network = selectedNetwork
205
+ network = await getSupportedNetwork(supportedNetworks)
170
206
  }
171
207
 
172
208
  if (!supportedNetworks.includes(network)) {
173
209
  console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`)
174
-
175
210
  return
176
211
  }
177
212
 
178
213
  if (!groupId) {
179
- let groupIds: string[]
214
+ const groupIds = await getGroupIds(network)
180
215
 
181
- const spinnerGroups = new Spinner("Fetching groups")
216
+ if (groupIds === null) return
182
217
 
183
- spinnerGroups.start()
184
-
185
- try {
186
- const semaphoreSubgraphGroups = new SemaphoreSubgraph(network)
218
+ groupId = await getGroupId(groupIds)
219
+ }
187
220
 
188
- groupIds = await semaphoreSubgraphGroups.getGroupIds()
221
+ let groupMembers: string[]
189
222
 
190
- spinnerGroups.stop()
191
- } catch {
192
- try {
193
- const semaphoreEthersGroups = new SemaphoreEthers(network)
223
+ const spinner = new Spinner(`Fetching members of group ${groupId}`)
194
224
 
195
- groupIds = await semaphoreEthersGroups.getGroupIds()
225
+ spinner.start()
196
226
 
197
- spinnerGroups.stop()
198
- } catch {
199
- spinnerGroups.stop()
227
+ try {
228
+ const semaphoreSubgraph = new SemaphoreSubgraph(network)
200
229
 
201
- console.info(`\n ${logSymbols.error}`, "error: unexpected error with the SemaphoreEthers package")
230
+ const group = await semaphoreSubgraph.getGroup(groupId, { members: true })
231
+ groupMembers = group.members
202
232
 
203
- return
204
- }
205
- }
233
+ spinner.stop()
234
+ } catch {
235
+ try {
236
+ const semaphoreEthers = new SemaphoreEthers(network)
206
237
 
207
- if (groupIds.length === 0) {
208
- console.info(`\n ${logSymbols.info}`, "info: there are no groups in this network\n")
238
+ groupMembers = await semaphoreEthers.getGroupMembers(groupId)
209
239
 
240
+ spinner.stop()
241
+ } catch {
242
+ spinner.stop()
243
+ console.info(`\n ${logSymbols.error}`, "error: the group does not exist\n")
210
244
  return
211
245
  }
246
+ }
212
247
 
213
- const { selectedGroupId } = await inquirer.prompt({
214
- name: "selectedGroupId",
215
- type: "list",
216
- message: "Select one of the following existing group ids:",
217
- choices: groupIds
218
- })
248
+ if (groupMembers.length === 0) {
249
+ console.info(`\n ${logSymbols.info}`, "info: there are no members in this group\n")
250
+ return
251
+ }
252
+
253
+ const content = `${chalk.bold("Members")} (${groupMembers.length}): \n${groupMembers
254
+ .map((member: string, i: number) => ` ${i}. ${member}`)
255
+ .join("\n")}`
219
256
 
220
- groupId = selectedGroupId
257
+ console.info(`\n${content}\n`)
258
+ })
259
+
260
+ program
261
+ .command("get-proofs")
262
+ .description("Get the proofs from a supported network (e.g. goerli or arbitrum).")
263
+ .argument("[group-id]", "Identifier of the group.")
264
+ .option("-n, --network <network-name>", "Supported Ethereum network.")
265
+ .allowExcessArguments(false)
266
+ .action(async (groupId, { network }) => {
267
+ if (!network) {
268
+ network = await getSupportedNetwork(supportedNetworks)
221
269
  }
222
270
 
223
- if (!members && !signals) {
224
- const { showMembers } = await inquirer.prompt({
225
- name: "showMembers",
226
- type: "confirm",
227
- message: "Do you want to show members?",
228
- default: false
229
- })
271
+ if (!supportedNetworks.includes(network)) {
272
+ console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`)
273
+ return
274
+ }
230
275
 
231
- members = showMembers
276
+ if (!groupId) {
277
+ const groupIds = await getGroupIds(network)
232
278
 
233
- const { showSignals } = await inquirer.prompt({
234
- name: "showSignals",
235
- type: "confirm",
236
- message: "Do you want to show signals?",
237
- default: false
238
- })
279
+ if (groupIds === null) return
239
280
 
240
- signals = showSignals
281
+ groupId = await getGroupId(groupIds)
241
282
  }
242
283
 
243
- let group: GroupResponse
284
+ let verifiedProofs: any[]
244
285
 
245
- const spinner = new Spinner(`Fetching group ${groupId}`)
286
+ const spinner = new Spinner(`Fetching proofs of group ${groupId}`)
246
287
 
247
288
  spinner.start()
248
289
 
249
290
  try {
250
291
  const semaphoreSubgraph = new SemaphoreSubgraph(network)
251
292
 
252
- group = await semaphoreSubgraph.getGroup(groupId, { members, verifiedProofs: signals })
293
+ const group = await semaphoreSubgraph.getGroup(groupId, { verifiedProofs: true })
294
+ verifiedProofs = group.verifiedProofs
253
295
 
254
296
  spinner.stop()
255
297
  } catch {
256
298
  try {
257
299
  const semaphoreEthers = new SemaphoreEthers(network)
258
300
 
259
- group = await semaphoreEthers.getGroup(groupId)
260
-
261
- if (members) {
262
- group.members = await semaphoreEthers.getGroupMembers(groupId)
263
- }
264
-
265
- if (signals) {
266
- group.verifiedProofs = await semaphoreEthers.getGroupVerifiedProofs(groupId)
267
- }
268
-
269
- group.admin = await semaphoreEthers.getGroupAdmin(groupId)
301
+ verifiedProofs = await semaphoreEthers.getGroupVerifiedProofs(groupId)
270
302
 
271
303
  spinner.stop()
272
304
  } catch {
273
305
  spinner.stop()
274
-
275
306
  console.info(`\n ${logSymbols.error}`, "error: the group does not exist\n")
276
-
277
307
  return
278
308
  }
279
309
  }
280
310
 
281
- let content = ` ${chalk.bold("Id")}: ${group.id}\n`
282
- content += ` ${chalk.bold("Admin")}: ${group.admin}\n`
283
- content += ` ${chalk.bold("Merkle tree")}:\n`
284
- content += ` Root: ${group.merkleTree.root}\n`
285
- content += ` Depth: ${group.merkleTree.depth}\n`
286
- content += ` Zero value: ${group.merkleTree.zeroValue}\n`
287
- content += ` Number of leaves: ${group.merkleTree.numberOfLeaves}`
288
-
289
- if (members) {
290
- content += `\n\n ${chalk.bold("Members")}: \n${group.members
291
- .map((member: string, i: number) => ` ${i}. ${member}`)
292
- .join("\n")}`
311
+ if (verifiedProofs.length === 0) {
312
+ console.info(`\n ${logSymbols.info}`, "info: there are no proofs in this group\n")
313
+ return
293
314
  }
294
315
 
295
- if (signals) {
296
- content += `\n\n ${chalk.bold("Signals")}: \n${group.verifiedProofs
297
- .map(({ signal }: any) => ` - ${signal}`)
298
- .join("\n")}`
299
- }
316
+ const content = `${chalk.bold("Proofs")} (${verifiedProofs.length}): \n${verifiedProofs
317
+ .map(
318
+ ({ signal, merkleTreeRoot, externalNullifier, nullifierHash }: any, i: number) =>
319
+ ` ${i}. signal: ${signal} \n merkleTreeRoot: ${merkleTreeRoot} \n externalNullifier: ${externalNullifier} \n nullifierHash: ${nullifierHash}`
320
+ )
321
+ .join("\n")}`
300
322
 
301
323
  console.info(`\n${content}\n`)
302
324
  })
@@ -0,0 +1,48 @@
1
+ import inquirer from "inquirer"
2
+
3
+ export async function getProjectName() {
4
+ const { projectName } = await inquirer.prompt({
5
+ name: "projectName",
6
+ type: "input",
7
+ message: "What is your project name?",
8
+ default: "my-app"
9
+ })
10
+ return projectName
11
+ }
12
+
13
+ export async function getSupportedTemplate(supportedTemplates: { value: string; name: string }[]) {
14
+ const { selectedTemplate } = await inquirer.prompt({
15
+ name: "selectedTemplate",
16
+ type: "list",
17
+ message: "Select one of the supported templates:",
18
+ default: 0,
19
+ choices: supportedTemplates.map((template) => ({
20
+ value: template.value,
21
+ name: `${template.value} (${template.name})`
22
+ }))
23
+ })
24
+ return selectedTemplate
25
+ }
26
+
27
+ export async function getSupportedNetwork(supportedNetworks: string[]) {
28
+ const { selectedNetwork } = await inquirer.prompt({
29
+ name: "selectedNetwork",
30
+ type: "list",
31
+ message: "Select one of the supported networks:",
32
+ default: 0,
33
+ choices: supportedNetworks
34
+ })
35
+ return selectedNetwork
36
+ }
37
+
38
+ export async function getGroupId(groupIds: string[]) {
39
+ const { selectedGroupId } = await inquirer.prompt({
40
+ name: "selectedGroupId",
41
+ type: "list",
42
+ message: "Select one of the following existing group ids:",
43
+ default: 0,
44
+ choices: groupIds
45
+ })
46
+
47
+ return selectedGroupId
48
+ }