diff --git a/.commitlintrc.json b/.commitlintrc.json
index c30e5a97..f4fbb7dd 100644
--- a/.commitlintrc.json
+++ b/.commitlintrc.json
@@ -1,3 +1,3 @@
 {
-  "extends": ["@commitlint/config-conventional"]
+    "extends": ["@commitlint/config-conventional"]
 }
diff --git a/.editorconfig b/.editorconfig
index a8c26d5c..c94bae0f 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -7,7 +7,7 @@ charset = utf-8
 trim_trailing_whitespace = true
 insert_final_newline = true
 max_line_length = 120
-indent_size = 2
+indent_size = 4
 
 [*.md]
 trim_trailing_whitespace = false
diff --git a/.eslintrc.json b/.eslintrc.json
index e3414633..0f12126e 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,59 +1,26 @@
 {
-  "root": true,
-  "env": {
-    "es6": true
-  },
-  "extends": [
-    "airbnb",
-    "airbnb/hooks",
-    "airbnb-typescript",
-    "plugin:jest/recommended",
-    "plugin:jest/style",
-    "prettier"
-  ],
-  "parser": "@typescript-eslint/parser",
-  "parserOptions": {
-    "ecmaVersion": 6,
-    "sourceType": "module",
-    "project": ["./tsconfig.json", "./cli/**/tsconfig.json"]
-  },
-  "plugins": ["@typescript-eslint", "jest"],
-  "rules": {
-    "no-console": [
-      "warn",
-      {
-        "allow": ["info", "warn", "error"]
-      }
-    ],
-    "no-promise-executor-return": "off",
-    "no-unsafe-optional-chaining": "warn",
-    "no-restricted-syntax": "off",
-    "consistent-return": "warn",
-    "no-underscore-dangle": "off",
-    "no-param-reassign": "off",
-    "no-await-in-loop": "off",
-    "import/no-cycle": "off",
-    "import/extensions": "off",
-    "no-bitwise": "off",
-    "jest/no-disabled-tests": "warn",
-    "jest/no-focused-tests": "error",
-    "jest/no-identical-title": "error",
-    "jest/prefer-to-have-length": "warn",
-    "jest/valid-expect": "warn",
-    "class-methods-use-this": "warn",
-    "import/no-extraneous-dependencies": "off",
-    "import/no-relative-packages": "off",
-    "@typescript-eslint/no-loop-func": "off",
-    "@typescript-eslint/no-shadow": "off",
-    "@typescript-eslint/no-explicit-any": "off",
-    "@typescript-eslint/lines-between-class-members": "off",
-    "@typescript-eslint/naming-convention": [
-      "error",
-      {
-        "selector": "variable",
-        "format": ["camelCase", "PascalCase", "UPPER_CASE", "snake_case"],
-        "leadingUnderscore": "allow"
-      }
-    ]
-  }
+    "root": true,
+    "env": {
+        "es6": true
+    },
+    "extends": ["airbnb-base", "airbnb-typescript/base", "plugin:jest/recommended", "plugin:jest/style", "prettier"],
+    "parser": "@typescript-eslint/parser",
+    "parserOptions": {
+        "ecmaVersion": 6,
+        "sourceType": "module",
+        "project": ["./tsconfig.json", "./packages/*/tsconfig.json"]
+    },
+    "plugins": ["@typescript-eslint", "jest"],
+    "rules": {
+        "no-underscore-dangle": "off",
+        "import/no-extraneous-dependencies": "off",
+        "no-bitwise": "off",
+        "no-await-in-loop": "off",
+        "no-restricted-syntax": "off",
+        "no-console": ["warn", { "allow": ["info", "warn", "error", "log"] }],
+        "@typescript-eslint/lines-between-class-members": "off",
+        "no-param-reassign": "off",
+        "jest/expect-expect": "off",
+        "no-promise-executor-return": "warn"
+    }
 }
diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml
index d626be2e..46c8a9aa 100644
--- a/.github/workflows/deploy.yaml
+++ b/.github/workflows/deploy.yaml
@@ -1,22 +1,22 @@
 name: Deploy - dev environment
 on:
-  push:
-    branches:
-      - dev
+    push:
+        branches:
+            - dev
 
 jobs:
-  deploy:
-    name: Deploy
-    runs-on: ubuntu-20.04
-    steps:
-      - name: Checkout Repo
-        uses: actions/checkout@v3
+    deploy:
+        name: Deploy
+        runs-on: ubuntu-20.04
+        steps:
+            - name: Checkout Repo
+              uses: actions/checkout@v3
 
-      - name: Install Dependencies
-        run: yarn install
+            - name: Install Dependencies
+              run: yarn install
 
-      - name: Deploy to Firebase
-        run: yarn firebase:deploy
-        working-directory: firebase
-        env:
-          FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
+            - name: Deploy to Firebase
+              run: yarn firebase:deploy
+              working-directory: firebase
+              env:
+                  FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
diff --git a/.lintstagedrc.json b/.lintstagedrc.json
index acbb84b7..9afe6370 100644
--- a/.lintstagedrc.json
+++ b/.lintstagedrc.json
@@ -1,3 +1,3 @@
 {
-  "**/*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix"]
+    "**/*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix"]
 }
diff --git a/.prettierrc.json b/.prettierrc.json
index a6d13f8d..27140f2b 100644
--- a/.prettierrc.json
+++ b/.prettierrc.json
@@ -1,5 +1,5 @@
 {
-  "semi": false,
-  "arrowParens": "always",
-  "trailingComma": "none"
+    "semi": false,
+    "arrowParens": "always",
+    "trailingComma": "none"
 }
diff --git a/README.md b/README.md
index 07353cfe..7a9d3d6b 100644
--- a/README.md
+++ b/README.md
@@ -24,13 +24,13 @@ The idea is to have a NodeJS CLI published as NPM package and use Firebase Cloud
 
 ### Actors
 
-- **Coordinator**: an individual responsible for conducting and monitoring the ceremony. Basically, the coordinator have to prepare and conduct each step of the Phase 2 ceremony, from beginning to end.
-- **Participant**: an individual who wants to contribute to the ceremony. The participant computes the contribution locally on their machine, generates an attestation, and makes it publicly available to everyone.
+-   **Coordinator**: an individual responsible for conducting and monitoring the ceremony. Basically, the coordinator have to prepare and conduct each step of the Phase 2 ceremony, from beginning to end.
+-   **Participant**: an individual who wants to contribute to the ceremony. The participant computes the contribution locally on their machine, generates an attestation, and makes it publicly available to everyone.
 
 ### Components
 
-- **phase2cli**: all-in-one command-line for interfacing with zkSNARK Phase 2 Trusted Setup ceremonies. Both the participant and the coordinator can use it to interact with the ceremony, from its setup to generating a contribution.
-- **firebase**: 3rd party Firebase CLI tool used to bootstrap the project to the cloud, locally emulate functions, db, storage and rules.
+-   **phase2cli**: all-in-one command-line for interfacing with zkSNARK Phase 2 Trusted Setup ceremonies. Both the participant and the coordinator can use it to interact with the ceremony, from its setup to generating a contribution.
+-   **firebase**: 3rd party Firebase CLI tool used to bootstrap the project to the cloud, locally emulate functions, db, storage and rules.
 
 ## Getting Started
 
@@ -83,9 +83,9 @@ yarn prettier:fix
 
 What's missing
 
-- Code of conduct
-- Contributing
-- Support
-- License
+-   Code of conduct
+-   Contributing
+-   Support
+-   License
 
 **Please, follow the project boards to stay up-to-date!**
diff --git a/apps/backend/README.md b/apps/backend/README.md
deleted file mode 100644
index 1b1d426f..00000000
--- a/apps/backend/README.md
+++ /dev/null
@@ -1,18 +0,0 @@
-# mpc-phase2-suite-firebase
-
-MPC Phase 2 backend for Firebase services management
-
-- Description (w/ Features).
-- Install + Configs
-  - Firebase login
-  - Firebase init (select everything except Hosting and Remote Config)
-  - Console
-    - App
-    - Service Account Key generation
-- Usage + Examples
-- License
-- Support + FAQ?
-  - FIREBASE_CONFIG & GCLOUD_PROJECT not set warn - because we are running on NodeJS and not on cloud.
-  - Emulator scheduled functions execution: emulator terminal + emulator shell terminal (here, call scheduled function, e.g., scheduledFunction()).
-
-Coordinator Guide .md for more infos about the Firebase configuration?
diff --git a/apps/backend/firebase.json b/apps/backend/firebase.json
deleted file mode 100644
index 609a3adf..00000000
--- a/apps/backend/firebase.json
+++ /dev/null
@@ -1,36 +0,0 @@
-{
-  "firestore": {
-    "rules": "firestore.rules",
-    "indexes": "firestore.indexes.json"
-  },
-  "functions": {
-    "predeploy": "yarn --prefix \"$RESOURCE_DIR\" build",
-    "source": "."
-  },
-  "storage": {
-    "rules": "storage.rules"
-  },
-  "emulators": {
-    "auth": {
-      "port": 9099
-    },
-    "functions": {
-      "port": 5001
-    },
-    "firestore": {
-      "port": 8080
-    },
-    "database": {
-      "port": 9000
-    },
-    "pubsub": {
-      "port": 8085
-    },
-    "storage": {
-      "port": 9199
-    },
-    "ui": {
-      "enabled": true
-    }
-  }
-}
diff --git a/apps/backend/firestore.indexes.json b/apps/backend/firestore.indexes.json
deleted file mode 100644
index b9e83f41..00000000
--- a/apps/backend/firestore.indexes.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
-  "indexes": [
-    {
-      "collectionGroup": "ceremonies",
-      "queryScope": "COLLECTION",
-      "fields": [
-        {
-          "fieldPath": "state",
-          "order": "ASCENDING"
-        },
-        {
-          "fieldPath": "endDate",
-          "order": "ASCENDING"
-        }
-      ]
-    },
-    {
-      "collectionGroup": "ceremonies",
-      "queryScope": "COLLECTION",
-      "fields": [
-        {
-          "fieldPath": "state",
-          "order": "ASCENDING"
-        },
-        {
-          "fieldPath": "startDate",
-          "order": "ASCENDING"
-        }
-      ]
-    }
-  ],
-  "fieldOverrides": []
-}
diff --git a/apps/backend/package.json b/apps/backend/package.json
deleted file mode 100644
index 83a6915c..00000000
--- a/apps/backend/package.json
+++ /dev/null
@@ -1,71 +0,0 @@
-{
-  "name": "@mpcts/backend",
-  "version": "0.0.1",
-  "description": "MPC Phase 2 backend for Firebase services management",
-  "repository": "https://github.com/quadratic-funding/mpc-phase2-suite/apps/backend",
-  "homepage": "https://github.com/quadratic-funding/mpc-phase2-suite",
-  "bugs": "https://github.com/quadratic-funding/mpc-phase2-suite/issues",
-  "author": {
-    "name": "Giacomo (0xjei)"
-  },
-  "license": "MIT",
-  "private": false,
-  "main": "dist/src/functions/index.js",
-  "types": "dist/types/src/index.d.ts",
-  "type": "module",
-  "engines": {
-    "node": "16"
-  },
-  "files": [
-    "dist/",
-    "src/",
-    "test/",
-    "types",
-    "README.md"
-  ],
-  "keywords": [
-    "typescript",
-    "zero-knowledge",
-    "zk-snarks",
-    "phase-2",
-    "trusted-setup",
-    "ceremony",
-    "snarkjs",
-    "circom"
-  ],
-  "scripts": {
-    "build": "tsc",
-    "firebase:login": "firebase login",
-    "firebase:logout": "firebase logout",
-    "firebase:init": "firebase init",
-    "firebase:deploy": "yarn firestore:get-indexes && firebase deploy",
-    "firebase:deploy-functions": "firebase deploy --only functions",
-    "firebase:deploy-firestore": "yarn firestore:get-indexes && firebase deploy --only firestore",
-    "firebase:deploy-storage": "firebase deploy --only storage",
-    "firebase:log-functions": "firebase functions:log",
-    "firestore:get-indexes": "firebase firestore:indexes > firestore.indexes.json",
-    "emulator:serve": "yarn build && firebase emulators:start",
-    "emulator:serve-functions": "yarn build && firebase emulators:start --only functions",
-    "emulator:shell": "yarn build && firebase functions:shell"
-  },
-  "devDependencies": {
-    "@firebase/rules-unit-testing": "^2.0.4",
-    "@types/uuid": "^8.3.4",
-    "firebase-functions-test": "^2.3.0",
-    "firebase-tools": "^11.8.0",
-    "typescript": "^4.7.4"
-  },
-  "dependencies": {
-    "@aws-sdk/client-s3": "^3.178.0",
-    "@aws-sdk/middleware-endpoint": "^3.178.0",
-    "@aws-sdk/s3-request-presigner": "^3.178.0",
-    "blakejs": "^1.2.1",
-    "dotenv": "^16.0.1",
-    "firebase-admin": "^11.0.1",
-    "firebase-functions": "^3.22.0",
-    "snarkjs": "^0.5.0",
-    "timer-node": "^5.0.6",
-    "uuid": "^8.3.2",
-    "winston": "^3.8.1"
-  }
-}
diff --git a/apps/backend/src/functions/auth.ts b/apps/backend/src/functions/auth.ts
deleted file mode 100644
index 21fbc585..00000000
--- a/apps/backend/src/functions/auth.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import * as functions from "firebase-functions"
-import { UserRecord } from "firebase-functions/v1/auth"
-import admin from "firebase-admin"
-import dotenv from "dotenv"
-import { GENERIC_ERRORS, logMsg } from "../lib/logs.js"
-import { getCurrentServerTimestampInMillis } from "../lib/utils.js"
-import { MsgType } from "../../types/index.js"
-
-dotenv.config()
-
-/**
- * Auth-triggered function which writes a user document to Firestore.
- */
-export const registerAuthUser = functions.auth.user().onCreate(async (user: UserRecord) => {
-  // Get DB.
-  const firestore = admin.firestore()
-
-  // Get user information.
-  if (!user.uid) logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
-
-  // The user object has basic properties such as display name, email, etc.
-  const { displayName } = user
-  const { email } = user
-  const { photoURL } = user
-  const { emailVerified } = user
-
-  // Metadata.
-  const { creationTime } = user.metadata
-  const { lastSignInTime } = user.metadata
-
-  // The user's ID, unique to the Firebase project. Do NOT use
-  // this value to authenticate with your backend server, if
-  // you have one. Use User.getToken() instead.
-  const { uid } = user
-
-  // Reference to a document using uid.
-  const userRef = firestore.collection("users").doc(uid)
-
-  // Set document (nb. we refer to providerData[0] because we use Github OAuth provider only).
-  await userRef.set({
-    name: displayName,
-    // Metadata.
-    creationTime,
-    lastSignInTime,
-    // Optional.
-    email: email || "",
-    emailVerified: emailVerified || false,
-    photoURL: photoURL || "",
-    lastUpdated: getCurrentServerTimestampInMillis()
-  })
-
-  logMsg(`User ${uid} correctly stored`, MsgType.INFO)
-})
-
-/**
- * Set custom claims for role-based access control on the newly created user.
- */
-export const processSignUpWithCustomClaims = functions.auth.user().onCreate(async (user: UserRecord) => {
-  // Get user information.
-  if (!user.uid) logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
-
-  let customClaims: any
-  // Check if user meets role criteria to be a coordinator.
-  if (
-    user.email &&
-    (user.email.endsWith(`@${process.env.CUSTOM_CLAIMS_COORDINATOR_EMAIL_ADDRESS_OR_DOMAIN}`) ||
-      user.email === process.env.CUSTOM_CLAIMS_COORDINATOR_EMAIL_ADDRESS_OR_DOMAIN)
-  ) {
-    customClaims = { coordinator: true }
-
-    logMsg(`User ${user.uid} identified as coordinator`, MsgType.INFO)
-  } else {
-    customClaims = { participant: true }
-
-    logMsg(`User ${user.uid} identified as participant`, MsgType.INFO)
-  }
-
-  try {
-    // Set custom user claims on this newly created user.
-    await admin.auth().setCustomUserClaims(user.uid, customClaims)
-  } catch (error: any) {
-    logMsg(`Something went wrong: ${error.toString()}`, MsgType.ERROR)
-  }
-})
diff --git a/apps/backend/src/functions/ceremony.ts b/apps/backend/src/functions/ceremony.ts
deleted file mode 100644
index 2cfe3885..00000000
--- a/apps/backend/src/functions/ceremony.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import * as functions from "firebase-functions"
-import dotenv from "dotenv"
-import { DocumentSnapshot } from "firebase-functions/v1/firestore"
-import { CeremonyState, MsgType } from "../../types/index.js"
-import { queryCeremoniesByStateAndDate } from "../lib/utils.js"
-import { GENERIC_LOGS, logMsg } from "../lib/logs.js"
-
-dotenv.config()
-
-/**
- * Automatically look and (if any) start scheduled ceremonies.
- */
-export const startCeremony = functions.pubsub.schedule(`every 30 minutes`).onRun(async () => {
-  // Get ceremonies in `scheduled` state.
-  const scheduledCeremoniesQuerySnap = await queryCeremoniesByStateAndDate(CeremonyState.SCHEDULED, "startDate", "<=")
-
-  if (scheduledCeremoniesQuerySnap.empty) logMsg(GENERIC_LOGS.GENLOG_NO_CEREMONIES_READY_TO_BE_OPENED, MsgType.INFO)
-  else {
-    scheduledCeremoniesQuerySnap.forEach(async (ceremonyDoc: DocumentSnapshot) => {
-      logMsg(`Ceremony ${ceremonyDoc.id} opened`, MsgType.INFO)
-
-      // Update ceremony state to `running`.
-      await ceremonyDoc.ref.set({ state: CeremonyState.OPENED }, { merge: true })
-    })
-  }
-})
-
-/**
- * Automatically look and (if any) stop running ceremonies.
- */
-export const stopCeremony = functions.pubsub.schedule(`every 30 minutes`).onRun(async () => {
-  // Get ceremonies in `running` state.
-  const runningCeremoniesQuerySnap = await queryCeremoniesByStateAndDate(CeremonyState.OPENED, "endDate", "<=")
-
-  if (runningCeremoniesQuerySnap.empty) logMsg(GENERIC_LOGS.GENLOG_NO_CEREMONIES_READY_TO_BE_CLOSED, MsgType.INFO)
-  else {
-    runningCeremoniesQuerySnap.forEach(async (ceremonyDoc: DocumentSnapshot) => {
-      logMsg(`Ceremony ${ceremonyDoc.id} closed`, MsgType.INFO)
-
-      // Update ceremony state to `finished`.
-      await ceremonyDoc.ref.set({ state: CeremonyState.CLOSED }, { merge: true })
-    })
-  }
-})
diff --git a/apps/backend/src/functions/contribute.ts b/apps/backend/src/functions/contribute.ts
deleted file mode 100644
index 320612cf..00000000
--- a/apps/backend/src/functions/contribute.ts
+++ /dev/null
@@ -1,644 +0,0 @@
-import * as functions from "firebase-functions"
-import admin from "firebase-admin"
-import dotenv from "dotenv"
-import {
-  CeremonyState,
-  CeremonyTimeoutType,
-  MsgType,
-  ParticipantContributionStep,
-  ParticipantStatus,
-  TimeoutType
-} from "../../types/index.js"
-import { GENERIC_ERRORS, GENERIC_LOGS, logMsg } from "../lib/logs.js"
-import { collections, timeoutsCollectionFields } from "../lib/constants.js"
-import {
-  getCeremonyCircuits,
-  getCurrentServerTimestampInMillis,
-  getParticipantById,
-  queryCeremoniesByStateAndDate,
-  queryValidTimeoutsByDate
-} from "../lib/utils.js"
-
-dotenv.config()
-
-/**
- * Check if a user can participate for the given ceremony (e.g., new contributor, after timeout expiration, etc.).
- */
-export const checkParticipantForCeremony = functions.https.onCall(
-  async (data: any, context: functions.https.CallableContext) => {
-    console.log(context.auth)
-    console.log(context.auth?.token.participant)
-    console.log(context.auth?.token.coordinator)
-
-    // Check if sender is authenticated.
-    if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
-      logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
-
-    if (!data.ceremonyId) logMsg(GENERIC_ERRORS.GENERR_NO_CEREMONY_PROVIDED, MsgType.ERROR)
-
-    // Get DB.
-    const firestore = admin.firestore()
-
-    // Get data.
-    const { ceremonyId } = data
-    const userId = context.auth?.uid
-
-    // Look for the ceremony.
-    const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
-
-    // Check existence.
-    if (!ceremonyDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_CEREMONY, MsgType.ERROR)
-
-    // Get ceremony data.
-    const ceremonyData = ceremonyDoc.data()
-
-    // Check if running.
-    if (!ceremonyData || ceremonyData.state !== CeremonyState.OPENED)
-      logMsg(GENERIC_ERRORS.GENERR_CEREMONY_NOT_OPENED, MsgType.ERROR)
-
-    // Look for the user among ceremony participants.
-    const participantDoc = await firestore
-      .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
-      .doc(userId!)
-      .get()
-
-    if (!participantDoc.exists) {
-      // Create a new Participant doc for the sender.
-      await participantDoc.ref.set({
-        status: ParticipantStatus.WAITING,
-        contributionProgress: 0,
-        contributions: [],
-        lastUpdated: getCurrentServerTimestampInMillis()
-      })
-
-      logMsg(`User ${userId} has been registered as participant for ceremony ${ceremonyDoc.id}`, MsgType.INFO)
-    } else {
-      // Check if the participant has completed the contributions for all circuits.
-      const participantData = participantDoc.data()
-
-      if (!participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
-
-      logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
-
-      const circuits = await getCeremonyCircuits(`${collections.ceremonies}/${ceremonyDoc.id}/${collections.circuits}`)
-
-      // Already contributed to all circuits or currently contributor without any timeout.
-      if (
-        participantData?.contributionProgress === circuits.length &&
-        participantData?.status === ParticipantStatus.DONE
-      ) {
-        logMsg(
-          `Participant ${participantDoc.id} has already contributed to all circuits or is the current contributor to that circuit (no timed out yet)`,
-          MsgType.DEBUG
-        )
-
-        return false
-      }
-
-      if (participantData?.status === ParticipantStatus.TIMEDOUT) {
-        // Get `valid` timeouts (i.e., endDate is not expired).
-        const validTimeoutsQuerySnap = await queryValidTimeoutsByDate(
-          ceremonyDoc.id,
-          participantDoc.id,
-          timeoutsCollectionFields.endDate
-        )
-
-        if (validTimeoutsQuerySnap.empty) {
-          // TODO: need to remove unstable contributions (only one without doc link) and temp data, contributor must restart from step 1.
-          // The participant can retry the contribution.
-          await participantDoc.ref.set(
-            {
-              status: ParticipantStatus.EXHUMED,
-              contributionStep: ParticipantContributionStep.DOWNLOADING,
-              lastUpdated: getCurrentServerTimestampInMillis()
-            },
-            { merge: true }
-          )
-
-          logMsg(`Participant ${participantDoc.id} can retry the contribution from right now`, MsgType.DEBUG)
-
-          return true
-        }
-        logMsg(`Participant ${participantDoc.id} cannot retry the contribution yet`, MsgType.DEBUG)
-
-        return false
-      }
-    }
-
-    return true
-  }
-)
-
-/**
- * Check and remove the current contributor who is taking more than a specified amount of time for completing the contribution.
- */
-export const checkAndRemoveBlockingContributor = functions.pubsub.schedule("every 1 minutes").onRun(async () => {
-  if (!process.env.CF_RETRY_WAITING_TIME_IN_DAYS) logMsg(GENERIC_ERRORS.GENERR_WRONG_ENV_CONFIGURATION, MsgType.ERROR)
-
-  // Get DB.
-  const firestore = admin.firestore()
-  const currentDate = getCurrentServerTimestampInMillis()
-
-  // Get ceremonies in `opened` state.
-  const openedCeremoniesQuerySnap = await queryCeremoniesByStateAndDate(CeremonyState.OPENED, "endDate", ">=")
-
-  if (openedCeremoniesQuerySnap.empty) logMsg(GENERIC_ERRORS.GENERR_NO_CEREMONIES_OPENED, MsgType.ERROR)
-
-  // For each ceremony.
-  for (const ceremonyDoc of openedCeremoniesQuerySnap.docs) {
-    if (!ceremonyDoc.exists || !ceremonyDoc.data()) logMsg(GENERIC_ERRORS.GENERR_INVALID_CEREMONY, MsgType.ERROR)
-
-    logMsg(`Ceremony document ${ceremonyDoc.id} okay`, MsgType.DEBUG)
-
-    // Get data.
-    const { timeoutType: ceremonyTimeoutType, penalty } = ceremonyDoc.data()
-
-    // Get circuits.
-    const circuitsDocs = await getCeremonyCircuits(
-      `${collections.ceremonies}/${ceremonyDoc.id}/${collections.circuits}`
-    )
-
-    // For each circuit.
-    for (const circuitDoc of circuitsDocs) {
-      if (!circuitDoc.exists || !circuitDoc.data()) logMsg(GENERIC_ERRORS.GENERR_INVALID_CIRCUIT, MsgType.ERROR)
-
-      const circuitData = circuitDoc.data()
-
-      logMsg(`Circuit document ${circuitDoc.id} okay`, MsgType.DEBUG)
-
-      // Get data.
-      const { waitingQueue, avgTimings } = circuitData
-      const { contributors, currentContributor, failedContributions, completedContributions } = waitingQueue
-      const { fullContribution: avgFullContribution } = avgTimings
-
-      // Check for current contributor.
-      if (!currentContributor) logMsg(GENERIC_ERRORS.GENERR_NO_CURRENT_CONTRIBUTOR, MsgType.WARN)
-
-      // Check if first contributor.
-      if (
-        !currentContributor &&
-        avgFullContribution === 0 &&
-        completedContributions === 0 &&
-        ceremonyTimeoutType === CeremonyTimeoutType.DYNAMIC
-      )
-        logMsg(GENERIC_ERRORS.GENERR_NO_TIMEOUT_FIRST_COTRIBUTOR, MsgType.DEBUG)
-
-      if (
-        !!currentContributor &&
-        ((avgFullContribution > 0 && completedContributions > 0) || ceremonyTimeoutType === CeremonyTimeoutType.FIXED)
-      ) {
-        // Get current contributor data (i.e., participant).
-        const participantDoc = await getParticipantById(ceremonyDoc.id, currentContributor)
-
-        if (!participantDoc.exists || !participantDoc.data())
-          logMsg(GENERIC_ERRORS.GENERR_INVALID_PARTICIPANT, MsgType.WARN)
-        else {
-          const participantData = participantDoc.data()
-          const contributionStartedAt = participantData?.contributionStartedAt
-          const verificationStartedAt = participantData?.verificationStartedAt
-          const currentContributionStep = participantData?.contributionStep
-
-          logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
-
-          // Check for blocking contributions (frontend-side).
-          const timeoutToleranceThreshold =
-            ceremonyTimeoutType === CeremonyTimeoutType.DYNAMIC
-              ? (avgFullContribution / 100) * Number(circuitData.timeoutThreshold)
-              : 0
-
-          const timeoutExpirationDateInMillisForBlockingContributor =
-            ceremonyTimeoutType === CeremonyTimeoutType.DYNAMIC
-              ? Number(contributionStartedAt) + Number(avgFullContribution) + Number(timeoutToleranceThreshold)
-              : Number(contributionStartedAt) + Number(circuitData.timeoutMaxContributionWaitingTime) * 60000
-
-          logMsg(`Contribution start date ${contributionStartedAt}`, MsgType.DEBUG)
-          if (ceremonyTimeoutType === CeremonyTimeoutType.DYNAMIC) {
-            logMsg(`Average contribution per circuit time ${avgFullContribution} ms`, MsgType.DEBUG)
-            logMsg(`Timeout tolerance threshold set to ${timeoutToleranceThreshold}`, MsgType.DEBUG)
-          }
-          logMsg(`BC Timeout expirartion date ${timeoutExpirationDateInMillisForBlockingContributor} ms`, MsgType.DEBUG)
-
-          // Check for blocking verifications (backend-side).
-          const timeoutExpirationDateInMillisForBlockingFunction = !verificationStartedAt
-            ? 0
-            : Number(verificationStartedAt) + 3540000 // 3540000 = 59 minutes in ms.
-
-          logMsg(`Verification start date ${verificationStartedAt}`, MsgType.DEBUG)
-          logMsg(`CF Timeout expirartion date ${timeoutExpirationDateInMillisForBlockingFunction} ms`, MsgType.DEBUG)
-
-          // Get timeout type.
-          let timeoutType = 0
-
-          if (
-            timeoutExpirationDateInMillisForBlockingContributor < currentDate &&
-            currentContributionStep >= ParticipantContributionStep.DOWNLOADING &&
-            currentContributionStep <= ParticipantContributionStep.UPLOADING
-          )
-            timeoutType = TimeoutType.BLOCKING_CONTRIBUTION
-
-          if (
-            timeoutExpirationDateInMillisForBlockingFunction > 0 &&
-            timeoutExpirationDateInMillisForBlockingFunction < currentDate &&
-            currentContributionStep === ParticipantContributionStep.VERIFYING
-          )
-            timeoutType = TimeoutType.BLOCKING_CLOUD_FUNCTION
-
-          logMsg(`Ceremony Timeout type ${ceremonyTimeoutType}`, MsgType.DEBUG)
-          logMsg(`Timeout type ${timeoutType}`, MsgType.DEBUG)
-
-          // Check if one timeout should be triggered.
-          if (timeoutType !== 0) {
-            // Timeout the participant.
-            const batch = firestore.batch()
-
-            // 1. Update circuit' waiting queue.
-            contributors.shift(1)
-
-            let newCurrentContributor = ""
-
-            if (contributors.length > 0) {
-              // There's someone else ready to contribute.
-              newCurrentContributor = contributors.at(0)
-
-              // Pass the baton to the next participant.
-              const newCurrentContributorDoc = await firestore
-                .collection(`${collections.ceremonies}/${ceremonyDoc.id}/${collections.participants}`)
-                .doc(newCurrentContributor)
-                .get()
-
-              if (newCurrentContributorDoc.exists) {
-                batch.update(newCurrentContributorDoc.ref, {
-                  status: ParticipantStatus.WAITING,
-                  lastUpdated: getCurrentServerTimestampInMillis()
-                })
-              }
-            }
-
-            batch.update(circuitDoc.ref, {
-              waitingQueue: {
-                ...circuitData.waitingQueue,
-                contributors,
-                currentContributor: newCurrentContributor,
-                failedContributions: failedContributions + 1
-              },
-              lastUpdated: getCurrentServerTimestampInMillis()
-            })
-
-            logMsg(`Batch: update for circuit' waiting queue`, MsgType.DEBUG)
-
-            // 2. Change blocking contributor status.
-            batch.update(participantDoc.ref, {
-              status: ParticipantStatus.TIMEDOUT,
-              lastUpdated: getCurrentServerTimestampInMillis()
-            })
-
-            logMsg(`Batch: change blocking contributor status to TIMEDOUT`, MsgType.DEBUG)
-
-            // 3. Create a new collection of timeouts (to keep track of participants timeouts).
-            const retryWaitingTimeInMillis =
-              timeoutType === TimeoutType.BLOCKING_CONTRIBUTION
-                ? Number(penalty) * 60000 // 60000 = amount of ms x minute.
-                : Number(process.env.CF_RETRY_WAITING_TIME_IN_DAYS) * 86400000 // 86400000 = amount of ms x day.
-
-            // Timeout collection.
-            const timeoutDoc = await firestore
-              .collection(
-                `${collections.ceremonies}/${ceremonyDoc.id}/${collections.participants}/${participantDoc.id}/${collections.timeouts}`
-              )
-              .doc()
-              .get()
-
-            batch.create(timeoutDoc.ref, {
-              type: timeoutType,
-              startDate: currentDate,
-              endDate: currentDate + retryWaitingTimeInMillis
-            })
-
-            logMsg(`Batch: add timeout document for blocking contributor`, MsgType.DEBUG)
-
-            await batch.commit()
-
-            logMsg(`Blocking contributor ${participantDoc.id} timedout. Cause ${timeoutType}`, MsgType.INFO)
-          } else logMsg(GENERIC_LOGS.GENLOG_NO_TIMEOUT, MsgType.INFO)
-        }
-      }
-    }
-  }
-})
-
-/**
- * Progress to next contribution step for the current contributor of a specified circuit in a given ceremony.
- */
-export const progressToNextContributionStep = functions.https.onCall(
-  async (data: any, context: functions.https.CallableContext) => {
-    // Check if sender is authenticated.
-    if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
-      logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
-
-    if (!data.ceremonyId) logMsg(GENERIC_ERRORS.GENERR_NO_CEREMONY_PROVIDED, MsgType.ERROR)
-
-    // Get DB.
-    const firestore = admin.firestore()
-
-    // Get data.
-    const { ceremonyId } = data
-    const userId = context.auth?.uid
-
-    // Look for the ceremony.
-    const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
-
-    // Check existence.
-    if (!ceremonyDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_CEREMONY, MsgType.ERROR)
-
-    // Get ceremony data.
-    const ceremonyData = ceremonyDoc.data()
-
-    // Check if running.
-    if (!ceremonyData || ceremonyData.state !== CeremonyState.OPENED)
-      logMsg(GENERIC_ERRORS.GENERR_CEREMONY_NOT_OPENED, MsgType.ERROR)
-
-    logMsg(`Ceremony document ${ceremonyId} okay`, MsgType.DEBUG)
-
-    // Look for the user among ceremony participants.
-    const participantDoc = await firestore
-      .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
-      .doc(userId!)
-      .get()
-
-    // Check existence.
-    if (!participantDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_PARTICIPANT, MsgType.ERROR)
-
-    // Get participant data.
-    const participantData = participantDoc.data()
-
-    if (!participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
-
-    logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
-
-    // Check if participant is able to advance to next contribution step.
-    if (participantData?.status !== ParticipantStatus.CONTRIBUTING)
-      logMsg(`Participant ${participantDoc.id} is not contributing`, MsgType.ERROR)
-
-    // Make the advancement.
-    const progress = participantData?.contributionStep + 1
-
-    logMsg(`Current contribution step should be ${participantData?.contributionStep}`, MsgType.DEBUG)
-    logMsg(`Next contribution step should be ${progress}`, MsgType.DEBUG)
-
-    // nb. DOWNLOADING (=1) must be set when coordinating the waiting queue while COMPLETED (=5) must be set in verifyContribution().
-    if (progress <= ParticipantContributionStep.DOWNLOADING || progress >= ParticipantContributionStep.COMPLETED)
-      logMsg(`Wrong contribution step ${progress} for ${participantDoc.id}`, MsgType.ERROR)
-
-    if (progress === ParticipantContributionStep.VERIFYING)
-      await participantDoc.ref.update({
-        contributionStep: progress,
-        verificationStartedAt: getCurrentServerTimestampInMillis(),
-        lastUpdated: getCurrentServerTimestampInMillis()
-      })
-    else
-      await participantDoc.ref.update({
-        contributionStep: progress,
-        lastUpdated: getCurrentServerTimestampInMillis()
-      })
-  }
-)
-
-/**
- * Temporary store the contribution computation time for the current contributor.
- */
-export const temporaryStoreCurrentContributionComputationTime = functions.https.onCall(
-  async (data: any, context: functions.https.CallableContext) => {
-    // Check if sender is authenticated.
-    if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
-      logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
-
-    if (!data.ceremonyId || data.contributionComputationTime <= 0)
-      logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
-
-    // Get DB.
-    const firestore = admin.firestore()
-
-    // Get data.
-    const { ceremonyId } = data
-    const userId = context.auth?.uid
-
-    // Look for documents.
-    const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
-    const participantDoc = await firestore
-      .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
-      .doc(userId!)
-      .get()
-
-    // Check existence.
-    if (!ceremonyDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_CEREMONY, MsgType.ERROR)
-    if (!participantDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_PARTICIPANT, MsgType.ERROR)
-
-    // Get data.
-    const participantData = participantDoc.data()
-
-    if (!participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
-
-    logMsg(`Ceremony document ${ceremonyId} okay`, MsgType.DEBUG)
-    logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
-
-    // Check if has reached the computing step while contributing.
-    if (participantData?.contributionStep !== ParticipantContributionStep.COMPUTING)
-      logMsg(GENERIC_ERRORS.GENERR_INVALID_PARTICIPANT_CONTRIBUTION_STEP, MsgType.ERROR)
-
-    // Update.
-    await participantDoc.ref.set(
-      {
-        ...participantData!,
-        tempContributionData: {
-          contributionComputationTime: data.contributionComputationTime
-        },
-        lastUpdated: getCurrentServerTimestampInMillis()
-      },
-      { merge: true }
-    )
-  }
-)
-
-/**
- * Permanently store the contribution computation hash for attestation generation for the current contributor.
- */
-export const permanentlyStoreCurrentContributionTimeAndHash = functions.https.onCall(
-  async (data: any, context: functions.https.CallableContext) => {
-    // Check if sender is authenticated.
-    if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
-      logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
-
-    if (!data.ceremonyId || data.contributionComputationTime <= 0 || !data.contributionHash)
-      logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
-
-    // Get DB.
-    const firestore = admin.firestore()
-
-    // Get data.
-    const { ceremonyId } = data
-    const userId = context.auth?.uid
-
-    // Look for documents.
-    const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
-    const participantDoc = await firestore
-      .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
-      .doc(userId!)
-      .get()
-
-    // Check existence.
-    if (!ceremonyDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_CEREMONY, MsgType.ERROR)
-    if (!participantDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_PARTICIPANT, MsgType.ERROR)
-
-    // Get data.
-    const participantData = participantDoc.data()
-
-    if (!participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
-
-    logMsg(`Ceremony document ${ceremonyId} okay`, MsgType.DEBUG)
-    logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
-
-    // Check if has reached the computing step while contributing or is finalizing.
-    if (
-      participantData?.contributionStep === ParticipantContributionStep.COMPUTING ||
-      (context?.auth?.token.coordinator && participantData?.status === ParticipantStatus.FINALIZING)
-    )
-      // Update.
-      await participantDoc.ref.set(
-        {
-          ...participantData!,
-          contributions: [
-            ...participantData!.contributions,
-            {
-              hash: data.contributionHash!,
-              computationTime: data.contributionComputationTime
-            }
-          ],
-          lastUpdated: getCurrentServerTimestampInMillis()
-        },
-        { merge: true }
-      )
-    else logMsg(GENERIC_ERRORS.GENERR_INVALID_PARTICIPANT_CONTRIBUTION_STEP, MsgType.ERROR)
-  }
-)
-
-/**
- * Temporary store the the Multi-Part Upload identifier for the current contributor.
- */
-export const temporaryStoreCurrentContributionMultiPartUploadId = functions.https.onCall(
-  async (data: any, context: functions.https.CallableContext) => {
-    // Check if sender is authenticated.
-    if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
-      logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
-
-    if (!data.ceremonyId || !data.uploadId) logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
-
-    // Get DB.
-    const firestore = admin.firestore()
-
-    // Get data.
-    const { ceremonyId } = data
-    const userId = context.auth?.uid
-
-    // Look for documents.
-    const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
-    const participantDoc = await firestore
-      .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
-      .doc(userId!)
-      .get()
-
-    // Check existence.
-    if (!ceremonyDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_CEREMONY, MsgType.ERROR)
-    if (!participantDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_PARTICIPANT, MsgType.ERROR)
-
-    // Get data.
-    const participantData = participantDoc.data()
-
-    if (!participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
-
-    logMsg(`Ceremony document ${ceremonyId} okay`, MsgType.DEBUG)
-    logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
-
-    // Check if has reached the uploading step while contributing.
-    if (participantData?.contributionStep !== ParticipantContributionStep.UPLOADING)
-      logMsg(GENERIC_ERRORS.GENERR_INVALID_PARTICIPANT_CONTRIBUTION_STEP, MsgType.ERROR)
-
-    // Update.
-    await participantDoc.ref.set(
-      {
-        ...participantData!,
-        tempContributionData: {
-          ...participantData?.tempContributionData,
-          uploadId: data.uploadId,
-          chunks: []
-        },
-        lastUpdated: getCurrentServerTimestampInMillis()
-      },
-      { merge: true }
-    )
-  }
-)
-
-/**
- * Temporary store the ETag and PartNumber for each uploaded chunk in order to make the upload resumable from last chunk.
- */
-export const temporaryStoreCurrentContributionUploadedChunkData = functions.https.onCall(
-  async (data: any, context: functions.https.CallableContext) => {
-    // Check if sender is authenticated.
-    if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
-      logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
-
-    if (!data.ceremonyId || !data.eTag || data.partNumber <= 0)
-      logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
-
-    // Get DB.
-    const firestore = admin.firestore()
-
-    // Get data.
-    const { ceremonyId } = data
-    const userId = context.auth?.uid
-
-    // Look for documents.
-    const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
-    const participantDoc = await firestore
-      .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
-      .doc(userId!)
-      .get()
-
-    // Check existence.
-    if (!ceremonyDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_CEREMONY, MsgType.ERROR)
-    if (!participantDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_PARTICIPANT, MsgType.ERROR)
-
-    // Get data.
-    const participantData = participantDoc.data()
-
-    if (!participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
-
-    logMsg(`Ceremony document ${ceremonyId} okay`, MsgType.DEBUG)
-    logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
-
-    // Check if has reached the uploading step while contributing.
-    if (participantData?.contributionStep !== ParticipantContributionStep.UPLOADING)
-      logMsg(GENERIC_ERRORS.GENERR_INVALID_PARTICIPANT_CONTRIBUTION_STEP, MsgType.ERROR)
-
-    const chunks = participantData?.tempContributionData.chunks ? participantData?.tempContributionData.chunks : []
-
-    // Add last chunk.
-    chunks.push({
-      ETag: data.eTag,
-      PartNumber: data.partNumber
-    })
-
-    // Update.
-    await participantDoc.ref.set(
-      {
-        ...participantData!,
-        tempContributionData: {
-          ...participantData?.tempContributionData,
-          chunks
-        },
-        lastUpdated: getCurrentServerTimestampInMillis()
-      },
-      { merge: true }
-    )
-  }
-)
diff --git a/apps/backend/src/functions/finalize.ts b/apps/backend/src/functions/finalize.ts
deleted file mode 100644
index 0917fbf2..00000000
--- a/apps/backend/src/functions/finalize.ts
+++ /dev/null
@@ -1,240 +0,0 @@
-import * as functions from "firebase-functions"
-import admin from "firebase-admin"
-import path from "path"
-import os from "os"
-import fs from "fs"
-import blake from "blakejs"
-import { logMsg, GENERIC_ERRORS } from "../lib/logs.js"
-import { collections } from "../lib/constants.js"
-import { CeremonyState, MsgType, ParticipantStatus } from "../../types/index.js"
-import {
-  getCeremonyCircuits,
-  getCurrentServerTimestampInMillis,
-  getFinalContributionDocument,
-  getS3Client,
-  tempDownloadFromBucket
-} from "../lib/utils.js"
-
-/**
- * Check and prepare the coordinator for the ceremony finalization.
- */
-export const checkAndPrepareCoordinatorForFinalization = functions.https.onCall(
-  async (data: any, context: functions.https.CallableContext) => {
-    // Check if sender is authenticated.
-    if (!context.auth || !context.auth.token.coordinator)
-      logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
-
-    if (!data.ceremonyId) logMsg(GENERIC_ERRORS.GENERR_NO_CEREMONY_PROVIDED, MsgType.ERROR)
-
-    // Get DB.
-    const firestore = admin.firestore()
-
-    // Get data.
-    const { ceremonyId } = data
-    const userId = context.auth?.uid
-
-    // Look for the ceremony.
-    const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
-
-    // Check existence.
-    if (!ceremonyDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_CEREMONY, MsgType.ERROR)
-
-    // Get ceremony data.
-    const ceremonyData = ceremonyDoc.data()
-
-    // Check if running.
-    if (!ceremonyData || ceremonyData.state !== CeremonyState.CLOSED)
-      logMsg(GENERIC_ERRORS.GENERR_CEREMONY_NOT_CLOSED, MsgType.ERROR)
-
-    // Look for the coordinator among ceremony participant.
-    const participantDoc = await firestore
-      .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
-      .doc(userId!)
-      .get()
-
-    // Check if the coordinator has completed the contributions for all circuits.
-    const participantData = participantDoc.data()
-
-    if (!participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
-
-    logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
-
-    const circuits = await getCeremonyCircuits(`${collections.ceremonies}/${ceremonyDoc.id}/${collections.circuits}`)
-
-    // Already contributed to all circuits.
-    if (
-      participantData?.contributionProgress === circuits.length + 1 ||
-      participantData?.status === ParticipantStatus.DONE
-    ) {
-      // Update participant status.
-      await participantDoc.ref.set(
-        {
-          status: ParticipantStatus.FINALIZING,
-          lastUpdated: getCurrentServerTimestampInMillis()
-        },
-        { merge: true }
-      )
-
-      logMsg(`Coordinator ${participantDoc.id} ready for finalization`, MsgType.DEBUG)
-
-      return true
-    }
-    return false
-  }
-)
-
-/**
- * Add Verifier smart contract and verification key files metadata to the last final contribution for verifiability/integrity of the ceremony.
- */
-export const finalizeLastContribution = functions.https.onCall(
-  async (data: any, context: functions.https.CallableContext): Promise<any> => {
-    if (!context.auth || !context.auth.token.coordinator) logMsg(GENERIC_ERRORS.GENERR_NO_COORDINATOR, MsgType.ERROR)
-
-    if (!data.ceremonyId || !data.circuitId || !data.bucketName)
-      logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
-
-    // Get DB.
-    const firestore = admin.firestore()
-
-    // Get Storage.
-    const S3 = await getS3Client()
-
-    // Get data.
-    const { ceremonyId, circuitId, bucketName } = data
-    const userId = context.auth?.uid
-
-    // Look for documents.
-    const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
-    const circuitDoc = await firestore
-      .collection(`${collections.ceremonies}/${ceremonyId}/${collections.circuits}`)
-      .doc(circuitId)
-      .get()
-    const participantDoc = await firestore
-      .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
-      .doc(userId!)
-      .get()
-    const contributionDoc = await getFinalContributionDocument(
-      `${collections.ceremonies}/${ceremonyId}/${collections.circuits}/${circuitId}/${collections.contributions}`
-    )
-
-    if (!ceremonyDoc.exists || !circuitDoc.exists || !participantDoc.exists || !contributionDoc.exists)
-      logMsg(GENERIC_ERRORS.GENERR_INVALID_DOCUMENTS, MsgType.ERROR)
-
-    // Get data from docs.
-    const ceremonyData = ceremonyDoc.data()
-    const circuitData = circuitDoc.data()
-    const participantData = participantDoc.data()
-    const contributionData = contributionDoc.data()
-
-    if (!ceremonyData || !circuitData || !participantData || !contributionData)
-      logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
-
-    logMsg(`Ceremony document ${ceremonyDoc.id} okay`, MsgType.DEBUG)
-    logMsg(`Circuit document ${circuitDoc.id} okay`, MsgType.DEBUG)
-    logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
-    logMsg(`Contribution document ${contributionDoc.id} okay`, MsgType.DEBUG)
-
-    // Filenames.
-    const verificationKeyFilename = `${circuitData?.prefix}_vkey.json`
-    const verifierContractFilename = `${circuitData?.prefix}_verifier.sol`
-
-    // Get storage paths.
-    const verificationKeyStoragePath = `${collections.circuits}/${circuitData?.prefix}/${verificationKeyFilename}`
-    const verifierContractStoragePath = `${collections.circuits}/${circuitData?.prefix}/${verifierContractFilename}`
-
-    // Temporary store files from bucket.
-    const verificationKeyTmpFilePath = path.join(os.tmpdir(), verificationKeyFilename)
-    const verifierContractTmpFilePath = path.join(os.tmpdir(), verifierContractFilename)
-
-    await tempDownloadFromBucket(S3, bucketName, verificationKeyStoragePath, verificationKeyTmpFilePath)
-    await tempDownloadFromBucket(S3, bucketName, verifierContractStoragePath, verifierContractTmpFilePath)
-
-    // Compute blake2b hash before unlink.
-    const verificationKeyBuffer = fs.readFileSync(verificationKeyTmpFilePath)
-    const verifierContractBuffer = fs.readFileSync(verifierContractTmpFilePath)
-
-    logMsg(`Downloads from storage completed`, MsgType.INFO)
-
-    const verificationKeyBlake2bHash = blake.blake2bHex(verificationKeyBuffer)
-    const verifierContractBlake2bHash = blake.blake2bHex(verifierContractBuffer)
-
-    // Unlink folders.
-    fs.unlinkSync(verificationKeyTmpFilePath)
-    fs.unlinkSync(verifierContractTmpFilePath)
-
-    // Update DB.
-    const batch = firestore.batch()
-
-    batch.update(contributionDoc.ref, {
-      files: {
-        ...contributionData?.files,
-        verificationKeyBlake2bHash,
-        verificationKeyFilename,
-        verificationKeyStoragePath,
-        verifierContractBlake2bHash,
-        verifierContractFilename,
-        verifierContractStoragePath
-      },
-      lastUpdated: getCurrentServerTimestampInMillis()
-    })
-
-    await batch.commit()
-
-    logMsg(
-      `Circuit ${circuitId} correctly finalized - Ceremony ${ceremonyDoc.id} - Coordinator ${participantDoc.id}`,
-      MsgType.INFO
-    )
-  }
-)
-
-/**
- * Finalize a closed ceremony.
- */
-export const finalizeCeremony = functions.https.onCall(
-  async (data: any, context: functions.https.CallableContext): Promise<any> => {
-    if (!context.auth || !context.auth.token.coordinator) logMsg(GENERIC_ERRORS.GENERR_NO_COORDINATOR, MsgType.ERROR)
-
-    if (!data.ceremonyId) logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
-
-    // Get DB.
-    const firestore = admin.firestore()
-    // Update DB.
-    const batch = firestore.batch()
-
-    const { ceremonyId } = data
-    const userId = context.auth?.uid
-
-    // Look for documents.
-    const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
-    const participantDoc = await firestore
-      .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
-      .doc(userId!)
-      .get()
-
-    if (!ceremonyDoc.exists || !participantDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_DOCUMENTS, MsgType.ERROR)
-
-    // Get data from docs.
-    const ceremonyData = ceremonyDoc.data()
-    const participantData = participantDoc.data()
-
-    if (!ceremonyData || !participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
-
-    logMsg(`Ceremony document ${ceremonyDoc.id} okay`, MsgType.DEBUG)
-    logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
-
-    // Check if the ceremony has state equal to closed.
-    if (ceremonyData?.state === CeremonyState.CLOSED && participantData?.status === ParticipantStatus.FINALIZING) {
-      // Finalize the ceremony.
-      batch.update(ceremonyDoc.ref, { state: CeremonyState.FINALIZED })
-
-      // Update coordinator status.
-      batch.update(participantDoc.ref, {
-        status: ParticipantStatus.FINALIZED
-      })
-
-      await batch.commit()
-
-      logMsg(`Ceremony ${ceremonyDoc.id} correctly finalized - Coordinator ${participantDoc.id}`, MsgType.INFO)
-    }
-  }
-)
diff --git a/apps/backend/src/functions/index.ts b/apps/backend/src/functions/index.ts
deleted file mode 100644
index 1c2f2a0c..00000000
--- a/apps/backend/src/functions/index.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import admin from "firebase-admin"
-import { registerAuthUser, processSignUpWithCustomClaims } from "./auth.js"
-import { startCeremony, stopCeremony } from "./ceremony.js"
-import { setupCeremony, initEmptyWaitingQueueForCircuit } from "./setup.js"
-import {
-  checkParticipantForCeremony,
-  checkAndRemoveBlockingContributor,
-  progressToNextContributionStep,
-  temporaryStoreCurrentContributionComputationTime,
-  permanentlyStoreCurrentContributionTimeAndHash,
-  temporaryStoreCurrentContributionMultiPartUploadId,
-  temporaryStoreCurrentContributionUploadedChunkData
-} from "./contribute.js"
-import {
-  coordinateContributors,
-  verifycontribution,
-  refreshParticipantAfterContributionVerification,
-  makeProgressToNextContribution,
-  resumeContributionAfterTimeoutExpiration
-} from "./waitingQueue.js"
-import { checkAndPrepareCoordinatorForFinalization, finalizeLastContribution, finalizeCeremony } from "./finalize.js"
-import {
-  createBucket,
-  checkIfObjectExist,
-  generateGetObjectPreSignedUrl,
-  startMultiPartUpload,
-  generatePreSignedUrlsParts,
-  completeMultiPartUpload
-} from "./storage.js"
-
-admin.initializeApp()
-
-export {
-  registerAuthUser,
-  processSignUpWithCustomClaims,
-  startCeremony,
-  stopCeremony,
-  checkAndPrepareCoordinatorForFinalization,
-  finalizeLastContribution,
-  finalizeCeremony,
-  setupCeremony,
-  initEmptyWaitingQueueForCircuit,
-  checkParticipantForCeremony,
-  checkAndRemoveBlockingContributor,
-  progressToNextContributionStep,
-  temporaryStoreCurrentContributionComputationTime,
-  permanentlyStoreCurrentContributionTimeAndHash,
-  temporaryStoreCurrentContributionMultiPartUploadId,
-  temporaryStoreCurrentContributionUploadedChunkData,
-  coordinateContributors,
-  verifycontribution,
-  refreshParticipantAfterContributionVerification,
-  makeProgressToNextContribution,
-  resumeContributionAfterTimeoutExpiration,
-  createBucket,
-  checkIfObjectExist,
-  generateGetObjectPreSignedUrl,
-  startMultiPartUpload,
-  generatePreSignedUrlsParts,
-  completeMultiPartUpload
-}
diff --git a/apps/backend/src/functions/setup.ts b/apps/backend/src/functions/setup.ts
deleted file mode 100644
index ad21cdcd..00000000
--- a/apps/backend/src/functions/setup.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-import * as functions from "firebase-functions"
-import admin from "firebase-admin"
-import dotenv from "dotenv"
-import { QueryDocumentSnapshot } from "firebase-functions/v1/firestore"
-import { CeremonyState, CeremonyType, MsgType } from "../../types/index.js"
-import { GENERIC_ERRORS, logMsg } from "../lib/logs.js"
-import { getCurrentServerTimestampInMillis } from "../lib/utils.js"
-import { collections } from "../lib/constants.js"
-
-dotenv.config()
-
-/**
- * Bootstrap/Setup every necessary document for running a ceremony.
- */
-export const setupCeremony = functions.https.onCall(
-  async (data: any, context: functions.https.CallableContext): Promise<any> => {
-    if (!context.auth || !context.auth.token.coordinator) logMsg(GENERIC_ERRORS.GENERR_NO_COORDINATOR, MsgType.ERROR)
-
-    if (!data.ceremonyInputData || !data.ceremonyPrefix || !data.circuits)
-      logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
-
-    // Database.
-    const firestore = admin.firestore()
-    const batch = firestore.batch()
-
-    // Get data.
-    const { ceremonyInputData, ceremonyPrefix, circuits } = data
-    const userId = context.auth?.uid
-
-    // Ceremonies.
-    const ceremonyDoc = await firestore.collection(`${collections.ceremonies}/`).doc().get()
-
-    batch.create(ceremonyDoc.ref, {
-      title: ceremonyInputData.title,
-      description: ceremonyInputData.description,
-      startDate: new Date(ceremonyInputData.startDate).valueOf(),
-      endDate: new Date(ceremonyInputData.endDate).valueOf(),
-      prefix: ceremonyPrefix,
-      state: CeremonyState.SCHEDULED,
-      type: CeremonyType.PHASE2,
-      penalty: ceremonyInputData.penalty,
-      timeoutType: ceremonyInputData.timeoutMechanismType,
-      coordinatorId: userId,
-      lastUpdated: getCurrentServerTimestampInMillis()
-    })
-
-    // Circuits.
-    if (!circuits.length) logMsg(GENERIC_ERRORS.GENERR_NO_CIRCUIT_PROVIDED, MsgType.ERROR)
-
-    for (const circuit of circuits) {
-      const circuitDoc = await firestore
-        .collection(`${collections.ceremonies}/${ceremonyDoc.ref.id}/${collections.circuits}`)
-        .doc()
-        .get()
-
-      batch.create(circuitDoc.ref, {
-        ...circuit,
-        lastUpdated: getCurrentServerTimestampInMillis()
-      })
-    }
-
-    await batch.commit()
-
-    logMsg(`Ceremony ${ceremonyDoc.id} setup successfully completed - Coordinator ${userId}`, MsgType.INFO)
-  }
-)
-
-/**
- * Initialize an empty Waiting Queue field for the newly created circuit document.
- */
-export const initEmptyWaitingQueueForCircuit = functions.firestore
-  .document(`/${collections.ceremonies}/{ceremony}/${collections.circuits}/{circuit}`)
-  .onCreate(async (doc: QueryDocumentSnapshot) => {
-    // Get DB.
-    const firestore = admin.firestore()
-
-    // Get doc info.
-    const circuitId = doc.id
-    const circuitData = doc.data()
-    const parentCollectionPath = doc.ref.parent.path // == /ceremonies/{ceremony}/circuits/.
-
-    // Empty waiting queue.
-    const waitingQueue = {
-      contributors: [],
-      currentContributor: "",
-      completedContributions: 0, // == nextZkeyIndex.
-      failedContributions: 0
-    }
-
-    // Update the circuit document.
-    await firestore
-      .collection(parentCollectionPath)
-      .doc(circuitId)
-      .set(
-        {
-          ...circuitData,
-          waitingQueue,
-          lastUpdated: getCurrentServerTimestampInMillis()
-        },
-        { merge: true }
-      )
-
-    logMsg(`Empty waiting queue successfully initialized for circuit ${circuitId} - Ceremony ${doc.id}`, MsgType.INFO)
-  })
diff --git a/apps/backend/src/functions/storage.ts b/apps/backend/src/functions/storage.ts
deleted file mode 100644
index aa3ada62..00000000
--- a/apps/backend/src/functions/storage.ts
+++ /dev/null
@@ -1,325 +0,0 @@
-import * as functions from "firebase-functions"
-import admin from "firebase-admin"
-import {
-  GetObjectCommand,
-  CreateMultipartUploadCommand,
-  UploadPartCommand,
-  CompleteMultipartUploadCommand,
-  HeadObjectCommand,
-  CreateBucketCommand
-} from "@aws-sdk/client-s3"
-import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
-import dotenv from "dotenv"
-import { MsgType, ParticipantContributionStep, ParticipantStatus } from "../../types/index.js"
-import { logMsg, GENERIC_ERRORS } from "../lib/logs.js"
-import { getS3Client } from "../lib/utils.js"
-import { collections } from "../lib/constants.js"
-
-dotenv.config()
-
-/**
- * Create a new AWS S3 bucket for a particular ceremony.
- */
-export const createBucket = functions.https.onCall(
-  async (data: any, context: functions.https.CallableContext): Promise<any> => {
-    // Checks.
-    if (!context.auth || !context.auth.token.coordinator) logMsg(GENERIC_ERRORS.GENERR_NO_COORDINATOR, MsgType.ERROR)
-
-    if (!data.bucketName) logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
-
-    // Connect w/ S3.
-    const S3 = await getS3Client()
-
-    // Prepare command.
-    const command = new CreateBucketCommand({
-      Bucket: data.bucketName,
-      CreateBucketConfiguration: {
-        LocationConstraint: process.env.AWS_REGION!
-      }
-    })
-
-    try {
-      // Send command.
-      const response = await S3.send(command)
-
-      // Check response.
-      if (response.$metadata.httpStatusCode === 200 && !!response.Location) {
-        logMsg(`Bucket successfully created`, MsgType.LOG)
-
-        return true
-      }
-    } catch (error: any) {
-      if (error.$metadata.httpStatusCode === 400 && error.Code === "InvalidBucketName") {
-        logMsg(`Bucket not created: ${error.Code}`, MsgType.LOG)
-
-        return false
-      }
-
-      logMsg(`Generic error when creating a new S3 bucket: ${error}`, MsgType.ERROR)
-    }
-  }
-)
-
-/**
- * Check if a specified object exist in a given AWS S3 bucket.
- */
-export const checkIfObjectExist = functions.https.onCall(
-  async (data: any, context: functions.https.CallableContext): Promise<any> => {
-    // Checks.
-    if (!context.auth || !context.auth.token.coordinator) logMsg(GENERIC_ERRORS.GENERR_NO_COORDINATOR, MsgType.ERROR)
-
-    if (!data.bucketName || !data.objectKey) logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
-
-    // Connect w/ S3.
-    const S3 = await getS3Client()
-
-    // Prepare command.
-    const command = new HeadObjectCommand({ Bucket: data.bucketName, Key: data.objectKey })
-
-    try {
-      // Send command.
-      const response = await S3.send(command)
-
-      // Check response.
-      if (response.$metadata.httpStatusCode === 200 && !!response.ETag) {
-        logMsg(`Object: ${data.objectKey} exists!`, MsgType.LOG)
-
-        return true
-      }
-    } catch (error: any) {
-      if (error.$metadata.httpStatusCode === 404 && !error.ETag) {
-        logMsg(`Object: ${data.objectKey} does not exist!`, MsgType.LOG)
-
-        return false
-      }
-
-      logMsg(`Generic error when checking for object on S3 bucket: ${error}`, MsgType.ERROR)
-    }
-  }
-)
-
-/**
- * Generate a new AWS S3 pre signed url to upload/download an object (GET).
- */
-export const generateGetObjectPreSignedUrl = functions.https.onCall(async (data: any): Promise<any> => {
-  if (!data.bucketName || !data.objectKey) logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
-
-  // Connect w/ S3.
-  const S3 = await getS3Client()
-
-  // Prepare the command.
-  const command = new GetObjectCommand({ Bucket: data.bucketName, Key: data.objectKey })
-
-  // Get the PreSignedUrl.
-  const url = await getSignedUrl(S3, command, { expiresIn: Number(process.env.AWS_PRESIGNED_URL_EXPIRATION!) })
-
-  logMsg(`Single Pre-Signed URL ${url}`, MsgType.LOG)
-
-  return url
-})
-
-/**
- * Initiate a multi part upload for a specific object in AWS S3 bucket.
- */
-export const startMultiPartUpload = functions.https.onCall(
-  async (data: any, context: functions.https.CallableContext): Promise<any> => {
-    if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
-      logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
-
-    if (!data.bucketName || !data.objectKey || (context.auth?.token.participant && !data.ceremonyId))
-      logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
-
-    // Get DB.
-    const firestore = admin.firestore()
-
-    // Get data.
-    const { bucketName, objectKey, ceremonyId } = data
-    const userId = context.auth?.uid
-
-    if (context.auth?.token.participant && !!ceremonyId) {
-      // Look for documents.
-      const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
-      const participantDoc = await firestore
-        .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
-        .doc(userId!)
-        .get()
-
-      if (!ceremonyDoc.exists || !participantDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_DOCUMENTS, MsgType.ERROR)
-
-      // Get data from docs.
-      const ceremonyData = ceremonyDoc.data()
-      const participantData = participantDoc.data()
-
-      if (!ceremonyData || !participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
-
-      logMsg(`Ceremony document ${ceremonyDoc.id} okay`, MsgType.DEBUG)
-      logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
-
-      // Check participant status and contribution step.
-      const { status, contributionStep } = participantData!
-
-      if (status !== ParticipantStatus.CONTRIBUTING && contributionStep !== ParticipantContributionStep.UPLOADING)
-        logMsg(`Participant ${participantDoc.id} is not able to start a multi part upload right now`, MsgType.ERROR)
-    }
-
-    // Connect w/ S3.
-    const S3 = await getS3Client()
-
-    // Prepare command.
-    const command = new CreateMultipartUploadCommand({ Bucket: bucketName, Key: objectKey })
-
-    // Send command.
-    const responseInitiate = await S3.send(command)
-    const uploadId = responseInitiate.UploadId
-
-    logMsg(`Upload ID: ${uploadId}`, MsgType.LOG)
-
-    return uploadId
-  }
-)
-
-/**
- * Generate a PreSignedUrl for each part of the given multi part upload.
- */
-export const generatePreSignedUrlsParts = functions.https.onCall(
-  async (data: any, context: functions.https.CallableContext): Promise<any> => {
-    if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
-      logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
-
-    if (
-      !data.bucketName ||
-      !data.objectKey ||
-      !data.uploadId ||
-      data.numberOfParts <= 0 ||
-      (context.auth?.token.participant && !data.ceremonyId)
-    )
-      logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
-
-    // Get DB.
-    const firestore = admin.firestore()
-
-    // Get data.
-    const { bucketName, objectKey, uploadId, numberOfParts, ceremonyId } = data
-    const userId = context.auth?.uid
-
-    if (context.auth?.token.participant && !!ceremonyId) {
-      // Look for documents.
-      const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
-      const participantDoc = await firestore
-        .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
-        .doc(userId!)
-        .get()
-
-      if (!ceremonyDoc.exists || !participantDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_DOCUMENTS, MsgType.ERROR)
-
-      // Get data from docs.
-      const ceremonyData = ceremonyDoc.data()
-      const participantData = participantDoc.data()
-
-      if (!ceremonyData || !participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
-
-      logMsg(`Ceremony document ${ceremonyDoc.id} okay`, MsgType.DEBUG)
-      logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
-
-      // Check participant status and contribution step.
-      const { status, contributionStep } = participantData!
-
-      if (status !== ParticipantStatus.CONTRIBUTING && contributionStep !== ParticipantContributionStep.UPLOADING)
-        logMsg(`Participant ${participantDoc.id} is not able to start a multi part upload right now`, MsgType.ERROR)
-    }
-
-    // Connect w/ S3.
-    const S3 = await getS3Client()
-
-    const parts = []
-
-    for (let i = 0; i < numberOfParts; i += 1) {
-      // Prepare command for each part.
-      const command = new UploadPartCommand({
-        Bucket: bucketName,
-        Key: objectKey,
-        PartNumber: i + 1,
-        UploadId: uploadId
-      })
-
-      // Get the PreSignedUrl for uploading the specific part.
-      const signedUrl = await getSignedUrl(S3, command, {
-        expiresIn: Number(process.env.AWS_PRESIGNED_URL_EXPIRATION!)
-      })
-
-      parts.push(signedUrl)
-    }
-
-    return parts
-  }
-)
-
-/**
- * Ultimate the multi part upload for a specific object in AWS S3 bucket.
- */
-export const completeMultiPartUpload = functions.https.onCall(
-  async (data: any, context: functions.https.CallableContext): Promise<any> => {
-    if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
-      logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
-
-    if (
-      !data.bucketName ||
-      !data.objectKey ||
-      !data.uploadId ||
-      !data.parts ||
-      (context.auth?.token.participant && !data.ceremonyId)
-    )
-      logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
-
-    // Get DB.
-    const firestore = admin.firestore()
-
-    // Get data.
-    const { bucketName, objectKey, uploadId, parts, ceremonyId } = data
-    const userId = context.auth?.uid
-
-    if (context.auth?.token.participant && !!ceremonyId) {
-      // Look for documents.
-      const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
-      const participantDoc = await firestore
-        .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
-        .doc(userId!)
-        .get()
-
-      if (!ceremonyDoc.exists || !participantDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_DOCUMENTS, MsgType.ERROR)
-
-      // Get data from docs.
-      const ceremonyData = ceremonyDoc.data()
-      const participantData = participantDoc.data()
-
-      if (!ceremonyData || !participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
-
-      logMsg(`Ceremony document ${ceremonyDoc.id} okay`, MsgType.DEBUG)
-      logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
-
-      // Check participant status and contribution step.
-      const { status, contributionStep } = participantData!
-
-      if (status !== ParticipantStatus.CONTRIBUTING && contributionStep !== ParticipantContributionStep.UPLOADING)
-        logMsg(`Participant ${participantDoc.id} is not able to start a multi part upload right now`, MsgType.ERROR)
-    }
-
-    // Connect w/ S3.
-    const S3 = await getS3Client()
-
-    // Prepare command.
-    const command = new CompleteMultipartUploadCommand({
-      Bucket: bucketName,
-      Key: objectKey,
-      UploadId: uploadId,
-      MultipartUpload: { Parts: parts }
-    })
-
-    // Send command.
-    const responseComplete = await S3.send(command)
-
-    logMsg(`Upload for ${data.uploadId} completed! Object location ${responseComplete.Location}`, MsgType.LOG)
-
-    return responseComplete.Location
-  }
-)
diff --git a/apps/backend/src/functions/waitingQueue.ts b/apps/backend/src/functions/waitingQueue.ts
deleted file mode 100644
index bb09f4f1..00000000
--- a/apps/backend/src/functions/waitingQueue.ts
+++ /dev/null
@@ -1,736 +0,0 @@
-import * as functionsV1 from "firebase-functions/v1"
-import * as functionsV2 from "firebase-functions/v2"
-import admin from "firebase-admin"
-import dotenv from "dotenv"
-import { QueryDocumentSnapshot } from "firebase-functions/v1/firestore"
-import { Change } from "firebase-functions"
-import { zKey } from "snarkjs"
-import path from "path"
-import os from "os"
-import fs from "fs"
-import { Timer } from "timer-node"
-import blake from "blakejs"
-import winston from "winston"
-import { FieldValue } from "firebase-admin/firestore"
-import { CeremonyState, MsgType, ParticipantContributionStep, ParticipantStatus } from "../../types/index.js"
-import {
-  deleteObject,
-  formatZkeyIndex,
-  getCircuitDocumentByPosition,
-  getCurrentServerTimestampInMillis,
-  getS3Client,
-  sleep,
-  tempDownloadFromBucket,
-  uploadFileToBucket
-} from "../lib/utils.js"
-import { collections, names } from "../lib/constants.js"
-import { GENERIC_ERRORS, logMsg } from "../lib/logs.js"
-
-dotenv.config()
-
-/**
- * Automate the coordination for participants contributions.
- * @param circuit <QueryDocumentSnapshot> - the circuit document.
- * @param participant <QueryDocumentSnapshot> - the participant document.
- * @param ceremonyId <string> - the ceremony identifier.
- */
-const coordinate = async (circuit: QueryDocumentSnapshot, participant: QueryDocumentSnapshot, ceremonyId?: string) => {
-  // Get DB.
-  const firestore = admin.firestore()
-  // Update DB.
-  const batch = firestore.batch()
-
-  // Get info.
-  const participantId = participant.id
-  const circuitData = circuit.data()
-  const participantData = participant.data()
-
-  logMsg(`Circuit document ${circuit.id} okay`, MsgType.DEBUG)
-  logMsg(`Participant document ${participantId} okay`, MsgType.DEBUG)
-
-  const { waitingQueue } = circuitData
-  const { contributors } = waitingQueue
-  let { currentContributor } = waitingQueue
-  let newParticipantStatus = 0
-  let newContributionStep = 0
-
-  // Case 1: Participant is ready to contribute and there's nobody in the queue.
-  if (!contributors.length && !currentContributor) {
-    logMsg(`Coordination use-case 1: Participant is ready to contribute and there's nobody in the queue`, MsgType.INFO)
-
-    currentContributor = participantId
-    newParticipantStatus = ParticipantStatus.CONTRIBUTING
-    newContributionStep = ParticipantContributionStep.DOWNLOADING
-  }
-
-  // Case 2: Participant is ready to contribute but there's another participant currently contributing.
-  if (currentContributor !== participantId) {
-    logMsg(
-      `Coordination use-case 2: Participant is ready to contribute but there's another participant currently contributing`,
-      MsgType.INFO
-    )
-
-    newParticipantStatus = ParticipantStatus.WAITING
-  }
-
-  // Case 3: the participant has finished the contribution so this case is used to update the i circuit queue.
-  if (
-    currentContributor === participantId &&
-    (participantData.status === ParticipantStatus.CONTRIBUTED || participantData.status === ParticipantStatus.DONE) &&
-    participantData.contributionStep === ParticipantContributionStep.COMPLETED
-  ) {
-    logMsg(
-      `Coordination use-case 3: Participant has finished the contribution so this case is used to update the i circuit queue`,
-      MsgType.INFO
-    )
-
-    contributors.shift(1)
-
-    if (contributors.length > 0) {
-      // There's someone else ready to contribute.
-      currentContributor = contributors.at(0)
-
-      // Pass the baton to the next participant.
-      const newCurrentContributorDoc = await firestore
-        .collection(`${ceremonyId}/${collections.participants}`)
-        .doc(currentContributor)
-        .get()
-
-      if (newCurrentContributorDoc.exists) {
-        batch.update(newCurrentContributorDoc.ref, {
-          status: ParticipantStatus.WAITING,
-          lastUpdated: getCurrentServerTimestampInMillis()
-        })
-
-        logMsg(`Batch update use-case 3: New current contributor`, MsgType.INFO)
-      }
-    } else currentContributor = ""
-  }
-
-  // Updates for cases 1 and 2.
-  if (newParticipantStatus !== 0) {
-    contributors.push(participantId)
-
-    batch.update(participant.ref, {
-      status: newParticipantStatus,
-      contributionStartedAt:
-        newParticipantStatus === ParticipantStatus.CONTRIBUTING ? getCurrentServerTimestampInMillis() : 0,
-      lastUpdated: getCurrentServerTimestampInMillis()
-    })
-
-    // Case 1 only.
-    if (newContributionStep !== 0)
-      batch.update(participant.ref, {
-        contributionStep: newContributionStep,
-        lastUpdated: getCurrentServerTimestampInMillis()
-      })
-
-    logMsg(`Batch update use-case 1 or 2: participant updates`, MsgType.INFO)
-  }
-
-  // Update waiting queue.
-  batch.update(circuit.ref, {
-    waitingQueue: {
-      ...waitingQueue,
-      contributors,
-      currentContributor
-    },
-    lastUpdated: getCurrentServerTimestampInMillis()
-  })
-
-  logMsg(`Batch update all use-cases: update circuit waiting queue`, MsgType.INFO)
-
-  await batch.commit()
-}
-
-/**
- * Coordinate waiting queue contributors.
- */
-export const coordinateContributors = functionsV1.firestore
-  .document(`${collections.ceremonies}/{ceremonyId}/${collections.participants}/{participantId}`)
-  .onUpdate(async (change: Change<QueryDocumentSnapshot>) => {
-    // Before changes.
-    const participantBefore = change.before
-    const dataBefore = participantBefore.data()
-    const {
-      contributionProgress: beforeContributionProgress,
-      status: beforeStatus,
-      contributionStep: beforeContributionStep
-    } = dataBefore
-
-    // After changes.
-    const participantAfter = change.after
-    const dataAfter = participantAfter.data()
-    const {
-      contributionProgress: afterContributionProgress,
-      status: afterStatus,
-      contributionStep: afterContributionStep
-    } = dataAfter
-
-    // Get the ceremony identifier (this does not change from before/after).
-    const ceremonyId = participantBefore.ref.parent.parent!.path
-
-    if (!ceremonyId) logMsg(GENERIC_ERRORS.GENERR_NO_CEREMONY_PROVIDED, MsgType.ERROR)
-
-    logMsg(`Coordinating participants for ceremony ${ceremonyId}`, MsgType.INFO)
-
-    logMsg(`Participant document ${participantBefore.id} okay`, MsgType.DEBUG)
-    logMsg(`Participant document ${participantAfter.id} okay`, MsgType.DEBUG)
-    logMsg(
-      `Participant ${participantBefore.id} the status from ${beforeStatus} to ${afterStatus} and the contribution progress from ${beforeContributionProgress} to ${afterContributionProgress}`,
-      MsgType.INFO
-    )
-
-    // nb. existance checked above.
-    const circuitsPath = `${participantBefore.ref.parent.parent!.path}/${collections.circuits}`
-
-    // When a participant changes is status to ready, is "ready" to become a contributor.
-    if (afterStatus === ParticipantStatus.READY) {
-      // When beforeContributionProgress === 0 is a new participant, when beforeContributionProgress === afterContributionProgress the participant is retrying.
-      if (beforeContributionProgress === 0 || beforeContributionProgress === afterContributionProgress) {
-        logMsg(
-          `Participant has status READY and before contribution progress ${beforeContributionProgress} is different from after contribution progress ${afterContributionProgress}`,
-          MsgType.INFO
-        )
-
-        // i -> k where i == 0
-        // (participant newly created). We work only on circuit k.
-        const circuit = await getCircuitDocumentByPosition(circuitsPath, afterContributionProgress)
-
-        logMsg(`Circuit document ${circuit.id} okay`, MsgType.DEBUG)
-
-        // The circuit info (i.e., the queue) is useful only to check turns for contribution.
-        // The participant info is useful to really pass the baton (starting the contribution).
-        // So, the info on the circuit says "it's your turn" while the info on the participant says "okay, i'm ready/waiting etc.".
-        // The contribution progress number completes everything because indicates which circuit is involved.
-        await coordinate(circuit, participantAfter)
-        logMsg(`Circuit ${circuit.id} has been updated (waiting queue)`, MsgType.INFO)
-      }
-
-      if (afterContributionProgress === beforeContributionProgress + 1 && beforeContributionProgress !== 0) {
-        logMsg(
-          `Participant has status READY and before contribution progress ${beforeContributionProgress} is different from before contribution progress ${afterContributionProgress}`,
-          MsgType.INFO
-        )
-
-        // i -> k where k === i + 1
-        // (participant has already contributed to i and the contribution has been verified,
-        // participant now is ready to be put in line for contributing on k circuit).
-
-        const afterCircuit = await getCircuitDocumentByPosition(circuitsPath, afterContributionProgress)
-
-        // logMsg(`Circuit document ${beforeCircuit.id} okay`, MsgType.DEBUG)
-        logMsg(`Circuit document ${afterCircuit.id} okay`, MsgType.DEBUG)
-
-        // Coordinate after circuit (update waiting queue).
-        await coordinate(afterCircuit, participantAfter)
-        logMsg(`After circuit ${afterCircuit.id} has been updated (waiting queue)`, MsgType.INFO)
-      }
-    }
-
-    // The contributor has finished the contribution and the waiting queue for the circuit needs to be updated.
-    if (
-      (afterStatus === ParticipantStatus.DONE && beforeStatus !== ParticipantStatus.DONE) ||
-      (beforeContributionProgress === afterContributionProgress &&
-        afterStatus === ParticipantStatus.CONTRIBUTED &&
-        beforeStatus === ParticipantStatus.CONTRIBUTING &&
-        beforeContributionStep === ParticipantContributionStep.VERIFYING &&
-        afterContributionStep === ParticipantContributionStep.COMPLETED)
-    ) {
-      logMsg(`Participant has status DONE or has finished the contribution`, MsgType.INFO)
-
-      // Update the last circuits waiting queue.
-      const beforeCircuit = await getCircuitDocumentByPosition(circuitsPath, beforeContributionProgress)
-
-      logMsg(`Circuit document ${beforeCircuit.id} okay`, MsgType.DEBUG)
-
-      // Coordinate before circuit (update waiting queue + pass the baton to the next).
-      await coordinate(beforeCircuit, participantAfter, ceremonyId)
-      logMsg(
-        `Before circuit ${beforeCircuit.id} has been updated (waiting queue + pass the baton to next)`,
-        MsgType.INFO
-      )
-    }
-  })
-
-/**
- * Automate the contribution verification.
- */
-export const verifycontribution = functionsV2.https.onCall(
-  { memory: "32GiB", cpu: 8, timeoutSeconds: 3600, retry: true, maxInstances: 1000 },
-  async (request: functionsV2.https.CallableRequest<any>): Promise<any> => {
-    const verifyCloudFunctionTimer = new Timer({ label: "verifyCloudFunction" })
-    verifyCloudFunctionTimer.start()
-
-    if (!request.auth || (!request.auth.token.participant && !request.auth.token.coordinator))
-      logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
-
-    if (!request.data.ceremonyId || !request.data.circuitId || !request.data.ghUsername || !request.data.bucketName)
-      logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
-
-    // Get DB.
-    const firestore = admin.firestore()
-
-    // Get Storage.
-    const S3 = await getS3Client()
-
-    // Get data.
-    const { ceremonyId, circuitId, ghUsername, bucketName } = request.data
-    const userId = request.auth?.uid
-
-    // Look for documents.
-    const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
-    const circuitDoc = await firestore
-      .collection(`${collections.ceremonies}/${ceremonyId}/${collections.circuits}`)
-      .doc(circuitId)
-      .get()
-    const participantDoc = await firestore
-      .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
-      .doc(userId!)
-      .get()
-
-    if (!ceremonyDoc.exists || !circuitDoc.exists || !participantDoc.exists)
-      logMsg(GENERIC_ERRORS.GENERR_INVALID_DOCUMENTS, MsgType.ERROR)
-
-    // Get data from docs.
-    const ceremonyData = ceremonyDoc.data()
-    const circuitData = circuitDoc.data()
-    const participantData = participantDoc.data()
-
-    if (!ceremonyData || !circuitData || !participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
-
-    logMsg(`Ceremony document ${ceremonyDoc.id} okay`, MsgType.DEBUG)
-    logMsg(`Circuit document ${circuitDoc.id} okay`, MsgType.DEBUG)
-    logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
-
-    let valid = false
-    let verificationComputationTime = 0
-    const fullContributionTime = 0
-
-    // Check if is the verification for ceremony finalization.
-    const finalize = ceremonyData?.state === CeremonyState.CLOSED && request.auth && request.auth.token.coordinator
-
-    if (participantData?.status === ParticipantStatus.CONTRIBUTING || finalize) {
-      // Compute last zkey index.
-      const lastZkeyIndex = formatZkeyIndex(circuitData!.waitingQueue.completedContributions + 1)
-
-      // Reconstruct transcript path.
-      const transcriptFilename = `${circuitData?.prefix}_${
-        finalize
-          ? `${ghUsername}_final_verification_transcript.log`
-          : `${lastZkeyIndex}_${ghUsername}_verification_transcript.log`
-      }`
-      const transcriptStoragePath = `${collections.circuits}/${circuitData?.prefix}/${names.transcripts}/${transcriptFilename}`
-      const transcriptTempFilePath = path.join(os.tmpdir(), transcriptFilename)
-
-      // Custom logger for verification transcript.
-      const transcriptLogger = winston.createLogger({
-        level: "info",
-        format: winston.format.printf((log) => log.message),
-        transports: [
-          // Write all logs with importance level of `info` to `transcript.json`.
-          new winston.transports.File({
-            filename: transcriptTempFilePath,
-            level: "info"
-          })
-        ]
-      })
-
-      transcriptLogger.info(
-        `${finalize ? `Final verification` : `Verification`} transcript for ${
-          circuitData?.prefix
-        } circuit Phase 2 contribution.\n${
-          finalize ? `Coordinator ` : `Contributor # ${Number(lastZkeyIndex)}`
-        } (${ghUsername})\n`
-      )
-
-      // Get storage paths.
-      const potStoragePath = `${names.pot}/${circuitData?.files.potFilename}`
-      const firstZkeyStoragePath = `${collections.circuits}/${circuitData?.prefix}/${collections.contributions}/${circuitData?.prefix}_00000.zkey`
-      const lastZkeyStoragePath = `${collections.circuits}/${circuitData?.prefix}/${collections.contributions}/${
-        circuitData?.prefix
-      }_${finalize ? `final` : lastZkeyIndex}.zkey`
-
-      // Temporary store files from bucket.
-      const { potFilename } = circuitData!.files
-      const firstZkeyFilename = `${circuitData?.prefix}_00000.zkey`
-      const lastZkeyFilename = `${circuitData?.prefix}_${finalize ? `final` : lastZkeyIndex}.zkey`
-
-      const potTempFilePath = path.join(os.tmpdir(), potFilename)
-      const firstZkeyTempFilePath = path.join(os.tmpdir(), firstZkeyFilename)
-      const lastZkeyTempFilePath = path.join(os.tmpdir(), lastZkeyFilename)
-
-      // Download from AWS S3 bucket.
-      await tempDownloadFromBucket(S3, bucketName, potStoragePath, potTempFilePath)
-      logMsg(`${potStoragePath} downloaded`, MsgType.DEBUG)
-
-      await tempDownloadFromBucket(S3, bucketName, firstZkeyStoragePath, firstZkeyTempFilePath)
-      logMsg(`${firstZkeyStoragePath} downloaded`, MsgType.DEBUG)
-
-      await tempDownloadFromBucket(S3, bucketName, lastZkeyStoragePath, lastZkeyTempFilePath)
-      logMsg(`${lastZkeyStoragePath} downloaded`, MsgType.DEBUG)
-
-      logMsg(`Downloads from storage completed`, MsgType.INFO)
-
-      // Verify contribution.
-      const verificationComputationTimer = new Timer({ label: "verificationComputation" })
-      verificationComputationTimer.start()
-
-      valid = await zKey.verifyFromInit(firstZkeyTempFilePath, potTempFilePath, lastZkeyTempFilePath, transcriptLogger)
-
-      verificationComputationTimer.stop()
-
-      verificationComputationTime = verificationComputationTimer.ms()
-
-      // Compute blake2b hash before unlink.
-      const lastZkeyBuffer = fs.readFileSync(lastZkeyTempFilePath)
-      const lastZkeyBlake2bHash = blake.blake2bHex(lastZkeyBuffer)
-
-      // Unlink folders.
-      fs.unlinkSync(potTempFilePath)
-      fs.unlinkSync(firstZkeyTempFilePath)
-      fs.unlinkSync(lastZkeyTempFilePath)
-
-      logMsg(`Contribution is ${valid ? `valid` : `invalid`}`, MsgType.INFO)
-      logMsg(`Verification computation time ${verificationComputationTime} ms`, MsgType.INFO)
-
-      // Update DB.
-      const batch = firestore.batch()
-
-      // Contribution.
-      const contributionDoc = await firestore
-        .collection(
-          `${collections.ceremonies}/${ceremonyId}/${collections.circuits}/${circuitId}/${collections.contributions}`
-        )
-        .doc()
-        .get()
-
-      if (valid) {
-        // Sleep ~5 seconds to wait for verification transcription.
-        await sleep(5000)
-
-        // Upload transcript (small file - multipart upload not required).
-        await uploadFileToBucket(S3, bucketName, transcriptStoragePath, transcriptTempFilePath)
-
-        // Compute blake2b hash.
-        const transcriptBuffer = fs.readFileSync(transcriptTempFilePath)
-        const transcriptBlake2bHash = blake.blake2bHex(transcriptBuffer)
-
-        fs.unlinkSync(transcriptTempFilePath)
-
-        // Get contribution computation time.
-        const contributions = participantData?.contributions.filter(
-          (contribution: { hash: string; doc: string; computationTime: number }) =>
-            !!contribution.hash && !!contribution.computationTime && !contribution.doc
-        )
-
-        if (contributions.length !== 1)
-          logMsg(`There should be only one contribution without a doc link`, MsgType.ERROR)
-
-        const contributionComputationTime = contributions[0].computationTime
-
-        // Update only when coordinator is finalizing the ceremony.
-        batch.create(contributionDoc.ref, {
-          participantId: participantDoc.id,
-          contributionComputationTime,
-          verificationComputationTime,
-          zkeyIndex: finalize ? `final` : lastZkeyIndex,
-          files: {
-            transcriptFilename,
-            lastZkeyFilename,
-            transcriptStoragePath,
-            lastZkeyStoragePath,
-            transcriptBlake2bHash,
-            lastZkeyBlake2bHash
-          },
-          valid,
-          lastUpdated: getCurrentServerTimestampInMillis()
-        })
-
-        logMsg(`Batch: create contribution document`, MsgType.DEBUG)
-
-        verifyCloudFunctionTimer.stop()
-        const verifyCloudFunctionTime = verifyCloudFunctionTimer.ms()
-
-        if (!finalize) {
-          // Circuit.
-          const { completedContributions, failedContributions } = circuitData!.waitingQueue
-          const {
-            contributionComputation: avgContributionComputation,
-            fullContribution: avgFullContribution,
-            verifyCloudFunction: avgVerifyCloudFunction
-          } = circuitData!.avgTimings
-
-          logMsg(`Current average full contribution (down + comp + up) time ${avgFullContribution} ms`, MsgType.INFO)
-          logMsg(`Current verify cloud function time ${avgVerifyCloudFunction} ms`, MsgType.INFO)
-
-          // Calculate full contribution time.
-          const fullContributionTime = participantData?.verificationStartedAt - participantData?.contributionStartedAt
-
-          // Update avg timings.
-          const newAvgContributionComputationTime =
-            avgContributionComputation > 0
-              ? (avgContributionComputation + contributionComputationTime) / 2
-              : contributionComputationTime
-          const newAvgFullContributionTime =
-            avgFullContribution > 0 ? (avgFullContribution + fullContributionTime) / 2 : fullContributionTime
-          const newAvgVerifyCloudFunctionTime =
-            avgVerifyCloudFunction > 0
-              ? (avgVerifyCloudFunction + verifyCloudFunctionTime) / 2
-              : verifyCloudFunctionTime
-
-          logMsg(`New average contribution computation time ${newAvgContributionComputationTime} ms`, MsgType.INFO)
-          logMsg(`New average full contribution (down + comp + up) time ${newAvgFullContributionTime} ms`, MsgType.INFO)
-          logMsg(`New verify cloud function time ${newAvgVerifyCloudFunctionTime} ms`, MsgType.INFO)
-
-          batch.update(circuitDoc.ref, {
-            avgTimings: {
-              contributionComputation: valid ? newAvgContributionComputationTime : contributionComputationTime,
-              fullContribution: valid ? newAvgFullContributionTime : fullContributionTime,
-              verifyCloudFunction: valid ? newAvgVerifyCloudFunctionTime : verifyCloudFunctionTime
-            },
-            waitingQueue: {
-              ...circuitData?.waitingQueue,
-              completedContributions: valid ? completedContributions + 1 : completedContributions,
-              failedContributions: valid ? failedContributions : failedContributions + 1
-            },
-            lastUpdated: getCurrentServerTimestampInMillis()
-          })
-        }
-
-        logMsg(`Batch: update timings and waiting queue for circuit`, MsgType.DEBUG)
-
-        await batch.commit()
-      } else {
-        // Delete invalid contribution from storage.
-        await deleteObject(S3, bucketName, lastZkeyStoragePath)
-
-        // Unlink transcript temp file.
-        fs.unlinkSync(transcriptTempFilePath)
-
-        // Create a new contribution doc without files.
-        batch.create(contributionDoc.ref, {
-          participantId: participantDoc.id,
-          verificationComputationTime,
-          zkeyIndex: finalize ? `final` : lastZkeyIndex,
-          valid,
-          lastUpdated: getCurrentServerTimestampInMillis()
-        })
-
-        logMsg(`Batch: create invalid contribution document`, MsgType.DEBUG)
-
-        if (!finalize) {
-          const { failedContributions } = circuitData!.waitingQueue
-
-          // Update the failed contributions.
-          batch.update(circuitDoc.ref, {
-            waitingQueue: {
-              ...circuitData?.waitingQueue,
-              failedContributions: failedContributions + 1
-            },
-            lastUpdated: getCurrentServerTimestampInMillis()
-          })
-        }
-        logMsg(`Batch: update invalid contributions counter`, MsgType.DEBUG)
-
-        await batch.commit()
-      }
-    }
-
-    logMsg(
-      `Participant ${userId} has verified the contribution #${participantData?.contributionProgress}`,
-      MsgType.INFO
-    )
-    logMsg(`Returned values: valid ${valid} - verificationComputationTime ${verificationComputationTime}`, MsgType.INFO)
-
-    return {
-      valid,
-      fullContributionTime,
-      verifyCloudFunctionTime: verifyCloudFunctionTimer.ms()
-    }
-  }
-)
-
-/**
- * Update the participant document after a contribution.
- */
-export const refreshParticipantAfterContributionVerification = functionsV1.firestore
-  .document(
-    `/${collections.ceremonies}/{ceremony}/${collections.circuits}/{circuit}/${collections.contributions}/{contributions}`
-  )
-  .onCreate(async (doc: QueryDocumentSnapshot) => {
-    // Get DB.
-    const firestore = admin.firestore()
-
-    // Get doc info.
-    const contributionId = doc.id
-    const contributionData = doc.data()
-    const ceremonyCircuitsCollectionPath = doc.ref.parent.parent?.parent?.path // == /ceremonies/{ceremony}/circuits/.
-    const ceremonyParticipantsCollectionPath = `${doc.ref.parent.parent?.parent?.parent?.path}/${collections.participants}` // == /ceremonies/{ceremony}/participants.
-
-    if (!ceremonyCircuitsCollectionPath || !ceremonyParticipantsCollectionPath)
-      logMsg(GENERIC_ERRORS.GENERR_WRONG_PATHS, MsgType.ERROR)
-
-    // Looks for documents.
-    const circuits = await firestore.collection(ceremonyCircuitsCollectionPath!).listDocuments()
-    const participantDoc = await firestore
-      .collection(ceremonyParticipantsCollectionPath)
-      .doc(contributionData.participantId)
-      .get()
-
-    if (!participantDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_DOCUMENTS, MsgType.ERROR)
-
-    // Get data.
-    const participantData = participantDoc.data()
-
-    if (!participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
-
-    logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
-
-    const participantContributions = participantData?.contributions
-
-    // Update the only one contribution with missing doc (i.e., the last one).
-    participantContributions.forEach(
-      (participantContribution: { hash: string; doc: string; computationTime: number }) => {
-        if (
-          !!participantContribution.hash &&
-          !!participantContribution.computationTime &&
-          !participantContribution.doc
-        ) {
-          participantContribution.doc = contributionId
-        }
-      }
-    )
-
-    // Don't update the participant status and progress when finalizing.
-    if (participantData!.status !== ParticipantStatus.FINALIZING) {
-      const newStatus =
-        participantData!.contributionProgress + 1 > circuits.length
-          ? ParticipantStatus.DONE
-          : ParticipantStatus.CONTRIBUTED
-
-      await firestore.collection(ceremonyParticipantsCollectionPath).doc(contributionData.participantId).set(
-        {
-          status: newStatus,
-          contributionStep: ParticipantContributionStep.COMPLETED,
-          contributions: participantContributions,
-          tempContributionData: FieldValue.delete(),
-          lastUpdated: getCurrentServerTimestampInMillis()
-        },
-        { merge: true }
-      )
-
-      logMsg(`Participant ${contributionData.participantId} updated after contribution`, MsgType.DEBUG)
-    } else {
-      await firestore.collection(ceremonyParticipantsCollectionPath).doc(contributionData.participantId).set(
-        {
-          contributions: participantContributions,
-          lastUpdated: getCurrentServerTimestampInMillis()
-        },
-        { merge: true }
-      )
-
-      logMsg(`Coordinator ${contributionData.participantId} updated after final contribution`, MsgType.DEBUG)
-    }
-  })
-
-/**
- * Make the progress to next contribution after successfully verified the contribution.
- */
-export const makeProgressToNextContribution = functionsV1.https.onCall(
-  async (data: any, context: functionsV1.https.CallableContext): Promise<any> => {
-    if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
-      logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
-
-    if (!data.ceremonyId) logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
-
-    // Get DB.
-    const firestore = admin.firestore()
-
-    // Get data.
-    const { ceremonyId } = data
-    const userId = context.auth?.uid
-
-    // Look for documents.
-    const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
-    const participantDoc = await firestore
-      .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
-      .doc(userId!)
-      .get()
-
-    if (!ceremonyDoc.exists || !participantDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_DOCUMENTS, MsgType.ERROR)
-
-    // Get data from docs.
-    const ceremonyData = ceremonyDoc.data()
-    const participantData = participantDoc.data()
-
-    if (!ceremonyData || !participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
-
-    logMsg(`Ceremony document ${ceremonyDoc.id} okay`, MsgType.DEBUG)
-    logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
-
-    const { contributionProgress, contributionStep, status } = participantData!
-
-    // Check for contribution completion here.
-    if (contributionStep !== ParticipantContributionStep.COMPLETED && status !== ParticipantStatus.WAITING)
-      logMsg(`Cannot progress!`, MsgType.ERROR)
-
-    await participantDoc.ref.update({
-      contributionProgress: contributionProgress + 1,
-      status: ParticipantStatus.READY,
-      lastUpdated: getCurrentServerTimestampInMillis()
-    })
-
-    logMsg(`Participant ${userId} progressed to ${contributionProgress + 1}`, MsgType.DEBUG)
-  }
-)
-
-/**
- * Resume a contribution after a timeout expiration.
- */
-export const resumeContributionAfterTimeoutExpiration = functionsV1.https.onCall(
-  async (data: any, context: functionsV1.https.CallableContext): Promise<any> => {
-    if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
-      logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
-
-    if (!data.ceremonyId) logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
-
-    // Get DB.
-    const firestore = admin.firestore()
-
-    // Get data.
-    const { ceremonyId } = data
-    const userId = context.auth?.uid
-
-    // Look for documents.
-    const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
-    const participantDoc = await firestore
-      .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
-      .doc(userId!)
-      .get()
-
-    if (!ceremonyDoc.exists || !participantDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_DOCUMENTS, MsgType.ERROR)
-
-    // Get data from docs.
-    const ceremonyData = ceremonyDoc.data()
-    const participantData = participantDoc.data()
-
-    if (!ceremonyData || !participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
-
-    logMsg(`Ceremony document ${ceremonyDoc.id} okay`, MsgType.DEBUG)
-    logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
-
-    const { contributionProgress, status } = participantData!
-
-    // Check if can resume.
-    if (status !== ParticipantStatus.EXHUMED)
-      logMsg(`Cannot resume the contribution after a timeout expiration`, MsgType.ERROR)
-
-    await participantDoc.ref.update({
-      status: ParticipantStatus.READY,
-      lastUpdated: getCurrentServerTimestampInMillis()
-    })
-
-    logMsg(`Participant ${userId} has resumed the contribution for circuit ${contributionProgress}`, MsgType.DEBUG)
-  }
-)
diff --git a/apps/backend/src/lib/constants.ts b/apps/backend/src/lib/constants.ts
deleted file mode 100644
index cac1337b..00000000
--- a/apps/backend/src/lib/constants.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-/** Firebase */
-export const collections = {
-  users: "users",
-  participants: "participants",
-  ceremonies: "ceremonies",
-  circuits: "circuits",
-  contributions: "contributions",
-  timeouts: "timeouts"
-}
-
-export const names = {
-  output: `output`,
-  setup: `setup`,
-  contribute: `contribute`,
-  pot: `pot`,
-  zkeys: `zkeys`,
-  metadata: `metadata`,
-  transcripts: `transcripts`,
-  attestation: `attestation`
-}
-
-export const ceremoniesCollectionFields = {
-  coordinatorId: "coordinatorId",
-  description: "description",
-  endDate: "endDate",
-  lastUpdated: "lastUpdated",
-  prefix: "prefix",
-  startDate: "startDate",
-  state: "state",
-  title: "title",
-  type: "type"
-}
-
-export const contributionsCollectionFields = {
-  contributionTime: "contributionTime",
-  files: "files",
-  lastUpdated: "lastUpdated",
-  participantId: "participantId",
-  valid: "valid",
-  verificationTime: "verificationTime",
-  zkeyIndex: "zKeyIndex"
-}
-
-export const timeoutsCollectionFields = {
-  endDate: "endDate",
-  startDate: "startDate"
-}
diff --git a/apps/backend/src/lib/logs.ts b/apps/backend/src/lib/logs.ts
deleted file mode 100644
index 92b69789..00000000
--- a/apps/backend/src/lib/logs.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import * as functions from "firebase-functions"
-import { MsgType } from "../../types/index.js"
-
-export const GENERIC_ERRORS = {
-  GENERR_MISSING_INPUT: `You have not provided all the necessary data`,
-  GENERR_NO_AUTH_USER_FOUND: `The given id does not belong to an authenticated user`,
-  GENERR_NO_COORDINATOR: `The given id does not belong to a coordinator`,
-  GENERR_NO_CEREMONY_PROVIDED: `No ceremony has been provided`,
-  GENERR_NO_CIRCUIT_PROVIDED: `No circuit has been provided`,
-  GENERR_NO_CEREMONIES_OPENED: `No ceremonies are opened to contributions`,
-  GENERR_INVALID_CEREMONY: `The given ceremony is invalid`,
-  GENERR_INVALID_CIRCUIT: `The given circuit is invalid`,
-  GENERR_INVALID_PARTICIPANT: `The given participant is invalid`,
-  GENERR_CEREMONY_NOT_OPENED: `The given ceremony is not opened to contributions`,
-  GENERR_CEREMONY_NOT_CLOSED: `The given ceremony is not closed for finalization`,
-  GENERR_INVALID_PARTICIPANT_STATUS: `The participant has an invalid status`,
-  GENERR_INVALID_PARTICIPANT_CONTRIBUTION_STEP: `The participant has an invalid contribution step`,
-  GENERR_INVALID_CONTRIBUTION_PROGRESS: `The contribution progress is invalid`,
-  GENERR_INVALID_DOCUMENTS: `One or more provided identifier does not belong to a document`,
-  GENERR_NO_DATA: `Data not found`,
-  GENERR_NO_CIRCUIT: `Circuits not found`,
-  GENERR_NO_PARTICIPANT: `Participant not found`,
-  GENERR_NO_CONTRIBUTION: `Contributions not found`,
-  GENERR_NO_CURRENT_CONTRIBUTOR: `There is no current contributor for the circuit`,
-  GENERR_NO_TIMEOUT_FIRST_COTRIBUTOR: `Cannot compute a dynamic timeout for the first contributor`,
-  GENERR_NO_CIRCUITS: `Circuits not found for the ceremony`,
-  GENERR_NO_CONTRIBUTIONS: `Contributions not found for the circuit`,
-  GENERR_NO_RETRY: `The retry waiting time has not passed away yet`,
-  GENERR_WRONG_PATHS: `Wrong storage or database paths`,
-  GENERR_WRONG_FIELD: `Wrong document field`,
-  GENERR_WRONG_ENV_CONFIGURATION: `Your environment variables are not configured properly`
-}
-
-export const GENERIC_LOGS = {
-  GENLOG_NO_CEREMONIES_READY_TO_BE_OPENED: `There are no cerimonies ready to be opened to contributions`,
-  GENLOG_NO_CEREMONIES_READY_TO_BE_CLOSED: `There are no cerimonies ready to be closed`,
-  GENLOG_NO_CURRENT_CONTRIBUTOR: `There is no current contributor for the circuit`,
-  GENLOG_NO_TIMEOUT: `The timeout must not be triggered yet`
-}
-
-/**
- * Print a message customizing the default logger.
- * @param msg <string> - the msg to be shown.
- * @param msgType <MsgType> - the type of the message (e.g., debug, error).
- */
-export const logMsg = (msg: string, msgType: MsgType) => {
-  switch (msgType) {
-    case MsgType.INFO:
-      functions.logger.info(`[INFO] ${msg}`)
-      break
-    case MsgType.DEBUG:
-      functions.logger.debug(`[DEBUG] ${msg}`)
-      break
-    case MsgType.WARN:
-      functions.logger.warn(`[WARN] ${msg}`)
-      break
-    case MsgType.ERROR: {
-      functions.logger.error(`[ERROR] ${msg}`)
-      process.exit(0)
-    }
-    // eslint-disable-next-line
-    case MsgType.LOG:
-      functions.logger.log(`[LOG] ${msg}`)
-      break
-    default:
-      console.log(`[LOG] ${msg}`)
-      break
-  }
-}
diff --git a/apps/backend/src/lib/utils.ts b/apps/backend/src/lib/utils.ts
deleted file mode 100644
index 9d04950e..00000000
--- a/apps/backend/src/lib/utils.ts
+++ /dev/null
@@ -1,320 +0,0 @@
-import { DocumentData, DocumentSnapshot, Timestamp, WhereFilterOp } from "firebase-admin/firestore"
-import admin from "firebase-admin"
-import * as functions from "firebase-functions"
-import dotenv from "dotenv"
-import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3"
-import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
-import { createWriteStream } from "node:fs"
-import { pipeline } from "node:stream"
-import { promisify } from "node:util"
-import { readFileSync } from "fs"
-import fetch from "node-fetch"
-import mime from "mime-types"
-import { GENERIC_ERRORS, logMsg } from "./logs.js"
-import { ceremoniesCollectionFields, collections, timeoutsCollectionFields } from "./constants.js"
-import { CeremonyState, MsgType } from "../../types/index.js"
-
-dotenv.config()
-
-/**
- * Return the current server timestamp in milliseconds.
- * @returns <number>
- */
-export const getCurrentServerTimestampInMillis = (): number => Timestamp.now().toMillis()
-
-/**
- * Query ceremonies by state and (start/end) date value.
- * @param state <CeremonyState> - the value of the state to be queried.
- * @param dateField <string> - the start or end date field.
- * @param check <WhereFilerOp> - the query filter (where check).
- * @returns <Promise<admin.firestore.QuerySnapshot<admin.firestore.DocumentData>>>
- */
-export const queryCeremoniesByStateAndDate = async (
-  state: CeremonyState,
-  dateField: string,
-  check: WhereFilterOp
-): Promise<admin.firestore.QuerySnapshot<admin.firestore.DocumentData>> => {
-  // Get DB.
-  const firestoreDb = admin.firestore()
-
-  if (dateField !== ceremoniesCollectionFields.startDate && dateField !== ceremoniesCollectionFields.endDate)
-    logMsg(GENERIC_ERRORS.GENERR_WRONG_FIELD, MsgType.ERROR)
-
-  return firestoreDb
-    .collection(collections.ceremonies)
-    .where(ceremoniesCollectionFields.state, "==", state)
-    .where(dateField, check, getCurrentServerTimestampInMillis())
-    .get()
-}
-
-/**
- * Query timeouts by (start/end) date value.
- * @param ceremonyId <string> - the unique identifier of the ceremony.
- * @param participantId <string> - the unique identifier of the participant.
- * @param dateField <string> - the name of the date field.
- * @returns <Promise<admin.firestore.QuerySnapshot<admin.firestore.DocumentData>>>
- */
-export const queryValidTimeoutsByDate = async (
-  ceremonyId: string,
-  participantId: string,
-  dateField: string
-): Promise<admin.firestore.QuerySnapshot<admin.firestore.DocumentData>> => {
-  // Get DB.
-  const firestoreDb = admin.firestore()
-
-  if (dateField !== timeoutsCollectionFields.startDate && dateField !== timeoutsCollectionFields.endDate)
-    logMsg(GENERIC_ERRORS.GENERR_WRONG_FIELD, MsgType.ERROR)
-
-  return firestoreDb
-    .collection(
-      `${collections.ceremonies}/${ceremonyId}/${collections.participants}/${participantId}/${collections.timeouts}`
-    )
-    .where(dateField, ">=", getCurrentServerTimestampInMillis())
-    .get()
-}
-
-/**
- * Return the document belonging to a participant with a specified id (if exist).
- * @param ceremonyId <string> - the unique identifier of the ceremony.
- * @param participantId <string> - the unique identifier of the participant.
- * @returns <Promise<DocumentSnapshot<DocumentData>>>
- */
-export const getParticipantById = async (
-  ceremonyId: string,
-  participantId: string
-): Promise<DocumentSnapshot<DocumentData>> => {
-  // Get DB.
-  const firestore = admin.firestore()
-
-  const participantDoc = await firestore
-    .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
-    .doc(participantId)
-    .get()
-
-  if (!participantDoc.exists) logMsg(GENERIC_ERRORS.GENERR_NO_PARTICIPANT, MsgType.ERROR)
-
-  return participantDoc
-}
-
-/**
- * Return all circuits for a given ceremony (if any).
- * @param circuitsPath <string> - the collection path from ceremonies to circuits.
- * @returns Promise<Array<admin.firestore.QueryDocumentSnapshot<admin.firestore.DocumentData>>>
- */
-export const getCeremonyCircuits = async (
-  circuitsPath: string
-): Promise<Array<admin.firestore.QueryDocumentSnapshot<admin.firestore.DocumentData>>> => {
-  // Get DB.
-  const firestore = admin.firestore()
-
-  // Query for all docs.
-  const circuitsQuerySnap = await firestore.collection(circuitsPath).get()
-  const circuitDocs = circuitsQuerySnap.docs
-
-  if (!circuitDocs) logMsg(GENERIC_ERRORS.GENERR_NO_CIRCUITS, MsgType.ERROR)
-
-  return circuitDocs
-}
-
-/**
- * Format the next zkey index.
- * @param progress <number> - the progression in zkey index (= contributions).
- * @returns <string>
- */
-export const formatZkeyIndex = (progress: number): string => {
-  if (!process.env.FIRST_ZKEY_INDEX) logMsg(GENERIC_ERRORS.GENERR_WRONG_ENV_CONFIGURATION, MsgType.ERROR)
-
-  const initialZkeyIndex = process.env.FIRST_ZKEY_INDEX!
-
-  let index = progress.toString()
-
-  while (index.length < initialZkeyIndex.length) {
-    index = `0${index}`
-  }
-
-  return index
-}
-
-/**
- * Get the document for the circuit of the ceremony with a given sequence position.
- * @param circuitsPath <string> - the collection path from ceremonies to circuits.
- * @param position <number> - the sequence position of the circuit.
- * @returns Promise<admin.firestore.QueryDocumentSnapshot<admin.firestore.DocumentData>>
- */
-export const getCircuitDocumentByPosition = async (
-  circuitsPath: string,
-  position: number
-): Promise<admin.firestore.QueryDocumentSnapshot<admin.firestore.DocumentData>> => {
-  // Query for all circuit docs.
-  const circuitDocs = await getCeremonyCircuits(circuitsPath)
-
-  // Filter by position.
-  const filteredCircuits = circuitDocs.filter(
-    (circuit: admin.firestore.DocumentData) => circuit.data().sequencePosition === position
-  )
-
-  if (!filteredCircuits) logMsg(GENERIC_ERRORS.GENERR_NO_CIRCUIT, MsgType.ERROR)
-
-  // Get the circuit (nb. there will be only one circuit w/ that position).
-  const circuit = filteredCircuits.at(0)
-
-  if (!circuit) logMsg(GENERIC_ERRORS.GENERR_NO_CIRCUIT, MsgType.ERROR)
-
-  functions.logger.info(`Circuit w/ UID ${circuit?.id} at position ${position}`)
-
-  return circuit!
-}
-
-/**
- * Get the final contribution document for a specific circuit.
- * @param contributionsPath <string> - the collection path from circuit to contributions.
- * @returns Promise<admin.firestore.QueryDocumentSnapshot<admin.firestore.DocumentData>>
- */
-export const getFinalContributionDocument = async (
-  contributionsPath: string
-): Promise<admin.firestore.QueryDocumentSnapshot<admin.firestore.DocumentData>> => {
-  // Get DB.
-  const firestore = admin.firestore()
-
-  // Query for all contribution docs for circuit.
-  const contributionsQuerySnap = await firestore.collection(contributionsPath).get()
-  const contributionsDocs = contributionsQuerySnap.docs
-
-  if (!contributionsDocs) logMsg(GENERIC_ERRORS.GENERR_NO_CONTRIBUTIONS, MsgType.ERROR)
-
-  // Filter by index.
-  const filteredContributions = contributionsDocs.filter(
-    (contribution: admin.firestore.DocumentData) => contribution.data().zkeyIndex === "final"
-  )
-
-  if (!filteredContributions) logMsg(GENERIC_ERRORS.GENERR_NO_CONTRIBUTION, MsgType.ERROR)
-
-  // Get the contribution (nb. there will be only one final contribution).
-  const finalContribution = filteredContributions.at(0)
-
-  if (!finalContribution) logMsg(GENERIC_ERRORS.GENERR_NO_CONTRIBUTION, MsgType.ERROR)
-
-  return finalContribution!
-}
-
-/**
- * Return a new instance of the AWS S3 Client.
- * @returns <Promise<S3Client>
- */
-export const getS3Client = async (): Promise<S3Client> => {
-  if (
-    !process.env.AWS_ACCESS_KEY_ID ||
-    !process.env.AWS_SECRET_ACCESS_KEY ||
-    !process.env.AWS_REGION ||
-    !process.env.AWS_PRESIGNED_URL_EXPIRATION
-  )
-    logMsg(GENERIC_ERRORS.GENERR_WRONG_ENV_CONFIGURATION, MsgType.ERROR)
-
-  // Connect w/ S3.
-  return new S3Client({
-    credentials: {
-      accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
-      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!
-    },
-    region: process.env.AWS_REGION!
-  })
-}
-
-/**
- * Downloads and temporarily write a file from S3 bucket.
- * @param client <S3Client> - the AWS S3 client.
- * @param bucketName <string> - the name of the AWS S3 bucket.
- * @param objectKey <string> - the location of the object in the AWS S3 bucket.
- * @param tempFilePath <string> - the local path where the file will be written.
- */
-export const tempDownloadFromBucket = async (
-  client: S3Client,
-  bucketName: string,
-  objectKey: string,
-  tempFilePath: string
-) => {
-  // Prepare get object command.
-  const command = new GetObjectCommand({ Bucket: bucketName, Key: objectKey })
-
-  // Get pre-signed url.
-  const url = await getSignedUrl(client, command, { expiresIn: Number(process.env.AWS_PRESIGNED_URL_EXPIRATION!) })
-
-  // Download the file.
-  const response: any = await fetch(url, {
-    method: "GET",
-    headers: {
-      "Access-Control-Allow-Origin": "*"
-    }
-  })
-
-  if (!response.ok)
-    logMsg(`Something went wrong when downloading the file from the bucket: ${response.statusText}`, MsgType.ERROR)
-
-  // Temporarily write the file.
-  const streamPipeline = promisify(pipeline)
-  await streamPipeline(response.body!, createWriteStream(tempFilePath))
-}
-
-/**
- * Sleeps the function execution for given millis.
- * @dev to be used in combination with loggers when writing data into files.
- * @param ms <number> - sleep amount in milliseconds
- * @returns <Promise<unknown>>
- */
-export const sleep = (ms: number): Promise<unknown> => new Promise((resolve) => setTimeout(resolve, ms))
-
-/**
- * Upload a file from S3 bucket.
- * @param client <S3Client> - the AWS S3 client.
- * @param bucketName <string> - the name of the AWS S3 bucket.
- * @param objectKey <string> - the location of the object in the AWS S3 bucket.
- * @param tempFilePath <string> - the local path where the file will be written.
- */
-export const uploadFileToBucket = async (
-  client: S3Client,
-  bucketName: string,
-  objectKey: string,
-  tempFilePath: string
-) => {
-  // Get file content type.
-  const contentType = mime.lookup(tempFilePath) || ""
-
-  // Prepare command.
-  const command = new PutObjectCommand({ Bucket: bucketName, Key: objectKey, ContentType: contentType })
-
-  // Get pre-signed url.
-  const url = await getSignedUrl(client, command, { expiresIn: Number(process.env.AWS_PRESIGNED_URL_EXPIRATION!) })
-
-  // Make upload request (PUT).
-  const uploadTranscriptResponse = await fetch(url, {
-    method: "PUT",
-    body: readFileSync(tempFilePath),
-    headers: { "Content-Type": contentType }
-  })
-
-  // Check response.
-  if (!uploadTranscriptResponse.ok)
-    logMsg(`Something went wrong when uploading the transcript: ${uploadTranscriptResponse.statusText}`, MsgType.ERROR)
-
-  logMsg(`File uploaded successfully`, MsgType.DEBUG)
-}
-
-/**
- * Delete a file from S3 bucket.
- * @param client <S3Client> - the AWS S3 client.
- * @param bucketName <string> - the name of the AWS S3 bucket.
- * @param objectKey <string> - the location of the object in the AWS S3 bucket.
- */
-export const deleteObject = async (client: S3Client, bucketName: string, objectKey: string) => {
-  try {
-    // Prepare command.
-    const command = new DeleteObjectCommand({ Bucket: bucketName, Key: objectKey })
-
-    // Send command.
-    const data = await client.send(command)
-
-    logMsg(`Object ${objectKey} successfully deleted: ${data.$metadata.httpStatusCode}`, MsgType.INFO)
-  } catch (error: any) {
-    logMsg(`Something went wrong while deleting the ${objectKey} object: ${error}`, MsgType.ERROR)
-  }
-}
diff --git a/apps/backend/test/index.test.ts b/apps/backend/test/index.test.ts
deleted file mode 100644
index 19c8ebc5..00000000
--- a/apps/backend/test/index.test.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import chai from "chai"
-import chaiAsPromised from "chai-as-promised"
-import admin from "firebase-admin"
-import firebaseFncTest from "firebase-functions-test"
-// Import the exported function definitions from our functions/index.js file
-import { registerAuthUser } from "../src/functions/index.js"
-
-// Config chai.
-chai.use(chaiAsPromised)
-const { expect } = chai
-
-// Initialize the firebase-functions-test SDK using environment variables.
-// These variables are automatically set by firebase emulators:exec
-//
-// This configuration will be used to initialize the Firebase Admin SDK, so
-// when we use the Admin SDK in the tests below we can be confident it will
-// communicate with the emulators, not production.
-const test = firebaseFncTest({
-  databaseURL: process.env.FIREBASE_FIRESTORE_DATABASE_URL,
-  storageBucket: process.env.FIREBASE_STORAGE_BUCKET
-})
-
-describe("Unit tests", () => {
-  afterAll(() => {
-    test.cleanup()
-  })
-
-  it("tests an Auth function that interacts with Firestore", async () => {
-    const wrapped = test.wrap(registerAuthUser)
-
-    // Make a fake user to pass to the function
-    const uid = `${new Date().getTime()}`
-    const displayName = "UserA"
-    const email = `user-${uid}@example.com`
-    const photoURL = `https://www...."`
-
-    const user = test.auth.makeUserRecord({
-      uid,
-      displayName,
-      email,
-      photoURL
-    })
-
-    // Call the function
-    await wrapped(user)
-
-    // Check the data was written to the Firestore emulator
-    const snap = await admin.firestore().collection("users").doc(uid).get()
-    const data = snap.data()
-
-    expect(data?.name).to.eql(displayName)
-    expect(data?.email).to.eql(email)
-    expect(data?.photoURL).to.eql(photoURL)
-  })
-
-  it("should reject an Auth function when called without an authenticated user", async () => {
-    const wrapped = test.wrap(registerAuthUser)
-
-    // Call the function
-    await expect(wrapped).to.be.rejectedWith(Error)
-  })
-})
diff --git a/apps/backend/tsconfig.json b/apps/backend/tsconfig.json
deleted file mode 100644
index a257b9e4..00000000
--- a/apps/backend/tsconfig.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "extends": "../../tsconfig.json",
-  "compilerOptions": {
-    "outDir": "dist/",
-    "declarationDir": "dist/types"
-  },
-  "include": ["src/**/*", "test/**/*", "types/**/*"],
-  "exclude": ["node_modules", "tsconfig.json", "dist/**/*"]
-}
diff --git a/apps/backend/types/index.ts b/apps/backend/types/index.ts
deleted file mode 100644
index 8f78b742..00000000
--- a/apps/backend/types/index.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-export enum CeremonyState {
-  SCHEDULED = 1,
-  OPENED = 2,
-  PAUSED = 3,
-  CLOSED = 4,
-  FINALIZED = 5
-}
-
-export enum ParticipantStatus {
-  CREATED = 1,
-  WAITING = 2,
-  READY = 3,
-  CONTRIBUTING = 4,
-  CONTRIBUTED = 5,
-  DONE = 6,
-  FINALIZING = 7,
-  FINALIZED = 8,
-  TIMEDOUT = 9,
-  EXHUMED = 10
-}
-
-export enum ParticipantContributionStep {
-  DOWNLOADING = 1,
-  COMPUTING = 2,
-  UPLOADING = 3,
-  VERIFYING = 4,
-  COMPLETED = 5
-}
-
-export enum CeremonyType {
-  PHASE1 = 1,
-  PHASE2 = 2
-}
-
-export enum MsgType {
-  INFO = 1,
-  DEBUG = 2,
-  WARN = 3,
-  ERROR = 4,
-  LOG = 5
-}
-
-export enum RequestType {
-  PUT = 1,
-  GET = 2
-}
-
-export enum TimeoutType {
-  BLOCKING_CONTRIBUTION = 1,
-  BLOCKING_CLOUD_FUNCTION = 2
-}
-
-export enum CeremonyTimeoutType {
-  DYNAMIC = 1,
-  FIXED = 2
-}
diff --git a/apps/backend/types/snarkjs.d.ts b/apps/backend/types/snarkjs.d.ts
deleted file mode 100644
index ab419e76..00000000
--- a/apps/backend/types/snarkjs.d.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-/** Declaration file generated by dts-gen */
-
-declare module "snarkjs" {
-  // eslint-disable-next-line @typescript-eslint/no-use-before-define
-  export = snarkjs
-
-  declare const snarkjs: {
-    groth16: {
-      exportSolidityCallData: any
-      fullProve: any
-      prove: any
-      verify: any
-    }
-    plonk: {
-      exportSolidityCallData: any
-      fullProve: any
-      prove: any
-      setup: any
-      verify: any
-    }
-    powersOfTau: {
-      beacon: any
-      challengeContribute: any
-      contribute: any
-      convert: any
-      exportChallenge: any
-      exportJson: any
-      importResponse: any
-      newAccumulator: any
-      preparePhase2: any
-      truncate: any
-      verify: any
-    }
-    r1cs: {
-      exportJson: any
-      info: any
-      print: any
-    }
-    wtns: {
-      calculate: any
-      debug: any
-      exportJson: any
-    }
-    zKey: {
-      beacon: any
-      bellmanContribute: any
-      contribute: any
-      exportBellman: any
-      exportJson: any
-      exportSolidityVerifier: any
-      exportVerificationKey: any
-      importBellman: any
-      newZKey: any
-      verifyFromInit: any
-      verifyFromR1cs: any
-    }
-  }
-}
diff --git a/apps/phase2cli/env.json.default b/apps/phase2cli/env.json.default
deleted file mode 100644
index 2ea6473d..00000000
--- a/apps/phase2cli/env.json.default
+++ /dev/null
@@ -1,28 +0,0 @@
-{
-    "firebase": {
-        "FIREBASE_API_KEY": "your-firebase-api-key",
-        "FIREBASE_AUTH_DOMAIN": "your-firebase-auth-domain",
-        "FIREBASE_PROJECT_ID": "your-firebase-project-id",
-        "FIREBASE_STORAGE_BUCKET": "your-firebase-storage-bucket",
-        "FIREBASE_MESSAGING_SENDER_ID": "your-firebase-messaging-sender-id",
-        "FIREBASE_APP_ID": "your-firebase-app-id",
-        "FIREBASE_CF_URL_VERIFY_CONTRIBUTION: "your-verify-contribution-url"
-    },
-    "github": {
-        "GITHUB_CLIENT_ID": "your-github-oauth-app-client-id"
-    },
-    "localPaths": {
-        "LOCAL_PATH_DIR_CIRCUITS_R1CS": "./circuits/r1cs",
-        "LOCAL_PATH_DIR_CIRCUITS_METADATA": "./circuits/metadata",
-        "LOCAL_PATH_DIR_ZKEYS": "./zkeys",
-        "LOCAL_PATH_DIR_PTAU": "./circuits/ptau"
-    },
-    "others": {
-        "FIRST_ZKEY_INDEX": "00000"
-    },
-    "config": {
-      "CONFIG_CEREMONY_BUCKET_POSTFIX": "-phase2-ceremony-contributions",
-      "CONFIG_PRESIGNED_URL_EXPIRATION_IN_SECONDS": "900",
-      "CONFIG_STREAM_CHUNK_SIZE_IN_MB": "512"
-    }
-}
diff --git a/apps/phase2cli/package.json b/apps/phase2cli/package.json
deleted file mode 100644
index 65dcb11e..00000000
--- a/apps/phase2cli/package.json
+++ /dev/null
@@ -1,87 +0,0 @@
-{
-  "name": "@zkmpc/phase2cli",
-  "version": "0.0.2",
-  "description": "All-in-one interactive command-line for interfacing with zkSNARK Phase 2 Trusted Setup ceremonies",
-  "main": "dist/src/index.js",
-  "repository": "https://github.com/quadratic-funding/mpc-phase2-suite/cli",
-  "homepage": "https://github.com/quadratic-funding/mpc-phase2-suite",
-  "bugs": "https://github.com/quadratic-funding/mpc-phase2-suite/issues",
-  "author": {
-    "name": "Giacomo (0xjei)"
-  },
-  "license": "MIT",
-  "private": false,
-  "type": "module",
-  "types": "dist/types/index.d.ts",
-  "files": [
-    "dist/",
-    "src/",
-    "README.md"
-  ],
-  "keywords": [
-    "typescript",
-    "zero-knowledge",
-    "zk-snarks",
-    "phase-2",
-    "trusted-setup",
-    "ceremony",
-    "snarkjs",
-    "circom"
-  ],
-  "bin": {
-    "phase2cli": "dist/src/index.js"
-  },
-  "scripts": {
-    "build": "tsc",
-    "start": "node dist/src/index.js",
-    "auth": "yarn start auth",
-    "contribute": "yarn start contribute",
-    "clean": "yarn start clean",
-    "logout": "yarn start logout",
-    "coordinate:setup": "yarn start coordinate setup",
-    "coordinate:observe": "yarn start coordinate observe",
-    "coordinate:finalize": "yarn start coordinate finalize"
-  },
-  "devDependencies": {
-    "@types/clear": "^0.1.2",
-    "@types/cli-progress": "^3.11.0",
-    "@types/conf": "^3.0.0",
-    "@types/figlet": "^1.5.4",
-    "@types/mime-types": "^2.1.1",
-    "@types/node-emoji": "^1.8.1",
-    "@types/node-fetch": "^2.6.2",
-    "@types/ora": "^3.2.0",
-    "@types/prompts": "^2.0.14",
-    "@types/winston": "^2.4.4",
-    "typescript": "^4.7.4"
-  },
-  "dependencies": {
-    "@adobe/node-fetch-retry": "^2.2.0",
-    "@octokit/auth-oauth-app": "^5.0.1",
-    "@octokit/auth-oauth-device": "^4.0.0",
-    "@octokit/request": "^6.2.0",
-    "@zkmpc/actions": "^0.0.0",
-    "blakejs": "^1.2.1",
-    "boxen": "^7.0.0",
-    "chalk": "^5.0.1",
-    "clear": "^0.1.0",
-    "cli-progress": "^3.11.2",
-    "clipboardy": "^3.0.0",
-    "commander": "^9.4.0",
-    "conf": "^10.2.0",
-    "dotenv": "^16.0.1",
-    "figlet": "^1.5.2",
-    "firebase": "^9.9.1",
-    "log-symbols": "^5.1.0",
-    "mime-types": "^2.1.35",
-    "node-disk-info": "^1.3.0",
-    "node-emoji": "^1.11.0",
-    "node-fetch": "^3.2.10",
-    "open": "^8.4.0",
-    "ora": "^6.1.2",
-    "prompts": "^2.4.2",
-    "snarkjs": "^0.5.0",
-    "timer-node": "^5.0.6",
-    "winston": "^3.8.1"
-  }
-}
diff --git a/apps/phase2cli/src/commands/auth.ts b/apps/phase2cli/src/commands/auth.ts
deleted file mode 100644
index 1cc6a538..00000000
--- a/apps/phase2cli/src/commands/auth.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/usr/bin/env node
-
-import { getNewOAuthTokenUsingGithubDeviceFlow, signInToFirebaseWithGithubToken } from "@zkmpc/actions"
-import { symbols, theme } from "../lib/constants.js"
-import { GITHUB_ERRORS, handleAuthErrors, showError } from "../lib/errors.js"
-import { bootstrapCommandExec, getGithubUsername, terminate } from "../lib/utils.js"
-import { readLocalJsonFile } from "../lib/files.js"
-import { getStoredOAuthToken, hasStoredOAuthToken, setStoredOAuthToken } from "../lib/auth.js"
-
-// Get local configs.
-const { github } = readLocalJsonFile("../../env.json")
-
-/**
- * Look for the Github 2.0 OAuth token in the local storage if present; otherwise manage the request for a new token.
- * @returns <Promise<string>>
- */
-const handleGithubToken = async (): Promise<string> => {
-  let token: string
-
-  if (hasStoredOAuthToken())
-    // Get stored token.
-    token = String(getStoredOAuthToken())
-  else {
-    if (!github.GITHUB_CLIENT_ID) showError(GITHUB_ERRORS.GITHUB_NOT_CONFIGURED_PROPERLY, true)
-
-    // Request a new token.
-    token = await getNewOAuthTokenUsingGithubDeviceFlow(github.GITHUB_CLIENT_ID)
-
-    // Store the new token.
-    setStoredOAuthToken(token)
-  }
-
-  return token
-}
-
-/**
- * Auth command.
- * @dev TODO: add docs.
- */
-const auth = async () => {
-  try {
-    const { firebaseApp } = await bootstrapCommandExec()
-
-    if (!github.GITHUB_CLIENT_ID) showError(GITHUB_ERRORS.GITHUB_NOT_CONFIGURED_PROPERLY, true)
-
-    // Manage OAuth Github token.
-    const token = await handleGithubToken()
-
-    // Sign in with credentials.
-    await signInToFirebaseWithGithubToken(firebaseApp, token)
-
-    // Get Github username.
-    const ghUsername = await getGithubUsername(token)
-
-    console.log(`${symbols.success} You are authenticated as ${theme.bold(`@${ghUsername}`)}`)
-    console.log(
-      `${
-        symbols.info
-      } You can now contribute to zk-SNARK Phase2 Trusted Setup running ceremonies by running ${theme.bold(
-        theme.italic(`phase2cli contribute`)
-      )} command`
-    )
-
-    terminate(ghUsername)
-  } catch (err: any) {
-    handleAuthErrors(err)
-  }
-}
-
-export default auth
diff --git a/apps/phase2cli/src/commands/clean.ts b/apps/phase2cli/src/commands/clean.ts
deleted file mode 100644
index 235c544c..00000000
--- a/apps/phase2cli/src/commands/clean.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/usr/bin/env node
-
-import { emojis, paths, symbols, theme } from "../lib/constants.js"
-import { showError } from "../lib/errors.js"
-import { deleteDir, directoryExists } from "../lib/files.js"
-import { askForConfirmation } from "../lib/prompts.js"
-import { bootstrapCommandExec, customSpinner, sleep } from "../lib/utils.js"
-
-/**
- * Clean command.
- */
-const clean = async () => {
-  try {
-    // Initialize services.
-    await bootstrapCommandExec()
-
-    const spinner = customSpinner(`Cleaning up...`, "clock")
-
-    if (directoryExists(paths.outputPath)) {
-      console.log(theme.bold(`${symbols.warning} Be careful, this action is irreversible!`))
-
-      const { confirmation } = await askForConfirmation(
-        "Are you sure you want to continue with the clean up?",
-        "Yes",
-        "No"
-      )
-
-      if (confirmation) {
-        spinner.start()
-
-        // Do the clean up.
-        deleteDir(paths.outputPath)
-
-        // nb. simulate waiting time for 1s.
-        await sleep(1000)
-
-        spinner.succeed(`Cleanup was successfully completed ${emojis.broom}`)
-      }
-    } else {
-      console.log(`${symbols.info} There is nothing to clean ${emojis.eyes}`)
-    }
-  } catch (err: any) {
-    showError(`Something went wrong: ${err.toString()}`, true)
-  }
-}
-
-export default clean
diff --git a/apps/phase2cli/src/commands/contribute.ts b/apps/phase2cli/src/commands/contribute.ts
deleted file mode 100644
index 01509c05..00000000
--- a/apps/phase2cli/src/commands/contribute.ts
+++ /dev/null
@@ -1,166 +0,0 @@
-#!/usr/bin/env node
-
-import { getOpenedCeremonies, getCeremonyCircuits } from "@zkmpc/actions"
-import { httpsCallable } from "firebase/functions"
-import { handleCurrentAuthUserSignIn } from "../lib/auth.js"
-import { theme, emojis, collections, symbols, paths } from "../lib/constants.js"
-import { askForCeremonySelection } from "../lib/prompts.js"
-import { ParticipantContributionStep, ParticipantStatus } from "../../types/index.js"
-import {
-  bootstrapCommandExec,
-  terminate,
-  handleTimedoutMessageForContributor,
-  getContributorContributionsVerificationResults,
-  customSpinner,
-  getEntropyOrBeacon,
-  simpleLoader
-} from "../lib/utils.js"
-import { getDocumentById } from "../lib/firebase.js"
-import listenForContribution from "../lib/listeners.js"
-import { FIREBASE_ERRORS, GENERIC_ERRORS, showError } from "../lib/errors.js"
-import { checkAndMakeNewDirectoryIfNonexistent } from "../lib/files.js"
-
-/**
- * Contribute command.
- */
-const contribute = async () => {
-  try {
-    // Initialize services.
-    const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExec()
-    const checkParticipantForCeremony = httpsCallable(firebaseFunctions, "checkParticipantForCeremony")
-
-    // Handle current authenticated user sign in.
-    const { user, token, username } = await handleCurrentAuthUserSignIn(firebaseApp)
-
-    // Get running cerimonies info (if any).
-    const runningCeremoniesDocs = await getOpenedCeremonies(firestoreDatabase)
-
-    if (runningCeremoniesDocs.length === 0) showError(FIREBASE_ERRORS.FIREBASE_CEREMONY_NOT_OPENED, true)
-
-    console.log(
-      `${symbols.warning} ${theme.bold(
-        `The contribution process is based on a waiting queue mechanism (one contributor at a time) with an upper-bound time constraint per each contribution (does not restart if the process is halted for any reason).\n${symbols.info} Any contribution could take the bulk of your computational resources and memory based on the size of the circuit`
-      )} ${emojis.fire}\n`
-    )
-
-    // Ask to select a ceremony.
-    const ceremony = await askForCeremonySelection(runningCeremoniesDocs)
-
-    // Get ceremony circuits.
-    const circuits = await getCeremonyCircuits(firestoreDatabase, ceremony.id)
-    const numberOfCircuits = circuits.length
-
-    const spinner = customSpinner(`Checking eligibility...`, `clock`)
-    spinner.start()
-
-    // Call Cloud Function for participant check and registration.
-    const { data: canParticipate } = await checkParticipantForCeremony({ ceremonyId: ceremony.id })
-
-    // Get participant document.
-    // To be moved (maybe helpers folder? w/ query?)
-    const participantDoc = await getDocumentById(
-      `${collections.ceremonies}/${ceremony.id}/${collections.participants}`,
-      user.uid
-    )
-
-    // Get updated data from snap.
-    const participantData = participantDoc.data()
-
-    if (!participantData) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
-
-    // Check if the user can take part of the waiting queue for contributing.
-    if (canParticipate) {
-      spinner.succeed(`You are eligible to contribute to the ceremony ${emojis.tada}\n`)
-
-      // Check for output directory.
-      checkAndMakeNewDirectoryIfNonexistent(paths.outputPath)
-      checkAndMakeNewDirectoryIfNonexistent(paths.contributePath)
-      checkAndMakeNewDirectoryIfNonexistent(paths.contributionsPath)
-      checkAndMakeNewDirectoryIfNonexistent(paths.attestationPath)
-      checkAndMakeNewDirectoryIfNonexistent(paths.contributionTranscriptsPath)
-
-      // Check if entropy is needed.
-      let entropy = ""
-
-      if (
-        (participantData?.contributionProgress === numberOfCircuits &&
-          participantData?.contributionStep < ParticipantContributionStep.UPLOADING) ||
-        participantData?.contributionProgress < numberOfCircuits
-      )
-        entropy = await getEntropyOrBeacon(true)
-
-      // Listen to circuits and participant document changes.
-      await listenForContribution(
-        participantDoc,
-        ceremony,
-        firestoreDatabase,
-        circuits,
-        firebaseFunctions,
-        token,
-        username,
-        entropy
-      )
-    } else {
-      spinner.warn(`You are not eligible to contribute to the ceremony right now`)
-
-      await handleTimedoutMessageForContributor(participantData!, participantDoc.id, ceremony.id, false, username)
-    }
-
-    // Check if already contributed.
-    if (
-      ((!canParticipate && participantData?.status === ParticipantStatus.DONE) ||
-        participantData?.status === ParticipantStatus.FINALIZED) &&
-      participantData?.contributions.length > 0
-    ) {
-      spinner.fail(`You are not eligible to contribute to the ceremony\n`)
-
-      await simpleLoader(`Checking for contributions...`, `clock`, 1500)
-
-      // Return true and false based on contribution verification.
-      const contributionsValidity = await getContributorContributionsVerificationResults(
-        ceremony.id,
-        participantDoc.id,
-        circuits,
-        false
-      )
-      const numberOfValidContributions = contributionsValidity.filter(Boolean).length
-
-      if (numberOfValidContributions) {
-        console.log(
-          `Congrats, you have already contributed to ${theme.magenta(
-            theme.bold(numberOfValidContributions)
-          )} out of ${theme.magenta(theme.bold(numberOfCircuits))} circuits ${emojis.tada}`
-        )
-
-        // Show valid/invalid contributions per each circuit.
-        let idx = 0
-        for (const contributionValidity of contributionsValidity) {
-          console.log(
-            `${contributionValidity ? symbols.success : symbols.error} ${theme.bold(`Circuit`)} ${theme.bold(
-              theme.magenta(idx + 1)
-            )}`
-          )
-          idx += 1
-        }
-
-        console.log(
-          `\nWe wanna thank you for your participation in preserving the security for ${theme.bold(
-            ceremony.data.title
-          )} Trusted Setup ceremony ${emojis.pray}`
-        )
-      } else
-        console.log(
-          `\nYou have not successfully contributed to any of the ${theme.bold(
-            theme.magenta(circuits.length)
-          )} circuits ${emojis.upsideDown}`
-        )
-
-      // Graceful exit.
-      terminate(username)
-    }
-  } catch (err: any) {
-    showError(`Something went wrong: ${err.toString()}`, true)
-  }
-}
-
-export default contribute
diff --git a/apps/phase2cli/src/commands/finalize.ts b/apps/phase2cli/src/commands/finalize.ts
deleted file mode 100644
index 424072ef..00000000
--- a/apps/phase2cli/src/commands/finalize.ts
+++ /dev/null
@@ -1,261 +0,0 @@
-#!/usr/bin/env node
-import crypto from "crypto"
-import { zKey } from "snarkjs"
-import open from "open"
-import { getCeremonyCircuits } from "@zkmpc/actions"
-import { httpsCallable } from "firebase/functions"
-import { handleCurrentAuthUserSignIn, onlyCoordinator } from "../lib/auth.js"
-import { collections, emojis, paths, solidityVersion, symbols, theme } from "../lib/constants.js"
-import { GENERIC_ERRORS, showError } from "../lib/errors.js"
-import {
-  checkAndMakeNewDirectoryIfNonexistent,
-  getLocalFilePath,
-  readFile,
-  writeFile,
-  writeLocalJsonFile
-} from "../lib/files.js"
-import { askForCeremonySelection } from "../lib/prompts.js"
-import { getClosedCeremonies } from "../lib/queries.js"
-import {
-  bootstrapCommandExec,
-  customSpinner,
-  getBucketName,
-  getContributorContributionsVerificationResults,
-  getEntropyOrBeacon,
-  getValidContributionAttestation,
-  makeContribution,
-  multiPartUpload,
-  publishGist,
-  sleep,
-  terminate
-} from "../lib/utils.js"
-import { getDocumentById } from "../lib/firebase.js"
-
-/**
- * Finalize command.
- */
-const finalize = async () => {
-  try {
-    // Initialize services.
-    const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExec()
-
-    // Setup ceremony callable Cloud Function initialization.
-    const checkAndPrepareCoordinatorForFinalization = httpsCallable(
-      firebaseFunctions,
-      "checkAndPrepareCoordinatorForFinalization"
-    )
-    const finalizeLastContribution = httpsCallable(firebaseFunctions, "finalizeLastContribution")
-    const finalizeCeremony = httpsCallable(firebaseFunctions, "finalizeCeremony")
-
-    // Handle current authenticated user sign in.
-    const { user, token, username } = await handleCurrentAuthUserSignIn(firebaseApp)
-
-    // Check custom claims for coordinator role.
-    await onlyCoordinator(user)
-
-    // Get closed cerimonies info (if any).
-    const closedCeremoniesDocs = await getClosedCeremonies()
-
-    console.log(
-      `${symbols.warning} The computation of the final contribution could take the bulk of your computational resources and memory based on the size of the circuit ${emojis.fire}\n`
-    )
-
-    // Ask to select a ceremony.
-    const ceremony = await askForCeremonySelection(closedCeremoniesDocs)
-
-    // Get coordinator participant document.
-    const participantDoc = await getDocumentById(
-      `${collections.ceremonies}/${ceremony.id}/${collections.participants}`,
-      user.uid
-    )
-
-    const { data: canFinalize } = await checkAndPrepareCoordinatorForFinalization({ ceremonyId: ceremony.id })
-
-    if (!canFinalize) showError(`You are not able to finalize the ceremony`, true)
-
-    // Clean directories.
-    checkAndMakeNewDirectoryIfNonexistent(paths.outputPath)
-    checkAndMakeNewDirectoryIfNonexistent(paths.finalizePath)
-    checkAndMakeNewDirectoryIfNonexistent(paths.finalZkeysPath)
-    checkAndMakeNewDirectoryIfNonexistent(paths.finalPotPath)
-    checkAndMakeNewDirectoryIfNonexistent(paths.finalAttestationsPath)
-    checkAndMakeNewDirectoryIfNonexistent(paths.verificationKeysPath)
-    checkAndMakeNewDirectoryIfNonexistent(paths.verifierContractsPath)
-
-    // Handle random beacon request/generation.
-    const beacon = await getEntropyOrBeacon(false)
-    const beaconHashStr = crypto.createHash("sha256").update(beacon).digest("hex")
-    console.log(`${symbols.info} Your final beacon hash: ${theme.bold(beaconHashStr)}`)
-
-    // Get ceremony circuits.
-    const circuits = await getCeremonyCircuits(firestoreDatabase, ceremony.id)
-
-    // Attestation preamble.
-    const attestationPreamble = `Hey, I'm ${username} and I have finalized the ${ceremony.data.title} MPC Phase2 Trusted Setup ceremony.\nThe following are the finalization signatures:`
-
-    // Finalize each circuit
-    for await (const circuit of circuits) {
-      await makeContribution(ceremony, circuit, beaconHashStr, username, true, firebaseFunctions)
-
-      // 6. Export the verification key.
-
-      // Paths config.
-      const finalZkeyLocalPath = `${paths.finalZkeysPath}/${circuit.data.prefix}_final.zkey`
-      const verificationKeyLocalPath = `${paths.verificationKeysPath}/${circuit.data.prefix}_vkey.json`
-      const verificationKeyStoragePath = `${collections.circuits}/${circuit.data.prefix}/${circuit.data.prefix}_vkey.json`
-
-      const spinner = customSpinner(`Extracting verification key...`, "clock")
-      spinner.start()
-
-      // Export vkey.
-      const verificationKeyJSONData = await zKey.exportVerificationKey(finalZkeyLocalPath)
-
-      spinner.text = `Writing verification key locally...`
-
-      // Write locally.
-      writeLocalJsonFile(verificationKeyLocalPath, verificationKeyJSONData)
-
-      // nb. need to wait for closing the file descriptor.
-      await sleep(1500)
-
-      // Upload vkey to storage.
-      const startMultiPartUpload = httpsCallable(firebaseFunctions, "startMultiPartUpload")
-      const generatePreSignedUrlsParts = httpsCallable(firebaseFunctions, "generatePreSignedUrlsParts")
-      const completeMultiPartUpload = httpsCallable(firebaseFunctions, "completeMultiPartUpload")
-
-      const bucketName = getBucketName(ceremony.data.prefix)
-
-      await multiPartUpload(
-        startMultiPartUpload,
-        generatePreSignedUrlsParts,
-        completeMultiPartUpload,
-        bucketName,
-        verificationKeyStoragePath,
-        verificationKeyLocalPath
-      )
-
-      spinner.succeed(`Verification key correctly stored`)
-
-      // 7. Turn the verifier into a smart contract.
-      const verifierContractLocalPath = `${paths.verifierContractsPath}/${circuit.data.name}_verifier.sol`
-      const verifierContractStoragePath = `${collections.circuits}/${circuit.data.prefix}/${circuit.data.prefix}_verifier.sol`
-
-      spinner.text = `Extracting verifier contract...`
-      spinner.start()
-
-      // Export solidity verifier.
-      let verifierCode = await zKey.exportSolidityVerifier(
-        finalZkeyLocalPath,
-        { groth16: readFile(getLocalFilePath("../../../node_modules/snarkjs/templates/verifier_groth16.sol.ejs")) },
-        console
-      )
-
-      // Update solidity version.
-      verifierCode = verifierCode.replace(/pragma solidity \^\d+\.\d+\.\d+/, `pragma solidity ^${solidityVersion}`)
-
-      spinner.text = `Writing verifier contract locally...`
-
-      // Write locally.
-      writeFile(verifierContractLocalPath, verifierCode)
-
-      // nb. need to wait for closing the file descriptor.
-      await sleep(1500)
-
-      // Upload vkey to storage.
-      await multiPartUpload(
-        startMultiPartUpload,
-        generatePreSignedUrlsParts,
-        completeMultiPartUpload,
-        bucketName,
-        verifierContractStoragePath,
-        verifierContractLocalPath
-      )
-      spinner.succeed(`Verifier contract correctly stored`)
-
-      spinner.text = `Finalizing circuit...`
-      spinner.start()
-
-      // Finalize circuit contribution.
-      await finalizeLastContribution({
-        ceremonyId: ceremony.id,
-        circuitId: circuit.id,
-        bucketName
-      })
-
-      spinner.succeed(`Circuit successfully finalized`)
-    }
-
-    process.stdout.write(`\n`)
-
-    const spinner = customSpinner(`Finalizing the ceremony...`, "clock")
-    spinner.start()
-
-    // Setup ceremony on the server.
-    await finalizeCeremony({
-      ceremonyId: ceremony.id
-    })
-
-    spinner.succeed(
-      `Congrats, you have correctly finalized the ${theme.bold(ceremony.data.title)} circuits ${emojis.tada}\n`
-    )
-
-    spinner.text = `Generating public finalization attestation...`
-    spinner.start()
-
-    // Get updated participant data.
-    const participantData = participantDoc.data()
-
-    if (!participantData) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
-
-    // Return true and false based on contribution verification.
-    const contributionsValidity = await getContributorContributionsVerificationResults(
-      ceremony.id,
-      participantDoc.id,
-      circuits,
-      true
-    )
-
-    // Get only valid contribution hashes.
-    const attestation = await getValidContributionAttestation(
-      contributionsValidity,
-      circuits,
-      participantData!,
-      ceremony.id,
-      participantDoc.id,
-      attestationPreamble,
-      true
-    )
-
-    writeFile(`${paths.finalAttestationsPath}/${ceremony.data.prefix}_final_attestation.log`, Buffer.from(attestation))
-
-    // nb. wait for closing file descriptor.
-    await sleep(1000)
-
-    spinner.text = `Uploading public finalization attestation as Github Gist...`
-
-    const gistUrl = await publishGist(token, attestation, ceremony.data.prefix, ceremony.data.title)
-
-    spinner.succeed(
-      `Public finalization attestation successfully published as Github Gist at this link ${theme.bold(
-        theme.underlined(gistUrl)
-      )}`
-    )
-
-    // Attestation link via Twitter.
-    const attestationTweet = `https://twitter.com/intent/tweet?text=I%20have%20finalized%20the%20${ceremony.data.title}%20Phase%202%20Trusted%20Setup%20ceremony!%20You%20can%20view%20my%20final%20attestation%20here:%20${gistUrl}%20#Ethereum%20#ZKP%20#PSE`
-
-    console.log(
-      `\nYou can tweet about the ceremony finalization if you'd like (click on the link below ${
-        emojis.pointDown
-      }) \n\n${theme.underlined(attestationTweet)}`
-    )
-
-    await open(attestationTweet)
-
-    terminate(username)
-  } catch (err: any) {
-    showError(`Something went wrong: ${err.toString()}`, true)
-  }
-}
-
-export default finalize
diff --git a/apps/phase2cli/src/commands/index.ts b/apps/phase2cli/src/commands/index.ts
deleted file mode 100644
index b60ad60c..00000000
--- a/apps/phase2cli/src/commands/index.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import setup from "./setup.js"
-import auth from "./auth.js"
-import contribute from "./contribute.js"
-import observe from "./observe.js"
-import finalize from "./finalize.js"
-import clean from "./clean.js"
-import logout from "./logout.js"
-
-export { setup, auth, contribute, observe, finalize, clean, logout }
diff --git a/apps/phase2cli/src/commands/logout.ts b/apps/phase2cli/src/commands/logout.ts
deleted file mode 100644
index f666fb22..00000000
--- a/apps/phase2cli/src/commands/logout.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/usr/bin/env node
-
-import { getAuth, signOut } from "firebase/auth"
-import { deleteStoredOAuthToken, handleCurrentAuthUserSignIn } from "../lib/auth.js"
-import { emojis, symbols, theme } from "../lib/constants.js"
-import { showError } from "../lib/errors.js"
-import { askForConfirmation } from "../lib/prompts.js"
-import { bootstrapCommandExec, customSpinner } from "../lib/utils.js"
-
-/**
- * Logout command.
- */
-const logout = async () => {
-  try {
-    // Initialize services.
-    const { firebaseApp } = await bootstrapCommandExec()
-
-    // Handle current authenticated user sign in.
-    await handleCurrentAuthUserSignIn(firebaseApp)
-
-    // Inform the user about deassociation in Github and re run auth
-    console.log(
-      `${symbols.warning} We do not use any Github access token for authentication; thus we cannot revoke the authorization from your Github account for this CLI application`
-    )
-    console.log(
-      `${symbols.info} You can do this manually as reported in the official Github documentation ${
-        emojis.pointDown
-      }\n\n${theme.bold(
-        theme.underlined(
-          `https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/reviewing-your-authorized-applications-oauth`
-        )
-      )}\n`
-    )
-
-    // Ask for confirmation.
-    const { confirmation } = await askForConfirmation("Are you sure you want to log out?", "Yes", "No")
-
-    if (confirmation) {
-      const spinner = customSpinner(`Logging out...`, "clock")
-      spinner.start()
-
-      // Sign out.
-      const auth = getAuth()
-      await signOut(auth)
-
-      // Delete local token.
-      deleteStoredOAuthToken()
-
-      spinner.stop()
-      console.log(`${symbols.success} Logout successfully completed ${emojis.wave}`)
-    }
-  } catch (err: any) {
-    showError(`Something went wrong: ${err.toString()}`, true)
-  }
-}
-
-export default logout
diff --git a/apps/phase2cli/src/commands/observe.ts b/apps/phase2cli/src/commands/observe.ts
deleted file mode 100644
index 2551ecaa..00000000
--- a/apps/phase2cli/src/commands/observe.ts
+++ /dev/null
@@ -1,181 +0,0 @@
-#!/usr/bin/env node
-
-import readline from "readline"
-import logSymbols from "log-symbols"
-import { getOpenedCeremonies, getCeremonyCircuits } from "@zkmpc/actions"
-import { FirebaseDocumentInfo } from "../../types/index.js"
-import { onlyCoordinator, handleCurrentAuthUserSignIn } from "../lib/auth.js"
-import {
-  bootstrapCommandExec,
-  convertToDoubleDigits,
-  customSpinner,
-  getSecondsMinutesHoursFromMillis,
-  sleep
-} from "../lib/utils.js"
-import { askForCeremonySelection } from "../lib/prompts.js"
-import { getCurrentContributorContribution } from "../lib/queries.js"
-import { GENERIC_ERRORS, showError } from "../lib/errors.js"
-import { theme, emojis, symbols, observationWaitingTimeInMillis } from "../lib/constants.js"
-
-/**
- * Clean cursor lines from current position back to root (default: zero).
- * @param currentCursorPos - the current position of the cursor.
- * @returns <number>
- */
-const cleanCursorPosBackToRoot = (currentCursorPos: number) => {
-  while (currentCursorPos < 0) {
-    // Get back and clean line by line.
-    readline.cursorTo(process.stdout, 0)
-    readline.clearLine(process.stdout, 0)
-    readline.moveCursor(process.stdout, -1, -1)
-
-    currentCursorPos += 1
-  }
-
-  return currentCursorPos
-}
-
-/**
- * Show the latest updates for the given circuit.
- * @param ceremony <FirebaseDocumentInfo> - the Firebase document containing info about the ceremony.
- * @param circuit <FirebaseDocumentInfo> - the Firebase document containing info about the circuit.
- * @returns Promise<number> return the current position of the cursor (i.e., number of lines displayed).
- */
-const displayLatestCircuitUpdates = async (
-  ceremony: FirebaseDocumentInfo,
-  circuit: FirebaseDocumentInfo
-): Promise<number> => {
-  let observation = theme.bold(`- Circuit # ${theme.magenta(circuit.data.sequencePosition)}`) // Observation output.
-  let cursorPos = -1 // Current cursor position (nb. decrease every time there's a new line!).
-
-  const { waitingQueue } = circuit.data
-
-  // Get info from circuit.
-  const { currentContributor } = waitingQueue
-  const { completedContributions } = waitingQueue
-
-  if (!currentContributor) {
-    observation += `\n> Nobody's currently waiting to contribute ${emojis.eyes}`
-    cursorPos -= 1
-  } else {
-    // Search for currentContributor' contribution.
-    const contributions = await getCurrentContributorContribution(ceremony.id, circuit.id, currentContributor)
-
-    if (!contributions.length) {
-      // The contributor is currently contributing.
-      observation += `\n> Participant ${theme.bold(`#${completedContributions + 1}`)} (${theme.bold(
-        currentContributor
-      )}) is currently contributing ${emojis.fire}`
-
-      cursorPos -= 1
-    } else {
-      // The contributor has contributed.
-      observation += `\n> Participant ${theme.bold(`#${completedContributions}`)} (${theme.bold(
-        currentContributor
-      )}) has completed the contribution ${emojis.tada}`
-
-      cursorPos -= 1
-
-      // The contributor has finished the contribution.
-      const contributionData = contributions.at(0)?.data
-
-      if (!contributionData) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
-
-      // Convert times to seconds.
-      const {
-        seconds: contributionTimeSeconds,
-        minutes: contributionTimeMinutes,
-        hours: contributionTimeHours
-      } = getSecondsMinutesHoursFromMillis(contributionData?.contributionTime)
-      const {
-        seconds: verificationTimeSeconds,
-        minutes: verificationTimeMinutes,
-        hours: verificationTimeHours
-      } = getSecondsMinutesHoursFromMillis(contributionData?.verificationTime)
-
-      observation += `\n> The ${theme.bold("computation")} took ${theme.bold(
-        `${convertToDoubleDigits(contributionTimeHours)}:${convertToDoubleDigits(
-          contributionTimeMinutes
-        )}:${convertToDoubleDigits(contributionTimeSeconds)}`
-      )}`
-      observation += `\n> The ${theme.bold("verification")} took ${theme.bold(
-        `${convertToDoubleDigits(verificationTimeHours)}:${convertToDoubleDigits(
-          verificationTimeMinutes
-        )}:${convertToDoubleDigits(verificationTimeSeconds)}`
-      )}`
-      observation += `\n> Contribution ${
-        contributionData?.valid
-          ? `${theme.bold("VALID")} ${symbols.success}`
-          : `${theme.bold("INVALID")} ${symbols.error}`
-      }`
-
-      cursorPos -= 3
-    }
-  }
-
-  // Show observation for circuit.
-  process.stdout.write(`${observation}\n\n`)
-  cursorPos -= 1
-
-  return cursorPos
-}
-
-/**
- * Observe command.
- */
-const observe = async () => {
-  try {
-    // Initialize services.
-    const { firebaseApp, firestoreDatabase } = await bootstrapCommandExec()
-
-    // Handle current authenticated user sign in.
-    const { user } = await handleCurrentAuthUserSignIn(firebaseApp)
-
-    // Check custom claims for coordinator role.
-    await onlyCoordinator(user)
-
-    // Get running cerimonies info (if any).
-    const runningCeremoniesDocs = await getOpenedCeremonies(firestoreDatabase)
-
-    // Ask to select a ceremony.
-    const ceremony = await askForCeremonySelection(runningCeremoniesDocs)
-
-    console.log(`${logSymbols.info} Refresh rate set to ~3 seconds for waiting queue updates\n`)
-
-    let cursorPos = 0 // Keep track of current cursor position.
-
-    const spinner = customSpinner(`Getting ready...`, "clock")
-    spinner.start()
-
-    // Get circuit updates every 3 seconds.
-    setInterval(async () => {
-      // Clean cursor position back to root.
-      cursorPos = cleanCursorPosBackToRoot(cursorPos)
-
-      const spinner = customSpinner(`Updating...`, "clock")
-      spinner.start()
-
-      // Get updates from circuits.
-      const circuits = await getCeremonyCircuits(firestoreDatabase, ceremony.id)
-
-      await sleep(observationWaitingTimeInMillis / 10) // Just for a smoother UX/UI experience.
-
-      spinner.stop()
-
-      // Observe changes for each circuit
-      for await (const circuit of circuits) cursorPos += await displayLatestCircuitUpdates(ceremony, circuit)
-
-      process.stdout.write(`Press CTRL+C to exit`)
-
-      await sleep(1000) // Just for a smoother UX/UI experience.
-    }, observationWaitingTimeInMillis)
-
-    await sleep(observationWaitingTimeInMillis) // Wait until the first update.
-
-    spinner.stop()
-  } catch (err: any) {
-    showError(`Something went wrong: ${err.toString()}`, true)
-  }
-}
-
-export default observe
diff --git a/apps/phase2cli/src/commands/setup.ts b/apps/phase2cli/src/commands/setup.ts
deleted file mode 100644
index 4adbbbf3..00000000
--- a/apps/phase2cli/src/commands/setup.ts
+++ /dev/null
@@ -1,649 +0,0 @@
-#!/usr/bin/env node
-
-import { zKey, r1cs } from "snarkjs"
-import winston from "winston"
-import blake from "blakejs"
-import boxen from "boxen"
-import { httpsCallable } from "firebase/functions"
-import { Dirent, renameSync } from "fs"
-import {
-  theme,
-  symbols,
-  emojis,
-  potFilenameTemplate,
-  potDownloadUrlTemplate,
-  paths,
-  names,
-  collections
-} from "../lib/constants.js"
-import { handleCurrentAuthUserSignIn, onlyCoordinator } from "../lib/auth.js"
-import {
-  bootstrapCommandExec,
-  convertToDoubleDigits,
-  customSpinner,
-  estimatePoT,
-  extractPoTFromFilename,
-  extractPrefix,
-  getBucketName,
-  getCircuitMetadataFromR1csFile,
-  multiPartUpload,
-  simpleLoader,
-  sleep,
-  terminate
-} from "../lib/utils.js"
-import {
-  askCeremonyInputData,
-  askCircomCompilerVersionAndCommitHash,
-  askCircuitInputData,
-  askForCircuitSelectionFromLocalDir,
-  askForConfirmation,
-  askForPtauSelectionFromLocalDir,
-  askForZkeySelectionFromLocalDir,
-  askPowersOftau
-} from "../lib/prompts.js"
-import {
-  cleanDir,
-  directoryExists,
-  downloadFileFromUrl,
-  getDirFilesSubPaths,
-  getFileStats,
-  readFile
-} from "../lib/files.js"
-import { CeremonyTimeoutType, Circuit, CircuitFiles, CircuitInputData, CircuitTimings } from "../../types/index.js"
-import { GENERIC_ERRORS, showError } from "../lib/errors.js"
-import { createS3Bucket, objectExist } from "../lib/storage.js"
-
-/**
- * Return the files from the current working directory which have the extension specified as input.
- * @param cwd <string> - the current working directory.
- * @param ext <string> - the file extension.
- * @returns <Promise<Array<Dirent>>>
- */
-const getSpecifiedFilesFromCwd = async (cwd: string, ext: string): Promise<Array<Dirent>> => {
-  // Check if the current directory contains the .r1cs files.
-  const cwdFiles = await getDirFilesSubPaths(cwd)
-  const cwdExtFiles = cwdFiles.filter((file: Dirent) => file.name.includes(ext))
-
-  return cwdExtFiles
-}
-
-/**
- * Handle one or more circuit addition for the specified ceremony.
- * @param cwd <string> - the current working directory.
- * @param cwdR1csFiles <Array<Dirent>> - the list of R1CS files in the current working directory.
- * @param timeoutMechanismType <CeremonyTimeoutType> - the choosen timeout mechanism type for the ceremony.
- * @param isCircomVersionEqualAmongCircuits <boolean> - true if the circom compiler version is equal among circuits; otherwise false.
- * @returns <Promise<Array<CircuitInputData>>>
- */
-const handleCircuitsAddition = async (
-  cwd: string,
-  cwdR1csFiles: Array<Dirent>,
-  timeoutMechanismType: CeremonyTimeoutType,
-  isCircomVersionEqualAmongCircuits: boolean
-): Promise<Array<CircuitInputData>> => {
-  const circuitsInputData: Array<CircuitInputData> = []
-
-  let wannaAddAnotherCircuit = true // Loop flag.
-  let circuitSequencePosition = 1 // Sequential circuit position for handling the contributions queue for the ceremony.
-  let leftCircuits: Array<Dirent> = cwdR1csFiles
-
-  // Clear directory.
-  cleanDir(paths.metadataPath)
-
-  while (wannaAddAnotherCircuit) {
-    console.log(theme.bold(`\n- Circuit # ${theme.magenta(`${circuitSequencePosition}`)}\n`))
-
-    // Interactively select a circuit.
-    const circuitNameWithExt = await askForCircuitSelectionFromLocalDir(leftCircuits)
-
-    // Remove the selected circuit from the list.
-    leftCircuits = leftCircuits.filter((dirent: Dirent) => dirent.name !== circuitNameWithExt)
-
-    // Ask for circuit input data.
-    const circuitInputData = await askCircuitInputData(timeoutMechanismType, isCircomVersionEqualAmongCircuits)
-
-    // Remove .r1cs file extension.
-    const circuitName = circuitNameWithExt.substring(0, circuitNameWithExt.indexOf("."))
-    const circuitPrefix = extractPrefix(circuitName)
-
-    // R1CS circuit file path.
-    const r1csMetadataFilePath = `${paths.metadataPath}/${circuitPrefix}_${names.metadata}.log`
-    const r1csFilePath = `${cwd}/${circuitName}.r1cs`
-
-    // Custom logger for R1CS metadata save.
-    const logger = winston.createLogger({
-      level: "info",
-      transports: new winston.transports.File({
-        filename: r1csMetadataFilePath,
-        format: winston.format.printf((log) => log.message),
-        level: "info"
-      })
-    })
-
-    const spinner = customSpinner(`Looking for metadata...`, "clock")
-    spinner.start()
-
-    // Read .r1cs file and log/store info.
-    await r1cs.info(r1csFilePath, logger)
-
-    // Sleep to avoid logger unexpected termination.
-    await sleep(1000)
-
-    // Store data.
-    circuitsInputData.push({
-      ...circuitInputData,
-      name: circuitName,
-      prefix: circuitPrefix,
-      sequencePosition: circuitSequencePosition
-    })
-
-    spinner.succeed(
-      `Metadata stored in your working directory ${theme.bold(theme.underlined(r1csMetadataFilePath.substring(1)))}\n`
-    )
-
-    let readyToAssembly = false
-
-    // In case of negative confirmation or no more circuits left.
-    if (leftCircuits.length !== 0) {
-      // Ask for another circuit.
-      const { confirmation } = await askForConfirmation("Want to add another circuit for the ceremony?", "Okay", "No")
-
-      if (confirmation === undefined) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
-
-      if (confirmation === false) readyToAssembly = true
-      else circuitSequencePosition += 1
-    } else readyToAssembly = true
-
-    // Assembly the ceremony.
-    if (readyToAssembly) wannaAddAnotherCircuit = false
-  }
-
-  return circuitsInputData
-}
-
-/**
- * Check if the smallest pot has been already downloaded.
- * @param neededPowers <number> - the representation of the constraints of the circuit in terms of powers.
- * @returns <Promise<boolean>>
- */
-const checkIfPotAlreadyDownloaded = async (neededPowers: number): Promise<boolean> => {
-  // Get files from dir.
-  const potFiles = await getDirFilesSubPaths(paths.potPath)
-
-  let alreadyDownloaded = false
-
-  for (const potFile of potFiles) {
-    const powers = extractPoTFromFilename(potFile.name)
-
-    if (powers === neededPowers) alreadyDownloaded = true
-  }
-
-  return alreadyDownloaded
-}
-
-/**
- * Setup a new Groth16 zkSNARK Phase 2 Trusted Setup ceremony.
- */
-const setup = async () => {
-  // Circuit data state.
-  let circuitsInputData: Array<CircuitInputData> = []
-  const circuits: Array<Circuit> = []
-
-  /** CORE */
-  try {
-    // Get current working directory.
-    const cwd = process.cwd()
-
-    const { firebaseApp, firebaseFunctions } = await bootstrapCommandExec()
-
-    // Setup ceremony callable Cloud Function initialization.
-    const setupCeremony = httpsCallable(firebaseFunctions, "setupCeremony")
-    const createBucket = httpsCallable(firebaseFunctions, "createBucket")
-    const startMultiPartUpload = httpsCallable(firebaseFunctions, "startMultiPartUpload")
-    const generatePreSignedUrlsParts = httpsCallable(firebaseFunctions, "generatePreSignedUrlsParts")
-    const completeMultiPartUpload = httpsCallable(firebaseFunctions, "completeMultiPartUpload")
-    const checkIfObjectExist = httpsCallable(firebaseFunctions, "checkIfObjectExist")
-
-    // Handle current authenticated user sign in.
-    const { user, username } = await handleCurrentAuthUserSignIn(firebaseApp)
-
-    // Check custom claims for coordinator role.
-    await onlyCoordinator(user)
-
-    console.log(
-      `${symbols.warning} To setup a zkSNARK Groth16 Phase 2 Trusted Setup ceremony you need to have the Rank-1 Constraint System (R1CS) file for each circuit in your working directory`
-    )
-    console.log(`${symbols.info} Current working directory: ${theme.bold(theme.underlined(cwd))}\n`)
-
-    // Check if the current directory contains the .r1cs files.
-    const cwdR1csFiles = await getSpecifiedFilesFromCwd(cwd, `.r1cs`)
-    if (!cwdR1csFiles.length) showError(`Your working directory must contain the R1CS files for each circuit`, true)
-
-    // Ask for ceremony input data.
-    const ceremonyInputData = await askCeremonyInputData()
-    const ceremonyPrefix = extractPrefix(ceremonyInputData.title)
-
-    // Check for circom compiler version and commit hash.
-    const { confirmation: isCircomVersionEqualAmongCircuits } = await askForConfirmation(
-      "Was the same version of the circom compiler used for each circuit that will be designated for the ceremony?",
-      "Yes",
-      "No"
-    )
-
-    // Check for output directory.
-    if (!directoryExists(paths.outputPath)) cleanDir(paths.outputPath)
-
-    // Clean directories.
-    cleanDir(paths.setupPath)
-    cleanDir(paths.potPath)
-    cleanDir(paths.metadataPath)
-    cleanDir(paths.zkeysPath)
-
-    if (isCircomVersionEqualAmongCircuits) {
-      // Ask for circom compiler data.
-      const { version, commitHash } = await askCircomCompilerVersionAndCommitHash()
-
-      // Ask to add circuits.
-      circuitsInputData = await handleCircuitsAddition(
-        cwd,
-        cwdR1csFiles,
-        ceremonyInputData.timeoutMechanismType,
-        isCircomVersionEqualAmongCircuits
-      )
-
-      // Add the data to the circuit input data.
-      circuitsInputData = circuitsInputData.map((circuitInputData: CircuitInputData) => ({
-        ...circuitInputData,
-        compiler: { version, commitHash }
-      }))
-    } else
-      circuitsInputData = await handleCircuitsAddition(
-        cwd,
-        cwdR1csFiles,
-        ceremonyInputData.timeoutMechanismType,
-        isCircomVersionEqualAmongCircuits
-      )
-
-    await simpleLoader(`Assembling your ceremony...`, `clock`, 2000)
-
-    // Ceremony summary.
-    let summary = `${`${theme.bold(ceremonyInputData.title)}\n${theme.italic(ceremonyInputData.description)}`}
-    \n${`Opening: ${theme.bold(
-      theme.underlined(ceremonyInputData.startDate.toUTCString().replace("GMT", "UTC"))
-    )}\nEnding: ${theme.bold(theme.underlined(ceremonyInputData.endDate.toUTCString().replace("GMT", "UTC")))}`}
-    \n${theme.bold(
-      ceremonyInputData.timeoutMechanismType === CeremonyTimeoutType.DYNAMIC ? `Dynamic` : `Fixed`
-    )} Timeout / ${theme.bold(ceremonyInputData.penalty)}m Penalty`
-
-    for (let i = 0; i < circuitsInputData.length; i += 1) {
-      const circuitInputData = circuitsInputData[i]
-
-      // Read file.
-      const r1csMetadataFilePath = `${paths.metadataPath}/${circuitInputData.prefix}_metadata.log`
-      const circuitMetadata = readFile(r1csMetadataFilePath)
-
-      // Extract info from file.
-      const curve = getCircuitMetadataFromR1csFile(circuitMetadata, /Curve: .+\n/s)
-      const wires = Number(getCircuitMetadataFromR1csFile(circuitMetadata, /# of Wires: .+\n/s))
-      const constraints = Number(getCircuitMetadataFromR1csFile(circuitMetadata, /# of Constraints: .+\n/s))
-      const privateInputs = Number(getCircuitMetadataFromR1csFile(circuitMetadata, /# of Private Inputs: .+\n/s))
-      const publicOutputs = Number(getCircuitMetadataFromR1csFile(circuitMetadata, /# of Public Inputs: .+\n/s))
-      const labels = Number(getCircuitMetadataFromR1csFile(circuitMetadata, /# of Labels: .+\n/s))
-      const outputs = Number(getCircuitMetadataFromR1csFile(circuitMetadata, /# of Outputs: .+\n/s))
-      const pot = estimatePoT(constraints, outputs)
-
-      // Store info.
-      circuits.push({
-        ...circuitInputData,
-        metadata: {
-          curve,
-          wires,
-          constraints,
-          privateInputs,
-          publicOutputs,
-          labels,
-          outputs,
-          pot
-        }
-      })
-
-      // Show circuit summary.
-      summary += `\n\n${theme.bold(`- CIRCUIT # ${theme.bold(theme.magenta(`${circuitInputData.sequencePosition}`))}`)}
-      \n${`${theme.bold(circuitInputData.name)}\n${theme.italic(circuitInputData.description)}
-      \nCurve: ${theme.bold(curve)}\nCompiler: v${theme.bold(`${circuitInputData.compiler.version}`)} (${theme.bold(
-        circuitInputData.compiler.commitHash?.slice(0, 7)
-      )})\nSource: ${theme.bold(circuitInputData.template.source.split(`/`).at(-1))}(${theme.bold(
-        circuitInputData.template.paramsConfiguration
-      )})\n${
-        ceremonyInputData.timeoutMechanismType === CeremonyTimeoutType.DYNAMIC
-          ? `Threshold: ${theme.bold(circuitInputData.timeoutThreshold)}%`
-          : `Max Contribution Time: ${theme.bold(circuitInputData.timeoutMaxContributionWaitingTime)}m`
-      }
-      \n# Wires: ${theme.bold(wires)}\n# Constraints: ${theme.bold(constraints)}\n# Private Inputs: ${theme.bold(
-        privateInputs
-      )}\n# Public Inputs: ${theme.bold(publicOutputs)}\n# Labels: ${theme.bold(labels)}\n# Outputs: ${theme.bold(
-        outputs
-      )}\n# PoT: ${theme.bold(pot)}`}`
-    }
-
-    // Show ceremony summary.
-    console.log(
-      boxen(summary, {
-        title: theme.magenta(`CEREMONY SUMMARY`),
-        titleAlignment: "center",
-        textAlignment: "left",
-        margin: 1,
-        padding: 1
-      })
-    )
-
-    // Ask for confirmation.
-    const { confirmation } = await askForConfirmation("Please, confirm to create the ceremony", "Okay", "Exit")
-
-    if (confirmation) {
-      // Create the bucket.
-      const bucketName = getBucketName(ceremonyPrefix)
-
-      const spinner = customSpinner(`Creating the storage bucket...`, `clock`)
-      spinner.start()
-
-      await createS3Bucket(createBucket, bucketName)
-      await sleep(1000)
-
-      spinner.succeed(`Storage bucket ${bucketName} successfully created`)
-
-      // Get local zkeys (if any).
-      spinner.text = "Checking for pre-computed zkeys..."
-      spinner.start()
-
-      const cwdZkeysFiles = await getSpecifiedFilesFromCwd(cwd, `.zkey`)
-
-      await sleep(1000)
-
-      spinner.stop()
-
-      let leftPreComputedZkeys: Array<Dirent> = cwdZkeysFiles
-
-      // Circuit setup.
-      for (let i = 0; i < circuits.length; i += 1) {
-        // Flag for generation of zkey from scratch.
-        let wannaGenerateZkey = true
-        // Flag for PoT download.
-        let wannaUsePreDownloadedPoT = false
-
-        // Get the current circuit
-        const circuit = circuits[i]
-
-        // Convert to double digits powers (e.g., 9 -> 09).
-        let stringifyNeededPowers = convertToDoubleDigits(circuit.metadata.pot)
-        let smallestPotForCircuit = `${potFilenameTemplate}${stringifyNeededPowers}.ptau`
-
-        // Circuit r1cs and zkey file names.
-        const r1csFileName = `${circuit.name}.r1cs`
-        const firstZkeyFileName = `${circuit.prefix}_00000.zkey`
-        let preComputedZkeyNameWithExt = ``
-
-        const r1csLocalPathAndFileName = `${cwd}/${r1csFileName}`
-        let potLocalPathAndFileName = `${paths.potPath}/${smallestPotForCircuit}`
-        let zkeyLocalPathAndFileName = `${paths.zkeysPath}/${firstZkeyFileName}`
-
-        const potStoragePath = `${names.pot}`
-        const r1csStoragePath = `${collections.circuits}/${circuit.prefix}`
-        const zkeyStoragePath = `${collections.circuits}/${circuit.prefix}/${collections.contributions}`
-
-        const r1csStorageFilePath = `${r1csStoragePath}/${r1csFileName}`
-        let potStorageFilePath = `${potStoragePath}/${smallestPotForCircuit}`
-        const zkeyStorageFilePath = `${zkeyStoragePath}/${firstZkeyFileName}`
-
-        console.log(theme.bold(`\n- Setup for Circuit # ${theme.magenta(`${circuit.sequencePosition}`)}\n`))
-
-        if (!leftPreComputedZkeys.length) console.log(`${symbols.warning} There are no pre-computed zKeys`)
-        else {
-          const { confirmation } = await askForConfirmation(
-            `Do you wanna select a pre-computed zkey for the ${circuit.name} circuit?`,
-            `Yes`,
-            `No`
-          )
-
-          if (confirmation) {
-            // Ask for zKey selection.
-            preComputedZkeyNameWithExt = await askForZkeySelectionFromLocalDir(leftPreComputedZkeys)
-
-            // Switch to pre-computed zkey path.
-            zkeyLocalPathAndFileName = `${cwd}/${preComputedZkeyNameWithExt}`
-
-            // Switch the flag.
-            wannaGenerateZkey = false
-          }
-        }
-
-        // If the coordinator wants to use a pre-computed zkey, needs to provide the related ptau.
-        if (!wannaGenerateZkey) {
-          spinner.text = "Checking for Powers of Tau..."
-          spinner.start()
-
-          const cwdPtausFiles = await getSpecifiedFilesFromCwd(cwd, `.ptau`)
-          await sleep(1000)
-
-          if (!cwdPtausFiles.length) {
-            spinner.warn(`No Powers of Tau (.ptau) files found`)
-
-            // Download the PoT.
-            const { powers } = await askPowersOftau(circuit.metadata.pot)
-
-            // Convert to double digits powers (e.g., 9 -> 09).
-            stringifyNeededPowers = convertToDoubleDigits(Number(powers))
-            smallestPotForCircuit = `${potFilenameTemplate}${stringifyNeededPowers}.ptau`
-
-            // Override.
-            potLocalPathAndFileName = `${paths.potPath}/${smallestPotForCircuit}`
-            potStorageFilePath = `${potStoragePath}/${smallestPotForCircuit}`
-          } else {
-            spinner.stop()
-
-            // Ask for ptau selection.
-            smallestPotForCircuit = await askForPtauSelectionFromLocalDir(cwdPtausFiles, circuit.metadata.pot)
-
-            // Update.
-            stringifyNeededPowers = convertToDoubleDigits(extractPoTFromFilename(smallestPotForCircuit))
-
-            // Switch to new ptau path.
-            potLocalPathAndFileName = `${cwd}/${smallestPotForCircuit}`
-            potStorageFilePath = `${potStoragePath}/${smallestPotForCircuit}`
-
-            wannaUsePreDownloadedPoT = true
-          }
-        }
-
-        // Check if the smallest pot has been already downloaded.
-        const alreadyDownloaded =
-          (await checkIfPotAlreadyDownloaded(Number(smallestPotForCircuit))) || wannaUsePreDownloadedPoT
-
-        if (!alreadyDownloaded) {
-          // Get smallest suitable pot for circuit.
-          const spinner = customSpinner(
-            `Downloading ${theme.bold(`#${stringifyNeededPowers}`)} Powers of Tau from PPoT...`,
-            "clock"
-          )
-          spinner.start()
-
-          // Download PoT file.
-          const potDownloadUrl = `${potDownloadUrlTemplate}${smallestPotForCircuit}`
-          const destFilePath = `${paths.potPath}/${smallestPotForCircuit}`
-
-          await downloadFileFromUrl(destFilePath, potDownloadUrl)
-
-          spinner.succeed(`Powers of Tau ${theme.bold(`#${stringifyNeededPowers}`)} correctly downloaded`)
-        } else
-          console.log(`${symbols.success} Powers of Tau ${theme.bold(`#${stringifyNeededPowers}`)} already downloaded`)
-
-        // Check if the smallest pot has been already uploaded.
-        const alreadyUploadedPot = await objectExist(
-          checkIfObjectExist,
-          bucketName,
-          `${ceremonyPrefix}/${names.pot}/${smallestPotForCircuit}`
-        )
-
-        // Validity check for the pre-computed zKey (avoids to upload an invalid combination of r1cs, ptau and zkey files).
-        if (!wannaGenerateZkey) {
-          // Check validity.
-          await simpleLoader(`Checking pre-computed zkey validity...`, `clock`, 1500)
-
-          const valid = await zKey.verifyFromR1cs(
-            r1csLocalPathAndFileName,
-            potLocalPathAndFileName,
-            zkeyLocalPathAndFileName,
-            console
-          )
-
-          // nb. workaround for file descriptor closing.
-          await sleep(3000)
-
-          if (valid) {
-            spinner.succeed(`Your pre-computed zKey is valid`)
-
-            // Remove the selected zkey from the list.
-            leftPreComputedZkeys = leftPreComputedZkeys.filter(
-              (dirent: Dirent) => dirent.name !== preComputedZkeyNameWithExt
-            )
-
-            // Rename to first zkey filename.
-            renameSync(`${cwd}/${preComputedZkeyNameWithExt}`, `${circuit.prefix}_00000.zkey`)
-          } else {
-            spinner.fail(`Something went wrong during the verification of your pre-computed zKey`)
-
-            // Ask to generate a new one from scratch.
-            const { confirmation } = await askForConfirmation(
-              `Do you wanna generate a new zkey for the ${circuit.name} circuit? (nb. A negative answer will ABORT the entire setup process)`,
-              `Yes`,
-              `No`
-            )
-
-            if (!confirmation) showError(`You have choosen to abort the entire setup process`, true)
-            else wannaGenerateZkey = true
-          }
-        }
-
-        // Generate a brand new zKey.
-        if (wannaGenerateZkey) {
-          console.log(
-            `${symbols.warning} ${theme.bold(
-              `The computation of your zKey is starting soon (nb. do not interrupt the process because this will ABORT the entire setup process)`
-            )}\n`
-          )
-
-          // Compute first .zkey file (without any contribution).
-          await zKey.newZKey(r1csLocalPathAndFileName, potLocalPathAndFileName, zkeyLocalPathAndFileName, console)
-
-          console.log(`\n${symbols.success} First zkey ${theme.bold(firstZkeyFileName)} successfully computed`)
-        }
-
-        // Upload zkey.
-        await multiPartUpload(
-          startMultiPartUpload,
-          generatePreSignedUrlsParts,
-          completeMultiPartUpload,
-          bucketName,
-          zkeyStorageFilePath,
-          zkeyLocalPathAndFileName
-        )
-
-        console.log(`${symbols.success} First zkey ${theme.bold(firstZkeyFileName)} successfully saved on storage`)
-
-        // PoT.
-        if (!alreadyUploadedPot) {
-          // Upload.
-          await multiPartUpload(
-            startMultiPartUpload,
-            generatePreSignedUrlsParts,
-            completeMultiPartUpload,
-            bucketName,
-            potStorageFilePath,
-            potLocalPathAndFileName
-          )
-
-          console.log(
-            `${symbols.success} Powers of Tau ${theme.bold(smallestPotForCircuit)} successfully saved on storage`
-          )
-        } else {
-          console.log(`${symbols.success} Powers of Tau ${theme.bold(smallestPotForCircuit)} already stored`)
-        }
-
-        // Upload R1CS.
-        await multiPartUpload(
-          startMultiPartUpload,
-          generatePreSignedUrlsParts,
-          completeMultiPartUpload,
-          bucketName,
-          r1csStorageFilePath,
-          r1csLocalPathAndFileName
-        )
-
-        console.log(`${symbols.success} R1CS ${theme.bold(r1csFileName)} successfully saved on storage`)
-
-        // Circuit-related files info.
-        const circuitFiles: CircuitFiles = {
-          files: {
-            r1csFilename: r1csFileName,
-            potFilename: smallestPotForCircuit,
-            initialZkeyFilename: firstZkeyFileName,
-            r1csStoragePath: r1csStorageFilePath,
-            potStoragePath: potStorageFilePath,
-            initialZkeyStoragePath: zkeyStorageFilePath,
-            r1csBlake2bHash: blake.blake2bHex(r1csStorageFilePath),
-            potBlake2bHash: blake.blake2bHex(potStorageFilePath),
-            initialZkeyBlake2bHash: blake.blake2bHex(zkeyStorageFilePath)
-          }
-        }
-
-        // nb. these will be validated after the first contribution.
-        const circuitTimings: CircuitTimings = {
-          avgTimings: {
-            contributionComputation: 0,
-            fullContribution: 0,
-            verifyCloudFunction: 0
-          }
-        }
-
-        circuits[i] = {
-          ...circuit,
-          ...circuitFiles,
-          ...circuitTimings,
-          zKeySizeInBytes: getFileStats(zkeyLocalPathAndFileName).size
-        }
-
-        // Reset flags.
-        wannaGenerateZkey = true
-        wannaUsePreDownloadedPoT = false
-      }
-
-      process.stdout.write(`\n`)
-
-      /** POPULATE DB */
-      spinner.text = `Storing ceremony data...`
-      spinner.start()
-
-      // Setup ceremony on the server.
-      await setupCeremony({
-        ceremonyInputData,
-        ceremonyPrefix,
-        circuits
-      })
-
-      // nb. workaround for CF termination.
-      await sleep(1000)
-
-      spinner.succeed(
-        `Congrats, you have successfully completed your ${theme.bold(ceremonyInputData.title)} ceremony setup ${
-          emojis.tada
-        }`
-      )
-    }
-
-    terminate(username)
-  } catch (err: any) {
-    showError(`Something went wrong: ${err.toString()}`, true)
-  }
-}
-
-export default setup
diff --git a/apps/phase2cli/src/index.ts b/apps/phase2cli/src/index.ts
deleted file mode 100755
index 5514f8b7..00000000
--- a/apps/phase2cli/src/index.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/usr/bin/env node
-
-import { createCommand } from "commander"
-import { setup, auth, contribute, observe, finalize, clean, logout } from "./commands/index.js"
-import { readLocalJsonFile } from "./lib/files.js"
-
-// Get pkg info (e.g., name, version).
-const pkg = readLocalJsonFile("../../package.json")
-
-const program = createCommand()
-
-// Entry point.
-program.name(pkg.name).description(pkg.description).version(pkg.version)
-
-// User commands.
-program.command("auth").description("authenticate yourself using your Github account (OAuth 2.0)").action(auth)
-program
-  .command("contribute")
-  .description("compute contributions for a Phase2 Trusted Setup ceremony circuits")
-  .action(contribute)
-program
-  .command("clean")
-  .description("clean up output generated by commands from the current working directory")
-  .action(clean)
-program
-  .command("logout")
-  .description("sign out from Firebae Auth service and delete Github OAuth 2.0 token from local storage")
-  .action(logout)
-
-// Only coordinator commands.
-const ceremony = program.command("coordinate").description("commands for coordinating a ceremony")
-
-ceremony
-  .command("setup")
-  .description("setup a Groth16 Phase 2 Trusted Setup ceremony for zk-SNARK circuits")
-  .action(setup)
-
-ceremony
-  .command("observe")
-  .description("observe in real-time the waiting queue of each ceremony circuit")
-  .action(observe)
-
-ceremony
-  .command("finalize")
-  .description(
-    "finalize a Phase2 Trusted Setup ceremony by applying a beacon, exporting verification key and verifier contract"
-  )
-  .action(finalize)
-
-program.parseAsync()
diff --git a/apps/phase2cli/src/lib/constants.ts b/apps/phase2cli/src/lib/constants.ts
deleted file mode 100644
index 39c839ae..00000000
--- a/apps/phase2cli/src/lib/constants.ts
+++ /dev/null
@@ -1,139 +0,0 @@
-import chalk from "chalk"
-import logSymbols from "log-symbols"
-import emoji from "node-emoji"
-
-/** Theme */
-export const theme = {
-  yellow: chalk.yellow,
-  magenta: chalk.magenta,
-  red: chalk.red,
-  green: chalk.green,
-  underlined: chalk.underline,
-  bold: chalk.bold,
-  italic: chalk.italic
-}
-
-export const symbols = {
-  success: logSymbols.success,
-  warning: logSymbols.warning,
-  error: logSymbols.error,
-  info: logSymbols.info
-}
-
-export const emojis = {
-  tada: emoji.get("tada"),
-  key: emoji.get("key"),
-  broom: emoji.get("broom"),
-  pointDown: emoji.get("point_down"),
-  eyes: emoji.get("eyes"),
-  wave: emoji.get("wave"),
-  clipboard: emoji.get("clipboard"),
-  fire: emoji.get("fire"),
-  clock: emoji.get("hourglass"),
-  dizzy: emoji.get("dizzy_face"),
-  rocket: emoji.get("rocket"),
-  oldKey: emoji.get("old_key"),
-  pray: emoji.get("pray"),
-  moon: emoji.get("moon"),
-  upsideDown: emoji.get("upside_down_face"),
-  arrowUp: emoji.get("arrow_up"),
-  arrowDown: emoji.get("arrow_down")
-}
-
-/** ZK related */
-export const potDownloadUrlTemplate = `https://hermez.s3-eu-west-1.amazonaws.com/`
-export const potFilenameTemplate = `powersOfTau28_hez_final_`
-export const firstZkeyIndex = `00000`
-export const numIterationsExp = 10
-export const solidityVersion = "0.8.0"
-
-/** Commands related */
-export const observationWaitingTimeInMillis = 3000 // 3 seconds.
-
-/** Shared */
-export const names = {
-  output: `output`,
-  setup: `setup`,
-  contribute: `contribute`,
-  finalize: `finalize`,
-  pot: `pot`,
-  zkeys: `zkeys`,
-  vkeys: `vkeys`,
-  metadata: `metadata`,
-  transcripts: `transcripts`,
-  attestation: `attestation`,
-  verifiers: `verifiers`
-}
-
-const outputPath = `./${names.output}`
-const setupPath = `${outputPath}/${names.setup}`
-const contributePath = `${outputPath}/${names.contribute}`
-const finalizePath = `${outputPath}/${names.finalize}`
-const potPath = `${setupPath}/${names.pot}`
-const zkeysPath = `${setupPath}/${names.zkeys}`
-const metadataPath = `${setupPath}/${names.metadata}`
-const contributionsPath = `${contributePath}/${names.zkeys}`
-const contributionTranscriptsPath = `${contributePath}/${names.transcripts}`
-const attestationPath = `${contributePath}/${names.attestation}`
-const finalZkeysPath = `${finalizePath}/${names.zkeys}`
-const finalPotPath = `${finalizePath}/${names.pot}`
-const finalTranscriptsPath = `${finalizePath}/${names.transcripts}`
-const finalAttestationsPath = `${finalizePath}/${names.attestation}`
-const verificationKeysPath = `${finalizePath}/${names.vkeys}`
-const verifierContractsPath = `${finalizePath}/${names.verifiers}`
-
-export const paths = {
-  outputPath,
-  setupPath,
-  contributePath,
-  finalizePath,
-  potPath,
-  zkeysPath,
-  metadataPath,
-  contributionsPath,
-  contributionTranscriptsPath,
-  attestationPath,
-  finalZkeysPath,
-  finalPotPath,
-  finalTranscriptsPath,
-  finalAttestationsPath,
-  verificationKeysPath,
-  verifierContractsPath
-}
-
-/** Firebase */
-export const collections = {
-  users: "users",
-  participants: "participants",
-  ceremonies: "ceremonies",
-  circuits: "circuits",
-  contributions: "contributions",
-  timeouts: "timeouts"
-}
-
-export const ceremoniesCollectionFields = {
-  coordinatorId: "coordinatorId",
-  description: "description",
-  endDate: "endDate",
-  lastUpdated: "lastUpdated",
-  prefix: "prefix",
-  startDate: "startDate",
-  state: "state",
-  title: "title",
-  type: "type"
-}
-
-export const contributionsCollectionFields = {
-  contributionTime: "contributionTime",
-  files: "files",
-  lastUpdated: "lastUpdated",
-  participantId: "participantId",
-  valid: "valid",
-  verificationTime: "verificationTime",
-  zkeyIndex: "zKeyIndex"
-}
-
-export const timeoutsCollectionFields = {
-  startDate: "startDate",
-  endDate: "endDate"
-}
diff --git a/apps/phase2cli/src/lib/errors.ts b/apps/phase2cli/src/lib/errors.ts
deleted file mode 100644
index d84b8488..00000000
--- a/apps/phase2cli/src/lib/errors.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-import { deleteStoredOAuthToken } from "./auth.js"
-import { emojis, symbols } from "./constants.js"
-
-/** Firebase */
-export const FIREBASE_ERRORS = {
-  FIREBASE_NOT_CONFIGURED_PROPERLY: `Check that all FIREBASE environment variables are configured properly`,
-  FIREBASE_DEFAULT_APP_DOUBLE_CONFIG: `Wrong double default configuration for Firebase application`,
-  FIREBASE_TOKEN_EXPIRED_REMOVED_PERMISSIONS: `Unsuccessful check authorization response from Github. This usually happens when a token expires or the CLI do not have permissions associated with your Github account`,
-  FIREBASE_USER_DISABLED: `Your Github account has been disabled and can no longer be used to contribute. Get in touch with the coordinator to find out more`,
-  FIREBASE_FAILED_CREDENTIALS_VERIFICATION: `Firebase cannot verify your Github credentials. This usually happens due to network errors`,
-  FIREBASE_NETWORK_ERROR: `Unable to reach Firebase. This usually happens due to network errors`,
-  FIREBASE_CEREMONY_NOT_OPENED: `There are no ceremonies opened to contributions`,
-  FIREBASE_CEREMONY_NOT_CLOSED: `There are no ceremonies ready to finalization`
-}
-
-/** Github */
-export const GITHUB_ERRORS = {
-  GITHUB_NOT_CONFIGURED_PROPERLY: `Github \`CLIENT_ID\` environment variable is not configured properly`,
-  GITHUB_ACCOUNT_ASSOCIATION_REJECTED: `You refused to associate your Github account with the CLI`,
-  GITHUB_SERVER_TIMEDOUT: `Github server has timed out. This usually happens due to network error or Github server downtime`,
-  GITHUB_GET_USERNAME_FAILED: `Something went wrong while retrieving your Github username`,
-  GITHUB_NOT_AUTHENTICATED: `You are not authenticated. Please, run \`phase2cli auth\` command first`,
-  GITHUB_GIST_PUBLICATION_FAILED: `Something went wrong while publishing a Gist from your Github account`
-}
-
-/** Generic */
-export const GENERIC_ERRORS = {
-  GENERIC_NOT_CONFIGURED_PROPERLY: `Check that all CONFIG environment variables are configured properly`,
-  GENERIC_ERROR_RETRIEVING_DATA: `Something went wrong when retrieving the data from the database`,
-  GENERIC_FILE_ERROR: `File not found`,
-  GENERIC_NOT_COORDINATOR: `You are not a coordinator for the ceremony`,
-  GENERIC_COUNTDOWN_EXPIRED: `The amount of time for completing the operation has expired`,
-  GENERIC_R1CS_MISSING_INFO: `The necessary information was not found in the given R1CS file`,
-  GENERIC_COUNTDOWN_EXPIRATION: `Your time to carry out the action has expired`,
-  GENERIC_CEREMONY_SELECTION: `You have aborted the ceremony selection process`,
-  GENERIC_CIRCUIT_SELECTION: `You have aborted the circuit selection process`,
-  GENERIC_DATA_INPUT: `You have aborted the process without providing any of the requested data`,
-  GENERIC_CONTRIBUTION_HASH_INVALID: `You have aborted the process and do not have provided the requested data`
-}
-
-/**
- * Print an error string and gracefully terminate the process.
- * @param err <string> - the error string to be shown.
- * @param doExit <boolean> - when true the function terminate the process; otherwise not.
- */
-export const showError = (err: string, doExit: boolean) => {
-  // Print the error.
-  console.error(`${symbols.error} ${err}`)
-
-  // Terminate the process.
-  if (doExit) process.exit(0)
-}
-
-/**
- * Error handling for auth command.
- * @param err <any> - any error that may happen while running the auth command.
- */
-export const handleAuthErrors = (err: any) => {
-  const error = err.toString()
-
-  /** Firebase */
-
-  if (error.includes("Firebase: Unsuccessful check authorization response from Github")) {
-    showError(FIREBASE_ERRORS.FIREBASE_TOKEN_EXPIRED_REMOVED_PERMISSIONS, false)
-
-    // Clean expired token from local storage.
-    deleteStoredOAuthToken()
-
-    console.log(`${symbols.success} Removed expired token from your local storage ${emojis.broom}`)
-    console.log(
-      `${symbols.info} Please, run \`phase2cli auth\` again to generate a new token and associate your Github account`
-    )
-
-    process.exit(0)
-  }
-
-  if (error.includes("Firebase: Firebase App named '[DEFAULT]' already exists with different options or config"))
-    showError(FIREBASE_ERRORS.FIREBASE_DEFAULT_APP_DOUBLE_CONFIG, true)
-
-  if (error.includes("Firebase: Error (auth/user-disabled)")) showError(FIREBASE_ERRORS.FIREBASE_USER_DISABLED, true)
-
-  if (error.includes("Firebase: Error (auth/network-request-failed)"))
-    showError(FIREBASE_ERRORS.FIREBASE_NETWORK_ERROR, true)
-
-  if (error.includes("Firebase: Remote site 5XX from github.com for VERIFY_CREDENTIAL (auth/invalid-credential)"))
-    showError(FIREBASE_ERRORS.FIREBASE_FAILED_CREDENTIALS_VERIFICATION, true)
-
-  /** Github */
-
-  if (error.includes("HttpError: The authorization request was denied"))
-    showError(GITHUB_ERRORS.GITHUB_ACCOUNT_ASSOCIATION_REJECTED, true)
-
-  if (error.includes("HttpError: request to https://github.com/login/device/code failed, reason: connect ETIMEDOUT"))
-    showError(GITHUB_ERRORS.GITHUB_SERVER_TIMEDOUT, true)
-
-  /** Generic */
-
-  showError(`Something went wrong: ${error}`, true)
-}
diff --git a/apps/phase2cli/src/lib/listeners.ts b/apps/phase2cli/src/lib/listeners.ts
deleted file mode 100644
index af093665..00000000
--- a/apps/phase2cli/src/lib/listeners.ts
+++ /dev/null
@@ -1,435 +0,0 @@
-import { DocumentData, DocumentSnapshot, Firestore, onSnapshot } from "firebase/firestore"
-import { Functions, httpsCallable } from "firebase/functions"
-import { getCeremonyCircuits } from "@zkmpc/actions"
-import { FirebaseDocumentInfo, ParticipantContributionStep, ParticipantStatus } from "../../types/index.js"
-import { collections, emojis, symbols, theme } from "./constants.js"
-import { getCurrentContributorContribution } from "./queries.js"
-import {
-  convertToDoubleDigits,
-  customSpinner,
-  formatZkeyIndex,
-  generatePublicAttestation,
-  getContributorContributionsVerificationResults,
-  getNextCircuitForContribution,
-  getSecondsMinutesHoursFromMillis,
-  handleDiskSpaceRequirementForNextContribution,
-  handleTimedoutMessageForContributor,
-  makeContribution,
-  simpleCountdown,
-  terminate
-} from "./utils.js"
-import { GENERIC_ERRORS, showError } from "./errors.js"
-import { getDocumentById } from "./firebase.js"
-
-/**
- * Return the index of a given participant in a circuit waiting queue.
- * @param contributors <Array<string>> - the list of the contributors in queue for a circuit.
- * @param participantId <string> - the unique identifier of the participant.
- * @returns <number>
- */
-const getParticipantPositionInQueue = (contributors: Array<string>, participantId: string): number =>
-  contributors.indexOf(participantId) + 1
-
-/**
- * Listen to circuit document changes and reacts in realtime.
- * @param participantId <string> - the unique identifier of the contributor.
- * @param ceremonyId <string> - the unique identifier of the ceremony.
- * @param circuit <FirebaseDocumentInfo> - the document information about the current circuit.
- */
-const listenToCircuitChanges = (participantId: string, ceremonyId: string, circuit: FirebaseDocumentInfo) => {
-  const unsubscriberForCircuitDocument = onSnapshot(circuit.ref, async (circuitDocSnap: DocumentSnapshot) => {
-    // Get updated data from snap.
-    const newCircuitData = circuitDocSnap.data()
-
-    if (!newCircuitData) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
-
-    // Get data.
-    const { avgTimings, waitingQueue } = newCircuitData!
-    const { fullContribution, verifyCloudFunction } = avgTimings
-    const { currentContributor, completedContributions } = waitingQueue
-
-    // Retrieve current contributor data.
-    const currentContributorDoc = await getDocumentById(
-      `${collections.ceremonies}/${ceremonyId}/${collections.participants}`,
-      currentContributor
-    )
-
-    // Get updated data from snap.
-    const currentContributorData = currentContributorDoc.data()
-
-    if (!currentContributorData) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
-
-    // Get updated position for contributor in the queue.
-    const newParticipantPositionInQueue = getParticipantPositionInQueue(waitingQueue.contributors, participantId)
-
-    let newEstimatedWaitingTime = 0
-
-    // Show new time estimation.
-    if (fullContribution > 0 && verifyCloudFunction > 0)
-      newEstimatedWaitingTime = (fullContribution + verifyCloudFunction) * (newParticipantPositionInQueue - 1)
-
-    const {
-      seconds: estSeconds,
-      minutes: estMinutes,
-      hours: estHours
-    } = getSecondsMinutesHoursFromMillis(newEstimatedWaitingTime)
-
-    // Check if is the current contributor.
-    if (newParticipantPositionInQueue === 1) {
-      console.log(
-        `\n${symbols.success} Your turn has come ${emojis.tada}\n${symbols.info} Your contribution will begin soon`
-      )
-      unsubscriberForCircuitDocument()
-    } else {
-      // Position and time.
-      console.log(
-        `\n${symbols.info} ${
-          newParticipantPositionInQueue === 2
-            ? `You are the next contributor`
-            : `Your position in the waiting queue is ${theme.bold(theme.magenta(newParticipantPositionInQueue - 1))}`
-        } (${
-          newEstimatedWaitingTime > 0
-            ? `${theme.bold(
-                `${convertToDoubleDigits(estHours)}:${convertToDoubleDigits(estMinutes)}:${convertToDoubleDigits(
-                  estSeconds
-                )}`
-              )} left before your turn)`
-            : `no time estimation)`
-        }`
-      )
-
-      // Participant data.
-      console.log(` - Contributor # ${theme.bold(theme.magenta(completedContributions + 1))}`)
-
-      // Data for displaying info about steps.
-      const currentZkeyIndex = formatZkeyIndex(completedContributions)
-      const nextZkeyIndex = formatZkeyIndex(completedContributions + 1)
-
-      let interval: NodeJS.Timer
-
-      const unsubscriberForCurrentContributorDocument = onSnapshot(
-        currentContributorDoc.ref,
-        async (currentContributorDocSnap: DocumentSnapshot) => {
-          // Get updated data from snap.
-          const newCurrentContributorData = currentContributorDocSnap.data()
-
-          if (!newCurrentContributorData) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
-
-          // Get current contributor data.
-          const { contributionStep, contributionStartedAt } = newCurrentContributorData!
-
-          // Average time.
-          const timeSpentWhileContributing = Date.now() - contributionStartedAt
-          const remainingTime = fullContribution - timeSpentWhileContributing
-
-          // Clear previous step interval (if exist).
-          if (interval) clearInterval(interval)
-
-          switch (contributionStep) {
-            case ParticipantContributionStep.DOWNLOADING: {
-              const message = `   ${symbols.info} Downloading contribution ${theme.bold(`#${currentZkeyIndex}`)}`
-              interval = simpleCountdown(remainingTime, message)
-
-              break
-            }
-            case ParticipantContributionStep.COMPUTING: {
-              process.stdout.write(
-                `   ${symbols.success} Contribution ${theme.bold(`#${currentZkeyIndex}`)} correctly downloaded\n`
-              )
-
-              const message = `   ${symbols.info} Computing contribution ${theme.bold(`#${nextZkeyIndex}`)}`
-              interval = simpleCountdown(remainingTime, message)
-
-              break
-            }
-            case ParticipantContributionStep.UPLOADING: {
-              process.stdout.write(
-                `   ${symbols.success} Contribution ${theme.bold(`#${nextZkeyIndex}`)} successfully computed\n`
-              )
-
-              const message = `   ${symbols.info} Uploading contribution ${theme.bold(`#${nextZkeyIndex}`)}`
-              interval = simpleCountdown(remainingTime, message)
-
-              break
-            }
-            case ParticipantContributionStep.VERIFYING: {
-              process.stdout.write(
-                `   ${symbols.success} Contribution ${theme.bold(`#${nextZkeyIndex}`)} successfully uploaded\n`
-              )
-
-              const message = `   ${symbols.info} Contribution verification ${theme.bold(`#${nextZkeyIndex}`)}`
-              interval = simpleCountdown(remainingTime, message)
-
-              break
-            }
-            case ParticipantContributionStep.COMPLETED: {
-              process.stdout.write(
-                `   ${symbols.success} Contribution ${theme.bold(`#${nextZkeyIndex}`)} has been correctly verified\n`
-              )
-
-              const currentContributorContributions = await getCurrentContributorContribution(
-                ceremonyId,
-                circuit.id,
-                currentContributorDocSnap.id
-              )
-
-              if (currentContributorContributions.length !== 1)
-                process.stdout.write(`   ${symbols.error} We could not recover the contribution data`)
-              else {
-                const contribution = currentContributorContributions.at(0)
-
-                const { valid } = contribution?.data!
-
-                console.log(
-                  `   ${valid ? symbols.success : symbols.error} Contribution ${theme.bold(`#${nextZkeyIndex}`)} is ${
-                    valid ? `VALID` : `INVALID`
-                  }`
-                )
-              }
-
-              unsubscriberForCurrentContributorDocument()
-              break
-            }
-            default: {
-              showError(`Wrong contribution step`, true)
-              break
-            }
-          }
-        }
-      )
-    }
-  })
-}
-
-// Listen to changes on the user-related participant document.
-export default async (
-  participantDoc: DocumentSnapshot<DocumentData>,
-  ceremony: FirebaseDocumentInfo,
-  firestoreDatabase: Firestore,
-  circuits: Array<FirebaseDocumentInfo>,
-  firebaseFunctions: Functions,
-  ghToken: string,
-  ghUsername: string,
-  entropy: string
-) => {
-  // Get number of circuits for the selected ceremony.
-  const numberOfCircuits = circuits.length
-
-  // Listen to participant document changes.
-  const unsubscriberForParticipantDocument = onSnapshot(
-    participantDoc.ref,
-    async (participantDocSnap: DocumentSnapshot) => {
-      // Get updated data from snap.
-      const newParticipantData = participantDocSnap.data()
-      const oldParticipantData = participantDoc.data()
-
-      if (!newParticipantData || !oldParticipantData) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
-
-      // Extract updated participant document data.
-      const { contributionProgress, status, contributionStep, contributions, tempContributionData } =
-        newParticipantData!
-      const {
-        contributionStep: oldContributionStep,
-        tempContributionData: oldTempContributionData,
-        contributionProgress: oldContributionProgress,
-        contributions: oldContributions,
-        status: oldStatus
-      } = oldParticipantData!
-      const participantId = participantDoc.id
-
-      // 0. Whem joining for the first time the waiting queue.
-      if (
-        status === ParticipantStatus.WAITING &&
-        !contributionStep &&
-        !contributions.length &&
-        contributionProgress === 0
-      ) {
-        // Get next circuit.
-        const nextCircuit = getNextCircuitForContribution(circuits, contributionProgress + 1)
-
-        // Check disk space requirements for participant.
-        const makeProgressToNextContribution = httpsCallable(firebaseFunctions, "makeProgressToNextContribution")
-        await handleDiskSpaceRequirementForNextContribution(makeProgressToNextContribution, nextCircuit, ceremony.id)
-      }
-
-      // A. Do not have completed the contributions for each circuit; move to the next one.
-      if (contributionProgress > 0 && contributionProgress <= circuits.length) {
-        // Get updated circuits data.
-        const circuits = await getCeremonyCircuits(firestoreDatabase, ceremony.id)
-        const circuit = circuits[contributionProgress - 1]
-        const { waitingQueue } = circuit.data
-
-        // Check if the contribution step is valid for starting/resuming the contribution.
-        const isStepValidForStartingOrResumingContribution =
-          (contributionStep === ParticipantContributionStep.DOWNLOADING &&
-            status === ParticipantStatus.CONTRIBUTING &&
-            (!oldContributionStep ||
-              oldContributionStep !== contributionStep ||
-              (oldContributionStep === contributionStep &&
-                status === oldStatus &&
-                oldContributionProgress === contributionProgress) ||
-              oldStatus === ParticipantStatus.EXHUMED)) ||
-          (contributionStep === ParticipantContributionStep.COMPUTING &&
-            oldContributionStep === contributionStep &&
-            oldContributions.length === contributions.length) ||
-          (contributionStep === ParticipantContributionStep.UPLOADING &&
-            !oldTempContributionData &&
-            !tempContributionData &&
-            contributionStep === oldContributionStep) ||
-          (!!oldTempContributionData &&
-            !!tempContributionData &&
-            JSON.stringify(Object.keys(oldTempContributionData).sort()) ===
-              JSON.stringify(Object.keys(tempContributionData).sort()) &&
-            JSON.stringify(Object.values(oldTempContributionData).sort()) ===
-              JSON.stringify(Object.values(tempContributionData).sort()))
-
-        // A.1 If the participant is in `waiting` status, he/she must receive updates from the circuit's waiting queue.
-        if (status === ParticipantStatus.WAITING && oldStatus !== ParticipantStatus.TIMEDOUT) {
-          console.log(
-            `${theme.bold(`\n- Circuit # ${theme.magenta(`${circuit.data.sequencePosition}`)}`)} (Waiting Queue)`
-          )
-
-          listenToCircuitChanges(participantId, ceremony.id, circuit)
-        }
-        // A.2 If the participant is in `contributing` status and is the current contributor, he/she must compute the contribution.
-        if (
-          status === ParticipantStatus.CONTRIBUTING &&
-          contributionStep !== ParticipantContributionStep.VERIFYING &&
-          waitingQueue.currentContributor === participantId &&
-          isStepValidForStartingOrResumingContribution
-        ) {
-          console.log(
-            `\n${symbols.success} Your contribution will ${
-              contributionStep === ParticipantContributionStep.DOWNLOADING ? `start` : `resume`
-            } soon ${emojis.clock}`
-          )
-
-          // Compute the contribution.
-          await makeContribution(ceremony, circuit, entropy, ghUsername, false, firebaseFunctions, newParticipantData!)
-        }
-
-        // A.3 Current contributor has already started the verification step.
-        if (
-          status === ParticipantStatus.CONTRIBUTING &&
-          waitingQueue.currentContributor === participantId &&
-          contributionStep === oldContributionStep &&
-          contributionStep === ParticipantContributionStep.VERIFYING &&
-          contributionProgress === oldContributionProgress
-        ) {
-          const spinner = customSpinner(`Resuming your contribution...`, `clock`)
-          spinner.start()
-
-          // Get current and next index.
-          const currentZkeyIndex = formatZkeyIndex(contributionProgress)
-          const nextZkeyIndex = formatZkeyIndex(contributionProgress + 1)
-
-          // Calculate remaining est. time for verification.
-          const avgVerifyCloudFunctionTime = circuit.data.avgTimings.verifyCloudFunction
-          const verificationStartedAt = newParticipantData?.verificationStartedAt
-          const estRemainingTimeInMillis = avgVerifyCloudFunctionTime - (Date.now() - verificationStartedAt)
-          const { seconds, minutes, hours } = getSecondsMinutesHoursFromMillis(estRemainingTimeInMillis)
-
-          spinner.succeed(`Your contribution will resume soon ${emojis.clock}`)
-
-          console.log(
-            `${theme.bold(`\n- Circuit # ${theme.magenta(`${circuit.data.sequencePosition}`)}`)} (Contribution Steps)`
-          )
-          console.log(`${symbols.success} Contribution ${theme.bold(`#${currentZkeyIndex}`)} already downloaded`)
-          console.log(`${symbols.success} Contribution ${theme.bold(`#${nextZkeyIndex}`)} already computed`)
-          console.log(`${symbols.success} Contribution ${theme.bold(`#${nextZkeyIndex}`)} already saved on storage`)
-          console.log(
-            `${symbols.info} Contribution verification already started (est. time ${theme.bold(
-              `${convertToDoubleDigits(hours)}:${convertToDoubleDigits(minutes)}:${convertToDoubleDigits(seconds)}`
-            )})`
-          )
-        }
-
-        // A.4 Server has terminated the already started verification step above.
-        if (
-          ((status === ParticipantStatus.DONE && oldStatus === ParticipantStatus.DONE) ||
-            (status === ParticipantStatus.CONTRIBUTED && oldStatus === ParticipantStatus.CONTRIBUTED)) &&
-          oldContributionProgress === contributionProgress - 1 &&
-          contributionStep === ParticipantContributionStep.COMPLETED
-        ) {
-          console.log(`\n${symbols.success} Contribute verification has been completed`)
-
-          // Return true and false based on contribution verification.
-          const contributionsValidity = await getContributorContributionsVerificationResults(
-            ceremony.id,
-            participantDoc.id,
-            circuits,
-            false
-          )
-
-          // Check last contribution validity.
-          const isContributionValid = contributionsValidity[oldContributionProgress - 1]
-
-          console.log(
-            `${isContributionValid ? symbols.success : symbols.error} Your contribution ${
-              isContributionValid ? `is ${theme.bold("VALID")}` : `is ${theme.bold("INVALID")}`
-            }`
-          )
-        }
-
-        // A.5 Current contributor timedout.
-        if (status === ParticipantStatus.TIMEDOUT && contributionStep !== ParticipantContributionStep.COMPLETED)
-          await handleTimedoutMessageForContributor(
-            newParticipantData!,
-            participantDoc.id,
-            ceremony.id,
-            true,
-            ghUsername
-          )
-
-        // A.6 Contributor has finished the contribution and we need to check the memory before progressing.
-        if (status === ParticipantStatus.CONTRIBUTED && contributionStep === ParticipantContributionStep.COMPLETED) {
-          // Get next circuit for contribution.
-          const nextCircuit = getNextCircuitForContribution(circuits, contributionProgress + 1)
-
-          // Check disk space requirements for participant.
-          const makeProgressToNextContribution = httpsCallable(firebaseFunctions, "makeProgressToNextContribution")
-          const wannaGenerateAttestation = await handleDiskSpaceRequirementForNextContribution(
-            makeProgressToNextContribution,
-            nextCircuit,
-            ceremony.id
-          )
-
-          if (wannaGenerateAttestation) {
-            // Generate attestation with valid contributions.
-            await generatePublicAttestation(ceremony, participantId, newParticipantData!, circuits, ghUsername, ghToken)
-
-            unsubscriberForParticipantDocument()
-            terminate(ghUsername)
-          }
-        }
-
-        // A.7 If the participant is in `EXHUMED` status can be only after a timeout expiration.
-        if (status === ParticipantStatus.EXHUMED) {
-          // Check disk space requirements for participant before resuming the contribution.
-          const resumeContributionAfterTimeoutExpiration = httpsCallable(
-            firebaseFunctions,
-            "resumeContributionAfterTimeoutExpiration"
-          )
-          await handleDiskSpaceRequirementForNextContribution(
-            resumeContributionAfterTimeoutExpiration,
-            circuit,
-            ceremony.id
-          )
-        }
-
-        // B. Already contributed to each circuit.
-        if (
-          status === ParticipantStatus.DONE &&
-          contributionStep === ParticipantContributionStep.COMPLETED &&
-          contributionProgress === numberOfCircuits &&
-          contributions.length === numberOfCircuits
-        ) {
-          await generatePublicAttestation(ceremony, participantId, newParticipantData!, circuits, ghUsername, ghToken)
-
-          unsubscriberForParticipantDocument()
-          terminate(ghUsername)
-        }
-      }
-    }
-  )
-}
diff --git a/apps/phase2cli/src/lib/prompts.ts b/apps/phase2cli/src/lib/prompts.ts
deleted file mode 100644
index 639fed70..00000000
--- a/apps/phase2cli/src/lib/prompts.ts
+++ /dev/null
@@ -1,539 +0,0 @@
-import { Dirent } from "fs"
-import prompts, { Answers, Choice, PromptObject } from "prompts"
-import {
-  CeremonyInputData,
-  CeremonyTimeoutType,
-  CircomCompilerData,
-  CircuitInputData,
-  FirebaseDocumentInfo
-} from "../../types/index.js"
-import { symbols, theme } from "./constants.js"
-import { GENERIC_ERRORS, showError } from "./errors.js"
-import { extractPoTFromFilename, extractPrefix, getCreatedCeremoniesPrefixes } from "./utils.js"
-
-/**
- * Show a binary question with custom options for confirmation purposes.
- * @param question <string> - the question to be answered.
- * @param active <string> - the active option (= yes).
- * @param inactive <string> - the inactive option (= no).
- * @returns <Promise<Answers<string>>>
- */
-export const askForConfirmation = async (question: string, active = "yes", inactive = "no"): Promise<Answers<string>> =>
-  prompts({
-    type: "toggle",
-    name: "confirmation",
-    message: theme.bold(question),
-    initial: false,
-    active,
-    inactive
-  })
-
-/**
- * Show a series of questions about the ceremony.
- * @returns <Promise<CeremonyInputData>> - the necessary information for the ceremony entered by the coordinator.
- */
-export const askCeremonyInputData = async (): Promise<CeremonyInputData> => {
-  // Get ceremonies prefixes to check for duplicates.
-  const ceremoniesPrefixes = await getCreatedCeremoniesPrefixes()
-
-  const noEndDateCeremonyQuestions: Array<PromptObject> = [
-    {
-      type: "text",
-      name: "title",
-      message: theme.bold(`Give a title to your ceremony`),
-      validate: (title: string) => {
-        if (title.length <= 0) return theme.red(`${symbols.error} You must provide a valid title for your ceremony!`)
-
-        if (ceremoniesPrefixes.includes(extractPrefix(title)))
-          return theme.red(`${symbols.error} The title is already in use for another ceremony!`)
-
-        return true
-      }
-    },
-    {
-      type: "text",
-      name: "description",
-      message: theme.bold(`Add a description`),
-      validate: (title: string) =>
-        title.length > 0 || theme.red(`${symbols.error} You must provide a valid description!`)
-    },
-    {
-      type: "date",
-      name: "startDate",
-      message: theme.bold(`When should the ceremony open?`),
-      validate: (d: any) =>
-        new Date(d).valueOf() > Date.now()
-          ? true
-          : theme.red(`${symbols.error} You cannot start a ceremony in the past!`)
-    }
-  ]
-
-  const { title, description, startDate } = await prompts(noEndDateCeremonyQuestions)
-
-  if (!title || !description || !startDate) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
-
-  const { endDate } = await prompts({
-    type: "date",
-    name: "endDate",
-    message: theme.bold(`And when close?`),
-    validate: (d) =>
-      new Date(d).valueOf() > new Date(startDate).valueOf()
-        ? true
-        : theme.red(`${symbols.error} You cannot close a ceremony before the opening!`)
-  })
-
-  if (!endDate) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
-
-  // Choose timeout mechanism.
-  const { confirmation: timeoutMechanismType } = await askForConfirmation(
-    `Choose which timeout mechanism you would like to use to penalize blocking contributors`,
-    `Dynamic`,
-    `Fixed`
-  )
-
-  const { penalty } = await prompts({
-    type: "number",
-    name: "penalty",
-    message: theme.bold(`Specify the amount of time a blocking contributor needs to wait when timedout (in minutes):`),
-    validate: (penalty: number) => {
-      if (penalty < 0) return theme.red(`${symbols.error} You must provide a penalty greater than zero`)
-
-      return true
-    }
-  })
-
-  if (penalty < 0) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
-
-  return {
-    title,
-    description,
-    startDate,
-    endDate,
-    timeoutMechanismType: timeoutMechanismType ? CeremonyTimeoutType.DYNAMIC : CeremonyTimeoutType.FIXED,
-    penalty
-  }
-}
-
-/**
- * Show a series of questions about the circom compiler.
- * @returns <Promise<CircomCompilerData>> - the necessary information for the circom compiler entered by the coordinator.
- */
-export const askCircomCompilerVersionAndCommitHash = async (): Promise<CircomCompilerData> => {
-  const questions: Array<PromptObject> = [
-    {
-      type: "text",
-      name: "version",
-      message: theme.bold(`Give the circom compiler version`),
-      validate: (version: string) => {
-        if (version.length <= 0) return theme.red(`${symbols.error} You must provide a valid version (e.g., 2.0.1)`)
-
-        if (!version.match(/^[0-9].[0-9.]*$/))
-          return theme.red(`${symbols.error} You must provide a valid version (e.g., 2.0.1)`)
-
-        return true
-      }
-    },
-    {
-      type: "text",
-      name: "commitHash",
-      message: theme.bold(`Give the commit hash of the circom compiler version`),
-      validate: (commitHash: string) =>
-        commitHash.length === 40 ||
-        theme.red(
-          `${symbols.error} You must provide a valid commit hash (e.g., b7ad01b11f9b4195e38ecc772291251260ab2c67)`
-        )
-    }
-  ]
-
-  const { version, commitHash } = await prompts(questions)
-
-  if (!version || !commitHash) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
-
-  return {
-    version,
-    commitHash
-  }
-}
-
-/**
- * Show a series of questions about the circuits.
- * @param timeoutMechanismType <CeremonyTimeoutType> - the choosen timeout mechanism type for the ceremony.
- * @param isCircomVersionDifferentAmongCircuits <boolean> - true if the circom compiler version is equal among circuits; otherwise false.
- * @returns Promise<Array<Circuit>> - the necessary information for the circuits entered by the coordinator.
- */
-export const askCircuitInputData = async (
-  timeoutMechanismType: CeremonyTimeoutType,
-  isCircomVersionEqualAmongCircuits: boolean
-): Promise<CircuitInputData> => {
-  const circuitQuestions: Array<PromptObject> = [
-    {
-      name: "description",
-      type: "text",
-      message: theme.bold(`Add a description`),
-      validate: (value) => (value.length ? true : theme.red(`${symbols.error} You must provide a valid description`))
-    },
-    {
-      name: "templateSource",
-      type: "text",
-      message: theme.bold(`Give the external reference to the source template (.circom file)`),
-      validate: (value) =>
-        value.length > 0 && value.match(/(https?:\/\/[^\s]+\.circom$)/g)
-          ? true
-          : theme.red(`${symbols.error} You must provide a valid link to the .circom source template`)
-    },
-    {
-      name: "templateCommitHash",
-      type: "text",
-      message: theme.bold(`Give the commit hash of the source template`),
-      validate: (commitHash: string) =>
-        commitHash.length === 40 ||
-        theme.red(
-          `${symbols.error} You must provide a valid commit hash (e.g., b7ad01b11f9b4195e38ecc772291251260ab2c67)`
-        )
-    }
-  ]
-
-  // Prompt for circuit data.
-  const { description, templateSource, templateCommitHash } = await prompts(circuitQuestions)
-
-  if (!description || !templateSource || !templateCommitHash) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
-
-  // Ask for dynamic or fixed data.
-  let paramsConfiguration: Array<string> = []
-  let timeoutThreshold = 0
-  let timeoutMaxContributionWaitingTime = 0
-  let circomVersion = ""
-  let circomCommitHash = ""
-
-  // Ask for params config values (if any).
-  const { confirmation: needConfiguration } = await askForConfirmation(
-    `Did the source template need configuration?`,
-    `Yes`,
-    `No`
-  )
-
-  if (needConfiguration) {
-    const { templateParamsValues } = await prompts({
-      name: "templateParamsValues",
-      type: "text",
-      message: theme.bold(`Please, provide a comma-separated list of the parameters values used for configuration`),
-      validate: (value: string) =>
-        value.split(",").length === 1 ||
-        (value.split(`,`).length > 1 && value.includes(",")) ||
-        theme.red(
-          `${symbols.error} You must provide a valid comma-separated list of parameters values (e.g., 10,2,1,2)`
-        )
-    })
-
-    if (!templateParamsValues) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
-
-    paramsConfiguration = templateParamsValues.split(",")
-  }
-
-  // Ask for circom info (if different from other circuits).
-  if (!isCircomVersionEqualAmongCircuits) {
-    const { version, commitHash } = await askCircomCompilerVersionAndCommitHash()
-
-    if (!version || !commitHash) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
-
-    circomVersion = version
-    circomCommitHash = commitHash
-  }
-
-  // Ask for dynamic timeout mechanism data.
-  if (timeoutMechanismType === CeremonyTimeoutType.DYNAMIC) {
-    const { threshold } = await prompts({
-      type: "number",
-      name: "threshold",
-      message: theme.bold(`Provide an additional threshold up to the total average contribution time (in percentage):`),
-      validate: (threshold: number) => {
-        if (threshold < 0 || threshold > 100)
-          return theme.red(`${symbols.error} You must provide a threshold between 0 and 100`)
-
-        return true
-      }
-    })
-
-    if (threshold < 0 || threshold > 100) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
-
-    timeoutThreshold = threshold
-  }
-
-  // Ask for fixed timeout mechanism data.
-  if (timeoutMechanismType === CeremonyTimeoutType.FIXED) {
-    const { maxContributionWaitingTime } = await prompts({
-      type: "number",
-      name: `maxContributionWaitingTime`,
-      message: theme.bold(`Specify the max amount of time tolerable while contributing (in minutes):`),
-      validate: (threshold: number) => {
-        if (threshold <= 0)
-          return theme.red(`${symbols.error} You must provide a maximum contribution waiting time greater than zero`)
-
-        return true
-      }
-    })
-
-    if (maxContributionWaitingTime <= 0) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
-
-    timeoutMaxContributionWaitingTime = maxContributionWaitingTime
-  }
-
-  if (
-    (timeoutMechanismType === CeremonyTimeoutType.DYNAMIC && timeoutThreshold < 0) ||
-    (timeoutMechanismType === CeremonyTimeoutType.FIXED && timeoutMaxContributionWaitingTime < 0) ||
-    (needConfiguration && paramsConfiguration.length === 0) ||
-    (isCircomVersionEqualAmongCircuits && !!circomVersion && !!circomCommitHash)
-  )
-    showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
-
-  return timeoutMechanismType === CeremonyTimeoutType.DYNAMIC
-    ? {
-        description,
-        timeoutThreshold,
-        compiler: {
-          version: circomVersion,
-          commitHash: circomCommitHash
-        },
-        template: {
-          source: templateSource,
-          commitHash: templateCommitHash,
-          paramsConfiguration
-        }
-      }
-    : {
-        description,
-        timeoutMaxContributionWaitingTime,
-        compiler: {
-          version: circomVersion,
-          commitHash: circomCommitHash
-        },
-        template: {
-          source: templateSource,
-          commitHash: templateCommitHash,
-          paramsConfiguration
-        }
-      }
-}
-
-/**
- * Request the powers of the Powers of Tau for a specified circuit.
- * @param suggestedPowers <number> - the minimal number of powers necessary for circuit zKey generation.
- * @returns Promise<Array<Circuit>> - the necessary information for the circuits entered by the coordinator.
- */
-export const askPowersOftau = async (suggestedPowers: number): Promise<any> => {
-  const question: PromptObject = {
-    name: "powers",
-    type: "number",
-    message: theme.bold(
-      `Please, provide the amounts of powers you have used to generate the pre-computed zkey (>= ${suggestedPowers}):`
-    ),
-    validate: (value) =>
-      value >= suggestedPowers
-        ? true
-        : theme.red(`${symbols.error} You must provide a value greater than or equal to ${suggestedPowers}`)
-  }
-
-  // Prompt for circuit data.
-  const { powers } = await prompts(question)
-
-  if (powers < suggestedPowers) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
-
-  return {
-    powers
-  }
-}
-
-/**
- * Prompt the list of circuits from a specific directory.
- * @param circuitsDirents <Array<Dirent>>
- * @returns Promise<string>
- */
-export const askForCircuitSelectionFromLocalDir = async (circuitsDirents: Array<Dirent>): Promise<string> => {
-  const choices: Array<Choice> = []
-
-  // Make a 'Choice' for each circuit.
-  for (const circuitDirent of circuitsDirents) {
-    choices.push({
-      title: circuitDirent.name,
-      value: circuitDirent.name
-    })
-  }
-
-  // Ask for selection.
-  const { circuit } = await prompts({
-    type: "select",
-    name: "circuit",
-    message: theme.bold("Select a circuit"),
-    choices,
-    initial: 0
-  })
-
-  if (!circuit) showError(GENERIC_ERRORS.GENERIC_CIRCUIT_SELECTION, true)
-
-  return circuit
-}
-
-/**
- * Prompt the list of pre-computed zkeys files from a specific directory.
- * @param zkeysDirents <Array<Dirent>>
- * @returns Promise<string>
- */
-export const askForZkeySelectionFromLocalDir = async (zkeysDirents: Array<Dirent>): Promise<string> => {
-  const choices: Array<Choice> = []
-
-  // Make a 'Choice' for each zkey.
-  for (const zkeyDirent of zkeysDirents) {
-    choices.push({
-      title: zkeyDirent.name,
-      value: zkeyDirent.name
-    })
-  }
-
-  // Ask for selection.
-  const { zkey } = await prompts({
-    type: "select",
-    name: "zkey",
-    message: theme.bold("Select a pre-computed zkey"),
-    choices,
-    initial: 0
-  })
-
-  if (!zkey) showError(GENERIC_ERRORS.GENERIC_CIRCUIT_SELECTION, true)
-
-  return zkey
-}
-
-/**
- * Prompt the list of ptau files from a specific directory.
- * @param ptausDirents <Array<Dirent>>
- * @param suggestedPowers <number> - the minimal number of powers necessary for circuit zKey generation.
- * @returns Promise<string>
- */
-export const askForPtauSelectionFromLocalDir = async (
-  ptausDirents: Array<Dirent>,
-  suggestedPowers: number
-): Promise<string> => {
-  const choices: Array<Choice> = []
-
-  // Make a 'Choice' for each ptau.
-  for (const ptauDirent of ptausDirents) {
-    const powers = extractPoTFromFilename(ptauDirent.name)
-
-    if (powers >= suggestedPowers)
-      choices.push({
-        title: ptauDirent.name,
-        value: ptauDirent.name
-      })
-  }
-
-  // Ask for selection.
-  const { ptau } = await prompts({
-    type: "select",
-    name: "ptau",
-    message: theme.bold("Select the Powers of Tau file used to generate the zKey"),
-    choices,
-    initial: 0,
-    validate: (value) =>
-      extractPoTFromFilename(value) >= suggestedPowers
-        ? true
-        : theme.red(
-            `${symbols.error} You must select a Powers of Tau file having an equal to or greater than ${suggestedPowers} amount of powers`
-          )
-  })
-
-  if (!ptau) showError(GENERIC_ERRORS.GENERIC_CIRCUIT_SELECTION, true)
-
-  return ptau
-}
-
-/**
- * Prompt the list of opened ceremonies for selection.
- * @param openedCeremoniesDocs <Array<FirebaseDocumentInfo>> - The uid and data of opened cerimonies documents.
- * @returns Promise<FirebaseDocumentInfo>
- */
-export const askForCeremonySelection = async (
-  openedCeremoniesDocs: Array<FirebaseDocumentInfo>
-): Promise<FirebaseDocumentInfo> => {
-  const choices: Array<Choice> = []
-
-  // Make a 'Choice' for each opened ceremony.
-  for (const ceremonyDoc of openedCeremoniesDocs) {
-    const now = Date.now()
-    const daysLeft = Math.ceil(Math.abs(now - ceremonyDoc.data.endDate) / (1000 * 60 * 60 * 24))
-
-    choices.push({
-      title: ceremonyDoc.data.title,
-      description: `${ceremonyDoc.data.description} (${theme.magenta(daysLeft)} ${
-        now - ceremonyDoc.data.endDate < 0 ? `days left` : `days gone since closing`
-      })`,
-      value: ceremonyDoc
-    })
-  }
-
-  // Ask for selection.
-  const { ceremony } = await prompts({
-    type: "select",
-    name: "ceremony",
-    message: theme.bold("Select a ceremony"),
-    choices,
-    initial: 0
-  })
-
-  if (!ceremony) showError(GENERIC_ERRORS.GENERIC_CEREMONY_SELECTION, true)
-
-  return ceremony
-}
-
-/**
- * Prompt the list of circuits for a specific ceremony for selection.
- * @param circuitsDocs <Array<FirebaseDocumentInfo>> - The uid and data of ceremony circuits.
- * @returns Promise<FirebaseDocumentInfo>
- */
-export const askForCircuitSelectionFromFirebase = async (
-  circuitsDocs: Array<FirebaseDocumentInfo>
-): Promise<FirebaseDocumentInfo> => {
-  const choices: Array<Choice> = []
-
-  // Make a 'Choice' for each circuit.
-  for (const circuitDoc of circuitsDocs) {
-    choices.push({
-      title: `${circuitDoc.data.name}`,
-      description: `(#${theme.magenta(circuitDoc.data.sequencePosition)}) ${circuitDoc.data.description}`,
-      value: circuitDoc
-    })
-  }
-
-  // Ask for selection.
-  const { circuit } = await prompts({
-    type: "select",
-    name: "circuit",
-    message: theme.bold("Select a circuit"),
-    choices,
-    initial: 0
-  })
-
-  if (!circuit) showError(GENERIC_ERRORS.GENERIC_CIRCUIT_SELECTION, true)
-
-  return circuit
-}
-
-/**
- * Prompt for entropy or beacon.
- * @param askEntropy <boolean> - true when requesting entropy; otherwise false.
- * @returns <Promise<string>>
- */
-export const askForEntropyOrBeacon = async (askEntropy: boolean): Promise<string> => {
-  const { entropyOrBeacon } = await prompts({
-    type: "text",
-    name: "entropyOrBeacon",
-    style: `${askEntropy ? `password` : `text`}`,
-    message: theme.bold(`Provide ${askEntropy ? `some entropy` : `the final beacon`}`),
-    validate: (title: string) =>
-      title.length > 0 ||
-      theme.red(`${symbols.error} You must provide a valid value for the ${askEntropy ? `entropy` : `beacon`}!`)
-  })
-
-  if (!entropyOrBeacon) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
-
-  return entropyOrBeacon
-}
diff --git a/apps/phase2cli/src/lib/queries.ts b/apps/phase2cli/src/lib/queries.ts
deleted file mode 100644
index 230ce305..00000000
--- a/apps/phase2cli/src/lib/queries.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-import { where } from "firebase/firestore"
-import { FirebaseDocumentInfo, CeremonyState } from "../../types/index.js"
-import { queryCollection, getAllCollectionDocs } from "./firebase.js"
-import {
-  ceremoniesCollectionFields,
-  collections,
-  contributionsCollectionFields,
-  timeoutsCollectionFields
-} from "./constants.js"
-import { fromQueryToFirebaseDocumentInfo, getServerTimestampInMillis } from "./utils.js"
-import { FIREBASE_ERRORS, showError } from "./errors.js"
-
-/**
- * Query for closed ceremonies documents and return their data (if any).
- * @returns <Promise<Array<FirebaseDocumentInfo>>>
- */
-export const getClosedCeremonies = async (): Promise<Array<FirebaseDocumentInfo>> => {
-  let closedStateCeremoniesQuerySnap: any
-
-  try {
-    closedStateCeremoniesQuerySnap = await queryCollection(collections.ceremonies, [
-      where(ceremoniesCollectionFields.state, "==", CeremonyState.CLOSED),
-      where(ceremoniesCollectionFields.endDate, "<=", Date.now())
-    ])
-
-    if (closedStateCeremoniesQuerySnap.empty && closedStateCeremoniesQuerySnap.size === 0)
-      showError(FIREBASE_ERRORS.FIREBASE_CEREMONY_NOT_CLOSED, true)
-  } catch (err: any) {
-    showError(err.toString(), true)
-  }
-
-  return fromQueryToFirebaseDocumentInfo(closedStateCeremoniesQuerySnap.docs)
-}
-
-/**
- * Retrieve all ceremonies.
- * @returns Promise<Array<FirebaseDocumentInfo>>
- */
-export const getAllCeremonies = async (): Promise<Array<FirebaseDocumentInfo>> =>
-  fromQueryToFirebaseDocumentInfo(await getAllCollectionDocs(`${collections.ceremonies}`)).sort(
-    (a: FirebaseDocumentInfo, b: FirebaseDocumentInfo) => a.data.sequencePosition - b.data.sequencePosition
-  )
-
-/**
- * Query for contribution from given participant for a given circuit (if any).
- * @param ceremonyId <string> - the identifier of the ceremony.
- * @param circuitId <string> - the identifier of the circuit.
- * @param participantId <string> - the identifier of the participant.
- * @returns <Promise<Array<FirebaseDocumentInfo>>>
- */
-export const getCurrentContributorContribution = async (
-  ceremonyId: string,
-  circuitId: string,
-  participantId: string
-): Promise<Array<FirebaseDocumentInfo>> => {
-  const participantContributionQuerySnap = await queryCollection(
-    `${collections.ceremonies}/${ceremonyId}/${collections.circuits}/${circuitId}/${collections.contributions}`,
-    [where(contributionsCollectionFields.participantId, "==", participantId)]
-  )
-
-  return fromQueryToFirebaseDocumentInfo(participantContributionQuerySnap.docs)
-}
-
-/**
- * Query for circuits with a contribution from given participant.
- * @param ceremonyId <string> - the identifier of the ceremony.
- * @param circuits <Array<FirebaseDocumentInfo>> - the circuits of the ceremony
- * @param participantId <string> - the identifier of the participant.
- * @returns <Promise<Array<FirebaseDocumentInfo>>>
- */
-export const getCircuitsWithParticipantContribution = async (
-  ceremonyId: string,
-  circuits: Array<FirebaseDocumentInfo>,
-  participantId: string
-): Promise<Array<string>> => {
-  const circuitsWithContributionIds: Array<string> = [] // nb. store circuit identifier.
-
-  for (const circuit of circuits) {
-    const participantContributionQuerySnap = await queryCollection(
-      `${collections.ceremonies}/${ceremonyId}/${collections.circuits}/${circuit.id}/${collections.contributions}`,
-      [where(contributionsCollectionFields.participantId, "==", participantId)]
-    )
-
-    if (participantContributionQuerySnap.size === 1) circuitsWithContributionIds.push(circuit.id)
-  }
-
-  return circuitsWithContributionIds
-}
-
-/**
- * Query for the active timeout from given participant for a given ceremony (if any).
- * @param ceremonyId <string> - the identifier of the ceremony.
- * @param participantId <string> - the identifier of the participant.
- * @returns Promise<Array<FirebaseDocumentInfo>>
- */
-export const getCurrentActiveParticipantTimeout = async (
-  ceremonyId: string,
-  participantId: string
-): Promise<Array<FirebaseDocumentInfo>> => {
-  const participantTimeoutQuerySnap = await queryCollection(
-    `${collections.ceremonies}/${ceremonyId}/${collections.participants}/${participantId}/${collections.timeouts}`,
-    [where(timeoutsCollectionFields.endDate, ">=", getServerTimestampInMillis())]
-  )
-
-  return fromQueryToFirebaseDocumentInfo(participantTimeoutQuerySnap.docs)
-}
diff --git a/apps/phase2cli/src/lib/storage.ts b/apps/phase2cli/src/lib/storage.ts
deleted file mode 100644
index 93bfc211..00000000
--- a/apps/phase2cli/src/lib/storage.ts
+++ /dev/null
@@ -1,295 +0,0 @@
-import { HttpsCallable } from "firebase/functions"
-import fs from "fs"
-import fetch from "@adobe/node-fetch-retry"
-import { createWriteStream } from "node:fs"
-import https from "https"
-import { ChunkWithUrl, ETagWithPartNumber, ProgressBarType } from "../../types/index.js"
-import { GENERIC_ERRORS, showError } from "./errors.js"
-import { readLocalJsonFile } from "./files.js"
-import { convertToGB, customProgressBar, sleep } from "./utils.js"
-
-// Get local configs.
-const { config } = readLocalJsonFile("../../env.json")
-
-export const createS3Bucket = async (cf: HttpsCallable<unknown, unknown>, bucketName: string): Promise<boolean> => {
-  // Call createBucket() Cloud Function.
-  const response: any = await cf({
-    bucketName
-  })
-
-  // Return true if exists, otherwise false.
-  return response.data
-}
-
-/**
- * Check if an object exists in a given AWS S3 bucket.
- * @param cf <HttpsCallable<unknown, unknown>> - the corresponding cloud function.
- * @param bucketName <string> - the name of the AWS S3 bucket.
- * @param objectKey <string> - the identifier of the object.
- * @returns Promise<string> - true if the object exists, otherwise false.
- */
-export const objectExist = async (
-  cf: HttpsCallable<unknown, unknown>,
-  bucketName: string,
-  objectKey: string
-): Promise<boolean> => {
-  // Call checkIfObjectExist() Cloud Function.
-  const response: any = await cf({
-    bucketName,
-    objectKey
-  })
-
-  // Return true if exists, otherwise false.
-  return response.data
-}
-
-/**
- * Initiate the multi part upload in AWS S3 Bucket for a large object.
- * @param cf <HttpsCallable<unknown, unknown>> - the corresponding cloud function.
- * @param bucketName <string> - the name of the AWS S3 bucket.
- * @param objectKey <string> - the identifier of the object.
- * @param ceremonyId <string> - the identifier of the ceremony.
- * @returns Promise<string> - the Upload ID reference.
- */
-export const openMultiPartUpload = async (
-  cf: HttpsCallable<unknown, unknown>,
-  bucketName: string,
-  objectKey: string,
-  ceremonyId?: string
-): Promise<string> => {
-  // Call startMultiPartUpload() Cloud Function.
-  const response: any = await cf({
-    bucketName,
-    objectKey,
-    ceremonyId
-  })
-
-  // Return Multi Part Upload ID.
-  return response.data
-}
-
-/**
- * Get chunks and signed urls for a multi part upload.
- * @param cf <HttpsCallable<unknown, unknown>> - the corresponding cloud function.
- * @param bucketName <string> - the name of the AWS S3 bucket.
- * @param objectKey <string> - the identifier of the object.
- * @param filePath <string> - the local path where the file to be uploaded is located.
- * @param uploadId <string> - the multi part upload unique identifier.
- * @param expirationInSeconds <number> - the pre signed url expiration in seconds.
- * @param ceremonyId <string> - the identifier of the ceremony.
- * @returns Promise<Array, Array>
- */
-export const getChunksAndPreSignedUrls = async (
-  cf: HttpsCallable<unknown, unknown>,
-  bucketName: string,
-  objectKey: string,
-  filePath: string,
-  uploadId: string,
-  expirationInSeconds: number,
-  ceremonyId?: string
-): Promise<Array<ChunkWithUrl>> => {
-  // Configuration checks.
-  if (!config.CONFIG_STREAM_CHUNK_SIZE_IN_MB) showError(GENERIC_ERRORS.GENERIC_NOT_CONFIGURED_PROPERLY, true)
-
-  // Open a read stream.
-  const stream = fs.createReadStream(filePath, { highWaterMark: config.CONFIG_STREAM_CHUNK_SIZE_IN_MB * 1024 * 1024 })
-
-  // Read and store chunks.
-  const chunks = []
-  for await (const chunk of stream) chunks.push(chunk)
-
-  const numberOfParts = chunks.length
-  if (!numberOfParts) showError(GENERIC_ERRORS.GENERIC_FILE_ERROR, true)
-
-  // Call generatePreSignedUrlsParts() Cloud Function.
-  const response: any = await cf({
-    bucketName,
-    objectKey,
-    uploadId,
-    numberOfParts,
-    expirationInSeconds,
-    ceremonyId
-  })
-
-  return chunks.map((val1, index) => ({
-    partNumber: index + 1,
-    chunk: val1,
-    preSignedUrl: response.data[index]
-  }))
-}
-
-/**
- * Make a PUT request to upload each part for a multi part upload.
- * @param chunksWithUrls <Array<ChunkWithUrl>> - the array containing chunks and corresponding pre signed urls.
- * @param contentType <string | false> - the content type of the file to upload.
- * @param cf <HttpsCallable<unknown, unknown>> - the CF for enable resumable upload from last chunk by temporarily store the ETags and PartNumbers of already uploaded chunks.
- * @param ceremonyId <string> - the unique identifier of the ceremony.
- * @param alreadyUploadedChunks <any> - the ETag and PartNumber temporary information about the already uploaded chunks.
- * @returns <Promise<Array<ETagWithPartNumber>>>
- */
-export const uploadParts = async (
-  chunksWithUrls: Array<ChunkWithUrl>,
-  contentType: string | false,
-  cf?: HttpsCallable<unknown, unknown>,
-  ceremonyId?: string,
-  alreadyUploadedChunks?: any
-): Promise<Array<ETagWithPartNumber>> => {
-  // PartNumber and ETags.
-  let partNumbersAndETags = []
-
-  // Restore the already uploaded chunks in the same order.
-  if (alreadyUploadedChunks) partNumbersAndETags = alreadyUploadedChunks
-
-  // Resume from last uploaded chunk (0 for new multi-part upload).
-  const lastChunkIndex = partNumbersAndETags.length
-
-  // Define a custom progress bar starting from last updated chunk.
-  const progressBar = customProgressBar(ProgressBarType.UPLOAD)
-  progressBar.start(chunksWithUrls.length, lastChunkIndex)
-
-  for (let i = lastChunkIndex; i < chunksWithUrls.length; i += 1) {
-    // Make PUT call.
-    const putResponse = await fetch(chunksWithUrls[i].preSignedUrl, {
-      retryOptions: {
-        retryInitialDelay: 500, // 500 ms.
-        socketTimeout: 60000, // 60 seconds.
-        retryMaxDuration: 300000 // 5 minutes.
-      },
-      method: "PUT",
-      body: chunksWithUrls[i].chunk,
-      headers: {
-        "Content-Type": contentType.toString(),
-        "Content-Length": chunksWithUrls[i].chunk.length.toString()
-      },
-      agent: new https.Agent({ keepAlive: true })
-    })
-
-    // Extract data.
-    const eTag = putResponse.headers.get("etag")
-    const { partNumber } = chunksWithUrls[i]
-
-    // Store PartNumber and ETag.
-    partNumbersAndETags.push({
-      ETag: eTag,
-      PartNumber: partNumber
-    })
-
-    // nb. to be done only when contributing.
-    if (!!ceremonyId && !!cf)
-      // Call CF to temporary store the chunks ETag and PartNumber info (useful for resumable upload).
-      await cf({
-        ceremonyId,
-        eTag,
-        partNumber
-      })
-
-    // Increment the progress bar.
-    progressBar.increment(1)
-  }
-
-  await sleep(1000)
-
-  progressBar.stop()
-
-  return partNumbersAndETags
-}
-
-/**
- * Close the multi part upload in AWS S3 Bucket for a large object.
- * @param cf <HttpsCallable<unknown, unknown>> - the corresponding cloud function.
- * @param bucketName <string> - the name of the AWS S3 bucket.
- * @param objectKey <string> - the identifier of the object.
- * @param uploadId <string> - the multi part upload unique identifier.
- * @param parts Array<ETagWithPartNumber> - the uploaded parts.
- * @param ceremonyId <string> - the identifier of the ceremony.
- * @returns Promise<string> - the location of the uploaded file.
- */
-export const closeMultiPartUpload = async (
-  cf: HttpsCallable<unknown, unknown>,
-  bucketName: string,
-  objectKey: string,
-  uploadId: string,
-  parts: Array<ETagWithPartNumber>,
-  ceremonyId?: string
-): Promise<string> => {
-  // Call completeMultiPartUpload() Cloud Function.
-  const response: any = await cf({
-    bucketName,
-    objectKey,
-    uploadId,
-    parts,
-    ceremonyId
-  })
-
-  // Return uploaded file location.
-  return response.data
-}
-
-/**
- * Download locally a specified file from the given bucket.
- * @param cf <HttpsCallable<unknown, unknown>> - the corresponding cloud function.
- * @param bucketName <string> - the name of the AWS S3 bucket.
- * @param objectKey <string> - the identifier of the object (storage path).
- * @param localPath <string> - the path where the file will be written.
- * @return <Promise<void>>
- */
-export const downloadLocalFileFromBucket = async (
-  cf: HttpsCallable<unknown, unknown>,
-  bucketName: string,
-  objectKey: string,
-  localPath: string
-): Promise<void> => {
-  // Call generateGetObjectPreSignedUrl() Cloud Function.
-  const response: any = await cf({
-    bucketName,
-    objectKey
-  })
-
-  // Get the pre-signed url.
-  const preSignedUrl = response.data
-
-  // Get request.
-  const getResponse = await fetch(preSignedUrl)
-
-  if (!getResponse.ok) showError(`${GENERIC_ERRORS.GENERIC_FILE_ERROR} - ${getResponse.statusText}`, true)
-
-  const contentLength = Number(getResponse.headers.get(`content-length`))
-  const contentLengthInGB = convertToGB(contentLength, true)
-
-  // Create a new write stream.
-  const writeStream = createWriteStream(localPath)
-
-  // Define a custom progress bar starting from last updated chunk.
-  const progressBar = customProgressBar(ProgressBarType.DOWNLOAD)
-
-  // Progress bar step size.
-  const progressBarStepSize = contentLengthInGB / 100
-
-  let writtenData = 0
-  let nextStepSize = progressBarStepSize
-
-  // Init the progress bar.
-  progressBar.start(contentLengthInGB < 0.01 ? 0.01 : Number(contentLengthInGB.toFixed(2)), 0)
-
-  // Write chunk by chunk.
-  for await (const chunk of getResponse.body) {
-    // Write.
-    writeStream.write(chunk)
-
-    // Update.
-    writtenData += chunk.length
-
-    // Check if the progress bar must advance.
-    while (convertToGB(writtenData, true) >= nextStepSize) {
-      // Update.
-      nextStepSize += progressBarStepSize
-
-      // Increment bar.
-      progressBar.update(contentLengthInGB < 0.01 ? 0.01 : parseFloat(nextStepSize.toFixed(2)).valueOf())
-    }
-  }
-
-  await sleep(2000) // workaround for fs close.
-
-  progressBar.stop()
-}
diff --git a/apps/phase2cli/src/lib/utils.ts b/apps/phase2cli/src/lib/utils.ts
deleted file mode 100644
index 0858ea65..00000000
--- a/apps/phase2cli/src/lib/utils.ts
+++ /dev/null
@@ -1,1403 +0,0 @@
-import { request } from "@octokit/request"
-import { DocumentData, QueryDocumentSnapshot, Timestamp } from "firebase/firestore"
-import ora, { Ora } from "ora"
-import figlet from "figlet"
-import clear from "clear"
-import { zKey } from "snarkjs"
-import winston, { Logger } from "winston"
-import { Functions, HttpsCallable, httpsCallable, httpsCallableFromURL } from "firebase/functions"
-import { Timer } from "timer-node"
-import mime from "mime-types"
-import { getDiskInfoSync } from "node-disk-info"
-import Drive from "node-disk-info/dist/classes/drive.js"
-import open from "open"
-import { Presets, SingleBar } from "cli-progress"
-import {
-  FirebaseDocumentInfo,
-  FirebaseServices,
-  ParticipantContributionStep,
-  ParticipantStatus,
-  ProgressBarType,
-  Timing,
-  VerifyContributionComputation
-} from "../../types/index.js"
-import { collections, emojis, firstZkeyIndex, numIterationsExp, paths, symbols, theme } from "./constants.js"
-import { initServices, uploadFileToStorage } from "./firebase.js"
-import { GENERIC_ERRORS, GITHUB_ERRORS, showError } from "./errors.js"
-import { askForConfirmation, askForEntropyOrBeacon } from "./prompts.js"
-import { readFile, readLocalJsonFile, writeFile } from "./files.js"
-import {
-  closeMultiPartUpload,
-  downloadLocalFileFromBucket,
-  getChunksAndPreSignedUrls,
-  openMultiPartUpload,
-  uploadParts
-} from "./storage.js"
-import { getAllCeremonies, getCurrentActiveParticipantTimeout, getCurrentContributorContribution } from "./queries.js"
-
-// Get local configs.
-const { firebase, config } = readLocalJsonFile("../../env.json")
-
-/**
- * Get the Github username for the logged in user.
- * @param token <string> - the Github OAuth 2.0 token.
- * @returns <Promise<string>> - the user Github username.
- */
-export const getGithubUsername = async (token: string): Promise<string> => {
-  // Get user info from Github APIs.
-  const response = await request("GET https://api.github.com/user", {
-    headers: {
-      authorization: `token ${token}`
-    }
-  })
-
-  if (response) return response.data.login
-  showError(GITHUB_ERRORS.GITHUB_GET_USERNAME_FAILED, true)
-
-  return process.exit(0) // nb. workaround to avoid type issues.
-}
-
-/**
- * Get the current amout of available memory for user root disk (mounted in `/` root).
- * @returns <number> - the available memory in kB.
- */
-export const getParticipantCurrentDiskAvailableSpace = (): number => {
-  const disks = getDiskInfoSync()
-  const root = disks.filter((disk: Drive) => disk.mounted === `/`)
-
-  if (root.length !== 1) showError(`Something went wrong while retrieving your root disk available memory`, true)
-
-  const rootDisk = root.at(0)!
-
-  return rootDisk.available
-}
-
-/**
- * Convert bytes or chilobytes into gigabytes with customizable precision.
- * @param bytesOrKB <number> - bytes or KB to be converted.
- * @param isBytes <boolean> - true if the input is in bytes; otherwise false for KB input.
- * @returns <number>
- */
-export const convertToGB = (bytesOrKB: number, isBytes: boolean): number =>
-  Number(bytesOrKB / 1024 ** (isBytes ? 3 : 2))
-
-/**
- * Return an array of true of false based on contribution verification result per each circuit.
- * @param ceremonyId <string> - the unique identifier of the ceremony.
- * @param participantId <string> - the unique identifier of the contributor.
- * @param circuits <Array<FirebaseDocumentInfo>> - the Firestore documents of the ceremony circuits.
- * @param finalize <boolean> - true when finalizing; otherwise false.
- * @returns <Promise<Array<boolean>>>
- */
-export const getContributorContributionsVerificationResults = async (
-  ceremonyId: string,
-  participantId: string,
-  circuits: Array<FirebaseDocumentInfo>,
-  finalize: boolean
-): Promise<Array<boolean>> => {
-  // Keep track contributions verification results.
-  const contributions: Array<boolean> = []
-
-  // Retrieve valid/invalid contributions.
-  for await (const circuit of circuits) {
-    // Get contributions to circuit from contributor.
-    const contributionsToCircuit = await getCurrentContributorContribution(ceremonyId, circuit.id, participantId)
-
-    let contribution: FirebaseDocumentInfo
-
-    if (finalize)
-      // There should be two contributions from coordinator (one is finalization).
-      contribution = contributionsToCircuit
-        .filter((contribution: FirebaseDocumentInfo) => contribution.data.zkeyIndex === "final")
-        .at(0)!
-    // There will be only one contribution.
-    else contribution = contributionsToCircuit.at(0)!
-
-    if (contribution) {
-      // Get data.
-      const contributionData = contribution.data
-
-      if (!contributionData) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
-
-      // Update contributions validity.
-      contributions.push(!!contributionData?.valid)
-    }
-  }
-
-  return contributions
-}
-
-/**
- * Return the attestation made only from valid contributions.
- * @param contributionsValidities Array<boolean> - an array of booleans (true when contribution is valid; otherwise false).
- * @param circuits <Array<FirebaseDocumentInfo>> - the Firestore documents of the ceremony circuits.
- * @param participantData <DocumentData> - the document data of the participant.
- * @param ceremonyId <string> - the unique identifier of the ceremony.
- * @param participantId <string> - the unique identifier of the contributor.
- * @param attestationPreamble <string> - the preamble of the attestation.
- * @param finalize <boolean> - true only when finalizing, otherwise false.
- * @returns <Promise<string>> - the complete attestation string.
- */
-export const getValidContributionAttestation = async (
-  contributionsValidities: Array<boolean>,
-  circuits: Array<FirebaseDocumentInfo>,
-  participantData: DocumentData,
-  ceremonyId: string,
-  participantId: string,
-  attestationPreamble: string,
-  finalize: boolean
-): Promise<string> => {
-  let attestation = attestationPreamble
-
-  // For each contribution validity.
-  for (let idx = 0; idx < contributionsValidities.length; idx += 1) {
-    if (contributionsValidities[idx]) {
-      // Extract data from circuit.
-      const circuit = circuits[idx]
-
-      let contributionHash: string = ""
-
-      // Get the contribution hash.
-      if (finalize) {
-        const numberOfContributions = participantData.contributions.length
-        contributionHash = participantData.contributions[numberOfContributions / 2 + idx].hash
-      } else contributionHash = participantData.contributions[idx].hash
-
-      // Get the contribution data.
-      const contributions = await getCurrentContributorContribution(ceremonyId, circuit.id, participantId)
-
-      let contributionData: DocumentData
-
-      if (finalize)
-        contributionData = contributions.filter(
-          (contribution: FirebaseDocumentInfo) => contribution.data.zkeyIndex === "final"
-        )[0].data!
-      else contributionData = contributions.at(0)?.data!
-
-      // Attestate.
-      attestation = `${attestation}\n\nCircuit # ${circuit.data.sequencePosition} (${
-        circuit.data.prefix
-      })\nContributor # ${
-        contributionData?.zkeyIndex > 0 ? Number(contributionData?.zkeyIndex) : contributionData?.zkeyIndex
-      }\n${contributionHash}`
-    }
-  }
-
-  return attestation
-}
-
-/**
- * Publish a new attestation through a Github Gist.
- * @param token <string> - the Github OAuth 2.0 token.
- * @param content <string> - the content of the attestation.
- * @param ceremonyPrefix <string> - the ceremony prefix.
- * @param ceremonyTitle <string> - the ceremony title.
- */
-export const publishGist = async (
-  token: string,
-  content: string,
-  ceremonyPrefix: string,
-  ceremonyTitle: string
-): Promise<string> => {
-  const response = await request("POST /gists", {
-    description: `Attestation for ${ceremonyTitle} MPC Phase 2 Trusted Setup ceremony`,
-    public: true,
-    files: {
-      [`${ceremonyPrefix}_attestation.txt`]: {
-        content
-      }
-    },
-    headers: {
-      authorization: `token ${token}`
-    }
-  })
-
-  if (response && response.data.html_url) return response.data.html_url
-  showError(GITHUB_ERRORS.GITHUB_GIST_PUBLICATION_FAILED, true)
-
-  return process.exit(0) // nb. workaround to avoid type issues.
-}
-
-/**
- * Helper for obtaining uid and data for query document snapshots.
- * @param queryDocSnap <Array<QueryDocumentSnapshot>> - the array of query document snapshot to be converted.
- * @returns Array<FirebaseDocumentInfo>
- */
-export const fromQueryToFirebaseDocumentInfo = (
-  queryDocSnap: Array<QueryDocumentSnapshot>
-): Array<FirebaseDocumentInfo> =>
-  queryDocSnap.map((doc: QueryDocumentSnapshot<DocumentData>) => ({
-    id: doc.id,
-    ref: doc.ref,
-    data: doc.data()
-  }))
-
-/**
- * Extract from milliseconds the seconds, minutes, hours and days.
- * @param millis <number>
- * @returns <Timing>
- */
-export const getSecondsMinutesHoursFromMillis = (millis: number): Timing => {
-  // Get seconds from millis.
-  let delta = millis / 1000
-
-  const days = Math.floor(delta / 86400)
-  delta -= days * 86400
-
-  const hours = Math.floor(delta / 3600) % 24
-  delta -= hours * 3600
-
-  const minutes = Math.floor(delta / 60) % 60
-  delta -= minutes * 60
-
-  const seconds = Math.floor(delta) % 60
-
-  return {
-    seconds: seconds >= 60 ? 59 : seconds,
-    minutes: minutes >= 60 ? 59 : minutes,
-    hours: hours >= 24 ? 23 : hours,
-    days
-  }
-}
-
-/**
- * Return a string with double digits if the amount is one digit only.
- * @param amount <number>
- * @returns <string>
- */
-export const convertToDoubleDigits = (amount: number): string => (amount < 10 ? `0${amount}` : amount.toString())
-
-/**
- * Sleeps the function execution for given millis.
- * @dev to be used in combination with loggers when writing data into files.
- * @param ms <number> - sleep amount in milliseconds
- * @returns <Promise<unknown>>
- */
-export const sleep = (ms: number): Promise<unknown> => new Promise((resolve) => setTimeout(resolve, ms))
-
-/**
- * Return a custom spinner.
- * @param text <string> - the text that should be displayed as spinner status.
- * @param spinnerLogo <any> - the logo.
- * @returns <Ora> - a new Ora custom spinner.
- */
-export const customSpinner = (text: string, spinnerLogo: any): Ora =>
-  ora({
-    text,
-    spinner: spinnerLogo
-  })
-
-/**
- * Return a simple graphical loader to simulate loading or describe an asynchronous task.
- * @param loadingText <string> - the text that should be displayed while the loader is spinning.
- * @param logo <any> - the logo of the loader.
- * @param durationInMillis <number> - the loader duration time in milliseconds.
- * @param afterLoadingText <string> - the text that should be displayed for the loader stop.
- * @returns <Promise<void>>.
- */
-export const simpleLoader = async (
-  loadingText: string,
-  logo: any,
-  durationInMillis: number,
-  afterLoadingText?: string
-): Promise<void> => {
-  // Define the loader.
-  const loader = customSpinner(loadingText, logo)
-
-  loader.start()
-
-  // nb. wait for `durationInMillis` time while loader is spinning.
-  await sleep(durationInMillis)
-
-  if (afterLoadingText) loader.succeed(afterLoadingText)
-  else loader.stop()
-}
-
-/**
- * Return a custom progress bar.
- * @param type <ProgressBarType> - the type of the progress bar.
- * @returns <SingleBar> - a new custom (single) progress bar.
- */
-export const customProgressBar = (type: ProgressBarType): SingleBar => {
-  // Formats.
-  const uploadFormat = `${emojis.arrowUp}  Uploading [${theme.magenta("{bar}")}] {percentage}% | {value}/{total} Chunks`
-  const downloadFormat = `${emojis.arrowDown}  Downloading [${theme.magenta(
-    "{bar}"
-  )}] {percentage}% | {value}/{total} GB`
-
-  // Define a progress bar showing percentage of completion and chunks downloaded/uploaded.
-  return new SingleBar(
-    {
-      format: type === ProgressBarType.DOWNLOAD ? downloadFormat : uploadFormat,
-      hideCursor: true,
-      clearOnComplete: true
-    },
-    Presets.legacy
-  )
-}
-
-/**
- * Return the bucket name based on ceremony prefix.
- * @param ceremonyPrefix <string> - the ceremony prefix.
- * @returns <string>
- */
-export const getBucketName = (ceremonyPrefix: string): string => {
-  if (!config.CONFIG_CEREMONY_BUCKET_POSTFIX) showError(GENERIC_ERRORS.GENERIC_NOT_CONFIGURED_PROPERLY, true)
-
-  return `${ceremonyPrefix}${config.CONFIG_CEREMONY_BUCKET_POSTFIX!}`
-}
-
-/**
- * Return the ceremonies prefixes for every ceremony.
- * @returns Promise<Array<string>>
- */
-export const getCreatedCeremoniesPrefixes = async (): Promise<Array<string>> => {
-  // Get all ceremonies documents.
-  const ceremonies = await getAllCeremonies()
-
-  let ceremoniesPrefixes = []
-
-  // Return prefixes (if any ceremony).
-  if (ceremonies.length > 0)
-    ceremoniesPrefixes = ceremonies.map((ceremony: FirebaseDocumentInfo) => ceremony.data.prefix)
-
-  return ceremoniesPrefixes
-}
-
-/**
- * Upload a file by subdividing it in chunks to AWS S3 bucket.
- * @param startMultiPartUploadCF <HttpsCallable<unknown, unknown>> - the CF for initiating a multi part upload.
- * @param generatePreSignedUrlsPartsCF <HttpsCallable<unknown, unknown>> - the CF for generating the pre-signed urls for each chunk.
- * @param completeMultiPartUploadCF <HttpsCallable<unknown, unknown>> - the CF for completing a multi part upload.
- * @param bucketName <string> - the name of the AWS S3 bucket.
- * @param objectKey <string> - the path of the object inside the AWS S3 bucket.
- * @param localPath <string> - the local path of the file to be uploaded.
- * @param temporaryStoreCurrentContributionMultiPartUploadId <HttpsCallable<unknown, unknown>> - the CF for enable resumable upload from last chunk by temporarily store the ETags and PartNumbers of already uploaded chunks.
- * @param temporaryStoreCurrentContributionUploadedChunkData <HttpsCallable<unknown, unknown>> - the CF for enable resumable upload from last chunk by temporarily store the ETags and PartNumbers of already uploaded chunks.
- * @param ceremonyId <string> - the unique identifier of the ceremony.
- * @param tempContributionData <any> - the temporary information necessary to resume an already started multi-part upload.
- */
-export const multiPartUpload = async (
-  startMultiPartUploadCF: HttpsCallable<unknown, unknown>,
-  generatePreSignedUrlsPartsCF: HttpsCallable<unknown, unknown>,
-  completeMultiPartUploadCF: HttpsCallable<unknown, unknown>,
-  bucketName: string,
-  objectKey: string,
-  localPath: string,
-  temporaryStoreCurrentContributionMultiPartUploadId?: HttpsCallable<unknown, unknown>,
-  temporaryStoreCurrentContributionUploadedChunkData?: HttpsCallable<unknown, unknown>,
-  ceremonyId?: string,
-  tempContributionData?: any
-) => {
-  // Configuration checks.
-  if (!config.CONFIG_PRESIGNED_URL_EXPIRATION_IN_SECONDS)
-    showError(GENERIC_ERRORS.GENERIC_NOT_CONFIGURED_PROPERLY, true)
-
-  // Get content type.
-  const contentType = mime.lookup(localPath)
-
-  // The Multi-Part Upload unique identifier.
-  let uploadIdZkey = ""
-  // Already uploaded chunks temp info (nb. useful only when resuming).
-  let alreadyUploadedChunks = []
-
-  // Check if the contributor can resume an already started multi-part upload.
-  if (!tempContributionData || (!!tempContributionData && !tempContributionData.uploadId)) {
-    // Start from scratch.
-    const spinner = customSpinner(`Starting upload process...`, `clock`)
-    spinner.start()
-
-    uploadIdZkey = await openMultiPartUpload(startMultiPartUploadCF, bucketName, objectKey, ceremonyId)
-
-    if (temporaryStoreCurrentContributionMultiPartUploadId)
-      // Store Multi-Part Upload ID after generation.
-      await temporaryStoreCurrentContributionMultiPartUploadId({
-        ceremonyId,
-        uploadId: uploadIdZkey
-      })
-
-    spinner.stop()
-  } else {
-    // Read temp info from Firestore.
-    uploadIdZkey = tempContributionData.uploadId
-    alreadyUploadedChunks = tempContributionData.chunks
-  }
-
-  // Step 2
-  const spinner = customSpinner(`Splitting file in chunks...`, `clock`)
-  spinner.start()
-
-  const chunksWithUrlsZkey = await getChunksAndPreSignedUrls(
-    generatePreSignedUrlsPartsCF,
-    bucketName,
-    objectKey,
-    localPath,
-    uploadIdZkey,
-    config.CONFIG_PRESIGNED_URL_EXPIRATION_IN_SECONDS!,
-    ceremonyId
-  )
-
-  // Step 3
-  const partNumbersAndETagsZkey = await uploadParts(
-    chunksWithUrlsZkey,
-    contentType,
-    temporaryStoreCurrentContributionUploadedChunkData,
-    ceremonyId,
-    alreadyUploadedChunks
-  )
-
-  // Step 4
-  spinner.text = `Completing upload...`
-  spinner.start()
-
-  await closeMultiPartUpload(
-    completeMultiPartUploadCF,
-    bucketName,
-    objectKey,
-    uploadIdZkey,
-    partNumbersAndETagsZkey,
-    ceremonyId
-  )
-
-  spinner.stop()
-}
-
-/**
- * Get a value from a key information about a circuit.
- * @param circuitInfo <string> - the stringified content of the .r1cs file.
- * @param rgx <RegExp> - regular expression to match the key.
- * @returns <string>
- */
-export const getCircuitMetadataFromR1csFile = (circuitInfo: string, rgx: RegExp): string => {
-  // Match.
-  const matchInfo = circuitInfo.match(rgx)
-
-  if (!matchInfo) showError(GENERIC_ERRORS.GENERIC_R1CS_MISSING_INFO, true)
-
-  // Split and return the value.
-  return matchInfo?.at(0)?.split(":")[1].replace(" ", "").split("#")[0].replace("\n", "")!
-}
-
-/**
- * Return the necessary Power of Tau "powers" given the number of circuits constraints.
- * @param constraints <number> - the number of circuit contraints.
- * @param outputs <number> - the number of circuit outputs.
- * @returns <number>
- */
-export const estimatePoT = (constraints: number, outputs: number): number => {
-  let power = 2
-  let pot = 2 ** power
-
-  while (constraints + outputs > pot) {
-    power += 1
-    pot = 2 ** power
-  }
-
-  return power
-}
-
-/**
- * Get the powers from pot file name
- * @dev the pot files must follow these convention (i_am_a_pot_file_09.ptau) where the numbers before '.ptau' are the powers.
- * @param potFileName <string>
- * @returns <number>
- */
-export const extractPoTFromFilename = (potFileName: string): number =>
-  Number(potFileName.split("_").pop()?.split(".").at(0))
-
-/**
- * Extract a prefix (like_this) from a provided string with special characters and spaces.
- * @dev replaces all symbols and whitespaces with underscore.
- * @param str <string>
- * @returns <string>
- */
-export const extractPrefix = (str: string): string =>
-  // eslint-disable-next-line no-useless-escape
-  str.replace(/[`\s~!@#$%^&*()|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, "-").toLowerCase()
-
-/**
- * Format the next zkey index.
- * @param progress <number> - the progression in zkey index (= contributions).
- * @returns <string>
- */
-export const formatZkeyIndex = (progress: number): string => {
-  let index = progress.toString()
-
-  while (index.length < firstZkeyIndex.length) {
-    index = `0${index}`
-  }
-
-  return index
-}
-
-/**
- * Convert milliseconds to seconds.
- * @param millis <number>
- * @returns <number>
- */
-export const convertMillisToSeconds = (millis: number): number => Number((millis / 1000).toFixed(2))
-
-/**
- * Return the current server timestamp in milliseconds.
- * @returns <number>
- */
-export const getServerTimestampInMillis = (): number => Timestamp.now().toMillis()
-
-/**
- * Bootstrap whatever is needed for a new command execution (clean terminal, print header, init Firebase services).
- * @returns <Promise<FirebaseServices>>
- */
-export const bootstrapCommandExec = async (): Promise<FirebaseServices> => {
-  // Clean terminal window.
-  clear()
-
-  // Print header.
-  console.log(theme.magenta(figlet.textSync("Phase 2 cli", { font: "Ogre" })))
-
-  // Initialize Firebase services
-  return initServices()
-}
-
-/**
- * Gracefully terminate the command execution
- * @params ghUsername <string> - the Github username of the user.
- */
-export const terminate = async (ghUsername: string) => {
-  console.log(`\nSee you, ${theme.bold(`@${ghUsername}`)} ${emojis.wave}`)
-
-  process.exit(0)
-}
-
-/**
- * Make a new countdown and throws an error when time is up.
- * @param durationInSeconds <number> - the amount of time to be counted in seconds.
- * @param intervalInSeconds <number> - update interval in seconds.
- */
-export const createExpirationCountdown = (durationInSeconds: number, intervalInSeconds: number) => {
-  let seconds = durationInSeconds <= 60 ? durationInSeconds : 60
-
-  setInterval(() => {
-    try {
-      if (durationInSeconds !== 0) {
-        // Update times.
-        durationInSeconds -= intervalInSeconds
-        seconds -= intervalInSeconds
-
-        if (seconds % 60 === 0) seconds = 0
-
-        process.stdout.write(
-          `${symbols.warning} Expires in ${theme.bold(
-            theme.magenta(`00:${Math.floor(durationInSeconds / 60)}:${seconds}`)
-          )}\r`
-        )
-      } else showError(GENERIC_ERRORS.GENERIC_COUNTDOWN_EXPIRED, true)
-    } catch (err: any) {
-      // Workaround to the \r.
-      process.stdout.write(`\n\n`)
-      showError(GENERIC_ERRORS.GENERIC_COUNTDOWN_EXPIRATION, true)
-    }
-  }, intervalInSeconds * 1000)
-}
-
-/**
- * Create and return a simple countdown for a specified amount of time.
- * @param remainingTime <number> - the amount of time to be counted.
- * @param message <string> - the message to be shown.
- * @returns <NodeJS.Timer>
- */
-export const simpleCountdown = (remainingTime: number, message: string): NodeJS.Timer =>
-  setInterval(() => {
-    remainingTime -= 1000
-
-    const {
-      seconds: cdSeconds,
-      minutes: cdMinutes,
-      hours: cdHours
-    } = getSecondsMinutesHoursFromMillis(Math.abs(remainingTime))
-
-    process.stdout.write(
-      `${message} (${remainingTime < 0 ? theme.bold(`-`) : ``}${convertToDoubleDigits(cdHours)}:${convertToDoubleDigits(
-        cdMinutes
-      )}:${convertToDoubleDigits(cdSeconds)})\r`
-    )
-  }, 1000)
-
-/**
- * Handle the request/generation for a random entropy or beacon value.
- * @param askEntropy <boolean> - true when requesting entropy; otherwise false.
- * @return <Promise<string>>
- */
-export const getEntropyOrBeacon = async (askEntropy: boolean): Promise<string> => {
-  let entropyOrBeacon: any
-  let randomEntropy = false
-
-  if (askEntropy) {
-    // Prompt for entropy.
-    const { confirmation } = await askForConfirmation(`Do you prefer to enter entropy manually?`)
-    if (confirmation === undefined) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
-
-    randomEntropy = !confirmation
-  }
-
-  if (randomEntropy) {
-    const spinner = customSpinner(`Generating random entropy...`, "clock")
-    spinner.start()
-
-    // Took inspiration from here https://github.com/glamperd/setup-mpc-ui/blob/master/client/src/state/Compute.tsx#L112.
-    entropyOrBeacon = new Uint8Array(256).map(() => Math.random() * 256).toString()
-
-    spinner.succeed(`Random entropy successfully generated`)
-  }
-
-  if (!askEntropy || !randomEntropy) entropyOrBeacon = await askForEntropyOrBeacon(askEntropy)
-
-  return entropyOrBeacon
-}
-
-/**
- * Manage the communication of timeout-related messages for a contributor.
- * @param participantData <DocumentData> - the data of the participant document.
- * @param participantId <string> - the unique identifier of the contributor.
- * @param ceremonyId <string> - the unique identifier of the ceremony.
- * @param isContributing <boolean>
- * @param ghUsername <string>
- */
-export const handleTimedoutMessageForContributor = async (
-  participantData: DocumentData,
-  participantId: string,
-  ceremonyId: string,
-  isContributing: boolean,
-  ghUsername: string
-): Promise<void> => {
-  // Extract data.
-  const { status, contributionStep, contributionProgress } = participantData
-
-  // Check if the contributor has been timedout.
-  if (status === ParticipantStatus.TIMEDOUT && contributionStep !== ParticipantContributionStep.COMPLETED) {
-    if (!isContributing) console.log(theme.bold(`\n- Circuit # ${theme.magenta(contributionProgress)}`))
-    else process.stdout.write(`\n`)
-
-    console.log(
-      `${symbols.error} ${
-        isContributing ? `You have been timedout while contributing` : `Timeout still in progress.`
-      }\n\n${
-        symbols.warning
-      } This can happen due to network or memory issues, un/intentional crash, or contributions lasting for too long.`
-    )
-
-    // nb. workaround to retrieve the latest timeout data from the database.
-    await simpleLoader(`Checking timeout...`, `clock`, 1000)
-
-    // Check when the participant will be able to retry the contribution.
-    const activeTimeouts = await getCurrentActiveParticipantTimeout(ceremonyId, participantId)
-
-    if (activeTimeouts.length !== 1) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
-
-    const activeTimeoutData = activeTimeouts.at(0)?.data
-
-    if (!activeTimeoutData) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
-
-    const { seconds, minutes, hours, days } = getSecondsMinutesHoursFromMillis(
-      activeTimeoutData?.endDate - getServerTimestampInMillis()
-    )
-
-    console.log(
-      `${symbols.info} You can retry your contribution in ${theme.bold(
-        `${convertToDoubleDigits(days)}:${convertToDoubleDigits(hours)}:${convertToDoubleDigits(
-          minutes
-        )}:${convertToDoubleDigits(seconds)}`
-      )} (dd/hh/mm/ss)`
-    )
-
-    terminate(ghUsername)
-  }
-}
-
-/**
- * Compute a new Groth 16 Phase 2 contribution.
- * @param lastZkey <string> - the local path to last zkey.
- * @param newZkey <string> - the local path to new zkey.
- * @param name <string> - the name of the contributor.
- * @param entropyOrBeacon <string> - the value representing the entropy or beacon.
- * @param logger <Logger | Console> - custom winston or console logger.
- * @param finalize <boolean> - true when finalizing the ceremony with the last contribution; otherwise false.
- * @param contributionComputationTime <number> - the contribution computation time in milliseconds for the circuit.
- */
-export const computeContribution = async (
-  lastZkey: string,
-  newZkey: string,
-  name: string,
-  entropyOrBeacon: string,
-  logger: Logger | Console,
-  finalize: boolean,
-  contributionComputationTime: number
-) => {
-  // Format average contribution time.
-  const { seconds, minutes, hours } = getSecondsMinutesHoursFromMillis(contributionComputationTime)
-
-  // Custom spinner for visual feedback.
-  const text = `${finalize ? `Applying beacon...` : `Computing contribution...`} ${
-    contributionComputationTime > 0
-      ? `(ETA ${theme.bold(
-          `${convertToDoubleDigits(hours)}:${convertToDoubleDigits(minutes)}:${convertToDoubleDigits(seconds)}`
-        )} |`
-      : ``
-  }`
-
-  let counter = 0
-
-  // Format time.
-  const {
-    seconds: counterSeconds,
-    minutes: counterMinutes,
-    hours: counterHours
-  } = getSecondsMinutesHoursFromMillis(counter)
-
-  const spinner = customSpinner(
-    `${text} ${convertToDoubleDigits(counterHours)}:${convertToDoubleDigits(counterMinutes)}:${convertToDoubleDigits(
-      counterSeconds
-    )})\r`,
-    `clock`
-  )
-  spinner.start()
-
-  const interval = setInterval(() => {
-    counter += 1000
-
-    const {
-      seconds: counterSeconds,
-      minutes: counterMinutes,
-      hours: counterHours
-    } = getSecondsMinutesHoursFromMillis(counter)
-
-    spinner.text = `${text} ${convertToDoubleDigits(counterHours)}:${convertToDoubleDigits(
-      counterMinutes
-    )}:${convertToDoubleDigits(counterSeconds)})\r`
-  }, 1000)
-
-  if (finalize)
-    // Finalize applying a random beacon.
-    await zKey.beacon(lastZkey, newZkey, name, entropyOrBeacon, numIterationsExp, logger)
-  // Compute the next contribution.
-  else await zKey.contribute(lastZkey, newZkey, name, entropyOrBeacon, logger)
-
-  // nb. workaround to logger descriptor close.
-  await sleep(1000)
-
-  spinner.stop()
-  clearInterval(interval)
-}
-
-/**
- * Create a custom logger.
- * @dev useful for keeping track of `info` logs from snarkjs and use them to generate the contribution transcript.
- * @param transcriptFilename <string> - logger output file.
- * @returns <Logger>
- */
-export const getTranscriptLogger = (transcriptFilename: string): Logger =>
-  // Create a custom logger.
-  winston.createLogger({
-    level: "info",
-    format: winston.format.printf((log) => log.message),
-    transports: [
-      // Write all logs with importance level of `info` to `transcript.json`.
-      new winston.transports.File({
-        filename: transcriptFilename,
-        level: "info"
-      })
-    ]
-  })
-
-/**
- * Make a progress to the next contribution step for the current contributor.
- * @param firebaseFunctions <Functions> - the object containing the firebase functions.
- * @param ceremonyId <string> - the ceremony unique identifier.
- * @param showSpinner <boolean> - true to show a custom spinner on the terminal; otherwise false.
- * @param message <string> - custom message string based on next contribution step value.
- */
-export const makeContributionStepProgress = async (
-  firebaseFunctions: Functions,
-  ceremonyId: string,
-  showSpinner: boolean,
-  message: string
-) => {
-  // Get CF.
-  const progressToNextContributionStep = httpsCallable(firebaseFunctions, "progressToNextContributionStep")
-
-  // Custom spinner for visual feedback.
-  const spinner: Ora = customSpinner(`Getting ready for ${message} step`, "clock")
-
-  if (showSpinner) spinner.start()
-
-  // Progress to next contribution step.
-  await progressToNextContributionStep({ ceremonyId })
-
-  if (showSpinner) spinner.stop()
-}
-
-/**
- * Return the next circuit where the participant needs to compute or has computed the contribution.
- * @param circuits <Array<FirebaseDocumentInfo>> - the ceremony circuits document.
- * @param nextCircuitPosition <number> - the position in the sequence of circuits where the next contribution must be done.
- * @returns <FirebaseDocumentInfo>
- */
-export const getNextCircuitForContribution = (
-  circuits: Array<FirebaseDocumentInfo>,
-  nextCircuitPosition: number
-): FirebaseDocumentInfo => {
-  // Filter for sequence position (should match contribution progress).
-  const filteredCircuits = circuits.filter(
-    (circuit: FirebaseDocumentInfo) => circuit.data.sequencePosition === nextCircuitPosition
-  )
-
-  // There must be only one.
-  if (filteredCircuits.length !== 1) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
-
-  return filteredCircuits.at(0)!
-}
-
-/**
- * Return the memory space requirement for a zkey in GB.
- * @param zKeySizeInBytes <number> - the size of the zkey in bytes.
- * @returns <number>
- */
-export const getZkeysSpaceRequirementsForContributionInGB = (zKeySizeInBytes: number): number =>
-  // nb. mul per 2 is necessary because download latest + compute newest.
-  convertToGB(zKeySizeInBytes * 2, true)
-
-/**
- * Return the available disk space of the current contributor in GB.
- * @returns <number>
- */
-export const getContributorAvailableDiskSpaceInGB = (): number =>
-  convertToGB(getParticipantCurrentDiskAvailableSpace(), false)
-
-/**
- * Check if the contributor has enough space before starting the contribution for next circuit.
- * @param cf <HttpsCallable<unknown, unknown>> - the corresponding cloud function.
- * @param nextCircuit <FirebaseDocumentInfo> - the circuit document.
- * @param ceremonyId <string> - the unique identifier of the ceremony.
- * @return <Promise<void>>
- */
-export const handleDiskSpaceRequirementForNextContribution = async (
-  cf: HttpsCallable<unknown, unknown>,
-  nextCircuit: FirebaseDocumentInfo,
-  ceremonyId: string
-): Promise<boolean> => {
-  // Get memory info.
-  const zKeysSpaceRequirementsInGB = getZkeysSpaceRequirementsForContributionInGB(nextCircuit.data.zKeySizeInBytes)
-  const availableDiskSpaceInGB = getContributorAvailableDiskSpaceInGB()
-
-  // Extract data.
-  const { sequencePosition } = nextCircuit.data
-
-  process.stdout.write(`\n`)
-
-  await simpleLoader(`Checking your memory...`, `clock`, 1000)
-
-  // Check memory requirement.
-  if (availableDiskSpaceInGB < zKeysSpaceRequirementsInGB) {
-    console.log(theme.bold(`- Circuit # ${theme.magenta(`${sequencePosition}`)}`))
-
-    console.log(
-      `${symbols.error} You do not have enough memory to make a contribution (Required ${
-        zKeysSpaceRequirementsInGB < 0.01 ? theme.bold(`< 0.01`) : theme.bold(zKeysSpaceRequirementsInGB)
-      } GB (available ${
-        availableDiskSpaceInGB > 0 ? theme.bold(availableDiskSpaceInGB.toFixed(2)) : theme.bold(0)
-      } GB)\n`
-    )
-
-    if (sequencePosition > 1) {
-      // The user has computed at least one valid contribution. Therefore, can choose if free up memory and contrinue with next contribution or generate the final attestation.
-      console.log(
-        `${symbols.info} You have time until ceremony ends to free up your memory, complete contributions and publish the attestation`
-      )
-
-      const { confirmation } = await askForConfirmation(
-        `Are you sure you want to generate and publish the attestation for your contributions?`
-      )
-
-      if (!confirmation) {
-        process.stdout.write(`\n`)
-
-        // nb. here the user is not able to generate an attestation because does not have contributed yet. Therefore, return an error and exit.
-        showError(`Please, free up your disk space and run again this command to contribute`, true)
-      }
-    } else {
-      // nb. here the user is not able to generate an attestation because does not have contributed yet. Therefore, return an error and exit.
-      showError(`Please, free up your disk space and run again this command to contribute`, true)
-    }
-  } else {
-    console.log(
-      `${symbols.success} You have enough memory for contributing to ${theme.bold(
-        `Circuit ${theme.magenta(sequencePosition)}`
-      )}`
-    )
-
-    const spinner = customSpinner(
-      `Joining ${theme.bold(`Circuit ${theme.magenta(sequencePosition)}`)} waiting queue...`,
-      `clock`
-    )
-    spinner.start()
-
-    await cf({ ceremonyId })
-
-    spinner.succeed(`All set for contribution to ${theme.bold(`Circuit ${theme.magenta(sequencePosition)}`)}`)
-
-    return false
-  }
-
-  return true
-}
-
-/**
- * Generate the public attestation for the contributor.
- * @param ceremonyDoc <FirebaseDocumentInfo> - the ceremony document.
- * @param participantId <string> - the unique identifier of the participant.
- * @param participantData <DocumentData> - the data of the participant document.
- * @param circuits <Array<FirebaseDocumentInfo> - the ceremony circuits documents.
- * @param ghUsername <string> - the Github username of the contributor.
- * @param ghToken <string> - the Github access token of the contributor.
- */
-export const generatePublicAttestation = async (
-  ceremonyDoc: FirebaseDocumentInfo,
-  participantId: string,
-  participantData: DocumentData,
-  circuits: Array<FirebaseDocumentInfo>,
-  ghUsername: string,
-  ghToken: string
-): Promise<void> => {
-  // Attestation preamble.
-  const attestationPreamble = `Hey, I'm ${ghUsername} and I have contributed to the ${ceremonyDoc.data.title} MPC Phase2 Trusted Setup ceremony.\nThe following are my contribution signatures:`
-
-  // Return true and false based on contribution verification.
-  const contributionsValidity = await getContributorContributionsVerificationResults(
-    ceremonyDoc.id,
-    participantId,
-    circuits,
-    false
-  )
-  const numberOfValidContributions = contributionsValidity.filter(Boolean).length
-
-  console.log(
-    `\nCongrats, you have successfully contributed to ${theme.magenta(
-      theme.bold(numberOfValidContributions)
-    )} out of ${theme.magenta(theme.bold(circuits.length))} circuits ${emojis.tada}`
-  )
-
-  // Show valid/invalid contributions per each circuit.
-  let idx = 0
-
-  for (const contributionValidity of contributionsValidity) {
-    console.log(
-      `${contributionValidity ? symbols.success : symbols.error} ${theme.bold(`Circuit`)} ${theme.bold(
-        theme.magenta(idx + 1)
-      )}`
-    )
-    idx += 1
-  }
-
-  process.stdout.write(`\n`)
-
-  const spinner = customSpinner("Uploading public attestation...", "clock")
-  spinner.start()
-
-  // Get only valid contribution hashes.
-  const attestation = await getValidContributionAttestation(
-    contributionsValidity,
-    circuits,
-    participantData!,
-    ceremonyDoc.id,
-    participantId,
-    attestationPreamble,
-    false
-  )
-
-  writeFile(`${paths.attestationPath}/${ceremonyDoc.data.prefix}_attestation.log`, Buffer.from(attestation))
-  await sleep(1000)
-
-  // TODO: If fails for permissions problems, ask to do manually.
-  const gistUrl = await publishGist(ghToken, attestation, ceremonyDoc.data.prefix, ceremonyDoc.data.title)
-
-  spinner.succeed(
-    `Public attestation successfully published as Github Gist at this link ${theme.bold(theme.underlined(gistUrl))}`
-  )
-
-  // Attestation link via Twitter.
-  const attestationTweet = `https://twitter.com/intent/tweet?text=I%20contributed%20to%20the%20${ceremonyDoc.data.title}%20Phase%202%20Trusted%20Setup%20ceremony!%20You%20can%20contribute%20here:%20https://github.com/quadratic-funding/mpc-phase2-suite%20You%20can%20view%20my%20attestation%20here:%20${gistUrl}%20#Ethereum%20#ZKP`
-
-  console.log(
-    `\nWe appreciate your contribution to preserving the ${ceremonyDoc.data.title} security! ${
-      emojis.key
-    }  You can tweet about your participation if you'd like (click on the link below ${
-      emojis.pointDown
-    }) \n\n${theme.underlined(attestationTweet)}`
-  )
-
-  await open(attestationTweet)
-}
-
-/**
- * Download a local copy of the zkey.
- * @param cf <HttpsCallable<unknown, unknown>> - the corresponding cloud function.
- * @param bucketName <string> - the name of the AWS S3 bucket.
- * @param objectKey <string> - the identifier of the object (storage path).
- * @param localPath <string> - the path where the file will be written.
- * @param showSpinner <boolean> - true to show a custom spinner on the terminal; otherwise false.
- */
-export const downloadContribution = async (
-  cf: HttpsCallable<unknown, unknown>,
-  bucketName: string,
-  objectKey: string,
-  localPath: string,
-  showSpinner: boolean
-) => {
-  // Custom spinner for visual feedback.
-  const spinner: Ora = customSpinner(`Downloading contribution...`, "clock")
-
-  if (showSpinner) spinner.start()
-
-  // Download from storage.
-  await downloadLocalFileFromBucket(cf, bucketName, objectKey, localPath)
-
-  if (showSpinner) spinner.stop()
-}
-
-/**
- * Upload the new zkey to the storage.
- * @param storagePath <string> - the Storage path where the zkey will be stored.
- * @param localPath <string> - the local path where the zkey is stored.
- * @param showSpinner <boolean> - true to show a custom spinner on the terminal; otherwise false.
- */
-export const uploadContribution = async (storagePath: string, localPath: string, showSpinner: boolean) => {
-  // Custom spinner for visual feedback.
-  const spinner = customSpinner("Storing your contribution...", "clock")
-  if (showSpinner) spinner.start()
-
-  // Upload to storage.
-  await uploadFileToStorage(localPath, storagePath)
-
-  if (showSpinner) spinner.stop()
-}
-
-/**
- * Compute a new Groth16 contribution verification.
- * @param ceremony <FirebaseDocumentInfo> - the ceremony document.
- * @param circuit <FirebaseDocumentInfo> - the circuit document.
- * @param ghUsername <string> - the Github username of the user.
- * @param avgVerifyCloudFunctionTime <number> - the average verify Cloud Function execution time in milliseconds.
- * @param firebaseFunctions <Functions> - the object containing the firebase functions.
- * @returns <Promise<VerifyContributionComputation>>
- */
-export const computeVerification = async (
-  ceremony: FirebaseDocumentInfo,
-  circuit: FirebaseDocumentInfo,
-  ghUsername: string,
-  avgVerifyCloudFunctionTime: number,
-  firebaseFunctions: Functions
-): Promise<VerifyContributionComputation> => {
-  // Format average verification time.
-  const { seconds, minutes, hours } = getSecondsMinutesHoursFromMillis(avgVerifyCloudFunctionTime)
-
-  // Custom spinner for visual feedback.
-  const spinner = customSpinner(
-    `Verifying your contribution... ${
-      avgVerifyCloudFunctionTime > 0
-        ? `(est. time ${theme.bold(
-            `${convertToDoubleDigits(hours)}:${convertToDoubleDigits(minutes)}:${convertToDoubleDigits(seconds)}`
-          )})`
-        : ``
-    }\n`,
-    "clock"
-  )
-
-  spinner.start()
-
-  // Verify contribution callable Cloud Function.
-  const verifyContribution = httpsCallableFromURL(firebaseFunctions!, firebase.FIREBASE_CF_URL_VERIFY_CONTRIBUTION!, {
-    timeout: 3600000
-  })
-
-  // The verification must be done remotely (Cloud Functions).
-  const response = await verifyContribution({
-    ceremonyId: ceremony.id,
-    circuitId: circuit.id,
-    ghUsername,
-    bucketName: getBucketName(ceremony.data.prefix)
-  })
-
-  spinner.stop()
-
-  if (!response) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
-
-  const { data }: any = response
-
-  return {
-    valid: data.valid,
-    verificationComputationTime: data.verificationComputationTime,
-    verifyCloudFunctionTime: data.verifyCloudFunctionTime,
-    fullContributionTime: data.fullContributionTime
-  }
-}
-
-/**
- * Compute a new contribution for the participant.
- * @param ceremony <FirebaseDocumentInfo> - the ceremony document.
- * @param circuit <FirebaseDocumentInfo> - the circuit document.
- * @param entropyOrBeacon <any> - the entropy/beacon for the contribution.
- * @param ghUsername <string> - the Github username of the user.
- * @param finalize <boolean> - true if the contribution finalize the ceremony; otherwise false.
- * @param firebaseFunctions <Functions> - the object containing the firebase functions.
- * @param newParticipantData <DocumentData> - the object containing the participant data.
- * @returns <Promise<string>> - new updated attestation file.
- */
-export const makeContribution = async (
-  ceremony: FirebaseDocumentInfo,
-  circuit: FirebaseDocumentInfo,
-  entropyOrBeacon: any,
-  ghUsername: string,
-  finalize: boolean,
-  firebaseFunctions: Functions,
-  newParticipantData?: DocumentData
-): Promise<void> => {
-  // Extract data from circuit.
-  const currentProgress = circuit.data.waitingQueue.completedContributions
-  const { avgTimings } = circuit.data
-
-  // Compute zkey indexes.
-  const currentZkeyIndex = formatZkeyIndex(currentProgress)
-  const nextZkeyIndex = formatZkeyIndex(currentProgress + 1)
-
-  // Paths config.
-  const transcriptsPath = finalize ? paths.finalTranscriptsPath : paths.contributionTranscriptsPath
-  const contributionsPath = finalize ? paths.finalZkeysPath : paths.contributionsPath
-
-  // Get custom transcript logger.
-  const contributionTranscriptLocalPath = `${transcriptsPath}/${circuit.data.prefix}_${
-    finalize ? `${ghUsername}_final` : nextZkeyIndex
-  }.log`
-  const transcriptLogger = getTranscriptLogger(contributionTranscriptLocalPath)
-  const bucketName = getBucketName(ceremony.data.prefix)
-
-  // Write first message.
-  transcriptLogger.info(
-    `${finalize ? `Final` : `Contribution`} transcript for ${circuit.data.prefix} phase 2 contribution.\n${
-      finalize ? `Coordinator: ${ghUsername}` : `Contributor # ${Number(nextZkeyIndex)}`
-    } (${ghUsername})\n`
-  )
-
-  console.log(
-    `${theme.bold(`\n- Circuit # ${theme.magenta(`${circuit.data.sequencePosition}`)}`)} (Contribution Steps)`
-  )
-
-  if (
-    finalize ||
-    (!!newParticipantData?.contributionStep &&
-      newParticipantData?.contributionStep === ParticipantContributionStep.DOWNLOADING)
-  ) {
-    const spinner = customSpinner(`Preparing for download...`, `clock`)
-    spinner.start()
-
-    // 1. Download last contribution.
-    const storagePath = `${collections.circuits}/${circuit.data.prefix}/${collections.contributions}/${circuit.data.prefix}_${currentZkeyIndex}.zkey`
-    const localPath = `${contributionsPath}/${circuit.data.prefix}_${currentZkeyIndex}.zkey`
-
-    // Download w/ Presigned urls.
-    const generateGetObjectPreSignedUrl = httpsCallable(firebaseFunctions!, "generateGetObjectPreSignedUrl")
-
-    spinner.stop()
-
-    await downloadContribution(generateGetObjectPreSignedUrl, bucketName, storagePath, localPath, false)
-
-    console.log(`${symbols.success} Contribution ${theme.bold(`#${currentZkeyIndex}`)} correctly downloaded`)
-
-    // Make the step if not finalizing.
-    if (!finalize) await makeContributionStepProgress(firebaseFunctions!, ceremony.id, true, "computation")
-  } else console.log(`${symbols.success} Contribution ${theme.bold(`#${currentZkeyIndex}`)} already downloaded`)
-
-  if (
-    finalize ||
-    (!!newParticipantData?.contributionStep &&
-      newParticipantData?.contributionStep === ParticipantContributionStep.DOWNLOADING) ||
-    newParticipantData?.contributionStep === ParticipantContributionStep.COMPUTING
-  ) {
-    const contributionComputationTimer = new Timer({ label: "contributionComputation" }) // Compute time (only for statistics).
-
-    // 2.A Compute the new contribution.
-    contributionComputationTimer.start()
-
-    await computeContribution(
-      `${contributionsPath}/${circuit.data.prefix}_${currentZkeyIndex}.zkey`,
-      `${contributionsPath}/${circuit.data.prefix}_${finalize ? `final` : nextZkeyIndex}.zkey`,
-      ghUsername,
-      entropyOrBeacon,
-      transcriptLogger,
-      finalize,
-      avgTimings.contributionComputation
-    )
-
-    contributionComputationTimer.stop()
-
-    const contributionComputationTime = contributionComputationTimer.ms()
-
-    const spinner = customSpinner(`Storing contribution time and hash...`, `clock`)
-    spinner.start()
-
-    // nb. workaround for file descriptor close.
-    await sleep(2000)
-
-    // 2.B Generate attestation from single contribution transcripts from each circuit (queue this contribution).
-    const transcript = readFile(contributionTranscriptLocalPath)
-
-    const matchContributionHash = transcript.match(/Contribution.+Hash.+\n\t\t.+\n\t\t.+\n.+\n\t\t.+\n/)
-
-    if (!matchContributionHash) showError(GENERIC_ERRORS.GENERIC_CONTRIBUTION_HASH_INVALID, true)
-
-    const contributionHash = matchContributionHash?.at(0)?.replace("\n\t\t", "")!
-
-    const permanentlyStoreCurrentContributionTimeAndHash = httpsCallable(
-      firebaseFunctions!,
-      "permanentlyStoreCurrentContributionTimeAndHash"
-    )
-
-    await permanentlyStoreCurrentContributionTimeAndHash({
-      ceremonyId: ceremony.id,
-      contributionComputationTime,
-      contributionHash
-    })
-
-    const {
-      seconds: computationSeconds,
-      minutes: computationMinutes,
-      hours: computationHours
-    } = getSecondsMinutesHoursFromMillis(contributionComputationTime)
-
-    spinner.succeed(
-      `${finalize ? "Contribution" : `Contribution ${theme.bold(`#${nextZkeyIndex}`)}`} computation took ${theme.bold(
-        `${convertToDoubleDigits(computationHours)}:${convertToDoubleDigits(
-          computationMinutes
-        )}:${convertToDoubleDigits(computationSeconds)}`
-      )}`
-    )
-
-    // Make the step if not finalizing.
-    if (!finalize) await makeContributionStepProgress(firebaseFunctions!, ceremony.id, true, "upload")
-  } else console.log(`${symbols.success} Contribution ${theme.bold(`#${nextZkeyIndex}`)} already computed`)
-
-  if (
-    finalize ||
-    (!!newParticipantData?.contributionStep &&
-      newParticipantData?.contributionStep === ParticipantContributionStep.DOWNLOADING) ||
-    newParticipantData?.contributionStep === ParticipantContributionStep.COMPUTING ||
-    newParticipantData?.contributionStep === ParticipantContributionStep.UPLOADING
-  ) {
-    // 3. Store file.
-    const storagePath = `${collections.circuits}/${circuit.data.prefix}/${collections.contributions}/${
-      circuit.data.prefix
-    }_${finalize ? `final` : nextZkeyIndex}.zkey`
-    const localPath = `${contributionsPath}/${circuit.data.prefix}_${finalize ? `final` : nextZkeyIndex}.zkey`
-
-    // Upload.
-    const startMultiPartUpload = httpsCallable(firebaseFunctions, "startMultiPartUpload")
-    const generatePreSignedUrlsParts = httpsCallable(firebaseFunctions, "generatePreSignedUrlsParts")
-    const completeMultiPartUpload = httpsCallable(firebaseFunctions, "completeMultiPartUpload")
-
-    if (!finalize) {
-      const temporaryStoreCurrentContributionMultiPartUploadId = httpsCallable(
-        firebaseFunctions,
-        "temporaryStoreCurrentContributionMultiPartUploadId"
-      )
-      const temporaryStoreCurrentContributionUploadedChunk = httpsCallable(
-        firebaseFunctions,
-        "temporaryStoreCurrentContributionUploadedChunkData"
-      )
-
-      await multiPartUpload(
-        startMultiPartUpload,
-        generatePreSignedUrlsParts,
-        completeMultiPartUpload,
-        bucketName,
-        storagePath,
-        localPath,
-        temporaryStoreCurrentContributionMultiPartUploadId,
-        temporaryStoreCurrentContributionUploadedChunk,
-        ceremony.id,
-        newParticipantData?.tempContributionData
-      )
-    } else
-      await multiPartUpload(
-        startMultiPartUpload,
-        generatePreSignedUrlsParts,
-        completeMultiPartUpload,
-        bucketName,
-        storagePath,
-        localPath
-      )
-
-    console.log(
-      `${symbols.success} ${
-        finalize ? `Contribution` : `Contribution ${theme.bold(`#${nextZkeyIndex}`)}`
-      } correctly saved on storage`
-    )
-
-    // Make the step if not finalizing.
-    if (!finalize) await makeContributionStepProgress(firebaseFunctions!, ceremony.id, true, "verification")
-  } else
-    console.log(
-      `${symbols.success} ${
-        finalize ? `Contribution` : `Contribution ${theme.bold(`#${nextZkeyIndex}`)}`
-      } already saved on storage`
-    )
-
-  if (
-    finalize ||
-    (!!newParticipantData?.contributionStep &&
-      newParticipantData?.contributionStep === ParticipantContributionStep.DOWNLOADING) ||
-    newParticipantData?.contributionStep === ParticipantContributionStep.COMPUTING ||
-    newParticipantData?.contributionStep === ParticipantContributionStep.UPLOADING ||
-    newParticipantData?.contributionStep === ParticipantContributionStep.VERIFYING
-  ) {
-    // 5. Verify contribution.
-    const { valid, verifyCloudFunctionTime, fullContributionTime } = await computeVerification(
-      ceremony,
-      circuit,
-      ghUsername,
-      avgTimings.verifyCloudFunction,
-      firebaseFunctions
-    )
-
-    const {
-      seconds: verificationSeconds,
-      minutes: verificationMinutes,
-      hours: verificationHours
-    } = getSecondsMinutesHoursFromMillis(verifyCloudFunctionTime)
-
-    console.log(
-      `${valid ? symbols.success : symbols.error} ${
-        finalize ? `Contribution` : `Contribution ${theme.bold(`#${nextZkeyIndex}`)}`
-      } ${valid ? `is ${theme.bold("VALID")}` : `is ${theme.bold("INVALID")}`}`
-    )
-    console.log(
-      `${symbols.success} ${
-        finalize ? `Contribution` : `Contribution ${theme.bold(`#${nextZkeyIndex}`)}`
-      } verification took ${theme.bold(
-        `${convertToDoubleDigits(verificationHours)}:${convertToDoubleDigits(
-          verificationMinutes
-        )}:${convertToDoubleDigits(verificationSeconds)}`
-      )}`
-    )
-
-    const {
-      seconds: contributionSeconds,
-      minutes: contributionMinutes,
-      hours: contributionHours
-    } = getSecondsMinutesHoursFromMillis(fullContributionTime + verifyCloudFunctionTime)
-    console.log(
-      `${symbols.info} Your contribution took ${theme.bold(
-        `${convertToDoubleDigits(contributionHours)}:${convertToDoubleDigits(
-          contributionMinutes
-        )}:${convertToDoubleDigits(contributionSeconds)}`
-      )}`
-    )
-  }
-}
diff --git a/apps/phase2cli/test/index.test.ts b/apps/phase2cli/test/index.test.ts
deleted file mode 100644
index a8fc65bd..00000000
--- a/apps/phase2cli/test/index.test.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-describe("feature 1", () => {
-  it("should console log 'Hello, World!'", () => {
-    console.log("Hello, World!")
-  })
-})
diff --git a/apps/phase2cli/tsconfig.json b/apps/phase2cli/tsconfig.json
deleted file mode 100644
index 6b6f556f..00000000
--- a/apps/phase2cli/tsconfig.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "extends": "../../tsconfig.json",
-  "compilerOptions": {
-    "outDir": "dist/",
-    "declarationDir": "dist/types"
-  },
-  "include": ["src/**/*", "test/**/*", "types/**/*", "*.json"],
-  "exclude": ["node_modules", "tsconfig.json", "dist/**/*"]
-}
diff --git a/apps/phase2cli/types/index.ts b/apps/phase2cli/types/index.ts
deleted file mode 100644
index 469eb5fa..00000000
--- a/apps/phase2cli/types/index.ts
+++ /dev/null
@@ -1,221 +0,0 @@
-import { FirebaseApp } from "firebase/app"
-import { DocumentData, DocumentReference, Firestore } from "firebase/firestore"
-import { Functions } from "firebase/functions"
-import { FirebaseStorage } from "firebase/storage"
-import { User as FirebaseAuthUser } from "firebase/auth"
-
-export enum CeremonyState {
-  SCHEDULED = 1,
-  OPENED = 2,
-  PAUSED = 3,
-  CLOSED = 4,
-  FINALIZED = 5
-}
-
-export enum CeremonyType {
-  PHASE1 = 1,
-  PHASE2 = 2
-}
-
-export enum ProgressBarType {
-  DOWNLOAD = 1,
-  UPLOAD = 2
-}
-
-export enum ParticipantStatus {
-  CREATED = 1,
-  WAITING = 2,
-  READY = 3,
-  CONTRIBUTING = 4,
-  CONTRIBUTED = 5,
-  DONE = 6,
-  FINALIZING = 7,
-  FINALIZED = 8,
-  TIMEDOUT = 9,
-  EXHUMED = 10
-}
-
-export type GithubOAuthRequest = {
-  device_code: string
-  user_code: string
-  verification_uri: string
-  expires_in: number
-  interval: number
-}
-
-export type GithubOAuthResponse = {
-  clientSecret: string
-  type: string
-  tokenType: string
-  clientType: string
-  clientId: string
-  token: string
-  scopes: string[]
-}
-
-export type FirebaseServices = {
-  firebaseApp: FirebaseApp
-  firestoreDatabase: Firestore
-  firebaseStorage: FirebaseStorage
-  firebaseFunctions: Functions
-}
-
-export type LocalPathDirectories = {
-  r1csDirPath: string
-  metadataDirPath: string
-  zkeysDirPath: string
-  ptauDirPath: string
-}
-
-export type FirebaseDocumentInfo = {
-  id: string
-  ref: DocumentReference<DocumentData>
-  data: DocumentData
-}
-
-export type User = {
-  name: string
-  username: string
-  providerId: string
-  createdAt: Date
-  lastLoginAt: Date
-}
-
-export type AuthUser = {
-  user: FirebaseAuthUser
-  token: string
-  username: string
-}
-
-export type CeremonyInputData = {
-  title: string
-  description: string
-  startDate: Date
-  endDate: Date
-  timeoutMechanismType: CeremonyTimeoutType
-  penalty: number
-}
-
-export type CircomCompilerData = {
-  version: string
-  commitHash: string
-}
-
-export type SourceTemplateData = {
-  source: string
-  commitHash: string
-  paramsConfiguration: Array<string>
-}
-
-export type CircuitInputData = {
-  name?: string
-  description: string
-  timeoutThreshold?: number
-  timeoutMaxContributionWaitingTime?: number
-  sequencePosition?: number
-  prefix?: string
-  zKeySizeInBytes?: number
-  compiler: CircomCompilerData
-  template: SourceTemplateData
-}
-
-export type Ceremony = CeremonyInputData & {
-  prefix: string
-  state: CeremonyState
-  type: CeremonyType
-  coordinatorId: string
-  lastUpdated: number
-}
-
-export type CircuitMetadata = {
-  curve: string
-  wires: number
-  constraints: number
-  privateInputs: number
-  publicOutputs: number
-  labels: number
-  outputs: number
-  pot: number
-}
-
-export type CircuitFiles = {
-  files?: {
-    potFilename: string
-    r1csFilename: string
-    initialZkeyFilename: string
-    potStoragePath: string
-    r1csStoragePath: string
-    initialZkeyStoragePath: string
-    potBlake2bHash: string
-    r1csBlake2bHash: string
-    initialZkeyBlake2bHash: string
-  }
-}
-
-export type CircuitTimings = {
-  avgTimings?: {
-    contributionComputation: number
-    fullContribution: number
-    verifyCloudFunction: number
-  }
-}
-
-export type Circuit = CircuitInputData &
-  CircuitFiles &
-  CircuitTimings & {
-    metadata: CircuitMetadata
-    lastUpdated?: number
-  }
-
-export type Timing = {
-  seconds: number
-  minutes: number
-  hours: number
-  days: number
-}
-
-export type CeremonyTimeoutData = {
-  type: CeremonyTimeoutType
-  penalty: number
-}
-
-export type VerifyContributionComputation = {
-  valid: boolean
-  verificationComputationTime: number
-  verifyCloudFunctionTime: number
-  fullContributionTime: number
-}
-
-export type ChunkWithUrl = {
-  partNumber: number
-  chunk: Buffer
-  preSignedUrl: string
-}
-
-export type ETagWithPartNumber = {
-  ETag: string | null
-  PartNumber: number
-}
-
-export enum RequestType {
-  PUT = 1,
-  GET = 2
-}
-
-export enum ParticipantContributionStep {
-  DOWNLOADING = 1,
-  COMPUTING = 2,
-  UPLOADING = 3,
-  VERIFYING = 4,
-  COMPLETED = 5
-}
-
-export enum TimeoutType {
-  BLOCKING_CONTRIBUTION = 1,
-  BLOCKING_CLOUD_FUNCTION = 2
-}
-
-export enum CeremonyTimeoutType {
-  DYNAMIC = 1,
-  FIXED = 2
-}
diff --git a/apps/phase2cli/types/snarkjs.d.ts b/apps/phase2cli/types/snarkjs.d.ts
deleted file mode 100644
index ab419e76..00000000
--- a/apps/phase2cli/types/snarkjs.d.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-/** Declaration file generated by dts-gen */
-
-declare module "snarkjs" {
-  // eslint-disable-next-line @typescript-eslint/no-use-before-define
-  export = snarkjs
-
-  declare const snarkjs: {
-    groth16: {
-      exportSolidityCallData: any
-      fullProve: any
-      prove: any
-      verify: any
-    }
-    plonk: {
-      exportSolidityCallData: any
-      fullProve: any
-      prove: any
-      setup: any
-      verify: any
-    }
-    powersOfTau: {
-      beacon: any
-      challengeContribute: any
-      contribute: any
-      convert: any
-      exportChallenge: any
-      exportJson: any
-      importResponse: any
-      newAccumulator: any
-      preparePhase2: any
-      truncate: any
-      verify: any
-    }
-    r1cs: {
-      exportJson: any
-      info: any
-      print: any
-    }
-    wtns: {
-      calculate: any
-      debug: any
-      exportJson: any
-    }
-    zKey: {
-      beacon: any
-      bellmanContribute: any
-      contribute: any
-      exportBellman: any
-      exportJson: any
-      exportSolidityVerifier: any
-      exportVerificationKey: any
-      importBellman: any
-      newZKey: any
-      verifyFromInit: any
-      verifyFromR1cs: any
-    }
-  }
-}
diff --git a/babel.config.json b/babel.config.json
new file mode 100644
index 00000000..05d294cc
--- /dev/null
+++ b/babel.config.json
@@ -0,0 +1,13 @@
+{
+    "presets": [
+        [
+            "@babel/preset-env",
+            {
+                "targets": {
+                    "node": "current"
+                }
+            }
+        ],
+        "@babel/preset-typescript"
+    ]
+}
diff --git a/jest.config.ts b/jest.config.ts
deleted file mode 100644
index 55ce7c42..00000000
--- a/jest.config.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-export default {
-  rootDir: "./",
-  collectCoverageFrom: ["**/src/index.ts", "!**/dist/**", "!**/node_modules/**"],
-  preset: "ts-jest",
-  testEnvironment: "node",
-  testPathIgnorePatterns: [".d.ts", ".js"],
-  clearMocks: true,
-  collectCoverage: true,
-  coverageDirectory: "coverage/",
-  coverageProvider: "v8",
-  verbose: true,
-  coverageThreshold: {
-    global: {
-      branches: 90,
-      functions: 95,
-      lines: 95,
-      statements: 95
-    }
-  }
-}
diff --git a/jest.json b/jest.json
new file mode 100644
index 00000000..c50a0c91
--- /dev/null
+++ b/jest.json
@@ -0,0 +1,31 @@
+{
+    "testPathIgnorePatterns": [".d.ts", ".js"],
+    "moduleFileExtensions": ["ts", "js"],
+    "transform": {
+        "\\.(t|j)s?$": "babel-jest"
+    },
+    "testMatch": ["**/test/**/*.test.*"],
+    "globals": {
+        "ts-jest": {
+            "tsConfig": "tsconfig.json"
+        }
+    },
+    "moduleNameMapper": {
+        "@zkmpc/(.*)$": "<rootDir>/packages/$1"
+    },
+    "setupFiles": ["dotenv/config"],
+    "collectCoverageFrom": ["<rootDir>/src/**/*.ts", "!<rootDir>/src/**/index.ts", "!<rootDir>/src/**/*.d.ts"],
+    "verbose": true,
+    "collectCoverage": true,
+    "collectCoverageFrom": ["packages/**/*", "!**/dist/**", "!**/node_modules/**"],
+    "coverageDirectory": "coverage/",
+    "coverageProvider": "v8",
+    "coverageThreshold": {
+        "global": {
+            "branches": 90,
+            "functions": 95,
+            "lines": 95,
+            "statements": 95
+        }
+    }
+}
diff --git a/lerna.json b/lerna.json
index aebebbab..9d05b6b1 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,6 @@
 {
-  "$schema": "node_modules/lerna/schemas/lerna-schema.json",
-  "useWorkspaces": true,
-  "version": "0.0.0"
+    "packages": ["packages/*"],
+    "npmClient": "yarn",
+    "useWorkspaces": true,
+    "version": "0.0.0"
 }
diff --git a/package.json b/package.json
index 7e64d416..ae5c4be7 100644
--- a/package.json
+++ b/package.json
@@ -1,69 +1,71 @@
 {
-  "name": "mpc-phase2-suite",
-  "description": "MPC Phase 2 suite of tools for conducting zkSNARKs trusted setup ceremonies",
-  "repository": "git@github.com:quadratic-funding/mpc-phase2-suite.git",
-  "homepage": "https://github.com/quadratic-funding/mpc-phase2-suite",
-  "bugs": "https://github.com/quadratic-funding/mpc-phase2-suite/issues",
-  "license": "MIT",
-  "private": true,
-  "keywords": [
-    "typescript",
-    "zero-knowledge",
-    "zk-snarks",
-    "phase-2",
-    "trusted-setup",
-    "ceremony",
-    "snarkjs",
-    "circom"
-  ],
-  "scripts": {
-    "build": "lerna run build",
-    "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
-    "lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
-    "prettier": "prettier -c .",
-    "prettier:fix": "prettier -w .",
-    "test": "export GOOGLE_APPLICATION_CREDENTIALS=\"./apps/backend/serviceAccountKey.json\" && jest --coverage --detectOpenHandles",
-    "test:watch": "export GOOGLE_APPLICATION_CREDENTIALS=\"./apps/backend/serviceAccountKey.json\" && jest --coverage --watch --detectOpenHandles",
-    "commit": "cz",
-    "precommit": "lint-staged"
-  },
-  "devDependencies": {
-    "@commitlint/cli": "^17.0.3",
-    "@commitlint/config-conventional": "^17.0.3",
-    "@types/chai": "^4.3.1",
-    "@types/chai-as-promised": "^7.1.5",
-    "@types/jest": "^28.1.6",
-    "@types/node": "^18.6.3",
-    "@typescript-eslint/eslint-plugin": "^5.32.0",
-    "@typescript-eslint/parser": "^5.32.0",
-    "chai": "^4.3.6",
-    "chai-as-promised": "^7.1.1",
-    "commitizen": "^4.2.5",
-    "cz-conventional-changelog": "^3.3.0",
-    "eslint": "^8.21.0",
-    "eslint-config-airbnb": "^19.0.4",
-    "eslint-config-airbnb-typescript": "^17.0.0",
-    "eslint-config-prettier": "^8.5.0",
-    "eslint-plugin-import": "^2.26.0",
-    "eslint-plugin-jest": "^26.7.0",
-    "eslint-plugin-jsx-a11y": "^6.6.1",
-    "eslint-plugin-react": "^7.30.1",
-    "eslint-plugin-react-hooks": "^4.6.0",
-    "jest": "^28.1.3",
-    "jest-config": "^28.1.3",
-    "lerna": "^6.0.3",
-    "lint-staged": "^13.0.3",
-    "prettier": "^2.7.1",
-    "ts-jest": "^28.0.7",
-    "ts-node": "^10.9.1"
-  },
-  "config": {
-    "commitizen": {
-      "path": "./node_modules/cz-conventional-changelog"
-    }
-  },
-  "workspaces": [
-    "apps/*",
-    "packages/*"
-  ]
+    "name": "zkmpc",
+    "description": "MPC Phase 2 suite of tools for conducting zkSNARKs trusted setup ceremonies",
+    "repository": "git@github.com:quadratic-funding/mpc-phase2-suite.git",
+    "homepage": "https://github.com/quadratic-funding/mpc-phase2-suite",
+    "bugs": "https://github.com/quadratic-funding/mpc-phase2-suite/issues",
+    "license": "MIT",
+    "private": true,
+    "keywords": [
+        "typescript",
+        "zero-knowledge",
+        "zk-snarks",
+        "phase-2",
+        "trusted-setup",
+        "ceremony",
+        "snarkjs",
+        "circom"
+    ],
+    "scripts": {
+        "build": "lerna run build",
+        "pretest": "yarn build",
+        "test": "export GOOGLE_APPLICATION_CREDENTIALS=\"./packages/backend/serviceAccountKey.json\" && jest --config=jest.json --detectOpenHandles --forceExit  --coverage",
+        "test:watch": "export GOOGLE_APPLICATION_CREDENTIALS=\"./packages/backend/serviceAccountKey.json\" && jest --config=jest.json --watch --detectOpenHandles --forceExit  --coverage",
+        "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
+        "lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
+        "prettier": "prettier -c .",
+        "prettier:fix": "prettier -w .",
+        "precommit": "lint-staged",
+        "commit": "cz"
+    },
+    "devDependencies": {
+        "@babel/core": "^7.20.2",
+        "@babel/preset-env": "^7.20.2",
+        "@babel/preset-typescript": "^7.18.6",
+        "@commitlint/cli": "^17.3.0",
+        "@commitlint/config-conventional": "^17.3.0",
+        "@rollup/plugin-typescript": "^9.0.2",
+        "@types/chai": "^4.3.4",
+        "@types/chai-as-promised": "^7.1.5",
+        "@types/jest": "^29.2.3",
+        "@types/node": "^18.11.9",
+        "@typescript-eslint/eslint-plugin": "^5.44.0",
+        "@typescript-eslint/parser": "^5.44.0",
+        "babel-jest": "^29.3.1",
+        "chai": "^4.3.7",
+        "chai-as-promised": "^7.1.1",
+        "commitizen": "^4.2.5",
+        "cz-conventional-changelog": "^3.3.0",
+        "eslint": "^8.28.0",
+        "eslint-config-airbnb-base": "15.0.0",
+        "eslint-config-airbnb-typescript": "^17.0.0",
+        "eslint-config-prettier": "^8.5.0",
+        "eslint-plugin-import": "^2.26.0",
+        "eslint-plugin-jest": "^27.1.5",
+        "jest": "^29.3.1",
+        "jest-config": "^29.3.1",
+        "lerna": "^6.0.3",
+        "lint-staged": "^13.0.3",
+        "prettier": "^2.8.0",
+        "rimraf": "^3.0.2",
+        "rollup": "^3.4.0"
+    },
+    "config": {
+        "commitizen": {
+            "path": "./node_modules/cz-conventional-changelog"
+        }
+    },
+    "workspaces": [
+        "packages/*"
+    ]
 }
diff --git a/packages/actions/.env.test.default b/packages/actions/.env.test.default
new file mode 100644
index 00000000..e66be979
--- /dev/null
+++ b/packages/actions/.env.test.default
@@ -0,0 +1,8 @@
+# Firebase.
+FIREBASE_FIRESTORE_DATABASE_URL="YOUR-FIREBASE-FIRESTORE-DATABASE-URL-HERE"
+FIREBASE_API_KEY="YOUR-FIREBASE-API-KEY"
+FIREBASE_AUTH_DOMAIN="YOUR-FIREBASE-AUTH-DOMAIN"
+FIREBASE_PROJECT_ID="YOUR-FIREBASE-PROJECT-ID"
+FIREBASE_MESSAGING_SENDER_ID="YOUR-FIREBASE-MESSAGING-SENDER-ID"
+FIREBASE_APP_ID="YOUR-FIREBASE-APP-ID"
+FIREBASE_CF_URL_VERIFY_CONTRIBUTION="YOUR-FIREBASE-CF-URL-VERIFY-CONTRIBUTION"
\ No newline at end of file
diff --git a/packages/actions/.gitignore b/packages/actions/.gitignore
index 1186f217..ed90a519 100644
--- a/packages/actions/.gitignore
+++ b/packages/actions/.gitignore
@@ -6,9 +6,7 @@ yarn-error.log
 
 # environment
 .env
+.env.test
 
 # build
-dist/
-
-# ceremony
-output/
\ No newline at end of file
+dist/
\ No newline at end of file
diff --git a/packages/actions/build.tsconfig.json b/packages/actions/build.tsconfig.json
new file mode 100644
index 00000000..ea775ea0
--- /dev/null
+++ b/packages/actions/build.tsconfig.json
@@ -0,0 +1,9 @@
+{
+    "extends": "../../tsconfig.json",
+    "compilerOptions": {
+        "baseUrl": ".",
+        "outDir": "dist/",
+        "declarationDir": "dist/types"
+    },
+    "include": ["src/**/*", "test/**/*", "types/**/*", "rollup.config.ts"]
+}
diff --git a/packages/actions/package.json b/packages/actions/package.json
index fc5212d4..b358bd2c 100644
--- a/packages/actions/package.json
+++ b/packages/actions/package.json
@@ -1,39 +1,57 @@
 {
-  "name": "@zkmpc/actions",
-  "version": "0.0.0",
-  "description": "A set of actions and helpers for CLI commands",
-  "type": "module",
-  "main": "dist/src/index.js",
-  "repository": "https://github.com/quadratic-funding/mpc-phase2-suite",
-  "homepage": "https://github.com/quadratic-funding/mpc-phase2-suite",
-  "bugs": "https://github.com/quadratic-funding/mpc-phase2-suite/issues",
-  "license": "MIT",
-  "private": false,
-  "types": "dist/types/index.d.ts",
-  "files": [
-    "dist/",
-    "src/",
-    "types/"
-  ],
-  "keywords": [
-    "typescript",
-    "zero-knowledge",
-    "zk-snarks",
-    "phase-2",
-    "trusted-setup",
-    "ceremony",
-    "snarkjs",
-    "circom"
-  ],
-  "scripts": {
-    "build": "tsc"
-  },
-  "dependencies": {
-    "@octokit/auth-oauth-device": "^4.0.3",
-    "chai": "^4.3.7",
-    "chai-as-promised": "^7.1.1",
-    "clipboardy": "^3.0.0",
-    "firebase": "^9.14.0",
-    "firebase-admin": "^11.2.1"
-  }
+    "name": "@zkmpc/actions",
+    "version": "0.0.1",
+    "description": "A set of actions and helpers for CLI commands",
+    "repository": "https://github.com/quadratic-funding/mpc-phase2-suite",
+    "homepage": "https://github.com/quadratic-funding/mpc-phase2-suite",
+    "bugs": "https://github.com/quadratic-funding/mpc-phase2-suite/issues",
+    "license": "MIT",
+    "private": false,
+    "main": "dist/src/index.node.js",
+    "exports": {
+        "import": "./dist/src/index.node.mjs",
+        "require": "./dist/src/index.node.js"
+    },
+    "types": "dist/types/index.d.ts",
+    "engines": {
+        "node": "16"
+    },
+    "files": [
+        "dist/",
+        "src/",
+        "types/"
+    ],
+    "keywords": [
+        "typescript",
+        "zero-knowledge",
+        "zk-snarks",
+        "phase-2",
+        "trusted-setup",
+        "ceremony",
+        "snarkjs",
+        "circom"
+    ],
+    "scripts": {
+        "build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript",
+        "build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript",
+        "pre:publish": "yarn build"
+    },
+    "dependencies": {
+        "@octokit/auth-oauth-device": "^4.0.3",
+        "chai": "^4.3.7",
+        "chai-as-promised": "^7.1.1",
+        "dotenv": "^16.0.3",
+        "firebase": "^9.14.0",
+        "firebase-admin": "^11.3.0"
+    },
+    "devDependencies": {
+        "@types/rollup-plugin-auto-external": "^2.0.2",
+        "rollup-plugin-auto-external": "^2.0.0",
+        "rollup-plugin-cleanup": "^3.2.1",
+        "rollup-plugin-typescript2": "^0.34.1",
+        "typescript": "^4.9.3"
+    },
+    "publishConfig": {
+        "access": "public"
+    }
 }
diff --git a/packages/actions/rollup.config.ts b/packages/actions/rollup.config.ts
new file mode 100644
index 00000000..cc9ac131
--- /dev/null
+++ b/packages/actions/rollup.config.ts
@@ -0,0 +1,30 @@
+import * as fs from "fs"
+import typescript from "rollup-plugin-typescript2"
+import cleanup from "rollup-plugin-cleanup"
+import autoExternal from "rollup-plugin-auto-external"
+
+const pkg = JSON.parse(fs.readFileSync("./package.json", "utf-8"))
+const banner = `/**
+ * @module ${pkg.name}
+ * @version ${pkg.version}
+ * @file ${pkg.description}
+ * @copyright Ethereum Foundation 2022
+ * @license ${pkg.license}
+ * @see [Github]{@link ${pkg.homepage}}
+*/`
+
+export default {
+    input: "src/index.ts",
+    output: [
+        { file: pkg.exports.require, format: "cjs", banner, exports: "auto" },
+        { file: pkg.exports.import, format: "es", banner }
+    ],
+    plugins: [
+        autoExternal(),
+        typescript({
+            tsconfig: "./build.tsconfig.json",
+            useTsconfigDeclarationDir: true
+        }),
+        cleanup({ comments: "jsdoc" })
+    ]
+}
diff --git a/packages/actions/src/core/auth/index.ts b/packages/actions/src/core/auth/index.ts
index 80cb59a4..d65f1690 100644
--- a/packages/actions/src/core/auth/index.ts
+++ b/packages/actions/src/core/auth/index.ts
@@ -1,7 +1,7 @@
 import { FirebaseApp } from "firebase/app"
-import { getAuth, signInWithCredential, User } from "firebase/auth"
+import { getAuth, initializeAuth, signInWithCredential, User } from "firebase/auth"
 import { createOAuthDeviceAuth } from "@octokit/auth-oauth-device"
-import { exchangeGithubTokenForFirebaseCredentials, onVerification } from "../lib/utils.js"
+import { exchangeGithubTokenForFirebaseCredentials, onVerification } from "../lib/utils"
 
 /**
  * Return the Github OAuth 2.0 token using manual Device Flow authentication process.
@@ -9,30 +9,30 @@ import { exchangeGithubTokenForFirebaseCredentials, onVerification } from "../li
  * @returns <string> the Github OAuth 2.0 token.
  */
 export const getNewOAuthTokenUsingGithubDeviceFlow = async (clientId: string): Promise<string> => {
-  /**
-   * Github OAuth 2.0 Device Flow.
-   * # Step 1: Request device and user verification codes and gets auth verification uri.
-   * # Step 2: The app prompts the user to enter a user verification code at https://github.com/login/device.
-   * # Step 3: The app polls/asks for the user authentication status.
-   */
-
-  const clientType = "oauth-app"
-  const tokenType = "oauth"
-
-  // # Step 1.
-  const auth = createOAuthDeviceAuth({
-    clientType,
-    clientId,
-    scopes: ["gist"],
-    onVerification
-  })
-
-  // # Step 3.
-  const { token } = await auth({
-    type: tokenType
-  })
-
-  return token
+    /**
+     * Github OAuth 2.0 Device Flow.
+     * # Step 1: Request device and user verification codes and gets auth verification uri.
+     * # Step 2: The app prompts the user to enter a user verification code at https://github.com/login/device.
+     * # Step 3: The app polls/asks for the user authentication status.
+     */
+
+    const clientType = "oauth-app"
+    const tokenType = "oauth"
+
+    // # Step 1.
+    const auth = createOAuthDeviceAuth({
+        clientType,
+        clientId,
+        scopes: ["gist"],
+        onVerification
+    })
+
+    // # Step 3.
+    const { token } = await auth({
+        type: tokenType
+    })
+
+    return token
 }
 
 /**
@@ -41,11 +41,11 @@ export const getNewOAuthTokenUsingGithubDeviceFlow = async (clientId: string): P
  * @returns
  */
 export const getCurrentFirebaseAuthUser = (firebaseApp: FirebaseApp): User => {
-  const user = getAuth(firebaseApp).currentUser
+    const user = getAuth(firebaseApp).currentUser
 
-  if (!user) throw new Error(`Cannot retrieve the current authenticated user for given Firebase Application`)
+    if (!user) throw new Error(`Cannot retrieve the current authenticated user for given Firebase Application`)
 
-  return user
+    return user
 }
 
 /**
@@ -54,6 +54,6 @@ export const getCurrentFirebaseAuthUser = (firebaseApp: FirebaseApp): User => {
  * @param token <string> - the Github OAuth 2.0 token to be exchanged.
  */
 export const signInToFirebaseWithGithubToken = async (firebaseApp: FirebaseApp, token: string) => {
-  // Sign in with the credential.
-  await signInWithCredential(getAuth(firebaseApp), exchangeGithubTokenForFirebaseCredentials(token))
+    // Sign in with the credential.
+    await signInWithCredential(initializeAuth(firebaseApp), exchangeGithubTokenForFirebaseCredentials(token))
 }
diff --git a/packages/actions/src/core/contribute/index.ts b/packages/actions/src/core/contribute/index.ts
index c3543856..c51d20dc 100644
--- a/packages/actions/src/core/contribute/index.ts
+++ b/packages/actions/src/core/contribute/index.ts
@@ -1,11 +1,6 @@
 import { Firestore, where } from "firebase/firestore"
-import {
-  CeremonyCollectionField,
-  CeremonyState,
-  Collections,
-  FirebaseDocumentInfo
-} from "packages/actions/types/index.js"
-import { queryCollection, fromQueryToFirebaseDocumentInfo, getAllCollectionDocs } from "../../helpers/query.js"
+import { CeremonyCollectionField, CeremonyState, Collections, FirebaseDocumentInfo } from "../../../types/index"
+import { queryCollection, fromQueryToFirebaseDocumentInfo, getAllCollectionDocs } from "../../helpers/query"
 
 /**
  * Query for opened ceremonies documents and return their data (if any).
@@ -13,14 +8,14 @@ import { queryCollection, fromQueryToFirebaseDocumentInfo, getAllCollectionDocs
  * @returns <Promise<Array<FirebaseDocumentInfo>>>
  */
 export const getOpenedCeremonies = async (firestoreDatabase: Firestore): Promise<Array<FirebaseDocumentInfo>> => {
-  const runningStateCeremoniesQuerySnap = await queryCollection(firestoreDatabase, Collections.CEREMONIES, [
-    where(CeremonyCollectionField.STATE, "==", CeremonyState.OPENED),
-    where(CeremonyCollectionField.END_DATE, ">=", Date.now())
-  ])
+    const runningStateCeremoniesQuerySnap = await queryCollection(firestoreDatabase, Collections.CEREMONIES, [
+        where(CeremonyCollectionField.STATE, "==", CeremonyState.OPENED),
+        where(CeremonyCollectionField.END_DATE, ">=", Date.now())
+    ])
 
-  return runningStateCeremoniesQuerySnap.empty && runningStateCeremoniesQuerySnap.size === 0
-    ? []
-    : fromQueryToFirebaseDocumentInfo(runningStateCeremoniesQuerySnap.docs)
+    return runningStateCeremoniesQuerySnap.empty && runningStateCeremoniesQuerySnap.size === 0
+        ? []
+        : fromQueryToFirebaseDocumentInfo(runningStateCeremoniesQuerySnap.docs)
 }
 
 /**
@@ -30,9 +25,9 @@ export const getOpenedCeremonies = async (firestoreDatabase: Firestore): Promise
  * @returns Promise<Array<FirebaseDocumentInfo>>
  */
 export const getCeremonyCircuits = async (
-  firestoreDatabase: Firestore,
-  ceremonyId: string
+    firestoreDatabase: Firestore,
+    ceremonyId: string
 ): Promise<Array<FirebaseDocumentInfo>> =>
-  fromQueryToFirebaseDocumentInfo(
-    await getAllCollectionDocs(firestoreDatabase, `${Collections.CEREMONIES}/${ceremonyId}/${Collections.CIRCUITS}`)
-  ).sort((a: FirebaseDocumentInfo, b: FirebaseDocumentInfo) => a.data.sequencePosition - b.data.sequencePosition)
+    fromQueryToFirebaseDocumentInfo(
+        await getAllCollectionDocs(firestoreDatabase, `${Collections.CEREMONIES}/${ceremonyId}/${Collections.CIRCUITS}`)
+    ).sort((a: FirebaseDocumentInfo, b: FirebaseDocumentInfo) => a.data.sequencePosition - b.data.sequencePosition)
diff --git a/packages/actions/src/core/lib/utils.ts b/packages/actions/src/core/lib/utils.ts
index 84a104a0..0506e190 100644
--- a/packages/actions/src/core/lib/utils.ts
+++ b/packages/actions/src/core/lib/utils.ts
@@ -1,5 +1,5 @@
 import open from "open"
-import clipboard from "clipboardy"
+// import clipboard from "clipboardy" // TODO: need a substitute.
 import { Verification } from "@octokit/auth-oauth-device/dist-types/types"
 import { OAuthCredential, GithubAuthProvider } from "firebase/auth"
 
@@ -10,25 +10,25 @@ import { OAuthCredential, GithubAuthProvider } from "firebase/auth"
  * @param intervalInSeconds <number> - the amount of time that must elapse between updates (default 1s === 1ms).
  */
 const createExpirationCountdown = (durationInSeconds: number, intervalInSeconds = 1000) => {
-  let seconds = durationInSeconds <= 60 ? durationInSeconds : 60
-
-  setInterval(() => {
-    try {
-      if (durationInSeconds !== 0) {
-        // Update times.
-        durationInSeconds -= intervalInSeconds
-        seconds -= intervalInSeconds
-
-        if (seconds % 60 === 0) seconds = 0
-
-        process.stdout.write(`Expires in 00:${Math.floor(durationInSeconds / 60)}:${seconds}\r`)
-      } else console.log(`Expired`)
-    } catch (err: any) {
-      // Workaround to the \r.
-      process.stdout.write(`\n\n`)
-      console.log(`Expired`)
-    }
-  }, intervalInSeconds * 1000)
+    let seconds = durationInSeconds <= 60 ? durationInSeconds : 60
+
+    setInterval(() => {
+        try {
+            if (durationInSeconds !== 0) {
+                // Update times.
+                durationInSeconds -= intervalInSeconds
+                seconds -= intervalInSeconds
+
+                if (seconds % 60 === 0) seconds = 0
+
+                process.stdout.write(`Expires in 00:${Math.floor(durationInSeconds / 60)}:${seconds}\r`)
+            } else console.log(`Expired`)
+        } catch (err: any) {
+            // Workaround to the \r.
+            process.stdout.write(`\n\n`)
+            console.log(`Expired`)
+        }
+    }, intervalInSeconds * 1000)
 }
 
 /**
@@ -36,21 +36,22 @@ const createExpirationCountdown = (durationInSeconds: number, intervalInSeconds
  * @param verification <Verification> - the data from Github OAuth2.0 device flow.
  */
 export const onVerification = async (verification: Verification): Promise<void> => {
-  // Automatically open the page (# Step 2).
-  await open(verification.verification_uri)
-
-  // Copy code to clipboard.
-  clipboard.writeSync(verification.user_code)
-  clipboard.readSync()
-
-  // Display data.
-  // TODO. custom theme is missing.
-  console.log(
-    `Visit ${verification.verification_uri} on this device to authenticate\nYour auth code: ${verification.user_code}`
-  )
-
-  // Countdown for time expiration.
-  createExpirationCountdown(verification.expires_in, 1)
+    // Automatically open the page (# Step 2).
+    await open(verification.verification_uri)
+
+    // TODO: need a substitute for `clipboardy` package.
+    // Copy code to clipboard.
+    // clipboard.writeSync(verification.user_code)
+    // clipboard.readSync()
+
+    // Display data.
+    // TODO. custom theme is missing.
+    console.log(
+        `Visit ${verification.verification_uri} on this device to authenticate\nYour auth code: ${verification.user_code}`
+    )
+
+    // Countdown for time expiration.
+    createExpirationCountdown(verification.expires_in, 1)
 }
 
 /**
@@ -59,4 +60,4 @@ export const onVerification = async (verification: Verification): Promise<void>
  * @returns <OAuthCredential> - the Firebase OAuth credential object.
  */
 export const exchangeGithubTokenForFirebaseCredentials = (token: string): OAuthCredential =>
-  GithubAuthProvider.credential(token)
+    GithubAuthProvider.credential(token)
diff --git a/packages/actions/src/helpers/query.ts b/packages/actions/src/helpers/query.ts
index 8f6bf7c0..93b50151 100644
--- a/packages/actions/src/helpers/query.ts
+++ b/packages/actions/src/helpers/query.ts
@@ -1,14 +1,14 @@
 import {
-  collection as collectionRef,
-  DocumentData,
-  Firestore,
-  getDocs,
-  query,
-  QueryConstraint,
-  QueryDocumentSnapshot,
-  QuerySnapshot
+    collection as collectionRef,
+    DocumentData,
+    Firestore,
+    getDocs,
+    query,
+    QueryConstraint,
+    QueryDocumentSnapshot,
+    QuerySnapshot
 } from "firebase/firestore"
-import { FirebaseDocumentInfo } from "packages/actions/types/index.js"
+import { FirebaseDocumentInfo } from "../../types/index"
 
 /**
  * Helper for query a collection based on certain constraints.
@@ -18,15 +18,15 @@ import { FirebaseDocumentInfo } from "packages/actions/types/index.js"
  * @returns <Promise<QuerySnapshot<DocumentData>>> - return the matching documents (if any).
  */
 export const queryCollection = async (
-  firestoreDatabase: Firestore,
-  collection: string,
-  queryConstraints: Array<QueryConstraint>
+    firestoreDatabase: Firestore,
+    collection: string,
+    queryConstraints: Array<QueryConstraint>
 ): Promise<QuerySnapshot<DocumentData>> => {
-  // Make a query.
-  const q = query(collectionRef(firestoreDatabase, collection), ...queryConstraints)
+    // Make a query.
+    const q = query(collectionRef(firestoreDatabase, collection), ...queryConstraints)
 
-  // Get docs.
-  return getDocs(q)
+    // Get docs.
+    return getDocs(q)
 }
 
 /**
@@ -35,13 +35,13 @@ export const queryCollection = async (
  * @returns Array<FirebaseDocumentInfo>
  */
 export const fromQueryToFirebaseDocumentInfo = (
-  queryDocSnap: Array<QueryDocumentSnapshot>
+    queryDocSnap: Array<QueryDocumentSnapshot>
 ): Array<FirebaseDocumentInfo> =>
-  queryDocSnap.map((doc: QueryDocumentSnapshot<DocumentData>) => ({
-    id: doc.id,
-    ref: doc.ref,
-    data: doc.data()
-  }))
+    queryDocSnap.map((doc: QueryDocumentSnapshot<DocumentData>) => ({
+        id: doc.id,
+        ref: doc.ref,
+        data: doc.data()
+    }))
 
 /**
  * Fetch for all documents in a collection.
@@ -50,7 +50,7 @@ export const fromQueryToFirebaseDocumentInfo = (
  * @returns <Promise<Array<QueryDocumentSnapshot<DocumentData>>>> - return all documents (if any).
  */
 export const getAllCollectionDocs = async (
-  firestoreDatabase: Firestore,
-  collection: string
+    firestoreDatabase: Firestore,
+    collection: string
 ): Promise<Array<QueryDocumentSnapshot<DocumentData>>> =>
-  (await getDocs(collectionRef(firestoreDatabase, collection))).docs
+    (await getDocs(collectionRef(firestoreDatabase, collection))).docs
diff --git a/packages/actions/src/index.ts b/packages/actions/src/index.ts
index 4722a47d..0efa5916 100644
--- a/packages/actions/src/index.ts
+++ b/packages/actions/src/index.ts
@@ -1,14 +1,14 @@
 import {
-  getCurrentFirebaseAuthUser,
-  getNewOAuthTokenUsingGithubDeviceFlow,
-  signInToFirebaseWithGithubToken
-} from "./core/auth/index.js"
-import { getOpenedCeremonies, getCeremonyCircuits } from "./core/contribute/index.js"
+    getCurrentFirebaseAuthUser,
+    getNewOAuthTokenUsingGithubDeviceFlow,
+    signInToFirebaseWithGithubToken
+} from "./core/auth/index"
+import { getOpenedCeremonies, getCeremonyCircuits } from "./core/contribute/index"
 
 export {
-  getCurrentFirebaseAuthUser,
-  getNewOAuthTokenUsingGithubDeviceFlow,
-  signInToFirebaseWithGithubToken,
-  getOpenedCeremonies,
-  getCeremonyCircuits
+    getCurrentFirebaseAuthUser,
+    getNewOAuthTokenUsingGithubDeviceFlow,
+    signInToFirebaseWithGithubToken,
+    getOpenedCeremonies,
+    getCeremonyCircuits
 }
diff --git a/packages/actions/test/e2e.test.ts b/packages/actions/test/e2e.test.ts
deleted file mode 100644
index f5ba58f0..00000000
--- a/packages/actions/test/e2e.test.ts
+++ /dev/null
@@ -1,154 +0,0 @@
-import admin from "firebase-admin"
-import { initializeApp } from "firebase/app"
-import { getFirestore } from "firebase/firestore"
-import { getFunctions, httpsCallable } from "firebase/functions"
-import { getAuth, signInAnonymously } from "firebase/auth"
-import chai from "chai"
-import chaiAsPromised from "chai-as-promised"
-import { getCurrentFirebaseAuthUser, getCeremonyCircuits, getOpenedCeremonies } from "../src/index.js"
-import { FirebaseDocumentInfo } from "../types/index.js"
-
-// Config chai.
-chai.use(chaiAsPromised)
-
-describe("e2e Test Sample", () => {
-  // Sample data for running the test.
-  let openedCeremonies: Array<FirebaseDocumentInfo> = []
-  let selectedCeremonyCircuits: Array<FirebaseDocumentInfo> = []
-  let selectedCeremony: FirebaseDocumentInfo
-
-  // Sample user.
-  const sampleUser = {
-    uid: "", // Defined after the anonymous sign in.
-    creationTime: Date.now(),
-    email: "alice@example.com",
-    emailVerified: false,
-    lastSignInTime: Date.now() + 1,
-    lastUpdated: Date.now() + 1,
-    name: "Alice",
-    photoURL: ""
-  }
-
-  // Sample ceremony.
-  const sampleCeremony = {
-    uid: "rcWHse2WuwrmGYEtCDhS",
-    coordinatorId: "0cqqE9RamNOvOZbHYhftzeno0955", // different from sample user.
-    description: "Dummy ceremony",
-    endDate: 1671290221000,
-    startDate: 1667315821000,
-    lastUpdated: Date.now() + 1,
-    penalty: 720,
-    prefix: "dummy-ceremony",
-    state: 2, // Opened.
-    timeoutType: 2,
-    type: 2
-  }
-
-  // Sample circuit.
-  const sampleCircuit = {
-    uid: "KInbENc5N6WzuwG89Vil",
-    avgTimings: {
-      contributionComputation: 0,
-      fullContribution: 0,
-      verifyCloudFunction: 0
-    },
-    description: "sample circuit",
-    files: {
-      initialZkeyBlake2bHash: "",
-      initialZkeyFilename: "",
-      initialZkeyStoragePath: "",
-      potBlake2bHash: "",
-      potFilename: "",
-      potStoragePath: "",
-      r1csBlake2bHash: "",
-      r1csFilename: "",
-      r1csStoragePath: ""
-    },
-    lastUpdated: Date.now() + 1,
-    metadata: {
-      constraints: 3,
-      curve: "bn-128",
-      labels: 8,
-      outputs: 3,
-      pot: 3,
-      privateInputs: 4,
-      publicOutputs: 0,
-      wires: 8
-    },
-    name: "sample circuit",
-    prefix: "sample_circuit",
-    sequencePosition: 1,
-    timeoutMaxContributionWaitingTime: 10,
-    waitingQueue: {
-      completedContributions: 0,
-      contributors: [],
-      currentContributor: "",
-      failedContributions: 0
-    },
-    zKeySizeInBytes: 4380
-  }
-
-  // Step 0. Initialization of Firebase Admin SDK and sample user.
-  admin.initializeApp({})
-  const sampleUserApp = initializeApp({})
-
-  // Get Firebase services for admin.
-  const adminFirestore = admin.firestore()
-
-  // Sample user.
-  const sampleUserFirestore = getFirestore(sampleUserApp)
-  const sampleUserFunctions = getFunctions(sampleUserApp)
-
-  beforeEach(async () => {
-    // Sign in anonymously.
-    const auth = getAuth(sampleUserApp)
-    const aliceCredentials = await signInAnonymously(auth)
-
-    // Set uid.
-    sampleUser.uid = aliceCredentials.user.uid
-
-    // Step 2.A Create a one dummy ceremony.
-    await adminFirestore
-      .collection(`ceremonies`)
-      .doc(sampleCeremony.uid)
-      .set({
-        ...sampleCeremony
-      })
-    await adminFirestore
-      .collection(`ceremonies/${sampleCeremony.uid}/circuits`)
-      .doc(sampleCircuit.uid)
-      .set({
-        ...sampleCircuit
-      })
-
-    // Step 2.A Get opened ceremonies (I need to create a one dummy ceremony with at least one circuit) using the query (action helper).
-    openedCeremonies = await getOpenedCeremonies(sampleUserFirestore)
-
-    // Select the first ceremony.
-    selectedCeremony = openedCeremonies.at(0)!
-
-    // Step 2.B Get ceremony circuits using the query (action helper).
-    selectedCeremonyCircuits = await getCeremonyCircuits(sampleUserFirestore, selectedCeremony.id)
-  })
-
-  it(`shouldn't be possible for a non authenticated user to call checkParticipantForCeremony() CF to check for eligibility`, async () => {
-    // should fetch custom claims now.
-    const user = getCurrentFirebaseAuthUser(sampleUserApp)
-    console.log(user)
-
-    // Step 2.C Call checkParticipantForCeremony Cloud Function and get the result (should return `true`).
-    const checkParticipantForCeremony = httpsCallable(sampleUserFunctions, "checkParticipantForCeremony")
-
-    const data = await checkParticipantForCeremony({ ceremonyId: selectedCeremony.id })
-
-    console.log(data)
-    console.log(selectedCeremonyCircuits)
-  })
-
-  afterAll(() => {
-    // Step 3. Clean ceremony and user from DB.
-    // adminFirestore.collection(`users`).doc(alice.uid).delete()
-    adminFirestore.collection(`ceremonies`).doc(sampleCeremony.uid).delete()
-    adminFirestore.collection(`ceremonies/${sampleCeremony.uid}/circuits`).doc(sampleCircuit.uid).delete()
-  })
-})
diff --git a/packages/actions/test/index.test.ts b/packages/actions/test/index.test.ts
new file mode 100644
index 00000000..572c98f5
--- /dev/null
+++ b/packages/actions/test/index.test.ts
@@ -0,0 +1,153 @@
+import admin from "firebase-admin"
+import { initializeApp } from "firebase/app"
+import { getFirestore } from "firebase/firestore"
+import { getFunctions, httpsCallable } from "firebase/functions"
+import { getAuth, signInAnonymously } from "firebase/auth"
+import chai, { assert } from "chai"
+import chaiAsPromised from "chai-as-promised"
+import { FirebaseDocumentInfo } from "types"
+import dotenv from "dotenv"
+import { getOpenedCeremonies } from "../src/index"
+
+dotenv.config({ path: `${__dirname}/../.env.test` })
+
+// Config chai.
+chai.use(chaiAsPromised)
+
+describe("Sample e2e", () => {
+    // Sample data for running the test.
+    let openedCeremonies: Array<FirebaseDocumentInfo> = []
+    let selectedCeremony: FirebaseDocumentInfo
+
+    // Sample user.
+    const sampleUser = {
+        uid: "", // Defined after the anonymous sign in.
+        creationTime: Date.now(),
+        email: "sampleuser@example.com",
+        emailVerified: false,
+        lastSignInTime: Date.now() + 1,
+        lastUpdated: Date.now() + 1,
+        name: "Sample",
+        photoURL: ""
+    }
+
+    // Sample ceremony.
+    const sampleCeremony = {
+        uid: "rcWHse2WuwrmGYEtCDhS",
+        coordinatorId: "0cqqE9RamNOvOZbHYhftzeno0955", // different from sample user.
+        description: "Sample ceremony",
+        startDate: Date.now() + 86400000,
+        endDate: Date.now() + 86400000 * 2,
+        lastUpdated: Date.now(),
+        penalty: 720,
+        prefix: "sample-ceremony",
+        state: 2, // Opened.
+        timeoutType: 2,
+        type: 2
+    }
+
+    // Sample circuit.
+    const sampleCircuit = {
+        uid: "KInbENc5N6WzuwG89Vil",
+        avgTimings: {
+            contributionComputation: 0,
+            fullContribution: 0,
+            verifyCloudFunction: 0
+        },
+        description: "sample circuit",
+        files: {
+            initialZkeyBlake2bHash: "",
+            initialZkeyFilename: "",
+            initialZkeyStoragePath: "",
+            potBlake2bHash: "",
+            potFilename: "",
+            potStoragePath: "",
+            r1csBlake2bHash: "",
+            r1csFilename: "",
+            r1csStoragePath: ""
+        },
+        lastUpdated: Date.now() + 1,
+        metadata: {
+            constraints: 3,
+            curve: "bn-128",
+            labels: 8,
+            outputs: 3,
+            pot: 3,
+            privateInputs: 4,
+            publicOutputs: 0,
+            wires: 8
+        },
+        name: "sample circuit",
+        prefix: "sample_circuit",
+        sequencePosition: 1,
+        timeoutMaxContributionWaitingTime: 10,
+        waitingQueue: {
+            completedContributions: 0,
+            contributors: [],
+            currentContributor: "",
+            failedContributions: 0
+        },
+        zKeySizeInBytes: 4380
+    }
+
+    // Init Firebase App for Admin and Sample user.
+    admin.initializeApp({ projectId: process.env.FIREBASE_PROJECT_ID })
+    const adminFirestore = admin.firestore()
+
+    const sampleUserApp = initializeApp({
+        apiKey: process.env.FIREBASE_API_KEY,
+        authDomain: process.env.FIREBASE_AUTH_DOMAIN,
+        projectId: process.env.FIREBASE_PROJECT_ID,
+        messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
+        appId: process.env.FIREBASE_APP_ID
+    })
+
+    const sampleUserFirestore = getFirestore(sampleUserApp)
+    const sampleUserFunctions = getFunctions(sampleUserApp)
+
+    beforeEach(async () => {
+        // Sign in anonymously.
+        const auth = getAuth(sampleUserApp)
+        const sampleUserCredentials = await signInAnonymously(auth)
+
+        // Set uid.
+        sampleUser.uid = sampleUserCredentials.user.uid
+
+        // Create the sample ceremony.
+        await adminFirestore
+            .collection(`ceremonies`)
+            .doc(sampleCeremony.uid)
+            .set({
+                ...sampleCeremony
+            })
+        await adminFirestore
+            .collection(`ceremonies/${sampleCeremony.uid}/circuits`)
+            .doc(sampleCircuit.uid)
+            .set({
+                ...sampleCircuit
+            })
+
+        // Get opened ceremonies.
+        openedCeremonies = await getOpenedCeremonies(sampleUserFirestore)
+
+        // Select the first ceremony.
+        selectedCeremony = openedCeremonies.at(0)!
+    })
+
+    it("should reject when user is not authenticated", async () => {
+        // Call checkParticipantForCeremony Cloud Function and check the result.
+        const checkParticipantForCeremony = httpsCallable(sampleUserFunctions, "checkParticipantForCeremony", {})
+
+        assert.isRejected(checkParticipantForCeremony({ ceremonyId: selectedCeremony.id }))
+    })
+
+    afterAll(async () => {
+        // Clean ceremony and user from DB.
+        adminFirestore.collection(`users`).doc(sampleUser.uid).delete()
+        await adminFirestore.collection(`ceremonies`).doc(sampleCeremony.uid).delete()
+        await adminFirestore.collection(`ceremonies/${sampleCeremony.uid}/circuits`).doc(sampleCircuit.uid).delete()
+
+        // Delete admin app.
+        await Promise.all(admin.apps.map((app) => app?.delete()))
+    })
+})
diff --git a/packages/actions/tsconfig.json b/packages/actions/tsconfig.json
index d6ddd9ec..ea775ea0 100644
--- a/packages/actions/tsconfig.json
+++ b/packages/actions/tsconfig.json
@@ -1,9 +1,9 @@
 {
-  "extends": "../../tsconfig.json",
-  "compilerOptions": {
-    "outDir": "dist/",
-    "declarationDir": "dist/types"
-  },
-  "include": ["src/**/*", "test/**/*", "types/**/*", "*.json", "types"],
-  "exclude": ["node_modules", "tsconfig.json"]
+    "extends": "../../tsconfig.json",
+    "compilerOptions": {
+        "baseUrl": ".",
+        "outDir": "dist/",
+        "declarationDir": "dist/types"
+    },
+    "include": ["src/**/*", "test/**/*", "types/**/*", "rollup.config.ts"]
 }
diff --git a/packages/actions/types/index.ts b/packages/actions/types/index.ts
index cd63c6d3..0f720271 100644
--- a/packages/actions/types/index.ts
+++ b/packages/actions/types/index.ts
@@ -2,37 +2,37 @@ import { DocumentReference, DocumentData } from "firebase/firestore"
 
 /** Enumeratives */
 export const enum CeremonyState {
-  SCHEDULED = 1,
-  OPENED = 2,
-  PAUSED = 3,
-  CLOSED = 4,
-  FINALIZED = 5
+    SCHEDULED = 1,
+    OPENED = 2,
+    PAUSED = 3,
+    CLOSED = 4,
+    FINALIZED = 5
 }
 
 export const enum Collections {
-  USERS = "users",
-  PARTICIPANTS = "participants",
-  CEREMONIES = "ceremonies",
-  CIRCUITS = "circuits",
-  CONTRIBUTIONS = "contributions",
-  TIMEOUTS = "timeouts"
+    USERS = "users",
+    PARTICIPANTS = "participants",
+    CEREMONIES = "ceremonies",
+    CIRCUITS = "circuits",
+    CONTRIBUTIONS = "contributions",
+    TIMEOUTS = "timeouts"
 }
 
 export const enum CeremonyCollectionField {
-  COORDINATOR_ID = "coordinatorId",
-  DESCRIPTION = "description",
-  START_DATE = "startDate",
-  END_DATE = "endDate",
-  LAST_UPDATED = "lastUpdated",
-  PREFIX = "prefix",
-  STATE = "state",
-  TITLE = "title",
-  TYPE = "type"
+    COORDINATOR_ID = "coordinatorId",
+    DESCRIPTION = "description",
+    START_DATE = "startDate",
+    END_DATE = "endDate",
+    LAST_UPDATED = "lastUpdated",
+    PREFIX = "prefix",
+    STATE = "state",
+    TITLE = "title",
+    TYPE = "type"
 }
 
 /** Types */
 export type FirebaseDocumentInfo = {
-  id: string
-  ref: DocumentReference<DocumentData>
-  data: DocumentData
+    id: string
+    ref: DocumentReference<DocumentData>
+    data: DocumentData
 }
diff --git a/apps/backend/.default.env b/packages/backend/.default.env
similarity index 100%
rename from apps/backend/.default.env
rename to packages/backend/.default.env
diff --git a/apps/backend/.firebaserc b/packages/backend/.firebaserc
similarity index 100%
rename from apps/backend/.firebaserc
rename to packages/backend/.firebaserc
diff --git a/apps/backend/.gitignore b/packages/backend/.gitignore
similarity index 100%
rename from apps/backend/.gitignore
rename to packages/backend/.gitignore
diff --git a/packages/backend/README.md b/packages/backend/README.md
new file mode 100644
index 00000000..7fff708a
--- /dev/null
+++ b/packages/backend/README.md
@@ -0,0 +1,18 @@
+# mpc-phase2-suite-firebase
+
+MPC Phase 2 backend for Firebase services management
+
+-   Description (w/ Features).
+-   Install + Configs
+    -   Firebase login
+    -   Firebase init (select everything except Hosting and Remote Config)
+    -   Console
+        -   App
+        -   Service Account Key generation
+-   Usage + Examples
+-   License
+-   Support + FAQ?
+    -   FIREBASE_CONFIG & GCLOUD_PROJECT not set warn - because we are running on NodeJS and not on cloud.
+    -   Emulator scheduled functions execution: emulator terminal + emulator shell terminal (here, call scheduled function, e.g., scheduledFunction()).
+
+Coordinator Guide .md for more infos about the Firebase configuration?
diff --git a/packages/backend/build.tsconfig.json b/packages/backend/build.tsconfig.json
new file mode 100644
index 00000000..ea775ea0
--- /dev/null
+++ b/packages/backend/build.tsconfig.json
@@ -0,0 +1,9 @@
+{
+    "extends": "../../tsconfig.json",
+    "compilerOptions": {
+        "baseUrl": ".",
+        "outDir": "dist/",
+        "declarationDir": "dist/types"
+    },
+    "include": ["src/**/*", "test/**/*", "types/**/*", "rollup.config.ts"]
+}
diff --git a/packages/backend/firebase.json b/packages/backend/firebase.json
new file mode 100644
index 00000000..ddfa3fb5
--- /dev/null
+++ b/packages/backend/firebase.json
@@ -0,0 +1,36 @@
+{
+    "firestore": {
+        "rules": "firestore.rules",
+        "indexes": "firestore.indexes.json"
+    },
+    "functions": {
+        "predeploy": "yarn --prefix \"$RESOURCE_DIR\" build",
+        "source": "."
+    },
+    "storage": {
+        "rules": "storage.rules"
+    },
+    "emulators": {
+        "auth": {
+            "port": 9099
+        },
+        "functions": {
+            "port": 5001
+        },
+        "firestore": {
+            "port": 8080
+        },
+        "database": {
+            "port": 9000
+        },
+        "pubsub": {
+            "port": 8085
+        },
+        "storage": {
+            "port": 9199
+        },
+        "ui": {
+            "enabled": true
+        }
+    }
+}
diff --git a/packages/backend/firestore.indexes.json b/packages/backend/firestore.indexes.json
new file mode 100644
index 00000000..a96ab11d
--- /dev/null
+++ b/packages/backend/firestore.indexes.json
@@ -0,0 +1,33 @@
+{
+    "indexes": [
+        {
+            "collectionGroup": "ceremonies",
+            "queryScope": "COLLECTION",
+            "fields": [
+                {
+                    "fieldPath": "state",
+                    "order": "ASCENDING"
+                },
+                {
+                    "fieldPath": "endDate",
+                    "order": "ASCENDING"
+                }
+            ]
+        },
+        {
+            "collectionGroup": "ceremonies",
+            "queryScope": "COLLECTION",
+            "fields": [
+                {
+                    "fieldPath": "state",
+                    "order": "ASCENDING"
+                },
+                {
+                    "fieldPath": "startDate",
+                    "order": "ASCENDING"
+                }
+            ]
+        }
+    ],
+    "fieldOverrides": []
+}
diff --git a/apps/backend/firestore.rules b/packages/backend/firestore.rules
similarity index 100%
rename from apps/backend/firestore.rules
rename to packages/backend/firestore.rules
diff --git a/packages/backend/package.json b/packages/backend/package.json
new file mode 100644
index 00000000..168ef4d0
--- /dev/null
+++ b/packages/backend/package.json
@@ -0,0 +1,84 @@
+{
+    "name": "@zkmpc/backend",
+    "version": "0.0.1",
+    "description": "MPC Phase 2 backend for Firebase services management",
+    "repository": "https://github.com/quadratic-funding/mpc-phase2-suite/apps/backend",
+    "homepage": "https://github.com/quadratic-funding/mpc-phase2-suite",
+    "bugs": "https://github.com/quadratic-funding/mpc-phase2-suite/issues",
+    "author": {
+        "name": "Giacomo (0xjei)"
+    },
+    "license": "MIT",
+    "private": false,
+    "main": "dist/src/functions/index.node.js",
+    "exports": {
+        "import": "./dist/src/functions/index.node.mjs",
+        "require": "./dist/src/functions/index.node.js"
+    },
+    "type": "module",
+    "types": "dist/types/index.d.ts",
+    "engines": {
+        "node": "16"
+    },
+    "files": [
+        "dist/",
+        "src/",
+        "test/",
+        "types",
+        "README.md"
+    ],
+    "keywords": [
+        "typescript",
+        "zero-knowledge",
+        "zk-snarks",
+        "phase-2",
+        "trusted-setup",
+        "ceremony",
+        "snarkjs",
+        "circom"
+    ],
+    "scripts": {
+        "build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript",
+        "build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript",
+        "firebase:login": "firebase login",
+        "firebase:logout": "firebase logout",
+        "firebase:init": "firebase init",
+        "firebase:deploy": "yarn firestore:get-indexes && firebase deploy",
+        "firebase:deploy-functions": "firebase deploy --only functions",
+        "firebase:deploy-firestore": "yarn firestore:get-indexes && firebase deploy --only firestore",
+        "firebase:deploy-storage": "firebase deploy --only storage",
+        "firebase:log-functions": "firebase functions:log",
+        "firestore:get-indexes": "firebase firestore:indexes > firestore.indexes.json",
+        "emulator:serve": "yarn build && firebase emulators:start",
+        "emulator:serve-functions": "yarn build && firebase emulators:start --only functions",
+        "emulator:shell": "yarn build && firebase functions:shell",
+        "prepublish": "yarn build"
+    },
+    "devDependencies": {
+        "@firebase/rules-unit-testing": "^2.0.5",
+        "@types/rollup-plugin-auto-external": "^2.0.2",
+        "@types/uuid": "^8.3.4",
+        "firebase-functions-test": "^3.0.0",
+        "firebase-tools": "^11.16.1",
+        "rollup-plugin-auto-external": "^2.0.0",
+        "rollup-plugin-cleanup": "^3.2.1",
+        "rollup-plugin-typescript2": "^0.34.1",
+        "typescript": "^4.9.3"
+    },
+    "dependencies": {
+        "@aws-sdk/client-s3": "^3.178.0",
+        "@aws-sdk/middleware-endpoint": "^3.178.0",
+        "@aws-sdk/s3-request-presigner": "^3.178.0",
+        "blakejs": "^1.2.1",
+        "dotenv": "^16.0.3",
+        "firebase-admin": "^11.3.0",
+        "firebase-functions": "^4.1.0",
+        "snarkjs": "^0.5.0",
+        "timer-node": "^5.0.6",
+        "uuid": "^9.0.0",
+        "winston": "^3.8.2"
+    },
+    "publishConfig": {
+        "access": "public"
+    }
+}
diff --git a/packages/backend/rollup.config.ts b/packages/backend/rollup.config.ts
new file mode 100644
index 00000000..85bd94b5
--- /dev/null
+++ b/packages/backend/rollup.config.ts
@@ -0,0 +1,30 @@
+import * as fs from "fs"
+import typescript from "rollup-plugin-typescript2"
+import cleanup from "rollup-plugin-cleanup"
+import autoExternal from "rollup-plugin-auto-external"
+
+const pkg = JSON.parse(fs.readFileSync("./package.json", "utf-8"))
+const banner = `/**
+ * @module ${pkg.name}
+ * @version ${pkg.version}
+ * @file ${pkg.description}
+ * @copyright Ethereum Foundation 2022
+ * @license ${pkg.license}
+ * @see [Github]{@link ${pkg.homepage}}
+*/`
+
+export default {
+    input: "src/functions/index.ts",
+    output: [
+        { file: pkg.exports.require, format: "cjs", banner, exports: "auto" },
+        { file: pkg.exports.import, format: "es", banner }
+    ],
+    plugins: [
+        autoExternal(),
+        typescript({
+            tsconfig: "./build.tsconfig.json",
+            useTsconfigDeclarationDir: true
+        }),
+        cleanup({ comments: "jsdoc" })
+    ]
+}
diff --git a/packages/backend/src/functions/auth.ts b/packages/backend/src/functions/auth.ts
new file mode 100644
index 00000000..e2c9bbb9
--- /dev/null
+++ b/packages/backend/src/functions/auth.ts
@@ -0,0 +1,84 @@
+import * as functions from "firebase-functions"
+import { UserRecord } from "firebase-functions/v1/auth"
+import admin from "firebase-admin"
+import dotenv from "dotenv"
+import { GENERIC_ERRORS, logMsg } from "../lib/logs"
+import { getCurrentServerTimestampInMillis } from "../lib/utils"
+import { MsgType } from "../../types/index"
+
+dotenv.config()
+
+/**
+ * Auth-triggered function which writes a user document to Firestore.
+ */
+export const registerAuthUser = functions.auth.user().onCreate(async (user: UserRecord) => {
+    // Get DB.
+    const firestore = admin.firestore()
+
+    // Get user information.
+    if (!user.uid) logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
+
+    // The user object has basic properties such as display name, email, etc.
+    const { displayName } = user
+    const { email } = user
+    const { photoURL } = user
+    const { emailVerified } = user
+
+    // Metadata.
+    const { creationTime } = user.metadata
+    const { lastSignInTime } = user.metadata
+
+    // The user's ID, unique to the Firebase project. Do NOT use
+    // this value to authenticate with your backend server, if
+    // you have one. Use User.getToken() instead.
+    const { uid } = user
+
+    // Reference to a document using uid.
+    const userRef = firestore.collection("users").doc(uid)
+
+    // Set document (nb. we refer to providerData[0] because we use Github OAuth provider only).
+    await userRef.set({
+        name: displayName,
+        // Metadata.
+        creationTime,
+        lastSignInTime,
+        // Optional.
+        email: email || "",
+        emailVerified: emailVerified || false,
+        photoURL: photoURL || "",
+        lastUpdated: getCurrentServerTimestampInMillis()
+    })
+
+    logMsg(`User ${uid} correctly stored`, MsgType.INFO)
+})
+
+/**
+ * Set custom claims for role-based access control on the newly created user.
+ */
+export const processSignUpWithCustomClaims = functions.auth.user().onCreate(async (user: UserRecord) => {
+    // Get user information.
+    if (!user.uid) logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
+
+    let customClaims: any
+    // Check if user meets role criteria to be a coordinator.
+    if (
+        user.email &&
+        (user.email.endsWith(`@${process.env.CUSTOM_CLAIMS_COORDINATOR_EMAIL_ADDRESS_OR_DOMAIN}`) ||
+            user.email === process.env.CUSTOM_CLAIMS_COORDINATOR_EMAIL_ADDRESS_OR_DOMAIN)
+    ) {
+        customClaims = { coordinator: true }
+
+        logMsg(`User ${user.uid} identified as coordinator`, MsgType.INFO)
+    } else {
+        customClaims = { participant: true }
+
+        logMsg(`User ${user.uid} identified as participant`, MsgType.INFO)
+    }
+
+    try {
+        // Set custom user claims on this newly created user.
+        await admin.auth().setCustomUserClaims(user.uid, customClaims)
+    } catch (error: any) {
+        logMsg(`Something went wrong: ${error.toString()}`, MsgType.ERROR)
+    }
+})
diff --git a/packages/backend/src/functions/ceremony.ts b/packages/backend/src/functions/ceremony.ts
new file mode 100644
index 00000000..7836be39
--- /dev/null
+++ b/packages/backend/src/functions/ceremony.ts
@@ -0,0 +1,44 @@
+import * as functions from "firebase-functions"
+import dotenv from "dotenv"
+import { DocumentSnapshot } from "firebase-functions/v1/firestore"
+import { CeremonyState, MsgType } from "../../types/index"
+import { queryCeremoniesByStateAndDate } from "../lib/utils"
+import { GENERIC_LOGS, logMsg } from "../lib/logs"
+
+dotenv.config()
+
+/**
+ * Automatically look and (if any) start scheduled ceremonies.
+ */
+export const startCeremony = functions.pubsub.schedule(`every 30 minutes`).onRun(async () => {
+    // Get ceremonies in `scheduled` state.
+    const scheduledCeremoniesQuerySnap = await queryCeremoniesByStateAndDate(CeremonyState.SCHEDULED, "startDate", "<=")
+
+    if (scheduledCeremoniesQuerySnap.empty) logMsg(GENERIC_LOGS.GENLOG_NO_CEREMONIES_READY_TO_BE_OPENED, MsgType.INFO)
+    else {
+        scheduledCeremoniesQuerySnap.forEach(async (ceremonyDoc: DocumentSnapshot) => {
+            logMsg(`Ceremony ${ceremonyDoc.id} opened`, MsgType.INFO)
+
+            // Update ceremony state to `running`.
+            await ceremonyDoc.ref.set({ state: CeremonyState.OPENED }, { merge: true })
+        })
+    }
+})
+
+/**
+ * Automatically look and (if any) stop running ceremonies.
+ */
+export const stopCeremony = functions.pubsub.schedule(`every 30 minutes`).onRun(async () => {
+    // Get ceremonies in `running` state.
+    const runningCeremoniesQuerySnap = await queryCeremoniesByStateAndDate(CeremonyState.OPENED, "endDate", "<=")
+
+    if (runningCeremoniesQuerySnap.empty) logMsg(GENERIC_LOGS.GENLOG_NO_CEREMONIES_READY_TO_BE_CLOSED, MsgType.INFO)
+    else {
+        runningCeremoniesQuerySnap.forEach(async (ceremonyDoc: DocumentSnapshot) => {
+            logMsg(`Ceremony ${ceremonyDoc.id} closed`, MsgType.INFO)
+
+            // Update ceremony state to `finished`.
+            await ceremonyDoc.ref.set({ state: CeremonyState.CLOSED }, { merge: true })
+        })
+    }
+})
diff --git a/packages/backend/src/functions/contribute.ts b/packages/backend/src/functions/contribute.ts
new file mode 100644
index 00000000..a53d7861
--- /dev/null
+++ b/packages/backend/src/functions/contribute.ts
@@ -0,0 +1,656 @@
+import * as functions from "firebase-functions"
+import admin from "firebase-admin"
+import dotenv from "dotenv"
+import {
+    CeremonyState,
+    CeremonyTimeoutType,
+    MsgType,
+    ParticipantContributionStep,
+    ParticipantStatus,
+    TimeoutType
+} from "../../types/index"
+import { GENERIC_ERRORS, GENERIC_LOGS, logMsg } from "../lib/logs"
+import { collections, timeoutsCollectionFields } from "../lib/constants"
+import {
+    getCeremonyCircuits,
+    getCurrentServerTimestampInMillis,
+    getParticipantById,
+    queryCeremoniesByStateAndDate,
+    queryValidTimeoutsByDate
+} from "../lib/utils"
+
+dotenv.config()
+
+/**
+ * Check if a user can participate for the given ceremony (e.g., new contributor, after timeout expiration, etc.).
+ */
+export const checkParticipantForCeremony = functions.https.onCall(
+    async (data: any, context: functions.https.CallableContext) => {
+        console.log(context.auth)
+        console.log(context.auth?.token.participant)
+        console.log(context.auth?.token.coordinator)
+
+        // Check if sender is authenticated.
+        if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
+            logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
+
+        if (!data.ceremonyId) logMsg(GENERIC_ERRORS.GENERR_NO_CEREMONY_PROVIDED, MsgType.ERROR)
+
+        // Get DB.
+        const firestore = admin.firestore()
+
+        // Get data.
+        const { ceremonyId } = data
+        const userId = context.auth?.uid
+
+        // Look for the ceremony.
+        const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
+
+        // Check existence.
+        if (!ceremonyDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_CEREMONY, MsgType.ERROR)
+
+        // Get ceremony data.
+        const ceremonyData = ceremonyDoc.data()
+
+        // Check if running.
+        if (!ceremonyData || ceremonyData.state !== CeremonyState.OPENED)
+            logMsg(GENERIC_ERRORS.GENERR_CEREMONY_NOT_OPENED, MsgType.ERROR)
+
+        // Look for the user among ceremony participants.
+        const participantDoc = await firestore
+            .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
+            .doc(userId!)
+            .get()
+
+        if (!participantDoc.exists) {
+            // Create a new Participant doc for the sender.
+            await participantDoc.ref.set({
+                status: ParticipantStatus.WAITING,
+                contributionProgress: 0,
+                contributions: [],
+                lastUpdated: getCurrentServerTimestampInMillis()
+            })
+
+            logMsg(`User ${userId} has been registered as participant for ceremony ${ceremonyDoc.id}`, MsgType.INFO)
+        } else {
+            // Check if the participant has completed the contributions for all circuits.
+            const participantData = participantDoc.data()
+
+            if (!participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
+
+            logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
+
+            const circuits = await getCeremonyCircuits(
+                `${collections.ceremonies}/${ceremonyDoc.id}/${collections.circuits}`
+            )
+
+            // Already contributed to all circuits or currently contributor without any timeout.
+            if (
+                participantData?.contributionProgress === circuits.length &&
+                participantData?.status === ParticipantStatus.DONE
+            ) {
+                logMsg(
+                    `Participant ${participantDoc.id} has already contributed to all circuits or is the current contributor to that circuit (no timed out yet)`,
+                    MsgType.DEBUG
+                )
+
+                return false
+            }
+
+            if (participantData?.status === ParticipantStatus.TIMEDOUT) {
+                // Get `valid` timeouts (i.e., endDate is not expired).
+                const validTimeoutsQuerySnap = await queryValidTimeoutsByDate(
+                    ceremonyDoc.id,
+                    participantDoc.id,
+                    timeoutsCollectionFields.endDate
+                )
+
+                if (validTimeoutsQuerySnap.empty) {
+                    // TODO: need to remove unstable contributions (only one without doc link) and temp data, contributor must restart from step 1.
+                    // The participant can retry the contribution.
+                    await participantDoc.ref.set(
+                        {
+                            status: ParticipantStatus.EXHUMED,
+                            contributionStep: ParticipantContributionStep.DOWNLOADING,
+                            lastUpdated: getCurrentServerTimestampInMillis()
+                        },
+                        { merge: true }
+                    )
+
+                    logMsg(`Participant ${participantDoc.id} can retry the contribution from right now`, MsgType.DEBUG)
+
+                    return true
+                }
+                logMsg(`Participant ${participantDoc.id} cannot retry the contribution yet`, MsgType.DEBUG)
+
+                return false
+            }
+        }
+
+        return true
+    }
+)
+
+/**
+ * Check and remove the current contributor who is taking more than a specified amount of time for completing the contribution.
+ */
+export const checkAndRemoveBlockingContributor = functions.pubsub.schedule("every 1 minutes").onRun(async () => {
+    if (!process.env.CF_RETRY_WAITING_TIME_IN_DAYS) logMsg(GENERIC_ERRORS.GENERR_WRONG_ENV_CONFIGURATION, MsgType.ERROR)
+
+    // Get DB.
+    const firestore = admin.firestore()
+    const currentDate = getCurrentServerTimestampInMillis()
+
+    // Get ceremonies in `opened` state.
+    const openedCeremoniesQuerySnap = await queryCeremoniesByStateAndDate(CeremonyState.OPENED, "endDate", ">=")
+
+    if (openedCeremoniesQuerySnap.empty) logMsg(GENERIC_ERRORS.GENERR_NO_CEREMONIES_OPENED, MsgType.ERROR)
+
+    // For each ceremony.
+    for (const ceremonyDoc of openedCeremoniesQuerySnap.docs) {
+        if (!ceremonyDoc.exists || !ceremonyDoc.data()) logMsg(GENERIC_ERRORS.GENERR_INVALID_CEREMONY, MsgType.ERROR)
+
+        logMsg(`Ceremony document ${ceremonyDoc.id} okay`, MsgType.DEBUG)
+
+        // Get data.
+        const { timeoutType: ceremonyTimeoutType, penalty } = ceremonyDoc.data()
+
+        // Get circuits.
+        const circuitsDocs = await getCeremonyCircuits(
+            `${collections.ceremonies}/${ceremonyDoc.id}/${collections.circuits}`
+        )
+
+        // For each circuit.
+        for (const circuitDoc of circuitsDocs) {
+            if (!circuitDoc.exists || !circuitDoc.data()) logMsg(GENERIC_ERRORS.GENERR_INVALID_CIRCUIT, MsgType.ERROR)
+
+            const circuitData = circuitDoc.data()
+
+            logMsg(`Circuit document ${circuitDoc.id} okay`, MsgType.DEBUG)
+
+            // Get data.
+            const { waitingQueue, avgTimings } = circuitData
+            const { contributors, currentContributor, failedContributions, completedContributions } = waitingQueue
+            const { fullContribution: avgFullContribution } = avgTimings
+
+            // Check for current contributor.
+            if (!currentContributor) logMsg(GENERIC_ERRORS.GENERR_NO_CURRENT_CONTRIBUTOR, MsgType.WARN)
+
+            // Check if first contributor.
+            if (
+                !currentContributor &&
+                avgFullContribution === 0 &&
+                completedContributions === 0 &&
+                ceremonyTimeoutType === CeremonyTimeoutType.DYNAMIC
+            )
+                logMsg(GENERIC_ERRORS.GENERR_NO_TIMEOUT_FIRST_COTRIBUTOR, MsgType.DEBUG)
+
+            if (
+                !!currentContributor &&
+                ((avgFullContribution > 0 && completedContributions > 0) ||
+                    ceremonyTimeoutType === CeremonyTimeoutType.FIXED)
+            ) {
+                // Get current contributor data (i.e., participant).
+                const participantDoc = await getParticipantById(ceremonyDoc.id, currentContributor)
+
+                if (!participantDoc.exists || !participantDoc.data())
+                    logMsg(GENERIC_ERRORS.GENERR_INVALID_PARTICIPANT, MsgType.WARN)
+                else {
+                    const participantData = participantDoc.data()
+                    const contributionStartedAt = participantData?.contributionStartedAt
+                    const verificationStartedAt = participantData?.verificationStartedAt
+                    const currentContributionStep = participantData?.contributionStep
+
+                    logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
+
+                    // Check for blocking contributions (frontend-side).
+                    const timeoutToleranceThreshold =
+                        ceremonyTimeoutType === CeremonyTimeoutType.DYNAMIC
+                            ? (avgFullContribution / 100) * Number(circuitData.timeoutThreshold)
+                            : 0
+
+                    const timeoutExpirationDateInMillisForBlockingContributor =
+                        ceremonyTimeoutType === CeremonyTimeoutType.DYNAMIC
+                            ? Number(contributionStartedAt) +
+                              Number(avgFullContribution) +
+                              Number(timeoutToleranceThreshold)
+                            : Number(contributionStartedAt) +
+                              Number(circuitData.timeoutMaxContributionWaitingTime) * 60000
+
+                    logMsg(`Contribution start date ${contributionStartedAt}`, MsgType.DEBUG)
+                    if (ceremonyTimeoutType === CeremonyTimeoutType.DYNAMIC) {
+                        logMsg(`Average contribution per circuit time ${avgFullContribution} ms`, MsgType.DEBUG)
+                        logMsg(`Timeout tolerance threshold set to ${timeoutToleranceThreshold}`, MsgType.DEBUG)
+                    }
+                    logMsg(
+                        `BC Timeout expirartion date ${timeoutExpirationDateInMillisForBlockingContributor} ms`,
+                        MsgType.DEBUG
+                    )
+
+                    // Check for blocking verifications (backend-side).
+                    const timeoutExpirationDateInMillisForBlockingFunction = !verificationStartedAt
+                        ? 0
+                        : Number(verificationStartedAt) + 3540000 // 3540000 = 59 minutes in ms.
+
+                    logMsg(`Verification start date ${verificationStartedAt}`, MsgType.DEBUG)
+                    logMsg(
+                        `CF Timeout expirartion date ${timeoutExpirationDateInMillisForBlockingFunction} ms`,
+                        MsgType.DEBUG
+                    )
+
+                    // Get timeout type.
+                    let timeoutType = 0
+
+                    if (
+                        timeoutExpirationDateInMillisForBlockingContributor < currentDate &&
+                        currentContributionStep >= ParticipantContributionStep.DOWNLOADING &&
+                        currentContributionStep <= ParticipantContributionStep.UPLOADING
+                    )
+                        timeoutType = TimeoutType.BLOCKING_CONTRIBUTION
+
+                    if (
+                        timeoutExpirationDateInMillisForBlockingFunction > 0 &&
+                        timeoutExpirationDateInMillisForBlockingFunction < currentDate &&
+                        currentContributionStep === ParticipantContributionStep.VERIFYING
+                    )
+                        timeoutType = TimeoutType.BLOCKING_CLOUD_FUNCTION
+
+                    logMsg(`Ceremony Timeout type ${ceremonyTimeoutType}`, MsgType.DEBUG)
+                    logMsg(`Timeout type ${timeoutType}`, MsgType.DEBUG)
+
+                    // Check if one timeout should be triggered.
+                    if (timeoutType !== 0) {
+                        // Timeout the participant.
+                        const batch = firestore.batch()
+
+                        // 1. Update circuit' waiting queue.
+                        contributors.shift(1)
+
+                        let newCurrentContributor = ""
+
+                        if (contributors.length > 0) {
+                            // There's someone else ready to contribute.
+                            newCurrentContributor = contributors.at(0)
+
+                            // Pass the baton to the next participant.
+                            const newCurrentContributorDoc = await firestore
+                                .collection(`${collections.ceremonies}/${ceremonyDoc.id}/${collections.participants}`)
+                                .doc(newCurrentContributor)
+                                .get()
+
+                            if (newCurrentContributorDoc.exists) {
+                                batch.update(newCurrentContributorDoc.ref, {
+                                    status: ParticipantStatus.WAITING,
+                                    lastUpdated: getCurrentServerTimestampInMillis()
+                                })
+                            }
+                        }
+
+                        batch.update(circuitDoc.ref, {
+                            waitingQueue: {
+                                ...circuitData.waitingQueue,
+                                contributors,
+                                currentContributor: newCurrentContributor,
+                                failedContributions: failedContributions + 1
+                            },
+                            lastUpdated: getCurrentServerTimestampInMillis()
+                        })
+
+                        logMsg(`Batch: update for circuit' waiting queue`, MsgType.DEBUG)
+
+                        // 2. Change blocking contributor status.
+                        batch.update(participantDoc.ref, {
+                            status: ParticipantStatus.TIMEDOUT,
+                            lastUpdated: getCurrentServerTimestampInMillis()
+                        })
+
+                        logMsg(`Batch: change blocking contributor status to TIMEDOUT`, MsgType.DEBUG)
+
+                        // 3. Create a new collection of timeouts (to keep track of participants timeouts).
+                        const retryWaitingTimeInMillis =
+                            timeoutType === TimeoutType.BLOCKING_CONTRIBUTION
+                                ? Number(penalty) * 60000 // 60000 = amount of ms x minute.
+                                : Number(process.env.CF_RETRY_WAITING_TIME_IN_DAYS) * 86400000 // 86400000 = amount of ms x day.
+
+                        // Timeout collection.
+                        const timeoutDoc = await firestore
+                            .collection(
+                                `${collections.ceremonies}/${ceremonyDoc.id}/${collections.participants}/${participantDoc.id}/${collections.timeouts}`
+                            )
+                            .doc()
+                            .get()
+
+                        batch.create(timeoutDoc.ref, {
+                            type: timeoutType,
+                            startDate: currentDate,
+                            endDate: currentDate + retryWaitingTimeInMillis
+                        })
+
+                        logMsg(`Batch: add timeout document for blocking contributor`, MsgType.DEBUG)
+
+                        await batch.commit()
+
+                        logMsg(`Blocking contributor ${participantDoc.id} timedout. Cause ${timeoutType}`, MsgType.INFO)
+                    } else logMsg(GENERIC_LOGS.GENLOG_NO_TIMEOUT, MsgType.INFO)
+                }
+            }
+        }
+    }
+})
+
+/**
+ * Progress to next contribution step for the current contributor of a specified circuit in a given ceremony.
+ */
+export const progressToNextContributionStep = functions.https.onCall(
+    async (data: any, context: functions.https.CallableContext) => {
+        // Check if sender is authenticated.
+        if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
+            logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
+
+        if (!data.ceremonyId) logMsg(GENERIC_ERRORS.GENERR_NO_CEREMONY_PROVIDED, MsgType.ERROR)
+
+        // Get DB.
+        const firestore = admin.firestore()
+
+        // Get data.
+        const { ceremonyId } = data
+        const userId = context.auth?.uid
+
+        // Look for the ceremony.
+        const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
+
+        // Check existence.
+        if (!ceremonyDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_CEREMONY, MsgType.ERROR)
+
+        // Get ceremony data.
+        const ceremonyData = ceremonyDoc.data()
+
+        // Check if running.
+        if (!ceremonyData || ceremonyData.state !== CeremonyState.OPENED)
+            logMsg(GENERIC_ERRORS.GENERR_CEREMONY_NOT_OPENED, MsgType.ERROR)
+
+        logMsg(`Ceremony document ${ceremonyId} okay`, MsgType.DEBUG)
+
+        // Look for the user among ceremony participants.
+        const participantDoc = await firestore
+            .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
+            .doc(userId!)
+            .get()
+
+        // Check existence.
+        if (!participantDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_PARTICIPANT, MsgType.ERROR)
+
+        // Get participant data.
+        const participantData = participantDoc.data()
+
+        if (!participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
+
+        logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
+
+        // Check if participant is able to advance to next contribution step.
+        if (participantData?.status !== ParticipantStatus.CONTRIBUTING)
+            logMsg(`Participant ${participantDoc.id} is not contributing`, MsgType.ERROR)
+
+        // Make the advancement.
+        const progress = Number(participantData?.contributionStep) + 1
+
+        logMsg(`Current contribution step should be ${participantData?.contributionStep}`, MsgType.DEBUG)
+        logMsg(`Next contribution step should be ${progress}`, MsgType.DEBUG)
+
+        // nb. DOWNLOADING (=1) must be set when coordinating the waiting queue while COMPLETED (=5) must be set in verifyContribution().
+        if (progress <= ParticipantContributionStep.DOWNLOADING || progress >= ParticipantContributionStep.COMPLETED)
+            logMsg(`Wrong contribution step ${progress} for ${participantDoc.id}`, MsgType.ERROR)
+
+        if (progress === ParticipantContributionStep.VERIFYING)
+            await participantDoc.ref.update({
+                contributionStep: progress,
+                verificationStartedAt: getCurrentServerTimestampInMillis(),
+                lastUpdated: getCurrentServerTimestampInMillis()
+            })
+        else
+            await participantDoc.ref.update({
+                contributionStep: progress,
+                lastUpdated: getCurrentServerTimestampInMillis()
+            })
+    }
+)
+
+/**
+ * Temporary store the contribution computation time for the current contributor.
+ */
+export const temporaryStoreCurrentContributionComputationTime = functions.https.onCall(
+    async (data: any, context: functions.https.CallableContext) => {
+        // Check if sender is authenticated.
+        if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
+            logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
+
+        if (!data.ceremonyId || data.contributionComputationTime <= 0)
+            logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
+
+        // Get DB.
+        const firestore = admin.firestore()
+
+        // Get data.
+        const { ceremonyId } = data
+        const userId = context.auth?.uid
+
+        // Look for documents.
+        const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
+        const participantDoc = await firestore
+            .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
+            .doc(userId!)
+            .get()
+
+        // Check existence.
+        if (!ceremonyDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_CEREMONY, MsgType.ERROR)
+        if (!participantDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_PARTICIPANT, MsgType.ERROR)
+
+        // Get data.
+        const participantData = participantDoc.data()
+
+        if (!participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
+
+        logMsg(`Ceremony document ${ceremonyId} okay`, MsgType.DEBUG)
+        logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
+
+        // Check if has reached the computing step while contributing.
+        if (participantData?.contributionStep !== ParticipantContributionStep.COMPUTING)
+            logMsg(GENERIC_ERRORS.GENERR_INVALID_PARTICIPANT_CONTRIBUTION_STEP, MsgType.ERROR)
+
+        // Update.
+        await participantDoc.ref.set(
+            {
+                ...participantData!,
+                tempContributionData: {
+                    contributionComputationTime: data.contributionComputationTime
+                },
+                lastUpdated: getCurrentServerTimestampInMillis()
+            },
+            { merge: true }
+        )
+    }
+)
+
+/**
+ * Permanently store the contribution computation hash for attestation generation for the current contributor.
+ */
+export const permanentlyStoreCurrentContributionTimeAndHash = functions.https.onCall(
+    async (data: any, context: functions.https.CallableContext) => {
+        // Check if sender is authenticated.
+        if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
+            logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
+
+        if (!data.ceremonyId || data.contributionComputationTime <= 0 || !data.contributionHash)
+            logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
+
+        // Get DB.
+        const firestore = admin.firestore()
+
+        // Get data.
+        const { ceremonyId } = data
+        const userId = context.auth?.uid
+
+        // Look for documents.
+        const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
+        const participantDoc = await firestore
+            .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
+            .doc(userId!)
+            .get()
+
+        // Check existence.
+        if (!ceremonyDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_CEREMONY, MsgType.ERROR)
+        if (!participantDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_PARTICIPANT, MsgType.ERROR)
+
+        // Get data.
+        const participantData = participantDoc.data()
+
+        if (!participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
+
+        logMsg(`Ceremony document ${ceremonyId} okay`, MsgType.DEBUG)
+        logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
+
+        // Check if has reached the computing step while contributing or is finalizing.
+        if (
+            participantData?.contributionStep === ParticipantContributionStep.COMPUTING ||
+            (context?.auth?.token.coordinator && participantData?.status === ParticipantStatus.FINALIZING)
+        )
+            // Update.
+            await participantDoc.ref.set(
+                {
+                    ...participantData!,
+                    contributions: [
+                        ...participantData!.contributions,
+                        {
+                            hash: data.contributionHash!,
+                            computationTime: data.contributionComputationTime
+                        }
+                    ],
+                    lastUpdated: getCurrentServerTimestampInMillis()
+                },
+                { merge: true }
+            )
+        else logMsg(GENERIC_ERRORS.GENERR_INVALID_PARTICIPANT_CONTRIBUTION_STEP, MsgType.ERROR)
+    }
+)
+
+/**
+ * Temporary store the the Multi-Part Upload identifier for the current contributor.
+ */
+export const temporaryStoreCurrentContributionMultiPartUploadId = functions.https.onCall(
+    async (data: any, context: functions.https.CallableContext) => {
+        // Check if sender is authenticated.
+        if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
+            logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
+
+        if (!data.ceremonyId || !data.uploadId) logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
+
+        // Get DB.
+        const firestore = admin.firestore()
+
+        // Get data.
+        const { ceremonyId } = data
+        const userId = context.auth?.uid
+
+        // Look for documents.
+        const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
+        const participantDoc = await firestore
+            .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
+            .doc(userId!)
+            .get()
+
+        // Check existence.
+        if (!ceremonyDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_CEREMONY, MsgType.ERROR)
+        if (!participantDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_PARTICIPANT, MsgType.ERROR)
+
+        // Get data.
+        const participantData = participantDoc.data()
+
+        if (!participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
+
+        logMsg(`Ceremony document ${ceremonyId} okay`, MsgType.DEBUG)
+        logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
+
+        // Check if has reached the uploading step while contributing.
+        if (participantData?.contributionStep !== ParticipantContributionStep.UPLOADING)
+            logMsg(GENERIC_ERRORS.GENERR_INVALID_PARTICIPANT_CONTRIBUTION_STEP, MsgType.ERROR)
+
+        // Update.
+        await participantDoc.ref.set(
+            {
+                ...participantData!,
+                tempContributionData: {
+                    ...participantData?.tempContributionData,
+                    uploadId: data.uploadId,
+                    chunks: []
+                },
+                lastUpdated: getCurrentServerTimestampInMillis()
+            },
+            { merge: true }
+        )
+    }
+)
+
+/**
+ * Temporary store the ETag and PartNumber for each uploaded chunk in order to make the upload resumable from last chunk.
+ */
+export const temporaryStoreCurrentContributionUploadedChunkData = functions.https.onCall(
+    async (data: any, context: functions.https.CallableContext) => {
+        // Check if sender is authenticated.
+        if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
+            logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
+
+        if (!data.ceremonyId || !data.eTag || data.partNumber <= 0)
+            logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
+
+        // Get DB.
+        const firestore = admin.firestore()
+
+        // Get data.
+        const { ceremonyId } = data
+        const userId = context.auth?.uid
+
+        // Look for documents.
+        const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
+        const participantDoc = await firestore
+            .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
+            .doc(userId!)
+            .get()
+
+        // Check existence.
+        if (!ceremonyDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_CEREMONY, MsgType.ERROR)
+        if (!participantDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_PARTICIPANT, MsgType.ERROR)
+
+        // Get data.
+        const participantData = participantDoc.data()
+
+        if (!participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
+
+        logMsg(`Ceremony document ${ceremonyId} okay`, MsgType.DEBUG)
+        logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
+
+        // Check if has reached the uploading step while contributing.
+        if (participantData?.contributionStep !== ParticipantContributionStep.UPLOADING)
+            logMsg(GENERIC_ERRORS.GENERR_INVALID_PARTICIPANT_CONTRIBUTION_STEP, MsgType.ERROR)
+
+        const chunks = participantData?.tempContributionData.chunks ? participantData?.tempContributionData.chunks : []
+
+        // Add last chunk.
+        chunks.push({
+            ETag: data.eTag,
+            PartNumber: data.partNumber
+        })
+
+        // Update.
+        await participantDoc.ref.set(
+            {
+                ...participantData!,
+                tempContributionData: {
+                    ...participantData?.tempContributionData,
+                    chunks
+                },
+                lastUpdated: getCurrentServerTimestampInMillis()
+            },
+            { merge: true }
+        )
+    }
+)
diff --git a/packages/backend/src/functions/finalize.ts b/packages/backend/src/functions/finalize.ts
new file mode 100644
index 00000000..8f0b4de3
--- /dev/null
+++ b/packages/backend/src/functions/finalize.ts
@@ -0,0 +1,245 @@
+import * as functions from "firebase-functions"
+import admin from "firebase-admin"
+import path from "path"
+import os from "os"
+import fs from "fs"
+import blake from "blakejs"
+import { logMsg, GENERIC_ERRORS } from "../lib/logs"
+import { collections } from "../lib/constants"
+import { CeremonyState, MsgType, ParticipantStatus } from "../../types/index"
+import {
+    getCeremonyCircuits,
+    getCurrentServerTimestampInMillis,
+    getFinalContributionDocument,
+    getS3Client,
+    tempDownloadFromBucket
+} from "../lib/utils"
+
+/**
+ * Check and prepare the coordinator for the ceremony finalization.
+ */
+export const checkAndPrepareCoordinatorForFinalization = functions.https.onCall(
+    async (data: any, context: functions.https.CallableContext) => {
+        // Check if sender is authenticated.
+        if (!context.auth || !context.auth.token.coordinator)
+            logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
+
+        if (!data.ceremonyId) logMsg(GENERIC_ERRORS.GENERR_NO_CEREMONY_PROVIDED, MsgType.ERROR)
+
+        // Get DB.
+        const firestore = admin.firestore()
+
+        // Get data.
+        const { ceremonyId } = data
+        const userId = context.auth?.uid
+
+        // Look for the ceremony.
+        const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
+
+        // Check existence.
+        if (!ceremonyDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_CEREMONY, MsgType.ERROR)
+
+        // Get ceremony data.
+        const ceremonyData = ceremonyDoc.data()
+
+        // Check if running.
+        if (!ceremonyData || ceremonyData.state !== CeremonyState.CLOSED)
+            logMsg(GENERIC_ERRORS.GENERR_CEREMONY_NOT_CLOSED, MsgType.ERROR)
+
+        // Look for the coordinator among ceremony participant.
+        const participantDoc = await firestore
+            .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
+            .doc(userId!)
+            .get()
+
+        // Check if the coordinator has completed the contributions for all circuits.
+        const participantData = participantDoc.data()
+
+        if (!participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
+
+        logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
+
+        const circuits = await getCeremonyCircuits(
+            `${collections.ceremonies}/${ceremonyDoc.id}/${collections.circuits}`
+        )
+
+        // Already contributed to all circuits.
+        if (
+            participantData?.contributionProgress === circuits.length + 1 ||
+            participantData?.status === ParticipantStatus.DONE
+        ) {
+            // Update participant status.
+            await participantDoc.ref.set(
+                {
+                    status: ParticipantStatus.FINALIZING,
+                    lastUpdated: getCurrentServerTimestampInMillis()
+                },
+                { merge: true }
+            )
+
+            logMsg(`Coordinator ${participantDoc.id} ready for finalization`, MsgType.DEBUG)
+
+            return true
+        }
+        return false
+    }
+)
+
+/**
+ * Add Verifier smart contract and verification key files metadata to the last final contribution for verifiability/integrity of the ceremony.
+ */
+export const finalizeLastContribution = functions.https.onCall(
+    async (data: any, context: functions.https.CallableContext): Promise<any> => {
+        if (!context.auth || !context.auth.token.coordinator)
+            logMsg(GENERIC_ERRORS.GENERR_NO_COORDINATOR, MsgType.ERROR)
+
+        if (!data.ceremonyId || !data.circuitId || !data.bucketName)
+            logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
+
+        // Get DB.
+        const firestore = admin.firestore()
+
+        // Get Storage.
+        const S3 = await getS3Client()
+
+        // Get data.
+        const { ceremonyId, circuitId, bucketName } = data
+        const userId = context.auth?.uid
+
+        // Look for documents.
+        const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
+        const circuitDoc = await firestore
+            .collection(`${collections.ceremonies}/${ceremonyId}/${collections.circuits}`)
+            .doc(circuitId)
+            .get()
+        const participantDoc = await firestore
+            .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
+            .doc(userId!)
+            .get()
+        const contributionDoc = await getFinalContributionDocument(
+            `${collections.ceremonies}/${ceremonyId}/${collections.circuits}/${circuitId}/${collections.contributions}`
+        )
+
+        if (!ceremonyDoc.exists || !circuitDoc.exists || !participantDoc.exists || !contributionDoc.exists)
+            logMsg(GENERIC_ERRORS.GENERR_INVALID_DOCUMENTS, MsgType.ERROR)
+
+        // Get data from docs.
+        const ceremonyData = ceremonyDoc.data()
+        const circuitData = circuitDoc.data()
+        const participantData = participantDoc.data()
+        const contributionData = contributionDoc.data()
+
+        if (!ceremonyData || !circuitData || !participantData || !contributionData)
+            logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
+
+        logMsg(`Ceremony document ${ceremonyDoc.id} okay`, MsgType.DEBUG)
+        logMsg(`Circuit document ${circuitDoc.id} okay`, MsgType.DEBUG)
+        logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
+        logMsg(`Contribution document ${contributionDoc.id} okay`, MsgType.DEBUG)
+
+        // Filenames.
+        const verificationKeyFilename = `${circuitData?.prefix}_vkey.json`
+        const verifierContractFilename = `${circuitData?.prefix}_verifier.sol`
+
+        // Get storage paths.
+        const verificationKeyStoragePath = `${collections.circuits}/${circuitData?.prefix}/${verificationKeyFilename}`
+        const verifierContractStoragePath = `${collections.circuits}/${circuitData?.prefix}/${verifierContractFilename}`
+
+        // Temporary store files from bucket.
+        const verificationKeyTmpFilePath = path.join(os.tmpdir(), verificationKeyFilename)
+        const verifierContractTmpFilePath = path.join(os.tmpdir(), verifierContractFilename)
+
+        await tempDownloadFromBucket(S3, bucketName, verificationKeyStoragePath, verificationKeyTmpFilePath)
+        await tempDownloadFromBucket(S3, bucketName, verifierContractStoragePath, verifierContractTmpFilePath)
+
+        // Compute blake2b hash before unlink.
+        const verificationKeyBuffer = fs.readFileSync(verificationKeyTmpFilePath)
+        const verifierContractBuffer = fs.readFileSync(verifierContractTmpFilePath)
+
+        logMsg(`Downloads from storage completed`, MsgType.INFO)
+
+        const verificationKeyBlake2bHash = blake.blake2bHex(verificationKeyBuffer)
+        const verifierContractBlake2bHash = blake.blake2bHex(verifierContractBuffer)
+
+        // Unlink folders.
+        fs.unlinkSync(verificationKeyTmpFilePath)
+        fs.unlinkSync(verifierContractTmpFilePath)
+
+        // Update DB.
+        const batch = firestore.batch()
+
+        batch.update(contributionDoc.ref, {
+            files: {
+                ...contributionData?.files,
+                verificationKeyBlake2bHash,
+                verificationKeyFilename,
+                verificationKeyStoragePath,
+                verifierContractBlake2bHash,
+                verifierContractFilename,
+                verifierContractStoragePath
+            },
+            lastUpdated: getCurrentServerTimestampInMillis()
+        })
+
+        await batch.commit()
+
+        logMsg(
+            `Circuit ${circuitId} correctly finalized - Ceremony ${ceremonyDoc.id} - Coordinator ${participantDoc.id}`,
+            MsgType.INFO
+        )
+    }
+)
+
+/**
+ * Finalize a closed ceremony.
+ */
+export const finalizeCeremony = functions.https.onCall(
+    async (data: any, context: functions.https.CallableContext): Promise<any> => {
+        if (!context.auth || !context.auth.token.coordinator)
+            logMsg(GENERIC_ERRORS.GENERR_NO_COORDINATOR, MsgType.ERROR)
+
+        if (!data.ceremonyId) logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
+
+        // Get DB.
+        const firestore = admin.firestore()
+        // Update DB.
+        const batch = firestore.batch()
+
+        const { ceremonyId } = data
+        const userId = context.auth?.uid
+
+        // Look for documents.
+        const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
+        const participantDoc = await firestore
+            .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
+            .doc(userId!)
+            .get()
+
+        if (!ceremonyDoc.exists || !participantDoc.exists)
+            logMsg(GENERIC_ERRORS.GENERR_INVALID_DOCUMENTS, MsgType.ERROR)
+
+        // Get data from docs.
+        const ceremonyData = ceremonyDoc.data()
+        const participantData = participantDoc.data()
+
+        if (!ceremonyData || !participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
+
+        logMsg(`Ceremony document ${ceremonyDoc.id} okay`, MsgType.DEBUG)
+        logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
+
+        // Check if the ceremony has state equal to closed.
+        if (ceremonyData?.state === CeremonyState.CLOSED && participantData?.status === ParticipantStatus.FINALIZING) {
+            // Finalize the ceremony.
+            batch.update(ceremonyDoc.ref, { state: CeremonyState.FINALIZED })
+
+            // Update coordinator status.
+            batch.update(participantDoc.ref, {
+                status: ParticipantStatus.FINALIZED
+            })
+
+            await batch.commit()
+
+            logMsg(`Ceremony ${ceremonyDoc.id} correctly finalized - Coordinator ${participantDoc.id}`, MsgType.INFO)
+        }
+    }
+)
diff --git a/packages/backend/src/functions/index.ts b/packages/backend/src/functions/index.ts
new file mode 100644
index 00000000..0dad8244
--- /dev/null
+++ b/packages/backend/src/functions/index.ts
@@ -0,0 +1,61 @@
+import admin from "firebase-admin"
+import { registerAuthUser, processSignUpWithCustomClaims } from "./auth"
+import { startCeremony, stopCeremony } from "./ceremony"
+import { setupCeremony, initEmptyWaitingQueueForCircuit } from "./setup"
+import {
+    checkParticipantForCeremony,
+    checkAndRemoveBlockingContributor,
+    progressToNextContributionStep,
+    temporaryStoreCurrentContributionComputationTime,
+    permanentlyStoreCurrentContributionTimeAndHash,
+    temporaryStoreCurrentContributionMultiPartUploadId,
+    temporaryStoreCurrentContributionUploadedChunkData
+} from "./contribute"
+import {
+    coordinateContributors,
+    verifycontribution,
+    refreshParticipantAfterContributionVerification,
+    makeProgressToNextContribution,
+    resumeContributionAfterTimeoutExpiration
+} from "./waitingQueue"
+import { checkAndPrepareCoordinatorForFinalization, finalizeLastContribution, finalizeCeremony } from "./finalize"
+import {
+    createBucket,
+    checkIfObjectExist,
+    generateGetObjectPreSignedUrl,
+    startMultiPartUpload,
+    generatePreSignedUrlsParts,
+    completeMultiPartUpload
+} from "./storage"
+
+admin.initializeApp()
+
+export {
+    registerAuthUser,
+    processSignUpWithCustomClaims,
+    startCeremony,
+    stopCeremony,
+    checkAndPrepareCoordinatorForFinalization,
+    finalizeLastContribution,
+    finalizeCeremony,
+    setupCeremony,
+    initEmptyWaitingQueueForCircuit,
+    checkParticipantForCeremony,
+    checkAndRemoveBlockingContributor,
+    progressToNextContributionStep,
+    temporaryStoreCurrentContributionComputationTime,
+    permanentlyStoreCurrentContributionTimeAndHash,
+    temporaryStoreCurrentContributionMultiPartUploadId,
+    temporaryStoreCurrentContributionUploadedChunkData,
+    coordinateContributors,
+    verifycontribution,
+    refreshParticipantAfterContributionVerification,
+    makeProgressToNextContribution,
+    resumeContributionAfterTimeoutExpiration,
+    createBucket,
+    checkIfObjectExist,
+    generateGetObjectPreSignedUrl,
+    startMultiPartUpload,
+    generatePreSignedUrlsParts,
+    completeMultiPartUpload
+}
diff --git a/packages/backend/src/functions/setup.ts b/packages/backend/src/functions/setup.ts
new file mode 100644
index 00000000..5607053a
--- /dev/null
+++ b/packages/backend/src/functions/setup.ts
@@ -0,0 +1,108 @@
+import * as functions from "firebase-functions"
+import admin from "firebase-admin"
+import dotenv from "dotenv"
+import { QueryDocumentSnapshot } from "firebase-functions/v1/firestore"
+import { CeremonyState, CeremonyType, MsgType } from "../../types/index"
+import { GENERIC_ERRORS, logMsg } from "../lib/logs"
+import { getCurrentServerTimestampInMillis } from "../lib/utils"
+import { collections } from "../lib/constants"
+
+dotenv.config()
+
+/**
+ * Bootstrap/Setup every necessary document for running a ceremony.
+ */
+export const setupCeremony = functions.https.onCall(
+    async (data: any, context: functions.https.CallableContext): Promise<any> => {
+        if (!context.auth || !context.auth.token.coordinator)
+            logMsg(GENERIC_ERRORS.GENERR_NO_COORDINATOR, MsgType.ERROR)
+
+        if (!data.ceremonyInputData || !data.ceremonyPrefix || !data.circuits)
+            logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
+
+        // Database.
+        const firestore = admin.firestore()
+        const batch = firestore.batch()
+
+        // Get data.
+        const { ceremonyInputData, ceremonyPrefix, circuits } = data
+        const userId = context.auth?.uid
+
+        // Ceremonies.
+        const ceremonyDoc = await firestore.collection(`${collections.ceremonies}/`).doc().get()
+
+        batch.create(ceremonyDoc.ref, {
+            title: ceremonyInputData.title,
+            description: ceremonyInputData.description,
+            startDate: new Date(ceremonyInputData.startDate).valueOf(),
+            endDate: new Date(ceremonyInputData.endDate).valueOf(),
+            prefix: ceremonyPrefix,
+            state: CeremonyState.SCHEDULED,
+            type: CeremonyType.PHASE2,
+            penalty: ceremonyInputData.penalty,
+            timeoutType: ceremonyInputData.timeoutMechanismType,
+            coordinatorId: userId,
+            lastUpdated: getCurrentServerTimestampInMillis()
+        })
+
+        // Circuits.
+        if (!circuits.length) logMsg(GENERIC_ERRORS.GENERR_NO_CIRCUIT_PROVIDED, MsgType.ERROR)
+
+        for (const circuit of circuits) {
+            const circuitDoc = await firestore
+                .collection(`${collections.ceremonies}/${ceremonyDoc.ref.id}/${collections.circuits}`)
+                .doc()
+                .get()
+
+            batch.create(circuitDoc.ref, {
+                ...circuit,
+                lastUpdated: getCurrentServerTimestampInMillis()
+            })
+        }
+
+        await batch.commit()
+
+        logMsg(`Ceremony ${ceremonyDoc.id} setup successfully completed - Coordinator ${userId}`, MsgType.INFO)
+    }
+)
+
+/**
+ * Initialize an empty Waiting Queue field for the newly created circuit document.
+ */
+export const initEmptyWaitingQueueForCircuit = functions.firestore
+    .document(`/${collections.ceremonies}/{ceremony}/${collections.circuits}/{circuit}`)
+    .onCreate(async (doc: QueryDocumentSnapshot) => {
+        // Get DB.
+        const firestore = admin.firestore()
+
+        // Get doc info.
+        const circuitId = doc.id
+        const circuitData = doc.data()
+        const parentCollectionPath = doc.ref.parent.path // == /ceremonies/{ceremony}/circuits/.
+
+        // Empty waiting queue.
+        const waitingQueue = {
+            contributors: [],
+            currentContributor: "",
+            completedContributions: 0, // == nextZkeyIndex.
+            failedContributions: 0
+        }
+
+        // Update the circuit document.
+        await firestore
+            .collection(parentCollectionPath)
+            .doc(circuitId)
+            .set(
+                {
+                    ...circuitData,
+                    waitingQueue,
+                    lastUpdated: getCurrentServerTimestampInMillis()
+                },
+                { merge: true }
+            )
+
+        logMsg(
+            `Empty waiting queue successfully initialized for circuit ${circuitId} - Ceremony ${doc.id}`,
+            MsgType.INFO
+        )
+    })
diff --git a/packages/backend/src/functions/storage.ts b/packages/backend/src/functions/storage.ts
new file mode 100644
index 00000000..40622a39
--- /dev/null
+++ b/packages/backend/src/functions/storage.ts
@@ -0,0 +1,341 @@
+import * as functions from "firebase-functions"
+import admin from "firebase-admin"
+import {
+    GetObjectCommand,
+    CreateMultipartUploadCommand,
+    UploadPartCommand,
+    CompleteMultipartUploadCommand,
+    HeadObjectCommand,
+    CreateBucketCommand
+} from "@aws-sdk/client-s3"
+import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
+import dotenv from "dotenv"
+import { MsgType, ParticipantContributionStep, ParticipantStatus } from "../../types/index"
+import { logMsg, GENERIC_ERRORS } from "../lib/logs"
+import { getS3Client } from "../lib/utils"
+import { collections } from "../lib/constants"
+
+dotenv.config()
+
+/**
+ * Create a new AWS S3 bucket for a particular ceremony.
+ */
+export const createBucket = functions.https.onCall(
+    async (data: any, context: functions.https.CallableContext): Promise<any> => {
+        // Checks.
+        if (!context.auth || !context.auth.token.coordinator)
+            logMsg(GENERIC_ERRORS.GENERR_NO_COORDINATOR, MsgType.ERROR)
+
+        if (!data.bucketName) logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
+
+        // Connect w/ S3.
+        const S3 = await getS3Client()
+
+        // Prepare command.
+        const command = new CreateBucketCommand({
+            Bucket: data.bucketName,
+            CreateBucketConfiguration: {
+                LocationConstraint: process.env.AWS_REGION!
+            }
+        })
+
+        try {
+            // Send command.
+            const response = await S3.send(command)
+
+            // Check response.
+            if (response.$metadata.httpStatusCode === 200 && !!response.Location) {
+                logMsg(`Bucket successfully created`, MsgType.LOG)
+
+                return true
+            }
+        } catch (error: any) {
+            if (error.$metadata.httpStatusCode === 400 && error.Code === "InvalidBucketName") {
+                logMsg(`Bucket not created: ${error.Code}`, MsgType.LOG)
+            }
+
+            logMsg(`Generic error when creating a new S3 bucket: ${error}`, MsgType.ERROR)
+        }
+
+        return false
+    }
+)
+
+/**
+ * Check if a specified object exist in a given AWS S3 bucket.
+ */
+export const checkIfObjectExist = functions.https.onCall(
+    async (data: any, context: functions.https.CallableContext): Promise<any> => {
+        // Checks.
+        if (!context.auth || !context.auth.token.coordinator)
+            logMsg(GENERIC_ERRORS.GENERR_NO_COORDINATOR, MsgType.ERROR)
+
+        if (!data.bucketName || !data.objectKey) logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
+
+        // Connect w/ S3.
+        const S3 = await getS3Client()
+
+        // Prepare command.
+        const command = new HeadObjectCommand({ Bucket: data.bucketName, Key: data.objectKey })
+
+        try {
+            // Send command.
+            const response = await S3.send(command)
+
+            // Check response.
+            if (response.$metadata.httpStatusCode === 200 && !!response.ETag) {
+                logMsg(`Object: ${data.objectKey} exists!`, MsgType.LOG)
+
+                return true
+            }
+        } catch (error: any) {
+            if (error.$metadata.httpStatusCode === 404 && !error.ETag) {
+                logMsg(`Object: ${data.objectKey} does not exist!`, MsgType.LOG)
+
+                return false
+            }
+
+            logMsg(`Generic error when checking for object on S3 bucket: ${error}`, MsgType.ERROR)
+        }
+
+        return false
+    }
+)
+
+/**
+ * Generate a new AWS S3 pre signed url to upload/download an object (GET).
+ */
+export const generateGetObjectPreSignedUrl = functions.https.onCall(async (data: any): Promise<any> => {
+    if (!data.bucketName || !data.objectKey) logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
+
+    // Connect w/ S3.
+    const S3 = await getS3Client()
+
+    // Prepare the command.
+    const command = new GetObjectCommand({ Bucket: data.bucketName, Key: data.objectKey })
+
+    // Get the PreSignedUrl.
+    const url = await getSignedUrl(S3, command, { expiresIn: Number(process.env.AWS_PRESIGNED_URL_EXPIRATION!) })
+
+    logMsg(`Single Pre-Signed URL ${url}`, MsgType.LOG)
+
+    return url
+})
+
+/**
+ * Initiate a multi part upload for a specific object in AWS S3 bucket.
+ */
+export const startMultiPartUpload = functions.https.onCall(
+    async (data: any, context: functions.https.CallableContext): Promise<any> => {
+        if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
+            logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
+
+        if (!data.bucketName || !data.objectKey || (context.auth?.token.participant && !data.ceremonyId))
+            logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
+
+        // Get DB.
+        const firestore = admin.firestore()
+
+        // Get data.
+        const { bucketName, objectKey, ceremonyId } = data
+        const userId = context.auth?.uid
+
+        if (context.auth?.token.participant && !!ceremonyId) {
+            // Look for documents.
+            const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
+            const participantDoc = await firestore
+                .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
+                .doc(userId!)
+                .get()
+
+            if (!ceremonyDoc.exists || !participantDoc.exists)
+                logMsg(GENERIC_ERRORS.GENERR_INVALID_DOCUMENTS, MsgType.ERROR)
+
+            // Get data from docs.
+            const ceremonyData = ceremonyDoc.data()
+            const participantData = participantDoc.data()
+
+            if (!ceremonyData || !participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
+
+            logMsg(`Ceremony document ${ceremonyDoc.id} okay`, MsgType.DEBUG)
+            logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
+
+            // Check participant status and contribution step.
+            const { status, contributionStep } = participantData!
+
+            if (status !== ParticipantStatus.CONTRIBUTING && contributionStep !== ParticipantContributionStep.UPLOADING)
+                logMsg(
+                    `Participant ${participantDoc.id} is not able to start a multi part upload right now`,
+                    MsgType.ERROR
+                )
+        }
+
+        // Connect w/ S3.
+        const S3 = await getS3Client()
+
+        // Prepare command.
+        const command = new CreateMultipartUploadCommand({ Bucket: bucketName, Key: objectKey })
+
+        // Send command.
+        const responseInitiate = await S3.send(command)
+        const uploadId = responseInitiate.UploadId
+
+        logMsg(`Upload ID: ${uploadId}`, MsgType.LOG)
+
+        return uploadId
+    }
+)
+
+/**
+ * Generate a PreSignedUrl for each part of the given multi part upload.
+ */
+export const generatePreSignedUrlsParts = functions.https.onCall(
+    async (data: any, context: functions.https.CallableContext): Promise<any> => {
+        if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
+            logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
+
+        if (
+            !data.bucketName ||
+            !data.objectKey ||
+            !data.uploadId ||
+            data.numberOfParts <= 0 ||
+            (context.auth?.token.participant && !data.ceremonyId)
+        )
+            logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
+
+        // Get DB.
+        const firestore = admin.firestore()
+
+        // Get data.
+        const { bucketName, objectKey, uploadId, numberOfParts, ceremonyId } = data
+        const userId = context.auth?.uid
+
+        if (context.auth?.token.participant && !!ceremonyId) {
+            // Look for documents.
+            const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
+            const participantDoc = await firestore
+                .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
+                .doc(userId!)
+                .get()
+
+            if (!ceremonyDoc.exists || !participantDoc.exists)
+                logMsg(GENERIC_ERRORS.GENERR_INVALID_DOCUMENTS, MsgType.ERROR)
+
+            // Get data from docs.
+            const ceremonyData = ceremonyDoc.data()
+            const participantData = participantDoc.data()
+
+            if (!ceremonyData || !participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
+
+            logMsg(`Ceremony document ${ceremonyDoc.id} okay`, MsgType.DEBUG)
+            logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
+
+            // Check participant status and contribution step.
+            const { status, contributionStep } = participantData!
+
+            if (status !== ParticipantStatus.CONTRIBUTING && contributionStep !== ParticipantContributionStep.UPLOADING)
+                logMsg(
+                    `Participant ${participantDoc.id} is not able to start a multi part upload right now`,
+                    MsgType.ERROR
+                )
+        }
+
+        // Connect w/ S3.
+        const S3 = await getS3Client()
+
+        const parts = []
+
+        for (let i = 0; i < numberOfParts; i += 1) {
+            // Prepare command for each part.
+            const command = new UploadPartCommand({
+                Bucket: bucketName,
+                Key: objectKey,
+                PartNumber: i + 1,
+                UploadId: uploadId
+            })
+
+            // Get the PreSignedUrl for uploading the specific part.
+            const signedUrl = await getSignedUrl(S3, command, {
+                expiresIn: Number(process.env.AWS_PRESIGNED_URL_EXPIRATION!)
+            })
+
+            parts.push(signedUrl)
+        }
+
+        return parts
+    }
+)
+
+/**
+ * Ultimate the multi part upload for a specific object in AWS S3 bucket.
+ */
+export const completeMultiPartUpload = functions.https.onCall(
+    async (data: any, context: functions.https.CallableContext): Promise<any> => {
+        if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
+            logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
+
+        if (
+            !data.bucketName ||
+            !data.objectKey ||
+            !data.uploadId ||
+            !data.parts ||
+            (context.auth?.token.participant && !data.ceremonyId)
+        )
+            logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
+
+        // Get DB.
+        const firestore = admin.firestore()
+
+        // Get data.
+        const { bucketName, objectKey, uploadId, parts, ceremonyId } = data
+        const userId = context.auth?.uid
+
+        if (context.auth?.token.participant && !!ceremonyId) {
+            // Look for documents.
+            const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
+            const participantDoc = await firestore
+                .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
+                .doc(userId!)
+                .get()
+
+            if (!ceremonyDoc.exists || !participantDoc.exists)
+                logMsg(GENERIC_ERRORS.GENERR_INVALID_DOCUMENTS, MsgType.ERROR)
+
+            // Get data from docs.
+            const ceremonyData = ceremonyDoc.data()
+            const participantData = participantDoc.data()
+
+            if (!ceremonyData || !participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
+
+            logMsg(`Ceremony document ${ceremonyDoc.id} okay`, MsgType.DEBUG)
+            logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
+
+            // Check participant status and contribution step.
+            const { status, contributionStep } = participantData!
+
+            if (status !== ParticipantStatus.CONTRIBUTING && contributionStep !== ParticipantContributionStep.UPLOADING)
+                logMsg(
+                    `Participant ${participantDoc.id} is not able to start a multi part upload right now`,
+                    MsgType.ERROR
+                )
+        }
+
+        // Connect w/ S3.
+        const S3 = await getS3Client()
+
+        // Prepare command.
+        const command = new CompleteMultipartUploadCommand({
+            Bucket: bucketName,
+            Key: objectKey,
+            UploadId: uploadId,
+            MultipartUpload: { Parts: parts }
+        })
+
+        // Send command.
+        const responseComplete = await S3.send(command)
+
+        logMsg(`Upload for ${data.uploadId} completed! Object location ${responseComplete.Location}`, MsgType.LOG)
+
+        return responseComplete.Location
+    }
+)
diff --git a/packages/backend/src/functions/waitingQueue.ts b/packages/backend/src/functions/waitingQueue.ts
new file mode 100644
index 00000000..d6d2e150
--- /dev/null
+++ b/packages/backend/src/functions/waitingQueue.ts
@@ -0,0 +1,764 @@
+import * as functionsV1 from "firebase-functions/v1"
+import * as functionsV2 from "firebase-functions/v2"
+import admin from "firebase-admin"
+import dotenv from "dotenv"
+import { QueryDocumentSnapshot } from "firebase-functions/v1/firestore"
+import { Change } from "firebase-functions"
+import { zKey } from "snarkjs"
+import path from "path"
+import os from "os"
+import fs from "fs"
+import { Timer } from "timer-node"
+import blake from "blakejs"
+import winston from "winston"
+import { FieldValue } from "firebase-admin/firestore"
+import { CeremonyState, MsgType, ParticipantContributionStep, ParticipantStatus } from "../../types/index"
+import {
+    deleteObject,
+    formatZkeyIndex,
+    getCircuitDocumentByPosition,
+    getCurrentServerTimestampInMillis,
+    getS3Client,
+    sleep,
+    tempDownloadFromBucket,
+    uploadFileToBucket
+} from "../lib/utils"
+import { collections, names } from "../lib/constants"
+import { GENERIC_ERRORS, logMsg } from "../lib/logs"
+
+dotenv.config()
+
+/**
+ * Automate the coordination for participants contributions.
+ * @param circuit <QueryDocumentSnapshot> - the circuit document.
+ * @param participant <QueryDocumentSnapshot> - the participant document.
+ * @param ceremonyId <string> - the ceremony identifier.
+ */
+const coordinate = async (circuit: QueryDocumentSnapshot, participant: QueryDocumentSnapshot, ceremonyId?: string) => {
+    // Get DB.
+    const firestore = admin.firestore()
+    // Update DB.
+    const batch = firestore.batch()
+
+    // Get info.
+    const participantId = participant.id
+    const circuitData = circuit.data()
+    const participantData = participant.data()
+
+    logMsg(`Circuit document ${circuit.id} okay`, MsgType.DEBUG)
+    logMsg(`Participant document ${participantId} okay`, MsgType.DEBUG)
+
+    const { waitingQueue } = circuitData
+    const { contributors } = waitingQueue
+    let { currentContributor } = waitingQueue
+    let newParticipantStatus = 0
+    let newContributionStep = 0
+
+    // Case 1: Participant is ready to contribute and there's nobody in the queue.
+    if (!contributors.length && !currentContributor) {
+        logMsg(
+            `Coordination use-case 1: Participant is ready to contribute and there's nobody in the queue`,
+            MsgType.INFO
+        )
+
+        currentContributor = participantId
+        newParticipantStatus = ParticipantStatus.CONTRIBUTING
+        newContributionStep = ParticipantContributionStep.DOWNLOADING
+    }
+
+    // Case 2: Participant is ready to contribute but there's another participant currently contributing.
+    if (currentContributor !== participantId) {
+        logMsg(
+            `Coordination use-case 2: Participant is ready to contribute but there's another participant currently contributing`,
+            MsgType.INFO
+        )
+
+        newParticipantStatus = ParticipantStatus.WAITING
+    }
+
+    // Case 3: the participant has finished the contribution so this case is used to update the i circuit queue.
+    if (
+        currentContributor === participantId &&
+        (participantData.status === ParticipantStatus.CONTRIBUTED ||
+            participantData.status === ParticipantStatus.DONE) &&
+        participantData.contributionStep === ParticipantContributionStep.COMPLETED
+    ) {
+        logMsg(
+            `Coordination use-case 3: Participant has finished the contribution so this case is used to update the i circuit queue`,
+            MsgType.INFO
+        )
+
+        contributors.shift(1)
+
+        if (contributors.length > 0) {
+            // There's someone else ready to contribute.
+            currentContributor = contributors.at(0)
+
+            // Pass the baton to the next participant.
+            const newCurrentContributorDoc = await firestore
+                .collection(`${ceremonyId}/${collections.participants}`)
+                .doc(currentContributor)
+                .get()
+
+            if (newCurrentContributorDoc.exists) {
+                batch.update(newCurrentContributorDoc.ref, {
+                    status: ParticipantStatus.WAITING,
+                    lastUpdated: getCurrentServerTimestampInMillis()
+                })
+
+                logMsg(`Batch update use-case 3: New current contributor`, MsgType.INFO)
+            }
+        } else currentContributor = ""
+    }
+
+    // Updates for cases 1 and 2.
+    if (newParticipantStatus !== 0) {
+        contributors.push(participantId)
+
+        batch.update(participant.ref, {
+            status: newParticipantStatus,
+            contributionStartedAt:
+                newParticipantStatus === ParticipantStatus.CONTRIBUTING ? getCurrentServerTimestampInMillis() : 0,
+            lastUpdated: getCurrentServerTimestampInMillis()
+        })
+
+        // Case 1 only.
+        if (newContributionStep !== 0)
+            batch.update(participant.ref, {
+                contributionStep: newContributionStep,
+                lastUpdated: getCurrentServerTimestampInMillis()
+            })
+
+        logMsg(`Batch update use-case 1 or 2: participant updates`, MsgType.INFO)
+    }
+
+    // Update waiting queue.
+    batch.update(circuit.ref, {
+        waitingQueue: {
+            ...waitingQueue,
+            contributors,
+            currentContributor
+        },
+        lastUpdated: getCurrentServerTimestampInMillis()
+    })
+
+    logMsg(`Batch update all use-cases: update circuit waiting queue`, MsgType.INFO)
+
+    await batch.commit()
+}
+
+/**
+ * Coordinate waiting queue contributors.
+ */
+export const coordinateContributors = functionsV1.firestore
+    .document(`${collections.ceremonies}/{ceremonyId}/${collections.participants}/{participantId}`)
+    .onUpdate(async (change: Change<QueryDocumentSnapshot>) => {
+        // Before changes.
+        const participantBefore = change.before
+        const dataBefore = participantBefore.data()
+        const {
+            contributionProgress: beforeContributionProgress,
+            status: beforeStatus,
+            contributionStep: beforeContributionStep
+        } = dataBefore
+
+        // After changes.
+        const participantAfter = change.after
+        const dataAfter = participantAfter.data()
+        const {
+            contributionProgress: afterContributionProgress,
+            status: afterStatus,
+            contributionStep: afterContributionStep
+        } = dataAfter
+
+        // Get the ceremony identifier (this does not change from before/after).
+        const ceremonyId = participantBefore.ref.parent.parent!.path
+
+        if (!ceremonyId) logMsg(GENERIC_ERRORS.GENERR_NO_CEREMONY_PROVIDED, MsgType.ERROR)
+
+        logMsg(`Coordinating participants for ceremony ${ceremonyId}`, MsgType.INFO)
+
+        logMsg(`Participant document ${participantBefore.id} okay`, MsgType.DEBUG)
+        logMsg(`Participant document ${participantAfter.id} okay`, MsgType.DEBUG)
+        logMsg(
+            `Participant ${participantBefore.id} the status from ${beforeStatus} to ${afterStatus} and the contribution progress from ${beforeContributionProgress} to ${afterContributionProgress}`,
+            MsgType.INFO
+        )
+
+        // nb. existance checked above.
+        const circuitsPath = `${participantBefore.ref.parent.parent!.path}/${collections.circuits}`
+
+        // When a participant changes is status to ready, is "ready" to become a contributor.
+        if (afterStatus === ParticipantStatus.READY) {
+            // When beforeContributionProgress === 0 is a new participant, when beforeContributionProgress === afterContributionProgress the participant is retrying.
+            if (beforeContributionProgress === 0 || beforeContributionProgress === afterContributionProgress) {
+                logMsg(
+                    `Participant has status READY and before contribution progress ${beforeContributionProgress} is different from after contribution progress ${afterContributionProgress}`,
+                    MsgType.INFO
+                )
+
+                // i -> k where i == 0
+                // (participant newly created). We work only on circuit k.
+                const circuit = await getCircuitDocumentByPosition(circuitsPath, afterContributionProgress)
+
+                logMsg(`Circuit document ${circuit.id} okay`, MsgType.DEBUG)
+
+                // The circuit info (i.e., the queue) is useful only to check turns for contribution.
+                // The participant info is useful to really pass the baton (starting the contribution).
+                // So, the info on the circuit says "it's your turn" while the info on the participant says "okay, i'm ready/waiting etc.".
+                // The contribution progress number completes everything because indicates which circuit is involved.
+                await coordinate(circuit, participantAfter)
+                logMsg(`Circuit ${circuit.id} has been updated (waiting queue)`, MsgType.INFO)
+            }
+
+            if (afterContributionProgress === beforeContributionProgress + 1 && beforeContributionProgress !== 0) {
+                logMsg(
+                    `Participant has status READY and before contribution progress ${beforeContributionProgress} is different from before contribution progress ${afterContributionProgress}`,
+                    MsgType.INFO
+                )
+
+                // i -> k where k === i + 1
+                // (participant has already contributed to i and the contribution has been verified,
+                // participant now is ready to be put in line for contributing on k circuit).
+
+                const afterCircuit = await getCircuitDocumentByPosition(circuitsPath, afterContributionProgress)
+
+                // logMsg(`Circuit document ${beforeCircuit.id} okay`, MsgType.DEBUG)
+                logMsg(`Circuit document ${afterCircuit.id} okay`, MsgType.DEBUG)
+
+                // Coordinate after circuit (update waiting queue).
+                await coordinate(afterCircuit, participantAfter)
+                logMsg(`After circuit ${afterCircuit.id} has been updated (waiting queue)`, MsgType.INFO)
+            }
+        }
+
+        // The contributor has finished the contribution and the waiting queue for the circuit needs to be updated.
+        if (
+            (afterStatus === ParticipantStatus.DONE && beforeStatus !== ParticipantStatus.DONE) ||
+            (beforeContributionProgress === afterContributionProgress &&
+                afterStatus === ParticipantStatus.CONTRIBUTED &&
+                beforeStatus === ParticipantStatus.CONTRIBUTING &&
+                beforeContributionStep === ParticipantContributionStep.VERIFYING &&
+                afterContributionStep === ParticipantContributionStep.COMPLETED)
+        ) {
+            logMsg(`Participant has status DONE or has finished the contribution`, MsgType.INFO)
+
+            // Update the last circuits waiting queue.
+            const beforeCircuit = await getCircuitDocumentByPosition(circuitsPath, beforeContributionProgress)
+
+            logMsg(`Circuit document ${beforeCircuit.id} okay`, MsgType.DEBUG)
+
+            // Coordinate before circuit (update waiting queue + pass the baton to the next).
+            await coordinate(beforeCircuit, participantAfter, ceremonyId)
+            logMsg(
+                `Before circuit ${beforeCircuit.id} has been updated (waiting queue + pass the baton to next)`,
+                MsgType.INFO
+            )
+        }
+    })
+
+/**
+ * Automate the contribution verification.
+ */
+export const verifycontribution = functionsV2.https.onCall(
+    { memory: "32GiB", cpu: 8, timeoutSeconds: 3600 },
+    async (request: functionsV2.https.CallableRequest<any>): Promise<any> => {
+        const verifyCloudFunctionTimer = new Timer({ label: "verifyCloudFunction" })
+        verifyCloudFunctionTimer.start()
+
+        if (!request.auth || (!request.auth.token.participant && !request.auth.token.coordinator))
+            logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
+
+        if (!request.data.ceremonyId || !request.data.circuitId || !request.data.ghUsername || !request.data.bucketName)
+            logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
+
+        // Get DB.
+        const firestore = admin.firestore()
+
+        // Get Storage.
+        const S3 = await getS3Client()
+
+        // Get data.
+        const { ceremonyId, circuitId, ghUsername, bucketName } = request.data
+        const userId = request.auth?.uid
+
+        // Look for documents.
+        const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
+        const circuitDoc = await firestore
+            .collection(`${collections.ceremonies}/${ceremonyId}/${collections.circuits}`)
+            .doc(circuitId)
+            .get()
+        const participantDoc = await firestore
+            .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
+            .doc(userId!)
+            .get()
+
+        if (!ceremonyDoc.exists || !circuitDoc.exists || !participantDoc.exists)
+            logMsg(GENERIC_ERRORS.GENERR_INVALID_DOCUMENTS, MsgType.ERROR)
+
+        // Get data from docs.
+        const ceremonyData = ceremonyDoc.data()
+        const circuitData = circuitDoc.data()
+        const participantData = participantDoc.data()
+
+        if (!ceremonyData || !circuitData || !participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
+
+        logMsg(`Ceremony document ${ceremonyDoc.id} okay`, MsgType.DEBUG)
+        logMsg(`Circuit document ${circuitDoc.id} okay`, MsgType.DEBUG)
+        logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
+
+        let valid = false
+        let verificationComputationTime = 0
+        let fullContributionTime = 0
+
+        // Check if is the verification for ceremony finalization.
+        const finalize = ceremonyData?.state === CeremonyState.CLOSED && request.auth && request.auth.token.coordinator
+
+        if (participantData?.status === ParticipantStatus.CONTRIBUTING || finalize) {
+            // Compute last zkey index.
+            const lastZkeyIndex = formatZkeyIndex(circuitData!.waitingQueue.completedContributions + 1)
+
+            // Reconstruct transcript path.
+            const transcriptFilename = `${circuitData?.prefix}_${
+                finalize
+                    ? `${ghUsername}_final_verification_transcript.log`
+                    : `${lastZkeyIndex}_${ghUsername}_verification_transcript.log`
+            }`
+            const transcriptStoragePath = `${collections.circuits}/${circuitData?.prefix}/${names.transcripts}/${transcriptFilename}`
+            const transcriptTempFilePath = path.join(os.tmpdir(), transcriptFilename)
+
+            // Custom logger for verification transcript.
+            const transcriptLogger = winston.createLogger({
+                level: "info",
+                format: winston.format.printf((log) => log.message),
+                transports: [
+                    // Write all logs with importance level of `info` to `transcript.json`.
+                    new winston.transports.File({
+                        filename: transcriptTempFilePath,
+                        level: "info"
+                    })
+                ]
+            })
+
+            transcriptLogger.info(
+                `${finalize ? `Final verification` : `Verification`} transcript for ${
+                    circuitData?.prefix
+                } circuit Phase 2 contribution.\n${
+                    finalize ? `Coordinator ` : `Contributor # ${Number(lastZkeyIndex)}`
+                } (${ghUsername})\n`
+            )
+
+            // Get storage paths.
+            const potStoragePath = `${names.pot}/${circuitData?.files.potFilename}`
+            const firstZkeyStoragePath = `${collections.circuits}/${circuitData?.prefix}/${collections.contributions}/${circuitData?.prefix}_00000.zkey`
+            const lastZkeyStoragePath = `${collections.circuits}/${circuitData?.prefix}/${collections.contributions}/${
+                circuitData?.prefix
+            }_${finalize ? `final` : lastZkeyIndex}.zkey`
+
+            // Temporary store files from bucket.
+            const { potFilename } = circuitData!.files
+            const firstZkeyFilename = `${circuitData?.prefix}_00000.zkey`
+            const lastZkeyFilename = `${circuitData?.prefix}_${finalize ? `final` : lastZkeyIndex}.zkey`
+
+            const potTempFilePath = path.join(os.tmpdir(), potFilename)
+            const firstZkeyTempFilePath = path.join(os.tmpdir(), firstZkeyFilename)
+            const lastZkeyTempFilePath = path.join(os.tmpdir(), lastZkeyFilename)
+
+            // Download from AWS S3 bucket.
+            await tempDownloadFromBucket(S3, bucketName, potStoragePath, potTempFilePath)
+            logMsg(`${potStoragePath} downloaded`, MsgType.DEBUG)
+
+            await tempDownloadFromBucket(S3, bucketName, firstZkeyStoragePath, firstZkeyTempFilePath)
+            logMsg(`${firstZkeyStoragePath} downloaded`, MsgType.DEBUG)
+
+            await tempDownloadFromBucket(S3, bucketName, lastZkeyStoragePath, lastZkeyTempFilePath)
+            logMsg(`${lastZkeyStoragePath} downloaded`, MsgType.DEBUG)
+
+            logMsg(`Downloads from storage completed`, MsgType.INFO)
+
+            // Verify contribution.
+            const verificationComputationTimer = new Timer({ label: "verificationComputation" })
+            verificationComputationTimer.start()
+
+            valid = await zKey.verifyFromInit(
+                firstZkeyTempFilePath,
+                potTempFilePath,
+                lastZkeyTempFilePath,
+                transcriptLogger
+            )
+
+            verificationComputationTimer.stop()
+
+            verificationComputationTime = verificationComputationTimer.ms()
+
+            // Compute blake2b hash before unlink.
+            const lastZkeyBuffer = fs.readFileSync(lastZkeyTempFilePath)
+            const lastZkeyBlake2bHash = blake.blake2bHex(lastZkeyBuffer)
+
+            // Unlink folders.
+            fs.unlinkSync(potTempFilePath)
+            fs.unlinkSync(firstZkeyTempFilePath)
+            fs.unlinkSync(lastZkeyTempFilePath)
+
+            logMsg(`Contribution is ${valid ? `valid` : `invalid`}`, MsgType.INFO)
+            logMsg(`Verification computation time ${verificationComputationTime} ms`, MsgType.INFO)
+
+            // Update DB.
+            const batch = firestore.batch()
+
+            // Contribution.
+            const contributionDoc = await firestore
+                .collection(
+                    `${collections.ceremonies}/${ceremonyId}/${collections.circuits}/${circuitId}/${collections.contributions}`
+                )
+                .doc()
+                .get()
+
+            if (valid) {
+                // Sleep ~5 seconds to wait for verification transcription.
+                await sleep(5000)
+
+                // Upload transcript (small file - multipart upload not required).
+                await uploadFileToBucket(S3, bucketName, transcriptStoragePath, transcriptTempFilePath)
+
+                // Compute blake2b hash.
+                const transcriptBuffer = fs.readFileSync(transcriptTempFilePath)
+                const transcriptBlake2bHash = blake.blake2bHex(transcriptBuffer)
+
+                fs.unlinkSync(transcriptTempFilePath)
+
+                // Get contribution computation time.
+                const contributions = participantData?.contributions.filter(
+                    (contribution: { hash: string; doc: string; computationTime: number }) =>
+                        !!contribution.hash && !!contribution.computationTime && !contribution.doc
+                )
+
+                if (contributions.length !== 1)
+                    logMsg(`There should be only one contribution without a doc link`, MsgType.ERROR)
+
+                const contributionComputationTime = contributions[0].computationTime
+
+                // Update only when coordinator is finalizing the ceremony.
+                batch.create(contributionDoc.ref, {
+                    participantId: participantDoc.id,
+                    contributionComputationTime,
+                    verificationComputationTime,
+                    zkeyIndex: finalize ? `final` : lastZkeyIndex,
+                    files: {
+                        transcriptFilename,
+                        lastZkeyFilename,
+                        transcriptStoragePath,
+                        lastZkeyStoragePath,
+                        transcriptBlake2bHash,
+                        lastZkeyBlake2bHash
+                    },
+                    valid,
+                    lastUpdated: getCurrentServerTimestampInMillis()
+                })
+
+                logMsg(`Batch: create contribution document`, MsgType.DEBUG)
+
+                verifyCloudFunctionTimer.stop()
+                const verifyCloudFunctionTime = verifyCloudFunctionTimer.ms()
+
+                if (!finalize) {
+                    // Circuit.
+                    const { completedContributions, failedContributions } = circuitData!.waitingQueue
+                    const {
+                        contributionComputation: avgContributionComputation,
+                        fullContribution: avgFullContribution,
+                        verifyCloudFunction: avgVerifyCloudFunction
+                    } = circuitData!.avgTimings
+
+                    logMsg(
+                        `Current average full contribution (down + comp + up) time ${avgFullContribution} ms`,
+                        MsgType.INFO
+                    )
+                    logMsg(`Current verify cloud function time ${avgVerifyCloudFunction} ms`, MsgType.INFO)
+
+                    // Calculate full contribution time.
+                    fullContributionTime =
+                        Number(participantData?.verificationStartedAt) - Number(participantData?.contributionStartedAt)
+
+                    // Update avg timings.
+                    const newAvgContributionComputationTime =
+                        avgContributionComputation > 0
+                            ? (avgContributionComputation + contributionComputationTime) / 2
+                            : contributionComputationTime
+                    const newAvgFullContributionTime =
+                        avgFullContribution > 0
+                            ? (avgFullContribution + fullContributionTime) / 2
+                            : fullContributionTime
+                    const newAvgVerifyCloudFunctionTime =
+                        avgVerifyCloudFunction > 0
+                            ? (avgVerifyCloudFunction + verifyCloudFunctionTime) / 2
+                            : verifyCloudFunctionTime
+
+                    logMsg(
+                        `New average contribution computation time ${newAvgContributionComputationTime} ms`,
+                        MsgType.INFO
+                    )
+                    logMsg(
+                        `New average full contribution (down + comp + up) time ${newAvgFullContributionTime} ms`,
+                        MsgType.INFO
+                    )
+                    logMsg(`New verify cloud function time ${newAvgVerifyCloudFunctionTime} ms`, MsgType.INFO)
+
+                    batch.update(circuitDoc.ref, {
+                        avgTimings: {
+                            contributionComputation: valid
+                                ? newAvgContributionComputationTime
+                                : contributionComputationTime,
+                            fullContribution: valid ? newAvgFullContributionTime : fullContributionTime,
+                            verifyCloudFunction: valid ? newAvgVerifyCloudFunctionTime : verifyCloudFunctionTime
+                        },
+                        waitingQueue: {
+                            ...circuitData?.waitingQueue,
+                            completedContributions: valid ? completedContributions + 1 : completedContributions,
+                            failedContributions: valid ? failedContributions : failedContributions + 1
+                        },
+                        lastUpdated: getCurrentServerTimestampInMillis()
+                    })
+                }
+
+                logMsg(`Batch: update timings and waiting queue for circuit`, MsgType.DEBUG)
+
+                await batch.commit()
+            } else {
+                // Delete invalid contribution from storage.
+                await deleteObject(S3, bucketName, lastZkeyStoragePath)
+
+                // Unlink transcript temp file.
+                fs.unlinkSync(transcriptTempFilePath)
+
+                // Create a new contribution doc without files.
+                batch.create(contributionDoc.ref, {
+                    participantId: participantDoc.id,
+                    verificationComputationTime,
+                    zkeyIndex: finalize ? `final` : lastZkeyIndex,
+                    valid,
+                    lastUpdated: getCurrentServerTimestampInMillis()
+                })
+
+                logMsg(`Batch: create invalid contribution document`, MsgType.DEBUG)
+
+                if (!finalize) {
+                    const { failedContributions } = circuitData!.waitingQueue
+
+                    // Update the failed contributions.
+                    batch.update(circuitDoc.ref, {
+                        waitingQueue: {
+                            ...circuitData?.waitingQueue,
+                            failedContributions: failedContributions + 1
+                        },
+                        lastUpdated: getCurrentServerTimestampInMillis()
+                    })
+                }
+                logMsg(`Batch: update invalid contributions counter`, MsgType.DEBUG)
+
+                await batch.commit()
+            }
+        }
+
+        logMsg(
+            `Participant ${userId} has verified the contribution #${participantData?.contributionProgress}`,
+            MsgType.INFO
+        )
+        logMsg(
+            `Returned values: valid ${valid} - verificationComputationTime ${verificationComputationTime}`,
+            MsgType.INFO
+        )
+
+        return {
+            valid,
+            fullContributionTime,
+            verifyCloudFunctionTime: verifyCloudFunctionTimer.ms()
+        }
+    }
+)
+
+/**
+ * Update the participant document after a contribution.
+ */
+export const refreshParticipantAfterContributionVerification = functionsV1.firestore
+    .document(
+        `/${collections.ceremonies}/{ceremony}/${collections.circuits}/{circuit}/${collections.contributions}/{contributions}`
+    )
+    .onCreate(async (doc: QueryDocumentSnapshot) => {
+        // Get DB.
+        const firestore = admin.firestore()
+
+        // Get doc info.
+        const contributionId = doc.id
+        const contributionData = doc.data()
+        const ceremonyCircuitsCollectionPath = doc.ref.parent.parent?.parent?.path // == /ceremonies/{ceremony}/circuits/.
+        const ceremonyParticipantsCollectionPath = `${doc.ref.parent.parent?.parent?.parent?.path}/${collections.participants}` // == /ceremonies/{ceremony}/participants.
+
+        if (!ceremonyCircuitsCollectionPath || !ceremonyParticipantsCollectionPath)
+            logMsg(GENERIC_ERRORS.GENERR_WRONG_PATHS, MsgType.ERROR)
+
+        // Looks for documents.
+        const circuits = await firestore.collection(ceremonyCircuitsCollectionPath!).listDocuments()
+        const participantDoc = await firestore
+            .collection(ceremonyParticipantsCollectionPath)
+            .doc(contributionData.participantId)
+            .get()
+
+        if (!participantDoc.exists) logMsg(GENERIC_ERRORS.GENERR_INVALID_DOCUMENTS, MsgType.ERROR)
+
+        // Get data.
+        const participantData = participantDoc.data()
+
+        if (!participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
+
+        logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
+
+        const participantContributions = participantData?.contributions
+
+        // Update the only one contribution with missing doc (i.e., the last one).
+        participantContributions.forEach(
+            (participantContribution: { hash: string; doc: string; computationTime: number }) => {
+                if (
+                    !!participantContribution.hash &&
+                    !!participantContribution.computationTime &&
+                    !participantContribution.doc
+                ) {
+                    participantContribution.doc = contributionId
+                }
+            }
+        )
+
+        // Don't update the participant status and progress when finalizing.
+        if (participantData!.status !== ParticipantStatus.FINALIZING) {
+            const newStatus =
+                participantData!.contributionProgress + 1 > circuits.length
+                    ? ParticipantStatus.DONE
+                    : ParticipantStatus.CONTRIBUTED
+
+            await firestore.collection(ceremonyParticipantsCollectionPath).doc(contributionData.participantId).set(
+                {
+                    status: newStatus,
+                    contributionStep: ParticipantContributionStep.COMPLETED,
+                    contributions: participantContributions,
+                    tempContributionData: FieldValue.delete(),
+                    lastUpdated: getCurrentServerTimestampInMillis()
+                },
+                { merge: true }
+            )
+
+            logMsg(`Participant ${contributionData.participantId} updated after contribution`, MsgType.DEBUG)
+        } else {
+            await firestore.collection(ceremonyParticipantsCollectionPath).doc(contributionData.participantId).set(
+                {
+                    contributions: participantContributions,
+                    lastUpdated: getCurrentServerTimestampInMillis()
+                },
+                { merge: true }
+            )
+
+            logMsg(`Coordinator ${contributionData.participantId} updated after final contribution`, MsgType.DEBUG)
+        }
+    })
+
+/**
+ * Make the progress to next contribution after successfully verified the contribution.
+ */
+export const makeProgressToNextContribution = functionsV1.https.onCall(
+    async (data: any, context: functionsV1.https.CallableContext): Promise<any> => {
+        if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
+            logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
+
+        if (!data.ceremonyId) logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
+
+        // Get DB.
+        const firestore = admin.firestore()
+
+        // Get data.
+        const { ceremonyId } = data
+        const userId = context.auth?.uid
+
+        // Look for documents.
+        const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
+        const participantDoc = await firestore
+            .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
+            .doc(userId!)
+            .get()
+
+        if (!ceremonyDoc.exists || !participantDoc.exists)
+            logMsg(GENERIC_ERRORS.GENERR_INVALID_DOCUMENTS, MsgType.ERROR)
+
+        // Get data from docs.
+        const ceremonyData = ceremonyDoc.data()
+        const participantData = participantDoc.data()
+
+        if (!ceremonyData || !participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
+
+        logMsg(`Ceremony document ${ceremonyDoc.id} okay`, MsgType.DEBUG)
+        logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
+
+        const { contributionProgress, contributionStep, status } = participantData!
+
+        // Check for contribution completion here.
+        if (contributionStep !== ParticipantContributionStep.COMPLETED && status !== ParticipantStatus.WAITING)
+            logMsg(`Cannot progress!`, MsgType.ERROR)
+
+        await participantDoc.ref.update({
+            contributionProgress: contributionProgress + 1,
+            status: ParticipantStatus.READY,
+            lastUpdated: getCurrentServerTimestampInMillis()
+        })
+
+        logMsg(`Participant ${userId} progressed to ${contributionProgress + 1}`, MsgType.DEBUG)
+    }
+)
+
+/**
+ * Resume a contribution after a timeout expiration.
+ */
+export const resumeContributionAfterTimeoutExpiration = functionsV1.https.onCall(
+    async (data: any, context: functionsV1.https.CallableContext): Promise<any> => {
+        if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
+            logMsg(GENERIC_ERRORS.GENERR_NO_AUTH_USER_FOUND, MsgType.ERROR)
+
+        if (!data.ceremonyId) logMsg(GENERIC_ERRORS.GENERR_MISSING_INPUT, MsgType.ERROR)
+
+        // Get DB.
+        const firestore = admin.firestore()
+
+        // Get data.
+        const { ceremonyId } = data
+        const userId = context.auth?.uid
+
+        // Look for documents.
+        const ceremonyDoc = await firestore.collection(collections.ceremonies).doc(ceremonyId).get()
+        const participantDoc = await firestore
+            .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
+            .doc(userId!)
+            .get()
+
+        if (!ceremonyDoc.exists || !participantDoc.exists)
+            logMsg(GENERIC_ERRORS.GENERR_INVALID_DOCUMENTS, MsgType.ERROR)
+
+        // Get data from docs.
+        const ceremonyData = ceremonyDoc.data()
+        const participantData = participantDoc.data()
+
+        if (!ceremonyData || !participantData) logMsg(GENERIC_ERRORS.GENERR_NO_DATA, MsgType.ERROR)
+
+        logMsg(`Ceremony document ${ceremonyDoc.id} okay`, MsgType.DEBUG)
+        logMsg(`Participant document ${participantDoc.id} okay`, MsgType.DEBUG)
+
+        const { contributionProgress, status } = participantData!
+
+        // Check if can resume.
+        if (status !== ParticipantStatus.EXHUMED)
+            logMsg(`Cannot resume the contribution after a timeout expiration`, MsgType.ERROR)
+
+        await participantDoc.ref.update({
+            status: ParticipantStatus.READY,
+            lastUpdated: getCurrentServerTimestampInMillis()
+        })
+
+        logMsg(`Participant ${userId} has resumed the contribution for circuit ${contributionProgress}`, MsgType.DEBUG)
+    }
+)
diff --git a/packages/backend/src/lib/constants.ts b/packages/backend/src/lib/constants.ts
new file mode 100644
index 00000000..bb4ee804
--- /dev/null
+++ b/packages/backend/src/lib/constants.ts
@@ -0,0 +1,47 @@
+/** Firebase */
+export const collections = {
+    users: "users",
+    participants: "participants",
+    ceremonies: "ceremonies",
+    circuits: "circuits",
+    contributions: "contributions",
+    timeouts: "timeouts"
+}
+
+export const names = {
+    output: `output`,
+    setup: `setup`,
+    contribute: `contribute`,
+    pot: `pot`,
+    zkeys: `zkeys`,
+    metadata: `metadata`,
+    transcripts: `transcripts`,
+    attestation: `attestation`
+}
+
+export const ceremoniesCollectionFields = {
+    coordinatorId: "coordinatorId",
+    description: "description",
+    endDate: "endDate",
+    lastUpdated: "lastUpdated",
+    prefix: "prefix",
+    startDate: "startDate",
+    state: "state",
+    title: "title",
+    type: "type"
+}
+
+export const contributionsCollectionFields = {
+    contributionTime: "contributionTime",
+    files: "files",
+    lastUpdated: "lastUpdated",
+    participantId: "participantId",
+    valid: "valid",
+    verificationTime: "verificationTime",
+    zkeyIndex: "zKeyIndex"
+}
+
+export const timeoutsCollectionFields = {
+    endDate: "endDate",
+    startDate: "startDate"
+}
diff --git a/packages/backend/src/lib/logs.ts b/packages/backend/src/lib/logs.ts
new file mode 100644
index 00000000..969de607
--- /dev/null
+++ b/packages/backend/src/lib/logs.ts
@@ -0,0 +1,69 @@
+import * as functions from "firebase-functions"
+import { MsgType } from "../../types/index"
+
+export const GENERIC_ERRORS = {
+    GENERR_MISSING_INPUT: `You have not provided all the necessary data`,
+    GENERR_NO_AUTH_USER_FOUND: `The given id does not belong to an authenticated user`,
+    GENERR_NO_COORDINATOR: `The given id does not belong to a coordinator`,
+    GENERR_NO_CEREMONY_PROVIDED: `No ceremony has been provided`,
+    GENERR_NO_CIRCUIT_PROVIDED: `No circuit has been provided`,
+    GENERR_NO_CEREMONIES_OPENED: `No ceremonies are opened to contributions`,
+    GENERR_INVALID_CEREMONY: `The given ceremony is invalid`,
+    GENERR_INVALID_CIRCUIT: `The given circuit is invalid`,
+    GENERR_INVALID_PARTICIPANT: `The given participant is invalid`,
+    GENERR_CEREMONY_NOT_OPENED: `The given ceremony is not opened to contributions`,
+    GENERR_CEREMONY_NOT_CLOSED: `The given ceremony is not closed for finalization`,
+    GENERR_INVALID_PARTICIPANT_STATUS: `The participant has an invalid status`,
+    GENERR_INVALID_PARTICIPANT_CONTRIBUTION_STEP: `The participant has an invalid contribution step`,
+    GENERR_INVALID_CONTRIBUTION_PROGRESS: `The contribution progress is invalid`,
+    GENERR_INVALID_DOCUMENTS: `One or more provided identifier does not belong to a document`,
+    GENERR_NO_DATA: `Data not found`,
+    GENERR_NO_CIRCUIT: `Circuits not found`,
+    GENERR_NO_PARTICIPANT: `Participant not found`,
+    GENERR_NO_CONTRIBUTION: `Contributions not found`,
+    GENERR_NO_CURRENT_CONTRIBUTOR: `There is no current contributor for the circuit`,
+    GENERR_NO_TIMEOUT_FIRST_COTRIBUTOR: `Cannot compute a dynamic timeout for the first contributor`,
+    GENERR_NO_CIRCUITS: `Circuits not found for the ceremony`,
+    GENERR_NO_CONTRIBUTIONS: `Contributions not found for the circuit`,
+    GENERR_NO_RETRY: `The retry waiting time has not passed away yet`,
+    GENERR_WRONG_PATHS: `Wrong storage or database paths`,
+    GENERR_WRONG_FIELD: `Wrong document field`,
+    GENERR_WRONG_ENV_CONFIGURATION: `Your environment variables are not configured properly`
+}
+
+export const GENERIC_LOGS = {
+    GENLOG_NO_CEREMONIES_READY_TO_BE_OPENED: `There are no cerimonies ready to be opened to contributions`,
+    GENLOG_NO_CEREMONIES_READY_TO_BE_CLOSED: `There are no cerimonies ready to be closed`,
+    GENLOG_NO_CURRENT_CONTRIBUTOR: `There is no current contributor for the circuit`,
+    GENLOG_NO_TIMEOUT: `The timeout must not be triggered yet`
+}
+
+/**
+ * Print a message customizing the default logger.
+ * @param msg <string> - the msg to be shown.
+ * @param msgType <MsgType> - the type of the message (e.g., debug, error).
+ */
+export const logMsg = (msg: string, msgType: MsgType) => {
+    switch (msgType) {
+        case MsgType.INFO:
+            functions.logger.info(`[INFO] ${msg}`)
+            break
+        case MsgType.DEBUG:
+            functions.logger.debug(`[DEBUG] ${msg}`)
+            break
+        case MsgType.WARN:
+            functions.logger.warn(`[WARN] ${msg}`)
+            break
+        case MsgType.ERROR: {
+            functions.logger.error(`[ERROR] ${msg}`)
+            process.exit(0)
+        }
+        // eslint-disable-next-line
+        case MsgType.LOG:
+            functions.logger.log(`[LOG] ${msg}`)
+            break
+        default:
+            console.log(`[LOG] ${msg}`)
+            break
+    }
+}
diff --git a/packages/backend/src/lib/utils.ts b/packages/backend/src/lib/utils.ts
new file mode 100644
index 00000000..1bc63b0d
--- /dev/null
+++ b/packages/backend/src/lib/utils.ts
@@ -0,0 +1,324 @@
+import { DocumentData, DocumentSnapshot, Timestamp, WhereFilterOp } from "firebase-admin/firestore"
+import admin from "firebase-admin"
+import * as functions from "firebase-functions"
+import dotenv from "dotenv"
+import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3"
+import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
+import { createWriteStream } from "node:fs"
+import { pipeline } from "node:stream"
+import { promisify } from "node:util"
+import { readFileSync } from "fs"
+import fetch from "node-fetch"
+import mime from "mime-types"
+import { setTimeout } from "timers/promises"
+import { GENERIC_ERRORS, logMsg } from "./logs"
+import { ceremoniesCollectionFields, collections, timeoutsCollectionFields } from "./constants"
+import { CeremonyState, MsgType } from "../../types/index"
+
+dotenv.config()
+
+/**
+ * Return the current server timestamp in milliseconds.
+ * @returns <number>
+ */
+export const getCurrentServerTimestampInMillis = (): number => Timestamp.now().toMillis()
+
+/**
+ * Query ceremonies by state and (start/end) date value.
+ * @param state <CeremonyState> - the value of the state to be queried.
+ * @param dateField <string> - the start or end date field.
+ * @param check <WhereFilerOp> - the query filter (where check).
+ * @returns <Promise<admin.firestore.QuerySnapshot<admin.firestore.DocumentData>>>
+ */
+export const queryCeremoniesByStateAndDate = async (
+    state: CeremonyState,
+    dateField: string,
+    check: WhereFilterOp
+): Promise<admin.firestore.QuerySnapshot<admin.firestore.DocumentData>> => {
+    // Get DB.
+    const firestoreDb = admin.firestore()
+
+    if (dateField !== ceremoniesCollectionFields.startDate && dateField !== ceremoniesCollectionFields.endDate)
+        logMsg(GENERIC_ERRORS.GENERR_WRONG_FIELD, MsgType.ERROR)
+
+    return firestoreDb
+        .collection(collections.ceremonies)
+        .where(ceremoniesCollectionFields.state, "==", state)
+        .where(dateField, check, getCurrentServerTimestampInMillis())
+        .get()
+}
+
+/**
+ * Query timeouts by (start/end) date value.
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
+ * @param participantId <string> - the unique identifier of the participant.
+ * @param dateField <string> - the name of the date field.
+ * @returns <Promise<admin.firestore.QuerySnapshot<admin.firestore.DocumentData>>>
+ */
+export const queryValidTimeoutsByDate = async (
+    ceremonyId: string,
+    participantId: string,
+    dateField: string
+): Promise<admin.firestore.QuerySnapshot<admin.firestore.DocumentData>> => {
+    // Get DB.
+    const firestoreDb = admin.firestore()
+
+    if (dateField !== timeoutsCollectionFields.startDate && dateField !== timeoutsCollectionFields.endDate)
+        logMsg(GENERIC_ERRORS.GENERR_WRONG_FIELD, MsgType.ERROR)
+
+    return firestoreDb
+        .collection(
+            `${collections.ceremonies}/${ceremonyId}/${collections.participants}/${participantId}/${collections.timeouts}`
+        )
+        .where(dateField, ">=", getCurrentServerTimestampInMillis())
+        .get()
+}
+
+/**
+ * Return the document belonging to a participant with a specified id (if exist).
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
+ * @param participantId <string> - the unique identifier of the participant.
+ * @returns <Promise<DocumentSnapshot<DocumentData>>>
+ */
+export const getParticipantById = async (
+    ceremonyId: string,
+    participantId: string
+): Promise<DocumentSnapshot<DocumentData>> => {
+    // Get DB.
+    const firestore = admin.firestore()
+
+    const participantDoc = await firestore
+        .collection(`${collections.ceremonies}/${ceremonyId}/${collections.participants}`)
+        .doc(participantId)
+        .get()
+
+    if (!participantDoc.exists) logMsg(GENERIC_ERRORS.GENERR_NO_PARTICIPANT, MsgType.ERROR)
+
+    return participantDoc
+}
+
+/**
+ * Return all circuits for a given ceremony (if any).
+ * @param circuitsPath <string> - the collection path from ceremonies to circuits.
+ * @returns Promise<Array<admin.firestore.QueryDocumentSnapshot<admin.firestore.DocumentData>>>
+ */
+export const getCeremonyCircuits = async (
+    circuitsPath: string
+): Promise<Array<admin.firestore.QueryDocumentSnapshot<admin.firestore.DocumentData>>> => {
+    // Get DB.
+    const firestore = admin.firestore()
+
+    // Query for all docs.
+    const circuitsQuerySnap = await firestore.collection(circuitsPath).get()
+    const circuitDocs = circuitsQuerySnap.docs
+
+    if (!circuitDocs) logMsg(GENERIC_ERRORS.GENERR_NO_CIRCUITS, MsgType.ERROR)
+
+    return circuitDocs
+}
+
+/**
+ * Format the next zkey index.
+ * @param progress <number> - the progression in zkey index (= contributions).
+ * @returns <string>
+ */
+export const formatZkeyIndex = (progress: number): string => {
+    if (!process.env.FIRST_ZKEY_INDEX) logMsg(GENERIC_ERRORS.GENERR_WRONG_ENV_CONFIGURATION, MsgType.ERROR)
+
+    const initialZkeyIndex = process.env.FIRST_ZKEY_INDEX!
+
+    let index = progress.toString()
+
+    while (index.length < initialZkeyIndex.length) {
+        index = `0${index}`
+    }
+
+    return index
+}
+
+/**
+ * Get the document for the circuit of the ceremony with a given sequence position.
+ * @param circuitsPath <string> - the collection path from ceremonies to circuits.
+ * @param position <number> - the sequence position of the circuit.
+ * @returns Promise<admin.firestore.QueryDocumentSnapshot<admin.firestore.DocumentData>>
+ */
+export const getCircuitDocumentByPosition = async (
+    circuitsPath: string,
+    position: number
+): Promise<admin.firestore.QueryDocumentSnapshot<admin.firestore.DocumentData>> => {
+    // Query for all circuit docs.
+    const circuitDocs = await getCeremonyCircuits(circuitsPath)
+
+    // Filter by position.
+    const filteredCircuits = circuitDocs.filter(
+        (circuit: admin.firestore.DocumentData) => circuit.data().sequencePosition === position
+    )
+
+    if (!filteredCircuits) logMsg(GENERIC_ERRORS.GENERR_NO_CIRCUIT, MsgType.ERROR)
+
+    // Get the circuit (nb. there will be only one circuit w/ that position).
+    const circuit = filteredCircuits.at(0)
+
+    if (!circuit) logMsg(GENERIC_ERRORS.GENERR_NO_CIRCUIT, MsgType.ERROR)
+
+    functions.logger.info(`Circuit w/ UID ${circuit?.id} at position ${position}`)
+
+    return circuit!
+}
+
+/**
+ * Get the final contribution document for a specific circuit.
+ * @param contributionsPath <string> - the collection path from circuit to contributions.
+ * @returns Promise<admin.firestore.QueryDocumentSnapshot<admin.firestore.DocumentData>>
+ */
+export const getFinalContributionDocument = async (
+    contributionsPath: string
+): Promise<admin.firestore.QueryDocumentSnapshot<admin.firestore.DocumentData>> => {
+    // Get DB.
+    const firestore = admin.firestore()
+
+    // Query for all contribution docs for circuit.
+    const contributionsQuerySnap = await firestore.collection(contributionsPath).get()
+    const contributionsDocs = contributionsQuerySnap.docs
+
+    if (!contributionsDocs) logMsg(GENERIC_ERRORS.GENERR_NO_CONTRIBUTIONS, MsgType.ERROR)
+
+    // Filter by index.
+    const filteredContributions = contributionsDocs.filter(
+        (contribution: admin.firestore.DocumentData) => contribution.data().zkeyIndex === "final"
+    )
+
+    if (!filteredContributions) logMsg(GENERIC_ERRORS.GENERR_NO_CONTRIBUTION, MsgType.ERROR)
+
+    // Get the contribution (nb. there will be only one final contribution).
+    const finalContribution = filteredContributions.at(0)
+
+    if (!finalContribution) logMsg(GENERIC_ERRORS.GENERR_NO_CONTRIBUTION, MsgType.ERROR)
+
+    return finalContribution!
+}
+
+/**
+ * Return a new instance of the AWS S3 Client.
+ * @returns <Promise<S3Client>
+ */
+export const getS3Client = async (): Promise<S3Client> => {
+    if (
+        !process.env.AWS_ACCESS_KEY_ID ||
+        !process.env.AWS_SECRET_ACCESS_KEY ||
+        !process.env.AWS_REGION ||
+        !process.env.AWS_PRESIGNED_URL_EXPIRATION
+    )
+        logMsg(GENERIC_ERRORS.GENERR_WRONG_ENV_CONFIGURATION, MsgType.ERROR)
+
+    // Connect w/ S3.
+    return new S3Client({
+        credentials: {
+            accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
+            secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!
+        },
+        region: process.env.AWS_REGION!
+    })
+}
+
+/**
+ * Downloads and temporarily write a file from S3 bucket.
+ * @param client <S3Client> - the AWS S3 client.
+ * @param bucketName <string> - the name of the AWS S3 bucket.
+ * @param objectKey <string> - the location of the object in the AWS S3 bucket.
+ * @param tempFilePath <string> - the local path where the file will be written.
+ */
+export const tempDownloadFromBucket = async (
+    client: S3Client,
+    bucketName: string,
+    objectKey: string,
+    tempFilePath: string
+) => {
+    // Prepare get object command.
+    const command = new GetObjectCommand({ Bucket: bucketName, Key: objectKey })
+
+    // Get pre-signed url.
+    const url = await getSignedUrl(client, command, { expiresIn: Number(process.env.AWS_PRESIGNED_URL_EXPIRATION!) })
+
+    // Download the file.
+    const response: any = await fetch(url, {
+        method: "GET",
+        headers: {
+            "Access-Control-Allow-Origin": "*"
+        }
+    })
+
+    if (!response.ok)
+        logMsg(`Something went wrong when downloading the file from the bucket: ${response.statusText}`, MsgType.ERROR)
+
+    // Temporarily write the file.
+    const streamPipeline = promisify(pipeline)
+    await streamPipeline(response.body!, createWriteStream(tempFilePath))
+}
+
+/**
+ * Sleeps the function execution for given millis.
+ * @dev to be used in combination with loggers when writing data into files.
+ * @param ms <number> - sleep amount in milliseconds
+ * @returns <Promise<void>>
+ */
+export const sleep = async (ms: number): Promise<void> => setTimeout(ms)
+
+/**
+ * Upload a file from S3 bucket.
+ * @param client <S3Client> - the AWS S3 client.
+ * @param bucketName <string> - the name of the AWS S3 bucket.
+ * @param objectKey <string> - the location of the object in the AWS S3 bucket.
+ * @param tempFilePath <string> - the local path where the file will be written.
+ */
+export const uploadFileToBucket = async (
+    client: S3Client,
+    bucketName: string,
+    objectKey: string,
+    tempFilePath: string
+) => {
+    // Get file content type.
+    const contentType = mime.lookup(tempFilePath) || ""
+
+    // Prepare command.
+    const command = new PutObjectCommand({ Bucket: bucketName, Key: objectKey, ContentType: contentType })
+
+    // Get pre-signed url.
+    const url = await getSignedUrl(client, command, { expiresIn: Number(process.env.AWS_PRESIGNED_URL_EXPIRATION!) })
+
+    // Make upload request (PUT).
+    const uploadTranscriptResponse = await fetch(url, {
+        method: "PUT",
+        body: readFileSync(tempFilePath),
+        headers: { "Content-Type": contentType }
+    })
+
+    // Check response.
+    if (!uploadTranscriptResponse.ok)
+        logMsg(
+            `Something went wrong when uploading the transcript: ${uploadTranscriptResponse.statusText}`,
+            MsgType.ERROR
+        )
+
+    logMsg(`File uploaded successfully`, MsgType.DEBUG)
+}
+
+/**
+ * Delete a file from S3 bucket.
+ * @param client <S3Client> - the AWS S3 client.
+ * @param bucketName <string> - the name of the AWS S3 bucket.
+ * @param objectKey <string> - the location of the object in the AWS S3 bucket.
+ */
+export const deleteObject = async (client: S3Client, bucketName: string, objectKey: string) => {
+    try {
+        // Prepare command.
+        const command = new DeleteObjectCommand({ Bucket: bucketName, Key: objectKey })
+
+        // Send command.
+        const data = await client.send(command)
+
+        logMsg(`Object ${objectKey} successfully deleted: ${data.$metadata.httpStatusCode}`, MsgType.INFO)
+    } catch (error: any) {
+        logMsg(`Something went wrong while deleting the ${objectKey} object: ${error}`, MsgType.ERROR)
+    }
+}
diff --git a/apps/backend/storage.rules b/packages/backend/storage.rules
similarity index 100%
rename from apps/backend/storage.rules
rename to packages/backend/storage.rules
diff --git a/packages/backend/test/index.test.ts b/packages/backend/test/index.test.ts
new file mode 100644
index 00000000..158c750a
--- /dev/null
+++ b/packages/backend/test/index.test.ts
@@ -0,0 +1,55 @@
+import chai from "chai"
+import chaiAsPromised from "chai-as-promised"
+import admin from "firebase-admin"
+import firebaseFncTest from "firebase-functions-test"
+// Import the exported function definitions from our functions/index.js file
+import { registerAuthUser } from "../src/functions/index"
+
+// Config chai.
+chai.use(chaiAsPromised)
+const { assert } = chai
+
+// Initialize the firebase-functions-test SDK using environment variables.
+// These variables are automatically set by firebase emulators:exec
+//
+// This configuration will be used to initialize the Firebase Admin SDK, so
+// when we use the Admin SDK in the tests below we can be confident it will
+// communicate with the emulators, not production.
+const test = firebaseFncTest({
+    databaseURL: process.env.FIREBASE_FIRESTORE_DATABASE_URL,
+    storageBucket: process.env.FIREBASE_STORAGE_BUCKET
+})
+
+describe("CF Unit Tests", () => {
+    afterAll(() => {
+        test.cleanup()
+    })
+
+    it("should call an authorized CF and interact with Firestore", async () => {
+        const wrapped = test.wrap(registerAuthUser)
+
+        // Make a fake user to pass to the function
+        const uid = `${new Date().getTime()}`
+        const displayName = "UserA"
+        const email = `user-${uid}@example.com`
+        const photoURL = `https://www...."`
+
+        const user = test.auth.makeUserRecord({
+            uid,
+            displayName,
+            email,
+            photoURL
+        })
+
+        // Call the function
+        await wrapped(user)
+
+        // Check the data was written to the Firestore emulator
+        const snap = await admin.firestore().collection("users").doc(uid).get()
+        const data = snap.data()
+
+        assert.propertyVal(data, "name", displayName)
+        assert.propertyVal(data, "email", email)
+        assert.propertyVal(data, "photoURL", photoURL)
+    })
+})
diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json
new file mode 100644
index 00000000..ea775ea0
--- /dev/null
+++ b/packages/backend/tsconfig.json
@@ -0,0 +1,9 @@
+{
+    "extends": "../../tsconfig.json",
+    "compilerOptions": {
+        "baseUrl": ".",
+        "outDir": "dist/",
+        "declarationDir": "dist/types"
+    },
+    "include": ["src/**/*", "test/**/*", "types/**/*", "rollup.config.ts"]
+}
diff --git a/packages/backend/types/index.ts b/packages/backend/types/index.ts
new file mode 100644
index 00000000..85bf3662
--- /dev/null
+++ b/packages/backend/types/index.ts
@@ -0,0 +1,56 @@
+export enum CeremonyState {
+    SCHEDULED = 1,
+    OPENED = 2,
+    PAUSED = 3,
+    CLOSED = 4,
+    FINALIZED = 5
+}
+
+export enum ParticipantStatus {
+    CREATED = 1,
+    WAITING = 2,
+    READY = 3,
+    CONTRIBUTING = 4,
+    CONTRIBUTED = 5,
+    DONE = 6,
+    FINALIZING = 7,
+    FINALIZED = 8,
+    TIMEDOUT = 9,
+    EXHUMED = 10
+}
+
+export enum ParticipantContributionStep {
+    DOWNLOADING = 1,
+    COMPUTING = 2,
+    UPLOADING = 3,
+    VERIFYING = 4,
+    COMPLETED = 5
+}
+
+export enum CeremonyType {
+    PHASE1 = 1,
+    PHASE2 = 2
+}
+
+export enum MsgType {
+    INFO = 1,
+    DEBUG = 2,
+    WARN = 3,
+    ERROR = 4,
+    LOG = 5
+}
+
+export enum RequestType {
+    PUT = 1,
+    GET = 2
+}
+
+export enum TimeoutType {
+    BLOCKING_CONTRIBUTION = 1,
+    BLOCKING_CLOUD_FUNCTION = 2
+}
+
+export enum CeremonyTimeoutType {
+    DYNAMIC = 1,
+    FIXED = 2
+}
diff --git a/packages/backend/types/snarkjs.d.ts b/packages/backend/types/snarkjs.d.ts
new file mode 100644
index 00000000..1ac7c1ca
--- /dev/null
+++ b/packages/backend/types/snarkjs.d.ts
@@ -0,0 +1,58 @@
+/** Declaration file generated by dts-gen */
+
+declare module "snarkjs" {
+    // eslint-disable-next-line @typescript-eslint/no-use-before-define
+    export = snarkjs
+
+    declare const snarkjs: {
+        groth16: {
+            exportSolidityCallData: any
+            fullProve: any
+            prove: any
+            verify: any
+        }
+        plonk: {
+            exportSolidityCallData: any
+            fullProve: any
+            prove: any
+            setup: any
+            verify: any
+        }
+        powersOfTau: {
+            beacon: any
+            challengeContribute: any
+            contribute: any
+            convert: any
+            exportChallenge: any
+            exportJson: any
+            importResponse: any
+            newAccumulator: any
+            preparePhase2: any
+            truncate: any
+            verify: any
+        }
+        r1cs: {
+            exportJson: any
+            info: any
+            print: any
+        }
+        wtns: {
+            calculate: any
+            debug: any
+            exportJson: any
+        }
+        zKey: {
+            beacon: any
+            bellmanContribute: any
+            contribute: any
+            exportBellman: any
+            exportJson: any
+            exportSolidityVerifier: any
+            exportVerificationKey: any
+            importBellman: any
+            newZKey: any
+            verifyFromInit: any
+            verifyFromR1cs: any
+        }
+    }
+}
diff --git a/packages/phase2cli/.env.default b/packages/phase2cli/.env.default
new file mode 100644
index 00000000..f0b916e9
--- /dev/null
+++ b/packages/phase2cli/.env.default
@@ -0,0 +1,15 @@
+# Firebase.
+FIREBASE_FIRESTORE_DATABASE_URL="YOUR-FIREBASE-FIRESTORE-DATABASE-URL-HERE"
+FIREBASE_API_KEY="YOUR-FIREBASE-API-KEY"
+FIREBASE_AUTH_DOMAIN="YOUR-FIREBASE-AUTH-DOMAIN"
+FIREBASE_PROJECT_ID="YOUR-FIREBASE-PROJECT-ID"
+FIREBASE_MESSAGING_SENDER_ID="YOUR-FIREBASE-MESSAGING-SENDER-ID"
+FIREBASE_APP_ID="YOUR-FIREBASE-APP-ID"
+FIREBASE_CF_URL_VERIFY_CONTRIBUTION="YOUR-FIREBASE-CF-URL-VERIFY-CONTRIBUTION"
+# Github.
+GITHUB_CLIENT_ID="YOUR-GITHUB-CLIENT-ID"
+# Misc.
+CONFIG_NODE_OPTION_MAX_OLD_SPACE_SIZE="YOUR-CONFIG-NODE-OPTION-MAX-OLD-SPACE-SIZE"
+CONFIG_STREAM_CHUNK_SIZE_IN_MB="YOUR-CONFIG-STREAM-CHUNK-SIZE-IN-MB"
+CONFIG_CEREMONY_BUCKET_POSTFIX="YOUR-CONFIG-CEREMONY-BUCKET-POSTFIX"
+CONFIG_PRESIGNED_URL_EXPIRATION_IN_SECONDS=YOUR-CONFIG-PRESIGNED-URL-EXPIRATION-IN-SECONDS
\ No newline at end of file
diff --git a/apps/phase2cli/.gitignore b/packages/phase2cli/.gitignore
similarity index 96%
rename from apps/phase2cli/.gitignore
rename to packages/phase2cli/.gitignore
index ed2dad8d..a4a65418 100644
--- a/apps/phase2cli/.gitignore
+++ b/packages/phase2cli/.gitignore
@@ -6,6 +6,7 @@ yarn-error.log
 
 # environment
 env.json
+.env
 
 # build
 dist/
diff --git a/apps/phase2cli/README.md b/packages/phase2cli/README.md
similarity index 66%
rename from apps/phase2cli/README.md
rename to packages/phase2cli/README.md
index 2d42b144..20130280 100644
--- a/apps/phase2cli/README.md
+++ b/packages/phase2cli/README.md
@@ -38,11 +38,11 @@
 
 ## Commands
 
-- `phase2cli`: CLI entry point.
-- `phase2cli auth`: Starts the Device Flow authentication workflow for Github OAuth 2.0.
-- `phase2cli contribute`: Allow a user to participate by computing a contribution for each circuit of a selected ceremony (from those currently running).
-- `phase2cli coordinate setup`: Allow the coordinator to setup a new ceremony for a particular set/variants of circuits.
-- `phase2cli coordinate observe`: Allow the coordinator to monitor in real-time who is currently contributing for a circuit of a ceremony.
+-   `phase2cli`: CLI entry point.
+-   `phase2cli auth`: Starts the Device Flow authentication workflow for Github OAuth 2.0.
+-   `phase2cli contribute`: Allow a user to participate by computing a contribution for each circuit of a selected ceremony (from those currently running).
+-   `phase2cli coordinate setup`: Allow the coordinator to setup a new ceremony for a particular set/variants of circuits.
+-   `phase2cli coordinate observe`: Allow the coordinator to monitor in real-time who is currently contributing for a circuit of a ceremony.
 
 ## Getting Started
 
@@ -69,23 +69,23 @@ Navigate to the `phase2cli/` folder and make a copy of the .env.json.default fil
 
 ```json
 {
-  "firebase": {
-    "FIREBASE_FIRESTORE_DATABASE_URL": "your-firebase-firestore-database-url",
-    "FIREBASE_API_KEY": "your-firebase-api-key",
-    "FIREBASE_AUTH_DOMAIN": "your-firebase-auth-domain",
-    "FIREBASE_PROJECT_ID": "your-firebase-project-id",
-    "FIREBASE_STORAGE_BUCKET": "your-firebase-storage-bucket",
-    "FIREBASE_MESSAGING_SENDER_ID": "your-firebase-messaging-sender-id",
-    "FIREBASE_APP_ID": "your-firebase-app-id"
-  },
-  "github": {
-    "GITHUB_CLIENT_ID": "your-github-oauth-app-client-id"
-  }
+    "firebase": {
+        "FIREBASE_FIRESTORE_DATABASE_URL": "your-firebase-firestore-database-url",
+        "FIREBASE_API_KEY": "your-firebase-api-key",
+        "FIREBASE_AUTH_DOMAIN": "your-firebase-auth-domain",
+        "FIREBASE_PROJECT_ID": "your-firebase-project-id",
+        "FIREBASE_STORAGE_BUCKET": "your-firebase-storage-bucket",
+        "FIREBASE_MESSAGING_SENDER_ID": "your-firebase-messaging-sender-id",
+        "FIREBASE_APP_ID": "your-firebase-app-id"
+    },
+    "github": {
+        "GITHUB_CLIENT_ID": "your-github-oauth-app-client-id"
+    }
 }
 ```
 
-- The `firebase` object contains your Firebase Application configuration.
-- The `github` object contains your Github OAuth Application client identifier.
+-   The `firebase` object contains your Firebase Application configuration.
+-   The `github` object contains your Github OAuth Application client identifier.
 
 ### Usage
 
diff --git a/packages/phase2cli/build.tsconfig.json b/packages/phase2cli/build.tsconfig.json
new file mode 100644
index 00000000..277a5e72
--- /dev/null
+++ b/packages/phase2cli/build.tsconfig.json
@@ -0,0 +1,9 @@
+{
+    "extends": "../../tsconfig.json",
+    "compilerOptions": {
+        "outDir": "dist/",
+        "moduleResolution": "node",
+        "declarationDir": "dist/types"
+    },
+    "include": ["src/**/*", "test/**/*", "types/**/*"]
+}
diff --git a/packages/phase2cli/package.json b/packages/phase2cli/package.json
new file mode 100644
index 00000000..4db13fe1
--- /dev/null
+++ b/packages/phase2cli/package.json
@@ -0,0 +1,103 @@
+{
+    "name": "@zkmpc/phase2cli",
+    "version": "0.0.1",
+    "description": "All-in-one interactive command-line for interfacing with zkSNARK Phase 2 Trusted Setup ceremonies",
+    "repository": "https://github.com/quadratic-funding/mpc-phase2-suite/cli",
+    "homepage": "https://github.com/quadratic-funding/mpc-phase2-suite",
+    "bugs": "https://github.com/quadratic-funding/mpc-phase2-suite/issues",
+    "author": {
+        "name": "Giacomo (0xjei)"
+    },
+    "license": "MIT",
+    "private": false,
+    "exports": {
+        "import": "./dist/src/index.node.mjs",
+        "require": "./dist/src/index.node.js"
+    },
+    "types": "dist/types/src/index.d.ts",
+    "engines": {
+        "node": "16"
+    },
+    "files": [
+        "dist/",
+        "src/",
+        "types/",
+        "README.md"
+    ],
+    "keywords": [
+        "typescript",
+        "zero-knowledge",
+        "zk-snarks",
+        "phase-2",
+        "trusted-setup",
+        "ceremony",
+        "snarkjs",
+        "circom"
+    ],
+    "bin": {
+        "phase2cli": "./dist/src/index.node.mjs"
+    },
+    "scripts": {
+        "build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript",
+        "build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript",
+        "pre:publish": "yarn build",
+        "start": "node ./dist/src/index.node.mjs",
+        "auth": "yarn start auth",
+        "contribute": "yarn start contribute",
+        "clean": "yarn start clean",
+        "logout": "yarn start logout",
+        "coordinate:setup": "yarn start coordinate setup",
+        "coordinate:observe": "yarn start coordinate observe",
+        "coordinate:finalize": "yarn start coordinate finalize"
+    },
+    "peerDependencies": {
+        "@zkmpc/actions": "^0.0.1"
+    },
+    "devDependencies": {
+        "@types/clear": "^0.1.2",
+        "@types/cli-progress": "^3.11.0",
+        "@types/conf": "^3.0.0",
+        "@types/figlet": "^1.5.5",
+        "@types/mime-types": "^2.1.1",
+        "@types/node-emoji": "^1.8.2",
+        "@types/node-fetch": "^2.6.2",
+        "@types/ora": "^3.2.0",
+        "@types/prompts": "^2.4.1",
+        "@types/rollup-plugin-auto-external": "^2.0.2",
+        "@types/winston": "^2.4.4",
+        "rollup-plugin-auto-external": "^2.0.0",
+        "rollup-plugin-cleanup": "^3.2.1",
+        "rollup-plugin-typescript2": "^0.34.1",
+        "typescript": "^4.9.3"
+    },
+    "dependencies": {
+        "@adobe/node-fetch-retry": "^2.2.0",
+        "@octokit/auth-oauth-app": "^5.0.4",
+        "@octokit/auth-oauth-device": "^4.0.3",
+        "@octokit/request": "^6.2.2",
+        "blakejs": "^1.2.1",
+        "boxen": "^7.0.0",
+        "chalk": "^5.1.2",
+        "clear": "^0.1.0",
+        "cli-progress": "^3.11.2",
+        "commander": "^9.4.1",
+        "conf": "^10.2.0",
+        "dotenv": "^16.0.3",
+        "figlet": "^1.5.2",
+        "firebase": "^9.14.0",
+        "log-symbols": "^5.1.0",
+        "mime-types": "^2.1.35",
+        "node-disk-info": "^1.3.0",
+        "node-emoji": "^1.11.0",
+        "node-fetch": "^3.3.0",
+        "open": "^8.4.0",
+        "ora": "^6.1.2",
+        "prompts": "^2.4.2",
+        "snarkjs": "^0.5.0",
+        "timer-node": "^5.0.6",
+        "winston": "^3.8.2"
+    },
+    "publishConfig": {
+        "access": "public"
+    }
+}
diff --git a/packages/phase2cli/rollup.config.ts b/packages/phase2cli/rollup.config.ts
new file mode 100644
index 00000000..18c4ce0c
--- /dev/null
+++ b/packages/phase2cli/rollup.config.ts
@@ -0,0 +1,30 @@
+import * as fs from "fs"
+import typescript from "rollup-plugin-typescript2"
+import autoExternal from "rollup-plugin-auto-external"
+import cleanup from "rollup-plugin-cleanup"
+
+const pkg = JSON.parse(fs.readFileSync("./package.json", "utf-8"))
+const banner = `/**
+ * @module ${pkg.name}
+ * @version ${pkg.version}
+ * @file ${pkg.description}
+ * @copyright Ethereum Foundation 2022
+ * @license ${pkg.license}
+ * @see [Github]{@link ${pkg.homepage}}
+*/`
+
+export default {
+    input: "src/index.ts",
+    output: [
+        { file: pkg.exports.require, format: "cjs", banner, exports: "auto" },
+        { file: pkg.exports.import, format: "es", banner }
+    ],
+    plugins: [
+        autoExternal(),
+        typescript({
+            tsconfig: "./build.tsconfig.json",
+            useTsconfigDeclarationDir: true
+        }),
+        cleanup({ comments: "jsdoc" })
+    ]
+}
diff --git a/packages/phase2cli/src/commands/auth.ts b/packages/phase2cli/src/commands/auth.ts
new file mode 100644
index 00000000..f32e9aa5
--- /dev/null
+++ b/packages/phase2cli/src/commands/auth.ts
@@ -0,0 +1,115 @@
+#!/usr/bin/env node
+
+import { getNewOAuthTokenUsingGithubDeviceFlow, signInToFirebaseWithGithubToken } from "@zkmpc/actions"
+import dotenv from "dotenv"
+import { emojis, symbols, theme } from "../lib/constants"
+import { FIREBASE_ERRORS, GITHUB_ERRORS, showError } from "../lib/errors"
+import { bootstrapCommandExec, getGithubUsername, terminate } from "../lib/utils"
+import { getStoredOAuthToken, hasStoredOAuthToken, setStoredOAuthToken } from "../lib/auth"
+
+dotenv.config()
+
+/**
+ * Look for the Github 2.0 OAuth token in the local storage if present; otherwise manage the request for a new token.
+ * @returns <Promise<string>>
+ */
+const handleGithubToken = async (): Promise<string> => {
+    let token: string
+
+    if (hasStoredOAuthToken())
+        // Get stored token.
+        token = String(getStoredOAuthToken())
+    else {
+        if (!process.env.GITHUB_CLIENT_ID) showError(GITHUB_ERRORS.GITHUB_NOT_CONFIGURED_PROPERLY, true)
+
+        // Request a new token.
+        token = await getNewOAuthTokenUsingGithubDeviceFlow(process.env.GITHUB_CLIENT_ID)
+
+        // Store the new token.
+        setStoredOAuthToken(token)
+    }
+
+    return token
+}
+
+/**
+ * Auth command.
+ * @dev TODO: add docs.
+ */
+const auth = async () => {
+    console.log(process.env.GITHUB_CLIENT_ID)
+
+    try {
+        const { firebaseApp } = await bootstrapCommandExec()
+
+        if (!process.env.GITHUB_CLIENT_ID) showError(GITHUB_ERRORS.GITHUB_NOT_CONFIGURED_PROPERLY, true)
+
+        // Manage OAuth Github token.
+        const token = await handleGithubToken()
+
+        // Sign in with credentials.
+        await signInToFirebaseWithGithubToken(firebaseApp, token)
+
+        // Get Github username.
+        const ghUsername = await getGithubUsername(token)
+
+        console.log(`${symbols.success} You are authenticated as ${theme.bold(`@${ghUsername}`)}`)
+        console.log(
+            `${
+                symbols.info
+            } You can now contribute to zk-SNARK Phase2 Trusted Setup running ceremonies by running ${theme.bold(
+                theme.italic(`phase2cli contribute`)
+            )} command`
+        )
+
+        terminate(ghUsername)
+    } catch (err: any) {
+        const error = err.toString()
+
+        /** Firebase */
+
+        if (error.includes("Firebase: Unsuccessful check authorization response from Github")) {
+            showError(FIREBASE_ERRORS.FIREBASE_TOKEN_EXPIRED_REMOVED_PERMISSIONS, false)
+
+            // Clean expired token from local storage.
+            // deleteStoredOAuthToken()
+
+            console.log(`${symbols.success} Removed expired token from your local storage ${emojis.broom}`)
+            console.log(
+                `${symbols.info} Please, run \`phase2cli auth\` again to generate a new token and associate your Github account`
+            )
+
+            process.exit(0)
+        }
+
+        if (error.includes("Firebase: Firebase App named '[DEFAULT]' already exists with different options or config"))
+            showError(FIREBASE_ERRORS.FIREBASE_DEFAULT_APP_DOUBLE_CONFIG, true)
+
+        if (error.includes("Firebase: Error (auth/user-disabled)"))
+            showError(FIREBASE_ERRORS.FIREBASE_USER_DISABLED, true)
+
+        if (error.includes("Firebase: Error (auth/network-request-failed)"))
+            showError(FIREBASE_ERRORS.FIREBASE_NETWORK_ERROR, true)
+
+        if (error.includes("Firebase: Remote site 5XX from github.com for VERIFY_CREDENTIAL (auth/invalid-credential)"))
+            showError(FIREBASE_ERRORS.FIREBASE_FAILED_CREDENTIALS_VERIFICATION, true)
+
+        /** Github */
+
+        if (error.includes("HttpError: The authorization request was denied"))
+            showError(GITHUB_ERRORS.GITHUB_ACCOUNT_ASSOCIATION_REJECTED, true)
+
+        if (
+            error.includes(
+                "HttpError: request to https://github.com/login/device/code failed, reason: connect ETIMEDOUT"
+            )
+        )
+            showError(GITHUB_ERRORS.GITHUB_SERVER_TIMEDOUT, true)
+
+        /** Generic */
+
+        showError(`Something went wrong: ${error}`, true)
+    }
+}
+
+export default auth
diff --git a/packages/phase2cli/src/commands/clean.ts b/packages/phase2cli/src/commands/clean.ts
new file mode 100644
index 00000000..d92cd6da
--- /dev/null
+++ b/packages/phase2cli/src/commands/clean.ts
@@ -0,0 +1,47 @@
+#!/usr/bin/env node
+
+import { emojis, paths, symbols, theme } from "../lib/constants"
+import { showError } from "../lib/errors"
+import { deleteDir, directoryExists } from "../lib/files"
+import { askForConfirmation } from "../lib/prompts"
+import { bootstrapCommandExec, customSpinner, sleep } from "../lib/utils"
+
+/**
+ * Clean command.
+ */
+const clean = async () => {
+    try {
+        // Initialize services.
+        await bootstrapCommandExec()
+
+        const spinner = customSpinner(`Cleaning up...`, "clock")
+
+        if (directoryExists(paths.outputPath)) {
+            console.log(theme.bold(`${symbols.warning} Be careful, this action is irreversible!`))
+
+            const { confirmation } = await askForConfirmation(
+                "Are you sure you want to continue with the clean up?",
+                "Yes",
+                "No"
+            )
+
+            if (confirmation) {
+                spinner.start()
+
+                // Do the clean up.
+                deleteDir(paths.outputPath)
+
+                // nb. simulate waiting time for 1s.
+                await sleep(1000)
+
+                spinner.succeed(`Cleanup was successfully completed ${emojis.broom}`)
+            }
+        } else {
+            console.log(`${symbols.info} There is nothing to clean ${emojis.eyes}`)
+        }
+    } catch (err: any) {
+        showError(`Something went wrong: ${err.toString()}`, true)
+    }
+}
+
+export default clean
diff --git a/packages/phase2cli/src/commands/contribute.ts b/packages/phase2cli/src/commands/contribute.ts
new file mode 100644
index 00000000..87ff57b3
--- /dev/null
+++ b/packages/phase2cli/src/commands/contribute.ts
@@ -0,0 +1,165 @@
+#!/usr/bin/env node
+
+import { getOpenedCeremonies, getCeremonyCircuits } from "@zkmpc/actions"
+import { httpsCallable } from "firebase/functions"
+import { handleCurrentAuthUserSignIn } from "../lib/auth"
+import { theme, emojis, collections, symbols, paths } from "../lib/constants"
+import { askForCeremonySelection, getEntropyOrBeacon } from "../lib/prompts"
+import { ParticipantContributionStep, ParticipantStatus } from "../../types/index"
+import {
+    bootstrapCommandExec,
+    terminate,
+    handleTimedoutMessageForContributor,
+    getContributorContributionsVerificationResults,
+    customSpinner,
+    simpleLoader
+} from "../lib/utils"
+import { getDocumentById } from "../lib/firebase"
+import listenForContribution from "../lib/listeners"
+import { FIREBASE_ERRORS, GENERIC_ERRORS, showError } from "../lib/errors"
+import { checkAndMakeNewDirectoryIfNonexistent } from "../lib/files"
+
+/**
+ * Contribute command.
+ */
+const contribute = async () => {
+    try {
+        // Initialize services.
+        const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExec()
+        const checkParticipantForCeremony = httpsCallable(firebaseFunctions, "checkParticipantForCeremony")
+
+        // Handle current authenticated user sign in.
+        const { user, token, username } = await handleCurrentAuthUserSignIn(firebaseApp)
+
+        // Get running cerimonies info (if any).
+        const runningCeremoniesDocs = await getOpenedCeremonies(firestoreDatabase)
+
+        if (runningCeremoniesDocs.length === 0) showError(FIREBASE_ERRORS.FIREBASE_CEREMONY_NOT_OPENED, true)
+
+        console.log(
+            `${symbols.warning} ${theme.bold(
+                `The contribution process is based on a waiting queue mechanism (one contributor at a time) with an upper-bound time constraint per each contribution (does not restart if the process is halted for any reason).\n${symbols.info} Any contribution could take the bulk of your computational resources and memory based on the size of the circuit`
+            )} ${emojis.fire}\n`
+        )
+
+        // Ask to select a ceremony.
+        const ceremony = await askForCeremonySelection(runningCeremoniesDocs)
+
+        // Get ceremony circuits.
+        const circuits = await getCeremonyCircuits(firestoreDatabase, ceremony.id)
+        const numberOfCircuits = circuits.length
+
+        const spinner = customSpinner(`Checking eligibility...`, `clock`)
+        spinner.start()
+
+        // Call Cloud Function for participant check and registration.
+        const { data: canParticipate } = await checkParticipantForCeremony({ ceremonyId: ceremony.id })
+
+        // Get participant document.
+        // To be moved (maybe helpers folder? w/ query?)
+        const participantDoc = await getDocumentById(
+            `${collections.ceremonies}/${ceremony.id}/${collections.participants}`,
+            user.uid
+        )
+
+        // Get updated data from snap.
+        const participantData = participantDoc.data()
+
+        if (!participantData) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
+
+        // Check if the user can take part of the waiting queue for contributing.
+        if (canParticipate) {
+            spinner.succeed(`You are eligible to contribute to the ceremony ${emojis.tada}\n`)
+
+            // Check for output directory.
+            checkAndMakeNewDirectoryIfNonexistent(paths.outputPath)
+            checkAndMakeNewDirectoryIfNonexistent(paths.contributePath)
+            checkAndMakeNewDirectoryIfNonexistent(paths.contributionsPath)
+            checkAndMakeNewDirectoryIfNonexistent(paths.attestationPath)
+            checkAndMakeNewDirectoryIfNonexistent(paths.contributionTranscriptsPath)
+
+            // Check if entropy is needed.
+            let entropy = ""
+
+            if (
+                (participantData?.contributionProgress === numberOfCircuits &&
+                    participantData?.contributionStep < ParticipantContributionStep.UPLOADING) ||
+                participantData?.contributionProgress < numberOfCircuits
+            )
+                entropy = await getEntropyOrBeacon(true)
+
+            // Listen to circuits and participant document changes.
+            await listenForContribution(
+                participantDoc,
+                ceremony,
+                firestoreDatabase,
+                circuits,
+                firebaseFunctions,
+                token,
+                username,
+                entropy
+            )
+        } else {
+            spinner.warn(`You are not eligible to contribute to the ceremony right now`)
+
+            await handleTimedoutMessageForContributor(participantData!, participantDoc.id, ceremony.id, false, username)
+        }
+
+        // Check if already contributed.
+        if (
+            ((!canParticipate && participantData?.status === ParticipantStatus.DONE) ||
+                participantData?.status === ParticipantStatus.FINALIZED) &&
+            participantData?.contributions.length > 0
+        ) {
+            spinner.fail(`You are not eligible to contribute to the ceremony\n`)
+
+            await simpleLoader(`Checking for contributions...`, `clock`, 1500)
+
+            // Return true and false based on contribution verification.
+            const contributionsValidity = await getContributorContributionsVerificationResults(
+                ceremony.id,
+                participantDoc.id,
+                circuits,
+                false
+            )
+            const numberOfValidContributions = contributionsValidity.filter(Boolean).length
+
+            if (numberOfValidContributions) {
+                console.log(
+                    `Congrats, you have already contributed to ${theme.magenta(
+                        theme.bold(numberOfValidContributions)
+                    )} out of ${theme.magenta(theme.bold(numberOfCircuits))} circuits ${emojis.tada}`
+                )
+
+                // Show valid/invalid contributions per each circuit.
+                let idx = 0
+                for (const contributionValidity of contributionsValidity) {
+                    console.log(
+                        `${contributionValidity ? symbols.success : symbols.error} ${theme.bold(
+                            `Circuit`
+                        )} ${theme.bold(theme.magenta(idx + 1))}`
+                    )
+                    idx += 1
+                }
+
+                console.log(
+                    `\nWe wanna thank you for your participation in preserving the security for ${theme.bold(
+                        ceremony.data.title
+                    )} Trusted Setup ceremony ${emojis.pray}`
+                )
+            } else
+                console.log(
+                    `\nYou have not successfully contributed to any of the ${theme.bold(
+                        theme.magenta(circuits.length)
+                    )} circuits ${emojis.upsideDown}`
+                )
+
+            // Graceful exit.
+            terminate(username)
+        }
+    } catch (err: any) {
+        showError(`Something went wrong: ${err.toString()}`, true)
+    }
+}
+
+export default contribute
diff --git a/packages/phase2cli/src/commands/finalize.ts b/packages/phase2cli/src/commands/finalize.ts
new file mode 100644
index 00000000..ae92d9db
--- /dev/null
+++ b/packages/phase2cli/src/commands/finalize.ts
@@ -0,0 +1,270 @@
+#!/usr/bin/env node
+import crypto from "crypto"
+import { zKey } from "snarkjs"
+import open from "open"
+import { getCeremonyCircuits } from "@zkmpc/actions"
+import { httpsCallable } from "firebase/functions"
+import { handleCurrentAuthUserSignIn, onlyCoordinator } from "../lib/auth"
+import { collections, emojis, paths, solidityVersion, symbols, theme } from "../lib/constants"
+import { GENERIC_ERRORS, showError } from "../lib/errors"
+import {
+    checkAndMakeNewDirectoryIfNonexistent,
+    getLocalFilePath,
+    readFile,
+    writeFile,
+    writeLocalJsonFile
+} from "../lib/files"
+import { askForCeremonySelection, getEntropyOrBeacon } from "../lib/prompts"
+import { getClosedCeremonies } from "../lib/queries"
+import {
+    bootstrapCommandExec,
+    customSpinner,
+    getBucketName,
+    getContributorContributionsVerificationResults,
+    getValidContributionAttestation,
+    makeContribution,
+    multiPartUpload,
+    publishGist,
+    sleep,
+    terminate
+} from "../lib/utils"
+import { getDocumentById } from "../lib/firebase"
+
+/**
+ * Finalize command.
+ */
+const finalize = async () => {
+    try {
+        // Initialize services.
+        const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExec()
+
+        // Setup ceremony callable Cloud Function initialization.
+        const checkAndPrepareCoordinatorForFinalization = httpsCallable(
+            firebaseFunctions,
+            "checkAndPrepareCoordinatorForFinalization"
+        )
+        const finalizeLastContribution = httpsCallable(firebaseFunctions, "finalizeLastContribution")
+        const finalizeCeremony = httpsCallable(firebaseFunctions, "finalizeCeremony")
+
+        // Handle current authenticated user sign in.
+        const { user, token, username } = await handleCurrentAuthUserSignIn(firebaseApp)
+
+        // Check custom claims for coordinator role.
+        await onlyCoordinator(user)
+
+        // Get closed cerimonies info (if any).
+        const closedCeremoniesDocs = await getClosedCeremonies()
+
+        console.log(
+            `${symbols.warning} The computation of the final contribution could take the bulk of your computational resources and memory based on the size of the circuit ${emojis.fire}\n`
+        )
+
+        // Ask to select a ceremony.
+        const ceremony = await askForCeremonySelection(closedCeremoniesDocs)
+
+        // Get coordinator participant document.
+        const participantDoc = await getDocumentById(
+            `${collections.ceremonies}/${ceremony.id}/${collections.participants}`,
+            user.uid
+        )
+
+        const { data: canFinalize } = await checkAndPrepareCoordinatorForFinalization({ ceremonyId: ceremony.id })
+
+        if (!canFinalize) showError(`You are not able to finalize the ceremony`, true)
+
+        // Clean directories.
+        checkAndMakeNewDirectoryIfNonexistent(paths.outputPath)
+        checkAndMakeNewDirectoryIfNonexistent(paths.finalizePath)
+        checkAndMakeNewDirectoryIfNonexistent(paths.finalZkeysPath)
+        checkAndMakeNewDirectoryIfNonexistent(paths.finalPotPath)
+        checkAndMakeNewDirectoryIfNonexistent(paths.finalAttestationsPath)
+        checkAndMakeNewDirectoryIfNonexistent(paths.verificationKeysPath)
+        checkAndMakeNewDirectoryIfNonexistent(paths.verifierContractsPath)
+
+        // Handle random beacon request/generation.
+        const beacon = await getEntropyOrBeacon(false)
+        const beaconHashStr = crypto.createHash("sha256").update(beacon).digest("hex")
+        console.log(`${symbols.info} Your final beacon hash: ${theme.bold(beaconHashStr)}`)
+
+        // Get ceremony circuits.
+        const circuits = await getCeremonyCircuits(firestoreDatabase, ceremony.id)
+
+        // Attestation preamble.
+        const attestationPreamble = `Hey, I'm ${username} and I have finalized the ${ceremony.data.title} MPC Phase2 Trusted Setup ceremony.\nThe following are the finalization signatures:`
+
+        // Finalize each circuit
+        for await (const circuit of circuits) {
+            await makeContribution(ceremony, circuit, beaconHashStr, username, true, firebaseFunctions)
+
+            // 6. Export the verification key.
+
+            // Paths config.
+            const finalZkeyLocalPath = `${paths.finalZkeysPath}/${circuit.data.prefix}_final.zkey`
+            const verificationKeyLocalPath = `${paths.verificationKeysPath}/${circuit.data.prefix}_vkey.json`
+            const verificationKeyStoragePath = `${collections.circuits}/${circuit.data.prefix}/${circuit.data.prefix}_vkey.json`
+
+            const spinner = customSpinner(`Extracting verification key...`, "clock")
+            spinner.start()
+
+            // Export vkey.
+            const verificationKeyJSONData = await zKey.exportVerificationKey(finalZkeyLocalPath)
+
+            spinner.text = `Writing verification key locally...`
+
+            // Write locally.
+            writeLocalJsonFile(verificationKeyLocalPath, verificationKeyJSONData)
+
+            // nb. need to wait for closing the file descriptor.
+            await sleep(1500)
+
+            // Upload vkey to storage.
+            const startMultiPartUpload = httpsCallable(firebaseFunctions, "startMultiPartUpload")
+            const generatePreSignedUrlsParts = httpsCallable(firebaseFunctions, "generatePreSignedUrlsParts")
+            const completeMultiPartUpload = httpsCallable(firebaseFunctions, "completeMultiPartUpload")
+
+            const bucketName = getBucketName(ceremony.data.prefix)
+
+            await multiPartUpload(
+                startMultiPartUpload,
+                generatePreSignedUrlsParts,
+                completeMultiPartUpload,
+                bucketName,
+                verificationKeyStoragePath,
+                verificationKeyLocalPath
+            )
+
+            spinner.succeed(`Verification key correctly stored`)
+
+            // 7. Turn the verifier into a smart contract.
+            const verifierContractLocalPath = `${paths.verifierContractsPath}/${circuit.data.name}_verifier.sol`
+            const verifierContractStoragePath = `${collections.circuits}/${circuit.data.prefix}/${circuit.data.prefix}_verifier.sol`
+
+            spinner.text = `Extracting verifier contract...`
+            spinner.start()
+
+            // Export solidity verifier.
+            let verifierCode = await zKey.exportSolidityVerifier(
+                finalZkeyLocalPath,
+                {
+                    groth16: readFile(
+                        getLocalFilePath("../../../node_modules/snarkjs/templates/verifier_groth16.sol.ejs")
+                    )
+                },
+                console
+            )
+
+            // Update solidity version.
+            verifierCode = verifierCode.replace(
+                /pragma solidity \^\d+\.\d+\.\d+/,
+                `pragma solidity ^${solidityVersion}`
+            )
+
+            spinner.text = `Writing verifier contract locally...`
+
+            // Write locally.
+            writeFile(verifierContractLocalPath, verifierCode)
+
+            // nb. need to wait for closing the file descriptor.
+            await sleep(1500)
+
+            // Upload vkey to storage.
+            await multiPartUpload(
+                startMultiPartUpload,
+                generatePreSignedUrlsParts,
+                completeMultiPartUpload,
+                bucketName,
+                verifierContractStoragePath,
+                verifierContractLocalPath
+            )
+            spinner.succeed(`Verifier contract correctly stored`)
+
+            spinner.text = `Finalizing circuit...`
+            spinner.start()
+
+            // Finalize circuit contribution.
+            await finalizeLastContribution({
+                ceremonyId: ceremony.id,
+                circuitId: circuit.id,
+                bucketName
+            })
+
+            spinner.succeed(`Circuit successfully finalized`)
+        }
+
+        process.stdout.write(`\n`)
+
+        const spinner = customSpinner(`Finalizing the ceremony...`, "clock")
+        spinner.start()
+
+        // Setup ceremony on the server.
+        await finalizeCeremony({
+            ceremonyId: ceremony.id
+        })
+
+        spinner.succeed(
+            `Congrats, you have correctly finalized the ${theme.bold(ceremony.data.title)} circuits ${emojis.tada}\n`
+        )
+
+        spinner.text = `Generating public finalization attestation...`
+        spinner.start()
+
+        // Get updated participant data.
+        const participantData = participantDoc.data()
+
+        if (!participantData) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
+
+        // Return true and false based on contribution verification.
+        const contributionsValidity = await getContributorContributionsVerificationResults(
+            ceremony.id,
+            participantDoc.id,
+            circuits,
+            true
+        )
+
+        // Get only valid contribution hashes.
+        const attestation = await getValidContributionAttestation(
+            contributionsValidity,
+            circuits,
+            participantData!,
+            ceremony.id,
+            participantDoc.id,
+            attestationPreamble,
+            true
+        )
+
+        writeFile(
+            `${paths.finalAttestationsPath}/${ceremony.data.prefix}_final_attestation.log`,
+            Buffer.from(attestation)
+        )
+
+        // nb. wait for closing file descriptor.
+        await sleep(1000)
+
+        spinner.text = `Uploading public finalization attestation as Github Gist...`
+
+        const gistUrl = await publishGist(token, attestation, ceremony.data.prefix, ceremony.data.title)
+
+        spinner.succeed(
+            `Public finalization attestation successfully published as Github Gist at this link ${theme.bold(
+                theme.underlined(gistUrl)
+            )}`
+        )
+
+        // Attestation link via Twitter.
+        const attestationTweet = `https://twitter.com/intent/tweet?text=I%20have%20finalized%20the%20${ceremony.data.title}%20Phase%202%20Trusted%20Setup%20ceremony!%20You%20can%20view%20my%20final%20attestation%20here:%20${gistUrl}%20#Ethereum%20#ZKP%20#PSE`
+
+        console.log(
+            `\nYou can tweet about the ceremony finalization if you'd like (click on the link below ${
+                emojis.pointDown
+            }) \n\n${theme.underlined(attestationTweet)}`
+        )
+
+        await open(attestationTweet)
+
+        terminate(username)
+    } catch (err: any) {
+        showError(`Something went wrong: ${err.toString()}`, true)
+    }
+}
+
+export default finalize
diff --git a/packages/phase2cli/src/commands/index.ts b/packages/phase2cli/src/commands/index.ts
new file mode 100644
index 00000000..94333249
--- /dev/null
+++ b/packages/phase2cli/src/commands/index.ts
@@ -0,0 +1,9 @@
+import setup from "./setup"
+import auth from "./auth"
+import contribute from "./contribute"
+import observe from "./observe"
+import finalize from "./finalize"
+import clean from "./clean"
+import logout from "./logout"
+
+export { setup, auth, contribute, observe, finalize, clean, logout }
diff --git a/packages/phase2cli/src/commands/logout.ts b/packages/phase2cli/src/commands/logout.ts
new file mode 100644
index 00000000..433e0d95
--- /dev/null
+++ b/packages/phase2cli/src/commands/logout.ts
@@ -0,0 +1,57 @@
+#!/usr/bin/env node
+
+import { getAuth, signOut } from "firebase/auth"
+import { deleteStoredOAuthToken, handleCurrentAuthUserSignIn } from "../lib/auth"
+import { emojis, symbols, theme } from "../lib/constants"
+import { showError } from "../lib/errors"
+import { askForConfirmation } from "../lib/prompts"
+import { bootstrapCommandExec, customSpinner } from "../lib/utils"
+
+/**
+ * Logout command.
+ */
+const logout = async () => {
+    try {
+        // Initialize services.
+        const { firebaseApp } = await bootstrapCommandExec()
+
+        // Handle current authenticated user sign in.
+        await handleCurrentAuthUserSignIn(firebaseApp)
+
+        // Inform the user about deassociation in Github and re run auth
+        console.log(
+            `${symbols.warning} We do not use any Github access token for authentication; thus we cannot revoke the authorization from your Github account for this CLI application`
+        )
+        console.log(
+            `${symbols.info} You can do this manually as reported in the official Github documentation ${
+                emojis.pointDown
+            }\n\n${theme.bold(
+                theme.underlined(
+                    `https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/reviewing-your-authorized-applications-oauth`
+                )
+            )}\n`
+        )
+
+        // Ask for confirmation.
+        const { confirmation } = await askForConfirmation("Are you sure you want to log out?", "Yes", "No")
+
+        if (confirmation) {
+            const spinner = customSpinner(`Logging out...`, "clock")
+            spinner.start()
+
+            // Sign out.
+            const auth = getAuth()
+            await signOut(auth)
+
+            // Delete local token.
+            deleteStoredOAuthToken()
+
+            spinner.stop()
+            console.log(`${symbols.success} Logout successfully completed ${emojis.wave}`)
+        }
+    } catch (err: any) {
+        showError(`Something went wrong: ${err.toString()}`, true)
+    }
+}
+
+export default logout
diff --git a/packages/phase2cli/src/commands/observe.ts b/packages/phase2cli/src/commands/observe.ts
new file mode 100644
index 00000000..dd7b5bc9
--- /dev/null
+++ b/packages/phase2cli/src/commands/observe.ts
@@ -0,0 +1,181 @@
+#!/usr/bin/env node
+
+import readline from "readline"
+import logSymbols from "log-symbols"
+import { getOpenedCeremonies, getCeremonyCircuits } from "@zkmpc/actions"
+import { FirebaseDocumentInfo } from "../../types/index"
+import { onlyCoordinator, handleCurrentAuthUserSignIn } from "../lib/auth"
+import {
+    bootstrapCommandExec,
+    convertToDoubleDigits,
+    customSpinner,
+    getSecondsMinutesHoursFromMillis,
+    sleep
+} from "../lib/utils"
+import { askForCeremonySelection } from "../lib/prompts"
+import { getCurrentContributorContribution } from "../lib/queries"
+import { GENERIC_ERRORS, showError } from "../lib/errors"
+import { theme, emojis, symbols, observationWaitingTimeInMillis } from "../lib/constants"
+
+/**
+ * Clean cursor lines from current position back to root (default: zero).
+ * @param currentCursorPos - the current position of the cursor.
+ * @returns <number>
+ */
+const cleanCursorPosBackToRoot = (currentCursorPos: number) => {
+    while (currentCursorPos < 0) {
+        // Get back and clean line by line.
+        readline.cursorTo(process.stdout, 0)
+        readline.clearLine(process.stdout, 0)
+        readline.moveCursor(process.stdout, -1, -1)
+
+        currentCursorPos += 1
+    }
+
+    return currentCursorPos
+}
+
+/**
+ * Show the latest updates for the given circuit.
+ * @param ceremony <FirebaseDocumentInfo> - the Firebase document containing info about the ceremony.
+ * @param circuit <FirebaseDocumentInfo> - the Firebase document containing info about the circuit.
+ * @returns Promise<number> return the current position of the cursor (i.e., number of lines displayed).
+ */
+const displayLatestCircuitUpdates = async (
+    ceremony: FirebaseDocumentInfo,
+    circuit: FirebaseDocumentInfo
+): Promise<number> => {
+    let observation = theme.bold(`- Circuit # ${theme.magenta(circuit.data.sequencePosition)}`) // Observation output.
+    let cursorPos = -1 // Current cursor position (nb. decrease every time there's a new line!).
+
+    const { waitingQueue } = circuit.data
+
+    // Get info from circuit.
+    const { currentContributor } = waitingQueue
+    const { completedContributions } = waitingQueue
+
+    if (!currentContributor) {
+        observation += `\n> Nobody's currently waiting to contribute ${emojis.eyes}`
+        cursorPos -= 1
+    } else {
+        // Search for currentContributor' contribution.
+        const contributions = await getCurrentContributorContribution(ceremony.id, circuit.id, currentContributor)
+
+        if (!contributions.length) {
+            // The contributor is currently contributing.
+            observation += `\n> Participant ${theme.bold(`#${completedContributions + 1}`)} (${theme.bold(
+                currentContributor
+            )}) is currently contributing ${emojis.fire}`
+
+            cursorPos -= 1
+        } else {
+            // The contributor has contributed.
+            observation += `\n> Participant ${theme.bold(`#${completedContributions}`)} (${theme.bold(
+                currentContributor
+            )}) has completed the contribution ${emojis.tada}`
+
+            cursorPos -= 1
+
+            // The contributor has finished the contribution.
+            const contributionData = contributions.at(0)?.data
+
+            if (!contributionData) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
+
+            // Convert times to seconds.
+            const {
+                seconds: contributionTimeSeconds,
+                minutes: contributionTimeMinutes,
+                hours: contributionTimeHours
+            } = getSecondsMinutesHoursFromMillis(contributionData?.contributionTime)
+            const {
+                seconds: verificationTimeSeconds,
+                minutes: verificationTimeMinutes,
+                hours: verificationTimeHours
+            } = getSecondsMinutesHoursFromMillis(contributionData?.verificationTime)
+
+            observation += `\n> The ${theme.bold("computation")} took ${theme.bold(
+                `${convertToDoubleDigits(contributionTimeHours)}:${convertToDoubleDigits(
+                    contributionTimeMinutes
+                )}:${convertToDoubleDigits(contributionTimeSeconds)}`
+            )}`
+            observation += `\n> The ${theme.bold("verification")} took ${theme.bold(
+                `${convertToDoubleDigits(verificationTimeHours)}:${convertToDoubleDigits(
+                    verificationTimeMinutes
+                )}:${convertToDoubleDigits(verificationTimeSeconds)}`
+            )}`
+            observation += `\n> Contribution ${
+                contributionData?.valid
+                    ? `${theme.bold("VALID")} ${symbols.success}`
+                    : `${theme.bold("INVALID")} ${symbols.error}`
+            }`
+
+            cursorPos -= 3
+        }
+    }
+
+    // Show observation for circuit.
+    process.stdout.write(`${observation}\n\n`)
+    cursorPos -= 1
+
+    return cursorPos
+}
+
+/**
+ * Observe command.
+ */
+const observe = async () => {
+    try {
+        // Initialize services.
+        const { firebaseApp, firestoreDatabase } = await bootstrapCommandExec()
+
+        // Handle current authenticated user sign in.
+        const { user } = await handleCurrentAuthUserSignIn(firebaseApp)
+
+        // Check custom claims for coordinator role.
+        await onlyCoordinator(user)
+
+        // Get running cerimonies info (if any).
+        const runningCeremoniesDocs = await getOpenedCeremonies(firestoreDatabase)
+
+        // Ask to select a ceremony.
+        const ceremony = await askForCeremonySelection(runningCeremoniesDocs)
+
+        console.log(`${logSymbols.info} Refresh rate set to ~3 seconds for waiting queue updates\n`)
+
+        let cursorPos = 0 // Keep track of current cursor position.
+
+        let spinner = customSpinner(`Getting ready...`, "clock")
+        spinner.start()
+
+        // Get circuit updates every 3 seconds.
+        setInterval(async () => {
+            // Clean cursor position back to root.
+            cursorPos = cleanCursorPosBackToRoot(cursorPos)
+
+            spinner = customSpinner(`Updating...`, "clock")
+            spinner.start()
+
+            // Get updates from circuits.
+            const circuits = await getCeremonyCircuits(firestoreDatabase, ceremony.id)
+
+            await sleep(observationWaitingTimeInMillis / 10) // Just for a smoother UX/UI experience.
+
+            spinner.stop()
+
+            // Observe changes for each circuit
+            for await (const circuit of circuits) cursorPos += await displayLatestCircuitUpdates(ceremony, circuit)
+
+            process.stdout.write(`Press CTRL+C to exit`)
+
+            await sleep(1000) // Just for a smoother UX/UI experience.
+        }, observationWaitingTimeInMillis)
+
+        await sleep(observationWaitingTimeInMillis) // Wait until the first update.
+
+        spinner.stop()
+    } catch (err: any) {
+        showError(`Something went wrong: ${err.toString()}`, true)
+    }
+}
+
+export default observe
diff --git a/packages/phase2cli/src/commands/setup.ts b/packages/phase2cli/src/commands/setup.ts
new file mode 100644
index 00000000..97ddfc4a
--- /dev/null
+++ b/packages/phase2cli/src/commands/setup.ts
@@ -0,0 +1,675 @@
+#!/usr/bin/env node
+
+import { zKey, r1cs } from "snarkjs"
+import winston from "winston"
+import blake from "blakejs"
+import boxen from "boxen"
+import { httpsCallable } from "firebase/functions"
+import { Dirent, renameSync } from "fs"
+import {
+    theme,
+    symbols,
+    emojis,
+    potFilenameTemplate,
+    potDownloadUrlTemplate,
+    paths,
+    names,
+    collections
+} from "../lib/constants"
+import { handleCurrentAuthUserSignIn, onlyCoordinator } from "../lib/auth"
+import {
+    bootstrapCommandExec,
+    convertToDoubleDigits,
+    customSpinner,
+    estimatePoT,
+    extractPoTFromFilename,
+    extractPrefix,
+    getBucketName,
+    getCircuitMetadataFromR1csFile,
+    multiPartUpload,
+    simpleLoader,
+    sleep,
+    terminate
+} from "../lib/utils"
+import {
+    askCeremonyInputData,
+    askCircomCompilerVersionAndCommitHash,
+    askCircuitInputData,
+    askForCircuitSelectionFromLocalDir,
+    askForConfirmation,
+    askForPtauSelectionFromLocalDir,
+    askForZkeySelectionFromLocalDir,
+    askPowersOftau
+} from "../lib/prompts"
+import {
+    cleanDir,
+    directoryExists,
+    downloadFileFromUrl,
+    getDirFilesSubPaths,
+    getFileStats,
+    readFile
+} from "../lib/files"
+import { CeremonyTimeoutType, Circuit, CircuitFiles, CircuitInputData, CircuitTimings } from "../../types/index"
+import { GENERIC_ERRORS, showError } from "../lib/errors"
+import { createS3Bucket, objectExist } from "../lib/storage"
+
+/**
+ * Return the files from the current working directory which have the extension specified as input.
+ * @param cwd <string> - the current working directory.
+ * @param ext <string> - the file extension.
+ * @returns <Promise<Array<Dirent>>>
+ */
+const getSpecifiedFilesFromCwd = async (cwd: string, ext: string): Promise<Array<Dirent>> => {
+    // Check if the current directory contains the .r1cs files.
+    const cwdFiles = await getDirFilesSubPaths(cwd)
+    const cwdExtFiles = cwdFiles.filter((file: Dirent) => file.name.includes(ext))
+
+    return cwdExtFiles
+}
+
+/**
+ * Handle one or more circuit addition for the specified ceremony.
+ * @param cwd <string> - the current working directory.
+ * @param cwdR1csFiles <Array<Dirent>> - the list of R1CS files in the current working directory.
+ * @param timeoutMechanismType <CeremonyTimeoutType> - the choosen timeout mechanism type for the ceremony.
+ * @param isCircomVersionEqualAmongCircuits <boolean> - true if the circom compiler version is equal among circuits; otherwise false.
+ * @returns <Promise<Array<CircuitInputData>>>
+ */
+const handleCircuitsAddition = async (
+    cwd: string,
+    cwdR1csFiles: Array<Dirent>,
+    timeoutMechanismType: CeremonyTimeoutType,
+    isCircomVersionEqualAmongCircuits: boolean
+): Promise<Array<CircuitInputData>> => {
+    const circuitsInputData: Array<CircuitInputData> = []
+
+    let wannaAddAnotherCircuit = true // Loop flag.
+    let circuitSequencePosition = 1 // Sequential circuit position for handling the contributions queue for the ceremony.
+    let leftCircuits: Array<Dirent> = cwdR1csFiles
+
+    // Clear directory.
+    cleanDir(paths.metadataPath)
+
+    while (wannaAddAnotherCircuit) {
+        console.log(theme.bold(`\n- Circuit # ${theme.magenta(`${circuitSequencePosition}`)}\n`))
+
+        // Interactively select a circuit.
+        const circuitNameWithExt = await askForCircuitSelectionFromLocalDir(leftCircuits)
+
+        // Remove the selected circuit from the list.
+        leftCircuits = leftCircuits.filter((dirent: Dirent) => dirent.name !== circuitNameWithExt)
+
+        // Ask for circuit input data.
+        const circuitInputData = await askCircuitInputData(timeoutMechanismType, isCircomVersionEqualAmongCircuits)
+
+        // Remove .r1cs file extension.
+        const circuitName = circuitNameWithExt.substring(0, circuitNameWithExt.indexOf("."))
+        const circuitPrefix = extractPrefix(circuitName)
+
+        // R1CS circuit file path.
+        const r1csMetadataFilePath = `${paths.metadataPath}/${circuitPrefix}_${names.metadata}.log`
+        const r1csFilePath = `${cwd}/${circuitName}.r1cs`
+
+        // Custom logger for R1CS metadata save.
+        const logger = winston.createLogger({
+            level: "info",
+            transports: new winston.transports.File({
+                filename: r1csMetadataFilePath,
+                format: winston.format.printf((log) => log.message),
+                level: "info"
+            })
+        })
+
+        const metadataSpinner = customSpinner(`Looking for metadata...`, "clock")
+        metadataSpinner.start()
+
+        // Read .r1cs file and log/store info.
+        await r1cs.info(r1csFilePath, logger)
+
+        // Sleep to avoid logger unexpected termination.
+        await sleep(1000)
+
+        // Store data.
+        circuitsInputData.push({
+            ...circuitInputData,
+            name: circuitName,
+            prefix: circuitPrefix,
+            sequencePosition: circuitSequencePosition
+        })
+
+        metadataSpinner.succeed(
+            `Metadata stored in your working directory ${theme.bold(
+                theme.underlined(r1csMetadataFilePath.substring(1))
+            )}\n`
+        )
+
+        let readyToAssembly = false
+
+        // In case of negative confirmation or no more circuits left.
+        if (leftCircuits.length !== 0) {
+            // Ask for another circuit.
+            const { confirmation: wannaAddNewCircuit } = await askForConfirmation(
+                "Want to add another circuit for the ceremony?",
+                "Okay",
+                "No"
+            )
+
+            if (wannaAddNewCircuit === undefined) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
+
+            if (wannaAddNewCircuit === false) readyToAssembly = true
+            else circuitSequencePosition += 1
+        } else readyToAssembly = true
+
+        // Assembly the ceremony.
+        if (readyToAssembly) wannaAddAnotherCircuit = false
+    }
+
+    return circuitsInputData
+}
+
+/**
+ * Check if the smallest pot has been already downloaded.
+ * @param neededPowers <number> - the representation of the constraints of the circuit in terms of powers.
+ * @returns <Promise<boolean>>
+ */
+const checkIfPotAlreadyDownloaded = async (neededPowers: number): Promise<boolean> => {
+    // Get files from dir.
+    const potFiles = await getDirFilesSubPaths(paths.potPath)
+
+    let alreadyDownloaded = false
+
+    for (const potFile of potFiles) {
+        const powers = extractPoTFromFilename(potFile.name)
+
+        if (powers === neededPowers) alreadyDownloaded = true
+    }
+
+    return alreadyDownloaded
+}
+
+/**
+ * Setup a new Groth16 zkSNARK Phase 2 Trusted Setup ceremony.
+ */
+const setup = async () => {
+    // Circuit data state.
+    let circuitsInputData: Array<CircuitInputData> = []
+    const circuits: Array<Circuit> = []
+
+    /** CORE */
+    try {
+        // Get current working directory.
+        const cwd = process.cwd()
+
+        const { firebaseApp, firebaseFunctions } = await bootstrapCommandExec()
+
+        // Setup ceremony callable Cloud Function initialization.
+        const setupCeremony = httpsCallable(firebaseFunctions, "setupCeremony")
+        const createBucket = httpsCallable(firebaseFunctions, "createBucket")
+        const startMultiPartUpload = httpsCallable(firebaseFunctions, "startMultiPartUpload")
+        const generatePreSignedUrlsParts = httpsCallable(firebaseFunctions, "generatePreSignedUrlsParts")
+        const completeMultiPartUpload = httpsCallable(firebaseFunctions, "completeMultiPartUpload")
+        const checkIfObjectExist = httpsCallable(firebaseFunctions, "checkIfObjectExist")
+
+        // Handle current authenticated user sign in.
+        const { user, username } = await handleCurrentAuthUserSignIn(firebaseApp)
+
+        // Check custom claims for coordinator role.
+        await onlyCoordinator(user)
+
+        console.log(
+            `${symbols.warning} To setup a zkSNARK Groth16 Phase 2 Trusted Setup ceremony you need to have the Rank-1 Constraint System (R1CS) file for each circuit in your working directory`
+        )
+        console.log(`${symbols.info} Current working directory: ${theme.bold(theme.underlined(cwd))}\n`)
+
+        // Check if the current directory contains the .r1cs files.
+        const cwdR1csFiles = await getSpecifiedFilesFromCwd(cwd, `.r1cs`)
+        if (!cwdR1csFiles.length) showError(`Your working directory must contain the R1CS files for each circuit`, true)
+
+        // Ask for ceremony input data.
+        const ceremonyInputData = await askCeremonyInputData()
+        const ceremonyPrefix = extractPrefix(ceremonyInputData.title)
+
+        // Check for circom compiler version and commit hash.
+        const { confirmation: isCircomVersionEqualAmongCircuits } = await askForConfirmation(
+            "Was the same version of the circom compiler used for each circuit that will be designated for the ceremony?",
+            "Yes",
+            "No"
+        )
+
+        // Check for output directory.
+        if (!directoryExists(paths.outputPath)) cleanDir(paths.outputPath)
+
+        // Clean directories.
+        cleanDir(paths.setupPath)
+        cleanDir(paths.potPath)
+        cleanDir(paths.metadataPath)
+        cleanDir(paths.zkeysPath)
+
+        if (isCircomVersionEqualAmongCircuits) {
+            // Ask for circom compiler data.
+            const { version, commitHash } = await askCircomCompilerVersionAndCommitHash()
+
+            // Ask to add circuits.
+            circuitsInputData = await handleCircuitsAddition(
+                cwd,
+                cwdR1csFiles,
+                ceremonyInputData.timeoutMechanismType,
+                isCircomVersionEqualAmongCircuits
+            )
+
+            // Add the data to the circuit input data.
+            circuitsInputData = circuitsInputData.map((circuitInputData: CircuitInputData) => ({
+                ...circuitInputData,
+                compiler: { version, commitHash }
+            }))
+        } else
+            circuitsInputData = await handleCircuitsAddition(
+                cwd,
+                cwdR1csFiles,
+                ceremonyInputData.timeoutMechanismType,
+                isCircomVersionEqualAmongCircuits
+            )
+
+        await simpleLoader(`Assembling your ceremony...`, `clock`, 2000)
+
+        // Ceremony summary.
+        let summary = `${`${theme.bold(ceremonyInputData.title)}\n${theme.italic(ceremonyInputData.description)}`}
+    \n${`Opening: ${theme.bold(
+        theme.underlined(ceremonyInputData.startDate.toUTCString().replace("GMT", "UTC"))
+    )}\nEnding: ${theme.bold(theme.underlined(ceremonyInputData.endDate.toUTCString().replace("GMT", "UTC")))}`}
+    \n${theme.bold(
+        ceremonyInputData.timeoutMechanismType === CeremonyTimeoutType.DYNAMIC ? `Dynamic` : `Fixed`
+    )} Timeout / ${theme.bold(ceremonyInputData.penalty)}m Penalty`
+
+        for (let i = 0; i < circuitsInputData.length; i += 1) {
+            const circuitInputData = circuitsInputData[i]
+
+            // Read file.
+            const r1csMetadataFilePath = `${paths.metadataPath}/${circuitInputData.prefix}_metadata.log`
+            const circuitMetadata = readFile(r1csMetadataFilePath)
+
+            // Extract info from file.
+            const curve = getCircuitMetadataFromR1csFile(circuitMetadata, /Curve: .+\n/s)
+            const wires = Number(getCircuitMetadataFromR1csFile(circuitMetadata, /# of Wires: .+\n/s))
+            const constraints = Number(getCircuitMetadataFromR1csFile(circuitMetadata, /# of Constraints: .+\n/s))
+            const privateInputs = Number(getCircuitMetadataFromR1csFile(circuitMetadata, /# of Private Inputs: .+\n/s))
+            const publicOutputs = Number(getCircuitMetadataFromR1csFile(circuitMetadata, /# of Public Inputs: .+\n/s))
+            const labels = Number(getCircuitMetadataFromR1csFile(circuitMetadata, /# of Labels: .+\n/s))
+            const outputs = Number(getCircuitMetadataFromR1csFile(circuitMetadata, /# of Outputs: .+\n/s))
+            const pot = estimatePoT(constraints, outputs)
+
+            // Store info.
+            circuits.push({
+                ...circuitInputData,
+                metadata: {
+                    curve,
+                    wires,
+                    constraints,
+                    privateInputs,
+                    publicOutputs,
+                    labels,
+                    outputs,
+                    pot
+                }
+            })
+
+            // Show circuit summary.
+            summary += `\n\n${theme.bold(
+                `- CIRCUIT # ${theme.bold(theme.magenta(`${circuitInputData.sequencePosition}`))}`
+            )}
+      \n${`${theme.bold(circuitInputData.name)}\n${theme.italic(circuitInputData.description)}
+      \nCurve: ${theme.bold(curve)}\nCompiler: v${theme.bold(`${circuitInputData.compiler.version}`)} (${theme.bold(
+          circuitInputData.compiler.commitHash?.slice(0, 7)
+      )})\nSource: ${theme.bold(circuitInputData.template.source.split(`/`).at(-1))}(${theme.bold(
+          circuitInputData.template.paramsConfiguration
+      )})\n${
+          ceremonyInputData.timeoutMechanismType === CeremonyTimeoutType.DYNAMIC
+              ? `Threshold: ${theme.bold(circuitInputData.timeoutThreshold)}%`
+              : `Max Contribution Time: ${theme.bold(circuitInputData.timeoutMaxContributionWaitingTime)}m`
+      }
+      \n# Wires: ${theme.bold(wires)}\n# Constraints: ${theme.bold(constraints)}\n# Private Inputs: ${theme.bold(
+          privateInputs
+      )}\n# Public Inputs: ${theme.bold(publicOutputs)}\n# Labels: ${theme.bold(labels)}\n# Outputs: ${theme.bold(
+          outputs
+      )}\n# PoT: ${theme.bold(pot)}`}`
+        }
+
+        // Show ceremony summary.
+        console.log(
+            boxen(summary, {
+                title: theme.magenta(`CEREMONY SUMMARY`),
+                titleAlignment: "center",
+                textAlignment: "left",
+                margin: 1,
+                padding: 1
+            })
+        )
+
+        // Ask for confirmation.
+        const { confirmation } = await askForConfirmation("Please, confirm to create the ceremony", "Okay", "Exit")
+
+        if (confirmation) {
+            // Create the bucket.
+            const bucketName = getBucketName(ceremonyPrefix)
+
+            const spinner = customSpinner(`Creating the storage bucket...`, `clock`)
+            spinner.start()
+
+            await createS3Bucket(createBucket, bucketName)
+            await sleep(1000)
+
+            spinner.succeed(`Storage bucket ${bucketName} successfully created`)
+
+            // Get local zkeys (if any).
+            spinner.text = "Checking for pre-computed zkeys..."
+            spinner.start()
+
+            const cwdZkeysFiles = await getSpecifiedFilesFromCwd(cwd, `.zkey`)
+
+            await sleep(1000)
+
+            spinner.stop()
+
+            let leftPreComputedZkeys: Array<Dirent> = cwdZkeysFiles
+
+            // Circuit setup.
+            for (let i = 0; i < circuits.length; i += 1) {
+                // Flag for generation of zkey from scratch.
+                let wannaGenerateZkey = true
+                // Flag for PoT download.
+                let wannaUsePreDownloadedPoT = false
+
+                // Get the current circuit
+                const circuit = circuits[i]
+
+                // Convert to double digits powers (e.g., 9 -> 09).
+                let stringifyNeededPowers = convertToDoubleDigits(circuit.metadata.pot)
+                let smallestPotForCircuit = `${potFilenameTemplate}${stringifyNeededPowers}.ptau`
+
+                // Circuit r1cs and zkey file names.
+                const r1csFileName = `${circuit.name}.r1cs`
+                const firstZkeyFileName = `${circuit.prefix}_00000.zkey`
+                let preComputedZkeyNameWithExt = ``
+
+                const r1csLocalPathAndFileName = `${cwd}/${r1csFileName}`
+                let potLocalPathAndFileName = `${paths.potPath}/${smallestPotForCircuit}`
+                let zkeyLocalPathAndFileName = `${paths.zkeysPath}/${firstZkeyFileName}`
+
+                const potStoragePath = `${names.pot}`
+                const r1csStoragePath = `${collections.circuits}/${circuit.prefix}`
+                const zkeyStoragePath = `${collections.circuits}/${circuit.prefix}/${collections.contributions}`
+
+                const r1csStorageFilePath = `${r1csStoragePath}/${r1csFileName}`
+                let potStorageFilePath = `${potStoragePath}/${smallestPotForCircuit}`
+                const zkeyStorageFilePath = `${zkeyStoragePath}/${firstZkeyFileName}`
+
+                console.log(theme.bold(`\n- Setup for Circuit # ${theme.magenta(`${circuit.sequencePosition}`)}\n`))
+
+                if (!leftPreComputedZkeys.length) console.log(`${symbols.warning} There are no pre-computed zKeys`)
+                else {
+                    const { confirmation: preComputedZkeySelection } = await askForConfirmation(
+                        `Do you wanna select a pre-computed zkey for the ${circuit.name} circuit?`,
+                        `Yes`,
+                        `No`
+                    )
+
+                    if (preComputedZkeySelection) {
+                        // Ask for zKey selection.
+                        preComputedZkeyNameWithExt = await askForZkeySelectionFromLocalDir(leftPreComputedZkeys)
+
+                        // Switch to pre-computed zkey path.
+                        zkeyLocalPathAndFileName = `${cwd}/${preComputedZkeyNameWithExt}`
+
+                        // Switch the flag.
+                        wannaGenerateZkey = false
+                    }
+                }
+
+                // If the coordinator wants to use a pre-computed zkey, needs to provide the related ptau.
+                if (!wannaGenerateZkey) {
+                    spinner.text = "Checking for Powers of Tau..."
+                    spinner.start()
+
+                    const cwdPtausFiles = await getSpecifiedFilesFromCwd(cwd, `.ptau`)
+                    await sleep(1000)
+
+                    if (!cwdPtausFiles.length) {
+                        spinner.warn(`No Powers of Tau (.ptau) files found`)
+
+                        // Download the PoT.
+                        const { powers } = await askPowersOftau(circuit.metadata.pot)
+
+                        // Convert to double digits powers (e.g., 9 -> 09).
+                        stringifyNeededPowers = convertToDoubleDigits(Number(powers))
+                        smallestPotForCircuit = `${potFilenameTemplate}${stringifyNeededPowers}.ptau`
+
+                        // Override.
+                        potLocalPathAndFileName = `${paths.potPath}/${smallestPotForCircuit}`
+                        potStorageFilePath = `${potStoragePath}/${smallestPotForCircuit}`
+                    } else {
+                        spinner.stop()
+
+                        // Ask for ptau selection.
+                        smallestPotForCircuit = await askForPtauSelectionFromLocalDir(
+                            cwdPtausFiles,
+                            circuit.metadata.pot
+                        )
+
+                        // Update.
+                        stringifyNeededPowers = convertToDoubleDigits(extractPoTFromFilename(smallestPotForCircuit))
+
+                        // Switch to new ptau path.
+                        potLocalPathAndFileName = `${cwd}/${smallestPotForCircuit}`
+                        potStorageFilePath = `${potStoragePath}/${smallestPotForCircuit}`
+
+                        wannaUsePreDownloadedPoT = true
+                    }
+                }
+
+                // Check if the smallest pot has been already downloaded.
+                const alreadyDownloaded =
+                    (await checkIfPotAlreadyDownloaded(Number(smallestPotForCircuit))) || wannaUsePreDownloadedPoT
+
+                if (!alreadyDownloaded) {
+                    // Get smallest suitable pot for circuit.
+                    const downloadSpinner = customSpinner(
+                        `Downloading ${theme.bold(`#${stringifyNeededPowers}`)} Powers of Tau from PPoT...`,
+                        "clock"
+                    )
+                    downloadSpinner.start()
+
+                    // Download PoT file.
+                    const potDownloadUrl = `${potDownloadUrlTemplate}${smallestPotForCircuit}`
+                    const destFilePath = `${paths.potPath}/${smallestPotForCircuit}`
+
+                    await downloadFileFromUrl(destFilePath, potDownloadUrl)
+
+                    downloadSpinner.succeed(
+                        `Powers of Tau ${theme.bold(`#${stringifyNeededPowers}`)} correctly downloaded`
+                    )
+                } else
+                    console.log(
+                        `${symbols.success} Powers of Tau ${theme.bold(`#${stringifyNeededPowers}`)} already downloaded`
+                    )
+
+                // Check if the smallest pot has been already uploaded.
+                const alreadyUploadedPot = await objectExist(
+                    checkIfObjectExist,
+                    bucketName,
+                    `${ceremonyPrefix}/${names.pot}/${smallestPotForCircuit}`
+                )
+
+                // Validity check for the pre-computed zKey (avoids to upload an invalid combination of r1cs, ptau and zkey files).
+                if (!wannaGenerateZkey) {
+                    // Check validity.
+                    await simpleLoader(`Checking pre-computed zkey validity...`, `clock`, 1500)
+
+                    const valid = await zKey.verifyFromR1cs(
+                        r1csLocalPathAndFileName,
+                        potLocalPathAndFileName,
+                        zkeyLocalPathAndFileName,
+                        console
+                    )
+
+                    // nb. workaround for file descriptor closing.
+                    await sleep(3000)
+
+                    if (valid) {
+                        spinner.succeed(`Your pre-computed zKey is valid`)
+
+                        // Remove the selected zkey from the list.
+                        leftPreComputedZkeys = leftPreComputedZkeys.filter(
+                            (dirent: Dirent) => dirent.name !== preComputedZkeyNameWithExt
+                        )
+
+                        // Rename to first zkey filename.
+                        renameSync(`${cwd}/${preComputedZkeyNameWithExt}`, `${circuit.prefix}_00000.zkey`)
+                    } else {
+                        spinner.fail(`Something went wrong during the verification of your pre-computed zKey`)
+
+                        // Ask to generate a new one from scratch.
+                        const { confirmation: zkeyGeneration } = await askForConfirmation(
+                            `Do you wanna generate a new zkey for the ${circuit.name} circuit? (nb. A negative answer will ABORT the entire setup process)`,
+                            `Yes`,
+                            `No`
+                        )
+
+                        if (!zkeyGeneration) showError(`You have choosen to abort the entire setup process`, true)
+                        else wannaGenerateZkey = true
+                    }
+                }
+
+                // Generate a brand new zKey.
+                if (wannaGenerateZkey) {
+                    console.log(
+                        `${symbols.warning} ${theme.bold(
+                            `The computation of your zKey is starting soon (nb. do not interrupt the process because this will ABORT the entire setup process)`
+                        )}\n`
+                    )
+
+                    // Compute first .zkey file (without any contribution).
+                    await zKey.newZKey(
+                        r1csLocalPathAndFileName,
+                        potLocalPathAndFileName,
+                        zkeyLocalPathAndFileName,
+                        console
+                    )
+
+                    console.log(
+                        `\n${symbols.success} First zkey ${theme.bold(firstZkeyFileName)} successfully computed`
+                    )
+                }
+
+                // Upload zkey.
+                await multiPartUpload(
+                    startMultiPartUpload,
+                    generatePreSignedUrlsParts,
+                    completeMultiPartUpload,
+                    bucketName,
+                    zkeyStorageFilePath,
+                    zkeyLocalPathAndFileName
+                )
+
+                console.log(
+                    `${symbols.success} First zkey ${theme.bold(firstZkeyFileName)} successfully saved on storage`
+                )
+
+                // PoT.
+                if (!alreadyUploadedPot) {
+                    // Upload.
+                    await multiPartUpload(
+                        startMultiPartUpload,
+                        generatePreSignedUrlsParts,
+                        completeMultiPartUpload,
+                        bucketName,
+                        potStorageFilePath,
+                        potLocalPathAndFileName
+                    )
+
+                    console.log(
+                        `${symbols.success} Powers of Tau ${theme.bold(
+                            smallestPotForCircuit
+                        )} successfully saved on storage`
+                    )
+                } else {
+                    console.log(`${symbols.success} Powers of Tau ${theme.bold(smallestPotForCircuit)} already stored`)
+                }
+
+                // Upload R1CS.
+                await multiPartUpload(
+                    startMultiPartUpload,
+                    generatePreSignedUrlsParts,
+                    completeMultiPartUpload,
+                    bucketName,
+                    r1csStorageFilePath,
+                    r1csLocalPathAndFileName
+                )
+
+                console.log(`${symbols.success} R1CS ${theme.bold(r1csFileName)} successfully saved on storage`)
+
+                // Circuit-related files info.
+                const circuitFiles: CircuitFiles = {
+                    files: {
+                        r1csFilename: r1csFileName,
+                        potFilename: smallestPotForCircuit,
+                        initialZkeyFilename: firstZkeyFileName,
+                        r1csStoragePath: r1csStorageFilePath,
+                        potStoragePath: potStorageFilePath,
+                        initialZkeyStoragePath: zkeyStorageFilePath,
+                        r1csBlake2bHash: blake.blake2bHex(r1csStorageFilePath),
+                        potBlake2bHash: blake.blake2bHex(potStorageFilePath),
+                        initialZkeyBlake2bHash: blake.blake2bHex(zkeyStorageFilePath)
+                    }
+                }
+
+                // nb. these will be validated after the first contribution.
+                const circuitTimings: CircuitTimings = {
+                    avgTimings: {
+                        contributionComputation: 0,
+                        fullContribution: 0,
+                        verifyCloudFunction: 0
+                    }
+                }
+
+                circuits[i] = {
+                    ...circuit,
+                    ...circuitFiles,
+                    ...circuitTimings,
+                    zKeySizeInBytes: getFileStats(zkeyLocalPathAndFileName).size
+                }
+
+                // Reset flags.
+                wannaGenerateZkey = true
+                wannaUsePreDownloadedPoT = false
+            }
+
+            process.stdout.write(`\n`)
+
+            /** POPULATE DB */
+            spinner.text = `Storing ceremony data...`
+            spinner.start()
+
+            // Setup ceremony on the server.
+            await setupCeremony({
+                ceremonyInputData,
+                ceremonyPrefix,
+                circuits
+            })
+
+            // nb. workaround for CF termination.
+            await sleep(1000)
+
+            spinner.succeed(
+                `Congrats, you have successfully completed your ${theme.bold(ceremonyInputData.title)} ceremony setup ${
+                    emojis.tada
+                }`
+            )
+        }
+
+        terminate(username)
+    } catch (err: any) {
+        showError(`Something went wrong: ${err.toString()}`, true)
+    }
+}
+
+export default setup
diff --git a/packages/phase2cli/src/index.ts b/packages/phase2cli/src/index.ts
new file mode 100755
index 00000000..c2599853
--- /dev/null
+++ b/packages/phase2cli/src/index.ts
@@ -0,0 +1,50 @@
+#!/usr/bin/env node
+
+import { createCommand } from "commander"
+import { setup, auth, contribute, observe, finalize, clean, logout } from "./commands/index"
+import { readLocalJsonFile } from "./lib/files"
+
+// Get pkg info (e.g., name, version).
+const pkg = readLocalJsonFile("../../package.json")
+
+const program = createCommand()
+
+// Entry point.
+program.name(pkg.name).description(pkg.description).version(pkg.version)
+
+// User commands.
+program.command("auth").description("authenticate yourself using your Github account (OAuth 2.0)").action(auth)
+program
+    .command("contribute")
+    .description("compute contributions for a Phase2 Trusted Setup ceremony circuits")
+    .action(contribute)
+program
+    .command("clean")
+    .description("clean up output generated by commands from the current working directory")
+    .action(clean)
+program
+    .command("logout")
+    .description("sign out from Firebae Auth service and delete Github OAuth 2.0 token from local storage")
+    .action(logout)
+
+// Only coordinator commands.
+const ceremony = program.command("coordinate").description("commands for coordinating a ceremony")
+
+ceremony
+    .command("setup")
+    .description("setup a Groth16 Phase 2 Trusted Setup ceremony for zk-SNARK circuits")
+    .action(setup)
+
+ceremony
+    .command("observe")
+    .description("observe in real-time the waiting queue of each ceremony circuit")
+    .action(observe)
+
+ceremony
+    .command("finalize")
+    .description(
+        "finalize a Phase2 Trusted Setup ceremony by applying a beacon, exporting verification key and verifier contract"
+    )
+    .action(finalize)
+
+program.parseAsync()
diff --git a/apps/phase2cli/src/lib/auth.ts b/packages/phase2cli/src/lib/auth.ts
similarity index 62%
rename from apps/phase2cli/src/lib/auth.ts
rename to packages/phase2cli/src/lib/auth.ts
index d07d5baa..7c9acd55 100644
--- a/apps/phase2cli/src/lib/auth.ts
+++ b/packages/phase2cli/src/lib/auth.ts
@@ -2,24 +2,24 @@ import Conf from "conf"
 import { FirebaseApp } from "firebase/app"
 import { signInToFirebaseWithGithubToken, getCurrentFirebaseAuthUser } from "@zkmpc/actions"
 import { IdTokenResult, User } from "firebase/auth"
-import { AuthUser } from "../../types/index.js"
-import { readLocalJsonFile } from "./files.js"
-import { GENERIC_ERRORS, GITHUB_ERRORS, showError } from "./errors.js"
-import { emojis, theme } from "./constants.js"
-import { getGithubUsername } from "./utils.js"
+import { AuthUser } from "../../types/index"
+import { readLocalJsonFile } from "./files"
+import { GENERIC_ERRORS, GITHUB_ERRORS, showError } from "./errors"
+import { emojis, theme } from "./constants"
+import { getGithubUsername } from "./utils"
 
 // Get local configs.
 const { name } = readLocalJsonFile("../../package.json")
 
 // Local configstore for storing auth data (e.g., tokens).
 const config = new Conf({
-  projectName: name,
-  schema: {
-    authToken: {
-      type: "string",
-      default: ""
+    projectName: name,
+    schema: {
+        authToken: {
+            type: "string",
+            default: ""
+        }
     }
-  }
 })
 
 /**
@@ -50,9 +50,9 @@ export const deleteStoredOAuthToken = () => config.delete("authToken")
  * @returns <Promise<string>> - the Github OAuth 2.0 token.
  */
 export const checkForStoredOAuthToken = async (): Promise<string> => {
-  if (!hasStoredOAuthToken()) showError(GITHUB_ERRORS.GITHUB_NOT_AUTHENTICATED, true)
+    if (!hasStoredOAuthToken()) showError(GITHUB_ERRORS.GITHUB_NOT_AUTHENTICATED, true)
 
-  return String(getStoredOAuthToken())
+    return String(getStoredOAuthToken())
 }
 
 /**
@@ -61,10 +61,10 @@ export const checkForStoredOAuthToken = async (): Promise<string> => {
  * @returns <Promise<IdTokenResult>>
  */
 const getTokenAndClaims = async (user: User): Promise<IdTokenResult> => {
-  // Force refresh to update custom claims.
-  await user.getIdToken(true)
+    // Force refresh to update custom claims.
+    await user.getIdToken(true)
 
-  return user.getIdTokenResult()
+    return user.getIdTokenResult()
 }
 
 /**
@@ -72,9 +72,9 @@ const getTokenAndClaims = async (user: User): Promise<IdTokenResult> => {
  * @param user <User> - the current authenticated user.
  */
 export const onlyCoordinator = async (user: User) => {
-  const userTokenAndClaims = await getTokenAndClaims(user)
+    const userTokenAndClaims = await getTokenAndClaims(user)
 
-  if (!userTokenAndClaims.claims.coordinator) showError(GENERIC_ERRORS.GENERIC_NOT_COORDINATOR, true)
+    if (!userTokenAndClaims.claims.coordinator) showError(GENERIC_ERRORS.GENERIC_NOT_COORDINATOR, true)
 }
 
 /**
@@ -82,25 +82,25 @@ export const onlyCoordinator = async (user: User) => {
  * @returns <Promise<AuthUser>>
  */
 export const handleCurrentAuthUserSignIn = async (firebaseApp: FirebaseApp): Promise<AuthUser> => {
-  // Get/Set OAuth Token.
-  const token = await checkForStoredOAuthToken()
+    // Get/Set OAuth Token.
+    const token = await checkForStoredOAuthToken()
 
-  // Sign in.
-  // TODO: maybe this is not correct for #171.
-  await signInToFirebaseWithGithubToken(firebaseApp, token)
+    // Sign in.
+    // TODO: maybe this is not correct for #171.
+    await signInToFirebaseWithGithubToken(firebaseApp, token)
 
-  // Get current authenticated user.
-  const user = await getCurrentFirebaseAuthUser(firebaseApp)
+    // Get current authenticated user.
+    const user = await getCurrentFirebaseAuthUser(firebaseApp)
 
-  // Get the username of the authenticated user.
-  const username = await getGithubUsername(token)
+    // Get the username of the authenticated user.
+    const username = await getGithubUsername(token)
 
-  // Greet the user.
-  console.log(`Greetings, @${theme.bold(theme.bold(username))} ${emojis.wave}\n`)
+    // Greet the user.
+    console.log(`Greetings, @${theme.bold(theme.bold(username))} ${emojis.wave}\n`)
 
-  return {
-    user,
-    token,
-    username
-  }
+    return {
+        user,
+        token,
+        username
+    }
 }
diff --git a/packages/phase2cli/src/lib/constants.ts b/packages/phase2cli/src/lib/constants.ts
new file mode 100644
index 00000000..f1b45f17
--- /dev/null
+++ b/packages/phase2cli/src/lib/constants.ts
@@ -0,0 +1,139 @@
+import chalk from "chalk"
+import logSymbols from "log-symbols"
+import emoji from "node-emoji"
+
+/** Theme */
+export const theme = {
+    yellow: chalk.yellow,
+    magenta: chalk.magenta,
+    red: chalk.red,
+    green: chalk.green,
+    underlined: chalk.underline,
+    bold: chalk.bold,
+    italic: chalk.italic
+}
+
+export const symbols = {
+    success: logSymbols.success,
+    warning: logSymbols.warning,
+    error: logSymbols.error,
+    info: logSymbols.info
+}
+
+export const emojis = {
+    tada: emoji.get("tada"),
+    key: emoji.get("key"),
+    broom: emoji.get("broom"),
+    pointDown: emoji.get("point_down"),
+    eyes: emoji.get("eyes"),
+    wave: emoji.get("wave"),
+    clipboard: emoji.get("clipboard"),
+    fire: emoji.get("fire"),
+    clock: emoji.get("hourglass"),
+    dizzy: emoji.get("dizzy_face"),
+    rocket: emoji.get("rocket"),
+    oldKey: emoji.get("old_key"),
+    pray: emoji.get("pray"),
+    moon: emoji.get("moon"),
+    upsideDown: emoji.get("upside_down_face"),
+    arrowUp: emoji.get("arrow_up"),
+    arrowDown: emoji.get("arrow_down")
+}
+
+/** ZK related */
+export const potDownloadUrlTemplate = `https://hermez.s3-eu-west-1.amazonaws.com/`
+export const potFilenameTemplate = `powersOfTau28_hez_final_`
+export const firstZkeyIndex = `00000`
+export const numIterationsExp = 10
+export const solidityVersion = "0.8.0"
+
+/** Commands related */
+export const observationWaitingTimeInMillis = 3000 // 3 seconds.
+
+/** Shared */
+export const names = {
+    output: `output`,
+    setup: `setup`,
+    contribute: `contribute`,
+    finalize: `finalize`,
+    pot: `pot`,
+    zkeys: `zkeys`,
+    vkeys: `vkeys`,
+    metadata: `metadata`,
+    transcripts: `transcripts`,
+    attestation: `attestation`,
+    verifiers: `verifiers`
+}
+
+const outputPath = `./${names.output}`
+const setupPath = `${outputPath}/${names.setup}`
+const contributePath = `${outputPath}/${names.contribute}`
+const finalizePath = `${outputPath}/${names.finalize}`
+const potPath = `${setupPath}/${names.pot}`
+const zkeysPath = `${setupPath}/${names.zkeys}`
+const metadataPath = `${setupPath}/${names.metadata}`
+const contributionsPath = `${contributePath}/${names.zkeys}`
+const contributionTranscriptsPath = `${contributePath}/${names.transcripts}`
+const attestationPath = `${contributePath}/${names.attestation}`
+const finalZkeysPath = `${finalizePath}/${names.zkeys}`
+const finalPotPath = `${finalizePath}/${names.pot}`
+const finalTranscriptsPath = `${finalizePath}/${names.transcripts}`
+const finalAttestationsPath = `${finalizePath}/${names.attestation}`
+const verificationKeysPath = `${finalizePath}/${names.vkeys}`
+const verifierContractsPath = `${finalizePath}/${names.verifiers}`
+
+export const paths = {
+    outputPath,
+    setupPath,
+    contributePath,
+    finalizePath,
+    potPath,
+    zkeysPath,
+    metadataPath,
+    contributionsPath,
+    contributionTranscriptsPath,
+    attestationPath,
+    finalZkeysPath,
+    finalPotPath,
+    finalTranscriptsPath,
+    finalAttestationsPath,
+    verificationKeysPath,
+    verifierContractsPath
+}
+
+/** Firebase */
+export const collections = {
+    users: "users",
+    participants: "participants",
+    ceremonies: "ceremonies",
+    circuits: "circuits",
+    contributions: "contributions",
+    timeouts: "timeouts"
+}
+
+export const ceremoniesCollectionFields = {
+    coordinatorId: "coordinatorId",
+    description: "description",
+    endDate: "endDate",
+    lastUpdated: "lastUpdated",
+    prefix: "prefix",
+    startDate: "startDate",
+    state: "state",
+    title: "title",
+    type: "type"
+}
+
+export const contributionsCollectionFields = {
+    contributionTime: "contributionTime",
+    files: "files",
+    lastUpdated: "lastUpdated",
+    participantId: "participantId",
+    valid: "valid",
+    verificationTime: "verificationTime",
+    zkeyIndex: "zKeyIndex"
+}
+
+export const timeoutsCollectionFields = {
+    startDate: "startDate",
+    endDate: "endDate"
+}
diff --git a/packages/phase2cli/src/lib/errors.ts b/packages/phase2cli/src/lib/errors.ts
new file mode 100644
index 00000000..fb42b88d
--- /dev/null
+++ b/packages/phase2cli/src/lib/errors.ts
@@ -0,0 +1,51 @@
+import { symbols } from "./constants"
+
+/** Firebase */
+export const FIREBASE_ERRORS = {
+    FIREBASE_NOT_CONFIGURED_PROPERLY: `Check that all FIREBASE environment variables are configured properly`,
+    FIREBASE_DEFAULT_APP_DOUBLE_CONFIG: `Wrong double default configuration for Firebase application`,
+    FIREBASE_TOKEN_EXPIRED_REMOVED_PERMISSIONS: `Unsuccessful check authorization response from Github. This usually happens when a token expires or the CLI do not have permissions associated with your Github account`,
+    FIREBASE_USER_DISABLED: `Your Github account has been disabled and can no longer be used to contribute. Get in touch with the coordinator to find out more`,
+    FIREBASE_FAILED_CREDENTIALS_VERIFICATION: `Firebase cannot verify your Github credentials. This usually happens due to network errors`,
+    FIREBASE_NETWORK_ERROR: `Unable to reach Firebase. This usually happens due to network errors`,
+    FIREBASE_CEREMONY_NOT_OPENED: `There are no ceremonies opened to contributions`,
+    FIREBASE_CEREMONY_NOT_CLOSED: `There are no ceremonies ready to finalization`
+}
+
+/** Github */
+export const GITHUB_ERRORS = {
+    GITHUB_NOT_CONFIGURED_PROPERLY: `Github \`CLIENT_ID\` environment variable is not configured properly`,
+    GITHUB_ACCOUNT_ASSOCIATION_REJECTED: `You refused to associate your Github account with the CLI`,
+    GITHUB_SERVER_TIMEDOUT: `Github server has timed out. This usually happens due to network error or Github server downtime`,
+    GITHUB_GET_USERNAME_FAILED: `Something went wrong while retrieving your Github username`,
+    GITHUB_NOT_AUTHENTICATED: `You are not authenticated. Please, run \`phase2cli auth\` command first`,
+    GITHUB_GIST_PUBLICATION_FAILED: `Something went wrong while publishing a Gist from your Github account`
+}
+
+/** Generic */
+export const GENERIC_ERRORS = {
+    GENERIC_NOT_CONFIGURED_PROPERLY: `Check that all CONFIG environment variables are configured properly`,
+    GENERIC_ERROR_RETRIEVING_DATA: `Something went wrong when retrieving the data from the database`,
+    GENERIC_FILE_ERROR: `File not found`,
+    GENERIC_NOT_COORDINATOR: `You are not a coordinator for the ceremony`,
+    GENERIC_COUNTDOWN_EXPIRED: `The amount of time for completing the operation has expired`,
+    GENERIC_R1CS_MISSING_INFO: `The necessary information was not found in the given R1CS file`,
+    GENERIC_COUNTDOWN_EXPIRATION: `Your time to carry out the action has expired`,
+    GENERIC_CEREMONY_SELECTION: `You have aborted the ceremony selection process`,
+    GENERIC_CIRCUIT_SELECTION: `You have aborted the circuit selection process`,
+    GENERIC_DATA_INPUT: `You have aborted the process without providing any of the requested data`,
+    GENERIC_CONTRIBUTION_HASH_INVALID: `You have aborted the process and do not have provided the requested data`
+}
+
+/**
+ * Print an error string and gracefully terminate the process.
+ * @param err <string> - the error string to be shown.
+ * @param doExit <boolean> - when true the function terminate the process; otherwise not.
+ */
+export const showError = (err: string, doExit: boolean) => {
+    // Print the error.
+    console.error(`${symbols.error} ${err}`)
+
+    // Terminate the process.
+    if (doExit) process.exit(0)
+}
diff --git a/apps/phase2cli/src/lib/files.ts b/packages/phase2cli/src/lib/files.ts
similarity index 64%
rename from apps/phase2cli/src/lib/files.ts
rename to packages/phase2cli/src/lib/files.ts
index 40da18ab..bb362281 100644
--- a/apps/phase2cli/src/lib/files.ts
+++ b/packages/phase2cli/src/lib/files.ts
@@ -5,8 +5,7 @@ import { promisify } from "node:util"
 import fetch from "node-fetch"
 import path from "path"
 import { fileURLToPath } from "url"
-import { GENERIC_ERRORS, showError } from "./errors.js"
-
+import { GENERIC_ERRORS, showError } from "./errors"
 /**
  * Check a directory path
  * @param filePath <string> - the absolute or relative path.
@@ -16,23 +15,23 @@ export const directoryExists = (filePath: string): boolean => fs.existsSync(file
 
 /**
  * Write a new file locally.
- * @param path <string> - local path for file with extension.
+ * @param writePath <string> - local path for file with extension.
  * @param data <Buffer> - content to be written.
  */
-export const writeFile = (path: string, data: Buffer): void => fs.writeFileSync(path, data)
+export const writeFile = (writePath: string, data: Buffer): void => fs.writeFileSync(writePath, data)
 
 /**
  * Read a new file from local storage.
- * @param path <string> - local path for file with extension.
+ * @param readPath <string> - local path for file with extension.
  */
-export const readFile = (path: string): string => fs.readFileSync(path, "utf-8")
+export const readFile = (readPath: string): string => fs.readFileSync(readPath, "utf-8")
 
 /**
  * Get back the statistics of the provided file.
- * @param path <string> - local path for file with extension.
+ * @param getStatsPath <string> - local path for file with extension.
  * @returns <Stats>
  */
-export const getFileStats = (path: string): Stats => fs.statSync(path)
+export const getFileStats = (getStatsPath: string): Stats => fs.statSync(getStatsPath)
 
 /**
  * Return the sub paths for each file stored in the given directory.
@@ -40,11 +39,11 @@ export const getFileStats = (path: string): Stats => fs.statSync(path)
  * @returns
  */
 export const getDirFilesSubPaths = async (dirPath: string): Promise<Array<Dirent>> => {
-  // Get Dirent sub paths for folders and files.
-  const subPaths = await fs.promises.readdir(dirPath, { withFileTypes: true })
+    // Get Dirent sub paths for folders and files.
+    const subPaths = await fs.promises.readdir(dirPath, { withFileTypes: true })
 
-  // Return Dirent sub paths for files only.
-  return subPaths.filter((dirent: Dirent) => dirent.isFile())
+    // Return Dirent sub paths for files only.
+    return subPaths.filter((dirent: Dirent) => dirent.isFile())
 }
 
 /**
@@ -54,14 +53,14 @@ export const getDirFilesSubPaths = async (dirPath: string): Promise<Array<Dirent
  * @returns <string>
  */
 export const getMatchingSubPathFile = (subPaths: Array<Dirent>, fileNameToMatch: string): string => {
-  // Filter.
-  const matchingPaths = subPaths.filter((subpath: Dirent) => subpath.name === fileNameToMatch)
+    // Filter.
+    const matchingPaths = subPaths.filter((subpath: Dirent) => subpath.name === fileNameToMatch)
 
-  // Check.
-  if (!matchingPaths.length) showError(GENERIC_ERRORS.GENERIC_FILE_ERROR, true)
+    // Check.
+    if (!matchingPaths.length) showError(GENERIC_ERRORS.GENERIC_FILE_ERROR, true)
 
-  // Return file name.
-  return matchingPaths[0].name
+    // Return file name.
+    return matchingPaths[0].name
 }
 
 /**
@@ -69,7 +68,7 @@ export const getMatchingSubPathFile = (subPaths: Array<Dirent>, fileNameToMatch:
  * @param dirPath <string> - the directory path.
  */
 export const deleteDir = (dirPath: string): void => {
-  fs.rmSync(dirPath, { recursive: true, force: true })
+    fs.rmSync(dirPath, { recursive: true, force: true })
 }
 
 /**
@@ -77,8 +76,8 @@ export const deleteDir = (dirPath: string): void => {
  * @param dirPath <string> - the directory path.
  */
 export const cleanDir = (dirPath: string): void => {
-  deleteDir(dirPath)
-  fs.mkdirSync(dirPath)
+    deleteDir(dirPath)
+    fs.mkdirSync(dirPath)
 }
 
 /**
@@ -86,7 +85,7 @@ export const cleanDir = (dirPath: string): void => {
  * @param dirPath <string> - the directory path.
  */
 export const checkAndMakeNewDirectoryIfNonexistent = (dirPath: string): void => {
-  if (!directoryExists(dirPath)) fs.mkdirSync(dirPath)
+    if (!directoryExists(dirPath)) fs.mkdirSync(dirPath)
 }
 
 /**
@@ -95,9 +94,9 @@ export const checkAndMakeNewDirectoryIfNonexistent = (dirPath: string): void =>
  * @returns <any>
  */
 export const readJSONFile = (filePath: string): any => {
-  if (!directoryExists(filePath)) showError(GENERIC_ERRORS.GENERIC_FILE_ERROR, true)
+    if (!directoryExists(filePath)) showError(GENERIC_ERRORS.GENERIC_FILE_ERROR, true)
 
-  return JSON.parse(readFile(filePath))
+    return JSON.parse(readFile(filePath))
 }
 
 /**
@@ -106,7 +105,7 @@ export const readJSONFile = (filePath: string): any => {
  * @param data <JSON>
  */
 export const writeLocalJsonFile = (filePath: string, data: JSON) => {
-  fs.writeFileSync(filePath, JSON.stringify(data), "utf-8")
+    fs.writeFileSync(filePath, JSON.stringify(data), "utf-8")
 }
 
 /**
@@ -114,8 +113,8 @@ export const writeLocalJsonFile = (filePath: string, data: JSON) => {
  * @returns <string> - the local project (e.g., dist/) directory name.
  */
 export const getLocalDirname = (): string => {
-  const filename = fileURLToPath(import.meta.url)
-  return path.dirname(filename)
+    const filename = fileURLToPath(import.meta.url)
+    return path.dirname(filename)
 }
 
 /**
@@ -138,9 +137,9 @@ export const readLocalJsonFile = (filePath: string): any => readJSONFile(path.jo
  * @returns <Promise<boolean>>
  */
 export const checkIfDirectoryIsEmpty = async (dirPath: string): Promise<boolean> => {
-  const dirNumberOfFiles = await getDirFilesSubPaths(dirPath)
+    const dirNumberOfFiles = await getDirFilesSubPaths(dirPath)
 
-  return !(dirNumberOfFiles.length > 0)
+    return !(dirNumberOfFiles.length > 0)
 }
 
 /**
@@ -149,11 +148,11 @@ export const checkIfDirectoryIsEmpty = async (dirPath: string): Promise<boolean>
  * @param url <string> - the download url.
  */
 export const downloadFileFromUrl = async (dest: string, url: string): Promise<void> => {
-  const streamPipeline = promisify(pipeline)
+    const streamPipeline = promisify(pipeline)
 
-  const response = await fetch(url)
+    const response = await fetch(url)
 
-  if (!response.ok) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
+    if (!response.ok) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
 
-  if (response.body) await streamPipeline(response.body, createWriteStream(dest))
+    if (response.body) await streamPipeline(response.body, createWriteStream(dest))
 }
diff --git a/apps/phase2cli/src/lib/firebase.ts b/packages/phase2cli/src/lib/firebase.ts
similarity index 53%
rename from apps/phase2cli/src/lib/firebase.ts
rename to packages/phase2cli/src/lib/firebase.ts
index 7b23e1c3..cbaf5b8c 100644
--- a/apps/phase2cli/src/lib/firebase.ts
+++ b/packages/phase2cli/src/lib/firebase.ts
@@ -1,27 +1,26 @@
 import { FirebaseApp, FirebaseOptions, initializeApp } from "firebase/app"
 import {
-  collection as collectionRef,
-  doc,
-  DocumentData,
-  DocumentSnapshot,
-  Firestore,
-  getDoc,
-  getDocs,
-  getFirestore,
-  query,
-  QueryConstraint,
-  QueryDocumentSnapshot,
-  QuerySnapshot
+    collection as collectionRef,
+    doc,
+    DocumentData,
+    DocumentSnapshot,
+    Firestore,
+    getDoc,
+    getDocs,
+    getFirestore,
+    query,
+    QueryConstraint,
+    QueryDocumentSnapshot,
+    QuerySnapshot
 } from "firebase/firestore"
 import { Functions, getFunctions } from "firebase/functions"
-import { FirebaseStorage, getBytes, getDownloadURL, getStorage, ref, uploadBytes, UploadResult } from "firebase/storage"
+import { FirebaseStorage, getBytes, getDownloadURL, ref, uploadBytes, UploadResult } from "firebase/storage"
 import { readFileSync } from "fs"
-import { FirebaseServices } from "../../types/index.js"
-import { FIREBASE_ERRORS, showError } from "./errors.js"
-import { readLocalJsonFile } from "./files.js"
+import dotenv from "dotenv"
+import { FirebaseServices } from "../../types/index"
+import { FIREBASE_ERRORS, showError } from "./errors"
 
-// Get local configs.
-const { firebase } = readLocalJsonFile("../../env.json")
+dotenv.config()
 
 /** Firebase App and services */
 let firebaseApp: FirebaseApp
@@ -43,18 +42,6 @@ const initializeFirebaseApp = (options: FirebaseOptions): FirebaseApp => initial
  */
 const getFirestoreDatabase = (app: FirebaseApp): Firestore => getFirestore(app)
 
-/**
- * This method returns the Firestore storage instance associated to the given Firebase application.
- * @param app <FirebaseApp> - the Firebase application.
- * @returns <Firestore> - the Firebase Storage associated to the application.
- */
-const getFirebaseStorage = (app: FirebaseApp): FirebaseStorage => {
-  if (app.options.storageBucket !== `${`${app.options.projectId}.appspot.com`}`)
-    showError(FIREBASE_ERRORS.FIREBASE_NOT_CONFIGURED_PROPERLY, true)
-
-  return getStorage(app)
-}
-
 /**
  * This method returns the Cloud Functions instance associated to the given Firebase application.
  * @param app <FirebaseApp> - the Firebase application.
@@ -67,35 +54,31 @@ const getFirebaseFunctions = (app: FirebaseApp): Functions => getFunctions(app)
  * @returns <Promise<FirebaseServices>> - the initialized Firebase services.
  */
 export const initServices = async (): Promise<FirebaseServices> => {
-  if (
-    !firebase.FIREBASE_API_KEY ||
-    !firebase.FIREBASE_AUTH_DOMAIN ||
-    !firebase.FIREBASE_PROJECT_ID ||
-    !firebase.FIREBASE_STORAGE_BUCKET ||
-    !firebase.FIREBASE_MESSAGING_SENDER_ID ||
-    !firebase.FIREBASE_APP_ID ||
-    !firebase.FIREBASE_CF_URL_VERIFY_CONTRIBUTION
-  )
-    showError(FIREBASE_ERRORS.FIREBASE_NOT_CONFIGURED_PROPERLY, true)
-
-  firebaseApp = initializeFirebaseApp({
-    apiKey: firebase.FIREBASE_API_KEY,
-    authDomain: firebase.FIREBASE_AUTH_DOMAIN,
-    projectId: firebase.FIREBASE_PROJECT_ID,
-    storageBucket: firebase.FIREBASE_STORAGE_BUCKET,
-    messagingSenderId: firebase.FIREBASE_MESSAGING_SENDER_ID,
-    appId: firebase.FIREBASE_APP_ID
-  })
-  firestoreDatabase = getFirestoreDatabase(firebaseApp)
-  firebaseStorage = getFirebaseStorage(firebaseApp)
-  firebaseFunctions = getFirebaseFunctions(firebaseApp)
-
-  return {
-    firebaseApp,
-    firestoreDatabase,
-    firebaseStorage,
-    firebaseFunctions
-  }
+    if (
+        !process.env.FIREBASE_API_KEY ||
+        !process.env.FIREBASE_AUTH_DOMAIN ||
+        !process.env.FIREBASE_PROJECT_ID ||
+        !process.env.FIREBASE_MESSAGING_SENDER_ID ||
+        !process.env.FIREBASE_APP_ID ||
+        !process.env.FIREBASE_CF_URL_VERIFY_CONTRIBUTION
+    )
+        showError(FIREBASE_ERRORS.FIREBASE_NOT_CONFIGURED_PROPERLY, true)
+
+    firebaseApp = initializeFirebaseApp({
+        apiKey: process.env.FIREBASE_API_KEY,
+        authDomain: process.env.FIREBASE_AUTH_DOMAIN,
+        projectId: process.env.FIREBASE_PROJECT_ID,
+        messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
+        appId: process.env.FIREBASE_APP_ID
+    })
+    firestoreDatabase = getFirestoreDatabase(firebaseApp)
+    firebaseFunctions = getFirebaseFunctions(firebaseApp)
+
+    return {
+        firebaseApp,
+        firestoreDatabase,
+        firebaseFunctions
+    }
 }
 
 /**
@@ -105,12 +88,12 @@ export const initServices = async (): Promise<FirebaseServices> => {
  * @returns <Promise<DocumentSnapshot<DocumentData>>> - return the document from Firestore.
  */
 export const getDocumentById = async (
-  collection: string,
-  documentUID: string
+    collection: string,
+    documentUID: string
 ): Promise<DocumentSnapshot<DocumentData>> => {
-  const docRef = doc(firestoreDatabase, collection, documentUID)
+    const docRef = doc(firestoreDatabase, collection, documentUID)
 
-  return getDoc(docRef)
+    return getDoc(docRef)
 }
 
 /**
@@ -120,14 +103,14 @@ export const getDocumentById = async (
  * @returns <Promise<QuerySnapshot<DocumentData>>> - return the matching documents (if any).
  */
 export const queryCollection = async (
-  collection: string,
-  queryConstraints: Array<QueryConstraint>
+    collection: string,
+    queryConstraints: Array<QueryConstraint>
 ): Promise<QuerySnapshot<DocumentData>> => {
-  // Make a query.
-  const q = query(collectionRef(firestoreDatabase, collection), ...queryConstraints)
+    // Make a query.
+    const q = query(collectionRef(firestoreDatabase, collection), ...queryConstraints)
 
-  // Get docs.
-  return getDocs(q)
+    // Get docs.
+    return getDocs(q)
 }
 
 /**
@@ -136,7 +119,7 @@ export const queryCollection = async (
  * @returns <Promise<Array<QueryDocumentSnapshot<DocumentData>>>> - return all documents (if any).
  */
 export const getAllCollectionDocs = async (collection: string): Promise<Array<QueryDocumentSnapshot<DocumentData>>> =>
-  (await getDocs(collectionRef(firestoreDatabase, collection))).docs
+    (await getDocs(collectionRef(firestoreDatabase, collection))).docs
 
 /**
  * Download locally a zkey file from storage.
@@ -144,11 +127,11 @@ export const getAllCollectionDocs = async (collection: string): Promise<Array<Qu
  * @returns <Promise<any>>
  */
 export const downloadFileFromStorage = async (path: string): Promise<Buffer> => {
-  // Create a reference with folder path.
-  const pathReference = ref(firebaseStorage, path)
+    // Create a reference with folder path.
+    const pathReference = ref(firebaseStorage, path)
 
-  // Bufferized file content.
-  return Buffer.from(await getBytes(pathReference))
+    // Bufferized file content.
+    return Buffer.from(await getBytes(pathReference))
 }
 
 /**
@@ -158,10 +141,10 @@ export const downloadFileFromStorage = async (path: string): Promise<Buffer> =>
  * @returns <Promise<any>>
  */
 export const uploadFileToStorage = async (localPath: string, storagePath: string): Promise<UploadResult> => {
-  // Create a reference with folder path.
-  const pathReference = ref(firebaseStorage, storagePath)
+    // Create a reference with folder path.
+    const pathReference = ref(firebaseStorage, storagePath)
 
-  return uploadBytes(pathReference, readFileSync(localPath))
+    return uploadBytes(pathReference, readFileSync(localPath))
 }
 
 /**
@@ -171,17 +154,17 @@ export const uploadFileToStorage = async (localPath: string, storagePath: string
  * @returns
  */
 export const checkIfStorageFileExists = async (pathToFile: string): Promise<boolean> => {
-  try {
-    // Get a reference.
-    const pathReference = ref(firebaseStorage, pathToFile)
-
-    // Try to get url for download.
-    await getDownloadURL(pathReference)
-
-    // Url for download exists (true).
-    return true
-  } catch (error) {
-    // Url does not exists (false).
-    return false
-  }
+    try {
+        // Get a reference.
+        const pathReference = ref(firebaseStorage, pathToFile)
+
+        // Try to get url for download.
+        await getDownloadURL(pathReference)
+
+        // Url for download exists (true).
+        return true
+    } catch (error) {
+        // Url does not exists (false).
+        return false
+    }
 }
diff --git a/packages/phase2cli/src/lib/listeners.ts b/packages/phase2cli/src/lib/listeners.ts
new file mode 100644
index 00000000..e10aa932
--- /dev/null
+++ b/packages/phase2cli/src/lib/listeners.ts
@@ -0,0 +1,595 @@
+import { DocumentData, DocumentSnapshot, Firestore, onSnapshot } from "firebase/firestore"
+import { Functions, HttpsCallable, httpsCallable } from "firebase/functions"
+import { getCeremonyCircuits } from "@zkmpc/actions"
+import { FirebaseDocumentInfo, ParticipantContributionStep, ParticipantStatus } from "../../types/index"
+import { collections, emojis, symbols, theme } from "./constants"
+import { getCurrentContributorContribution } from "./queries"
+import {
+    convertToDoubleDigits,
+    customSpinner,
+    formatZkeyIndex,
+    generatePublicAttestation,
+    getContributorContributionsVerificationResults,
+    getNextCircuitForContribution,
+    getParticipantCurrentDiskAvailableSpace,
+    getSecondsMinutesHoursFromMillis,
+    handleTimedoutMessageForContributor,
+    makeContribution,
+    simpleCountdown,
+    simpleLoader,
+    terminate
+} from "./utils"
+import { GENERIC_ERRORS, showError } from "./errors"
+import { getDocumentById } from "./firebase"
+import { askForConfirmation } from "./prompts"
+import { convertToGB } from "./storage"
+
+/**
+ * Return the memory space requirement for a zkey in GB.
+ * @param zKeySizeInBytes <number> - the size of the zkey in bytes.
+ * @returns <number>
+ */
+const getZkeysSpaceRequirementsForContributionInGB = (zKeySizeInBytes: number): number =>
+    // nb. mul per 2 is necessary because download latest + compute newest.
+    convertToGB(zKeySizeInBytes * 2, true)
+
+/**
+ * Return the available disk space of the current contributor in GB.
+ * @returns <number>
+ */
+const getContributorAvailableDiskSpaceInGB = (): number => convertToGB(getParticipantCurrentDiskAvailableSpace(), false)
+
+/**
+ * Check if the contributor has enough space before starting the contribution for next circuit.
+ * @param cf <HttpsCallable<unknown, unknown>> - the corresponding cloud function.
+ * @param nextCircuit <FirebaseDocumentInfo> - the circuit document.
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
+ * @return <Promise<void>>
+ */
+const handleDiskSpaceRequirementForNextContribution = async (
+    cf: HttpsCallable<unknown, unknown>,
+    nextCircuit: FirebaseDocumentInfo,
+    ceremonyId: string
+): Promise<boolean> => {
+    // Get memory info.
+    const zKeysSpaceRequirementsInGB = getZkeysSpaceRequirementsForContributionInGB(nextCircuit.data.zKeySizeInBytes)
+    const availableDiskSpaceInGB = getContributorAvailableDiskSpaceInGB()
+
+    // Extract data.
+    const { sequencePosition } = nextCircuit.data
+
+    process.stdout.write(`\n`)
+
+    await simpleLoader(`Checking your memory...`, `clock`, 1000)
+
+    // Check memory requirement.
+    if (availableDiskSpaceInGB < zKeysSpaceRequirementsInGB) {
+        console.log(theme.bold(`- Circuit # ${theme.magenta(`${sequencePosition}`)}`))
+
+        console.log(
+            `${symbols.error} You do not have enough memory to make a contribution (Required ${
+                zKeysSpaceRequirementsInGB < 0.01 ? theme.bold(`< 0.01`) : theme.bold(zKeysSpaceRequirementsInGB)
+            } GB (available ${
+                availableDiskSpaceInGB > 0 ? theme.bold(availableDiskSpaceInGB.toFixed(2)) : theme.bold(0)
+            } GB)\n`
+        )
+
+        if (sequencePosition > 1) {
+            // The user has computed at least one valid contribution. Therefore, can choose if free up memory and contrinue with next contribution or generate the final attestation.
+            console.log(
+                `${symbols.info} You have time until ceremony ends to free up your memory, complete contributions and publish the attestation`
+            )
+
+            const { confirmation } = await askForConfirmation(
+                `Are you sure you want to generate and publish the attestation for your contributions?`
+            )
+
+            if (!confirmation) {
+                process.stdout.write(`\n`)
+
+                // nb. here the user is not able to generate an attestation because does not have contributed yet. Therefore, return an error and exit.
+                showError(`Please, free up your disk space and run again this command to contribute`, true)
+            }
+        } else {
+            // nb. here the user is not able to generate an attestation because does not have contributed yet. Therefore, return an error and exit.
+            showError(`Please, free up your disk space and run again this command to contribute`, true)
+        }
+    } else {
+        console.log(
+            `${symbols.success} You have enough memory for contributing to ${theme.bold(
+                `Circuit ${theme.magenta(sequencePosition)}`
+            )}`
+        )
+
+        const spinner = customSpinner(
+            `Joining ${theme.bold(`Circuit ${theme.magenta(sequencePosition)}`)} waiting queue...`,
+            `clock`
+        )
+        spinner.start()
+
+        await cf({ ceremonyId })
+
+        spinner.succeed(`All set for contribution to ${theme.bold(`Circuit ${theme.magenta(sequencePosition)}`)}`)
+
+        return false
+    }
+
+    return true
+}
+
+/**
+ * Return the index of a given participant in a circuit waiting queue.
+ * @param contributors <Array<string>> - the list of the contributors in queue for a circuit.
+ * @param participantId <string> - the unique identifier of the participant.
+ * @returns <number>
+ */
+const getParticipantPositionInQueue = (contributors: Array<string>, participantId: string): number =>
+    contributors.indexOf(participantId) + 1
+
+/**
+ * Listen to circuit document changes and reacts in realtime.
+ * @param participantId <string> - the unique identifier of the contributor.
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
+ * @param circuit <FirebaseDocumentInfo> - the document information about the current circuit.
+ */
+const listenToCircuitChanges = (participantId: string, ceremonyId: string, circuit: FirebaseDocumentInfo) => {
+    const unsubscriberForCircuitDocument = onSnapshot(circuit.ref, async (circuitDocSnap: DocumentSnapshot) => {
+        // Get updated data from snap.
+        const newCircuitData = circuitDocSnap.data()
+
+        if (!newCircuitData) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
+
+        // Get data.
+        const { avgTimings, waitingQueue } = newCircuitData!
+        const { fullContribution, verifyCloudFunction } = avgTimings
+        const { currentContributor, completedContributions } = waitingQueue
+
+        // Retrieve current contributor data.
+        const currentContributorDoc = await getDocumentById(
+            `${collections.ceremonies}/${ceremonyId}/${collections.participants}`,
+            currentContributor
+        )
+
+        // Get updated data from snap.
+        const currentContributorData = currentContributorDoc.data()
+
+        if (!currentContributorData) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
+
+        // Get updated position for contributor in the queue.
+        const newParticipantPositionInQueue = getParticipantPositionInQueue(waitingQueue.contributors, participantId)
+
+        let newEstimatedWaitingTime = 0
+
+        // Show new time estimation.
+        if (fullContribution > 0 && verifyCloudFunction > 0)
+            newEstimatedWaitingTime = (fullContribution + verifyCloudFunction) * (newParticipantPositionInQueue - 1)
+
+        const {
+            seconds: estSeconds,
+            minutes: estMinutes,
+            hours: estHours
+        } = getSecondsMinutesHoursFromMillis(newEstimatedWaitingTime)
+
+        // Check if is the current contributor.
+        if (newParticipantPositionInQueue === 1) {
+            console.log(
+                `\n${symbols.success} Your turn has come ${emojis.tada}\n${symbols.info} Your contribution will begin soon`
+            )
+            unsubscriberForCircuitDocument()
+        } else {
+            // Position and time.
+            console.log(
+                `\n${symbols.info} ${
+                    newParticipantPositionInQueue === 2
+                        ? `You are the next contributor`
+                        : `Your position in the waiting queue is ${theme.bold(
+                              theme.magenta(newParticipantPositionInQueue - 1)
+                          )}`
+                } (${
+                    newEstimatedWaitingTime > 0
+                        ? `${theme.bold(
+                              `${convertToDoubleDigits(estHours)}:${convertToDoubleDigits(
+                                  estMinutes
+                              )}:${convertToDoubleDigits(estSeconds)}`
+                          )} left before your turn)`
+                        : `no time estimation)`
+                }`
+            )
+
+            // Participant data.
+            console.log(` - Contributor # ${theme.bold(theme.magenta(completedContributions + 1))}`)
+
+            // Data for displaying info about steps.
+            const currentZkeyIndex = formatZkeyIndex(completedContributions)
+            const nextZkeyIndex = formatZkeyIndex(completedContributions + 1)
+
+            let interval: NodeJS.Timer
+
+            const unsubscriberForCurrentContributorDocument = onSnapshot(
+                currentContributorDoc.ref,
+                async (currentContributorDocSnap: DocumentSnapshot) => {
+                    // Get updated data from snap.
+                    const newCurrentContributorData = currentContributorDocSnap.data()
+
+                    if (!newCurrentContributorData) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
+
+                    // Get current contributor data.
+                    const { contributionStep, contributionStartedAt } = newCurrentContributorData!
+
+                    // Average time.
+                    const timeSpentWhileContributing = Date.now() - contributionStartedAt
+                    const remainingTime = fullContribution - timeSpentWhileContributing
+
+                    // Clear previous step interval (if exist).
+                    if (interval) clearInterval(interval)
+
+                    switch (contributionStep) {
+                        case ParticipantContributionStep.DOWNLOADING: {
+                            const message = `   ${symbols.info} Downloading contribution ${theme.bold(
+                                `#${currentZkeyIndex}`
+                            )}`
+                            interval = simpleCountdown(remainingTime, message)
+
+                            break
+                        }
+                        case ParticipantContributionStep.COMPUTING: {
+                            process.stdout.write(
+                                `   ${symbols.success} Contribution ${theme.bold(
+                                    `#${currentZkeyIndex}`
+                                )} correctly downloaded\n`
+                            )
+
+                            const message = `   ${symbols.info} Computing contribution ${theme.bold(
+                                `#${nextZkeyIndex}`
+                            )}`
+                            interval = simpleCountdown(remainingTime, message)
+
+                            break
+                        }
+                        case ParticipantContributionStep.UPLOADING: {
+                            process.stdout.write(
+                                `   ${symbols.success} Contribution ${theme.bold(
+                                    `#${nextZkeyIndex}`
+                                )} successfully computed\n`
+                            )
+
+                            const message = `   ${symbols.info} Uploading contribution ${theme.bold(
+                                `#${nextZkeyIndex}`
+                            )}`
+                            interval = simpleCountdown(remainingTime, message)
+
+                            break
+                        }
+                        case ParticipantContributionStep.VERIFYING: {
+                            process.stdout.write(
+                                `   ${symbols.success} Contribution ${theme.bold(
+                                    `#${nextZkeyIndex}`
+                                )} successfully uploaded\n`
+                            )
+
+                            const message = `   ${symbols.info} Contribution verification ${theme.bold(
+                                `#${nextZkeyIndex}`
+                            )}`
+                            interval = simpleCountdown(remainingTime, message)
+
+                            break
+                        }
+                        case ParticipantContributionStep.COMPLETED: {
+                            process.stdout.write(
+                                `   ${symbols.success} Contribution ${theme.bold(
+                                    `#${nextZkeyIndex}`
+                                )} has been correctly verified\n`
+                            )
+
+                            const currentContributorContributions = await getCurrentContributorContribution(
+                                ceremonyId,
+                                circuit.id,
+                                currentContributorDocSnap.id
+                            )
+
+                            if (currentContributorContributions.length !== 1)
+                                process.stdout.write(`   ${symbols.error} We could not recover the contribution data`)
+                            else {
+                                const contribution = currentContributorContributions.at(0)
+
+                                const data = contribution?.data
+
+                                console.log(
+                                    `   ${data?.valid ? symbols.success : symbols.error} Contribution ${theme.bold(
+                                        `#${nextZkeyIndex}`
+                                    )} is ${data?.valid ? `VALID` : `INVALID`}`
+                                )
+                            }
+
+                            unsubscriberForCurrentContributorDocument()
+                            break
+                        }
+                        default: {
+                            showError(`Wrong contribution step`, true)
+                            break
+                        }
+                    }
+                }
+            )
+        }
+    })
+}
+
+// Listen to changes on the user-related participant document.
+export default async (
+    participantDoc: DocumentSnapshot<DocumentData>,
+    ceremony: FirebaseDocumentInfo,
+    firestoreDatabase: Firestore,
+    circuits: Array<FirebaseDocumentInfo>,
+    firebaseFunctions: Functions,
+    ghToken: string,
+    ghUsername: string,
+    entropy: string
+) => {
+    // Get number of circuits for the selected ceremony.
+    const numberOfCircuits = circuits.length
+
+    // Listen to participant document changes.
+    const unsubscriberForParticipantDocument = onSnapshot(
+        participantDoc.ref,
+        async (participantDocSnap: DocumentSnapshot) => {
+            // Get updated data from snap.
+            const newParticipantData = participantDocSnap.data()
+            const oldParticipantData = participantDoc.data()
+
+            if (!newParticipantData || !oldParticipantData)
+                showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
+
+            // Extract updated participant document data.
+            const { contributionProgress, status, contributionStep, contributions, tempContributionData } =
+                newParticipantData!
+            const {
+                contributionStep: oldContributionStep,
+                tempContributionData: oldTempContributionData,
+                contributionProgress: oldContributionProgress,
+                contributions: oldContributions,
+                status: oldStatus
+            } = oldParticipantData!
+            const participantId = participantDoc.id
+
+            // 0. Whem joining for the first time the waiting queue.
+            if (
+                status === ParticipantStatus.WAITING &&
+                !contributionStep &&
+                !contributions.length &&
+                contributionProgress === 0
+            ) {
+                // Get next circuit.
+                const nextCircuit = getNextCircuitForContribution(circuits, contributionProgress + 1)
+
+                // Check disk space requirements for participant.
+                const makeProgressToNextContribution = httpsCallable(
+                    firebaseFunctions,
+                    "makeProgressToNextContribution"
+                )
+                await handleDiskSpaceRequirementForNextContribution(
+                    makeProgressToNextContribution,
+                    nextCircuit,
+                    ceremony.id
+                )
+            }
+
+            // A. Do not have completed the contributions for each circuit; move to the next one.
+            if (contributionProgress > 0 && contributionProgress <= circuits.length) {
+                // Get updated circuits data.
+                const updatedCircuits = await getCeremonyCircuits(firestoreDatabase, ceremony.id)
+                const circuit = updatedCircuits[contributionProgress - 1]
+                const { waitingQueue } = circuit.data
+
+                // Check if the contribution step is valid for starting/resuming the contribution.
+                const isStepValidForStartingOrResumingContribution =
+                    (contributionStep === ParticipantContributionStep.DOWNLOADING &&
+                        status === ParticipantStatus.CONTRIBUTING &&
+                        (!oldContributionStep ||
+                            oldContributionStep !== contributionStep ||
+                            (oldContributionStep === contributionStep &&
+                                status === oldStatus &&
+                                oldContributionProgress === contributionProgress) ||
+                            oldStatus === ParticipantStatus.EXHUMED)) ||
+                    (contributionStep === ParticipantContributionStep.COMPUTING &&
+                        oldContributionStep === contributionStep &&
+                        oldContributions.length === contributions.length) ||
+                    (contributionStep === ParticipantContributionStep.UPLOADING &&
+                        !oldTempContributionData &&
+                        !tempContributionData &&
+                        contributionStep === oldContributionStep) ||
+                    (!!oldTempContributionData &&
+                        !!tempContributionData &&
+                        JSON.stringify(Object.keys(oldTempContributionData).sort()) ===
+                            JSON.stringify(Object.keys(tempContributionData).sort()) &&
+                        JSON.stringify(Object.values(oldTempContributionData).sort()) ===
+                            JSON.stringify(Object.values(tempContributionData).sort()))
+
+                // A.1 If the participant is in `waiting` status, he/she must receive updates from the circuit's waiting queue.
+                if (status === ParticipantStatus.WAITING && oldStatus !== ParticipantStatus.TIMEDOUT) {
+                    console.log(
+                        `${theme.bold(
+                            `\n- Circuit # ${theme.magenta(`${circuit.data.sequencePosition}`)}`
+                        )} (Waiting Queue)`
+                    )
+
+                    listenToCircuitChanges(participantId, ceremony.id, circuit)
+                }
+                // A.2 If the participant is in `contributing` status and is the current contributor, he/she must compute the contribution.
+                if (
+                    status === ParticipantStatus.CONTRIBUTING &&
+                    contributionStep !== ParticipantContributionStep.VERIFYING &&
+                    waitingQueue.currentContributor === participantId &&
+                    isStepValidForStartingOrResumingContribution
+                ) {
+                    console.log(
+                        `\n${symbols.success} Your contribution will ${
+                            contributionStep === ParticipantContributionStep.DOWNLOADING ? `start` : `resume`
+                        } soon ${emojis.clock}`
+                    )
+
+                    // Compute the contribution.
+                    await makeContribution(
+                        ceremony,
+                        circuit,
+                        entropy,
+                        ghUsername,
+                        false,
+                        firebaseFunctions,
+                        newParticipantData!
+                    )
+                }
+
+                // A.3 Current contributor has already started the verification step.
+                if (
+                    status === ParticipantStatus.CONTRIBUTING &&
+                    waitingQueue.currentContributor === participantId &&
+                    contributionStep === oldContributionStep &&
+                    contributionStep === ParticipantContributionStep.VERIFYING &&
+                    contributionProgress === oldContributionProgress
+                ) {
+                    const spinner = customSpinner(`Resuming your contribution...`, `clock`)
+                    spinner.start()
+
+                    // Get current and next index.
+                    const currentZkeyIndex = formatZkeyIndex(contributionProgress)
+                    const nextZkeyIndex = formatZkeyIndex(contributionProgress + 1)
+
+                    // Calculate remaining est. time for verification.
+                    const avgVerifyCloudFunctionTime = circuit.data.avgTimings.verifyCloudFunction
+                    const verificationStartedAt = newParticipantData?.verificationStartedAt
+                    const estRemainingTimeInMillis = avgVerifyCloudFunctionTime - (Date.now() - verificationStartedAt)
+                    const { seconds, minutes, hours } = getSecondsMinutesHoursFromMillis(estRemainingTimeInMillis)
+
+                    spinner.succeed(`Your contribution will resume soon ${emojis.clock}`)
+
+                    console.log(
+                        `${theme.bold(
+                            `\n- Circuit # ${theme.magenta(`${circuit.data.sequencePosition}`)}`
+                        )} (Contribution Steps)`
+                    )
+                    console.log(
+                        `${symbols.success} Contribution ${theme.bold(`#${currentZkeyIndex}`)} already downloaded`
+                    )
+                    console.log(`${symbols.success} Contribution ${theme.bold(`#${nextZkeyIndex}`)} already computed`)
+                    console.log(
+                        `${symbols.success} Contribution ${theme.bold(`#${nextZkeyIndex}`)} already saved on storage`
+                    )
+                    console.log(
+                        `${symbols.info} Contribution verification already started (est. time ${theme.bold(
+                            `${convertToDoubleDigits(hours)}:${convertToDoubleDigits(minutes)}:${convertToDoubleDigits(
+                                seconds
+                            )}`
+                        )})`
+                    )
+                }
+
+                // A.4 Server has terminated the already started verification step above.
+                if (
+                    ((status === ParticipantStatus.DONE && oldStatus === ParticipantStatus.DONE) ||
+                        (status === ParticipantStatus.CONTRIBUTED && oldStatus === ParticipantStatus.CONTRIBUTED)) &&
+                    oldContributionProgress === contributionProgress - 1 &&
+                    contributionStep === ParticipantContributionStep.COMPLETED
+                ) {
+                    console.log(`\n${symbols.success} Contribute verification has been completed`)
+
+                    // Return true and false based on contribution verification.
+                    const contributionsValidity = await getContributorContributionsVerificationResults(
+                        ceremony.id,
+                        participantDoc.id,
+                        updatedCircuits,
+                        false
+                    )
+
+                    // Check last contribution validity.
+                    const isContributionValid = contributionsValidity[oldContributionProgress - 1]
+
+                    console.log(
+                        `${isContributionValid ? symbols.success : symbols.error} Your contribution ${
+                            isContributionValid ? `is ${theme.bold("VALID")}` : `is ${theme.bold("INVALID")}`
+                        }`
+                    )
+                }
+
+                // A.5 Current contributor timedout.
+                if (status === ParticipantStatus.TIMEDOUT && contributionStep !== ParticipantContributionStep.COMPLETED)
+                    await handleTimedoutMessageForContributor(
+                        newParticipantData!,
+                        participantDoc.id,
+                        ceremony.id,
+                        true,
+                        ghUsername
+                    )
+
+                // A.6 Contributor has finished the contribution and we need to check the memory before progressing.
+                if (
+                    status === ParticipantStatus.CONTRIBUTED &&
+                    contributionStep === ParticipantContributionStep.COMPLETED
+                ) {
+                    // Get next circuit for contribution.
+                    const nextCircuit = getNextCircuitForContribution(updatedCircuits, contributionProgress + 1)
+
+                    // Check disk space requirements for participant.
+                    const makeProgressToNextContribution = httpsCallable(
+                        firebaseFunctions,
+                        "makeProgressToNextContribution"
+                    )
+                    const wannaGenerateAttestation = await handleDiskSpaceRequirementForNextContribution(
+                        makeProgressToNextContribution,
+                        nextCircuit,
+                        ceremony.id
+                    )
+
+                    if (wannaGenerateAttestation) {
+                        // Generate attestation with valid contributions.
+                        await generatePublicAttestation(
+                            ceremony,
+                            participantId,
+                            newParticipantData!,
+                            updatedCircuits,
+                            ghUsername,
+                            ghToken
+                        )
+
+                        unsubscriberForParticipantDocument()
+                        terminate(ghUsername)
+                    }
+                }
+
+                // A.7 If the participant is in `EXHUMED` status can be only after a timeout expiration.
+                if (status === ParticipantStatus.EXHUMED) {
+                    // Check disk space requirements for participant before resuming the contribution.
+                    const resumeContributionAfterTimeoutExpiration = httpsCallable(
+                        firebaseFunctions,
+                        "resumeContributionAfterTimeoutExpiration"
+                    )
+                    await handleDiskSpaceRequirementForNextContribution(
+                        resumeContributionAfterTimeoutExpiration,
+                        circuit,
+                        ceremony.id
+                    )
+                }
+
+                // B. Already contributed to each circuit.
+                if (
+                    status === ParticipantStatus.DONE &&
+                    contributionStep === ParticipantContributionStep.COMPLETED &&
+                    contributionProgress === numberOfCircuits &&
+                    contributions.length === numberOfCircuits
+                ) {
+                    await generatePublicAttestation(
+                        ceremony,
+                        participantId,
+                        newParticipantData!,
+                        updatedCircuits,
+                        ghUsername,
+                        ghToken
+                    )
+
+                    unsubscriberForParticipantDocument()
+                    terminate(ghUsername)
+                }
+            }
+        }
+    )
+}
diff --git a/packages/phase2cli/src/lib/prompts.ts b/packages/phase2cli/src/lib/prompts.ts
new file mode 100644
index 00000000..65d63843
--- /dev/null
+++ b/packages/phase2cli/src/lib/prompts.ts
@@ -0,0 +1,582 @@
+import { Dirent } from "fs"
+import prompts, { Answers, Choice, PromptObject } from "prompts"
+import {
+    CeremonyInputData,
+    CeremonyTimeoutType,
+    CircomCompilerData,
+    CircuitInputData,
+    FirebaseDocumentInfo
+} from "../../types/index"
+import { symbols, theme } from "./constants"
+import { GENERIC_ERRORS, showError } from "./errors"
+import { customSpinner, extractPoTFromFilename, extractPrefix, getCreatedCeremoniesPrefixes } from "./utils"
+
+/**
+ * Show a binary question with custom options for confirmation purposes.
+ * @param question <string> - the question to be answered.
+ * @param active <string> - the active option (= yes).
+ * @param inactive <string> - the inactive option (= no).
+ * @returns <Promise<Answers<string>>>
+ */
+export const askForConfirmation = async (question: string, active = "yes", inactive = "no"): Promise<Answers<string>> =>
+    prompts({
+        type: "toggle",
+        name: "confirmation",
+        message: theme.bold(question),
+        initial: false,
+        active,
+        inactive
+    })
+
+/**
+ * Prompt for entropy or beacon.
+ * @param askEntropy <boolean> - true when requesting entropy; otherwise false.
+ * @returns <Promise<string>>
+ */
+export const askForEntropyOrBeacon = async (askEntropy: boolean): Promise<string> => {
+    const { entropyOrBeacon } = await prompts({
+        type: "text",
+        name: "entropyOrBeacon",
+        style: `${askEntropy ? `password` : `text`}`,
+        message: theme.bold(`Provide ${askEntropy ? `some entropy` : `the final beacon`}`),
+        validate: (title: string) =>
+            title.length > 0 ||
+            theme.red(`${symbols.error} You must provide a valid value for the ${askEntropy ? `entropy` : `beacon`}!`)
+    })
+
+    if (!entropyOrBeacon) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
+
+    return entropyOrBeacon
+}
+
+/**
+ * Handle the request/generation for a random entropy or beacon value.
+ * @param askEntropy <boolean> - true when requesting entropy; otherwise false.
+ * @return <Promise<string>>
+ */
+export const getEntropyOrBeacon = async (askEntropy: boolean): Promise<string> => {
+    let entropyOrBeacon: any
+    let randomEntropy = false
+
+    if (askEntropy) {
+        // Prompt for entropy.
+        const { confirmation } = await askForConfirmation(`Do you prefer to enter entropy manually?`)
+        if (confirmation === undefined) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
+
+        randomEntropy = !confirmation
+    }
+
+    if (randomEntropy) {
+        const spinner = customSpinner(`Generating random entropy...`, "clock")
+        spinner.start()
+
+        // Took inspiration from here https://github.com/glamperd/setup-mpc-ui/blob/master/client/src/state/Compute.tsx#L112.
+        entropyOrBeacon = new Uint8Array(256).map(() => Math.random() * 256).toString()
+
+        spinner.succeed(`Random entropy successfully generated`)
+    }
+
+    if (!askEntropy || !randomEntropy) entropyOrBeacon = await askForEntropyOrBeacon(askEntropy)
+
+    return entropyOrBeacon
+}
+
+/**
+ * Show a series of questions about the ceremony.
+ * @returns <Promise<CeremonyInputData>> - the necessary information for the ceremony entered by the coordinator.
+ */
+export const askCeremonyInputData = async (): Promise<CeremonyInputData> => {
+    // Get ceremonies prefixes to check for duplicates.
+    const ceremoniesPrefixes = await getCreatedCeremoniesPrefixes()
+
+    const noEndDateCeremonyQuestions: Array<PromptObject> = [
+        {
+            type: "text",
+            name: "title",
+            message: theme.bold(`Give a title to your ceremony`),
+            validate: (title: string) => {
+                if (title.length <= 0)
+                    return theme.red(`${symbols.error} You must provide a valid title for your ceremony!`)
+
+                if (ceremoniesPrefixes.includes(extractPrefix(title)))
+                    return theme.red(`${symbols.error} The title is already in use for another ceremony!`)
+
+                return true
+            }
+        },
+        {
+            type: "text",
+            name: "description",
+            message: theme.bold(`Add a description`),
+            validate: (title: string) =>
+                title.length > 0 || theme.red(`${symbols.error} You must provide a valid description!`)
+        },
+        {
+            type: "date",
+            name: "startDate",
+            message: theme.bold(`When should the ceremony open?`),
+            validate: (d: any) =>
+                new Date(d).valueOf() > Date.now()
+                    ? true
+                    : theme.red(`${symbols.error} You cannot start a ceremony in the past!`)
+        }
+    ]
+
+    const { title, description, startDate } = await prompts(noEndDateCeremonyQuestions)
+
+    if (!title || !description || !startDate) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
+
+    const { endDate } = await prompts({
+        type: "date",
+        name: "endDate",
+        message: theme.bold(`And when close?`),
+        validate: (d) =>
+            new Date(d).valueOf() > new Date(startDate).valueOf()
+                ? true
+                : theme.red(`${symbols.error} You cannot close a ceremony before the opening!`)
+    })
+
+    if (!endDate) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
+
+    // Choose timeout mechanism.
+    const { confirmation: timeoutMechanismType } = await askForConfirmation(
+        `Choose which timeout mechanism you would like to use to penalize blocking contributors`,
+        `Dynamic`,
+        `Fixed`
+    )
+
+    const { penalty } = await prompts({
+        type: "number",
+        name: "penalty",
+        message: theme.bold(
+            `Specify the amount of time a blocking contributor needs to wait when timedout (in minutes):`
+        ),
+        validate: (pnlt: number) => {
+            if (pnlt < 0) return theme.red(`${symbols.error} You must provide a penalty greater than zero`)
+
+            return true
+        }
+    })
+
+    if (penalty < 0) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
+
+    return {
+        title,
+        description,
+        startDate,
+        endDate,
+        timeoutMechanismType: timeoutMechanismType ? CeremonyTimeoutType.DYNAMIC : CeremonyTimeoutType.FIXED,
+        penalty
+    }
+}
+
+/**
+ * Show a series of questions about the circom compiler.
+ * @returns <Promise<CircomCompilerData>> - the necessary information for the circom compiler entered by the coordinator.
+ */
+export const askCircomCompilerVersionAndCommitHash = async (): Promise<CircomCompilerData> => {
+    const questions: Array<PromptObject> = [
+        {
+            type: "text",
+            name: "version",
+            message: theme.bold(`Give the circom compiler version`),
+            validate: (version: string) => {
+                if (version.length <= 0)
+                    return theme.red(`${symbols.error} You must provide a valid version (e.g., 2.0.1)`)
+
+                if (!version.match(/^[0-9].[0-9.]*$/))
+                    return theme.red(`${symbols.error} You must provide a valid version (e.g., 2.0.1)`)
+
+                return true
+            }
+        },
+        {
+            type: "text",
+            name: "commitHash",
+            message: theme.bold(`Give the commit hash of the circom compiler version`),
+            validate: (commitHash: string) =>
+                commitHash.length === 40 ||
+                theme.red(
+                    `${symbols.error} You must provide a valid commit hash (e.g., b7ad01b11f9b4195e38ecc772291251260ab2c67)`
+                )
+        }
+    ]
+
+    const { version, commitHash } = await prompts(questions)
+
+    if (!version || !commitHash) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
+
+    return {
+        version,
+        commitHash
+    }
+}
+
+/**
+ * Show a series of questions about the circuits.
+ * @param timeoutMechanismType <CeremonyTimeoutType> - the choosen timeout mechanism type for the ceremony.
+ * @param isCircomVersionDifferentAmongCircuits <boolean> - true if the circom compiler version is equal among circuits; otherwise false.
+ * @returns Promise<Array<Circuit>> - the necessary information for the circuits entered by the coordinator.
+ */
+export const askCircuitInputData = async (
+    timeoutMechanismType: CeremonyTimeoutType,
+    isCircomVersionEqualAmongCircuits: boolean
+): Promise<CircuitInputData> => {
+    const circuitQuestions: Array<PromptObject> = [
+        {
+            name: "description",
+            type: "text",
+            message: theme.bold(`Add a description`),
+            validate: (value) =>
+                value.length ? true : theme.red(`${symbols.error} You must provide a valid description`)
+        },
+        {
+            name: "templateSource",
+            type: "text",
+            message: theme.bold(`Give the external reference to the source template (.circom file)`),
+            validate: (value) =>
+                value.length > 0 && value.match(/(https?:\/\/[^\s]+\.circom$)/g)
+                    ? true
+                    : theme.red(`${symbols.error} You must provide a valid link to the .circom source template`)
+        },
+        {
+            name: "templateCommitHash",
+            type: "text",
+            message: theme.bold(`Give the commit hash of the source template`),
+            validate: (commitHash: string) =>
+                commitHash.length === 40 ||
+                theme.red(
+                    `${symbols.error} You must provide a valid commit hash (e.g., b7ad01b11f9b4195e38ecc772291251260ab2c67)`
+                )
+        }
+    ]
+
+    // Prompt for circuit data.
+    const { description, templateSource, templateCommitHash } = await prompts(circuitQuestions)
+
+    if (!description || !templateSource || !templateCommitHash) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
+
+    // Ask for dynamic or fixed data.
+    let paramsConfiguration: Array<string> = []
+    let timeoutThreshold = 0
+    let timeoutMaxContributionWaitingTime = 0
+    let circomVersion = ""
+    let circomCommitHash = ""
+
+    // Ask for params config values (if any).
+    const { confirmation: needConfiguration } = await askForConfirmation(
+        `Did the source template need configuration?`,
+        `Yes`,
+        `No`
+    )
+
+    if (needConfiguration) {
+        const { templateParamsValues } = await prompts({
+            name: "templateParamsValues",
+            type: "text",
+            message: theme.bold(
+                `Please, provide a comma-separated list of the parameters values used for configuration`
+            ),
+            validate: (value: string) =>
+                value.split(",").length === 1 ||
+                (value.split(`,`).length > 1 && value.includes(",")) ||
+                theme.red(
+                    `${symbols.error} You must provide a valid comma-separated list of parameters values (e.g., 10,2,1,2)`
+                )
+        })
+
+        if (!templateParamsValues) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
+
+        paramsConfiguration = templateParamsValues.split(",")
+    }
+
+    // Ask for circom info (if different from other circuits).
+    if (!isCircomVersionEqualAmongCircuits) {
+        const { version, commitHash } = await askCircomCompilerVersionAndCommitHash()
+
+        if (!version || !commitHash) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
+
+        circomVersion = version
+        circomCommitHash = commitHash
+    }
+
+    // Ask for dynamic timeout mechanism data.
+    if (timeoutMechanismType === CeremonyTimeoutType.DYNAMIC) {
+        const { threshold } = await prompts({
+            type: "number",
+            name: "threshold",
+            message: theme.bold(
+                `Provide an additional threshold up to the total average contribution time (in percentage):`
+            ),
+            validate: (thresh: number) => {
+                if (thresh < 0 || thresh > 100)
+                    return theme.red(`${symbols.error} You must provide a threshold between 0 and 100`)
+
+                return true
+            }
+        })
+
+        if (threshold < 0 || threshold > 100) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
+
+        timeoutThreshold = threshold
+    }
+
+    // Ask for fixed timeout mechanism data.
+    if (timeoutMechanismType === CeremonyTimeoutType.FIXED) {
+        const { maxContributionWaitingTime } = await prompts({
+            type: "number",
+            name: `maxContributionWaitingTime`,
+            message: theme.bold(`Specify the max amount of time tolerable while contributing (in minutes):`),
+            validate: (threshold: number) => {
+                if (threshold <= 0)
+                    return theme.red(
+                        `${symbols.error} You must provide a maximum contribution waiting time greater than zero`
+                    )
+
+                return true
+            }
+        })
+
+        if (maxContributionWaitingTime <= 0) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
+
+        timeoutMaxContributionWaitingTime = maxContributionWaitingTime
+    }
+
+    if (
+        (timeoutMechanismType === CeremonyTimeoutType.DYNAMIC && timeoutThreshold < 0) ||
+        (timeoutMechanismType === CeremonyTimeoutType.FIXED && timeoutMaxContributionWaitingTime < 0) ||
+        (needConfiguration && paramsConfiguration.length === 0) ||
+        (isCircomVersionEqualAmongCircuits && !!circomVersion && !!circomCommitHash)
+    )
+        showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
+
+    return timeoutMechanismType === CeremonyTimeoutType.DYNAMIC
+        ? {
+              description,
+              timeoutThreshold,
+              compiler: {
+                  version: circomVersion,
+                  commitHash: circomCommitHash
+              },
+              template: {
+                  source: templateSource,
+                  commitHash: templateCommitHash,
+                  paramsConfiguration
+              }
+          }
+        : {
+              description,
+              timeoutMaxContributionWaitingTime,
+              compiler: {
+                  version: circomVersion,
+                  commitHash: circomCommitHash
+              },
+              template: {
+                  source: templateSource,
+                  commitHash: templateCommitHash,
+                  paramsConfiguration
+              }
+          }
+}
+
+/**
+ * Request the powers of the Powers of Tau for a specified circuit.
+ * @param suggestedPowers <number> - the minimal number of powers necessary for circuit zKey generation.
+ * @returns Promise<Array<Circuit>> - the necessary information for the circuits entered by the coordinator.
+ */
+export const askPowersOftau = async (suggestedPowers: number): Promise<any> => {
+    const question: PromptObject = {
+        name: "powers",
+        type: "number",
+        message: theme.bold(
+            `Please, provide the amounts of powers you have used to generate the pre-computed zkey (>= ${suggestedPowers}):`
+        ),
+        validate: (value) =>
+            value >= suggestedPowers
+                ? true
+                : theme.red(`${symbols.error} You must provide a value greater than or equal to ${suggestedPowers}`)
+    }
+
+    // Prompt for circuit data.
+    const { powers } = await prompts(question)
+
+    if (powers < suggestedPowers) showError(GENERIC_ERRORS.GENERIC_DATA_INPUT, true)
+
+    return {
+        powers
+    }
+}
+
+/**
+ * Prompt the list of circuits from a specific directory.
+ * @param circuitsDirents <Array<Dirent>>
+ * @returns Promise<string>
+ */
+export const askForCircuitSelectionFromLocalDir = async (circuitsDirents: Array<Dirent>): Promise<string> => {
+    const choices: Array<Choice> = []
+
+    // Make a 'Choice' for each circuit.
+    for (const circuitDirent of circuitsDirents) {
+        choices.push({
+            title: circuitDirent.name,
+            value: circuitDirent.name
+        })
+    }
+
+    // Ask for selection.
+    const { circuit } = await prompts({
+        type: "select",
+        name: "circuit",
+        message: theme.bold("Select a circuit"),
+        choices,
+        initial: 0
+    })
+
+    if (!circuit) showError(GENERIC_ERRORS.GENERIC_CIRCUIT_SELECTION, true)
+
+    return circuit
+}
+
+/**
+ * Prompt the list of pre-computed zkeys files from a specific directory.
+ * @param zkeysDirents <Array<Dirent>>
+ * @returns Promise<string>
+ */
+export const askForZkeySelectionFromLocalDir = async (zkeysDirents: Array<Dirent>): Promise<string> => {
+    const choices: Array<Choice> = []
+
+    // Make a 'Choice' for each zkey.
+    for (const zkeyDirent of zkeysDirents) {
+        choices.push({
+            title: zkeyDirent.name,
+            value: zkeyDirent.name
+        })
+    }
+
+    // Ask for selection.
+    const { zkey } = await prompts({
+        type: "select",
+        name: "zkey",
+        message: theme.bold("Select a pre-computed zkey"),
+        choices,
+        initial: 0
+    })
+
+    if (!zkey) showError(GENERIC_ERRORS.GENERIC_CIRCUIT_SELECTION, true)
+
+    return zkey
+}
+
+/**
+ * Prompt the list of ptau files from a specific directory.
+ * @param ptausDirents <Array<Dirent>>
+ * @param suggestedPowers <number> - the minimal number of powers necessary for circuit zKey generation.
+ * @returns Promise<string>
+ */
+export const askForPtauSelectionFromLocalDir = async (
+    ptausDirents: Array<Dirent>,
+    suggestedPowers: number
+): Promise<string> => {
+    const choices: Array<Choice> = []
+
+    // Make a 'Choice' for each ptau.
+    for (const ptauDirent of ptausDirents) {
+        const powers = extractPoTFromFilename(ptauDirent.name)
+
+        if (powers >= suggestedPowers)
+            choices.push({
+                title: ptauDirent.name,
+                value: ptauDirent.name
+            })
+    }
+
+    // Ask for selection.
+    const { ptau } = await prompts({
+        type: "select",
+        name: "ptau",
+        message: theme.bold("Select the Powers of Tau file used to generate the zKey"),
+        choices,
+        initial: 0,
+        validate: (value) =>
+            extractPoTFromFilename(value) >= suggestedPowers
+                ? true
+                : theme.red(
+                      `${symbols.error} You must select a Powers of Tau file having an equal to or greater than ${suggestedPowers} amount of powers`
+                  )
+    })
+
+    if (!ptau) showError(GENERIC_ERRORS.GENERIC_CIRCUIT_SELECTION, true)
+
+    return ptau
+}
+
+/**
+ * Prompt the list of opened ceremonies for selection.
+ * @param openedCeremoniesDocs <Array<FirebaseDocumentInfo>> - The uid and data of opened cerimonies documents.
+ * @returns Promise<FirebaseDocumentInfo>
+ */
+export const askForCeremonySelection = async (
+    openedCeremoniesDocs: Array<FirebaseDocumentInfo>
+): Promise<FirebaseDocumentInfo> => {
+    const choices: Array<Choice> = []
+
+    // Make a 'Choice' for each opened ceremony.
+    for (const ceremonyDoc of openedCeremoniesDocs) {
+        const now = Date.now()
+        const daysLeft = Math.ceil(Math.abs(now - ceremonyDoc.data.endDate) / (1000 * 60 * 60 * 24))
+
+        choices.push({
+            title: ceremonyDoc.data.title,
+            description: `${ceremonyDoc.data.description} (${theme.magenta(daysLeft)} ${
+                now - ceremonyDoc.data.endDate < 0 ? `days left` : `days gone since closing`
+            })`,
+            value: ceremonyDoc
+        })
+    }
+
+    // Ask for selection.
+    const { ceremony } = await prompts({
+        type: "select",
+        name: "ceremony",
+        message: theme.bold("Select a ceremony"),
+        choices,
+        initial: 0
+    })
+
+    if (!ceremony) showError(GENERIC_ERRORS.GENERIC_CEREMONY_SELECTION, true)
+
+    return ceremony
+}
+
+/**
+ * Prompt the list of circuits for a specific ceremony for selection.
+ * @param circuitsDocs <Array<FirebaseDocumentInfo>> - The uid and data of ceremony circuits.
+ * @returns Promise<FirebaseDocumentInfo>
+ */
+export const askForCircuitSelectionFromFirebase = async (
+    circuitsDocs: Array<FirebaseDocumentInfo>
+): Promise<FirebaseDocumentInfo> => {
+    const choices: Array<Choice> = []
+
+    // Make a 'Choice' for each circuit.
+    for (const circuitDoc of circuitsDocs) {
+        choices.push({
+            title: `${circuitDoc.data.name}`,
+            description: `(#${theme.magenta(circuitDoc.data.sequencePosition)}) ${circuitDoc.data.description}`,
+            value: circuitDoc
+        })
+    }
+
+    // Ask for selection.
+    const { circuit } = await prompts({
+        type: "select",
+        name: "circuit",
+        message: theme.bold("Select a circuit"),
+        choices,
+        initial: 0
+    })
+
+    if (!circuit) showError(GENERIC_ERRORS.GENERIC_CIRCUIT_SELECTION, true)
+
+    return circuit
+}
diff --git a/packages/phase2cli/src/lib/queries.ts b/packages/phase2cli/src/lib/queries.ts
new file mode 100644
index 00000000..e0126e68
--- /dev/null
+++ b/packages/phase2cli/src/lib/queries.ts
@@ -0,0 +1,119 @@
+import { DocumentData, QueryDocumentSnapshot, Timestamp, where } from "firebase/firestore"
+import { FirebaseDocumentInfo, CeremonyState } from "../../types/index"
+import { queryCollection, getAllCollectionDocs } from "./firebase"
+import {
+    ceremoniesCollectionFields,
+    collections,
+    contributionsCollectionFields,
+    timeoutsCollectionFields
+} from "./constants"
+import { FIREBASE_ERRORS, showError } from "./errors"
+
+/**
+ * Helper for obtaining uid and data for query document snapshots.
+ * @param queryDocSnap <Array<QueryDocumentSnapshot>> - the array of query document snapshot to be converted.
+ * @returns Array<FirebaseDocumentInfo>
+ */
+export const fromQueryToFirebaseDocumentInfo = (
+    queryDocSnap: Array<QueryDocumentSnapshot>
+): Array<FirebaseDocumentInfo> =>
+    queryDocSnap.map((doc: QueryDocumentSnapshot<DocumentData>) => ({
+        id: doc.id,
+        ref: doc.ref,
+        data: doc.data()
+    }))
+
+/**
+ * Query for closed ceremonies documents and return their data (if any).
+ * @returns <Promise<Array<FirebaseDocumentInfo>>>
+ */
+export const getClosedCeremonies = async (): Promise<Array<FirebaseDocumentInfo>> => {
+    let closedStateCeremoniesQuerySnap: any
+
+    try {
+        closedStateCeremoniesQuerySnap = await queryCollection(collections.ceremonies, [
+            where(ceremoniesCollectionFields.state, "==", CeremonyState.CLOSED),
+            where(ceremoniesCollectionFields.endDate, "<=", Date.now())
+        ])
+
+        if (closedStateCeremoniesQuerySnap.empty && closedStateCeremoniesQuerySnap.size === 0)
+            showError(FIREBASE_ERRORS.FIREBASE_CEREMONY_NOT_CLOSED, true)
+    } catch (err: any) {
+        showError(err.toString(), true)
+    }
+
+    return fromQueryToFirebaseDocumentInfo(closedStateCeremoniesQuerySnap.docs)
+}
+
+/**
+ * Retrieve all ceremonies.
+ * @returns Promise<Array<FirebaseDocumentInfo>>
+ */
+export const getAllCeremonies = async (): Promise<Array<FirebaseDocumentInfo>> =>
+    fromQueryToFirebaseDocumentInfo(await getAllCollectionDocs(`${collections.ceremonies}`)).sort(
+        (a: FirebaseDocumentInfo, b: FirebaseDocumentInfo) => a.data.sequencePosition - b.data.sequencePosition
+    )
+
+/**
+ * Query for contribution from given participant for a given circuit (if any).
+ * @param ceremonyId <string> - the identifier of the ceremony.
+ * @param circuitId <string> - the identifier of the circuit.
+ * @param participantId <string> - the identifier of the participant.
+ * @returns <Promise<Array<FirebaseDocumentInfo>>>
+ */
+export const getCurrentContributorContribution = async (
+    ceremonyId: string,
+    circuitId: string,
+    participantId: string
+): Promise<Array<FirebaseDocumentInfo>> => {
+    const participantContributionQuerySnap = await queryCollection(
+        `${collections.ceremonies}/${ceremonyId}/${collections.circuits}/${circuitId}/${collections.contributions}`,
+        [where(contributionsCollectionFields.participantId, "==", participantId)]
+    )
+
+    return fromQueryToFirebaseDocumentInfo(participantContributionQuerySnap.docs)
+}
+
+/**
+ * Query for circuits with a contribution from given participant.
+ * @param ceremonyId <string> - the identifier of the ceremony.
+ * @param circuits <Array<FirebaseDocumentInfo>> - the circuits of the ceremony
+ * @param participantId <string> - the identifier of the participant.
+ * @returns <Promise<Array<FirebaseDocumentInfo>>>
+ */
+export const getCircuitsWithParticipantContribution = async (
+    ceremonyId: string,
+    circuits: Array<FirebaseDocumentInfo>,
+    participantId: string
+): Promise<Array<string>> => {
+    const circuitsWithContributionIds: Array<string> = [] // nb. store circuit identifier.
+
+    for (const circuit of circuits) {
+        const participantContributionQuerySnap = await queryCollection(
+            `${collections.ceremonies}/${ceremonyId}/${collections.circuits}/${circuit.id}/${collections.contributions}`,
+            [where(contributionsCollectionFields.participantId, "==", participantId)]
+        )
+
+        if (participantContributionQuerySnap.size === 1) circuitsWithContributionIds.push(circuit.id)
+    }
+
+    return circuitsWithContributionIds
+}
+
+/**
+ * Query for the active timeout from given participant for a given ceremony (if any).
+ * @param ceremonyId <string> - the identifier of the ceremony.
+ * @param participantId <string> - the identifier of the participant.
+ * @returns Promise<Array<FirebaseDocumentInfo>>
+ */
+export const getCurrentActiveParticipantTimeout = async (
+    ceremonyId: string,
+    participantId: string
+): Promise<Array<FirebaseDocumentInfo>> => {
+    const participantTimeoutQuerySnap = await queryCollection(
+        `${collections.ceremonies}/${ceremonyId}/${collections.participants}/${participantId}/${collections.timeouts}`,
+        [where(timeoutsCollectionFields.endDate, ">=", Timestamp.now().toMillis())]
+    )
+
+    return fromQueryToFirebaseDocumentInfo(participantTimeoutQuerySnap.docs)
+}
diff --git a/packages/phase2cli/src/lib/storage.ts b/packages/phase2cli/src/lib/storage.ts
new file mode 100644
index 00000000..e03b771c
--- /dev/null
+++ b/packages/phase2cli/src/lib/storage.ts
@@ -0,0 +1,325 @@
+import { HttpsCallable } from "firebase/functions"
+import fs from "fs"
+import fetch from "@adobe/node-fetch-retry"
+import { createWriteStream } from "node:fs"
+import https from "https"
+import dotenv from "dotenv"
+import { SingleBar, Presets } from "cli-progress"
+import { ChunkWithUrl, ETagWithPartNumber, ProgressBarType } from "../../types/index"
+import { GENERIC_ERRORS, showError } from "./errors"
+import { emojis, theme } from "./constants"
+
+dotenv.config()
+
+/**
+ * Return a custom progress bar.
+ * @param type <ProgressBarType> - the type of the progress bar.
+ * @returns <SingleBar> - a new custom (single) progress bar.
+ */
+export const customProgressBar = (type: ProgressBarType): SingleBar => {
+    // Formats.
+    const uploadFormat = `${emojis.arrowUp}  Uploading [${theme.magenta(
+        "{bar}"
+    )}] {percentage}% | {value}/{total} Chunks`
+    const downloadFormat = `${emojis.arrowDown}  Downloading [${theme.magenta(
+        "{bar}"
+    )}] {percentage}% | {value}/{total} GB`
+
+    // Define a progress bar showing percentage of completion and chunks downloaded/uploaded.
+    return new SingleBar(
+        {
+            format: type === ProgressBarType.DOWNLOAD ? downloadFormat : uploadFormat,
+            hideCursor: true,
+            clearOnComplete: true
+        },
+        Presets.legacy
+    )
+}
+
+/**
+ * Convert bytes or chilobytes into gigabytes with customizable precision.
+ * @param bytesOrKB <number> - bytes or KB to be converted.
+ * @param isBytes <boolean> - true if the input is in bytes; otherwise false for KB input.
+ * @returns <number>
+ */
+export const convertToGB = (bytesOrKB: number, isBytes: boolean): number =>
+    Number(bytesOrKB / 1024 ** (isBytes ? 3 : 2))
+
+export const createS3Bucket = async (cf: HttpsCallable<unknown, unknown>, bucketName: string): Promise<boolean> => {
+    // Call createBucket() Cloud Function.
+    const response: any = await cf({
+        bucketName
+    })
+
+    // Return true if exists, otherwise false.
+    return response.data
+}
+
+/**
+ * Check if an object exists in a given AWS S3 bucket.
+ * @param cf <HttpsCallable<unknown, unknown>> - the corresponding cloud function.
+ * @param bucketName <string> - the name of the AWS S3 bucket.
+ * @param objectKey <string> - the identifier of the object.
+ * @returns Promise<string> - true if the object exists, otherwise false.
+ */
+export const objectExist = async (
+    cf: HttpsCallable<unknown, unknown>,
+    bucketName: string,
+    objectKey: string
+): Promise<boolean> => {
+    // Call checkIfObjectExist() Cloud Function.
+    const response: any = await cf({
+        bucketName,
+        objectKey
+    })
+
+    // Return true if exists, otherwise false.
+    return response.data
+}
+
+/**
+ * Initiate the multi part upload in AWS S3 Bucket for a large object.
+ * @param cf <HttpsCallable<unknown, unknown>> - the corresponding cloud function.
+ * @param bucketName <string> - the name of the AWS S3 bucket.
+ * @param objectKey <string> - the identifier of the object.
+ * @param ceremonyId <string> - the identifier of the ceremony.
+ * @returns Promise<string> - the Upload ID reference.
+ */
+export const openMultiPartUpload = async (
+    cf: HttpsCallable<unknown, unknown>,
+    bucketName: string,
+    objectKey: string,
+    ceremonyId?: string
+): Promise<string> => {
+    // Call startMultiPartUpload() Cloud Function.
+    const response: any = await cf({
+        bucketName,
+        objectKey,
+        ceremonyId
+    })
+
+    // Return Multi Part Upload ID.
+    return response.data
+}
+
+/**
+ * Get chunks and signed urls for a multi part upload.
+ * @param cf <HttpsCallable<unknown, unknown>> - the corresponding cloud function.
+ * @param bucketName <string> - the name of the AWS S3 bucket.
+ * @param objectKey <string> - the identifier of the object.
+ * @param filePath <string> - the local path where the file to be uploaded is located.
+ * @param uploadId <string> - the multi part upload unique identifier.
+ * @param expirationInSeconds <number> - the pre signed url expiration in seconds.
+ * @param ceremonyId <string> - the identifier of the ceremony.
+ * @returns Promise<Array, Array>
+ */
+export const getChunksAndPreSignedUrls = async (
+    cf: HttpsCallable<unknown, unknown>,
+    bucketName: string,
+    objectKey: string,
+    filePath: string,
+    uploadId: string,
+    expirationInSeconds: number,
+    ceremonyId?: string
+): Promise<Array<ChunkWithUrl>> => {
+    // Configuration checks.
+    if (!process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB) showError(GENERIC_ERRORS.GENERIC_NOT_CONFIGURED_PROPERLY, true)
+
+    // Open a read stream.
+    const stream = fs.createReadStream(filePath, {
+        highWaterMark: Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB) * 1024 * 1024
+    })
+
+    // Read and store chunks.
+    const chunks = []
+    for await (const chunk of stream) chunks.push(chunk)
+
+    const numberOfParts = chunks.length
+    if (!numberOfParts) showError(GENERIC_ERRORS.GENERIC_FILE_ERROR, true)
+
+    // Call generatePreSignedUrlsParts() Cloud Function.
+    const response: any = await cf({
+        bucketName,
+        objectKey,
+        uploadId,
+        numberOfParts,
+        expirationInSeconds,
+        ceremonyId
+    })
+
+    return chunks.map((val1, index) => ({
+        partNumber: index + 1,
+        chunk: val1,
+        preSignedUrl: response.data[index]
+    }))
+}
+
+/**
+ * Make a PUT request to upload each part for a multi part upload.
+ * @param chunksWithUrls <Array<ChunkWithUrl>> - the array containing chunks and corresponding pre signed urls.
+ * @param contentType <string | false> - the content type of the file to upload.
+ * @param cf <HttpsCallable<unknown, unknown>> - the CF for enable resumable upload from last chunk by temporarily store the ETags and PartNumbers of already uploaded chunks.
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
+ * @param alreadyUploadedChunks <any> - the ETag and PartNumber temporary information about the already uploaded chunks.
+ * @returns <Promise<Array<ETagWithPartNumber>>>
+ */
+export const uploadParts = async (
+    chunksWithUrls: Array<ChunkWithUrl>,
+    contentType: string | false,
+    cf?: HttpsCallable<unknown, unknown>,
+    ceremonyId?: string,
+    alreadyUploadedChunks?: any
+): Promise<Array<ETagWithPartNumber>> => {
+    // PartNumber and ETags.
+    let partNumbersAndETags = []
+
+    // Restore the already uploaded chunks in the same order.
+    if (alreadyUploadedChunks) partNumbersAndETags = alreadyUploadedChunks
+
+    // Resume from last uploaded chunk (0 for new multi-part upload).
+    const lastChunkIndex = partNumbersAndETags.length
+
+    // Define a custom progress bar starting from last updated chunk.
+    const progressBar = customProgressBar(ProgressBarType.UPLOAD)
+    progressBar.start(chunksWithUrls.length, lastChunkIndex)
+
+    for (let i = lastChunkIndex; i < chunksWithUrls.length; i += 1) {
+        // Make PUT call.
+        const putResponse = await fetch(chunksWithUrls[i].preSignedUrl, {
+            retryOptions: {
+                retryInitialDelay: 500, // 500 ms.
+                socketTimeout: 60000, // 60 seconds.
+                retryMaxDuration: 300000 // 5 minutes.
+            },
+            method: "PUT",
+            body: chunksWithUrls[i].chunk,
+            headers: {
+                "Content-Type": contentType.toString(),
+                "Content-Length": chunksWithUrls[i].chunk.length.toString()
+            },
+            agent: new https.Agent({ keepAlive: true })
+        })
+
+        // Extract data.
+        const eTag = putResponse.headers.get("etag")
+        const { partNumber } = chunksWithUrls[i]
+
+        // Store PartNumber and ETag.
+        partNumbersAndETags.push({
+            ETag: eTag,
+            PartNumber: partNumber
+        })
+
+        // nb. to be done only when contributing.
+        if (!!ceremonyId && !!cf)
+            // Call CF to temporary store the chunks ETag and PartNumber info (useful for resumable upload).
+            await cf({
+                ceremonyId,
+                eTag,
+                partNumber
+            })
+
+        // Increment the progress bar.
+        progressBar.increment(1)
+    }
+
+    return partNumbersAndETags
+}
+
+/**
+ * Close the multi part upload in AWS S3 Bucket for a large object.
+ * @param cf <HttpsCallable<unknown, unknown>> - the corresponding cloud function.
+ * @param bucketName <string> - the name of the AWS S3 bucket.
+ * @param objectKey <string> - the identifier of the object.
+ * @param uploadId <string> - the multi part upload unique identifier.
+ * @param parts Array<ETagWithPartNumber> - the uploaded parts.
+ * @param ceremonyId <string> - the identifier of the ceremony.
+ * @returns Promise<string> - the location of the uploaded file.
+ */
+export const closeMultiPartUpload = async (
+    cf: HttpsCallable<unknown, unknown>,
+    bucketName: string,
+    objectKey: string,
+    uploadId: string,
+    parts: Array<ETagWithPartNumber>,
+    ceremonyId?: string
+): Promise<string> => {
+    // Call completeMultiPartUpload() Cloud Function.
+    const response: any = await cf({
+        bucketName,
+        objectKey,
+        uploadId,
+        parts,
+        ceremonyId
+    })
+
+    // Return uploaded file location.
+    return response.data
+}
+
+/**
+ * Download locally a specified file from the given bucket.
+ * @param cf <HttpsCallable<unknown, unknown>> - the corresponding cloud function.
+ * @param bucketName <string> - the name of the AWS S3 bucket.
+ * @param objectKey <string> - the identifier of the object (storage path).
+ * @param localPath <string> - the path where the file will be written.
+ * @return <Promise<void>>
+ */
+export const downloadLocalFileFromBucket = async (
+    cf: HttpsCallable<unknown, unknown>,
+    bucketName: string,
+    objectKey: string,
+    localPath: string
+): Promise<void> => {
+    // Call generateGetObjectPreSignedUrl() Cloud Function.
+    const response: any = await cf({
+        bucketName,
+        objectKey
+    })
+
+    // Get the pre-signed url.
+    const preSignedUrl = response.data
+
+    // Get request.
+    const getResponse = await fetch(preSignedUrl)
+
+    if (!getResponse.ok) showError(`${GENERIC_ERRORS.GENERIC_FILE_ERROR} - ${getResponse.statusText}`, true)
+
+    const contentLength = Number(getResponse.headers.get(`content-length`))
+    const contentLengthInGB = convertToGB(contentLength, true)
+
+    // Create a new write stream.
+    const writeStream = createWriteStream(localPath)
+
+    // Define a custom progress bar starting from last updated chunk.
+    const progressBar = customProgressBar(ProgressBarType.DOWNLOAD)
+
+    // Progress bar step size.
+    const progressBarStepSize = contentLengthInGB / 100
+
+    let writtenData = 0
+    let nextStepSize = progressBarStepSize
+
+    // Init the progress bar.
+    progressBar.start(contentLengthInGB < 0.01 ? 0.01 : Number(contentLengthInGB.toFixed(2)), 0)
+
+    // Write chunk by chunk.
+    for await (const chunk of getResponse.body) {
+        // Write.
+        writeStream.write(chunk)
+
+        // Update.
+        writtenData += chunk.length
+
+        // Check if the progress bar must advance.
+        while (convertToGB(writtenData, true) >= nextStepSize) {
+            // Update.
+            nextStepSize += progressBarStepSize
+
+            // Increment bar.
+            progressBar.update(contentLengthInGB < 0.01 ? 0.01 : parseFloat(nextStepSize.toFixed(2)).valueOf())
+        }
+    }
+
+    progressBar.stop()
+}
diff --git a/packages/phase2cli/src/lib/utils.ts b/packages/phase2cli/src/lib/utils.ts
new file mode 100644
index 00000000..3956d045
--- /dev/null
+++ b/packages/phase2cli/src/lib/utils.ts
@@ -0,0 +1,1230 @@
+import { request } from "@octokit/request"
+import { DocumentData, Timestamp } from "firebase/firestore"
+import ora, { Ora } from "ora"
+import figlet from "figlet"
+import clear from "clear"
+import { zKey } from "snarkjs"
+import winston, { Logger } from "winston"
+import { Functions, HttpsCallable, httpsCallable, httpsCallableFromURL } from "firebase/functions"
+import { Timer } from "timer-node"
+import mime from "mime-types"
+import { getDiskInfoSync } from "node-disk-info"
+import Drive from "node-disk-info/dist/classes/drive"
+import open from "open"
+import dotenv from "dotenv"
+import {
+    FirebaseDocumentInfo,
+    FirebaseServices,
+    ParticipantContributionStep,
+    ParticipantStatus,
+    Timing,
+    VerifyContributionComputation
+} from "../../types/index"
+import { collections, emojis, firstZkeyIndex, numIterationsExp, paths, symbols, theme } from "./constants"
+import { initServices, uploadFileToStorage } from "./firebase"
+import { GENERIC_ERRORS, GITHUB_ERRORS, showError } from "./errors"
+import { readFile, writeFile } from "./files"
+import {
+    closeMultiPartUpload,
+    downloadLocalFileFromBucket,
+    getChunksAndPreSignedUrls,
+    openMultiPartUpload,
+    uploadParts
+} from "./storage"
+import { getAllCeremonies, getCurrentActiveParticipantTimeout, getCurrentContributorContribution } from "./queries"
+
+dotenv.config()
+
+/**
+ * Get the Github username for the logged in user.
+ * @param token <string> - the Github OAuth 2.0 token.
+ * @returns <Promise<string>> - the user Github username.
+ */
+export const getGithubUsername = async (token: string): Promise<string> => {
+    // Get user info from Github APIs.
+    const response = await request("GET https://api.github.com/user", {
+        headers: {
+            authorization: `token ${token}`
+        }
+    })
+
+    if (response) return response.data.login
+    showError(GITHUB_ERRORS.GITHUB_GET_USERNAME_FAILED, true)
+
+    return process.exit(0) // nb. workaround to avoid type issues.
+}
+
+/**
+ * Get the current amout of available memory for user root disk (mounted in `/` root).
+ * @returns <number> - the available memory in kB.
+ */
+export const getParticipantCurrentDiskAvailableSpace = (): number => {
+    const disks = getDiskInfoSync()
+    const root = disks.filter((disk: Drive) => disk.mounted === `/`)
+
+    if (root.length !== 1) showError(`Something went wrong while retrieving your root disk available memory`, true)
+
+    const rootDisk = root.at(0)!
+
+    return rootDisk.available
+}
+
+/**
+ * Return an array of true of false based on contribution verification result per each circuit.
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
+ * @param participantId <string> - the unique identifier of the contributor.
+ * @param circuits <Array<FirebaseDocumentInfo>> - the Firestore documents of the ceremony circuits.
+ * @param finalize <boolean> - true when finalizing; otherwise false.
+ * @returns <Promise<Array<boolean>>>
+ */
+export const getContributorContributionsVerificationResults = async (
+    ceremonyId: string,
+    participantId: string,
+    circuits: Array<FirebaseDocumentInfo>,
+    finalize: boolean
+): Promise<Array<boolean>> => {
+    // Keep track contributions verification results.
+    const contributions: Array<boolean> = []
+
+    // Retrieve valid/invalid contributions.
+    for await (const circuit of circuits) {
+        // Get contributions to circuit from contributor.
+        const contributionsToCircuit = await getCurrentContributorContribution(ceremonyId, circuit.id, participantId)
+
+        let contribution: FirebaseDocumentInfo
+
+        if (finalize)
+            // There should be two contributions from coordinator (one is finalization).
+            contribution = contributionsToCircuit
+                .filter((contrib: FirebaseDocumentInfo) => contrib.data.zkeyIndex === "final")
+                .at(0)!
+        // There will be only one contribution.
+        else contribution = contributionsToCircuit.at(0)!
+
+        if (contribution) {
+            // Get data.
+            const contributionData = contribution.data
+
+            if (!contributionData) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
+
+            // Update contributions validity.
+            contributions.push(!!contributionData?.valid)
+        }
+    }
+
+    return contributions
+}
+
+/**
+ * Return the attestation made only from valid contributions.
+ * @param contributionsValidities Array<boolean> - an array of booleans (true when contribution is valid; otherwise false).
+ * @param circuits <Array<FirebaseDocumentInfo>> - the Firestore documents of the ceremony circuits.
+ * @param participantData <DocumentData> - the document data of the participant.
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
+ * @param participantId <string> - the unique identifier of the contributor.
+ * @param attestationPreamble <string> - the preamble of the attestation.
+ * @param finalize <boolean> - true only when finalizing, otherwise false.
+ * @returns <Promise<string>> - the complete attestation string.
+ */
+export const getValidContributionAttestation = async (
+    contributionsValidities: Array<boolean>,
+    circuits: Array<FirebaseDocumentInfo>,
+    participantData: DocumentData,
+    ceremonyId: string,
+    participantId: string,
+    attestationPreamble: string,
+    finalize: boolean
+): Promise<string> => {
+    let attestation = attestationPreamble
+
+    // For each contribution validity.
+    for (let idx = 0; idx < contributionsValidities.length; idx += 1) {
+        if (contributionsValidities[idx]) {
+            // Extract data from circuit.
+            const circuit = circuits[idx]
+
+            let contributionHash: string = ""
+
+            // Get the contribution hash.
+            if (finalize) {
+                const numberOfContributions = participantData.contributions.length
+                contributionHash = participantData.contributions[numberOfContributions / 2 + idx].hash
+            } else contributionHash = participantData.contributions[idx].hash
+
+            // Get the contribution data.
+            const contributions = await getCurrentContributorContribution(ceremonyId, circuit.id, participantId)
+
+            let contributionData: DocumentData
+
+            if (finalize)
+                contributionData = contributions.filter(
+                    (contribution: FirebaseDocumentInfo) => contribution.data.zkeyIndex === "final"
+                )[0].data!
+            else contributionData = contributions.at(0)?.data!
+
+            // Attestate.
+            attestation = `${attestation}\n\nCircuit # ${circuit.data.sequencePosition} (${
+                circuit.data.prefix
+            })\nContributor # ${
+                contributionData?.zkeyIndex > 0 ? Number(contributionData?.zkeyIndex) : contributionData?.zkeyIndex
+            }\n${contributionHash}`
+        }
+    }
+
+    return attestation
+}
+
+/**
+ * Publish a new attestation through a Github Gist.
+ * @param token <string> - the Github OAuth 2.0 token.
+ * @param content <string> - the content of the attestation.
+ * @param ceremonyPrefix <string> - the ceremony prefix.
+ * @param ceremonyTitle <string> - the ceremony title.
+ */
+export const publishGist = async (
+    token: string,
+    content: string,
+    ceremonyPrefix: string,
+    ceremonyTitle: string
+): Promise<string> => {
+    const response = await request("POST /gists", {
+        description: `Attestation for ${ceremonyTitle} MPC Phase 2 Trusted Setup ceremony`,
+        public: true,
+        files: {
+            [`${ceremonyPrefix}_attestation.txt`]: {
+                content
+            }
+        },
+        headers: {
+            authorization: `token ${token}`
+        }
+    })
+
+    if (response && response.data.html_url) return response.data.html_url
+    showError(GITHUB_ERRORS.GITHUB_GIST_PUBLICATION_FAILED, true)
+
+    return process.exit(0) // nb. workaround to avoid type issues.
+}
+
+/**
+ * Extract from milliseconds the seconds, minutes, hours and days.
+ * @param millis <number>
+ * @returns <Timing>
+ */
+export const getSecondsMinutesHoursFromMillis = (millis: number): Timing => {
+    // Get seconds from millis.
+    let delta = millis / 1000
+
+    const days = Math.floor(delta / 86400)
+    delta -= days * 86400
+
+    const hours = Math.floor(delta / 3600) % 24
+    delta -= hours * 3600
+
+    const minutes = Math.floor(delta / 60) % 60
+    delta -= minutes * 60
+
+    const seconds = Math.floor(delta) % 60
+
+    return {
+        seconds: seconds >= 60 ? 59 : seconds,
+        minutes: minutes >= 60 ? 59 : minutes,
+        hours: hours >= 24 ? 23 : hours,
+        days
+    }
+}
+
+/**
+ * Return a string with double digits if the amount is one digit only.
+ * @param amount <number>
+ * @returns <string>
+ */
+export const convertToDoubleDigits = (amount: number): string => (amount < 10 ? `0${amount}` : amount.toString())
+
+/**
+ * Sleeps the function execution for given millis.
+ * @dev to be used in combination with loggers when writing data into files.
+ * @param ms <number> - sleep amount in milliseconds
+ * @returns <Promise<any>>
+ */
+export const sleep = (ms: number): Promise<any> => new Promise((resolve) => setTimeout(resolve, ms))
+
+/**
+ * Return a custom spinner.
+ * @param text <string> - the text that should be displayed as spinner status.
+ * @param spinnerLogo <any> - the logo.
+ * @returns <Ora> - a new Ora custom spinner.
+ */
+export const customSpinner = (text: string, spinnerLogo: any): Ora =>
+    ora({
+        text,
+        spinner: spinnerLogo
+    })
+
+/**
+ * Return a simple graphical loader to simulate loading or describe an asynchronous task.
+ * @param loadingText <string> - the text that should be displayed while the loader is spinning.
+ * @param logo <any> - the logo of the loader.
+ * @param durationInMillis <number> - the loader duration time in milliseconds.
+ * @param afterLoadingText <string> - the text that should be displayed for the loader stop.
+ * @returns <Promise<void>>.
+ */
+export const simpleLoader = async (
+    loadingText: string,
+    logo: any,
+    durationInMillis: number,
+    afterLoadingText?: string
+): Promise<void> => {
+    // Define the loader.
+    const loader = customSpinner(loadingText, logo)
+
+    loader.start()
+
+    // nb. wait for `durationInMillis` time while loader is spinning.
+    await sleep(durationInMillis)
+
+    if (afterLoadingText) loader.succeed(afterLoadingText)
+    else loader.stop()
+}
+
+/**
+ * Return the bucket name based on ceremony prefix.
+ * @param ceremonyPrefix <string> - the ceremony prefix.
+ * @returns <string>
+ */
+export const getBucketName = (ceremonyPrefix: string): string => {
+    if (!process.env.CONFIG_CEREMONY_BUCKET_POSTFIX) showError(GENERIC_ERRORS.GENERIC_NOT_CONFIGURED_PROPERLY, true)
+
+    return `${ceremonyPrefix}${process.env.CONFIG_CEREMONY_BUCKET_POSTFIX!}`
+}
+
+/**
+ * Return the ceremonies prefixes for every ceremony.
+ * @returns Promise<Array<string>>
+ */
+export const getCreatedCeremoniesPrefixes = async (): Promise<Array<string>> => {
+    // Get all ceremonies documents.
+    const ceremonies = await getAllCeremonies()
+
+    let ceremoniesPrefixes = []
+
+    // Return prefixes (if any ceremony).
+    if (ceremonies.length > 0)
+        ceremoniesPrefixes = ceremonies.map((ceremony: FirebaseDocumentInfo) => ceremony.data.prefix)
+
+    return ceremoniesPrefixes
+}
+
+/**
+ * Upload a file by subdividing it in chunks to AWS S3 bucket.
+ * @param startMultiPartUploadCF <HttpsCallable<unknown, unknown>> - the CF for initiating a multi part upload.
+ * @param generatePreSignedUrlsPartsCF <HttpsCallable<unknown, unknown>> - the CF for generating the pre-signed urls for each chunk.
+ * @param completeMultiPartUploadCF <HttpsCallable<unknown, unknown>> - the CF for completing a multi part upload.
+ * @param bucketName <string> - the name of the AWS S3 bucket.
+ * @param objectKey <string> - the path of the object inside the AWS S3 bucket.
+ * @param localPath <string> - the local path of the file to be uploaded.
+ * @param temporaryStoreCurrentContributionMultiPartUploadId <HttpsCallable<unknown, unknown>> - the CF for enable resumable upload from last chunk by temporarily store the ETags and PartNumbers of already uploaded chunks.
+ * @param temporaryStoreCurrentContributionUploadedChunkData <HttpsCallable<unknown, unknown>> - the CF for enable resumable upload from last chunk by temporarily store the ETags and PartNumbers of already uploaded chunks.
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
+ * @param tempContributionData <any> - the temporary information necessary to resume an already started multi-part upload.
+ */
+export const multiPartUpload = async (
+    startMultiPartUploadCF: HttpsCallable<unknown, unknown>,
+    generatePreSignedUrlsPartsCF: HttpsCallable<unknown, unknown>,
+    completeMultiPartUploadCF: HttpsCallable<unknown, unknown>,
+    bucketName: string,
+    objectKey: string,
+    localPath: string,
+    temporaryStoreCurrentContributionMultiPartUploadId?: HttpsCallable<unknown, unknown>,
+    temporaryStoreCurrentContributionUploadedChunkData?: HttpsCallable<unknown, unknown>,
+    ceremonyId?: string,
+    tempContributionData?: any
+) => {
+    // Configuration checks.
+    if (!process.env.CONFIG_PRESIGNED_URL_EXPIRATION_IN_SECONDS)
+        showError(GENERIC_ERRORS.GENERIC_NOT_CONFIGURED_PROPERLY, true)
+
+    // Get content type.
+    const contentType = mime.lookup(localPath)
+
+    // The Multi-Part Upload unique identifier.
+    let uploadIdZkey = ""
+    // Already uploaded chunks temp info (nb. useful only when resuming).
+    let alreadyUploadedChunks = []
+
+    // Check if the contributor can resume an already started multi-part upload.
+    if (!tempContributionData || (!!tempContributionData && !tempContributionData.uploadId)) {
+        // Start from scratch.
+        const spinner = customSpinner(`Starting upload process...`, `clock`)
+        spinner.start()
+
+        uploadIdZkey = await openMultiPartUpload(startMultiPartUploadCF, bucketName, objectKey, ceremonyId)
+
+        if (temporaryStoreCurrentContributionMultiPartUploadId)
+            // Store Multi-Part Upload ID after generation.
+            await temporaryStoreCurrentContributionMultiPartUploadId({
+                ceremonyId,
+                uploadId: uploadIdZkey
+            })
+
+        spinner.stop()
+    } else {
+        // Read temp info from Firestore.
+        uploadIdZkey = tempContributionData.uploadId
+        alreadyUploadedChunks = tempContributionData.chunks
+    }
+
+    // Step 2
+    const spinner = customSpinner(`Splitting file in chunks...`, `clock`)
+    spinner.start()
+
+    const chunksWithUrlsZkey = await getChunksAndPreSignedUrls(
+        generatePreSignedUrlsPartsCF,
+        bucketName,
+        objectKey,
+        localPath,
+        uploadIdZkey,
+        Number(process.env.CONFIG_PRESIGNED_URL_EXPIRATION_IN_SECONDS!),
+        ceremonyId
+    )
+
+    // Step 3
+    const partNumbersAndETagsZkey = await uploadParts(
+        chunksWithUrlsZkey,
+        contentType,
+        temporaryStoreCurrentContributionUploadedChunkData,
+        ceremonyId,
+        alreadyUploadedChunks
+    )
+
+    // Step 4
+    spinner.text = `Completing upload...`
+    spinner.start()
+
+    await closeMultiPartUpload(
+        completeMultiPartUploadCF,
+        bucketName,
+        objectKey,
+        uploadIdZkey,
+        partNumbersAndETagsZkey,
+        ceremonyId
+    )
+
+    spinner.stop()
+}
+
+/**
+ * Get a value from a key information about a circuit.
+ * @param circuitInfo <string> - the stringified content of the .r1cs file.
+ * @param rgx <RegExp> - regular expression to match the key.
+ * @returns <string>
+ */
+export const getCircuitMetadataFromR1csFile = (circuitInfo: string, rgx: RegExp): string => {
+    // Match.
+    const matchInfo = circuitInfo.match(rgx)
+
+    if (!matchInfo) showError(GENERIC_ERRORS.GENERIC_R1CS_MISSING_INFO, true)
+
+    // Split and return the value.
+    return matchInfo?.at(0)?.split(":")[1].replace(" ", "").split("#")[0].replace("\n", "")!
+}
+
+/**
+ * Return the necessary Power of Tau "powers" given the number of circuits constraints.
+ * @param constraints <number> - the number of circuit contraints.
+ * @param outputs <number> - the number of circuit outputs.
+ * @returns <number>
+ */
+export const estimatePoT = (constraints: number, outputs: number): number => {
+    let power = 2
+    let pot = 2 ** power
+
+    while (constraints + outputs > pot) {
+        power += 1
+        pot = 2 ** power
+    }
+
+    return power
+}
+
+/**
+ * Get the powers from pot file name
+ * @dev the pot files must follow these convention (i_am_a_pot_file_09.ptau) where the numbers before '.ptau' are the powers.
+ * @param potFileName <string>
+ * @returns <number>
+ */
+export const extractPoTFromFilename = (potFileName: string): number =>
+    Number(potFileName.split("_").pop()?.split(".").at(0))
+
+/**
+ * Extract a prefix (like_this) from a provided string with special characters and spaces.
+ * @dev replaces all symbols and whitespaces with underscore.
+ * @param str <string>
+ * @returns <string>
+ */
+export const extractPrefix = (str: string): string =>
+    // eslint-disable-next-line no-useless-escape
+    str.replace(/[`\s~!@#$%^&*()|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, "-").toLowerCase()
+
+/**
+ * Format the next zkey index.
+ * @param progress <number> - the progression in zkey index (= contributions).
+ * @returns <string>
+ */
+export const formatZkeyIndex = (progress: number): string => {
+    let index = progress.toString()
+
+    while (index.length < firstZkeyIndex.length) {
+        index = `0${index}`
+    }
+
+    return index
+}
+
+/**
+ * Convert milliseconds to seconds.
+ * @param millis <number>
+ * @returns <number>
+ */
+export const convertMillisToSeconds = (millis: number): number => Number((millis / 1000).toFixed(2))
+
+/**
+ * Bootstrap whatever is needed for a new command execution (clean terminal, print header, init Firebase services).
+ * @returns <Promise<FirebaseServices>>
+ */
+export const bootstrapCommandExec = async (): Promise<FirebaseServices> => {
+    // Clean terminal window.
+    clear()
+
+    // Print header.
+    console.log(theme.magenta(figlet.textSync("Phase 2 cli", { font: "Ogre" })))
+
+    // Initialize Firebase services
+    return initServices()
+}
+
+/**
+ * Gracefully terminate the command execution
+ * @params ghUsername <string> - the Github username of the user.
+ */
+export const terminate = async (ghUsername: string) => {
+    console.log(`\nSee you, ${theme.bold(`@${ghUsername}`)} ${emojis.wave}`)
+
+    process.exit(0)
+}
+
+/**
+ * Make a new countdown and throws an error when time is up.
+ * @param durationInSeconds <number> - the amount of time to be counted in seconds.
+ * @param intervalInSeconds <number> - update interval in seconds.
+ */
+export const createExpirationCountdown = (durationInSeconds: number, intervalInSeconds: number) => {
+    let seconds = durationInSeconds <= 60 ? durationInSeconds : 60
+
+    setInterval(() => {
+        try {
+            if (durationInSeconds !== 0) {
+                // Update times.
+                durationInSeconds -= intervalInSeconds
+                seconds -= intervalInSeconds
+
+                if (seconds % 60 === 0) seconds = 0
+
+                process.stdout.write(
+                    `${symbols.warning} Expires in ${theme.bold(
+                        theme.magenta(`00:${Math.floor(durationInSeconds / 60)}:${seconds}`)
+                    )}\r`
+                )
+            } else showError(GENERIC_ERRORS.GENERIC_COUNTDOWN_EXPIRED, true)
+        } catch (err: any) {
+            // Workaround to the \r.
+            process.stdout.write(`\n\n`)
+            showError(GENERIC_ERRORS.GENERIC_COUNTDOWN_EXPIRATION, true)
+        }
+    }, intervalInSeconds * 1000)
+}
+
+/**
+ * Create and return a simple countdown for a specified amount of time.
+ * @param remainingTime <number> - the amount of time to be counted.
+ * @param message <string> - the message to be shown.
+ * @returns <NodeJS.Timer>
+ */
+export const simpleCountdown = (remainingTime: number, message: string): NodeJS.Timer =>
+    setInterval(() => {
+        remainingTime -= 1000
+
+        const {
+            seconds: cdSeconds,
+            minutes: cdMinutes,
+            hours: cdHours
+        } = getSecondsMinutesHoursFromMillis(Math.abs(remainingTime))
+
+        process.stdout.write(
+            `${message} (${remainingTime < 0 ? theme.bold(`-`) : ``}${convertToDoubleDigits(
+                cdHours
+            )}:${convertToDoubleDigits(cdMinutes)}:${convertToDoubleDigits(cdSeconds)})\r`
+        )
+    }, 1000)
+
+/**
+ * Manage the communication of timeout-related messages for a contributor.
+ * @param participantData <DocumentData> - the data of the participant document.
+ * @param participantId <string> - the unique identifier of the contributor.
+ * @param ceremonyId <string> - the unique identifier of the ceremony.
+ * @param isContributing <boolean>
+ * @param ghUsername <string>
+ */
+export const handleTimedoutMessageForContributor = async (
+    participantData: DocumentData,
+    participantId: string,
+    ceremonyId: string,
+    isContributing: boolean,
+    ghUsername: string
+): Promise<void> => {
+    // Extract data.
+    const { status, contributionStep, contributionProgress } = participantData
+
+    // Check if the contributor has been timedout.
+    if (status === ParticipantStatus.TIMEDOUT && contributionStep !== ParticipantContributionStep.COMPLETED) {
+        if (!isContributing) console.log(theme.bold(`\n- Circuit # ${theme.magenta(contributionProgress)}`))
+        else process.stdout.write(`\n`)
+
+        console.log(
+            `${symbols.error} ${
+                isContributing ? `You have been timedout while contributing` : `Timeout still in progress.`
+            }\n\n${
+                symbols.warning
+            } This can happen due to network or memory issues, un/intentional crash, or contributions lasting for too long.`
+        )
+
+        // nb. workaround to retrieve the latest timeout data from the database.
+        await simpleLoader(`Checking timeout...`, `clock`, 1000)
+
+        // Check when the participant will be able to retry the contribution.
+        const activeTimeouts = await getCurrentActiveParticipantTimeout(ceremonyId, participantId)
+
+        if (activeTimeouts.length !== 1) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
+
+        const activeTimeoutData = activeTimeouts.at(0)?.data
+
+        if (!activeTimeoutData) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
+
+        const { seconds, minutes, hours, days } = getSecondsMinutesHoursFromMillis(
+            Number(activeTimeoutData?.endDate) - Timestamp.now().toMillis()
+        )
+
+        console.log(
+            `${symbols.info} You can retry your contribution in ${theme.bold(
+                `${convertToDoubleDigits(days)}:${convertToDoubleDigits(hours)}:${convertToDoubleDigits(
+                    minutes
+                )}:${convertToDoubleDigits(seconds)}`
+            )} (dd/hh/mm/ss)`
+        )
+
+        terminate(ghUsername)
+    }
+}
+
+/**
+ * Compute a new Groth 16 Phase 2 contribution.
+ * @param lastZkey <string> - the local path to last zkey.
+ * @param newZkey <string> - the local path to new zkey.
+ * @param name <string> - the name of the contributor.
+ * @param entropyOrBeacon <string> - the value representing the entropy or beacon.
+ * @param logger <Logger | Console> - custom winston or console logger.
+ * @param finalize <boolean> - true when finalizing the ceremony with the last contribution; otherwise false.
+ * @param contributionComputationTime <number> - the contribution computation time in milliseconds for the circuit.
+ */
+export const computeContribution = async (
+    lastZkey: string,
+    newZkey: string,
+    name: string,
+    entropyOrBeacon: string,
+    logger: Logger | Console,
+    finalize: boolean,
+    contributionComputationTime: number
+) => {
+    // Format average contribution time.
+    const { seconds, minutes, hours } = getSecondsMinutesHoursFromMillis(contributionComputationTime)
+
+    // Custom spinner for visual feedback.
+    const text = `${finalize ? `Applying beacon...` : `Computing contribution...`} ${
+        contributionComputationTime > 0
+            ? `(ETA ${theme.bold(
+                  `${convertToDoubleDigits(hours)}:${convertToDoubleDigits(minutes)}:${convertToDoubleDigits(seconds)}`
+              )} |`
+            : ``
+    }`
+
+    let counter = 0
+
+    // Format time.
+    const {
+        seconds: counterSeconds,
+        minutes: counterMinutes,
+        hours: counterHours
+    } = getSecondsMinutesHoursFromMillis(counter)
+
+    const spinner = customSpinner(
+        `${text} ${convertToDoubleDigits(counterHours)}:${convertToDoubleDigits(
+            counterMinutes
+        )}:${convertToDoubleDigits(counterSeconds)})\r`,
+        `clock`
+    )
+    spinner.start()
+
+    const interval = setInterval(() => {
+        counter += 1000
+
+        const {
+            seconds: counterSec,
+            minutes: counterMin,
+            hours: counterHrs
+        } = getSecondsMinutesHoursFromMillis(counter)
+
+        spinner.text = `${text} ${convertToDoubleDigits(counterHrs)}:${convertToDoubleDigits(
+            counterMin
+        )}:${convertToDoubleDigits(counterSec)})\r`
+    }, 1000)
+
+    if (finalize)
+        // Finalize applying a random beacon.
+        await zKey.beacon(lastZkey, newZkey, name, entropyOrBeacon, numIterationsExp, logger)
+    // Compute the next contribution.
+    else await zKey.contribute(lastZkey, newZkey, name, entropyOrBeacon, logger)
+
+    // nb. workaround to logger descriptor close.
+    await sleep(1000)
+
+    spinner.stop()
+    clearInterval(interval)
+}
+
+/**
+ * Create a custom logger.
+ * @dev useful for keeping track of `info` logs from snarkjs and use them to generate the contribution transcript.
+ * @param transcriptFilename <string> - logger output file.
+ * @returns <Logger>
+ */
+export const getTranscriptLogger = (transcriptFilename: string): Logger =>
+    // Create a custom logger.
+    winston.createLogger({
+        level: "info",
+        format: winston.format.printf((log) => log.message),
+        transports: [
+            // Write all logs with importance level of `info` to `transcript.json`.
+            new winston.transports.File({
+                filename: transcriptFilename,
+                level: "info"
+            })
+        ]
+    })
+
+/**
+ * Make a progress to the next contribution step for the current contributor.
+ * @param firebaseFunctions <Functions> - the object containing the firebase functions.
+ * @param ceremonyId <string> - the ceremony unique identifier.
+ * @param showSpinner <boolean> - true to show a custom spinner on the terminal; otherwise false.
+ * @param message <string> - custom message string based on next contribution step value.
+ */
+export const makeContributionStepProgress = async (
+    firebaseFunctions: Functions,
+    ceremonyId: string,
+    showSpinner: boolean,
+    message: string
+) => {
+    // Get CF.
+    const progressToNextContributionStep = httpsCallable(firebaseFunctions, "progressToNextContributionStep")
+
+    // Custom spinner for visual feedback.
+    const spinner: Ora = customSpinner(`Getting ready for ${message} step`, "clock")
+
+    if (showSpinner) spinner.start()
+
+    // Progress to next contribution step.
+    await progressToNextContributionStep({ ceremonyId })
+
+    if (showSpinner) spinner.stop()
+}
+
+/**
+ * Return the next circuit where the participant needs to compute or has computed the contribution.
+ * @param circuits <Array<FirebaseDocumentInfo>> - the ceremony circuits document.
+ * @param nextCircuitPosition <number> - the position in the sequence of circuits where the next contribution must be done.
+ * @returns <FirebaseDocumentInfo>
+ */
+export const getNextCircuitForContribution = (
+    circuits: Array<FirebaseDocumentInfo>,
+    nextCircuitPosition: number
+): FirebaseDocumentInfo => {
+    // Filter for sequence position (should match contribution progress).
+    const filteredCircuits = circuits.filter(
+        (circuit: FirebaseDocumentInfo) => circuit.data.sequencePosition === nextCircuitPosition
+    )
+
+    // There must be only one.
+    if (filteredCircuits.length !== 1) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
+
+    return filteredCircuits.at(0)!
+}
+
+/**
+ * Generate the public attestation for the contributor.
+ * @param ceremonyDoc <FirebaseDocumentInfo> - the ceremony document.
+ * @param participantId <string> - the unique identifier of the participant.
+ * @param participantData <DocumentData> - the data of the participant document.
+ * @param circuits <Array<FirebaseDocumentInfo> - the ceremony circuits documents.
+ * @param ghUsername <string> - the Github username of the contributor.
+ * @param ghToken <string> - the Github access token of the contributor.
+ */
+export const generatePublicAttestation = async (
+    ceremonyDoc: FirebaseDocumentInfo,
+    participantId: string,
+    participantData: DocumentData,
+    circuits: Array<FirebaseDocumentInfo>,
+    ghUsername: string,
+    ghToken: string
+): Promise<void> => {
+    // Attestation preamble.
+    const attestationPreamble = `Hey, I'm ${ghUsername} and I have contributed to the ${ceremonyDoc.data.title} MPC Phase2 Trusted Setup ceremony.\nThe following are my contribution signatures:`
+
+    // Return true and false based on contribution verification.
+    const contributionsValidity = await getContributorContributionsVerificationResults(
+        ceremonyDoc.id,
+        participantId,
+        circuits,
+        false
+    )
+    const numberOfValidContributions = contributionsValidity.filter(Boolean).length
+
+    console.log(
+        `\nCongrats, you have successfully contributed to ${theme.magenta(
+            theme.bold(numberOfValidContributions)
+        )} out of ${theme.magenta(theme.bold(circuits.length))} circuits ${emojis.tada}`
+    )
+
+    // Show valid/invalid contributions per each circuit.
+    let idx = 0
+
+    for (const contributionValidity of contributionsValidity) {
+        console.log(
+            `${contributionValidity ? symbols.success : symbols.error} ${theme.bold(`Circuit`)} ${theme.bold(
+                theme.magenta(idx + 1)
+            )}`
+        )
+        idx += 1
+    }
+
+    process.stdout.write(`\n`)
+
+    const spinner = customSpinner("Uploading public attestation...", "clock")
+    spinner.start()
+
+    // Get only valid contribution hashes.
+    const attestation = await getValidContributionAttestation(
+        contributionsValidity,
+        circuits,
+        participantData!,
+        ceremonyDoc.id,
+        participantId,
+        attestationPreamble,
+        false
+    )
+
+    writeFile(`${paths.attestationPath}/${ceremonyDoc.data.prefix}_attestation.log`, Buffer.from(attestation))
+    await sleep(1000)
+
+    // TODO: If fails for permissions problems, ask to do manually.
+    const gistUrl = await publishGist(ghToken, attestation, ceremonyDoc.data.prefix, ceremonyDoc.data.title)
+
+    spinner.succeed(
+        `Public attestation successfully published as Github Gist at this link ${theme.bold(theme.underlined(gistUrl))}`
+    )
+
+    // Attestation link via Twitter.
+    const attestationTweet = `https://twitter.com/intent/tweet?text=I%20contributed%20to%20the%20${ceremonyDoc.data.title}%20Phase%202%20Trusted%20Setup%20ceremony!%20You%20can%20contribute%20here:%20https://github.com/quadratic-funding/mpc-phase2-suite%20You%20can%20view%20my%20attestation%20here:%20${gistUrl}%20#Ethereum%20#ZKP`
+
+    console.log(
+        `\nWe appreciate your contribution to preserving the ${ceremonyDoc.data.title} security! ${
+            emojis.key
+        }  You can tweet about your participation if you'd like (click on the link below ${
+            emojis.pointDown
+        }) \n\n${theme.underlined(attestationTweet)}`
+    )
+
+    await open(attestationTweet)
+}
+
+/**
+ * Download a local copy of the zkey.
+ * @param cf <HttpsCallable<unknown, unknown>> - the corresponding cloud function.
+ * @param bucketName <string> - the name of the AWS S3 bucket.
+ * @param objectKey <string> - the identifier of the object (storage path).
+ * @param localPath <string> - the path where the file will be written.
+ * @param showSpinner <boolean> - true to show a custom spinner on the terminal; otherwise false.
+ */
+export const downloadContribution = async (
+    cf: HttpsCallable<unknown, unknown>,
+    bucketName: string,
+    objectKey: string,
+    localPath: string,
+    showSpinner: boolean
+) => {
+    // Custom spinner for visual feedback.
+    const spinner: Ora = customSpinner(`Downloading contribution...`, "clock")
+
+    if (showSpinner) spinner.start()
+
+    // Download from storage.
+    await downloadLocalFileFromBucket(cf, bucketName, objectKey, localPath)
+
+    if (showSpinner) spinner.stop()
+}
+
+/**
+ * Upload the new zkey to the storage.
+ * @param storagePath <string> - the Storage path where the zkey will be stored.
+ * @param localPath <string> - the local path where the zkey is stored.
+ * @param showSpinner <boolean> - true to show a custom spinner on the terminal; otherwise false.
+ */
+export const uploadContribution = async (storagePath: string, localPath: string, showSpinner: boolean) => {
+    // Custom spinner for visual feedback.
+    const spinner = customSpinner("Storing your contribution...", "clock")
+    if (showSpinner) spinner.start()
+
+    // Upload to storage.
+    await uploadFileToStorage(localPath, storagePath)
+
+    if (showSpinner) spinner.stop()
+}
+
+/**
+ * Compute a new Groth16 contribution verification.
+ * @param ceremony <FirebaseDocumentInfo> - the ceremony document.
+ * @param circuit <FirebaseDocumentInfo> - the circuit document.
+ * @param ghUsername <string> - the Github username of the user.
+ * @param avgVerifyCloudFunctionTime <number> - the average verify Cloud Function execution time in milliseconds.
+ * @param firebaseFunctions <Functions> - the object containing the firebase functions.
+ * @returns <Promise<VerifyContributionComputation>>
+ */
+export const computeVerification = async (
+    ceremony: FirebaseDocumentInfo,
+    circuit: FirebaseDocumentInfo,
+    ghUsername: string,
+    avgVerifyCloudFunctionTime: number,
+    firebaseFunctions: Functions
+): Promise<VerifyContributionComputation> => {
+    // Format average verification time.
+    const { seconds, minutes, hours } = getSecondsMinutesHoursFromMillis(avgVerifyCloudFunctionTime)
+
+    // Custom spinner for visual feedback.
+    const spinner = customSpinner(
+        `Verifying your contribution... ${
+            avgVerifyCloudFunctionTime > 0
+                ? `(est. time ${theme.bold(
+                      `${convertToDoubleDigits(hours)}:${convertToDoubleDigits(minutes)}:${convertToDoubleDigits(
+                          seconds
+                      )}`
+                  )})`
+                : ``
+        }\n`,
+        "clock"
+    )
+
+    spinner.start()
+
+    // Verify contribution callable Cloud Function.
+    const verifyContribution = httpsCallableFromURL(
+        firebaseFunctions!,
+        process.env.FIREBASE_CF_URL_VERIFY_CONTRIBUTION!,
+        {
+            timeout: 3600000
+        }
+    )
+
+    // The verification must be done remotely (Cloud Functions).
+    const response = await verifyContribution({
+        ceremonyId: ceremony.id,
+        circuitId: circuit.id,
+        ghUsername,
+        bucketName: getBucketName(ceremony.data.prefix)
+    })
+
+    spinner.stop()
+
+    if (!response) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
+
+    const { data }: any = response
+
+    return {
+        valid: data.valid,
+        verificationComputationTime: data.verificationComputationTime,
+        verifyCloudFunctionTime: data.verifyCloudFunctionTime,
+        fullContributionTime: data.fullContributionTime
+    }
+}
+
+/**
+ * Compute a new contribution for the participant.
+ * @param ceremony <FirebaseDocumentInfo> - the ceremony document.
+ * @param circuit <FirebaseDocumentInfo> - the circuit document.
+ * @param entropyOrBeacon <any> - the entropy/beacon for the contribution.
+ * @param ghUsername <string> - the Github username of the user.
+ * @param finalize <boolean> - true if the contribution finalize the ceremony; otherwise false.
+ * @param firebaseFunctions <Functions> - the object containing the firebase functions.
+ * @param newParticipantData <DocumentData> - the object containing the participant data.
+ * @returns <Promise<string>> - new updated attestation file.
+ */
+export const makeContribution = async (
+    ceremony: FirebaseDocumentInfo,
+    circuit: FirebaseDocumentInfo,
+    entropyOrBeacon: any,
+    ghUsername: string,
+    finalize: boolean,
+    firebaseFunctions: Functions,
+    newParticipantData?: DocumentData
+): Promise<void> => {
+    // Extract data from circuit.
+    const currentProgress = circuit.data.waitingQueue.completedContributions
+    const { avgTimings } = circuit.data
+
+    // Compute zkey indexes.
+    const currentZkeyIndex = formatZkeyIndex(currentProgress)
+    const nextZkeyIndex = formatZkeyIndex(currentProgress + 1)
+
+    // Paths config.
+    const transcriptsPath = finalize ? paths.finalTranscriptsPath : paths.contributionTranscriptsPath
+    const contributionsPath = finalize ? paths.finalZkeysPath : paths.contributionsPath
+
+    // Get custom transcript logger.
+    const contributionTranscriptLocalPath = `${transcriptsPath}/${circuit.data.prefix}_${
+        finalize ? `${ghUsername}_final` : nextZkeyIndex
+    }.log`
+    const transcriptLogger = getTranscriptLogger(contributionTranscriptLocalPath)
+    const bucketName = getBucketName(ceremony.data.prefix)
+
+    // Write first message.
+    transcriptLogger.info(
+        `${finalize ? `Final` : `Contribution`} transcript for ${circuit.data.prefix} phase 2 contribution.\n${
+            finalize ? `Coordinator: ${ghUsername}` : `Contributor # ${Number(nextZkeyIndex)}`
+        } (${ghUsername})\n`
+    )
+
+    console.log(
+        `${theme.bold(`\n- Circuit # ${theme.magenta(`${circuit.data.sequencePosition}`)}`)} (Contribution Steps)`
+    )
+
+    if (
+        finalize ||
+        (!!newParticipantData?.contributionStep &&
+            newParticipantData?.contributionStep === ParticipantContributionStep.DOWNLOADING)
+    ) {
+        const spinner = customSpinner(`Preparing for download...`, `clock`)
+        spinner.start()
+
+        // 1. Download last contribution.
+        const storagePath = `${collections.circuits}/${circuit.data.prefix}/${collections.contributions}/${circuit.data.prefix}_${currentZkeyIndex}.zkey`
+        const localPath = `${contributionsPath}/${circuit.data.prefix}_${currentZkeyIndex}.zkey`
+
+        // Download w/ Presigned urls.
+        const generateGetObjectPreSignedUrl = httpsCallable(firebaseFunctions!, "generateGetObjectPreSignedUrl")
+
+        spinner.stop()
+
+        await downloadContribution(generateGetObjectPreSignedUrl, bucketName, storagePath, localPath, false)
+
+        console.log(`${symbols.success} Contribution ${theme.bold(`#${currentZkeyIndex}`)} correctly downloaded`)
+
+        // Make the step if not finalizing.
+        if (!finalize) await makeContributionStepProgress(firebaseFunctions!, ceremony.id, true, "computation")
+    } else console.log(`${symbols.success} Contribution ${theme.bold(`#${currentZkeyIndex}`)} already downloaded`)
+
+    if (
+        finalize ||
+        (!!newParticipantData?.contributionStep &&
+            newParticipantData?.contributionStep === ParticipantContributionStep.DOWNLOADING) ||
+        newParticipantData?.contributionStep === ParticipantContributionStep.COMPUTING
+    ) {
+        const contributionComputationTimer = new Timer({ label: "contributionComputation" }) // Compute time (only for statistics).
+
+        // 2.A Compute the new contribution.
+        contributionComputationTimer.start()
+
+        await computeContribution(
+            `${contributionsPath}/${circuit.data.prefix}_${currentZkeyIndex}.zkey`,
+            `${contributionsPath}/${circuit.data.prefix}_${finalize ? `final` : nextZkeyIndex}.zkey`,
+            ghUsername,
+            entropyOrBeacon,
+            transcriptLogger,
+            finalize,
+            avgTimings.contributionComputation
+        )
+
+        contributionComputationTimer.stop()
+
+        const contributionComputationTime = contributionComputationTimer.ms()
+
+        const spinner = customSpinner(`Storing contribution time and hash...`, `clock`)
+        spinner.start()
+
+        // nb. workaround for file descriptor close.
+        await sleep(2000)
+
+        // 2.B Generate attestation from single contribution transcripts from each circuit (queue this contribution).
+        const transcript = readFile(contributionTranscriptLocalPath)
+
+        const matchContributionHash = transcript.match(/Contribution.+Hash.+\n\t\t.+\n\t\t.+\n.+\n\t\t.+\n/)
+
+        if (!matchContributionHash) showError(GENERIC_ERRORS.GENERIC_CONTRIBUTION_HASH_INVALID, true)
+
+        const contributionHash = matchContributionHash?.at(0)?.replace("\n\t\t", "")!
+
+        const permanentlyStoreCurrentContributionTimeAndHash = httpsCallable(
+            firebaseFunctions!,
+            "permanentlyStoreCurrentContributionTimeAndHash"
+        )
+
+        await permanentlyStoreCurrentContributionTimeAndHash({
+            ceremonyId: ceremony.id,
+            contributionComputationTime,
+            contributionHash
+        })
+
+        const {
+            seconds: computationSeconds,
+            minutes: computationMinutes,
+            hours: computationHours
+        } = getSecondsMinutesHoursFromMillis(contributionComputationTime)
+
+        spinner.succeed(
+            `${
+                finalize ? "Contribution" : `Contribution ${theme.bold(`#${nextZkeyIndex}`)}`
+            } computation took ${theme.bold(
+                `${convertToDoubleDigits(computationHours)}:${convertToDoubleDigits(
+                    computationMinutes
+                )}:${convertToDoubleDigits(computationSeconds)}`
+            )}`
+        )
+
+        // Make the step if not finalizing.
+        if (!finalize) await makeContributionStepProgress(firebaseFunctions!, ceremony.id, true, "upload")
+    } else console.log(`${symbols.success} Contribution ${theme.bold(`#${nextZkeyIndex}`)} already computed`)
+
+    if (
+        finalize ||
+        (!!newParticipantData?.contributionStep &&
+            newParticipantData?.contributionStep === ParticipantContributionStep.DOWNLOADING) ||
+        newParticipantData?.contributionStep === ParticipantContributionStep.COMPUTING ||
+        newParticipantData?.contributionStep === ParticipantContributionStep.UPLOADING
+    ) {
+        // 3. Store file.
+        const storagePath = `${collections.circuits}/${circuit.data.prefix}/${collections.contributions}/${
+            circuit.data.prefix
+        }_${finalize ? `final` : nextZkeyIndex}.zkey`
+        const localPath = `${contributionsPath}/${circuit.data.prefix}_${finalize ? `final` : nextZkeyIndex}.zkey`
+
+        // Upload.
+        const startMultiPartUpload = httpsCallable(firebaseFunctions, "startMultiPartUpload")
+        const generatePreSignedUrlsParts = httpsCallable(firebaseFunctions, "generatePreSignedUrlsParts")
+        const completeMultiPartUpload = httpsCallable(firebaseFunctions, "completeMultiPartUpload")
+
+        if (!finalize) {
+            const temporaryStoreCurrentContributionMultiPartUploadId = httpsCallable(
+                firebaseFunctions,
+                "temporaryStoreCurrentContributionMultiPartUploadId"
+            )
+            const temporaryStoreCurrentContributionUploadedChunk = httpsCallable(
+                firebaseFunctions,
+                "temporaryStoreCurrentContributionUploadedChunkData"
+            )
+
+            await multiPartUpload(
+                startMultiPartUpload,
+                generatePreSignedUrlsParts,
+                completeMultiPartUpload,
+                bucketName,
+                storagePath,
+                localPath,
+                temporaryStoreCurrentContributionMultiPartUploadId,
+                temporaryStoreCurrentContributionUploadedChunk,
+                ceremony.id,
+                newParticipantData?.tempContributionData
+            )
+        } else
+            await multiPartUpload(
+                startMultiPartUpload,
+                generatePreSignedUrlsParts,
+                completeMultiPartUpload,
+                bucketName,
+                storagePath,
+                localPath
+            )
+
+        console.log(
+            `${symbols.success} ${
+                finalize ? `Contribution` : `Contribution ${theme.bold(`#${nextZkeyIndex}`)}`
+            } correctly saved on storage`
+        )
+
+        // Make the step if not finalizing.
+        if (!finalize) await makeContributionStepProgress(firebaseFunctions!, ceremony.id, true, "verification")
+    } else
+        console.log(
+            `${symbols.success} ${
+                finalize ? `Contribution` : `Contribution ${theme.bold(`#${nextZkeyIndex}`)}`
+            } already saved on storage`
+        )
+
+    if (
+        finalize ||
+        (!!newParticipantData?.contributionStep &&
+            newParticipantData?.contributionStep === ParticipantContributionStep.DOWNLOADING) ||
+        newParticipantData?.contributionStep === ParticipantContributionStep.COMPUTING ||
+        newParticipantData?.contributionStep === ParticipantContributionStep.UPLOADING ||
+        newParticipantData?.contributionStep === ParticipantContributionStep.VERIFYING
+    ) {
+        // 5. Verify contribution.
+        const { valid, verifyCloudFunctionTime, fullContributionTime } = await computeVerification(
+            ceremony,
+            circuit,
+            ghUsername,
+            avgTimings.verifyCloudFunction,
+            firebaseFunctions
+        )
+
+        const {
+            seconds: verificationSeconds,
+            minutes: verificationMinutes,
+            hours: verificationHours
+        } = getSecondsMinutesHoursFromMillis(verifyCloudFunctionTime)
+
+        console.log(
+            `${valid ? symbols.success : symbols.error} ${
+                finalize ? `Contribution` : `Contribution ${theme.bold(`#${nextZkeyIndex}`)}`
+            } ${valid ? `is ${theme.bold("VALID")}` : `is ${theme.bold("INVALID")}`}`
+        )
+        console.log(
+            `${symbols.success} ${
+                finalize ? `Contribution` : `Contribution ${theme.bold(`#${nextZkeyIndex}`)}`
+            } verification took ${theme.bold(
+                `${convertToDoubleDigits(verificationHours)}:${convertToDoubleDigits(
+                    verificationMinutes
+                )}:${convertToDoubleDigits(verificationSeconds)}`
+            )}`
+        )
+
+        const {
+            seconds: contributionSeconds,
+            minutes: contributionMinutes,
+            hours: contributionHours
+        } = getSecondsMinutesHoursFromMillis(fullContributionTime + verifyCloudFunctionTime)
+        console.log(
+            `${symbols.info} Your contribution took ${theme.bold(
+                `${convertToDoubleDigits(contributionHours)}:${convertToDoubleDigits(
+                    contributionMinutes
+                )}:${convertToDoubleDigits(contributionSeconds)}`
+            )}`
+        )
+    }
+}
diff --git a/packages/phase2cli/test/index.test.ts b/packages/phase2cli/test/index.test.ts
new file mode 100644
index 00000000..1baf9e1d
--- /dev/null
+++ b/packages/phase2cli/test/index.test.ts
@@ -0,0 +1,5 @@
+describe("Sample", () => {
+    it("should console.log", () => {
+        console.log("Hello, World!")
+    })
+})
diff --git a/packages/phase2cli/tsconfig.json b/packages/phase2cli/tsconfig.json
new file mode 100644
index 00000000..277a5e72
--- /dev/null
+++ b/packages/phase2cli/tsconfig.json
@@ -0,0 +1,9 @@
+{
+    "extends": "../../tsconfig.json",
+    "compilerOptions": {
+        "outDir": "dist/",
+        "moduleResolution": "node",
+        "declarationDir": "dist/types"
+    },
+    "include": ["src/**/*", "test/**/*", "types/**/*"]
+}
diff --git a/apps/phase2cli/types/actions.d.ts b/packages/phase2cli/types/actions.d.ts
similarity index 100%
rename from apps/phase2cli/types/actions.d.ts
rename to packages/phase2cli/types/actions.d.ts
diff --git a/packages/phase2cli/types/index.ts b/packages/phase2cli/types/index.ts
new file mode 100644
index 00000000..bf70d397
--- /dev/null
+++ b/packages/phase2cli/types/index.ts
@@ -0,0 +1,219 @@
+import { FirebaseApp } from "firebase/app"
+import { DocumentData, DocumentReference, Firestore } from "firebase/firestore"
+import { Functions } from "firebase/functions"
+import { User as FirebaseAuthUser } from "firebase/auth"
+
+export enum CeremonyState {
+    SCHEDULED = 1,
+    OPENED = 2,
+    PAUSED = 3,
+    CLOSED = 4,
+    FINALIZED = 5
+}
+
+export enum CeremonyType {
+    PHASE1 = 1,
+    PHASE2 = 2
+}
+
+export enum ProgressBarType {
+    DOWNLOAD = 1,
+    UPLOAD = 2
+}
+
+export enum ParticipantStatus {
+    CREATED = 1,
+    WAITING = 2,
+    READY = 3,
+    CONTRIBUTING = 4,
+    CONTRIBUTED = 5,
+    DONE = 6,
+    FINALIZING = 7,
+    FINALIZED = 8,
+    TIMEDOUT = 9,
+    EXHUMED = 10
+}
+
+export type GithubOAuthRequest = {
+    device_code: string
+    user_code: string
+    verification_uri: string
+    expires_in: number
+    interval: number
+}
+
+export type GithubOAuthResponse = {
+    clientSecret: string
+    type: string
+    tokenType: string
+    clientType: string
+    clientId: string
+    token: string
+    scopes: string[]
+}
+
+export type FirebaseServices = {
+    firebaseApp: FirebaseApp
+    firestoreDatabase: Firestore
+    firebaseFunctions: Functions
+}
+
+export type LocalPathDirectories = {
+    r1csDirPath: string
+    metadataDirPath: string
+    zkeysDirPath: string
+    ptauDirPath: string
+}
+
+export type FirebaseDocumentInfo = {
+    id: string
+    ref: DocumentReference<DocumentData>
+    data: DocumentData
+}
+
+export type User = {
+    name: string
+    username: string
+    providerId: string
+    createdAt: Date
+    lastLoginAt: Date
+}
+
+export type AuthUser = {
+    user: FirebaseAuthUser
+    token: string
+    username: string
+}
+
+export type CeremonyInputData = {
+    title: string
+    description: string
+    startDate: Date
+    endDate: Date
+    timeoutMechanismType: CeremonyTimeoutType
+    penalty: number
+}
+
+export type CircomCompilerData = {
+    version: string
+    commitHash: string
+}
+
+export type SourceTemplateData = {
+    source: string
+    commitHash: string
+    paramsConfiguration: Array<string>
+}
+
+export type CircuitInputData = {
+    name?: string
+    description: string
+    timeoutThreshold?: number
+    timeoutMaxContributionWaitingTime?: number
+    sequencePosition?: number
+    prefix?: string
+    zKeySizeInBytes?: number
+    compiler: CircomCompilerData
+    template: SourceTemplateData
+}
+
+export type Ceremony = CeremonyInputData & {
+    prefix: string
+    state: CeremonyState
+    type: CeremonyType
+    coordinatorId: string
+    lastUpdated: number
+}
+
+export type CircuitMetadata = {
+    curve: string
+    wires: number
+    constraints: number
+    privateInputs: number
+    publicOutputs: number
+    labels: number
+    outputs: number
+    pot: number
+}
+
+export type CircuitFiles = {
+    files?: {
+        potFilename: string
+        r1csFilename: string
+        initialZkeyFilename: string
+        potStoragePath: string
+        r1csStoragePath: string
+        initialZkeyStoragePath: string
+        potBlake2bHash: string
+        r1csBlake2bHash: string
+        initialZkeyBlake2bHash: string
+    }
+}
+
+export type CircuitTimings = {
+    avgTimings?: {
+        contributionComputation: number
+        fullContribution: number
+        verifyCloudFunction: number
+    }
+}
+
+export type Circuit = CircuitInputData &
+    CircuitFiles &
+    CircuitTimings & {
+        metadata: CircuitMetadata
+        lastUpdated?: number
+    }
+
+export type Timing = {
+    seconds: number
+    minutes: number
+    hours: number
+    days: number
+}
+
+export type CeremonyTimeoutData = {
+    type: CeremonyTimeoutType
+    penalty: number
+}
+
+export type VerifyContributionComputation = {
+    valid: boolean
+    verificationComputationTime: number
+    verifyCloudFunctionTime: number
+    fullContributionTime: number
+}
+
+export type ChunkWithUrl = {
+    partNumber: number
+    chunk: Buffer
+    preSignedUrl: string
+}
+
+export type ETagWithPartNumber = {
+    ETag: string | null
+    PartNumber: number
+}
+
+export enum RequestType {
+    PUT = 1,
+    GET = 2
+}
+
+export enum ParticipantContributionStep {
+    DOWNLOADING = 1,
+    COMPUTING = 2,
+    UPLOADING = 3,
+    VERIFYING = 4,
+    COMPLETED = 5
+}
+
+export enum TimeoutType {
+    BLOCKING_CONTRIBUTION = 1,
+    BLOCKING_CLOUD_FUNCTION = 2
+}
+
+export enum CeremonyTimeoutType {
+    DYNAMIC = 1,
+    FIXED = 2
+}
diff --git a/packages/phase2cli/types/snarkjs.d.ts b/packages/phase2cli/types/snarkjs.d.ts
new file mode 100644
index 00000000..1ac7c1ca
--- /dev/null
+++ b/packages/phase2cli/types/snarkjs.d.ts
@@ -0,0 +1,58 @@
+/** Declaration file generated by dts-gen */
+
+declare module "snarkjs" {
+    // eslint-disable-next-line @typescript-eslint/no-use-before-define
+    export = snarkjs
+
+    declare const snarkjs: {
+        groth16: {
+            exportSolidityCallData: any
+            fullProve: any
+            prove: any
+            verify: any
+        }
+        plonk: {
+            exportSolidityCallData: any
+            fullProve: any
+            prove: any
+            setup: any
+            verify: any
+        }
+        powersOfTau: {
+            beacon: any
+            challengeContribute: any
+            contribute: any
+            convert: any
+            exportChallenge: any
+            exportJson: any
+            importResponse: any
+            newAccumulator: any
+            preparePhase2: any
+            truncate: any
+            verify: any
+        }
+        r1cs: {
+            exportJson: any
+            info: any
+            print: any
+        }
+        wtns: {
+            calculate: any
+            debug: any
+            exportJson: any
+        }
+        zKey: {
+            beacon: any
+            bellmanContribute: any
+            contribute: any
+            exportBellman: any
+            exportJson: any
+            exportSolidityVerifier: any
+            exportVerificationKey: any
+            importBellman: any
+            newZKey: any
+            verifyFromInit: any
+            verifyFromR1cs: any
+        }
+    }
+}
diff --git a/tsconfig.json b/tsconfig.json
index 82bcd805..41254034 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,27 +1,20 @@
 {
-  "compilerOptions": {
-    "baseUrl": ".",
-    "strict": true,
-    "allowJs": true,
-    "target": "ES5",
-    "module": "esnext",
-    "moduleResolution": "node",
-    "esModuleInterop": true,
-    "resolveJsonModule": true,
-    "preserveConstEnums": true,
-    "skipLibCheck": true,
-    "declaration": true,
-    "allowSyntheticDefaultImports": true,
-    "declarationDir": "types",
-    "typeRoots": ["node_modules/@types", "types"],
-    "noImplicitReturns": true,
-    "noUnusedLocals": true,
-    "sourceMap": true
-  },
-  "ts-node": {
     "compilerOptions": {
-      "target": "esnext",
-      "module": "commonjs"
+        "baseUrl": ".",
+        "strict": true,
+        "target": "ESNext",
+        "module": "ESNext",
+        "moduleResolution": "node",
+        "esModuleInterop": true,
+        "resolveJsonModule": true,
+        "preserveConstEnums": true,
+        "skipLibCheck": true,
+        "declaration": true,
+        "allowSyntheticDefaultImports": true,
+        "declarationDir": "types",
+        "typeRoots": ["node_modules/@types", "types"],
+        "paths": {
+            "@zkmpc/*": ["packages/*/src"]
+        }
     }
-  }
 }