@devtion/devcli 0.0.0-b499eaf → 0.0.0-c1f4cbe
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/.env +14 -0
- package/dist/index.js +388 -29
- package/dist/public/mini-semaphore.wasm +0 -0
- package/dist/public/mini-semaphore.zkey +0 -0
- package/dist/types/commands/authBandada.d.ts +2 -0
- package/dist/types/commands/authSIWE.d.ts +7 -0
- package/dist/types/commands/ceremony/index.d.ts +3 -0
- package/dist/types/commands/ceremony/listParticipants.d.ts +2 -0
- package/dist/types/commands/index.d.ts +2 -0
- package/dist/types/lib/bandada.d.ts +6 -0
- package/dist/types/lib/files.d.ts +1 -0
- package/dist/types/lib/localConfigs.d.ts +38 -0
- package/dist/types/types/index.d.ts +63 -0
- package/package.json +10 -3
- package/src/commands/auth.ts +7 -1
- package/src/commands/authBandada.ts +120 -0
- package/src/commands/authSIWE.ts +178 -0
- package/src/commands/ceremony/index.ts +20 -0
- package/src/commands/ceremony/listParticipants.ts +30 -0
- package/src/commands/contribute.ts +16 -11
- package/src/commands/finalize.ts +3 -3
- package/src/commands/index.ts +2 -0
- package/src/commands/logout.ts +3 -1
- package/src/index.ts +18 -5
- package/src/lib/bandada.ts +51 -0
- package/src/lib/errors.ts +1 -1
- package/src/lib/localConfigs.ts +54 -0
- package/src/lib/services.ts +38 -13
- package/src/lib/utils.ts +3 -1
- package/src/types/index.ts +68 -0
package/dist/.env
CHANGED
|
@@ -23,6 +23,13 @@ FIREBASE_CF_URL_VERIFY_CONTRIBUTION=https://verifycontribution-mq4aqokliq-ew.a.r
|
|
|
23
23
|
|
|
24
24
|
# The unique identifier for the Github client associated to the OAuth Application.
|
|
25
25
|
AUTH_GITHUB_CLIENT_ID=e9f8a5fabdfe0d95618c
|
|
26
|
+
### BANDADA AUTHENTICATION ###
|
|
27
|
+
# The Bandada API URL to be used for authentication.
|
|
28
|
+
BANDADA_API_URL=https://api.bandada.pse.dev/
|
|
29
|
+
# The Bandada group id that will be used for the credentials criteria (e.g. GH followers)
|
|
30
|
+
BANDADA_GROUP_ID=40887196405294111455930028907236
|
|
31
|
+
# The Bandada dashboard URL to administrate the groups
|
|
32
|
+
BANDADA_DASHBOARD_URL=https://bandada.pse.dev/
|
|
26
33
|
|
|
27
34
|
### AWS S3 STORAGE ###
|
|
28
35
|
### These configs are related to the configuration of the interaction with the
|
|
@@ -38,4 +45,11 @@ CONFIG_CEREMONY_BUCKET_POSTFIX=-p0tion-development-environment
|
|
|
38
45
|
# The amount of time in seconds which indicates the duration about the validity of a pre-signed URL.
|
|
39
46
|
# default: 7200 seconds = 2 hours.
|
|
40
47
|
CONFIG_PRESIGNED_URL_EXPIRATION_IN_SECONDS=7200
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Sign In With Ethereum
|
|
51
|
+
# Auth0 client id
|
|
52
|
+
AUTH_SIWE_CLIENT_ID=tRuFnJNoPTJtKr1RynYfty6uJ16QzHXA
|
|
53
|
+
# The Auth0 application url that support SIWE + Device Flow Authentication
|
|
54
|
+
AUTH0_APPLICATION_URL=https://dev-l0tyk1agsmopw1xa.us.auth0.com
|
|
41
55
|
|
package/dist/index.js
CHANGED
|
@@ -10,19 +10,19 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { createCommand } from 'commander';
|
|
12
12
|
import fs, { readFileSync, createWriteStream, existsSync, renameSync } from 'fs';
|
|
13
|
-
import { dirname } from 'path';
|
|
13
|
+
import path, { dirname } from 'path';
|
|
14
14
|
import { fileURLToPath } from 'url';
|
|
15
|
-
import { zKey } from 'snarkjs';
|
|
15
|
+
import { zKey, groth16 } from 'snarkjs';
|
|
16
16
|
import boxen from 'boxen';
|
|
17
17
|
import { pipeline } from 'node:stream';
|
|
18
18
|
import { promisify } from 'node:util';
|
|
19
19
|
import fetch$1 from 'node-fetch';
|
|
20
|
-
import { commonTerms, formatZkeyIndex, getZkeyStorageFilePath, finalContributionIndex, createCustomLoggerForFile, getBucketName, progressToNextContributionStep, permanentlyStoreCurrentContributionTimeAndHash, convertToDoubleDigits, multiPartUpload, verifyContribution, generateGetObjectPreSignedUrl, convertBytesOrKbToGb, numExpIterations, getDocumentById, getParticipantsCollectionPath, fromQueryToFirebaseDocumentInfo, getAllCollectionDocs, extractPrefix, autoGenerateEntropy, vmConfigurationTypes, initializeFirebaseCoreServices, signInToFirebaseWithCredentials, getCurrentFirebaseAuthUser, isCoordinator, parseCeremonyFile, blake512FromPath, checkIfObjectExist, setupCeremony, genesisZkeyIndex, getR1csStorageFilePath, getWasmStorageFilePath, getPotStorageFilePath, extractPoTFromFilename, potFileDownloadMainUrl, createS3Bucket, potFilenameTemplate, getR1CSInfo, getOpenedCeremonies, getCeremonyCircuits, checkParticipantForCeremony, getCurrentActiveParticipantTimeout, getCircuitBySequencePosition, getCircuitContributionsFromContributor, progressToNextCircuitForContribution, resumeContributionAfterTimeoutExpiration, generateValidContributionsAttestation, getContributionsValidityForContributor, getClosedCeremonies, checkAndPrepareCoordinatorForFinalization, computeSHA256ToHex, finalizeCeremony, getVerificationKeyStorageFilePath, verificationKeyAcronym, getVerifierContractStorageFilePath, verifierSmartContractAcronym, finalizeCircuit, exportVkey, exportVerifierContract } from '@devtion/actions';
|
|
20
|
+
import { commonTerms, formatZkeyIndex, getZkeyStorageFilePath, finalContributionIndex, createCustomLoggerForFile, getBucketName, progressToNextContributionStep, permanentlyStoreCurrentContributionTimeAndHash, convertToDoubleDigits, multiPartUpload, verifyContribution, generateGetObjectPreSignedUrl, convertBytesOrKbToGb, numExpIterations, getDocumentById, getParticipantsCollectionPath, fromQueryToFirebaseDocumentInfo, getAllCollectionDocs, extractPrefix, autoGenerateEntropy, vmConfigurationTypes, initializeFirebaseCoreServices, signInToFirebaseWithCredentials, getCurrentFirebaseAuthUser, isCoordinator, parseCeremonyFile, blake512FromPath, checkIfObjectExist, setupCeremony, genesisZkeyIndex, getR1csStorageFilePath, getWasmStorageFilePath, getPotStorageFilePath, extractPoTFromFilename, potFileDownloadMainUrl, createS3Bucket, potFilenameTemplate, getR1CSInfo, getOpenedCeremonies, getCeremonyCircuits, checkParticipantForCeremony, getCurrentActiveParticipantTimeout, getCircuitBySequencePosition, getCircuitContributionsFromContributor, progressToNextCircuitForContribution, resumeContributionAfterTimeoutExpiration, generateValidContributionsAttestation, getContributionsValidityForContributor, getClosedCeremonies, checkAndPrepareCoordinatorForFinalization, computeSHA256ToHex, finalizeCeremony, getVerificationKeyStorageFilePath, verificationKeyAcronym, getVerifierContractStorageFilePath, verifierSmartContractAcronym, finalizeCircuit, exportVkey, exportVerifierContract, getAllCeremonies } from '@devtion/actions';
|
|
21
21
|
import fetch from '@adobe/node-fetch-retry';
|
|
22
22
|
import { request } from '@octokit/request';
|
|
23
23
|
import { SingleBar, Presets } from 'cli-progress';
|
|
24
24
|
import dotenv from 'dotenv';
|
|
25
|
-
import { GithubAuthProvider, getAuth, signOut } from 'firebase/auth';
|
|
25
|
+
import { GithubAuthProvider, signInWithCustomToken, getAuth, signOut } from 'firebase/auth';
|
|
26
26
|
import { getDiskInfoSync } from 'node-disk-info';
|
|
27
27
|
import ora from 'ora';
|
|
28
28
|
import { Timer } from 'timer-node';
|
|
@@ -36,7 +36,10 @@ import figlet from 'figlet';
|
|
|
36
36
|
import { createOAuthDeviceAuth } from '@octokit/auth-oauth-device';
|
|
37
37
|
import clipboard from 'clipboardy';
|
|
38
38
|
import open from 'open';
|
|
39
|
-
import {
|
|
39
|
+
import { Identity } from '@semaphore-protocol/identity';
|
|
40
|
+
import { httpsCallable } from 'firebase/functions';
|
|
41
|
+
import { ApiSdk } from '@bandada/api-sdk';
|
|
42
|
+
import { Timestamp, onSnapshot, doc, collection, getDocs } from 'firebase/firestore';
|
|
40
43
|
import readline from 'readline';
|
|
41
44
|
|
|
42
45
|
/**
|
|
@@ -97,7 +100,7 @@ const CORE_SERVICES_ERRORS = {
|
|
|
97
100
|
FIREBASE_TOKEN_EXPIRED_REMOVED_PERMISSIONS: `The Github authorization has failed due to lack of association between your account and the CLI`,
|
|
98
101
|
FIREBASE_USER_DISABLED: `The Github account has been suspended by the ceremony coordinator(s), blocking the possibility of contribution. Please, contact them to understand the motivation behind it.`,
|
|
99
102
|
FIREBASE_FAILED_CREDENTIALS_VERIFICATION: `Firebase cannot verify your Github credentials due to network errors. Please, try once again later.`,
|
|
100
|
-
FIREBASE_NETWORK_ERROR: `Unable to reach Firebase due to network
|
|
103
|
+
FIREBASE_NETWORK_ERROR: `Unable to reach Firebase due to network errors. Please, try once again later and make sure your Internet connection is stable.`,
|
|
101
104
|
FIREBASE_CEREMONY_NOT_OPENED: `There are no ceremonies opened to contributions`,
|
|
102
105
|
FIREBASE_CEREMONY_NOT_CLOSED: `There are no ceremonies ready to finalization`,
|
|
103
106
|
AWS_CEREMONY_BUCKET_CREATION: `Unable to create a new bucket for the ceremony. Something went wrong during the creation. Please, repeat the process by providing a new ceremony name of the ceremony.`,
|
|
@@ -235,6 +238,14 @@ const checkAndMakeNewDirectoryIfNonexistent = (directoryLocalPath) => {
|
|
|
235
238
|
const writeLocalJsonFile = (filePath, data) => {
|
|
236
239
|
fs.writeFileSync(filePath, JSON.stringify(data), "utf-8");
|
|
237
240
|
};
|
|
241
|
+
/**
|
|
242
|
+
* Return the local current project directory name.
|
|
243
|
+
* @returns <string> - the local project (e.g., dist/) directory name.
|
|
244
|
+
*/
|
|
245
|
+
const getLocalDirname = () => {
|
|
246
|
+
const filename = fileURLToPath(import.meta.url);
|
|
247
|
+
return path.dirname(filename);
|
|
248
|
+
};
|
|
238
249
|
|
|
239
250
|
// Get npm package name.
|
|
240
251
|
const packagePath$4 = `${dirname(fileURLToPath(import.meta.url))}/..`;
|
|
@@ -250,6 +261,14 @@ const config = new Conf({
|
|
|
250
261
|
accessToken: {
|
|
251
262
|
type: "string",
|
|
252
263
|
default: ""
|
|
264
|
+
},
|
|
265
|
+
bandadaIdentity: {
|
|
266
|
+
type: "string",
|
|
267
|
+
default: ""
|
|
268
|
+
},
|
|
269
|
+
authMethod: {
|
|
270
|
+
type: "string",
|
|
271
|
+
default: ""
|
|
253
272
|
}
|
|
254
273
|
}
|
|
255
274
|
});
|
|
@@ -310,6 +329,39 @@ const setLocalAccessToken = (token) => config.set("accessToken", token);
|
|
|
310
329
|
* Delete the stored access token.
|
|
311
330
|
*/
|
|
312
331
|
const deleteLocalAccessToken = () => config.delete("accessToken");
|
|
332
|
+
/**
|
|
333
|
+
* Return the Bandada identity, if present.
|
|
334
|
+
* @returns <string | undefined> - the Bandada identity if present, otherwise undefined.
|
|
335
|
+
*/
|
|
336
|
+
const getLocalBandadaIdentity = () => config.get("bandadaIdentity");
|
|
337
|
+
/**
|
|
338
|
+
* Check if the Bandada identity exists in the local storage.
|
|
339
|
+
* @returns <boolean>
|
|
340
|
+
*/
|
|
341
|
+
const checkLocalBandadaIdentity = () => config.has("bandadaIdentity") && !!config.get("bandadaIdentity");
|
|
342
|
+
/**
|
|
343
|
+
* Set the Bandada identity.
|
|
344
|
+
* @param identity <string> - the Bandada identity to be stored.
|
|
345
|
+
*/
|
|
346
|
+
const setLocalBandadaIdentity = (identity) => config.set("bandadaIdentity", identity);
|
|
347
|
+
/**
|
|
348
|
+
* Delete the stored Bandada identity.
|
|
349
|
+
*/
|
|
350
|
+
const deleteLocalBandadaIdentity = () => config.delete("bandadaIdentity");
|
|
351
|
+
/**
|
|
352
|
+
* Return the authentication method, if present.
|
|
353
|
+
* @returns <string | undefined> - the authentication method if present, otherwise undefined.
|
|
354
|
+
*/
|
|
355
|
+
const getLocalAuthMethod = () => config.get("authMethod");
|
|
356
|
+
/**
|
|
357
|
+
* Set the authentication method.
|
|
358
|
+
* @param method <string> - the authentication method to be stored.
|
|
359
|
+
*/
|
|
360
|
+
const setLocalAuthMethod = (method) => config.set("authMethod", method);
|
|
361
|
+
/**
|
|
362
|
+
* Delete the stored authentication method.
|
|
363
|
+
*/
|
|
364
|
+
const deleteLocalAuthMethod = () => config.delete("authMethod");
|
|
313
365
|
/**
|
|
314
366
|
* Get the complete local file path.
|
|
315
367
|
* @param cwd <string> - the current working directory path.
|
|
@@ -420,7 +472,7 @@ const getGithubAuthenticatedUserGists = async (githubToken, params) => {
|
|
|
420
472
|
headers: {
|
|
421
473
|
authorization: `token ${githubToken}`
|
|
422
474
|
},
|
|
423
|
-
per_page: params.perPage,
|
|
475
|
+
per_page: params.perPage, // max items per page = 100.
|
|
424
476
|
page: params.page
|
|
425
477
|
});
|
|
426
478
|
if (response && response.status === 200)
|
|
@@ -468,8 +520,9 @@ const getPublicAttestationGist = async (githubToken, publicAttestationFilename)
|
|
|
468
520
|
* @returns <string> - the third-party provider handle of the user.
|
|
469
521
|
*/
|
|
470
522
|
const getUserHandleFromProviderUserId = (providerUserId) => {
|
|
471
|
-
if (providerUserId.indexOf("-") === -1)
|
|
472
|
-
|
|
523
|
+
if (providerUserId.indexOf("-") === -1) {
|
|
524
|
+
return providerUserId;
|
|
525
|
+
}
|
|
473
526
|
return providerUserId.split("-")[0];
|
|
474
527
|
};
|
|
475
528
|
/**
|
|
@@ -1560,16 +1613,37 @@ const checkAuth = async (firebaseApp) => {
|
|
|
1560
1613
|
showError(THIRD_PARTY_SERVICES_ERRORS.GITHUB_NOT_AUTHENTICATED, true);
|
|
1561
1614
|
// Retrieve local access token.
|
|
1562
1615
|
const token = String(getLocalAccessToken());
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1616
|
+
let providerUserId;
|
|
1617
|
+
let username;
|
|
1618
|
+
const authMethod = getLocalAuthMethod();
|
|
1619
|
+
switch (authMethod) {
|
|
1620
|
+
case "github": {
|
|
1621
|
+
// Get credentials.
|
|
1622
|
+
const credentials = exchangeGithubTokenForCredentials(token);
|
|
1623
|
+
// Sign in to Firebase using credentials.
|
|
1624
|
+
await signInToFirebase(firebaseApp, credentials);
|
|
1625
|
+
// Get Github unique identifier (handle-id).
|
|
1626
|
+
providerUserId = await getGithubProviderUserId(String(token));
|
|
1627
|
+
username = getUserHandleFromProviderUserId(providerUserId);
|
|
1628
|
+
break;
|
|
1629
|
+
}
|
|
1630
|
+
case "bandada": {
|
|
1631
|
+
const userCredentials = await signInWithCustomToken(getAuth(), token);
|
|
1632
|
+
providerUserId = userCredentials.user.uid;
|
|
1633
|
+
username = providerUserId;
|
|
1634
|
+
break;
|
|
1635
|
+
}
|
|
1636
|
+
case "siwe": {
|
|
1637
|
+
const userCredentials = await signInWithCustomToken(getAuth(), token);
|
|
1638
|
+
providerUserId = userCredentials.user.uid;
|
|
1639
|
+
username = providerUserId;
|
|
1640
|
+
break;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1567
1643
|
// Get current authenticated user.
|
|
1568
1644
|
const user = getCurrentFirebaseAuthUser(firebaseApp);
|
|
1569
|
-
// Get Github unique identifier (handle-id).
|
|
1570
|
-
const providerUserId = await getGithubProviderUserId(String(token));
|
|
1571
1645
|
// Greet the user.
|
|
1572
|
-
console.log(`Greetings, @${theme.text.bold(
|
|
1646
|
+
console.log(`Greetings, @${theme.text.bold(username)} ${theme.emojis.wave}\n`);
|
|
1573
1647
|
return {
|
|
1574
1648
|
user,
|
|
1575
1649
|
token,
|
|
@@ -2205,6 +2279,7 @@ const auth = async () => {
|
|
|
2205
2279
|
// Generate a new access token using Github Device Flow (OAuth 2.0).
|
|
2206
2280
|
const newToken = await executeGithubDeviceFlow(String(process.env.AUTH_GITHUB_CLIENT_ID));
|
|
2207
2281
|
// Store the new access token.
|
|
2282
|
+
setLocalAuthMethod("github");
|
|
2208
2283
|
setLocalAccessToken(newToken);
|
|
2209
2284
|
}
|
|
2210
2285
|
else
|
|
@@ -2225,6 +2300,244 @@ const auth = async () => {
|
|
|
2225
2300
|
terminate(providerUserId);
|
|
2226
2301
|
};
|
|
2227
2302
|
|
|
2303
|
+
const { BANDADA_API_URL } = process.env;
|
|
2304
|
+
const bandadaApi = new ApiSdk(BANDADA_API_URL);
|
|
2305
|
+
const addMemberToGroup = async (groupId, dashboardUrl, identity) => {
|
|
2306
|
+
const commitment = identity.commitment.toString();
|
|
2307
|
+
const group = await bandadaApi.getGroup(groupId);
|
|
2308
|
+
const providerName = group.credentials.id.split("_")[0].toLowerCase();
|
|
2309
|
+
// 6. open a new window with the url:
|
|
2310
|
+
const url = `${dashboardUrl}credentials?group=${groupId}&member=${commitment}&provider=${providerName}`;
|
|
2311
|
+
console.log(`${theme.text.bold(`Verification URL:`)} ${theme.text.underlined(url)}`);
|
|
2312
|
+
open(url);
|
|
2313
|
+
const { confirmation } = await askForConfirmation("Did you join the Bandada group in the browser?");
|
|
2314
|
+
if (!confirmation)
|
|
2315
|
+
showError("You must join the Bandada group to continue the login process", true);
|
|
2316
|
+
};
|
|
2317
|
+
const isGroupMember = async (groupId, identity) => {
|
|
2318
|
+
const commitment = identity.commitment.toString();
|
|
2319
|
+
const isMember = await bandadaApi.isGroupMember(groupId, commitment);
|
|
2320
|
+
return isMember;
|
|
2321
|
+
};
|
|
2322
|
+
|
|
2323
|
+
const { BANDADA_DASHBOARD_URL, BANDADA_GROUP_ID } = process.env;
|
|
2324
|
+
const authBandada = async () => {
|
|
2325
|
+
try {
|
|
2326
|
+
const { firebaseFunctions } = await bootstrapCommandExecutionAndServices();
|
|
2327
|
+
const spinner = customSpinner(`Checking identity string for Semaphore...`, `clock`);
|
|
2328
|
+
spinner.start();
|
|
2329
|
+
// 1. check if _identity string exists in local storage
|
|
2330
|
+
let identityString;
|
|
2331
|
+
const isIdentityStringStored = checkLocalBandadaIdentity();
|
|
2332
|
+
if (isIdentityStringStored) {
|
|
2333
|
+
identityString = getLocalBandadaIdentity();
|
|
2334
|
+
spinner.succeed(`Identity seed found\n`);
|
|
2335
|
+
}
|
|
2336
|
+
else {
|
|
2337
|
+
spinner.warn(`Identity seed not found\n`);
|
|
2338
|
+
// 2. generate a random _identity string and save it in local storage
|
|
2339
|
+
const { seed } = await prompts({
|
|
2340
|
+
type: "text",
|
|
2341
|
+
name: "seed",
|
|
2342
|
+
message: theme.text.bold(`Enter a secret string to use as your identity seed in Semaphore:`),
|
|
2343
|
+
initial: false
|
|
2344
|
+
});
|
|
2345
|
+
identityString = seed;
|
|
2346
|
+
setLocalBandadaIdentity(identityString);
|
|
2347
|
+
}
|
|
2348
|
+
// 3. create a semaphore identity with _identity string as a seed
|
|
2349
|
+
const identity = new Identity(identityString);
|
|
2350
|
+
// 4. check if the user is a member of the group
|
|
2351
|
+
console.log(`Checking Bandada membership...`);
|
|
2352
|
+
const isMember = await isGroupMember(BANDADA_GROUP_ID, identity);
|
|
2353
|
+
if (!isMember) {
|
|
2354
|
+
await addMemberToGroup(BANDADA_GROUP_ID, BANDADA_DASHBOARD_URL, identity);
|
|
2355
|
+
}
|
|
2356
|
+
// 5. generate a proof that the user owns the commitment.
|
|
2357
|
+
spinner.text = `Generating proof of identity...`;
|
|
2358
|
+
spinner.start();
|
|
2359
|
+
// publicSignals = [hash(externalNullifier, identityNullifier), commitment]
|
|
2360
|
+
const initDirectoryName = getLocalDirname();
|
|
2361
|
+
const directoryName = initDirectoryName.includes("/src") ? "." : initDirectoryName;
|
|
2362
|
+
const { proof, publicSignals } = await groth16.fullProve({
|
|
2363
|
+
identityTrapdoor: identity.trapdoor,
|
|
2364
|
+
identityNullifier: identity.nullifier,
|
|
2365
|
+
externalNullifier: BANDADA_GROUP_ID
|
|
2366
|
+
}, `${directoryName}/public/mini-semaphore.wasm`, `${directoryName}/public/mini-semaphore.zkey`);
|
|
2367
|
+
spinner.succeed(`Proof generated.\n`);
|
|
2368
|
+
spinner.text = `Sending proof to verification...`;
|
|
2369
|
+
spinner.start();
|
|
2370
|
+
// 6. send proof to a cloud function that verifies it and checks membership
|
|
2371
|
+
const cf = httpsCallable(firebaseFunctions, commonTerms.cloudFunctionsNames.bandadaValidateProof);
|
|
2372
|
+
const result = await cf({
|
|
2373
|
+
proof,
|
|
2374
|
+
publicSignals
|
|
2375
|
+
});
|
|
2376
|
+
const { valid, token, message } = result.data;
|
|
2377
|
+
if (!valid) {
|
|
2378
|
+
showError(message, true);
|
|
2379
|
+
deleteLocalAuthMethod();
|
|
2380
|
+
deleteLocalAccessToken();
|
|
2381
|
+
deleteLocalBandadaIdentity();
|
|
2382
|
+
}
|
|
2383
|
+
spinner.succeed(`Proof verified.\n`);
|
|
2384
|
+
spinner.text = `Authenticating...`;
|
|
2385
|
+
spinner.start();
|
|
2386
|
+
// 7. Auth to p0tion firebase
|
|
2387
|
+
const credentials = await signInWithCustomToken(getAuth(), token);
|
|
2388
|
+
setLocalAuthMethod("bandada");
|
|
2389
|
+
setLocalAccessToken(token);
|
|
2390
|
+
spinner.succeed(`Authenticated as ${theme.text.bold(credentials.user.uid)}.`);
|
|
2391
|
+
console.log(`\n${theme.symbols.warning} You can always log out by running the ${theme.text.bold(`phase2cli logout`)} command`);
|
|
2392
|
+
}
|
|
2393
|
+
catch (error) {
|
|
2394
|
+
// Delete local token.
|
|
2395
|
+
console.log("An error crashed the process. Deleting local token and identity.");
|
|
2396
|
+
console.error(error);
|
|
2397
|
+
deleteLocalAuthMethod();
|
|
2398
|
+
deleteLocalAccessToken();
|
|
2399
|
+
deleteLocalBandadaIdentity();
|
|
2400
|
+
}
|
|
2401
|
+
process.exit(0);
|
|
2402
|
+
};
|
|
2403
|
+
|
|
2404
|
+
const showVerificationCodeAndUri = async (OAuthDeviceCode) => {
|
|
2405
|
+
// Copy code to clipboard.
|
|
2406
|
+
let noClipboard = false;
|
|
2407
|
+
try {
|
|
2408
|
+
clipboard.writeSync(OAuthDeviceCode.user_code);
|
|
2409
|
+
clipboard.readSync();
|
|
2410
|
+
}
|
|
2411
|
+
catch (error) {
|
|
2412
|
+
noClipboard = true;
|
|
2413
|
+
}
|
|
2414
|
+
// Display data.
|
|
2415
|
+
console.log(`${theme.symbols.warning} Visit ${theme.text.bold(theme.text.underlined(OAuthDeviceCode.verification_uri))} on this device to generate a new token and authenticate\n`);
|
|
2416
|
+
console.log(theme.colors.magenta(figlet.textSync("Code is Below", { font: "ANSI Shadow" })), "\n");
|
|
2417
|
+
const message = !noClipboard ? `has been copied to your clipboard (${theme.emojis.clipboard})` : ``;
|
|
2418
|
+
console.log(`${theme.symbols.info} Your auth code: ${theme.text.bold(OAuthDeviceCode.user_code)} ${message} ${theme.symbols.success}\n`);
|
|
2419
|
+
const spinner = customSpinner(`Redirecting to Github...`, `clock`);
|
|
2420
|
+
spinner.start();
|
|
2421
|
+
await sleep(10000); // ~10s to make users able to read the CLI.
|
|
2422
|
+
try {
|
|
2423
|
+
// Automatically open the page (# Step 2).
|
|
2424
|
+
await open(OAuthDeviceCode.verification_uri);
|
|
2425
|
+
}
|
|
2426
|
+
catch (error) {
|
|
2427
|
+
console.log(`${theme.symbols.info} Please authenticate via GitHub at ${OAuthDeviceCode.verification_uri}`);
|
|
2428
|
+
}
|
|
2429
|
+
spinner.stop();
|
|
2430
|
+
};
|
|
2431
|
+
/**
|
|
2432
|
+
* Return the token to sign in to Firebase after passing the SIWE Device Flow
|
|
2433
|
+
* @param clientId <string> - The client id of the Auth0 application.
|
|
2434
|
+
* @param firebaseFunctions <any> - The Firebase functions instance to call the cloud function
|
|
2435
|
+
* @returns <string> - The token to sign in to Firebase
|
|
2436
|
+
*/
|
|
2437
|
+
const executeSIWEDeviceFlow = async (clientId, firebaseFunctions) => {
|
|
2438
|
+
// Call Auth0 endpoint to request device code uri
|
|
2439
|
+
const OAuthDeviceCode = (await fetch$1(`${process.env.AUTH0_APPLICATION_URL}/oauth/device/code`, {
|
|
2440
|
+
method: "POST",
|
|
2441
|
+
headers: { "content-type": "application/json" },
|
|
2442
|
+
body: JSON.stringify({
|
|
2443
|
+
client_id: clientId,
|
|
2444
|
+
scope: "openid",
|
|
2445
|
+
audience: `${process.env.AUTH0_APPLICATION_URL}/api/v2/`
|
|
2446
|
+
})
|
|
2447
|
+
}).then((_res) => _res.json()));
|
|
2448
|
+
await showVerificationCodeAndUri(OAuthDeviceCode);
|
|
2449
|
+
// Poll Auth0 endpoint until you get token or request expires
|
|
2450
|
+
let isSignedIn = false;
|
|
2451
|
+
let isExpired = false;
|
|
2452
|
+
let auth0Token = "";
|
|
2453
|
+
while (!isSignedIn && !isExpired) {
|
|
2454
|
+
// Call Auth0 endpoint to request token
|
|
2455
|
+
const OAuthToken = (await fetch$1(`${process.env.AUTH0_APPLICATION_URL}/oauth/token`, {
|
|
2456
|
+
method: "POST",
|
|
2457
|
+
headers: { "content-type": "application/json" },
|
|
2458
|
+
body: JSON.stringify({
|
|
2459
|
+
client_id: clientId,
|
|
2460
|
+
device_code: OAuthDeviceCode.device_code,
|
|
2461
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
2462
|
+
})
|
|
2463
|
+
}).then((_res) => _res.json()));
|
|
2464
|
+
if (OAuthToken.error) {
|
|
2465
|
+
if (OAuthToken.error === "authorization_pending") {
|
|
2466
|
+
// Wait for the user to sign in
|
|
2467
|
+
await sleep(OAuthDeviceCode.interval * 1000);
|
|
2468
|
+
}
|
|
2469
|
+
else if (OAuthToken.error === "slow_down") {
|
|
2470
|
+
// Wait for the user to sign in
|
|
2471
|
+
await sleep(OAuthDeviceCode.interval * 1000 * 2);
|
|
2472
|
+
}
|
|
2473
|
+
else if (OAuthToken.error === "expired_token") {
|
|
2474
|
+
// The user didn't sign in on time
|
|
2475
|
+
isExpired = true;
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
else {
|
|
2479
|
+
// The user signed in
|
|
2480
|
+
isSignedIn = true;
|
|
2481
|
+
auth0Token = OAuthToken.access_token;
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
// Send token to cloud function to check nonce, create user and retrieve token
|
|
2485
|
+
const cf = httpsCallable(firebaseFunctions, commonTerms.cloudFunctionsNames.checkNonceOfSIWEAddress);
|
|
2486
|
+
const result = await cf({
|
|
2487
|
+
auth0Token
|
|
2488
|
+
});
|
|
2489
|
+
const { token, valid, message } = result.data;
|
|
2490
|
+
if (!valid) {
|
|
2491
|
+
showError(message, true);
|
|
2492
|
+
deleteLocalAuthMethod();
|
|
2493
|
+
deleteLocalAccessToken();
|
|
2494
|
+
}
|
|
2495
|
+
return token;
|
|
2496
|
+
};
|
|
2497
|
+
/**
|
|
2498
|
+
* Auth command using Sign In With Ethereum
|
|
2499
|
+
* @notice The auth command allows a user to make the association of their Ethereum account with the CLI by leveraging SIWE as an authentication mechanism.
|
|
2500
|
+
* @dev Under the hood, the command handles a manual Device Flow following the guidelines in the SIWE documentation.
|
|
2501
|
+
*/
|
|
2502
|
+
const authSIWE = async () => {
|
|
2503
|
+
try {
|
|
2504
|
+
const { firebaseFunctions } = await bootstrapCommandExecutionAndServices();
|
|
2505
|
+
// Console more context for the user.
|
|
2506
|
+
console.log(`${theme.symbols.info} ${theme.text.bold(`You are about to authenticate on this CLI using your Ethereum address (device flow - OAuth 2.0 mechanism).\n${theme.symbols.warning} Please, note that only a Sign-in With Ethereum signature will be required`)}\n`);
|
|
2507
|
+
const spinner = customSpinner(`Checking authentication token...`, `clock`);
|
|
2508
|
+
spinner.start();
|
|
2509
|
+
await sleep(5000);
|
|
2510
|
+
// Manage OAuth Github or SIWE token.
|
|
2511
|
+
const isLocalTokenStored = checkLocalAccessToken();
|
|
2512
|
+
if (!isLocalTokenStored) {
|
|
2513
|
+
spinner.fail(`No local authentication token found\n`);
|
|
2514
|
+
// Generate a new access token using Github Device Flow (OAuth 2.0).
|
|
2515
|
+
const newToken = await executeSIWEDeviceFlow(String(process.env.AUTH_SIWE_CLIENT_ID), firebaseFunctions);
|
|
2516
|
+
// Store the new access token.
|
|
2517
|
+
setLocalAuthMethod("siwe");
|
|
2518
|
+
setLocalAccessToken(newToken);
|
|
2519
|
+
}
|
|
2520
|
+
else
|
|
2521
|
+
spinner.succeed(`Local authentication token found\n`);
|
|
2522
|
+
// Get access token from local store.
|
|
2523
|
+
const token = String(getLocalAccessToken());
|
|
2524
|
+
spinner.text = `Authenticating...`;
|
|
2525
|
+
spinner.start();
|
|
2526
|
+
// Exchange token for credential.
|
|
2527
|
+
const credentials = await signInWithCustomToken(getAuth(), token);
|
|
2528
|
+
spinner.succeed(`Authenticated as ${theme.text.bold(credentials.user.uid)}.`);
|
|
2529
|
+
console.log(`\n${theme.symbols.warning} You can always log out by running the ${theme.text.bold(`phase2cli logout`)} command`);
|
|
2530
|
+
process.exit(0);
|
|
2531
|
+
}
|
|
2532
|
+
catch (error) {
|
|
2533
|
+
// Delete local token.
|
|
2534
|
+
console.log("An error crashed the process. Deleting local token and identity.");
|
|
2535
|
+
console.error(error);
|
|
2536
|
+
deleteLocalAuthMethod();
|
|
2537
|
+
deleteLocalAccessToken();
|
|
2538
|
+
}
|
|
2539
|
+
};
|
|
2540
|
+
|
|
2228
2541
|
/**
|
|
2229
2542
|
* Return the verification result for latest contribution.
|
|
2230
2543
|
* @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
|
|
@@ -2347,8 +2660,8 @@ const handleDiskSpaceRequirementForNextContribution = async (cloudFunctions, cer
|
|
|
2347
2660
|
spinner.fail(`You may not have enough memory to calculate the contribution for the Circuit ${theme.colors.magenta(`${circuitSequencePosition}`)}.\n\n${theme.symbols.info} The required amount of disk space is ${contributionDiskSpaceRequirement < 0.01
|
|
2348
2661
|
? theme.text.bold(`< 0.01`)
|
|
2349
2662
|
: theme.text.bold(contributionDiskSpaceRequirement)} GB but you only have ${participantFreeDiskSpace > 0 ? theme.text.bold(participantFreeDiskSpace.toFixed(2)) : theme.text.bold(0)} GB available memory \nThe estimate ${theme.text.bold("may not be 100% correct")} since is based on the aggregate free memory on your disks but some may not be detected!\n`);
|
|
2350
|
-
const {
|
|
2351
|
-
wannaContributeOrHaveEnoughMemory = !!
|
|
2663
|
+
const { confirmationEnoughMemory } = await askForConfirmation(`Please, we kindly ask you to continue with the contribution if you have noticed the estimate is wrong and you have enough memory in your machine`, "Continue", "Exit");
|
|
2664
|
+
wannaContributeOrHaveEnoughMemory = !!confirmationEnoughMemory;
|
|
2352
2665
|
if (circuitSequencePosition > 1) {
|
|
2353
2666
|
console.log(`${theme.symbols.info} Please note, you have time until ceremony ends to free up your memory and complete remaining contributions`);
|
|
2354
2667
|
// Asks the contributor if their wants to terminate contributions for the ceremony.
|
|
@@ -2416,8 +2729,12 @@ const handlePublicAttestation = async (firestoreDatabase, circuits, ceremonyId,
|
|
|
2416
2729
|
// Write public attestation locally.
|
|
2417
2730
|
writeFile(getAttestationLocalFilePath(`${ceremonyPrefix}_${commonTerms.foldersAndPathsTerms.attestation}.log`), Buffer.from(publicAttestation));
|
|
2418
2731
|
await sleep(1000); // workaround for file descriptor unexpected close.
|
|
2419
|
-
|
|
2420
|
-
|
|
2732
|
+
let gistUrl = "";
|
|
2733
|
+
const isGithub = getLocalAuthMethod() === "github";
|
|
2734
|
+
if (isGithub) {
|
|
2735
|
+
gistUrl = await publishGist(participantAccessToken, publicAttestation, ceremonyName, ceremonyPrefix);
|
|
2736
|
+
console.log(`\n${theme.symbols.info} Your public attestation has been successfully posted as Github Gist (${theme.text.bold(theme.text.underlined(gistUrl))})`);
|
|
2737
|
+
}
|
|
2421
2738
|
// Prepare a ready-to-share tweet.
|
|
2422
2739
|
await handleTweetGeneration(ceremonyName, gistUrl);
|
|
2423
2740
|
};
|
|
@@ -2731,7 +3048,7 @@ const contribute = async (opt) => {
|
|
|
2731
3048
|
const userDoc = await getDocumentById(firestoreDatabase, commonTerms.collections.users.name, user.uid);
|
|
2732
3049
|
const userData = userDoc.data();
|
|
2733
3050
|
if (!userData) {
|
|
2734
|
-
spinner.fail(`Unfortunately we could not find a user document with your information. This likely means that you did not pass the GitHub reputation checks and therefore are not
|
|
3051
|
+
spinner.fail(`Unfortunately we could not find a user document with your information. This likely means that you did not pass the GitHub reputation checks and therefore are not eligible to contribute to any ceremony. If you believe you pass the requirements, it might be possible that your profile is private and we were not able to fetch your real statistics, in this case please consider making your profile public for the duration of the contribution. Please contact the coordinator if you believe this to be an error.`);
|
|
2735
3052
|
process.exit(0);
|
|
2736
3053
|
}
|
|
2737
3054
|
// Check the user's current participant readiness for contribution status (eligible, already contributed, timed out).
|
|
@@ -2931,7 +3248,7 @@ const handleVerificationKey = async (cloudFunctions, bucketName, finalZkeyLocalF
|
|
|
2931
3248
|
spinner.text = "Writing verification key...";
|
|
2932
3249
|
// Write the verification key locally.
|
|
2933
3250
|
writeLocalJsonFile(verificationKeyLocalFilePath, vKey);
|
|
2934
|
-
await sleep(3000); //
|
|
3251
|
+
await sleep(3000); // workaround for file descriptor.
|
|
2935
3252
|
// Upload verification key to storage.
|
|
2936
3253
|
await multiPartUpload(cloudFunctions, bucketName, verificationKeyStorageFilePath, verificationKeyLocalFilePath, Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB));
|
|
2937
3254
|
spinner.succeed(`Verification key correctly saved on storage`);
|
|
@@ -2957,7 +3274,7 @@ const handleVerifierSmartContract = async (cloudFunctions, bucketName, finalZkey
|
|
|
2957
3274
|
spinner.text = `Writing verifier smart contract...`;
|
|
2958
3275
|
// Write the verification key locally.
|
|
2959
3276
|
writeFile(verifierContractLocalFilePath, verifierCode);
|
|
2960
|
-
await sleep(3000); //
|
|
3277
|
+
await sleep(3000); // workaround for file descriptor.
|
|
2961
3278
|
// Upload verifier smart contract to storage.
|
|
2962
3279
|
await multiPartUpload(cloudFunctions, bucketName, verifierContractStorageFilePath, verifierContractLocalFilePath, Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB));
|
|
2963
3280
|
spinner.succeed(`Verifier smart contract correctly saved on storage`);
|
|
@@ -2983,7 +3300,7 @@ const handleVerifierSmartContract = async (cloudFunctions, bucketName, finalZkey
|
|
|
2983
3300
|
const handleCircuitFinalization = async (cloudFunctions, firestoreDatabase, ceremony, circuit, participant, beacon, coordinatorIdentifier, circuitsLength) => {
|
|
2984
3301
|
// Step (1).
|
|
2985
3302
|
await handleStartOrResumeContribution(cloudFunctions, firestoreDatabase, ceremony, circuit, participant, computeSHA256ToHex(beacon), coordinatorIdentifier, true, circuitsLength);
|
|
2986
|
-
await sleep(2000); //
|
|
3303
|
+
await sleep(2000); // workaround for descriptors.
|
|
2987
3304
|
// Extract data.
|
|
2988
3305
|
const { prefix: circuitPrefix } = circuit.data;
|
|
2989
3306
|
const { prefix: ceremonyPrefix } = ceremony.data;
|
|
@@ -3132,7 +3449,9 @@ const logout = async () => {
|
|
|
3132
3449
|
const auth = getAuth();
|
|
3133
3450
|
await signOut(auth);
|
|
3134
3451
|
// Delete local token.
|
|
3452
|
+
deleteLocalAuthMethod();
|
|
3135
3453
|
deleteLocalAccessToken();
|
|
3454
|
+
deleteLocalBandadaIdentity();
|
|
3136
3455
|
await sleep(3000); // ~3s.
|
|
3137
3456
|
spinner.stop();
|
|
3138
3457
|
console.log(`${theme.symbols.success} Logout successfully completed`);
|
|
@@ -3194,6 +3513,37 @@ const listCeremonies = async () => {
|
|
|
3194
3513
|
}
|
|
3195
3514
|
};
|
|
3196
3515
|
|
|
3516
|
+
const listParticipants = async () => {
|
|
3517
|
+
try {
|
|
3518
|
+
const { firestoreDatabase } = await bootstrapCommandExecutionAndServices();
|
|
3519
|
+
const allCeremonies = await getAllCeremonies(firestoreDatabase);
|
|
3520
|
+
const selectedCeremony = await promptForCeremonySelection(allCeremonies, true);
|
|
3521
|
+
const docRef = doc(firestoreDatabase, commonTerms.collections.ceremonies.name, selectedCeremony.id);
|
|
3522
|
+
const participantsRef = collection(docRef, "participants");
|
|
3523
|
+
const participantsSnapshot = await getDocs(participantsRef);
|
|
3524
|
+
const participants = participantsSnapshot.docs.map((participantDoc) => participantDoc.data().userId);
|
|
3525
|
+
console.log(participants);
|
|
3526
|
+
/* const usersRef = collection(firestoreDatabase, "users")
|
|
3527
|
+
const usersSnapshot = await getDocs(usersRef)
|
|
3528
|
+
const users = usersSnapshot.docs.map((userDoc) => userDoc.data())
|
|
3529
|
+
console.log(users) */
|
|
3530
|
+
}
|
|
3531
|
+
catch (err) {
|
|
3532
|
+
showError(`Something went wrong: ${err.toString()}`, true);
|
|
3533
|
+
}
|
|
3534
|
+
process.exit(0);
|
|
3535
|
+
};
|
|
3536
|
+
|
|
3537
|
+
const setCeremonyCommands = (program) => {
|
|
3538
|
+
const ceremony = program.command("ceremony").description("manage ceremonies");
|
|
3539
|
+
ceremony
|
|
3540
|
+
.command("participants")
|
|
3541
|
+
.description("retrieve participants list of a ceremony")
|
|
3542
|
+
.requiredOption("-c, --ceremony <string>", "the prefix of the ceremony you want to retrieve information about", "")
|
|
3543
|
+
.action(listParticipants);
|
|
3544
|
+
return ceremony;
|
|
3545
|
+
};
|
|
3546
|
+
|
|
3197
3547
|
// Get pkg info (e.g., name, version).
|
|
3198
3548
|
const packagePath = `${dirname(fileURLToPath(import.meta.url))}/..`;
|
|
3199
3549
|
const { description, version, name } = JSON.parse(readFileSync(`${packagePath}/package.json`, "utf8"));
|
|
@@ -3202,6 +3552,14 @@ const program = createCommand();
|
|
|
3202
3552
|
program.name(name).description(description).version(version);
|
|
3203
3553
|
// User commands.
|
|
3204
3554
|
program.command("auth").description("authenticate yourself using your Github account (OAuth 2.0)").action(auth);
|
|
3555
|
+
program
|
|
3556
|
+
.command("auth-bandada")
|
|
3557
|
+
.description("authenticate yourself in a privacy-perserving manner using Bandada")
|
|
3558
|
+
.action(authBandada);
|
|
3559
|
+
program
|
|
3560
|
+
.command("auth-siwe")
|
|
3561
|
+
.description("authenticate yourself using your Ethereum account (Sign In With Ethereum - SIWE)")
|
|
3562
|
+
.action(authSIWE);
|
|
3205
3563
|
program
|
|
3206
3564
|
.command("contribute")
|
|
3207
3565
|
.description("compute contributions for a Phase2 Trusted Setup ceremony circuits")
|
|
@@ -3220,25 +3578,26 @@ program
|
|
|
3220
3578
|
.action(logout);
|
|
3221
3579
|
program
|
|
3222
3580
|
.command("validate")
|
|
3223
|
-
.description("
|
|
3581
|
+
.description("validate that a Ceremony Setup file is correct")
|
|
3224
3582
|
.requiredOption("-t, --template <path>", "The path to the ceremony setup template", "")
|
|
3225
3583
|
.option("-c, --constraints <number>", "The number of constraints to check against")
|
|
3226
3584
|
.action(validate);
|
|
3227
3585
|
// Only coordinator commands.
|
|
3228
|
-
const
|
|
3229
|
-
|
|
3586
|
+
const coordinate = program.command("coordinate").description("commands for coordinating a ceremony");
|
|
3587
|
+
coordinate
|
|
3230
3588
|
.command("setup")
|
|
3231
3589
|
.description("setup a Groth16 Phase 2 Trusted Setup ceremony for zk-SNARK circuits")
|
|
3232
3590
|
.option("-t, --template <path>", "The path to the ceremony setup template", "")
|
|
3233
3591
|
.option("-a, --auth <string>", "The Github OAuth 2.0 token", "")
|
|
3234
3592
|
.action(setup);
|
|
3235
|
-
|
|
3593
|
+
coordinate
|
|
3236
3594
|
.command("observe")
|
|
3237
3595
|
.description("observe in real-time the waiting queue of each ceremony circuit")
|
|
3238
3596
|
.action(observe);
|
|
3239
|
-
|
|
3597
|
+
coordinate
|
|
3240
3598
|
.command("finalize")
|
|
3241
3599
|
.description("finalize a Phase2 Trusted Setup ceremony by applying a beacon, exporting verification key and verifier contract")
|
|
3242
3600
|
.option("-a, --auth <string>", "the Github OAuth 2.0 token", "")
|
|
3243
3601
|
.action(finalize);
|
|
3602
|
+
setCeremonyCommands(program);
|
|
3244
3603
|
program.parseAsync(process.argv);
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth command using Sign In With Ethereum
|
|
3
|
+
* @notice The auth command allows a user to make the association of their Ethereum account with the CLI by leveraging SIWE as an authentication mechanism.
|
|
4
|
+
* @dev Under the hood, the command handles a manual Device Flow following the guidelines in the SIWE documentation.
|
|
5
|
+
*/
|
|
6
|
+
declare const authSIWE: () => Promise<void>;
|
|
7
|
+
export default authSIWE;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { default as setup } from "./setup.js";
|
|
2
2
|
export { default as auth } from "./auth.js";
|
|
3
|
+
export { default as authBandada } from "./authBandada.js";
|
|
4
|
+
export { default as authSIWE } from "./authSIWE.js";
|
|
3
5
|
export { default as contribute } from "./contribute.js";
|
|
4
6
|
export { default as observe } from "./observe.js";
|
|
5
7
|
export { default as finalize } from "./finalize.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { GroupResponse } from "@bandada/api-sdk";
|
|
2
|
+
import { Identity } from "@semaphore-protocol/identity";
|
|
3
|
+
export declare const getGroup: (groupId: string) => Promise<GroupResponse | null>;
|
|
4
|
+
export declare const getMembersOfGroup: (groupId: string) => Promise<string[] | null>;
|
|
5
|
+
export declare const addMemberToGroup: (groupId: string, dashboardUrl: string, identity: Identity) => Promise<void>;
|
|
6
|
+
export declare const isGroupMember: (groupId: string, identity: Identity) => Promise<boolean>;
|