diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 000000000..e54fe4d14
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,7 @@
+node_modules
+.github
+cache
+dist
+docker-compose*
+.env*
+Dockerfile
diff --git a/.env.example b/.env.example
new file mode 100644
index 000000000..e467a87af
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,8 @@
+REVERSE_PROXY_PORT=8000
+REVERSE_PROXY_UI_PORT=8080
+CFG_VERSION=latest
+CGW_VERSION=latest
+TXS_VERSION=latest
+UI_VERSION=latest
+RPC_NODE_URL=http://host.docker.internal:8545
+ETHEREUM_NODE_URL=http://host.docker.internal:8545
\ No newline at end of file
diff --git a/.github/workflows/api.yml b/.github/workflows/api.yml
new file mode 100644
index 000000000..2927076d4
--- /dev/null
+++ b/.github/workflows/api.yml
@@ -0,0 +1,237 @@
+name: API CI
+
+on:
+    push:
+
+jobs:
+    buildAndTest:
+        name: CI Pipeline
+        runs-on: ubuntu-latest
+        strategy:
+            matrix:
+                node-version: ['18'] # Add other versions if needed
+
+        steps:
+            - name: Checkout repository
+              uses: actions/checkout@v3
+              with:
+                  fetch-depth: 0
+
+            - name: Configure AWS credentials
+              uses: aws-actions/configure-aws-credentials@v1
+              with:
+                  aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+                  aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+                  aws-region: us-east-1
+
+            - name: Install Node.js ${{ matrix.node-version }}
+              uses: actions/setup-node@v2
+              with:
+                  node-version: ${{ matrix.node-version }}
+
+            - name: Restore Node.js dependencies
+              uses: actions/cache@v3
+              with:
+                  path: ./node_modules
+                  key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
+                  restore-keys: |
+                      ${{ runner.os }}-node-
+
+            - name: Install Node.js dependencies
+              run: yarn install --frozen-lockfile
+
+            - name: Cache Node.js dependencies
+              uses: actions/cache/save@v3
+              if: always()
+              with:
+                  path: ./node_modules
+                  key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
+
+            - name: Start Hardhat RPC
+              run: |
+                  yarn hardhat > /dev/null &
+                  yarn hardhat-deploy
+
+            - name: Start Safe Transaction Service
+              run: |
+                  docker compose --env-file=.env.example -f docker-compose.safe.yml up -d --remove-orphans
+                  docker compose exec txs-web python manage.py insert_safe_master_copy --address "0xd916a690676e925Ac9Faf2d01869c13Fd9757ef2"
+                  sudo chmod -R a+rwx ./docker/data
+
+            - name: Run Tests
+              env:
+                  NODE_OPTIONS: --max-old-space-size=8192
+              run: |
+                  docker compose -f docker-compose.yml -f docker-compose.api.yml run \
+                  -e AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} \
+                  -e AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} \
+                  -e NODE_OPTIONS='--max-old-space-size=8192' \
+                  -T api \
+                  npx nx run api:test --verbose
+
+            # - name: SonarCloud Scan
+            #   uses: SonarSource/sonarcloud-github-action@master
+            #   env:
+            #       GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+            #       SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+            #   with:
+            #       projectBaseDir: apps/api
+
+    bumpVersion:
+        name: 'Bump Version on develop'
+        runs-on: ubuntu-latest
+        if: github.ref == 'refs/heads/develop'
+        needs: buildAndTest
+        outputs:
+            newTag: ${{ steps.version-bump.outputs.newTag }}
+
+        steps:
+            - name: 'Checkout source code'
+              uses: 'actions/checkout@v2'
+              with:
+                  ref: ${{ github.ref }}
+
+            - name: 'Automated Version Bump'
+              id: version-bump
+              uses: 'phips28/gh-action-bump-version@master'
+              with:
+                  tag-prefix: 'v'
+                  tag-suffix: '-api'
+                  commit-message: 'CI: bumps version to {{version}}'
+              env:
+                  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+                  PACKAGEJSON_DIR: 'apps/api'
+
+    buildAndPushImage:
+        name: Build and Push docker image
+        runs-on: ubuntu-latest
+        if: github.ref == 'refs/heads/develop'
+        needs: bumpVersion
+        steps:
+            - name: Checkout
+              uses: actions/checkout@v2
+
+            - name: Install Node.js ${{ matrix.node-version }}
+              uses: actions/setup-node@v2
+              with:
+                  node-version: ${{ matrix.node-version }}
+
+            - name: Configure AWS credentials
+              uses: aws-actions/configure-aws-credentials@v1
+              with:
+                  aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+                  aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+                  aws-region: eu-west-3
+
+            - name: Login to ECR
+              uses: docker/login-action@v1
+              with:
+                  registry: 275440070213.dkr.ecr.eu-west-3.amazonaws.com
+
+            - name: Docker meta
+              id: meta
+              uses: docker/metadata-action@v3
+              with:
+                  images: |
+                      275440070213.dkr.ecr.eu-west-3.amazonaws.com/api
+                  tags: |
+                      type=ref,event=branch
+                      type=sha
+                      type=semver,pattern={{version}},value=${{needs.bumpVersion.outputs.newTag}}
+                      type=semver,pattern={{major}}.{{minor}},value=${{needs.bumpVersion.outputs.newTag}}
+                      type=semver,pattern={{raw}},value=${{needs.bumpVersion.outputs.newTag}}
+
+            - name: Set correct version
+              run: npm version ${{needs.bumpVersion.outputs.newTag}} --allow-same-version=true --git-tag-version=false
+              working-directory: ./apps/api
+
+            - name: Build
+              uses: docker/build-push-action@v2
+              with:
+                  context: .
+                  file: apps/api/Dockerfile
+                  push: true
+                  tags: ${{ steps.meta.outputs.tags }}
+                  labels: ${{ steps.meta.outputs.labels }}
+
+    buildAndPushHotfixImage:
+        name: Build and Push hotfix docker image
+        runs-on: ubuntu-latest
+        if: startsWith(github.ref,'refs/heads/hotfix/')
+        needs: buildAndTest
+        steps:
+            - name: Checkout
+              uses: actions/checkout@v2
+
+            - name: Install Node.js ${{ matrix.node-version }}
+              uses: actions/setup-node@v2
+              with:
+                  node-version: ${{ matrix.node-version }}
+
+            - name: Configure AWS credentials
+              uses: aws-actions/configure-aws-credentials@v1
+              with:
+                  aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+                  aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+                  aws-region: eu-west-3
+
+            - name: Login to ECR
+              uses: docker/login-action@v1
+              with:
+                  registry: 275440070213.dkr.ecr.eu-west-3.amazonaws.com
+
+            - name: Docker meta
+              id: meta
+              uses: docker/metadata-action@v3
+              with:
+                  images: |
+                      275440070213.dkr.ecr.eu-west-3.amazonaws.com/api
+                  tags: |
+                      type=ref,event=branch
+                      type=sha
+
+            - name: Build
+              uses: docker/build-push-action@v2
+              with:
+                  context: .
+                  file: apps/api/Dockerfile
+                  push: true
+                  tags: ${{ steps.meta.outputs.tags }}
+                  labels: ${{ steps.meta.outputs.labels }}
+
+    autodeploy:
+        name: Auto deploy develop to dev.api.thx.network
+        runs-on: ubuntu-latest
+        if: github.ref == 'refs/heads/develop'
+        needs: [buildAndPushImage, bumpVersion]
+        steps:
+            - name: Install Node.js ${{ matrix.node-version }}
+              uses: actions/setup-node@v2
+              with:
+                  node-version: ${{ matrix.node-version }}
+
+            - name: Install deploy-scripts
+              run: npm install -g thxprotocol/deploy-scripts
+
+            - name: Configure AWS credentials
+              uses: aws-actions/configure-aws-credentials@v1
+              with:
+                  aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+                  aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+                  aws-region: eu-west-3
+
+            - name: Deploy-script
+              run: thx-deploy ApiDev sha-$(echo ${{github.sha}} | cut -c1-7)
+
+    discord:
+        name: Update Discord
+        runs-on: ubuntu-latest
+        if: github.ref == 'refs/heads/develop'
+        needs: [autodeploy, bumpVersion]
+        steps:
+            - name: Send message
+              env:
+                  DISCORD_WEBHOOK: ${{ secrets.DISCORD_AWS_WEBHOOK }}
+              uses: Ilshidur/action-discord@master
+              with:
+                  args: "${{ needs.autodeploy.result == 'success' && '✅' || '⛔' }} Released APIDev `${{ needs.bumpVersion.outputs.newTag }}`"
diff --git a/.github/workflows/auth.yml b/.github/workflows/auth.yml
new file mode 100644
index 000000000..9f8e4d689
--- /dev/null
+++ b/.github/workflows/auth.yml
@@ -0,0 +1,188 @@
+name: Auth CI
+
+on:
+    push:
+
+jobs:
+    buildAndTest:
+        name: CI Pipeline
+        runs-on: ubuntu-latest
+        strategy:
+            matrix:
+                node-version: ['18'] # Add other versions if needed
+
+        steps:
+            - name: Checkout repository
+              uses: actions/checkout@v3
+              with:
+                  fetch-depth: 0
+
+            - name: Run test
+              run: docker compose -f docker-compose.yml -f docker-compose.auth.yml run -T auth sh -c "npx nx lint auth && npx nx test auth"
+
+            # - name: SonarCloud Scan
+            #   uses: SonarSource/sonarcloud-github-action@master
+            #   env:
+            #       GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+            #       SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+            #   with:
+            #       projectBaseDir: apps/auth
+
+    bumpVersion:
+        name: 'Bump Version on develop'
+        runs-on: ubuntu-latest
+        if: github.ref == 'refs/heads/develop'
+        needs: buildAndTest
+        outputs:
+            newTag: ${{ steps.version-bump.outputs.newTag }}
+
+        steps:
+            - name: 'Checkout source code'
+              uses: 'actions/checkout@v2'
+              with:
+                  ref: ${{ github.ref }}
+
+            - name: 'Automated Version Bump'
+              id: version-bump
+              uses: 'phips28/gh-action-bump-version@master'
+              with:
+                  tag-prefix: 'v'
+                  tag-suffix: '-auth'
+                  commit-message: 'CI: bumps version to {{version}}'
+              env:
+                  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+                  PACKAGEJSON_DIR: 'apps/auth'
+
+    buildAndPushImage:
+        name: Build and Push docker image
+        runs-on: ubuntu-latest
+        if: github.ref == 'refs/heads/develop'
+        needs: bumpVersion
+        steps:
+            - name: Checkout
+              uses: actions/checkout@v2
+
+            - name: Install Node.js 16.x
+              uses: actions/setup-node@v1
+              with:
+                  node-version: 16.x
+
+            - name: Configure AWS credentials
+              uses: aws-actions/configure-aws-credentials@v1
+              with:
+                  aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+                  aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+                  aws-region: eu-west-3
+
+            - name: Login to ECR
+              uses: docker/login-action@v1
+              with:
+                  registry: 275440070213.dkr.ecr.eu-west-3.amazonaws.com
+
+            - name: Docker meta
+              id: meta
+              uses: docker/metadata-action@v3
+              with:
+                  images: |
+                      275440070213.dkr.ecr.eu-west-3.amazonaws.com/auth
+                  tags: |
+                      type=ref,event=branch
+                      type=sha
+                      type=semver,pattern={{version}},value=${{needs.bumpVersion.outputs.newTag}}
+                      type=semver,pattern={{major}}.{{minor}},value=${{needs.bumpVersion.outputs.newTag}}
+                      type=semver,pattern={{raw}},value=${{needs.bumpVersion.outputs.newTag}}
+
+            - name: Set correct version
+              run: npm version ${{needs.bumpVersion.outputs.newTag}} --allow-same-version=true --git-tag-version=false
+              working-directory: ./apps/auth
+
+            - name: Build
+              uses: docker/build-push-action@v2
+              with:
+                  context: .
+                  file: apps/auth/Dockerfile
+                  push: true
+                  tags: ${{ steps.meta.outputs.tags }}
+                  labels: ${{ steps.meta.outputs.labels }}
+
+    buildAndPushHotfixImage:
+        name: Build and Push Hotfix docker image
+        runs-on: ubuntu-latest
+        if: startsWith(github.ref,'refs/heads/hotfix/')
+        needs: buildAndTest
+        steps:
+            - name: Checkout
+              uses: actions/checkout@v2
+
+            - name: Install Node.js 16.x
+              uses: actions/setup-node@v1
+              with:
+                  node-version: 16.x
+
+            - name: Configure AWS credentials
+              uses: aws-actions/configure-aws-credentials@v1
+              with:
+                  aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+                  aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+                  aws-region: eu-west-3
+
+            - name: Login to ECR
+              uses: docker/login-action@v1
+              with:
+                  registry: 275440070213.dkr.ecr.eu-west-3.amazonaws.com
+
+            - name: Docker meta
+              id: meta
+              uses: docker/metadata-action@v3
+              with:
+                  images: |
+                      275440070213.dkr.ecr.eu-west-3.amazonaws.com/auth
+                  tags: |
+                      type=ref,event=branch
+                      type=sha
+
+            - name: Build
+              uses: docker/build-push-action@v2
+              with:
+                  context: .
+                  push: true
+                  file: apps/auth/Dockerfile
+                  tags: ${{ steps.meta.outputs.tags }}
+                  labels: ${{ steps.meta.outputs.labels }}
+
+    autodeploy:
+        name: Auto deploy develop to dev.auth.thx.network
+        runs-on: ubuntu-latest
+        if: github.ref == 'refs/heads/develop'
+        needs: [buildAndPushImage, bumpVersion]
+        steps:
+            - name: Install Node.js 16.x
+              uses: actions/setup-node@v1
+              with:
+                  node-version: 16.x
+
+            - name: Install deploy-scripts
+              run: npm install -g thxprotocol/deploy-scripts
+
+            - name: Configure AWS credentials
+              uses: aws-actions/configure-aws-credentials@v1
+              with:
+                  aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+                  aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+                  aws-region: eu-west-3
+
+            - name: Deploy-script
+              run: thx-deploy AuthDev sha-$(echo ${{github.sha}} | cut -c1-7)
+
+    discord:
+        name: Update Discord
+        runs-on: ubuntu-latest
+        if: github.ref == 'refs/heads/develop'
+        needs: [autodeploy, bumpVersion]
+        steps:
+            - name: Send message
+              env:
+                  DISCORD_WEBHOOK: ${{ secrets.DISCORD_AWS_WEBHOOK }}
+              uses: Ilshidur/action-discord@master
+              with:
+                  args: "${{ needs.autodeploy.result == 'success' && '✅' || '⛔' }} Released AuthDev `${{ needs.bumpVersion.outputs.newTag }}`"
diff --git a/.gitignore b/.gitignore
index 66be901ef..cdbe75633 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,4 +43,7 @@ Thumbs.db
 !.env.example
 certs/*
 
-.nx/cache
\ No newline at end of file
+.nx/cache
+
+# Docker
+docker/data/**/*
\ No newline at end of file
diff --git a/apps/api/.env.example b/apps/api/.env.example
new file mode 100644
index 000000000..1b4d383ac
--- /dev/null
+++ b/apps/api/.env.example
@@ -0,0 +1,41 @@
+MONGODB_URI="mongodb://root:root@localhost:27017/api?authSource=admin&ssl=false"
+MONGODB_URI_TEST_OVERRIDE="mongodb://root:root@localhost:27017/api_test?authSource=admin&ssl=false"
+MONGODB_NAME="api"
+MONGODB_USER="root"
+MONGODB_PASSWORD="root"
+AUTH_URL="https://local.auth.thx.network"
+API_URL="https://localhost:3001"
+DASHBOARD_URL="https://localhost:8082"
+WALLET_URL="https://localhost:8083"
+WIDGET_URL="https://localhost:8080"
+HARDHAT_RPC="https://localhost:8547"
+HARDHAT_RPC_TEST_OVERRIDE="http://127.0.0.1:8545"
+POLYGON_RPC=""
+ETHEREUM_RPC=""
+PRIVATE_KEY="0x873c254263b17925b686f971d7724267710895f1585bb0533db8e693a2af32ff"
+INITIAL_ACCESS_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+PORT="3000"
+AUTH_CLIENT_ID="ISsh6Xw6wDISihJS2xs2_"
+AUTH_CLIENT_SECRET="BnQrAHSb4UDnpR-9klfFAQbZvq_RjVZgLTKwRD3qfOjawX21jrnbBxvSOU84EwqAy1J-fNKvxD2qZen5Gm8jXg"
+INFURA_PROJECT_ID=""
+INFURA_IPFS_PROJECT_ID=""
+INFURA_IPFS_PROJECT_SECRET=""
+RATE_LIMIT_REWARD_GIVE="100"
+RATE_LIMIT_REWARD_GIVE_WINDOW="900"
+MAX_FEE_PER_GAS="400"
+POLYGON_NAME="maticdev"
+POLYGON_MUMBAI_NAME="mumbaidev"
+SAFE_TXS_SERVICE="http://localhost:8000/txs"
+HARDHAT_NAME="hardhat"
+AWS_ACCESS_KEY_ID=""
+AWS_SECRET_ACCESS_KEY=""
+AWS_S3_PUBLIC_BUCKET_NAME="local-thx-storage-bucket"
+AWS_S3_PUBLIC_BUCKET_REGION="eu-west-3"
+AWS_S3_PRIVATE_BUCKET_NAME="local-thx-private-storage-bucket"
+AWS_S3_PRIVATE_BUCKET_REGION="eu-west-3"
+POLYGON_RELAYER=""
+POLYGON_RELAYER_API_KEY=""
+POLYGON_RELAYER_API_SECRET=""
+LOCAL_CERT="../../certs/localhost.crt"
+LOCAL_CERT_KEY="../../certs/localhost.key"
+CWD="/usr/src/app/apps/api/src/"
\ No newline at end of file
diff --git a/apps/api/.eslintrc.json b/apps/api/.eslintrc.json
new file mode 100644
index 000000000..5626944bd
--- /dev/null
+++ b/apps/api/.eslintrc.json
@@ -0,0 +1,18 @@
+{
+    "extends": ["../../.eslintrc.json"],
+    "ignorePatterns": ["!**/*"],
+    "overrides": [
+        {
+            "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
+            "rules": {}
+        },
+        {
+            "files": ["*.ts", "*.tsx"],
+            "rules": {}
+        },
+        {
+            "files": ["*.js", "*.jsx"],
+            "rules": {}
+        }
+    ]
+}
diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile
new file mode 100644
index 000000000..365597cd4
--- /dev/null
+++ b/apps/api/Dockerfile
@@ -0,0 +1,60 @@
+#####################################################################################################
+## Develop stage
+#####################################################################################################
+FROM node:18-slim AS develop
+
+WORKDIR /usr/src/app
+
+ENV NODE_OPTIONS="--max_old_space_size=8192"
+
+RUN apt-get update \
+    && apt-get install -y g++ make python3-pip build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev \
+    --no-install-recommends \
+    && rm -rf /var/lib/apt/lists/*
+
+COPY package.json yarn.lock ./
+RUN yarn
+COPY . .
+
+CMD [ "npx", "nx", "serve", "api" ]
+
+#####################################################################################################
+## Build stage
+#####################################################################################################
+FROM node:18-slim AS build
+
+ENV NODE_ENV=production
+
+WORKDIR /usr/src/app
+COPY --from=develop ./usr/src/app/ ./
+RUN npx nx build api --prod
+COPY ./newrelic.js ./yarn.lock ./dist/apps/api/
+COPY ./libs/contracts/exports ./dist/apps/api/libs/contracts/exports
+
+#####################################################################################################
+## Production stage
+#####################################################################################################
+FROM node:18-slim AS production
+
+ENV NODE_ENV=production
+
+WORKDIR /usr/src/app
+COPY --from=build ./usr/src/app/dist/apps/api/package.json ./usr/src/app/dist/apps/api/yarn.lock  ./
+
+# Install dependencies and packages
+RUN apt-get update \
+    && apt-get install -y g++ make python3-pip build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev \
+    --no-install-recommends \
+    && rm -rf /var/lib/apt/lists/*
+
+# Install your application dependencies (assuming it uses Node.js)
+RUN yarn
+
+# Clean up unnecessary packages and files
+RUN apt-get purge -y --auto-remove build-essential && \
+    apt-get clean && \
+    rm -rf /var/lib/apt/lists/*
+
+COPY --from=build ./usr/src/app/dist/apps/api ./
+
+CMD [ "main.js" ]
\ No newline at end of file
diff --git a/apps/api/jest.config.ts b/apps/api/jest.config.ts
new file mode 100644
index 000000000..f0a4a6364
--- /dev/null
+++ b/apps/api/jest.config.ts
@@ -0,0 +1,11 @@
+/* eslint-disable */
+export default {
+    displayName: 'api',
+    preset: '../../jest.preset.js',
+    testEnvironment: 'node',
+    transform: {
+        '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
+    },
+    moduleFileExtensions: ['ts', 'js', 'html'],
+    coverageDirectory: '../../coverage/apps/api',
+};
diff --git a/apps/api/package.json b/apps/api/package.json
new file mode 100644
index 000000000..ee78b15a1
--- /dev/null
+++ b/apps/api/package.json
@@ -0,0 +1,20 @@
+{
+    "name": "@thxnetwork/api",
+    "contributors": [
+        "Peter Polman <peter@thx.network>",
+        "Valeria Grazzini <vgrazzini@gmail.com>",
+        "GarfDev <garfdev.13@gmail.com>",
+        "Bram Rongen <mail@bramrongen.nl>",
+        "Justina Mary <justinamary27@gmail.com>",
+        "Evert Kors <evert@hatching.io>",
+        "jochemvn <jochemvn@gmail.com>"
+    ],
+    "license": "AGPL-3.0",
+    "version": "1.52.132",
+    "scripts": {
+        "migrate": "yarn run migrate:db && yarn run migrate:contracts",
+        "migrate:contracts": "node upgradeContractsToLatest.js",
+        "migrate:db": "node migrate-mongo.js up",
+        "migrate:db:down": "node migrate-mongo.js down"
+    }
+}
diff --git a/apps/api/project.json b/apps/api/project.json
new file mode 100644
index 000000000..7961a9c19
--- /dev/null
+++ b/apps/api/project.json
@@ -0,0 +1,127 @@
+{
+    "name": "api",
+    "$schema": "../../node_modules/nx/schemas/project-schema.json",
+    "sourceRoot": "apps/api/src",
+    "projectType": "application",
+    "tags": [],
+    "targets": {
+        "build": {
+            "executor": "@nx/webpack:webpack",
+            "outputs": ["{options.outputPath}"],
+            "defaultConfiguration": "production",
+            "options": {
+                "target": "node",
+                "compiler": "tsc",
+                "outputPath": "dist/apps/api",
+                "main": "apps/api/src/main.ts",
+                "tsConfig": "apps/api/tsconfig.app.json",
+                "assets": ["apps/api/src/assets", "apps/api/src/app/migrations"],
+                "webpackConfig": "apps/api/webpack.config.js",
+                "generatePackageJson": true,
+                "additionalEntryPoints": [
+                    {
+                        "entryPath": "apps/api/scripts/upgradeContractsToLatest.ts",
+                        "entryName": "upgradeContractsToLatest"
+                    },
+                    {
+                        "entryPath": "apps/api/scripts/migrate-mongo.ts",
+                        "entryName": "migrate-mongo"
+                    },
+                    {
+                        "entryPath": "apps/api/scripts/script.ts",
+                        "entryName": "script"
+                    }
+                ]
+            },
+            "configurations": {
+                "development": {},
+                "production": {
+                    "optimization": true,
+                    "extractLicenses": true,
+                    "inspect": false
+                }
+            }
+        },
+        "serve": {
+            "executor": "@nx/js:node",
+            "options": {
+                "buildTarget": "api:build",
+                "host": "localhost",
+                "port": 3000,
+                "inspect": false,
+                "watch": true
+            },
+            "configurations": {
+                "production": {
+                    "buildTarget": "api:build:production"
+                }
+            }
+        },
+        "hardhat": {
+            "dependsOn": ["^build"],
+            "executor": "nx:run-commands",
+            "options": {
+                "command": "npx hardhat node --hostname 0.0.0.0",
+                "cwd": "apps/api/src/app/hardhat"
+            }
+        },
+        "hardhat-deploy": {
+            "dependsOn": ["^build"],
+            "executor": "nx:run-commands",
+            "options": {
+                "command": "npx hardhat run scripts/deploy.ts --network localhost",
+                "cwd": "apps/api/src/app/hardhat"
+            }
+        },
+        "lint": {
+            "executor": "@nx/eslint:lint",
+            "outputs": ["{options.outputFile}"],
+            "options": {
+                "lintFilePatterns": ["apps/api/**/*.ts"]
+            }
+        },
+        "test": {
+            "executor": "@nx/jest:jest",
+            "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
+            "options": {
+                "jestConfig": "apps/api/jest.config.ts",
+                "testTimeout": 60000,
+                "passWithNoTests": false,
+                "bail": false,
+                "runInBand": true,
+                "logHeapUsage": true
+            }
+        },
+        "script": {
+            "dependsOn": ["^build"],
+            "executor": "nx:run-commands",
+            "options": {
+                "command": "node script.js",
+                "cwd": "dist/apps/api"
+            }
+        },
+        "migrate-contracts": {
+            "dependsOn": ["^build"],
+            "executor": "nx:run-commands",
+            "options": {
+                "command": "node upgradeContractsToLatest.js",
+                "cwd": "dist/apps/api"
+            }
+        },
+        "migrate-db": {
+            "dependsOn": ["^build"],
+            "executor": "nx:run-commands",
+            "options": {
+                "command": "node migrate-mongo.js up",
+                "cwd": "dist/apps/api"
+            }
+        },
+        "migrate-db-create": {
+            "executor": "nx:run-commands",
+            "options": {
+                "command": "migrate-mongo create -f src/app/config/migrate-mongo-create-only.json",
+                "cwd": "apps/api"
+            }
+        }
+    }
+}
diff --git a/apps/api/scripts/migrate-mongo.ts b/apps/api/scripts/migrate-mongo.ts
new file mode 100644
index 000000000..eca6d7799
--- /dev/null
+++ b/apps/api/scripts/migrate-mongo.ts
@@ -0,0 +1,4 @@
+import { migrateMongoScript } from '@thxnetwork/common/index';
+import migrateMongoConfig from '../src/app/config/migrate-mongo';
+
+migrateMongoScript(migrateMongoConfig);
diff --git a/apps/api/scripts/script.ts b/apps/api/scripts/script.ts
new file mode 100644
index 000000000..db419c30c
--- /dev/null
+++ b/apps/api/scripts/script.ts
@@ -0,0 +1,23 @@
+import db from '@thxnetwork/api/util/database';
+import main from './src/veLiquidity';
+// import main from './src/veTransfer';
+// import main from './src/veRewards';
+// import main from './src/time';
+// import main from './src/galachain';
+// import main from './src/sdk';
+// import main from './src/vethx';
+// import main from './src/safe';
+// import main from './src/ipfs';
+// import main from './src/invoices';
+// import main from './src/demo';
+// import main from './src/preview';
+// import main from './src/metamask';
+
+db.connect(process.env.MONGODB_URI_PROD);
+
+main()
+    .then(() => process.exit(0))
+    .catch((error) => {
+        console.error(error);
+        process.exit(1);
+    });
diff --git a/apps/api/scripts/src/demo.ts b/apps/api/scripts/src/demo.ts
new file mode 100644
index 000000000..42a652de3
--- /dev/null
+++ b/apps/api/scripts/src/demo.ts
@@ -0,0 +1,267 @@
+import path from 'path';
+import db from '@thxnetwork/api/util/database';
+import { ChainId, QuestVariant, QuestSocialRequirement, AccessTokenKind } from '@thxnetwork/common/enums';
+import { Widget } from '@thxnetwork/api/models/Widget';
+import {
+    readCSV,
+    getYoutubeID,
+    getTwitterUsername,
+    createPool,
+    getSuggestion,
+    getTwitterTWeet,
+    getTwitterUser,
+} from './utils/index';
+import { RewardCoin } from '@thxnetwork/api/models/RewardCoin';
+import { Collaborator } from '@thxnetwork/api/models/Collaborator';
+import { Pool, QuestDaily, QuestInvite, QuestSocial, QuestCustom } from '@thxnetwork/api/models';
+
+const csvFilePath = path.join(__dirname, '../../../', 'quests.csv');
+// const sub = '60a38b36bf804b033c5e3faa'; // Local
+const sub = '6074cbdd1459355fae4b6a14'; // Peter
+const sub2 = '655d0c5dde9eca4f50999423'; // Prasanth
+const sub3 = '627d06fbb0d159d419292240'; // Mieszko
+const collaborators = [
+    {
+        sub,
+        email: 'peter@thx.network',
+    },
+    {
+        sub: sub2,
+        email: 'mieszko@thx.network',
+    },
+    {
+        sub: sub3,
+        email: 'prasanth@thx.network',
+    },
+];
+
+// const chainId = ChainId.Hardhat;
+const chainId = ChainId.Polygon;
+// const erc20Id = '64d3a4149f7e6d78c9366982'; // Local
+const erc20Id = '6464c665633c1cf385d8cc2b'; // THX Network (POS) on Prod
+
+export default async function main() {
+    const start = Date.now();
+    const skipped = [];
+    const results = [];
+    console.log('Start', new Date());
+
+    let tokens = 0;
+    try {
+        const data: any = await readCSV(csvFilePath);
+
+        for (const sql of data) {
+            const videoUrl = sql['Youtube Video URL'];
+            const tweetUrl = sql['Twitter Tweet URL'];
+            const serverId = sql['Discord Server ID'];
+            const missingMaterials = sql['Sales Material'] === 'Missing';
+            const gameName = sql['Game'];
+            const gameDomain = sql['Game Domain'];
+
+            if (!missingMaterials || !gameName || !gameDomain) continue;
+
+            let pool = await Pool.findOne({ 'settings.title': gameName });
+            console.log('===============');
+            console.log('Import: ', gameName, gameDomain);
+            if (pool) {
+                await Widget.updateOne({ poolId: pool._id }, { domain: new URL(gameDomain).origin });
+            } else {
+                pool = await createPool(sub, gameName, gameDomain);
+            }
+
+            await pool.updateOne({ chainId: ChainId.Polygon });
+
+            const poolId = pool._id;
+            await pool.updateOne({ chainId });
+
+            // Remove all existing data
+            await Promise.all([
+                QuestDaily.deleteMany({ poolId }),
+                QuestInvite.deleteMany({ poolId }),
+                QuestSocial.deleteMany({ poolId }),
+                QuestCustom.deleteMany({ poolId }),
+                RewardCoin.deleteMany({ poolId, erc20Id }),
+                Collaborator.deleteMany({ poolId }),
+            ]);
+
+            // Create social quest youtube like
+            if (videoUrl && videoUrl !== 'N/A') {
+                const videoId = getYoutubeID(videoUrl);
+                const socialQuestYoutubeLike = {
+                    poolId,
+                    index: 0,
+                    uuid: db.createUUID(),
+                    title: `Watch & Like`,
+                    description: '',
+                    amount: 75,
+                    kind: AccessTokenKind.Google,
+                    interaction: QuestSocialRequirement.YouTubeLike,
+                    content: videoId,
+                    contentMetadata: JSON.stringify({ videoId }),
+                    isPublished: true,
+                    variant: QuestVariant.YouTube,
+                };
+                await QuestSocial.create(socialQuestYoutubeLike);
+            }
+
+            // Create social quest twitter retweet
+            if (tweetUrl && tweetUrl !== 'N/A') {
+                const username = getTwitterUsername(tweetUrl);
+                const twitterUser = await getTwitterUser(username);
+                const socialQuestFollow = {
+                    poolId,
+                    index: 1,
+                    uuid: db.createUUID(),
+                    title: `Follow ${sql['Game']}`,
+                    description: '',
+                    amount: 75,
+                    kind: AccessTokenKind.Twitter,
+                    interaction: QuestSocialRequirement.TwitterFollow,
+                    content: twitterUser.id,
+                    variant: QuestVariant.Twitter,
+                    contentMetadata: JSON.stringify({
+                        id: twitterUser.id,
+                        name: twitterUser.name,
+                        username: twitterUser.username,
+                        profileImgUrl: twitterUser.profile_image_url,
+                    }),
+                    isPublished: true,
+                };
+                await QuestSocial.create(socialQuestFollow);
+
+                const tweetId = tweetUrl.match(/\/(\d+)(?:\?|$)/)[1];
+                const [tweet] = await getTwitterTWeet(tweetId);
+                const socialQuestRetweet = {
+                    poolId,
+                    uuid: db.createUUID(),
+                    variant: QuestVariant.Twitter,
+                    title: `Boost Tweet!`,
+                    description: '',
+                    amount: 50,
+                    kind: AccessTokenKind.Twitter,
+                    interaction: QuestSocialRequirement.TwitterLikeRetweet,
+                    content: tweetId,
+                    contentMetadata: JSON.stringify({
+                        url: tweetUrl,
+                        username: twitterUser.username,
+                        text: tweet.text,
+                    }),
+                    isPublished: true,
+                };
+                await QuestSocial.create(socialQuestRetweet);
+            }
+
+            // Create social quest twitter retweet
+            if (serverId && serverId !== 'N/A') {
+                const inviteURL = sql['Discord Invite URL'];
+                const socialQuestJoin = {
+                    poolId,
+                    variant: QuestVariant.Discord,
+                    uuid: db.createUUID(),
+                    title: `Join ${gameName} Discord`,
+                    description: 'Become a part of our fam!',
+                    amount: 75,
+                    kind: AccessTokenKind.Discord,
+                    interaction: QuestSocialRequirement.DiscordGuildJoined,
+                    content: serverId,
+                    contentMetadata: JSON.stringify({ serverId, inviteURL: inviteURL || undefined }),
+                    isPublished: true,
+                };
+                await QuestSocial.create(socialQuestJoin);
+            }
+
+            // Create default erc20 rewards
+            await RewardCoin.create({
+                poolId,
+                uuid: db.createUUID(),
+                title: `Small bag of $THX`,
+                description: 'A token of appreciation offered to you by THX Network. Could also be your own token!',
+                image: 'https://thx-storage-bucket.s3.eu-west-3.amazonaws.com/widget-referral-xmzfznsqschvqxzvgn47qo-xtencq4fmgjg7qgwewmybj-(1)-8EHr7ckbrEZLqUyxqJK1LG.png',
+                pointPrice: 1000,
+                limit: 1000,
+                amount: 10,
+                erc20Id,
+                isPublished: true,
+            });
+
+            // Create colloborators
+            for (const c of collaborators) {
+                await Collaborator.create({
+                    poolId,
+                    sub: c.sub,
+                    state: 1,
+                    uuid: db.createUUID(),
+                    email: c.email,
+                });
+            }
+
+            // Iterate over available quests and create
+            for (let i = 1; i < 3; i++) {
+                const questType = sql[`Q${i} - Type`];
+                const points = sql[`Q${i} - Points`];
+                const title = sql[`Q${i} - Title`];
+                if (!questType || !points || !title) {
+                    console.log(`Incomplete Q${i}!`);
+                    continue;
+                }
+
+                let titleSuggestion, descriptionSuggestion;
+                if (['Daily', 'Custom'].includes(questType)) {
+                    titleSuggestion = await getSuggestion(sql[`Q${i} - Title`], 40);
+                    // titleSuggestion = sql[`Q${i} - Title`];
+                    tokens += titleSuggestion.tokensUsed;
+                    descriptionSuggestion = await getSuggestion(sql[`Q${i} - Description`], 100);
+                    // descriptionSuggestion = sql[`Q${i} - Description`];
+                    tokens += descriptionSuggestion.tokensUsed;
+                }
+
+                switch (questType) {
+                    case 'Daily': {
+                        const dailyQuest = {
+                            poolId,
+                            variant: QuestVariant.Daily,
+                            title: titleSuggestion.content,
+                            description: descriptionSuggestion.content,
+                            amounts: [5, 10, 20, 40, 80, 160, 360],
+                            isPublished: true,
+                        };
+                        await QuestDaily.create(dailyQuest);
+                        console.log(sql[`Q${i} - Type`], titleSuggestion.content, 'quest created!');
+                        break;
+                    }
+                    case 'Custom': {
+                        const customQuest = {
+                            poolId,
+                            variant: QuestVariant.Custom,
+                            title: titleSuggestion.content,
+                            description: descriptionSuggestion.content,
+                            amount: Number(sql[`Q${i} - Points`]),
+                            limit: 0,
+                            isPublished: true,
+                        };
+                        await QuestCustom.create(customQuest);
+                        console.log(sql[`Q${i} - Type`], titleSuggestion.content, 'quest created!');
+                        break;
+                    }
+                    default: {
+                        console.log(sql[`Q${i} - Type`], 'quest skipped...');
+                    }
+                }
+            }
+            results.push([sql['Game'], `https://dashboard.thx.network/preview/${poolId}`]);
+        }
+    } catch (err) {
+        console.error(err);
+    }
+    console.log('===============');
+    console.log('COPY BELOW INTO SHEET');
+    console.log('===============');
+    results.forEach((item) => {
+        console.log(`${item[0]}\t${item[1]}`);
+    });
+    console.log('===============');
+    console.log('Skipped', skipped);
+    console.log('End', new Date());
+    console.log('Duration', Date.now() - start, 'seconds');
+    console.log('Tokens Spent', tokens);
+}
diff --git a/apps/api/scripts/src/galachain.ts b/apps/api/scripts/src/galachain.ts
new file mode 100644
index 000000000..a1b34faf8
--- /dev/null
+++ b/apps/api/scripts/src/galachain.ts
@@ -0,0 +1,83 @@
+import { ethers } from 'ethers';
+import { AllowanceType } from '@gala-chain/api';
+import { GalachainContract, getContract } from '../../src/app/util/galachain';
+import GalachainService from '../../src/app/services/GalachainService';
+import BigNumber from 'bignumber.js';
+
+const PRIVATE_KEY_ADMIN = '62172f65ecab45f423f7088128eee8946c5b3c03911cb0b061b1dd9032337271';
+const PRIVATE_KEY_DISTRIBUTOR = '096b2543a26e164e5f8887c737fe31d04734abe657416eacf0b5a52e6c5fa684';
+const PRIVATE_KEY_USER = '97093724e1748ebfa6aa2d2ec4ec68df8678423ab9a12eb2d27ddc74e35e5db9';
+
+export default async function main() {
+    const nftClassKey = {
+        collection: 'Weapons',
+        category: 'Blades',
+        type: 'none',
+        additionalKey: 'none',
+    };
+    // Define coin class key
+    const tokenInfo = {
+        decimals: 0,
+        tokenClass: nftClassKey,
+        name: 'Sting',
+        symbol: 'WBSting',
+        description: 'This collection holds weapons of any sort!',
+        image: 'https://pbs.twimg.com/profile_images/1640708099177877505/4U-ya--t_400x400.jpg',
+        isNonFungible: true,
+        maxSupply: new BigNumber(100),
+    };
+    const profileContract = getContract(GalachainContract.PublicKeyContract);
+    const tokenContract = getContract(GalachainContract.GalaChainToken);
+
+    // Generate random wallet
+    const walletAdmin = new ethers.Wallet(PRIVATE_KEY_ADMIN);
+    console.log({ walletAdmin });
+
+    const walletDistributor = new ethers.Wallet(PRIVATE_KEY_DISTRIBUTOR);
+    console.log({ walletDistributor });
+
+    const walletUser = new ethers.Wallet(PRIVATE_KEY_USER);
+    console.log({ walletUser });
+
+    // Register distributor
+    const distributor = await GalachainService.registerUser(
+        profileContract,
+        walletDistributor.publicKey,
+        PRIVATE_KEY_ADMIN,
+    );
+    console.log(distributor);
+
+    // Register user
+    const user = await GalachainService.registerUser(profileContract, walletUser.publicKey, PRIVATE_KEY_ADMIN);
+    console.log(user);
+
+    // Admin creates an NFT
+    const nft = await GalachainService.create(tokenContract, tokenInfo, nftClassKey, PRIVATE_KEY_ADMIN);
+    console.log(nft);
+
+    // Approve minting of maxsupply for admin
+    const result = await GalachainService.approve(
+        tokenContract,
+        nftClassKey,
+        walletDistributor.address,
+        100,
+        AllowanceType.Mint,
+        PRIVATE_KEY_ADMIN,
+    );
+    console.log(result);
+
+    // Distributor mints 5 tokens
+    await GalachainService.mint(tokenContract, nftClassKey, walletDistributor.address, 5, PRIVATE_KEY_DISTRIBUTOR);
+
+    // Get balance of tokens for distributor
+    const balances = (await GalachainService.balanceOf(tokenContract, nftClassKey, PRIVATE_KEY_DISTRIBUTOR)) as {
+        quantity: number;
+    }[];
+    console.log(balances);
+
+    // Balance of the user
+    const [balanceUser] = (await GalachainService.balanceOf(tokenContract, nftClassKey, PRIVATE_KEY_USER)) as {
+        quantity: number;
+    }[];
+    console.log(balanceUser);
+}
diff --git a/apps/api/scripts/src/invoices.ts b/apps/api/scripts/src/invoices.ts
new file mode 100644
index 000000000..ae4d752b7
--- /dev/null
+++ b/apps/api/scripts/src/invoices.ts
@@ -0,0 +1,16 @@
+import InvoiceService from '@thxnetwork/api/services/InvoiceService';
+import { startOfMonth, endOfMonth, addHours, subMonths } from 'date-fns';
+
+export default async function main() {
+    const currentDate = subMonths(new Date(), 0);
+    const invoicePeriodstartDate = startOfMonth(currentDate);
+    const invoicePeriodEndDate = endOfMonth(currentDate);
+
+    // Account for UTC + 2 timezone offset
+    const offset = 2;
+
+    await InvoiceService.upsertInvoices(
+        addHours(invoicePeriodstartDate, offset),
+        addHours(invoicePeriodEndDate, offset),
+    );
+}
diff --git a/apps/api/scripts/src/ipfs.ts b/apps/api/scripts/src/ipfs.ts
new file mode 100644
index 000000000..d7b3d08f6
--- /dev/null
+++ b/apps/api/scripts/src/ipfs.ts
@@ -0,0 +1,29 @@
+import { ERC721 } from '@thxnetwork/api/models/ERC721';
+import { ERC721Metadata } from '@thxnetwork/api/models/ERC721Metadata';
+import pinataSDK from '@pinata/sdk';
+import axios from 'axios';
+
+const pinata = new pinataSDK({ pinataJWTKey: process.env.PINATA_API_JWT });
+
+class PinataIPFS {
+    static async addURL(url: string) {
+        const response = await axios.get(url, { responseType: 'stream' });
+        const urlParts = url.split('/');
+        const name = urlParts[urlParts.length - 1];
+        const { IpfsHash } = await pinata.pinFileToIPFS(response.data, {
+            pinataMetadata: { name },
+            pinataOptions: { cidVersion: 0 },
+        });
+        return IpfsHash;
+    }
+}
+
+export default async function main() {
+    const nft = await ERC721.findById('64c8f15e01506efa24c1c72c');
+    const metadataList = await ERC721Metadata.find({ erc721Id: nft._id });
+    for (const metadata of metadataList) {
+        const imgCid = await PinataIPFS.addURL(metadata.imageUrl);
+        const metadataCid = await PinataIPFS.addURL('https://api.thx.network/v1/metadata/' + String(metadata._id));
+        console.log(metadata.name, String(metadata._id), imgCid, metadataCid);
+    }
+}
diff --git a/apps/api/scripts/src/metamask.ts b/apps/api/scripts/src/metamask.ts
new file mode 100644
index 000000000..7700240d4
--- /dev/null
+++ b/apps/api/scripts/src/metamask.ts
@@ -0,0 +1,136 @@
+import path from 'path';
+import fs from 'fs';
+import { MongoClient, Db } from 'mongodb';
+import { Wallet } from '@thxnetwork/api/models/Wallet';
+import { QuestSocialEntry } from '@thxnetwork/api/models/QuestSocialEntry';
+import {
+    ERC1155Token,
+    ERC20Token,
+    ERC721Token,
+    Participant,
+    Pool,
+    QuestCustomEntry,
+    QuestDailyEntry,
+    QuestGitcoinEntry,
+    QuestWeb3Entry,
+    RewardCoinPayment,
+    RewardCouponPayment,
+    RewardCustomPayment,
+    RewardDiscordRolePayment,
+    RewardNFTPayment,
+} from '@thxnetwork/api/models';
+
+export default async function main() {
+    const filePath = path.join(__dirname, '../../../metamask-accounts.csv');
+    const client = new MongoClient(process.env.MONGODB_URI_AUTH_PROD);
+
+    await client.connect();
+
+    const db: Db = client.db('auth-prod');
+    const accounts = await db.collection('accounts').find({ variant: 4 }).toArray();
+    const subs = accounts.map(({ _id }) => String(_id));
+    const wallets = await Wallet.find({
+        sub: { $in: subs },
+        address: { $exists: true },
+        $and: [{ version: { $exists: false } }, { safeVersion: { $exists: false } }],
+    });
+    const participants = await Participant.find({ sub: { $in: subs } });
+    const poolIds = participants.map((p) => p.poolId);
+    const pools = await Pool.find({ _id: { $in: poolIds } });
+
+    const csvData = await Promise.all(
+        accounts.map(async (account) => {
+            const sub = String(account._id);
+            const w = wallets.find((p) => p.sub === sub);
+            const p = participants.find((p) => p.sub === sub);
+            const pool = p && pools.find((pool) => String(pool._id) === p.poolId);
+
+            const [
+                dailyCount,
+                socialCount,
+                customCount,
+                web3Count,
+                gitcoinCount,
+                erc20Count,
+                erc721Count,
+                erc1155Count,
+                coinCount,
+                nftCount,
+                discordCount,
+                customrewardCount,
+                couponCount,
+            ] = await Promise.all(
+                [
+                    QuestDailyEntry,
+                    QuestSocialEntry,
+                    QuestCustomEntry,
+                    QuestWeb3Entry,
+                    QuestGitcoinEntry,
+                    // Coins
+                    ERC20Token,
+                    // NFT
+                    ERC721Token,
+                    ERC1155Token,
+                    // RewardPayments
+                    RewardCoinPayment,
+                    RewardNFTPayment,
+                    RewardDiscordRolePayment,
+                    RewardCustomPayment,
+                    RewardCouponPayment,
+                ].map((Model) => Model.countDocuments({ sub })),
+            );
+
+            return [
+                sub,
+                pool && pool.settings && pool.settings.title,
+                p && p.balance,
+                p && p.score,
+                p && p.questEntryCount,
+                p && p.updatedAt,
+                w && w.address,
+                dailyCount,
+                socialCount,
+                customCount,
+                web3Count,
+                gitcoinCount,
+                erc20Count,
+                erc721Count,
+                erc1155Count,
+                coinCount,
+                nftCount,
+                discordCount,
+                customrewardCount,
+                couponCount,
+            ].join(',');
+        }),
+    );
+
+    csvData.push(
+        [
+            'sub',
+            'campaign',
+            'balance',
+            'score',
+            'questEntryCount',
+            'updatedAt',
+            'address',
+            'dailyCount',
+            'socialCount',
+            'customCount',
+            'web3Count',
+            'gitcoinCoun',
+            'erc20Count',
+            'erc721Count',
+            'erc1155Count',
+            'coinCount',
+            'nftCount',
+            'discordCount',
+            'customrewardCount',
+            'couponCount',
+        ].join(','),
+    );
+
+    fs.writeFileSync(filePath, csvData.reverse().join('\n'), 'utf8');
+}
+// This query finds all wallets that have an address and do not have a version nor safeVersion field. Indicating they are metamask wallets
+// { $and: [{ version: { $exists: false} }, { safeVersion: { $exists: false } }], sub: { $exists: true }, poolId: { $exists: true } }
diff --git a/apps/api/scripts/src/preview.ts b/apps/api/scripts/src/preview.ts
new file mode 100644
index 000000000..5b15277d8
--- /dev/null
+++ b/apps/api/scripts/src/preview.ts
@@ -0,0 +1,177 @@
+import puppeteer from 'puppeteer';
+import fs from 'fs';
+import db from '@thxnetwork/api/util/database';
+import { Brand, Widget } from '@thxnetwork/api/models';
+import { createCanvas, loadImage, registerFont } from 'canvas';
+import { assetsPath } from '@thxnetwork/api/util/path';
+import path from 'path';
+import CanvasService from '@thxnetwork/api/services/CanvasService';
+
+// Provide before running
+const poolIds = ['660f101c4a0130f6f8315762', '660f10e4e298a7a04bbb35ae'];
+
+// Load on boot as registration on runtime results in font not being loaded in time
+const fontPath = path.resolve(assetsPath, 'fa-solid-900.ttf');
+const family = 'Font Awesome 5 Pro Solid';
+const defaultBackgroundImgPath = path.resolve(assetsPath, 'bg.png');
+const defaultLogoImgPath = path.resolve(assetsPath, 'logo.png');
+
+// ENV
+// const widgetBaseUrl = 'https://dev-app.thx.network';
+const widgetBaseUrl = 'https://app.thx.network';
+
+registerFont(fontPath, { family, style: 'normal', weight: '900' });
+
+// db.connect(process.env.MONGODB_URI);
+// db.connect(process.env.MONGODB_URI_DEV);
+db.connect(process.env.MONGODB_URI_PROD);
+
+async function createCampaignWidgetPreviewImage({ poolId, logoImgUrl, backgroundImgUrl }: TBrand) {
+    const widget = await Widget.findOne({ poolId });
+    if (!widget) return;
+    const theme = JSON.parse(widget.theme);
+
+    const rightOffset = 20;
+    const bottomOffset = 90;
+    const widgetHeight = 700;
+    const widgetWidth = 400;
+
+    // Get screenshot image
+    const widgetUrl = `${widgetBaseUrl}/c/${poolId}/quests`;
+    const fileName = `${poolId}.jpg`;
+
+    // Can not use asset path here on runtime
+    const outputPath = path.resolve(__dirname, fileName);
+    await captureScreenshot(widgetUrl, outputPath, widgetWidth, widgetHeight);
+
+    // Read screenshot from disk
+    const file = fs.readFileSync(outputPath);
+    if (!file) throw new Error('Screenshot failed');
+
+    // Load the base64 image data into an Image object
+    const bg = await loadImage(backgroundImgUrl || defaultBackgroundImgPath);
+    const logo = await loadImage(logoImgUrl || defaultLogoImgPath);
+    const screenshot = await loadImage(file);
+
+    // Create a canvas with the desired dimensions
+    const canvasHeight = widgetHeight + bottomOffset + rightOffset; // 810
+    const canvasWidth = Math.floor((canvasHeight / 9) * 16); // 1440
+    const canvas = createCanvas(canvasWidth, canvasHeight);
+    const ctx = canvas.getContext('2d');
+
+    // Draw the loaded image onto the canvas
+    CanvasService.drawImageBg(canvas, ctx, bg);
+
+    // Draw the logo
+    const logoRatio = logo.width / logo.height;
+    const logoWidth = 200;
+    const logoHeight = logoWidth / logoRatio;
+
+    ctx.drawImage(logo, canvasWidth / 2 - logoWidth / 2, canvasHeight / 2 - logoHeight / 2, logoWidth, logoHeight);
+
+    const launcherRadius = 30;
+    const launcherCenterOffset = 50;
+
+    // Draw the launcher circle
+    ctx.beginPath();
+    ctx.arc(canvasWidth - launcherCenterOffset, canvasHeight - launcherCenterOffset, launcherRadius, 0, 2 * Math.PI);
+    ctx.fillStyle = theme.elements.launcherBg.color;
+    ctx.fill();
+
+    const notificationRadius = 10;
+    const notificationX = canvasWidth - launcherCenterOffset - launcherRadius / 2 - notificationRadius / 2;
+    const notificationY = canvasHeight - launcherCenterOffset - launcherRadius / 2 - notificationRadius / 2;
+
+    // Draw the launcher icon
+    const fontSizeIcon = 20;
+    ctx.font = `900 ${fontSizeIcon}px "${family}"`;
+    ctx.fillStyle = theme.elements.launcherIcon.color; //;
+    ctx.fillText(
+        `\uf06b`,
+        canvasWidth - launcherCenterOffset - fontSizeIcon / 2,
+        canvasHeight - launcherCenterOffset + fontSizeIcon / 3,
+    );
+
+    // Draw the notification circle
+    ctx.beginPath();
+    ctx.arc(notificationX, notificationY, notificationRadius, 0, 2 * Math.PI);
+    ctx.fillStyle = 'red';
+    ctx.fill();
+
+    // Draw the notificition counter
+    const fontSizeNotification = 16;
+    ctx.font = `bold normal ${fontSizeNotification}px "Arial"`;
+    ctx.fillStyle = 'white';
+    ctx.fillText('3', notificationX - notificationRadius / 2, notificationY + notificationRadius / 2);
+
+    // Draw the widget screenshot
+    const borderRadius = 10;
+    const widgetX = canvasWidth - widgetWidth - rightOffset;
+    const widgetY = canvasHeight - widgetHeight - bottomOffset;
+
+    // Round the borders by clipping
+    drawImageRounded(ctx, widgetX, widgetY, widgetWidth, widgetHeight, borderRadius);
+    ctx.clip();
+    ctx.drawImage(screenshot, widgetX, widgetY, widgetWidth, widgetHeight);
+
+    // Convert the canvas content to a buffer
+    // const dataUrl = canvas.toDataURL('image/png');
+    const buffer = canvas.toBuffer('image/png');
+
+    return buffer;
+}
+
+export async function captureScreenshot(url, outputFileName, width, height) {
+    const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));
+
+    const browser = await puppeteer.launch({ headless: 'new' });
+    const page = await browser.newPage();
+
+    await page.setViewport({ width, height });
+    await page.goto(url);
+
+    // Collapse CSS animation needs to finish
+    await delay(3000);
+
+    await page.screenshot({ path: outputFileName });
+
+    await browser.close();
+}
+
+function drawImageRounded(ctx, x, y, width, height, radius) {
+    ctx.beginPath();
+    ctx.moveTo(x + radius, y);
+    ctx.lineTo(x + width - radius, y);
+    ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
+    ctx.lineTo(x + width, y + height - radius);
+    ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
+    ctx.lineTo(x + radius, y + height);
+    ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
+    ctx.lineTo(x, y + radius);
+    ctx.quadraticCurveTo(x, y, x + radius, y);
+    ctx.closePath();
+}
+
+export default async function main() {
+    const start = Date.now();
+
+    console.log('Start', new Date());
+    const brands = await Brand.find({ poolId: { $in: poolIds } });
+    for (const index in brands) {
+        try {
+            const brand = brands[index];
+            const previewFile = await createCampaignWidgetPreviewImage(brand);
+            if (!previewFile) continue;
+            // Write the image buffer data to the file
+            const output = path.join('/Users/peterpolman/Desktop/previews', `${brand.poolId}.png`);
+            fs.writeFileSync(output, previewFile);
+
+            console.log(`${Number(index) + 1}/${brands.length} ${brand.poolId}`);
+        } catch (error) {
+            console.error(brands[index].poolId, error);
+        }
+    }
+
+    console.log('End', new Date());
+    console.log('Duration', Date.now() - start, 'seconds');
+}
diff --git a/apps/api/scripts/src/safe.ts b/apps/api/scripts/src/safe.ts
new file mode 100644
index 000000000..883ead62c
--- /dev/null
+++ b/apps/api/scripts/src/safe.ts
@@ -0,0 +1,31 @@
+import { safeVersion } from '@thxnetwork/api/services/ContractService';
+import { toChecksumAddress } from 'web3-utils';
+import { Wallet } from '@thxnetwork/api/models/Wallet';
+import { ChainId } from '@thxnetwork/common/enums';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { SafeFactory } from '@safe-global/protocol-kit';
+
+export default async function main() {
+    const SAFE = toChecksumAddress(''); // Provide values
+    const RELAYER = toChecksumAddress(''); // Provide values
+    const ACCOUNT = toChecksumAddress(''); // Provide values
+    const wallet = await Wallet.findOne({
+        address: SAFE,
+        chainId: ChainId.Polygon,
+    });
+    if (SAFE !== wallet.address) throw new Error('Provided address does not equal Safe address.');
+
+    const { ethAdapter } = getProvider(ChainId.Polygon);
+    const safeFactory = await SafeFactory.create({
+        safeVersion,
+        ethAdapter,
+    });
+    const safeAccountConfig = {
+        owners: [RELAYER, ACCOUNT],
+        threshold: 2,
+    };
+    const safeAddress = await safeFactory.predictSafeAddress(safeAccountConfig);
+    console.log(safeAddress);
+
+    await safeFactory.deploySafe({ safeAccountConfig, options: { gasLimit: '3000000' } });
+}
diff --git a/apps/api/scripts/src/sdk.ts b/apps/api/scripts/src/sdk.ts
new file mode 100644
index 000000000..e3b278c01
--- /dev/null
+++ b/apps/api/scripts/src/sdk.ts
@@ -0,0 +1,12 @@
+import { THXAPIClient } from '@thxnetwork/sdk/clients';
+import { API_URL, AUTH_URL } from '@thxnetwork/api/config/secrets';
+
+export default async function main() {
+    const thx = new THXAPIClient({
+        authUrl: AUTH_URL,
+        apiUrl: API_URL,
+        clientId: 'BitG_fGJI5k70kQgEeyID',
+        clientSecret: 'pniCrGc49hb_l18_MrpahhJC8SexAV1nHE9RR9CkZA2qA_YbRmJd1hSHl5fcpJA1ngmRwuoys47JfLtYJDlSgA',
+    });
+    await thx.events.create({ event: 'test', identity: '4de81b20-c71d-11ee-ac82-a970a9e4ebc4' });
+}
diff --git a/apps/api/scripts/src/time.ts b/apps/api/scripts/src/time.ts
new file mode 100644
index 000000000..0c2f65ad5
--- /dev/null
+++ b/apps/api/scripts/src/time.ts
@@ -0,0 +1,14 @@
+import { ethers } from 'ethers';
+
+export async function increaseBlockTime(provider, seconds) {
+    await provider.send('evm_increaseTime', [seconds]);
+    await provider.send('evm_mine', []);
+}
+
+export default async function main() {
+    const HARDHAT_RPC = 'http://127.0.0.1:8545/';
+    const hardhatProvider = new ethers.providers.JsonRpcProvider(HARDHAT_RPC);
+
+    // Travel past first week else this throws "Reward distribution has not started yet"
+    await increaseBlockTime(hardhatProvider, 60 * 60 * 24 * 7);
+}
diff --git a/apps/api/scripts/src/utils/index.ts b/apps/api/scripts/src/utils/index.ts
new file mode 100644
index 000000000..4e3d9cffb
--- /dev/null
+++ b/apps/api/scripts/src/utils/index.ts
@@ -0,0 +1,98 @@
+import fs from 'fs';
+import OpenAI from 'openai';
+import csvParser from 'csv-parser';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import { DEFAULT_COLORS, DEFAULT_ELEMENTS } from '@thxnetwork/common/constants';
+import { Widget } from '@thxnetwork/api/models/Widget';
+import { v4 } from 'uuid';
+import { twitterClient } from '@thxnetwork/api/util/twitter';
+
+async function readCSV(csvFilePath: string) {
+    const data: any = [];
+
+    return new Promise((resolve, reject) => {
+        fs.createReadStream(csvFilePath)
+            .pipe(csvParser())
+            .on('data', (row) => {
+                data.push(row);
+            })
+            .on('end', () => {
+                console.log('CSV data read successfully:');
+                resolve(data);
+            })
+            .on('error', (err) => {
+                reject(new Error('Error while reading CSV: ' + err.message));
+            });
+    });
+}
+
+function getYoutubeID(url) {
+    if (url && url.toLowerCase().includes('shorts')) return;
+
+    const result = /^https?:\/\/(www\.)?youtu\.be/.test(url)
+        ? url.replace(/^https?:\/\/(www\.)?youtu\.be\/([\w-]{11}).*/, '$2')
+        : url.replace(/.*\?v=([\w-]{11}).*/, '$1');
+
+    return result;
+}
+
+function getTwitterUsername(url: string) {
+    return url.split('/')[3];
+}
+
+async function createPool(sub: string, title: string, gameUrl: string) {
+    const pool = await PoolService.deploy(sub, title);
+
+    await Widget.create({
+        uuid: v4(),
+        poolId: pool._id,
+        align: 'right',
+        message: 'Hi there!👋 Click me to earn rewards with quests...',
+        domain: new URL(gameUrl).origin,
+        theme: JSON.stringify({ elements: DEFAULT_ELEMENTS, colors: DEFAULT_COLORS }),
+    });
+
+    return pool;
+}
+
+const openai = new OpenAI({
+    apiKey: process.env.OPENAI_API_KEY,
+});
+
+async function getSuggestion(content: string, length: number) {
+    if (!content) return;
+
+    const prompt = `You are a content writer for games. Please rephrase this text in a short, engaging and active form. Apply a maximum character length of ${length} characters:`;
+    const data = await openai.chat.completions.create({
+        model: 'gpt-3.5-turbo',
+        messages: [{ role: 'user', content: prompt + content }],
+    });
+
+    return {
+        content: data.choices[0].message.content,
+        tokensUsed: Number(data.usage.total_tokens),
+    };
+}
+async function getTwitterTWeet(tweetId: string) {
+    const { data } = await twitterClient({
+        method: 'GET',
+        url: `/tweets`,
+        params: {
+            ids: tweetId,
+            expansions: 'author_id',
+        },
+    });
+    return data.data;
+}
+async function getTwitterUser(username: string) {
+    const { data } = await twitterClient({
+        method: 'GET',
+        url: `/users/by/username/${username}`,
+        params: {
+            'user.fields': 'profile_image_url',
+        },
+    });
+    return data.data;
+}
+
+export { readCSV, getYoutubeID, getTwitterUsername, createPool, getSuggestion, getTwitterTWeet, getTwitterUser };
diff --git a/apps/api/scripts/src/veLiquidity.ts b/apps/api/scripts/src/veLiquidity.ts
new file mode 100644
index 000000000..33020e1d5
--- /dev/null
+++ b/apps/api/scripts/src/veLiquidity.ts
@@ -0,0 +1,47 @@
+import { ethers } from 'ethers';
+import { parseUnits } from 'ethers/lib/utils';
+import { PRIVATE_KEY } from '@thxnetwork/api/config/secrets';
+import { getArtifact, contractNetworks } from '@thxnetwork/api/hardhat';
+import { ChainId } from '@thxnetwork/common/enums';
+
+async function increaseBlockTime(provider, seconds) {
+    await provider.send('evm_increaseTime', [seconds]);
+    await provider.send('evm_mine', []);
+}
+
+export default async function main() {
+    const HARDHAT_RPC = 'http://127.0.0.1:8545/';
+    const hardhatProvider = new ethers.providers.JsonRpcProvider(HARDHAT_RPC);
+    // const TO = '0x029E2d4D2b6938c92c48dbf422a4e500425a08D8';
+    const TO = '0xaf9d56684466fcFcEA0a2B7fC137AB864d642946';
+    // const TO = '0x7b8fc09eb5D80eadA6AE74b112463eA006DC25B5';
+    const AMOUNT_USDC = parseUnits('12300', 6).toString();
+    const AMOUNT_THX = parseUnits('45600', 18).toString();
+    const chainId = ChainId.Hardhat;
+    const signer = new ethers.Wallet(PRIVATE_KEY, hardhatProvider) as unknown as ethers.Signer;
+
+    const usdc = new ethers.Contract(contractNetworks[chainId].USDC, getArtifact('USDC').abi, signer);
+    await usdc.transfer(TO, AMOUNT_USDC);
+
+    const thx = new ethers.Contract(contractNetworks[chainId].THX, getArtifact('THX').abi, signer);
+    await thx.transfer(TO, AMOUNT_THX);
+
+    // Increase time till past veTHX reward distribution start time
+    await increaseBlockTime(hardhatProvider, 60 * 60 * 24 * 7);
+
+    // const usdcPerMonthInWei = ethers.utils.parseUnits('146700', 6);
+    // const secondsPerMonth = 30 * 24 * 60 * 60;
+    // const usdcPerSecond = usdcPerMonthInWei.div(secondsPerMonth);
+    // const splitter = new ethers.Contract(
+    //     contractNetworks[chainId].THXPaymentSplitter,
+    //     contractArtifacts['THXPaymentSplitter'].abi,
+    //     signer,
+    // );
+    // await splitter.setRate(TO, usdcPerSecond);
+    // const rate = await splitter.rates(TO);
+    // console.log(usdcPerSecond.toString(), rate.toString());
+
+    // await usdc.approve(splitter.address, usdcPerMonthInWei);
+    // await splitter.deposit(TO, usdcPerMonthInWei);
+    // console.log((await splitter.balanceOf(TO)).toString());
+}
diff --git a/apps/api/scripts/src/veRewards.ts b/apps/api/scripts/src/veRewards.ts
new file mode 100644
index 000000000..76a796ad2
--- /dev/null
+++ b/apps/api/scripts/src/veRewards.ts
@@ -0,0 +1,48 @@
+import { PRIVATE_KEY } from '@thxnetwork/api/config/secrets';
+import { contractNetworks, getArtifact } from '@thxnetwork/api/hardhat';
+import { ChainId } from '@thxnetwork/common/enums';
+import { ethers } from 'ethers';
+import { parseUnits } from 'ethers/lib/utils';
+import { increaseBlockTime } from './time';
+
+export default async function main() {
+    const HARDHAT_RPC = 'http://127.0.0.1:8545/';
+    const hardhatProvider = new ethers.providers.JsonRpcProvider(HARDHAT_RPC);
+    const chainId = ChainId.Hardhat;
+    const signer = new ethers.Wallet(PRIVATE_KEY, hardhatProvider) as unknown as ethers.Signer;
+    const bpt = new ethers.Contract(contractNetworks[chainId].BPT, getArtifact('BPT').abi, signer);
+    const bal = new ethers.Contract(contractNetworks[chainId].BAL, getArtifact('BAL').abi, signer);
+    const rewardFaucet = new ethers.Contract(
+        contractNetworks[chainId].RewardFaucet,
+        getArtifact('RewardFaucet').abi,
+        signer,
+    );
+    const AMOUNTBAL = parseUnits('100').toString();
+    const AMOUNTBPT = parseUnits('1000').toString();
+
+    // Travel past reward distribution start time
+    await increaseBlockTime(hardhatProvider, 60 * 60 * 24 * 7);
+
+    // Deposit reward tokens into rdthx
+    await bal.approve(rewardFaucet.address, AMOUNTBAL);
+    await bpt.approve(rewardFaucet.address, AMOUNTBPT);
+
+    // // Claim all pending BAL rewards and prepare for distriubtion
+    // // await ve.claimExternalRewards();
+    // // Mock externalRewards by transfering directly into rd
+    // await bal.transfer(rd.address, AMOUNT);
+
+    // Make sure to redistribute past rewards before depositing
+    // await rf.distributePastRewards(bpt.address);
+    // await rf.distributePastRewards(bal.address);
+
+    // Spread the amount evenly over 4 weeks from the current block
+    // Can only run after reward distribution has started
+    const tx1 = await rewardFaucet.depositEqualWeeksPeriod(bal.address, AMOUNTBAL, '4');
+    console.log(await tx1.wait());
+    const tx2 = await rewardFaucet.depositEqualWeeksPeriod(bpt.address, AMOUNTBPT, '4');
+    console.log(await tx2.wait());
+
+    console.log(await rewardFaucet.getUpcomingRewardsForNWeeks(bal.address, '4'));
+    console.log(await rewardFaucet.getUpcomingRewardsForNWeeks(bpt.address, '4'));
+}
diff --git a/apps/api/scripts/src/veTransfer.ts b/apps/api/scripts/src/veTransfer.ts
new file mode 100644
index 000000000..4ea35161a
--- /dev/null
+++ b/apps/api/scripts/src/veTransfer.ts
@@ -0,0 +1,19 @@
+import { ethers } from 'ethers';
+import { PRIVATE_KEY } from '@thxnetwork/api/config/secrets';
+import { getArtifact, contractNetworks } from '@thxnetwork/api/hardhat';
+import { ChainId } from '@thxnetwork/common/enums';
+import { parseUnits } from 'ethers/lib/utils';
+
+export default async function main() {
+    const HARDHAT_RPC = 'http://127.0.0.1:8545/';
+    const hardhatProvider = new ethers.providers.JsonRpcProvider(HARDHAT_RPC);
+    // const TO = '0x9013ae40FCd95D46BA4902F3974A71C40793680B';
+    const TO = '0xaf9d56684466fcFcEA0a2B7fC137AB864d642946';
+
+    const AMOUNT = parseUnits('10000').toString();
+    const chainId = ChainId.Hardhat;
+    const signer = new ethers.Wallet(PRIVATE_KEY, hardhatProvider) as unknown as ethers.Signer;
+    const bpt = new ethers.Contract(contractNetworks[chainId].BPT, getArtifact('BPT').abi, signer);
+
+    await bpt.transfer(TO, AMOUNT);
+}
diff --git a/apps/api/scripts/upgradeContractsToLatest.ts b/apps/api/scripts/upgradeContractsToLatest.ts
new file mode 100644
index 000000000..ecc0bdb7d
--- /dev/null
+++ b/apps/api/scripts/upgradeContractsToLatest.ts
@@ -0,0 +1,20 @@
+import db from '../src/app/util/database';
+import { MONGODB_URI } from '../src/app/config/secrets';
+
+db.connect(MONGODB_URI);
+
+async function main() {
+    const startTime = Date.now();
+    // Add prerelease jobs here
+    // ...
+
+    const endTime = Date.now();
+    console.log(`🔔 Duration: ${Math.floor((endTime - startTime) / 1000)} seconds`);
+}
+
+main()
+    .then(() => process.exit(0))
+    .catch((error) => {
+        console.error(error);
+        process.exit(1);
+    });
diff --git a/apps/api/sonar-project.properties b/apps/api/sonar-project.properties
new file mode 100644
index 000000000..ffea24075
--- /dev/null
+++ b/apps/api/sonar-project.properties
@@ -0,0 +1,6 @@
+sonar.sources=.
+sonar.tests="src"
+sonar.test.inclusions="src/**/*.test.ts"
+sonar.projectKey=thxnetwork_api
+sonar.organization=thxnetwork
+sonar.javascript.lcov.reportPaths=../../coverage/apps/api/lcov.info
diff --git a/apps/api/src/app/config/migrate-mongo-create-only.json b/apps/api/src/app/config/migrate-mongo-create-only.json
new file mode 100644
index 000000000..f6776da66
--- /dev/null
+++ b/apps/api/src/app/config/migrate-mongo-create-only.json
@@ -0,0 +1,4 @@
+{
+    "migrationsDir": "src/app/migrations",
+    "moduleSystem": "commonjs"
+}
diff --git a/apps/api/src/app/config/migrate-mongo.ts b/apps/api/src/app/config/migrate-mongo.ts
new file mode 100644
index 000000000..d98ee850c
--- /dev/null
+++ b/apps/api/src/app/config/migrate-mongo.ts
@@ -0,0 +1,13 @@
+import path from 'path';
+import { MONGODB_URI } from '@thxnetwork/api/config/secrets';
+
+export default {
+    migrationFileExtension: '.js',
+    mongodb: {
+        url: MONGODB_URI,
+    },
+    migrationsDir: path.join(path.resolve(__dirname), 'app/migrations'),
+    changelogCollectionName: 'changelog',
+    useFileHash: false,
+    moduleSystem: 'commonjs',
+};
diff --git a/apps/api/src/app/config/secrets.ts b/apps/api/src/app/config/secrets.ts
new file mode 100644
index 000000000..cfd49366f
--- /dev/null
+++ b/apps/api/src/app/config/secrets.ts
@@ -0,0 +1,121 @@
+import { Speed } from '@openzeppelin/defender-relay-client';
+import path from 'path';
+
+const required = [
+    'AUTH_URL',
+    'API_URL',
+    'DASHBOARD_URL',
+    'MONGODB_URI',
+    'PORT',
+    'AUTH_CLIENT_ID',
+    'AUTH_CLIENT_SECRET',
+    'INITIAL_ACCESS_TOKEN',
+    'AWS_ACCESS_KEY_ID',
+    'AWS_SECRET_ACCESS_KEY',
+    'AWS_S3_PUBLIC_BUCKET_NAME',
+    'AWS_S3_PUBLIC_BUCKET_REGION',
+    'AWS_S3_PRIVATE_BUCKET_NAME',
+    'AWS_S3_PRIVATE_BUCKET_REGION',
+    'SAFE_TXS_SERVICE',
+    'CWD',
+];
+
+if (process.env.NODE_ENV === 'production') {
+    required.push(
+        ...[
+            'GCLOUD_RECAPTCHA_API_KEY',
+            'POLYGON_RPC',
+            'POLYGON_NAME',
+            'POLYGON_RELAYER',
+            'POLYGON_RELAYER_API_KEY',
+            'POLYGON_RELAYER_API_SECRET',
+            'INFURA_IPFS_PROJECT_ID',
+            'INFURA_IPFS_PROJECT_SECRET',
+            'RELAYER_SPEED',
+            'TWITTER_API_TOKEN',
+        ],
+    );
+} else if (process.env.NODE_ENV === 'development') {
+    required.push(...['PRIVATE_KEY', 'HARDHAT_RPC', 'LOCAL_CERT', 'LOCAL_CERT_KEY', 'TWITTER_API_TOKEN']);
+}
+
+required.forEach((value: string) => {
+    if (!process.env[value]) {
+        console.log(`Set ${value} environment variable.`);
+        process.exit(1);
+    }
+});
+
+// This allows you to use a single .env file with both regular and test configuration. This allows for an
+// easy to use setup locally without having hardcoded credentials during test runs.
+if (process.env.NODE_ENV === 'test') {
+    if (process.env.MONGODB_URI_TEST_OVERRIDE !== undefined)
+        process.env.MONGODB_URI = process.env.MONGODB_URI_TEST_OVERRIDE;
+    if (process.env.HARDHAT_RPC_TEST_OVERRIDE) process.env.HARDHAT_RPC = process.env.HARDHAT_RPC_TEST_OVERRIDE;
+}
+
+export const VERSION = 'v1';
+export const CWD = process.env.CWD || path.resolve(__dirname, '../../../apps/api/src');
+export const NODE_ENV = process.env.NODE_ENV || 'development';
+export const AUTH_URL = process.env.AUTH_URL || '';
+export const API_URL = process.env.API_URL || '';
+export const WALLET_URL = process.env.WALLET_URL || '';
+export const DASHBOARD_URL = process.env.DASHBOARD_URL || '';
+export const WIDGET_URL = process.env.WIDGET_URL || '';
+export const PUBLIC_URL = process.env.PUBLIC_URL || '';
+export const HARDHAT_RPC = process.env.HARDHAT_RPC || '';
+export const POLYGON_RPC = process.env.POLYGON_RPC || 'https://rpc.ankr.com/polygon';
+export const ETHEREUM_RPC = process.env.ETHEREUM_RPC || 'https://rpc.ankr.com/eth';
+export const MONGODB_URI = String(process.env.MONGODB_URI) || '';
+export const PRIVATE_KEY = process.env.PRIVATE_KEY || '';
+export const PORT = process.env.PORT || '';
+export const AUTH_CLIENT_ID = process.env.AUTH_CLIENT_ID || '';
+export const AUTH_CLIENT_SECRET = process.env.AUTH_CLIENT_SECRET || '';
+export const RATE_LIMIT_REWARD_GIVE = Number(process.env.RATE_LIMIT_REWARD_GIVE) || '';
+export const RATE_LIMIT_REWARD_CLAIM = Number(process.env.RATE_LIMIT_REWARD_CLAIM) || '';
+export const RATE_LIMIT_REWARD_GIVE_WINDOW = Number(process.env.RATE_LIMIT_REWARD_GIVE_WINDOW) || '';
+export const RATE_LIMIT_REWARD_CLAIM_WINDOW = Number(process.env.RATE_LIMIT_REWARD_CLAIM_WINDOW) || '';
+export const INITIAL_ACCESS_TOKEN = process.env.INITIAL_ACCESS_TOKEN || '';
+export const CIRCULATING_SUPPLY = process.env.CIRCULATING_SUPPLY || '';
+export const INFURA_PROJECT_ID = process.env.INFURA_PROJECT_ID || '';
+export const INFURA_IPFS_PROJECT_ID = process.env.INFURA_IPFS_PROJECT_ID || '';
+export const INFURA_IPFS_PROJECT_SECRET = process.env.INFURA_IPFS_PROJECT_SECRET || '';
+export const MINIMUM_GAS_LIMIT = 54680 || '';
+export const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID || '';
+export const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY || '';
+export const AWS_S3_PUBLIC_BUCKET_NAME = process.env.AWS_S3_PUBLIC_BUCKET_NAME || '';
+export const AWS_S3_PUBLIC_BUCKET_REGION = process.env.AWS_S3_PUBLIC_BUCKET_REGION || '';
+export const AWS_S3_PRIVATE_BUCKET_NAME = process.env.AWS_S3_PRIVATE_BUCKET_NAME || '';
+export const AWS_S3_PRIVATE_BUCKET_REGION = process.env.AWS_S3_PRIVATE_BUCKET_REGION || '';
+export const POLYGON_RELAYER = process.env.POLYGON_RELAYER || '';
+export const POLYGON_RELAYER_API_KEY = process.env.POLYGON_RELAYER_API_KEY || '';
+export const POLYGON_RELAYER_API_SECRET = process.env.POLYGON_RELAYER_API_SECRET || '';
+export const LOCAL_CERT = process.env.LOCAL_CERT || '';
+export const LOCAL_CERT_KEY = process.env.LOCAL_CERT_KEY || '';
+export const ADDRESS_ZERO = '0x0000000000000000000000000000000000000000';
+export const RELAYER_SPEED = (process.env.RELAYER_SPEED || 'fastest') as Speed;
+export const MIXPANEL_TOKEN = process.env.MIXPANEL_TOKEN || '';
+export const MIXPANEL_API_URL = 'https://api.mixpanel.com';
+export const CYPRESS_EMAIL = process.env.CYPRESS_EMAIL || 'cypress@thx.network';
+export const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY || '';
+export const STRIPE_SECRET_WEBHOOK = process.env.STRIPE_SECRET_WEBHOOK || '';
+export const ALCHEMY_API_KEY = process.env.ALCHEMY_API_KEY || '';
+export const TWITTER_API_TOKEN = process.env.TWITTER_API_TOKEN || '';
+export const IPFS_BASE_URL = 'https://ipfs.io/ipfs/';
+export const WEBHOOK_REFERRAL = process.env.WEBHOOK_REFERRAL || '';
+export const WEBHOOK_MILESTONE = process.env.WEBHOOK_MILESTONE || '';
+export const SAFE_TXS_SERVICE = process.env.SAFE_TXS_SERVICE || 'https://safe-transaction-polygon.safe.global';
+export const BOT_TOKEN = process.env.BOT_TOKEN || '';
+export const DISCORD_CLIENT_ID = process.env.DISCORD_CLIENT_ID || '';
+export const GITCOIN_API_KEY = process.env.GITCOIN_API_KEY || '';
+export const BALANCER_POOL_ID = '0xb204bf10bc3a5435017d3db247f56da601dfe08a0002000000000000000000fe';
+export const PINATA_API_JWT = process.env.PINATA_API_JWT || '';
+export const ALLOWED_API_CLIENT_ID = process.env.ALLOWED_API_CLIENT_ID || '';
+export const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID || '';
+export const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET || '';
+export const GCLOUD_PROJECT_ID = process.env.GCLOUD_PROJECT_ID || '';
+export const GCLOUD_RECAPTCHA_API_KEY = process.env.GCLOUD_RECAPTCHA_API_KEY || '';
+export const GCLOUD_RECAPTCHA_SITE_KEY = process.env.GCLOUD_RECAPTCHA_SITE_KEY || '';
+export const THX_CLIENT_ID = process.env.THX_CLIENT_ID || '';
+export const THX_CLIENT_SECRET = process.env.THX_CLIENT_SECRET || '';
+export const WEBHOOK_SIGNING_SECRET = process.env.WEBHOOK_SIGNING_SECRET || '';
diff --git a/apps/api/src/app/connection-profiles/cpp-curator.json b/apps/api/src/app/connection-profiles/cpp-curator.json
new file mode 100644
index 000000000..36719936d
--- /dev/null
+++ b/apps/api/src/app/connection-profiles/cpp-curator.json
@@ -0,0 +1,37 @@
+{
+    "name": "test-network-CuratorOrg",
+    "version": "1.0.0",
+    "client": {
+        "organization": "CuratorOrg"
+    },
+    "organizations": {
+        "CuratorOrg": {
+            "mspid": "CuratorOrg",
+            "peers": ["peer0.curator.local"],
+            "certificateAuthorities": ["ca.curator.local"]
+        }
+    },
+    "peers": {
+        "peer0.curator.local": {
+            "url": "grpcs://localhost:7041",
+            "tlsCACerts": {
+                "path": "/Users/peterpolman/Sites/galachain/test-network/fablo-target/fabric-config/crypto-config/peerOrganizations/curator.local/peers/peer0.curator.local/tls/ca.crt"
+            },
+            "grpcOptions": {
+                "ssl-target-name-override": "peer0.curator.local"
+            }
+        }
+    },
+    "certificateAuthorities": {
+        "ca.curator.local": {
+            "url": "https://localhost:7040",
+            "caName": "ca.curator.local",
+            "tlsCACerts": {
+                "path": "/Users/peterpolman/Sites/galachain/test-network/fablo-target/fabric-config/crypto-config/peerOrganizations/curator.local/peers/ca.curator.local/tls/ca.crt"
+            },
+            "httpOptions": {
+                "verify": false
+            }
+        }
+    }
+}
diff --git a/apps/api/src/app/connection-profiles/cpp-partner.json b/apps/api/src/app/connection-profiles/cpp-partner.json
new file mode 100644
index 000000000..4afcd6ebc
--- /dev/null
+++ b/apps/api/src/app/connection-profiles/cpp-partner.json
@@ -0,0 +1,41 @@
+{
+  "name": "test-network-PartnerOrg1",
+  "version": "1.0.0",
+  "client": {
+    "organization": "PartnerOrg1"
+  },
+  "organizations": {
+    "PartnerOrg1": {
+      "mspid": "PartnerOrg1",
+      "peers": [
+        "peer0.partner1.local"
+      ],
+      "certificateAuthorities": [
+        "ca.partner1.local"
+      ]
+    }
+  },
+  "peers": {
+    "peer0.partner1.local": {
+      "url": "grpcs://localhost:7061",
+      "tlsCACerts": {
+        "path": "/Users/peterpolman/Sites/galachain/test-network/fablo-target/fabric-config/crypto-config/peerOrganizations/partner1.local/peers/peer0.partner1.local/tls/ca.crt"
+      },
+      "grpcOptions": {
+        "ssl-target-name-override": "peer0.partner1.local"
+      }
+    }
+  },
+  "certificateAuthorities": {
+    "ca.partner1.local": {
+      "url": "https://localhost:7060",
+      "caName": "ca.partner1.local",
+      "tlsCACerts": {
+        "path": "/Users/peterpolman/Sites/galachain/test-network/fablo-target/fabric-config/crypto-config/peerOrganizations/partner1.local/peers/ca.partner1.local/tls/ca.crt"
+      },
+      "httpOptions": {
+        "verify": false
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/apps/api/src/app/connection-profiles/cpp-users.json b/apps/api/src/app/connection-profiles/cpp-users.json
new file mode 100644
index 000000000..f95a21cca
--- /dev/null
+++ b/apps/api/src/app/connection-profiles/cpp-users.json
@@ -0,0 +1,59 @@
+{
+  "name": "test-network-UsersOrg1",
+  "version": "1.0.0",
+  "client": {
+    "organization": "UsersOrg1"
+  },
+  "organizations": {
+    "CuratorOrg": {
+      "mspid": "CuratorOrg",
+      "peers": [
+        "peer0.curator.local"
+      ]
+    },
+    "PartnerOrg1": {
+      "mspid": "PartnerOrg1",
+      "peers": [
+        "peer0.partner1.local"
+      ]
+    },
+    "UsersOrg1": {
+      "mspid": "UsersOrg1",
+      "certificateAuthorities": [
+        "ca.users1.local"
+      ]
+    }
+  },
+  "peers": {
+    "peer0.curator.local": {
+      "url": "grpcs://localhost:7041",
+      "tlsCACerts": {
+        "path": "/Users/peterpolman/Sites/galachain/test-network/fablo-target/fabric-config/crypto-config/peerOrganizations/curator.local/peers/peer0.curator.local/tls/ca.crt"
+      },
+      "grpcOptions": {
+        "ssl-target-name-override": "peer0.curator.local"
+      }
+    },
+    "peer0.partner1.local": {
+      "url": "grpcs://localhost:7061",
+      "tlsCACerts": {
+        "path": "/Users/peterpolman/Sites/galachain/test-network/fablo-target/fabric-config/crypto-config/peerOrganizations/partner1.local/peers/peer0.partner1.local/tls/ca.crt"
+      },
+      "grpcOptions": {
+        "ssl-target-name-override": "peer0.partner1.local"
+      }
+    }
+  },
+  "certificateAuthorities": {
+    "ca.users1.local": {
+      "url": "https://localhost:7080",
+      "caName": "ca.users1.local",
+      "tlsCACerts": {
+        "path": "/Users/peterpolman/Sites/galachain/test-network/fablo-target/fabric-config/crypto-config/peerOrganizations/users1.local/peers/ca.users1.local/tls/ca.crt"
+      },
+      "httpOptions": {
+        "verify": false
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/apps/api/src/app/controllers/account/account.router.ts b/apps/api/src/app/controllers/account/account.router.ts
new file mode 100644
index 000000000..003598405
--- /dev/null
+++ b/apps/api/src/app/controllers/account/account.router.ts
@@ -0,0 +1,68 @@
+import express from 'express';
+import { assertRequestInput, guard } from '@thxnetwork/api/middlewares';
+
+import RouterWallet from './wallet/wallets.router';
+
+// Account
+import * as ReadAccount from './get.controller';
+import * as UpdateAccount from './patch.controller';
+import * as DeleteAccount from './delete.controller';
+
+// Social OAuth
+import * as CreateAccountDisconnect from './disconnect/post.controller';
+
+import * as ReadAccountDiscord from './discord/get.controller';
+import * as GetAccountByDiscordId from './discord/get.by-discord-id.controller';
+import * as CreateTwitterTweet from './twitter/tweet/post.controller';
+import * as CreateTwitterUser from './twitter/user/post.controller';
+import * as CreateTwitterSearch from './twitter/search/post.controller';
+import * as CreateTwitterUserByUsername from './twitter/user/by/username/post.controller';
+
+const router: express.Router = express.Router();
+
+router.use('/wallets', RouterWallet);
+
+router.get('/', guard.check(['account:read']), ReadAccount.controller);
+router.patch('/', guard.check(['account:read', 'account:write']), UpdateAccount.controller);
+router.delete('/', guard.check(['account:write']), DeleteAccount.controller);
+
+router.post(
+    '/disconnect',
+    guard.check(['account:read', 'account:write']),
+    assertRequestInput(CreateAccountDisconnect.validation),
+    CreateAccountDisconnect.controller,
+);
+
+router.get('/discord', guard.check(['account:read']), ReadAccountDiscord.controller);
+router.get(
+    '/discord/:discordId',
+    guard.check(['account:read']),
+    assertRequestInput(GetAccountByDiscordId.validation),
+    GetAccountByDiscordId.controller,
+);
+router.post(
+    '/twitter/tweet',
+    assertRequestInput(CreateTwitterTweet.validation),
+    guard.check(['account:read']),
+    CreateTwitterTweet.controller,
+);
+router.post(
+    '/twitter/user/',
+    assertRequestInput(CreateTwitterUser.validation),
+    guard.check(['account:read']),
+    CreateTwitterUser.controller,
+);
+router.post(
+    '/twitter/search/',
+    assertRequestInput(CreateTwitterSearch.validation),
+    guard.check(['account:read']),
+    CreateTwitterSearch.controller,
+);
+router.post(
+    '/twitter/user/by/username',
+    assertRequestInput(CreateTwitterUserByUsername.validation),
+    guard.check(['account:read']),
+    CreateTwitterUserByUsername.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/account/delete.controller.ts b/apps/api/src/app/controllers/account/delete.controller.ts
new file mode 100644
index 000000000..7a051c137
--- /dev/null
+++ b/apps/api/src/app/controllers/account/delete.controller.ts
@@ -0,0 +1,9 @@
+import { Response, Request } from 'express';
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+
+const controller = async (req: Request, res: Response) => {
+    await AccountProxy.remove(req.auth.sub);
+
+    res.status(204).end();
+};
+export { controller };
diff --git a/apps/api/src/app/controllers/account/disconnect/post.controller.ts b/apps/api/src/app/controllers/account/disconnect/post.controller.ts
new file mode 100644
index 000000000..6b69a1810
--- /dev/null
+++ b/apps/api/src/app/controllers/account/disconnect/post.controller.ts
@@ -0,0 +1,13 @@
+import { Response, Request } from 'express';
+import { body } from 'express-validator';
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+
+const validation = [body('kind').isString()];
+
+const controller = async (req: Request, res: Response) => {
+    const account = await AccountProxy.findById(req.auth.sub);
+    await AccountProxy.disconnect(account, req.body.kind);
+
+    res.end();
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/account/discord/get.by-discord-id.controller.ts b/apps/api/src/app/controllers/account/discord/get.by-discord-id.controller.ts
new file mode 100644
index 000000000..5be5f6a9f
--- /dev/null
+++ b/apps/api/src/app/controllers/account/discord/get.by-discord-id.controller.ts
@@ -0,0 +1,15 @@
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import { Wallet } from '@thxnetwork/api/models/Wallet';
+
+const validation = [param('discordId')];
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['Account']
+    const account = await AccountProxy.getByDiscordId(req.params.discordId);
+    const wallets = await Wallet.find({ sub: account.sub });
+
+    res.json({ account, wallets });
+};
+export { validation, controller };
diff --git a/apps/api/src/app/controllers/account/discord/get.controller.ts b/apps/api/src/app/controllers/account/discord/get.controller.ts
new file mode 100644
index 000000000..52c260ea4
--- /dev/null
+++ b/apps/api/src/app/controllers/account/discord/get.controller.ts
@@ -0,0 +1,19 @@
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+import DiscordDataProxy from '@thxnetwork/api/proxies/DiscordDataProxy';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { AccessTokenKind, OAuthDiscordScope } from '@thxnetwork/common/enums';
+import { Request, Response } from 'express';
+
+const controller = async (req: Request, res: Response) => {
+    const account = await AccountProxy.findById(req.auth.sub);
+    const token = await AccountProxy.getToken(account, AccessTokenKind.Discord, [
+        OAuthDiscordScope.Identify,
+        OAuthDiscordScope.Guilds,
+    ]);
+    if (!token) throw new NotFoundError('Discord token not found.');
+
+    const guilds = await DiscordDataProxy.getGuilds(token);
+
+    res.json({ guilds });
+};
+export { controller };
diff --git a/apps/api/src/app/controllers/account/get.controller.ts b/apps/api/src/app/controllers/account/get.controller.ts
new file mode 100644
index 000000000..82c5ae188
--- /dev/null
+++ b/apps/api/src/app/controllers/account/get.controller.ts
@@ -0,0 +1,39 @@
+import { Request, Response } from 'express';
+import { WalletVariant, AccountVariant } from '@thxnetwork/common/enums';
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+import WalletService from '@thxnetwork/api/services/WalletService';
+import THXService from '@thxnetwork/api/services/THXService';
+import ContractService from '@thxnetwork/api/services/ContractService';
+
+const validation = [];
+
+const controller = async (req: Request, res: Response) => {
+    const account = await AccountProxy.findById(req.auth.sub);
+
+    // Connect identity if none exists
+    await THXService.connect(account);
+
+    // Remove actual tokens from response
+    account.tokens = account.tokens.map(({ kind, userId, scopes, metadata }) => ({
+        kind,
+        userId,
+        scopes,
+        metadata,
+    })) as TToken[];
+
+    // If account variant is metamask and no wallet is found then create it
+    if (account.variant === AccountVariant.Metamask) {
+        const wallet = await WalletService.findOne({ sub: req.auth.sub, variant: WalletVariant.WalletConnect });
+        if (!wallet) {
+            await WalletService.createWalletConnect({
+                sub: req.auth.sub,
+                address: account.address,
+                chainId: ContractService.getChainId(),
+            });
+        }
+    }
+
+    res.json(account);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/account/patch.controller.ts b/apps/api/src/app/controllers/account/patch.controller.ts
new file mode 100644
index 000000000..d9804e943
--- /dev/null
+++ b/apps/api/src/app/controllers/account/patch.controller.ts
@@ -0,0 +1,9 @@
+import { Request, Response } from 'express';
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+
+const controller = async (req: Request, res: Response) => {
+    const account = await AccountProxy.update(req.auth.sub, req.body);
+    res.json(account);
+};
+
+export { controller };
diff --git a/apps/api/src/app/controllers/account/twitter/search/post.controller.ts b/apps/api/src/app/controllers/account/twitter/search/post.controller.ts
new file mode 100644
index 000000000..91cb4f4cc
--- /dev/null
+++ b/apps/api/src/app/controllers/account/twitter/search/post.controller.ts
@@ -0,0 +1,17 @@
+import { TwitterQuery } from '@thxnetwork/common/twitter';
+import { Request, Response } from 'express';
+import { body } from 'express-validator';
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+import TwitterDataProxy from '@thxnetwork/api/proxies/TwitterDataProxy';
+
+const validation = [body('operators').customSanitizer((ops) => TwitterQuery.parse(ops))];
+
+const controller = async (req: Request, res: Response) => {
+    const account = await AccountProxy.findById(req.auth.sub);
+    const query = TwitterQuery.create(req.body.operators);
+    const posts = await TwitterDataProxy.search(account, query);
+
+    res.json(posts);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/account/twitter/tweet/post.controller.ts b/apps/api/src/app/controllers/account/twitter/tweet/post.controller.ts
new file mode 100644
index 000000000..0a18d7fe1
--- /dev/null
+++ b/apps/api/src/app/controllers/account/twitter/tweet/post.controller.ts
@@ -0,0 +1,15 @@
+import { Request, Response } from 'express';
+import { body } from 'express-validator';
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+import TwitterDataProxy from '@thxnetwork/api/proxies/TwitterDataProxy';
+
+const validation = [body('tweetId').isString()];
+
+const controller = async (req: Request, res: Response) => {
+    const account = await AccountProxy.findById(req.auth.sub);
+    const tweet = await TwitterDataProxy.getTweet(account, req.body.tweetId);
+
+    res.json(tweet);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/account/twitter/user/by/username/post.controller.ts b/apps/api/src/app/controllers/account/twitter/user/by/username/post.controller.ts
new file mode 100644
index 000000000..54c11fe52
--- /dev/null
+++ b/apps/api/src/app/controllers/account/twitter/user/by/username/post.controller.ts
@@ -0,0 +1,15 @@
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+import TwitterDataProxy from '@thxnetwork/api/proxies/TwitterDataProxy';
+import { Request, Response } from 'express';
+import { body } from 'express-validator';
+
+const validation = [body('username').isString()];
+
+const controller = async (req: Request, res: Response) => {
+    const account = await AccountProxy.findById(req.auth.sub);
+    const user = await TwitterDataProxy.getUserByUsername(account, req.body.username);
+
+    res.json(user);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/account/twitter/user/post.controller.ts b/apps/api/src/app/controllers/account/twitter/user/post.controller.ts
new file mode 100644
index 000000000..56149d6f0
--- /dev/null
+++ b/apps/api/src/app/controllers/account/twitter/user/post.controller.ts
@@ -0,0 +1,15 @@
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+import TwitterDataProxy from '@thxnetwork/api/proxies/TwitterDataProxy';
+import { Request, Response } from 'express';
+import { body } from 'express-validator';
+
+const validation = [body('userId').isString()];
+
+const controller = async (req: Request, res: Response) => {
+    const account = await AccountProxy.findById(req.auth.sub);
+    const user = await TwitterDataProxy.getUser(account, req.body.userId);
+
+    res.json(user);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/account/wallet/confirm/post.controller.ts b/apps/api/src/app/controllers/account/wallet/confirm/post.controller.ts
new file mode 100644
index 000000000..942713c10
--- /dev/null
+++ b/apps/api/src/app/controllers/account/wallet/confirm/post.controller.ts
@@ -0,0 +1,28 @@
+import { Request, Response } from 'express';
+import { body, query } from 'express-validator';
+import { BadRequestError, ForbiddenError } from '@thxnetwork/api/util/errors';
+import { Transaction } from '@thxnetwork/api/models';
+import SafeService from '@thxnetwork/api/services/SafeService';
+
+const validation = [
+    body('chainId').isNumeric(),
+    body('safeTxHash').isString(),
+    body('signature').isString(),
+    query('walletId').isMongoId(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const walletId = req.query.walletId as string;
+    const wallet = await SafeService.findById(walletId);
+    if (!wallet) throw new BadRequestError('Wallet not found.');
+    if (wallet.sub !== req.auth.sub) throw new ForbiddenError('Wallet not owned by sub.');
+
+    const tx = await Transaction.findOne({ safeTxHash: req.body.safeTxHash });
+    if (!tx) throw new BadRequestError('Transaction not found.');
+
+    await SafeService.confirm(wallet, req.body.safeTxHash, req.body.signature);
+
+    res.json(wallet);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/account/wallet/list.controller.ts b/apps/api/src/app/controllers/account/wallet/list.controller.ts
new file mode 100644
index 000000000..5727a0583
--- /dev/null
+++ b/apps/api/src/app/controllers/account/wallet/list.controller.ts
@@ -0,0 +1,15 @@
+import { Request, Response } from 'express';
+import { query } from 'express-validator';
+import WalletService from '@thxnetwork/api/services/WalletService';
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+
+const validation = [query('chainId').optional().isNumeric()];
+
+const controller = async (req: Request, res: Response) => {
+    const account = await AccountProxy.findById(req.auth.sub);
+    const wallets = await WalletService.list(account);
+
+    res.json(wallets);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/account/wallet/post.controller.ts b/apps/api/src/app/controllers/account/wallet/post.controller.ts
new file mode 100644
index 000000000..f34782890
--- /dev/null
+++ b/apps/api/src/app/controllers/account/wallet/post.controller.ts
@@ -0,0 +1,26 @@
+import { Request, Response } from 'express';
+import { recoverSigner } from '@thxnetwork/api/util/network';
+import { body } from 'express-validator';
+import WalletService from '@thxnetwork/api/services/WalletService';
+
+const validation = [
+    body('variant').isString(),
+    body('message').optional().isString(),
+    body('signature').optional().isString(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const { message, signature, variant } = req.body;
+    const data: Partial<TWallet> = { sub: req.auth.sub };
+
+    // If no message and signature are present prepare a wallet to connect later
+    if (signature && message) {
+        data.address = recoverSigner(message, signature);
+    }
+
+    await WalletService.create(variant, data);
+
+    res.status(201).end();
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/account/wallet/wallets.router.ts b/apps/api/src/app/controllers/account/wallet/wallets.router.ts
new file mode 100644
index 000000000..e07041a04
--- /dev/null
+++ b/apps/api/src/app/controllers/account/wallet/wallets.router.ts
@@ -0,0 +1,23 @@
+import express from 'express';
+import * as ListWallets from './list.controller';
+import * as CreateWallets from './post.controller';
+import * as CreateWalletConfirm from './confirm/post.controller';
+import { assertRequestInput, guard } from '@thxnetwork/api/middlewares';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.get('/', guard.check(['account:read']), assertRequestInput(ListWallets.validation), ListWallets.controller);
+router.post(
+    '/',
+    guard.check(['account:read', 'account:write']),
+    assertRequestInput(CreateWallets.validation),
+    CreateWallets.controller,
+);
+router.post(
+    '/confirm',
+    guard.check(['account:read', 'account:write']),
+    assertRequestInput(CreateWalletConfirm.validation),
+    CreateWalletConfirm.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/account/wallet/wallets.test.ts b/apps/api/src/app/controllers/account/wallet/wallets.test.ts
new file mode 100644
index 000000000..983a9186d
--- /dev/null
+++ b/apps/api/src/app/controllers/account/wallet/wallets.test.ts
@@ -0,0 +1,86 @@
+import request from 'supertest';
+import app from '@thxnetwork/api/';
+import { widgetAccessToken, sub, userWalletPrivateKey4 } from '@thxnetwork/api/util/jest/constants';
+import { afterAllCallback, beforeAllCallback } from '@thxnetwork/api/util/jest/config';
+import { ChainId, WalletVariant } from '@thxnetwork/common/enums';
+import { signMessage } from '@thxnetwork/api/util/jest/network';
+import { safeVersion } from '@thxnetwork/api/services/ContractService';
+
+const user = request.agent(app);
+
+describe('Account Wallets', () => {
+    beforeAll(() => beforeAllCallback({ skipWalletCreation: true }));
+    afterAll(afterAllCallback);
+
+    describe('GET /wallets', () => {
+        it('HTTP 200 if OK', (done) => {
+            user.get(`/v1/account/wallets`)
+                .set({ Authorization: widgetAccessToken })
+                .expect((res: request.Response) => {
+                    expect(res.body.length).toEqual(0);
+                })
+                .expect(200, done);
+        });
+    });
+
+    // Create Safe Wallet
+    describe('POST /wallets (Safe)', () => {
+        it('HTTP 200 if OK', (done) => {
+            const message = 'test';
+            const signature = signMessage(userWalletPrivateKey4, message);
+            user.post(`/v1/account/wallets`)
+                .set({ Authorization: widgetAccessToken })
+                .send({
+                    variant: WalletVariant.Safe,
+                    message,
+                    signature,
+                })
+                .expect(201, done);
+        });
+    });
+
+    describe('POST /wallets (WalletConnect)', () => {
+        it('HTTP 200 if OK', (done) => {
+            const message = 'test';
+            const signature = signMessage(userWalletPrivateKey4, message);
+            user.post(`/v1/account/wallets`)
+                .set({ Authorization: widgetAccessToken })
+                .send({
+                    variant: WalletVariant.WalletConnect,
+                    message,
+                    signature,
+                })
+                .expect(201, done);
+        });
+    });
+
+    // Create WebConnect wallet
+
+    describe('GET /wallets', () => {
+        it('HTTP 200 if OK', (done) => {
+            user.get('/v1/account/wallets')
+                .set({ Authorization: widgetAccessToken })
+                .expect((res: request.Response) => {
+                    expect(res.body.length).toEqual(2);
+
+                    const safe = res.body.find((wallet: any) => wallet.variant === WalletVariant.Safe);
+                    const wallet = res.body.find((wallet: any) => wallet.variant === WalletVariant.WalletConnect);
+                    expect(safe.sub).toEqual(sub);
+                    expect(safe.chainId).toEqual(ChainId.Hardhat);
+                    expect(safe.variant).toBe(WalletVariant.Safe);
+                    expect(safe.address).toBeDefined();
+                    expect(safe.safeVersion).toBe(safeVersion);
+
+                    expect(wallet.sub).toEqual(sub);
+                    expect(wallet.chainId).toEqual(ChainId.Hardhat);
+                    expect(wallet.variant).toBe(WalletVariant.WalletConnect);
+                    expect(wallet.address).toBeDefined();
+                })
+                .expect(200, done);
+        });
+    });
+
+    describe('POST /wallets', () => {
+        //
+    });
+});
diff --git a/apps/api/src/app/controllers/brands/brands.router.ts b/apps/api/src/app/controllers/brands/brands.router.ts
new file mode 100644
index 000000000..5f46aaca6
--- /dev/null
+++ b/apps/api/src/app/controllers/brands/brands.router.ts
@@ -0,0 +1,20 @@
+import express from 'express';
+import { assertRequestInput, checkJwt, corsHandler, guard } from '@thxnetwork/api/middlewares';
+
+import * as GetBrand from './get.controller';
+import * as PutBrand from './put.controller';
+
+const router: express.Router = express.Router();
+router.get('/', GetBrand.controller);
+
+router
+    .use(checkJwt)
+    .use(corsHandler)
+    .put(
+        '/',
+        guard.check(['brands:write', 'brands:read']),
+        assertRequestInput(PutBrand.validation),
+        PutBrand.controller,
+    );
+
+export default router;
diff --git a/apps/api/src/app/controllers/brands/get.controller.ts b/apps/api/src/app/controllers/brands/get.controller.ts
new file mode 100644
index 000000000..24452b312
--- /dev/null
+++ b/apps/api/src/app/controllers/brands/get.controller.ts
@@ -0,0 +1,9 @@
+import { Request, Response } from 'express';
+import BrandService from '@thxnetwork/api/services/BrandService';
+
+const controller = async (req: Request, res: Response) => {
+    const brand = await BrandService.get(req.header('X-PoolId'));
+    res.json(brand);
+};
+
+export { controller };
diff --git a/apps/api/src/app/controllers/brands/put.controller.ts b/apps/api/src/app/controllers/brands/put.controller.ts
new file mode 100644
index 000000000..2bf9a077b
--- /dev/null
+++ b/apps/api/src/app/controllers/brands/put.controller.ts
@@ -0,0 +1,34 @@
+import { isValidUrl } from '@thxnetwork/api/util/url';
+import { Request, Response } from 'express';
+import { body } from 'express-validator';
+import { ForbiddenError } from '@thxnetwork/api/util/errors';
+import BrandService from '../../services/BrandService';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import CanvasService from '@thxnetwork/api/services/CanvasService';
+import ImageService from '@thxnetwork/api/services/ImageService';
+
+const validation = [
+    body('logoImgUrl').custom((logoImgUrl?: string) => {
+        return logoImgUrl && logoImgUrl.length ? isValidUrl(logoImgUrl) : true;
+    }),
+    body('backgroundImgUrl').custom((backgroundImgUrl?: string) => {
+        return backgroundImgUrl && backgroundImgUrl.length ? isValidUrl(backgroundImgUrl) : true;
+    }),
+];
+const controller = async (req: Request, res: Response) => {
+    const poolId = req.header('X-PoolId');
+    const hasAccess = await PoolService.isSubjectAllowed(req.auth.sub, poolId);
+    if (!hasAccess) throw new ForbiddenError('Not your pool');
+
+    // Update logo and bg changes
+    const { logoImgUrl, backgroundImgUrl } = req.body;
+    const brand = await BrandService.update({ poolId }, { logoImgUrl, backgroundImgUrl });
+
+    // Create campaign preview
+    const previewFile = await CanvasService.createPreviewImage(brand);
+    brand.previewImgUrl = await ImageService.uploadToS3(previewFile, `${poolId}_campaign_preview.png`, 'image/*');
+
+    res.json(await brand.save());
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/client/client.router.ts b/apps/api/src/app/controllers/client/client.router.ts
new file mode 100644
index 000000000..791575cd0
--- /dev/null
+++ b/apps/api/src/app/controllers/client/client.router.ts
@@ -0,0 +1,19 @@
+import express from 'express';
+import * as ListController from './list.controller';
+import * as PostController from './post.controller';
+import * as PatchController from './patch.controller';
+import { assertPoolAccess, assertRequestInput, guard } from '@thxnetwork/api/middlewares';
+
+const router: express.Router = express.Router();
+
+router.get('/', guard.check(['clients:read']), assertPoolAccess, ListController.controller);
+router.patch(
+    '/:id',
+    guard.check(['clients:read', 'clients:write']),
+    assertRequestInput(PatchController.validation),
+    assertPoolAccess,
+    PatchController.controller,
+);
+router.post('/', guard.check(['clients:read', 'clients:write']), assertPoolAccess, PostController.controller);
+
+export default router;
diff --git a/apps/api/src/app/controllers/client/list.controller.ts b/apps/api/src/app/controllers/client/list.controller.ts
new file mode 100644
index 000000000..a9c38d26c
--- /dev/null
+++ b/apps/api/src/app/controllers/client/list.controller.ts
@@ -0,0 +1,16 @@
+import { Request, Response } from 'express';
+import { Client } from '@thxnetwork/api/models/Client';
+import ClientProxy from '@thxnetwork/api/proxies/ClientProxy';
+
+const controller = async (req: Request, res: Response) => {
+    const poolId = req.header('X-PoolId');
+    const clients = await Client.find({ poolId });
+    const promises = clients.map(async (client) => {
+        return await ClientProxy.getCredentials(client.toJSON());
+    });
+    const result = await Promise.all(promises);
+
+    res.status(200).json(result);
+};
+
+export { controller };
diff --git a/apps/api/src/app/controllers/client/patch.controller.ts b/apps/api/src/app/controllers/client/patch.controller.ts
new file mode 100644
index 000000000..87787cc65
--- /dev/null
+++ b/apps/api/src/app/controllers/client/patch.controller.ts
@@ -0,0 +1,15 @@
+import ClientProxy from '@thxnetwork/api/proxies/ClientProxy';
+import { Request, Response } from 'express';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { body, param } from 'express-validator';
+
+const validation = [param('id').exists(), body('name').exists()];
+const controller = async (req: Request, res: Response) => {
+    let client = await ClientProxy.get(req.params.id);
+    if (!client) throw new NotFoundError('Cannot found Client ID in this request');
+
+    client = await ClientProxy.update(client.clientId, { name: req.body['name'] });
+    res.status(200).json(client);
+};
+
+export { validation, controller };
diff --git a/apps/api/src/app/controllers/client/post.controller.ts b/apps/api/src/app/controllers/client/post.controller.ts
new file mode 100644
index 000000000..81cc6a8c2
--- /dev/null
+++ b/apps/api/src/app/controllers/client/post.controller.ts
@@ -0,0 +1,34 @@
+import { Request, Response } from 'express';
+import { GrantVariant } from '@thxnetwork/common/enums';
+import { widgetScopes } from '@thxnetwork/api/util/jest/constants';
+import ClientProxy from '@thxnetwork/api/proxies/ClientProxy';
+
+const controller = async (req: Request, res: Response) => {
+    const poolId = req.header('X-PoolId');
+    const { grantType, redirectUri, requestUri, name } = req.body;
+    const grantMap = {
+        [GrantVariant.AuthorizationCode]: {
+            application_type: 'web',
+            grant_types: [grantType],
+            request_uris: [requestUri],
+            redirect_uris: [redirectUri],
+            post_logout_redirect_uris: [requestUri],
+            response_types: ['code'],
+            scope: widgetScopes,
+        },
+        [GrantVariant.ClientCredentials]: {
+            application_type: 'web',
+            grant_types: [grantType],
+            request_uris: [],
+            redirect_uris: [],
+            response_types: [],
+            scope: 'openid events:write identities:read identities:write pools:write pools:read',
+        },
+    };
+    const payload = grantMap[grantType];
+    const client = await ClientProxy.create(req.auth.sub, poolId, payload, name);
+
+    res.json(client);
+};
+
+export { controller };
diff --git a/apps/api/src/app/controllers/coupons/coupons.router.ts b/apps/api/src/app/controllers/coupons/coupons.router.ts
new file mode 100644
index 000000000..75aece92d
--- /dev/null
+++ b/apps/api/src/app/controllers/coupons/coupons.router.ts
@@ -0,0 +1,22 @@
+import { assertRequestInput, guard } from '@thxnetwork/api/middlewares';
+import express from 'express';
+import * as ListCouponCode from './list.controller';
+import * as RemoveCouponCode from './delete.controller';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.get(
+    '/',
+    guard.check(['coupon_rewards:read']),
+    assertRequestInput(ListCouponCode.validation),
+    ListCouponCode.controller,
+);
+
+router.delete(
+    '/:couponCodeId/',
+    guard.check(['coupon_rewards:write']),
+    assertRequestInput(RemoveCouponCode.validation),
+    RemoveCouponCode.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/coupons/delete.controller.ts b/apps/api/src/app/controllers/coupons/delete.controller.ts
new file mode 100644
index 000000000..a2f0f93af
--- /dev/null
+++ b/apps/api/src/app/controllers/coupons/delete.controller.ts
@@ -0,0 +1,30 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import { CouponCode } from '@thxnetwork/api/models/CouponCode';
+import { RewardCouponPayment } from '@thxnetwork/api/models/RewardCouponPayment';
+import { ForbiddenError, NotFoundError } from '@thxnetwork/api/util/errors';
+import { RewardVariant } from '@thxnetwork/common/enums';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import RewardService from '@thxnetwork/api/services/RewardService';
+
+const validation = [param('couponCodeId').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const couponCode = await CouponCode.findById(req.params.couponCodeId);
+    if (!couponCode) throw new NotFoundError('Coupon code not found');
+
+    // Check if user is allowed to access the pool for this couponRewardId
+    const reward = await RewardService.findById(RewardVariant.Coupon, couponCode.couponRewardId);
+    const isAllowed = await PoolService.isSubjectAllowed(req.auth.sub, reward.poolId);
+    if (!isAllowed) throw new ForbiddenError('Not allowed to access these coupon codes');
+
+    // Check if the coupon code has already been purchased
+    const payment = await RewardCouponPayment.exists({ couponCodeId: req.params.couponCodeId });
+    if (payment) throw new ForbiddenError('Coupon code has been redeemed');
+
+    await CouponCode.findByIdAndDelete(req.params.couponCodeId);
+
+    res.status(204).end();
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/coupons/list.controller.ts b/apps/api/src/app/controllers/coupons/list.controller.ts
new file mode 100644
index 000000000..2136f2717
--- /dev/null
+++ b/apps/api/src/app/controllers/coupons/list.controller.ts
@@ -0,0 +1,32 @@
+import { Request, Response } from 'express';
+import { query } from 'express-validator';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import { RewardVariant } from '@thxnetwork/common/enums';
+import RewardService from '@thxnetwork/api/services/RewardService';
+import { ForbiddenError } from '@thxnetwork/api/util/errors';
+
+const validation = [
+    query('couponRewardId').isMongoId(),
+    query('page').isInt(),
+    query('limit').isInt(),
+    query('query').isString(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const couponRewardId = req.query.couponRewardId as string;
+    const page = Number(req.query.page);
+    const limit = Number(req.query.limit);
+    const query = req.query.query as string;
+    console.log(query);
+
+    // Check if user is allowed to access the pool for this couponRewardId
+    const reward = await RewardService.findById(RewardVariant.Coupon, couponRewardId);
+    const isAllowed = await PoolService.isSubjectAllowed(req.auth.sub, reward.poolId);
+    if (!isAllowed) throw new ForbiddenError('Not allowed to access these coupon codes');
+
+    const result = await PoolService.findCouponCodes({ couponRewardId, query }, page, limit);
+
+    res.json(result);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/data/data.router.ts b/apps/api/src/app/controllers/data/data.router.ts
new file mode 100644
index 000000000..facda5c9a
--- /dev/null
+++ b/apps/api/src/app/controllers/data/data.router.ts
@@ -0,0 +1,35 @@
+import express, { Request, Response } from 'express';
+import axios, { AxiosRequestConfig, Method } from 'axios';
+import { MIXPANEL_API_URL } from '@thxnetwork/api/config/secrets';
+import { ForbiddenError } from '@thxnetwork/api/util/errors';
+// import { getIP } from '@thxnetwork/api/util/ip';
+
+const router: express.Router = express.Router();
+
+const mixpanelProxy = function (options: AxiosRequestConfig) {
+    if (!options.url.startsWith('/')) throw new ForbiddenError();
+    axios.defaults.baseURL = MIXPANEL_API_URL;
+    return axios(options);
+};
+
+router.all('*', async (req: Request, res: Response) => {
+    // if (req.body.data) {
+    //     const dataDecoded = Buffer.from(req.body.data, 'base64').toString();
+    //     const dataObject = JSON.parse(dataDecoded);
+    //     const data = dataObject.map((item) => {
+    //         if (!item || !item.event) return item;
+    //         return { properties: { ...item.properties, real_ip: getIP(req) } };
+    //     });
+    //     const dataString = JSON.stringify(data);
+    //     req.body.data = Buffer.from(dataString).toString('base64');
+    // }
+    await mixpanelProxy({
+        method: req.method as Method,
+        url: req.originalUrl.replace(req.baseUrl, ''),
+        headers: { 'X-REAL-IP': req.ip },
+        params: req.body,
+    });
+    res.end();
+});
+
+export default router;
diff --git a/apps/api/src/app/controllers/earn/earn.router.ts b/apps/api/src/app/controllers/earn/earn.router.ts
new file mode 100644
index 000000000..9993a31fd
--- /dev/null
+++ b/apps/api/src/app/controllers/earn/earn.router.ts
@@ -0,0 +1,10 @@
+import express from 'express';
+import * as ListPriceController from './prices/list.controller';
+import * as ListAPRController from './metrics/list.controller';
+
+const router: express.Router = express.Router();
+
+router.get('/prices', ListPriceController.controller);
+router.get('/metrics', ListAPRController.controller);
+
+export default router;
diff --git a/apps/api/src/app/controllers/earn/metrics/list.controller.ts b/apps/api/src/app/controllers/earn/metrics/list.controller.ts
new file mode 100644
index 000000000..ec1f02bb6
--- /dev/null
+++ b/apps/api/src/app/controllers/earn/metrics/list.controller.ts
@@ -0,0 +1,16 @@
+import BalancerService from '@thxnetwork/api/services/BalancerService';
+import WalletService from '@thxnetwork/api/services/WalletService';
+import { ChainId } from '@thxnetwork/common/enums';
+import { Request, Response } from 'express';
+import { query } from 'express-validator';
+
+const validation = [query('walletId').optional().isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const wallet = await WalletService.findById(req.query.walletId as string);
+    const result = BalancerService.getMetrics(wallet ? wallet.chainId : ChainId.Polygon);
+
+    res.json(result);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/earn/prices/list.controller.ts b/apps/api/src/app/controllers/earn/prices/list.controller.ts
new file mode 100644
index 000000000..28304a0d0
--- /dev/null
+++ b/apps/api/src/app/controllers/earn/prices/list.controller.ts
@@ -0,0 +1,9 @@
+import BalancerService from '@thxnetwork/api/services/BalancerService';
+import { Request, Response } from 'express';
+
+const controller = async (req: Request, res: Response) => {
+    const pricing = BalancerService.getPricing();
+    res.json(pricing);
+};
+
+export { controller };
diff --git a/apps/api/src/app/controllers/erc1155/delete.controller.ts b/apps/api/src/app/controllers/erc1155/delete.controller.ts
new file mode 100644
index 000000000..bb6a20f6d
--- /dev/null
+++ b/apps/api/src/app/controllers/erc1155/delete.controller.ts
@@ -0,0 +1,16 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import { ForbiddenError } from '@thxnetwork/api/util/errors';
+import { ERC1155 } from '@thxnetwork/api/models/ERC1155';
+
+const validation = [param('id').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const erc1155 = await ERC1155.findById(req.params.id);
+    if (erc1155.sub !== req.auth.sub) throw new ForbiddenError('Not your ERC1155');
+
+    await erc1155.deleteOne();
+
+    return res.status(204).end();
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc1155/erc1155.router.ts b/apps/api/src/app/controllers/erc1155/erc1155.router.ts
new file mode 100644
index 000000000..90c2f465c
--- /dev/null
+++ b/apps/api/src/app/controllers/erc1155/erc1155.router.ts
@@ -0,0 +1,99 @@
+import express from 'express';
+import { assertPoolAccess, assertRequestInput, guard } from '@thxnetwork/api/middlewares';
+import { upload } from '@thxnetwork/api/util/multer';
+
+import * as ReadERC1155 from './get.controller';
+import * as ListERC1155 from './list.controller';
+import * as RemoveERC1155 from './delete.controller';
+import * as ListERC1155Metadata from './metadata/list.controller';
+import * as ListERC1155Token from './token/list.controller';
+import * as ReadERC1155Token from './token/get.controller';
+import * as CreateERC1155 from './post.controller';
+import * as CreateERC1155Metadata from './metadata/post.controller';
+
+import * as UpdateERC1155 from './patch.controller';
+import * as ReadERC1155Metadata from './metadata/get.controller';
+import * as PatchERC1155Metadata from './metadata/patch.controller';
+import * as DeleteERC1155Metadata from './metadata/delete.controller';
+import * as ImportERC1155Contract from './import/post.controller';
+import * as PreviewERC1155Contract from './import/preview/post.controller';
+import * as CreateERC1155Transfer from './transfer/post.controller';
+
+const router: express.Router = express.Router();
+
+router.get(
+    '/token',
+    guard.check(['erc1155:read']),
+    assertRequestInput(ListERC1155Token.validation),
+    ListERC1155Token.controller,
+);
+router.get('/token/:id', guard.check(['erc1155:read']), ReadERC1155Token.controller);
+router.get('/', guard.check(['erc1155:read']), assertRequestInput(ListERC1155.validation), ListERC1155.controller);
+router.get('/:id', guard.check(['erc1155:read']), assertRequestInput(ReadERC1155.validation), ReadERC1155.controller);
+
+router.post(
+    '/',
+    upload.single('file'),
+    guard.check(['erc1155:read', 'erc1155:write']),
+    assertRequestInput(CreateERC1155.validation),
+    CreateERC1155.controller,
+);
+router.post(
+    '/import',
+    ImportERC1155Contract.controller,
+    assertPoolAccess,
+    assertRequestInput(ImportERC1155Contract.validation),
+);
+router.post('/preview', assertRequestInput(PreviewERC1155Contract.validation), PreviewERC1155Contract.controller);
+router.patch(
+    '/:id/metadata/:metadataId',
+    guard.check(['erc1155:write']),
+    assertRequestInput(PatchERC1155Metadata.validation),
+    PatchERC1155Metadata.controller,
+);
+
+router.delete(
+    '/:id/metadata/:metadataId',
+    guard.check(['erc1155:write']),
+    assertRequestInput(DeleteERC1155Metadata.validation),
+    DeleteERC1155Metadata.controller,
+);
+
+router.get('/:id/metadata', guard.check(['erc1155:read']), ListERC1155Metadata.controller);
+
+router.post(
+    '/:id/metadata/',
+    guard.check(['erc1155:write']),
+    assertRequestInput(CreateERC1155Metadata.validation),
+    CreateERC1155Metadata.controller,
+);
+
+router.patch(
+    '/:id',
+    guard.check(['erc1155:write', 'erc1155:read']),
+    assertRequestInput(UpdateERC1155.validation),
+    UpdateERC1155.controller,
+);
+
+router.get(
+    '/:id/metadata/:metadataId',
+    guard.check(['erc1155:read']),
+    ReadERC1155Metadata.controller,
+    assertRequestInput(ReadERC1155Metadata.validation),
+);
+
+router.post(
+    '/transfer',
+    // guard.check(['erc1155_transfer:read', 'erc1155_transfer:write']),
+    assertRequestInput(CreateERC1155Transfer.validation),
+    CreateERC1155Transfer.controller,
+);
+
+router.delete(
+    '/:id',
+    guard.check(['erc1155:read', 'erc1155:write']),
+    assertRequestInput(RemoveERC1155.validation),
+    RemoveERC1155.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/erc1155/erc1155.test.ts b/apps/api/src/app/controllers/erc1155/erc1155.test.ts
new file mode 100644
index 000000000..b222e7714
--- /dev/null
+++ b/apps/api/src/app/controllers/erc1155/erc1155.test.ts
@@ -0,0 +1,89 @@
+import request from 'supertest';
+import app from '@thxnetwork/api/';
+import { ChainId } from '@thxnetwork/common/enums';
+import { isAddress } from 'web3-utils';
+import { afterAllCallback, beforeAllCallback } from '@thxnetwork/api/util/jest/config';
+import { dashboardAccessToken } from '@thxnetwork/api/util/jest/constants';
+import { createImage } from '@thxnetwork/api/util/jest/images';
+
+const user = request.agent(app);
+
+describe('ERC1155', () => {
+    const chainId = ChainId.Hardhat,
+        name = 'Planets of the Galaxy',
+        description = 'Collection full of rarities.';
+    let erc1155ID: string;
+
+    beforeAll(beforeAllCallback);
+    afterAll(afterAllCallback);
+
+    describe('POST /erc1155', () => {
+        it('should create and return contract details', async () => {
+            const logoImg = createImage();
+            await user
+                .post('/v1/erc1155')
+                .set('Authorization', dashboardAccessToken)
+                .attach('file', logoImg, { filename: 'logoImg.jpg', contentType: 'image/jpg' })
+                .field({
+                    chainId,
+                    name,
+                    description,
+                })
+                .expect(({ body }: request.Response) => {
+                    expect(body._id).toBeDefined();
+                    expect(body.chainId).toBe(chainId);
+                    expect(body.name).toBe(name);
+                    expect(body.description).toBe(description);
+                    expect(isAddress(body.address)).toBe(true);
+                    expect(body.logoImgUrl).toBeDefined();
+                    erc1155ID = body._id;
+                })
+                .expect(201);
+        });
+    });
+
+    describe('GET /erc1155/:id', () => {
+        it('should return contract details', (done) => {
+            user.get('/v1/erc1155/' + erc1155ID)
+                .set('Authorization', dashboardAccessToken)
+                .send()
+                .expect(({ body }: request.Response) => {
+                    expect(body.chainId).toBe(chainId);
+                    expect(body.name).toBe(name);
+                    expect(body.description).toBe(description);
+                    expect(isAddress(body.address)).toBe(true);
+                    expect(body.logoImgUrl).toBeDefined();
+                })
+                .expect(200, done);
+        });
+        it('should 400 for invalid ID', (done) => {
+            user.get('/v1/erc1155/' + 'invalid_id')
+                .set('Authorization', dashboardAccessToken)
+                .send()
+                .expect(({ body }: request.Response) => {
+                    expect(body.errors[0].msg).toContain('Invalid value');
+                })
+                .expect(400, done);
+        });
+        it('should 404 if not known', (done) => {
+            user.get('/v1/erc1155/' + '62397f69760ac5f9ab4454df')
+                .set('Authorization', dashboardAccessToken)
+                .send()
+                .expect(({ body }: request.Response) => {
+                    expect(body.error.message).toContain('Not Found');
+                })
+                .expect(404, done);
+        });
+        describe('PATCH /erc1155/:id', () => {
+            it('should update a created token', (done) => {
+                user.patch('/v1/erc1155/' + erc1155ID)
+                    .set('Authorization', dashboardAccessToken)
+                    .send()
+                    .expect(({ body }: request.Response) => {
+                        expect(body).toBeDefined();
+                    })
+                    .expect(200, done);
+            });
+        });
+    });
+});
diff --git a/apps/api/src/app/controllers/erc1155/get.controller.ts b/apps/api/src/app/controllers/erc1155/get.controller.ts
new file mode 100644
index 000000000..943de3d44
--- /dev/null
+++ b/apps/api/src/app/controllers/erc1155/get.controller.ts
@@ -0,0 +1,21 @@
+import { param } from 'express-validator';
+import { Request, Response } from 'express';
+import ERC1155Service from '@thxnetwork/api/services/ERC1155Service';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+
+const validation = [param('id').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    let erc1155 = await ERC1155Service.findById(req.params.id);
+
+    if (!erc1155) throw new NotFoundError();
+    // Check if pending transaction is mined.
+    if (!erc1155.address) erc1155 = await ERC1155Service.queryDeployTransaction(erc1155);
+    // Still no address.
+    if (!erc1155.address) return res.send(erc1155);
+    const owner = await erc1155.contract.methods.owner().call();
+
+    res.json({ ...erc1155.toJSON(), owner });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc1155/import/erc1155-import.test.ts b/apps/api/src/app/controllers/erc1155/import/erc1155-import.test.ts
new file mode 100644
index 000000000..f7ed86915
--- /dev/null
+++ b/apps/api/src/app/controllers/erc1155/import/erc1155-import.test.ts
@@ -0,0 +1,123 @@
+import request from 'supertest';
+import app from '@thxnetwork/api/';
+import { ChainId } from '@thxnetwork/common/enums';
+import { afterAllCallback, beforeAllCallback } from '@thxnetwork/api/util/jest/config';
+import { dashboardAccessToken, sub } from '@thxnetwork/api/util/jest/constants';
+import { ERC1155TokenState } from '@thxnetwork/common/enums';
+import { ERC1155Document } from '@thxnetwork/api/models/ERC1155';
+import { alchemy } from '@thxnetwork/api/util/alchemy';
+import { deployERC1155, mockGetNftsForOwner } from '@thxnetwork/api/util/jest/erc1155';
+import { PoolDocument } from '@thxnetwork/api/models';
+import { Contract } from 'web3-eth-contract';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { ethers } from 'ethers';
+import TransactionService from '@thxnetwork/api/services/TransactionService';
+
+const user = request.agent(app);
+
+describe('ERC1155 import', () => {
+    let erc1155: ERC1155Document, pool: PoolDocument, nftContract: Contract;
+    const chainId = ChainId.Hardhat,
+        nftName = 'Test Collection';
+
+    beforeAll(beforeAllCallback);
+    afterAll(afterAllCallback);
+
+    describe('POST /pools', () => {
+        it('HTTP 201', (done) => {
+            user.post('/v1/pools')
+                .set('Authorization', dashboardAccessToken)
+                .send({ chainId })
+                .expect((res: request.Response) => {
+                    pool = res.body;
+                })
+                .expect(201, done);
+        });
+    });
+
+    describe('POST /erc1155/import', () => {
+        it('HTTP 201`', async () => {
+            // Create 1 NFT collection
+            nftContract = await deployERC1155();
+            const id = 1;
+            const amount = 1;
+            // Mint 1 token in the collection
+            await TransactionService.sendAsync(
+                nftContract.options.address,
+                nftContract.methods.mint(pool.safeAddress, id, amount, ethers.constants.HashZero),
+                chainId,
+            );
+
+            // Mock Alchemy SDK return value for getNftsForOwner
+            jest.spyOn(alchemy.nft, 'getNftsForOwner').mockImplementation(() =>
+                Promise.resolve(mockGetNftsForOwner(nftContract.options.address) as any),
+            );
+
+            // Run the import for the deployed contract address
+            await user
+                .post('/v1/erc1155/import')
+                .set({ 'Authorization': dashboardAccessToken, 'X-PoolId': pool._id })
+                .send({ chainId, contractAddress: nftContract.options.address, name: nftName })
+                .expect(({ body }: request.Response) => {
+                    expect(body.erc1155._id).toBeDefined();
+                    expect(body.erc1155.address).toBe(nftContract.options.address);
+
+                    erc1155 = body.erc1155;
+                })
+                .expect(201);
+        });
+    });
+
+    describe('GET /erc1155/:id', () => {
+        const { defaultAccount } = getProvider(chainId);
+
+        it('HTTP 200', (done) => {
+            user.get(`/v1/erc1155/${erc1155._id}`)
+                .set('Authorization', dashboardAccessToken)
+                .send()
+                .expect(({ body }: request.Response) => {
+                    expect(body.chainId).toBe(chainId);
+                    expect(body.sub).toBe(sub);
+                    expect(body.name).toBe(nftName);
+                    expect(body.address).toBe(nftContract.options.address);
+                    expect(body.owner).toBe(defaultAccount);
+                })
+                .expect(200, done);
+        });
+    });
+
+    describe('GET /erc1155/token', () => {
+        it('HTTP 200', (done) => {
+            user.get(`/v1/erc1155/token`)
+                .query({ walletId: pool.safe._id })
+                .set('Authorization', dashboardAccessToken)
+                .send()
+                .expect(({ body }: request.Response) => {
+                    expect(body.length).toBe(1);
+                    expect(body[0].sub).toBe(sub);
+                    expect(body[0].erc1155Id).toBe(erc1155._id);
+                    expect(body[0].state).toBe(ERC1155TokenState.Minted);
+                    expect(body[0].recipient).toBe(pool.safeAddress);
+                    expect(body[0].tokenUri).toBeDefined();
+                    expect(body[0].tokenId).toBeDefined();
+                    expect(body[0].metadataId).toBeDefined();
+                })
+                .expect(200, done);
+        });
+    });
+
+    describe('GET /erc1155/:id/metadata', () => {
+        it('HTTP 200', (done) => {
+            user.get(`/v1/erc1155/${erc1155._id}/metadata`)
+                .set('Authorization', dashboardAccessToken)
+                .send()
+                .expect(({ body }: request.Response) => {
+                    expect(body.total).toBe(1);
+                    expect(body.results[0].name).toBeDefined();
+                    expect(body.results[0].description).toBeDefined();
+                    expect(body.results[0].image).toBeDefined();
+                })
+                .expect(200, done);
+        });
+    });
+});
diff --git a/apps/api/src/app/controllers/erc1155/import/post.controller.ts b/apps/api/src/app/controllers/erc1155/import/post.controller.ts
new file mode 100644
index 000000000..05c19128b
--- /dev/null
+++ b/apps/api/src/app/controllers/erc1155/import/post.controller.ts
@@ -0,0 +1,104 @@
+import { body } from 'express-validator';
+import { Request, Response } from 'express';
+import { ERC1155Token } from '@thxnetwork/api/models/ERC1155Token';
+import { ERC1155 } from '@thxnetwork/api/models/ERC1155';
+import { ForbiddenError, NotFoundError } from '@thxnetwork/api/util/errors';
+import { ERC1155TokenState } from '@thxnetwork/common/enums';
+import { getNFTsForOwner, parseIPFSImageUrl } from '@thxnetwork/api/util/alchemy';
+import { ChainId, NFTVariant } from '@thxnetwork/common/enums';
+import { logger } from '@thxnetwork/api/util/logger';
+import { ERC1155Metadata } from '@thxnetwork/api/models/ERC1155Metadata';
+import { toChecksumAddress } from 'web3-utils';
+import PoolService from '@thxnetwork/api/services/PoolService';
+
+const validation = [body('contractAddress').exists().isString(), body('chainId').exists().isNumeric()];
+
+const controller = async (req: Request, res: Response) => {
+    const chainId = Number(req.body.chainId) as ChainId;
+    const contractAddress = toChecksumAddress(req.body.contractAddress);
+    const pool = await PoolService.getById(req.header('X-PoolId'));
+    const ownedNfts = await getNFTsForOwner(pool.safeAddress, contractAddress);
+    if (!ownedNfts.length) throw new NotFoundError('Could not find NFT tokens for this contract address');
+
+    let erc1155 = await ERC1155.findOne({
+        sub: req.auth.sub,
+        chainId,
+        address: contractAddress,
+    });
+
+    // If erc1155 already exists check if it is owned by the authenticated user
+    if (erc1155 && erc1155.sub !== req.auth.sub) {
+        throw new ForbiddenError('This is not your contract.');
+    }
+
+    // If erc1155 is owned or not existing continue with update or upsert
+    erc1155 = await ERC1155.findOneAndUpdate(
+        {
+            chainId,
+            sub: req.auth.sub,
+            address: contractAddress,
+        },
+        {
+            chainId,
+            sub: req.auth.sub,
+            address: contractAddress,
+            variant: NFTVariant.ERC1155,
+            name: req.body.name,
+            archived: false,
+            baseURL: '',
+        },
+        { upsert: true, new: true },
+    );
+    const erc1155Tokens = await Promise.all(
+        ownedNfts.map(async ({ name, description, image, collection, tokenId, tokenUri }) => {
+            try {
+                const erc1155Id = erc1155.id;
+                const imageUrl = parseIPFSImageUrl(image.originalUrl);
+                const metadata = await ERC1155Metadata.findOneAndUpdate(
+                    {
+                        erc1155Id,
+                        tokenId,
+                    },
+                    {
+                        erc1155Id,
+                        tokenId,
+                        name,
+                        description,
+                        imageUrl,
+                        image: imageUrl,
+                        externalUrl: collection.externalUrl,
+                    },
+                    { upsert: true, new: true },
+                );
+                const walletId = String(pool.safe._id);
+                const erc1155Token = await ERC1155Token.findOneAndUpdate(
+                    {
+                        erc1155Id,
+                        tokenId,
+                        sub: req.auth.sub,
+                        recipient: pool.safeAddress,
+                    },
+                    {
+                        erc1155Id,
+                        tokenId,
+                        walletId,
+                        tokenUri,
+                        sub: req.auth.sub,
+                        recipient: pool.safeAddress,
+                        state: ERC1155TokenState.Minted,
+                        metadataId: String(metadata._id),
+                    },
+                    { upsert: true, new: true },
+                );
+
+                return { ...erc1155Token.toJSON(), metadata: metadata.toJSON() };
+            } catch (error) {
+                logger.error(error);
+            }
+        }),
+    );
+
+    res.status(201).json({ erc1155, erc1155Tokens });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc1155/import/preview/post.controller.ts b/apps/api/src/app/controllers/erc1155/import/preview/post.controller.ts
new file mode 100644
index 000000000..d365f80e3
--- /dev/null
+++ b/apps/api/src/app/controllers/erc1155/import/preview/post.controller.ts
@@ -0,0 +1,22 @@
+import { Request, Response } from 'express';
+import { body } from 'express-validator';
+import { getNFTsForOwner, parseIPFSImageUrl } from '@thxnetwork/api/util/alchemy';
+
+const validation = [body('address').exists().isString(), body('contractAddress').exists().isString()];
+
+const controller = async (req: Request, res: Response) => {
+    const ownedNFTs = await getNFTsForOwner(req.body.address, req.body.contractAddress);
+    res.status(200).json(
+        ownedNFTs.map((nft) => {
+            return {
+                balance: nft.balance,
+                name: nft.name,
+                description: nft.description,
+                tokenId: nft.tokenId,
+                tokenUri: nft.tokenUri,
+                image: parseIPFSImageUrl(nft.image.originalUrl),
+            };
+        }),
+    );
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc1155/list.controller.ts b/apps/api/src/app/controllers/erc1155/list.controller.ts
new file mode 100644
index 000000000..a26f607c8
--- /dev/null
+++ b/apps/api/src/app/controllers/erc1155/list.controller.ts
@@ -0,0 +1,13 @@
+import { Request, Response } from 'express';
+import { ERC1155Document } from '@thxnetwork/api/models/ERC1155';
+import ERC1155Service from '@thxnetwork/api/services/ERC1155Service';
+
+const validation = [];
+
+const controller = async (req: Request, res: Response) => {
+    const result = await ERC1155Service.findBySub(req.auth.sub);
+
+    res.json(result.map((erc1155: ERC1155Document) => erc1155._id));
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc1155/metadata/delete.controller.ts b/apps/api/src/app/controllers/erc1155/metadata/delete.controller.ts
new file mode 100644
index 000000000..a6fc60555
--- /dev/null
+++ b/apps/api/src/app/controllers/erc1155/metadata/delete.controller.ts
@@ -0,0 +1,13 @@
+import { param } from 'express-validator';
+import { Request, Response } from 'express';
+import ERC1155Service from '@thxnetwork/api/services/ERC1155Service';
+
+const validation = [param('metadataId').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['ERC1155 Metadata']
+    await ERC1155Service.deleteMetadata(req.params.metadataId);
+    res.status(200).json({ success: true });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc1155/metadata/get.controller.ts b/apps/api/src/app/controllers/erc1155/metadata/get.controller.ts
new file mode 100644
index 000000000..323cf385c
--- /dev/null
+++ b/apps/api/src/app/controllers/erc1155/metadata/get.controller.ts
@@ -0,0 +1,13 @@
+import { param } from 'express-validator';
+import { Request, Response } from 'express';
+import ERC1155Service from '@thxnetwork/api/services/ERC1155Service';
+
+const validation = [param('id').isMongoId(), param('metadataId').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['ERC1155 Metadata']
+    const metadata = await ERC1155Service.findMetadataById(req.params.metadataId);
+    res.json(metadata);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc1155/metadata/list.controller.ts b/apps/api/src/app/controllers/erc1155/metadata/list.controller.ts
new file mode 100644
index 000000000..8735e715c
--- /dev/null
+++ b/apps/api/src/app/controllers/erc1155/metadata/list.controller.ts
@@ -0,0 +1,25 @@
+import { Request, Response } from 'express';
+import ERC1155Service from '@thxnetwork/api/services/ERC1155Service';
+import { param, query } from 'express-validator';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+
+const validation = [
+    param('id').isMongoId(),
+    query('limit').optional().isInt({ gt: 0 }),
+    query('page').optional().isInt({ gt: 0 }),
+];
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['ERC1155']
+    const erc1155 = await ERC1155Service.findById(req.params.id);
+    if (!erc1155) throw new NotFoundError('Could not find this NFT in the database');
+
+    const result = await ERC1155Service.findMetadataByNFT(
+        req.params.id,
+        req.query.page ? Number(req.query.page) : null,
+        req.query.limit ? Number(req.query.limit) : null,
+    );
+    res.json(result);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc1155/metadata/patch.controller.ts b/apps/api/src/app/controllers/erc1155/metadata/patch.controller.ts
new file mode 100644
index 000000000..b4367c831
--- /dev/null
+++ b/apps/api/src/app/controllers/erc1155/metadata/patch.controller.ts
@@ -0,0 +1,34 @@
+import { Request, Response } from 'express';
+import { body, param } from 'express-validator';
+
+import ERC1155Service from '@thxnetwork/api/services/ERC1155Service';
+import { BadRequestError, NotFoundError } from '@thxnetwork/api/util/errors';
+
+const validation = [
+    param('id').isMongoId(),
+    param('metadataId').isMongoId(),
+    body('title').optional().isString().isLength({ min: 0, max: 100 }),
+    body('description').optional().isString().isLength({ min: 0, max: 400 }),
+    body('attributes').exists(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const erc1155 = await ERC1155Service.findById(req.params.id);
+    if (!erc1155) throw new NotFoundError('Could not find this NFT in the database');
+
+    const metadata = await ERC1155Service.findMetadataById(req.params.metadataId);
+    if (!metadata) throw new NotFoundError('Could not find this NFT Metadata in the database');
+
+    const tokens = metadata.tokens || [];
+    if (tokens.length) throw new BadRequestError('There token minted with this metadata');
+
+    await metadata.updateOne({
+        title: req.body.title,
+        description: req.body.description,
+        attributes: req.body.attributes,
+    });
+
+    res.json({ ...metadata.toJSON(), tokens });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc1155/metadata/post.controller.ts b/apps/api/src/app/controllers/erc1155/metadata/post.controller.ts
new file mode 100644
index 000000000..70b1658a4
--- /dev/null
+++ b/apps/api/src/app/controllers/erc1155/metadata/post.controller.ts
@@ -0,0 +1,52 @@
+import { Request, Response } from 'express';
+import { body, param } from 'express-validator';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { ERC1155Metadata } from '@thxnetwork/api/models/ERC1155Metadata';
+import { IPFS_BASE_URL, NODE_ENV } from '@thxnetwork/api/config/secrets';
+import { ERC1155Token } from '@thxnetwork/api/models/ERC1155Token';
+import ERC1155Service from '@thxnetwork/api/services/ERC1155Service';
+import IPFSService from '@thxnetwork/api/services/IPFSService';
+
+const validation = [
+    param('id').isMongoId(),
+    body('name').optional().isString(),
+    body('imageUrl').optional().isURL(),
+    body('description').optional().isString(),
+    body('externalUrl').optional().isURL(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const erc1155 = await ERC1155Service.findById(req.params.id);
+    if (!erc1155) throw new NotFoundError('Could not find this NFT in the database');
+
+    let image = req.body.imageUrl;
+
+    if (req.body.imageUrl && NODE_ENV === 'production') {
+        const cid = await IPFSService.addUrlSource(req.body.imageUrl);
+        image = IPFS_BASE_URL + cid;
+    }
+
+    const erc1155Id = String(erc1155._id);
+    const count = await ERC1155Metadata.countDocuments({ erc1155Id });
+    const tokenId = count + 1;
+    const metadata = await ERC1155Metadata.create({
+        erc1155Id,
+        name: req.body.name,
+        image,
+        imageUrl: req.body.imageUrl,
+        description: req.body.description,
+        externalUrl: req.body.externalUrl,
+        tokenId,
+    });
+
+    // Should also create token
+    await ERC1155Token.create({
+        sub: req.auth.sub,
+        erc1155Id,
+        metadatId: metadata._id,
+        tokenId,
+    });
+
+    res.status(201).json(metadata);
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc1155/patch.controller.ts b/apps/api/src/app/controllers/erc1155/patch.controller.ts
new file mode 100644
index 000000000..88d871ec3
--- /dev/null
+++ b/apps/api/src/app/controllers/erc1155/patch.controller.ts
@@ -0,0 +1,17 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import { ForbiddenError, NotFoundError } from '@thxnetwork/api/util/errors';
+import ERC1155Service from '@thxnetwork/api/services/ERC1155Service';
+
+const validation = [param('id').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['ERC1155']
+    const erc1155 = await ERC1155Service.findById(req.params.id);
+    if (!erc1155) throw new NotFoundError('Could not find the token for this id');
+    if (erc1155.sub !== req.auth.sub) throw new ForbiddenError('Not your ERC721');
+
+    const result = await ERC1155Service.update(erc1155, req.body);
+    return res.json(result);
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc1155/post.controller.ts b/apps/api/src/app/controllers/erc1155/post.controller.ts
new file mode 100644
index 000000000..2224d6c6c
--- /dev/null
+++ b/apps/api/src/app/controllers/erc1155/post.controller.ts
@@ -0,0 +1,37 @@
+import { Request, Response } from 'express';
+import { body, check, query } from 'express-validator';
+import { NFTVariant } from '@thxnetwork/common/enums';
+import ERC1155Service from '@thxnetwork/api/services/ERC1155Service';
+import ImageService from '@thxnetwork/api/services/ImageService';
+
+const validation = [
+    body('name').exists().isString(),
+    body('description').exists().isString(),
+    body('chainId').exists().isNumeric(),
+    check('file')
+        .optional()
+        .custom((value, { req }) => {
+            return ['jpg', 'jpeg', 'gif', 'png'].includes(req.file.mimetype);
+        }),
+    query('forceSync').optional().isBoolean(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['ERC1155']
+    const logoImgUrl = req.file && (await ImageService.upload(req.file));
+    const forceSync = req.query.forceSync !== undefined ? req.query.forceSync === 'true' : false;
+    const erc1155 = await ERC1155Service.deploy(
+        {
+            variant: NFTVariant.ERC1155,
+            sub: req.auth.sub,
+            chainId: req.body.chainId,
+            name: req.body.name,
+            description: req.body.description,
+            logoImgUrl,
+        },
+        forceSync,
+    );
+    res.status(201).json(erc1155);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc1155/token/get.controller.ts b/apps/api/src/app/controllers/erc1155/token/get.controller.ts
new file mode 100644
index 000000000..b8d781b09
--- /dev/null
+++ b/apps/api/src/app/controllers/erc1155/token/get.controller.ts
@@ -0,0 +1,34 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import ERC1155Service from '@thxnetwork/api/services/ERC1155Service';
+import SafeService from '@thxnetwork/api/services/SafeService';
+
+const validation = [param('id').isMongoId(), param('walletId').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const token = await ERC1155Service.queryMintTransaction(await ERC1155Service.findTokenById(req.params.id));
+    if (!token) throw new NotFoundError('ERC1155Token not found');
+
+    const erc1155 = await ERC1155Service.findById(token.erc1155Id);
+    if (!erc1155) throw new NotFoundError('ERC1155 not found');
+
+    const metadata = await ERC1155Service.findMetadataById(token.metadataId);
+    if (!metadata) throw new NotFoundError('ERC1155Metadata not found');
+
+    const wallet = await SafeService.findById(req.query.walletId as string);
+    if (!wallet) throw new NotFoundError('Wallet not found for account');
+
+    const balance = await erc1155.contract.methods.balanceOf(wallet.address, metadata.tokenId).call();
+    const tokenUri = token.tokenId ? await erc1155.contract.methods.uri(token.tokenId).call() : '';
+
+    res.json({
+        ...token.toJSON(),
+        nft: erc1155.toJSON(),
+        metadata: metadata.toJSON(),
+        tokenUri,
+        balance,
+    });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc1155/token/list.controller.ts b/apps/api/src/app/controllers/erc1155/token/list.controller.ts
new file mode 100644
index 000000000..9541e7cb4
--- /dev/null
+++ b/apps/api/src/app/controllers/erc1155/token/list.controller.ts
@@ -0,0 +1,30 @@
+import { Request, Response } from 'express';
+import { ERC1155TokenDocument } from '@thxnetwork/api/models/ERC1155Token';
+import { query } from 'express-validator';
+import { BadRequestError } from '@thxnetwork/api/util/errors';
+import SafeService from '@thxnetwork/api/services/SafeService';
+import ERC1155Service from '@thxnetwork/api/services/ERC1155Service';
+
+const validation = [query('walletId').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const wallet = await SafeService.findById(req.query.walletId as string);
+    if (!wallet) throw new BadRequestError('Wallet not found');
+
+    const tokens = await ERC1155Service.findTokensByWallet(wallet);
+    const result = await Promise.all(
+        tokens.map(async (token: ERC1155TokenDocument) => {
+            const erc1155 = await ERC1155Service.findById(token.erc1155Id);
+            if (!erc1155) return;
+
+            const metadata = await ERC1155Service.findMetadataById(token.metadataId);
+            if (!metadata) return;
+
+            return Object.assign(token.toJSON() as TERC1155Token, { metadata, nft: erc1155 });
+        }),
+    );
+
+    res.json(result.reverse().filter((token) => !!token));
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc1155/transfer/post.controller.ts b/apps/api/src/app/controllers/erc1155/transfer/post.controller.ts
new file mode 100644
index 000000000..3ce087089
--- /dev/null
+++ b/apps/api/src/app/controllers/erc1155/transfer/post.controller.ts
@@ -0,0 +1,42 @@
+import { Request, Response } from 'express';
+import { body } from 'express-validator';
+import { ForbiddenError, NotFoundError } from '@thxnetwork/api/util/errors';
+import { ERC1155Token } from '@thxnetwork/api/models/ERC1155Token';
+import { Transaction } from '@thxnetwork/api/models/Transaction';
+import { ERC1155 } from '@thxnetwork/api/models/ERC1155';
+import ERC1155Service from '@thxnetwork/api/services/ERC1155Service';
+import SafeService from '@thxnetwork/api/services/SafeService';
+
+const validation = [
+    body('walletId').isMongoId(),
+    body('erc1155Id').isMongoId(),
+    body('erc1155TokenId').isMongoId(),
+    body('erc1155Amount').isInt(),
+    body('to').isString(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const erc1155 = await ERC1155.findById(req.body.erc1155Id);
+    if (!erc1155) throw new NotFoundError('Could not find the ERC1155');
+
+    const erc1155Token = await ERC1155Token.findById(req.body.erc1155TokenId);
+    if (!erc1155Token) throw new NotFoundError('Could not find token for wallet');
+
+    const wallet = await SafeService.findById(req.body.walletId);
+    if (!wallet) throw new NotFoundError('Could not find wallet for account');
+
+    const balance = await erc1155.contract.methods.balanceOf(wallet.address, erc1155Token.tokenId).call();
+    if (Number(balance) < Number(req.body.erc1155Amount)) throw new ForbiddenError('Insufficient balance');
+
+    const receiverToken = await ERC1155Service.transferFrom(
+        erc1155,
+        wallet,
+        req.body.to,
+        erc1155Token,
+        req.body.erc1155Amount,
+    );
+    const tx = await Transaction.findById(receiverToken.transactions[0]);
+
+    res.status(201).json(tx);
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc20/allowance/allowance.router.ts b/apps/api/src/app/controllers/erc20/allowance/allowance.router.ts
new file mode 100644
index 000000000..0c07c5e26
--- /dev/null
+++ b/apps/api/src/app/controllers/erc20/allowance/allowance.router.ts
@@ -0,0 +1,16 @@
+import express from 'express';
+import { assertRequestInput, guard } from '@thxnetwork/api/middlewares';
+import * as ListController from './get.controller';
+import * as CreateController from './post.controller';
+
+const router: express.Router = express.Router();
+
+router.post(
+    '/',
+    guard.check(['erc20:read']),
+    assertRequestInput(CreateController.validation),
+    CreateController.controller,
+);
+router.get('/', guard.check(['erc20:read']), assertRequestInput(ListController.validation), ListController.controller);
+
+export default router;
diff --git a/apps/api/src/app/controllers/erc20/allowance/get.controller.ts b/apps/api/src/app/controllers/erc20/allowance/get.controller.ts
new file mode 100644
index 000000000..0a2df2a60
--- /dev/null
+++ b/apps/api/src/app/controllers/erc20/allowance/get.controller.ts
@@ -0,0 +1,27 @@
+import { Request, Response } from 'express';
+import { query } from 'express-validator';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import ContractService from '@thxnetwork/api/services/ContractService';
+import WalletService from '@thxnetwork/api/services/WalletService';
+
+const validation = [
+    query('tokenAddress').isEthereumAddress(),
+    query('spender').isEthereumAddress(),
+    query('walletId').isMongoId(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const walletId = req.query.walletId as string;
+    const wallet = await WalletService.findById(walletId);
+    if (!wallet) throw new NotFoundError('Could not find wallet for account');
+
+    const contract = ContractService.getContract(
+        'THXERC20_LimitedSupply',
+        wallet.chainId,
+        req.query.tokenAddress as string,
+    );
+    const allowance = await contract.allowance(wallet.address, req.query.spender);
+
+    res.json({ allowanceInWei: allowance.toString() });
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc20/allowance/post.controller.ts b/apps/api/src/app/controllers/erc20/allowance/post.controller.ts
new file mode 100644
index 000000000..982c380af
--- /dev/null
+++ b/apps/api/src/app/controllers/erc20/allowance/post.controller.ts
@@ -0,0 +1,40 @@
+import { Request, Response } from 'express';
+import { body, query } from 'express-validator';
+import { ForbiddenError, NotFoundError } from '@thxnetwork/api/util/errors';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { BigNumber } from 'alchemy-sdk';
+import { getArtifact } from '@thxnetwork/api/hardhat';
+import TransactionService from '@thxnetwork/api/services/TransactionService';
+import WalletService from '@thxnetwork/api/services/WalletService';
+
+const validation = [
+    body('tokenAddress').isEthereumAddress(),
+    body('spender').isEthereumAddress(),
+    body('amountInWei').isString(),
+    query('walletId').isMongoId(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const walletId = req.query.walletId as string;
+    const wallet = await WalletService.findById(walletId);
+    if (!wallet) throw new NotFoundError('Wallet not found');
+    if (wallet.sub !== req.auth.sub) throw new ForbiddenError('Wallet not owned by sub.');
+
+    const { web3 } = getProvider(wallet.chainId);
+    const { abi } = getArtifact('THXERC20_LimitedSupply');
+    const contract = new web3.eth.Contract(abi, req.body.tokenAddress);
+    const amount = await contract.methods.balanceOf(wallet.address).call();
+
+    // Check sufficient BPT Balance
+    if (BigNumber.from(amount).lt(BigNumber.from(req.body.amountInWei))) {
+        throw new ForbiddenError('Insufficient balance');
+    }
+
+    const fn = contract.methods.approve(req.body.spender, req.body.amountInWei);
+
+    // Propose tx data to relayer and return safeTxHash to client to sign
+    const tx = await TransactionService.sendSafeAsync(wallet, contract.options.address, fn);
+
+    res.status(201).json([tx]);
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc20/balance/balance.router.ts b/apps/api/src/app/controllers/erc20/balance/balance.router.ts
new file mode 100644
index 000000000..3b176152d
--- /dev/null
+++ b/apps/api/src/app/controllers/erc20/balance/balance.router.ts
@@ -0,0 +1,9 @@
+import express from 'express';
+import { assertRequestInput, guard } from '@thxnetwork/api/middlewares';
+import * as ReadController from './get.controller';
+
+const router: express.Router = express.Router();
+
+router.get('/', guard.check(['erc20:read']), assertRequestInput(ReadController.validation), ReadController.controller);
+
+export default router;
diff --git a/apps/api/src/app/controllers/erc20/balance/get.controller.ts b/apps/api/src/app/controllers/erc20/balance/get.controller.ts
new file mode 100644
index 000000000..31e944cfe
--- /dev/null
+++ b/apps/api/src/app/controllers/erc20/balance/get.controller.ts
@@ -0,0 +1,23 @@
+import { Request, Response } from 'express';
+import { query } from 'express-validator';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import ContractService from '@thxnetwork/api/services/ContractService';
+import WalletService from '@thxnetwork/api/services/WalletService';
+
+const validation = [query('walletId').isMongoId(), query('tokenAddress').isEthereumAddress()];
+
+const controller = async (req: Request, res: Response) => {
+    const walletId = req.query.walletId as string;
+    const wallet = await WalletService.findById(walletId);
+    if (!wallet) throw new NotFoundError('Wallet not found');
+
+    const contract = ContractService.getContract(
+        'THXERC20_LimitedSupply',
+        wallet.chainId,
+        req.query.tokenAddress as string,
+    );
+    const balance = await contract.balanceOf(wallet.address);
+
+    res.json({ balanceInWei: balance.toString() });
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc20/delete.controller.ts b/apps/api/src/app/controllers/erc20/delete.controller.ts
new file mode 100644
index 000000000..79e2529df
--- /dev/null
+++ b/apps/api/src/app/controllers/erc20/delete.controller.ts
@@ -0,0 +1,12 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import { ERC20 } from '@thxnetwork/api/models';
+
+const validation = [param('id').exists().isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    await ERC20.deleteOne({ _id: req.params.id });
+    return res.status(204).end();
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc20/erc20.router.ts b/apps/api/src/app/controllers/erc20/erc20.router.ts
new file mode 100644
index 000000000..4b5839b72
--- /dev/null
+++ b/apps/api/src/app/controllers/erc20/erc20.router.ts
@@ -0,0 +1,72 @@
+import express from 'express';
+import { assertRequestInput, guard } from '@thxnetwork/api/middlewares';
+import { upload } from '@thxnetwork/api/util/multer';
+
+import RouterAllowance from './allowance/allowance.router';
+import RouterTransfer from './transfer/transfer.router';
+import RouterBalance from './balance/balance.router';
+import RouterPreview from './preview/preview.router';
+
+import * as CreateController from './post.controller';
+import * as ReadController from './get.controller';
+import * as UpdateController from './patch.controller';
+import * as DeleteController from './delete.controller';
+import * as ListController from './list.controller';
+
+import * as ListERC20Token from './token/list.controller';
+import * as ReadERC20Token from './token/get.controller';
+import * as ImportERC20 from './token/post.controller';
+
+const router: express.Router = express.Router();
+
+router.use('/transfer', RouterTransfer);
+router.use('/balance', RouterBalance);
+router.use('/allowance', RouterAllowance);
+router.use('/preview', RouterPreview);
+
+// Token Resource should move into /wallet
+router.get(
+    '/token',
+    guard.check(['erc20:read']),
+    assertRequestInput(ListERC20Token.validation),
+    ListERC20Token.controller,
+);
+router.get('/token/:id', guard.check(['erc20:read']), ReadERC20Token.controller);
+
+// Should be /import controller
+router.post(
+    '/token',
+    guard.check(['erc20:write', 'erc20:read']),
+    assertRequestInput(ImportERC20.validation),
+    ImportERC20.controller,
+);
+// End
+
+router.post(
+    '/',
+    upload.single('file'),
+    guard.check(['erc20:write', 'erc20:read']),
+    assertRequestInput(CreateController.validation),
+    CreateController.controller,
+);
+router.get(
+    '/:id',
+    guard.check(['erc20:read']),
+    assertRequestInput(ReadController.validation),
+    ReadController.controller,
+);
+router.patch(
+    '/:id',
+    guard.check(['erc20:write', 'erc20:read']),
+    assertRequestInput(UpdateController.validation),
+    UpdateController.controller,
+);
+router.delete(
+    '/:id',
+    guard.check(['erc20:write']),
+    assertRequestInput(DeleteController.validation),
+    DeleteController.controller,
+);
+router.get('/', guard.check(['erc20:read']), assertRequestInput(ListController.validation), ListController.controller);
+
+export default router;
diff --git a/apps/api/src/app/controllers/erc20/erc20.test.ts b/apps/api/src/app/controllers/erc20/erc20.test.ts
new file mode 100644
index 000000000..79c1aac86
--- /dev/null
+++ b/apps/api/src/app/controllers/erc20/erc20.test.ts
@@ -0,0 +1,129 @@
+import request from 'supertest';
+import app from '@thxnetwork/api/';
+import { ChainId, ERC20Type } from '@thxnetwork/common/enums';
+import { afterAllCallback, beforeAllCallback } from '@thxnetwork/api/util/jest/config';
+import { dashboardAccessToken } from '@thxnetwork/api/util/jest/constants';
+import { createImage } from '@thxnetwork/api/util/jest/images';
+import { toWei } from 'web3-utils';
+import { isAddress } from 'ethers/lib/utils';
+
+const http = request.agent(app);
+
+describe('ERC20', () => {
+    const totalSupply = toWei('1000'),
+        name = 'Test Token',
+        symbol = 'TTK';
+    let tokenAddress: string, tokenName: string, tokenSymbol: string, erc20Id: string;
+
+    beforeAll(beforeAllCallback);
+    afterAll(afterAllCallback);
+
+    describe('POST /erc20', () => {
+        it('Able to create unlimited token and return address', (done) => {
+            http.post('/v1/erc20')
+                .set('Authorization', dashboardAccessToken)
+                .send({
+                    name: 'Test Token',
+                    symbol: 'TTK',
+                    chainId: ChainId.Hardhat,
+                    totalSupply: 0,
+                    type: ERC20Type.Unlimited,
+                })
+                .expect(({ body }: request.Response) => {
+                    expect(body._id).toBeDefined();
+                    expect(isAddress(body.address)).toBe(true);
+                })
+                .expect(201, done);
+        });
+
+        it('Able to create limited token and return address', async () => {
+            const image = createImage();
+            await http
+                .post('/v1/erc20')
+                .set('Authorization', dashboardAccessToken)
+                .attach('file', image, {
+                    filename: 'test.jpg',
+                    contentType: 'image/jpg',
+                })
+                .field({
+                    name,
+                    symbol,
+                    chainId: ChainId.Hardhat,
+                    totalSupply,
+                    type: ERC20Type.Limited,
+                })
+                .expect(({ body }: request.Response) => {
+                    expect(isAddress(body._id)).toBeDefined();
+                    expect(isAddress(body.address)).toBe(true);
+                    expect(body.logoImgUrl).toBeDefined();
+                    erc20Id = body._id;
+                    tokenAddress = body.address;
+                    tokenName = body.name;
+                    tokenSymbol = body.symbol;
+                })
+                .expect(201);
+        });
+
+        it('Able to return list of created token', (done) => {
+            http.get('/v1/erc20')
+                .set('Authorization', dashboardAccessToken)
+                .expect(({ body }: request.Response) => {
+                    expect(body.length).toEqual(2);
+                })
+                .expect(200, done);
+        });
+
+        it('Able to return a created token', (done) => {
+            http.get('/v1/erc20/' + erc20Id)
+                .set('Authorization', dashboardAccessToken)
+                .expect(({ body }: request.Response) => {
+                    expect(body).toBeDefined();
+                    expect(isAddress(body.address)).toBe(true);
+                    expect(body.type).toBe(ERC20Type.Limited);
+                    expect(body.totalSupplyInWei).toBe(totalSupply);
+                    expect(body.name).toBe(name);
+                    expect(body.symbol).toBe(symbol);
+                    expect(body.decimals).toBe(18);
+                })
+                .expect(200, done);
+        });
+    });
+
+    describe('PATCH /erc20', () => {
+        it('should to update a created token', (done) => {
+            http.patch('/v1/erc20/' + erc20Id)
+                .set('Authorization', dashboardAccessToken)
+                .send()
+                .expect(({ body }: request.Response) => {
+                    expect(body).toBeDefined();
+                })
+                .expect(200, done);
+        });
+    });
+    describe('DELETE /erc20/:id', () => {
+        it('Able to delete created token', (done) => {
+            http.delete('/v1/erc20/' + erc20Id)
+                .set('Authorization', dashboardAccessToken)
+                .expect(204, done);
+        });
+    });
+
+    describe('POST /erc20/preview', () => {
+        it('should return name symbol and total supply of an oncChain ERC20Token', (done) => {
+            http.get('/v1/erc20/preview')
+                .set('Authorization', dashboardAccessToken)
+                .query({
+                    chainId: ChainId.Hardhat,
+                    address: tokenAddress,
+                })
+                .send()
+                .expect(({ body }: request.Response) => {
+                    expect(body).toBeDefined();
+                    expect(body.name).toBe(tokenName);
+                    expect(body.symbol).toBe(tokenSymbol);
+                    expect(body.totalSupplyInWei).toBe(totalSupply);
+                })
+                .expect(200, done);
+        });
+    });
+});
diff --git a/apps/api/src/app/controllers/erc20/get.controller.ts b/apps/api/src/app/controllers/erc20/get.controller.ts
new file mode 100644
index 000000000..0f49f8840
--- /dev/null
+++ b/apps/api/src/app/controllers/erc20/get.controller.ts
@@ -0,0 +1,37 @@
+import { Request, Response } from 'express';
+import ERC20Service from '@thxnetwork/api/services/ERC20Service';
+import { param } from 'express-validator';
+import { fromWei } from 'web3-utils';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+
+const validation = [param('id').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    let erc20 = await ERC20Service.queryDeployTransaction(await ERC20Service.getById(req.params.id));
+    if (!erc20) throw new NotFoundError('ERC20 not found');
+
+    // Check if pending transaction is mined.
+    if (!erc20.address) erc20 = await ERC20Service.queryDeployTransaction(erc20);
+
+    // Still no address.
+    if (!erc20.address) return res.send(erc20);
+
+    const { defaultAccount } = getProvider(erc20.chainId);
+    const [totalSupplyInWei, decimalsString, adminBalanceInWei] = await Promise.all([
+        erc20.contract.methods.totalSupply().call(),
+        erc20.contract.methods.decimals().call(),
+        erc20.contract.methods.balanceOf(defaultAccount).call(),
+    ]);
+    const decimals = Number(decimalsString);
+    const adminBalance = Number(fromWei(adminBalanceInWei, 'ether'));
+
+    res.status(200).json({
+        ...erc20.toJSON(),
+        totalSupplyInWei,
+        decimals,
+        adminBalance,
+    });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc20/list.controller.ts b/apps/api/src/app/controllers/erc20/list.controller.ts
new file mode 100644
index 000000000..40d5ecc8c
--- /dev/null
+++ b/apps/api/src/app/controllers/erc20/list.controller.ts
@@ -0,0 +1,11 @@
+import ERC20Service from '@thxnetwork/api/services/ERC20Service';
+import { Request, Response } from 'express';
+
+const validation = [];
+
+const controller = async (req: Request, res: Response) => {
+    const erc20s = await ERC20Service.findBySub(req.auth.sub);
+    return res.json(erc20s);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc20/patch.controller.ts b/apps/api/src/app/controllers/erc20/patch.controller.ts
new file mode 100644
index 000000000..c1b8b88ae
--- /dev/null
+++ b/apps/api/src/app/controllers/erc20/patch.controller.ts
@@ -0,0 +1,15 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import ERC20Service from '@thxnetwork/api/services/ERC20Service';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+
+const validation = [param('id').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const erc20 = await ERC20Service.getById(req.params.id);
+    if (!erc20) throw new NotFoundError('Could not find the token for this id');
+
+    const result = await ERC20Service.update(erc20, req.body);
+    return res.json(result);
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc20/post.controller.ts b/apps/api/src/app/controllers/erc20/post.controller.ts
new file mode 100644
index 000000000..902fe4be3
--- /dev/null
+++ b/apps/api/src/app/controllers/erc20/post.controller.ts
@@ -0,0 +1,39 @@
+import { Request, Response } from 'express';
+import { body, check, query } from 'express-validator';
+import ERC20Service from '@thxnetwork/api/services/ERC20Service';
+import ImageService from '@thxnetwork/api/services/ImageService';
+
+const validation = [
+    body('name').exists().isString(),
+    body('symbol').exists().isString(),
+    body('chainId').exists().isNumeric(),
+    body('type').exists().isNumeric(),
+    body('totalSupply').optional().isNumeric(),
+    check('file')
+        .optional()
+        .custom((value, { req }) => {
+            return ['jpg', 'jpeg', 'gif', 'png'].includes(req.file.mimetype);
+        }),
+    query('forceSync').optional().isBoolean(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const logoImgUrl = req.file && (await ImageService.upload(req.file));
+    const forceSync = req.query.forceSync !== undefined ? req.query.forceSync === 'true' : false;
+
+    const erc20 = await ERC20Service.deploy(
+        {
+            name: req.body.name,
+            symbol: req.body.symbol,
+            chainId: req.body.chainId,
+            totalSupply: req.body.totalSupply,
+            type: req.body.type,
+            sub: req.auth.sub,
+            logoImgUrl,
+        },
+        forceSync,
+    );
+
+    res.status(201).json(erc20);
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc20/preview/get.controller.ts b/apps/api/src/app/controllers/erc20/preview/get.controller.ts
new file mode 100644
index 000000000..efc11edb9
--- /dev/null
+++ b/apps/api/src/app/controllers/erc20/preview/get.controller.ts
@@ -0,0 +1,20 @@
+import { Request, Response } from 'express';
+import { query } from 'express-validator';
+import { ChainId } from '@thxnetwork/common/enums';
+import ContractService from '@thxnetwork/api/services/ContractService';
+
+const validation = [query('chainId').isInt(), query('address').isEthereumAddress()];
+
+const controller = async (req: Request, res: Response) => {
+    const chainId = req.query.chainId as unknown as ChainId;
+    const contractAddress = req.query.address as string;
+    const contract = ContractService.getContract('THXERC20_LimitedSupply', chainId, contractAddress);
+    const [name, symbol, totalSupplyInWei] = await Promise.all([
+        contract.name(),
+        contract.symbol(),
+        contract.totalSupply(),
+    ]);
+
+    res.json({ name, symbol, totalSupplyInWei: totalSupplyInWei.toString() });
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc20/preview/preview.router.ts b/apps/api/src/app/controllers/erc20/preview/preview.router.ts
new file mode 100644
index 000000000..3b176152d
--- /dev/null
+++ b/apps/api/src/app/controllers/erc20/preview/preview.router.ts
@@ -0,0 +1,9 @@
+import express from 'express';
+import { assertRequestInput, guard } from '@thxnetwork/api/middlewares';
+import * as ReadController from './get.controller';
+
+const router: express.Router = express.Router();
+
+router.get('/', guard.check(['erc20:read']), assertRequestInput(ReadController.validation), ReadController.controller);
+
+export default router;
diff --git a/apps/api/src/app/controllers/erc20/token/get.controller.ts b/apps/api/src/app/controllers/erc20/token/get.controller.ts
new file mode 100644
index 000000000..a34aa3da7
--- /dev/null
+++ b/apps/api/src/app/controllers/erc20/token/get.controller.ts
@@ -0,0 +1,31 @@
+import { Request, Response } from 'express';
+import { param, query } from 'express-validator';
+import { fromWei } from 'web3-utils';
+import { BadRequestError, NotFoundError } from '@thxnetwork/api/util/errors';
+import ERC20Service from '@thxnetwork/api/services/ERC20Service';
+import WalletService from '@thxnetwork/api/services/WalletService';
+
+const validation = [param('id').isMongoId(), query('walletId').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const token = await ERC20Service.getTokenById(req.params.id);
+    if (!token) throw new NotFoundError('ERC20Token not found');
+
+    const erc20 = await ERC20Service.getById(token.erc20Id);
+    if (!erc20) throw new NotFoundError('ERC20 not found');
+
+    const wallet = await WalletService.findById(req.query.walletId as string);
+    if (!wallet) throw new BadRequestError('Wallet not found');
+
+    const walletBalanceInWei = await erc20.contract.methods.balanceOf(wallet.address).call();
+    const walletBalance = Number(fromWei(walletBalanceInWei, 'ether'));
+
+    res.json({
+        ...token.toJSON(),
+        walletBalanceInWei,
+        walletBalance,
+        erc20,
+    });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc20/token/list.controller.ts b/apps/api/src/app/controllers/erc20/token/list.controller.ts
new file mode 100644
index 000000000..b9997026d
--- /dev/null
+++ b/apps/api/src/app/controllers/erc20/token/list.controller.ts
@@ -0,0 +1,22 @@
+import { Request, Response } from 'express';
+import { query } from 'express-validator';
+import { BadRequestError } from '@thxnetwork/api/util/errors';
+import ERC20Service from '@thxnetwork/api/services/ERC20Service';
+import SafeService from '@thxnetwork/api/services/SafeService';
+
+const validation = [query('walletId').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const wallet = await SafeService.findById(req.query.walletId as string);
+    if (!wallet) throw new BadRequestError('Wallet not found');
+
+    const tokens = await ERC20Service.getTokensForWallet(wallet);
+
+    res.json(
+        tokens.reverse().filter((token: TERC20Token & { erc20: TERC20 }) => {
+            return token && wallet.chainId === token.erc20.chainId;
+        }),
+    );
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc20/token/post.controller.ts b/apps/api/src/app/controllers/erc20/token/post.controller.ts
new file mode 100644
index 000000000..9142aa86d
--- /dev/null
+++ b/apps/api/src/app/controllers/erc20/token/post.controller.ts
@@ -0,0 +1,21 @@
+import { Request, Response } from 'express';
+import { body } from 'express-validator';
+import ERC20Service from '@thxnetwork/api/services/ERC20Service';
+
+const validation = [
+    body('address').exists().isString(),
+    body('chainId').exists().isInt(),
+    body('logoImgUrl').optional().isString(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const erc20 = await ERC20Service.importToken(
+        Number(req.body.chainId),
+        req.body.address,
+        req.auth.sub,
+        req.body.logoImgUrl,
+    );
+
+    res.status(201).json(erc20);
+};
+export { controller, validation };
diff --git a/apps/campaign/.gitkeep b/apps/api/src/app/controllers/erc20/token/token.router.ts
similarity index 100%
rename from apps/campaign/.gitkeep
rename to apps/api/src/app/controllers/erc20/token/token.router.ts
diff --git a/apps/api/src/app/controllers/erc20/transfer/erc20-transfer.test.ts b/apps/api/src/app/controllers/erc20/transfer/erc20-transfer.test.ts
new file mode 100644
index 000000000..48fd89d29
--- /dev/null
+++ b/apps/api/src/app/controllers/erc20/transfer/erc20-transfer.test.ts
@@ -0,0 +1,104 @@
+import request from 'supertest';
+import app from '@thxnetwork/api/';
+import { WalletDocument, ERC20, ERC20Document } from '@thxnetwork/api/models';
+import { ChainId, ERC20Type, WalletVariant } from '@thxnetwork/common/enums';
+import { afterAllCallback, beforeAllCallback } from '@thxnetwork/api/util/jest/config';
+import {
+    dashboardAccessToken,
+    userWalletAddress2,
+    userWalletPrivateKey,
+    widgetAccessToken,
+} from '@thxnetwork/api/util/jest/constants';
+import { toWei } from 'web3-utils';
+import { poll } from '@thxnetwork/api/util/polling';
+import { signTxHash } from '@thxnetwork/api/util/jest/network';
+
+const user = request.agent(app);
+
+describe('ERC20 Transfer', () => {
+    let erc20: ERC20Document, wallet: WalletDocument;
+
+    beforeAll(beforeAllCallback);
+    afterAll(afterAllCallback);
+
+    describe('POST /erc20', () => {
+        it('HTTP 201', (done) => {
+            user.post('/v1/erc20')
+                .set('Authorization', dashboardAccessToken)
+                .send({
+                    name: 'Test Token',
+                    symbol: 'TTK',
+                    totalSupply: toWei('100', 'ether'),
+                    type: ERC20Type.Limited,
+                    chainId: ChainId.Hardhat,
+                })
+                .expect(({ body }: request.Response) => {
+                    expect(body._id).toBeDefined();
+                    erc20 = body;
+                })
+                .expect(201, done);
+        });
+    });
+
+    describe('GET /wallet and transfer erc20', () => {
+        it('HTTP 200', (done) => {
+            user.get('/v1/account/wallets')
+                .set({ Authorization: widgetAccessToken })
+                .send()
+                .expect(async ({ body }: request.Response) => {
+                    wallet = body.find((w: WalletDocument) => w.variant === WalletVariant.Safe);
+                    expect(wallet).toBeDefined();
+                    expect(wallet.address).toBeDefined();
+                })
+                .expect(200, done);
+        });
+
+        it('Transfer ERC20', async () => {
+            const { contract } = await ERC20.findById(erc20._id);
+            await contract.methods.transfer(wallet.address, toWei('100', 'ether')).send();
+
+            const balanceInWei = await contract.methods.balanceOf(wallet.address).call();
+            expect(balanceInWei).toBe(toWei('100', 'ether'));
+        });
+    });
+
+    describe('POST /erc20/transfer', () => {
+        it('HTTP 201', async () => {
+            const res = await user
+                .post('/v1/erc20/transfer')
+                .set({ Authorization: widgetAccessToken })
+                .send({
+                    walletId: String(wallet._id),
+                    erc20Id: erc20._id,
+                    to: userWalletAddress2,
+                    amount: toWei('1', 'ether'),
+                    chainId: ChainId.Hardhat,
+                });
+            expect(res.body.safeTxHash).toBeDefined();
+            expect(res.status).toBe(201);
+
+            const { safeTxHash, signature } = await signTxHash(
+                wallet.address,
+                res.body.safeTxHash,
+                userWalletPrivateKey,
+            );
+            const res2 = await user
+                .post(`/v1/account/wallets/confirm`)
+                .set({ Authorization: widgetAccessToken })
+                .query({ walletId: String(wallet._id) })
+                .send({ chainId: ChainId.Hardhat, safeTxHash, signature });
+
+            expect(res2.status).toBe(200);
+        });
+        it('Wait for balance', async () => {
+            const { contract } = await ERC20.findById(erc20._id);
+            await poll(
+                contract.methods.balanceOf(userWalletAddress2).call,
+                (result: string) => result !== toWei('1', 'ether'),
+                1000,
+            );
+            const balanceInWei = await contract.methods.balanceOf(userWalletAddress2).call();
+            expect(balanceInWei).toEqual(toWei('1', 'ether'));
+        });
+    });
+});
diff --git a/apps/api/src/app/controllers/erc20/transfer/post.controller.ts b/apps/api/src/app/controllers/erc20/transfer/post.controller.ts
new file mode 100644
index 000000000..c741ccb32
--- /dev/null
+++ b/apps/api/src/app/controllers/erc20/transfer/post.controller.ts
@@ -0,0 +1,32 @@
+import { Request, Response } from 'express';
+import { body } from 'express-validator';
+import { InsufficientBalanceError, NotFoundError } from '@thxnetwork/api/util/errors';
+import { BN } from 'bn.js';
+import { ERC20 } from '@thxnetwork/api/models';
+import SafeService from '@thxnetwork/api/services/SafeService';
+import ERC20Service from '@thxnetwork/api/services/ERC20Service';
+
+const validation = [
+    body('walletId').isMongoId(),
+    body('erc20Id').isMongoId(),
+    body('to').isString(),
+    body('amount').isString(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const erc20 = await ERC20.findById(req.body.erc20Id);
+    if (!erc20) throw new NotFoundError('Could not find the ERC20');
+
+    const wallet = await SafeService.findById(req.body.walletId);
+    if (!wallet) throw new NotFoundError('Could not find wallet for account');
+
+    const walletBalanceInWei = await erc20.contract.methods.balanceOf(wallet.address).call();
+    const balanceInWei = new BN(walletBalanceInWei);
+    const amountInWei = new BN(req.body.amount);
+    if (amountInWei.gt(balanceInWei)) throw new InsufficientBalanceError();
+
+    const tx = await ERC20Service.transferFrom(erc20, wallet, req.body.to, String(amountInWei));
+
+    res.status(201).json(tx);
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc20/transfer/transfer.router.ts b/apps/api/src/app/controllers/erc20/transfer/transfer.router.ts
new file mode 100644
index 000000000..36036cb0d
--- /dev/null
+++ b/apps/api/src/app/controllers/erc20/transfer/transfer.router.ts
@@ -0,0 +1,9 @@
+import express from 'express';
+import { assertRequestInput } from '@thxnetwork/api/middlewares';
+import * as CreateController from './post.controller';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.post('/', assertRequestInput(CreateController.validation), CreateController.controller);
+
+export default router;
diff --git a/apps/api/src/app/controllers/erc721/delete.controller.ts b/apps/api/src/app/controllers/erc721/delete.controller.ts
new file mode 100644
index 000000000..693743e22
--- /dev/null
+++ b/apps/api/src/app/controllers/erc721/delete.controller.ts
@@ -0,0 +1,16 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import { ForbiddenError } from '@thxnetwork/api/util/errors';
+import { ERC721 } from '@thxnetwork/api/models/ERC721';
+
+const validation = [param('id').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const erc721 = await ERC721.findById(req.params.id);
+    if (erc721.sub !== req.auth.sub) throw new ForbiddenError('Not your ERC721');
+
+    await erc721.deleteOne();
+
+    return res.status(204).end();
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc721/erc721.router.ts b/apps/api/src/app/controllers/erc721/erc721.router.ts
new file mode 100644
index 000000000..a1a79c610
--- /dev/null
+++ b/apps/api/src/app/controllers/erc721/erc721.router.ts
@@ -0,0 +1,107 @@
+import express from 'express';
+import { assertPoolAccess, assertRequestInput, guard } from '@thxnetwork/api/middlewares';
+import { upload } from '@thxnetwork/api/util/multer';
+
+import * as ReadERC721 from './get.controller';
+import * as ListERC721 from './list.controller';
+import * as ListERC721Metadata from './metadata/list.controller';
+import * as ListERC721Token from './token/list.controller';
+import * as ReadERC721Token from './token/get.controller';
+import * as RemoveERC721 from './delete.controller';
+import * as CreateERC721 from './post.controller';
+import * as CreateMultipleERC721Metadata from './metadata/images/post.controller';
+import * as UpdateERC721 from './patch.controller';
+import * as ReadERC721Metadata from './metadata/get.controller';
+import * as CreateERC721Metadata from './metadata/post.controller';
+import * as PatchERC721Metadata from './metadata/patch.controller';
+import * as DeleteERC721Metadata from './metadata/delete.controller';
+import * as ImportERC721Contract from './import/post.controller';
+import * as PreviewERC721Contract from './import/preview/post.controller';
+import * as CreateERC721Transfer from './transfer/post.controller';
+
+const router: express.Router = express.Router();
+
+router.get(
+    '/token',
+    guard.check(['erc721:read']),
+    assertRequestInput(ListERC721Token.validation),
+    ListERC721Token.controller,
+);
+router.get('/token/:id', guard.check(['erc721:read']), ReadERC721Token.controller);
+router.get('/', guard.check(['erc721:read']), assertRequestInput(ListERC721.validation), ListERC721.controller);
+router.get('/:id', guard.check(['erc721:read']), assertRequestInput(ReadERC721.validation), ReadERC721.controller);
+
+router.post(
+    '/',
+    upload.single('file'),
+    guard.check(['erc721:read', 'erc721:write']),
+    assertRequestInput(CreateERC721.validation),
+    CreateERC721.controller,
+);
+
+router.post(
+    '/transfer',
+    // guard.check(['erc721_transfer:read', 'erc721_transfer:write']),
+    assertRequestInput(CreateERC721Transfer.validation),
+    CreateERC721Transfer.controller,
+);
+router.post(
+    '/import',
+    ImportERC721Contract.controller,
+    assertPoolAccess,
+    assertRequestInput(ImportERC721Contract.validation),
+);
+router.post('/preview', assertRequestInput(PreviewERC721Contract.validation), PreviewERC721Contract.controller);
+router.patch(
+    '/:id/metadata/:metadataId',
+    guard.check(['erc721:write']),
+    assertRequestInput(PatchERC721Metadata.validation),
+    PatchERC721Metadata.controller,
+);
+
+router.delete(
+    '/:id/metadata/:metadataId',
+    guard.check(['erc721:write']),
+    assertRequestInput(DeleteERC721Metadata.validation),
+    DeleteERC721Metadata.controller,
+);
+
+router.get('/:id/metadata', guard.check(['erc721:read']), ListERC721Metadata.controller);
+
+router.post(
+    '/:id/metadata/',
+    guard.check(['erc721:write']),
+    assertRequestInput(CreateERC721Metadata.validation),
+    CreateERC721Metadata.controller,
+);
+
+router.post(
+    '/:id/metadata/zip',
+    upload.single('file'),
+    guard.check(['erc721:write']),
+    assertRequestInput(CreateMultipleERC721Metadata.validation),
+    CreateMultipleERC721Metadata.controller,
+);
+
+router.patch(
+    '/:id',
+    guard.check(['erc721:write', 'erc721:read']),
+    assertRequestInput(UpdateERC721.validation),
+    UpdateERC721.controller,
+);
+
+router.get(
+    '/:id/metadata/:metadataId',
+    guard.check(['erc721:read']),
+    ReadERC721Metadata.controller,
+    assertRequestInput(ReadERC721Metadata.validation),
+);
+
+router.delete(
+    '/:id',
+    guard.check(['erc721:read', 'erc721:write']),
+    assertRequestInput(RemoveERC721.validation),
+    RemoveERC721.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/erc721/erc721.test.ts b/apps/api/src/app/controllers/erc721/erc721.test.ts
new file mode 100644
index 000000000..22f962f5b
--- /dev/null
+++ b/apps/api/src/app/controllers/erc721/erc721.test.ts
@@ -0,0 +1,93 @@
+import request from 'supertest';
+import app from '@thxnetwork/api/';
+import { ChainId } from '@thxnetwork/common/enums';
+import { isAddress } from 'web3-utils';
+import { afterAllCallback, beforeAllCallback } from '@thxnetwork/api/util/jest/config';
+import { dashboardAccessToken } from '@thxnetwork/api/util/jest/constants';
+import { createImage } from '@thxnetwork/api/util/jest/images';
+
+const user = request.agent(app);
+
+describe('ERC721', () => {
+    const chainId = ChainId.Hardhat,
+        name = 'Planets of the Galaxy',
+        symbol = 'GLXY',
+        description = 'Collection full of rarities.';
+    let erc721ID: string;
+
+    beforeAll(beforeAllCallback);
+    afterAll(afterAllCallback);
+
+    describe('POST /erc721', () => {
+        it('should create and return contract details', async () => {
+            const logoImg = createImage();
+            await user
+                .post('/v1/erc721')
+                .set('Authorization', dashboardAccessToken)
+                .attach('file', logoImg, { filename: 'logoImg.jpg', contentType: 'image/jpg' })
+                .field({
+                    chainId,
+                    name,
+                    symbol,
+                    description,
+                })
+                .expect(({ body }: request.Response) => {
+                    expect(body._id).toBeDefined();
+                    expect(body.chainId).toBe(chainId);
+                    expect(body.name).toBe(name);
+                    expect(body.symbol).toBe(symbol);
+                    expect(body.description).toBe(description);
+                    expect(isAddress(body.address)).toBe(true);
+                    expect(body.logoImgUrl).toBeDefined();
+                    erc721ID = body._id;
+                })
+                .expect(201);
+        });
+    });
+
+    describe('GET /erc721/:id', () => {
+        it('should return contract details', (done) => {
+            user.get('/v1/erc721/' + erc721ID)
+                .set('Authorization', dashboardAccessToken)
+                .send()
+                .expect(({ body }: request.Response) => {
+                    expect(body.chainId).toBe(chainId);
+                    expect(body.name).toBe(name);
+                    expect(body.symbol).toBe(symbol);
+                    expect(body.description).toBe(description);
+                    expect(isAddress(body.address)).toBe(true);
+                    expect(body.logoImgUrl).toBeDefined();
+                })
+                .expect(200, done);
+        });
+        it('should 400 for invalid ID', (done) => {
+            user.get('/v1/erc721/' + 'invalid_id')
+                .set('Authorization', dashboardAccessToken)
+                .send()
+                .expect(({ body }: request.Response) => {
+                    expect(body.errors[0].msg).toContain('Invalid value');
+                })
+                .expect(400, done);
+        });
+        it('should 404 if not known', (done) => {
+            user.get('/v1/erc721/' + '62397f69760ac5f9ab4454df')
+                .set('Authorization', dashboardAccessToken)
+                .send()
+                .expect(({ body }: request.Response) => {
+                    expect(body.error.message).toContain('Not Found');
+                })
+                .expect(404, done);
+        });
+        describe('PATCH /erc721/:id', () => {
+            it('should update a created token', (done) => {
+                user.patch('/v1/erc721/' + erc721ID)
+                    .set('Authorization', dashboardAccessToken)
+                    .send()
+                    .expect(({ body }: request.Response) => {
+                        expect(body).toBeDefined();
+                    })
+                    .expect(200, done);
+            });
+        });
+    });
+});
diff --git a/apps/api/src/app/controllers/erc721/get.controller.ts b/apps/api/src/app/controllers/erc721/get.controller.ts
new file mode 100644
index 000000000..f4451041d
--- /dev/null
+++ b/apps/api/src/app/controllers/erc721/get.controller.ts
@@ -0,0 +1,28 @@
+import { param } from 'express-validator';
+import { Request, Response } from 'express';
+import ERC721Service from '@thxnetwork/api/services/ERC721Service';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+
+const validation = [param('id').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    let erc721 = await ERC721Service.findById(req.params.id);
+    if (!erc721) throw new NotFoundError();
+
+    // Check if pending transaction is mined.
+    if (!erc721.address) {
+        erc721 = await ERC721Service.queryDeployTransaction(erc721);
+    }
+
+    // Still no address.
+    if (!erc721.address) {
+        return res.send(erc721);
+    }
+
+    const totalSupply = await erc721.contract.methods.totalSupply().call();
+    const owner = await erc721.contract.methods.owner().call();
+
+    res.json({ ...erc721.toJSON(), totalSupply, owner });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc721/import/erc721-import.test.ts b/apps/api/src/app/controllers/erc721/import/erc721-import.test.ts
new file mode 100644
index 000000000..e146ef49d
--- /dev/null
+++ b/apps/api/src/app/controllers/erc721/import/erc721-import.test.ts
@@ -0,0 +1,102 @@
+import request from 'supertest';
+import app from '@thxnetwork/api/';
+import { Contract } from 'web3-eth-contract';
+import { ChainId } from '@thxnetwork/common/enums';
+import { afterAllCallback, beforeAllCallback } from '@thxnetwork/api/util/jest/config';
+import { dashboardAccessToken, sub } from '@thxnetwork/api/util/jest/constants';
+import { alchemy } from '@thxnetwork/api/util/alchemy';
+import { deployERC721, mockGetNftsForOwner } from '@thxnetwork/api/util/jest/erc721';
+import { ERC721Document, PoolDocument } from '@thxnetwork/api/models';
+import { getProvider } from '@thxnetwork/api/util/network';
+import TransactionService from '@thxnetwork/api/services/TransactionService';
+
+const user = request.agent(app);
+
+describe('ERC721 import', () => {
+    let erc721: ERC721Document, pool: PoolDocument, nftContract: Contract;
+    const chainId = ChainId.Hardhat,
+        nftName = 'Test Collection',
+        nftSymbol = 'TST';
+
+    beforeAll(beforeAllCallback);
+    afterAll(afterAllCallback);
+
+    describe('POST /pools', () => {
+        it('HTTP 201', (done) => {
+            user.post('/v1/pools')
+                .set('Authorization', dashboardAccessToken)
+                .send({ chainId })
+                .expect((res: request.Response) => {
+                    pool = res.body;
+                })
+                .expect(201, done);
+        });
+    });
+
+    describe('POST /erc721/import', () => {
+        it('HTTP 201`', async () => {
+            // Create 1 NFT collection
+            nftContract = await deployERC721(nftName, nftSymbol);
+
+            // Mint 1 token in the collection
+            await TransactionService.sendAsync(
+                nftContract.options.address,
+                nftContract.methods.mint(pool.safeAddress, 'tokenuri.json'),
+                chainId,
+            );
+
+            // Mock Alchemy SDK return value for getNftsForOwner
+            jest.spyOn(alchemy.nft, 'getNftsForOwner').mockImplementation(() =>
+                Promise.resolve(mockGetNftsForOwner(nftContract.options.address, nftName, nftSymbol) as any),
+            );
+
+            // Run the import for the deployed contract address
+            await user
+                .post('/v1/erc721/import')
+                .set({ 'Authorization': dashboardAccessToken, 'X-PoolId': pool._id })
+                .send({ chainId, contractAddress: nftContract.options.address })
+                .expect(({ body }: request.Response) => {
+                    expect(body.erc721._id).toBeDefined();
+                    expect(body.erc721.address).toBe(nftContract.options.address);
+                    erc721 = body.erc721;
+                })
+                .expect(201);
+        });
+    });
+
+    describe('GET /erc721/:id', () => {
+        const { defaultAccount } = getProvider(chainId);
+
+        it('HTTP 200', (done) => {
+            user.get(`/v1/erc721/${erc721._id}`)
+                .set('Authorization', dashboardAccessToken)
+                .send()
+                .expect(({ body }: request.Response) => {
+                    expect(body.chainId).toBe(chainId);
+                    expect(body.sub).toBe(sub);
+                    expect(body.name).toBe(nftName);
+                    expect(body.symbol).toBe(nftSymbol);
+                    expect(body.address).toBe(nftContract.options.address);
+                    expect(body.totalSupply).toBe('1');
+                    expect(body.owner).toBe(defaultAccount);
+                })
+                .expect(200, done);
+        });
+    });
+
+    describe('GET /erc721/:id/metadata', () => {
+        it('HTTP 200', (done) => {
+            user.get(`/v1/erc721/${erc721._id}/metadata`)
+                .set('Authorization', dashboardAccessToken)
+                .send()
+                .expect(({ body }: request.Response) => {
+                    expect(body.total).toBe(1);
+                    expect(body.results[0].name).toBeDefined();
+                    expect(body.results[0].description).toBeDefined();
+                    expect(body.results[0].image).toBeDefined();
+                    expect(body.results[0].externalUrl).toBeDefined();
+                })
+                .expect(200, done);
+        });
+    });
+});
diff --git a/apps/api/src/app/controllers/erc721/import/post.controller.ts b/apps/api/src/app/controllers/erc721/import/post.controller.ts
new file mode 100644
index 000000000..6c26c93ea
--- /dev/null
+++ b/apps/api/src/app/controllers/erc721/import/post.controller.ts
@@ -0,0 +1,88 @@
+import { body } from 'express-validator';
+import { Request, Response } from 'express';
+import { ERC721, ERC721Token, ERC721Metadata, Wallet } from '@thxnetwork/api/models';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { getNFTsForOwner, parseIPFSImageUrl } from '@thxnetwork/api/util/alchemy';
+import { ChainId, ERC721TokenState, NFTVariant } from '@thxnetwork/common/enums';
+import { toChecksumAddress } from 'web3-utils';
+
+const validation = [
+    body('address').isEthereumAddress(),
+    body('contractAddress').exists(),
+    body('chainId').exists().isNumeric(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const chainId = Number(req.body.chainId) as ChainId;
+    const contractAddress = toChecksumAddress(req.body.contractAddress);
+    const safeAddress = toChecksumAddress(req.body.address);
+    const ownedNfts = await getNFTsForOwner(safeAddress, contractAddress);
+    if (!ownedNfts.length) throw new NotFoundError('Could not find NFT tokens for this contract address');
+
+    const { address, name, symbol } = ownedNfts[0].contract;
+    const erc721 = await ERC721.findOneAndUpdate(
+        {
+            sub: req.auth.sub,
+            chainId,
+            address: toChecksumAddress(address),
+        },
+        {
+            variant: NFTVariant.ERC721,
+            sub: req.auth.sub,
+            chainId,
+            address: toChecksumAddress(address),
+            name,
+            symbol,
+            archived: false,
+        },
+        { upsert: true, new: true },
+    );
+    const erc721Tokens = await Promise.all(
+        ownedNfts.map(async ({ name, description, collection, tokenId, tokenUri, image }) => {
+            try {
+                const erc721Id = erc721.id;
+                const imageUrl = parseIPFSImageUrl(image.originalUrl);
+                const metadata = await ERC721Metadata.findOneAndUpdate(
+                    {
+                        erc721Id,
+                        externalUrl: collection.externalUrl,
+                    },
+                    {
+                        erc721Id,
+                        name,
+                        description,
+                        imageUrl,
+                        image: imageUrl,
+                        externalUrl: collection.externalUrl,
+                    },
+                    { upsert: true, new: true },
+                );
+                const safe = await Wallet.findOne({
+                    address: req.body.address,
+                    chainId: req.body.chainId,
+                });
+                const token = await ERC721Token.findOneAndUpdate(
+                    { tokenId, walletId: safe.id, erc721Id: erc721.id },
+                    {
+                        walletId: safe.id,
+                        erc721Id: erc721.id,
+                        recipient: safe.address,
+                        metadataId: metadata.id,
+                        tokenUri,
+                        tokenId,
+                        state: ERC721TokenState.Minted,
+                    },
+                    { upsert: true, new: true },
+                );
+
+                return { ...token.toJSON(), metadata: metadata.toJSON() };
+            } catch (error) {
+                console.log(error);
+            }
+        }),
+    );
+
+    res.status(201).json({ erc721, erc721Tokens });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc721/import/preview/post.controller.ts b/apps/api/src/app/controllers/erc721/import/preview/post.controller.ts
new file mode 100644
index 000000000..ecc02ab36
--- /dev/null
+++ b/apps/api/src/app/controllers/erc721/import/preview/post.controller.ts
@@ -0,0 +1,11 @@
+import { getNFTsForOwner } from '@thxnetwork/api/util/alchemy';
+import { Request, Response } from 'express';
+import { body } from 'express-validator';
+
+const validation = [body('address').exists().isString(), body('chainId').exists().isInt()];
+
+const controller = async (req: Request, res: Response) => {
+    const ownedNFTs = await getNFTsForOwner(req.body.address, req.body.contractAddress);
+    res.status(200).json(ownedNFTs);
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc721/list.controller.ts b/apps/api/src/app/controllers/erc721/list.controller.ts
new file mode 100644
index 000000000..6019f706d
--- /dev/null
+++ b/apps/api/src/app/controllers/erc721/list.controller.ts
@@ -0,0 +1,11 @@
+import { Request, Response } from 'express';
+import { ERC721Document } from '@thxnetwork/api/models/ERC721';
+import ERC721Service from '@thxnetwork/api/services/ERC721Service';
+
+const validation = [];
+
+const controller = async (req: Request, res: Response) => {
+    const result = await ERC721Service.findBySub(req.auth.sub);
+    res.json(result.map((erc721: ERC721Document) => erc721._id));
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc721/metadata/delete.controller.ts b/apps/api/src/app/controllers/erc721/metadata/delete.controller.ts
new file mode 100644
index 000000000..59fd840b7
--- /dev/null
+++ b/apps/api/src/app/controllers/erc721/metadata/delete.controller.ts
@@ -0,0 +1,13 @@
+import { param } from 'express-validator';
+import { Request, Response } from 'express';
+import ERC721Service from '@thxnetwork/api/services/ERC721Service';
+
+const validation = [param('metadataId').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['ERC721 Metadata']
+    await ERC721Service.deleteMetadata(req.params.metadataId);
+    res.status(200).json({ success: true });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc721/metadata/erc721-metadata.test.ts b/apps/api/src/app/controllers/erc721/metadata/erc721-metadata.test.ts
new file mode 100644
index 000000000..8fb8a2c4f
--- /dev/null
+++ b/apps/api/src/app/controllers/erc721/metadata/erc721-metadata.test.ts
@@ -0,0 +1,129 @@
+import request from 'supertest';
+import app from '@thxnetwork/api/';
+import { ChainId } from '@thxnetwork/common/enums';
+import { afterAllCallback, beforeAllCallback } from '@thxnetwork/api/util/jest/config';
+import { dashboardAccessToken } from '@thxnetwork/api/util/jest/constants';
+import { createArchiver } from '@thxnetwork/api/util/zip';
+import { createImage } from '@thxnetwork/api/util/jest/images';
+
+const user = request.agent(app);
+
+describe('ERC721 Metadata', () => {
+    let erc721ID: string, metadataId: string;
+    const chainId = ChainId.Hardhat,
+        name = 'Planets of the Galaxy',
+        symbol = 'GLXY',
+        description = 'Collection full of rarities.';
+
+    beforeAll(beforeAllCallback);
+    afterAll(afterAllCallback);
+
+    describe('POST /erc721', () => {
+        it('should create and return contract details', (done) => {
+            user.post('/v1/erc721')
+                .set('Authorization', dashboardAccessToken)
+                .send({
+                    chainId,
+                    name,
+                    symbol,
+                    description,
+                })
+                .expect(({ body }: request.Response) => {
+                    expect(body._id).toBeDefined();
+                    expect(body.address).toBeDefined();
+                    erc721ID = body._id;
+                })
+                .expect(201, done);
+        });
+    });
+
+    describe('POST /erc721/:id/metadata', () => {
+        const name = 'red',
+            description = 'large',
+            imageUrl = 'http://imageURL.com',
+            externalUrl = 'http://externalurl.com';
+
+        it('HTTP 201', (done) => {
+            user.post('/v1/erc721/' + erc721ID + '/metadata')
+                .set('Authorization', dashboardAccessToken)
+                .send({
+                    name,
+                    description,
+                    imageUrl,
+                    externalUrl,
+                })
+                .expect(({ body }: request.Response) => {
+                    expect(body._id).toBeDefined();
+                    expect(body.name).toBe(name);
+                    expect(body.description).toBe(description);
+                    expect(body.image).toBe(imageUrl);
+                    expect(body.imageUrl).toBe(imageUrl);
+                    expect(body.externalUrl).toBe(externalUrl);
+                    metadataId = body._id;
+                })
+                .expect(201, done);
+        });
+    });
+
+    describe('PATCH /metadata/:metadataId', () => {
+        const value1 = 'blue',
+            value2 = 'small',
+            value3 = 'http://imageURL2.com',
+            value4 = 'http://externalurl2.com';
+
+        it('should return modified metadata for metadataId', (done) => {
+            user.patch('/v1/erc721/' + erc721ID + '/metadata/' + metadataId)
+                .set('Authorization', dashboardAccessToken)
+                .send({
+                    name: value1,
+                    description: value2,
+                    imageUrl: value3,
+                    externalUrl: value4,
+                })
+                .expect(({ body }: request.Response) => {
+                    expect(body.name).toBe(value1);
+                    expect(body.description).toBe(value2);
+                    expect(body.image).toBe(value3);
+                    expect(body.imageUrl).toBe(value3);
+                    expect(body.externalUrl).toBe(value4);
+                })
+                .expect(200, done);
+        });
+    });
+
+    describe('POST /erc721/:id/metadata/zip', () => {
+        it('HTTP 201', async () => {
+            const image1 = createImage();
+            const image2 = createImage();
+            const image3 = createImage();
+            const zip = createArchiver().jsZip;
+            const zipFolder = zip.folder('testImages');
+            zipFolder.file('image1.jpg', image1, { binary: true });
+            zipFolder.file('image2.jpg', image2, { binary: true });
+            zipFolder.file('image3.jpg', image3, { binary: true });
+
+            const zipFile = await zip.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE' });
+            await user
+                .post('/v1/erc721/' + erc721ID + '/metadata/zip')
+                .set('Authorization', dashboardAccessToken)
+                .attach('file', zipFile, { filename: 'images.zip', contentType: 'application/zip' })
+                .field({
+                    description,
+                    propName: 'image',
+                })
+                .expect(201);
+        });
+    });
+
+    describe('GET /metadata', () => {
+        it('HTTP 200', (done) => {
+            user.get('/v1/erc721/' + erc721ID + '/metadata')
+                .set('Authorization', dashboardAccessToken)
+                .expect(({ body }: request.Response) => {
+                    expect(body.results.length).toBe(4);
+                    expect(body.total).toBe(4);
+                })
+                .expect(200, done);
+        });
+    });
+});
diff --git a/apps/api/src/app/controllers/erc721/metadata/get.controller.ts b/apps/api/src/app/controllers/erc721/metadata/get.controller.ts
new file mode 100644
index 000000000..b49b6c279
--- /dev/null
+++ b/apps/api/src/app/controllers/erc721/metadata/get.controller.ts
@@ -0,0 +1,13 @@
+import { param } from 'express-validator';
+import { Request, Response } from 'express';
+import { ERC721Metadata } from '@thxnetwork/api/models/ERC721Metadata';
+
+const validation = [param('id').isMongoId(), param('metadataId').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['ERC721 Metadata']
+    const metadata = await ERC721Metadata.findById(req.params.metadataId);
+    res.json(metadata);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc721/metadata/images/post.controller.ts b/apps/api/src/app/controllers/erc721/metadata/images/post.controller.ts
new file mode 100644
index 000000000..9c6f5b81d
--- /dev/null
+++ b/apps/api/src/app/controllers/erc721/metadata/images/post.controller.ts
@@ -0,0 +1,97 @@
+import { AWS_S3_PUBLIC_BUCKET_NAME, IPFS_BASE_URL, NODE_ENV } from '@thxnetwork/api/config/secrets';
+import { ERC721Metadata } from '@thxnetwork/api/models/ERC721Metadata';
+import ERC721Service from '@thxnetwork/api/services/ERC721Service';
+import ImageService from '@thxnetwork/api/services/ImageService';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { logger } from '@thxnetwork/api/util/logger';
+import { s3Client } from '@thxnetwork/api/util/s3';
+import { createArchiver } from '@thxnetwork/api/util/zip';
+import { PutObjectCommand } from '@aws-sdk/client-s3';
+import { Request, Response } from 'express';
+import { body, check, param } from 'express-validator';
+import short from 'short-uuid';
+import IPFSService from '@thxnetwork/api/services/IPFSService';
+import fileType from 'magic-bytes.js';
+
+const validation = [
+    param('id').isMongoId(),
+    body('propName').exists().isString(),
+    check('file').custom((value, { req }) => {
+        switch (req.file.mimetype) {
+            case 'application/octet-stream':
+            case 'application/zip':
+            case 'application/rar':
+                return true;
+            default:
+                return false;
+        }
+    }),
+];
+
+function parseFilename(filename: string, extension: string) {
+    return filename.toLowerCase().split(' ').join('-').split('.') + '-' + short.generate() + `.${extension}`;
+}
+
+const controller = async (req: Request, res: Response) => {
+    const erc721 = await ERC721Service.findById(req.params.id);
+    if (!erc721) throw new NotFoundError('Could not find this NFT in the database');
+
+    const zip = createArchiver().jsZip;
+    const contents = await zip.loadAsync(req.file.buffer);
+
+    for (const fileName of Object.keys(contents.files)) {
+        try {
+            const extension = fileName.substring(fileName.lastIndexOf('.')).substring(1);
+            if (!extension) continue;
+
+            const originalFileName = fileName.substring(0, fileName.lastIndexOf('.'));
+            if (!isValidExtension(extension)) continue;
+
+            const file = await zip.file(fileName).async('nodebuffer');
+            const isValid = await isValidFileType(file);
+            if (!isValid) continue;
+
+            const filename = parseFilename(originalFileName, extension);
+            await s3Client.send(
+                new PutObjectCommand({
+                    Key: filename,
+                    Bucket: AWS_S3_PUBLIC_BUCKET_NAME,
+                    ACL: 'public-read',
+                    Body: file,
+                }),
+            );
+
+            const imageUrl = req.file && (await ImageService.upload(req.file));
+            let image = imageUrl;
+            if (NODE_ENV === 'production') {
+                const cid = await IPFSService.addUrlSource(imageUrl);
+                image = IPFS_BASE_URL + cid;
+            }
+
+            await ERC721Metadata.create({
+                erc721Id: erc721.id,
+                name: req.body.name,
+                description: req.body.description,
+                externalUrl: req.body.externalUrl,
+                image,
+                imageUrl,
+            });
+        } catch (err) {
+            console.log(err);
+            logger.error(err);
+        }
+    }
+
+    res.status(201).end();
+};
+
+function isValidExtension(extension: string) {
+    return ['jpg', 'jpeg', 'gif', 'png'].includes(extension);
+}
+
+function isValidFileType(buffer: Buffer) {
+    const [type] = fileType(buffer);
+    return ['image/jpeg', 'image/png', 'image/gif'].includes(type.mime);
+}
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc721/metadata/list.controller.ts b/apps/api/src/app/controllers/erc721/metadata/list.controller.ts
new file mode 100644
index 000000000..995da7973
--- /dev/null
+++ b/apps/api/src/app/controllers/erc721/metadata/list.controller.ts
@@ -0,0 +1,24 @@
+import { Request, Response } from 'express';
+import ERC721Service from '@thxnetwork/api/services/ERC721Service';
+import { param, query } from 'express-validator';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+
+const validation = [
+    param('id').isMongoId(),
+    query('limit').optional().isInt({ gt: 0 }),
+    query('page').optional().isInt({ gt: 0 }),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const erc721 = await ERC721Service.findById(req.params.id);
+    if (!erc721) throw new NotFoundError('Could not find this NFT in the database');
+
+    const result = await ERC721Service.findMetadataByNFT(
+        erc721._id,
+        req.query.page ? Number(req.query.page) : null,
+        req.query.limit ? Number(req.query.limit) : null,
+    );
+    res.json(result);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc721/metadata/patch.controller.ts b/apps/api/src/app/controllers/erc721/metadata/patch.controller.ts
new file mode 100644
index 000000000..07d10cbd1
--- /dev/null
+++ b/apps/api/src/app/controllers/erc721/metadata/patch.controller.ts
@@ -0,0 +1,45 @@
+import { Request, Response } from 'express';
+import { body, param } from 'express-validator';
+import { BadRequestError, NotFoundError } from '@thxnetwork/api/util/errors';
+import ERC721Service from '@thxnetwork/api/services/ERC721Service';
+import IPFSService from '@thxnetwork/api/services/IPFSService';
+import { IPFS_BASE_URL, NODE_ENV } from '@thxnetwork/api/config/secrets';
+import { ERC721Metadata } from '@thxnetwork/api/models/ERC721Metadata';
+
+const validation = [
+    param('id').isMongoId(),
+    param('metadataId').isMongoId(),
+    body('name').optional().isString(),
+    body('description').optional().isString(),
+    body('externalUrl').optional().isURL(),
+    body('imageUrl').optional().isURL(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const erc721 = await ERC721Service.findById(req.params.id);
+    if (!erc721) throw new NotFoundError('Could not find this NFT in the database');
+
+    const metadata = await ERC721Metadata.findById(req.params.metadataId);
+    if (!metadata) throw new NotFoundError('Could not find this NFT Metadata in the database');
+
+    const tokens = metadata.tokens || [];
+    if (tokens.length) throw new BadRequestError('There token minted with this metadata');
+
+    let image = req.body.imageUrl;
+    if (req.body.imageUrl && NODE_ENV === 'production') {
+        const cid = await IPFSService.addUrlSource(req.body.imageUrl);
+        image = IPFS_BASE_URL + cid;
+    }
+
+    metadata.name = req.body.name || metadata.name;
+    metadata.image = image || metadata.image;
+    metadata.imageUrl = req.body.imageUrl || metadata.imageUrl;
+    metadata.description = req.body.description || metadata.description;
+    metadata.externalUrl = req.body.externalUrl || metadata.externalUrl;
+
+    await metadata.save();
+
+    res.json({ ...metadata.toJSON(), tokens });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc721/metadata/post.controller.ts b/apps/api/src/app/controllers/erc721/metadata/post.controller.ts
new file mode 100644
index 000000000..a37dfdb2f
--- /dev/null
+++ b/apps/api/src/app/controllers/erc721/metadata/post.controller.ts
@@ -0,0 +1,39 @@
+import { Request, Response } from 'express';
+import { body, param } from 'express-validator';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { ERC721Metadata } from '@thxnetwork/api/models/ERC721Metadata';
+import { IPFS_BASE_URL, NODE_ENV } from '@thxnetwork/api/config/secrets';
+import ERC721Service from '@thxnetwork/api/services/ERC721Service';
+import IPFSService from '@thxnetwork/api/services/IPFSService';
+
+const validation = [
+    param('id').isMongoId(),
+    body('name').optional().isString(),
+    body('imageUrl').optional().isURL(),
+    body('description').optional().isString(),
+    body('externalUrl').optional().isURL(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['ERC721']
+    const erc721 = await ERC721Service.findById(req.params.id);
+    if (!erc721) throw new NotFoundError('Could not find this NFT in the database');
+
+    let image = req.body.imageUrl;
+    if (req.body.imageUrl && NODE_ENV === 'production') {
+        const cid = await IPFSService.addUrlSource(req.body.imageUrl);
+        image = IPFS_BASE_URL + cid;
+    }
+
+    const metadata = await ERC721Metadata.create({
+        erc721Id: String(erc721._id),
+        name: req.body.name,
+        image,
+        imageUrl: req.body.imageUrl,
+        description: req.body.description,
+        externalUrl: req.body.externalUrl,
+    });
+
+    res.status(201).json(metadata);
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc721/patch.controller.ts b/apps/api/src/app/controllers/erc721/patch.controller.ts
new file mode 100644
index 000000000..77b4e9776
--- /dev/null
+++ b/apps/api/src/app/controllers/erc721/patch.controller.ts
@@ -0,0 +1,19 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import { ForbiddenError, NotFoundError } from '@thxnetwork/api/util/errors';
+import { ERC721 } from '@thxnetwork/api/models/ERC721';
+import ERC721Service from '@thxnetwork/api/services/ERC721Service';
+
+const validation = [param('id').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['ERC721']
+    const erc721 = await ERC721Service.findById(req.params.id);
+    if (!erc721) throw new NotFoundError('Could not find the token for this id');
+    if (erc721.sub !== req.auth.sub) throw new ForbiddenError('Not your ERC721');
+
+    const result = await ERC721.findByIdAndUpdate(req.params.id, req.body, { new: true });
+
+    res.json(result);
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc721/post.controller.ts b/apps/api/src/app/controllers/erc721/post.controller.ts
new file mode 100644
index 000000000..b7c1d67fd
--- /dev/null
+++ b/apps/api/src/app/controllers/erc721/post.controller.ts
@@ -0,0 +1,46 @@
+import { API_URL, IPFS_BASE_URL, VERSION } from '@thxnetwork/api/config/secrets';
+import { Request, Response } from 'express';
+import { body, check, query } from 'express-validator';
+import ERC721Service from '@thxnetwork/api/services/ERC721Service';
+import ImageService from '@thxnetwork/api/services/ImageService';
+import { AccountPlanType, NFTVariant } from '@thxnetwork/common/enums';
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+
+const validation = [
+    body('name').exists().isString(),
+    body('symbol').exists().isString(),
+    body('description').exists().isString(),
+    body('chainId').exists().isNumeric(),
+    check('file')
+        .optional()
+        .custom((value, { req }) => {
+            return ['jpg', 'jpeg', 'gif', 'png'].includes(req.file.mimetype);
+        }),
+    query('forceSync').optional().isBoolean(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['ERC721']
+
+    const logoImgUrl = req.file && (await ImageService.upload(req.file));
+    const forceSync = req.query.forceSync !== undefined ? req.query.forceSync === 'true' : false;
+    const account = await AccountProxy.findById(req.auth.sub);
+    const baseURL = account.plan === AccountPlanType.Premium ? IPFS_BASE_URL : `${API_URL}/${VERSION}/metadata/`;
+    const erc721 = await ERC721Service.deploy(
+        {
+            variant: NFTVariant.ERC721,
+            sub: req.auth.sub,
+            chainId: req.body.chainId,
+            name: req.body.name,
+            symbol: req.body.symbol,
+            description: req.body.description,
+            baseURL,
+            logoImgUrl,
+        },
+        forceSync,
+    );
+
+    res.status(201).json(erc721);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc721/token/get.controller.ts b/apps/api/src/app/controllers/erc721/token/get.controller.ts
new file mode 100644
index 000000000..8f91f5b10
--- /dev/null
+++ b/apps/api/src/app/controllers/erc721/token/get.controller.ts
@@ -0,0 +1,42 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import { fromWei } from 'web3-utils';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { ERC721Metadata } from '@thxnetwork/api/models/ERC721Metadata';
+import { ERC721Token } from '@thxnetwork/api/models/ERC721Token';
+import ERC721Service from '@thxnetwork/api/services/ERC721Service';
+
+const validation = [param('id').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const token = await ERC721Token.findById(req.params.id);
+    if (!token) throw new NotFoundError('ERC721Token not found');
+
+    const [erc721, metadata] = await Promise.all([
+        ERC721Service.findById(token.erc721Id),
+        ERC721Metadata.findById(token.metadataId),
+    ]);
+    if (!erc721) throw new NotFoundError('ERC721 not found');
+    if (!metadata) throw new NotFoundError('ERC721Metadata not found');
+
+    const balanceInWei = await erc721.contract.methods.balanceOf(token.recipient).call();
+    const balance = Number(fromWei(balanceInWei, 'ether'));
+
+    const [owner, tokenUri] = token.tokenId
+        ? await Promise.all([
+              erc721.contract.methods.ownerOf(token.tokenId).call(),
+              erc721.contract.methods.tokenURI(token.tokenId).call(),
+          ])
+        : [];
+
+    res.status(200).json({
+        ...token.toJSON(),
+        owner,
+        tokenUri,
+        balance,
+        nft: erc721.toJSON(),
+        metadata: metadata.toJSON(),
+    });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc721/token/list.controller.ts b/apps/api/src/app/controllers/erc721/token/list.controller.ts
new file mode 100644
index 000000000..e54b15b81
--- /dev/null
+++ b/apps/api/src/app/controllers/erc721/token/list.controller.ts
@@ -0,0 +1,30 @@
+import { Request, Response } from 'express';
+import { query } from 'express-validator';
+import { ERC721Token, ERC721TokenDocument, ERC721Metadata } from '@thxnetwork/api/models';
+import { BadRequestError } from '@thxnetwork/api/util/errors';
+import ERC721Service from '@thxnetwork/api/services/ERC721Service';
+import SafeService from '@thxnetwork/api/services/SafeService';
+
+const validation = [query('walletId').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const wallet = await SafeService.findById(req.query.walletId as string);
+    if (!wallet) throw new BadRequestError('Wallet not found');
+
+    const tokens = await ERC721Token.find({ walletId: wallet.id });
+    const result = await Promise.all(
+        tokens.map(async (token: ERC721TokenDocument) => {
+            const erc721 = await ERC721Service.findById(token.erc721Id);
+            if (!erc721) return;
+
+            const metadata = await ERC721Metadata.findById(token.metadataId);
+            if (!metadata) return;
+
+            return Object.assign(token.toJSON() as TERC721Token, { metadata, tokenUri: token.tokenUri, nft: erc721 });
+        }),
+    );
+
+    res.json(result.reverse().filter((token) => !!token));
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/erc721/transfer/erc721-transfer.test.ts b/apps/api/src/app/controllers/erc721/transfer/erc721-transfer.test.ts
new file mode 100644
index 000000000..451fc22c6
--- /dev/null
+++ b/apps/api/src/app/controllers/erc721/transfer/erc721-transfer.test.ts
@@ -0,0 +1,191 @@
+import request from 'supertest';
+import app from '@thxnetwork/api/';
+import { ChainId, NFTVariant } from '@thxnetwork/common/enums';
+import { afterAllCallback, beforeAllCallback } from '@thxnetwork/api/util/jest/config';
+import { sub, sub2, userWalletPrivateKey, widgetAccessToken } from '@thxnetwork/api/util/jest/constants';
+import { poll } from '@thxnetwork/api/util/polling';
+import { signTxHash } from '@thxnetwork/api/util/jest/network';
+import { safeVersion } from '@thxnetwork/api/services/ContractService';
+import { getProvider } from '@thxnetwork/api/util/network';
+import {
+    ERC721Token,
+    ERC721TokenDocument,
+    ERC721,
+    ERC721Document,
+    ERC721Metadata,
+    Wallet,
+    WalletDocument,
+    Pool,
+    PoolDocument,
+} from '@thxnetwork/api/models';
+import ERC721Service from '@thxnetwork/api/services/ERC721Service';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import SafeService from '@thxnetwork/api/services/SafeService';
+
+const user = request.agent(app);
+
+describe('ERC721 Transfer', () => {
+    let erc721: ERC721Document,
+        erc721Token: ERC721TokenDocument,
+        pool: PoolDocument,
+        wallet: WalletDocument,
+        safeTxHash = '';
+    const chainId = ChainId.Hardhat,
+        name = 'Test Collection',
+        symbol = 'TST',
+        baseURL = 'https://example.com',
+        logoImgUrl = 'https://img.url',
+        metadataName = 'Testname',
+        metadataImageUrl = 'Testimageurl',
+        metadataIPFSImageUrl = 'TestIPFSimageurl',
+        metadataDescription = 'Testdescription',
+        metadataExternalUrl = 'TestexternalURL';
+
+    beforeAll(beforeAllCallback);
+    afterAll(afterAllCallback);
+
+    it('Deploy Campaign Safe', async () => {
+        const { web3 } = getProvider(chainId);
+        pool = await PoolService.deploy(sub, 'My Reward Campaign');
+        const safe = await SafeService.create({ chainId, sub, safeVersion, poolId: String(pool._id) });
+
+        // Wait for safe address to return code
+        await poll(
+            () => web3.eth.getCode(safe.address),
+            (data: string) => data === '0x',
+            1000,
+        );
+        const code = await web3.eth.getCode(safe.address);
+        const result = code !== '0x';
+
+        expect(result).toBe(true);
+    });
+
+    it('Deploy ERC721', async () => {
+        const { web3 } = getProvider(chainId);
+
+        erc721 = await ERC721Service.deploy(
+            {
+                variant: NFTVariant.ERC721,
+                sub,
+                chainId,
+                name,
+                symbol,
+                description: '',
+                baseURL,
+                archived: false,
+                logoImgUrl,
+            },
+            true,
+        );
+
+        // Wait for nft address to return code
+        await poll(
+            async () => (await ERC721.findById(erc721._id)).address,
+            (address: string) => !address || !address.length,
+            1000,
+        );
+
+        erc721 = await ERC721.findById(erc721._id);
+
+        const code = await web3.eth.getCode(erc721.address);
+        const result = code !== '0x';
+        expect(result).toBe(true);
+    });
+
+    it('Add ERC721 minter', async () => {
+        pool = await Pool.findById(pool._id);
+
+        const safe = await SafeService.findOneByPool(pool);
+        erc721 = await ERC721.findById(erc721._id);
+
+        await ERC721Service.addMinter(erc721, safe.address);
+
+        // Wait for nft address to return code
+        await poll(
+            async () => await ERC721Service.isMinter(erc721, safe.address),
+            (isMinter: boolean) => !isMinter,
+            1000,
+        );
+
+        const isMinter = await ERC721Service.isMinter(erc721, safe.address);
+        expect(isMinter).toBe(true);
+    });
+
+    it('Create ERC721 Metadata', async () => {
+        // Create metadata for token
+        const metadata = await ERC721Metadata.create({
+            erc721Id: String(erc721._id),
+            name: metadataName,
+            image: metadataIPFSImageUrl,
+            imageUrl: metadataImageUrl,
+            description: metadataDescription,
+            externalUrl: metadataExternalUrl,
+        });
+        const safe = await SafeService.findOneByPool(pool);
+
+        // Wait for safe address to return code
+        const { web3 } = getProvider(chainId);
+        await poll(
+            () => web3.eth.getCode(safe.address),
+            (data: string) => data === '0x',
+            1000,
+        );
+
+        wallet = await SafeService.findOne({ sub, safeVersion: { $exists: true } });
+
+        // Mint a token for metadata
+        erc721Token = await ERC721Service.mint(safe, erc721, wallet, metadata);
+
+        // Wait for tokenId to be set in mint callback
+        await poll(
+            async () => (await ERC721Token.findById(erc721Token._id)).tokenId,
+            (tokenId?: number) => typeof tokenId === 'undefined',
+            1000,
+        );
+
+        erc721Token = await ERC721Token.findById(erc721Token._id);
+
+        expect(erc721Token.tokenId).toBeDefined();
+    });
+
+    it('Transfer ERC721 ownership', async () => {
+        const receiver = await Wallet.findOne({ sub: sub2, safeVersion });
+        const { status, body } = await user
+            .post('/v1/erc721/transfer')
+            .set({ Authorization: widgetAccessToken })
+            .send({
+                walletId: String(wallet._id),
+                erc721Id: erc721._id,
+                erc721TokenId: erc721Token._id,
+                to: receiver.address,
+            });
+
+        expect(status).toBe(201);
+        expect(body.safeTxHash).toBeDefined();
+
+        safeTxHash = body.safeTxHash;
+    });
+
+    it('Confirm tx', async () => {
+        const wallet = await SafeService.findOne({ sub, safeVersion: { $exists: true } });
+        const { signature } = await signTxHash(wallet.address, safeTxHash, userWalletPrivateKey);
+        const { status, body } = await user
+            .post(`/v1/account/wallets/confirm`)
+            .set({ Authorization: widgetAccessToken })
+            .query({ walletId: String(wallet._id) })
+            .send({ chainId: ChainId.Hardhat, safeTxHash, signature });
+        expect(status).toBe(200);
+    });
+
+    it('Wait for ownerOf', async () => {
+        const receiver = await Wallet.findOne({ sub: sub2, safeVersion });
+        const token = await ERC721Token.findById(erc721Token._id);
+        const { contract } = await ERC721.findById(erc721._id);
+
+        await poll(contract.methods.ownerOf(token.tokenId).call, (result: string) => result !== receiver.address, 1000);
+
+        const owner = await contract.methods.ownerOf(token.tokenId).call();
+        expect(owner).toEqual(receiver.address);
+    });
+});
diff --git a/apps/api/src/app/controllers/erc721/transfer/post.controller.ts b/apps/api/src/app/controllers/erc721/transfer/post.controller.ts
new file mode 100644
index 000000000..a2c7f22e9
--- /dev/null
+++ b/apps/api/src/app/controllers/erc721/transfer/post.controller.ts
@@ -0,0 +1,35 @@
+import { Request, Response } from 'express';
+import { body } from 'express-validator';
+import { ForbiddenError, NotFoundError } from '@thxnetwork/api/util/errors';
+import { ERC721Token } from '@thxnetwork/api/models/ERC721Token';
+import { ERC721 } from '@thxnetwork/api/models/ERC721';
+import { Transaction } from '@thxnetwork/api/models/Transaction';
+import ERC721Service from '@thxnetwork/api/services/ERC721Service';
+import SafeService from '@thxnetwork/api/services/SafeService';
+
+const validation = [
+    body('walletId').isMongoId(),
+    body('erc721Id').isMongoId(),
+    body('erc721TokenId').isMongoId(),
+    body('to').isString(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const erc721 = await ERC721.findById(req.body.erc721Id);
+    if (!erc721) throw new NotFoundError('Could not find the ERC721');
+
+    const erc721Token = await ERC721Token.findById(req.body.erc721TokenId);
+    if (!erc721Token) throw new NotFoundError('Could not find token for wallet');
+
+    const wallet = await SafeService.findById(req.body.walletId);
+    if (!wallet) throw new NotFoundError('Could not find wallet for account');
+
+    const owner = await erc721.contract.methods.ownerOf(erc721Token.tokenId).call();
+    if (owner !== wallet.address) throw new ForbiddenError('Account is not owner of given tokenId');
+
+    const receiverToken = await ERC721Service.transferFrom(erc721, wallet, req.body.to, erc721Token);
+    const tx = await Transaction.findById(receiverToken.transactions[0]);
+
+    res.status(201).json(tx);
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/events/events.router.ts b/apps/api/src/app/controllers/events/events.router.ts
new file mode 100644
index 000000000..b82941b96
--- /dev/null
+++ b/apps/api/src/app/controllers/events/events.router.ts
@@ -0,0 +1,9 @@
+import express from 'express';
+import * as CreateEvents from './post.controller';
+import { assertRequestInput, guard } from '@thxnetwork/api/middlewares';
+
+const router: express.Router = express.Router();
+
+router.post('/', guard.check(['events:write']), assertRequestInput(CreateEvents.validation), CreateEvents.controller);
+
+export default router;
diff --git a/apps/api/src/app/controllers/events/post.controller.ts b/apps/api/src/app/controllers/events/post.controller.ts
new file mode 100644
index 000000000..a6c806b00
--- /dev/null
+++ b/apps/api/src/app/controllers/events/post.controller.ts
@@ -0,0 +1,24 @@
+import { Request, Response } from 'express';
+import { body } from 'express-validator';
+import { Pool, Event, Identity, Client } from '@thxnetwork/api/models';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+
+const validation = [body('event').isString().isLength({ min: 0, max: 50 }), body('identityUuid').isUUID()];
+
+const controller = async (req: Request, res: Response) => {
+    const { identityUuid, event } = req.body;
+    const client = await Client.findOne({ clientId: req.auth.client_id });
+    if (!client) throw new NotFoundError('Could not find client for token');
+
+    const pool = await Pool.findById(client.poolId);
+    if (!pool) throw new NotFoundError('Could not find pool for client');
+
+    const identity = await Identity.findOne({ uuid: identityUuid });
+    if (!identity) throw new NotFoundError('Could not find ID for uuid');
+
+    await Event.create({ name: event, poolId: pool._id, identityId: identity._id });
+
+    res.status(201).end();
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/health/health.router.ts b/apps/api/src/app/controllers/health/health.router.ts
new file mode 100644
index 000000000..c973b0469
--- /dev/null
+++ b/apps/api/src/app/controllers/health/health.router.ts
@@ -0,0 +1,8 @@
+import express from 'express';
+import * as ListHealth from './list.controller';
+
+const router: express.Router = express.Router();
+
+router.get('/', ListHealth.controller);
+
+export default router;
diff --git a/apps/api/src/app/controllers/health/list.controller.ts b/apps/api/src/app/controllers/health/list.controller.ts
new file mode 100644
index 000000000..5336c29fb
--- /dev/null
+++ b/apps/api/src/app/controllers/health/list.controller.ts
@@ -0,0 +1,140 @@
+import { Request, Response } from 'express';
+import { fromWei } from 'web3-utils';
+import { NODE_ENV } from '@thxnetwork/api/config/secrets';
+import { ChainId } from '@thxnetwork/common/enums';
+import { logger } from '@thxnetwork/api/util/logger';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { ethers } from 'ethers';
+import { getArtifact, contractNetworks } from '@thxnetwork/api/hardhat';
+import { BigNumber } from 'alchemy-sdk';
+
+function handleError(error: Error) {
+    logger.error(error);
+    return { error: 'invalid response' };
+}
+
+async function getNetworkDetails(chainId: ChainId) {
+    try {
+        const { defaultAccount, web3, signer } = getProvider(chainId);
+        const rfthx = new ethers.Contract(
+            contractNetworks[chainId].RewardFaucet,
+            getArtifact('RewardFaucet').abi,
+            signer,
+        );
+        const registry = new ethers.Contract(
+            contractNetworks[chainId].THXRegistry,
+            getArtifact('THXRegistry').abi,
+            signer,
+        );
+        const rdthx = new ethers.Contract(
+            contractNetworks[chainId].RewardDistributor,
+            getArtifact('RewardDistributor').abi,
+            signer,
+        );
+        const bpt = new ethers.Contract(contractNetworks[chainId].BPT, getArtifact('BPT').abi, signer);
+        const bptGauge = new ethers.Contract(contractNetworks[chainId].BPTGauge, getArtifact('BPTGauge').abi, signer);
+        const veTHX = new ethers.Contract(
+            contractNetworks[chainId].VotingEscrow,
+            getArtifact('VotingEscrow').abi,
+            signer,
+        );
+        const bal = new ethers.Contract(contractNetworks[chainId].BAL, getArtifact('BAL').abi, signer);
+        // const thx = new ethers.Contract(contractNetworks[chainId].THX, contractArtifacts['THX'].abi, signer);
+        // const usdc = new ethers.Contract(contractNetworks[chainId].USDC, contractArtifacts['USDC'].abi, signer);
+
+        const address = {
+            registry: registry.address,
+            relayer: defaultAccount,
+            bptGauge: bptGauge.address,
+            bpt: await bpt.getAddress(),
+            bal: await bal.getAddress(),
+            thx: contractNetworks[chainId].THX,
+            usdc: contractNetworks[chainId].USDC,
+            vault: contractNetworks[chainId].BalancerVault,
+        };
+
+        const relayer = await Promise.all([
+            {
+                matic: fromWei(String(await web3.eth.getBalance(defaultAccount)), 'ether'),
+                bpt: fromWei(String(await bpt.balanceOf(defaultAccount)), 'ether'),
+                // bptGauge: fromWei(String(await bptGauge.balanceOf(defaultAccount)), 'ether'),
+                // bal: fromWei(String(await bal.balanceOf(defaultAccount)), 'ether'),
+                // thx: fromWei(String(await thx.balanceOf(defaultAccount)), 'ether'),
+                // usdc: fromWei(String(await usdc.balanceOf(defaultAccount)), 'ether'),
+            },
+        ]);
+        const total = fromWei(String(await rfthx.totalTokenRewards(bpt.address)), 'ether');
+        const currentBlock = await web3.eth.getBlock('latest');
+        const amountStaked = BigNumber.from(String(await bpt.balanceOf(bptGauge.address)));
+        const amountSupply = BigNumber.from(String(await bpt.totalSupply()));
+        const amountUnstaked = amountSupply.sub(amountStaked);
+        const amountLocked = await bptGauge.balanceOf(veTHX.address);
+
+        const metrics = {
+            unstaked: fromWei(amountUnstaked.toString(), 'ether'),
+            staked: fromWei(amountStaked.toString(), 'ether'),
+            locked: fromWei(amountLocked.toString(), 'ether'),
+        };
+        const getRewards = async (tokenAddress: string, now: string) => {
+            const currentWeek = fromWei(String(await rfthx.getTokenWeekAmounts(tokenAddress, now)));
+            const upcomingWeeks = (await rfthx.getUpcomingRewardsForNWeeks(tokenAddress, 4)).map((amount: BigNumber) =>
+                fromWei(String(amount)),
+            );
+            return [currentWeek, ...upcomingWeeks];
+        };
+        const distributor = {
+            total,
+            balances: await Promise.all([
+                {
+                    bpt: fromWei(String(await bpt.balanceOf(rfthx.address)), 'ether'),
+                    bal: fromWei(String(await bal.balanceOf(rfthx.address)), 'ether'),
+                },
+            ]),
+            rewards: {
+                bpt: await getRewards(await bpt.getAddress(), String(currentBlock.timestamp)),
+                bal: await getRewards(await bal.getAddress(), String(currentBlock.timestamp)),
+            },
+        };
+        const splitter = new ethers.Contract(
+            contractNetworks[chainId].THXPaymentSplitter,
+            getArtifact('THXPaymentSplitter').abi,
+            signer,
+        );
+
+        return {
+            blockTime: new Date(Number(currentBlock.timestamp) * 1000),
+            registry: {
+                payoutRate: BigNumber.from(await registry.getPayoutRate())
+                    .div(100)
+                    .toString(),
+                payee: await registry.getPayee(),
+            },
+            test: {
+                rate: (await splitter.rates('0x029E2d4D2b6938c92c48dbf422a4e500425a08D8')).toString(),
+                balance: (await splitter.balanceOf('0x029E2d4D2b6938c92c48dbf422a4e500425a08D8')).toString(),
+            },
+            address,
+            relayer,
+            metrics,
+            distributor,
+        };
+    } catch (error) {
+        return handleError(error);
+    }
+}
+
+const controller = async (req: Request, res: Response) => {
+    const result = {
+        networks: {},
+    };
+
+    if (NODE_ENV !== 'production') {
+        result.networks[ChainId.Hardhat] = await getNetworkDetails(ChainId.Hardhat);
+    } else {
+        result.networks[ChainId.Polygon] = await getNetworkDetails(ChainId.Polygon);
+    }
+
+    res.header('Content-Type', 'application/json').send(JSON.stringify(result, null, 4));
+};
+
+export { controller };
diff --git a/apps/api/src/app/controllers/identity/get.controller.ts b/apps/api/src/app/controllers/identity/get.controller.ts
new file mode 100644
index 000000000..fccd85df0
--- /dev/null
+++ b/apps/api/src/app/controllers/identity/get.controller.ts
@@ -0,0 +1,21 @@
+import { Request, Response } from 'express';
+import { Pool, Client } from '@thxnetwork/api/models';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { param } from 'express-validator';
+import IdentityService from '@thxnetwork/api/services/IdentityService';
+
+const validation = [param('salt').isString().isLength({ min: 0 })];
+
+const controller = async (req: Request, res: Response) => {
+    const client = await Client.findOne({ clientId: req.auth.client_id });
+    if (!client) throw new NotFoundError('Could not find client for token');
+
+    const pool = await Pool.findById(client.poolId);
+    if (!pool) throw new NotFoundError('Could not find pool for client');
+
+    const identity = await IdentityService.getIdentityForSalt(pool, req.params.salt);
+
+    res.json(identity.uuid);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/identity/identity.router.ts b/apps/api/src/app/controllers/identity/identity.router.ts
new file mode 100644
index 000000000..832a074e8
--- /dev/null
+++ b/apps/api/src/app/controllers/identity/identity.router.ts
@@ -0,0 +1,23 @@
+import express from 'express';
+import * as CreateController from './post.controller';
+import * as UpdateController from './patch.controller';
+import * as ReadController from './get.controller';
+import { assertRequestInput, guard } from '@thxnetwork/api/middlewares';
+
+const router: express.Router = express.Router();
+
+router.patch('/:uuid', assertRequestInput(UpdateController.validation), UpdateController.controller);
+router.get(
+    '/:salt',
+    guard.check(['identities:read']),
+    assertRequestInput(ReadController.validation),
+    ReadController.controller,
+);
+router.post(
+    '/',
+    guard.check(['identities:write']),
+    assertRequestInput(CreateController.validation),
+    CreateController.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/identity/patch.controller.ts b/apps/api/src/app/controllers/identity/patch.controller.ts
new file mode 100644
index 000000000..07a5de856
--- /dev/null
+++ b/apps/api/src/app/controllers/identity/patch.controller.ts
@@ -0,0 +1,24 @@
+import { Request, Response } from 'express';
+import { Pool, Identity } from '@thxnetwork/api/models';
+import { ForbiddenError, NotFoundError } from '@thxnetwork/api/util/errors';
+import { param } from 'express-validator';
+
+const validation = [param('uuid').isUUID()];
+
+const controller = async (req: Request, res: Response) => {
+    const pool = await Pool.findById(req.header('X-PoolId'));
+    if (!pool) throw new NotFoundError('Pool not found.');
+
+    const { uuid } = req.params;
+    const { sub } = req.auth;
+
+    // Throw if Identity is connected already
+    const isConnected = await Identity.exists({ uuid, sub: { $exists: true } });
+    if (isConnected) throw new ForbiddenError('Identity already connected.');
+
+    const identity = await Identity.findOneAndUpdate({ uuid }, { sub }, { new: true });
+
+    res.json(identity);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/identity/post.controller.ts b/apps/api/src/app/controllers/identity/post.controller.ts
new file mode 100644
index 000000000..375901c07
--- /dev/null
+++ b/apps/api/src/app/controllers/identity/post.controller.ts
@@ -0,0 +1,21 @@
+import { Request, Response } from 'express';
+import { Pool, Identity, Client } from '@thxnetwork/api/models';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { uuidV1 } from '@thxnetwork/api/util/uuid';
+
+const validation = [];
+
+const controller = async (req: Request, res: Response) => {
+    const client = await Client.findOne({ clientId: req.auth.client_id });
+    if (!client) throw new NotFoundError('Could not find client for token');
+
+    const pool = await Pool.findById(client.poolId);
+    if (!pool) throw new NotFoundError('Could not find pool for client');
+
+    const uuid = uuidV1();
+    const id = await Identity.create({ poolId: pool._id, uuid });
+
+    res.json(id.uuid);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/index.ts b/apps/api/src/app/controllers/index.ts
new file mode 100644
index 000000000..73251ba9b
--- /dev/null
+++ b/apps/api/src/app/controllers/index.ts
@@ -0,0 +1,67 @@
+import express from 'express';
+import RouterHealth from './health/health.router';
+import RouterAccount from './account/account.router';
+import RouterPools from './pools/pools.router';
+import RouterToken from './token/token.router';
+import RouterParticipants from './participants/participants.router';
+import RouterMetadata from './metadata/metadata.router';
+import RouterUpload from './upload/upload.router';
+import RouterERC20 from './erc20/erc20.router';
+import RouterERC721 from './erc721/erc721.router';
+import RouterERC1155 from './erc1155/erc1155.router';
+import RouterClients from './client/client.router';
+import RouterQRCodes from './qr-codes/qr-codes.router';
+import RouterBrands from './brands/brands.router';
+import RouterWidget from './widget/widget.router';
+import RouterQuests from './quests/quests.router';
+import RouterRewards from './rewards/rewards.router';
+import RouterLeaderboards from './leaderboards/leaderboards.router';
+import RouterWebhook from './webhook/webhook.router';
+import RouterWebhooks from './webhooks/webhooks.router';
+import RouterPrices from './earn/earn.router';
+import RouterWidgets from './widgets/widgets.router';
+import RouterIdentity from './identity/identity.router';
+import RouterEvents from './events/events.router';
+import RouterData from './data/data.router';
+import RouterLiquidity from './liquidity/liquidity.router';
+import RouterVoteEscrow from './ve/ve.router';
+import RouterJobs from './jobs/jobs.router';
+import RouterCoupons from './coupons/coupons.router';
+import { checkJwt, corsHandler } from '@thxnetwork/api/middlewares';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.use('/ping', (_req, res) => res.send('pong'));
+router.use('/health', RouterHealth);
+router.use('/data', RouterData);
+router.use('/token', RouterToken);
+router.use('/metadata', RouterMetadata);
+router.use('/brands', RouterBrands);
+router.use('/widget', RouterWidget);
+router.use('/leaderboards', RouterLeaderboards);
+router.use('/claims', RouterQRCodes); // Legacy QR codes still redirect to /claims/r/:uuid
+router.use('/qr-codes', RouterQRCodes);
+router.use('/quests', RouterQuests);
+router.use('/rewards', RouterRewards);
+router.use('/webhook', RouterWebhook);
+router.use('/earn', RouterPrices);
+router.use(checkJwt, corsHandler);
+router.use('/jobs', RouterJobs);
+router.use('/upload', RouterUpload);
+router.use('/identity', RouterIdentity);
+router.use('/events', RouterEvents);
+router.use('/coupons', RouterCoupons);
+router.use('/account', RouterAccount);
+router.use('/participants', RouterParticipants);
+router.use('/pools', RouterPools);
+router.use('/widgets', RouterWidgets);
+router.use('/clients', RouterClients);
+router.use('/webhooks', RouterWebhooks);
+router.use('/ve', RouterVoteEscrow);
+router.use('/liquidity', RouterLiquidity);
+
+router.use('/erc20', RouterERC20);
+router.use('/erc721', RouterERC721);
+router.use('/erc1155', RouterERC1155);
+
+export { router };
diff --git a/apps/api/src/app/controllers/jobs/get.controller.ts b/apps/api/src/app/controllers/jobs/get.controller.ts
new file mode 100644
index 000000000..38a5a45af
--- /dev/null
+++ b/apps/api/src/app/controllers/jobs/get.controller.ts
@@ -0,0 +1,12 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import { Job } from '@thxnetwork/api/models/Job';
+
+const validation = [param('id').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const job = await Job.findById(req.params.id);
+    res.json(job);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/jobs/jobs.router.ts b/apps/api/src/app/controllers/jobs/jobs.router.ts
new file mode 100644
index 000000000..a2d23db45
--- /dev/null
+++ b/apps/api/src/app/controllers/jobs/jobs.router.ts
@@ -0,0 +1,16 @@
+import express from 'express';
+import * as ReadJobs from './get.controller';
+import { assertRequestInput, guard } from '@thxnetwork/api/middlewares';
+
+const router: express.Router = express.Router();
+
+router.get(
+    '/:id',
+    guard.check([
+        // 'jobs:read'
+    ]),
+    assertRequestInput(ReadJobs.validation),
+    ReadJobs.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/leaderboards/get.controller.ts b/apps/api/src/app/controllers/leaderboards/get.controller.ts
new file mode 100644
index 000000000..4416f5bfe
--- /dev/null
+++ b/apps/api/src/app/controllers/leaderboards/get.controller.ts
@@ -0,0 +1,25 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import PoolService from '@thxnetwork/api/services/PoolService';
+
+const validation = [param('campaignId').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const pool = await PoolService.getById(req.params.campaignId);
+    const leaderboard = await PoolService.findParticipants(pool, 1, 10);
+    const result = leaderboard.results.map((p) => {
+        return {
+            rank: p.rank,
+            account: {
+                username: p.account && p.account.username,
+                profileImg: p.account && p.account.profileImg,
+            },
+            questsCompleted: p.questEntryCount,
+            score: p.score,
+        };
+    });
+
+    res.json(result);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/leaderboards/leaderboards.router.ts b/apps/api/src/app/controllers/leaderboards/leaderboards.router.ts
new file mode 100644
index 000000000..cd1648e4a
--- /dev/null
+++ b/apps/api/src/app/controllers/leaderboards/leaderboards.router.ts
@@ -0,0 +1,11 @@
+import express from 'express';
+import { assertRequestInput } from '@thxnetwork/api/middlewares';
+import * as ListLeaderboard from './list.controller';
+import * as ReadLeaderboard from './get.controller';
+
+export const router: express.Router = express.Router();
+
+router.get('/', assertRequestInput(ListLeaderboard.validation), ListLeaderboard.controller);
+router.get('/:campaignId', assertRequestInput(ReadLeaderboard.validation), ReadLeaderboard.controller);
+
+export default router;
diff --git a/apps/api/src/app/controllers/leaderboards/list.controller.ts b/apps/api/src/app/controllers/leaderboards/list.controller.ts
new file mode 100644
index 000000000..990bf9649
--- /dev/null
+++ b/apps/api/src/app/controllers/leaderboards/list.controller.ts
@@ -0,0 +1,67 @@
+import { Request, Response } from 'express';
+import { Pool, PoolDocument, Brand } from '@thxnetwork/api/models';
+import { Widget } from '@thxnetwork/api/models/Widget';
+import { query } from 'express-validator';
+import { Participant } from '@thxnetwork/api/models/Participant';
+import RewardService from '@thxnetwork/api/services/RewardService';
+import QuestService from '@thxnetwork/api/services/QuestService';
+
+const matchTitle = (search) => {
+    if (!search || !search.length) return;
+    return new RegExp(
+        search
+            .split(/\s+/)
+            .map((word) => `(?=.*${word})`)
+            .join(''),
+        'i',
+    );
+};
+
+export const paginatedResults = async (page: number, limit: number, search: string) => {
+    const startIndex = (page - 1) * limit;
+    const $match = {
+        'rank': { $exists: true },
+        'settings.isPublished': true,
+        ...(search && { 'settings.title': matchTitle(search) }),
+    };
+    const total = await Pool.countDocuments($match);
+    const results = await Pool.find($match).sort({ rank: 1 }).skip(startIndex).limit(limit);
+
+    return { page, total, limit, results };
+};
+
+const validation = [query('page').isInt(), query('limit').isInt(), query('search').optional().isString()];
+
+const controller = async (req: Request, res: Response) => {
+    const { page, limit, search } = req.query;
+    const result = await paginatedResults(Number(page), Number(limit), search ? String(search) : '');
+    const widgets = await Widget.find({ poolId: result.results.map((p: PoolDocument) => p._id) });
+    const brands = await Brand.find({ poolId: result.results.map((p: PoolDocument) => p._id) });
+
+    result.results = (await Promise.all(
+        result.results.map(async (pool) => {
+            const widget = widgets.find((w) => w.poolId === String(pool._id));
+            const brand = brands.find((b) => b.poolId === String(pool._id));
+            const participantCount = await Participant.countDocuments({ poolId: pool._id });
+            const questCount = await QuestService.count({ poolId: pool._id });
+            const rewardCount = await RewardService.count({ poolId: pool._id });
+            return {
+                _id: pool._id,
+                rank: pool.rank,
+                slug: pool.settings.slug || pool._id,
+                title: pool.settings.title,
+                domain: widget ? widget.domain : 'https://app.thx.network',
+                logoImgUrl: brand && brand.logoImgUrl,
+                backgroundImgUrl: brand && brand.backgroundImgUrl,
+                participantCount,
+                questCount,
+                rewardCount,
+                createdAt: pool.createdAt,
+            };
+        }),
+    )) as any;
+
+    res.json(result);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/liquidity/liquidity.router.ts b/apps/api/src/app/controllers/liquidity/liquidity.router.ts
new file mode 100644
index 000000000..49a74f5f5
--- /dev/null
+++ b/apps/api/src/app/controllers/liquidity/liquidity.router.ts
@@ -0,0 +1,12 @@
+import express from 'express';
+import { assertWallet, assertRequestInput } from '@thxnetwork/api/middlewares';
+import * as CreateLiquidity from './post.controller';
+import * as CreateLiquidityStaked from './stake/post.controller';
+
+const router: express.Router = express.Router();
+
+router.use('/', assertWallet);
+router.post('/', assertRequestInput(CreateLiquidity.validation), CreateLiquidity.controller);
+router.post('/stake', assertRequestInput(CreateLiquidityStaked.validation), CreateLiquidityStaked.controller);
+
+export default router;
diff --git a/apps/api/src/app/controllers/liquidity/post.controller.ts b/apps/api/src/app/controllers/liquidity/post.controller.ts
new file mode 100644
index 000000000..0abffce3e
--- /dev/null
+++ b/apps/api/src/app/controllers/liquidity/post.controller.ts
@@ -0,0 +1,17 @@
+import LiquidityService from '@thxnetwork/api/services/LiquidityService';
+import { Request, Response } from 'express';
+import { query, body } from 'express-validator';
+
+const validation = [
+    body('usdcAmountInWei').isString(),
+    body('thxAmountInWei').isString(),
+    body('slippage').isString(),
+    query('walletId').isMongoId(),
+];
+
+const controller = async ({ wallet, body }: Request, res: Response) => {
+    const tx = await LiquidityService.create(wallet, body.usdcAmountInWei, body.thxAmountInWei, body.slippage);
+
+    res.status(201).json([tx]);
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/liquidity/stake/post.controller.ts b/apps/api/src/app/controllers/liquidity/stake/post.controller.ts
new file mode 100644
index 000000000..8abe0ad29
--- /dev/null
+++ b/apps/api/src/app/controllers/liquidity/stake/post.controller.ts
@@ -0,0 +1,26 @@
+import { getArtifact } from '@thxnetwork/api/hardhat';
+import { BadRequestError } from '@thxnetwork/api/util/errors';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { contractNetworks } from '@thxnetwork/api/hardhat';
+import { Request, Response } from 'express';
+import { body, query } from 'express-validator';
+import { BigNumber } from 'alchemy-sdk';
+import LiquidityService from '@thxnetwork/api/services/LiquidityService';
+
+const validation = [body('amountInWei').isString(), query('walletId').isMongoId()];
+
+const controller = async ({ wallet, body }: Request, res: Response) => {
+    const { web3 } = getProvider(wallet.chainId);
+    const bpt = new web3.eth.Contract(getArtifact('BPT').abi, contractNetworks[wallet.chainId].BPT);
+
+    // Check if sender has sufficient BPT
+    const balanceInWei = await bpt.methods.balanceOf(wallet.address).call();
+    if (BigNumber.from(balanceInWei).lt(body.amountInWei)) {
+        throw new BadRequestError('Insufficient balance');
+    }
+
+    const tx = await LiquidityService.stake(wallet, body.amountInWei);
+
+    res.status(201).json([tx]);
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/metadata/erc1155/get.controller.ts b/apps/api/src/app/controllers/metadata/erc1155/get.controller.ts
new file mode 100644
index 000000000..a5c128aab
--- /dev/null
+++ b/apps/api/src/app/controllers/metadata/erc1155/get.controller.ts
@@ -0,0 +1,24 @@
+import { param } from 'express-validator';
+import { Request, Response } from 'express';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { ERC1155Metadata } from '@thxnetwork/api/models/ERC1155Metadata';
+
+const validation = [param('erc1155Id').isMongoId(), param('tokenId').isInt()];
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['ERC1155 Metadata']
+    const { erc1155Id, tokenId } = req.params;
+    const metadata = await ERC1155Metadata.findOne({ erc1155Id, tokenId });
+    if (!metadata) throw new NotFoundError('Could not find metadata for this ID');
+
+    const attributes = {
+        name: metadata.name,
+        description: metadata.description,
+        image: metadata.image,
+        external_url: metadata.externalUrl,
+    };
+
+    res.header('Content-Type', 'application/json').send(JSON.stringify(attributes, null, 4));
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/metadata/get.controller.ts b/apps/api/src/app/controllers/metadata/get.controller.ts
new file mode 100644
index 000000000..b89bf0067
--- /dev/null
+++ b/apps/api/src/app/controllers/metadata/get.controller.ts
@@ -0,0 +1,23 @@
+import { param } from 'express-validator';
+import { Request, Response } from 'express';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { ERC721Metadata } from '@thxnetwork/api/models/ERC721Metadata';
+
+const validation = [param('metadataId').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['ERC721 Metadata']
+    const metadata = await ERC721Metadata.findById(req.params.metadataId);
+    if (!metadata) throw new NotFoundError('Could not find metadata for this ID');
+
+    const attributes = {
+        name: metadata.name,
+        description: metadata.description,
+        image: metadata.image,
+        external_url: metadata.externalUrl,
+    };
+
+    res.header('Content-Type', 'application/json').send(JSON.stringify(attributes, null, 4));
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/metadata/metadata.router.ts b/apps/api/src/app/controllers/metadata/metadata.router.ts
new file mode 100644
index 000000000..49d43904f
--- /dev/null
+++ b/apps/api/src/app/controllers/metadata/metadata.router.ts
@@ -0,0 +1,15 @@
+import express from 'express';
+import { assertRequestInput } from '@thxnetwork/api/middlewares';
+import * as ReadMetadata from './get.controller';
+import * as ReadERC1155Metadata from './erc1155/get.controller';
+
+const router: express.Router = express.Router();
+
+router.get(
+    '/erc1155/:erc1155Id/:tokenId',
+    assertRequestInput(ReadERC1155Metadata.validation),
+    ReadERC1155Metadata.controller,
+);
+router.get('/:metadataId', assertRequestInput(ReadMetadata.validation), ReadMetadata.controller);
+
+export default router;
diff --git a/apps/api/src/app/controllers/participants/get.controller.ts b/apps/api/src/app/controllers/participants/get.controller.ts
new file mode 100644
index 000000000..0e649e376
--- /dev/null
+++ b/apps/api/src/app/controllers/participants/get.controller.ts
@@ -0,0 +1,38 @@
+import { Request, Response } from 'express';
+import { Participant } from '@thxnetwork/api/models/Participant';
+import { query } from 'express-validator';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+import IdentityService from '@thxnetwork/api/services/IdentityService';
+import PoolService from '@thxnetwork/api/services/PoolService';
+
+const validation = [query('poolId').optional().isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const poolId = req.query.poolId as string;
+    const query: { sub: string; poolId?: string } = { sub: req.auth.sub };
+    if (poolId) query.poolId = poolId;
+
+    // Get all participants for the authenticated user and optionally filter by poolId
+    const participants = await Participant.find(query);
+
+    // Run pool specific operations
+    if (poolId) {
+        const pool = await PoolService.getById(poolId);
+        const account = await AccountProxy.findById(req.auth.sub);
+        if (!account) throw new NotFoundError('Account not found.');
+
+        // Force connect account address as identity might be available
+        await IdentityService.forceConnect(pool, account);
+
+        // If no participants were found, create a participant for the authenticated user
+        if (!participants.length) {
+            const participant = await Participant.create({ poolId, sub: account.sub });
+            participants.push(participant);
+        }
+    }
+
+    res.json(participants);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/participants/participants.router.ts b/apps/api/src/app/controllers/participants/participants.router.ts
new file mode 100644
index 000000000..d4e914a45
--- /dev/null
+++ b/apps/api/src/app/controllers/participants/participants.router.ts
@@ -0,0 +1,21 @@
+import { assertRequestInput, guard } from '@thxnetwork/api/middlewares';
+import express from 'express';
+import * as ListParticipants from './get.controller';
+import * as UpdateParticipants from './patch.controller';
+
+const router: express.Router = express.Router();
+
+router.get(
+    '/',
+    guard.check(['point_balances:read']),
+    assertRequestInput(ListParticipants.validation),
+    ListParticipants.controller,
+);
+router.patch(
+    '/:id',
+    guard.check(['point_balances:read']),
+    assertRequestInput(UpdateParticipants.validation),
+    UpdateParticipants.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/participants/patch.controller.ts b/apps/api/src/app/controllers/participants/patch.controller.ts
new file mode 100644
index 000000000..4e06eb598
--- /dev/null
+++ b/apps/api/src/app/controllers/participants/patch.controller.ts
@@ -0,0 +1,31 @@
+import { Request, Response } from 'express';
+import { Participant } from '@thxnetwork/api/models/Participant';
+import { body, param } from 'express-validator';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+
+const validation = [
+    param('id').isMongoId(),
+    body('isSubscribed').optional().isBoolean(),
+    body('email').optional().isEmail(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const participant = await Participant.findById(req.params.id);
+    if (!participant) throw new NotFoundError('Participant not found.');
+
+    // If subscribed is true and email we set the participant flag to true and patch the account
+    if (req.body.isSubscribed && req.body.email) {
+        const isSubscribed = JSON.parse(req.body.isSubscribed);
+
+        if (isSubscribed) {
+            await AccountProxy.update(req.auth.sub, { email: String(req.body.email) } as TAccount);
+        }
+
+        await participant.updateOne({ isSubscribed });
+    }
+
+    res.status(204).end();
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/analytics/analytics.router.ts b/apps/api/src/app/controllers/pools/analytics/analytics.router.ts
new file mode 100644
index 000000000..ceb3dda7e
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/analytics/analytics.router.ts
@@ -0,0 +1,18 @@
+import express from 'express';
+import { assertRequestInput, assertPoolAccess, guard } from '@thxnetwork/api/middlewares';
+import RouterMetrics from './metrics/metrics.router';
+import * as ListAnalytics from './list.controller';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.get(
+    '/',
+    guard.check(['pools:read']),
+    assertPoolAccess,
+    assertRequestInput(ListAnalytics.validation),
+    ListAnalytics.controller,
+);
+
+router.use('/metrics', RouterMetrics);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/analytics/list.controller.ts b/apps/api/src/app/controllers/pools/analytics/list.controller.ts
new file mode 100644
index 000000000..dc912070f
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/analytics/list.controller.ts
@@ -0,0 +1,17 @@
+import { Request, Response } from 'express';
+import { param, query } from 'express-validator';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import AnalyticsService from '@thxnetwork/api/services/AnalyticsService';
+
+const validation = [param('id').isMongoId(), query('startDate').exists(), query('endDate').exists()];
+
+const controller = async (req: Request, res: Response) => {
+    const pool = await PoolService.getById(req.params.id);
+    const startDate = new Date(String(req.query.startDate));
+    const endDate = new Date(String(req.query.endDate));
+    const result = await AnalyticsService.getPoolAnalyticsForChart(pool, startDate, endDate);
+
+    res.json(result);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/analytics/metrics/list.controller.ts b/apps/api/src/app/controllers/pools/analytics/metrics/list.controller.ts
new file mode 100644
index 000000000..0c8b55f71
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/analytics/metrics/list.controller.ts
@@ -0,0 +1,20 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import AnalyticsService from '@thxnetwork/api/services/AnalyticsService';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import { Participant } from '@thxnetwork/api/models/Participant';
+
+const validation = [param('id').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['Pools']
+    const pool = await PoolService.getById(req.params.id);
+    const metrics = await AnalyticsService.getPoolMetrics(pool);
+    const participantCount = await Participant.countDocuments({ poolId: pool._id });
+    const participantActiveCount = await Participant.countDocuments({ poolId: pool._id, score: { $gt: 0 } });
+    const subscriptionCount = await Participant.countDocuments({ poolId: pool._id, isSubscribed: true });
+
+    res.json({ _id: pool._id, participantCount, participantActiveCount, subscriptionCount, ...metrics });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/analytics/metrics/metrics.router.ts b/apps/api/src/app/controllers/pools/analytics/metrics/metrics.router.ts
new file mode 100644
index 000000000..789d44352
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/analytics/metrics/metrics.router.ts
@@ -0,0 +1,15 @@
+import express from 'express';
+import { assertRequestInput, assertPoolAccess, guard } from '@thxnetwork/api/middlewares';
+import * as ListMetrics from './list.controller';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.get(
+    '/',
+    guard.check(['pools:read']),
+    assertPoolAccess,
+    assertRequestInput(ListMetrics.validation),
+    ListMetrics.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/collaborators/collaborators.router.ts b/apps/api/src/app/controllers/pools/collaborators/collaborators.router.ts
new file mode 100644
index 000000000..bf3469c98
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/collaborators/collaborators.router.ts
@@ -0,0 +1,30 @@
+import express from 'express';
+import { assertRequestInput, assertPoolAccess, guard } from '@thxnetwork/api/middlewares';
+import * as CreateController from './post.controller';
+import * as UpdateController from './patch.controller';
+import * as RemoveController from './delete.controller';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.post(
+    '/',
+    guard.check(['pools:read', 'pools:write']),
+    assertPoolAccess,
+    assertRequestInput(CreateController.validation),
+    CreateController.controller,
+);
+router.patch(
+    '/:uuid',
+    guard.check(['pools:read', 'pools:write']),
+    assertRequestInput(UpdateController.validation),
+    UpdateController.controller,
+);
+router.delete(
+    '/:uuid',
+    guard.check(['pools:read', 'pools:write']),
+    assertPoolAccess,
+    assertRequestInput(RemoveController.validation),
+    RemoveController.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/collaborators/delete.controller.ts b/apps/api/src/app/controllers/pools/collaborators/delete.controller.ts
new file mode 100644
index 000000000..9689f1a92
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/collaborators/delete.controller.ts
@@ -0,0 +1,21 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import { ForbiddenError, NotFoundError } from '@thxnetwork/api/util/errors';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import { Collaborator } from '@thxnetwork/api/models/Collaborator';
+
+const validation = [param('id').isMongoId(), param('uuid').isUUID(4)];
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['Pools']
+    const pool = await PoolService.getById(req.params.id);
+    const collaborator = await Collaborator.findOne({ poolId: pool._id, uuid: req.params.uuid });
+    if (!collaborator) throw new NotFoundError('Could not find collaborator');
+    if (collaborator.sub === pool.sub) throw new ForbiddenError('Can not remove campaign owner');
+
+    await collaborator.deleteOne();
+
+    res.status(204).end();
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/collaborators/patch.controller.ts b/apps/api/src/app/controllers/pools/collaborators/patch.controller.ts
new file mode 100644
index 000000000..ba372cac7
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/collaborators/patch.controller.ts
@@ -0,0 +1,26 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import { Collaborator } from '@thxnetwork/api/models/Collaborator';
+import { CollaboratorInviteState } from '@thxnetwork/common/enums';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+
+const validation = [param('id').isMongoId(), param('uuid').isUUID(4)];
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['Pools']
+    const pool = await PoolService.getById(req.params.id);
+    const collaborator = await Collaborator.findOne({ poolId: req.params.id, uuid: req.params.uuid });
+    if (!collaborator) throw new NotFoundError('Could not find collaboration invite');
+
+    if (pool.sub !== req.body.sub) {
+        await collaborator.updateOne({
+            sub: req.auth.sub,
+            state: CollaboratorInviteState.Accepted,
+        });
+    }
+
+    res.end();
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/collaborators/post.controller.ts b/apps/api/src/app/controllers/pools/collaborators/post.controller.ts
new file mode 100644
index 000000000..74fbb57e4
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/collaborators/post.controller.ts
@@ -0,0 +1,14 @@
+import { Request, Response } from 'express';
+import { param, body } from 'express-validator';
+import PoolService from '@thxnetwork/api/services/PoolService';
+
+const validation = [param('id').isMongoId(), body('email').isEmail()];
+
+const controller = async (req: Request, res: Response) => {
+    const pool = await PoolService.getById(req.params.id);
+    const collaborator = await PoolService.inviteCollaborator(pool, req.body.email);
+
+    res.json(collaborator);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/delete.controller.ts b/apps/api/src/app/controllers/pools/delete.controller.ts
new file mode 100644
index 000000000..14b773433
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/delete.controller.ts
@@ -0,0 +1,18 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { Pool } from '@thxnetwork/api/models';
+
+const validation = [param('id').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const pool = await PoolService.getById(req.params.id);
+    if (!pool) throw new NotFoundError('Could not find pool for this ID');
+
+    await Pool.deleteOne({ _id: pool._id });
+
+    res.status(204).end();
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/erc1155/balance/balance.router.ts b/apps/api/src/app/controllers/pools/erc1155/balance/balance.router.ts
new file mode 100644
index 000000000..1e4d828ad
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/erc1155/balance/balance.router.ts
@@ -0,0 +1,9 @@
+import express from 'express';
+import { assertRequestInput, guard } from '@thxnetwork/api/middlewares';
+import * as ReadBalances from './get.controller';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.get('/', guard.check(['erc1155:read']), assertRequestInput(ReadBalances.validation), ReadBalances.controller);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/erc1155/balance/get.controller.ts b/apps/api/src/app/controllers/pools/erc1155/balance/get.controller.ts
new file mode 100644
index 000000000..f8c0d37a5
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/erc1155/balance/get.controller.ts
@@ -0,0 +1,22 @@
+import { Request, Response } from 'express';
+import { query } from 'express-validator';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { Pool } from '@thxnetwork/api/models';
+import ContractService from '@thxnetwork/api/services/ContractService';
+import SafeService from '@thxnetwork/api/services/SafeService';
+
+const validation = [query('contractAddress').isEthereumAddress(), query('tokenId').isInt()];
+
+const controller = async (req: Request, res: Response) => {
+    const pool = await Pool.findById(req.params.id);
+    if (!pool) throw new NotFoundError('Pool not found');
+
+    const safe = await SafeService.findOneByPool(pool, pool.chainId);
+    if (!safe) throw new NotFoundError('Safe not found');
+
+    const contract = ContractService.getContract('THXERC1155', pool.chainId, req.query.contractAddress as string);
+    const balance = await contract.balanceOf(safe.address, req.query.tokenId);
+
+    res.json({ balance });
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/erc1155/erc1155.router.ts b/apps/api/src/app/controllers/pools/erc1155/erc1155.router.ts
new file mode 100644
index 000000000..3808e042a
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/erc1155/erc1155.router.ts
@@ -0,0 +1,8 @@
+import express from 'express';
+import RouterBalance from './balance/balance.router';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.use('/balance', RouterBalance);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/erc20/allowance/allowance.router.ts b/apps/api/src/app/controllers/pools/erc20/allowance/allowance.router.ts
new file mode 100644
index 000000000..81d369661
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/erc20/allowance/allowance.router.ts
@@ -0,0 +1,11 @@
+import express from 'express';
+import { assertRequestInput } from '@thxnetwork/api/middlewares';
+import * as ListAllowances from './get.controller';
+import * as CreateAllowances from './post.controller';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.get('/', assertRequestInput(ListAllowances.validation), ListAllowances.controller);
+router.post('/', assertRequestInput(CreateAllowances.validation), CreateAllowances.controller);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/erc20/allowance/get.controller.ts b/apps/api/src/app/controllers/pools/erc20/allowance/get.controller.ts
new file mode 100644
index 000000000..3c880a287
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/erc20/allowance/get.controller.ts
@@ -0,0 +1,31 @@
+import { Request, Response } from 'express';
+import { param, query } from 'express-validator';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import ContractService from '@thxnetwork/api/services/ContractService';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import SafeService from '@thxnetwork/api/services/SafeService';
+
+const validation = [
+    param('id').isMongoId(),
+    query('tokenAddress').isEthereumAddress(),
+    query('spender').isEthereumAddress(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const poolId = req.params.id as string;
+    const pool = await PoolService.getById(poolId);
+    if (!pool) throw new NotFoundError('Pool not found');
+
+    const safe = await SafeService.findOneByPool(pool);
+    if (!safe) throw new NotFoundError('Wallet not found');
+
+    const contract = ContractService.getContract(
+        'THXERC20_LimitedSupply',
+        safe.chainId,
+        req.query.tokenAddress as string,
+    );
+    const allowance = await contract.allowance(safe.address, req.query.spender);
+
+    res.json({ allowanceInWei: allowance.toString() });
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/erc20/allowance/post.controller.ts b/apps/api/src/app/controllers/pools/erc20/allowance/post.controller.ts
new file mode 100644
index 000000000..7b2fb3a41
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/erc20/allowance/post.controller.ts
@@ -0,0 +1,35 @@
+import { Request, Response } from 'express';
+import { body, param } from 'express-validator';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { getArtifact } from '@thxnetwork/api/hardhat';
+import TransactionService from '@thxnetwork/api/services/TransactionService';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import SafeService from '@thxnetwork/api/services/SafeService';
+
+const validation = [
+    param('id').isMongoId(),
+    body('tokenAddress').isEthereumAddress(),
+    body('spender').isEthereumAddress(),
+    body('amountInWei').isString(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const poolId = req.params.id as string;
+    const pool = await PoolService.getById(poolId);
+    if (!pool) throw new NotFoundError('Pool not found');
+
+    const safe = await SafeService.findOneByPool(pool);
+    if (!safe) throw new NotFoundError('Wallet not found');
+
+    const { web3 } = getProvider(safe.chainId);
+    const { abi } = getArtifact('THXERC20_LimitedSupply');
+    const contract = new web3.eth.Contract(abi, req.body.tokenAddress);
+    const fn = contract.methods.approve(req.body.spender, req.body.amountInWei);
+
+    // Propose tx data to relayer and return safeTxHash to track status
+    const tx = await TransactionService.sendSafeAsync(safe, contract.options.address, fn);
+
+    res.status(201).json([tx]);
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/erc20/balance/balance.router.ts b/apps/api/src/app/controllers/pools/erc20/balance/balance.router.ts
new file mode 100644
index 000000000..ceb7452d8
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/erc20/balance/balance.router.ts
@@ -0,0 +1,15 @@
+import express from 'express';
+import { assertRequestInput, assertPoolAccess, guard } from '@thxnetwork/api/middlewares';
+import * as ReadBalances from './get.controller';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.get(
+    '/',
+    guard.check(['pools:read', 'pools:write']),
+    assertPoolAccess,
+    assertRequestInput(ReadBalances.validation),
+    ReadBalances.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/erc20/balance/get.controller.ts b/apps/api/src/app/controllers/pools/erc20/balance/get.controller.ts
new file mode 100644
index 000000000..e93e318c8
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/erc20/balance/get.controller.ts
@@ -0,0 +1,27 @@
+import { Request, Response } from 'express';
+import { param, query } from 'express-validator';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import SafeService from '@thxnetwork/api/services/SafeService';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import ContractService from '@thxnetwork/api/services/ContractService';
+
+const validation = [param('id').isMongoId(), query('tokenAddress').isEthereumAddress()];
+
+const controller = async (req: Request, res: Response) => {
+    const pool = await PoolService.getById(req.params.id);
+    if (!pool) throw new NotFoundError('Campaign not found.');
+
+    const safe = await SafeService.findOneByPool(pool, pool.chainId);
+    if (!safe) throw new NotFoundError('Campaign Safe not found.');
+
+    const contract = ContractService.getContract(
+        'THXERC20_LimitedSupply',
+        safe.chainId,
+        req.query.tokenAddress as string,
+    );
+    const balance = await contract.balanceOf(safe.address);
+
+    res.json({ balanceInWei: balance.toString() });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/erc20/erc20.router.ts b/apps/api/src/app/controllers/pools/erc20/erc20.router.ts
new file mode 100644
index 000000000..6f3d56ca9
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/erc20/erc20.router.ts
@@ -0,0 +1,10 @@
+import express from 'express';
+import RouterBalance from './balance/balance.router';
+import RouterAllowance from './allowance/allowance.router';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.use('/balance', RouterBalance);
+router.use('/allowance', RouterAllowance);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/events/events.router.ts b/apps/api/src/app/controllers/pools/events/events.router.ts
new file mode 100644
index 000000000..da785004e
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/events/events.router.ts
@@ -0,0 +1,15 @@
+import express from 'express';
+import { assertRequestInput, assertPoolAccess, guard } from '@thxnetwork/api/middlewares';
+import * as ListEvents from './list.controller';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.get(
+    '/',
+    guard.check(['pools:read']),
+    assertPoolAccess,
+    assertRequestInput(ListEvents.validation),
+    ListEvents.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/events/list.controller.ts b/apps/api/src/app/controllers/pools/events/list.controller.ts
new file mode 100644
index 000000000..4077fd4ca
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/events/list.controller.ts
@@ -0,0 +1,29 @@
+import { Request, Response } from 'express';
+import { query } from 'express-validator';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { Event, EventDocument } from '@thxnetwork/api/models/Event';
+import { paginatedResults } from '@thxnetwork/api/util/pagination';
+import { Identity } from '@thxnetwork/api/models/Identity';
+import { Pool } from '@thxnetwork/api/models';
+
+const validation = [query('page').isInt(), query('limit').isInt()];
+
+const controller = async (req: Request, res: Response) => {
+    const pool = await Pool.findById(req.header('X-PoolId'));
+    if (!pool) throw new NotFoundError('Could not find pool for token');
+
+    const result = await paginatedResults(Event, Number(req.query.page), Number(req.query.limit), {
+        poolId: pool._id,
+    });
+
+    result.results = await Promise.all(
+        result.results.map(async (event: EventDocument) => {
+            const identity = await Identity.findById(event.identityId);
+            return { ...event.toJSON(), identity };
+        }),
+    );
+
+    res.json(result);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/get.controller.ts b/apps/api/src/app/controllers/pools/get.controller.ts
new file mode 100644
index 000000000..ab7b97c81
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/get.controller.ts
@@ -0,0 +1,65 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import { Participant, Widget, Wallet, Event, Identity } from '@thxnetwork/api/models';
+import { safeVersion } from '@thxnetwork/api/services/ContractService';
+import { logger } from '@thxnetwork/api/util/logger';
+import { ethers } from 'ethers';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import BrandService from '@thxnetwork/api/services/BrandService';
+import SafeService from '@thxnetwork/api/services/SafeService';
+import PaymentService from '@thxnetwork/api/services/PaymentService';
+
+const validation = [param('id').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const pool = await PoolService.getById(req.params.id);
+    let safe = await SafeService.findOneByPool(pool, pool.chainId);
+
+    // Deploy a Safe if none is found
+    if (!safe) {
+        safe = await SafeService.create({
+            chainId: pool.chainId,
+            sub: pool.sub,
+            safeVersion,
+            poolId: req.params.id,
+        });
+        logger.info(`[${req.params.id}] Deployed Campaign Safe ${safe.address}`);
+    }
+
+    // Create a galachain private key if none exists
+    if (!pool.settings.galachainPrivateKey) {
+        const privateKey = ethers.Wallet.createRandom().privateKey;
+        await pool.updateOne({ 'settings.galachainPrivateKey': privateKey });
+    }
+
+    // Fetch all other campaign entities
+    const [widget, brand, wallets, collaborators, owner, events, identities, subscriberCount, balance] =
+        await Promise.all([
+            Widget.findOne({ poolId: req.params.id }),
+            BrandService.get(req.params.id),
+            Wallet.find({ poolId: req.params.id }),
+            PoolService.findCollaborators(pool),
+            PoolService.findOwner(pool),
+            Event.find({ poolId: pool._id }).distinct('name'), // Seperate list (many)
+            Identity.find({ poolId: pool._id }), // Seperate list (many)
+            Participant.countDocuments({ poolId: req.params.id, isSubscribed: true }),
+            PaymentService.balanceOf(safe),
+        ]);
+
+    res.json({
+        ...pool.toJSON(),
+        balance,
+        address: pool.safeAddress,
+        safe,
+        identities,
+        events,
+        wallets,
+        widget,
+        brand,
+        subscriberCount,
+        owner,
+        collaborators,
+    });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/guilds/delete.controller.ts b/apps/api/src/app/controllers/pools/guilds/delete.controller.ts
new file mode 100644
index 000000000..10f8e185c
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/guilds/delete.controller.ts
@@ -0,0 +1,12 @@
+import { param } from 'express-validator';
+import { Request, Response } from 'express';
+import { DiscordGuild } from '@thxnetwork/api/models';
+
+const validation = [param('id').isMongoId(), param('guildId').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    await DiscordGuild.findByIdAndDelete(req.params.guildId);
+    res.status(204).end();
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/guilds/guilds.router.ts b/apps/api/src/app/controllers/pools/guilds/guilds.router.ts
new file mode 100644
index 000000000..8563b01d2
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/guilds/guilds.router.ts
@@ -0,0 +1,39 @@
+import express from 'express';
+import { assertRequestInput, assertPoolAccess, guard } from '@thxnetwork/api/middlewares';
+import * as CreateController from './post.controller';
+import * as UpdateController from './patch.controller';
+import * as RemoveController from './delete.controller';
+import * as ListController from './list.controller';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.get(
+    '/',
+    guard.check(['pools:read']),
+    assertPoolAccess,
+    assertRequestInput(ListController.validation),
+    ListController.controller,
+);
+router.post(
+    '/',
+    guard.check(['pools:read', 'pools:write']),
+    assertPoolAccess,
+    assertRequestInput(CreateController.validation),
+    CreateController.controller,
+);
+router.patch(
+    '/:guildId',
+    guard.check(['pools:read', 'pools:write']),
+    assertPoolAccess,
+    assertRequestInput(UpdateController.validation),
+    UpdateController.controller,
+);
+router.delete(
+    '/:guildId',
+    guard.check(['pools:read', 'pools:write']),
+    assertPoolAccess,
+    assertRequestInput(RemoveController.validation),
+    RemoveController.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/guilds/list.controller.ts b/apps/api/src/app/controllers/pools/guilds/list.controller.ts
new file mode 100644
index 000000000..3beb0e2b9
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/guilds/list.controller.ts
@@ -0,0 +1,13 @@
+import PoolService from '@thxnetwork/api/services/PoolService';
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+
+const validation = [param('id').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const pool = await PoolService.getById(req.params.id);
+    const guilds = await PoolService.findGuilds(pool);
+    res.json(guilds);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/guilds/patch.controller.ts b/apps/api/src/app/controllers/pools/guilds/patch.controller.ts
new file mode 100644
index 000000000..26f9f5a50
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/guilds/patch.controller.ts
@@ -0,0 +1,21 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import { DiscordGuild } from '@thxnetwork/api/models';
+import DiscordDataProxy from '@thxnetwork/api/proxies/DiscordDataProxy';
+import * as CreateController from './post.controller';
+
+const validation = [param('guildId').optional().isMongoId(), ...CreateController.validation];
+
+const controller = async (req: Request, res: Response) => {
+    const { secret, adminRoleId, channelId } = req.body;
+    const guild = await DiscordGuild.findByIdAndUpdate(
+        req.params.guildId,
+        { secret, channelId, adminRoleId, poolId: req.params.id },
+        { new: true },
+    );
+    const result = await DiscordDataProxy.getGuild({ ...guild.toJSON(), isConnected: true });
+
+    res.json(result);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/guilds/post.controller.ts b/apps/api/src/app/controllers/pools/guilds/post.controller.ts
new file mode 100644
index 000000000..d3137a6db
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/guilds/post.controller.ts
@@ -0,0 +1,27 @@
+import { DiscordGuild } from '@thxnetwork/api/models';
+import DiscordDataProxy from '@thxnetwork/api/proxies/DiscordDataProxy';
+import { Request, Response } from 'express';
+import { body, param } from 'express-validator';
+
+const validation = [
+    param('id').isMongoId(),
+    body('settings.channelId').optional().isString(),
+    body('settings.adminRoleId').optional().isString(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const { guildId, name, adminRoleId, channelId } = req.body;
+    const guild = await DiscordGuild.create({
+        sub: req.auth.sub,
+        guildId,
+        name,
+        channelId,
+        adminRoleId,
+        poolId: req.params.id,
+    });
+    const result = await DiscordDataProxy.getGuild({ ...guild.toJSON(), isConnected: true });
+
+    res.json(result);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/identities/delete.controller.ts b/apps/api/src/app/controllers/pools/identities/delete.controller.ts
new file mode 100644
index 000000000..43b11cad6
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/identities/delete.controller.ts
@@ -0,0 +1,17 @@
+import { Request, Response } from 'express';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { Identity } from '@thxnetwork/api/models/Identity';
+import { param } from 'express-validator';
+import PoolService from '@thxnetwork/api/services/PoolService';
+
+const validation = [param('id').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const pool = await PoolService.getById(req.params.id);
+    if (!pool) throw new NotFoundError('Could not find pool for client');
+
+    await Identity.findByIdAndDelete(req.params.identityId);
+    res.status(204).end();
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/identities/get.controller.ts b/apps/api/src/app/controllers/pools/identities/get.controller.ts
new file mode 100644
index 000000000..85e3ec6e1
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/identities/get.controller.ts
@@ -0,0 +1,19 @@
+import { Request, Response } from 'express';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { param, query } from 'express-validator';
+import PoolService from '@thxnetwork/api/services/PoolService';
+
+const validation = [param('id').isMongoId(), query('page').isInt(), query('limit').isInt()];
+
+const controller = async (req: Request, res: Response) => {
+    const pool = await PoolService.getById(req.params.id);
+    if (!pool) throw new NotFoundError('Could not find pool for client');
+
+    const page = Number(req.query.page);
+    const limit = Number(req.query.limit);
+    const identities = await PoolService.findIdentities(pool, page, limit);
+
+    res.json(identities);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/identities/identities.router.ts b/apps/api/src/app/controllers/pools/identities/identities.router.ts
new file mode 100644
index 000000000..0603f6a22
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/identities/identities.router.ts
@@ -0,0 +1,23 @@
+import express from 'express';
+import * as CreateController from './post.controller';
+import * as ListController from './get.controller';
+import * as DeleteController from './delete.controller';
+import { assertRequestInput, guard } from '@thxnetwork/api/middlewares';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.get('/', guard.check(['pools:read']), assertRequestInput(ListController.validation), ListController.controller);
+router.post(
+    '/',
+    guard.check(['pools:read', 'pools:write']),
+    assertRequestInput(CreateController.validation),
+    CreateController.controller,
+);
+router.delete(
+    '/:identityId',
+    guard.check(['pools:read', 'pools:write']),
+    assertRequestInput(DeleteController.validation),
+    DeleteController.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/identities/post.controller.ts b/apps/api/src/app/controllers/pools/identities/post.controller.ts
new file mode 100644
index 000000000..5f85c0308
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/identities/post.controller.ts
@@ -0,0 +1,20 @@
+import { Request, Response } from 'express';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { Identity } from '@thxnetwork/api/models/Identity';
+import { uuidV1 } from '@thxnetwork/api/util/uuid';
+import { param } from 'express-validator';
+import PoolService from '@thxnetwork/api/services/PoolService';
+
+const validation = [param('id').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const pool = await PoolService.getById(req.params.id);
+    if (!pool) throw new NotFoundError('Could not find pool for client');
+
+    const uuid = uuidV1();
+    const id = await Identity.create({ poolId: pool._id, uuid });
+
+    res.json(id.uuid);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/integrations/integrations.router.ts b/apps/api/src/app/controllers/pools/integrations/integrations.router.ts
new file mode 100644
index 000000000..886f3fc84
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/integrations/integrations.router.ts
@@ -0,0 +1,8 @@
+import express from 'express';
+import RouterTwitter from './twitter/twitter.router';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.use('/twitter', RouterTwitter);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/integrations/twitter/queries/delete.controller.ts b/apps/api/src/app/controllers/pools/integrations/twitter/queries/delete.controller.ts
new file mode 100644
index 000000000..f1a5284c3
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/integrations/twitter/queries/delete.controller.ts
@@ -0,0 +1,17 @@
+import { param } from 'express-validator';
+import { Request, Response } from 'express';
+import { TwitterQuery } from '@thxnetwork/api/models';
+import { ForbiddenError } from '@thxnetwork/api/util/errors';
+
+const validation = [param('id').isMongoId(), param('queryId').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const query = await TwitterQuery.findById(req.params.queryId);
+    if (query.poolId !== req.params.id) throw new ForbiddenError('Not your quest.');
+
+    await TwitterQuery.findByIdAndDelete(req.params.queryId);
+
+    res.status(204).end();
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/integrations/twitter/queries/list.controller.ts b/apps/api/src/app/controllers/pools/integrations/twitter/queries/list.controller.ts
new file mode 100644
index 000000000..368485254
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/integrations/twitter/queries/list.controller.ts
@@ -0,0 +1,13 @@
+import { Request, Response } from 'express';
+import { TwitterQuery } from '@thxnetwork/api/models';
+import { param } from 'express-validator';
+import TwitterQueryService from '@thxnetwork/api/services/TwitterQueryService';
+
+const validation = [param('id').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const queries = await TwitterQueryService.list({ poolId: req.params.id });
+    res.json(queries);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/integrations/twitter/queries/patch.controller.ts b/apps/api/src/app/controllers/pools/integrations/twitter/queries/patch.controller.ts
new file mode 100644
index 000000000..89addf2fe
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/integrations/twitter/queries/patch.controller.ts
@@ -0,0 +1,22 @@
+import { body, param } from 'express-validator';
+import { Request, Response } from 'express';
+import { TwitterQuery } from '@thxnetwork/api/models';
+import { TwitterQuery as TwitterQueryParser } from '@thxnetwork/common/twitter';
+
+const validation = [
+    param('id').isMongoId(),
+    param('queryId').isMongoId(),
+    body('operators').customSanitizer((ops) => TwitterQueryParser.parse(ops)),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const query = TwitterQueryParser.create(req.body.operators);
+    const twitterQuery = await TwitterQuery.findByIdAndUpdate(req.params.queryId, {
+        operators: req.body.operators,
+        query,
+    });
+
+    res.json(twitterQuery);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/integrations/twitter/queries/post.controller.ts b/apps/api/src/app/controllers/pools/integrations/twitter/queries/post.controller.ts
new file mode 100644
index 000000000..23dae01e8
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/integrations/twitter/queries/post.controller.ts
@@ -0,0 +1,31 @@
+import { body, param } from 'express-validator';
+import { Request, Response } from 'express';
+import { TwitterQuery } from '@thxnetwork/api/models';
+import { TwitterQuery as TwitterQueryParser } from '@thxnetwork/common/twitter';
+import { BadRequestError } from '@thxnetwork/api/util/errors';
+import TwitterQueryService from '@thxnetwork/api/services/TwitterQueryService';
+
+const validation = [param('id').isMongoId(), body('operators').customSanitizer((ops) => TwitterQueryParser.parse(ops))];
+
+const controller = async (req: Request, res: Response) => {
+    const query = TwitterQueryParser.create(req.body.operators);
+
+    // 512 is the max length for X API queries within the Basic plan
+    if (query.length > 512) {
+        throw new BadRequestError('Your query is too long! Please remove some fields.');
+    }
+
+    const twitterQuery = await TwitterQuery.create({
+        poolId: req.params.id,
+        operators: req.body.operators,
+        defaults: req.body.defaults,
+        query,
+    });
+
+    // Search initial posts and create quests
+    await TwitterQueryService.run([twitterQuery]);
+
+    res.status(201).json(twitterQuery);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/integrations/twitter/twitter.router.ts b/apps/api/src/app/controllers/pools/integrations/twitter/twitter.router.ts
new file mode 100644
index 000000000..ca75ffc7d
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/integrations/twitter/twitter.router.ts
@@ -0,0 +1,39 @@
+import express from 'express';
+import * as CreateController from './queries/post.controller';
+import * as UpdateController from './queries/patch.controller';
+import * as DeleteController from './queries/delete.controller';
+import * as ListController from './queries/list.controller';
+import { assertPoolAccess, assertRequestInput, guard } from '@thxnetwork/api/middlewares';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.get(
+    '/queries',
+    guard.check(['pools:read']),
+    assertPoolAccess,
+    assertRequestInput(ListController.validation),
+    ListController.controller,
+);
+router.post(
+    '/queries',
+    guard.check(['pools:read', 'pools:write']),
+    assertPoolAccess,
+    assertRequestInput(CreateController.validation),
+    CreateController.controller,
+);
+router.patch(
+    '/queries/queryId',
+    guard.check(['pools:read', 'pools:write']),
+    assertPoolAccess,
+    assertRequestInput(UpdateController.validation),
+    UpdateController.controller,
+);
+router.delete(
+    '/queries/:queryId',
+    guard.check(['pools:read', 'pools:write']),
+    assertPoolAccess,
+    assertRequestInput(DeleteController.validation),
+    DeleteController.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/invoices/invoices.router.ts b/apps/api/src/app/controllers/pools/invoices/invoices.router.ts
new file mode 100644
index 000000000..0f67df758
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/invoices/invoices.router.ts
@@ -0,0 +1,9 @@
+import express from 'express';
+import * as ListInvoices from './list.controller';
+import { assertRequestInput, guard } from '@thxnetwork/api/middlewares';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.get('/', guard.check(['pools:read']), assertRequestInput(ListInvoices.validation), ListInvoices.controller);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/invoices/list.controller.ts b/apps/api/src/app/controllers/pools/invoices/list.controller.ts
new file mode 100644
index 000000000..57e6a68b8
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/invoices/list.controller.ts
@@ -0,0 +1,15 @@
+import { Request, Response } from 'express';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import { Invoice } from '@thxnetwork/api/models';
+
+const validation = [];
+
+const controller = async (req: Request, res: Response) => {
+    const pool = await PoolService.getById(req.params.id);
+    if (!pool) throw new Error('Pool not found');
+
+    const invoices = await Invoice.find({ poolId: pool._id });
+    res.json(invoices);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/list.controller.ts b/apps/api/src/app/controllers/pools/list.controller.ts
new file mode 100644
index 000000000..de5486a88
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/list.controller.ts
@@ -0,0 +1,11 @@
+import { Request, Response } from 'express';
+import PoolService from '@thxnetwork/api/services/PoolService';
+
+const validation = [];
+
+const controller = async (req: Request, res: Response) => {
+    const pools = await PoolService.getAllBySub(req.auth.sub);
+    res.json(pools);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/participants/list.controller.ts b/apps/api/src/app/controllers/pools/participants/list.controller.ts
new file mode 100644
index 000000000..3190e4a54
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/participants/list.controller.ts
@@ -0,0 +1,21 @@
+import { Request, Response } from 'express';
+import { param, query } from 'express-validator';
+import PoolService from '@thxnetwork/api/services/PoolService';
+
+const validation = [
+    param('id').isMongoId(),
+    query('page').isInt(),
+    query('limit').isInt(),
+    query('page').optional().isString(),
+    query('query').optional().isString().isLength({ min: 3 }),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const pool = await PoolService.getById(req.params.id);
+    const { page, limit, query } = req.query;
+    const participants = await PoolService.findParticipants(pool, Number(page), Number(limit), query as string);
+
+    res.json(participants);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/participants/participants.router.ts b/apps/api/src/app/controllers/pools/participants/participants.router.ts
new file mode 100644
index 000000000..69b72b83b
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/participants/participants.router.ts
@@ -0,0 +1,23 @@
+import express from 'express';
+import { assertRequestInput, assertPoolAccess, guard } from '@thxnetwork/api/middlewares';
+import * as ListParticipants from './list.controller';
+import * as UpdateParticipants from './patch.controller';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.get(
+    '/',
+    guard.check(['pools:read']),
+    assertPoolAccess,
+    assertRequestInput(ListParticipants.validation),
+    ListParticipants.controller,
+);
+router.patch(
+    '/:participantId',
+    guard.check(['pools:read', 'pools:write']),
+    assertPoolAccess,
+    assertRequestInput(UpdateParticipants.validation),
+    UpdateParticipants.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/participants/patch.controller.ts b/apps/api/src/app/controllers/pools/participants/patch.controller.ts
new file mode 100644
index 000000000..9bc97b22d
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/participants/patch.controller.ts
@@ -0,0 +1,29 @@
+import { Participant } from '@thxnetwork/api/models/Participant';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { Request, Response } from 'express';
+import { body, param } from 'express-validator';
+
+const validation = [
+    param('id').isMongoId(),
+    param('participantId').isMongoId(),
+    body('pointBalance').optional().isInt({ min: 0 }),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const participant = await Participant.findById(req.params.participantId);
+    if (!participant) throw new NotFoundError('Participant not found.');
+
+    let pointBalance;
+    if (typeof req.body.pointBalance !== 'undefined') {
+        const { balance } = await Participant.findOneAndUpdate(
+            { poolId: participant.poolId, sub: participant.sub },
+            { balance: Number(req.body.pointBalance) },
+            { new: true, upsert: true },
+        );
+        pointBalance = balance;
+    }
+
+    res.json({ ...participant.toJSON(), pointBalance });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/patch.controller.ts b/apps/api/src/app/controllers/pools/patch.controller.ts
new file mode 100644
index 000000000..918c1510d
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/patch.controller.ts
@@ -0,0 +1,54 @@
+import { Request, Response } from 'express';
+import { body, param } from 'express-validator';
+import { BadRequestError, NotFoundError } from '@thxnetwork/api/util/errors';
+import { Pool } from '@thxnetwork/api/models';
+import { JobType, agenda } from '@thxnetwork/api/util/agenda';
+import PoolService from '@thxnetwork/api/services/PoolService';
+
+const validation = [
+    param('id').exists(),
+    body('settings.title').optional().isString().trim().escape().isLength({ max: 50 }),
+    body('settings.slug').optional().isString().trim().escape().isLength({ min: 3, max: 25 }),
+    body('settings.description').optional().isString().trim().escape().isLength({ max: 255 }),
+    body('settings.startDate').optional({ nullable: true }).isString(),
+    body('settings.endDate').optional({ nullable: true }).isString(),
+    body('settings.discordWebhookUrl').optional({ checkFalsy: true }).isURL(),
+    body('settings.isArchived').optional().isBoolean(),
+    body('settings.isPublished').optional().isBoolean(),
+    body('settings.isWeeklyDigestEnabled').optional().isBoolean(),
+    body('settings.isTwitterSyncEnabled').optional().isBoolean(),
+    body('settings.defaults.conditionalRewards.title').optional().isString(),
+    body('settings.defaults.conditionalRewards.description').optional().isString(),
+    body('settings.defaults.conditionalRewards.amount').optional().isInt(),
+    body('settings.defaults.conditionalRewards.hashtag').optional().isString(),
+    body('settings.defaults.conditionalRewards.isPublished').optional().isBoolean(),
+    body('settings.authenticationMethods').optional().isArray(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['Pools']
+    const pool = await PoolService.getById(req.params.id);
+    if (!pool) throw new NotFoundError('Could not find the Asset Pool for this id');
+
+    const { settings } = req.body;
+    const isSlugUsed = !!(await Pool.exists({
+        '_id': { $ne: pool._id },
+        'settings.slug': settings.slug,
+    }));
+    if (settings && settings.slug && isSlugUsed) {
+        throw new BadRequestError('This slug is in use already.');
+    }
+
+    const result = await Pool.findByIdAndUpdate(
+        pool._id,
+        { settings: Object.assign(pool.settings, req.body.settings) },
+        { new: true },
+    );
+
+    if (settings.isPublished && settings.isPublished !== pool.settings.isPublished) {
+        await agenda.now(JobType.UpdateCampaignRanks);
+    }
+
+    return res.json(result);
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/payments/payments.router.ts b/apps/api/src/app/controllers/pools/payments/payments.router.ts
new file mode 100644
index 000000000..143b2c945
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/payments/payments.router.ts
@@ -0,0 +1,15 @@
+import express from 'express';
+import { assertRequestInput, assertPoolAccess, guard } from '@thxnetwork/api/middlewares';
+import * as CreatePayments from './post.controller';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.post(
+    '/',
+    guard.check(['pools:read', 'pools:write']),
+    assertPoolAccess,
+    assertRequestInput(CreatePayments.validation),
+    CreatePayments.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/payments/post.controller.ts b/apps/api/src/app/controllers/pools/payments/post.controller.ts
new file mode 100644
index 000000000..8bd893246
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/payments/post.controller.ts
@@ -0,0 +1,52 @@
+import { Request, Response } from 'express';
+import { InsufficientAllowanceError, InsufficientBalanceError, NotFoundError } from '@thxnetwork/api/util/errors';
+import { body, param } from 'express-validator';
+import { BigNumber } from 'alchemy-sdk';
+import { contractArtifacts, contractNetworks } from '@thxnetwork/api/hardhat';
+import { getProvider } from '@thxnetwork/api/util/network';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import SafeService from '@thxnetwork/api/services/SafeService';
+import PaymentService from '@thxnetwork/api/services/PaymentService';
+
+const validation = [param('id').isMongoId(), body('amountInWei').exists(), body('planType').isInt()];
+
+// TODO
+// 1. Customer approves USDC for Campaign Safe for x allowance
+// 2. Campaign Safe calls multiSend for multiple transactions
+// 2.1 transfer 30% of USDC allowance to Company Safe
+// 2.2 joinPool 70% of USDC allowance to BalancerVault
+// 2.3 stake 100% of BPT
+// 2.4 transfer 75% of BPTGauge to RewardDistributor
+// 3. hold 25% of BPTGauge for Quest Incentives (or autocompounding)
+
+const controller = async (req: Request, res: Response) => {
+    const pool = await PoolService.getById(req.params.id);
+    if (!pool) throw new NotFoundError('Could not find campaign');
+
+    const safe = await SafeService.findOneByPool(pool, pool.chainId);
+    if (!safe) throw new NotFoundError('Could not find campaign Safe');
+
+    const amountInWei = BigNumber.from(req.body.amountInWei);
+    const addresses = contractNetworks[safe.chainId];
+
+    // Assert USDC balance for Safe to ensure throughput
+    const { web3 } = getProvider(safe.chainId);
+    const usdc = new web3.eth.Contract(contractArtifacts['USDC'].abi, addresses.USDC);
+    const balance = await usdc.methods.balanceOf(safe.address).call();
+    if (BigNumber.from(balance).lt(amountInWei)) {
+        throw new InsufficientBalanceError();
+    }
+
+    // Assert allowance for Safe to PaymentSplitter
+    const allowance = await usdc.methods.allowance(safe.address, addresses.THXPaymentSplitter).call();
+    if (BigNumber.from(allowance).lt(amountInWei)) {
+        throw new InsufficientAllowanceError();
+    }
+
+    // Execute approve from Safe to PaymentSplitter
+    await PaymentService.deposit(safe, req.auth.sub, amountInWei);
+
+    res.status(201).end();
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/pools.router.ts b/apps/api/src/app/controllers/pools/pools.router.ts
new file mode 100644
index 000000000..6774c92fe
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/pools.router.ts
@@ -0,0 +1,77 @@
+import express from 'express';
+import { assertRequestInput, assertPoolAccess, assertPayment, guard } from '@thxnetwork/api/middlewares';
+
+import * as ListController from './list.controller';
+import * as ReadController from './get.controller';
+import * as CreateController from './post.controller';
+import * as UpdateController from './patch.controller';
+import * as DeleteController from './delete.controller';
+
+import RouterCollaborators from './collaborators/collaborators.router';
+import RouterParticipants from './participants/participants.router';
+import RouterAnalytics from './analytics/analytics.router';
+import RouterEvents from './events/events.router';
+import RouterQuests from './quests/quests.router';
+import RouterRewards from './rewards/rewards.router';
+import RouterGuilds from './guilds/guilds.router';
+import RouterPayments from './payments/payments.router';
+import RouterWallets from './wallets/wallets.router';
+import RouterERC20 from './erc20/erc20.router';
+import RouterER1155 from './erc1155/erc1155.router';
+import RouterIdentities from './identities/identities.router';
+import RouterInvoices from './invoices/invoices.router';
+import RouterIntegrations from './integrations/integrations.router';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.get('/', guard.check(['pools:read']), assertRequestInput(ListController.validation), ListController.controller);
+router.post(
+    '/',
+    guard.check(['pools:read', 'pools:write']),
+    assertRequestInput(CreateController.validation),
+    CreateController.controller,
+);
+
+// This route is also asserted for payment but not for access
+router.use('/:id/collaborators', assertPayment, RouterCollaborators);
+
+// Everything below is asserted for campaign/pool access
+router.use('/:id', assertPoolAccess);
+router.get(
+    '/:id',
+    guard.check(['pools:read']),
+    assertRequestInput(ReadController.validation),
+    ReadController.controller,
+);
+router.patch(
+    '/:id',
+    guard.check(['pools:read', 'pools:write']),
+    assertRequestInput(UpdateController.validation),
+    UpdateController.controller,
+);
+router.delete(
+    '/:id',
+    guard.check(['pools:write']),
+    assertRequestInput(DeleteController.validation),
+    DeleteController.controller,
+);
+
+// Payment related routes that require access event if payment assertion fails
+router.use('/:id/erc20', RouterERC20); // Needed for payment processing
+router.use('/:id/payments', RouterPayments);
+router.use('/:id/invoices', RouterInvoices);
+
+// Everything below is asserted for payment
+router.use('/:id', assertPayment);
+router.use('/:id/analytics', RouterAnalytics);
+router.use('/:id/quests', RouterQuests);
+router.use('/:id/rewards', RouterRewards);
+router.use('/:id/participants', RouterParticipants);
+router.use('/:id/wallets', RouterWallets);
+router.use('/:id/events', RouterEvents);
+router.use('/:id/guilds', RouterGuilds);
+router.use('/:id/erc1155', RouterER1155);
+router.use('/:id/identities', RouterIdentities);
+router.use('/:id/integrations', RouterIntegrations);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/pools.test.ts b/apps/api/src/app/controllers/pools/pools.test.ts
new file mode 100644
index 000000000..ae6bbd964
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/pools.test.ts
@@ -0,0 +1,88 @@
+import request from 'supertest';
+import app from '@thxnetwork/api/';
+import { ChainId } from '@thxnetwork/common/enums';
+import { isAddress } from 'web3-utils';
+import { timeTravel } from '@thxnetwork/api/util/jest/network';
+import { dashboardAccessToken } from '@thxnetwork/api/util/jest/constants';
+import { afterAllCallback, beforeAllCallback } from '@thxnetwork/api/util/jest/config';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { poll } from '@thxnetwork/api/util/polling';
+
+const user = request.agent(app);
+
+describe('Default Pool', () => {
+    let poolId: string, safe: { address: string };
+
+    beforeAll(beforeAllCallback);
+    afterAll(afterAllCallback);
+
+    describe('POST /pools', () => {
+        it('HTTP 201 (success)', async () => {
+            const { body, status } = await user
+                .post('/v1/pools')
+                .set('Authorization', dashboardAccessToken)
+                .send({ title: 'My Pool', chainId: ChainId.Hardhat });
+            expect(status).toBe(201);
+            poolId = body._id;
+            expect(body.safe.address).toBeDefined();
+            safe = body.safe;
+            expect(body.settings.title).toBe('My Pool');
+        });
+
+        it('HTTP 200 (multisig deployed)', async () => {
+            // Wait for campaign safe to be deployed
+            const { web3 } = getProvider(ChainId.Hardhat);
+            await poll(
+                () => web3.eth.getCode(safe.address),
+                (data: string) => data === '0x',
+                1000,
+            );
+
+            await user
+                .get(`/v1/pools/${poolId}`)
+                .set({ 'X-PoolId': poolId, 'Authorization': dashboardAccessToken })
+                .expect((res: request.Response) => {
+                    expect(isAddress(res.body.safeAddress)).toBe(true);
+                })
+                .expect(200);
+        });
+    });
+
+    // describe('GET /pools/:id (post trial)', () => {
+    //     it('HTTP 403 after 2 weeks', async () => {
+    //         // Skip 2 weeks
+    //         await timeTravel(60 * 60 * 24 * 14);
+
+    //         await user
+    //             .get('/v1/pools/' + poolId)
+    //             .set({ Authorization: dashboardAccessToken })
+    //             .expect(403);
+    //     });
+    // });
+
+    describe('PATCH /pools/:id', () => {
+        it('HTTP 200', (done) => {
+            user.patch('/v1/pools/' + poolId)
+                .set({ 'X-PoolId': poolId, 'Authorization': dashboardAccessToken })
+                .send({
+                    settings: {
+                        title: 'My Pool 2',
+                        isArchived: true,
+                    },
+                })
+                .expect(({ body }: request.Response) => {
+                    expect(body.settings.title).toBe('My Pool 2');
+                    expect(body.settings.isArchived).toBe(true);
+                })
+                .expect(200, done);
+        });
+    });
+
+    describe('DELETE /pools/:id', () => {
+        it('HTTP 204', (done) => {
+            user.delete('/v1/pools/' + poolId)
+                .set({ 'X-PoolId': poolId, 'Authorization': dashboardAccessToken })
+                .expect(204, done);
+        });
+    });
+});
diff --git a/apps/api/src/app/controllers/pools/post.controller.ts b/apps/api/src/app/controllers/pools/post.controller.ts
new file mode 100644
index 000000000..1a9f6ae68
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/post.controller.ts
@@ -0,0 +1,24 @@
+import { Request, Response } from 'express';
+import { body } from 'express-validator';
+import ContractService, { safeVersion } from '@thxnetwork/api/services/ContractService';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import SafeService from '@thxnetwork/api/services/SafeService';
+
+const validation = [body('settings.title').optional().isString().trim().escape().isLength({ max: 50 })];
+
+const controller = async (req: Request, res: Response) => {
+    const { title } = req.body;
+    const pool = await PoolService.deploy(req.auth.sub, title || 'My Quest Campaign');
+
+    // Deploy a Safe for the campaign
+    const poolId = String(pool._id);
+    const chainId = ContractService.getChainId();
+    const safe = await SafeService.create({ chainId, sub: req.auth.sub, safeVersion, poolId });
+
+    // Update predicted safe address for pool
+    await pool.updateOne({ safeAddress: safe.address });
+
+    res.status(201).json({ ...pool.toJSON(), safeAddress: safe.address, safe });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/quests/delete.controller.ts b/apps/api/src/app/controllers/pools/quests/delete.controller.ts
new file mode 100644
index 000000000..de864badd
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/quests/delete.controller.ts
@@ -0,0 +1,26 @@
+import { param } from 'express-validator';
+import { Request, Response } from 'express';
+import { ForbiddenError } from '@thxnetwork/api/util/errors';
+import { QuestVariant } from '@thxnetwork/common/enums';
+import LockService from '@thxnetwork/api/services/LockService';
+import QuestService from '@thxnetwork/api/services/QuestService';
+
+const validation = [param('id').isMongoId(), param('variant').isInt(), param('questId').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const variant = req.params.variant as unknown as QuestVariant;
+    const poolId = req.params.id;
+    const questId = req.params.questId;
+
+    const quest = await QuestService.findById(variant, questId);
+    if (quest.poolId !== poolId) throw new ForbiddenError('Not your quest.');
+
+    await quest.deleteOne();
+
+    // Remove all locks for this quest
+    await LockService.removeAllLocks(questId);
+
+    res.status(204).end();
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/quests/entries/entries.router.ts b/apps/api/src/app/controllers/pools/quests/entries/entries.router.ts
new file mode 100644
index 000000000..3d16d3285
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/quests/entries/entries.router.ts
@@ -0,0 +1,15 @@
+import express from 'express';
+import { assertRequestInput, assertPoolAccess, guard } from '@thxnetwork/api/middlewares';
+import * as ListController from './list.controller';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.get(
+    '/',
+    guard.check(['pools:read']),
+    assertPoolAccess,
+    assertRequestInput(ListController.validation),
+    ListController.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/quests/entries/list.controller.ts b/apps/api/src/app/controllers/pools/quests/entries/list.controller.ts
new file mode 100644
index 000000000..fd731be20
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/quests/entries/list.controller.ts
@@ -0,0 +1,26 @@
+import { Request, Response } from 'express';
+import { param, query } from 'express-validator';
+import { QuestVariant } from '@thxnetwork/common/enums';
+import QuestService from '@thxnetwork/api/services/QuestService';
+
+const validation = [
+    param('id').isMongoId(),
+    param('variant').isString(),
+    param('questId').isMongoId(),
+    query('page').isInt(),
+    query('limit').isInt(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const variant = req.params.variant as unknown as QuestVariant;
+    const questId = req.params.questId as string;
+    const quest = await QuestService.findById(variant, questId);
+    const entries = await QuestService.findEntries(quest, {
+        page: Number(req.query.page),
+        limit: Number(req.query.limit),
+    });
+
+    res.json(entries);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/quests/list.controller.ts b/apps/api/src/app/controllers/pools/quests/list.controller.ts
new file mode 100644
index 000000000..0bd892333
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/quests/list.controller.ts
@@ -0,0 +1,60 @@
+import { param, query } from 'express-validator';
+import { Request, Response } from 'express';
+import {
+    QuestInvite,
+    QuestSocial,
+    QuestCustom,
+    QuestWeb3,
+    QuestGitcoin,
+    QuestDaily,
+    QuestWebhook,
+} from '@thxnetwork/api/models';
+
+const validation = [
+    param('id').isMongoId(),
+    query('page').isInt(),
+    query('limit').isInt(),
+    query('isPublished')
+        .optional()
+        .isBoolean()
+        .customSanitizer((value) => {
+            return value && JSON.parse(value);
+        }),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const poolId = req.params.id;
+    const page = Number(req.query.page);
+    const limit = Number(req.query.limit);
+    const $match = { poolId, isPublished: req.query.isPublished };
+    const pipeline = [
+        { $unionWith: { coll: QuestInvite.collection.name } },
+        { $unionWith: { coll: QuestSocial.collection.name } },
+        { $unionWith: { coll: QuestCustom.collection.name } },
+        { $unionWith: { coll: QuestWeb3.collection.name } },
+        { $unionWith: { coll: QuestGitcoin.collection.name } },
+        { $unionWith: { coll: QuestWebhook.collection.name } },
+        { $match },
+    ];
+    const arr = await Promise.all(
+        [QuestDaily, QuestInvite, QuestSocial, QuestCustom, QuestWeb3, QuestGitcoin, QuestWebhook].map(
+            async (model) => await model.countDocuments($match),
+        ),
+    );
+    const total = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
+    const results = await QuestDaily.aggregate([
+        ...pipeline,
+        { $sort: { index: 1 } },
+        { $skip: (page - 1) * limit },
+        { $limit: limit },
+    ]);
+
+    res.json({
+        total,
+        limit,
+        page,
+        results,
+    });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/quests/patch.controller.ts b/apps/api/src/app/controllers/pools/quests/patch.controller.ts
new file mode 100644
index 000000000..7a8af5128
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/quests/patch.controller.ts
@@ -0,0 +1,19 @@
+import { param } from 'express-validator';
+import { Request, Response } from 'express';
+import { QuestVariant } from '@thxnetwork/common/enums';
+import QuestService from '@thxnetwork/api/services/QuestService';
+import * as CreateController from '@thxnetwork/api/controllers/pools/quests/post.controller';
+
+const validation = [param('variant').isInt(), param('questId').isMongoId(), ...CreateController.validation];
+
+const controller = async (req: Request, res: Response) => {
+    const variant = req.params.variant as unknown as QuestVariant;
+    const questId = req.params.questId as string;
+
+    let quest = await QuestService.findById(variant, questId);
+    quest = await QuestService.update(quest, req.body, req.file);
+
+    res.json(quest);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/quests/post.controller.ts b/apps/api/src/app/controllers/pools/quests/post.controller.ts
new file mode 100644
index 000000000..1b5457657
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/quests/post.controller.ts
@@ -0,0 +1,59 @@
+import { body, param } from 'express-validator';
+import { Request, Response } from 'express';
+import { isValidUrl } from '@thxnetwork/api/util/url';
+import { ChainId } from '@thxnetwork/common/enums';
+import { isAddress } from 'web3-utils';
+import { defaults } from '@thxnetwork/api/util/validation';
+import QuestService from '@thxnetwork/api/services/QuestService';
+
+const validationBaseQuest = [
+    param('id').isMongoId(),
+    ...defaults.quest,
+    // Daily
+    body('amounts')
+        .optional()
+        .custom((amounts) => {
+            for (const amount of JSON.parse(amounts)) {
+                if (isNaN(amount)) return false;
+            }
+            return true;
+        })
+        .customSanitizer((amounts) => JSON.parse(amounts)),
+    body('eventName').optional().isString(),
+    // Invite
+    body('successUrl')
+        .optional()
+        .custom((value) => {
+            if (value === '' || isValidUrl(value)) return true;
+            return false;
+        }),
+    body('isMandatoryReview').optional().isBoolean(),
+    // Social
+    body('kind').optional().isString(),
+    body('interaction').optional().isNumeric(),
+    body('content').optional().isString(),
+    body('contentMetadata').optional().isString(),
+    // Custom
+    body('limit').optional().isInt(),
+    // Web3
+    body('contracts')
+        .optional()
+        .customSanitizer((contracts) => {
+            return JSON.parse(contracts).filter((contract: { address: string; chainId: ChainId }) =>
+                isAddress(contract.address),
+            );
+        }),
+    body('methodName').optional().isString(),
+    body('threshold').optional().isString(),
+    // Gitcoin
+    //
+];
+
+const validation = [param('id').isMongoId(), ...validationBaseQuest];
+
+const controller = async (req: Request, res: Response) => {
+    const quest = await QuestService.create(req.body.variant, req.params.id, req.body, req.file);
+    res.status(201).json(quest);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/quests/quests.router.ts b/apps/api/src/app/controllers/pools/quests/quests.router.ts
new file mode 100644
index 000000000..522451224
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/quests/quests.router.ts
@@ -0,0 +1,49 @@
+import express from 'express';
+import { assertRequestInput, assertPoolAccess, guard } from '@thxnetwork/api/middlewares';
+import { upload } from '@thxnetwork/api/util/multer';
+
+import * as ListController from './list.controller';
+import * as CreateController from './post.controller';
+import * as UpdateController from './patch.controller';
+import * as RemoveController from './delete.controller';
+
+import RouterQuestEntries from './entries/entries.router';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.get(
+    '/',
+    guard.check(['pools:read']),
+    upload.single('file'),
+    assertPoolAccess,
+    assertRequestInput(ListController.validation),
+    ListController.controller,
+);
+router.post(
+    '/:variant',
+    guard.check(['pools:read', 'pools:write']),
+    upload.single('file'),
+    assertPoolAccess,
+    assertRequestInput(CreateController.validation),
+    CreateController.controller,
+);
+router.patch(
+    '/:variant/:questId',
+    guard.check(['pools:read', 'pools:write']),
+    upload.single('file'),
+    assertPoolAccess,
+    assertRequestInput(UpdateController.validation),
+    UpdateController.controller,
+);
+router.delete(
+    '/:variant/:questId',
+    guard.check(['pools:read', 'pools:write']),
+    upload.single('file'),
+    assertPoolAccess,
+    assertRequestInput(RemoveController.validation),
+    RemoveController.controller,
+);
+
+router.use('/:variant/:questId/entries', RouterQuestEntries);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/rewards/coin-rewards.test.ts b/apps/api/src/app/controllers/pools/rewards/coin-rewards.test.ts
new file mode 100644
index 000000000..fc58e0d73
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/rewards/coin-rewards.test.ts
@@ -0,0 +1,110 @@
+import request from 'supertest';
+import app from '@thxnetwork/api/';
+import { ChainId, ERC20Type, RewardVariant } from '@thxnetwork/common/enums';
+import { dashboardAccessToken, tokenName, tokenSymbol } from '@thxnetwork/api/util/jest/constants';
+import { isAddress } from 'web3-utils';
+import { afterAllCallback, beforeAllCallback } from '@thxnetwork/api/util/jest/config';
+import { addMinutes } from '@thxnetwork/api/util/date';
+import { createImage } from '@thxnetwork/api/util/jest/images';
+import { ERC20Document } from '@thxnetwork/api/models/ERC20';
+import { RewardCoinDocument } from '@thxnetwork/api/models/RewardCoin';
+
+const user = request.agent(app);
+
+describe('Coin Rewards', () => {
+    let poolId: string, erc20: ERC20Document, reward: RewardCoinDocument;
+
+    beforeAll(beforeAllCallback);
+    afterAll(afterAllCallback);
+
+    it('POST /erc20', (done) => {
+        user.post('/v1/erc20')
+            .set('Authorization', dashboardAccessToken)
+            .send({
+                chainId: ChainId.Hardhat,
+                name: tokenName,
+                symbol: tokenSymbol,
+                type: ERC20Type.Unlimited,
+                totalSupply: 0,
+            })
+            .expect(({ body }: request.Response) => {
+                erc20 = body;
+                expect(isAddress(body.address)).toBe(true);
+            })
+            .expect(201, done);
+    });
+
+    it('POST /pools', (done) => {
+        user.post('/v1/pools')
+            .set('Authorization', dashboardAccessToken)
+            .send({
+                chainId: ChainId.Hardhat,
+            })
+            .expect((res: request.Response) => {
+                expect(isAddress(res.body.safeAddress)).toBe(true);
+                poolId = res.body._id;
+            })
+            .expect(201, done);
+    });
+
+    it('POST /pools/:poolId/rewards/:variant', (done) => {
+        const title = 'Lorem',
+            description = 'Ipsum',
+            expiryDate = addMinutes(new Date(), 30),
+            pointPrice = 200,
+            image = createImage(),
+            amount = '1',
+            limit = 0,
+            isPromoted = true,
+            isPublished = true;
+        user.post(`/v1/pools/${poolId}/rewards/${RewardVariant.Coin}`)
+            .set({ Authorization: dashboardAccessToken })
+            .attach('file', image, {
+                filename: 'test.jpg',
+                contentType: 'image/jpg',
+            })
+            .field({
+                title,
+                description,
+                image,
+                limit,
+                pointPrice,
+                expiryDate: new Date(expiryDate).toISOString(),
+                amount,
+                erc20Id: String(erc20._id),
+                isPromoted,
+                isPublished,
+            })
+            .expect((res: request.Response) => {
+                expect(res.body.uuid).toBeDefined();
+                expect(res.body.title).toBe(title);
+                expect(res.body.description).toBe(description);
+                expect(res.body.image).toBeDefined();
+                expect(res.body.amount).toBe(amount);
+                expect(res.body.pointPrice).toBe(pointPrice);
+                expect(new Date(res.body.expiryDate).getDate()).toBe(expiryDate.getDate());
+                expect(res.body.limit).toBe(limit);
+                expect(res.body.isPromoted).toBe(true);
+            })
+            .expect(201, done);
+    });
+
+    it('GET /pools/:poolId/rewards', (done) => {
+        user.get(`/v1/pools/${poolId}/rewards`)
+            .set({ Authorization: dashboardAccessToken })
+            .query({ page: 1, limit: 10, isPublished: true })
+            .expect((res: request.Response) => {
+                expect(res.body.results.length).toBe(1);
+                expect(res.body.limit).toBe(10);
+                expect(res.body.total).toBe(1);
+                reward = res.body.results[0];
+            })
+            .expect(200, done);
+    });
+
+    it('DELETE /pools/:poolId/rewards/:variant', (done) => {
+        user.delete(`/v1/pools/${poolId}/rewards/${reward.variant}/${reward._id}`)
+            .set({ Authorization: dashboardAccessToken })
+            .expect(204, done);
+    });
+});
diff --git a/apps/api/src/app/controllers/pools/rewards/delete.controller.ts b/apps/api/src/app/controllers/pools/rewards/delete.controller.ts
new file mode 100644
index 000000000..9c04fa72c
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/rewards/delete.controller.ts
@@ -0,0 +1,26 @@
+import { param } from 'express-validator';
+import { Request, Response } from 'express';
+import { RewardVariant } from '@thxnetwork/common/enums';
+import { ForbiddenError } from '@thxnetwork/api/util/errors';
+import { QRCodeEntry } from '@thxnetwork/api/models/QRCodeEntry';
+import RewardService from '@thxnetwork/api/services/RewardService';
+
+const validation = [param('id').isMongoId(), param('variant').isInt(), param('rewardId').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const poolId = req.params.id;
+    const variant = req.params.variant as unknown as RewardVariant;
+    const rewardId = req.params.rewardId;
+
+    const reward = await RewardService.findById(variant, rewardId);
+    if (reward.poolId !== poolId) throw new ForbiddenError('Not your reward.');
+
+    await reward.deleteOne();
+
+    // Delete QR codes for this reward if any
+    await QRCodeEntry.deleteMany({ rewardId });
+
+    res.status(204).end();
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/rewards/discord-role-rewards.router.ts b/apps/api/src/app/controllers/pools/rewards/discord-role-rewards.router.ts
new file mode 100644
index 000000000..9fc008b2d
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/rewards/discord-role-rewards.router.ts
@@ -0,0 +1,46 @@
+import { assertPoolAccess, assertRequestInput, guard } from '@thxnetwork/api/middlewares';
+import { upload } from '@thxnetwork/api/util/multer';
+import express from 'express';
+import * as ListDiscordRoleReward from './list.controller';
+import * as ListCouponCodePayments from './payments/list.controller';
+import * as CreateDiscordRoleReward from './post.controller';
+import * as UpdateDiscordRoleReward from './patch.controller';
+import * as RemoveDiscordRoleReward from './delete.controller';
+
+const router: express.Router = express.Router();
+
+router.get(
+    '/',
+    guard.check(['discord_role_rewards:read']),
+    assertPoolAccess,
+    assertRequestInput(ListDiscordRoleReward.validation),
+    ListDiscordRoleReward.controller,
+);
+
+router.get('/payments', ListCouponCodePayments.controller);
+
+router.patch(
+    '/:id',
+    upload.single('file'),
+    guard.check(['discord_role_rewards:write', 'discord_role_rewards:read']),
+    assertPoolAccess,
+    assertRequestInput(UpdateDiscordRoleReward.validation),
+    UpdateDiscordRoleReward.controller,
+);
+router.post(
+    '/',
+    upload.single('file'),
+    guard.check(['discord_role_rewards:write', 'discord_role_rewards:read']),
+    assertPoolAccess,
+    assertRequestInput(CreateDiscordRoleReward.validation),
+    CreateDiscordRoleReward.controller,
+);
+router.delete(
+    '/:id',
+    guard.check(['discord_role_rewards:write']),
+    assertPoolAccess,
+    assertRequestInput(RemoveDiscordRoleReward.validation),
+    RemoveDiscordRoleReward.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/rewards/list.controller.ts b/apps/api/src/app/controllers/pools/rewards/list.controller.ts
new file mode 100644
index 000000000..c55f2dcde
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/rewards/list.controller.ts
@@ -0,0 +1,70 @@
+import { param, query } from 'express-validator';
+import { Request, Response } from 'express';
+import {
+    CouponCode,
+    RewardNFT,
+    RewardCoupon,
+    RewardCoin,
+    RewardDiscordRole,
+    RewardCustom,
+    RewardGalachain,
+} from '@thxnetwork/api/models';
+import { RewardVariant } from '@thxnetwork/common/enums';
+
+const validation = [
+    param('id').isMongoId(),
+    query('page').isInt(),
+    query('limit').isInt(),
+    query('isPublished')
+        .optional()
+        .isBoolean()
+        .customSanitizer((value) => {
+            return value && JSON.parse(value);
+        }),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const poolId = req.params.id;
+    const page = Number(req.query.page);
+    const limit = Number(req.query.limit);
+    const $match = { poolId, isPublished: req.query.isPublished };
+    const pipeline = [
+        { $unionWith: { coll: RewardNFT.collection.name } },
+        { $unionWith: { coll: RewardCoupon.collection.name } },
+        { $unionWith: { coll: RewardCustom.collection.name } },
+        { $unionWith: { coll: RewardDiscordRole.collection.name } },
+        { $unionWith: { coll: RewardGalachain.collection.name } },
+        { $match },
+    ];
+    const arr = await Promise.all(
+        [RewardCoin, RewardNFT, RewardCoupon, RewardCustom, RewardDiscordRole].map(
+            async (model) => await model.countDocuments($match),
+        ),
+    );
+    const total = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
+    const results = await RewardCoin.aggregate([
+        ...pipeline,
+        { $sort: { index: 1 } },
+        { $skip: (page - 1) * limit },
+        { $limit: limit },
+    ]);
+
+    res.json({
+        total,
+        limit,
+        page,
+        results: await Promise.all(
+            results.map(async (reward) => {
+                // TODO Move this hack to a service method and make it part of the IRewardService
+                if (reward.variant === RewardVariant.Coupon) {
+                    const couponCodeCount = await CouponCode.countDocuments({ couponRewardId: reward._id });
+                    return { ...reward, couponCodeCount };
+                }
+
+                return reward;
+            }),
+        ),
+    });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/rewards/nft-rewards.test.ts b/apps/api/src/app/controllers/pools/rewards/nft-rewards.test.ts
new file mode 100644
index 000000000..0a7a65171
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/rewards/nft-rewards.test.ts
@@ -0,0 +1,134 @@
+import request from 'supertest';
+import app from '@thxnetwork/api/';
+import { ChainId } from '@thxnetwork/common/enums';
+import { dashboardAccessToken } from '@thxnetwork/api/util/jest/constants';
+import { isAddress } from 'web3-utils';
+import { afterAllCallback, beforeAllCallback } from '@thxnetwork/api/util/jest/config';
+import { addMinutes } from '@thxnetwork/api/util/date';
+import { createImage } from '@thxnetwork/api/util/jest/images';
+import { RewardNFTDocument } from '@thxnetwork/api/models/RewardNFT';
+import { ERC721Document } from '@thxnetwork/api/models/ERC721';
+import { ERC721MetadataDocument } from '@thxnetwork/api/models/ERC721Metadata';
+import { RewardVariant } from '@thxnetwork/common/enums';
+
+const user = request.agent(app);
+
+describe('NFT Rewards', () => {
+    let poolId: string, erc721metadata: ERC721MetadataDocument, erc721: ERC721Document, reward: RewardNFTDocument;
+    const name = 'Planets of the Galaxy',
+        symbol = 'GLXY',
+        description = 'description';
+
+    beforeAll(beforeAllCallback);
+    afterAll(afterAllCallback);
+
+    it('POST /erc721', (done) => {
+        user.post('/v1/erc721')
+            .set('Authorization', dashboardAccessToken)
+            .send({
+                chainId: ChainId.Hardhat,
+                name,
+                symbol,
+                description,
+            })
+            .expect(({ body }: request.Response) => {
+                expect(body._id).toBeDefined();
+                expect(body.address).toBeDefined();
+                erc721 = body;
+            })
+            .expect(201, done);
+    });
+
+    it('POST /erc721/:id/metadata', (done) => {
+        const config = {
+            name: 'Lorem',
+            description: 'Lorem ipsum dolor sit.',
+            imageUrl: 'https://image.com',
+            externalUrl: 'https://example.com',
+        };
+
+        user.post('/v1/erc721/' + erc721._id + '/metadata')
+            .set('Authorization', dashboardAccessToken)
+            .send(config)
+            .expect(({ body }: request.Response) => {
+                expect(body._id).toBeDefined();
+                expect(body.name).toBe(config.name);
+                expect(body.description).toBe(config.description);
+                expect(body.image).toBe(config.imageUrl);
+                expect(body.externalUrl).toBe(config.externalUrl);
+                erc721metadata = body;
+            })
+            .expect(201, done);
+    });
+
+    it('POST /pools', (done) => {
+        user.post('/v1/pools')
+            .set('Authorization', dashboardAccessToken)
+            .send({
+                chainId: ChainId.Hardhat,
+            })
+            .expect(({ body }: request.Response) => {
+                expect(isAddress(body.safeAddress)).toBe(true);
+                poolId = body._id;
+            })
+            .expect(201, done);
+    });
+
+    it('POST /pools/:poolId/rewards/:variant', (done) => {
+        const expiryDate = addMinutes(new Date(), 30);
+        const image = createImage();
+        const config = {
+            title: 'Lorem',
+            description: 'Lorem ipsum',
+            erc721Id: String(erc721._id),
+            metadataId: erc721metadata._id,
+            pointPrice: 200,
+            expiryDate: new Date(expiryDate).toISOString(),
+            limit: 0,
+            claimAmount: 0,
+            isPromoted: true,
+            variant: RewardVariant.NFT,
+            isPublished: true,
+        };
+        user.post(`/v1/pools/${poolId}/rewards/${RewardVariant.NFT}`)
+            .set({ Authorization: dashboardAccessToken })
+            .attach('file', image, {
+                filename: 'test.jpg',
+                contentType: 'image/jpg',
+            })
+            .field(config)
+            .expect((res: request.Response) => {
+                expect(res.body.uuid).toBeDefined();
+                expect(res.body.title).toBe(config.title);
+                expect(res.body.description).toBe(config.description);
+                expect(res.body.image).toBeDefined();
+                expect(res.body.pointPrice).toBe(config.pointPrice);
+                expect(new Date(res.body.expiryDate).getDate()).toBe(expiryDate.getDate());
+                expect(res.body.limit).toBe(config.limit);
+                expect(res.body.claimAmount).toBe(config.claimAmount);
+                expect(res.body.isPromoted).toBe(config.isPromoted);
+                expect(res.body.erc721Id).toBe(erc721._id);
+                expect(res.body.metadataId).toBe(erc721metadata._id);
+            })
+            .expect(201, done);
+    });
+
+    it('GET /pools/:poolId/rewards?page=:page&limit=:limit', (done) => {
+        user.get(`/v1/pools/${poolId}/rewards`)
+            .query({ page: 1, limit: 10, isPublished: true })
+            .set({ Authorization: dashboardAccessToken })
+            .expect((res: request.Response) => {
+                expect(res.body.results.length).toBe(1);
+                expect(res.body.limit).toBe(10);
+                expect(res.body.total).toBe(1);
+                reward = res.body.results[0];
+            })
+            .expect(200, done);
+    });
+
+    it('DELETE /rewards/:id', (done) => {
+        user.delete(`/v1/pools/${poolId}/rewards/${reward.variant}/${reward._id}`)
+            .set({ Authorization: dashboardAccessToken })
+            .expect(204, done);
+    });
+});
diff --git a/apps/api/src/app/controllers/pools/rewards/patch.controller.ts b/apps/api/src/app/controllers/pools/rewards/patch.controller.ts
new file mode 100644
index 000000000..f1805678f
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/rewards/patch.controller.ts
@@ -0,0 +1,19 @@
+import { param } from 'express-validator';
+import { Request, Response } from 'express';
+import { RewardVariant } from '@thxnetwork/common/enums';
+import RewardService from '@thxnetwork/api/services/RewardService';
+import * as CreateController from '@thxnetwork/api/controllers/pools/rewards/post.controller';
+
+const validation = [param('rewardId').isMongoId(), ...CreateController.validation];
+
+const controller = async (req: Request, res: Response) => {
+    const variant = req.params.variant as unknown as RewardVariant;
+    const rewardId = req.params.rewardId as string;
+
+    let reward = await RewardService.findById(variant, rewardId);
+    reward = await RewardService.update(reward, req.body, req.file);
+
+    res.json(reward);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/rewards/payments/list.controller.ts b/apps/api/src/app/controllers/pools/rewards/payments/list.controller.ts
new file mode 100644
index 000000000..98a713c9b
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/rewards/payments/list.controller.ts
@@ -0,0 +1,30 @@
+import { Request, Response } from 'express';
+import { param, query } from 'express-validator';
+import { RewardVariant } from '@thxnetwork/common/enums';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import RewardService from '@thxnetwork/api/services/RewardService';
+
+const validation = [
+    param('id').isMongoId(),
+    param('variant').isString(),
+    param('rewardId').isMongoId(),
+    query('page').isInt(),
+    query('limit').isInt(),
+    query('query').isString(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const variant = req.params.variant as unknown as RewardVariant;
+    const reward = await RewardService.findById(variant, req.params.rewardId);
+    if (!reward) throw new NotFoundError('Reward not found');
+
+    const payments = await RewardService.findPayments(reward, {
+        page: Number(req.query.page),
+        limit: Number(req.query.limit),
+        query: req.query.query as string,
+    });
+
+    res.json(payments);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/rewards/payments/payments.router.ts b/apps/api/src/app/controllers/pools/rewards/payments/payments.router.ts
new file mode 100644
index 000000000..d280e4317
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/rewards/payments/payments.router.ts
@@ -0,0 +1,15 @@
+import express from 'express';
+import { assertRequestInput, assertPoolAccess, guard } from '@thxnetwork/api/middlewares';
+import * as ListPayments from './list.controller';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.get(
+    '/',
+    guard.check(['pools:read']),
+    assertPoolAccess,
+    assertRequestInput(ListPayments.validation),
+    ListPayments.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/rewards/post.controller.ts b/apps/api/src/app/controllers/pools/rewards/post.controller.ts
new file mode 100644
index 000000000..aeb30d571
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/rewards/post.controller.ts
@@ -0,0 +1,57 @@
+import { body, param } from 'express-validator';
+import { Request, Response } from 'express';
+import { defaults } from '@thxnetwork/api/util/validation';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { RewardVariant } from '@thxnetwork/common/enums';
+import RewardService from '@thxnetwork/api/services/RewardService';
+import PoolService from '@thxnetwork/api/services/PoolService';
+
+const validationBaseQuest = [
+    param('id').isMongoId(),
+    ...defaults.reward,
+
+    // Coin
+    body('erc20Id').optional().isMongoId(),
+    body('amount').optional().isInt({ gt: 0 }),
+
+    // NFT
+    body('erc721Id').optional().isString(),
+    body('erc1155Id').optional().isString(),
+    body('metadataIds').optional().isString(),
+    body('tokenId').optional().isString(),
+
+    // Coupon
+    body('webshopURL').optional().isURL({ require_tld: false }),
+    body('codes')
+        .optional()
+        .custom((value: string) => value && Array.isArray(JSON.parse(value)))
+        .customSanitizer((value: string) => value && JSON.parse(value)),
+
+    // Custom
+    body('webhookId').optional().isMongoId(),
+    body('metadata').optional().isString(),
+    // DiscordRole
+    body('discordRoleId').optional().isString(),
+    // Galachain
+    body('contractChannelName').optional().isString(),
+    body('contractChaincodeName').optional().isString(),
+    body('contractContractName').optional().isString(),
+    body('tokenCollection').optional().isString(),
+    body('tokenCategory').optional().isString(),
+    body('tokenType').optional().isString(),
+    body('tokenAdditionalKey').optional().isString(),
+    body('amount').optional().isInt({ gt: 0 }),
+];
+
+const validation = [param('id').isMongoId(), ...validationBaseQuest];
+
+const controller = async (req: Request, res: Response) => {
+    const pool = await PoolService.getById(req.params.id);
+    if (!pool) throw new NotFoundError('Could not find pool');
+    const variant = req.params.variant as unknown as RewardVariant;
+    const reward = await RewardService.create(variant, req.params.id, req.body, req.file);
+
+    res.status(201).json(reward);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/rewards/rewards.router.ts b/apps/api/src/app/controllers/pools/rewards/rewards.router.ts
new file mode 100644
index 000000000..c3c5370a2
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/rewards/rewards.router.ts
@@ -0,0 +1,49 @@
+import express from 'express';
+import { assertRequestInput, assertPoolAccess, guard } from '@thxnetwork/api/middlewares';
+import { upload } from '@thxnetwork/api/util/multer';
+
+import * as ListController from './list.controller';
+import * as UpdateController from './patch.controller';
+import * as CreateController from './post.controller';
+import * as RemoveController from './delete.controller';
+
+import RouterRewardPayments from './payments/payments.router';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.get(
+    '/',
+    guard.check(['pools:read']),
+    upload.single('file'),
+    assertPoolAccess,
+    assertRequestInput(ListController.validation),
+    ListController.controller,
+);
+router.post(
+    '/:variant',
+    guard.check(['pools:read', 'pools:write']),
+    upload.single('file'),
+    assertPoolAccess,
+    assertRequestInput(CreateController.validation),
+    CreateController.controller,
+);
+router.patch(
+    '/:variant/:rewardId',
+    guard.check(['pools:read', 'pools:write']),
+    upload.single('file'),
+    assertPoolAccess,
+    assertRequestInput(UpdateController.validation),
+    UpdateController.controller,
+);
+router.delete(
+    '/:variant/:rewardId',
+    guard.check(['pools:read', 'pools:write']),
+    upload.single('file'),
+    assertPoolAccess,
+    assertRequestInput(RemoveController.validation),
+    RemoveController.controller,
+);
+
+router.use('/:variant/:rewardId/payments', RouterRewardPayments);
+
+export default router;
diff --git a/apps/api/src/app/controllers/pools/wallets/list.controller.ts b/apps/api/src/app/controllers/pools/wallets/list.controller.ts
new file mode 100644
index 000000000..3c3b0450a
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/wallets/list.controller.ts
@@ -0,0 +1,12 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import { Wallet } from '@thxnetwork/api/models';
+
+const validation = [param('id').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const wallets = await Wallet.find({ poolId: req.params.id });
+    res.json(wallets);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/pools/wallets/wallets.router.ts b/apps/api/src/app/controllers/pools/wallets/wallets.router.ts
new file mode 100644
index 000000000..c6cc65c99
--- /dev/null
+++ b/apps/api/src/app/controllers/pools/wallets/wallets.router.ts
@@ -0,0 +1,15 @@
+import express from 'express';
+import { assertRequestInput, assertPoolAccess, guard } from '@thxnetwork/api/middlewares';
+import * as ListWallets from './list.controller';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.get(
+    '/',
+    guard.check(['pools:read']),
+    assertPoolAccess,
+    assertRequestInput(ListWallets.validation),
+    ListWallets.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/qr-codes/collect/post.controller.ts b/apps/api/src/app/controllers/qr-codes/collect/post.controller.ts
new file mode 100644
index 000000000..d5dc5f47e
--- /dev/null
+++ b/apps/api/src/app/controllers/qr-codes/collect/post.controller.ts
@@ -0,0 +1,64 @@
+import { Request, Response } from 'express';
+import { param, query } from 'express-validator';
+import { BadRequestError, ForbiddenError, NotFoundError } from '@thxnetwork/api/util/errors';
+import { RewardNFT, RewardNFTPayment, QRCodeEntry, ERC721Metadata } from '@thxnetwork/api/models';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import ERC721Service from '@thxnetwork/api/services/ERC721Service';
+import SafeService from '@thxnetwork/api/services/SafeService';
+import WalletService from '@thxnetwork/api/services/WalletService';
+
+const validation = [param('uuid').isUUID(4), query('walletId').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    let entry = await QRCodeEntry.findOne({ uuid: req.params.uuid });
+    if (!entry) throw new BadRequestError('This claim URL is invalid.');
+    // Can not be claimed when sub is set for this claim URL and claim amount is greater than 1
+    if (entry.sub) throw new ForbiddenError('This NFT is claimed already.');
+
+    const reward = await RewardNFT.findById(entry.rewardId);
+    if (!reward) throw new BadRequestError('Reward not found');
+    // Can be claimed only if point price is 0
+    if (reward.pointPrice > 0) throw new ForbiddenError('Reward needs to be purchased with points.');
+
+    const pool = await PoolService.getById(reward.poolId);
+    if (!pool) throw new BadRequestError('Campaign not found.');
+
+    const safe = await SafeService.findOneByPool(pool, pool.chainId);
+    if (!safe) throw new BadRequestError('Safe not found.');
+
+    // Find wallet for the authenticated user
+    const wallet = await WalletService.findById(req.query.walletId as string);
+    if (!wallet) throw new NotFoundError('Wallet not found');
+
+    // Mint an NFT token if the erc721 and metadata for the claim exists.
+    const metadata = await ERC721Metadata.findById(reward.metadataId);
+    if (!metadata) throw new NotFoundError('Metadata not found');
+
+    const erc721 = await ERC721Service.findById(metadata.erc721Id);
+    if (!erc721) throw new NotFoundError('ERC721 not found');
+
+    // Mint the NFT
+    const token = await ERC721Service.mint(safe, erc721, wallet, metadata);
+
+    // Create a payment to register a completed claim.
+    const payment = await RewardNFTPayment.create({
+        sub: req.auth.sub,
+        rewardId: reward._id,
+        amount: reward.pointPrice,
+        poolId: pool._id,
+    });
+
+    // Mark claim as claimed by setting sub
+    entry = await QRCodeEntry.findByIdAndUpdate(entry._id, { sub: req.auth.sub, claimedAt: new Date() }, { new: true });
+
+    return res.json({
+        erc721,
+        entry,
+        payment,
+        token,
+        metadata,
+        reward,
+    });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/qr-codes/delete.controller.ts b/apps/api/src/app/controllers/qr-codes/delete.controller.ts
new file mode 100644
index 000000000..45160b29d
--- /dev/null
+++ b/apps/api/src/app/controllers/qr-codes/delete.controller.ts
@@ -0,0 +1,24 @@
+import { QRCodeEntry, RewardNFT } from '@thxnetwork/api/models';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import { ForbiddenError, NotFoundError } from '@thxnetwork/api/util/errors';
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+
+const validation = [param('uuid').isUUID(4)];
+
+const controller = async (req: Request, res: Response) => {
+    const entry = await QRCodeEntry.findOne({ uuid: req.params.uuid });
+    if (!entry) throw new NotFoundError('QR Code Entry not found');
+
+    const reward = await RewardNFT.findById(entry.rewardId);
+    if (!reward) throw new NotFoundError('Reward not found');
+
+    const isAllowed = await PoolService.isSubjectAllowed(req.auth.sub, reward.poolId);
+    if (!isAllowed) throw new ForbiddenError('Not allowed for delete.');
+
+    await entry.deleteOne();
+
+    res.status(204).end();
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/qr-codes/get.controller.ts b/apps/api/src/app/controllers/qr-codes/get.controller.ts
new file mode 100644
index 000000000..a753047f9
--- /dev/null
+++ b/apps/api/src/app/controllers/qr-codes/get.controller.ts
@@ -0,0 +1,29 @@
+import { Request, Response } from 'express';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { param } from 'express-validator';
+import { QRCodeEntry, RewardNFT, ERC721Metadata } from '@thxnetwork/api/models';
+import ERC721Service from '@thxnetwork/api/services/ERC721Service';
+import PoolService from '@thxnetwork/api/services/PoolService';
+
+const validation = [param('uuid').exists().isUUID(4)];
+
+const controller = async (req: Request, res: Response) => {
+    const entry = await QRCodeEntry.findOne({ uuid: req.params.uuid });
+    if (!entry) throw new NotFoundError('QR code entry not found');
+
+    const reward = await RewardNFT.findById(entry.rewardId);
+    if (!reward) throw new NotFoundError('Reward not found');
+
+    const pool = await PoolService.getById(reward.poolId);
+    if (!pool) throw new NotFoundError('Pool not found');
+
+    const erc721 = await ERC721Service.findById(reward.erc721Id);
+    if (!erc721) throw new NotFoundError('ERC721 not found');
+
+    const metadata = await ERC721Metadata.findById(reward.metadataId);
+    if (!metadata) throw new NotFoundError('Metadata not found');
+
+    return res.json({ pool, entry, erc721, metadata });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/qr-codes/list.controller.ts b/apps/api/src/app/controllers/qr-codes/list.controller.ts
new file mode 100644
index 000000000..083e5f07c
--- /dev/null
+++ b/apps/api/src/app/controllers/qr-codes/list.controller.ts
@@ -0,0 +1,44 @@
+import { QRCodeEntry, RewardNFT } from '@thxnetwork/api/models';
+import { ForbiddenError, NotFoundError } from '@thxnetwork/api/util/errors';
+import { Request, Response } from 'express';
+import { query } from 'express-validator';
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+import PoolService from '@thxnetwork/api/services/PoolService';
+
+const validation = [query('rewardId').isMongoId(), query('page').isInt(), query('limit').isInt()];
+
+const controller = async (req: Request, res: Response) => {
+    const page = Number(req.query.page);
+    const limit = Number(req.query.limit);
+    const rewardId = req.query.rewardId;
+
+    const reward = await RewardNFT.findById(rewardId);
+    if (!reward) throw new NotFoundError('Reward not found');
+
+    const isAllowed = await PoolService.isSubjectAllowed(req.auth.sub, reward.poolId);
+    if (!isAllowed) throw new ForbiddenError('Reward not accessible.');
+
+    const total = await QRCodeEntry.countDocuments({ rewardId });
+    const entries = await QRCodeEntry.find({ rewardId })
+        .limit(limit)
+        .skip((page - 1) * limit);
+    const subs = entries.map(({ sub }) => sub);
+    const accounts = await AccountProxy.find({ subs });
+    const results = entries.map((entry) => {
+        const account = accounts.find((account) => account.sub === entry.sub);
+        return Object.assign(entry.toJSON(), { account });
+    });
+    const meta = {
+        participantCount: await QRCodeEntry.countDocuments({ rewardId, sub: { $exists: true } }),
+    };
+
+    res.json({
+        total,
+        limit,
+        page,
+        results,
+        meta,
+    });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/qr-codes/post.controller.ts b/apps/api/src/app/controllers/qr-codes/post.controller.ts
new file mode 100644
index 000000000..74bb919e0
--- /dev/null
+++ b/apps/api/src/app/controllers/qr-codes/post.controller.ts
@@ -0,0 +1,26 @@
+import { RewardNFT } from '@thxnetwork/api/models';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { Request, Response } from 'express';
+import { body } from 'express-validator';
+import QRCodeService from '@thxnetwork/api/services/ClaimService';
+
+const validation = [
+    body('rewardId').isMongoId(),
+    body('claimAmount').isInt(),
+    body('redirectURL').isURL({ require_tld: false }),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const rewardId = req.body.rewardId;
+    const redirectURL = req.body.redirectURL;
+    const claimAmount = Number(req.body.claimAmount);
+
+    const reward = await RewardNFT.findById(rewardId);
+    if (!reward) throw new NotFoundError('Reward not found');
+
+    const entries = await QRCodeService.create({ rewardId, redirectURL }, claimAmount);
+
+    res.status(201).json(entries);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/qr-codes/qr-codes.router.ts b/apps/api/src/app/controllers/qr-codes/qr-codes.router.ts
new file mode 100644
index 000000000..8a7a4f044
--- /dev/null
+++ b/apps/api/src/app/controllers/qr-codes/qr-codes.router.ts
@@ -0,0 +1,33 @@
+import express from 'express';
+import { assertRequestInput, checkJwt, corsHandler, guard } from '@thxnetwork/api/middlewares';
+import * as ListEntry from './list.controller';
+import * as CreateEntry from './post.controller';
+import * as ReadEntry from './get.controller';
+import * as DeleteEntryController from './delete.controller';
+import * as ReadRedirectEntry from './redirect/get.controller';
+import * as UpdateEntryController from './collect/post.controller';
+
+const router: express.Router = express.Router();
+
+router.get('/:uuid', assertRequestInput(ReadEntry.validation), ReadEntry.controller);
+router.get('/r/:uuid', assertRequestInput(ReadRedirectEntry.validation), ReadRedirectEntry.controller);
+
+router.use(checkJwt, corsHandler);
+router.get('/', guard.check(['pools:read']), assertRequestInput(ListEntry.validation), ListEntry.controller);
+router.post('/', guard.check(['pools:read']), assertRequestInput(CreateEntry.validation), CreateEntry.controller);
+
+router.patch(
+    '/:uuid',
+    guard.check(['claims:read']),
+    assertRequestInput(UpdateEntryController.validation),
+    UpdateEntryController.controller,
+);
+
+router.delete(
+    '/:uuid',
+    guard.check(['pools:write']),
+    assertRequestInput(DeleteEntryController.validation),
+    DeleteEntryController.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/qr-codes/qr-codes.test.ts b/apps/api/src/app/controllers/qr-codes/qr-codes.test.ts
new file mode 100644
index 000000000..d4d5bf15c
--- /dev/null
+++ b/apps/api/src/app/controllers/qr-codes/qr-codes.test.ts
@@ -0,0 +1,192 @@
+import request from 'supertest';
+import app from '@thxnetwork/api/';
+import { ChainId, NFTVariant, RewardVariant } from '@thxnetwork/common/enums';
+import { sub, dashboardAccessToken, widgetAccessToken, widgetAccessToken2 } from '@thxnetwork/api/util/jest/constants';
+import { afterAllCallback, beforeAllCallback } from '@thxnetwork/api/util/jest/config';
+import {
+    PoolDocument,
+    ERC721Metadata,
+    ERC721MetadataDocument,
+    QRCodeEntryDocument,
+    ERC721Document,
+    RewardNFTDocument,
+} from '@thxnetwork/api/models';
+import { IPFS_BASE_URL } from '@thxnetwork/api/config/secrets';
+import { safeVersion } from '@thxnetwork/api/services/ContractService';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { poll } from '@thxnetwork/api/util/polling';
+import { WalletDocument } from '@thxnetwork/api/models/Wallet';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import ERC721Service from '@thxnetwork/api/services/ERC721Service';
+import SafeService from '@thxnetwork/api/services/SafeService';
+
+const user = request.agent(app);
+
+describe('QR Codes', () => {
+    let poolId: string,
+        pool: PoolDocument,
+        erc721: ERC721Document,
+        reward: RewardNFTDocument,
+        metadata: ERC721MetadataDocument,
+        wallet: WalletDocument,
+        qrcodes: QRCodeEntryDocument[];
+
+    const chainId = ChainId.Hardhat;
+
+    beforeAll(async () => {
+        await beforeAllCallback();
+
+        pool = await PoolService.deploy(sub, 'My Reward Campaign');
+        poolId = String(pool._id);
+
+        const safe = await SafeService.create({ sub, chainId, safeVersion, poolId });
+
+        // Wait for campaign safe to be deployed
+        const { web3 } = getProvider(ChainId.Hardhat);
+        await poll(
+            () => web3.eth.getCode(safe.address),
+            (data: string) => data === '0x',
+            1000,
+        );
+
+        erc721 = await ERC721Service.deploy({
+            variant: NFTVariant.ERC721,
+            sub,
+            chainId,
+            name: 'Test Collection',
+            symbol: 'TST',
+            description: '',
+            baseURL: 'https://example.com',
+            archived: false,
+            logoImgUrl: 'https://img.url',
+        });
+        metadata = await ERC721Metadata.create({
+            erc721Id: String(erc721._id),
+            name: 'Token Silver',
+            image: IPFS_BASE_URL + 'abcdef',
+            imageUrl: 'https://image.com/image.jpg',
+            description: 'Lorem ipsum dolor sit amet',
+            externalUrl: 'https://example.com',
+        });
+        wallet = await SafeService.findOne({ sub });
+    });
+    afterAll(afterAllCallback);
+
+    it('POST /pools/:poolId/rewards/:variant', (done) => {
+        user.post(`/v1/pools/${poolId}/rewards/${RewardVariant.NFT}`)
+            .set({ Authorization: dashboardAccessToken })
+            .send({
+                title: '',
+                description: '',
+                pointPrice: 0,
+                limit: 0,
+                variant: RewardVariant.NFT,
+                erc721Id: erc721._id,
+                metadataId: metadata._id,
+            })
+            .expect(({ body }: request.Response) => {
+                expect(body._id).toBeDefined();
+                reward = body;
+            })
+            .expect(201, done);
+    });
+
+    it('POST /qr-codes', (done) => {
+        user.post(`/v1/qr-codes`)
+            .set({ Authorization: dashboardAccessToken })
+            .send({
+                rewardId: reward._id,
+                claimAmount: 10,
+                redirectURL: 'https://example.com/redirect',
+            })
+            .expect(({ body }: request.Response) => {
+                expect(body).toHaveLength(10);
+            })
+            .expect(201, done);
+    });
+
+    it('GET /qr-codes?rewardId=:rewardId&page=:page&limit=:limit', (done) => {
+        user.get(`/v1/qr-codes`)
+            .set({ Authorization: dashboardAccessToken })
+            .query({
+                rewardId: reward._id,
+                page: 1,
+                limit: 15,
+            })
+            .expect(({ body }: request.Response) => {
+                expect(body.total).toBe(10);
+                expect(body.results).toHaveLength(10);
+                qrcodes = body.results;
+            })
+            .expect(200, done);
+    });
+
+    it('GET /qr-codes/:uuid', (done) => {
+        user.get(`/v1/qr-codes/${qrcodes[0].uuid}`)
+            .expect(({ body }: request.Response) => {
+                expect(body.entry).toBeDefined();
+                expect(body.erc721).toBeDefined();
+                expect(body.metadata).toBeDefined();
+            })
+            .expect(200, done);
+    });
+
+    it('PATCH /qr-codes/:uuid should succeed', (done) => {
+        user.patch(`/v1/qr-codes/${qrcodes[0].uuid}`)
+            .query({ walletId: String(wallet._id) })
+            .set({ Authorization: widgetAccessToken })
+            .expect(({ body }: request.Response) => {
+                expect(body.erc721).toBeDefined();
+                expect(body.entry).toBeDefined();
+                expect(body.payment).toBeDefined();
+                expect(body.token).toBeDefined();
+                expect(body.metadata).toBeDefined();
+                expect(body.reward).toBeDefined();
+            })
+            .expect(200, done);
+    });
+
+    it('GET /qr-codes/:uuid should return sub', (done) => {
+        user.get(`/v1/qr-codes/${qrcodes[0].uuid}`)
+            .set({ Authorization: widgetAccessToken })
+            .expect(({ body }: request.Response) => {
+                expect(body.entry).toBeDefined();
+                expect(body.entry.sub).toBeDefined();
+                expect(body.erc721).toBeDefined();
+                expect(body.metadata).toBeDefined();
+            })
+            .expect(200, done);
+    });
+
+    it('PATCH /qr-codes/:uuid should fail', (done) => {
+        user.patch(`/v1/qr-codes/${qrcodes[0].uuid}`)
+            .query({ walletId: String(wallet._id) })
+            .set({ Authorization: widgetAccessToken })
+            .expect(({ body }: request.Response) => {
+                expect(body.error.message).toBe('This NFT is claimed already.');
+            })
+            .expect(403, done);
+    });
+
+    it('PATCH /qr-codes/:uuid from other account should also fail', (done) => {
+        user.patch(`/v1/qr-codes/${qrcodes[0].uuid}`)
+            .query({ walletId: String(wallet._id) })
+            .set({ Authorization: widgetAccessToken2 })
+            .expect(({ body }: request.Response) => {
+                expect(body.error.message).toBe('This NFT is claimed already.');
+            })
+            .expect(403, done);
+    });
+
+    it('First attempt other claim for other account should succeed', (done) => {
+        user.patch(`/v1/qr-codes/${qrcodes[1].uuid}`)
+            .query({ walletId: String(wallet._id) })
+            .set({ Authorization: widgetAccessToken2 })
+            .expect(({ body }: request.Response) => {
+                expect(body.entry).toBeDefined();
+                expect(body.erc721).toBeDefined();
+                expect(body.metadata).toBeDefined();
+            })
+            .expect(200, done);
+    });
+});
diff --git a/apps/api/src/app/controllers/qr-codes/redirect/get.controller.ts b/apps/api/src/app/controllers/qr-codes/redirect/get.controller.ts
new file mode 100644
index 000000000..d85966835
--- /dev/null
+++ b/apps/api/src/app/controllers/qr-codes/redirect/get.controller.ts
@@ -0,0 +1,35 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import { RewardNFT, QRCodeEntry } from '@thxnetwork/api/models';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import { WIDGET_URL } from '@thxnetwork/api/config/secrets';
+
+const validation = [param('uuid').isUUID(4)];
+
+const controller = async (req: Request, res: Response) => {
+    const entry = await QRCodeEntry.findOne({ uuid: req.params.uuid });
+    if (!entry) throw new NotFoundError('QR code entry not found');
+
+    const reward = await RewardNFT.findById(entry.rewardId);
+    if (!reward) throw new NotFoundError('Reward not found');
+
+    // If redirectURL is set in entry, redirect immediately
+    if (entry.redirectURL) {
+        const url = new URL(entry.redirectURL);
+        url.searchParams.append('thx_widget_path', `/c/${req.params.uuid}`);
+
+        return res.redirect(302, url.toString());
+    }
+
+    // Redirect to campaign URl if not present in the entry
+    const pool = await PoolService.getById(reward.poolId);
+    if (!pool) throw new NotFoundError('Pool not found.');
+
+    const url = new URL(WIDGET_URL);
+    url.pathname = `/c/${pool.settings.slug}/c/${req.params.uuid}`;
+
+    return res.redirect(302, url.toString());
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/quests/custom/custom.router.ts b/apps/api/src/app/controllers/quests/custom/custom.router.ts
new file mode 100644
index 000000000..f2f9e9662
--- /dev/null
+++ b/apps/api/src/app/controllers/quests/custom/custom.router.ts
@@ -0,0 +1,16 @@
+import express from 'express';
+import * as CreateEntry from './entries/post.controller';
+import { assertRequestInput, assertAccount } from '@thxnetwork/api/middlewares';
+import { limitInSeconds } from '@thxnetwork/api/util/ratelimiter';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.post(
+    '/:id/entries',
+    limitInSeconds(3),
+    assertRequestInput(CreateEntry.validation),
+    assertAccount,
+    CreateEntry.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/quests/custom/custom.test.ts b/apps/api/src/app/controllers/quests/custom/custom.test.ts
new file mode 100644
index 000000000..16c2328f6
--- /dev/null
+++ b/apps/api/src/app/controllers/quests/custom/custom.test.ts
@@ -0,0 +1,84 @@
+import request from 'supertest';
+import app from '@thxnetwork/api/';
+import { v4 } from 'uuid';
+import { QuestVariant } from '@thxnetwork/common/enums';
+import { dashboardAccessToken, userWalletAddress2, widgetAccessToken2 } from '@thxnetwork/api/util/jest/constants';
+import { isAddress } from 'web3-utils';
+import { afterAllCallback, beforeAllCallback } from '@thxnetwork/api/util/jest/config';
+import { PoolDocument, QuestCustom } from '@thxnetwork/api/models';
+
+const user = request.agent(app);
+
+describe('Quests Custom ', () => {
+    let pool: PoolDocument, customQuest: TQuestCustom;
+    const eventName = v4();
+
+    beforeAll(beforeAllCallback);
+    afterAll(afterAllCallback);
+
+    it('POST /pools', (done) => {
+        user.post('/v1/pools')
+            .set('Authorization', dashboardAccessToken)
+            .send()
+            .expect((res: request.Response) => {
+                expect(isAddress(res.body.safeAddress)).toBe(true);
+                pool = res.body;
+            })
+            .expect(201, done);
+    });
+
+    it('POST /pools/:id/quests', (done) => {
+        user.post(`/v1/pools/${pool._id}/quests/${QuestVariant.Custom}`)
+            .set({ Authorization: dashboardAccessToken })
+            .send({
+                variant: QuestVariant.Custom,
+                title: 'Expiration date is next 30 min',
+                description: 'Lorem ipsum dolor sit amet',
+                amount: 100,
+                limit: 1,
+                index: 0,
+                eventName,
+            })
+            .expect(async (res: request.Response) => {
+                expect(res.body.uuid).toBeDefined();
+                expect(res.body.amount).toBe(100);
+                customQuest = res.body;
+                await QuestCustom.findByIdAndUpdate(customQuest._id, { eventName: customQuest.uuid });
+            })
+            .expect(201, done);
+    });
+
+    describe('Qualify (to be deprecated)', () => {
+        it('POST /webhook/milestone/:token/claim', (done) => {
+            user.post(`/v1/webhook/milestone/${customQuest.uuid}/claim`)
+                .send({
+                    address: userWalletAddress2,
+                })
+                .expect(201, done);
+        });
+
+        it('POST /webhook/milestone/:token/claim second time should also succeed', (done) => {
+            user.post(`/v1/webhook/milestone/${customQuest.uuid}/claim`)
+                .send({
+                    address: userWalletAddress2,
+                })
+                .expect(201, done);
+        });
+    });
+
+    describe('Collect', () => {
+        it('GET /account to update identity', (done) => {
+            user.get(`/v1/account`)
+                .set({ 'X-PoolId': pool._id, 'Authorization': widgetAccessToken2 })
+                .expect(200, done);
+        });
+
+        it('POST /quests/custom/:id/entries', async () => {
+            const { status } = await user
+                .post(`/v1/quests/custom/${customQuest._id}/entries`)
+                .set({ 'X-PoolId': pool._id, 'Authorization': widgetAccessToken2 })
+                .send({ recaptcha: 'test' });
+            expect(status).toBe(200);
+        });
+    });
+});
diff --git a/apps/api/src/app/controllers/quests/custom/entries/post.controller.ts b/apps/api/src/app/controllers/quests/custom/entries/post.controller.ts
new file mode 100644
index 000000000..bec226ac7
--- /dev/null
+++ b/apps/api/src/app/controllers/quests/custom/entries/post.controller.ts
@@ -0,0 +1,38 @@
+import { Request, Response } from 'express';
+import { QuestCustom } from '@thxnetwork/api/models';
+import { body, param } from 'express-validator';
+import { JobType, QuestVariant } from '@thxnetwork/common/enums';
+import { agenda } from '@thxnetwork/api/util/agenda';
+import QuestService from '@thxnetwork/api/services/QuestService';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+
+const validation = [param('id').isMongoId(), body('recaptcha').isString()];
+
+const controller = async ({ params, body, account }: Request, res: Response) => {
+    const quest = await QuestCustom.findById(params.id);
+    if (!quest) throw new NotFoundError('Quest not found.');
+
+    const data = { recaptcha: body.recaptcha };
+
+    // Running separately to avoid issues when getting validation results from Discord interactions
+    const isRealUser = await QuestService.isRealUser(quest.variant, { quest, account, data });
+    if (!isRealUser.result) return res.json({ error: isRealUser.reason });
+
+    const { result, reason } = await QuestService.getValidationResult(quest.variant, {
+        quest,
+        account,
+        data,
+    });
+    if (!result) return res.json({ error: reason });
+
+    const job = await agenda.now(JobType.CreateQuestEntry, {
+        variant: QuestVariant.Custom,
+        questId: String(quest._id),
+        sub: account.sub,
+        data: { ...data, isClaimed: true },
+    });
+
+    res.json({ jobId: job.attrs._id });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/quests/daily/daily.router.ts b/apps/api/src/app/controllers/quests/daily/daily.router.ts
new file mode 100644
index 000000000..09334a3ef
--- /dev/null
+++ b/apps/api/src/app/controllers/quests/daily/daily.router.ts
@@ -0,0 +1,16 @@
+import express from 'express';
+import * as CreateEntry from './entries/post.controller';
+import { assertAccount, assertRequestInput } from '@thxnetwork/api/middlewares';
+import { limitInSeconds } from '@thxnetwork/api/util/ratelimiter';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.post(
+    '/:id/entries',
+    limitInSeconds(3),
+    assertRequestInput(CreateEntry.validation),
+    assertAccount,
+    CreateEntry.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/quests/daily/entries/post.controller.ts b/apps/api/src/app/controllers/quests/daily/entries/post.controller.ts
new file mode 100644
index 000000000..89ebe229f
--- /dev/null
+++ b/apps/api/src/app/controllers/quests/daily/entries/post.controller.ts
@@ -0,0 +1,40 @@
+import { Request, Response } from 'express';
+import { body, param } from 'express-validator';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { JobType, QuestVariant } from '@thxnetwork/common/enums';
+import { agenda } from '@thxnetwork/api/util/agenda';
+import { QuestDaily } from '@thxnetwork/api/models';
+import { getIP } from '@thxnetwork/api/util/ip';
+import QuestService from '@thxnetwork/api/services/QuestService';
+
+const validation = [param('id').isMongoId(), body('recaptcha').isString()];
+
+const controller = async (req: Request, res: Response) => {
+    const { params, account } = req;
+    const quest = await QuestDaily.findById(params.id);
+    if (!quest) throw new NotFoundError('Could not find the Daily Reward');
+
+    // Only do this is no event requirement is set
+    const data = { recaptcha: req.body.recaptcha, metadata: {} };
+    if (!quest.eventName) {
+        data.metadata['ip'] = getIP(req);
+    }
+
+    // Running separately to avoid issues when getting validation results from Discord interactions
+    const isRealUser = await QuestService.isRealUser(quest.variant, { quest, account, data });
+    if (!isRealUser.result) return res.json({ error: isRealUser.reason });
+
+    const { result, reason } = await QuestService.getValidationResult(quest.variant, { quest, account, data });
+    if (!result) return res.json({ error: reason });
+
+    const job = await agenda.now(JobType.CreateQuestEntry, {
+        variant: QuestVariant.Daily,
+        questId: String(quest._id),
+        sub: account.sub,
+        data,
+    });
+
+    res.json({ jobId: job.attrs._id });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/quests/gitcoin/entries/post.controller.ts b/apps/api/src/app/controllers/quests/gitcoin/entries/post.controller.ts
new file mode 100644
index 000000000..832bd3589
--- /dev/null
+++ b/apps/api/src/app/controllers/quests/gitcoin/entries/post.controller.ts
@@ -0,0 +1,50 @@
+import { Request, Response } from 'express';
+import { body, param } from 'express-validator';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { recoverSigner } from '@thxnetwork/api/util/network';
+import { JobType, QuestVariant } from '@thxnetwork/common/enums';
+import { QuestGitcoin } from '@thxnetwork/api/models';
+import { agenda } from '@thxnetwork/api/util/agenda';
+import QuestService from '@thxnetwork/api/services/QuestService';
+import GitcoinService from '@thxnetwork/api/services/GitcoinService';
+
+const validation = [
+    param('id').isMongoId(),
+    body('signature').isString(),
+    body('chainId').isInt(),
+    body('recaptcha').isString(),
+];
+
+const controller = async ({ account, body, params }: Request, res: Response) => {
+    const quest = await QuestGitcoin.findById(params.id);
+    if (!quest) throw new NotFoundError('Quest not found');
+
+    const address = recoverSigner(body.message, body.signature);
+    const data = { recaptcha: body.recaptcha, metadata: { address } };
+
+    // Running separately to avoid issues when getting validation results from Discord interactions
+    const isRealUser = await QuestService.isRealUser(quest.variant, { quest, account, data });
+    if (!isRealUser.result) return res.json({ error: isRealUser.reason });
+
+    // Add wallet and add score for address
+    const { score, error } = await GitcoinService.getScoreUniqueHumanity(
+        quest.scorerId,
+        data.metadata.address.toLowerCase(),
+    );
+    if (error) return res.json({ error });
+    data.metadata['score'] = score;
+
+    const { result, reason } = await QuestService.getValidationResult(quest.variant, { quest, account, data });
+    if (!result) return res.json({ error: reason });
+
+    const job = await agenda.now(JobType.CreateQuestEntry, {
+        variant: QuestVariant.Gitcoin,
+        questId: String(quest._id),
+        sub: account.sub,
+        data,
+    });
+
+    res.json({ jobId: job.attrs._id });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/quests/gitcoin/gitcoin.router.ts b/apps/api/src/app/controllers/quests/gitcoin/gitcoin.router.ts
new file mode 100644
index 000000000..f2f9e9662
--- /dev/null
+++ b/apps/api/src/app/controllers/quests/gitcoin/gitcoin.router.ts
@@ -0,0 +1,16 @@
+import express from 'express';
+import * as CreateEntry from './entries/post.controller';
+import { assertRequestInput, assertAccount } from '@thxnetwork/api/middlewares';
+import { limitInSeconds } from '@thxnetwork/api/util/ratelimiter';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.post(
+    '/:id/entries',
+    limitInSeconds(3),
+    assertRequestInput(CreateEntry.validation),
+    assertAccount,
+    CreateEntry.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/quests/list.controller.ts b/apps/api/src/app/controllers/quests/list.controller.ts
new file mode 100644
index 000000000..82869975b
--- /dev/null
+++ b/apps/api/src/app/controllers/quests/list.controller.ts
@@ -0,0 +1,38 @@
+import { Request, Response } from 'express';
+import { getIP } from '@thxnetwork/api/util/ip';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import QuestService from '@thxnetwork/api/services/QuestService';
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+import { parseToken } from '@thxnetwork/api/util/jwt';
+
+// This endpoint is public so we do not get req.auth populated
+// so we need to decode the auth header manually when it is present
+const controller = async (req: Request, res: Response) => {
+    const token = parseToken(req.header('authorization'));
+    const sub = token && token.sub;
+
+    const pool = await PoolService.getById(req.header('X-PoolId'));
+    const account = sub && (await AccountProxy.findById(sub));
+
+    const ip = getIP(req);
+    // Results are returned in order of the QuestVariant enum keys
+    const [daily, invite, twitter, discord, youtube, custom, web3, gitcoin, webhook] = await QuestService.list({
+        pool,
+        account,
+        data: { ip },
+    });
+
+    res.json({
+        daily,
+        custom,
+        invite,
+        twitter,
+        discord,
+        youtube,
+        web3,
+        gitcoin,
+        webhook,
+    });
+};
+
+export { controller };
diff --git a/apps/api/src/app/controllers/quests/quests.router.ts b/apps/api/src/app/controllers/quests/quests.router.ts
new file mode 100644
index 000000000..8d3a44425
--- /dev/null
+++ b/apps/api/src/app/controllers/quests/quests.router.ts
@@ -0,0 +1,24 @@
+import { checkJwt, corsHandler } from '@thxnetwork/api/middlewares';
+import express from 'express';
+import * as ListQuests from './list.controller';
+import * as ListQuestsPublic from './recent/list.controller';
+import RouterQuestSocial from './social/social.router';
+import RouterQuestWeb3 from './web3/web3.router';
+import RouterQuestGitcoin from './gitcoin/gitcoin.router';
+import RouterQuestDaily from './daily/daily.router';
+import RouterQuestCustom from './custom/custom.router';
+import RouterQuestWebhook from './webhook/webhook.router';
+
+const router: express.Router = express.Router();
+
+router.get('/', ListQuests.controller);
+router.get('/public', ListQuestsPublic.controller);
+router.use(checkJwt).use(corsHandler);
+router.use('/social', RouterQuestSocial);
+router.use('/web3', RouterQuestWeb3);
+router.use('/gitcoin', RouterQuestGitcoin);
+router.use('/daily', RouterQuestDaily);
+router.use('/custom', RouterQuestCustom);
+router.use('/webhook', RouterQuestWebhook);
+
+export default router;
diff --git a/apps/api/src/app/controllers/quests/recent/list.controller.ts b/apps/api/src/app/controllers/quests/recent/list.controller.ts
new file mode 100644
index 000000000..9eba84e02
--- /dev/null
+++ b/apps/api/src/app/controllers/quests/recent/list.controller.ts
@@ -0,0 +1,99 @@
+import { Request, Response } from 'express';
+import { query } from 'express-validator';
+import {
+    Pool,
+    QuestDaily,
+    QuestInvite,
+    QuestSocial,
+    QuestCustom,
+    QuestWeb3,
+    QuestGitcoin,
+} from '@thxnetwork/api/models';
+const validation = [query('page').isInt(), query('limit').isInt(), query('search').optional().isString()];
+
+const controller = async (req: Request, res: Response) => {
+    const questModels = [QuestDaily, QuestInvite, QuestSocial, QuestCustom, QuestWeb3, QuestGitcoin];
+    const questLookupStages = questModels.map((model) => {
+        return {
+            $lookup: {
+                from: model.collection.name,
+                localField: 'poolId',
+                foreignField: 'poolId',
+                as: model.collection.name,
+            },
+        };
+    });
+
+    const decoratedPools = await Pool.aggregate([
+        {
+            $addFields: {
+                poolId: {
+                    $convert: {
+                        input: '$_id',
+                        to: 'string',
+                    },
+                },
+            },
+        },
+        ...questLookupStages,
+        {
+            $lookup: {
+                from: 'widget',
+                localField: 'poolId',
+                foreignField: 'poolId',
+                as: 'widget',
+            },
+        },
+        {
+            $lookup: {
+                from: 'brand',
+                localField: 'poolId',
+                foreignField: 'poolId',
+                as: 'brand',
+            },
+        },
+        {
+            $match: {
+                'settings.isPublished': true,
+                'rank': { $exists: true },
+            },
+        },
+    ]).exec();
+
+    const sortByDate = (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
+
+    const result = decoratedPools
+        // Format and sort all quests per pool
+        .map((result) => {
+            const mapper = (q) => ({
+                ...q,
+                amount: q.amounts ? q.amounts[q.amounts.length - 1] : q.amount,
+                widget: result.widget && result.widget[0],
+                domain: result.widget && result.widget[0] && result.widget[0].domain,
+                brand: result.brand && result.brand[0],
+            });
+
+            const quests = [
+                ...result[QuestDaily.collection.name].map(mapper).sort(sortByDate),
+                ...result[QuestInvite.collection.name].map(mapper).sort(sortByDate),
+                ...result[QuestSocial.collection.name].map(mapper).sort(sortByDate),
+                ...result[QuestCustom.collection.name].map(mapper).sort(sortByDate),
+                ...result[QuestWeb3.collection.name].map(mapper).sort(sortByDate),
+                ...result[QuestGitcoin.collection.name].map(mapper).sort(sortByDate),
+            ];
+
+            return {
+                quests,
+            };
+        })
+        // Last quest per pool
+        .map((pool) => pool.quests[0])
+        // Sort by createdAt
+        .sort(sortByDate)
+        // Cut of first 4 results
+        .slice(0, 4);
+
+    res.json(result);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/quests/social/entries/post.controller.ts b/apps/api/src/app/controllers/quests/social/entries/post.controller.ts
new file mode 100644
index 000000000..0450a969e
--- /dev/null
+++ b/apps/api/src/app/controllers/quests/social/entries/post.controller.ts
@@ -0,0 +1,68 @@
+import { Request, Response } from 'express';
+import { body, param } from 'express-validator';
+import { JobType, agenda } from '@thxnetwork/api/util/agenda';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { QuestVariant } from '@thxnetwork/common/enums';
+import { TwitterUser } from '@thxnetwork/api/models/TwitterUser';
+import { DiscordMessage, DiscordReaction, QuestSocial } from '@thxnetwork/api/models';
+import QuestService from '@thxnetwork/api/services/QuestService';
+import DiscordService from '@thxnetwork/api/services/DiscordService';
+import { QuestSocialRequirement } from '@thxnetwork/common/enums';
+
+const validation = [param('id').isMongoId(), body('recaptcha').isString()];
+
+async function controller({ params, body, account }: Request, res: Response) {
+    // Get the quest document
+    const quest = await QuestSocial.findById(params.id);
+    if (!quest) throw new NotFoundError('Quest not found');
+
+    // Get platform user id for account
+    const platformUserId = QuestService.findUserIdForInteraction(account, quest.interaction);
+    if (!platformUserId) return res.json({ error: 'Could not find platform user id.' });
+
+    const data = { metadata: { platformUserId, discord: {}, twitter: {} }, recaptcha: body.recaptcha };
+
+    // Running separately to avoid issues when getting validation results from Discord interactions
+    const isRealUser = await QuestService.isRealUser(quest.variant, { quest, account, data });
+    if (!isRealUser.result) return res.json({ error: isRealUser.reason });
+
+    // Get validation result for this quest entry
+    const { result, reason } = await QuestService.getValidationResult(quest.variant, { quest, account, data });
+    if (!result) return res.json({ error: reason });
+
+    // For Discord Bot quests we store server user name in metadata
+    if (quest.variant === QuestVariant.Discord && quest.interaction !== QuestSocialRequirement.DiscordGuildJoined) {
+        const guild = await DiscordService.getGuild(quest.poolId);
+        const member = guild && (await DiscordService.getMember(guild.id, platformUserId));
+
+        data.metadata.discord = {
+            guildId: guild && guild.id,
+            username: member.user.username,
+            joinedAt: new Date(member.joinedTimestamp).toISOString(),
+            reactionCount: guild
+                ? await DiscordReaction.countDocuments({ guildId: guild.id, userId: platformUserId })
+                : 0,
+            messageCount: guild
+                ? await DiscordMessage.countDocuments({ guildId: guild.id, userId: platformUserId })
+                : 0,
+        };
+    }
+
+    // For Twitter quests we store public metrics in metadata
+    if (quest.variant === QuestVariant.Twitter) {
+        const user = await TwitterUser.findOne({ userId: platformUserId });
+        data.metadata.twitter = user.publicMetrics;
+    }
+
+    // Schedule serial job
+    const job = await agenda.now(JobType.CreateQuestEntry, {
+        variant: quest.variant,
+        questId: String(quest._id),
+        sub: account.sub,
+        data,
+    });
+
+    res.json({ jobId: job.attrs._id });
+}
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/quests/social/social.router.ts b/apps/api/src/app/controllers/quests/social/social.router.ts
new file mode 100644
index 000000000..72c309e5a
--- /dev/null
+++ b/apps/api/src/app/controllers/quests/social/social.router.ts
@@ -0,0 +1,16 @@
+import express, { Router } from 'express';
+import { assertRequestInput, assertAccount } from '@thxnetwork/api/middlewares';
+import { limitInSeconds } from '@thxnetwork/api/util/ratelimiter';
+import * as CreateEntries from './entries/post.controller';
+
+const router: express.Router = Router({ mergeParams: true });
+
+router.post(
+    '/:id/entries',
+    limitInSeconds(3),
+    assertRequestInput(CreateEntries.validation),
+    assertAccount,
+    CreateEntries.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/quests/web3/entries/post.controller.ts b/apps/api/src/app/controllers/quests/web3/entries/post.controller.ts
new file mode 100644
index 000000000..6ba050221
--- /dev/null
+++ b/apps/api/src/app/controllers/quests/web3/entries/post.controller.ts
@@ -0,0 +1,59 @@
+import { Request, Response } from 'express';
+import { body, param } from 'express-validator';
+import { QuestWeb3 } from '@thxnetwork/api/models/QuestWeb3';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { agenda } from '@thxnetwork/api/util/agenda';
+import { recoverSigner } from '@thxnetwork/api/util/network';
+import { chainList } from '@thxnetwork/common/chains';
+import { JobType, QuestVariant } from '@thxnetwork/common/enums';
+import QuestWeb3Service from '@thxnetwork/api/services/QuestWeb3Service';
+import QuestService from '@thxnetwork/api/services/QuestService';
+
+const validation = [
+    param('id').isMongoId(),
+    body('signature').isString(),
+    body('chainId').isInt(),
+    body('recaptcha').isString(),
+];
+
+const controller = async ({ account, body, params }: Request, res: Response) => {
+    const quest = await QuestWeb3.findById(params.id);
+    if (!quest) throw new NotFoundError('Quest not found');
+
+    const address = recoverSigner(body.message, body.signature);
+    if (!address) throw new NotFoundError(`Could not recover address from signature.`);
+
+    const { rpc, name } = chainList[body.chainId];
+    if (!rpc) throw new NotFoundError(`Could not find RPC for ${name}`);
+
+    const data = { recaptcha: body.recaptcha, metadata: { address, rpc, chainId: body.chainId, callResult: '' } };
+
+    // Running separately to avoid issues when getting validation results from Discord interactions
+    const isRealUser = await QuestService.isRealUser(quest.variant, { quest, account, data });
+    if (!isRealUser.result) return res.json({ error: isRealUser.reason });
+
+    // Fetch the call result so we can store it in the entry
+    const callResult = await QuestWeb3Service.getCallResult({ quest, account, data });
+    if (!callResult.result) return res.json({ error: callResult.reason });
+    data.metadata.callResult = callResult.value.toString();
+
+    // Validate the result
+    const validationResult = await QuestService.getValidationResult(quest.variant, {
+        quest,
+        account,
+        data,
+    });
+    if (!validationResult.result) return res.json({ error: validationResult.reason });
+
+    // Schedule the job
+    const job = await agenda.now(JobType.CreateQuestEntry, {
+        variant: QuestVariant.Web3,
+        questId: String(quest._id),
+        sub: account.sub,
+        data,
+    });
+
+    res.json({ jobId: job.attrs._id });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/quests/web3/web3.router.ts b/apps/api/src/app/controllers/quests/web3/web3.router.ts
new file mode 100644
index 000000000..82a49522a
--- /dev/null
+++ b/apps/api/src/app/controllers/quests/web3/web3.router.ts
@@ -0,0 +1,10 @@
+import express from 'express';
+import * as Create from './entries/post.controller';
+import { assertAccount, assertRequestInput } from '@thxnetwork/api/middlewares';
+import { limitInSeconds } from '@thxnetwork/api/util/ratelimiter';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.post('/:id/entries', limitInSeconds(3), assertRequestInput(Create.validation), assertAccount, Create.controller);
+
+export default router;
diff --git a/apps/api/src/app/controllers/quests/webhook/entries/post.controller.ts b/apps/api/src/app/controllers/quests/webhook/entries/post.controller.ts
new file mode 100644
index 000000000..d0b6cf6ba
--- /dev/null
+++ b/apps/api/src/app/controllers/quests/webhook/entries/post.controller.ts
@@ -0,0 +1,42 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { agenda } from '@thxnetwork/api/util/agenda';
+import { JobType, QuestVariant } from '@thxnetwork/common/enums';
+import { QuestWebhook } from '@thxnetwork/api/models';
+import QuestService from '@thxnetwork/api/services/QuestService';
+
+const validation = [param('id').isMongoId()];
+
+const controller = async ({ account, body, params }: Request, res: Response) => {
+    const quest = await QuestWebhook.findById(params.id);
+    if (!quest) throw new NotFoundError('Quest not found');
+
+    const data = {
+        recaptcha: body.recaptcha,
+    };
+
+    // Running separately to avoid issues when getting validation results from Discord interactions
+    const isRealUser = await QuestService.isRealUser(quest.variant, { quest, account, data });
+    if (!isRealUser.result) return res.json({ error: isRealUser.reason });
+
+    // Validate the result
+    const validationResult = await QuestService.getValidationResult(quest.variant, {
+        quest,
+        account,
+        data,
+    });
+    if (!validationResult.result) return res.json({ error: validationResult.reason });
+
+    // Schedule the job
+    const job = await agenda.now(JobType.CreateQuestEntry, {
+        variant: QuestVariant.Webhook,
+        questId: String(quest._id),
+        sub: account.sub,
+        data: { ...data, metadata: validationResult },
+    });
+
+    res.json({ jobId: job.attrs._id });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/quests/webhook/webhook.router.ts b/apps/api/src/app/controllers/quests/webhook/webhook.router.ts
new file mode 100644
index 000000000..08f4c714c
--- /dev/null
+++ b/apps/api/src/app/controllers/quests/webhook/webhook.router.ts
@@ -0,0 +1,16 @@
+import express from 'express';
+import { assertAccount, assertRequestInput } from '@thxnetwork/api/middlewares';
+import { limitInSeconds } from '@thxnetwork/api/util/ratelimiter';
+import * as CreateEntry from './entries/post.controller';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.post(
+    '/:id/entries',
+    limitInSeconds(3),
+    assertRequestInput(CreateEntry.validation),
+    assertAccount,
+    CreateEntry.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/rewards/list.controller.ts b/apps/api/src/app/controllers/rewards/list.controller.ts
new file mode 100644
index 000000000..a8815d7ab
--- /dev/null
+++ b/apps/api/src/app/controllers/rewards/list.controller.ts
@@ -0,0 +1,22 @@
+import { Request, Response } from 'express';
+import { parseToken } from '@thxnetwork/api/util/jwt';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import RewardService from '@thxnetwork/api/services/RewardService';
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+
+const controller = async (req: Request, res: Response) => {
+    const token = parseToken(req.header('authorization'));
+    const sub = token && token.sub;
+
+    const pool = await PoolService.getById(req.header('X-PoolId'));
+    const account = sub && (await AccountProxy.findById(sub));
+
+    const [coin, nft, custom, coupon, discordRole, galachain] = await RewardService.list({
+        pool,
+        account,
+    });
+
+    res.json({ coin, nft, custom, coupon, discordRole, galachain });
+};
+
+export { controller };
diff --git a/apps/api/src/app/controllers/rewards/payments/list.controller.ts b/apps/api/src/app/controllers/rewards/payments/list.controller.ts
new file mode 100644
index 000000000..ce90a1944
--- /dev/null
+++ b/apps/api/src/app/controllers/rewards/payments/list.controller.ts
@@ -0,0 +1,12 @@
+import RewardService from '@thxnetwork/api/services/RewardService';
+import { Request, Response } from 'express';
+import { query } from 'express-validator';
+
+const validation = [query('walletId').optional().isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const payments = await RewardService.findPaymentsForSub(req.auth.sub);
+    res.json(payments);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/rewards/payments/post.controller.ts b/apps/api/src/app/controllers/rewards/payments/post.controller.ts
new file mode 100644
index 000000000..13957387b
--- /dev/null
+++ b/apps/api/src/app/controllers/rewards/payments/post.controller.ts
@@ -0,0 +1,47 @@
+import { Request, Response } from 'express';
+import { body, param } from 'express-validator';
+import { ForbiddenError, NotFoundError } from '@thxnetwork/api/util/errors';
+import { JobType, RewardVariant } from '@thxnetwork/common/enums';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+import RewardService from '@thxnetwork/api/services/RewardService';
+import SafeService from '@thxnetwork/api/services/SafeService';
+import { agenda } from '@thxnetwork/api/util/agenda';
+import { Wallet } from '@thxnetwork/api/models';
+
+const validation = [param('variant').isInt(), param('rewardId').isMongoId(), body('walletId').optional().isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const variant = req.params.variant as unknown as RewardVariant;
+    const rewardId = req.params.rewardId as string;
+
+    const reward = await RewardService.findById(variant, rewardId);
+    if (!reward) throw new NotFoundError('Reward not found');
+
+    const pool = await PoolService.getById(reward.poolId);
+    if (!pool) throw new NotFoundError('Campaign not found');
+
+    const safe = await SafeService.findOneByPool(pool);
+    if (!safe) throw new NotFoundError('Campaign Safe not found');
+
+    const account = await AccountProxy.findById(req.auth.sub);
+    if (!account) throw new NotFoundError('Account not found');
+
+    const wallet = req.body.walletId ? await Wallet.findById(req.body.walletId) : null;
+    const validationResult = await RewardService.getValidationResult({ reward, account, safe, wallet });
+    if (!validationResult.result) {
+        throw new ForbiddenError(validationResult.reason);
+    }
+
+    // Serialize payment processing with job queue
+    const job = await agenda.now(JobType.CreateRewardPayment, {
+        variant,
+        rewardId,
+        sub: account.sub,
+        walletId: req.body.walletId,
+    });
+
+    res.json({ jobId: job.attrs._id });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/rewards/rewards.router.ts b/apps/api/src/app/controllers/rewards/rewards.router.ts
new file mode 100644
index 000000000..059aeb181
--- /dev/null
+++ b/apps/api/src/app/controllers/rewards/rewards.router.ts
@@ -0,0 +1,18 @@
+import { assertRequestInput, checkJwt, corsHandler } from '@thxnetwork/api/middlewares';
+import express, { Router } from 'express';
+import * as ListRewards from './list.controller';
+import * as CreateRewardPayment from './payments/post.controller';
+import * as ListRewardPayment from './payments/list.controller';
+
+const router: express.Router = express.Router({ mergeParams: true });
+
+router.get('/', ListRewards.controller);
+router.use(checkJwt, corsHandler);
+router.post(
+    '/:variant/:rewardId/payments',
+    assertRequestInput(CreateRewardPayment.validation),
+    CreateRewardPayment.controller,
+);
+router.get('/payments', assertRequestInput(ListRewardPayment.validation), ListRewardPayment.controller);
+
+export default router;
diff --git a/apps/api/src/app/controllers/token/cs/get.controller.ts b/apps/api/src/app/controllers/token/cs/get.controller.ts
new file mode 100644
index 000000000..fc8a3444c
--- /dev/null
+++ b/apps/api/src/app/controllers/token/cs/get.controller.ts
@@ -0,0 +1,8 @@
+import { Request, Response } from 'express';
+import { CIRCULATING_SUPPLY } from '@thxnetwork/api/config/secrets';
+
+const controller = async (req: Request, res: Response) => {
+    res.header('Content-Type', 'text/plain').send(CIRCULATING_SUPPLY);
+};
+
+export { controller };
diff --git a/apps/api/src/app/controllers/token/token.router.ts b/apps/api/src/app/controllers/token/token.router.ts
new file mode 100644
index 000000000..0865ea18b
--- /dev/null
+++ b/apps/api/src/app/controllers/token/token.router.ts
@@ -0,0 +1,11 @@
+import express, { Router } from 'express';
+
+import * as ReadTokenCirculatingSupply from './cs/get.controller';
+import * as ReadTokenTotalSupply from './ts/get.controller';
+
+const router: express.Router = express.Router();
+
+router.get('/cs', ReadTokenCirculatingSupply.controller);
+router.get('/ts', ReadTokenTotalSupply.controller);
+
+export default router;
diff --git a/apps/api/src/app/controllers/token/ts/get.controller.ts b/apps/api/src/app/controllers/token/ts/get.controller.ts
new file mode 100644
index 000000000..c34b4eb73
--- /dev/null
+++ b/apps/api/src/app/controllers/token/ts/get.controller.ts
@@ -0,0 +1,8 @@
+import { Request, Response } from 'express';
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['THX Token']
+    res.header('Content-Type', 'text/plain').send('100000000');
+};
+
+export { controller };
diff --git a/apps/api/src/app/controllers/transactions/list.controller.ts b/apps/api/src/app/controllers/transactions/list.controller.ts
new file mode 100644
index 000000000..f6f9deaf0
--- /dev/null
+++ b/apps/api/src/app/controllers/transactions/list.controller.ts
@@ -0,0 +1,21 @@
+import { Request, Response } from 'express';
+import { query } from 'express-validator';
+import { Wallet } from '@thxnetwork/api/models';
+import { ChainId } from '@thxnetwork/common/enums';
+import { NODE_ENV } from '@thxnetwork/api/config/secrets';
+
+const validation = [query('chainId').optional().isNumeric(), query('poolId').optional().isString()];
+
+const controller = async (req: Request, res: Response) => {
+    const chainId = NODE_ENV === 'production' ? ChainId.Polygon : ChainId.Hardhat;
+    const wallets = await Wallet.find({
+        sub: req.auth.sub,
+        chainId,
+        safeVersion: { $exists: true },
+        poolId: req.query.poolId,
+    });
+
+    res.json(wallets);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/upload/put.controller.ts b/apps/api/src/app/controllers/upload/put.controller.ts
new file mode 100644
index 000000000..25315103c
--- /dev/null
+++ b/apps/api/src/app/controllers/upload/put.controller.ts
@@ -0,0 +1,10 @@
+import { Request, Response } from 'express';
+import ImageService from '@thxnetwork/api/services/ImageService';
+
+const controller = async (req: Request, res: Response) => {
+    if (!req.file) return res.status(440).send('There no file to process');
+    const publicUrl = await ImageService.upload(req.file);
+    res.send({ publicUrl });
+};
+
+export { controller };
diff --git a/apps/api/src/app/controllers/upload/upload.router.ts b/apps/api/src/app/controllers/upload/upload.router.ts
new file mode 100644
index 000000000..e513f42d5
--- /dev/null
+++ b/apps/api/src/app/controllers/upload/upload.router.ts
@@ -0,0 +1,9 @@
+import express, { Router } from 'express';
+import { upload } from '@thxnetwork/api/util/multer';
+import * as PutUpload from './put.controller';
+
+const router: express.Router = express.Router();
+
+router.put('/', upload.single('file'), PutUpload.controller);
+
+export default router;
diff --git a/apps/api/src/app/controllers/ve/claim/post.controller.ts b/apps/api/src/app/controllers/ve/claim/post.controller.ts
new file mode 100644
index 000000000..6e95be582
--- /dev/null
+++ b/apps/api/src/app/controllers/ve/claim/post.controller.ts
@@ -0,0 +1,14 @@
+import { Request, Response } from 'express';
+import { query } from 'express-validator';
+import VoteEscrowService from '@thxnetwork/api/services/VoteEscrowService';
+
+const validation = [query('walletId').isMongoId()];
+
+const controller = async ({ wallet }: Request, res: Response) => {
+    // TODO Check if wallet has tokens to claim
+    // Propose the claimTokens transaction
+    const txs = await VoteEscrowService.claimTokens(wallet);
+
+    res.status(201).json(txs);
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/ve/deposit/post.controller.ts b/apps/api/src/app/controllers/ve/deposit/post.controller.ts
new file mode 100644
index 000000000..edf43a092
--- /dev/null
+++ b/apps/api/src/app/controllers/ve/deposit/post.controller.ts
@@ -0,0 +1,35 @@
+import { Request, Response } from 'express';
+import { body, query } from 'express-validator';
+import { ForbiddenError } from '@thxnetwork/api/util/errors';
+import { BigNumber } from 'alchemy-sdk';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { contractNetworks } from '@thxnetwork/api/hardhat';
+import VoteEscrowService from '@thxnetwork/api/services/VoteEscrowService';
+
+const validation = [body('amountInWei').isString(), body('lockEndTimestamp').isInt(), query('walletId').isMongoId()];
+
+const controller = async ({ body, wallet }: Request, res: Response) => {
+    // Check sufficient BPTGauge approval
+    const amount = await VoteEscrowService.getAllowance(
+        wallet,
+        contractNetworks[wallet.chainId].BPTGauge,
+        contractNetworks[wallet.chainId].VotingEscrow,
+    );
+    if (BigNumber.from(amount).lt(body.amountInWei)) throw new ForbiddenError('Insufficient allowance');
+
+    // Check lockEndTimestamp to be more than today + 3 months
+    const { web3 } = getProvider();
+    const latest = await web3.eth.getBlockNumber();
+    const now = (await web3.eth.getBlock(latest)).timestamp;
+    if (now > body.lockEndTimestamp) throw new ForbiddenError('lockEndTimestamp needs be larger than today');
+
+    // Check SmartWalletWhitelist
+    const isApproved = await VoteEscrowService.isApprovedAddress(wallet.address, wallet.chainId);
+    if (!isApproved) throw new ForbiddenError('Wallet address is not on whitelist.');
+
+    // Deposit funds for wallet
+    const tx = await VoteEscrowService.deposit(wallet, body.amountInWei, body.lockEndTimestamp);
+
+    res.status(201).json([tx]);
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/ve/increase/post.controller.ts b/apps/api/src/app/controllers/ve/increase/post.controller.ts
new file mode 100644
index 000000000..53150979d
--- /dev/null
+++ b/apps/api/src/app/controllers/ve/increase/post.controller.ts
@@ -0,0 +1,56 @@
+import { Request, Response } from 'express';
+import { body, query } from 'express-validator';
+import { ForbiddenError } from '@thxnetwork/api/util/errors';
+import { BigNumber } from 'alchemy-sdk';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { contractNetworks } from '@thxnetwork/api/hardhat';
+import VoteEscrowService from '@thxnetwork/api/services/VoteEscrowService';
+
+const validation = [
+    query('walletId').isMongoId(),
+    body('amountInWei').optional().isString(),
+    body('lockEndTimestamp').optional().isInt(),
+];
+
+const controller = async ({ wallet, body }: Request, res: Response) => {
+    // Check SmartWalletWhitelist
+    const isApproved = await VoteEscrowService.isApprovedAddress(wallet.address, wallet.chainId);
+    if (!isApproved) throw new ForbiddenError('Wallet address is not on whitelist.');
+
+    const txList = [];
+    if (body.amountInWei) {
+        // Check sufficient BPTGauge approval
+        const amount = await VoteEscrowService.getAllowance(
+            wallet,
+            contractNetworks[wallet.chainId].BPTGauge,
+            contractNetworks[wallet.chainId].VotingEscrow,
+        );
+        if (BigNumber.from(amount).lt(body.amountInWei)) throw new ForbiddenError('Insufficient allowance');
+
+        // TODO Check sufficient balance
+        const txs = await VoteEscrowService.increaseAmount(wallet, body.amountInWei);
+        txList.push(txs);
+    }
+
+    if (body.lockEndTimestamp) {
+        // Check lockEndTimestamp to be more than today + 90 days
+        const { web3 } = getProvider();
+        const latest = await web3.eth.getBlockNumber();
+        const now = (await web3.eth.getBlock(latest)).timestamp;
+        if (body.lockEndTimestamp < now) {
+            throw new ForbiddenError('lockEndTimestamp needs be larger than today');
+        }
+
+        // Check if lockEndTimestamp is more than current lock end
+        const lock = await VoteEscrowService.list(wallet);
+        if (body.lockEndTimestamp < Number(lock.end)) {
+            throw new ForbiddenError('lockEndTimestamp needs be larger than current lock end');
+        }
+
+        const txs = await VoteEscrowService.increaseUnlockTime(wallet, body.lockEndTimestamp);
+        txList.push(txs);
+    }
+
+    res.status(201).json(txList);
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/ve/list.controller.ts b/apps/api/src/app/controllers/ve/list.controller.ts
new file mode 100644
index 000000000..49c406db4
--- /dev/null
+++ b/apps/api/src/app/controllers/ve/list.controller.ts
@@ -0,0 +1,34 @@
+import { Request, Response } from 'express';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { contractArtifacts, contractNetworks } from '@thxnetwork/api/hardhat';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { query } from 'express-validator';
+import WalletService from '@thxnetwork/api/services/WalletService';
+import VoteEscrowService from '@thxnetwork/api/services/VoteEscrowService';
+const parseMs = (s) => Number(s) * 1000;
+
+const validation = [query('walletId').isMongoId()];
+const controller = async (req: Request, res: Response) => {
+    const walletId = req.query.walletId as string;
+    const wallet = await WalletService.findById(walletId);
+    if (!wallet) throw new NotFoundError('Wallet not found.');
+
+    const { web3 } = getProvider(wallet.chainId);
+    const ve = new web3.eth.Contract(
+        contractArtifacts['VotingEscrow'].abi,
+        contractNetworks[wallet.chainId].VotingEscrow,
+    );
+
+    // Check for lock and determine ve fn to call
+    // Get veTHX balance and pending rewards
+    const [{ amount, end }, latestBlock, balance, rewards] = await Promise.all([
+        VoteEscrowService.list(wallet),
+        web3.eth.getBlock('latest'),
+        ve.methods.balanceOf(wallet.address).call(),
+        VoteEscrowService.listRewards(wallet),
+    ]);
+
+    res.json([{ balance, amount, end: parseMs(end), now: parseMs(latestBlock.timestamp), rewards }]);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/ve/ve.router.ts b/apps/api/src/app/controllers/ve/ve.router.ts
new file mode 100644
index 000000000..b54064f7d
--- /dev/null
+++ b/apps/api/src/app/controllers/ve/ve.router.ts
@@ -0,0 +1,20 @@
+import express from 'express';
+import { assertRequestInput } from '@thxnetwork/api/middlewares';
+import { assertWallet } from '@thxnetwork/api/middlewares/assertWallet';
+
+import * as ListController from './list.controller';
+import * as CreateVEDeposit from './deposit/post.controller';
+import * as CreateVEIncrease from './increase/post.controller';
+import * as CreateVEClaim from './claim/post.controller';
+import * as CreateVEWithdraw from './withdraw/post.controller';
+
+const router: express.Router = express.Router();
+
+router.use('/', assertWallet);
+router.get('/', assertRequestInput(ListController.validation), ListController.controller);
+router.post('/deposit', assertRequestInput(CreateVEDeposit.validation), CreateVEDeposit.controller);
+router.post('/increase', assertRequestInput(CreateVEIncrease.validation), CreateVEIncrease.controller);
+router.post('/claim', assertRequestInput(CreateVEClaim.validation), CreateVEClaim.controller);
+router.post('/withdraw', assertRequestInput(CreateVEWithdraw.validation), CreateVEWithdraw.controller);
+
+export default router;
diff --git a/apps/api/src/app/controllers/ve/ve.test.ts b/apps/api/src/app/controllers/ve/ve.test.ts
new file mode 100644
index 000000000..47cb78cb7
--- /dev/null
+++ b/apps/api/src/app/controllers/ve/ve.test.ts
@@ -0,0 +1,333 @@
+import request from 'supertest';
+import app from '@thxnetwork/api/';
+import { afterAllCallback, beforeAllCallback } from '@thxnetwork/api/util/jest/config';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { BigNumber, Contract, ethers } from 'ethers';
+import { ChainId } from '@thxnetwork/common/enums';
+import { contractArtifacts, contractNetworks } from '@thxnetwork/api/hardhat';
+import { sub, userWalletPrivateKey, widgetAccessToken } from '@thxnetwork/api/util/jest/constants';
+import { WalletDocument } from '@thxnetwork/api/models/Wallet';
+import { signTxHash, timeTravel } from '@thxnetwork/api/util/jest/network';
+import { poll } from '@thxnetwork/api/util/polling';
+import SafeService from '@thxnetwork/api/services/SafeService';
+
+const user = request.agent(app);
+const { signer } = getProvider(ChainId.Hardhat);
+
+describe('VESytem', () => {
+    beforeAll(beforeAllCallback);
+    afterAll(afterAllCallback);
+
+    const amountInWei = String(ethers.utils.parseUnits('1000', 'ether'));
+    const chainId = ChainId.Hardhat;
+
+    let safeWallet!: WalletDocument,
+        testBPT!: Contract,
+        testBPTGauge!: Contract,
+        testBAL!: Contract,
+        vethx!: Contract,
+        rdthx!: Contract,
+        rfthx!: Contract,
+        scthx!: Contract;
+
+    it('Deploy Tokens', async () => {
+        safeWallet = await SafeService.findOne({ sub, poolId: { $exists: false }, safeVersion: { $exists: true } });
+        expect(safeWallet.address).toBeDefined();
+
+        testBAL = new ethers.Contract(contractNetworks[chainId].BAL, contractArtifacts['BAL'].abi, signer);
+        testBPT = new ethers.Contract(contractNetworks[chainId].BPT, contractArtifacts['BPT'].abi, signer);
+        testBPTGauge = new ethers.Contract(
+            contractNetworks[chainId].BPTGauge,
+            contractArtifacts['BPTGauge'].abi,
+            signer,
+        );
+
+        vethx = new ethers.Contract(
+            contractNetworks[chainId].VotingEscrow,
+            contractArtifacts['VotingEscrow'].abi,
+            signer,
+        );
+        rdthx = new ethers.Contract(
+            contractNetworks[chainId].RewardDistributor,
+            contractArtifacts['RewardDistributor'].abi,
+            signer,
+        );
+        rfthx = new ethers.Contract(
+            contractNetworks[chainId].RewardFaucet,
+            contractArtifacts['RewardFaucet'].abi,
+            signer,
+        );
+        scthx = new ethers.Contract(
+            contractNetworks[chainId].SmartWalletWhitelist,
+            contractArtifacts['SmartWalletWhitelist'].abi,
+            signer,
+        );
+    });
+
+    describe('Create Reward Distribution', () => {
+        it('Create Reward Distribution after first week', async () => {
+            const amountBPT = String(ethers.utils.parseUnits('100000', 'ether'));
+            const amountBAL = String(ethers.utils.parseUnits('1000', 'ether'));
+
+            // Travel past first week else this throws "Reward distribution has not started yet"
+            await timeTravel(60 * 60 * 24 * 7);
+
+            // Deposit reward tokens into rdthx
+            await testBPT.approve(rfthx.address, amountBPT);
+            await testBAL.approve(rfthx.address, amountBAL);
+            await rfthx.depositEqualWeeksPeriod(testBPT.address, amountBPT, '4');
+            await rfthx.depositEqualWeeksPeriod(testBAL.address, amountBAL, '4');
+        });
+    });
+
+    describe('Stake BPT ', () => {
+        it('Balance = total', async () => {
+            let tx = await testBPT.transfer(safeWallet.address, amountInWei);
+            tx = await tx.wait();
+            const event = tx.events.find((ev) => ev.event === 'Transfer');
+            expect(event).toBeDefined();
+            const balanceInWei = await testBPT.balanceOf(safeWallet.address);
+            expect(balanceInWei.gt(0)).toBe(true);
+        });
+        it('Approve', async () => {
+            const { status, body } = await user
+                .post('/v1/erc20/allowance')
+                .set({ Authorization: widgetAccessToken })
+                .query({ walletId: String(safeWallet._id) })
+                .send({ tokenAddress: testBPT.address, amountInWei, spender: testBPTGauge.address });
+            expect(status).toBe(201);
+
+            for (const tx of body) {
+                expect(tx.safeTxHash).toBeDefined();
+
+                const { signature } = await signTxHash(safeWallet.address, tx.safeTxHash, userWalletPrivateKey);
+                await user
+                    .post('/v1/account/wallets/confirm')
+                    .set({ Authorization: widgetAccessToken })
+                    .query({ walletId: String(safeWallet._id) })
+                    .send({ chainId: ChainId.Hardhat, safeTxHash: tx.safeTxHash, signature })
+                    .expect(200);
+            }
+        });
+
+        it('Wait for approved amount', async () => {
+            // Replace with API call
+            await poll(
+                () => testBPT.allowance(safeWallet.address, testBPTGauge.address),
+                (result: BigNumber) => result.eq(0),
+                1000,
+            );
+        });
+
+        it('Stake 1000 BPT', async () => {
+            const { status, body } = await user
+                .post('/v1/liquidity/stake')
+                .set({ Authorization: widgetAccessToken })
+                .query({ walletId: String(safeWallet._id) })
+                .send({ amountInWei });
+            expect(status).toBe(201);
+            for (const tx of body) {
+                expect(tx.safeTxHash).toBeDefined();
+
+                const { signature } = await signTxHash(safeWallet.address, tx.safeTxHash, userWalletPrivateKey);
+                await user
+                    .post('/v1/account/wallets/confirm')
+                    .set({ Authorization: widgetAccessToken })
+                    .query({ walletId: String(safeWallet._id) })
+                    .send({ chainId: ChainId.Hardhat, safeTxHash: tx.safeTxHash, signature })
+                    .expect(200);
+            }
+        });
+
+        it('User received BPT-gauge', async () => {
+            await poll(
+                () => testBPTGauge.balanceOf(safeWallet.address),
+                (amount: BigNumber) => BigNumber.from(amount).eq(0),
+                1000,
+            );
+
+            const balanceInWei = await testBPTGauge.balanceOf(safeWallet.address);
+            expect(balanceInWei.gt(0)).toBe(true);
+        });
+    });
+
+    describe('Lock BPT-gauge ', () => {
+        it('WhiteList Safe Wallet', async () => {
+            // Move this to step 1 in VE UI modals
+            let tx = await scthx.approveWallet(safeWallet.address);
+            tx = await tx.wait();
+            const event = tx.events.find((ev) => ev.event === 'ApproveWallet');
+            expect(event).toBeDefined();
+        });
+
+        it('Approve', async () => {
+            const { status, body } = await user
+                .post('/v1/erc20/allowance')
+                .set({ Authorization: widgetAccessToken })
+                .query({ walletId: String(safeWallet._id) })
+                .send({
+                    tokenAddress: contractNetworks[chainId].BPTGauge,
+                    amountInWei,
+                    spender: contractNetworks[chainId].VotingEscrow,
+                });
+            expect(status).toBe(201);
+
+            for (const tx of body) {
+                expect(tx.safeTxHash).toBeDefined();
+
+                const { signature } = await signTxHash(safeWallet.address, tx.safeTxHash, userWalletPrivateKey);
+                await user
+                    .post('/v1/account/wallets/confirm')
+                    .set({ Authorization: widgetAccessToken })
+                    .query({ walletId: String(safeWallet._id) })
+                    .send({ chainId: ChainId.Hardhat, safeTxHash: tx.safeTxHash, signature })
+                    .expect(200);
+            }
+        });
+
+        it('Wait for approved amount', async () => {
+            // Replace with API call
+            await poll(
+                () => testBPTGauge.allowance(safeWallet.address, vethx.address),
+                (result: BigNumber) => result.eq(0),
+                1000,
+            );
+        });
+
+        it('Deposit 1000', async () => {
+            const lockEndTimestamp = Math.ceil(Date.now() / 1000) + 60 * 60 * 24 * 7 * 12; // 12 weeks from now
+            const { status, body } = await user
+                .post('/v1/ve/deposit')
+                .set({ Authorization: widgetAccessToken })
+                .query({ walletId: String(safeWallet._id) })
+                .send({ amountInWei, lockEndTimestamp });
+            expect(status).toBe(201);
+            for (const tx of body) {
+                expect(tx.safeTxHash).toBeDefined();
+
+                const { signature } = await signTxHash(safeWallet.address, tx.safeTxHash, userWalletPrivateKey);
+                await user
+                    .post('/v1/account/wallets/confirm')
+                    .set({ Authorization: widgetAccessToken })
+                    .query({ walletId: String(safeWallet._id) })
+                    .send({ chainId: ChainId.Hardhat, safeTxHash: tx.safeTxHash, signature })
+                    .expect(200);
+            }
+        });
+
+        it('Balance = total - deposit', async () => {
+            await poll(
+                () => vethx.locked(safeWallet.address),
+                (result: { amount: BigNumber }) => BigNumber.from(result.amount).eq(0),
+                1000,
+            );
+
+            const balanceInWei = await testBPTGauge.balanceOf(safeWallet.address);
+            const totalMinDeposit = BigNumber.from(amountInWei).sub(amountInWei);
+
+            expect(balanceInWei.eq(totalMinDeposit)).toBe(true);
+        });
+
+        it('List locks ', async () => {
+            const { status, body } = await user
+                .get('/v1/ve')
+                .query({ walletId: String(safeWallet._id) })
+                .set({ Authorization: widgetAccessToken })
+                .send();
+
+            expect(Number(body[0].rewards)).toBe;
+            expect(Number(body[0].end)).toBeGreaterThan(Number(body[0].now));
+            expect(body[0].amount).toBe(amountInWei);
+            expect(status).toBe(200);
+        });
+    });
+
+    // describe('Claim THX incentives', () => {
+    //     it('Claim Tokens (after 14 days)', async () => {
+    //         console.log(await rfthx.getUpcomingRewardsForNWeeks(testBPT.address, 4));
+    //         console.log(await rfthx.getUpcomingRewardsForNWeeks(testBAL.address, 4));
+
+    //         // Travel past end date of the first reward eligible week
+    //         await timeTravel(60 * 60 * 24 * 8);
+
+    //         console.log(await rfthx.getUpcomingRewardsForNWeeks(testBPT.address, 4));
+    //         console.log(await rfthx.getUpcomingRewardsForNWeeks(testBAL.address, 4));
+
+    //         const balance = await testBPT.balanceOf(safeWallet.address);
+    //         expect(balance).toBeDefined();
+
+    //         let tx = await rdthx.claimToken(safeWallet.address, testBPT.address);
+    //         tx = await tx.wait();
+
+    //         const event = tx.events.find((ev) => ev.event === 'TokenCheckpointed');
+    //         expect(event).toBeDefined();
+
+    //         const balanceAfterClaim = await testBPT.balanceOf(safeWallet.address);
+    //         expect(BigNumber.from(balance).lt(balanceAfterClaim)).toBe(true);
+    //     });
+    // });
+
+    describe('Withdraw BPT', () => {
+        it('Withdraw', async () => {
+            const { status, body } = await user
+                .post('/v1/ve/withdraw')
+                .set({ Authorization: widgetAccessToken })
+                .query({ walletId: String(safeWallet._id) })
+                .send({ isEarlyAttempt: false });
+            expect(status).toBe(403);
+            console.log(body);
+            expect(body.error.message).toBe('Funds are locked');
+        });
+
+        it('Withdraw Early 1000 - penalty', async () => {
+            const { status, body } = await user
+                .post('/v1/ve/withdraw')
+                .set({ Authorization: widgetAccessToken })
+                .query({ walletId: String(safeWallet._id) })
+                .send({ isEarlyAttempt: true });
+            expect(status).toBe(201);
+            for (const tx of body) {
+                expect(tx.safeTxHash).toBeDefined();
+
+                const { signature } = await signTxHash(safeWallet.address, tx.safeTxHash, userWalletPrivateKey);
+                await user
+                    .post('/v1/account/wallets/confirm')
+                    .set({ Authorization: widgetAccessToken })
+                    .query({ walletId: String(safeWallet._id) })
+                    .send({ chainId: ChainId.Hardhat, safeTxHash: tx.safeTxHash, signature })
+                    .expect(200);
+            }
+        });
+
+        // it('Balance = total + deposit - penalty', async () => {
+        //     await poll(
+        //         () => vethx.locked(safeWallet.address),
+        //         (result: { amount: BigNumber }) => !BigNumber.from(result.amount).eq(0),
+        //         1000,
+        //     );
+
+        //     const balanceInWei = await testBPT.balanceOf(safeWallet.address);
+        //     // const rdBalanceInWei = await testBPT.balanceOf(rdthx.address);
+        //     // console.log(
+        //     //     String(balanceInWei), // 1024259542566872427984000
+        //     //     String(rdBalanceInWei), //   25740457433127572016000
+        //     //     String(balanceInWei.add(rdBalanceInWei)), // 1050000000000000000000000
+        //     //     String(balanceInWei.add(rdBalanceInWei).sub(totalSupplyInWei)), //   50000000000000000000000
+        //     // );
+
+        //     // Due to early exit expect less BPT to be returned and the balance of
+        //     // the penalty treasury to increase. Formula to calculate the penalty is in
+        //     // the VE contract.
+        //     // Eg:
+        //     // balanceInWei     = 999917384259259259260000
+        //     // rdBalanceInWei   =     82615740740740740000
+        //     // console.log(String(balanceInWei), amountInWei);
+        //     // Should be larger due to reward claim
+        //     // console.log(String(balanceInWei));
+        //     // console.log(String(amountInWei));
+        //     // console.log(String(BigNumber.from(balanceInWei).sub(BigNumber.from(amountInWei))));
+        //     console.log('balanceInWei', balanceInWei.toString());
+        //     expect(BigNumber.from(balanceInWei).gt(BigNumber.from(amountInWei))).toBe(true);
+        // });
+    });
+});
diff --git a/apps/api/src/app/controllers/ve/withdraw/post.controller.ts b/apps/api/src/app/controllers/ve/withdraw/post.controller.ts
new file mode 100644
index 000000000..4321bc886
--- /dev/null
+++ b/apps/api/src/app/controllers/ve/withdraw/post.controller.ts
@@ -0,0 +1,36 @@
+import { Request, Response } from 'express';
+import { ForbiddenError } from '@thxnetwork/api/util/errors';
+import { contractArtifacts, contractNetworks } from '@thxnetwork/api/hardhat';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { body, query } from 'express-validator';
+import VoteEscrowService from '@thxnetwork/api/services/VoteEscrowService';
+
+const validation = [
+    query('walletId').isMongoId(),
+    body('isEarlyAttempt')
+        .isBoolean()
+        .customSanitizer((val: string) => (val ? JSON.parse(val) : false)),
+];
+
+const controller = async ({ wallet, body }: Request, res: Response) => {
+    // Check sufficient BPT approval
+    const { web3 } = getProvider();
+    const ve = new web3.eth.Contract(
+        contractArtifacts['VotingEscrow'].abi,
+        contractNetworks[wallet.chainId].VotingEscrow,
+    );
+    const lock = await ve.methods.locked(wallet.address).call();
+    const now = (await web3.eth.getBlock('latest')).timestamp;
+
+    // Check if client requests early exit and end date has not past
+    const isEarlyWithdraw = Number(lock.end) > Number(now);
+    if (!body.isEarlyAttempt && isEarlyWithdraw) {
+        throw new ForbiddenError('Funds are locked');
+    }
+
+    // Propose the withdraw transaction
+    const txs = await VoteEscrowService.withdraw(wallet, isEarlyWithdraw);
+
+    res.status(201).json(txs);
+};
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/webhook/daily/daily-quest-webhook.test.ts b/apps/api/src/app/controllers/webhook/daily/daily-quest-webhook.test.ts
new file mode 100644
index 000000000..743c384c7
--- /dev/null
+++ b/apps/api/src/app/controllers/webhook/daily/daily-quest-webhook.test.ts
@@ -0,0 +1,95 @@
+import request from 'supertest';
+import app from '@thxnetwork/api/';
+import { QuestVariant } from '@thxnetwork/common/enums';
+import { account4, dashboardAccessToken, widgetAccessToken4 } from '@thxnetwork/api/util/jest/constants';
+import { isAddress } from 'web3-utils';
+import { afterAllCallback, beforeAllCallback } from '@thxnetwork/api/util/jest/config';
+import { QuestDailyDocument } from '@thxnetwork/api/models';
+import { poll } from '@thxnetwork/api/util/polling';
+import { Job } from '@thxnetwork/api/models/Job';
+import { v4 } from 'uuid';
+
+const user = request.agent(app);
+
+describe('Daily Rewards WebHooks', () => {
+    let poolId: string, dailyReward: QuestDailyDocument;
+    const eventName = v4();
+
+    beforeAll(beforeAllCallback);
+    afterAll(afterAllCallback);
+
+    it('POST /pools', (done) => {
+        user.post('/v1/pools')
+            .set('Authorization', dashboardAccessToken)
+            .send()
+            .expect((res: request.Response) => {
+                expect(isAddress(res.body.safeAddress)).toBe(true);
+                poolId = res.body._id;
+            })
+            .expect(201, done);
+    });
+
+    it('POST /daily-rewards', (done) => {
+        user.post(`/v1/pools/${poolId}/quests/${QuestVariant.Daily}`)
+            .set({ 'X-PoolId': poolId, 'Authorization': dashboardAccessToken })
+            .send({
+                isPublished: true,
+                variant: QuestVariant.Daily,
+                title: 'Expiration date is next 30 min',
+                description: 'Lorem ipsum dolor sit amet',
+                amounts: JSON.stringify([100]),
+                eventName,
+                index: 0,
+            })
+            .expect(async ({ body }: request.Response) => {
+                expect(body.uuid).toBeDefined();
+                expect(body.eventName).toBeDefined();
+                expect(body.amounts[0]).toBe(100);
+                dailyReward = body;
+            })
+            .expect(201, done);
+    });
+
+    it('POST /webhook/daily/:uuid', async () => {
+        const { status } = await user.post(`/v1/webhook/daily/${eventName}`).send({
+            address: account4.address,
+        });
+        expect(status).toBe(201);
+    });
+
+    it('GET /participant to update identity', async () => {
+        const { status } = await user
+            .get(`/v1/participants`)
+            .query({ poolId })
+            .set({ Authorization: widgetAccessToken4 });
+        expect(status).toBe(200);
+    });
+
+    it('POST /quests/daily/:id/entries', async () => {
+        const { status, body } = await user
+            .post(`/v1/quests/daily/${dailyReward._id}/entries`)
+            .set({ 'X-PoolId': poolId, 'Authorization': widgetAccessToken4 })
+            .send({ recaptcha: 'test' });
+        expect(body.jobId).toBeDefined();
+        expect(status).toBe(200);
+
+        await poll(
+            () => Job.findById(body.jobId),
+            (job: any) => !job.lastRunAt,
+            1000,
+        );
+
+        const job = await Job.findById(body.jobId);
+        expect(job.lastRunAt).toBeDefined();
+    });
+
+    it('POST /quests/daily/:id/entries should throw an error', (done) => {
+        user.post(`/v1/quests/daily/${dailyReward._id}/entries`)
+            .set({ 'X-PoolId': poolId, 'Authorization': widgetAccessToken4 })
+            .send({ recaptcha: 'test' })
+            .expect(({ body }: request.Response) => {
+                expect(body.error).toBe('You have completed this quest within the last 24 hours.');
+            })
+            .expect(200, done);
+    });
+});
diff --git a/apps/api/src/app/controllers/webhook/daily/post.controller.ts b/apps/api/src/app/controllers/webhook/daily/post.controller.ts
new file mode 100644
index 000000000..7813e179b
--- /dev/null
+++ b/apps/api/src/app/controllers/webhook/daily/post.controller.ts
@@ -0,0 +1,34 @@
+import { BadRequestError, NotFoundError } from '@thxnetwork/api/util/errors';
+import { Request, Response } from 'express';
+import { body, param } from 'express-validator';
+import { getIdentityForAddress, getIdentityForCode } from '../milestones/claim/post.controller';
+import { Event } from '@thxnetwork/api/models/Event';
+import { Pool, QuestDaily } from '@thxnetwork/api/models';
+
+const validation = [
+    param('uuid').isUUID('4'),
+    body('code').optional().isUUID(4),
+    body('address').optional().isEthereumAddress(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const quest = await QuestDaily.findOne({ eventName: req.params.uuid });
+    if (!quest) throw new NotFoundError('Could not find a daily reward for this token');
+
+    const pool = await Pool.findById(quest.poolId);
+    if (!pool) throw new NotFoundError('Could not find a campaign pool for this reward.');
+
+    if (!req.body.code && !req.body.address) {
+        throw new BadRequestError('This request requires either a wallet code or address');
+    }
+
+    const identity = req.body.code
+        ? await getIdentityForCode(pool, req.body.code)
+        : await getIdentityForAddress(pool, req.body.address);
+
+    await Event.create({ name: quest.eventName, identityId: identity._id, poolId: pool._id });
+
+    res.status(201).end();
+};
+
+export { validation, controller };
diff --git a/apps/api/src/app/controllers/webhook/gateway/post.controller.ts b/apps/api/src/app/controllers/webhook/gateway/post.controller.ts
new file mode 100644
index 000000000..8ef66b167
--- /dev/null
+++ b/apps/api/src/app/controllers/webhook/gateway/post.controller.ts
@@ -0,0 +1,68 @@
+import crypto from 'crypto';
+import { Request, Response } from 'express';
+import { body } from 'express-validator';
+import { WEBHOOK_SIGNING_SECRET } from '@thxnetwork/api/config/secrets';
+import { Wallet } from '@thxnetwork/api/models';
+import { logger } from '@thxnetwork/api/util/logger';
+import { formatUnits } from 'ethers/lib/utils';
+import VoteEscrowService from '@thxnetwork/api/services/VoteEscrowService';
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+import BalancerService from '@thxnetwork/api/services/BalancerService';
+
+const validation = [body('payload').isString(), body('signature').isString()];
+
+// Helper method to verify payload signature
+function constructEvent(payload, signature, secret) {
+    const hmac = crypto.createHmac('sha256', secret);
+    hmac.update(payload);
+    const calculatedSignature = hmac.digest('base64');
+    if (signature !== calculatedSignature) throw new Error('Failed signature verification');
+    return JSON.parse(payload);
+}
+
+const controller = async (req: Request, res: Response) => {
+    let result = false;
+    try {
+        // Verifies and parses the payload using the WEBHOOK_SIGNING_SECRET which you can get in Developer -> Webhooks
+        const event = constructEvent(req.body.payload, req.body.signature, WEBHOOK_SIGNING_SECRET);
+
+        switch (event.type) {
+            case 'quest_entry.create': {
+                const { identities, metadata } = event;
+                if (!identities.length) throw new Error('No identities found in the event');
+
+                const account = await AccountProxy.getByIdentity(identities[0]);
+                if (!account) throw new Error('No account found for the identity');
+
+                const wallets = await Wallet.find({
+                    sub: account.sub,
+                    chainId: { $exists: true },
+                    address: { $exists: true },
+                });
+                if (!wallets.length) throw new Error('No wallets found for the account');
+
+                // Get largest lock and validate with provided metadata
+                const promises = wallets.map(async (wallet) => await VoteEscrowService.list(wallet));
+                const locks = await Promise.all(promises);
+                const [largestLock] = locks.sort((a, b) => b.amount - a.amount);
+                const lockAmount = formatUnits(largestLock.amount, 18);
+                const bptPrice = BalancerService.pricing['20USDC-80THX'];
+                const largestAmountInUSD = Number(lockAmount) * bptPrice;
+
+                result = largestAmountInUSD >= Number(metadata);
+
+                break;
+            }
+            default: {
+                console.log('Unhandled event type ' + event.type);
+            }
+        }
+
+        return res.json({ result });
+    } catch (error) {
+        logger.error(error.message);
+        return res.status(400).send('Webhook Error: ' + error.message);
+    }
+};
+
+export { validation, controller };
diff --git a/apps/api/src/app/controllers/webhook/milestones/claim/post.controller.ts b/apps/api/src/app/controllers/webhook/milestones/claim/post.controller.ts
new file mode 100644
index 000000000..2ca826363
--- /dev/null
+++ b/apps/api/src/app/controllers/webhook/milestones/claim/post.controller.ts
@@ -0,0 +1,49 @@
+import { Request, Response } from 'express';
+import { BadRequestError, NotFoundError } from '@thxnetwork/api/util/errors';
+import { body, param } from 'express-validator';
+import { toChecksumAddress } from 'web3-utils';
+import { Pool, PoolDocument, Identity, Event, QuestCustom } from '@thxnetwork/api/models';
+import IdentityService from '@thxnetwork/api/services/IdentityService';
+
+const validation = [
+    param('uuid').isUUID('4'),
+    body('code').optional().isUUID(4),
+    body('address')
+        .optional()
+        .isEthereumAddress()
+        .customSanitizer((address) => toChecksumAddress(address)),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const customQuest = await QuestCustom.findOne({ uuid: req.params.uuid });
+    if (!customQuest) throw new NotFoundError('Could not find a milestone reward for this token');
+
+    const pool = await Pool.findById(customQuest.poolId);
+    if (!pool) throw new NotFoundError('Could not find a campaign pool for this reward.');
+
+    if (!req.body.code && !req.body.address) {
+        throw new BadRequestError('This request requires either a wallet code or address');
+    }
+
+    const identity = req.body.code
+        ? await getIdentityForCode(pool, req.body.code)
+        : await getIdentityForAddress(pool, req.body.address);
+
+    await Event.create({ name: customQuest.eventName, identityId: identity._id, poolId: pool._id });
+
+    res.status(201).end();
+};
+
+export function getIdentityForCode(pool: PoolDocument, code: string) {
+    return Identity.findOne({ poolId: pool._id, uuid: code });
+}
+
+// @peterpolman (FK still depends on this)
+// This function should deprecate as soon as clients implement the wallet onboarding webhook
+// Defaulting into identity derivation for the provided address. This will require FK to present derived
+// identity uuids in their client in order to connect the identity to their account.
+export function getIdentityForAddress(pool: PoolDocument, address: string) {
+    return IdentityService.getIdentityForSalt(pool, address);
+}
+
+export { validation, controller };
diff --git a/apps/api/src/app/controllers/webhook/webhook.router.ts b/apps/api/src/app/controllers/webhook/webhook.router.ts
new file mode 100644
index 000000000..39fe2e0a8
--- /dev/null
+++ b/apps/api/src/app/controllers/webhook/webhook.router.ts
@@ -0,0 +1,16 @@
+import express from 'express';
+import { assertRequestInput } from '@thxnetwork/api/middlewares';
+import * as MilestoneReward from './milestones/claim/post.controller';
+import * as DailyReward from './daily/post.controller';
+import * as CreateController from './gateway/post.controller';
+
+const router: express.Router = express.Router();
+
+// Custom webhooks for Webhook Quest consumption
+router.post('/gateway', assertRequestInput(CreateController.validation), CreateController.controller);
+
+// Deprecate soon
+router.post('/milestone/:uuid/claim', assertRequestInput(MilestoneReward.validation), MilestoneReward.controller);
+router.post('/daily/:uuid', assertRequestInput(DailyReward.validation), DailyReward.controller);
+
+export default router;
diff --git a/apps/api/src/app/controllers/webhooks/delete.controller.ts b/apps/api/src/app/controllers/webhooks/delete.controller.ts
new file mode 100644
index 000000000..5d0095c36
--- /dev/null
+++ b/apps/api/src/app/controllers/webhooks/delete.controller.ts
@@ -0,0 +1,12 @@
+import { param } from 'express-validator';
+import { Request, Response } from 'express';
+import { Webhook } from '@thxnetwork/api/models/Webhook';
+
+const validation = [param('id').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    await Webhook.findByIdAndDelete(req.params.id);
+    res.status(204).end();
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/webhooks/list.controller.ts b/apps/api/src/app/controllers/webhooks/list.controller.ts
new file mode 100644
index 000000000..5eaa09fab
--- /dev/null
+++ b/apps/api/src/app/controllers/webhooks/list.controller.ts
@@ -0,0 +1,25 @@
+import { Request, Response } from 'express';
+import { Webhook, WebhookDocument } from '@thxnetwork/api/models/Webhook';
+import { WebhookRequest } from '@thxnetwork/api/models/WebhookRequest';
+
+const validation = [];
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['Webhooks']
+    const poolId = req.header('x-poolid');
+    const webhooks = await Webhook.find({ poolId });
+    const response = await Promise.all(
+        webhooks.map(async (webhook: WebhookDocument) => {
+            const webhookRequests = await WebhookRequest.find({ webhookId: String(webhook._id) })
+                .sort({ createdAt: -1 })
+                .limit(50);
+            return {
+                ...webhook.toJSON(),
+                webhookRequests,
+            };
+        }),
+    );
+    res.json(response);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/webhooks/patch.controller.ts b/apps/api/src/app/controllers/webhooks/patch.controller.ts
new file mode 100644
index 000000000..fc72b93ac
--- /dev/null
+++ b/apps/api/src/app/controllers/webhooks/patch.controller.ts
@@ -0,0 +1,13 @@
+import { body, param } from 'express-validator';
+import { Request, Response } from 'express';
+import { Webhook } from '@thxnetwork/api/models/Webhook';
+
+const validation = [param('id').isMongoId(), body('url').isURL({ require_tld: false })];
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['Webhooks']
+    const webhook = await Webhook.findByIdAndUpdate(req.params.id, { url: req.body.url }, { new: true });
+    res.json(webhook);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/webhooks/post.controller.ts b/apps/api/src/app/controllers/webhooks/post.controller.ts
new file mode 100644
index 000000000..7375b5210
--- /dev/null
+++ b/apps/api/src/app/controllers/webhooks/post.controller.ts
@@ -0,0 +1,17 @@
+import { body } from 'express-validator';
+import { Request, Response } from 'express';
+import { Webhook } from '@thxnetwork/api/models/Webhook';
+
+const validation = [body('url').isURL({ require_tld: false })];
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['Webhooks']
+    const webhook = await Webhook.create({
+        poolId: req.header('x-poolid'),
+        url: req.body.url,
+    });
+
+    res.status(201).json({ ...webhook.toJSON(), webhookRequests: [] });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/webhooks/webhooks.router.ts b/apps/api/src/app/controllers/webhooks/webhooks.router.ts
new file mode 100644
index 000000000..2398c8be5
--- /dev/null
+++ b/apps/api/src/app/controllers/webhooks/webhooks.router.ts
@@ -0,0 +1,39 @@
+import express, { Router } from 'express';
+import { assertPoolAccess, assertRequestInput, guard } from '@thxnetwork/api/middlewares';
+import * as ListWebhook from './list.controller';
+import * as PatchWebhook from './patch.controller';
+import * as CreateWebhook from './post.controller';
+import * as DeleteWebhook from './delete.controller';
+
+const router: express.Router = express.Router();
+
+router.get(
+    '/',
+    guard.check(['webhooks:read']),
+    assertPoolAccess,
+    assertRequestInput(ListWebhook.validation),
+    ListWebhook.controller,
+);
+router.patch(
+    '/:id',
+    guard.check(['webhooks:read']),
+    assertPoolAccess,
+    assertRequestInput(PatchWebhook.validation),
+    PatchWebhook.controller,
+);
+router.post(
+    '/',
+    guard.check(['webhooks:write', 'webhooks:read']),
+    assertPoolAccess,
+    assertRequestInput(CreateWebhook.validation),
+    CreateWebhook.controller,
+);
+router.delete(
+    '/:id',
+    guard.check(['webhooks:write', 'webhooks:read']),
+    assertPoolAccess,
+    assertRequestInput(DeleteWebhook.validation),
+    DeleteWebhook.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/widget/get.controller.ts b/apps/api/src/app/controllers/widget/get.controller.ts
new file mode 100644
index 000000000..0807ff26b
--- /dev/null
+++ b/apps/api/src/app/controllers/widget/get.controller.ts
@@ -0,0 +1,31 @@
+import { AUTH_URL } from '@thxnetwork/api/config/secrets';
+import { Brand, Pool, Widget } from '@thxnetwork/api/models';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+
+const validation = [param('id').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    const widget = await Widget.findOne({ poolId: req.params.id });
+    if (!widget) throw new NotFoundError('Widget not found');
+
+    const pool = await Pool.findById(req.params.id);
+    if (!pool) throw new NotFoundError('Pool not found');
+
+    const brand = await Brand.findOne({ poolId: req.params.id });
+
+    res.json({
+        title: pool.settings.title,
+        description: pool.settings.description,
+        logoUrl: brand ? brand.logoImgUrl : AUTH_URL + '/img/logo-padding.png',
+        backgroundUrl: brand ? brand.backgroundImgUrl : '',
+        theme: widget.theme,
+        domain: widget.domain,
+        chainId: pool.chainId,
+        poolId: pool._id,
+        slug: pool.settings.slug || pool._id,
+    });
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/widget/js/get.controller.ts b/apps/api/src/app/controllers/widget/js/get.controller.ts
new file mode 100644
index 000000000..8d932ae4b
--- /dev/null
+++ b/apps/api/src/app/controllers/widget/js/get.controller.ts
@@ -0,0 +1,566 @@
+import { API_URL, AUTH_URL, DASHBOARD_URL, NODE_ENV, WIDGET_URL } from '@thxnetwork/api/config/secrets';
+import BrandService from '@thxnetwork/api/services/BrandService';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import { QuestInvite } from '@thxnetwork/api/models/QuestInvite';
+import { Widget } from '@thxnetwork/api/models/Widget';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+import { Request, Response } from 'express';
+import { query, param } from 'express-validator';
+import { minify } from 'terser';
+
+const validation = [
+    param('id').isMongoId(),
+    query('identity').optional().isUUID(),
+    query('containerSelector').optional().isString(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    // #swagger.tags = ['Widget']
+    const referralRewards = await QuestInvite.find({
+        poolId: req.params.id,
+    });
+    const refs = JSON.stringify(
+        referralRewards
+            .filter((r) => r.successUrl)
+            .map((r) => {
+                return {
+                    uuid: r.uuid,
+                    successUrl: r.successUrl,
+                };
+            }),
+    );
+
+    const pool = await PoolService.getById(req.params.id);
+    if (!pool) throw new NotFoundError('Pool not found.');
+
+    const expired = pool.settings.endDate ? pool.settings.endDate.getTime() <= Date.now() : false;
+    const brand = await BrandService.get(pool._id);
+    const widget = await Widget.findOne({ poolId: req.params.id });
+    const referrerHeader = req.header('Referrer');
+    const widgetOrigin = widget.domain ? new URL(widget.domain).origin : '';
+    const origin = referrerHeader ? new URL(referrerHeader).origin : '';
+
+    // Set active to true if there is a request made from the configured domain
+    if (widgetOrigin === origin && !widget.active) {
+        await widget.updateOne({ active: true });
+    }
+
+    const data = `
+if (typeof window.THXWidget !== 'undefined') {
+    window.THXWidget.onLoad();
+} else {
+    class THXWidget {
+        MD_BREAKPOINT = 990;
+        public isAuthenticated = false;
+    
+        constructor(settings) {
+            this.settings = settings;
+            this.theme = JSON.parse(settings.theme);
+            this.init();
+        }
+
+        get defaultStyles() {
+            const isCustomContainer = !!this.settings.containerSelector;
+            return {
+                sm: {
+                    width: '100%',
+                    height: '100%',
+                    maxHeight: 'none',
+                    top: 0,
+                    left: 0,
+                    right: 0,
+                    bottom: 0,
+                    border: 0,
+                    borderRadius: 0,
+                },
+                md: {
+                    top: 'auto',
+                    bottom: isCustomContainer ? 'auto' : '100px',
+                    maxHeight: isCustomContainer ? 'none' : '703px',
+                    width: isCustomContainer ? '100%' : '400px',
+                    border: 0,
+                    borderRadius: isCustomContainer ? '0px' : '10px',
+                    height: isCustomContainer ? '100%' : 'calc(100% - 115px)',
+                },
+            }
+        }
+
+        init() {
+            const waitForBody = () => new Promise((resolve) => {
+                const tick = () => {
+                    if (document.getElementsByTagName('body').length) {
+                        clearInterval(timer)
+                        resolve()
+                    }
+                }
+                const timer = setInterval(tick, 1000);
+            });
+            waitForBody().then(this.onLoad.bind(this));
+        }
+
+        public setIdentity(identity) {
+            this.iframe.contentWindow.postMessage({ message: 'thx.auth.identity', identity }, this.settings.widgetUrl);
+        }
+
+        public signin() {
+            this.iframe.contentWindow.postMessage({ message: 'thx.auth.signin' },  this.settings.widgetUrl);
+        }
+
+        public signout() {
+            this.iframe.contentWindow.postMessage({ message: 'thx.auth.signout' },  this.settings.widgetUrl);
+        }
+
+        public open(widgetPath) {
+            if (!widgetPath) return;
+
+            const { widgetUrl, poolId, chainId, theme } = this.settings;
+            const path = '/c/' + poolId + widgetPath;
+            const isMobile = window.matchMedia('(pointer:coarse)').matches;
+
+            if (isMobile) {
+                // Window _blank will be blocked by mobile OS so we redirect the current window
+                window.location.href = this.settings.widgetUrl + path   
+            } else {
+                this.iframe.contentWindow.postMessage({ message: 'thx.iframe.navigate', path }, widgetUrl);
+                this.show(true);
+            }
+        }
+    
+        public connect(uuid) {
+            this.open('/w/' + uuid);
+        }
+
+        public quests = {
+            list: () => {
+                this.iframe.contentWindow.postMessage({ message: 'thx.quests.list' }, this.settings.widgetUrl);
+            }
+        }
+
+        onLoad() {
+            this.referrals = JSON.parse(this.settings.refs).filter((r) => r.successUrl);
+            this.iframe = this.createIframe();
+
+            if (this.settings.isPublished) {
+                this.notifications = this.createNotifications(0);
+                this.message = this.createMessage();
+                this.launcher = this.settings.cssSelector ? this.selectLauncher() : this.createLauncher();
+                this.container = this.createContainer(this.iframe, this.launcher, this.message);
+            }
+
+            this.parseURL();
+            
+            window.matchMedia('(max-width: 990px)').addListener(this.onMatchMedia.bind(this));
+            window.onmessage = this.onMessage.bind(this);
+        }
+
+        parseURL() {
+            const url = new URL(window.location.href)
+            this.ref = url.searchParams.get('ref');
+            if (!this.ref) return;
+
+            this.successUrls = this.referrals.map((r) => r.successUrl);
+            if (!this.successUrls.length) return;
+        }
+    
+        get isSmallMedia() {
+            const getWidth = () => window.innerWidth;
+            return getWidth() < this.MD_BREAKPOINT;
+        }
+
+        createURL() {
+            const parentUrl = new URL(window.location.href)
+            const path = parentUrl.searchParams.get('thx_widget_path');
+            const { widgetUrl, poolId, chainId, theme, expired, logoUrl, backgroundUrl, title } = this.settings;
+            const url = new URL(widgetUrl);
+
+            url.pathname = this.widgetPath = '/c/' + poolId + (path || '/quests');
+            url.searchParams.append('origin', window.location.origin);
+            
+            return url;
+        }
+
+        createIframe() {
+            const { widgetUrl, poolId, chainId, theme, align, expired, containerSelector } = this.settings;
+            const iframe = document.createElement('iframe');
+            const styles = this.isSmallMedia ? this.defaultStyles['sm'] : this.defaultStyles['md'];
+            const url = this.createURL();
+
+            iframe.id = 'thx-iframe';
+            iframe.src = url;
+            iframe.setAttribute('data-hj-allow-iframe', true);
+
+            if (containerSelector) {
+                Object.assign(iframe.style, this.defaultStyles[this.isSmallMedia ? 'sm' : 'md']);
+                return iframe;
+            }
+
+            let top, bottom, left, right, marginLeft, marginTop, transformOrigin;
+        
+            if (!this.isSmallMedia) {
+                switch(align) {
+                    case 'left' :
+                        top = 'auto';
+                        bottom = !this.settings.cssSelector ? '100px' : '15px';
+                        left = '15px';
+                        right = 'auto';
+                        transformOrigin = 'bottom left';
+                        break;
+                    case 'right' :
+                        top = 'auto';
+                        bottom = !this.settings.cssSelector ? '100px' : '15px';
+                        left = 'auto';
+                        right = '15px';
+                        transformOrigin = 'bottom right';
+                        break;
+                    case 'center' :
+                        top = '50%';
+                        left = '50%';
+                        right = 'auto';
+                        bottom = 'auto';
+                        marginLeft = '-200px';
+                        marginTop = '-340px';
+                        transformOrigin = 'center center';
+                        break;
+                }
+            } else {
+                top = '0';
+                bottom = '0';
+                left = '0';
+                right = '0';
+            }
+
+            Object.assign(iframe.style, {
+                ...styles,
+                zIndex: 99999999,
+                display: 'flex',
+                top,
+                bottom, 
+                left, 
+                right,
+                marginLeft, 
+                marginTop,
+                position: 'fixed',
+                border: '0',
+                opacity: '0',
+                boxShadow: 'rgba(50, 50, 93, 0.25) 0px 50px 100px -20px, rgba(0, 0, 0, 0.3) 0px 30px 60px -30px',
+                transform: 'scale(0)',
+                transformOrigin,
+                transition: '.2s opacity ease, .1s transform ease',
+            });
+    
+            return iframe;
+        }
+    
+        createNotifications(counter) {
+            const notifications = document.createElement('div');
+            notifications.id = 'thx-notifications';
+            Object.assign(notifications.style, {
+                display: 'none',
+                fontFamily: 'Arial',
+                fontSize: '13px',
+                justifyContent: 'center',
+                alignItems: 'center',
+                width: '20px',
+                height: '20px',
+                color: '#FFFFFF',
+                position: 'absolute',
+                backgroundColor: '#CA0000',
+                borderRadius: '50%',
+                userSelect: 'none',
+            });
+            notifications.innerHTML = counter;
+            return notifications;
+        }
+
+        createMessage() {
+            const { message, logoUrl, align } = this.settings;
+            const messageBox = document.createElement('div');
+            const closeBox = document.createElement('button');
+
+            messageBox.id = 'thx-message';
+            
+            closeBox.innerHTML = '&times;';             
+            
+            Object.assign(closeBox.style, {
+                display: 'flex',
+                fontFamily: 'Arial',
+                fontSize: '16px',
+                justifyContent: 'center',
+                alignItems: 'center',
+                width: '20px',
+                height: '20px',
+                border: '0',
+                color: '#000000',
+                position: 'absolute',
+                backgroundColor: 'transparent',
+                top: '0',
+                right: '0',
+                opacity: '0.5',
+                transform: 'scale(.9)',
+                transition: '.2s opacity ease, .1s transform ease',
+            });
+            closeBox.addEventListener('mouseenter', () => {
+                closeBox.style.opacity = '1';
+                closeBox.style.transform = 'scale(1)';
+            });
+            closeBox.addEventListener('mouseleave', () => {
+                closeBox.style.opacity = '.5';
+                closeBox.style.transform = 'scale(.9)';
+            });
+            closeBox.addEventListener('click', () => {
+                this.message.remove();
+            });
+            
+            Object.assign(messageBox.style, {
+                zIndex: 9999999,
+                display: message ? 'flex' : 'none',
+                lineHeight: 1.5,
+                fontFamily: 'inherit, sans-serif',
+                fontSize: '12px',
+                fontWeight: 'normal',
+                justifyContent: 'center',
+                alignItems: 'center',
+                width: '200px',
+                color: '#000000',
+                position: 'fixed',
+                backgroundColor: '#FFFFFF',
+                borderRadius: '5px',
+                userSelect: 'none',
+                padding: '10px 10px 10px',
+                bottom: '90px',
+                right: align === 'right' ? '15px' : 'auto',
+                left: align === 'left' ? '15px' : 'auto',
+                boxShadow: 'rgb(50 50 93 / 25%) 0px 50px 100px -20px, rgb(0 0 0 / 30%) 0px 30px 60px -30px',
+                opacity: 0,
+                transform: 'scale(0)',
+                transition: '.2s opacity ease, .1s transform ease',
+            });
+
+            const wrapper = document.createElement('span');
+            wrapper.style.zIndex = 0;
+            wrapper.innerHTML = message;
+            messageBox.appendChild(wrapper);
+            messageBox.appendChild(closeBox);
+                
+            return messageBox;
+        }
+    
+        selectLauncher() {
+            const launcher = document.querySelector(this.settings.cssSelector);           
+            if (!launcher) {
+                console.error("THX widget can't find the launcher for selector: " + this.settings.cssSelector);
+                return;
+            }
+
+            launcher.addEventListener('click', this.onClickLauncher.bind(this));
+
+            setTimeout(() => {
+                const url = new URL(window.location.href)
+                const widgetPath = url.searchParams.get('thx_widget_path');  
+                
+                this.show(!!widgetPath);
+            }, 350);
+            
+            return launcher;
+        }
+
+        createLauncher() {
+            const svgGift = this.settings.iconImg 
+                ? '<img id="thx-svg-icon" style="display:block; margin: auto;" width="40" height="40" src="' + this.settings.iconImg + '" alt="Widget launcher icon" />'
+                : '<svg id="thx-svg-icon" style="display:block; margin: auto; fill: '+this.theme.elements.launcherIcon.color+'; width: 20px; height: 20px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M32 448c0 17.7 14.3 32 32 32h160V320H32v128zm256 32h160c17.7 0 32-14.3 32-32V320H288v160zm192-320h-42.1c6.2-12.1 10.1-25.5 10.1-40 0-48.5-39.5-88-88-88-41.6 0-68.5 21.3-103 68.3-34.5-47-61.4-68.3-103-68.3-48.5 0-88 39.5-88 88 0 14.5 3.8 27.9 10.1 40H32c-17.7 0-32 14.3-32 32v80c0 8.8 7.2 16 16 16h480c8.8 0 16-7.2 16-16v-80c0-17.7-14.3-32-32-32zm-326.1 0c-22.1 0-40-17.9-40-40s17.9-40 40-40c19.9 0 34.6 3.3 86.1 80h-86.1zm206.1 0h-86.1c51.4-76.5 65.7-80 86.1-80 22.1 0 40 17.9 40 40s-17.9 40-40 40z"/></svg>';
+            const launcher = document.createElement('div');
+            launcher.id = 'thx-launcher';
+
+            Object.assign(launcher.style, {
+                zIndex: 9999999,
+                display: 'flex',
+                width: '60px',
+                height: '60px',
+                backgroundColor: this.theme.elements.launcherBg.color,
+                borderRadius: '50%',
+                cursor: 'pointer',
+                position: 'fixed',
+                bottom: '15px',
+                right: !this.settings.cssSelector ? this.settings.align === 'right' ? '15px' : 'auto' : 'auto',
+                left: !this.settings.cssSelector ? this.settings.align === 'left' ? '15px' : 'auto' : 'auto',
+                opacity: 0,
+                transition: '.2s opacity ease, .1s transform ease',
+            });
+
+            launcher.innerHTML = svgGift;
+            launcher.addEventListener('click', this.onClickLauncher.bind(this));
+            launcher.appendChild(this.notifications);
+            
+            setTimeout(() => {
+                launcher.style.opacity = 1;
+                launcher.style.transform = 'scale(1)';
+
+                this.message.style.opacity = 1;
+                this.message.style.transform = 'scale(1)';
+
+                const url = new URL(window.location.href)
+                const widgetPath = url.searchParams.get('thx_widget_path');
+                this.show(!!widgetPath)
+            }, 350);
+    
+            return launcher;
+        }
+    
+        createContainer(iframe, launcher, message) {
+            const { containerSelector, cssSelector } = this.settings;
+            let container;
+            if (containerSelector) {
+                container = document.querySelector(containerSelector)
+                if (!container) throw new Error("Could not find an HTML element for selector: '" + containerSelector + "'.")
+                container.appendChild(iframe);   
+            } else {
+                container = document.createElement('div');
+                container.id = 'thx-container';
+                container.appendChild(iframe);   
+                
+                if (!cssSelector) {
+                    container.appendChild(launcher);
+                    container.appendChild(message);
+                }  
+                
+                document.body.appendChild(container);
+            }
+            return container;
+        }
+
+        storeRef(ref) {
+            if (!ref) return;
+            
+            window.localStorage.setItem('thx:widget:' + this.settings.poolId + ':ref', ref);
+            this.iframe.contentWindow.postMessage({ message: 'thx.config.ref', ref }, this.settings.widgetUrl);
+            this.timer = window.setInterval(this.onURLDetectionCallback.bind(this), 500);
+        }
+        
+        onMessage(event) {
+            if (event.origin !== this.settings.widgetUrl) return;
+            const { message, amount, isAuthenticated, url } = event.data;
+            switch (message) {
+                case 'thx.auth.signin': {
+                    this.onSignin(url);
+                    break
+                }
+                case 'thx.widget.ready': {
+                    this.onWidgetReady();
+                    break
+                }
+                case 'thx.reward.amount': {
+                    this.notifications.innerText = amount;
+                    this.notifications.style.display = amount ? 'flex' : 'none';
+                    break;
+                }
+                case 'thx.widget.toggle': {
+                    this.show(!Number(this.iframe.style.opacity));
+                    break;
+                }
+                case 'thx.auth.status': {
+                    this.isAuthenticated = isAuthenticated;
+                    break;
+                }
+            }
+        }
+
+        onSignin(url) {
+            window.open(url, '_blank');
+        }
+
+        onClickLauncher() {
+            const isMobile = window.matchMedia('(pointer:coarse)').matches;
+            if (window.ethereum && isMobile) {
+                const deeplink = 'https://metamask.app.link/dapp/';
+                const ua = navigator.userAgent.toLowerCase();
+                const isAndroid = ua.indexOf("android") > -1;
+                const url = isAndroid ? deeplink + this.createURL() : this.createURL();   
+                window.open(url, '_blank');
+            } else if (!window.ethereum && isMobile) { 
+                window.open(this.createURL(), '_blank');
+            } else {
+                this.show(!Number(this.iframe.style.opacity));
+            }
+            this.message.remove();
+        }
+
+        onWidgetReady() {      
+            const parentUrl = new URL(window.location.href)
+            const widgetPath = parentUrl.searchParams.get('thx_widget_path');
+
+            this.open(widgetPath);
+            this.storeRef(this.ref);
+
+            if (this.settings.identity) {
+                this.setIdentity(this.settings.identity)
+            }
+        }
+
+        show(isShown) {
+            const { containerSelector } = this.settings;
+            const shouldShow = isShown || !!containerSelector;
+
+            this.iframe.style.opacity = shouldShow ? '1' : '0';
+            this.iframe.style.transform = shouldShow ? 'scale(1)' : 'scale(0)';
+            
+            if (this.iframe.contentWindow) {
+                this.iframe.contentWindow.postMessage({ message: 'thx.iframe.show', shouldShow }, this.settings.widgetUrl);
+            }
+        
+            if (shouldShow) this.message.remove();
+        }
+
+        onURLDetectionCallback() {
+            for (const ref of this.referrals) {
+                if (!(this.successUrls.filter((url) => url.includes(window.location.origin + window.location.pathname))).length) continue;
+                this.iframe.contentWindow.postMessage({ message: 'thx.referral.claim.create', uuid: ref.uuid, }, this.settings.widgetUrl);
+
+                const index = this.referrals.findIndex((r) => ref.uuid);
+                this.referrals.splice(index, 1);
+            }
+            
+            if (!this.referrals.length) {
+                window.clearInterval(this.timer);
+            }
+        }
+    
+        onMatchMedia(x) {
+            if (x.matches) {
+                const iframe = document.getElementById('thx-iframe');
+                Object.assign(iframe.style, this.defaultStyles['sm']);
+            } else {
+                const iframe = document.getElementById('thx-iframe');
+                Object.assign(iframe.style, this.defaultStyles['md']);
+            }
+        }
+    }
+    window.THXWidget = new THXWidget({
+        apiUrl: '${API_URL}',
+        isPublished: window.location.origin.includes("${DASHBOARD_URL}") || ${widget.isPublished},
+        widgetUrl: '${WIDGET_URL}',
+        poolId: '${req.params.id}',
+        chainId: '${pool.chainId}',
+        title: '${pool.settings.title}',
+        cssSelector: '${widget.cssSelector || ''}',
+        logoUrl: '${brand && brand.logoImgUrl ? brand.logoImgUrl : AUTH_URL + '/img/logo-padding.png'}',
+        backgroundUrl: '${brand && brand.backgroundImgUrl ? brand.backgroundImgUrl : ''}',
+        iconImg: '${widget.iconImg || ''}',
+        message: '${widget.message || ''}',
+        align: '${widget.align || 'right'}',
+        theme: '${widget.theme}',
+        refs: ${JSON.stringify(refs)},
+        expired: '${expired}',
+        identity: '${req.query.identity || ''}',
+        containerSelector: '${req.query.containerSelector || ''}'
+    });
+}
+`;
+    const result = await minify(data, {
+        mangle: { toplevel: false },
+        sourceMap: NODE_ENV !== 'production',
+    });
+
+    res.set({ 'Content-Type': 'application/javascript' }).send(result.code);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/widget/widget.router.ts b/apps/api/src/app/controllers/widget/widget.router.ts
new file mode 100644
index 000000000..bfaf56d29
--- /dev/null
+++ b/apps/api/src/app/controllers/widget/widget.router.ts
@@ -0,0 +1,29 @@
+import express, { Request, Response, NextFunction } from 'express';
+import { assertRequestInput } from '@thxnetwork/api/middlewares';
+import * as ReadWidget from './get.controller';
+import * as ReadWidgetScript from './js/get.controller';
+import { Pool } from '@thxnetwork/api/models';
+
+const router: express.Router = express.Router();
+
+router.get('/:id.:ext', assertRequestInput(ReadWidgetScript.validation), ReadWidgetScript.controller);
+router.get(
+    '/:id',
+    async (req: Request, res: Response, next: NextFunction) => {
+        const isMongoId = (str: string) => {
+            const objectIdPattern = /^[0-9a-fA-F]{24}$/;
+            return objectIdPattern.test(str);
+        };
+
+        if (!isMongoId(req.params.id)) {
+            const pool = await Pool.findOne({ 'settings.slug': req.params.id });
+            req.params.id = String(pool._id);
+        }
+
+        next();
+    },
+    assertRequestInput(ReadWidget.validation),
+    ReadWidget.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/widgets/get.controller.ts b/apps/api/src/app/controllers/widgets/get.controller.ts
new file mode 100644
index 000000000..4526c8f58
--- /dev/null
+++ b/apps/api/src/app/controllers/widgets/get.controller.ts
@@ -0,0 +1,12 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import { Widget } from '@thxnetwork/api/models';
+
+const validation = [param('uuid').exists()];
+
+const controller = async (req: Request, res: Response) => {
+    const widget = await Widget.findOne({ uuid: req.params.uuid });
+    res.json(widget);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/widgets/list.controller.ts b/apps/api/src/app/controllers/widgets/list.controller.ts
new file mode 100644
index 000000000..0ff3a66b7
--- /dev/null
+++ b/apps/api/src/app/controllers/widgets/list.controller.ts
@@ -0,0 +1,9 @@
+import { Request, Response } from 'express';
+import { Widget } from '@thxnetwork/api/models';
+
+const controller = async (req: Request, res: Response) => {
+    const widgets = await Widget.find({ poolId: req.header('X-PoolId') });
+    res.json(widgets);
+};
+
+export { controller };
diff --git a/apps/api/src/app/controllers/widgets/patch.controller.ts b/apps/api/src/app/controllers/widgets/patch.controller.ts
new file mode 100644
index 000000000..b994d386d
--- /dev/null
+++ b/apps/api/src/app/controllers/widgets/patch.controller.ts
@@ -0,0 +1,33 @@
+import { Request, Response } from 'express';
+import { body } from 'express-validator';
+import { Widget } from '@thxnetwork/api/models';
+
+const validation = [
+    body('isPublished').optional().isBoolean(),
+    body('iconImg').optional().isString(),
+    body('align').optional().isString(),
+    body('theme').optional().isString(),
+    body('cssSelector').optional().isString(),
+    body('domain').optional().isURL({ require_tld: false }),
+    body('message').optional().isString().isLength({ max: 280 }).trim().escape(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    const widget = await Widget.findOneAndUpdate(
+        { uuid: req.params.uuid },
+        {
+            isPublished: req.body.isPublished,
+            iconImg: req.body.iconImg,
+            color: req.body.color,
+            align: req.body.align,
+            domain: req.body.domain,
+            message: req.body.message,
+            theme: req.body.theme,
+            cssSelector: req.body.cssSelector,
+        },
+        { new: true },
+    );
+    return res.json(widget);
+};
+
+export { controller, validation };
diff --git a/apps/api/src/app/controllers/widgets/widgets.router.ts b/apps/api/src/app/controllers/widgets/widgets.router.ts
new file mode 100644
index 000000000..be680b0b7
--- /dev/null
+++ b/apps/api/src/app/controllers/widgets/widgets.router.ts
@@ -0,0 +1,25 @@
+import express, { Router } from 'express';
+import { assertPoolAccess, assertRequestInput, guard } from '@thxnetwork/api/middlewares';
+import * as ReadWidget from './get.controller';
+import * as UpdateWidget from './patch.controller';
+import * as ListWidgets from './list.controller';
+
+const router: express.Router = express.Router();
+
+router.get('/', guard.check(['widgets:read']), assertPoolAccess, ListWidgets.controller);
+router.get(
+    '/:uuid',
+    guard.check(['widgets:read']),
+    assertRequestInput(ReadWidget.validation),
+    assertPoolAccess,
+    ReadWidget.controller,
+);
+router.patch(
+    '/:uuid',
+    guard.check(['widgets:write', 'widgets:read']),
+    assertRequestInput(UpdateWidget.validation),
+    assertPoolAccess,
+    UpdateWidget.controller,
+);
+
+export default router;
diff --git a/apps/api/src/app/controllers/widgets/widgets.test.ts b/apps/api/src/app/controllers/widgets/widgets.test.ts
new file mode 100644
index 000000000..7f86ace8b
--- /dev/null
+++ b/apps/api/src/app/controllers/widgets/widgets.test.ts
@@ -0,0 +1,73 @@
+import request, { Response } from 'supertest';
+import app from '@thxnetwork/api/';
+import { ChainId } from '@thxnetwork/common/enums';
+import { dashboardAccessToken } from '@thxnetwork/api/util/jest/constants';
+import { afterAllCallback, beforeAllCallback } from '@thxnetwork/api/util/jest/config';
+import { WidgetDocument } from '@thxnetwork/api/models/Widget';
+
+const user = request.agent(app);
+
+describe('Widgets', () => {
+    let poolId: string, widget: WidgetDocument;
+    const newTheme =
+            '{"elements":{"btnBg":{"label":"Button","color":"#FF0000"},"btnText":{"label":"Button Text","color":"#000000"},"text":{"label":"Text","color":"#ffffff"},"bodyBg":{"label":"Background","color":"#000000"},"cardBg":{"label":"Card","color":"#3b3b3b"},"navbarBg":{"label":"Navigation","color":"#3b3b3b"},"launcherBg":{"label":"Launcher","color":"#ffffff"},"launcherIcon":{"label":"Launcher Icon","color":"#000000"}},"colors":{"success":{"label":"Success","color":"#28a745"},"warning":{"label":"Warning","color":"#ffe500"},"danger":{"label":"Danger","color":"#dc3545"},"info":{"label":"Info","color":"#17a2b8"}}}',
+        align = 'left',
+        message = 'New message',
+        iconImg = 'https://image.icon';
+
+    beforeAll(beforeAllCallback);
+    afterAll(afterAllCallback);
+
+    it('POST /pools', (done) => {
+        user.post('/v1/pools')
+            .set({ Authorization: dashboardAccessToken })
+            .send({ chainId: ChainId.Hardhat })
+            .expect(({ body }: Response) => {
+                poolId = body._id;
+            })
+            .expect(201, done);
+    });
+
+    it('GET /widgets', (done) => {
+        user.get('/v1/widgets')
+            .set({ 'X-PoolId': poolId, 'Authorization': dashboardAccessToken })
+            .expect(({ body }: Response) => {
+                expect(body[0].uuid).toBeDefined();
+                expect(body[0].theme).toBeDefined();
+                expect(body[0].message).toEqual('Hi there!👋 Click me to complete quests and earn rewards...');
+                widget = body[0];
+            })
+            .expect(200, done);
+    });
+
+    it('PATCH /widgets/:uuid', (done) => {
+        user.patch('/v1/widgets/' + widget.uuid)
+            .set({ 'X-PoolId': poolId, 'Authorization': dashboardAccessToken })
+            .send({
+                iconImg,
+                align,
+                message,
+                theme: newTheme,
+            })
+            .expect(({ body }: Response) => {
+                expect(body.iconImg).toBe(iconImg);
+                expect(body.uuid).toBeDefined();
+                expect(body.theme).toEqual(newTheme);
+                expect(body.message).toEqual(message);
+                expect(body.align).toEqual(align);
+            })
+            .expect(200, done);
+    });
+
+    it('GET /widgets/:uuid', (done) => {
+        user.get('/v1/widgets/' + widget.uuid)
+            .set({ 'X-PoolId': poolId, 'Authorization': dashboardAccessToken })
+            .expect(({ body }: Response) => {
+                expect(body.iconImg).toBe(iconImg);
+                expect(body.uuid).toBeDefined();
+                expect(body.theme).toEqual(newTheme);
+                expect(body.message).toEqual(message);
+            })
+            .expect(200, done);
+    });
+});
diff --git a/apps/api/src/app/events/ClientReady.ts b/apps/api/src/app/events/ClientReady.ts
new file mode 100644
index 000000000..64dc61a40
--- /dev/null
+++ b/apps/api/src/app/events/ClientReady.ts
@@ -0,0 +1,11 @@
+import { commands } from './commands/thx';
+import { Client } from 'discord.js';
+import { commandRegister } from '@thxnetwork/api/util/discord';
+import { logger } from '@thxnetwork/api/util/logger';
+
+const onClientReady = async (client: Client<true>) => {
+    logger.info(`Ready! Logged in as ${client.user.tag}`);
+    await commandRegister(commands);
+};
+
+export default onClientReady;
diff --git a/apps/api/src/app/events/GuildCreate.ts b/apps/api/src/app/events/GuildCreate.ts
new file mode 100644
index 000000000..ea8a558c1
--- /dev/null
+++ b/apps/api/src/app/events/GuildCreate.ts
@@ -0,0 +1,17 @@
+import { Guild } from 'discord.js';
+import { logger } from '@thxnetwork/api/util/logger';
+import { handleError } from './commands/error';
+
+const onGuildCreate = async (guild: Guild) => {
+    logger.info(`Added to guild: ${guild.name}`);
+    try {
+        const member = await guild.members.fetch(guild.ownerId);
+        await member.send({
+            content: 'THX for the invite!🙏 Make sure to connect your campaign in THX Dashboard.',
+        });
+    } catch (error) {
+        handleError(error);
+    }
+};
+
+export default onGuildCreate;
diff --git a/apps/api/src/app/events/GuildDelete.ts b/apps/api/src/app/events/GuildDelete.ts
new file mode 100644
index 000000000..b69ea94b8
--- /dev/null
+++ b/apps/api/src/app/events/GuildDelete.ts
@@ -0,0 +1,15 @@
+import { Guild } from 'discord.js';
+import { logger } from '@thxnetwork/api/util/logger';
+import { handleError } from './commands/error';
+import { DiscordGuild } from '@thxnetwork/api/models';
+
+const onGuildDelete = async (guild: Guild) => {
+    try {
+        logger.info(`Removed campaign references for guild: ${guild.name}`);
+        await DiscordGuild.deleteMany({ guildId: guild.id });
+    } catch (error) {
+        handleError(error);
+    }
+};
+
+export default onGuildDelete;
diff --git a/apps/api/src/app/events/InteractionCreated.ts b/apps/api/src/app/events/InteractionCreated.ts
new file mode 100644
index 000000000..3985d902b
--- /dev/null
+++ b/apps/api/src/app/events/InteractionCreated.ts
@@ -0,0 +1,75 @@
+import {
+    ChatInputCommandInteraction,
+    StringSelectMenuInteraction,
+    ButtonInteraction,
+    AutocompleteInteraction,
+} from 'discord.js';
+import { handleError } from './commands/error';
+import { onSelectQuestComplete, onClickQuestComplete, onClickRewardList, onClickQuestList } from './handlers/index';
+import { logger } from '../util/logger';
+import { DiscordGuild } from '@thxnetwork/api/models';
+import { Pool, PoolDocument } from '@thxnetwork/api/models';
+import router from './commands/thx';
+
+export enum DiscordStringSelectMenuVariant {
+    QuestComplete = 'thx.campaign.quest.entry.create',
+    RewardBuy = 'thx.campaign.reward.payment.create',
+}
+
+export enum DiscordButtonVariant {
+    RewardBuy = 'thx.campaign.reward.payment.create',
+    QuestComplete = 'thx.campaign.quest.entry.create',
+    QuestList = 'thx.campaign.quest.list',
+    RewardList = 'thx.campaign.reward.list',
+}
+
+const stringSelectMenuMap = {
+    [DiscordStringSelectMenuVariant.QuestComplete]: onSelectQuestComplete,
+};
+
+export const onAutoComplete = async (interaction: AutocompleteInteraction) => {
+    if (!interaction.isAutocomplete()) return;
+
+    const discordGuilds = await DiscordGuild.find({ guildId: interaction.guildId });
+    const focusedValue = interaction.options.getFocused();
+    const campaigns = await Promise.all(discordGuilds.map(({ poolId }) => Pool.findById(poolId)));
+    const choices = campaigns.filter((c) => !!c).map((c: PoolDocument) => `${c.settings.title}`);
+    const filtered = choices.filter((choice) => choice.startsWith(focusedValue));
+
+    await interaction.respond(filtered.map((choice) => ({ name: choice, value: choice })));
+};
+
+const onInteractionCreated = async (
+    interaction: ButtonInteraction | ChatInputCommandInteraction | StringSelectMenuInteraction,
+) => {
+    try {
+        if (interaction.isButton()) {
+            logger.info(`#${interaction.user.id} clicked button #${interaction.customId}`);
+            if (interaction.customId.startsWith(DiscordButtonVariant.QuestComplete)) {
+                await onClickQuestComplete(interaction);
+            }
+            if (interaction.customId.startsWith(DiscordButtonVariant.QuestList)) {
+                await onClickQuestList(interaction);
+            }
+            if (interaction.customId.startsWith(DiscordButtonVariant.RewardList)) {
+                await onClickRewardList(interaction);
+            }
+        }
+
+        if (interaction.isStringSelectMenu()) {
+            logger.info(`#${interaction.user.id} picked ${interaction.values[0]} for ${interaction.customId}`);
+            if (!stringSelectMenuMap[interaction.customId])
+                throw new Error('Support for this action is not yet implemented!');
+            await stringSelectMenuMap[interaction.customId](interaction);
+        }
+
+        if (interaction.isCommand()) {
+            logger.info(`#${interaction.user.id} ran /${interaction.commandName}`);
+            router.executor(interaction);
+        }
+    } catch (error) {
+        handleError(error, interaction);
+    }
+};
+
+export default onInteractionCreated;
diff --git a/apps/api/src/app/events/MessageCreate.ts b/apps/api/src/app/events/MessageCreate.ts
new file mode 100644
index 000000000..ae2a63fb3
--- /dev/null
+++ b/apps/api/src/app/events/MessageCreate.ts
@@ -0,0 +1,59 @@
+import { Message } from 'discord.js';
+import { logger } from '../util/logger';
+import { DiscordMessage, DiscordGuild, QuestSocial, QuestSocialDocument } from '@thxnetwork/api/models';
+import { QuestSocialRequirement } from '@thxnetwork/common/enums';
+import AccountProxy from '../proxies/AccountProxy';
+
+const onMessageCreate = async (message: Message) => {
+    try {
+        // Only record messages for connected accounts
+        const connectedAccount = await AccountProxy.getByDiscordId(message.author.id);
+        if (!connectedAccount) return;
+
+        logger.info(`#${message.author.id} created message ${message.id} in guild ${message.guild.id}`);
+
+        const start = new Date();
+        start.setUTCHours(0, 0, 0, 0);
+
+        const end = new Date(start);
+        end.setUTCHours(23, 59, 59, 999);
+
+        const guild = await DiscordGuild.findOne({ guildId: message.guild.id });
+        const quests = await QuestSocial.find({
+            poolId: guild.poolId,
+            interaction: QuestSocialRequirement.DiscordMessage,
+        });
+        if (!quests.length) return;
+
+        // Return early if channel is not eligble for message tracking
+        const allowedChannels = quests.reduce((list: string[], q: QuestSocialDocument) => {
+            const { channels } = JSON.parse(q.contentMetadata);
+            return [...list, ...channels];
+        }, []);
+        if (!allowedChannels.includes(message.channelId)) return;
+
+        // Count the total amount of messages for today
+        const dailyMessageCount = await DiscordMessage.countDocuments({
+            guildId: message.guild.id,
+            memberId: message.author.id,
+            createdAt: { $gte: start, $lt: end },
+        });
+
+        // Get the highest limit for all available discord message quests in this campaign
+        const dailyMessageLimit = quests.reduce((highestLimit: number, quest: QuestSocialDocument) => {
+            const { limit } = JSON.parse(quest.contentMetadata);
+            return limit > highestLimit ? limit : highestLimit;
+        }, 0);
+
+        // Only track messages if daily limit has not been surpassed
+        if (dailyMessageCount > dailyMessageLimit) return;
+
+        // Store the message
+        const payload = { messageId: message.id, guildId: message.guild.id, memberId: message.author.id };
+        await DiscordMessage.findOneAndUpdate(payload, payload, { upsert: true });
+    } catch (error) {
+        logger.error(error);
+    }
+};
+
+export default onMessageCreate;
diff --git a/apps/api/src/app/events/MessageReactionAdd.ts b/apps/api/src/app/events/MessageReactionAdd.ts
new file mode 100644
index 000000000..3fc680414
--- /dev/null
+++ b/apps/api/src/app/events/MessageReactionAdd.ts
@@ -0,0 +1,33 @@
+import { MessageReaction } from 'discord.js';
+import { logger } from '../util/logger';
+import { DiscordReaction } from '@thxnetwork/api/models';
+
+const onMessageReactionAdd = async (reaction: MessageReaction) => {
+    try {
+        const users = await reaction.users.fetch();
+        const promises = users.map((user) => {
+            try {
+                // logger.info(
+                //     `#${user.id} created a reaction on message ${reaction.message.id} in guild ${reaction.message.guild.id}`,
+                // );
+                const filter = {
+                    guildId: reaction.message.guild.id,
+                    messageId: reaction.message.id,
+                    memberId: user.id,
+                };
+                return DiscordReaction.findOneAndUpdate(
+                    filter,
+                    { ...filter, content: reaction['_emoji'].name },
+                    { upsert: true },
+                );
+            } catch (error) {
+                logger.error(error);
+            }
+        });
+        await Promise.all(promises);
+    } catch (error) {
+        logger.error(error);
+    }
+};
+
+export default onMessageReactionAdd;
diff --git a/apps/api/src/app/events/commands/error.ts b/apps/api/src/app/events/commands/error.ts
new file mode 100644
index 000000000..4f96d24a7
--- /dev/null
+++ b/apps/api/src/app/events/commands/error.ts
@@ -0,0 +1,20 @@
+import { ButtonInteraction, CommandInteraction, StringSelectMenuInteraction } from 'discord.js';
+import { logger } from '@thxnetwork/api/util/logger';
+
+export const handleError = async (
+    error: Error,
+    interaction?: ButtonInteraction | CommandInteraction | StringSelectMenuInteraction,
+) => {
+    logger.info(error);
+    try {
+        if (interaction && interaction.isRepliable() && error.message) {
+            await interaction.reply({
+                content: error.message,
+                ephemeral: true,
+            });
+        }
+    } catch (error) {
+        logger.info(error);
+        // If the error reply fails we exit silently but log the cause
+    }
+};
diff --git a/apps/api/src/app/events/commands/index.ts b/apps/api/src/app/events/commands/index.ts
new file mode 100644
index 000000000..84139a19b
--- /dev/null
+++ b/apps/api/src/app/events/commands/index.ts
@@ -0,0 +1,11 @@
+import quest from './thx/quest';
+import buy from './thx/buy';
+import points from './thx/points';
+import info from './thx/info';
+
+export default {
+    quest,
+    buy,
+    points,
+    info,
+};
diff --git a/apps/api/src/app/events/commands/thx.ts b/apps/api/src/app/events/commands/thx.ts
new file mode 100644
index 000000000..78491ae1e
--- /dev/null
+++ b/apps/api/src/app/events/commands/thx.ts
@@ -0,0 +1,64 @@
+import { CommandInteraction, SlashCommandBuilder } from 'discord.js';
+import {
+    DiscordCommandVariant,
+    onSubcommandBuy,
+    onSubcommandComplete,
+    onSubcommandInfo,
+    onSubcommandPoints,
+} from './thx/index';
+
+export const commands: any[] = [
+    new SlashCommandBuilder()
+        .setName('info')
+        .setDescription('View your rank, quests and rewards in this campaign.')
+        .addStringOption((option) =>
+            option.setName('campaign').setDescription('Campaign to search for').setAutocomplete(true),
+        ),
+    new SlashCommandBuilder().setName('quest').setDescription('Complete a quest and earn points.'),
+    new SlashCommandBuilder().setName('buy').setDescription('Buy a reward with points.'),
+    new SlashCommandBuilder()
+        .setName('remove-points')
+        .setDescription('Remove an amount of points for a user.')
+        .addUserOption((option) =>
+            option.setName('user').setDescription('The user to transfer points to').setRequired(true),
+        )
+        .addIntegerOption((option) =>
+            option.setName('amount').setDescription('The amount of points to transfer').setRequired(true),
+        )
+        .addStringOption((option) =>
+            option.setName('campaign').setDescription('Campaign to search for').setAutocomplete(true),
+        )
+        .addStringOption((option) =>
+            option.setName('secret').setDescription('The optional secret for increased security'),
+        ),
+    new SlashCommandBuilder()
+        .setName('give-points')
+        .setDescription('Give an amount of points to a user.')
+        .addUserOption((option) =>
+            option.setName('user').setDescription('The user to transfer points to').setRequired(true),
+        )
+        .addIntegerOption((option) =>
+            option.setName('amount').setDescription('The amount of points to transfer').setRequired(true),
+        )
+        .addStringOption((option) =>
+            option.setName('campaign').setDescription('Campaign to search for').setAutocomplete(true),
+        )
+        .addStringOption((option) =>
+            option.setName('secret').setDescription('The optional secret for increased security'),
+        ),
+];
+
+export default {
+    data: commands,
+    executor: (interaction: CommandInteraction) => {
+        const commandMap = {
+            'quest': () => onSubcommandComplete(interaction),
+            'buy': () => onSubcommandBuy(interaction),
+            'info': () => onSubcommandInfo(interaction),
+            'give-points': () => onSubcommandPoints(interaction, DiscordCommandVariant.GivePoints),
+            'remove-points': () => onSubcommandPoints(interaction, DiscordCommandVariant.RemovePoints),
+        };
+        const command = interaction.commandName;
+        if (commandMap[command]) commandMap[command]();
+    },
+};
diff --git a/apps/api/src/app/events/commands/thx/buy.ts b/apps/api/src/app/events/commands/thx/buy.ts
new file mode 100644
index 000000000..23e87845c
--- /dev/null
+++ b/apps/api/src/app/events/commands/thx/buy.ts
@@ -0,0 +1,18 @@
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+import { createSelectMenuRewards } from '@thxnetwork/api/events/components';
+import { CommandInteraction } from 'discord.js';
+import { handleError } from '../error';
+
+export const onSubcommandBuy = async (interaction: CommandInteraction) => {
+    try {
+        const account = await AccountProxy.getByDiscordId(interaction.user.id);
+        if (!account) throw new Error('Please, connect your THX Account with Discord first.');
+
+        const row = await createSelectMenuRewards(interaction.guild);
+
+        interaction.reply({ components: [row as any], ephemeral: true });
+    } catch (error) {
+        handleError(error, interaction);
+    }
+};
+export default { onSubcommandBuy };
diff --git a/apps/api/src/app/events/commands/thx/index.ts b/apps/api/src/app/events/commands/thx/index.ts
new file mode 100644
index 000000000..2a08d413f
--- /dev/null
+++ b/apps/api/src/app/events/commands/thx/index.ts
@@ -0,0 +1,4 @@
+export * from './quest';
+export * from './buy';
+export * from './points';
+export * from './info';
diff --git a/apps/api/src/app/events/commands/thx/info.ts b/apps/api/src/app/events/commands/thx/info.ts
new file mode 100644
index 000000000..1cbcba9f5
--- /dev/null
+++ b/apps/api/src/app/events/commands/thx/info.ts
@@ -0,0 +1,85 @@
+import { ButtonStyle, CommandInteraction, Embed } from 'discord.js';
+import { Participant, Widget, Brand, Pool } from '@thxnetwork/api/models';
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+import DiscordDataProxy from '@thxnetwork/api/proxies/DiscordDataProxy';
+import { DiscordButtonVariant } from '../../InteractionCreated';
+import { handleError } from '../error';
+import { getDiscordGuild } from './points';
+
+export const onSubcommandInfo = async (interaction: CommandInteraction) => {
+    try {
+        const account = await AccountProxy.getByDiscordId(interaction.user.id);
+        if (!account) throw new Error('Please, connect your Discord.');
+
+        const { discordGuild, error } = await getDiscordGuild(interaction);
+        if (error) throw new Error(error);
+
+        const pool = await Pool.findById(discordGuild.poolId);
+        if (!pool) throw new Error('Could not find connected campaign.');
+
+        const participant = await Participant.findOne({ poolId: pool._id, sub: account.sub });
+        if (!participant) throw new Error('You have not participated in the campaign yet.');
+
+        const brand = await Brand.findOne({ poolId: pool._id });
+        const widget = await Widget.findOne({ poolId: pool._id });
+        const theme = JSON.parse(widget.theme);
+        const color = parseInt(theme.elements.btnBg.color.replace(/^#/, ''), 16);
+
+        const row = DiscordDataProxy.createButtonActionRow([
+            {
+                style: ButtonStyle.Primary,
+                label: 'Quests',
+                customId: DiscordButtonVariant.QuestList,
+                emoji: `✅`,
+            },
+            {
+                style: ButtonStyle.Primary,
+                label: 'Rewards',
+                customId: DiscordButtonVariant.RewardList,
+                emoji: `🎁`,
+            },
+            {
+                style: ButtonStyle.Link,
+                label: 'Campaign URL',
+                url: `${pool.campaignURL}`,
+            },
+        ]);
+
+        const embed: any = {
+            title: `${pool.settings.title}`,
+            description: pool.settings.description ? `${pool.settings.description}` : ` `,
+            color,
+            fields: [
+                {
+                    name: `Name`,
+                    value: `${account.username}`,
+                },
+                {
+                    name: `Points`,
+                    value: participant ? `${participant.balance}` : '0',
+                    inline: true,
+                },
+                {
+                    name: `Rank`,
+                    value: participant && participant.rank > 0 ? `#${participant.rank}` : 'None',
+                    inline: true,
+                },
+            ],
+        } as Embed;
+
+        if (brand && brand.backgroundImgUrl) {
+            embed['image'] = { url: brand && brand.backgroundImgUrl };
+        }
+
+        if (brand && brand.logoImgUrl) {
+            embed['thumbnail'] = {
+                url: brand.logoImgUrl,
+            };
+        }
+
+        interaction.reply({ embeds: [embed], components: [row as any], ephemeral: true });
+    } catch (error) {
+        handleError(error, interaction);
+    }
+};
+export default { onSubcommandInfo };
diff --git a/apps/api/src/app/events/commands/thx/points.ts b/apps/api/src/app/events/commands/thx/points.ts
new file mode 100644
index 000000000..55173f2cc
--- /dev/null
+++ b/apps/api/src/app/events/commands/thx/points.ts
@@ -0,0 +1,141 @@
+import { ButtonInteraction, CommandInteraction, User } from 'discord.js';
+import { WIDGET_URL } from '@thxnetwork/api/config/secrets';
+import { handleError } from '../error';
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+import PointBalanceService from '@thxnetwork/api/services/PointBalanceService';
+import { PoolDocument, Participant, DiscordGuild, Pool } from '@thxnetwork/api/models';
+
+export enum DiscordCommandVariant {
+    GivePoints = 0,
+    RemovePoints = 1,
+}
+
+async function removePoints(
+    pool: PoolDocument,
+    sender: TAccount,
+    receiver: TAccount,
+    senderUser: User,
+    receiverUser: User,
+    amount: number,
+) {
+    await PointBalanceService.subtract(pool, receiver, amount);
+
+    const participant = await Participant.findOne({
+        poolId: pool._id,
+        sub: receiver.sub,
+    });
+
+    const senderMessage = `The balance of <@${receiverUser.id}> has been decreased with **${amount} points** and is now **${participant.balance}**.`;
+    const receiverMessage = `<@${senderUser.id}> decreased your balance with **${amount}** resulting in a total of **${participant.balance} points**.`;
+
+    return { senderMessage, receiverMessage };
+}
+
+async function addPoints(
+    pool: PoolDocument,
+    sender: TAccount,
+    receiver: TAccount,
+    senderUser: User,
+    receiverUser: User,
+    amount: number,
+) {
+    await PointBalanceService.add(pool, receiver, amount);
+
+    const participant = await Participant.findOne({
+        poolId: pool._id,
+        sub: receiver.sub,
+    });
+    const senderMessage = `The balance of <@${receiverUser.id}> has been increased with **${amount} points** and is now **${participant.balance}**!`;
+    const receiverMessage = `<@${senderUser.id}> increased your balance with **${amount}** resulting in a total of **${participant.balance} points**.`;
+
+    return { senderMessage, receiverMessage };
+}
+
+const pointsFunctionMap = {
+    [DiscordCommandVariant.GivePoints]: addPoints,
+    [DiscordCommandVariant.RemovePoints]: removePoints,
+};
+
+export async function getDiscordGuild(interaction: CommandInteraction | ButtonInteraction) {
+    const discordGuilds = await DiscordGuild.find({ guildId: interaction.guild.id });
+    if (!discordGuilds.length) return { error: 'No campaign found ' };
+    if (discordGuilds.length === 1) return { discordGuild: discordGuilds[0] };
+
+    const choice = ((interaction as CommandInteraction).options as any).getString('campaign');
+    if (!choice) return { error: 'Please, select a campaign for this command.' };
+
+    const campaign = await Pool.findOne({ 'settings.title': choice });
+    if (!campaign) return { error: 'Could not find campaing for this choice.' };
+
+    const discordGuild = discordGuilds.find((g) => g.poolId === String(campaign._id));
+    return { discordGuild };
+}
+
+export const onSubcommandPoints = async (interaction: CommandInteraction, variant: DiscordCommandVariant) => {
+    try {
+        const account = await AccountProxy.getByDiscordId(interaction.user.id);
+        if (!account) throw new Error('Please, connect your THX Account with Discord first.');
+
+        const query = interaction.options.get('user').value as string;
+        if (!query) throw new Error('Please, provide a valid username.');
+
+        const result = await interaction.guild.members.search({ query, limit: 1 });
+        if (!result) throw new Error('Could not find user');
+        if (!result[query]) throw new Error('Could not find user');
+        const user = result[query];
+        if (!result[query]) throw new Error('Could not find user in search result');
+
+        const { discordGuild, error } = await getDiscordGuild(interaction);
+        if (error) throw new Error(error);
+
+        // Check optional secret
+        const secret = interaction.options.get('secret');
+        if (discordGuild.secret && discordGuild.secret.length) {
+            if (!secret) throw new Error('Please, provide a secret.');
+            if (discordGuild.secret !== secret.value) throw new Error('Please, provide a valid secret.');
+        }
+
+        // Check role
+        const member = await interaction.guild.members.fetch(interaction.user.id);
+        if (!member.roles.cache.has(discordGuild.adminRoleId)) {
+            const role = await interaction.guild.roles.fetch(discordGuild.adminRoleId);
+            throw new Error(`Only **${role.name}** roles have access to this command!`);
+        }
+
+        const amount = interaction.options.get('amount');
+        if (!amount.value || Number(amount.value) < 1) throw new Error('Please, provide a valid amount.');
+
+        const pool = await Pool.findById(discordGuild.poolId);
+        if (!pool) throw new Error('Could not find connected campaign.');
+
+        const receiver = await AccountProxy.getByDiscordId(user.id);
+        if (!receiver) {
+            user.send({
+                content: `<@${interaction.user.id}> failed to send you ${amount.value} points. Please [sign in](${WIDGET_URL}/c/${pool._id}), connect Discord and notify the sender!`,
+            });
+            throw new Error('Please, ask the receiver to connect a Discord account.');
+        }
+
+        // Determine if we should add or remove using pointsFunctionMap
+        const { senderMessage, receiverMessage } = await pointsFunctionMap[variant](
+            pool,
+            account,
+            receiver,
+            interaction.user,
+            user,
+            Number(amount.value),
+        );
+
+        // Send reaction to caller
+        interaction.reply({
+            content: senderMessage,
+            ephemeral: true,
+        });
+
+        // Send DM to user
+        user.send({ content: receiverMessage });
+    } catch (error) {
+        handleError(error, interaction);
+    }
+};
+export default { onSubcommandPoints };
diff --git a/apps/api/src/app/events/commands/thx/quest.ts b/apps/api/src/app/events/commands/thx/quest.ts
new file mode 100644
index 000000000..5c969579f
--- /dev/null
+++ b/apps/api/src/app/events/commands/thx/quest.ts
@@ -0,0 +1,21 @@
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+import { createSelectMenuQuests } from '@thxnetwork/api/events/components';
+import { CommandInteraction } from 'discord.js';
+import { handleError } from '../error';
+
+export const onSubcommandComplete = async (interaction: CommandInteraction) => {
+    try {
+        const account = await AccountProxy.getByDiscordId(interaction.user.id);
+        if (!account) throw new Error('Please, connect your THX Account with Discord first.');
+
+        const row = await createSelectMenuQuests(interaction);
+        if (!row) {
+            interaction.reply({ content: 'No quests found for this campaign.', ephemeral: true });
+        } else {
+            interaction.reply({ components: [row as any], ephemeral: true });
+        }
+    } catch (error) {
+        handleError(error, interaction);
+    }
+};
+export default { onSubcommandComplete };
diff --git a/apps/api/src/app/events/components/index.ts b/apps/api/src/app/events/components/index.ts
new file mode 100644
index 000000000..363a3f634
--- /dev/null
+++ b/apps/api/src/app/events/components/index.ts
@@ -0,0 +1,2 @@
+export * from './selectMenuQuests';
+export * from './selectMenuRewards';
diff --git a/apps/api/src/app/events/components/selectMenuQuests.ts b/apps/api/src/app/events/components/selectMenuQuests.ts
new file mode 100644
index 000000000..35c534d10
--- /dev/null
+++ b/apps/api/src/app/events/components/selectMenuQuests.ts
@@ -0,0 +1,75 @@
+import {
+    ActionRowBuilder,
+    ButtonInteraction,
+    CommandInteraction,
+    StringSelectMenuBuilder,
+    StringSelectMenuOptionBuilder,
+} from 'discord.js';
+import { DiscordStringSelectMenuVariant } from '../InteractionCreated';
+import { QuestInvite } from '@thxnetwork/api/models/QuestInvite';
+import { QuestWeb3 } from '@thxnetwork/api/models/QuestWeb3';
+import { questInteractionVariantMap } from '@thxnetwork/common/maps';
+import QuestService from '@thxnetwork/api/services/QuestService';
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+import {
+    DiscordGuild,
+    Pool,
+    PoolDocument,
+    QuestCustom,
+    QuestDaily,
+    QuestGitcoin,
+    QuestSocial,
+} from '@thxnetwork/api/models';
+
+async function findQuests(campaigns: PoolDocument[]) {
+    const poolId = campaigns.map(({ _id }) => String(_id));
+    return await Promise.all([
+        QuestDaily.find({ poolId, isPublished: true }),
+        QuestInvite.find({ poolId, isPublished: true }),
+        QuestSocial.find({ poolId, isPublished: true }),
+        QuestCustom.find({ poolId, isPublished: true }),
+        QuestWeb3.find({ poolId, isPublished: true }),
+        QuestGitcoin.find({ poolId, isPublished: true }),
+    ]);
+}
+
+async function createSelectMenuQuests(interaction: CommandInteraction | ButtonInteraction) {
+    const discordGuilds = await DiscordGuild.find({ guildId: interaction.guild.id });
+    if (!discordGuilds.length) throw new Error('Could not find server.');
+
+    const poolId = discordGuilds.map((g) => g.poolId);
+    const campaigns = await Pool.find({ _id: poolId });
+    if (!campaigns.length) throw new Error('No campaigns found for this server.');
+
+    const select = new StringSelectMenuBuilder();
+    select.setCustomId(DiscordStringSelectMenuVariant.QuestComplete).setPlaceholder('Complete a quest');
+
+    const account = await AccountProxy.getByDiscordId(interaction.user.id);
+    if (!account) throw new Error('No THX account found for this Discord user.');
+
+    const quests = (await findQuests(campaigns)).flat();
+    if (!quests.length) throw new Error('No quests found for this campaign.');
+
+    for (const index in quests) {
+        const quest: any = quests[index];
+
+        // Campaign might be removed
+        const campaign = campaigns.find((c) => String(c._id) === quest.poolId);
+        if (!campaign) continue;
+
+        const questId = String(quest._id);
+        const variant = quest.interaction ? questInteractionVariantMap[quest.interaction] : quest.variant;
+        const value = JSON.stringify({ questId, variant });
+        const amount = await QuestService.getAmount(variant, quest, account);
+        const options = new StringSelectMenuOptionBuilder()
+            .setLabel(`[${amount}] ${quest.title}`)
+            .setDescription(`${campaign.settings.title}`)
+            .setValue(value);
+
+        select.addOptions(options);
+    }
+
+    return new ActionRowBuilder().addComponents(select);
+}
+
+export { createSelectMenuQuests };
diff --git a/apps/api/src/app/events/components/selectMenuRewards.ts b/apps/api/src/app/events/components/selectMenuRewards.ts
new file mode 100644
index 000000000..397f3ff7c
--- /dev/null
+++ b/apps/api/src/app/events/components/selectMenuRewards.ts
@@ -0,0 +1,43 @@
+import { RewardVariant } from '@thxnetwork/common/enums';
+import { ActionRowBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, Guild } from 'discord.js';
+import { DiscordStringSelectMenuVariant } from '../InteractionCreated';
+import {
+    DiscordGuild,
+    RewardNFT,
+    RewardDiscordRole,
+    RewardCoin,
+    RewardCustom,
+    RewardCoupon,
+} from '@thxnetwork/api/models';
+
+async function createSelectMenuRewards(guild: Guild) {
+    const { poolId } = await DiscordGuild.findOne({ guildId: guild.id });
+    const results = await Promise.all([
+        RewardCoin.find({ poolId, pointPrice: { $gt: 0 } }),
+        RewardNFT.find({ poolId, pointPrice: { $gt: 0 } }),
+        RewardCustom.find({ poolId, pointPrice: { $gt: 0 } }),
+        RewardCoupon.find({ poolId, pointPrice: { $gt: 0 } }),
+        RewardDiscordRole.find({ poolId, pointPrice: { $gt: 0 } }),
+    ]);
+    const rewards = results.flat();
+    if (!rewards.length) throw new Error('No rewards found for this campaign.');
+
+    const select = new StringSelectMenuBuilder();
+    select.setCustomId(DiscordStringSelectMenuVariant.RewardBuy).setPlaceholder('Buy a reward');
+
+    for (const index in rewards) {
+        const reward = rewards[index];
+        const questId = String(reward._id);
+        const value = JSON.stringify({ questId, variant: reward.variant });
+        const options = new StringSelectMenuOptionBuilder()
+            .setLabel(reward.title)
+            .setDescription(`${reward.pointPrice} points (${RewardVariant[reward.variant]} Reward)`)
+            .setValue(value);
+
+        select.addOptions(options);
+    }
+
+    return new ActionRowBuilder().addComponents(select);
+}
+
+export { createSelectMenuRewards };
diff --git a/apps/api/src/app/events/handlers/button/quest.ts b/apps/api/src/app/events/handlers/button/quest.ts
new file mode 100644
index 000000000..315c3ea10
--- /dev/null
+++ b/apps/api/src/app/events/handlers/button/quest.ts
@@ -0,0 +1,28 @@
+import { ButtonInteraction } from 'discord.js';
+import { QuestVariant } from '@thxnetwork/common/enums';
+import { completeQuest } from '@thxnetwork/api/events/handlers/select/quest';
+import { handleError } from '../../commands/error';
+import { createSelectMenuQuests } from '../../components';
+
+export async function onClickQuestComplete(interaction: ButtonInteraction) {
+    try {
+        // Custom Id Syntax: DiscordButtonVariant.QuestComplete + ':' + questVariant ':' + questId
+        const data = interaction.customId.split(':');
+        const variant = data[1] as unknown as QuestVariant;
+        const questId = data[2];
+
+        await completeQuest(interaction, variant, questId);
+    } catch (error) {
+        handleError(error, interaction);
+    }
+}
+
+export async function onClickQuestList(interaction: ButtonInteraction) {
+    try {
+        const row = await createSelectMenuQuests(interaction);
+
+        interaction.reply({ components: [row as any], ephemeral: true });
+    } catch (error) {
+        handleError(error, interaction);
+    }
+}
diff --git a/apps/api/src/app/events/handlers/button/reward.ts b/apps/api/src/app/events/handlers/button/reward.ts
new file mode 100644
index 000000000..c88234877
--- /dev/null
+++ b/apps/api/src/app/events/handlers/button/reward.ts
@@ -0,0 +1,60 @@
+import { ButtonInteraction } from 'discord.js';
+import { RewardVariant } from '@thxnetwork/common/enums';
+import { handleError } from '../../commands/error';
+import {
+    RewardCoin,
+    RewardNFT,
+    DiscordGuild,
+    RewardCustom,
+    RewardCoupon,
+    RewardDiscordRole,
+} from '@thxnetwork/api/models';
+
+export async function onClickRewardRedeem(interaction: ButtonInteraction) {
+    try {
+        // Custom Id Syntax: DiscordButtonVariant.QuestComplete + ':' + questVariant ':' + questId
+        const data = interaction.customId.split(':');
+        const variant = data[1] as unknown as RewardVariant;
+        const questId = data[2];
+
+        // await completeReward(interaction, variant, questId);
+    } catch (error) {
+        handleError(error, interaction);
+    }
+}
+
+export async function onClickRewardList(interaction: ButtonInteraction) {
+    try {
+        const discordGuild = await DiscordGuild.findOne({ guildId: interaction.guild.id });
+        if (!discordGuild) throw new Error('Could not find this Discord server.');
+
+        const { poolId } = discordGuild;
+        const results = await Promise.all([
+            RewardCoin.find({ poolId, pointPrice: { $gt: 0 } }),
+            RewardNFT.find({ poolId, pointPrice: { $gt: 0 } }),
+            RewardCustom.find({ poolId, pointPrice: { $gt: 0 } }),
+            RewardCoupon.find({ poolId, pointPrice: { $gt: 0 } }),
+            RewardDiscordRole.find({ poolId, pointPrice: { $gt: 0 } }),
+        ]);
+        const rewards = results.flat();
+        if (!rewards.length) throw new Error('No rewards found for this campaign.');
+
+        const list = rewards.map(
+            (reward: any) =>
+                `${String(reward.pointPrice).padStart(4)} pts. ${reward.title} (${RewardVariant[reward.variant]})`,
+        );
+        const code = list.join('\n');
+        const embeds = [
+            {
+                title: `🎁 Rewards`,
+                description: rewards.length
+                    ? 'Use `/buy` to buy rewards with points. \n ```' + code + `\n` + '```'
+                    : 'No rewards available!',
+            },
+        ];
+
+        interaction.reply({ embeds, ephemeral: true });
+    } catch (error) {
+        handleError(error, interaction);
+    }
+}
diff --git a/apps/api/src/app/events/handlers/index.ts b/apps/api/src/app/events/handlers/index.ts
new file mode 100644
index 000000000..280d715e4
--- /dev/null
+++ b/apps/api/src/app/events/handlers/index.ts
@@ -0,0 +1,3 @@
+export * from './select/quest';
+export * from './button/quest';
+export * from './button/reward';
diff --git a/apps/api/src/app/events/handlers/select/quest.ts b/apps/api/src/app/events/handlers/select/quest.ts
new file mode 100644
index 000000000..3ef04e548
--- /dev/null
+++ b/apps/api/src/app/events/handlers/select/quest.ts
@@ -0,0 +1,138 @@
+import { ButtonInteraction, ButtonStyle, StringSelectMenuInteraction } from 'discord.js';
+import { JobType, QuestVariant } from '@thxnetwork/common/enums';
+import { handleError } from '../../commands/error';
+import { DiscordButtonVariant } from '../../InteractionCreated';
+import { Widget, Brand } from '@thxnetwork/api/models';
+import { agenda } from '@thxnetwork/api/util/agenda';
+import { DiscordDisconnected } from '@thxnetwork/api/util/errors';
+import { serviceMap } from '@thxnetwork/api/services/interfaces/IQuestService';
+import AccountProxy from '@thxnetwork/api/proxies/AccountProxy';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import QuestService from '@thxnetwork/api/services/QuestService';
+import DiscordDataProxy from '@thxnetwork/api/proxies/DiscordDataProxy';
+
+export async function completeQuest(
+    interaction: ButtonInteraction | StringSelectMenuInteraction,
+    variant: QuestVariant,
+    questId: string,
+) {
+    try {
+        const account = await AccountProxy.getByDiscordId(interaction.user.id);
+        if (!account) throw new DiscordDisconnected();
+
+        const Quest = serviceMap[variant].models.quest;
+        const quest = await Quest.findById(questId);
+        if (!quest) throw new Error('Could not find this quest.');
+
+        const pool = await PoolService.getById(quest.poolId);
+        if (!pool) throw new Error('Could not find this campaign.');
+
+        const { interaction: questInteraction } = quest as TQuestSocial;
+        const platformUserId = questInteraction && QuestService.findUserIdForInteraction(account, questInteraction);
+
+        const data = {
+            isClaimed: true,
+            platformUserId,
+        };
+        const availabilityValidation = await QuestService.isAvailable(variant, {
+            quest,
+            account,
+            data,
+        });
+        if (!availabilityValidation.result) throw new Error(availabilityValidation.reason);
+
+        const requirementValidation = await QuestService.getValidationResult(variant, {
+            quest,
+            account,
+            data,
+        });
+        if (!requirementValidation.result) throw new Error(requirementValidation.reason);
+
+        const amount = await QuestService.getAmount(variant, quest, account);
+
+        await agenda.now(JobType.CreateQuestEntry, {
+            variant,
+            questId: quest._id,
+            sub: account.sub,
+            data,
+        });
+
+        interaction.reply({
+            content: `Completed **${quest.title}** and earned **${amount} points**.`,
+            ephemeral: true,
+        });
+    } catch (error) {
+        handleError(error, interaction);
+    }
+}
+
+export async function onSelectQuestComplete(interaction: StringSelectMenuInteraction) {
+    try {
+        const { questId, variant } = JSON.parse(interaction.values[0]);
+
+        const account = await AccountProxy.getByDiscordId(interaction.user.id);
+        if (!account) throw new DiscordDisconnected();
+
+        const quest = await QuestService.findById(variant, questId);
+        if (!quest) throw new Error('Could not find this quest.');
+
+        const pool = await PoolService.getById(quest.poolId);
+        if (!pool) throw new Error('Could not find this campaign.');
+
+        const data = {};
+        const isAvailable = await QuestService.isAvailable(variant, { quest, account, data });
+        const brand = await Brand.findOne({ poolId: pool._id });
+        const widget = await Widget.findOne({ poolId: pool._id });
+        const theme = JSON.parse(widget.theme);
+        const amount = await QuestService.getAmount(quest.variant, quest, account);
+        const embedQuest = {
+            title: quest.title,
+            description: quest.description,
+            author: {
+                name: pool.settings.title,
+                icon_url: brand ? brand.logoImgUrl : '',
+                url: widget.domain,
+            },
+            image: { url: quest.image },
+            color: parseInt(theme.elements.btnBg.color.replace(/^#/, ''), 16),
+            fields: [
+                {
+                    name: 'Points',
+                    value: `${amount}`,
+                    inline: true,
+                },
+                {
+                    name: 'Type',
+                    value: `${QuestVariant[quest.variant]}`,
+                    inline: true,
+                },
+            ],
+        };
+
+        const row = DiscordDataProxy.createButtonActionRow([
+            {
+                emoji: isAvailable ? '✅' : '🔒',
+                label: 'Complete',
+                style: ButtonStyle.Success,
+                customId: `${DiscordButtonVariant.QuestComplete}:${variant}:${questId}`,
+                disabled: !isAvailable,
+            },
+            {
+                label: 'More Info',
+                style: ButtonStyle.Link,
+                url: `${pool.campaignURL}/quests`,
+            },
+        ]);
+        const components = [];
+        components.push(row);
+
+        interaction.reply({
+            ephemeral: true,
+            content: '',
+            embeds: [embedQuest],
+            components,
+        });
+    } catch (error) {
+        handleError(error, interaction);
+    }
+}
diff --git a/apps/api/src/app/events/index.ts b/apps/api/src/app/events/index.ts
new file mode 100644
index 000000000..14fd91d46
--- /dev/null
+++ b/apps/api/src/app/events/index.ts
@@ -0,0 +1,16 @@
+import { Events } from 'discord.js';
+import onClientReady from './ClientReady';
+import onInteractionCreated from './InteractionCreated';
+import onMessageReactionAdd from './MessageReactionAdd';
+import onMessageCreate from './MessageCreate';
+import onGuildCreate from './GuildCreate';
+import onGuildDelete from './GuildDelete';
+
+export default {
+    [Events.ClientReady]: onClientReady,
+    [Events.GuildCreate]: onGuildCreate,
+    [Events.GuildDelete]: onGuildDelete,
+    [Events.InteractionCreate]: onInteractionCreated,
+    [Events.MessageReactionAdd]: onMessageReactionAdd,
+    [Events.MessageCreate]: onMessageCreate,
+};
diff --git a/apps/api/src/app/hardhat/.gitignore b/apps/api/src/app/hardhat/.gitignore
new file mode 100644
index 000000000..e8c12ff4f
--- /dev/null
+++ b/apps/api/src/app/hardhat/.gitignore
@@ -0,0 +1,17 @@
+node_modules
+.env
+
+# Hardhat files
+/cache
+/artifacts
+
+# TypeChain files
+/typechain
+/typechain-types
+
+# solidity-coverage files
+/coverage
+/coverage.json
+
+# Hardhat Ignition default folder for deployments against a local node
+ignition/deployments/chain-31337
diff --git a/apps/api/src/app/hardhat/contracts/ImportSafe.sol b/apps/api/src/app/hardhat/contracts/ImportSafe.sol
new file mode 100644
index 000000000..5cdd89f4c
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/ImportSafe.sol
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity ^0.7.4;
+
+import '@gnosis.pm/safe-contracts/contracts/accessors/SimulateTxAccessor.sol';
+import '@gnosis.pm/safe-contracts/contracts/proxies/GnosisSafeProxyFactory.sol';
+import '@gnosis.pm/safe-contracts/contracts/handler/DefaultCallbackHandler.sol';
+// import '@gnosis.pm/safe-contracts/contracts/handler/CompatibilityFallbackHandler.sol';
+import '@gnosis.pm/safe-contracts/contracts/libraries/CreateCall.sol';
+import '@gnosis.pm/safe-contracts/contracts/libraries/MultiSend.sol';
+import '@gnosis.pm/safe-contracts/contracts/libraries/MultiSendCallOnly.sol';
+import '@gnosis.pm/safe-contracts/contracts/examples/libraries/SignMessage.sol';
+import '@gnosis.pm/safe-contracts/contracts/GnosisSafeL2.sol';
+import '@gnosis.pm/safe-contracts/contracts/GnosisSafe.sol';
+
+// Get the compiler to pick up these facets
+contract ImportSafe {
+    SimulateTxAccessor public simulateTxAccessor;
+    GnosisSafeProxyFactory public gnosisSafeProxyFactory;
+    DefaultCallbackHandler public defaultCallbackHandler;
+    // CompatibilityFallbackHandler public compatibilityFallbackHandler;
+    CreateCall public createCall;
+    MultiSend public multiSend;
+    MultiSendCallOnly public multiSendCallOnly;
+    SignMessageLib public signMessageLib;
+    GnosisSafeL2 public gnosisSafeL2;
+    GnosisSafe public gnosisSafe;
+}
diff --git a/apps/api/src/app/hardhat/contracts/interfaces/IAccessControlEvents.sol b/apps/api/src/app/hardhat/contracts/interfaces/IAccessControlEvents.sol
new file mode 100644
index 000000000..548d77c8b
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/interfaces/IAccessControlEvents.sol
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity ^0.7.6;
+
+interface IAccessControlEvents {
+    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
+    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
+    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
+}
diff --git a/apps/api/src/app/hardhat/contracts/mock/BAL.sol b/apps/api/src/app/hardhat/contracts/mock/BAL.sol
new file mode 100644
index 000000000..63c7a3a69
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/mock/BAL.sol
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: Apache-2.0
+
+pragma solidity ^0.7.6;
+
+import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
+
+contract BAL is ERC20 {
+    constructor(address to, uint256 amount) ERC20('Balancer', 'BAL') {
+        _mint(to, amount);
+    }
+
+    function mint(address to, uint256 amount) external {
+        _mint(to, amount);
+    }
+}
diff --git a/apps/api/src/app/hardhat/contracts/mock/BPT.sol b/apps/api/src/app/hardhat/contracts/mock/BPT.sol
new file mode 100644
index 000000000..886f3f1a2
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/mock/BPT.sol
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: Apache-2.0
+
+pragma solidity ^0.7.6;
+
+import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
+
+interface IWeightedPool is IERC20 {
+    function setVault(address _vault) external;
+    function getVault() external view returns (address);
+    function getPoolId() external view returns (bytes32);
+}
+
+contract BPT is ERC20, IWeightedPool {
+    address public vault;
+
+    constructor(address _to, uint256 _amount) ERC20('20USDC-80THX', '20USDC-80THX') {
+        _mint(_to, _amount);
+    }
+ 
+    function setVault(address _vault) external override {
+        vault = _vault;
+    }
+    
+    function getVault() external override view returns (address) {
+        return vault;
+    }
+    
+    function getPoolId() external override pure returns (bytes32) {
+        return 0xb204bf10bc3a5435017d3db247f56da601dfe08a0002000000000000000000fe;
+    }
+}
diff --git a/apps/api/src/app/hardhat/contracts/mock/BPTGauge.sol b/apps/api/src/app/hardhat/contracts/mock/BPTGauge.sol
new file mode 100644
index 000000000..99e1a65dd
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/mock/BPTGauge.sol
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma abicoder v2;
+pragma solidity ^0.7.6;
+
+import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
+
+interface IGauge is IERC20 {
+    function deposit(uint256 _value) external;
+    function withdraw(uint256 _value) external;
+    function lp_token() external view returns (address);
+    function working_supply() external view returns (uint256);
+}
+
+contract BPTGauge is ERC20, IGauge {
+    IERC20 public bpt;
+
+    event Deposit(address indexed user, uint256 value);
+    event Withdraw(address indexed user, uint256 value);
+
+    constructor(address _bpt) ERC20('Balancer 20USDC-80THX Gauge Deposit', '20USDC-80THX-gauge') {
+        bpt = IERC20(_bpt);
+    }
+
+    /*
+     *  @dev Deposit LP tokens in the gauge and mint BPTGauge for msg.sender
+     *  @param _value Amount of LP tokens to deposit
+     */
+    function deposit(uint256 _value) external override {
+        // Transfer BPT from the user to the gauge
+        bpt.transferFrom(msg.sender, address(this), _value);
+
+        // Mint BPTGauge tokens to the user
+        _mint(msg.sender, _value);
+
+        emit Deposit(msg.sender, _value);
+    }
+
+    /*
+     *  @dev Withdraw LP tokens from the gauge
+     *  @param _value Amount of LP tokens to withdraw
+     *  @notice This mock function will not decrease the totalSupply
+     */
+    function withdraw(uint256 _value) external override {
+        // Transfer staked BPT from the gauge to the user
+        bpt.transfer(msg.sender, _value);
+
+        // Burn BPTGauge for the user
+        transferFrom(msg.sender, address(0), _value);
+
+        emit Withdraw(msg.sender, _value);
+    }
+
+    function lp_token() external override view returns (address) {
+        return address(bpt);
+    }
+
+    function working_supply() external override pure returns (uint256) {
+        return 585909572986408132343905;
+    }
+}
diff --git a/apps/api/src/app/hardhat/contracts/mock/BalancerVault.sol b/apps/api/src/app/hardhat/contracts/mock/BalancerVault.sol
new file mode 100644
index 000000000..005373a23
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/mock/BalancerVault.sol
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma abicoder v2;
+pragma solidity ^0.7.6;
+
+import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
+import "@openzeppelin/contracts/math/SafeMath.sol";
+import 'hardhat/console.sol';
+
+contract BalancerVault {
+    using SafeMath for uint256;
+
+    ERC20 public bpt;
+    ERC20 public usdc;
+    ERC20 public thx;
+
+    struct JoinPoolRequest {
+        address[] assets;
+        uint256[] maxAmountsIn;
+        bytes userData;
+        bool fromInternalBalance;
+    }
+
+    constructor(address _bpt, address _usdc, address _thx) {
+        bpt = ERC20(_bpt);
+        usdc = ERC20(_usdc);
+        thx = ERC20(_thx);
+    }
+
+    function joinPool(bytes32 poolId, address sender, address recipient, JoinPoolRequest memory request) external {
+        usdc.transferFrom(sender, address(this), request.maxAmountsIn[0]);
+        thx.transferFrom(sender, address(this), request.maxAmountsIn[1]);
+
+        // Assumes BalancerVault has a BPT balance and transfers BPT to recipient
+        // Aligns decimals in order to get to a workable BPT amount
+        uint256 usdcAmount = request.maxAmountsIn[0].div(10**usdc.decimals());
+        uint256 thxAmount = request.maxAmountsIn[1].div(10**thx.decimals());
+        uint256 amount = usdcAmount.add(thxAmount).mul(10**bpt.decimals());
+
+        require(bpt.transfer(recipient, amount), 'BalancerVault: BPT transfer failed');
+    }
+}
diff --git a/apps/api/src/app/hardhat/contracts/mock/ExampleToken.sol b/apps/api/src/app/hardhat/contracts/mock/ExampleToken.sol
new file mode 100644
index 000000000..1188cbdaa
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/mock/ExampleToken.sol
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: Apache-2.0
+
+pragma solidity ^0.7.6;
+
+import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
+
+contract ExampleToken is ERC20 {
+    constructor(address to, uint256 amount) ERC20('Test Token', 'TEST') {
+        _mint(to, amount);
+    }
+}
diff --git a/apps/api/src/app/hardhat/contracts/mock/THX.sol b/apps/api/src/app/hardhat/contracts/mock/THX.sol
new file mode 100644
index 000000000..a9005b389
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/mock/THX.sol
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: Apache-2.0
+
+pragma solidity ^0.7.6;
+
+import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
+
+contract THX is ERC20 {
+    constructor(address to, uint256 amount) ERC20('THX Network (PoS)', 'THX') {
+        _mint(to, amount);
+    }
+}
diff --git a/apps/api/src/app/hardhat/contracts/mock/USDC.sol b/apps/api/src/app/hardhat/contracts/mock/USDC.sol
new file mode 100644
index 000000000..fb8e47d00
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/mock/USDC.sol
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: Apache-2.0
+
+pragma solidity ^0.7.6;
+
+import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
+
+contract USDC is ERC20 {
+    constructor(address to, uint256 amount) ERC20('USD Coin (PoS)', 'USDC.e') {
+        _setupDecimals(6);
+        _mint(to, amount);
+    }
+}
diff --git a/apps/api/src/app/hardhat/contracts/utils/BondPurchaseChecker.sol b/apps/api/src/app/hardhat/contracts/utils/BondPurchaseChecker.sol
new file mode 100644
index 000000000..1070088cd
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/utils/BondPurchaseChecker.sol
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma abicoder v2;
+pragma solidity ^0.7.6;
+
+interface ICustomBill {
+    struct Bill {
+        uint256 payout;
+        uint256 payoutClaimed;
+        uint256 vesting;
+        uint256 vestingTerm;
+        uint256 vestingStartTimestamp;
+        uint256 lastClaimTimestamp;
+        uint256 truePricePaid;
+    }
+
+    function getBillIds(address user) external view returns (uint[] memory);
+    function getBillInfo(uint256 billId) external view returns (Bill memory);
+}
+
+contract BondPurchaseChecker {
+    ICustomBill bond;
+
+    constructor(address _bondContractAddress) {
+        bond = ICustomBill(_bondContractAddress);
+    }
+
+    function largestPayoutOf(address _user) public view returns (uint256) {
+        uint[] memory billIds = bond.getBillIds(_user);
+        uint256 largestPayout = 0;
+
+        for (uint i = 0; i < billIds.length; i++) {
+            uint256 payout = bond.getBillInfo(billIds[i]).payout;
+            largestPayout = payout > largestPayout ? payout : largestPayout;
+        }
+
+        return largestPayout;
+    }
+}
diff --git a/apps/api/src/app/hardhat/contracts/utils/ERC1155/ITHX_ERC1155.sol b/apps/api/src/app/hardhat/contracts/utils/ERC1155/ITHX_ERC1155.sol
new file mode 100644
index 000000000..6d5bb9c4c
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/utils/ERC1155/ITHX_ERC1155.sol
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.7.6;
+
+import '@openzeppelin/contracts/token/ERC1155/IERC1155.sol';
+
+interface ITHX_ERC1155 is IERC1155 {
+    function mint(address to, uint256 id, uint256 amount, bytes memory data) external;
+    function mintBatch(
+        address to,
+        uint256[] memory ids,
+        uint256[] memory amounts,
+        bytes memory data
+    ) external;
+}
\ No newline at end of file
diff --git a/apps/api/src/app/hardhat/contracts/utils/ERC1155/THX_ERC1155.sol b/apps/api/src/app/hardhat/contracts/utils/ERC1155/THX_ERC1155.sol
new file mode 100644
index 000000000..dc1935fc5
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/utils/ERC1155/THX_ERC1155.sol
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.7.6;
+import '@openzeppelin/contracts/token/ERC1155/ERC1155.sol';
+import '@openzeppelin/contracts/access/AccessControl.sol';
+import '@openzeppelin/contracts/access/Ownable.sol';
+import '@openzeppelin/contracts/utils/Strings.sol';
+
+contract THX_ERC1155 is ERC1155, AccessControl, Ownable {
+    bytes32 public constant MINTER_ROLE = keccak256('MINTER_ROLE');
+
+    constructor(string memory URI_, address owner_) ERC1155(URI_) {
+        transferOwnership(owner_);
+        _setupRole(DEFAULT_ADMIN_ROLE, owner_);
+        _setupRole(MINTER_ROLE, owner_);
+    }
+
+    function mint(
+        address to,
+        uint256 id,
+        uint256 amount,
+        bytes memory data
+    ) public {
+        require(hasRole(MINTER_ROLE, msg.sender), 'NOT_MINTER');
+        _mint(to, id, amount, data);
+    }
+
+    function mintBatch(
+        address to,
+        uint256[] memory ids,
+        uint256[] memory amounts,
+        bytes memory data
+    ) public {
+        require(hasRole(MINTER_ROLE, msg.sender), 'NOT_MINTER');
+        _mintBatch(to, ids, amounts, data);
+    }
+}
diff --git a/apps/api/src/app/hardhat/contracts/utils/ERC20/LimitedSupplyToken.sol b/apps/api/src/app/hardhat/contracts/utils/ERC20/LimitedSupplyToken.sol
new file mode 100644
index 000000000..d65a01def
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/utils/ERC20/LimitedSupplyToken.sol
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: Apache-2.0
+
+pragma solidity ^0.7.6;
+
+/******************************************************************************\
+* @title ERC20 Limited Supply
+* @author Peter Polman <peter@thx.network>
+* @notice Used for point systems with a limited supply. Mints the full supply to the to argument given in the contructor. 
+* @dev Not upgradable contract.
+/******************************************************************************/
+
+import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
+
+contract LimitedSupplyToken is ERC20 {
+    constructor(
+        string memory _name,
+        string memory _symbol,
+        address to,
+        uint256 amount
+    ) ERC20(_name, _symbol) {
+        _mint(to, amount);
+    }
+}
diff --git a/apps/api/src/app/hardhat/contracts/utils/ERC20/THX_ERC20.sol b/apps/api/src/app/hardhat/contracts/utils/ERC20/THX_ERC20.sol
new file mode 100644
index 000000000..6ed0955b8
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/utils/ERC20/THX_ERC20.sol
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: Apache-2.0
+
+pragma solidity ^0.7.6;
+
+/******************************************************************************\
+* @title ERC20 Limited Supply
+* @author Peter Polman <peter@thx.network>
+* @notice Used for point systems with a limited supply. Mints the full supply to the to argument given in the contructor.
+* @dev Not upgradable contract.
+/******************************************************************************/
+
+import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
+
+contract THX_ERC20_LimitedSupply is ERC20 {
+    constructor(string memory _name, string memory _symbol, address to, uint256 amount) ERC20(_name, _symbol) {
+        _mint(to, amount);
+    }
+}
diff --git a/apps/api/src/app/hardhat/contracts/utils/ERC20/THX_ERC20_UnlimitedSupply.sol b/apps/api/src/app/hardhat/contracts/utils/ERC20/THX_ERC20_UnlimitedSupply.sol
new file mode 100644
index 000000000..7c6acc951
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/utils/ERC20/THX_ERC20_UnlimitedSupply.sol
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: Apache-2.0
+
+pragma solidity ^0.7.6;
+
+/******************************************************************************\
+* @title ERC20 Unlimited Supply
+* @author Evert Kors <evert@thx.network>
+* @notice Used for point systems with an unlimited supply. Mints the required tokens whenever they are needed.
+* @dev Not upgradable contract.
+/******************************************************************************/
+
+import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
+import '@openzeppelin/contracts/access/Ownable.sol';
+import '@openzeppelin/contracts/access/AccessControl.sol';
+
+contract THX_ERC20_UnlimitedSupply is ERC20, AccessControl, Ownable {
+    bytes32 public constant MINTER_ROLE = keccak256('MINTER_ROLE');
+
+    constructor(string memory name_, string memory symbol_, address owner_) ERC20(name_, symbol_) {
+        transferOwnership(owner_);
+        _setupRole(DEFAULT_ADMIN_ROLE, owner_);
+        _setupRole(MINTER_ROLE, owner_);
+    }
+
+    function _beforeTokenTransfer(address _from, address _to, uint256 _amount) internal override {
+        if (hasRole(MINTER_ROLE, _from)) {
+            _mint(_from, _amount);
+        }
+    }
+}
diff --git a/apps/api/src/app/hardhat/contracts/utils/ERC20/UnlimitedSupplyToken.sol b/apps/api/src/app/hardhat/contracts/utils/ERC20/UnlimitedSupplyToken.sol
new file mode 100644
index 000000000..2ebafbbdf
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/utils/ERC20/UnlimitedSupplyToken.sol
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: Apache-2.0
+
+pragma solidity ^0.7.6;
+
+/******************************************************************************\
+* @title ERC20 Unlimited Supply
+* @author Evert Kors <evert@thx.network>
+* @notice Used for point systems with an unlimited supply. Mints the required tokens whenever they are needed.
+* @dev Not upgradable contract.
+/******************************************************************************/
+
+import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
+import '@openzeppelin/contracts/access/Ownable.sol';
+import '@openzeppelin/contracts/access/AccessControl.sol';
+
+contract UnlimitedSupplyToken is ERC20, AccessControl, Ownable {
+    bytes32 public constant MINTER_ROLE = keccak256('MINTER_ROLE');
+
+    constructor(
+        string memory name_,
+        string memory symbol_,
+        address owner_
+    ) ERC20(name_, symbol_) {
+        transferOwnership(owner_);
+        _setupRole(DEFAULT_ADMIN_ROLE, owner_);
+        _setupRole(MINTER_ROLE, owner_);
+    }
+
+    function _beforeTokenTransfer(
+        address _from,
+        address _to,
+        uint256 _amount
+    ) internal override {
+        if (hasRole(MINTER_ROLE, _from)) {
+            _mint(_from, _amount);
+        }
+    }
+}
diff --git a/apps/api/src/app/hardhat/contracts/utils/ERC721/INonFungibleToken.sol b/apps/api/src/app/hardhat/contracts/utils/ERC721/INonFungibleToken.sol
new file mode 100644
index 000000000..37e4741cd
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/utils/ERC721/INonFungibleToken.sol
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity ^0.7.6;
+
+import '@openzeppelin/contracts/token/ERC721/IERC721.sol';
+
+interface INonFungibleToken is IERC721 {
+    function mint(address _recipient, string memory _tokenURI) external returns (uint256);
+}
diff --git a/apps/api/src/app/hardhat/contracts/utils/ERC721/ITHX_ERC721.sol b/apps/api/src/app/hardhat/contracts/utils/ERC721/ITHX_ERC721.sol
new file mode 100644
index 000000000..f9795535f
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/utils/ERC721/ITHX_ERC721.sol
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity ^0.7.6;
+
+import '@openzeppelin/contracts/token/ERC721/IERC721.sol';
+
+interface ITHX_ERC721 is IERC721 {
+    function mint(address _recipient, string memory _tokenURI) external returns (uint256);
+}
diff --git a/apps/api/src/app/hardhat/contracts/utils/ERC721/NonFungibleToken.sol b/apps/api/src/app/hardhat/contracts/utils/ERC721/NonFungibleToken.sol
new file mode 100644
index 000000000..b6779d648
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/utils/ERC721/NonFungibleToken.sol
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity ^0.7.6;
+
+import '@openzeppelin/contracts/token/ERC721/ERC721.sol';
+import '@openzeppelin/contracts/utils/Counters.sol';
+import '@openzeppelin/contracts/access/Ownable.sol';
+import '@openzeppelin/contracts/access/AccessControl.sol';
+import './INonFungibleToken.sol';
+
+contract NonFungibleToken is INonFungibleToken, ERC721, AccessControl, Ownable {
+    bytes32 public constant MINTER_ROLE = keccak256('MINTER_ROLE');
+    using Counters for Counters.Counter;
+    Counters.Counter private _tokenIds;
+
+    constructor(
+        string memory name_,
+        string memory symbol_,
+        string memory baseURI_,
+        address owner_
+    ) ERC721(name_, symbol_) {
+        transferOwnership(owner_);
+        _setupRole(DEFAULT_ADMIN_ROLE, owner_);
+        _setupRole(MINTER_ROLE, owner_);
+        _setBaseURI(baseURI_);
+    }
+
+    function mint(address _recipient, string memory _tokenURI) external override returns (uint256) {
+        require(hasRole(MINTER_ROLE, msg.sender), 'NOT_MINTER');
+
+        _tokenIds.increment();
+
+        uint256 newItemId = _tokenIds.current();
+        _mint(_recipient, newItemId);
+        _setTokenURI(newItemId, _tokenURI);
+
+        return newItemId;
+    }
+}
diff --git a/apps/api/src/app/hardhat/contracts/utils/ERC721/THX_ERC721.sol b/apps/api/src/app/hardhat/contracts/utils/ERC721/THX_ERC721.sol
new file mode 100644
index 000000000..7a2eef72a
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/utils/ERC721/THX_ERC721.sol
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity ^0.7.6;
+
+import '@openzeppelin/contracts/token/ERC721/ERC721.sol';
+import '@openzeppelin/contracts/utils/Counters.sol';
+import '@openzeppelin/contracts/access/Ownable.sol';
+import '@openzeppelin/contracts/access/AccessControl.sol';
+import './ITHX_ERC721.sol';
+
+contract THX_ERC721 is ITHX_ERC721, ERC721, AccessControl, Ownable {
+    bytes32 public constant MINTER_ROLE = keccak256('MINTER_ROLE');
+    using Counters for Counters.Counter;
+    Counters.Counter private _tokenIds;
+
+    constructor(
+        string memory name_,
+        string memory symbol_,
+        string memory baseURI_,
+        address owner_
+    ) ERC721(name_, symbol_) {
+        transferOwnership(owner_);
+        _setupRole(DEFAULT_ADMIN_ROLE, owner_);
+        _setupRole(MINTER_ROLE, owner_);
+        _setBaseURI(baseURI_);
+    }
+
+    function mint(address _recipient, string memory _tokenURI) external override returns (uint256) {
+        require(hasRole(MINTER_ROLE, msg.sender), 'NOT_MINTER');
+
+        _tokenIds.increment();
+
+        uint256 newItemId = _tokenIds.current();
+        _mint(_recipient, newItemId);
+        _setTokenURI(newItemId, _tokenURI);
+
+        return newItemId;
+    }
+}
diff --git a/apps/api/src/app/hardhat/contracts/utils/THXMembershipPurchaser.sol b/apps/api/src/app/hardhat/contracts/utils/THXMembershipPurchaser.sol
new file mode 100644
index 000000000..854ee84c1
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/utils/THXMembershipPurchaser.sol
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma abicoder v2;
+pragma solidity ^0.7.6;
+
+import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
+import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
+import '@openzeppelin/contracts/utils/Context.sol';
+import '@openzeppelin/contracts/math/SafeMath.sol';
+import '../mock/BalancerVault.sol';
+import '../mock/BPTGauge.sol';
+import '../mock/BPT.sol'; 
+import './THXRegistry.sol';
+
+contract THXMembershipPurchaser is ReentrancyGuard, Context {
+    address admin;
+    ITHXRegistry public registry;
+    IWeightedPool public bpt; 
+    IGauge public gauge;
+    BalancerVault public vault;
+
+    constructor(address _admin, address _registry) {
+        admin = _admin;
+        registry = ITHXRegistry(_registry);
+        gauge = BPTGauge(registry.getGauge());
+        bpt = IWeightedPool(gauge.lp_token());
+        vault = BalancerVault(bpt.getVault());
+    }
+
+    /*
+    * Joins the Balancer pool with the provided _tokenIn and stakes the received BPT-gauge tokens. On success the BPT-gauge tokens are
+    * transferred back to the caller and can be locked.
+    * @param _owner Owner of the payment balance
+    * @param _amountIn Amount of _tokenIn to provide as liquidity and stake
+    * @param _minAmountOut Minimum amount of BPT to receive
+    */
+    function joinAndStake(address _owner, address _tokenIn, uint256 _amountIn, uint256 _minAmountOut) external nonReentrant {
+        // @notice Avoids having the caller approve for multiple transfers
+        require(
+            IERC20(_tokenIn).transferFrom(_owner, address(this), _amountIn),
+            'MembershipPurchaser: transfer failed'
+        );
+
+        // Define the assets used for the Balancer pool join
+        address[] memory _assets = new address[](2);
+        _assets[0] = _tokenIn;
+        _assets[1] = address(0);
+
+        // Define the max amounts in for the Balancer pool join
+        uint256[] memory _maxAmountsIn = new uint256[](2);
+        _maxAmountsIn[0] = _amountIn;
+        _maxAmountsIn[1] = 0;
+
+        // Create Balancer liqiuidity position for _amountIn in _tokenIn
+        IERC20(_tokenIn).approve(address(vault), _maxAmountsIn[0]);
+        vault.joinPool(bpt.getPoolId(), address(this), address(this), BalancerVault.JoinPoolRequest({
+            assets: _assets,
+            maxAmountsIn: _maxAmountsIn,
+            userData: abi.encode(1, _maxAmountsIn, _minAmountOut), // WeightedPoolJoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT = 1
+            fromInternalBalance: false
+        }));
+
+        // Approve and stake all received BPT and receive BPT-gauge tokens
+        uint256 bptAmount = bpt.balanceOf(address(this));
+        bpt.approve(address(gauge), bptAmount);
+        gauge.deposit(bptAmount);
+
+        // Transfer BPT-gauge to the RewardDistributor for further distribution
+        uint256 bptGaugeAmount = gauge.balanceOf(address(this));
+        gauge.transfer(_owner, bptGaugeAmount);
+    }
+
+}
diff --git a/apps/api/src/app/hardhat/contracts/utils/THXPaymentSplitter.sol b/apps/api/src/app/hardhat/contracts/utils/THXPaymentSplitter.sol
new file mode 100644
index 000000000..924bd6849
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/utils/THXPaymentSplitter.sol
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma abicoder v2;
+pragma solidity ^0.7.6;
+
+import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
+import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';
+import '@openzeppelin/contracts/utils/Context.sol';
+import '@openzeppelin/contracts/math/SafeMath.sol';
+import '../mock/BalancerVault.sol';
+import '../mock/BPTGauge.sol';
+import '../mock/BPT.sol'; 
+import './THXRegistry.sol';
+
+contract THXPaymentSplitter is ReentrancyGuard, Context {
+    using SafeMath for uint256;
+
+    address admin;
+    ITHXRegistry public registry;
+    IGauge public gauge;
+    IWeightedPool public bpt; 
+    BalancerVault public vault;
+    IERC20 public paymentToken;
+
+    mapping(address => uint256) public rates;
+    mapping(address => uint256) private _balances;
+    mapping(address => uint256) private _lastPaymentAt;
+
+    constructor(address _admin, address _registry) {
+        admin = _admin;
+        registry = ITHXRegistry(_registry);
+        paymentToken = IERC20(registry.getPaymentToken());
+        gauge = BPTGauge(registry.getGauge());
+        bpt = IWeightedPool(gauge.lp_token());
+        vault = BalancerVault(bpt.getVault());
+    }
+
+    // @dev Only admin can change this
+    function setRegistry(address _registry) external {
+        require(_msgSender() == admin, 'PaymentSplitter: !admin');
+        registry = ITHXRegistry(_registry);
+    }
+
+    // Set the USDC rate in wei per second for the sender
+    function setRate(address _owner, uint256 _rate) external {
+        require(_msgSender() == admin, 'PaymentSplitter: !admin');
+        rates[_owner] = _rate;
+    }
+
+    /*
+    * Transfers USDC to the payee based on the payout rate. Then it adds liquidity for the remainder of the deposit amount and stakes the
+    * received BPT. On success the balance and last payment timestamp are stored in reference to the owner and used when fetching returning 
+    * the balance.
+    * @param _owner Owner of the payment balance
+    * @param _amount Amount of USDC to deposit
+    * 
+    * @notice Assumes paymentToken allowance for address(this) is sufficient for the call
+    * @dev ReentrancyGuard is implemented for the deposit call in case THXRegistry ever gets compromised
+    */
+    function deposit(address _owner, uint256 _amount, uint256 _minAmountOut) external nonReentrant {
+        // Don't allow payments if no rate is set for owner
+        require(rates[_owner] > 0, 'PaymentSplitter: !rate');
+ 
+        // Transfer deposit amount to self for further splitting. 
+        // @notice Avoids having the caller approve for multiple transfers
+        require(
+            paymentToken.transferFrom(_owner, address(this), _amount),
+            'PaymentSplitter: transfer failed'
+        );
+
+        // Transfer payout to payee in paymentToken as per payout rate determined by registry
+        uint256 payoutRate = registry.getPayoutRate();
+        uint256 payout = _amount.mul(payoutRate).div(10000); // 100% * 100 to allow for 2 decimals without devisisons
+        
+        require(
+            paymentToken.transfer(registry.getPayee(), payout),
+            'PaymentSplitter: transfer failed'
+        );
+
+        // Define the assets used for the Balancer pool join
+        address[] memory _assets = new address[](2);
+        _assets[0] = address(paymentToken);
+        _assets[1] = address(0);
+
+        // Subtract the payout that's transferred to the payee before setting the amounts in for pool join
+        uint256[] memory _maxAmountsIn = new uint256[](2);
+        _maxAmountsIn[0] = _amount.sub(payout);
+        _maxAmountsIn[1] = 0;
+
+        // Create Balancer liqiuidity position for remainder of the _amount
+        paymentToken.approve(address(vault), _maxAmountsIn[0]);
+        vault.joinPool(bpt.getPoolId(), address(this), address(this), BalancerVault.JoinPoolRequest({
+            assets: _assets,
+            maxAmountsIn: _maxAmountsIn,
+            userData: abi.encode(1, _maxAmountsIn, _minAmountOut), // WeightedPoolJoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT = 1
+            fromInternalBalance: false
+        }));
+
+        // Approve and stake all received BPT and receive BPT-gauge tokens
+        // TODO Potentially harmful if multiple transactions are mined in the same block
+        uint256 bptAmount = bpt.balanceOf(address(this));
+        bpt.approve(address(gauge), bptAmount);
+        gauge.deposit(bptAmount);
+
+        // Transfer BPT-gauge to the RewardDistributor for further distribution
+        uint256 bptGaugeAmount = gauge.balanceOf(address(this));
+        require(gauge.transfer(registry.getRewardDistributor(), bptGaugeAmount), 'PaymentSplitter: transfer failed');
+
+        // Increase total balance for _owner
+        _balances[_owner] = _balances[_owner].add(_amount);
+
+        // Store lastPaymentAt timestamp for _owner
+        _lastPaymentAt[_owner] = block.timestamp;
+    }
+ 
+    /*
+    * @return Current payment balance of owner measured since last payment made and rate set
+    */
+    function balanceOf(address _owner) public view returns (uint256) {
+        uint256 rate = rates[_owner]; // USDC rate in wei per second for _owner
+        uint256 lastPaymentAt = _lastPaymentAt[_owner]; // block.timestamp of last payment
+        // If no payments have been made return 0
+        if (lastPaymentAt == 0)  {
+            return 0;
+        }
+
+        // Required balances equals seconds since last payment * rate per second
+        uint256 currentBlock = block.timestamp;
+        
+        // Will always be positive since lastPaymentAt is always a historic timestamp
+        uint256 balanceRequired = currentBlock.sub(lastPaymentAt).mul(rate);
+
+        // If balance is insufficient return 0
+        if (_balances[_owner] < balanceRequired) {
+            return 0;
+        }
+        
+        // Safe to subtract as we have checked for balance to be greater than balanceRequired
+        return _balances[_owner].sub(balanceRequired);
+    }
+}
diff --git a/apps/api/src/app/hardhat/contracts/utils/THXRegistry.sol b/apps/api/src/app/hardhat/contracts/utils/THXRegistry.sol
new file mode 100644
index 000000000..097375e75
--- /dev/null
+++ b/apps/api/src/app/hardhat/contracts/utils/THXRegistry.sol
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity ^0.7.6;
+
+interface ITHXRegistry {
+    function getRewardDistributor() external view returns (address);
+    function getPayoutRate() external view returns (uint256);
+    function getPayee() external view returns (address);
+    function getPaymentToken() external view returns (address);
+    function getGauge() external view returns (address);
+}
+
+contract THXRegistry is ITHXRegistry {
+    address public paymentToken; // USDC
+    address public payee;
+    uint256 public payoutRate;
+    address public rewardDistributor; // VoteEscrow RewardDistributor
+    address public gauge; // Balancer ChildChain Liquidity Gauge
+
+    /*
+     * @param _paymentToken Token address used for payouts to payee
+     * @param _payee Payee address receiving payouts as per payoutRate
+     * @param _rewardDistributor VoteEscrow RewardDistributor address
+     * @param _gauge Balancer child chain gauge address
+     * @param _payoutRate Share of payouts to payee as percentage times 100 (3000)
+     */
+    constructor(address _paymentToken, address _payee, address _rewardDistributor, address _gauge) {
+        paymentToken = _paymentToken;
+        payee = _payee;
+        rewardDistributor = _rewardDistributor;
+        gauge = _gauge;
+    }
+
+    function getRewardDistributor() external view override returns (address) {
+        return rewardDistributor;
+    }
+
+    function getPayoutRate() external view override returns (uint256) {
+        return payoutRate;
+    }
+
+    // @notice Payout rate as a percentage times 100 (3000 for 30%)
+    // @dev Should be governed with parameter voting
+    function setPayoutRate(uint256 _rate) external {
+        require(msg.sender == payee, 'THXRegistry: !payee');
+        require(_rate >= 0 && _rate <= 10000, 'THXRegistry: payoutRate out of bounds');
+        payoutRate = _rate;
+    }
+    
+    function getPayee() external view override returns (address) {
+        return payee;
+    }
+
+    function getPaymentToken() external view override returns (address) {
+        return paymentToken;
+    }
+
+    function getGauge() external view override returns (address) {
+        return gauge;
+    }
+
+}
diff --git a/apps/api/src/app/hardhat/export/BAL.json b/apps/api/src/app/hardhat/export/BAL.json
new file mode 100644
index 000000000..d1e75ca5a
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/BAL.json
@@ -0,0 +1,315 @@
+{
+    "_format": "hh-sol-artifact-1",
+    "contractName": "BAL",
+    "sourceName": "contracts/mock/BAL.sol",
+    "abi": [
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "to",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "constructor"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "owner",
+                    "type": "address"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "value",
+                    "type": "uint256"
+                }
+            ],
+            "name": "Approval",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "from",
+                    "type": "address"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "to",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "value",
+                    "type": "uint256"
+                }
+            ],
+            "name": "Transfer",
+            "type": "event"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "owner",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                }
+            ],
+            "name": "allowance",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "approve",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "account",
+                    "type": "address"
+                }
+            ],
+            "name": "balanceOf",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "decimals",
+            "outputs": [
+                {
+                    "internalType": "uint8",
+                    "name": "",
+                    "type": "uint8"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "subtractedValue",
+                    "type": "uint256"
+                }
+            ],
+            "name": "decreaseAllowance",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "addedValue",
+                    "type": "uint256"
+                }
+            ],
+            "name": "increaseAllowance",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "to",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "mint",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "name",
+            "outputs": [
+                {
+                    "internalType": "string",
+                    "name": "",
+                    "type": "string"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "symbol",
+            "outputs": [
+                {
+                    "internalType": "string",
+                    "name": "",
+                    "type": "string"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "totalSupply",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "recipient",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "transfer",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "sender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "recipient",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "transferFrom",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        }
+    ],
+    "bytecode": "0x60806040523480156200001157600080fd5b5060405162000e2538038062000e25833981810160405260408110156200003757600080fd5b50805160209182015160408051808201825260088152672130b630b731b2b960c11b818601908152825180840190935260038084526210905360ea1b968401969096528151949593949193620000909290919062000240565b508051620000a690600490602084019062000240565b50506005805460ff1916601217905550620000c28282620000ca565b5050620002ec565b6001600160a01b03821662000126576040805162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b6200013460008383620001d9565b6200015081600254620001de60201b620005ba1790919060201c565b6002556001600160a01b0382166000908152602081815260409091205462000183918390620005ba620001de821b17901c565b6001600160a01b0383166000818152602081815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b505050565b60008282018381101562000239576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282620002785760008555620002c3565b82601f106200029357805160ff1916838001178555620002c3565b82800160010185558215620002c3579182015b82811115620002c3578251825591602001919060010190620002a6565b50620002d1929150620002d5565b5090565b5b80821115620002d15760008155600101620002d6565b610b2980620002fc6000396000f3fe608060405234801561001057600080fd5b50600436106100b45760003560e01c806340c10f191161007157806340c10f191461021057806370a082311461023e57806395d89b4114610264578063a457c2d71461026c578063a9059cbb14610298578063dd62ed3e146102c4576100b4565b806306fdde03146100b9578063095ea7b31461013657806318160ddd1461017657806323b872dd14610190578063313ce567146101c657806339509351146101e4575b600080fd5b6100c16102f2565b6040805160208082528351818301528351919283929083019185019080838360005b838110156100fb5781810151838201526020016100e3565b50505050905090810190601f1680156101285780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101626004803603604081101561014c57600080fd5b506001600160a01b038135169060200135610388565b604080519115158252519081900360200190f35b61017e6103a5565b60408051918252519081900360200190f35b610162600480360360608110156101a657600080fd5b506001600160a01b038135811691602081013590911690604001356103ab565b6101ce610432565b6040805160ff9092168252519081900360200190f35b610162600480360360408110156101fa57600080fd5b506001600160a01b03813516906020013561043b565b61023c6004803603604081101561022657600080fd5b506001600160a01b038135169060200135610489565b005b61017e6004803603602081101561025457600080fd5b50356001600160a01b0316610497565b6100c16104b2565b6101626004803603604081101561028257600080fd5b506001600160a01b038135169060200135610513565b610162600480360360408110156102ae57600080fd5b506001600160a01b03813516906020013561057b565b61017e600480360360408110156102da57600080fd5b506001600160a01b038135811691602001351661058f565b60038054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526060939092909183018282801561037e5780601f106103535761010080835404028352916020019161037e565b820191906000526020600020905b81548152906001019060200180831161036157829003601f168201915b5050505050905090565b600061039c61039561061b565b848461061f565b50600192915050565b60025490565b60006103b884848461070b565b610428846103c461061b565b61042385604051806060016040528060288152602001610a5e602891396001600160a01b038a1660009081526001602052604081209061040261061b565b6001600160a01b031681526020810191909152604001600020549190610866565b61061f565b5060019392505050565b60055460ff1690565b600061039c61044861061b565b84610423856001600061045961061b565b6001600160a01b03908116825260208083019390935260409182016000908120918c1681529252902054906105ba565b61049382826108fd565b5050565b6001600160a01b031660009081526020819052604090205490565b60048054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526060939092909183018282801561037e5780601f106103535761010080835404028352916020019161037e565b600061039c61052061061b565b8461042385604051806060016040528060258152602001610acf602591396001600061054a61061b565b6001600160a01b03908116825260208083019390935260409182016000908120918d16815292529020549190610866565b600061039c61058861061b565b848461070b565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b600082820183811015610614576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b3390565b6001600160a01b0383166106645760405162461bcd60e51b8152600401808060200182810382526024815260200180610aab6024913960400191505060405180910390fd5b6001600160a01b0382166106a95760405162461bcd60e51b8152600401808060200182810382526022815260200180610a166022913960400191505060405180910390fd5b6001600160a01b03808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b0383166107505760405162461bcd60e51b8152600401808060200182810382526025815260200180610a866025913960400191505060405180910390fd5b6001600160a01b0382166107955760405162461bcd60e51b81526004018080602001828103825260238152602001806109f36023913960400191505060405180910390fd5b6107a08383836109ed565b6107dd81604051806060016040528060268152602001610a38602691396001600160a01b0386166000908152602081905260409020549190610866565b6001600160a01b03808516600090815260208190526040808220939093559084168152205461080c90826105ba565b6001600160a01b038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600081848411156108f55760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156108ba5781810151838201526020016108a2565b50505050905090810190601f1680156108e75780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b6001600160a01b038216610958576040805162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b610964600083836109ed565b60025461097190826105ba565b6002556001600160a01b03821660009081526020819052604090205461099790826105ba565b6001600160a01b0383166000818152602081815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b50505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa264697066735822122054d3aaaafb4397e9ba9a956156cfdf7c50a74c32a8e663bdf85e3a80c501d06464736f6c63430007060033",
+    "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100b45760003560e01c806340c10f191161007157806340c10f191461021057806370a082311461023e57806395d89b4114610264578063a457c2d71461026c578063a9059cbb14610298578063dd62ed3e146102c4576100b4565b806306fdde03146100b9578063095ea7b31461013657806318160ddd1461017657806323b872dd14610190578063313ce567146101c657806339509351146101e4575b600080fd5b6100c16102f2565b6040805160208082528351818301528351919283929083019185019080838360005b838110156100fb5781810151838201526020016100e3565b50505050905090810190601f1680156101285780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101626004803603604081101561014c57600080fd5b506001600160a01b038135169060200135610388565b604080519115158252519081900360200190f35b61017e6103a5565b60408051918252519081900360200190f35b610162600480360360608110156101a657600080fd5b506001600160a01b038135811691602081013590911690604001356103ab565b6101ce610432565b6040805160ff9092168252519081900360200190f35b610162600480360360408110156101fa57600080fd5b506001600160a01b03813516906020013561043b565b61023c6004803603604081101561022657600080fd5b506001600160a01b038135169060200135610489565b005b61017e6004803603602081101561025457600080fd5b50356001600160a01b0316610497565b6100c16104b2565b6101626004803603604081101561028257600080fd5b506001600160a01b038135169060200135610513565b610162600480360360408110156102ae57600080fd5b506001600160a01b03813516906020013561057b565b61017e600480360360408110156102da57600080fd5b506001600160a01b038135811691602001351661058f565b60038054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526060939092909183018282801561037e5780601f106103535761010080835404028352916020019161037e565b820191906000526020600020905b81548152906001019060200180831161036157829003601f168201915b5050505050905090565b600061039c61039561061b565b848461061f565b50600192915050565b60025490565b60006103b884848461070b565b610428846103c461061b565b61042385604051806060016040528060288152602001610a5e602891396001600160a01b038a1660009081526001602052604081209061040261061b565b6001600160a01b031681526020810191909152604001600020549190610866565b61061f565b5060019392505050565b60055460ff1690565b600061039c61044861061b565b84610423856001600061045961061b565b6001600160a01b03908116825260208083019390935260409182016000908120918c1681529252902054906105ba565b61049382826108fd565b5050565b6001600160a01b031660009081526020819052604090205490565b60048054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526060939092909183018282801561037e5780601f106103535761010080835404028352916020019161037e565b600061039c61052061061b565b8461042385604051806060016040528060258152602001610acf602591396001600061054a61061b565b6001600160a01b03908116825260208083019390935260409182016000908120918d16815292529020549190610866565b600061039c61058861061b565b848461070b565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b600082820183811015610614576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b3390565b6001600160a01b0383166106645760405162461bcd60e51b8152600401808060200182810382526024815260200180610aab6024913960400191505060405180910390fd5b6001600160a01b0382166106a95760405162461bcd60e51b8152600401808060200182810382526022815260200180610a166022913960400191505060405180910390fd5b6001600160a01b03808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b0383166107505760405162461bcd60e51b8152600401808060200182810382526025815260200180610a866025913960400191505060405180910390fd5b6001600160a01b0382166107955760405162461bcd60e51b81526004018080602001828103825260238152602001806109f36023913960400191505060405180910390fd5b6107a08383836109ed565b6107dd81604051806060016040528060268152602001610a38602691396001600160a01b0386166000908152602081905260409020549190610866565b6001600160a01b03808516600090815260208190526040808220939093559084168152205461080c90826105ba565b6001600160a01b038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600081848411156108f55760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156108ba5781810151838201526020016108a2565b50505050905090810190601f1680156108e75780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b6001600160a01b038216610958576040805162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b610964600083836109ed565b60025461097190826105ba565b6002556001600160a01b03821660009081526020819052604090205461099790826105ba565b6001600160a01b0383166000818152602081815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b50505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa264697066735822122054d3aaaafb4397e9ba9a956156cfdf7c50a74c32a8e663bdf85e3a80c501d06464736f6c63430007060033",
+    "linkReferences": {},
+    "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/BPT.json b/apps/api/src/app/hardhat/export/BPT.json
new file mode 100644
index 000000000..e39ae6960
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/BPT.json
@@ -0,0 +1,349 @@
+{
+    "_format": "hh-sol-artifact-1",
+    "contractName": "BPT",
+    "sourceName": "contracts/mock/BPT.sol",
+    "abi": [
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "_to",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "_amount",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "constructor"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "owner",
+                    "type": "address"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "value",
+                    "type": "uint256"
+                }
+            ],
+            "name": "Approval",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "from",
+                    "type": "address"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "to",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "value",
+                    "type": "uint256"
+                }
+            ],
+            "name": "Transfer",
+            "type": "event"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "owner",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                }
+            ],
+            "name": "allowance",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "approve",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "account",
+                    "type": "address"
+                }
+            ],
+            "name": "balanceOf",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "decimals",
+            "outputs": [
+                {
+                    "internalType": "uint8",
+                    "name": "",
+                    "type": "uint8"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "subtractedValue",
+                    "type": "uint256"
+                }
+            ],
+            "name": "decreaseAllowance",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "getPoolId",
+            "outputs": [
+                {
+                    "internalType": "bytes32",
+                    "name": "",
+                    "type": "bytes32"
+                }
+            ],
+            "stateMutability": "pure",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "getVault",
+            "outputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "addedValue",
+                    "type": "uint256"
+                }
+            ],
+            "name": "increaseAllowance",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "name",
+            "outputs": [
+                {
+                    "internalType": "string",
+                    "name": "",
+                    "type": "string"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "_vault",
+                    "type": "address"
+                }
+            ],
+            "name": "setVault",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "symbol",
+            "outputs": [
+                {
+                    "internalType": "string",
+                    "name": "",
+                    "type": "string"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "totalSupply",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "recipient",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "transfer",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "sender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "recipient",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "transferFrom",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "vault",
+            "outputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        }
+    ],
+    "bytecode": "0x60806040523480156200001157600080fd5b5060405162000e0738038062000e07833981810160405260408110156200003757600080fd5b508051602091820151604080518082018252600c8082526b06460aaa688865a7060a890b60a31b82870181815284518086019095529184529583019590955280519394929390926200008d91600391906200023d565b508051620000a39060049060208401906200023d565b50506005805460ff1916601217905550620000bf8282620000c7565b5050620002e9565b6001600160a01b03821662000123576040805162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b6200013160008383620001d6565b6200014d81600254620001db60201b6200068f1790919060201c565b6002556001600160a01b03821660009081526020818152604090912054620001809183906200068f620001db821b17901c565b6001600160a01b0383166000818152602081815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b505050565b60008282018381101562000236576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282620002755760008555620002c0565b82601f106200029057805160ff1916838001178555620002c0565b82800160010185558215620002c0579182015b82811115620002c0578251825591602001919060010190620002a3565b50620002ce929150620002d2565b5090565b5b80821115620002ce5760008155600101620002d3565b610b0e80620002f96000396000f3fe608060405234801561001057600080fd5b50600436106100f55760003560e01c80636817031b11610097578063a457c2d711610066578063a457c2d7146102d3578063a9059cbb146102ff578063dd62ed3e1461032b578063fbfa77cf14610359576100f5565b80636817031b1461025957806370a08231146102815780638d928af8146102a757806395d89b41146102cb576100f5565b806323b872dd116100d357806323b872dd146101d1578063313ce5671461020757806338fff2d014610225578063395093511461022d576100f5565b806306fdde03146100fa578063095ea7b31461017757806318160ddd146101b7575b600080fd5b610102610361565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561013c578181015183820152602001610124565b50505050905090810190601f1680156101695780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101a36004803603604081101561018d57600080fd5b506001600160a01b0381351690602001356103f7565b604080519115158252519081900360200190f35b6101bf610414565b60408051918252519081900360200190f35b6101a3600480360360608110156101e757600080fd5b506001600160a01b0381358116916020810135909116906040013561041a565b61020f6104a1565b6040805160ff9092168252519081900360200190f35b6101bf6104aa565b6101a36004803603604081101561024357600080fd5b506001600160a01b0381351690602001356104ce565b61027f6004803603602081101561026f57600080fd5b50356001600160a01b031661051c565b005b6101bf6004803603602081101561029757600080fd5b50356001600160a01b0316610544565b6102af61055f565b604080516001600160a01b039092168252519081900360200190f35b610102610573565b6101a3600480360360408110156102e957600080fd5b506001600160a01b0381351690602001356105d4565b6101a36004803603604081101561031557600080fd5b506001600160a01b03813516906020013561063c565b6101bf6004803603604081101561034157600080fd5b506001600160a01b0381358116916020013516610650565b6102af61067b565b60038054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103ed5780601f106103c2576101008083540402835291602001916103ed565b820191906000526020600020905b8154815290600101906020018083116103d057829003601f168201915b5050505050905090565b600061040b6104046106f0565b84846106f4565b50600192915050565b60025490565b60006104278484846107e0565b610497846104336106f0565b61049285604051806060016040528060288152602001610a43602891396001600160a01b038a166000908152600160205260408120906104716106f0565b6001600160a01b03168152602081019190915260400160002054919061093b565b6106f4565b5060019392505050565b60055460ff1690565b7fb204bf10bc3a5435017d3db247f56da601dfe08a0002000000000000000000fe90565b600061040b6104db6106f0565b8461049285600160006104ec6106f0565b6001600160a01b03908116825260208083019390935260409182016000908120918c16815292529020549061068f565b600580546001600160a01b0390921661010002610100600160a81b0319909216919091179055565b6001600160a01b031660009081526020819052604090205490565b60055461010090046001600160a01b031690565b60048054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103ed5780601f106103c2576101008083540402835291602001916103ed565b600061040b6105e16106f0565b8461049285604051806060016040528060258152602001610ab4602591396001600061060b6106f0565b6001600160a01b03908116825260208083019390935260409182016000908120918d1681529252902054919061093b565b600061040b6106496106f0565b84846107e0565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b60055461010090046001600160a01b031681565b6000828201838110156106e9576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b3390565b6001600160a01b0383166107395760405162461bcd60e51b8152600401808060200182810382526024815260200180610a906024913960400191505060405180910390fd5b6001600160a01b03821661077e5760405162461bcd60e51b81526004018080602001828103825260228152602001806109fb6022913960400191505060405180910390fd5b6001600160a01b03808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b0383166108255760405162461bcd60e51b8152600401808060200182810382526025815260200180610a6b6025913960400191505060405180910390fd5b6001600160a01b03821661086a5760405162461bcd60e51b81526004018080602001828103825260238152602001806109d86023913960400191505060405180910390fd5b6108758383836109d2565b6108b281604051806060016040528060268152602001610a1d602691396001600160a01b038616600090815260208190526040902054919061093b565b6001600160a01b0380851660009081526020819052604080822093909355908416815220546108e1908261068f565b6001600160a01b038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600081848411156109ca5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561098f578181015183820152602001610977565b50505050905090810190601f1680156109bc5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b50505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa264697066735822122092787e3cbd971df817541c818008a4dc0435dc43da4a6c2f5e3e16fc0203cfd564736f6c63430007060033",
+    "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100f55760003560e01c80636817031b11610097578063a457c2d711610066578063a457c2d7146102d3578063a9059cbb146102ff578063dd62ed3e1461032b578063fbfa77cf14610359576100f5565b80636817031b1461025957806370a08231146102815780638d928af8146102a757806395d89b41146102cb576100f5565b806323b872dd116100d357806323b872dd146101d1578063313ce5671461020757806338fff2d014610225578063395093511461022d576100f5565b806306fdde03146100fa578063095ea7b31461017757806318160ddd146101b7575b600080fd5b610102610361565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561013c578181015183820152602001610124565b50505050905090810190601f1680156101695780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101a36004803603604081101561018d57600080fd5b506001600160a01b0381351690602001356103f7565b604080519115158252519081900360200190f35b6101bf610414565b60408051918252519081900360200190f35b6101a3600480360360608110156101e757600080fd5b506001600160a01b0381358116916020810135909116906040013561041a565b61020f6104a1565b6040805160ff9092168252519081900360200190f35b6101bf6104aa565b6101a36004803603604081101561024357600080fd5b506001600160a01b0381351690602001356104ce565b61027f6004803603602081101561026f57600080fd5b50356001600160a01b031661051c565b005b6101bf6004803603602081101561029757600080fd5b50356001600160a01b0316610544565b6102af61055f565b604080516001600160a01b039092168252519081900360200190f35b610102610573565b6101a3600480360360408110156102e957600080fd5b506001600160a01b0381351690602001356105d4565b6101a36004803603604081101561031557600080fd5b506001600160a01b03813516906020013561063c565b6101bf6004803603604081101561034157600080fd5b506001600160a01b0381358116916020013516610650565b6102af61067b565b60038054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103ed5780601f106103c2576101008083540402835291602001916103ed565b820191906000526020600020905b8154815290600101906020018083116103d057829003601f168201915b5050505050905090565b600061040b6104046106f0565b84846106f4565b50600192915050565b60025490565b60006104278484846107e0565b610497846104336106f0565b61049285604051806060016040528060288152602001610a43602891396001600160a01b038a166000908152600160205260408120906104716106f0565b6001600160a01b03168152602081019190915260400160002054919061093b565b6106f4565b5060019392505050565b60055460ff1690565b7fb204bf10bc3a5435017d3db247f56da601dfe08a0002000000000000000000fe90565b600061040b6104db6106f0565b8461049285600160006104ec6106f0565b6001600160a01b03908116825260208083019390935260409182016000908120918c16815292529020549061068f565b600580546001600160a01b0390921661010002610100600160a81b0319909216919091179055565b6001600160a01b031660009081526020819052604090205490565b60055461010090046001600160a01b031690565b60048054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103ed5780601f106103c2576101008083540402835291602001916103ed565b600061040b6105e16106f0565b8461049285604051806060016040528060258152602001610ab4602591396001600061060b6106f0565b6001600160a01b03908116825260208083019390935260409182016000908120918d1681529252902054919061093b565b600061040b6106496106f0565b84846107e0565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b60055461010090046001600160a01b031681565b6000828201838110156106e9576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b3390565b6001600160a01b0383166107395760405162461bcd60e51b8152600401808060200182810382526024815260200180610a906024913960400191505060405180910390fd5b6001600160a01b03821661077e5760405162461bcd60e51b81526004018080602001828103825260228152602001806109fb6022913960400191505060405180910390fd5b6001600160a01b03808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b0383166108255760405162461bcd60e51b8152600401808060200182810382526025815260200180610a6b6025913960400191505060405180910390fd5b6001600160a01b03821661086a5760405162461bcd60e51b81526004018080602001828103825260238152602001806109d86023913960400191505060405180910390fd5b6108758383836109d2565b6108b281604051806060016040528060268152602001610a1d602691396001600160a01b038616600090815260208190526040902054919061093b565b6001600160a01b0380851660009081526020819052604080822093909355908416815220546108e1908261068f565b6001600160a01b038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600081848411156109ca5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561098f578181015183820152602001610977565b50505050905090810190601f1680156109bc5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b50505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa264697066735822122092787e3cbd971df817541c818008a4dc0435dc43da4a6c2f5e3e16fc0203cfd564736f6c63430007060033",
+    "linkReferences": {},
+    "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/BPTGauge.json b/apps/api/src/app/hardhat/export/BPTGauge.json
new file mode 100644
index 000000000..ddf9bd5ed
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/BPTGauge.json
@@ -0,0 +1,395 @@
+{
+    "_format": "hh-sol-artifact-1",
+    "contractName": "BPTGauge",
+    "sourceName": "contracts/mock/BPTGauge.sol",
+    "abi": [
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "_bpt",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "constructor"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "owner",
+                    "type": "address"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "value",
+                    "type": "uint256"
+                }
+            ],
+            "name": "Approval",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "user",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "value",
+                    "type": "uint256"
+                }
+            ],
+            "name": "Deposit",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "from",
+                    "type": "address"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "to",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "value",
+                    "type": "uint256"
+                }
+            ],
+            "name": "Transfer",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "user",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "value",
+                    "type": "uint256"
+                }
+            ],
+            "name": "Withdraw",
+            "type": "event"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "owner",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                }
+            ],
+            "name": "allowance",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "approve",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "account",
+                    "type": "address"
+                }
+            ],
+            "name": "balanceOf",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "bpt",
+            "outputs": [
+                {
+                    "internalType": "contract IERC20",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "decimals",
+            "outputs": [
+                {
+                    "internalType": "uint8",
+                    "name": "",
+                    "type": "uint8"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "subtractedValue",
+                    "type": "uint256"
+                }
+            ],
+            "name": "decreaseAllowance",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "_value",
+                    "type": "uint256"
+                }
+            ],
+            "name": "deposit",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "addedValue",
+                    "type": "uint256"
+                }
+            ],
+            "name": "increaseAllowance",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "lp_token",
+            "outputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "name",
+            "outputs": [
+                {
+                    "internalType": "string",
+                    "name": "",
+                    "type": "string"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "symbol",
+            "outputs": [
+                {
+                    "internalType": "string",
+                    "name": "",
+                    "type": "string"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "totalSupply",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "recipient",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "transfer",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "sender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "recipient",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "transferFrom",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "_value",
+                    "type": "uint256"
+                }
+            ],
+            "name": "withdraw",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "working_supply",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "pure",
+            "type": "function"
+        }
+    ],
+    "bytecode": "0x60806040523480156200001157600080fd5b5060405162000ff438038062000ff483398101604081905262000034916200018d565b60405180606001604052806023815260200162000fd160239139604051806040016040528060128152602001713230555344432d38305448582d676175676560701b815250816003908051906020019062000091929190620000e1565b508051620000a7906004906020840190620000e1565b5050600580546001600160a01b0390931661010002610100600160a81b031960ff19909416601217939093169290921790915550620001bd565b828054600181600116156101000203166002900490600052602060002090601f01602090048101928262000119576000855562000164565b82601f106200013457805160ff191683800117855562000164565b8280016001018555821562000164579182015b828111156200016457825182559160200191906001019062000147565b506200017292915062000176565b5090565b5b8082111562000172576000815560010162000177565b6000602082840312156200019f578081fd5b81516001600160a01b0381168114620001b6578182fd5b9392505050565b610e0480620001cd6000396000f3fe608060405234801561001057600080fd5b50600436106101005760003560e01c8063546af3c311610097578063a457c2d711610066578063a457c2d7146101e8578063a9059cbb146101fb578063b6b55f251461020e578063dd62ed3e1461022157610100565b8063546af3c3146101b057806370a08231146101c557806382c63066146101d857806395d89b41146101e057610100565b806323b872dd116100d357806323b872dd146101605780632e1a7d4d14610173578063313ce56714610188578063395093511461019d57610100565b806306fdde0314610105578063095ea7b31461012357806317e280891461014357806318160ddd14610158575b600080fd5b61010d610234565b60405161011a9190610c63565b60405180910390f35b610136610131366004610ba6565b6102ca565b60405161011a9190610c58565b61014b6102e7565b60405161011a9190610cb6565b61014b6102f5565b61013661016e366004610b6b565b6102fb565b610186610181366004610bef565b610382565b005b61019061045c565b60405161011a9190610cbf565b6101366101ab366004610ba6565b610465565b6101b86104b3565b60405161011a9190610c07565b61014b6101d3366004610b1f565b6104c7565b6101b86104e6565b61010d6104fa565b6101366101f6366004610ba6565b61055b565b610136610209366004610ba6565b6105c3565b61018661021c366004610bef565b6105d7565b61014b61022f366004610b39565b6106a5565b60038054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156102c05780601f10610295576101008083540402835291602001916102c0565b820191906000526020600020905b8154815290600101906020018083116102a357829003601f168201915b5050505050905090565b60006102de6102d76106d0565b84846106d4565b50600192915050565b697c1238ba7d2d4f8ef86190565b60025490565b60006103088484846107c0565b610378846103146106d0565b61037385604051806060016040528060288152602001610d39602891396001600160a01b038a166000908152600160205260408120906103526106d0565b6001600160a01b03168152602081019190915260400160002054919061091b565b6106d4565b5060019392505050565b60055460405163a9059cbb60e01b81526101009091046001600160a01b03169063a9059cbb906103b89033908590600401610c3f565b602060405180830381600087803b1580156103d257600080fd5b505af11580156103e6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061040a9190610bcf565b50610417336000836102fb565b50336001600160a01b03167f884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364826040516104519190610cb6565b60405180910390a250565b60055460ff1690565b60006102de6104726106d0565b8461037385600160006104836106d0565b6001600160a01b03908116825260208083019390935260409182016000908120918c1681529252902054906109b2565b60055461010090046001600160a01b031681565b6001600160a01b0381166000908152602081905260409020545b919050565b60055461010090046001600160a01b031690565b60048054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156102c05780601f10610295576101008083540402835291602001916102c0565b60006102de6105686106d0565b8461037385604051806060016040528060258152602001610daa60259139600160006105926106d0565b6001600160a01b03908116825260208083019390935260409182016000908120918d1681529252902054919061091b565b60006102de6105d06106d0565b84846107c0565b6005546040516323b872dd60e01b81526101009091046001600160a01b0316906323b872dd9061060f90339030908690600401610c1b565b602060405180830381600087803b15801561062957600080fd5b505af115801561063d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106619190610bcf565b5061066c3382610a13565b336001600160a01b03167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c826040516104519190610cb6565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b3390565b6001600160a01b0383166107195760405162461bcd60e51b8152600401808060200182810382526024815260200180610d866024913960400191505060405180910390fd5b6001600160a01b03821661075e5760405162461bcd60e51b8152600401808060200182810382526022815260200180610cf16022913960400191505060405180910390fd5b6001600160a01b03808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b0383166108055760405162461bcd60e51b8152600401808060200182810382526025815260200180610d616025913960400191505060405180910390fd5b6001600160a01b03821661084a5760405162461bcd60e51b8152600401808060200182810382526023815260200180610cce6023913960400191505060405180910390fd5b610855838383610b03565b61089281604051806060016040528060268152602001610d13602691396001600160a01b038616600090815260208190526040902054919061091b565b6001600160a01b0380851660009081526020819052604080822093909355908416815220546108c190826109b2565b6001600160a01b038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600081848411156109aa5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561096f578181015183820152602001610957565b50505050905090810190601f16801561099c5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b600082820183811015610a0c576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b6001600160a01b038216610a6e576040805162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b610a7a60008383610b03565b600254610a8790826109b2565b6002556001600160a01b038216600090815260208190526040902054610aad90826109b2565b6001600160a01b0383166000818152602081815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b505050565b80356001600160a01b03811681146104e157600080fd5b600060208284031215610b30578081fd5b610a0c82610b08565b60008060408385031215610b4b578081fd5b610b5483610b08565b9150610b6260208401610b08565b90509250929050565b600080600060608486031215610b7f578081fd5b610b8884610b08565b9250610b9660208501610b08565b9150604084013590509250925092565b60008060408385031215610bb8578182fd5b610bc183610b08565b946020939093013593505050565b600060208284031215610be0578081fd5b81518015158114610a0c578182fd5b600060208284031215610c00578081fd5b5035919050565b6001600160a01b0391909116815260200190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b03929092168252602082015260400190565b901515815260200190565b6000602080835283518082850152825b81811015610c8f57858101830151858201604001528201610c73565b81811115610ca05783604083870101525b50601f01601f1916929092016040019392505050565b90815260200190565b60ff9190911681526020019056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa264697066735822122069a94b4190fe4204521f185375175472fa777f0f59edf162489bbd1a5bdb764264736f6c6343000706003342616c616e636572203230555344432d3830544858204761756765204465706f736974",
+    "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106101005760003560e01c8063546af3c311610097578063a457c2d711610066578063a457c2d7146101e8578063a9059cbb146101fb578063b6b55f251461020e578063dd62ed3e1461022157610100565b8063546af3c3146101b057806370a08231146101c557806382c63066146101d857806395d89b41146101e057610100565b806323b872dd116100d357806323b872dd146101605780632e1a7d4d14610173578063313ce56714610188578063395093511461019d57610100565b806306fdde0314610105578063095ea7b31461012357806317e280891461014357806318160ddd14610158575b600080fd5b61010d610234565b60405161011a9190610c63565b60405180910390f35b610136610131366004610ba6565b6102ca565b60405161011a9190610c58565b61014b6102e7565b60405161011a9190610cb6565b61014b6102f5565b61013661016e366004610b6b565b6102fb565b610186610181366004610bef565b610382565b005b61019061045c565b60405161011a9190610cbf565b6101366101ab366004610ba6565b610465565b6101b86104b3565b60405161011a9190610c07565b61014b6101d3366004610b1f565b6104c7565b6101b86104e6565b61010d6104fa565b6101366101f6366004610ba6565b61055b565b610136610209366004610ba6565b6105c3565b61018661021c366004610bef565b6105d7565b61014b61022f366004610b39565b6106a5565b60038054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156102c05780601f10610295576101008083540402835291602001916102c0565b820191906000526020600020905b8154815290600101906020018083116102a357829003601f168201915b5050505050905090565b60006102de6102d76106d0565b84846106d4565b50600192915050565b697c1238ba7d2d4f8ef86190565b60025490565b60006103088484846107c0565b610378846103146106d0565b61037385604051806060016040528060288152602001610d39602891396001600160a01b038a166000908152600160205260408120906103526106d0565b6001600160a01b03168152602081019190915260400160002054919061091b565b6106d4565b5060019392505050565b60055460405163a9059cbb60e01b81526101009091046001600160a01b03169063a9059cbb906103b89033908590600401610c3f565b602060405180830381600087803b1580156103d257600080fd5b505af11580156103e6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061040a9190610bcf565b50610417336000836102fb565b50336001600160a01b03167f884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364826040516104519190610cb6565b60405180910390a250565b60055460ff1690565b60006102de6104726106d0565b8461037385600160006104836106d0565b6001600160a01b03908116825260208083019390935260409182016000908120918c1681529252902054906109b2565b60055461010090046001600160a01b031681565b6001600160a01b0381166000908152602081905260409020545b919050565b60055461010090046001600160a01b031690565b60048054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156102c05780601f10610295576101008083540402835291602001916102c0565b60006102de6105686106d0565b8461037385604051806060016040528060258152602001610daa60259139600160006105926106d0565b6001600160a01b03908116825260208083019390935260409182016000908120918d1681529252902054919061091b565b60006102de6105d06106d0565b84846107c0565b6005546040516323b872dd60e01b81526101009091046001600160a01b0316906323b872dd9061060f90339030908690600401610c1b565b602060405180830381600087803b15801561062957600080fd5b505af115801561063d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106619190610bcf565b5061066c3382610a13565b336001600160a01b03167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c826040516104519190610cb6565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b3390565b6001600160a01b0383166107195760405162461bcd60e51b8152600401808060200182810382526024815260200180610d866024913960400191505060405180910390fd5b6001600160a01b03821661075e5760405162461bcd60e51b8152600401808060200182810382526022815260200180610cf16022913960400191505060405180910390fd5b6001600160a01b03808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b0383166108055760405162461bcd60e51b8152600401808060200182810382526025815260200180610d616025913960400191505060405180910390fd5b6001600160a01b03821661084a5760405162461bcd60e51b8152600401808060200182810382526023815260200180610cce6023913960400191505060405180910390fd5b610855838383610b03565b61089281604051806060016040528060268152602001610d13602691396001600160a01b038616600090815260208190526040902054919061091b565b6001600160a01b0380851660009081526020819052604080822093909355908416815220546108c190826109b2565b6001600160a01b038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600081848411156109aa5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561096f578181015183820152602001610957565b50505050905090810190601f16801561099c5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b600082820183811015610a0c576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b6001600160a01b038216610a6e576040805162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b610a7a60008383610b03565b600254610a8790826109b2565b6002556001600160a01b038216600090815260208190526040902054610aad90826109b2565b6001600160a01b0383166000818152602081815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b505050565b80356001600160a01b03811681146104e157600080fd5b600060208284031215610b30578081fd5b610a0c82610b08565b60008060408385031215610b4b578081fd5b610b5483610b08565b9150610b6260208401610b08565b90509250929050565b600080600060608486031215610b7f578081fd5b610b8884610b08565b9250610b9660208501610b08565b9150604084013590509250925092565b60008060408385031215610bb8578182fd5b610bc183610b08565b946020939093013593505050565b600060208284031215610be0578081fd5b81518015158114610a0c578182fd5b600060208284031215610c00578081fd5b5035919050565b6001600160a01b0391909116815260200190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b03929092168252602082015260400190565b901515815260200190565b6000602080835283518082850152825b81811015610c8f57858101830151858201604001528201610c73565b81811115610ca05783604083870101525b50601f01601f1916929092016040019392505050565b90815260200190565b60ff9190911681526020019056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa264697066735822122069a94b4190fe4204521f185375175472fa777f0f59edf162489bbd1a5bdb764264736f6c63430007060033",
+    "linkReferences": {},
+    "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/BalancerGaugeController.json b/apps/api/src/app/hardhat/export/BalancerGaugeController.json
new file mode 100644
index 000000000..ac3ffebdb
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/BalancerGaugeController.json
@@ -0,0 +1,26 @@
+{
+    "abi": [
+        {
+            "constant": true,
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "addr",
+                    "type": "address"
+                }
+            ],
+            "name": "gauge_relative_weight",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "payable": false,
+            "stateMutability": "view",
+            "type": "function"
+        }
+    ],
+    "bytecode": ""
+}
diff --git a/apps/api/src/app/hardhat/export/BalancerMinter.json b/apps/api/src/app/hardhat/export/BalancerMinter.json
new file mode 100644
index 000000000..41f4bc83c
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/BalancerMinter.json
@@ -0,0 +1,73 @@
+{
+    "_format": "hh-sol-artifact-1",
+    "contractName": "BalancerMinter",
+    "sourceName": "contracts/mock/BalancerMinter.sol",
+    "abi": [
+        {
+            "inputs": [
+                {
+                    "internalType": "contract IERC20",
+                    "name": "_balToken",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "constructor"
+        },
+        {
+            "inputs": [],
+            "name": "balToken",
+            "outputs": [
+                {
+                    "internalType": "contract IERC20",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "name": "gaugeMultiplier",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "gauge",
+                    "type": "address"
+                }
+            ],
+            "name": "mint",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        }
+    ],
+    "bytecode": "0x60a060405234801561001057600080fd5b5060405161031e38038061031e83398101604081905261002f91610040565b6001600160a01b0316608052610070565b60006020828403121561005257600080fd5b81516001600160a01b038116811461006957600080fd5b9392505050565b60805161028d61009160003960008181604b0152610183015261028d6000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806338d54645146100465780636a6278421461008a578063f74f5a75146100ab575b600080fd5b61006d7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b61009d6100983660046101e9565b6100cb565b604051908152602001610081565b61009d6100b93660046101e9565b60006020819052908152604090205481565b60006100d782336100dd565b92915050565b6040516370a0823160e01b81526001600160a01b03828116600483015260009182918516906370a0823190602401602060405180830381865afa158015610128573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061014c9190610219565b9050600a61015a8183610232565b6040516340c10f1960e01b81526001600160a01b038681166004830152602482018390529194507f0000000000000000000000000000000000000000000000000000000000000000909116906340c10f1990604401600060405180830381600087803b1580156101c957600080fd5b505af11580156101dd573d6000803e3d6000fd5b50505050505092915050565b6000602082840312156101fb57600080fd5b81356001600160a01b038116811461021257600080fd5b9392505050565b60006020828403121561022b57600080fd5b5051919050565b80820281158282048414176100d757634e487b7160e01b600052601160045260246000fdfea26469706673582212202629fbd885bb989433d9b6bb75ed570fa59f837050fe85071e7afb8b8e38dc1d64736f6c63430008120033",
+    "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100415760003560e01c806338d54645146100465780636a6278421461008a578063f74f5a75146100ab575b600080fd5b61006d7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b61009d6100983660046101e9565b6100cb565b604051908152602001610081565b61009d6100b93660046101e9565b60006020819052908152604090205481565b60006100d782336100dd565b92915050565b6040516370a0823160e01b81526001600160a01b03828116600483015260009182918516906370a0823190602401602060405180830381865afa158015610128573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061014c9190610219565b9050600a61015a8183610232565b6040516340c10f1960e01b81526001600160a01b038681166004830152602482018390529194507f0000000000000000000000000000000000000000000000000000000000000000909116906340c10f1990604401600060405180830381600087803b1580156101c957600080fd5b505af11580156101dd573d6000803e3d6000fd5b50505050505092915050565b6000602082840312156101fb57600080fd5b81356001600160a01b038116811461021257600080fd5b9392505050565b60006020828403121561022b57600080fd5b5051919050565b80820281158282048414176100d757634e487b7160e01b600052601160045260246000fdfea26469706673582212202629fbd885bb989433d9b6bb75ed570fa59f837050fe85071e7afb8b8e38dc1d64736f6c63430008120033",
+    "linkReferences": {},
+    "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/BalancerVault.json b/apps/api/src/app/hardhat/export/BalancerVault.json
new file mode 100644
index 000000000..58d527d12
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/BalancerVault.json
@@ -0,0 +1,121 @@
+{
+    "_format": "hh-sol-artifact-1",
+    "contractName": "BalancerVault",
+    "sourceName": "contracts/mock/BalancerVault.sol",
+    "abi": [
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "_bpt",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "_usdc",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "_thx",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "constructor"
+        },
+        {
+            "inputs": [],
+            "name": "bpt",
+            "outputs": [
+                {
+                    "internalType": "contract ERC20",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "bytes32",
+                    "name": "poolId",
+                    "type": "bytes32"
+                },
+                {
+                    "internalType": "address",
+                    "name": "sender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "recipient",
+                    "type": "address"
+                },
+                {
+                    "components": [
+                        {
+                            "internalType": "address[]",
+                            "name": "assets",
+                            "type": "address[]"
+                        },
+                        {
+                            "internalType": "uint256[]",
+                            "name": "maxAmountsIn",
+                            "type": "uint256[]"
+                        },
+                        {
+                            "internalType": "bytes",
+                            "name": "userData",
+                            "type": "bytes"
+                        },
+                        {
+                            "internalType": "bool",
+                            "name": "fromInternalBalance",
+                            "type": "bool"
+                        }
+                    ],
+                    "internalType": "struct BalancerVault.JoinPoolRequest",
+                    "name": "request",
+                    "type": "tuple"
+                }
+            ],
+            "name": "joinPool",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "thx",
+            "outputs": [
+                {
+                    "internalType": "contract ERC20",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "usdc",
+            "outputs": [
+                {
+                    "internalType": "contract ERC20",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        }
+    ],
+    "bytecode": "0x608060405234801561001057600080fd5b50604051610a7a380380610a7a83398101604081905261002f9161008d565b600080546001600160a01b039485166001600160a01b0319918216179091556001805493851693821693909317909255600280549190931691161790556100cf565b80516001600160a01b038116811461008857600080fd5b919050565b6000806000606084860312156100a1578283fd5b6100aa84610071565b92506100b860208501610071565b91506100c660408501610071565b90509250925092565b61099c806100de6000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80633e413bee14610051578063546af3c31461006f578063a81d22e214610077578063b95cac281461007f575b600080fd5b610059610094565b604051610066919061089c565b60405180910390f35b6100596100a3565b6100596100b2565b61009261008d366004610751565b6100c1565b005b6001546001600160a01b031681565b6000546001600160a01b031681565b6002546001600160a01b031681565b600154602082015180516001600160a01b03909216916323b872dd9186913091906000906100eb57fe5b60200260200101516040518463ffffffff1660e01b81526004016101119392919061085f565b602060405180830381600087803b15801561012b57600080fd5b505af115801561013f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101639190610735565b50600254602082015180516001600160a01b03909216916323b872dd918691309190600190811061019057fe5b60200260200101516040518463ffffffff1660e01b81526004016101b69392919061085f565b602060405180830381600087803b1580156101d057600080fd5b505af11580156101e4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102089190610735565b5060006102c1600160009054906101000a90046001600160a01b03166001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b15801561025c57600080fd5b505afa158015610270573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610294919061083e565b60ff16600a0a83602001516000815181106102ab57fe5b60200260200101516104b990919063ffffffff16565b90506000610365600260009054906101000a90046001600160a01b03166001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b15801561031657600080fd5b505afa15801561032a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061034e919061083e565b60ff16600a0a84602001516001815181106102ab57fe5b9050600061040660008054906101000a90046001600160a01b03166001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b1580156103b857600080fd5b505afa1580156103cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103f0919061083e565b60ff16600a0a6104008585610522565b90610583565b60005460405163a9059cbb60e01b81529192506001600160a01b03169063a9059cbb906104399088908590600401610883565b602060405180830381600087803b15801561045357600080fd5b505af1158015610467573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061048b9190610735565b6104b05760405162461bcd60e51b81526004016104a7906108b0565b60405180910390fd5b50505050505050565b600080821161050f576040805162461bcd60e51b815260206004820152601a60248201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604482015290519081900360640190fd5b81838161051857fe5b0490505b92915050565b60008282018381101561057c576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b6000826105925750600061051c565b8282028284828161059f57fe5b041461057c5760405162461bcd60e51b81526004018080602001828103825260218152602001806109466021913960400191505060405180910390fd5b80356001600160a01b03811681146105f357600080fd5b919050565b600082601f830112610608578081fd5b8135602061061d61061883610916565b6108f2565b8281528181019085830183850287018401881015610639578586fd5b855b8581101561065e5761064c826105dc565b8452928401929084019060010161063b565b5090979650505050505050565b600082601f83011261067b578081fd5b8135602061068b61061883610916565b82815281810190858301838502870184018810156106a7578586fd5b855b8581101561065e578135845292840192908401906001016106a9565b80356105f381610934565b600082601f8301126106e0578081fd5b813567ffffffffffffffff8111156106f457fe5b610707601f8201601f19166020016108f2565b81815284602083860101111561071b578283fd5b816020850160208301379081016020019190915292915050565b600060208284031215610746578081fd5b815161057c81610934565b60008060008060808587031215610766578283fd5b84359350610776602086016105dc565b9250610784604086016105dc565b9150606085013567ffffffffffffffff808211156107a0578283fd5b90860190608082890312156107b3578283fd5b6107bd60806108f2565b8235828111156107cb578485fd5b6107d78a8286016105f8565b8252506020830135828111156107eb578485fd5b6107f78a82860161066b565b60208301525060408301358281111561080e578485fd5b61081a8a8286016106d0565b60408301525061082c606084016106c5565b60608201529598949750929550505050565b60006020828403121561084f578081fd5b815160ff8116811461057c578182fd5b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b03929092168252602082015260400190565b6001600160a01b0391909116815260200190565b60208082526022908201527f42616c616e6365725661756c743a20425054207472616e73666572206661696c604082015261195960f21b606082015260800190565b60405181810167ffffffffffffffff8111828210171561090e57fe5b604052919050565b600067ffffffffffffffff82111561092a57fe5b5060209081020190565b801515811461094257600080fd5b5056fe536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f77a26469706673582212200da6e89226c943a50bf8e722c06434037f552da74481e18a14dd1d7970f5716d64736f6c63430007060033",
+    "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80633e413bee14610051578063546af3c31461006f578063a81d22e214610077578063b95cac281461007f575b600080fd5b610059610094565b604051610066919061089c565b60405180910390f35b6100596100a3565b6100596100b2565b61009261008d366004610751565b6100c1565b005b6001546001600160a01b031681565b6000546001600160a01b031681565b6002546001600160a01b031681565b600154602082015180516001600160a01b03909216916323b872dd9186913091906000906100eb57fe5b60200260200101516040518463ffffffff1660e01b81526004016101119392919061085f565b602060405180830381600087803b15801561012b57600080fd5b505af115801561013f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101639190610735565b50600254602082015180516001600160a01b03909216916323b872dd918691309190600190811061019057fe5b60200260200101516040518463ffffffff1660e01b81526004016101b69392919061085f565b602060405180830381600087803b1580156101d057600080fd5b505af11580156101e4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102089190610735565b5060006102c1600160009054906101000a90046001600160a01b03166001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b15801561025c57600080fd5b505afa158015610270573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610294919061083e565b60ff16600a0a83602001516000815181106102ab57fe5b60200260200101516104b990919063ffffffff16565b90506000610365600260009054906101000a90046001600160a01b03166001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b15801561031657600080fd5b505afa15801561032a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061034e919061083e565b60ff16600a0a84602001516001815181106102ab57fe5b9050600061040660008054906101000a90046001600160a01b03166001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b1580156103b857600080fd5b505afa1580156103cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103f0919061083e565b60ff16600a0a6104008585610522565b90610583565b60005460405163a9059cbb60e01b81529192506001600160a01b03169063a9059cbb906104399088908590600401610883565b602060405180830381600087803b15801561045357600080fd5b505af1158015610467573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061048b9190610735565b6104b05760405162461bcd60e51b81526004016104a7906108b0565b60405180910390fd5b50505050505050565b600080821161050f576040805162461bcd60e51b815260206004820152601a60248201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604482015290519081900360640190fd5b81838161051857fe5b0490505b92915050565b60008282018381101561057c576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b6000826105925750600061051c565b8282028284828161059f57fe5b041461057c5760405162461bcd60e51b81526004018080602001828103825260218152602001806109466021913960400191505060405180910390fd5b80356001600160a01b03811681146105f357600080fd5b919050565b600082601f830112610608578081fd5b8135602061061d61061883610916565b6108f2565b8281528181019085830183850287018401881015610639578586fd5b855b8581101561065e5761064c826105dc565b8452928401929084019060010161063b565b5090979650505050505050565b600082601f83011261067b578081fd5b8135602061068b61061883610916565b82815281810190858301838502870184018810156106a7578586fd5b855b8581101561065e578135845292840192908401906001016106a9565b80356105f381610934565b600082601f8301126106e0578081fd5b813567ffffffffffffffff8111156106f457fe5b610707601f8201601f19166020016108f2565b81815284602083860101111561071b578283fd5b816020850160208301379081016020019190915292915050565b600060208284031215610746578081fd5b815161057c81610934565b60008060008060808587031215610766578283fd5b84359350610776602086016105dc565b9250610784604086016105dc565b9150606085013567ffffffffffffffff808211156107a0578283fd5b90860190608082890312156107b3578283fd5b6107bd60806108f2565b8235828111156107cb578485fd5b6107d78a8286016105f8565b8252506020830135828111156107eb578485fd5b6107f78a82860161066b565b60208301525060408301358281111561080e578485fd5b61081a8a8286016106d0565b60408301525061082c606084016106c5565b60608201529598949750929550505050565b60006020828403121561084f578081fd5b815160ff8116811461057c578182fd5b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b03929092168252602082015260400190565b6001600160a01b0391909116815260200190565b60208082526022908201527f42616c616e6365725661756c743a20425054207472616e73666572206661696c604082015261195960f21b606082015260800190565b60405181810167ffffffffffffffff8111828210171561090e57fe5b604052919050565b600067ffffffffffffffff82111561092a57fe5b5060209081020190565b801515811461094257600080fd5b5056fe536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f77a26469706673582212200da6e89226c943a50bf8e722c06434037f552da74481e18a14dd1d7970f5716d64736f6c63430007060033",
+    "linkReferences": {},
+    "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/CreateCall.json b/apps/api/src/app/hardhat/export/CreateCall.json
new file mode 100644
index 000000000..b1e6d2346
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/CreateCall.json
@@ -0,0 +1,77 @@
+{
+  "_format": "hh-sol-artifact-1",
+  "contractName": "CreateCall",
+  "sourceName": "@gnosis.pm/safe-contracts/contracts/libraries/CreateCall.sol",
+  "abi": [
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": false,
+          "internalType": "address",
+          "name": "newContract",
+          "type": "address"
+        }
+      ],
+      "name": "ContractCreation",
+      "type": "event"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        },
+        {
+          "internalType": "bytes",
+          "name": "deploymentData",
+          "type": "bytes"
+        }
+      ],
+      "name": "performCreate",
+      "outputs": [
+        {
+          "internalType": "address",
+          "name": "newContract",
+          "type": "address"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        },
+        {
+          "internalType": "bytes",
+          "name": "deploymentData",
+          "type": "bytes"
+        },
+        {
+          "internalType": "bytes32",
+          "name": "salt",
+          "type": "bytes32"
+        }
+      ],
+      "name": "performCreate2",
+      "outputs": [
+        {
+          "internalType": "address",
+          "name": "newContract",
+          "type": "address"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    }
+  ],
+  "bytecode": "0x608060405234801561001057600080fd5b50610334806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80634847be6f1461003b5780634c8c9ea11461006a575b600080fd5b61004e610049366004610267565b61007d565b6040516001600160a01b03909116815260200160405180910390f35b61004e6100783660046102b7565b610124565b60008183518460200186f590506001600160a01b0381166100e15760405162461bcd60e51b815260206004820152601960248201527810dbdd5b19081b9bdd0819195c1b1bde4818dbdb9d1c9858dd603a1b60448201526064015b60405180910390fd5b6040516001600160a01b03821681527f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b5119060200160405180910390a19392505050565b600081516020830184f090506001600160a01b0381166101825760405162461bcd60e51b815260206004820152601960248201527810dbdd5b19081b9bdd0819195c1b1bde4818dbdb9d1c9858dd603a1b60448201526064016100d8565b6040516001600160a01b03821681527f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b5119060200160405180910390a192915050565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126101eb57600080fd5b813567ffffffffffffffff80821115610206576102066101c4565b604051601f8301601f19908116603f0116810190828211818310171561022e5761022e6101c4565b8160405283815286602085880101111561024757600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008060006060848603121561027c57600080fd5b83359250602084013567ffffffffffffffff81111561029a57600080fd5b6102a6868287016101da565b925050604084013590509250925092565b600080604083850312156102ca57600080fd5b82359150602083013567ffffffffffffffff8111156102e857600080fd5b6102f4858286016101da565b915050925092905056fea264697066735822122051feddc2dceae06ddb39e8e1b325167c8789615579f6ceb3a7d1a1238b4c744864736f6c63430008180033",
+  "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80634847be6f1461003b5780634c8c9ea11461006a575b600080fd5b61004e610049366004610267565b61007d565b6040516001600160a01b03909116815260200160405180910390f35b61004e6100783660046102b7565b610124565b60008183518460200186f590506001600160a01b0381166100e15760405162461bcd60e51b815260206004820152601960248201527810dbdd5b19081b9bdd0819195c1b1bde4818dbdb9d1c9858dd603a1b60448201526064015b60405180910390fd5b6040516001600160a01b03821681527f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b5119060200160405180910390a19392505050565b600081516020830184f090506001600160a01b0381166101825760405162461bcd60e51b815260206004820152601960248201527810dbdd5b19081b9bdd0819195c1b1bde4818dbdb9d1c9858dd603a1b60448201526064016100d8565b6040516001600160a01b03821681527f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b5119060200160405180910390a192915050565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126101eb57600080fd5b813567ffffffffffffffff80821115610206576102066101c4565b604051601f8301601f19908116603f0116810190828211818310171561022e5761022e6101c4565b8160405283815286602085880101111561024757600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008060006060848603121561027c57600080fd5b83359250602084013567ffffffffffffffff81111561029a57600080fd5b6102a6868287016101da565b925050604084013590509250925092565b600080604083850312156102ca57600080fd5b82359150602083013567ffffffffffffffff8111156102e857600080fd5b6102f4858286016101da565b915050925092905056fea264697066735822122051feddc2dceae06ddb39e8e1b325167c8789615579f6ceb3a7d1a1238b4c744864736f6c63430008180033",
+  "linkReferences": {},
+  "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/DefaultCallbackHandler.json b/apps/api/src/app/hardhat/export/DefaultCallbackHandler.json
new file mode 100644
index 000000000..e683b3764
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/DefaultCallbackHandler.json
@@ -0,0 +1,206 @@
+{
+  "_format": "hh-sol-artifact-1",
+  "contractName": "DefaultCallbackHandler",
+  "sourceName": "@gnosis.pm/safe-contracts/contracts/handler/DefaultCallbackHandler.sol",
+  "abi": [
+    {
+      "inputs": [],
+      "name": "NAME",
+      "outputs": [
+        {
+          "internalType": "string",
+          "name": "",
+          "type": "string"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "VERSION",
+      "outputs": [
+        {
+          "internalType": "string",
+          "name": "",
+          "type": "string"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256[]",
+          "name": "",
+          "type": "uint256[]"
+        },
+        {
+          "internalType": "uint256[]",
+          "name": "",
+          "type": "uint256[]"
+        },
+        {
+          "internalType": "bytes",
+          "name": "",
+          "type": "bytes"
+        }
+      ],
+      "name": "onERC1155BatchReceived",
+      "outputs": [
+        {
+          "internalType": "bytes4",
+          "name": "",
+          "type": "bytes4"
+        }
+      ],
+      "stateMutability": "pure",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        },
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        },
+        {
+          "internalType": "bytes",
+          "name": "",
+          "type": "bytes"
+        }
+      ],
+      "name": "onERC1155Received",
+      "outputs": [
+        {
+          "internalType": "bytes4",
+          "name": "",
+          "type": "bytes4"
+        }
+      ],
+      "stateMutability": "pure",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        },
+        {
+          "internalType": "bytes",
+          "name": "",
+          "type": "bytes"
+        }
+      ],
+      "name": "onERC721Received",
+      "outputs": [
+        {
+          "internalType": "bytes4",
+          "name": "",
+          "type": "bytes4"
+        }
+      ],
+      "stateMutability": "pure",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes4",
+          "name": "interfaceId",
+          "type": "bytes4"
+        }
+      ],
+      "name": "supportsInterface",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        },
+        {
+          "internalType": "bytes",
+          "name": "",
+          "type": "bytes"
+        },
+        {
+          "internalType": "bytes",
+          "name": "",
+          "type": "bytes"
+        }
+      ],
+      "name": "tokensReceived",
+      "outputs": [],
+      "stateMutability": "pure",
+      "type": "function"
+    }
+  ],
+  "bytecode": "0x608060405234801561001057600080fd5b50610588806100206000396000f3fe608060405234801561001057600080fd5b506004361061007c5760003560e01c8063a3f4df7e1161005b578063a3f4df7e146100fb578063bc197c8114610144578063f23a6e6114610166578063ffa1ad741461018657600080fd5b806223de291461008157806301ffc9a71461009b578063150b7a02146100c3575b600080fd5b61009961008f366004610261565b5050505050505050565b005b6100ae6100a936600461030c565b6101aa565b60405190151581526020015b60405180910390f35b6100e26100d136600461033d565b630a85bd0160e11b95945050505050565b6040516001600160e01b031990911681526020016100ba565b6101376040518060400160405280601881526020017f44656661756c742043616c6c6261636b2048616e646c6572000000000000000081525081565b6040516100ba91906103ac565b6100e2610152366004610440565b63bc197c8160e01b98975050505050505050565b6100e26101743660046104da565b63f23a6e6160e01b9695505050505050565b610137604051806040016040528060058152602001640312e302e360dc1b81525081565b60006001600160e01b03198216630271189760e51b14806101db57506001600160e01b03198216630a85bd0160e11b145b806101f657506001600160e01b031982166301ffc9a760e01b145b92915050565b80356001600160a01b038116811461021357600080fd5b919050565b60008083601f84011261022a57600080fd5b50813567ffffffffffffffff81111561024257600080fd5b60208301915083602082850101111561025a57600080fd5b9250929050565b60008060008060008060008060c0898b03121561027d57600080fd5b610286896101fc565b975061029460208a016101fc565b96506102a260408a016101fc565b955060608901359450608089013567ffffffffffffffff808211156102c657600080fd5b6102d28c838d01610218565b909650945060a08b01359150808211156102eb57600080fd5b506102f88b828c01610218565b999c989b5096995094979396929594505050565b60006020828403121561031e57600080fd5b81356001600160e01b03198116811461033657600080fd5b9392505050565b60008060008060006080868803121561035557600080fd5b61035e866101fc565b945061036c602087016101fc565b935060408601359250606086013567ffffffffffffffff81111561038f57600080fd5b61039b88828901610218565b969995985093965092949392505050565b60006020808352835180602085015260005b818110156103da578581018301518582016040015282016103be565b506000604082860101526040601f19601f8301168501019250505092915050565b60008083601f84011261040d57600080fd5b50813567ffffffffffffffff81111561042557600080fd5b6020830191508360208260051b850101111561025a57600080fd5b60008060008060008060008060a0898b03121561045c57600080fd5b610465896101fc565b975061047360208a016101fc565b9650604089013567ffffffffffffffff8082111561049057600080fd5b61049c8c838d016103fb565b909850965060608b01359150808211156104b557600080fd5b6104c18c838d016103fb565b909650945060808b01359150808211156102eb57600080fd5b60008060008060008060a087890312156104f357600080fd5b6104fc876101fc565b955061050a602088016101fc565b94506040870135935060608701359250608087013567ffffffffffffffff81111561053457600080fd5b61054089828a01610218565b979a969950949750929593949250505056fea2646970667358221220de19c10401b871e3bdbe864bca901a1383323ae0bca79d2027035d67f8ecb85064736f6c63430008180033",
+  "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061007c5760003560e01c8063a3f4df7e1161005b578063a3f4df7e146100fb578063bc197c8114610144578063f23a6e6114610166578063ffa1ad741461018657600080fd5b806223de291461008157806301ffc9a71461009b578063150b7a02146100c3575b600080fd5b61009961008f366004610261565b5050505050505050565b005b6100ae6100a936600461030c565b6101aa565b60405190151581526020015b60405180910390f35b6100e26100d136600461033d565b630a85bd0160e11b95945050505050565b6040516001600160e01b031990911681526020016100ba565b6101376040518060400160405280601881526020017f44656661756c742043616c6c6261636b2048616e646c6572000000000000000081525081565b6040516100ba91906103ac565b6100e2610152366004610440565b63bc197c8160e01b98975050505050505050565b6100e26101743660046104da565b63f23a6e6160e01b9695505050505050565b610137604051806040016040528060058152602001640312e302e360dc1b81525081565b60006001600160e01b03198216630271189760e51b14806101db57506001600160e01b03198216630a85bd0160e11b145b806101f657506001600160e01b031982166301ffc9a760e01b145b92915050565b80356001600160a01b038116811461021357600080fd5b919050565b60008083601f84011261022a57600080fd5b50813567ffffffffffffffff81111561024257600080fd5b60208301915083602082850101111561025a57600080fd5b9250929050565b60008060008060008060008060c0898b03121561027d57600080fd5b610286896101fc565b975061029460208a016101fc565b96506102a260408a016101fc565b955060608901359450608089013567ffffffffffffffff808211156102c657600080fd5b6102d28c838d01610218565b909650945060a08b01359150808211156102eb57600080fd5b506102f88b828c01610218565b999c989b5096995094979396929594505050565b60006020828403121561031e57600080fd5b81356001600160e01b03198116811461033657600080fd5b9392505050565b60008060008060006080868803121561035557600080fd5b61035e866101fc565b945061036c602087016101fc565b935060408601359250606086013567ffffffffffffffff81111561038f57600080fd5b61039b88828901610218565b969995985093965092949392505050565b60006020808352835180602085015260005b818110156103da578581018301518582016040015282016103be565b506000604082860101526040601f19601f8301168501019250505092915050565b60008083601f84011261040d57600080fd5b50813567ffffffffffffffff81111561042557600080fd5b6020830191508360208260051b850101111561025a57600080fd5b60008060008060008060008060a0898b03121561045c57600080fd5b610465896101fc565b975061047360208a016101fc565b9650604089013567ffffffffffffffff8082111561049057600080fd5b61049c8c838d016103fb565b909850965060608b01359150808211156104b557600080fd5b6104c18c838d016103fb565b909650945060808b01359150808211156102eb57600080fd5b60008060008060008060a087890312156104f357600080fd5b6104fc876101fc565b955061050a602088016101fc565b94506040870135935060608701359250608087013567ffffffffffffffff81111561053457600080fd5b61054089828a01610218565b979a969950949750929593949250505056fea2646970667358221220de19c10401b871e3bdbe864bca901a1383323ae0bca79d2027035d67f8ecb85064736f6c63430008180033",
+  "linkReferences": {},
+  "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/GnosisSafeL2.json b/apps/api/src/app/hardhat/export/GnosisSafeL2.json
new file mode 100644
index 000000000..859225a6f
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/GnosisSafeL2.json
@@ -0,0 +1,1147 @@
+{
+  "_format": "hh-sol-artifact-1",
+  "contractName": "GnosisSafeL2",
+  "sourceName": "@gnosis.pm/safe-contracts/contracts/GnosisSafeL2.sol",
+  "abi": [
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": false,
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        }
+      ],
+      "name": "AddedOwner",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "bytes32",
+          "name": "approvedHash",
+          "type": "bytes32"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        }
+      ],
+      "name": "ApproveHash",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": false,
+          "internalType": "address",
+          "name": "handler",
+          "type": "address"
+        }
+      ],
+      "name": "ChangedFallbackHandler",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": false,
+          "internalType": "address",
+          "name": "guard",
+          "type": "address"
+        }
+      ],
+      "name": "ChangedGuard",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "threshold",
+          "type": "uint256"
+        }
+      ],
+      "name": "ChangedThreshold",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": false,
+          "internalType": "address",
+          "name": "module",
+          "type": "address"
+        }
+      ],
+      "name": "DisabledModule",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": false,
+          "internalType": "address",
+          "name": "module",
+          "type": "address"
+        }
+      ],
+      "name": "EnabledModule",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": false,
+          "internalType": "bytes32",
+          "name": "txHash",
+          "type": "bytes32"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "payment",
+          "type": "uint256"
+        }
+      ],
+      "name": "ExecutionFailure",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "module",
+          "type": "address"
+        }
+      ],
+      "name": "ExecutionFromModuleFailure",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "module",
+          "type": "address"
+        }
+      ],
+      "name": "ExecutionFromModuleSuccess",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": false,
+          "internalType": "bytes32",
+          "name": "txHash",
+          "type": "bytes32"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "payment",
+          "type": "uint256"
+        }
+      ],
+      "name": "ExecutionSuccess",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": false,
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        }
+      ],
+      "name": "RemovedOwner",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": false,
+          "internalType": "address",
+          "name": "module",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        },
+        {
+          "indexed": false,
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        },
+        {
+          "indexed": false,
+          "internalType": "enum Enum.Operation",
+          "name": "operation",
+          "type": "uint8"
+        }
+      ],
+      "name": "SafeModuleTransaction",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": false,
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        },
+        {
+          "indexed": false,
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        },
+        {
+          "indexed": false,
+          "internalType": "enum Enum.Operation",
+          "name": "operation",
+          "type": "uint8"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "safeTxGas",
+          "type": "uint256"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "baseGas",
+          "type": "uint256"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "gasPrice",
+          "type": "uint256"
+        },
+        {
+          "indexed": false,
+          "internalType": "address",
+          "name": "gasToken",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "address payable",
+          "name": "refundReceiver",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "bytes",
+          "name": "signatures",
+          "type": "bytes"
+        },
+        {
+          "indexed": false,
+          "internalType": "bytes",
+          "name": "additionalInfo",
+          "type": "bytes"
+        }
+      ],
+      "name": "SafeMultiSigTransaction",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "sender",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        }
+      ],
+      "name": "SafeReceived",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "initiator",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "address[]",
+          "name": "owners",
+          "type": "address[]"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "threshold",
+          "type": "uint256"
+        },
+        {
+          "indexed": false,
+          "internalType": "address",
+          "name": "initializer",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "address",
+          "name": "fallbackHandler",
+          "type": "address"
+        }
+      ],
+      "name": "SafeSetup",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "bytes32",
+          "name": "msgHash",
+          "type": "bytes32"
+        }
+      ],
+      "name": "SignMsg",
+      "type": "event"
+    },
+    {
+      "stateMutability": "nonpayable",
+      "type": "fallback"
+    },
+    {
+      "inputs": [],
+      "name": "VERSION",
+      "outputs": [
+        {
+          "internalType": "string",
+          "name": "",
+          "type": "string"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "_threshold",
+          "type": "uint256"
+        }
+      ],
+      "name": "addOwnerWithThreshold",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "hashToApprove",
+          "type": "bytes32"
+        }
+      ],
+      "name": "approveHash",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "",
+          "type": "address"
+        },
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        }
+      ],
+      "name": "approvedHashes",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint256",
+          "name": "_threshold",
+          "type": "uint256"
+        }
+      ],
+      "name": "changeThreshold",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "dataHash",
+          "type": "bytes32"
+        },
+        {
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        },
+        {
+          "internalType": "bytes",
+          "name": "signatures",
+          "type": "bytes"
+        },
+        {
+          "internalType": "uint256",
+          "name": "requiredSignatures",
+          "type": "uint256"
+        }
+      ],
+      "name": "checkNSignatures",
+      "outputs": [],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "dataHash",
+          "type": "bytes32"
+        },
+        {
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        },
+        {
+          "internalType": "bytes",
+          "name": "signatures",
+          "type": "bytes"
+        }
+      ],
+      "name": "checkSignatures",
+      "outputs": [],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "prevModule",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "module",
+          "type": "address"
+        }
+      ],
+      "name": "disableModule",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "domainSeparator",
+      "outputs": [
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "module",
+          "type": "address"
+        }
+      ],
+      "name": "enableModule",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        },
+        {
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        },
+        {
+          "internalType": "enum Enum.Operation",
+          "name": "operation",
+          "type": "uint8"
+        },
+        {
+          "internalType": "uint256",
+          "name": "safeTxGas",
+          "type": "uint256"
+        },
+        {
+          "internalType": "uint256",
+          "name": "baseGas",
+          "type": "uint256"
+        },
+        {
+          "internalType": "uint256",
+          "name": "gasPrice",
+          "type": "uint256"
+        },
+        {
+          "internalType": "address",
+          "name": "gasToken",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "refundReceiver",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "_nonce",
+          "type": "uint256"
+        }
+      ],
+      "name": "encodeTransactionData",
+      "outputs": [
+        {
+          "internalType": "bytes",
+          "name": "",
+          "type": "bytes"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        },
+        {
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        },
+        {
+          "internalType": "enum Enum.Operation",
+          "name": "operation",
+          "type": "uint8"
+        },
+        {
+          "internalType": "uint256",
+          "name": "safeTxGas",
+          "type": "uint256"
+        },
+        {
+          "internalType": "uint256",
+          "name": "baseGas",
+          "type": "uint256"
+        },
+        {
+          "internalType": "uint256",
+          "name": "gasPrice",
+          "type": "uint256"
+        },
+        {
+          "internalType": "address",
+          "name": "gasToken",
+          "type": "address"
+        },
+        {
+          "internalType": "address payable",
+          "name": "refundReceiver",
+          "type": "address"
+        },
+        {
+          "internalType": "bytes",
+          "name": "signatures",
+          "type": "bytes"
+        }
+      ],
+      "name": "execTransaction",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "payable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        },
+        {
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        },
+        {
+          "internalType": "enum Enum.Operation",
+          "name": "operation",
+          "type": "uint8"
+        }
+      ],
+      "name": "execTransactionFromModule",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "success",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        },
+        {
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        },
+        {
+          "internalType": "enum Enum.Operation",
+          "name": "operation",
+          "type": "uint8"
+        }
+      ],
+      "name": "execTransactionFromModuleReturnData",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "success",
+          "type": "bool"
+        },
+        {
+          "internalType": "bytes",
+          "name": "returnData",
+          "type": "bytes"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "getChainId",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "start",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "pageSize",
+          "type": "uint256"
+        }
+      ],
+      "name": "getModulesPaginated",
+      "outputs": [
+        {
+          "internalType": "address[]",
+          "name": "array",
+          "type": "address[]"
+        },
+        {
+          "internalType": "address",
+          "name": "next",
+          "type": "address"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "getOwners",
+      "outputs": [
+        {
+          "internalType": "address[]",
+          "name": "",
+          "type": "address[]"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint256",
+          "name": "offset",
+          "type": "uint256"
+        },
+        {
+          "internalType": "uint256",
+          "name": "length",
+          "type": "uint256"
+        }
+      ],
+      "name": "getStorageAt",
+      "outputs": [
+        {
+          "internalType": "bytes",
+          "name": "",
+          "type": "bytes"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "getThreshold",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        },
+        {
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        },
+        {
+          "internalType": "enum Enum.Operation",
+          "name": "operation",
+          "type": "uint8"
+        },
+        {
+          "internalType": "uint256",
+          "name": "safeTxGas",
+          "type": "uint256"
+        },
+        {
+          "internalType": "uint256",
+          "name": "baseGas",
+          "type": "uint256"
+        },
+        {
+          "internalType": "uint256",
+          "name": "gasPrice",
+          "type": "uint256"
+        },
+        {
+          "internalType": "address",
+          "name": "gasToken",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "refundReceiver",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "_nonce",
+          "type": "uint256"
+        }
+      ],
+      "name": "getTransactionHash",
+      "outputs": [
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "module",
+          "type": "address"
+        }
+      ],
+      "name": "isModuleEnabled",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        }
+      ],
+      "name": "isOwner",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "nonce",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "prevOwner",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "_threshold",
+          "type": "uint256"
+        }
+      ],
+      "name": "removeOwner",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        },
+        {
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        },
+        {
+          "internalType": "enum Enum.Operation",
+          "name": "operation",
+          "type": "uint8"
+        }
+      ],
+      "name": "requiredTxGas",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "handler",
+          "type": "address"
+        }
+      ],
+      "name": "setFallbackHandler",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "guard",
+          "type": "address"
+        }
+      ],
+      "name": "setGuard",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address[]",
+          "name": "_owners",
+          "type": "address[]"
+        },
+        {
+          "internalType": "uint256",
+          "name": "_threshold",
+          "type": "uint256"
+        },
+        {
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        },
+        {
+          "internalType": "address",
+          "name": "fallbackHandler",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "paymentToken",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "payment",
+          "type": "uint256"
+        },
+        {
+          "internalType": "address payable",
+          "name": "paymentReceiver",
+          "type": "address"
+        }
+      ],
+      "name": "setup",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        }
+      ],
+      "name": "signedMessages",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "targetContract",
+          "type": "address"
+        },
+        {
+          "internalType": "bytes",
+          "name": "calldataPayload",
+          "type": "bytes"
+        }
+      ],
+      "name": "simulateAndRevert",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "prevOwner",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "oldOwner",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "newOwner",
+          "type": "address"
+        }
+      ],
+      "name": "swapOwner",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "stateMutability": "payable",
+      "type": "receive"
+    }
+  ],
+  "bytecode": "0x608060405234801561001057600080fd5b5060016004556131be806100256000396000f3fe6080604052600436106101dc5760003560e01c8063affed0e011610102578063e19a9dd911610095578063f08a032311610064578063f08a032314610620578063f698da2514610640578063f8dc5dd914610655578063ffa1ad741461067557610218565b8063e19a9dd9146105ab578063e318b52b146105cb578063e75235b8146105eb578063e86637db1461060057610218565b8063cc2f8452116100d1578063cc2f84521461051d578063d4d9bdcd1461054b578063d8d11f781461056b578063e009cfde1461058b57610218565b8063affed0e0146104a7578063b4faba09146104bd578063b63e800d146104dd578063c4ca3a9c146104fd57610218565b80635624b25b1161017a5780636a761202116101495780636a7612021461041a5780637d8329741461042d578063934f3a1114610465578063a0e67e2b1461048557610218565b80635624b25b146103805780635ae6bd37146103ad578063610b5925146103da578063694e80c3146103fa57610218565b80632f54bf6e116101b65780632f54bf6e146102f55780633408e47014610315578063468721a7146103325780635229073f1461035257610218565b80630d582f131461027e57806312fb68e0146102a05780632d9ad53d146102c057610218565b366102185760405134815233907f3d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3d9060200160405180910390a2005b34801561022457600080fd5b507f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d580548061024f57005b36600080373360601b365260008060143601600080855af190503d6000803e80610278573d6000fd5b503d6000f35b34801561028a57600080fd5b5061029e610299366004612581565b6106a6565b005b3480156102ac57600080fd5b5061029e6102bb36600461264f565b610806565b3480156102cc57600080fd5b506102e06102db3660046126c3565b610c68565b60405190151581526020015b60405180910390f35b34801561030157600080fd5b506102e06103103660046126c3565b610ca3565b34801561032157600080fd5b50465b6040519081526020016102ec565b34801561033e57600080fd5b506102e061034d3660046126ef565b610cdb565b34801561035e57600080fd5b5061037261036d3660046126ef565b610d31565b6040516102ec92919061279e565b34801561038c57600080fd5b506103a061039b3660046127b9565b610d67565b6040516102ec91906127db565b3480156103b957600080fd5b506103246103c83660046127ee565b60076020526000908152604090205481565b3480156103e657600080fd5b5061029e6103f53660046126c3565b610de2565b34801561040657600080fd5b5061029e6104153660046127ee565b610f24565b6102e061042836600461284f565b610fbc565b34801561043957600080fd5b50610324610448366004612581565b600860209081526000928352604080842090915290825290205481565b34801561047157600080fd5b5061029e610480366004612927565b611064565b34801561049157600080fd5b5061049a6110ae565b6040516102ec91906129d8565b3480156104b357600080fd5b5061032460055481565b3480156104c957600080fd5b5061029e6104d83660046129eb565b61119e565b3480156104e957600080fd5b5061029e6104f8366004612a3a565b6111c1565b34801561050957600080fd5b50610324610518366004612b2e565b6112e2565b34801561052957600080fd5b5061053d610538366004612581565b61137c565b6040516102ec929190612b9e565b34801561055757600080fd5b5061029e6105663660046127ee565b611475565b34801561057757600080fd5b50610324610586366004612bc8565b61150a565b34801561059757600080fd5b5061029e6105a6366004612c88565b611537565b3480156105b757600080fd5b5061029e6105c63660046126c3565b611666565b3480156105d757600080fd5b5061029e6105e6366004612cc1565b6116cb565b3480156105f757600080fd5b50600454610324565b34801561060c57600080fd5b506103a061061b366004612bc8565b6118ba565b34801561062c57600080fd5b5061029e61063b3660046126c3565b611993565b34801561064c57600080fd5b506103246119fc565b34801561066157600080fd5b5061029e610670366004612d0c565b611a53565b34801561068157600080fd5b506103a0604051806040016040528060058152602001640312e332e360dc1b81525081565b6106ae611bc6565b6001600160a01b038216158015906106d057506001600160a01b038216600114155b80156106e557506001600160a01b0382163014155b61070a5760405162461bcd60e51b815260040161070190612d4d565b60405180910390fd5b6001600160a01b0382811660009081526002602052604090205416156107425760405162461bcd60e51b815260040161070190612d6c565b60026020527fe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e080546001600160a01b038481166000818152604081208054939094166001600160a01b0319938416179093556001835283549091161790915560038054916107af83612da1565b90915550506040516001600160a01b03831681527f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea269060200160405180910390a180600454146108025761080281610f24565b5050565b610811816041611bff565b825110156108495760405162461bcd60e51b8152602060048201526005602482015264047533032360dc1b6044820152606401610701565b6000808060008060005b86811015610c5c576041818102890160208101516040820151919092015160ff16955090935091506000849003610a24579193508391610894876041611bff565b8210156108cb5760405162461bcd60e51b8152602060048201526005602482015264475330323160d81b6044820152606401610701565b87516108d8836020611c3b565b111561090e5760405162461bcd60e51b815260206004820152600560248201526423a998191960d91b6044820152606401610701565b60208289018101518951909161093190839061092b908790611c3b565b90611c3b565b11156109675760405162461bcd60e51b8152602060048201526005602482015264475330323360d81b6044820152606401610701565b6040516320c13b0b60e01b8082528a8501602001916001600160a01b038916906320c13b0b9061099d908f908690600401612dba565b602060405180830381865afa1580156109ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109de9190612ddf565b6001600160e01b03191614610a1d5760405162461bcd60e51b815260206004820152600560248201526411d4cc0c8d60da1b6044820152606401610701565b5050610bcb565b8360ff16600103610aa6579193508391336001600160a01b0384161480610a6d57506001600160a01b03851660009081526008602090815260408083208d845290915290205415155b610aa15760405162461bcd60e51b8152602060048201526005602482015264475330323560d81b6044820152606401610701565b610bcb565b601e8460ff161115610b6b576040517f19457468657265756d205369676e6564204d6573736167653a0a3332000000006020820152603c81018b9052600190605c0160405160208183030381529060405280519060200120600486610b0b9190612e09565b6040805160008152602081018083529390935260ff90911690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015610b5a573d6000803e3d6000fd5b505050602060405103519450610bcb565b6040805160008152602081018083528c905260ff861691810191909152606081018490526080810183905260019060a0016020604051602081039080840390855afa158015610bbe573d6000803e3d6000fd5b5050506020604051035194505b856001600160a01b0316856001600160a01b0316118015610c0557506001600160a01b038581166000908152600260205260409020541615155b8015610c1b57506001600160a01b038516600114155b610c4f5760405162461bcd60e51b815260206004820152600560248201526423a998191b60d91b6044820152606401610701565b9394508493600101610853565b50505050505050505050565b600060016001600160a01b03831614801590610c9d57506001600160a01b038281166000908152600160205260409020541615155b92915050565b60006001600160a01b038216600114801590610c9d5750506001600160a01b0390811660009081526002602052604090205416151590565b60007fb648d3644f584ed1c2232d53c46d87e693586486ad0d1175f8656013110b714e3386868686604051610d14959493929190612e5a565b60405180910390a1610d2885858585611c57565b95945050505050565b60006060610d4186868686610cdb565b915060405160203d0181016040523d81523d6000602083013e8091505094509492505050565b60606000610d76836020612ea6565b6001600160401b03811115610d8d57610d8d6125ad565b6040519080825280601f01601f191660200182016040528015610db7576020820181803683370190505b50905060005b83811015610dda5784810154602080830284010152600101610dbd565b509392505050565b610dea611bc6565b6001600160a01b03811615801590610e0c57506001600160a01b038116600114155b610e405760405162461bcd60e51b8152602060048201526005602482015264475331303160d81b6044820152606401610701565b6001600160a01b038181166000908152600160205260409020541615610e905760405162461bcd60e51b815260206004820152600560248201526423a998981960d91b6044820152606401610701565b600160208181527fcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f80546001600160a01b03858116600081815260408082208054949095166001600160a01b031994851617909455959095528254168417909155519182527fecdf3a3effea5783a3c4c2140e677577666428d44ed9d474a0b3a4c9943f844091015b60405180910390a150565b610f2c611bc6565b600354811115610f4e5760405162461bcd60e51b815260040161070190612ebd565b6001811015610f875760405162461bcd60e51b815260206004820152600560248201526423a999181960d91b6044820152606401610701565b60048190556040518181527f610f7ff2b304ae8903c3de74c60c6ab1f7d6226b3f52c5161905bb5ad4039c9390602001610f19565b600554600454604080516020810193909352339083015260608281019190915260009160800160405160208183030381529060405290507f66753cd2356569ee081232e3be8909b950e0a76c1f8460c3a5e3c2be32b11bed8d8d8d8d8d8d8d8d8d8d8d8c6040516110389c9b9a99989796959493929190612f05565b60405180910390a16110538d8d8d8d8d8d8d8d8d8d8d611d2e565b9d9c50505050505050505050505050565b6004548061109c5760405162461bcd60e51b8152602060048201526005602482015264475330303160d81b6044820152606401610701565b6110a884848484610806565b50505050565b606060006003546001600160401b038111156110cc576110cc6125ad565b6040519080825280602002602001820160405280156110f5578160200160208202803683370190505b506001600090815260026020527fe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e054919250906001600160a01b03165b6001600160a01b038116600114611196578083838151811061115657611156612f9c565b6001600160a01b0392831660209182029290920181019190915291811660009081526002909252604090912054168161118e81612da1565b925050611132565b509092915050565b600080825160208401855af480600052503d6020523d600060403e60403d016000fd5b6111ff8a8a808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508c9250612077915050565b6001600160a01b0384161561123657611236847f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d555565b6112768787878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061225392505050565b811561128d5761128b8260006001868561234d565b505b336001600160a01b03167f141df868a6331af528e38c83b7aa03edc19be66e37ae67f9285bf4f8e3c6a1a88b8b8b8b896040516112ce959493929190612fb2565b60405180910390a250505050505050505050565b6000805a905061132b878787878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525089925050505a612453565b61133457600080fd5b60005a611341908361301e565b90508060405160200161135691815260200190565b60408051601f198184030181529082905262461bcd60e51b8252610701916004016127db565b60606000826001600160401b03811115611398576113986125ad565b6040519080825280602002602001820160405280156113c1578160200160208202803683370190505b506001600160a01b0380861660009081526001602052604081205492945091165b6001600160a01b0381161580159061140457506001600160a01b038116600114155b801561140f57508482105b15611467578084838151811061142757611427612f9c565b6001600160a01b0392831660209182029290920181019190915291811660009081526001909252604090912054168161145f81612da1565b9250506113e2565b908352919491935090915050565b336000908152600260205260409020546001600160a01b03166114c25760405162461bcd60e51b8152602060048201526005602482015264047533033360dc1b6044820152606401610701565b336000818152600860209081526040808320858452909152808220600190555183917ff2a0eb156472d1440255b0d7c1e19cc07115d1051fe605b0dce69acfec884d9c91a350565b600061151f8c8c8c8c8c8c8c8c8c8c8c6118ba565b8051906020012090509b9a5050505050505050505050565b61153f611bc6565b6001600160a01b0381161580159061156157506001600160a01b038116600114155b6115955760405162461bcd60e51b8152602060048201526005602482015264475331303160d81b6044820152606401610701565b6001600160a01b038281166000908152600160205260409020548116908216146115e95760405162461bcd60e51b8152602060048201526005602482015264475331303360d81b6044820152606401610701565b6001600160a01b038181166000818152600160209081526040808320805488871685528285208054919097166001600160a01b03199182161790965592849052825490941690915591519081527faab4fa2b463f581b2b32cb3b7e3b704b9ce37cc209b5fb4d77e593ace405427691015b60405180910390a15050565b61166e611bc6565b7f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c88181556040516001600160a01b03831681527f1151116914515bc0891ff9047a6cb32cf902546f83066499bcf8ba33d2353fa29060200161165a565b6116d3611bc6565b6001600160a01b038116158015906116f557506001600160a01b038116600114155b801561170a57506001600160a01b0381163014155b6117265760405162461bcd60e51b815260040161070190612d4d565b6001600160a01b03818116600090815260026020526040902054161561175e5760405162461bcd60e51b815260040161070190612d6c565b6001600160a01b0382161580159061178057506001600160a01b038216600114155b61179c5760405162461bcd60e51b815260040161070190612d4d565b6001600160a01b038381166000908152600260205260409020548116908316146117f05760405162461bcd60e51b8152602060048201526005602482015264475332303560d81b6044820152606401610701565b6001600160a01b038281166000818152600260209081526040808320805487871680865283862080549289166001600160a01b0319938416179055968a1685528285208054821690971790965592849052825490941690915591519081527ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf910160405180910390a16040516001600160a01b03821681527f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea269060200160405180910390a1505050565b606060007fbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d860001b8d8d8d8d6040516118f4929190613031565b60405190819003812061191a949392918e908e908e908e908e908e908e90602001613041565b60408051601f1981840301815291905280516020909101209050601960f81b600160f81b6119466119fc565b6040516001600160f81b031993841660208201529290911660218301526022820152604281018290526062016040516020818303038152906040529150509b9a5050505050505050505050565b61199b611bc6565b6119c3817f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d555565b6040516001600160a01b03821681527f5ac6c46c93c8d0e53714ba3b53db3e7c046da994313d7ed0d192028bc7c228b090602001610f19565b60007f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a794692184660408051602081019390935282015230606082015260800160405160208183030381529060405280519060200120905090565b611a5b611bc6565b806001600354611a6b919061301e565b1015611a895760405162461bcd60e51b815260040161070190612ebd565b6001600160a01b03821615801590611aab57506001600160a01b038216600114155b611ac75760405162461bcd60e51b815260040161070190612d4d565b6001600160a01b03838116600090815260026020526040902054811690831614611b1b5760405162461bcd60e51b8152602060048201526005602482015264475332303560d81b6044820152606401610701565b6001600160a01b03828116600081815260026020526040808220805488861684529183208054929095166001600160a01b03199283161790945591815282549091169091556003805491611b6e836130b0565b90915550506040516001600160a01b03831681527ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf9060200160405180910390a18060045414611bc157611bc181610f24565b505050565b333014611bfd5760405162461bcd60e51b8152602060048201526005602482015264475330333160d81b6044820152606401610701565b565b600082600003611c1157506000610c9d565b6000611c1d8385612ea6565b905082611c2a85836130c7565b14611c3457600080fd5b9392505050565b600080611c4883856130e9565b905083811015611c3457600080fd5b600033600114801590611c815750336000908152600160205260409020546001600160a01b031615155b611cb55760405162461bcd60e51b815260206004820152600560248201526411d4cc4c0d60da1b6044820152606401610701565b611cc2858585855a612453565b90508015611cfa5760405133907f6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb890600090a2611d26565b60405133907facd2c8702804128fdb0db2bb49f6d127dd0181c13fd45dbfe16de0930e2bd37590600090a25b949350505050565b6000806000611d488e8e8e8e8e8e8e8e8e8e6005546118ba565b600580549192506000611d5a83612da1565b9091555050805160208201209150611d73828286611064565b506000611d9e7f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c85490565b90506001600160a01b03811615611e2457806001600160a01b03166375f0bb528f8f8f8f8f8f8f8f8f8f8f336040518d63ffffffff1660e01b8152600401611df19c9b9a999897969594939291906130fc565b600060405180830381600087803b158015611e0b57600080fd5b505af1158015611e1f573d6000803e3d6000fd5b505050505b611e50611e338a6109c46130e9565b603f611e408c6040612ea6565b611e4a91906130c7565b90612498565b611e5c906101f46130e9565b5a1015611e935760405162461bcd60e51b8152602060048201526005602482015264047533031360dc1b6044820152606401610701565b60005a9050611f048f8f8f8f8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050508e8c600014611ef1578e612453565b6109c45a611eff919061301e565b612453565b9350611f115a82906124af565b90508380611f1e57508915155b80611f2857508715155b611f5c5760405162461bcd60e51b8152602060048201526005602482015264475330313360d81b6044820152606401610701565b60008815611f7457611f71828b8b8b8b61234d565b90505b8415611fb85760408051858152602081018390527f442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e910160405180910390a1611ff2565b60408051858152602081018390527f23428b18acfb3ea64b08dc0c1d296ea9c09702c09083ca5272e64d115b687d23910160405180910390a15b50506001600160a01b0381161561206657604051631264e26d60e31b81526004810183905283151560248201526001600160a01b03821690639327136890604401600060405180830381600087803b15801561204d57600080fd5b505af1158015612061573d6000803e3d6000fd5b505050505b50509b9a5050505050505050505050565b600454156120af5760405162461bcd60e51b8152602060048201526005602482015264047533230360dc1b6044820152606401610701565b81518111156120d05760405162461bcd60e51b815260040161070190612ebd565b60018110156121095760405162461bcd60e51b815260206004820152600560248201526423a999181960d91b6044820152606401610701565b600160005b835181101561222057600084828151811061212b5761212b612f9c565b6020026020010151905060006001600160a01b0316816001600160a01b03161415801561216257506001600160a01b038116600114155b801561217757506001600160a01b0381163014155b80156121955750806001600160a01b0316836001600160a01b031614155b6121b15760405162461bcd60e51b815260040161070190612d4d565b6001600160a01b0381811660009081526002602052604090205416156121e95760405162461bcd60e51b815260040161070190612d6c565b6001600160a01b03928316600090815260026020526040902080546001600160a01b0319169382169390931790925560010161210e565b506001600160a01b0316600090815260026020526040902080546001600160a01b03191660011790559051600355600455565b600160008190526020527fcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f546001600160a01b0316156122bd5760405162461bcd60e51b8152602060048201526005602482015264047533130360dc1b6044820152606401610701565b6001600081905260208190527fcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f80546001600160a01b03191690911790556001600160a01b03821615610802576123198260008360015a612453565b6108025760405162461bcd60e51b8152602060048201526005602482015264047533030360dc1b6044820152606401610701565b6000806001600160a01b038316156123655782612367565b325b90506001600160a01b0384166123fa576123993a8610612387573a612389565b855b6123938989611c3b565b90611bff565b6040519092506001600160a01b0382169083156108fc029084906000818181858888f193505050506123f55760405162461bcd60e51b8152602060048201526005602482015264475330313160d81b6044820152606401610701565b612449565b612408856123938989611c3b565b91506124158482846124ca565b6124495760405162461bcd60e51b815260206004820152600560248201526423a998189960d91b6044820152606401610701565b5095945050505050565b6000600183600181111561246957612469612e22565b03612481576000808551602087018986f49050610d28565b600080855160208701888a87f19695505050505050565b6000818310156124a85781611c34565b5090919050565b6000828211156124be57600080fd5b6000611d26838561301e565b604080516001600160a01b03841660248201526044808201849052825180830390910181526064909101909152602080820180516001600160e01b031663a9059cbb60e01b1781528251600093929184919082896127105a03f13d801561253c5760208114612544576000935061254f565b81935061254f565b600051158215171593505b5050509392505050565b6001600160a01b038116811461256e57600080fd5b50565b803561257c81612559565b919050565b6000806040838503121561259457600080fd5b823561259f81612559565b946020939093013593505050565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126125d457600080fd5b81356001600160401b03808211156125ee576125ee6125ad565b604051601f8301601f19908116603f01168101908282118183101715612616576126166125ad565b8160405283815286602085880101111561262f57600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000806000806080858703121561266557600080fd5b8435935060208501356001600160401b038082111561268357600080fd5b61268f888389016125c3565b945060408701359150808211156126a557600080fd5b506126b2878288016125c3565b949793965093946060013593505050565b6000602082840312156126d557600080fd5b8135611c3481612559565b80356002811061257c57600080fd5b6000806000806080858703121561270557600080fd5b843561271081612559565b93506020850135925060408501356001600160401b0381111561273257600080fd5b61273e878288016125c3565b92505061274d606086016126e0565b905092959194509250565b6000815180845260005b8181101561277e57602081850181015186830182015201612762565b506000602082860101526020601f19601f83011685010191505092915050565b8215158152604060208201526000611d266040830184612758565b600080604083850312156127cc57600080fd5b50508035926020909101359150565b602081526000611c346020830184612758565b60006020828403121561280057600080fd5b5035919050565b60008083601f84011261281957600080fd5b5081356001600160401b0381111561283057600080fd5b60208301915083602082850101111561284857600080fd5b9250929050565b60008060008060008060008060008060006101408c8e03121561287157600080fd5b61287a8c612571565b9a5060208c013599506001600160401b038060408e0135111561289c57600080fd5b6128ac8e60408f01358f01612807565b909a5098506128bd60608e016126e0565b975060808d0135965060a08d0135955060c08d013594506128e060e08e01612571565b93506128ef6101008e01612571565b9250806101208e0135111561290357600080fd5b506129158d6101208e01358e016125c3565b90509295989b509295989b9093969950565b60008060006060848603121561293c57600080fd5b8335925060208401356001600160401b038082111561295a57600080fd5b612966878388016125c3565b9350604086013591508082111561297c57600080fd5b50612989868287016125c3565b9150509250925092565b60008151808452602080850194506020840160005b838110156129cd5781516001600160a01b0316875295820195908201906001016129a8565b509495945050505050565b602081526000611c346020830184612993565b600080604083850312156129fe57600080fd5b8235612a0981612559565b915060208301356001600160401b03811115612a2457600080fd5b612a30858286016125c3565b9150509250929050565b6000806000806000806000806000806101008b8d031215612a5a57600080fd5b8a356001600160401b0380821115612a7157600080fd5b818d0191508d601f830112612a8557600080fd5b813581811115612a9457600080fd5b8e60208260051b8501011115612aa957600080fd5b60208381019d50909b508d01359950612ac460408e01612571565b985060608d0135915080821115612ada57600080fd5b50612ae78d828e01612807565b9097509550612afa905060808c01612571565b9350612b0860a08c01612571565b925060c08b01359150612b1d60e08c01612571565b90509295989b9194979a5092959850565b600080600080600060808688031215612b4657600080fd5b8535612b5181612559565b94506020860135935060408601356001600160401b03811115612b7357600080fd5b612b7f88828901612807565b9094509250612b929050606087016126e0565b90509295509295909350565b604081526000612bb16040830185612993565b905060018060a01b03831660208301529392505050565b60008060008060008060008060008060006101408c8e031215612bea57600080fd5b8b35612bf581612559565b9a5060208c0135995060408c01356001600160401b03811115612c1757600080fd5b612c238e828f01612807565b909a509850612c36905060608d016126e0565b965060808c0135955060a08c0135945060c08c0135935060e08c0135612c5b81612559565b92506101008c0135612c6c81612559565b809250506101208c013590509295989b509295989b9093969950565b60008060408385031215612c9b57600080fd5b8235612ca681612559565b91506020830135612cb681612559565b809150509250929050565b600080600060608486031215612cd657600080fd5b8335612ce181612559565b92506020840135612cf181612559565b91506040840135612d0181612559565b809150509250925092565b600080600060608486031215612d2157600080fd5b8335612d2c81612559565b92506020840135612d3c81612559565b929592945050506040919091013590565b602080825260059082015264475332303360d81b604082015260600190565b60208082526005908201526411d4cc8c0d60da1b604082015260600190565b634e487b7160e01b600052601160045260246000fd5b600060018201612db357612db3612d8b565b5060010190565b604081526000612dcd6040830185612758565b8281036020840152610d288185612758565b600060208284031215612df157600080fd5b81516001600160e01b031981168114611c3457600080fd5b60ff8281168282160390811115610c9d57610c9d612d8b565b634e487b7160e01b600052602160045260246000fd5b60028110612e5657634e487b7160e01b600052602160045260246000fd5b9052565b6001600160a01b038681168252851660208201526040810184905260a060608201819052600090612e8d90830185612758565b9050612e9c6080830184612e38565b9695505050505050565b8082028115828204841417610c9d57610c9d612d8b565b602080825260059082015264475332303160d81b604082015260600190565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b600060018060a01b03808f1683528d60208401526101606040840152612f3061016084018d8f612edc565b612f3d606085018d612e38565b8a60808501528960a08501528860c085015281881660e0850152818716610100850152838103610120850152612f738187612758565b915050828103610140840152612f898185612758565b9f9e505050505050505050505050505050565b634e487b7160e01b600052603260045260246000fd5b6080808252810185905260008660a08301825b88811015612ff5578235612fd881612559565b6001600160a01b0316825260209283019290910190600101612fc5565b50602084019690965250506001600160a01b039283166040820152911660609091015292915050565b81810381811115610c9d57610c9d612d8b565b8183823760009101908152919050565b8b81526001600160a01b038b81166020830152604082018b9052606082018a9052610160820190613075608084018b612e38565b60a083019890985260c082019690965260e0810194909452918516610100840152909316610120820152610140019190915295945050505050565b6000816130bf576130bf612d8b565b506000190190565b6000826130e457634e487b7160e01b600052601260045260246000fd5b500490565b80820180821115610c9d57610c9d612d8b565b600060018060a01b03808f1683528d6020840152610160604084015261312761016084018d8f612edc565b613134606085018d612e38565b8a60808501528960a08501528860c085015281881660e085015281871661010085015283810361012085015261316a8187612758565b925050808416610140840152509d9c5050505050505050505050505056fea2646970667358221220976dca493dd95f9b904ec290061e789606e429364fc397e8d8017c4baf11a91664736f6c63430008180033",
+  "deployedBytecode": "0x6080604052600436106101dc5760003560e01c8063affed0e011610102578063e19a9dd911610095578063f08a032311610064578063f08a032314610620578063f698da2514610640578063f8dc5dd914610655578063ffa1ad741461067557610218565b8063e19a9dd9146105ab578063e318b52b146105cb578063e75235b8146105eb578063e86637db1461060057610218565b8063cc2f8452116100d1578063cc2f84521461051d578063d4d9bdcd1461054b578063d8d11f781461056b578063e009cfde1461058b57610218565b8063affed0e0146104a7578063b4faba09146104bd578063b63e800d146104dd578063c4ca3a9c146104fd57610218565b80635624b25b1161017a5780636a761202116101495780636a7612021461041a5780637d8329741461042d578063934f3a1114610465578063a0e67e2b1461048557610218565b80635624b25b146103805780635ae6bd37146103ad578063610b5925146103da578063694e80c3146103fa57610218565b80632f54bf6e116101b65780632f54bf6e146102f55780633408e47014610315578063468721a7146103325780635229073f1461035257610218565b80630d582f131461027e57806312fb68e0146102a05780632d9ad53d146102c057610218565b366102185760405134815233907f3d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3d9060200160405180910390a2005b34801561022457600080fd5b507f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d580548061024f57005b36600080373360601b365260008060143601600080855af190503d6000803e80610278573d6000fd5b503d6000f35b34801561028a57600080fd5b5061029e610299366004612581565b6106a6565b005b3480156102ac57600080fd5b5061029e6102bb36600461264f565b610806565b3480156102cc57600080fd5b506102e06102db3660046126c3565b610c68565b60405190151581526020015b60405180910390f35b34801561030157600080fd5b506102e06103103660046126c3565b610ca3565b34801561032157600080fd5b50465b6040519081526020016102ec565b34801561033e57600080fd5b506102e061034d3660046126ef565b610cdb565b34801561035e57600080fd5b5061037261036d3660046126ef565b610d31565b6040516102ec92919061279e565b34801561038c57600080fd5b506103a061039b3660046127b9565b610d67565b6040516102ec91906127db565b3480156103b957600080fd5b506103246103c83660046127ee565b60076020526000908152604090205481565b3480156103e657600080fd5b5061029e6103f53660046126c3565b610de2565b34801561040657600080fd5b5061029e6104153660046127ee565b610f24565b6102e061042836600461284f565b610fbc565b34801561043957600080fd5b50610324610448366004612581565b600860209081526000928352604080842090915290825290205481565b34801561047157600080fd5b5061029e610480366004612927565b611064565b34801561049157600080fd5b5061049a6110ae565b6040516102ec91906129d8565b3480156104b357600080fd5b5061032460055481565b3480156104c957600080fd5b5061029e6104d83660046129eb565b61119e565b3480156104e957600080fd5b5061029e6104f8366004612a3a565b6111c1565b34801561050957600080fd5b50610324610518366004612b2e565b6112e2565b34801561052957600080fd5b5061053d610538366004612581565b61137c565b6040516102ec929190612b9e565b34801561055757600080fd5b5061029e6105663660046127ee565b611475565b34801561057757600080fd5b50610324610586366004612bc8565b61150a565b34801561059757600080fd5b5061029e6105a6366004612c88565b611537565b3480156105b757600080fd5b5061029e6105c63660046126c3565b611666565b3480156105d757600080fd5b5061029e6105e6366004612cc1565b6116cb565b3480156105f757600080fd5b50600454610324565b34801561060c57600080fd5b506103a061061b366004612bc8565b6118ba565b34801561062c57600080fd5b5061029e61063b3660046126c3565b611993565b34801561064c57600080fd5b506103246119fc565b34801561066157600080fd5b5061029e610670366004612d0c565b611a53565b34801561068157600080fd5b506103a0604051806040016040528060058152602001640312e332e360dc1b81525081565b6106ae611bc6565b6001600160a01b038216158015906106d057506001600160a01b038216600114155b80156106e557506001600160a01b0382163014155b61070a5760405162461bcd60e51b815260040161070190612d4d565b60405180910390fd5b6001600160a01b0382811660009081526002602052604090205416156107425760405162461bcd60e51b815260040161070190612d6c565b60026020527fe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e080546001600160a01b038481166000818152604081208054939094166001600160a01b0319938416179093556001835283549091161790915560038054916107af83612da1565b90915550506040516001600160a01b03831681527f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea269060200160405180910390a180600454146108025761080281610f24565b5050565b610811816041611bff565b825110156108495760405162461bcd60e51b8152602060048201526005602482015264047533032360dc1b6044820152606401610701565b6000808060008060005b86811015610c5c576041818102890160208101516040820151919092015160ff16955090935091506000849003610a24579193508391610894876041611bff565b8210156108cb5760405162461bcd60e51b8152602060048201526005602482015264475330323160d81b6044820152606401610701565b87516108d8836020611c3b565b111561090e5760405162461bcd60e51b815260206004820152600560248201526423a998191960d91b6044820152606401610701565b60208289018101518951909161093190839061092b908790611c3b565b90611c3b565b11156109675760405162461bcd60e51b8152602060048201526005602482015264475330323360d81b6044820152606401610701565b6040516320c13b0b60e01b8082528a8501602001916001600160a01b038916906320c13b0b9061099d908f908690600401612dba565b602060405180830381865afa1580156109ba573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109de9190612ddf565b6001600160e01b03191614610a1d5760405162461bcd60e51b815260206004820152600560248201526411d4cc0c8d60da1b6044820152606401610701565b5050610bcb565b8360ff16600103610aa6579193508391336001600160a01b0384161480610a6d57506001600160a01b03851660009081526008602090815260408083208d845290915290205415155b610aa15760405162461bcd60e51b8152602060048201526005602482015264475330323560d81b6044820152606401610701565b610bcb565b601e8460ff161115610b6b576040517f19457468657265756d205369676e6564204d6573736167653a0a3332000000006020820152603c81018b9052600190605c0160405160208183030381529060405280519060200120600486610b0b9190612e09565b6040805160008152602081018083529390935260ff90911690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015610b5a573d6000803e3d6000fd5b505050602060405103519450610bcb565b6040805160008152602081018083528c905260ff861691810191909152606081018490526080810183905260019060a0016020604051602081039080840390855afa158015610bbe573d6000803e3d6000fd5b5050506020604051035194505b856001600160a01b0316856001600160a01b0316118015610c0557506001600160a01b038581166000908152600260205260409020541615155b8015610c1b57506001600160a01b038516600114155b610c4f5760405162461bcd60e51b815260206004820152600560248201526423a998191b60d91b6044820152606401610701565b9394508493600101610853565b50505050505050505050565b600060016001600160a01b03831614801590610c9d57506001600160a01b038281166000908152600160205260409020541615155b92915050565b60006001600160a01b038216600114801590610c9d5750506001600160a01b0390811660009081526002602052604090205416151590565b60007fb648d3644f584ed1c2232d53c46d87e693586486ad0d1175f8656013110b714e3386868686604051610d14959493929190612e5a565b60405180910390a1610d2885858585611c57565b95945050505050565b60006060610d4186868686610cdb565b915060405160203d0181016040523d81523d6000602083013e8091505094509492505050565b60606000610d76836020612ea6565b6001600160401b03811115610d8d57610d8d6125ad565b6040519080825280601f01601f191660200182016040528015610db7576020820181803683370190505b50905060005b83811015610dda5784810154602080830284010152600101610dbd565b509392505050565b610dea611bc6565b6001600160a01b03811615801590610e0c57506001600160a01b038116600114155b610e405760405162461bcd60e51b8152602060048201526005602482015264475331303160d81b6044820152606401610701565b6001600160a01b038181166000908152600160205260409020541615610e905760405162461bcd60e51b815260206004820152600560248201526423a998981960d91b6044820152606401610701565b600160208181527fcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f80546001600160a01b03858116600081815260408082208054949095166001600160a01b031994851617909455959095528254168417909155519182527fecdf3a3effea5783a3c4c2140e677577666428d44ed9d474a0b3a4c9943f844091015b60405180910390a150565b610f2c611bc6565b600354811115610f4e5760405162461bcd60e51b815260040161070190612ebd565b6001811015610f875760405162461bcd60e51b815260206004820152600560248201526423a999181960d91b6044820152606401610701565b60048190556040518181527f610f7ff2b304ae8903c3de74c60c6ab1f7d6226b3f52c5161905bb5ad4039c9390602001610f19565b600554600454604080516020810193909352339083015260608281019190915260009160800160405160208183030381529060405290507f66753cd2356569ee081232e3be8909b950e0a76c1f8460c3a5e3c2be32b11bed8d8d8d8d8d8d8d8d8d8d8d8c6040516110389c9b9a99989796959493929190612f05565b60405180910390a16110538d8d8d8d8d8d8d8d8d8d8d611d2e565b9d9c50505050505050505050505050565b6004548061109c5760405162461bcd60e51b8152602060048201526005602482015264475330303160d81b6044820152606401610701565b6110a884848484610806565b50505050565b606060006003546001600160401b038111156110cc576110cc6125ad565b6040519080825280602002602001820160405280156110f5578160200160208202803683370190505b506001600090815260026020527fe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e054919250906001600160a01b03165b6001600160a01b038116600114611196578083838151811061115657611156612f9c565b6001600160a01b0392831660209182029290920181019190915291811660009081526002909252604090912054168161118e81612da1565b925050611132565b509092915050565b600080825160208401855af480600052503d6020523d600060403e60403d016000fd5b6111ff8a8a808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508c9250612077915050565b6001600160a01b0384161561123657611236847f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d555565b6112768787878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061225392505050565b811561128d5761128b8260006001868561234d565b505b336001600160a01b03167f141df868a6331af528e38c83b7aa03edc19be66e37ae67f9285bf4f8e3c6a1a88b8b8b8b896040516112ce959493929190612fb2565b60405180910390a250505050505050505050565b6000805a905061132b878787878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525089925050505a612453565b61133457600080fd5b60005a611341908361301e565b90508060405160200161135691815260200190565b60408051601f198184030181529082905262461bcd60e51b8252610701916004016127db565b60606000826001600160401b03811115611398576113986125ad565b6040519080825280602002602001820160405280156113c1578160200160208202803683370190505b506001600160a01b0380861660009081526001602052604081205492945091165b6001600160a01b0381161580159061140457506001600160a01b038116600114155b801561140f57508482105b15611467578084838151811061142757611427612f9c565b6001600160a01b0392831660209182029290920181019190915291811660009081526001909252604090912054168161145f81612da1565b9250506113e2565b908352919491935090915050565b336000908152600260205260409020546001600160a01b03166114c25760405162461bcd60e51b8152602060048201526005602482015264047533033360dc1b6044820152606401610701565b336000818152600860209081526040808320858452909152808220600190555183917ff2a0eb156472d1440255b0d7c1e19cc07115d1051fe605b0dce69acfec884d9c91a350565b600061151f8c8c8c8c8c8c8c8c8c8c8c6118ba565b8051906020012090509b9a5050505050505050505050565b61153f611bc6565b6001600160a01b0381161580159061156157506001600160a01b038116600114155b6115955760405162461bcd60e51b8152602060048201526005602482015264475331303160d81b6044820152606401610701565b6001600160a01b038281166000908152600160205260409020548116908216146115e95760405162461bcd60e51b8152602060048201526005602482015264475331303360d81b6044820152606401610701565b6001600160a01b038181166000818152600160209081526040808320805488871685528285208054919097166001600160a01b03199182161790965592849052825490941690915591519081527faab4fa2b463f581b2b32cb3b7e3b704b9ce37cc209b5fb4d77e593ace405427691015b60405180910390a15050565b61166e611bc6565b7f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c88181556040516001600160a01b03831681527f1151116914515bc0891ff9047a6cb32cf902546f83066499bcf8ba33d2353fa29060200161165a565b6116d3611bc6565b6001600160a01b038116158015906116f557506001600160a01b038116600114155b801561170a57506001600160a01b0381163014155b6117265760405162461bcd60e51b815260040161070190612d4d565b6001600160a01b03818116600090815260026020526040902054161561175e5760405162461bcd60e51b815260040161070190612d6c565b6001600160a01b0382161580159061178057506001600160a01b038216600114155b61179c5760405162461bcd60e51b815260040161070190612d4d565b6001600160a01b038381166000908152600260205260409020548116908316146117f05760405162461bcd60e51b8152602060048201526005602482015264475332303560d81b6044820152606401610701565b6001600160a01b038281166000818152600260209081526040808320805487871680865283862080549289166001600160a01b0319938416179055968a1685528285208054821690971790965592849052825490941690915591519081527ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf910160405180910390a16040516001600160a01b03821681527f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea269060200160405180910390a1505050565b606060007fbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d860001b8d8d8d8d6040516118f4929190613031565b60405190819003812061191a949392918e908e908e908e908e908e908e90602001613041565b60408051601f1981840301815291905280516020909101209050601960f81b600160f81b6119466119fc565b6040516001600160f81b031993841660208201529290911660218301526022820152604281018290526062016040516020818303038152906040529150509b9a5050505050505050505050565b61199b611bc6565b6119c3817f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d555565b6040516001600160a01b03821681527f5ac6c46c93c8d0e53714ba3b53db3e7c046da994313d7ed0d192028bc7c228b090602001610f19565b60007f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a794692184660408051602081019390935282015230606082015260800160405160208183030381529060405280519060200120905090565b611a5b611bc6565b806001600354611a6b919061301e565b1015611a895760405162461bcd60e51b815260040161070190612ebd565b6001600160a01b03821615801590611aab57506001600160a01b038216600114155b611ac75760405162461bcd60e51b815260040161070190612d4d565b6001600160a01b03838116600090815260026020526040902054811690831614611b1b5760405162461bcd60e51b8152602060048201526005602482015264475332303560d81b6044820152606401610701565b6001600160a01b03828116600081815260026020526040808220805488861684529183208054929095166001600160a01b03199283161790945591815282549091169091556003805491611b6e836130b0565b90915550506040516001600160a01b03831681527ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf9060200160405180910390a18060045414611bc157611bc181610f24565b505050565b333014611bfd5760405162461bcd60e51b8152602060048201526005602482015264475330333160d81b6044820152606401610701565b565b600082600003611c1157506000610c9d565b6000611c1d8385612ea6565b905082611c2a85836130c7565b14611c3457600080fd5b9392505050565b600080611c4883856130e9565b905083811015611c3457600080fd5b600033600114801590611c815750336000908152600160205260409020546001600160a01b031615155b611cb55760405162461bcd60e51b815260206004820152600560248201526411d4cc4c0d60da1b6044820152606401610701565b611cc2858585855a612453565b90508015611cfa5760405133907f6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb890600090a2611d26565b60405133907facd2c8702804128fdb0db2bb49f6d127dd0181c13fd45dbfe16de0930e2bd37590600090a25b949350505050565b6000806000611d488e8e8e8e8e8e8e8e8e8e6005546118ba565b600580549192506000611d5a83612da1565b9091555050805160208201209150611d73828286611064565b506000611d9e7f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c85490565b90506001600160a01b03811615611e2457806001600160a01b03166375f0bb528f8f8f8f8f8f8f8f8f8f8f336040518d63ffffffff1660e01b8152600401611df19c9b9a999897969594939291906130fc565b600060405180830381600087803b158015611e0b57600080fd5b505af1158015611e1f573d6000803e3d6000fd5b505050505b611e50611e338a6109c46130e9565b603f611e408c6040612ea6565b611e4a91906130c7565b90612498565b611e5c906101f46130e9565b5a1015611e935760405162461bcd60e51b8152602060048201526005602482015264047533031360dc1b6044820152606401610701565b60005a9050611f048f8f8f8f8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050508e8c600014611ef1578e612453565b6109c45a611eff919061301e565b612453565b9350611f115a82906124af565b90508380611f1e57508915155b80611f2857508715155b611f5c5760405162461bcd60e51b8152602060048201526005602482015264475330313360d81b6044820152606401610701565b60008815611f7457611f71828b8b8b8b61234d565b90505b8415611fb85760408051858152602081018390527f442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e910160405180910390a1611ff2565b60408051858152602081018390527f23428b18acfb3ea64b08dc0c1d296ea9c09702c09083ca5272e64d115b687d23910160405180910390a15b50506001600160a01b0381161561206657604051631264e26d60e31b81526004810183905283151560248201526001600160a01b03821690639327136890604401600060405180830381600087803b15801561204d57600080fd5b505af1158015612061573d6000803e3d6000fd5b505050505b50509b9a5050505050505050505050565b600454156120af5760405162461bcd60e51b8152602060048201526005602482015264047533230360dc1b6044820152606401610701565b81518111156120d05760405162461bcd60e51b815260040161070190612ebd565b60018110156121095760405162461bcd60e51b815260206004820152600560248201526423a999181960d91b6044820152606401610701565b600160005b835181101561222057600084828151811061212b5761212b612f9c565b6020026020010151905060006001600160a01b0316816001600160a01b03161415801561216257506001600160a01b038116600114155b801561217757506001600160a01b0381163014155b80156121955750806001600160a01b0316836001600160a01b031614155b6121b15760405162461bcd60e51b815260040161070190612d4d565b6001600160a01b0381811660009081526002602052604090205416156121e95760405162461bcd60e51b815260040161070190612d6c565b6001600160a01b03928316600090815260026020526040902080546001600160a01b0319169382169390931790925560010161210e565b506001600160a01b0316600090815260026020526040902080546001600160a01b03191660011790559051600355600455565b600160008190526020527fcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f546001600160a01b0316156122bd5760405162461bcd60e51b8152602060048201526005602482015264047533130360dc1b6044820152606401610701565b6001600081905260208190527fcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f80546001600160a01b03191690911790556001600160a01b03821615610802576123198260008360015a612453565b6108025760405162461bcd60e51b8152602060048201526005602482015264047533030360dc1b6044820152606401610701565b6000806001600160a01b038316156123655782612367565b325b90506001600160a01b0384166123fa576123993a8610612387573a612389565b855b6123938989611c3b565b90611bff565b6040519092506001600160a01b0382169083156108fc029084906000818181858888f193505050506123f55760405162461bcd60e51b8152602060048201526005602482015264475330313160d81b6044820152606401610701565b612449565b612408856123938989611c3b565b91506124158482846124ca565b6124495760405162461bcd60e51b815260206004820152600560248201526423a998189960d91b6044820152606401610701565b5095945050505050565b6000600183600181111561246957612469612e22565b03612481576000808551602087018986f49050610d28565b600080855160208701888a87f19695505050505050565b6000818310156124a85781611c34565b5090919050565b6000828211156124be57600080fd5b6000611d26838561301e565b604080516001600160a01b03841660248201526044808201849052825180830390910181526064909101909152602080820180516001600160e01b031663a9059cbb60e01b1781528251600093929184919082896127105a03f13d801561253c5760208114612544576000935061254f565b81935061254f565b600051158215171593505b5050509392505050565b6001600160a01b038116811461256e57600080fd5b50565b803561257c81612559565b919050565b6000806040838503121561259457600080fd5b823561259f81612559565b946020939093013593505050565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126125d457600080fd5b81356001600160401b03808211156125ee576125ee6125ad565b604051601f8301601f19908116603f01168101908282118183101715612616576126166125ad565b8160405283815286602085880101111561262f57600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000806000806080858703121561266557600080fd5b8435935060208501356001600160401b038082111561268357600080fd5b61268f888389016125c3565b945060408701359150808211156126a557600080fd5b506126b2878288016125c3565b949793965093946060013593505050565b6000602082840312156126d557600080fd5b8135611c3481612559565b80356002811061257c57600080fd5b6000806000806080858703121561270557600080fd5b843561271081612559565b93506020850135925060408501356001600160401b0381111561273257600080fd5b61273e878288016125c3565b92505061274d606086016126e0565b905092959194509250565b6000815180845260005b8181101561277e57602081850181015186830182015201612762565b506000602082860101526020601f19601f83011685010191505092915050565b8215158152604060208201526000611d266040830184612758565b600080604083850312156127cc57600080fd5b50508035926020909101359150565b602081526000611c346020830184612758565b60006020828403121561280057600080fd5b5035919050565b60008083601f84011261281957600080fd5b5081356001600160401b0381111561283057600080fd5b60208301915083602082850101111561284857600080fd5b9250929050565b60008060008060008060008060008060006101408c8e03121561287157600080fd5b61287a8c612571565b9a5060208c013599506001600160401b038060408e0135111561289c57600080fd5b6128ac8e60408f01358f01612807565b909a5098506128bd60608e016126e0565b975060808d0135965060a08d0135955060c08d013594506128e060e08e01612571565b93506128ef6101008e01612571565b9250806101208e0135111561290357600080fd5b506129158d6101208e01358e016125c3565b90509295989b509295989b9093969950565b60008060006060848603121561293c57600080fd5b8335925060208401356001600160401b038082111561295a57600080fd5b612966878388016125c3565b9350604086013591508082111561297c57600080fd5b50612989868287016125c3565b9150509250925092565b60008151808452602080850194506020840160005b838110156129cd5781516001600160a01b0316875295820195908201906001016129a8565b509495945050505050565b602081526000611c346020830184612993565b600080604083850312156129fe57600080fd5b8235612a0981612559565b915060208301356001600160401b03811115612a2457600080fd5b612a30858286016125c3565b9150509250929050565b6000806000806000806000806000806101008b8d031215612a5a57600080fd5b8a356001600160401b0380821115612a7157600080fd5b818d0191508d601f830112612a8557600080fd5b813581811115612a9457600080fd5b8e60208260051b8501011115612aa957600080fd5b60208381019d50909b508d01359950612ac460408e01612571565b985060608d0135915080821115612ada57600080fd5b50612ae78d828e01612807565b9097509550612afa905060808c01612571565b9350612b0860a08c01612571565b925060c08b01359150612b1d60e08c01612571565b90509295989b9194979a5092959850565b600080600080600060808688031215612b4657600080fd5b8535612b5181612559565b94506020860135935060408601356001600160401b03811115612b7357600080fd5b612b7f88828901612807565b9094509250612b929050606087016126e0565b90509295509295909350565b604081526000612bb16040830185612993565b905060018060a01b03831660208301529392505050565b60008060008060008060008060008060006101408c8e031215612bea57600080fd5b8b35612bf581612559565b9a5060208c0135995060408c01356001600160401b03811115612c1757600080fd5b612c238e828f01612807565b909a509850612c36905060608d016126e0565b965060808c0135955060a08c0135945060c08c0135935060e08c0135612c5b81612559565b92506101008c0135612c6c81612559565b809250506101208c013590509295989b509295989b9093969950565b60008060408385031215612c9b57600080fd5b8235612ca681612559565b91506020830135612cb681612559565b809150509250929050565b600080600060608486031215612cd657600080fd5b8335612ce181612559565b92506020840135612cf181612559565b91506040840135612d0181612559565b809150509250925092565b600080600060608486031215612d2157600080fd5b8335612d2c81612559565b92506020840135612d3c81612559565b929592945050506040919091013590565b602080825260059082015264475332303360d81b604082015260600190565b60208082526005908201526411d4cc8c0d60da1b604082015260600190565b634e487b7160e01b600052601160045260246000fd5b600060018201612db357612db3612d8b565b5060010190565b604081526000612dcd6040830185612758565b8281036020840152610d288185612758565b600060208284031215612df157600080fd5b81516001600160e01b031981168114611c3457600080fd5b60ff8281168282160390811115610c9d57610c9d612d8b565b634e487b7160e01b600052602160045260246000fd5b60028110612e5657634e487b7160e01b600052602160045260246000fd5b9052565b6001600160a01b038681168252851660208201526040810184905260a060608201819052600090612e8d90830185612758565b9050612e9c6080830184612e38565b9695505050505050565b8082028115828204841417610c9d57610c9d612d8b565b602080825260059082015264475332303160d81b604082015260600190565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b600060018060a01b03808f1683528d60208401526101606040840152612f3061016084018d8f612edc565b612f3d606085018d612e38565b8a60808501528960a08501528860c085015281881660e0850152818716610100850152838103610120850152612f738187612758565b915050828103610140840152612f898185612758565b9f9e505050505050505050505050505050565b634e487b7160e01b600052603260045260246000fd5b6080808252810185905260008660a08301825b88811015612ff5578235612fd881612559565b6001600160a01b0316825260209283019290910190600101612fc5565b50602084019690965250506001600160a01b039283166040820152911660609091015292915050565b81810381811115610c9d57610c9d612d8b565b8183823760009101908152919050565b8b81526001600160a01b038b81166020830152604082018b9052606082018a9052610160820190613075608084018b612e38565b60a083019890985260c082019690965260e0810194909452918516610100840152909316610120820152610140019190915295945050505050565b6000816130bf576130bf612d8b565b506000190190565b6000826130e457634e487b7160e01b600052601260045260246000fd5b500490565b80820180821115610c9d57610c9d612d8b565b600060018060a01b03808f1683528d6020840152610160604084015261312761016084018d8f612edc565b613134606085018d612e38565b8a60808501528960a08501528860c085015281881660e085015281871661010085015283810361012085015261316a8187612758565b925050808416610140840152509d9c5050505050505050505050505056fea2646970667358221220976dca493dd95f9b904ec290061e789606e429364fc397e8d8017c4baf11a91664736f6c63430008180033",
+  "linkReferences": {},
+  "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/GnosisSafeProxyFactory.json b/apps/api/src/app/hardhat/export/GnosisSafeProxyFactory.json
new file mode 100644
index 000000000..c3a58e082
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/GnosisSafeProxyFactory.json
@@ -0,0 +1,172 @@
+{
+  "_format": "hh-sol-artifact-1",
+  "contractName": "GnosisSafeProxyFactory",
+  "sourceName": "@gnosis.pm/safe-contracts/contracts/proxies/GnosisSafeProxyFactory.sol",
+  "abi": [
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": false,
+          "internalType": "contract GnosisSafeProxy",
+          "name": "proxy",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "address",
+          "name": "singleton",
+          "type": "address"
+        }
+      ],
+      "name": "ProxyCreation",
+      "type": "event"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "_singleton",
+          "type": "address"
+        },
+        {
+          "internalType": "bytes",
+          "name": "initializer",
+          "type": "bytes"
+        },
+        {
+          "internalType": "uint256",
+          "name": "saltNonce",
+          "type": "uint256"
+        }
+      ],
+      "name": "calculateCreateProxyWithNonceAddress",
+      "outputs": [
+        {
+          "internalType": "contract GnosisSafeProxy",
+          "name": "proxy",
+          "type": "address"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "singleton",
+          "type": "address"
+        },
+        {
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        }
+      ],
+      "name": "createProxy",
+      "outputs": [
+        {
+          "internalType": "contract GnosisSafeProxy",
+          "name": "proxy",
+          "type": "address"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "_singleton",
+          "type": "address"
+        },
+        {
+          "internalType": "bytes",
+          "name": "initializer",
+          "type": "bytes"
+        },
+        {
+          "internalType": "uint256",
+          "name": "saltNonce",
+          "type": "uint256"
+        },
+        {
+          "internalType": "contract IProxyCreationCallback",
+          "name": "callback",
+          "type": "address"
+        }
+      ],
+      "name": "createProxyWithCallback",
+      "outputs": [
+        {
+          "internalType": "contract GnosisSafeProxy",
+          "name": "proxy",
+          "type": "address"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "_singleton",
+          "type": "address"
+        },
+        {
+          "internalType": "bytes",
+          "name": "initializer",
+          "type": "bytes"
+        },
+        {
+          "internalType": "uint256",
+          "name": "saltNonce",
+          "type": "uint256"
+        }
+      ],
+      "name": "createProxyWithNonce",
+      "outputs": [
+        {
+          "internalType": "contract GnosisSafeProxy",
+          "name": "proxy",
+          "type": "address"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "proxyCreationCode",
+      "outputs": [
+        {
+          "internalType": "bytes",
+          "name": "",
+          "type": "bytes"
+        }
+      ],
+      "stateMutability": "pure",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "proxyRuntimeCode",
+      "outputs": [
+        {
+          "internalType": "bytes",
+          "name": "",
+          "type": "bytes"
+        }
+      ],
+      "stateMutability": "pure",
+      "type": "function"
+    }
+  ],
+  "bytecode": "0x608060405234801561001057600080fd5b50610a07806100206000396000f3fe608060405234801561001057600080fd5b50600436106100625760003560e01c80631688f0b9146100675780632500510e1461009757806353e5d935146100aa57806361b69abd146100bf578063addacc0f146100d2578063d18af54d146100da575b600080fd5b61007a61007536600461057b565b6100ed565b6040516001600160a01b0390911681526020015b60405180910390f35b61007a6100a53660046105d4565b610168565b6100b26101fd565b60405161008e91906106af565b61007a6100cd3660046106c9565b610227565b6100b26102d0565b61007a6100e8366004610719565b6102e2565b60006100fa8484846103b8565b83519091501561011e5760008060008551602087016000865af10361011e57600080fd5b604080516001600160a01b038084168252861660208201527f4f51faf6c4561ff95f067657e43439f0f856d97c04d9ec9070a6199ad418e235910160405180910390a19392505050565b60006101ac8585858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508792506103b8915050565b6040516bffffffffffffffffffffffff19606083901b16602082015290915060340160408051601f198184030181529082905262461bcd60e51b82526101f4916004016106af565b60405180910390fd5b60606040518060200161020f906104a7565b601f1982820381018352601f90910116604052919050565b600082604051610236906104a7565b6001600160a01b039091168152602001604051809103906000f080158015610262573d6000803e3d6000fd5b508251909150156102875760008060008451602086016000865af10361028757600080fd5b604080516001600160a01b038084168252851660208201527f4f51faf6c4561ff95f067657e43439f0f856d97c04d9ec9070a6199ad418e235910160405180910390a192915050565b60606040518060200161020f906104b4565b600080838360405160200161031392919091825260601b6bffffffffffffffffffffffff1916602082015260340190565b6040516020818303038152906040528051906020012060001c90506103398686836100ed565b91506001600160a01b038316156103af576040516303ca56a360e31b81526001600160a01b03841690631e52b5189061037c9085908a908a908a90600401610785565b600060405180830381600087803b15801561039657600080fd5b505af11580156103aa573d6000803e3d6000fd5b505050505b50949350505050565b6000808380519060200120836040516020016103de929190918252602082015260400190565b604051602081830303815290604052805190602001209050600060405180602001610408906104a7565b601f1982820381018352601f90910116604081905261043591906001600160a01b038916906020016107c2565b6040516020818303038152906040529050818151826020016000f592506001600160a01b03831661049e5760405162461bcd60e51b815260206004820152601360248201527210dc99585d194c8818d85b1b0819985a5b1959606a1b60448201526064016101f4565b50509392505050565b610172806107e583390190565b607b8061095783390190565b6001600160a01b03811681146104d557600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126104ff57600080fd5b813567ffffffffffffffff8082111561051a5761051a6104d8565b604051601f8301601f19908116603f01168101908282118183101715610542576105426104d8565b8160405283815286602085880101111561055b57600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008060006060848603121561059057600080fd5b833561059b816104c0565b9250602084013567ffffffffffffffff8111156105b757600080fd5b6105c3868287016104ee565b925050604084013590509250925092565b600080600080606085870312156105ea57600080fd5b84356105f5816104c0565b9350602085013567ffffffffffffffff8082111561061257600080fd5b818701915087601f83011261062657600080fd5b81358181111561063557600080fd5b88602082850101111561064757600080fd5b95986020929092019750949560400135945092505050565b60005b8381101561067a578181015183820152602001610662565b50506000910152565b6000815180845261069b81602086016020860161065f565b601f01601f19169290920160200192915050565b6020815260006106c26020830184610683565b9392505050565b600080604083850312156106dc57600080fd5b82356106e7816104c0565b9150602083013567ffffffffffffffff81111561070357600080fd5b61070f858286016104ee565b9150509250929050565b6000806000806080858703121561072f57600080fd5b843561073a816104c0565b9350602085013567ffffffffffffffff81111561075657600080fd5b610762878288016104ee565b93505060408501359150606085013561077a816104c0565b939692955090935050565b6001600160a01b038581168252841660208201526080604082018190526000906107b190830185610683565b905082606083015295945050505050565b600083516107d481846020880161065f565b919091019182525060200191905056fe608060405234801561001057600080fd5b5060405161017238038061017283398101604081905261002f916100b9565b6001600160a01b0381166100945760405162461bcd60e51b815260206004820152602260248201527f496e76616c69642073696e676c65746f6e20616464726573732070726f766964604482015261195960f21b606482015260840160405180910390fd5b600080546001600160a01b0319166001600160a01b03929092169190911790556100e9565b6000602082840312156100cb57600080fd5b81516001600160a01b03811681146100e257600080fd5b9392505050565b607b806100f76000396000f3fe6080604052600080546001600160a01b0316632cf35bc960e11b823501602757808252602082f35b3682833781823684845af490503d82833e806040573d82fd5b503d81f3fea26469706673582212205dab0f9c57a2510fe28d054b166e432aaa601af179ed1254de2ac839212d9a3c64736f6c634300081800336080604052600080546001600160a01b0316632cf35bc960e11b823501602757808252602082f35b3682833781823684845af490503d82833e806040573d82fd5b503d81f3fea26469706673582212205dab0f9c57a2510fe28d054b166e432aaa601af179ed1254de2ac839212d9a3c64736f6c63430008180033a2646970667358221220215f3b29e9636281b241e36d8ba0bc701de95e768f9c59b8aebdc67eb6c0cdfa64736f6c63430008180033",
+  "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100625760003560e01c80631688f0b9146100675780632500510e1461009757806353e5d935146100aa57806361b69abd146100bf578063addacc0f146100d2578063d18af54d146100da575b600080fd5b61007a61007536600461057b565b6100ed565b6040516001600160a01b0390911681526020015b60405180910390f35b61007a6100a53660046105d4565b610168565b6100b26101fd565b60405161008e91906106af565b61007a6100cd3660046106c9565b610227565b6100b26102d0565b61007a6100e8366004610719565b6102e2565b60006100fa8484846103b8565b83519091501561011e5760008060008551602087016000865af10361011e57600080fd5b604080516001600160a01b038084168252861660208201527f4f51faf6c4561ff95f067657e43439f0f856d97c04d9ec9070a6199ad418e235910160405180910390a19392505050565b60006101ac8585858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508792506103b8915050565b6040516bffffffffffffffffffffffff19606083901b16602082015290915060340160408051601f198184030181529082905262461bcd60e51b82526101f4916004016106af565b60405180910390fd5b60606040518060200161020f906104a7565b601f1982820381018352601f90910116604052919050565b600082604051610236906104a7565b6001600160a01b039091168152602001604051809103906000f080158015610262573d6000803e3d6000fd5b508251909150156102875760008060008451602086016000865af10361028757600080fd5b604080516001600160a01b038084168252851660208201527f4f51faf6c4561ff95f067657e43439f0f856d97c04d9ec9070a6199ad418e235910160405180910390a192915050565b60606040518060200161020f906104b4565b600080838360405160200161031392919091825260601b6bffffffffffffffffffffffff1916602082015260340190565b6040516020818303038152906040528051906020012060001c90506103398686836100ed565b91506001600160a01b038316156103af576040516303ca56a360e31b81526001600160a01b03841690631e52b5189061037c9085908a908a908a90600401610785565b600060405180830381600087803b15801561039657600080fd5b505af11580156103aa573d6000803e3d6000fd5b505050505b50949350505050565b6000808380519060200120836040516020016103de929190918252602082015260400190565b604051602081830303815290604052805190602001209050600060405180602001610408906104a7565b601f1982820381018352601f90910116604081905261043591906001600160a01b038916906020016107c2565b6040516020818303038152906040529050818151826020016000f592506001600160a01b03831661049e5760405162461bcd60e51b815260206004820152601360248201527210dc99585d194c8818d85b1b0819985a5b1959606a1b60448201526064016101f4565b50509392505050565b610172806107e583390190565b607b8061095783390190565b6001600160a01b03811681146104d557600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126104ff57600080fd5b813567ffffffffffffffff8082111561051a5761051a6104d8565b604051601f8301601f19908116603f01168101908282118183101715610542576105426104d8565b8160405283815286602085880101111561055b57600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008060006060848603121561059057600080fd5b833561059b816104c0565b9250602084013567ffffffffffffffff8111156105b757600080fd5b6105c3868287016104ee565b925050604084013590509250925092565b600080600080606085870312156105ea57600080fd5b84356105f5816104c0565b9350602085013567ffffffffffffffff8082111561061257600080fd5b818701915087601f83011261062657600080fd5b81358181111561063557600080fd5b88602082850101111561064757600080fd5b95986020929092019750949560400135945092505050565b60005b8381101561067a578181015183820152602001610662565b50506000910152565b6000815180845261069b81602086016020860161065f565b601f01601f19169290920160200192915050565b6020815260006106c26020830184610683565b9392505050565b600080604083850312156106dc57600080fd5b82356106e7816104c0565b9150602083013567ffffffffffffffff81111561070357600080fd5b61070f858286016104ee565b9150509250929050565b6000806000806080858703121561072f57600080fd5b843561073a816104c0565b9350602085013567ffffffffffffffff81111561075657600080fd5b610762878288016104ee565b93505060408501359150606085013561077a816104c0565b939692955090935050565b6001600160a01b038581168252841660208201526080604082018190526000906107b190830185610683565b905082606083015295945050505050565b600083516107d481846020880161065f565b919091019182525060200191905056fe608060405234801561001057600080fd5b5060405161017238038061017283398101604081905261002f916100b9565b6001600160a01b0381166100945760405162461bcd60e51b815260206004820152602260248201527f496e76616c69642073696e676c65746f6e20616464726573732070726f766964604482015261195960f21b606482015260840160405180910390fd5b600080546001600160a01b0319166001600160a01b03929092169190911790556100e9565b6000602082840312156100cb57600080fd5b81516001600160a01b03811681146100e257600080fd5b9392505050565b607b806100f76000396000f3fe6080604052600080546001600160a01b0316632cf35bc960e11b823501602757808252602082f35b3682833781823684845af490503d82833e806040573d82fd5b503d81f3fea26469706673582212205dab0f9c57a2510fe28d054b166e432aaa601af179ed1254de2ac839212d9a3c64736f6c634300081800336080604052600080546001600160a01b0316632cf35bc960e11b823501602757808252602082f35b3682833781823684845af490503d82833e806040573d82fd5b503d81f3fea26469706673582212205dab0f9c57a2510fe28d054b166e432aaa601af179ed1254de2ac839212d9a3c64736f6c63430008180033a2646970667358221220215f3b29e9636281b241e36d8ba0bc701de95e768f9c59b8aebdc67eb6c0cdfa64736f6c63430008180033",
+  "linkReferences": {},
+  "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/Launchpad.json b/apps/api/src/app/hardhat/export/Launchpad.json
new file mode 100644
index 000000000..ad0bb3f89
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/Launchpad.json
@@ -0,0 +1,183 @@
+{
+    "_format": "hh-vyper-artifact-1",
+    "contractName": "Launchpad",
+    "sourceName": "contracts/Launchpad.vy",
+    "abi": [
+        {
+            "name": "VESystemCreated",
+            "inputs": [
+                {
+                    "name": "token",
+                    "type": "address",
+                    "indexed": true
+                },
+                {
+                    "name": "votingEscrow",
+                    "type": "address",
+                    "indexed": false
+                },
+                {
+                    "name": "rewardDistributor",
+                    "type": "address",
+                    "indexed": false
+                },
+                {
+                    "name": "rewardFaucet",
+                    "type": "address",
+                    "indexed": false
+                },
+                {
+                    "name": "admin",
+                    "type": "address",
+                    "indexed": false
+                }
+            ],
+            "anonymous": false,
+            "type": "event"
+        },
+        {
+            "stateMutability": "nonpayable",
+            "type": "constructor",
+            "inputs": [
+                {
+                    "name": "_votingEscrow",
+                    "type": "address"
+                },
+                {
+                    "name": "_rewardDistributor",
+                    "type": "address"
+                },
+                {
+                    "name": "_rewardFaucet",
+                    "type": "address"
+                },
+                {
+                    "name": "_balToken",
+                    "type": "address"
+                },
+                {
+                    "name": "_balMinter",
+                    "type": "address"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "stateMutability": "nonpayable",
+            "type": "function",
+            "name": "deploy",
+            "inputs": [
+                {
+                    "name": "tokenBptAddr",
+                    "type": "address"
+                },
+                {
+                    "name": "name",
+                    "type": "string"
+                },
+                {
+                    "name": "symbol",
+                    "type": "string"
+                },
+                {
+                    "name": "maxLockTime",
+                    "type": "uint256"
+                },
+                {
+                    "name": "rewardDistributorStartTime",
+                    "type": "uint256"
+                },
+                {
+                    "name": "admin_unlock_all",
+                    "type": "address"
+                },
+                {
+                    "name": "admin_early_unlock",
+                    "type": "address"
+                },
+                {
+                    "name": "rewardReceiver",
+                    "type": "address"
+                }
+            ],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "address"
+                },
+                {
+                    "name": "",
+                    "type": "address"
+                },
+                {
+                    "name": "",
+                    "type": "address"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "votingEscrow",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "address"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "rewardDistributor",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "address"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "rewardFaucet",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "address"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "balToken",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "address"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "balMinter",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "address"
+                }
+            ]
+        }
+    ],
+    "bytecode": "0x60206106c76000396000518060a01c6106c25760405260206106e76000396000518060a01c6106c25760605260206107076000396000518060a01c6106c25760805260206107276000396000518060a01c6106c25760a05260206107476000396000518060a01c6106c25760c052346106c257604051156100b157606051156100aa57608051156100a35760a0511561009c5760c05115156100b4565b60006100b4565b60006100b4565b60006100b4565b60005b61011757600c60e0527f7a65726f206164647265737300000000000000000000000000000000000000006101005260e05060e0518061010001601f826000031636823750506308c379a060a052602060c052601f19601f60e051011660440160bcfd5b60405161057552606051610595526080516105b55260a0516105d55260c0516105f55261057561014c61000039610615610000f36003361161000c5761055d565b60003560e01c346105635763915c40938118610498576101443610610563576004358060a01c6105635760405260243560040160408135116105635780358060605260208201818160803750505060443560040160208135116105635780358060c05260208201803560e05250505060a4358060a01c610563576101005260c4358060a01c610563576101205260e4358060a01c610563576101405260405160206105d560003960005118610121576004610160527f2162616c000000000000000000000000000000000000000000000000000000006101805261016050610160518061018001601f826000031636823750506308c379a061012052602061014052601f19601f61016051011660440161013cfd5b7f602d3d8160093d39f3363d3d373d3d3d363d730000000000000000000000000061018052602061057560003960005160601b610193527f5af43d82803e903d91602b57fd5bf300000000000000000000000000000000006101a75260366101806000f0801561056357610160527f602d3d8160093d39f3363d3d373d3d3d363d73000000000000000000000000006101a052602061059560003960005160601b6101b3527f5af43d82803e903d91602b57fd5bf300000000000000000000000000000000006101c75260366101a06000f08015610563576101805260016101a052610140516101c0526101405161022257610180516101c05260006101a0525b61016051639bf6abf36101e052610180604051610200528061022052806102000160605180825260208201818183608060045afa5050508051806020830101601f82600003163682375050601f19601f825160200101169050810190508061024052806102000160c0518082526020820160e051815250508051806020830101601f82600003163682375050601f19601f8251602001011690508101905033610260526101005161028052610120516102a0526064356102c05260206105d56000396000516102e05260206105f5600039600051610300526101c051610320526101a05161034052610180516103605250803b156105635760006101e06102246101fc6000855af1610339573d600060003e3d6000fd5b507f602d3d8160093d39f3363d3d373d3d3d363d73000000000000000000000000006102005260206105b560003960005160601b610213527f5af43d82803e903d91602b57fd5bf300000000000000000000000000000000006102275260366102006000f08015610563576101e0526101805163be2030946102005261016051610220526101e05161024052608435610260523361028052803b15610563576000610200608461021c6000855af16103f6573d600060003e3d6000fd5b506101e05163c4d66de8610200526101805161022052803b15610563576000610200602461021c6000855af1610431573d600060003e3d6000fd5b506040517fa1de2bb5131ee0bd88e8ab94ac9f5ecf9e9d0bcea61a294eb7df4bf7c222b641610160516102005261018051610220526101e0516102405233610260526080610200a2610160516102005261018051610220526101e051610240526060610200f35b634f2bfe5b81186104bf576004361061056357602061057560003960005160405260206040f35b63acc2166a81186104e6576004361061056357602061059560003960005160405260206040f35b63397bcd41811861050d57600436106105635760206105b560003960005160405260206040f35b6338d54645811861053457600436106105635760206105d560003960005160405260206040f35b6373f43d6d811861055b57600436106105635760206105f560003960005160405260206040f35b505b60006000fd5b600080fda165767970657283000307000b005b600080fd",
+    "deployedBytecode": "0x6003361161000c5761055d565b60003560e01c346105635763915c40938118610498576101443610610563576004358060a01c6105635760405260243560040160408135116105635780358060605260208201818160803750505060443560040160208135116105635780358060c05260208201803560e05250505060a4358060a01c610563576101005260c4358060a01c610563576101205260e4358060a01c610563576101405260405160206105d560003960005118610121576004610160527f2162616c000000000000000000000000000000000000000000000000000000006101805261016050610160518061018001601f826000031636823750506308c379a061012052602061014052601f19601f61016051011660440161013cfd5b7f602d3d8160093d39f3363d3d373d3d3d363d730000000000000000000000000061018052602061057560003960005160601b610193527f5af43d82803e903d91602b57fd5bf300000000000000000000000000000000006101a75260366101806000f0801561056357610160527f602d3d8160093d39f3363d3d373d3d3d363d73000000000000000000000000006101a052602061059560003960005160601b6101b3527f5af43d82803e903d91602b57fd5bf300000000000000000000000000000000006101c75260366101a06000f08015610563576101805260016101a052610140516101c0526101405161022257610180516101c05260006101a0525b61016051639bf6abf36101e052610180604051610200528061022052806102000160605180825260208201818183608060045afa5050508051806020830101601f82600003163682375050601f19601f825160200101169050810190508061024052806102000160c0518082526020820160e051815250508051806020830101601f82600003163682375050601f19601f8251602001011690508101905033610260526101005161028052610120516102a0526064356102c05260206105d56000396000516102e05260206105f5600039600051610300526101c051610320526101a05161034052610180516103605250803b156105635760006101e06102246101fc6000855af1610339573d600060003e3d6000fd5b507f602d3d8160093d39f3363d3d373d3d3d363d73000000000000000000000000006102005260206105b560003960005160601b610213527f5af43d82803e903d91602b57fd5bf300000000000000000000000000000000006102275260366102006000f08015610563576101e0526101805163be2030946102005261016051610220526101e05161024052608435610260523361028052803b15610563576000610200608461021c6000855af16103f6573d600060003e3d6000fd5b506101e05163c4d66de8610200526101805161022052803b15610563576000610200602461021c6000855af1610431573d600060003e3d6000fd5b506040517fa1de2bb5131ee0bd88e8ab94ac9f5ecf9e9d0bcea61a294eb7df4bf7c222b641610160516102005261018051610220526101e0516102405233610260526080610200a2610160516102005261018051610220526101e051610240526060610200f35b634f2bfe5b81186104bf576004361061056357602061057560003960005160405260206040f35b63acc2166a81186104e6576004361061056357602061059560003960005160405260206040f35b63397bcd41811861050d57600436106105635760206105b560003960005160405260206040f35b6338d54645811861053457600436106105635760206105d560003960005160405260206040f35b6373f43d6d811861055b57600436106105635760206105f560003960005160405260206040f35b505b60006000fd5b600080fda165767970657283000307000b",
+    "linkReferences": {},
+    "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/LensReward.json b/apps/api/src/app/hardhat/export/LensReward.json
new file mode 100644
index 000000000..ccec8ce42
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/LensReward.json
@@ -0,0 +1,93 @@
+{
+    "_format": "hh-sol-artifact-1",
+    "contractName": "LensReward",
+    "sourceName": "contracts/LensReward.sol",
+    "abi": [
+        {
+            "inputs": [
+                {
+                    "internalType": "contract IRewardDistributor",
+                    "name": "distributor",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "user",
+                    "type": "address"
+                },
+                {
+                    "internalType": "contract IERC20",
+                    "name": "token",
+                    "type": "address"
+                }
+            ],
+            "name": "getUserClaimableReward",
+            "outputs": [
+                {
+                    "components": [
+                        {
+                            "internalType": "address",
+                            "name": "token",
+                            "type": "address"
+                        },
+                        {
+                            "internalType": "uint256",
+                            "name": "claimableAmount",
+                            "type": "uint256"
+                        }
+                    ],
+                    "internalType": "struct LensReward.ClaimableRewards",
+                    "name": "",
+                    "type": "tuple"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "contract IRewardDistributor",
+                    "name": "distributor",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "user",
+                    "type": "address"
+                },
+                {
+                    "internalType": "contract IERC20[]",
+                    "name": "tokens",
+                    "type": "address[]"
+                }
+            ],
+            "name": "getUserClaimableRewardsAll",
+            "outputs": [
+                {
+                    "components": [
+                        {
+                            "internalType": "address",
+                            "name": "token",
+                            "type": "address"
+                        },
+                        {
+                            "internalType": "uint256",
+                            "name": "claimableAmount",
+                            "type": "uint256"
+                        }
+                    ],
+                    "internalType": "struct LensReward.ClaimableRewards[]",
+                    "name": "",
+                    "type": "tuple[]"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        }
+    ],
+    "bytecode": "0x608060405234801561001057600080fd5b50610552806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80635753bfca1461003b57806360491ff414610064575b600080fd5b61004e61004936600461030f565b610084565b60405161005b919061035a565b60405180910390f35b61007761007236600461037c565b610221565b60405161005b9190610411565b60408051808201909152600080825260208201526040516370a0823160e01b81526001600160a01b038481166004830152600091908416906370a0823190602401602060405180830381865afa1580156100e2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101069190610471565b60405163ca31879d60e01b81526001600160a01b03868116600483015285811660248301529192509086169063ca31879d906044016020604051808303816000875af115801561015a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061017e9190610471565b506040516370a0823160e01b81526001600160a01b038581166004830152600091908516906370a0823190602401602060405180830381865afa1580156101c9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101ed9190610471565b90506040518060400160405280856001600160a01b03168152602001838361021591906104a0565b90529695505050505050565b60608160008167ffffffffffffffff81111561023f5761023f6104b3565b60405190808252806020026020018201604052801561028457816020015b604080518082019091526000808252602082015281526020019060019003908161025d5790505b50905060005b828110156102ec576102be88888888858181106102a9576102a96104c9565b905060200201602081019061004991906104df565b8282815181106102d0576102d06104c9565b6020026020010181905250806102e590610503565b905061028a565b509695505050505050565b6001600160a01b038116811461030c57600080fd5b50565b60008060006060848603121561032457600080fd5b833561032f816102f7565b9250602084013561033f816102f7565b9150604084013561034f816102f7565b809150509250925092565b81516001600160a01b0316815260208083015190820152604081015b92915050565b6000806000806060858703121561039257600080fd5b843561039d816102f7565b935060208501356103ad816102f7565b9250604085013567ffffffffffffffff808211156103ca57600080fd5b818701915087601f8301126103de57600080fd5b8135818111156103ed57600080fd5b8860208260051b850101111561040257600080fd5b95989497505060200194505050565b602080825282518282018190526000919060409081850190868401855b828110156104645761045484835180516001600160a01b03168252602090810151910152565b928401929085019060010161042e565b5091979650505050505050565b60006020828403121561048357600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b818103818111156103765761037661048a565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b6000602082840312156104f157600080fd5b81356104fc816102f7565b9392505050565b6000600182016105155761051561048a565b506001019056fea264697066735822122027644c9f1cbb10d96d582d4eeb73f545853df009df64befe97d316d4df9f2cf264736f6c63430008120033",
+    "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80635753bfca1461003b57806360491ff414610064575b600080fd5b61004e61004936600461030f565b610084565b60405161005b919061035a565b60405180910390f35b61007761007236600461037c565b610221565b60405161005b9190610411565b60408051808201909152600080825260208201526040516370a0823160e01b81526001600160a01b038481166004830152600091908416906370a0823190602401602060405180830381865afa1580156100e2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101069190610471565b60405163ca31879d60e01b81526001600160a01b03868116600483015285811660248301529192509086169063ca31879d906044016020604051808303816000875af115801561015a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061017e9190610471565b506040516370a0823160e01b81526001600160a01b038581166004830152600091908516906370a0823190602401602060405180830381865afa1580156101c9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101ed9190610471565b90506040518060400160405280856001600160a01b03168152602001838361021591906104a0565b90529695505050505050565b60608160008167ffffffffffffffff81111561023f5761023f6104b3565b60405190808252806020026020018201604052801561028457816020015b604080518082019091526000808252602082015281526020019060019003908161025d5790505b50905060005b828110156102ec576102be88888888858181106102a9576102a96104c9565b905060200201602081019061004991906104df565b8282815181106102d0576102d06104c9565b6020026020010181905250806102e590610503565b905061028a565b509695505050505050565b6001600160a01b038116811461030c57600080fd5b50565b60008060006060848603121561032457600080fd5b833561032f816102f7565b9250602084013561033f816102f7565b9150604084013561034f816102f7565b809150509250925092565b81516001600160a01b0316815260208083015190820152604081015b92915050565b6000806000806060858703121561039257600080fd5b843561039d816102f7565b935060208501356103ad816102f7565b9250604085013567ffffffffffffffff808211156103ca57600080fd5b818701915087601f8301126103de57600080fd5b8135818111156103ed57600080fd5b8860208260051b850101111561040257600080fd5b95989497505060200194505050565b602080825282518282018190526000919060409081850190868401855b828110156104645761045484835180516001600160a01b03168252602090810151910152565b928401929085019060010161042e565b5091979650505050505050565b60006020828403121561048357600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b818103818111156103765761037661048a565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b6000602082840312156104f157600080fd5b81356104fc816102f7565b9392505050565b6000600182016105155761051561048a565b506001019056fea264697066735822122027644c9f1cbb10d96d582d4eeb73f545853df009df64befe97d316d4df9f2cf264736f6c63430008120033",
+    "linkReferences": {},
+    "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/LimitedSupplyToken.json b/apps/api/src/app/hardhat/export/LimitedSupplyToken.json
new file mode 100644
index 000000000..18d4a2eb5
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/LimitedSupplyToken.json
@@ -0,0 +1,298 @@
+[
+    {
+        "inputs": [
+            {
+                "internalType": "string",
+                "name": "_name",
+                "type": "string"
+            },
+            {
+                "internalType": "string",
+                "name": "_symbol",
+                "type": "string"
+            },
+            {
+                "internalType": "address",
+                "name": "to",
+                "type": "address"
+            },
+            {
+                "internalType": "uint256",
+                "name": "amount",
+                "type": "uint256"
+            }
+        ],
+        "stateMutability": "nonpayable",
+        "type": "constructor"
+    },
+    {
+        "anonymous": false,
+        "inputs": [
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "owner",
+                "type": "address"
+            },
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "spender",
+                "type": "address"
+            },
+            {
+                "indexed": false,
+                "internalType": "uint256",
+                "name": "value",
+                "type": "uint256"
+            }
+        ],
+        "name": "Approval",
+        "type": "event"
+    },
+    {
+        "anonymous": false,
+        "inputs": [
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "from",
+                "type": "address"
+            },
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "to",
+                "type": "address"
+            },
+            {
+                "indexed": false,
+                "internalType": "uint256",
+                "name": "value",
+                "type": "uint256"
+            }
+        ],
+        "name": "Transfer",
+        "type": "event"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "owner",
+                "type": "address"
+            },
+            {
+                "internalType": "address",
+                "name": "spender",
+                "type": "address"
+            }
+        ],
+        "name": "allowance",
+        "outputs": [
+            {
+                "internalType": "uint256",
+                "name": "",
+                "type": "uint256"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "spender",
+                "type": "address"
+            },
+            {
+                "internalType": "uint256",
+                "name": "amount",
+                "type": "uint256"
+            }
+        ],
+        "name": "approve",
+        "outputs": [
+            {
+                "internalType": "bool",
+                "name": "",
+                "type": "bool"
+            }
+        ],
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "account",
+                "type": "address"
+            }
+        ],
+        "name": "balanceOf",
+        "outputs": [
+            {
+                "internalType": "uint256",
+                "name": "",
+                "type": "uint256"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [],
+        "name": "decimals",
+        "outputs": [
+            {
+                "internalType": "uint8",
+                "name": "",
+                "type": "uint8"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "spender",
+                "type": "address"
+            },
+            {
+                "internalType": "uint256",
+                "name": "subtractedValue",
+                "type": "uint256"
+            }
+        ],
+        "name": "decreaseAllowance",
+        "outputs": [
+            {
+                "internalType": "bool",
+                "name": "",
+                "type": "bool"
+            }
+        ],
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "spender",
+                "type": "address"
+            },
+            {
+                "internalType": "uint256",
+                "name": "addedValue",
+                "type": "uint256"
+            }
+        ],
+        "name": "increaseAllowance",
+        "outputs": [
+            {
+                "internalType": "bool",
+                "name": "",
+                "type": "bool"
+            }
+        ],
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "inputs": [],
+        "name": "name",
+        "outputs": [
+            {
+                "internalType": "string",
+                "name": "",
+                "type": "string"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [],
+        "name": "symbol",
+        "outputs": [
+            {
+                "internalType": "string",
+                "name": "",
+                "type": "string"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [],
+        "name": "totalSupply",
+        "outputs": [
+            {
+                "internalType": "uint256",
+                "name": "",
+                "type": "uint256"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "recipient",
+                "type": "address"
+            },
+            {
+                "internalType": "uint256",
+                "name": "amount",
+                "type": "uint256"
+            }
+        ],
+        "name": "transfer",
+        "outputs": [
+            {
+                "internalType": "bool",
+                "name": "",
+                "type": "bool"
+            }
+        ],
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "sender",
+                "type": "address"
+            },
+            {
+                "internalType": "address",
+                "name": "recipient",
+                "type": "address"
+            },
+            {
+                "internalType": "uint256",
+                "name": "amount",
+                "type": "uint256"
+            }
+        ],
+        "name": "transferFrom",
+        "outputs": [
+            {
+                "internalType": "bool",
+                "name": "",
+                "type": "bool"
+            }
+        ],
+        "stateMutability": "nonpayable",
+        "type": "function"
+    }
+]
diff --git a/apps/api/src/app/hardhat/export/MultiSend.json b/apps/api/src/app/hardhat/export/MultiSend.json
new file mode 100644
index 000000000..a9d401966
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/MultiSend.json
@@ -0,0 +1,29 @@
+{
+  "_format": "hh-sol-artifact-1",
+  "contractName": "MultiSend",
+  "sourceName": "@gnosis.pm/safe-contracts/contracts/libraries/MultiSend.sol",
+  "abi": [
+    {
+      "inputs": [],
+      "stateMutability": "nonpayable",
+      "type": "constructor"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes",
+          "name": "transactions",
+          "type": "bytes"
+        }
+      ],
+      "name": "multiSend",
+      "outputs": [],
+      "stateMutability": "payable",
+      "type": "function"
+    }
+  ],
+  "bytecode": "0x60a060405234801561001057600080fd5b503060805260805161025261002f6000396000604201526102526000f3fe60806040526004361061001e5760003560e01c80638d80ff0a14610023575b600080fd5b61003661003136600461016b565b610038565b005b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001630036100cd5760405162461bcd60e51b815260206004820152603060248201527f4d756c746953656e642073686f756c64206f6e6c792062652063616c6c65642060448201526f1d9a584819195b1959d85d1958d85b1b60821b606482015260840160405180910390fd5b805160205b81811015610150578083015160f81c6001820184015160601c601583018501516035840186015160558501870160008560008114610117576001811461012757610132565b6000808585888a5af19150610132565b6000808585895af491505b508061013d57600080fd5b50508060550185019450505050506100d2565b505050565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561017d57600080fd5b813567ffffffffffffffff8082111561019557600080fd5b818401915084601f8301126101a957600080fd5b8135818111156101bb576101bb610155565b604051601f8201601f19908116603f011681019083821181831017156101e3576101e3610155565b816040528281528760208487010111156101fc57600080fd5b82602086016020830137600092810160200192909252509594505050505056fea264697066735822122048ffb0b3f2f2f62a39869c615643ea2c865185e980e34e3526010f09632332aa64736f6c63430008180033",
+  "deployedBytecode": "0x60806040526004361061001e5760003560e01c80638d80ff0a14610023575b600080fd5b61003661003136600461016b565b610038565b005b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001630036100cd5760405162461bcd60e51b815260206004820152603060248201527f4d756c746953656e642073686f756c64206f6e6c792062652063616c6c65642060448201526f1d9a584819195b1959d85d1958d85b1b60821b606482015260840160405180910390fd5b805160205b81811015610150578083015160f81c6001820184015160601c601583018501516035840186015160558501870160008560008114610117576001811461012757610132565b6000808585888a5af19150610132565b6000808585895af491505b508061013d57600080fd5b50508060550185019450505050506100d2565b505050565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561017d57600080fd5b813567ffffffffffffffff8082111561019557600080fd5b818401915084601f8301126101a957600080fd5b8135818111156101bb576101bb610155565b604051601f8201601f19908116603f011681019083821181831017156101e3576101e3610155565b816040528281528760208487010111156101fc57600080fd5b82602086016020830137600092810160200192909252509594505050505056fea264697066735822122048ffb0b3f2f2f62a39869c615643ea2c865185e980e34e3526010f09632332aa64736f6c63430008180033",
+  "linkReferences": {},
+  "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/MultiSendCallOnly.json b/apps/api/src/app/hardhat/export/MultiSendCallOnly.json
new file mode 100644
index 000000000..0582dd99b
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/MultiSendCallOnly.json
@@ -0,0 +1,24 @@
+{
+  "_format": "hh-sol-artifact-1",
+  "contractName": "MultiSendCallOnly",
+  "sourceName": "@gnosis.pm/safe-contracts/contracts/libraries/MultiSendCallOnly.sol",
+  "abi": [
+    {
+      "inputs": [
+        {
+          "internalType": "bytes",
+          "name": "transactions",
+          "type": "bytes"
+        }
+      ],
+      "name": "multiSend",
+      "outputs": [],
+      "stateMutability": "payable",
+      "type": "function"
+    }
+  ],
+  "bytecode": "0x608060405234801561001057600080fd5b506101ae806100206000396000f3fe60806040526004361061001e5760003560e01c80638d80ff0a14610023575b600080fd5b6100366100313660046100c7565b610038565b005b805160205b818110156100ac578083015160f81c6001820184015160601c601583018501516035840186015160558501870160008560008114610082576001811461001e5761008e565b6000808585888a5af191505b508061009957600080fd5b505080605501850194505050505061003d565b505050565b634e487b7160e01b600052604160045260246000fd5b6000602082840312156100d957600080fd5b813567ffffffffffffffff808211156100f157600080fd5b818401915084601f83011261010557600080fd5b813581811115610117576101176100b1565b604051601f8201601f19908116603f0116810190838211818310171561013f5761013f6100b1565b8160405282815287602084870101111561015857600080fd5b82602086016020830137600092810160200192909252509594505050505056fea26469706673582212207730848407a4c5a77116bb261887c8305f7996baf682400d9f5a0ab7cd4f3ce064736f6c63430008180033",
+  "deployedBytecode": "0x60806040526004361061001e5760003560e01c80638d80ff0a14610023575b600080fd5b6100366100313660046100c7565b610038565b005b805160205b818110156100ac578083015160f81c6001820184015160601c601583018501516035840186015160558501870160008560008114610082576001811461001e5761008e565b6000808585888a5af191505b508061009957600080fd5b505080605501850194505050505061003d565b505050565b634e487b7160e01b600052604160045260246000fd5b6000602082840312156100d957600080fd5b813567ffffffffffffffff808211156100f157600080fd5b818401915084601f83011261010557600080fd5b813581811115610117576101176100b1565b604051601f8201601f19908116603f0116810190838211818310171561013f5761013f6100b1565b8160405282815287602084870101111561015857600080fd5b82602086016020830137600092810160200192909252509594505050505056fea26469706673582212207730848407a4c5a77116bb261887c8305f7996baf682400d9f5a0ab7cd4f3ce064736f6c63430008180033",
+  "linkReferences": {},
+  "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/NonFungibleToken.json b/apps/api/src/app/hardhat/export/NonFungibleToken.json
new file mode 100644
index 000000000..a9e8c1adb
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/NonFungibleToken.json
@@ -0,0 +1,744 @@
+[
+    {
+        "inputs": [
+            {
+                "internalType": "string",
+                "name": "name_",
+                "type": "string"
+            },
+            {
+                "internalType": "string",
+                "name": "symbol_",
+                "type": "string"
+            },
+            {
+                "internalType": "string",
+                "name": "baseURI_",
+                "type": "string"
+            },
+            {
+                "internalType": "address",
+                "name": "owner_",
+                "type": "address"
+            }
+        ],
+        "stateMutability": "nonpayable",
+        "type": "constructor"
+    },
+    {
+        "anonymous": false,
+        "inputs": [
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "owner",
+                "type": "address"
+            },
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "approved",
+                "type": "address"
+            },
+            {
+                "indexed": true,
+                "internalType": "uint256",
+                "name": "tokenId",
+                "type": "uint256"
+            }
+        ],
+        "name": "Approval",
+        "type": "event"
+    },
+    {
+        "anonymous": false,
+        "inputs": [
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "owner",
+                "type": "address"
+            },
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "operator",
+                "type": "address"
+            },
+            {
+                "indexed": false,
+                "internalType": "bool",
+                "name": "approved",
+                "type": "bool"
+            }
+        ],
+        "name": "ApprovalForAll",
+        "type": "event"
+    },
+    {
+        "anonymous": false,
+        "inputs": [
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "previousOwner",
+                "type": "address"
+            },
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "newOwner",
+                "type": "address"
+            }
+        ],
+        "name": "OwnershipTransferred",
+        "type": "event"
+    },
+    {
+        "anonymous": false,
+        "inputs": [
+            {
+                "indexed": true,
+                "internalType": "bytes32",
+                "name": "role",
+                "type": "bytes32"
+            },
+            {
+                "indexed": true,
+                "internalType": "bytes32",
+                "name": "previousAdminRole",
+                "type": "bytes32"
+            },
+            {
+                "indexed": true,
+                "internalType": "bytes32",
+                "name": "newAdminRole",
+                "type": "bytes32"
+            }
+        ],
+        "name": "RoleAdminChanged",
+        "type": "event"
+    },
+    {
+        "anonymous": false,
+        "inputs": [
+            {
+                "indexed": true,
+                "internalType": "bytes32",
+                "name": "role",
+                "type": "bytes32"
+            },
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "account",
+                "type": "address"
+            },
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "sender",
+                "type": "address"
+            }
+        ],
+        "name": "RoleGranted",
+        "type": "event"
+    },
+    {
+        "anonymous": false,
+        "inputs": [
+            {
+                "indexed": true,
+                "internalType": "bytes32",
+                "name": "role",
+                "type": "bytes32"
+            },
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "account",
+                "type": "address"
+            },
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "sender",
+                "type": "address"
+            }
+        ],
+        "name": "RoleRevoked",
+        "type": "event"
+    },
+    {
+        "anonymous": false,
+        "inputs": [
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "from",
+                "type": "address"
+            },
+            {
+                "indexed": true,
+                "internalType": "address",
+                "name": "to",
+                "type": "address"
+            },
+            {
+                "indexed": true,
+                "internalType": "uint256",
+                "name": "tokenId",
+                "type": "uint256"
+            }
+        ],
+        "name": "Transfer",
+        "type": "event"
+    },
+    {
+        "inputs": [],
+        "name": "DEFAULT_ADMIN_ROLE",
+        "outputs": [
+            {
+                "internalType": "bytes32",
+                "name": "",
+                "type": "bytes32"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [],
+        "name": "MINTER_ROLE",
+        "outputs": [
+            {
+                "internalType": "bytes32",
+                "name": "",
+                "type": "bytes32"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "to",
+                "type": "address"
+            },
+            {
+                "internalType": "uint256",
+                "name": "tokenId",
+                "type": "uint256"
+            }
+        ],
+        "name": "approve",
+        "outputs": [],
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "owner",
+                "type": "address"
+            }
+        ],
+        "name": "balanceOf",
+        "outputs": [
+            {
+                "internalType": "uint256",
+                "name": "",
+                "type": "uint256"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [],
+        "name": "baseURI",
+        "outputs": [
+            {
+                "internalType": "string",
+                "name": "",
+                "type": "string"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "uint256",
+                "name": "tokenId",
+                "type": "uint256"
+            }
+        ],
+        "name": "getApproved",
+        "outputs": [
+            {
+                "internalType": "address",
+                "name": "",
+                "type": "address"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "bytes32",
+                "name": "role",
+                "type": "bytes32"
+            }
+        ],
+        "name": "getRoleAdmin",
+        "outputs": [
+            {
+                "internalType": "bytes32",
+                "name": "",
+                "type": "bytes32"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "bytes32",
+                "name": "role",
+                "type": "bytes32"
+            },
+            {
+                "internalType": "uint256",
+                "name": "index",
+                "type": "uint256"
+            }
+        ],
+        "name": "getRoleMember",
+        "outputs": [
+            {
+                "internalType": "address",
+                "name": "",
+                "type": "address"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "bytes32",
+                "name": "role",
+                "type": "bytes32"
+            }
+        ],
+        "name": "getRoleMemberCount",
+        "outputs": [
+            {
+                "internalType": "uint256",
+                "name": "",
+                "type": "uint256"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "bytes32",
+                "name": "role",
+                "type": "bytes32"
+            },
+            {
+                "internalType": "address",
+                "name": "account",
+                "type": "address"
+            }
+        ],
+        "name": "grantRole",
+        "outputs": [],
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "bytes32",
+                "name": "role",
+                "type": "bytes32"
+            },
+            {
+                "internalType": "address",
+                "name": "account",
+                "type": "address"
+            }
+        ],
+        "name": "hasRole",
+        "outputs": [
+            {
+                "internalType": "bool",
+                "name": "",
+                "type": "bool"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "owner",
+                "type": "address"
+            },
+            {
+                "internalType": "address",
+                "name": "operator",
+                "type": "address"
+            }
+        ],
+        "name": "isApprovedForAll",
+        "outputs": [
+            {
+                "internalType": "bool",
+                "name": "",
+                "type": "bool"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "_recipient",
+                "type": "address"
+            },
+            {
+                "internalType": "string",
+                "name": "_tokenURI",
+                "type": "string"
+            }
+        ],
+        "name": "mint",
+        "outputs": [
+            {
+                "internalType": "uint256",
+                "name": "",
+                "type": "uint256"
+            }
+        ],
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "inputs": [],
+        "name": "name",
+        "outputs": [
+            {
+                "internalType": "string",
+                "name": "",
+                "type": "string"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [],
+        "name": "owner",
+        "outputs": [
+            {
+                "internalType": "address",
+                "name": "",
+                "type": "address"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "uint256",
+                "name": "tokenId",
+                "type": "uint256"
+            }
+        ],
+        "name": "ownerOf",
+        "outputs": [
+            {
+                "internalType": "address",
+                "name": "",
+                "type": "address"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [],
+        "name": "renounceOwnership",
+        "outputs": [],
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "bytes32",
+                "name": "role",
+                "type": "bytes32"
+            },
+            {
+                "internalType": "address",
+                "name": "account",
+                "type": "address"
+            }
+        ],
+        "name": "renounceRole",
+        "outputs": [],
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "bytes32",
+                "name": "role",
+                "type": "bytes32"
+            },
+            {
+                "internalType": "address",
+                "name": "account",
+                "type": "address"
+            }
+        ],
+        "name": "revokeRole",
+        "outputs": [],
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "from",
+                "type": "address"
+            },
+            {
+                "internalType": "address",
+                "name": "to",
+                "type": "address"
+            },
+            {
+                "internalType": "uint256",
+                "name": "tokenId",
+                "type": "uint256"
+            }
+        ],
+        "name": "safeTransferFrom",
+        "outputs": [],
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "from",
+                "type": "address"
+            },
+            {
+                "internalType": "address",
+                "name": "to",
+                "type": "address"
+            },
+            {
+                "internalType": "uint256",
+                "name": "tokenId",
+                "type": "uint256"
+            },
+            {
+                "internalType": "bytes",
+                "name": "_data",
+                "type": "bytes"
+            }
+        ],
+        "name": "safeTransferFrom",
+        "outputs": [],
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "operator",
+                "type": "address"
+            },
+            {
+                "internalType": "bool",
+                "name": "approved",
+                "type": "bool"
+            }
+        ],
+        "name": "setApprovalForAll",
+        "outputs": [],
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "bytes4",
+                "name": "interfaceId",
+                "type": "bytes4"
+            }
+        ],
+        "name": "supportsInterface",
+        "outputs": [
+            {
+                "internalType": "bool",
+                "name": "",
+                "type": "bool"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [],
+        "name": "symbol",
+        "outputs": [
+            {
+                "internalType": "string",
+                "name": "",
+                "type": "string"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "uint256",
+                "name": "index",
+                "type": "uint256"
+            }
+        ],
+        "name": "tokenByIndex",
+        "outputs": [
+            {
+                "internalType": "uint256",
+                "name": "",
+                "type": "uint256"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "owner",
+                "type": "address"
+            },
+            {
+                "internalType": "uint256",
+                "name": "index",
+                "type": "uint256"
+            }
+        ],
+        "name": "tokenOfOwnerByIndex",
+        "outputs": [
+            {
+                "internalType": "uint256",
+                "name": "",
+                "type": "uint256"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "uint256",
+                "name": "tokenId",
+                "type": "uint256"
+            }
+        ],
+        "name": "tokenURI",
+        "outputs": [
+            {
+                "internalType": "string",
+                "name": "",
+                "type": "string"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [],
+        "name": "totalSupply",
+        "outputs": [
+            {
+                "internalType": "uint256",
+                "name": "",
+                "type": "uint256"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "from",
+                "type": "address"
+            },
+            {
+                "internalType": "address",
+                "name": "to",
+                "type": "address"
+            },
+            {
+                "internalType": "uint256",
+                "name": "tokenId",
+                "type": "uint256"
+            }
+        ],
+        "name": "transferFrom",
+        "outputs": [],
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "newOwner",
+                "type": "address"
+            }
+        ],
+        "name": "transferOwnership",
+        "outputs": [],
+        "stateMutability": "nonpayable",
+        "type": "function"
+    }
+]
diff --git a/apps/api/src/app/hardhat/export/RewardDistributor.json b/apps/api/src/app/hardhat/export/RewardDistributor.json
new file mode 100644
index 000000000..18ccb672a
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/RewardDistributor.json
@@ -0,0 +1,670 @@
+{
+    "_format": "hh-sol-artifact-1",
+    "contractName": "RewardDistributor",
+    "sourceName": "contracts/RewardDistributor.sol",
+    "abi": [
+        {
+            "inputs": [],
+            "stateMutability": "nonpayable",
+            "type": "constructor"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "newAdmin",
+                    "type": "address"
+                }
+            ],
+            "name": "NewAdmin",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": false,
+                    "internalType": "address",
+                    "name": "user",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "bool",
+                    "name": "enabled",
+                    "type": "bool"
+                }
+            ],
+            "name": "OnlyCallerOptIn",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": false,
+                    "internalType": "contract IERC20",
+                    "name": "token",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "RewardDeposit",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "token",
+                    "type": "address"
+                }
+            ],
+            "name": "TokenAdded",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": false,
+                    "internalType": "contract IERC20",
+                    "name": "token",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "lastCheckpointTimestamp",
+                    "type": "uint256"
+                }
+            ],
+            "name": "TokenCheckpointed",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": false,
+                    "internalType": "address",
+                    "name": "user",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "contract IERC20",
+                    "name": "token",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "userTokenTimeCursor",
+                    "type": "uint256"
+                }
+            ],
+            "name": "TokensClaimed",
+            "type": "event"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address[]",
+                    "name": "tokens",
+                    "type": "address[]"
+                }
+            ],
+            "name": "addAllowedRewardTokens",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "admin",
+            "outputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "name": "allowedRewardTokens",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "checkpoint",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "contract IERC20",
+                    "name": "token",
+                    "type": "address"
+                }
+            ],
+            "name": "checkpointToken",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "contract IERC20[]",
+                    "name": "tokens",
+                    "type": "address[]"
+                }
+            ],
+            "name": "checkpointTokens",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "user",
+                    "type": "address"
+                }
+            ],
+            "name": "checkpointUser",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "user",
+                    "type": "address"
+                },
+                {
+                    "internalType": "contract IERC20",
+                    "name": "token",
+                    "type": "address"
+                }
+            ],
+            "name": "claimToken",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "user",
+                    "type": "address"
+                },
+                {
+                    "internalType": "contract IERC20[]",
+                    "name": "tokens",
+                    "type": "address[]"
+                }
+            ],
+            "name": "claimTokens",
+            "outputs": [
+                {
+                    "internalType": "uint256[]",
+                    "name": "",
+                    "type": "uint256[]"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "contract IERC20",
+                    "name": "token",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "depositToken",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "contract IERC20[]",
+                    "name": "tokens",
+                    "type": "address[]"
+                },
+                {
+                    "internalType": "uint256[]",
+                    "name": "amounts",
+                    "type": "uint256[]"
+                }
+            ],
+            "name": "depositTokens",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "contract IERC20",
+                    "name": "token",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "faucetDepositToken",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "getAllowedRewardTokens",
+            "outputs": [
+                {
+                    "internalType": "address[]",
+                    "name": "",
+                    "type": "address[]"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "getDomainSeparator",
+            "outputs": [
+                {
+                    "internalType": "bytes32",
+                    "name": "",
+                    "type": "bytes32"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "account",
+                    "type": "address"
+                }
+            ],
+            "name": "getNextNonce",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "getTimeCursor",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "contract IERC20",
+                    "name": "token",
+                    "type": "address"
+                }
+            ],
+            "name": "getTokenLastBalance",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "contract IERC20",
+                    "name": "token",
+                    "type": "address"
+                }
+            ],
+            "name": "getTokenTimeCursor",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "contract IERC20",
+                    "name": "token",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "timestamp",
+                    "type": "uint256"
+                }
+            ],
+            "name": "getTokensDistributedInWeek",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "timestamp",
+                    "type": "uint256"
+                }
+            ],
+            "name": "getTotalSupplyAtTimestamp",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "user",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "timestamp",
+                    "type": "uint256"
+                }
+            ],
+            "name": "getUserBalanceAtTimestamp",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "user",
+                    "type": "address"
+                }
+            ],
+            "name": "getUserTimeCursor",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "user",
+                    "type": "address"
+                },
+                {
+                    "internalType": "contract IERC20",
+                    "name": "token",
+                    "type": "address"
+                }
+            ],
+            "name": "getUserTokenTimeCursor",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "getVotingEscrow",
+            "outputs": [
+                {
+                    "internalType": "contract IVotingEscrow",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "contract IVotingEscrow",
+                    "name": "votingEscrow",
+                    "type": "address"
+                },
+                {
+                    "internalType": "contract IRewardFaucet",
+                    "name": "rewardFaucet_",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "startTime",
+                    "type": "uint256"
+                },
+                {
+                    "internalType": "address",
+                    "name": "admin_",
+                    "type": "address"
+                }
+            ],
+            "name": "initialize",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "isInitialized",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "user",
+                    "type": "address"
+                }
+            ],
+            "name": "isOnlyCallerEnabled",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "rewardFaucet",
+            "outputs": [
+                {
+                    "internalType": "contract IRewardFaucet",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "bool",
+                    "name": "enabled",
+                    "type": "bool"
+                }
+            ],
+            "name": "setOnlyCallerCheck",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "user",
+                    "type": "address"
+                },
+                {
+                    "internalType": "bool",
+                    "name": "enabled",
+                    "type": "bool"
+                },
+                {
+                    "internalType": "bytes",
+                    "name": "signature",
+                    "type": "bytes"
+                }
+            ],
+            "name": "setOnlyCallerCheckWithSignature",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "newAdmin",
+                    "type": "address"
+                }
+            ],
+            "name": "transferAdmin",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        }
+    ],
+    "bytecode": "0x60e060405234801561001057600080fd5b5060408051808201825260118152702932bbb0b9322234b9ba3934b13aba37b960791b602080830191825283518085019094526001808552603160f81b9185019182529251909120608052915190912060a0527f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60c05260025560805160a05160c051612b476100b660003980611d9e525080611de0525080611dbf5250612b476000f3fe608060405234801561001057600080fd5b50600436106101e55760003560e01c806390193b7c1161010f578063de681faf116100a2578063ed3a088711610071578063ed3a0887146103ea578063f213bd8c146103fd578063f851a44014610410578063fcaa54ee14610418576101e5565b8063de681faf146103a7578063e811f44b146103ba578063ece7514d146103cd578063ed24911d146103e2576101e5565b8063be203094116100de578063be20309414610366578063c2c4c5c114610379578063ca31879d14610381578063d3dc4ca114610394576101e5565b806390193b7c1461031a578063905d10ac1461032d578063a1648aa514610340578063acbc142814610353576101e5565b80634c9a47d8116101875780638050a7ee116101565780638050a7ee146102cc57806382aa5ad4146102df578063876e69a1146102e757806388720467146102fa576101e5565b80634c9a47d8146102805780634f3c50901461029357806375829def146102a65780637b8d6221146102b9576101e5565b8063338b5dea116101c3578063338b5dea1461023d5780633902b9bc14610250578063392e53cd14610263578063397bcd4114610278576101e5565b806308b0308a146101ea57806314866e08146102085780632308805b1461021d575b600080fd5b6101f261042b565b6040516101ff91906127b8565b60405180910390f35b61021b61021636600461248e565b61043f565b005b61023061022b36600461248e565b61045b565b6040516101ff9190612892565b61021b61024b3660046125e0565b61048a565b61021b61025e36600461248e565b610543565b61026b61058e565b6040516101ff9190612887565b6101f2610597565b61021b61028e3660046125e0565b6105a6565b6102306102a1366004612788565b610633565b61021b6102b436600461248e565b610645565b61021b6102c736600461264a565b6106df565b6102306102da3660046125a8565b61087e565b610230610893565b6102306102f536600461248e565b610899565b61030d6103083660046124aa565b6108c4565b6040516101ff919061284f565b61023061032836600461248e565b610a87565b61021b61033b36600461260b565b610aa2565b61026b61034e36600461248e565b610b35565b61023061036136600461248e565b610b53565b61021b6103743660046126cc565b610b7e565b61021b610d45565b61023061038f3660046125a8565b610d5f565b6102306103a23660046125e0565b610e41565b6102306103b53660046125e0565b610e69565b61021b6103c83660046126b2565b610e91565b6103d5610e9b565b6040516101ff919061280e565b610230610efd565b61026b6103f836600461248e565b610f0c565b61021b61040b36600461260b565b610f21565b6101f26110bc565b61021b6104263660046124fc565b6110cb565b60035461010090046001600160a01b031690565b610447611159565b61045081611170565b610458611566565b50565b6001600160a01b0381166000908152600b6020526040902054600160801b90046001600160801b03165b919050565b610492611159565b6001600160a01b0382166000908152600a602052604090205460ff166104d35760405162461bcd60e51b81526004016104ca906128e3565b60405180910390fd5b6104de82600061156d565b6104f36001600160a01b0383163330846118e8565b6104fe82600161156d565b7f95bf5847357310d24f8d03d8bad76c8ee329dfd3a3cb200df21c7bd1619e93bd828260405161052f9291906127f5565b60405180910390a161053f611566565b5050565b61054b611159565b6001600160a01b0381166000908152600a602052604090205460ff166105835760405162461bcd60e51b81526004016104ca906128e3565b61045081600161156d565b60035460ff1681565b6004546001600160a01b031681565b6001600160a01b0382166000908152600a602052604090205460ff166105de5760405162461bcd60e51b81526004016104ca906128e3565b6004546001600160a01b031633146106085760405162461bcd60e51b81526004016104ca9061297b565b61061382600061156d565b6106286001600160a01b0383163330846118e8565b61053f82600161156d565b60009081526007602052604090205490565b6008546001600160a01b0316331461066f5760405162461bcd60e51b81526004016104ca90612ab6565b6001600160a01b0381166106955760405162461bcd60e51b81526004016104ca90612a1c565b600880546001600160a01b0319166001600160a01b0383169081179091556040517f71614071b88dee5e0b2ae578a9dd7b2ebbe9ae832ba419dc0242cd065a290b6c90600090a250565b6106e7611159565b6106f18382611942565b8260005b8181101561086e57600a600087878481811061070d57fe5b9050602002016020810190610722919061248e565b6001600160a01b0316815260208101919091526040016000205460ff1661075b5760405162461bcd60e51b81526004016104ca906128e3565b61078686868381811061076a57fe5b905060200201602081019061077f919061248e565b600061156d565b6107d0333086868581811061079757fe5b905060200201358989868181106107aa57fe5b90506020020160208101906107bf919061248e565b6001600160a01b03169291906118e8565b6107fb8686838181106107df57fe5b90506020020160208101906107f4919061248e565b600161156d565b7f95bf5847357310d24f8d03d8bad76c8ee329dfd3a3cb200df21c7bd1619e93bd86868381811061082857fe5b905060200201602081019061083d919061248e565b85858481811061084957fe5b9050602002013560405161085e9291906127f5565b60405180910390a16001016106f5565b5050610878611566565b50505050565b600061088a838361194f565b90505b92915050565b60065490565b6001600160a01b03166000908152600d6020526040902054600160401b90046001600160401b031690565b60606108ce611159565b836108d8816119cc565b6108e0611a04565b6108e985611170565b826000816001600160401b038111801561090257600080fd5b5060405190808252806020026020018201604052801561092c578160200160208202803683370190505b50905060005b82811015610a7357600a600088888481811061094a57fe5b905060200201602081019061095f919061248e565b6001600160a01b0316815260208101919091526040016000205460ff166109985760405162461bcd60e51b81526004016104ca906128e3565b6109a787878381811061076a57fe5b6109d1888888848181106109b757fe5b90506020020160208101906109cc919061248e565b611b58565b8282815181106109dd57fe5b60209081029190910101526004546001600160a01b031663c7b56abe888884818110610a0557fe5b9050602002016020810190610a1a919061248e565b6040518263ffffffff1660e01b8152600401610a3691906127b8565b600060405180830381600087803b158015610a5057600080fd5b505af1158015610a64573d6000803e3d6000fd5b50505050806001019050610932565b5092505050610a80611566565b9392505050565b6001600160a01b031660009081526020819052604090205490565b610aaa611159565b8060005b81811015610b2b57600a6000858584818110610ac657fe5b9050602002016020810190610adb919061248e565b6001600160a01b0316815260208101919091526040016000205460ff16610b145760405162461bcd60e51b81526004016104ca906128e3565b610b238484838181106107df57fe5b600101610aae565b505061053f611566565b6001600160a01b031660009081526001602052604090205460ff1690565b6001600160a01b03166000908152600b6020526040902054600160401b90046001600160401b031690565b60035460ff1615610ba15760405162461bcd60e51b81526004016104ca9061293c565b6003805460ff191660011790556001600160a01b03811615801590610bce57506001600160a01b03831615155b610bea5760405162461bcd60e51b81526004016104ca9061295c565b600880546001600160a01b038084166001600160a01b0319928316179092556004805486841692169190911790556003805491861661010002610100600160a81b0319909216919091179055610c3f82611d2a565b91506000610c4c42611d2a565b905080831015610c6e5760405162461bcd60e51b81526004016104ca906129e7565b80625c490001831115610c935760405162461bcd60e51b81526004016104ca90612a42565b80831415610d375760405163bd85b03960e01b81526000906001600160a01b0387169063bd85b03990610cca908590600401612892565b60206040518083038186803b158015610ce257600080fd5b505afa158015610cf6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d1a91906127a0565b11610d375760405162461bcd60e51b81526004016104ca90612a6e565b505060058190556006555050565b610d4d611159565b610d55611a04565b610d5d611566565b565b6000610d69611159565b82610d73816119cc565b6001600160a01b0383166000908152600a602052604090205460ff16610dab5760405162461bcd60e51b81526004016104ca906128e3565b610db3611a04565b610dbc84611170565b610dc783600061156d565b6000610dd38585611b58565b600480546040516363dab55f60e11b81529293506001600160a01b03169163c7b56abe91610e03918891016127b8565b600060405180830381600087803b158015610e1d57600080fd5b505af1158015610e31573d6000803e3d6000fd5b509294505050505061088d611566565b6001600160a01b03919091166000908152600c60209081526040808320938352929052205490565b6001600160a01b03919091166000908152600e60209081526040808320938352929052205490565b6104583382611d36565b60606009805480602002602001604051908101604052809291908181526020018280548015610ef357602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311610ed5575b5050505050905090565b6000610f07611d9a565b905090565b600a6020526000908152604090205460ff1681565b6008546001600160a01b03163314610f4b5760405162461bcd60e51b81526004016104ca90612ab6565b60005b818110156110b757600a6000848484818110610f6657fe5b9050602002016020810190610f7b919061248e565b6001600160a01b0316815260208101919091526040016000205460ff1615610fb55760405162461bcd60e51b81526004016104ca906128bc565b6001600a6000858585818110610fc757fe5b9050602002016020810190610fdc919061248e565b6001600160a01b031681526020810191909152604001600020805460ff1916911515919091179055600983838381811061101257fe5b9050602002016020810190611027919061248e565b81546001810183556000928352602090922090910180546001600160a01b0319166001600160a01b0390921691909117905582828281811061106557fe5b905060200201602081019061107a919061248e565b6001600160a01b03167f784c8f4dbf0ffedd6e72c76501c545a70f8b203b30a26ce542bf92ba87c248a460405160405180910390a2600101610f4e565b505050565b6008546001600160a01b031681565b60007fbd291ffccec065968fe20c5f8debdad73ab50837733f357eeae8814178015a9084846110f987610a87565b60405160200180858152602001846001600160a01b03168152602001831515815260200182815260200194505050505060405160208183030381529060405280519060200120905061114f8482846101f8611e58565b6108788484611d36565b61116a600280541415610190611e67565b60028055565b60035460405163010ae75760e01b815260009161010090046001600160a01b03169063010ae757906111a69085906004016127b8565b60206040518083038186803b1580156111be57600080fd5b505afa1580156111d2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111f691906127a0565b9050806112035750610458565b6001600160a01b0382166000908152600d6020526040812080549091600160401b9091046001600160401b0316908161124c5761124585600554600087611e75565b9050611289565b42821061125c5750505050610458565b508154600160801b90046001600160801b0316601481850311156112895761128685838387611e75565b90505b80611292575060015b6003546040516328d09d4760e01b815260009161010090046001600160a01b0316906328d09d47906112ca90899086906004016127f5565b60806040518083038186803b1580156112e257600080fd5b505afa1580156112f6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061131a919061271e565b9050826113775760055442116113425760405162461bcd60e51b81526004016104ca906129a0565b61135a6005546113558360400151611f54565b611f64565b845467ffffffffffffffff19166001600160401b03821617855592505b61137f6123f6565b60005b6032811015611519578260400151851015801561139f5750868411155b1561147557600184019350829150868411156113e75760405180608001604052806000600f0b81526020016000600f0b81526020016000815260200160008152509250611470565b6003546040516328d09d4760e01b81526101009091046001600160a01b0316906328d09d479061141d908b9088906004016127f5565b60806040518083038186803b15801561143557600080fd5b505afa158015611449573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061146d919061271e565b92505b611511565b42851061148157611519565b6000826040015186039050600081846020015102600f0b8460000151600f0b136114ac5760006114bd565b81846020015102846000015103600f0b5b9050801580156114cc57508886115b156114e3576114da42611f54565b96505050611519565b6001600160a01b038a166000908152600e602090815260408083208a84529091529020555062093a80909401935b600101611382565b505083546001600160801b0316600019929092016001600160401b03908116600160801b029290921767ffffffffffffffff60401b1916600160401b939092169290920217909155505050565b6001600255565b6001600160a01b0382166000908152600b6020526040812080549091600160401b9091046001600160401b031690816115ee574291506115ac42611d2a565b835467ffffffffffffffff19166001600160401b039190911617835560055442116115e95760405162461bcd60e51b81526004016104ca906129a0565b611640565b81420390508361164057600061160383611d2a565b61160c42611d2a565b1490506000620151804261161f42611f54565b0310905081801561162e575080155b1561163d57505050505061053f565b50505b825467ffffffffffffffff60401b1916600160401b426001600160401b0316021783556040516370a0823160e01b81526000906001600160a01b038716906370a08231906116929030906004016127b8565b60206040518083038186803b1580156116aa57600080fd5b505afa1580156116be573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116e291906127a0565b8454909150600090611705908390600160801b90046001600160801b0316611f70565b90508061171657505050505061053f565b6001600160801b0382111561173d5760405162461bcd60e51b81526004016104ca90612905565b600061174885611d2a565b6001600160a01b0389166000908152600c602052604081209192509081805b601481101561189f578462093a800193508342101561180a578715801561178d57508842145b1561179a578591506117ab565b878942038702816117a757fe5b0491505b6000858152602084905260409020546001600160801b03906117cd9084611f7e565b1161180557600085815260208490526040902080548301905589546001600160801b03600160801b808304821685018216029116178a555b61189f565b8715801561181757508884145b1561182457859150611835565b8789850387028161183157fe5b0491505b6000858152602084905260409020546001600160801b03906118579084611f7e565b1161188f57600085815260208490526040902080548301905589546001600160801b03600160801b808304821685018216029116178a555b9297508793508392600101611767565b507f9b7f1a85a4c9b4e59e1b6527d9969c50cdfb3a1a467d0c4a51fb0ed8bf07f1308b868a6040516118d39392919061289b565b60405180910390a15050505050505050505050565b604080516001600160a01b0380861660248301528416604482015260648082018490528251808303909101815260849091019091526020810180516001600160e01b03166323b872dd60e01b179052610878908590611f90565b61053f8183146067611e67565b6001600160a01b038083166000908152600f60209081526040808320938516835292905290812054801561198457905061088d565b6001600160a01b038085166000908152600d60209081526040808320549387168352600b9091529020546119c4916001600160401b039081169116611f64565b949350505050565b6001600160a01b03811660009081526001602052604090205460ff161561045857610458336001600160a01b03831614610191611e67565b6006546000611a1242611d2a565b905080821180611a2157504281145b15611a2d575050610d5d565b600360019054906101000a90046001600160a01b03166001600160a01b031663c2c4c5c16040518163ffffffff1660e01b8152600401600060405180830381600087803b158015611a7d57600080fd5b505af1158015611a91573d6000803e3d6000fd5b5050505060005b6014811015611b515781831115611aae57611b51565b60035460405163bd85b03960e01b81526101009091046001600160a01b03169063bd85b03990611ae2908690600401612892565b60206040518083038186803b158015611afa57600080fd5b505afa158015611b0e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b3291906127a0565b60008481526007602052604090205562093a8090920191600101611a98565b5050600655565b6001600160a01b0381166000908152600b6020526040812081611b7b858561194f565b6006546001600160a01b0387166000908152600d602052604081205492935091611be291611bc291611bbd9190600160401b90046001600160401b031661207a565b611f54565b8454611bdd90600160401b90046001600160401b0316611d2a565b61207a565b6001600160a01b038087166000908152600c60209081526040808320938b168352600e9091528120929350909190805b6014811015611c7e57848610611c2757611c7e565b600086815260076020526040902054611c3f57611c7e565b60008681526007602090815260408083205486835281842054928890529220540281611c6757fe5b62093a809790970196049190910190600101611c12565b506001600160a01b03808a166000908152600f60209081526040808320938c168352929052208590558015611d1e5785546001600160801b03600160801b80830482168490038216029116178655611ce06001600160a01b0389168a83612086565b7fff097c7d8b1957a4ff09ef1361b5fb54dcede3941ba836d0beb9d10bec725de689898388604051611d1594939291906127cc565b60405180910390a15b98975050505050505050565b62093a80908190040290565b6001600160a01b038216600081815260016020908152604091829020805460ff191685151590811790915582519384529083015280517fac9874a7a931a3f5c9f202c6d9cf40de5d21506993c9f9c38ca8265add89584c9281900390910190a15050565b60007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000611e076120d8565b3060405160200180868152602001858152602001848152602001838152602001826001600160a01b031681526020019550505050505060405160208183030381529060405280519060200120905090565b610878848484600019856120dc565b8161053f5761053f81612133565b60008282825b6080811015611f4857818310611e9057611f48565b6003546040516328d09d4760e01b81526002858501810104916000916101009091046001600160a01b0316906328d09d4790611ed2908d9086906004016127f5565b60806040518083038186803b158015611eea57600080fd5b505afa158015611efe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f22919061271e565b905088816040015111611f3757819450611f3e565b6001820393505b5050600101611e7b565b50909695505050505050565b600061088d62093a7f8301611d2a565b80820390821002900390565b600061088a83836001612143565b600082820161088a8482101583611e67565b600080836001600160a01b0316836040518082805190602001908083835b60208310611fcd5780518252601f199092019160209182019101611fae565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d806000811461202f576040519150601f19603f3d011682016040523d82523d6000602084013e612034565b606091505b5091509150600082141561204c573d6000803e3d6000fd5b610878815160001480612072575081806020019051602081101561206f57600080fd5b50515b6101a2611e67565b80820390821102900390565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b1790526110b7908490611f90565b4690565b60006120e785612159565b90506120fd6120f78783876121a5565b83611e67565b61210c428410156101b8611e67565b5050506001600160a01b039092166000908152602081905260409020805460010190555050565b610458816210905360ea1b6122c3565b60006121528484111583611e67565b5050900390565b6000612163611d9a565b82604051602001808061190160f01b81525060020183815260200182815260200192505050604051602081830303815290604052805190602001209050919050565b60006121b9846001600160a01b0316612324565b156122b15760408051630b135d3f60e11b808252600482018681526024830193845285516044840152855191936001600160a01b03891693631626ba7e938993899390929091606490910190602085019080838360005b83811015612228578181015183820152602001612210565b50505050905090810190601f1680156122555780820380516001836020036101000a031916815260200191505b50935050505060206040518083038186803b15801561227357600080fd5b505afa158015612287573d6000803e3d6000fd5b505050506040513d602081101561229d57600080fd5b50516001600160e01b031916149050610a80565b6122bc84848461232a565b9050610a80565b62461bcd60e51b600090815260206004526007602452600a808404818106603090810160081b958390069590950190829004918206850160101b01602363ffffff0060e086901c160160181b0190930160c81b60445260e882901c90606490fd5b3b151590565b600061233c82516041146101b9611e67565b60008060006020850151925060408501519150606085015160001a9050600060018783868660405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa1580156123b5573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811615801590611d1e5750876001600160a01b0316816001600160a01b03161498975050505050505050565b60405180608001604052806000600f0b81526020016000600f0b815260200160008152602001600081525090565b60008083601f840112612435578081fd5b5081356001600160401b0381111561244b578182fd5b602083019150836020808302850101111561246557600080fd5b9250929050565b8035801515811461048557600080fd5b8051600f81900b811461048557600080fd5b60006020828403121561249f578081fd5b813561088a81612afc565b6000806000604084860312156124be578182fd5b83356124c981612afc565b925060208401356001600160401b038111156124e3578283fd5b6124ef86828701612424565b9497909650939450505050565b600080600060608486031215612510578283fd5b833561251b81612afc565b9250602061252a85820161246c565b925060408501356001600160401b0380821115612545578384fd5b818701915087601f830112612558578384fd5b81358181111561256457fe5b612576601f8201601f19168501612ad9565b9150808252888482850101111561258b578485fd5b808484018584013784848284010152508093505050509250925092565b600080604083850312156125ba578182fd5b82356125c581612afc565b915060208301356125d581612afc565b809150509250929050565b600080604083850312156125f2578182fd5b82356125fd81612afc565b946020939093013593505050565b6000806020838503121561261d578182fd5b82356001600160401b03811115612632578283fd5b61263e85828601612424565b90969095509350505050565b6000806000806040858703121561265f578081fd5b84356001600160401b0380821115612675578283fd5b61268188838901612424565b90965094506020870135915080821115612699578283fd5b506126a687828801612424565b95989497509550505050565b6000602082840312156126c3578081fd5b61088a8261246c565b600080600080608085870312156126e1578182fd5b84356126ec81612afc565b935060208501356126fc81612afc565b925060408501359150606085013561271381612afc565b939692955090935050565b60006080828403121561272f578081fd5b604051608081018181106001600160401b038211171561274b57fe5b6040526127578361247c565b81526127656020840161247c565b602082015260408301516040820152606083015160608201528091505092915050565b600060208284031215612799578081fd5b5035919050565b6000602082840312156127b1578081fd5b5051919050565b6001600160a01b0391909116815260200190565b6001600160a01b0394851681529290931660208301526040820152606081019190915260800190565b6001600160a01b03929092168252602082015260400190565b6020808252825182820181905260009190848201906040850190845b81811015611f485783516001600160a01b03168352928401929184019160010161282a565b6020808252825182820181905260009190848201906040850190845b81811015611f485783518352928401929184019160010161286b565b901515815260200190565b90815260200190565b6001600160a01b039390931683526020830191909152604082015260600190565b6020808252600d908201526c185b1c9958591e48195e1a5cdd609a1b604082015260600190565b60208082526008908201526708585b1b1bddd95960c21b604082015260600190565b6020808252601e908201527f4d6178696d756d20746f6b656e2062616c616e63652065786365656465640000604082015260600190565b60208082526006908201526521747769636560d01b604082015260600190565b602080825260059082015264217a65726f60d81b604082015260600190565b6020808252600b908201526a1bdb9b1e4819985d58d95d60aa1b604082015260600190565b60208082526027908201527f52657761726420646973747269627574696f6e20686173206e6f7420737461726040820152661d1959081e595d60ca1b606082015260800190565b6020808252818101527f43616e6e6f74207374617274206265666f72652063757272656e74207765656b604082015260600190565b6020808252600c908201526b7a65726f206164647265737360a01b604082015260600190565b6020808252601290820152710626040eecacad6e640c8cad8c2f240dac2f60731b604082015260600190565b60208082526028908201527f5a65726f20746f74616c20737570706c7920726573756c747320696e206c6f736040820152677420746f6b656e7360c01b606082015260800190565b6020808252600990820152683737ba1030b236b4b760b91b604082015260600190565b6040518181016001600160401b0381118282101715612af457fe5b604052919050565b6001600160a01b038116811461045857600080fdfea2646970667358221220972c2bb8ecdbe63efa080ae50f636a301051bc328845b00b90454df26829a68764736f6c63430007060033",
+    "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106101e55760003560e01c806390193b7c1161010f578063de681faf116100a2578063ed3a088711610071578063ed3a0887146103ea578063f213bd8c146103fd578063f851a44014610410578063fcaa54ee14610418576101e5565b8063de681faf146103a7578063e811f44b146103ba578063ece7514d146103cd578063ed24911d146103e2576101e5565b8063be203094116100de578063be20309414610366578063c2c4c5c114610379578063ca31879d14610381578063d3dc4ca114610394576101e5565b806390193b7c1461031a578063905d10ac1461032d578063a1648aa514610340578063acbc142814610353576101e5565b80634c9a47d8116101875780638050a7ee116101565780638050a7ee146102cc57806382aa5ad4146102df578063876e69a1146102e757806388720467146102fa576101e5565b80634c9a47d8146102805780634f3c50901461029357806375829def146102a65780637b8d6221146102b9576101e5565b8063338b5dea116101c3578063338b5dea1461023d5780633902b9bc14610250578063392e53cd14610263578063397bcd4114610278576101e5565b806308b0308a146101ea57806314866e08146102085780632308805b1461021d575b600080fd5b6101f261042b565b6040516101ff91906127b8565b60405180910390f35b61021b61021636600461248e565b61043f565b005b61023061022b36600461248e565b61045b565b6040516101ff9190612892565b61021b61024b3660046125e0565b61048a565b61021b61025e36600461248e565b610543565b61026b61058e565b6040516101ff9190612887565b6101f2610597565b61021b61028e3660046125e0565b6105a6565b6102306102a1366004612788565b610633565b61021b6102b436600461248e565b610645565b61021b6102c736600461264a565b6106df565b6102306102da3660046125a8565b61087e565b610230610893565b6102306102f536600461248e565b610899565b61030d6103083660046124aa565b6108c4565b6040516101ff919061284f565b61023061032836600461248e565b610a87565b61021b61033b36600461260b565b610aa2565b61026b61034e36600461248e565b610b35565b61023061036136600461248e565b610b53565b61021b6103743660046126cc565b610b7e565b61021b610d45565b61023061038f3660046125a8565b610d5f565b6102306103a23660046125e0565b610e41565b6102306103b53660046125e0565b610e69565b61021b6103c83660046126b2565b610e91565b6103d5610e9b565b6040516101ff919061280e565b610230610efd565b61026b6103f836600461248e565b610f0c565b61021b61040b36600461260b565b610f21565b6101f26110bc565b61021b6104263660046124fc565b6110cb565b60035461010090046001600160a01b031690565b610447611159565b61045081611170565b610458611566565b50565b6001600160a01b0381166000908152600b6020526040902054600160801b90046001600160801b03165b919050565b610492611159565b6001600160a01b0382166000908152600a602052604090205460ff166104d35760405162461bcd60e51b81526004016104ca906128e3565b60405180910390fd5b6104de82600061156d565b6104f36001600160a01b0383163330846118e8565b6104fe82600161156d565b7f95bf5847357310d24f8d03d8bad76c8ee329dfd3a3cb200df21c7bd1619e93bd828260405161052f9291906127f5565b60405180910390a161053f611566565b5050565b61054b611159565b6001600160a01b0381166000908152600a602052604090205460ff166105835760405162461bcd60e51b81526004016104ca906128e3565b61045081600161156d565b60035460ff1681565b6004546001600160a01b031681565b6001600160a01b0382166000908152600a602052604090205460ff166105de5760405162461bcd60e51b81526004016104ca906128e3565b6004546001600160a01b031633146106085760405162461bcd60e51b81526004016104ca9061297b565b61061382600061156d565b6106286001600160a01b0383163330846118e8565b61053f82600161156d565b60009081526007602052604090205490565b6008546001600160a01b0316331461066f5760405162461bcd60e51b81526004016104ca90612ab6565b6001600160a01b0381166106955760405162461bcd60e51b81526004016104ca90612a1c565b600880546001600160a01b0319166001600160a01b0383169081179091556040517f71614071b88dee5e0b2ae578a9dd7b2ebbe9ae832ba419dc0242cd065a290b6c90600090a250565b6106e7611159565b6106f18382611942565b8260005b8181101561086e57600a600087878481811061070d57fe5b9050602002016020810190610722919061248e565b6001600160a01b0316815260208101919091526040016000205460ff1661075b5760405162461bcd60e51b81526004016104ca906128e3565b61078686868381811061076a57fe5b905060200201602081019061077f919061248e565b600061156d565b6107d0333086868581811061079757fe5b905060200201358989868181106107aa57fe5b90506020020160208101906107bf919061248e565b6001600160a01b03169291906118e8565b6107fb8686838181106107df57fe5b90506020020160208101906107f4919061248e565b600161156d565b7f95bf5847357310d24f8d03d8bad76c8ee329dfd3a3cb200df21c7bd1619e93bd86868381811061082857fe5b905060200201602081019061083d919061248e565b85858481811061084957fe5b9050602002013560405161085e9291906127f5565b60405180910390a16001016106f5565b5050610878611566565b50505050565b600061088a838361194f565b90505b92915050565b60065490565b6001600160a01b03166000908152600d6020526040902054600160401b90046001600160401b031690565b60606108ce611159565b836108d8816119cc565b6108e0611a04565b6108e985611170565b826000816001600160401b038111801561090257600080fd5b5060405190808252806020026020018201604052801561092c578160200160208202803683370190505b50905060005b82811015610a7357600a600088888481811061094a57fe5b905060200201602081019061095f919061248e565b6001600160a01b0316815260208101919091526040016000205460ff166109985760405162461bcd60e51b81526004016104ca906128e3565b6109a787878381811061076a57fe5b6109d1888888848181106109b757fe5b90506020020160208101906109cc919061248e565b611b58565b8282815181106109dd57fe5b60209081029190910101526004546001600160a01b031663c7b56abe888884818110610a0557fe5b9050602002016020810190610a1a919061248e565b6040518263ffffffff1660e01b8152600401610a3691906127b8565b600060405180830381600087803b158015610a5057600080fd5b505af1158015610a64573d6000803e3d6000fd5b50505050806001019050610932565b5092505050610a80611566565b9392505050565b6001600160a01b031660009081526020819052604090205490565b610aaa611159565b8060005b81811015610b2b57600a6000858584818110610ac657fe5b9050602002016020810190610adb919061248e565b6001600160a01b0316815260208101919091526040016000205460ff16610b145760405162461bcd60e51b81526004016104ca906128e3565b610b238484838181106107df57fe5b600101610aae565b505061053f611566565b6001600160a01b031660009081526001602052604090205460ff1690565b6001600160a01b03166000908152600b6020526040902054600160401b90046001600160401b031690565b60035460ff1615610ba15760405162461bcd60e51b81526004016104ca9061293c565b6003805460ff191660011790556001600160a01b03811615801590610bce57506001600160a01b03831615155b610bea5760405162461bcd60e51b81526004016104ca9061295c565b600880546001600160a01b038084166001600160a01b0319928316179092556004805486841692169190911790556003805491861661010002610100600160a81b0319909216919091179055610c3f82611d2a565b91506000610c4c42611d2a565b905080831015610c6e5760405162461bcd60e51b81526004016104ca906129e7565b80625c490001831115610c935760405162461bcd60e51b81526004016104ca90612a42565b80831415610d375760405163bd85b03960e01b81526000906001600160a01b0387169063bd85b03990610cca908590600401612892565b60206040518083038186803b158015610ce257600080fd5b505afa158015610cf6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d1a91906127a0565b11610d375760405162461bcd60e51b81526004016104ca90612a6e565b505060058190556006555050565b610d4d611159565b610d55611a04565b610d5d611566565b565b6000610d69611159565b82610d73816119cc565b6001600160a01b0383166000908152600a602052604090205460ff16610dab5760405162461bcd60e51b81526004016104ca906128e3565b610db3611a04565b610dbc84611170565b610dc783600061156d565b6000610dd38585611b58565b600480546040516363dab55f60e11b81529293506001600160a01b03169163c7b56abe91610e03918891016127b8565b600060405180830381600087803b158015610e1d57600080fd5b505af1158015610e31573d6000803e3d6000fd5b509294505050505061088d611566565b6001600160a01b03919091166000908152600c60209081526040808320938352929052205490565b6001600160a01b03919091166000908152600e60209081526040808320938352929052205490565b6104583382611d36565b60606009805480602002602001604051908101604052809291908181526020018280548015610ef357602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311610ed5575b5050505050905090565b6000610f07611d9a565b905090565b600a6020526000908152604090205460ff1681565b6008546001600160a01b03163314610f4b5760405162461bcd60e51b81526004016104ca90612ab6565b60005b818110156110b757600a6000848484818110610f6657fe5b9050602002016020810190610f7b919061248e565b6001600160a01b0316815260208101919091526040016000205460ff1615610fb55760405162461bcd60e51b81526004016104ca906128bc565b6001600a6000858585818110610fc757fe5b9050602002016020810190610fdc919061248e565b6001600160a01b031681526020810191909152604001600020805460ff1916911515919091179055600983838381811061101257fe5b9050602002016020810190611027919061248e565b81546001810183556000928352602090922090910180546001600160a01b0319166001600160a01b0390921691909117905582828281811061106557fe5b905060200201602081019061107a919061248e565b6001600160a01b03167f784c8f4dbf0ffedd6e72c76501c545a70f8b203b30a26ce542bf92ba87c248a460405160405180910390a2600101610f4e565b505050565b6008546001600160a01b031681565b60007fbd291ffccec065968fe20c5f8debdad73ab50837733f357eeae8814178015a9084846110f987610a87565b60405160200180858152602001846001600160a01b03168152602001831515815260200182815260200194505050505060405160208183030381529060405280519060200120905061114f8482846101f8611e58565b6108788484611d36565b61116a600280541415610190611e67565b60028055565b60035460405163010ae75760e01b815260009161010090046001600160a01b03169063010ae757906111a69085906004016127b8565b60206040518083038186803b1580156111be57600080fd5b505afa1580156111d2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111f691906127a0565b9050806112035750610458565b6001600160a01b0382166000908152600d6020526040812080549091600160401b9091046001600160401b0316908161124c5761124585600554600087611e75565b9050611289565b42821061125c5750505050610458565b508154600160801b90046001600160801b0316601481850311156112895761128685838387611e75565b90505b80611292575060015b6003546040516328d09d4760e01b815260009161010090046001600160a01b0316906328d09d47906112ca90899086906004016127f5565b60806040518083038186803b1580156112e257600080fd5b505afa1580156112f6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061131a919061271e565b9050826113775760055442116113425760405162461bcd60e51b81526004016104ca906129a0565b61135a6005546113558360400151611f54565b611f64565b845467ffffffffffffffff19166001600160401b03821617855592505b61137f6123f6565b60005b6032811015611519578260400151851015801561139f5750868411155b1561147557600184019350829150868411156113e75760405180608001604052806000600f0b81526020016000600f0b81526020016000815260200160008152509250611470565b6003546040516328d09d4760e01b81526101009091046001600160a01b0316906328d09d479061141d908b9088906004016127f5565b60806040518083038186803b15801561143557600080fd5b505afa158015611449573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061146d919061271e565b92505b611511565b42851061148157611519565b6000826040015186039050600081846020015102600f0b8460000151600f0b136114ac5760006114bd565b81846020015102846000015103600f0b5b9050801580156114cc57508886115b156114e3576114da42611f54565b96505050611519565b6001600160a01b038a166000908152600e602090815260408083208a84529091529020555062093a80909401935b600101611382565b505083546001600160801b0316600019929092016001600160401b03908116600160801b029290921767ffffffffffffffff60401b1916600160401b939092169290920217909155505050565b6001600255565b6001600160a01b0382166000908152600b6020526040812080549091600160401b9091046001600160401b031690816115ee574291506115ac42611d2a565b835467ffffffffffffffff19166001600160401b039190911617835560055442116115e95760405162461bcd60e51b81526004016104ca906129a0565b611640565b81420390508361164057600061160383611d2a565b61160c42611d2a565b1490506000620151804261161f42611f54565b0310905081801561162e575080155b1561163d57505050505061053f565b50505b825467ffffffffffffffff60401b1916600160401b426001600160401b0316021783556040516370a0823160e01b81526000906001600160a01b038716906370a08231906116929030906004016127b8565b60206040518083038186803b1580156116aa57600080fd5b505afa1580156116be573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116e291906127a0565b8454909150600090611705908390600160801b90046001600160801b0316611f70565b90508061171657505050505061053f565b6001600160801b0382111561173d5760405162461bcd60e51b81526004016104ca90612905565b600061174885611d2a565b6001600160a01b0389166000908152600c602052604081209192509081805b601481101561189f578462093a800193508342101561180a578715801561178d57508842145b1561179a578591506117ab565b878942038702816117a757fe5b0491505b6000858152602084905260409020546001600160801b03906117cd9084611f7e565b1161180557600085815260208490526040902080548301905589546001600160801b03600160801b808304821685018216029116178a555b61189f565b8715801561181757508884145b1561182457859150611835565b8789850387028161183157fe5b0491505b6000858152602084905260409020546001600160801b03906118579084611f7e565b1161188f57600085815260208490526040902080548301905589546001600160801b03600160801b808304821685018216029116178a555b9297508793508392600101611767565b507f9b7f1a85a4c9b4e59e1b6527d9969c50cdfb3a1a467d0c4a51fb0ed8bf07f1308b868a6040516118d39392919061289b565b60405180910390a15050505050505050505050565b604080516001600160a01b0380861660248301528416604482015260648082018490528251808303909101815260849091019091526020810180516001600160e01b03166323b872dd60e01b179052610878908590611f90565b61053f8183146067611e67565b6001600160a01b038083166000908152600f60209081526040808320938516835292905290812054801561198457905061088d565b6001600160a01b038085166000908152600d60209081526040808320549387168352600b9091529020546119c4916001600160401b039081169116611f64565b949350505050565b6001600160a01b03811660009081526001602052604090205460ff161561045857610458336001600160a01b03831614610191611e67565b6006546000611a1242611d2a565b905080821180611a2157504281145b15611a2d575050610d5d565b600360019054906101000a90046001600160a01b03166001600160a01b031663c2c4c5c16040518163ffffffff1660e01b8152600401600060405180830381600087803b158015611a7d57600080fd5b505af1158015611a91573d6000803e3d6000fd5b5050505060005b6014811015611b515781831115611aae57611b51565b60035460405163bd85b03960e01b81526101009091046001600160a01b03169063bd85b03990611ae2908690600401612892565b60206040518083038186803b158015611afa57600080fd5b505afa158015611b0e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b3291906127a0565b60008481526007602052604090205562093a8090920191600101611a98565b5050600655565b6001600160a01b0381166000908152600b6020526040812081611b7b858561194f565b6006546001600160a01b0387166000908152600d602052604081205492935091611be291611bc291611bbd9190600160401b90046001600160401b031661207a565b611f54565b8454611bdd90600160401b90046001600160401b0316611d2a565b61207a565b6001600160a01b038087166000908152600c60209081526040808320938b168352600e9091528120929350909190805b6014811015611c7e57848610611c2757611c7e565b600086815260076020526040902054611c3f57611c7e565b60008681526007602090815260408083205486835281842054928890529220540281611c6757fe5b62093a809790970196049190910190600101611c12565b506001600160a01b03808a166000908152600f60209081526040808320938c168352929052208590558015611d1e5785546001600160801b03600160801b80830482168490038216029116178655611ce06001600160a01b0389168a83612086565b7fff097c7d8b1957a4ff09ef1361b5fb54dcede3941ba836d0beb9d10bec725de689898388604051611d1594939291906127cc565b60405180910390a15b98975050505050505050565b62093a80908190040290565b6001600160a01b038216600081815260016020908152604091829020805460ff191685151590811790915582519384529083015280517fac9874a7a931a3f5c9f202c6d9cf40de5d21506993c9f9c38ca8265add89584c9281900390910190a15050565b60007f00000000000000000000000000000000000000000000000000000000000000007f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000611e076120d8565b3060405160200180868152602001858152602001848152602001838152602001826001600160a01b031681526020019550505050505060405160208183030381529060405280519060200120905090565b610878848484600019856120dc565b8161053f5761053f81612133565b60008282825b6080811015611f4857818310611e9057611f48565b6003546040516328d09d4760e01b81526002858501810104916000916101009091046001600160a01b0316906328d09d4790611ed2908d9086906004016127f5565b60806040518083038186803b158015611eea57600080fd5b505afa158015611efe573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f22919061271e565b905088816040015111611f3757819450611f3e565b6001820393505b5050600101611e7b565b50909695505050505050565b600061088d62093a7f8301611d2a565b80820390821002900390565b600061088a83836001612143565b600082820161088a8482101583611e67565b600080836001600160a01b0316836040518082805190602001908083835b60208310611fcd5780518252601f199092019160209182019101611fae565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d806000811461202f576040519150601f19603f3d011682016040523d82523d6000602084013e612034565b606091505b5091509150600082141561204c573d6000803e3d6000fd5b610878815160001480612072575081806020019051602081101561206f57600080fd5b50515b6101a2611e67565b80820390821102900390565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b1790526110b7908490611f90565b4690565b60006120e785612159565b90506120fd6120f78783876121a5565b83611e67565b61210c428410156101b8611e67565b5050506001600160a01b039092166000908152602081905260409020805460010190555050565b610458816210905360ea1b6122c3565b60006121528484111583611e67565b5050900390565b6000612163611d9a565b82604051602001808061190160f01b81525060020183815260200182815260200192505050604051602081830303815290604052805190602001209050919050565b60006121b9846001600160a01b0316612324565b156122b15760408051630b135d3f60e11b808252600482018681526024830193845285516044840152855191936001600160a01b03891693631626ba7e938993899390929091606490910190602085019080838360005b83811015612228578181015183820152602001612210565b50505050905090810190601f1680156122555780820380516001836020036101000a031916815260200191505b50935050505060206040518083038186803b15801561227357600080fd5b505afa158015612287573d6000803e3d6000fd5b505050506040513d602081101561229d57600080fd5b50516001600160e01b031916149050610a80565b6122bc84848461232a565b9050610a80565b62461bcd60e51b600090815260206004526007602452600a808404818106603090810160081b958390069590950190829004918206850160101b01602363ffffff0060e086901c160160181b0190930160c81b60445260e882901c90606490fd5b3b151590565b600061233c82516041146101b9611e67565b60008060006020850151925060408501519150606085015160001a9050600060018783868660405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa1580156123b5573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811615801590611d1e5750876001600160a01b0316816001600160a01b03161498975050505050505050565b60405180608001604052806000600f0b81526020016000600f0b815260200160008152602001600081525090565b60008083601f840112612435578081fd5b5081356001600160401b0381111561244b578182fd5b602083019150836020808302850101111561246557600080fd5b9250929050565b8035801515811461048557600080fd5b8051600f81900b811461048557600080fd5b60006020828403121561249f578081fd5b813561088a81612afc565b6000806000604084860312156124be578182fd5b83356124c981612afc565b925060208401356001600160401b038111156124e3578283fd5b6124ef86828701612424565b9497909650939450505050565b600080600060608486031215612510578283fd5b833561251b81612afc565b9250602061252a85820161246c565b925060408501356001600160401b0380821115612545578384fd5b818701915087601f830112612558578384fd5b81358181111561256457fe5b612576601f8201601f19168501612ad9565b9150808252888482850101111561258b578485fd5b808484018584013784848284010152508093505050509250925092565b600080604083850312156125ba578182fd5b82356125c581612afc565b915060208301356125d581612afc565b809150509250929050565b600080604083850312156125f2578182fd5b82356125fd81612afc565b946020939093013593505050565b6000806020838503121561261d578182fd5b82356001600160401b03811115612632578283fd5b61263e85828601612424565b90969095509350505050565b6000806000806040858703121561265f578081fd5b84356001600160401b0380821115612675578283fd5b61268188838901612424565b90965094506020870135915080821115612699578283fd5b506126a687828801612424565b95989497509550505050565b6000602082840312156126c3578081fd5b61088a8261246c565b600080600080608085870312156126e1578182fd5b84356126ec81612afc565b935060208501356126fc81612afc565b925060408501359150606085013561271381612afc565b939692955090935050565b60006080828403121561272f578081fd5b604051608081018181106001600160401b038211171561274b57fe5b6040526127578361247c565b81526127656020840161247c565b602082015260408301516040820152606083015160608201528091505092915050565b600060208284031215612799578081fd5b5035919050565b6000602082840312156127b1578081fd5b5051919050565b6001600160a01b0391909116815260200190565b6001600160a01b0394851681529290931660208301526040820152606081019190915260800190565b6001600160a01b03929092168252602082015260400190565b6020808252825182820181905260009190848201906040850190845b81811015611f485783516001600160a01b03168352928401929184019160010161282a565b6020808252825182820181905260009190848201906040850190845b81811015611f485783518352928401929184019160010161286b565b901515815260200190565b90815260200190565b6001600160a01b039390931683526020830191909152604082015260600190565b6020808252600d908201526c185b1c9958591e48195e1a5cdd609a1b604082015260600190565b60208082526008908201526708585b1b1bddd95960c21b604082015260600190565b6020808252601e908201527f4d6178696d756d20746f6b656e2062616c616e63652065786365656465640000604082015260600190565b60208082526006908201526521747769636560d01b604082015260600190565b602080825260059082015264217a65726f60d81b604082015260600190565b6020808252600b908201526a1bdb9b1e4819985d58d95d60aa1b604082015260600190565b60208082526027908201527f52657761726420646973747269627574696f6e20686173206e6f7420737461726040820152661d1959081e595d60ca1b606082015260800190565b6020808252818101527f43616e6e6f74207374617274206265666f72652063757272656e74207765656b604082015260600190565b6020808252600c908201526b7a65726f206164647265737360a01b604082015260600190565b6020808252601290820152710626040eecacad6e640c8cad8c2f240dac2f60731b604082015260600190565b60208082526028908201527f5a65726f20746f74616c20737570706c7920726573756c747320696e206c6f736040820152677420746f6b656e7360c01b606082015260800190565b6020808252600990820152683737ba1030b236b4b760b91b604082015260600190565b6040518181016001600160401b0381118282101715612af457fe5b604052919050565b6001600160a01b038116811461045857600080fdfea2646970667358221220972c2bb8ecdbe63efa080ae50f636a301051bc328845b00b90454df26829a68764736f6c63430007060033",
+    "linkReferences": {},
+    "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/RewardFaucet.json b/apps/api/src/app/hardhat/export/RewardFaucet.json
new file mode 100644
index 000000000..26d7680f1
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/RewardFaucet.json
@@ -0,0 +1,324 @@
+{
+    "_format": "hh-sol-artifact-1",
+    "contractName": "RewardFaucet",
+    "sourceName": "contracts/RewardFaucet.sol",
+    "abi": [
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": false,
+                    "internalType": "address",
+                    "name": "token",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "weekStart",
+                    "type": "uint256"
+                }
+            ],
+            "name": "DistributePast",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": false,
+                    "internalType": "address",
+                    "name": "token",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "totalAmount",
+                    "type": "uint256"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "weeksCount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "ExactWeekDistribution",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": false,
+                    "internalType": "address",
+                    "name": "token",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "moveAmount",
+                    "type": "uint256"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "pastWeekStart",
+                    "type": "uint256"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "nextWeekStart",
+                    "type": "uint256"
+                }
+            ],
+            "name": "MovePastRewards",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": false,
+                    "internalType": "address",
+                    "name": "token",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "totalAmount",
+                    "type": "uint256"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "weeksCount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "WeeksDistributions",
+            "type": "event"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "token",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "weeksCount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "depositEqualWeeksPeriod",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "token",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "weekTimeStamp",
+                    "type": "uint256"
+                }
+            ],
+            "name": "depositExactWeek",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "token",
+                    "type": "address"
+                }
+            ],
+            "name": "distributePastRewards",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "token",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "pointOfWeek",
+                    "type": "uint256"
+                }
+            ],
+            "name": "getTokenWeekAmounts",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "token",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "weeksCount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "getUpcomingRewardsForNWeeks",
+            "outputs": [
+                {
+                    "internalType": "uint256[]",
+                    "name": "",
+                    "type": "uint256[]"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "_rewardDistributor",
+                    "type": "address"
+                }
+            ],
+            "name": "initialize",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "isInitialized",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "token",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "pastWeekTimestamp",
+                    "type": "uint256"
+                }
+            ],
+            "name": "movePastRewards",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "rewardDistributor",
+            "outputs": [
+                {
+                    "internalType": "contract IRewardDistributor",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "token",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "weekStart",
+                    "type": "uint256"
+                }
+            ],
+            "name": "tokenWeekAmounts",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "token",
+                    "type": "address"
+                }
+            ],
+            "name": "totalTokenRewards",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "rewardAmount",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        }
+    ],
+    "bytecode": "0x608060405234801561001057600080fd5b506001600055611315806100256000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c806367b870111161007157806367b8701114610139578063871451781461014c578063acc2166a14610177578063c4d66de8146101a7578063c7b56abe146101ba578063cdcea6ad146101cd57600080fd5b80632a419597146100ae5780632d9cd3bb146100c357806336bcebb1146100d6578063392e53cd146101095780635530d7bd14610126575b600080fd5b6100c16100bc3660046110b4565b6101ed565b005b6100c16100d13660046110b4565b6104f0565b6100f66100e43660046110e7565b60026020526000908152604090205481565b6040519081526020015b60405180910390f35b6001546101169060ff1681565b6040519015158152602001610100565b6100c1610134366004611109565b61076c565b6100f6610147366004611109565b610876565b6100f661015a366004611109565b600360209081526000928352604080842090915290825290205481565b60015461018f9061010090046001600160a01b031681565b6040516001600160a01b039091168152602001610100565b6100c16101b53660046110e7565b6108af565b6100c16101c83660046110e7565b610952565b6101e06101db366004611109565b610b28565b6040516101009190611133565b6101f5610c00565b600081118015610206575060688111155b61023f5760405162461bcd60e51b8152602060048201526005602482015264217765656b60d81b60448201526064015b60405180910390fd5b6001600160a01b0383166000818152600260205260408082205490516370a0823160e01b8152306004820152919290916370a0823190602401602060405180830381865afa158015610295573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102b99190611177565b6102c391906111a6565b905060008082116102d457836102de565b6102de82856111b9565b905060006102ec84836111cc565b9050836001146103fa57600061030142610c59565b905060025b8581116103c0578581036103715761031f6001876111a6565b61032990846111ee565b61033390856111a6565b6001600160a01b0389166000908152600360209081526040808320868452909152812080549091906103669084906111b9565b909155506103c09050565b6001600160a01b0388166000908152600360209081526040808320858452909152812080548592906103a49084906111b9565b90915550506001016103b962093a80836111b9565b9150610306565b506103cb82846111a6565b6001600160a01b038816600090815260026020526040812080549091906103f39084906111b9565b9091555050505b61040f6001600160a01b038716333088610c70565b6001546001600160a01b036101009091048116906104309088168284610ce1565b60405163099348fb60e31b81526001600160a01b03888116600483015260248201849052821690634c9a47d890604401600060405180830381600087803b15801561047a57600080fd5b505af115801561048e573d6000803e3d6000fd5b5050604080516001600160a01b038b168152602081018790529081018890527f4088fafdc9c718bca399ea616e6c39e860759e8cc97fbda29803d42b0bc6a2249250606001905060405180910390a1505050506104eb6001600055565b505050565b6104f8610c00565b61050142610d70565b811015801561051d5750610519426303bfc4006111b9565b8111155b6105545760405162461bcd60e51b8152602060048201526008602482015267626164207765656b60c01b6044820152606401610236565b6001600160a01b0383166000818152600260205260408082205490516370a0823160e01b8152306004820152919290916370a0823190602401602060405180830381865afa1580156105aa573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105ce9190611177565b6105d891906111a6565b905060008082116105e957836105f3565b6105f382856111b9565b905061060a6001600160a01b038616333087610c70565b600061061584610d70565b905061062042610d70565b81036106af576001546001600160a01b036101009091048116906106479088168285610ce1565b60405163099348fb60e31b81526001600160a01b03888116600483015260248201859052821690634c9a47d890604401600060405180830381600087803b15801561069157600080fd5b505af11580156106a5573d6000803e3d6000fd5b5050505050610715565b6001600160a01b0386166000908152600360209081526040808320848452909152812080548492906106e29084906111b9565b90915550506001600160a01b0386166000908152600260205260408120805484929061070f9084906111b9565b90915550505b604080516001600160a01b0388168152602081018490529081018290527fb86a33c5016189ecd35a2699118bc2fe7c716f81d4e837eef84dee0bb67875f59060600160405180910390a15050506104eb6001600055565b600061077782610d70565b905062530e8061078642610d70565b61079091906111a6565b81106107c95760405162461bcd60e51b8152602060048201526008602482015267216f75746461746560c01b6044820152606401610236565b60006107d442610c59565b6001600160a01b0385166000908152600360209081526040808320868452909152808220805490839055838352908220805493945090928392906108199084906111b9565b9091555050604080516001600160a01b038716815260208101839052908101849052606081018390527f6e9630aa131b46b81742f09d3aea83da20d184c4dbe662050e8d6d2d554503929060800160405180910390a15050505050565b60008061088283610d70565b6001600160a01b038516600090815260036020908152604080832093835292905220549150505b92915050565b60015460ff16156108eb5760405162461bcd60e51b815260206004820152600660248201526521747769636560d01b6044820152606401610236565b6001600160a01b0381166109295760405162461bcd60e51b8152602060048201526005602482015264217a65726f60d81b6044820152606401610236565b600180546001600160a01b03909216610100026001600160a81b03199092169190911781179055565b6001600160a01b03811660009081526002602052604081205490036109745750565b600061097f42610d70565b90506000805b600a811015610a20576001600160a01b0384166000908152600360209081526040808320868452909152812054908190036109cf576109c762093a80856111a6565b935050610a10565b6001600160a01b03851660009081526003602090815260408083208784529091528120556109fd81846111b9565b9250610a0c62093a80856111a6565b9350505b610a1981611205565b9050610985565b5080156104eb576001600160a01b03831660009081526002602052604081208054839290610a4f9084906111a6565b90915550506001546001600160a01b03610100909104811690610a759085168284610ce1565b60405163099348fb60e31b81526001600160a01b03858116600483015260248201849052821690634c9a47d890604401600060405180830381600087803b158015610abf57600080fd5b505af1158015610ad3573d6000803e3d6000fd5b5050604080516001600160a01b0388168152602081018690529081018690527f33d07963ee4e58f177134d0a37785787f0056ee388c04e4aff075e61e2856d6c9250606001905060405180910390a150505050565b60606000610b3542610d70565b905060008367ffffffffffffffff811115610b5257610b5261121e565b604051908082528060200260200182016040528015610b7b578160200160208202803683370190505b50905060005b84811015610bf7576001600160a01b038616600090815260036020526040812090610baf8362093a806111ee565b610bb990866111b9565b815260200190815260200160002054828281518110610bda57610bda611234565b602090810291909101015280610bef81611205565b915050610b81565b50949350505050565b600260005403610c525760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610236565b6002600055565b60006108a9610c6b8362093a806111b9565b610d70565b6040516001600160a01b0380851660248301528316604482015260648101829052610cdb9085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152610d8c565b50505050565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663095ea7b360e01b179052610d328482610e61565b610cdb576040516001600160a01b038416602482015260006044820152610d6690859063095ea7b360e01b90606401610ca4565b610cdb8482610d8c565b6000610d7f62093a80836111cc565b6108a99062093a806111ee565b6000610de1826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316610f089092919063ffffffff16565b9050805160001480610e02575080806020019051810190610e02919061124a565b6104eb5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610236565b6000806000846001600160a01b031684604051610e7e9190611290565b6000604051808303816000865af19150503d8060008114610ebb576040519150601f19603f3d011682016040523d82523d6000602084013e610ec0565b606091505b5091509150818015610eea575080511580610eea575080806020019051810190610eea919061124a565b8015610eff57506001600160a01b0385163b15155b95945050505050565b6060610f178484600085610f1f565b949350505050565b606082471015610f805760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610236565b600080866001600160a01b03168587604051610f9c9190611290565b60006040518083038185875af1925050503d8060008114610fd9576040519150601f19603f3d011682016040523d82523d6000602084013e610fde565b606091505b5091509150610fef87838387610ffa565b979650505050505050565b60608315611069578251600003611062576001600160a01b0385163b6110625760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610236565b5081610f17565b610f17838381511561107e5781518083602001fd5b8060405162461bcd60e51b815260040161023691906112ac565b80356001600160a01b03811681146110af57600080fd5b919050565b6000806000606084860312156110c957600080fd5b6110d284611098565b95602085013595506040909401359392505050565b6000602082840312156110f957600080fd5b61110282611098565b9392505050565b6000806040838503121561111c57600080fd5b61112583611098565b946020939093013593505050565b6020808252825182820181905260009190848201906040850190845b8181101561116b5783518352928401929184019160010161114f565b50909695505050505050565b60006020828403121561118957600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b818103818111156108a9576108a9611190565b808201808211156108a9576108a9611190565b6000826111e957634e487b7160e01b600052601260045260246000fd5b500490565b80820281158282048414176108a9576108a9611190565b60006001820161121757611217611190565b5060010190565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b60006020828403121561125c57600080fd5b8151801515811461110257600080fd5b60005b8381101561128757818101518382015260200161126f565b50506000910152565b600082516112a281846020870161126c565b9190910192915050565b60208152600082518060208401526112cb81604085016020870161126c565b601f01601f1916919091016040019291505056fea264697066735822122000f918e3a59af92e2a14d9123b3fe1755ca0de77c365c4e3d6bd71bea319dedd64736f6c63430008120033",
+    "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100a95760003560e01c806367b870111161007157806367b8701114610139578063871451781461014c578063acc2166a14610177578063c4d66de8146101a7578063c7b56abe146101ba578063cdcea6ad146101cd57600080fd5b80632a419597146100ae5780632d9cd3bb146100c357806336bcebb1146100d6578063392e53cd146101095780635530d7bd14610126575b600080fd5b6100c16100bc3660046110b4565b6101ed565b005b6100c16100d13660046110b4565b6104f0565b6100f66100e43660046110e7565b60026020526000908152604090205481565b6040519081526020015b60405180910390f35b6001546101169060ff1681565b6040519015158152602001610100565b6100c1610134366004611109565b61076c565b6100f6610147366004611109565b610876565b6100f661015a366004611109565b600360209081526000928352604080842090915290825290205481565b60015461018f9061010090046001600160a01b031681565b6040516001600160a01b039091168152602001610100565b6100c16101b53660046110e7565b6108af565b6100c16101c83660046110e7565b610952565b6101e06101db366004611109565b610b28565b6040516101009190611133565b6101f5610c00565b600081118015610206575060688111155b61023f5760405162461bcd60e51b8152602060048201526005602482015264217765656b60d81b60448201526064015b60405180910390fd5b6001600160a01b0383166000818152600260205260408082205490516370a0823160e01b8152306004820152919290916370a0823190602401602060405180830381865afa158015610295573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102b99190611177565b6102c391906111a6565b905060008082116102d457836102de565b6102de82856111b9565b905060006102ec84836111cc565b9050836001146103fa57600061030142610c59565b905060025b8581116103c0578581036103715761031f6001876111a6565b61032990846111ee565b61033390856111a6565b6001600160a01b0389166000908152600360209081526040808320868452909152812080549091906103669084906111b9565b909155506103c09050565b6001600160a01b0388166000908152600360209081526040808320858452909152812080548592906103a49084906111b9565b90915550506001016103b962093a80836111b9565b9150610306565b506103cb82846111a6565b6001600160a01b038816600090815260026020526040812080549091906103f39084906111b9565b9091555050505b61040f6001600160a01b038716333088610c70565b6001546001600160a01b036101009091048116906104309088168284610ce1565b60405163099348fb60e31b81526001600160a01b03888116600483015260248201849052821690634c9a47d890604401600060405180830381600087803b15801561047a57600080fd5b505af115801561048e573d6000803e3d6000fd5b5050604080516001600160a01b038b168152602081018790529081018890527f4088fafdc9c718bca399ea616e6c39e860759e8cc97fbda29803d42b0bc6a2249250606001905060405180910390a1505050506104eb6001600055565b505050565b6104f8610c00565b61050142610d70565b811015801561051d5750610519426303bfc4006111b9565b8111155b6105545760405162461bcd60e51b8152602060048201526008602482015267626164207765656b60c01b6044820152606401610236565b6001600160a01b0383166000818152600260205260408082205490516370a0823160e01b8152306004820152919290916370a0823190602401602060405180830381865afa1580156105aa573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105ce9190611177565b6105d891906111a6565b905060008082116105e957836105f3565b6105f382856111b9565b905061060a6001600160a01b038616333087610c70565b600061061584610d70565b905061062042610d70565b81036106af576001546001600160a01b036101009091048116906106479088168285610ce1565b60405163099348fb60e31b81526001600160a01b03888116600483015260248201859052821690634c9a47d890604401600060405180830381600087803b15801561069157600080fd5b505af11580156106a5573d6000803e3d6000fd5b5050505050610715565b6001600160a01b0386166000908152600360209081526040808320848452909152812080548492906106e29084906111b9565b90915550506001600160a01b0386166000908152600260205260408120805484929061070f9084906111b9565b90915550505b604080516001600160a01b0388168152602081018490529081018290527fb86a33c5016189ecd35a2699118bc2fe7c716f81d4e837eef84dee0bb67875f59060600160405180910390a15050506104eb6001600055565b600061077782610d70565b905062530e8061078642610d70565b61079091906111a6565b81106107c95760405162461bcd60e51b8152602060048201526008602482015267216f75746461746560c01b6044820152606401610236565b60006107d442610c59565b6001600160a01b0385166000908152600360209081526040808320868452909152808220805490839055838352908220805493945090928392906108199084906111b9565b9091555050604080516001600160a01b038716815260208101839052908101849052606081018390527f6e9630aa131b46b81742f09d3aea83da20d184c4dbe662050e8d6d2d554503929060800160405180910390a15050505050565b60008061088283610d70565b6001600160a01b038516600090815260036020908152604080832093835292905220549150505b92915050565b60015460ff16156108eb5760405162461bcd60e51b815260206004820152600660248201526521747769636560d01b6044820152606401610236565b6001600160a01b0381166109295760405162461bcd60e51b8152602060048201526005602482015264217a65726f60d81b6044820152606401610236565b600180546001600160a01b03909216610100026001600160a81b03199092169190911781179055565b6001600160a01b03811660009081526002602052604081205490036109745750565b600061097f42610d70565b90506000805b600a811015610a20576001600160a01b0384166000908152600360209081526040808320868452909152812054908190036109cf576109c762093a80856111a6565b935050610a10565b6001600160a01b03851660009081526003602090815260408083208784529091528120556109fd81846111b9565b9250610a0c62093a80856111a6565b9350505b610a1981611205565b9050610985565b5080156104eb576001600160a01b03831660009081526002602052604081208054839290610a4f9084906111a6565b90915550506001546001600160a01b03610100909104811690610a759085168284610ce1565b60405163099348fb60e31b81526001600160a01b03858116600483015260248201849052821690634c9a47d890604401600060405180830381600087803b158015610abf57600080fd5b505af1158015610ad3573d6000803e3d6000fd5b5050604080516001600160a01b0388168152602081018690529081018690527f33d07963ee4e58f177134d0a37785787f0056ee388c04e4aff075e61e2856d6c9250606001905060405180910390a150505050565b60606000610b3542610d70565b905060008367ffffffffffffffff811115610b5257610b5261121e565b604051908082528060200260200182016040528015610b7b578160200160208202803683370190505b50905060005b84811015610bf7576001600160a01b038616600090815260036020526040812090610baf8362093a806111ee565b610bb990866111b9565b815260200190815260200160002054828281518110610bda57610bda611234565b602090810291909101015280610bef81611205565b915050610b81565b50949350505050565b600260005403610c525760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610236565b6002600055565b60006108a9610c6b8362093a806111b9565b610d70565b6040516001600160a01b0380851660248301528316604482015260648101829052610cdb9085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152610d8c565b50505050565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663095ea7b360e01b179052610d328482610e61565b610cdb576040516001600160a01b038416602482015260006044820152610d6690859063095ea7b360e01b90606401610ca4565b610cdb8482610d8c565b6000610d7f62093a80836111cc565b6108a99062093a806111ee565b6000610de1826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316610f089092919063ffffffff16565b9050805160001480610e02575080806020019051810190610e02919061124a565b6104eb5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610236565b6000806000846001600160a01b031684604051610e7e9190611290565b6000604051808303816000865af19150503d8060008114610ebb576040519150601f19603f3d011682016040523d82523d6000602084013e610ec0565b606091505b5091509150818015610eea575080511580610eea575080806020019051810190610eea919061124a565b8015610eff57506001600160a01b0385163b15155b95945050505050565b6060610f178484600085610f1f565b949350505050565b606082471015610f805760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610236565b600080866001600160a01b03168587604051610f9c9190611290565b60006040518083038185875af1925050503d8060008114610fd9576040519150601f19603f3d011682016040523d82523d6000602084013e610fde565b606091505b5091509150610fef87838387610ffa565b979650505050505050565b60608315611069578251600003611062576001600160a01b0385163b6110625760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610236565b5081610f17565b610f17838381511561107e5781518083602001fd5b8060405162461bcd60e51b815260040161023691906112ac565b80356001600160a01b03811681146110af57600080fd5b919050565b6000806000606084860312156110c957600080fd5b6110d284611098565b95602085013595506040909401359392505050565b6000602082840312156110f957600080fd5b61110282611098565b9392505050565b6000806040838503121561111c57600080fd5b61112583611098565b946020939093013593505050565b6020808252825182820181905260009190848201906040850190845b8181101561116b5783518352928401929184019160010161114f565b50909695505050505050565b60006020828403121561118957600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b818103818111156108a9576108a9611190565b808201808211156108a9576108a9611190565b6000826111e957634e487b7160e01b600052601260045260246000fd5b500490565b80820281158282048414176108a9576108a9611190565b60006001820161121757611217611190565b5060010190565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b60006020828403121561125c57600080fd5b8151801515811461110257600080fd5b60005b8381101561128757818101518382015260200161126f565b50506000910152565b600082516112a281846020870161126c565b9190910192915050565b60208152600082518060208401526112cb81604085016020870161126c565b601f01601f1916919091016040019291505056fea264697066735822122000f918e3a59af92e2a14d9123b3fe1755ca0de77c365c4e3d6bd71bea319dedd64736f6c63430008120033",
+    "linkReferences": {},
+    "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/SignMessageLib.json b/apps/api/src/app/hardhat/export/SignMessageLib.json
new file mode 100644
index 000000000..1d87ebbc2
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/SignMessageLib.json
@@ -0,0 +1,56 @@
+{
+  "_format": "hh-sol-artifact-1",
+  "contractName": "SignMessageLib",
+  "sourceName": "@gnosis.pm/safe-contracts/contracts/examples/libraries/SignMessage.sol",
+  "abi": [
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "bytes32",
+          "name": "msgHash",
+          "type": "bytes32"
+        }
+      ],
+      "name": "SignMsg",
+      "type": "event"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes",
+          "name": "message",
+          "type": "bytes"
+        }
+      ],
+      "name": "getMessageHash",
+      "outputs": [
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes",
+          "name": "_data",
+          "type": "bytes"
+        }
+      ],
+      "name": "signMessage",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    }
+  ],
+  "bytecode": "0x608060405234801561001057600080fd5b50610392806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80630a1028c41461003b57806385a5affe14610060575b600080fd5b61004e610049366004610220565b610075565b60405190815260200160405180910390f35b61007361006e3660046102d1565b610187565b005b6000807f60b3cbf8b4a223d68d641b3b6ddf9a298e7f33710cf3d3a9d1146b5a6150fbca60001b83805190602001206040516020016100be929190918252602082015260400190565b60408051601f19818403018152828252805160209182012063f698da2560e01b84529151919350601960f81b92600160f81b92309263f698da2592600480820193918290030181865afa158015610119573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061013d9190610343565b6040516001600160f81b0319938416602082015292909116602183015260228201526042810182905260620160405160208183030381529060405280519060200120915050919050565b60006101c883838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061007592505050565b600081815260076020526040808220600190555191925082917fe7f4675038f4f6034dfcbbb24c4dc08e4ebf10eb9d257d3d02c0f38d122ac6e49190a2505050565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561023257600080fd5b813567ffffffffffffffff8082111561024a57600080fd5b818401915084601f83011261025e57600080fd5b8135818111156102705761027061020a565b604051601f8201601f19908116603f011681019083821181831017156102985761029861020a565b816040528281528760208487010111156102b157600080fd5b826020860160208301376000928101602001929092525095945050505050565b600080602083850312156102e457600080fd5b823567ffffffffffffffff808211156102fc57600080fd5b818501915085601f83011261031057600080fd5b81358181111561031f57600080fd5b86602082850101111561033157600080fd5b60209290920196919550909350505050565b60006020828403121561035557600080fd5b505191905056fea2646970667358221220bc635c25890be8657513d1b90bb9807ae47e762473d8e970222944459b865a4e64736f6c63430008180033",
+  "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80630a1028c41461003b57806385a5affe14610060575b600080fd5b61004e610049366004610220565b610075565b60405190815260200160405180910390f35b61007361006e3660046102d1565b610187565b005b6000807f60b3cbf8b4a223d68d641b3b6ddf9a298e7f33710cf3d3a9d1146b5a6150fbca60001b83805190602001206040516020016100be929190918252602082015260400190565b60408051601f19818403018152828252805160209182012063f698da2560e01b84529151919350601960f81b92600160f81b92309263f698da2592600480820193918290030181865afa158015610119573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061013d9190610343565b6040516001600160f81b0319938416602082015292909116602183015260228201526042810182905260620160405160208183030381529060405280519060200120915050919050565b60006101c883838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061007592505050565b600081815260076020526040808220600190555191925082917fe7f4675038f4f6034dfcbbb24c4dc08e4ebf10eb9d257d3d02c0f38d122ac6e49190a2505050565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561023257600080fd5b813567ffffffffffffffff8082111561024a57600080fd5b818401915084601f83011261025e57600080fd5b8135818111156102705761027061020a565b604051601f8201601f19908116603f011681019083821181831017156102985761029861020a565b816040528281528760208487010111156102b157600080fd5b826020860160208301376000928101602001929092525095945050505050565b600080602083850312156102e457600080fd5b823567ffffffffffffffff808211156102fc57600080fd5b818501915085601f83011261031057600080fd5b81358181111561031f57600080fd5b86602082850101111561033157600080fd5b60209290920196919550909350505050565b60006020828403121561035557600080fd5b505191905056fea2646970667358221220bc635c25890be8657513d1b90bb9807ae47e762473d8e970222944459b865a4e64736f6c63430008180033",
+  "linkReferences": {},
+  "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/SimulateTxAccessor.json b/apps/api/src/app/hardhat/export/SimulateTxAccessor.json
new file mode 100644
index 000000000..66961a800
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/SimulateTxAccessor.json
@@ -0,0 +1,60 @@
+{
+  "_format": "hh-sol-artifact-1",
+  "contractName": "SimulateTxAccessor",
+  "sourceName": "@gnosis.pm/safe-contracts/contracts/accessors/SimulateTxAccessor.sol",
+  "abi": [
+    {
+      "inputs": [],
+      "stateMutability": "nonpayable",
+      "type": "constructor"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        },
+        {
+          "internalType": "bytes",
+          "name": "data",
+          "type": "bytes"
+        },
+        {
+          "internalType": "enum Enum.Operation",
+          "name": "operation",
+          "type": "uint8"
+        }
+      ],
+      "name": "simulate",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "estimate",
+          "type": "uint256"
+        },
+        {
+          "internalType": "bool",
+          "name": "success",
+          "type": "bool"
+        },
+        {
+          "internalType": "bytes",
+          "name": "returnData",
+          "type": "bytes"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    }
+  ],
+  "bytecode": "0x60a060405234801561001057600080fd5b503060805260805161035a61002f6000396000606a015261035a6000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80631c5fb21114610030575b600080fd5b61004361003e3660046101db565b61005b565b60405161005293929190610287565b60405180910390f35b60008060606001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001630036101025760405162461bcd60e51b815260206004820152603960248201527f53696d756c61746554784163636573736f722073686f756c64206f6e6c79206260448201527f652063616c6c6564207669612064656c656761746563616c6c00000000000000606482015260840160405180910390fd5b60005a905061014a898989898080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508b925050505a610180565b92505a61015790826102e7565b935060405160203d0181016040523d81523d6000602083013e8092505050955095509592505050565b600060018360018111156101965761019661030e565b036101ae576000808551602087018986f490506101be565b600080855160208701888a87f190505b95945050505050565b8035600281106101d657600080fd5b919050565b6000806000806000608086880312156101f357600080fd5b85356001600160a01b038116811461020a57600080fd5b945060208601359350604086013567ffffffffffffffff8082111561022e57600080fd5b818801915088601f83011261024257600080fd5b81358181111561025157600080fd5b89602082850101111561026357600080fd5b60208301955080945050505061027b606087016101c7565b90509295509295909350565b83815260006020841515602084015260606040840152835180606085015260005b818110156102c4578581018301518582016080015282016102a8565b506000608082860101526080601f19601f83011685010192505050949350505050565b8181038181111561030857634e487b7160e01b600052601160045260246000fd5b92915050565b634e487b7160e01b600052602160045260246000fdfea2646970667358221220bc1920ffea8ed24569e272631877ba36cb446b85161f4fe73e4d43555cbd51ac64736f6c63430008180033",
+  "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c80631c5fb21114610030575b600080fd5b61004361003e3660046101db565b61005b565b60405161005293929190610287565b60405180910390f35b60008060606001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001630036101025760405162461bcd60e51b815260206004820152603960248201527f53696d756c61746554784163636573736f722073686f756c64206f6e6c79206260448201527f652063616c6c6564207669612064656c656761746563616c6c00000000000000606482015260840160405180910390fd5b60005a905061014a898989898080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508b925050505a610180565b92505a61015790826102e7565b935060405160203d0181016040523d81523d6000602083013e8092505050955095509592505050565b600060018360018111156101965761019661030e565b036101ae576000808551602087018986f490506101be565b600080855160208701888a87f190505b95945050505050565b8035600281106101d657600080fd5b919050565b6000806000806000608086880312156101f357600080fd5b85356001600160a01b038116811461020a57600080fd5b945060208601359350604086013567ffffffffffffffff8082111561022e57600080fd5b818801915088601f83011261024257600080fd5b81358181111561025157600080fd5b89602082850101111561026357600080fd5b60208301955080945050505061027b606087016101c7565b90509295509295909350565b83815260006020841515602084015260606040840152835180606085015260005b818110156102c4578581018301518582016080015282016102a8565b506000608082860101526080601f19601f83011685010192505050949350505050565b8181038181111561030857634e487b7160e01b600052601160045260246000fd5b92915050565b634e487b7160e01b600052602160045260246000fdfea2646970667358221220bc1920ffea8ed24569e272631877ba36cb446b85161f4fe73e4d43555cbd51ac64736f6c63430008180033",
+  "linkReferences": {},
+  "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/SmartWalletWhitelist.json b/apps/api/src/app/hardhat/export/SmartWalletWhitelist.json
new file mode 100644
index 000000000..f6508b8dd
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/SmartWalletWhitelist.json
@@ -0,0 +1,229 @@
+{
+    "_format": "hh-sol-artifact-1",
+    "contractName": "SmartWalletWhitelist",
+    "sourceName": "contracts/utils/SmartWalletWhitelist.sol",
+    "abi": [
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "_admin",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "constructor"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": false,
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "name": "ApproveWallet",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": false,
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "name": "NewChecker",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": false,
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "name": "RevokeWallet",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": false,
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "name": "SetAllowAll",
+            "type": "event"
+        },
+        {
+            "inputs": [],
+            "name": "admin",
+            "outputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "_wallet",
+                    "type": "address"
+                }
+            ],
+            "name": "approveWallet",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address[]",
+                    "name": "_wallets",
+                    "type": "address[]"
+                }
+            ],
+            "name": "approveWalletList",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "_wallet",
+                    "type": "address"
+                }
+            ],
+            "name": "check",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "checker",
+            "outputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "isAllowAll",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "_wallet",
+                    "type": "address"
+                }
+            ],
+            "name": "revokeWallet",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address[]",
+                    "name": "_wallets",
+                    "type": "address[]"
+                }
+            ],
+            "name": "revokeWalletList",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "bool",
+                    "name": "_isAllowAll",
+                    "type": "bool"
+                }
+            ],
+            "name": "setAllowAll",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "_checker",
+                    "type": "address"
+                }
+            ],
+            "name": "setChecker",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "name": "wallets",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        }
+    ],
+    "bytecode": "0x60a06040526001805460ff60a01b1916905534801561001d57600080fd5b506040516107ed3803806107ed83398101604081905261003c9161004d565b6001600160a01b031660805261007d565b60006020828403121561005f57600080fd5b81516001600160a01b038116811461007657600080fd5b9392505050565b6080516107326100bb600039600081816101b1015281816101d50152818161030101528181610396015281816103e7015261054901526107326000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c8063808a9d4011610071578063808a9d401461012557806389b08f1114610138578063c23697a81461015b578063cf5303cf1461016e578063cf880f4c14610199578063f851a440146101ac57600080fd5b80630fcb0ae5146100ae5780631b833791146100c35780632c6766c6146100d6578063396ad9d3146100ff5780634d7d9c0114610112575b600080fd5b6100c16100bc3660046105dd565b6101d3565b005b6100c16100d1366004610606565b61027d565b6001546100ea90600160a01b900460ff1681565b60405190151581526020015b60405180910390f35b6100c161010d366004610606565b6102c1565b6100c161012036600461068c565b6102ff565b6100c16101333660046105dd565b610394565b6100ea6101463660046105dd565b60006020819052908152604090205460ff1681565b6100ea6101693660046105dd565b610473565b600154610181906001600160a01b031681565b6040516001600160a01b0390911681526020016100f6565b6100c16101a73660046105dd565b610547565b6101817f000000000000000000000000000000000000000000000000000000000000000081565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633146102245760405162461bcd60e51b815260040161021b906106a9565b60405180910390fd5b6001600160a01b03811660008181526020818152604091829020805460ff1916600117905590519182527fc1e7aae3f3125e58cfc69ab2a872a655dbb9427614aa85b29bb5abeaca4d6a9291015b60405180910390a150565b8060005b818110156102bb576102b384848381811061029e5761029e6106c9565b905060200201602081019061013391906105dd565b600101610281565b50505050565b8060005b818110156102bb576102f78484838181106102e2576102e26106c9565b90506020020160208101906100bc91906105dd565b6001016102c5565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633146103475760405162461bcd60e51b815260040161021b906106a9565b60018054821515600160a01b0260ff60a01b199091161790556040517fd6b910c3c867dfdca6ffe654abf208c60c6b831d36ddc04b3c5f43c2982caa379061027290831515815260200190565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633146103dc5760405162461bcd60e51b815260040161021b906106a9565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146104245760405162461bcd60e51b815260040161021b906106a9565b6001600160a01b03811660008181526020818152604091829020805460ff1916905590519182527f1b676c3cc753786cb95aff57280fd7406f1da74e2a8b9755fdd395aded3e16dd9101610272565b600154600090600160a01b900460ff161561049057506001919050565b6001600160a01b03821660009081526020819052604090205460ff1680156104b85792915050565b6001546001600160a01b03161561053e57600154604051631846d2f560e31b81526001600160a01b0385811660048301529091169063c23697a890602401602060405180830381865afa158015610513573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061053791906106df565b9392505050565b50600092915050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316331461058f5760405162461bcd60e51b815260040161021b906106a9565b600180546001600160a01b0319166001600160a01b0383169081179091556040519081527fbbb218c618aff8fc737012ba779aad87c1cf7a60b501bd2f72bb570517f02ca390602001610272565b6000602082840312156105ef57600080fd5b81356001600160a01b038116811461053757600080fd5b6000806020838503121561061957600080fd5b823567ffffffffffffffff8082111561063157600080fd5b818501915085601f83011261064557600080fd5b81358181111561065457600080fd5b8660208260051b850101111561066957600080fd5b60209290920196919550909350505050565b801515811461068957600080fd5b50565b60006020828403121561069e57600080fd5b81356105378161067b565b60208082526006908201526510b0b236b4b760d11b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b6000602082840312156106f157600080fd5b81516105378161067b56fea26469706673582212205ce205a4575174bf41d715179b300702d85eae262bc2732b0cb6d1c8c6358bcc64736f6c63430008120033",
+    "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100a95760003560e01c8063808a9d4011610071578063808a9d401461012557806389b08f1114610138578063c23697a81461015b578063cf5303cf1461016e578063cf880f4c14610199578063f851a440146101ac57600080fd5b80630fcb0ae5146100ae5780631b833791146100c35780632c6766c6146100d6578063396ad9d3146100ff5780634d7d9c0114610112575b600080fd5b6100c16100bc3660046105dd565b6101d3565b005b6100c16100d1366004610606565b61027d565b6001546100ea90600160a01b900460ff1681565b60405190151581526020015b60405180910390f35b6100c161010d366004610606565b6102c1565b6100c161012036600461068c565b6102ff565b6100c16101333660046105dd565b610394565b6100ea6101463660046105dd565b60006020819052908152604090205460ff1681565b6100ea6101693660046105dd565b610473565b600154610181906001600160a01b031681565b6040516001600160a01b0390911681526020016100f6565b6100c16101a73660046105dd565b610547565b6101817f000000000000000000000000000000000000000000000000000000000000000081565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633146102245760405162461bcd60e51b815260040161021b906106a9565b60405180910390fd5b6001600160a01b03811660008181526020818152604091829020805460ff1916600117905590519182527fc1e7aae3f3125e58cfc69ab2a872a655dbb9427614aa85b29bb5abeaca4d6a9291015b60405180910390a150565b8060005b818110156102bb576102b384848381811061029e5761029e6106c9565b905060200201602081019061013391906105dd565b600101610281565b50505050565b8060005b818110156102bb576102f78484838181106102e2576102e26106c9565b90506020020160208101906100bc91906105dd565b6001016102c5565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633146103475760405162461bcd60e51b815260040161021b906106a9565b60018054821515600160a01b0260ff60a01b199091161790556040517fd6b910c3c867dfdca6ffe654abf208c60c6b831d36ddc04b3c5f43c2982caa379061027290831515815260200190565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031633146103dc5760405162461bcd60e51b815260040161021b906106a9565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146104245760405162461bcd60e51b815260040161021b906106a9565b6001600160a01b03811660008181526020818152604091829020805460ff1916905590519182527f1b676c3cc753786cb95aff57280fd7406f1da74e2a8b9755fdd395aded3e16dd9101610272565b600154600090600160a01b900460ff161561049057506001919050565b6001600160a01b03821660009081526020819052604090205460ff1680156104b85792915050565b6001546001600160a01b03161561053e57600154604051631846d2f560e31b81526001600160a01b0385811660048301529091169063c23697a890602401602060405180830381865afa158015610513573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061053791906106df565b9392505050565b50600092915050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316331461058f5760405162461bcd60e51b815260040161021b906106a9565b600180546001600160a01b0319166001600160a01b0383169081179091556040519081527fbbb218c618aff8fc737012ba779aad87c1cf7a60b501bd2f72bb570517f02ca390602001610272565b6000602082840312156105ef57600080fd5b81356001600160a01b038116811461053757600080fd5b6000806020838503121561061957600080fd5b823567ffffffffffffffff8082111561063157600080fd5b818501915085601f83011261064557600080fd5b81358181111561065457600080fd5b8660208260051b850101111561066957600080fd5b60209290920196919550909350505050565b801515811461068957600080fd5b50565b60006020828403121561069e57600080fd5b81356105378161067b565b60208082526006908201526510b0b236b4b760d11b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b6000602082840312156106f157600080fd5b81516105378161067b56fea26469706673582212205ce205a4575174bf41d715179b300702d85eae262bc2732b0cb6d1c8c6358bcc64736f6c63430008120033",
+    "linkReferences": {},
+    "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/THX.json b/apps/api/src/app/hardhat/export/THX.json
new file mode 100644
index 000000000..56d838db6
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/THX.json
@@ -0,0 +1,297 @@
+{
+    "_format": "hh-sol-artifact-1",
+    "contractName": "THX",
+    "sourceName": "contracts/mock/THX.sol",
+    "abi": [
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "to",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "constructor"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "owner",
+                    "type": "address"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "value",
+                    "type": "uint256"
+                }
+            ],
+            "name": "Approval",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "from",
+                    "type": "address"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "to",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "value",
+                    "type": "uint256"
+                }
+            ],
+            "name": "Transfer",
+            "type": "event"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "owner",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                }
+            ],
+            "name": "allowance",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "approve",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "account",
+                    "type": "address"
+                }
+            ],
+            "name": "balanceOf",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "decimals",
+            "outputs": [
+                {
+                    "internalType": "uint8",
+                    "name": "",
+                    "type": "uint8"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "subtractedValue",
+                    "type": "uint256"
+                }
+            ],
+            "name": "decreaseAllowance",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "addedValue",
+                    "type": "uint256"
+                }
+            ],
+            "name": "increaseAllowance",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "name",
+            "outputs": [
+                {
+                    "internalType": "string",
+                    "name": "",
+                    "type": "string"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "symbol",
+            "outputs": [
+                {
+                    "internalType": "string",
+                    "name": "",
+                    "type": "string"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "totalSupply",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "recipient",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "transfer",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "sender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "recipient",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "transferFrom",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        }
+    ],
+    "bytecode": "0x60806040523480156200001157600080fd5b5060405162000cf738038062000cf7833981810160405260408110156200003757600080fd5b5080516020918201516040805180820182526011815270544858204e6574776f726b2028506f532960781b81860190815282518084019093526003808452620a890b60eb1b968401969096528151949593949193620000999290919062000249565b508051620000af90600490602084019062000249565b50506005805460ff1916601217905550620000cb8282620000d3565b5050620002f5565b6001600160a01b0382166200012f576040805162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b6200013d60008383620001e2565b6200015981600254620001e760201b620005731790919060201c565b6002556001600160a01b038216600090815260208181526040909120546200018c91839062000573620001e7821b17901c565b6001600160a01b0383166000818152602081815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b505050565b60008282018381101562000242576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282620002815760008555620002cc565b82601f106200029c57805160ff1916838001178555620002cc565b82800160010185558215620002cc579182015b82811115620002cc578251825591602001919060010190620002af565b50620002da929150620002de565b5090565b5b80821115620002da5760008155600101620002df565b6109f280620003056000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c8063395093511161007157806339509351146101d957806370a082311461020557806395d89b411461022b578063a457c2d714610233578063a9059cbb1461025f578063dd62ed3e1461028b576100a9565b806306fdde03146100ae578063095ea7b31461012b57806318160ddd1461016b57806323b872dd14610185578063313ce567146101bb575b600080fd5b6100b66102b9565b6040805160208082528351818301528351919283929083019185019080838360005b838110156100f05781810151838201526020016100d8565b50505050905090810190601f16801561011d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101576004803603604081101561014157600080fd5b506001600160a01b03813516906020013561034f565b604080519115158252519081900360200190f35b61017361036c565b60408051918252519081900360200190f35b6101576004803603606081101561019b57600080fd5b506001600160a01b03813581169160208101359091169060400135610372565b6101c36103f9565b6040805160ff9092168252519081900360200190f35b610157600480360360408110156101ef57600080fd5b506001600160a01b038135169060200135610402565b6101736004803603602081101561021b57600080fd5b50356001600160a01b0316610450565b6100b661046b565b6101576004803603604081101561024957600080fd5b506001600160a01b0381351690602001356104cc565b6101576004803603604081101561027557600080fd5b506001600160a01b038135169060200135610534565b610173600480360360408110156102a157600080fd5b506001600160a01b0381358116916020013516610548565b60038054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103455780601f1061031a57610100808354040283529160200191610345565b820191906000526020600020905b81548152906001019060200180831161032857829003601f168201915b5050505050905090565b600061036361035c6105d4565b84846105d8565b50600192915050565b60025490565b600061037f8484846106c4565b6103ef8461038b6105d4565b6103ea85604051806060016040528060288152602001610927602891396001600160a01b038a166000908152600160205260408120906103c96105d4565b6001600160a01b03168152602081019190915260400160002054919061081f565b6105d8565b5060019392505050565b60055460ff1690565b600061036361040f6105d4565b846103ea85600160006104206105d4565b6001600160a01b03908116825260208083019390935260409182016000908120918c168152925290205490610573565b6001600160a01b031660009081526020819052604090205490565b60048054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103455780601f1061031a57610100808354040283529160200191610345565b60006103636104d96105d4565b846103ea8560405180606001604052806025815260200161099860259139600160006105036105d4565b6001600160a01b03908116825260208083019390935260409182016000908120918d1681529252902054919061081f565b60006103636105416105d4565b84846106c4565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6000828201838110156105cd576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b3390565b6001600160a01b03831661061d5760405162461bcd60e51b81526004018080602001828103825260248152602001806109746024913960400191505060405180910390fd5b6001600160a01b0382166106625760405162461bcd60e51b81526004018080602001828103825260228152602001806108df6022913960400191505060405180910390fd5b6001600160a01b03808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b0383166107095760405162461bcd60e51b815260040180806020018281038252602581526020018061094f6025913960400191505060405180910390fd5b6001600160a01b03821661074e5760405162461bcd60e51b81526004018080602001828103825260238152602001806108bc6023913960400191505060405180910390fd5b6107598383836108b6565b61079681604051806060016040528060268152602001610901602691396001600160a01b038616600090815260208190526040902054919061081f565b6001600160a01b0380851660009081526020819052604080822093909355908416815220546107c59082610573565b6001600160a01b038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600081848411156108ae5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561087357818101518382015260200161085b565b50505050905090810190601f1680156108a05780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b50505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa264697066735822122077c110b83655fc51968e38fdc606b94ac5819151c022a8cf8ebfabe88559128464736f6c63430007060033",
+    "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100a95760003560e01c8063395093511161007157806339509351146101d957806370a082311461020557806395d89b411461022b578063a457c2d714610233578063a9059cbb1461025f578063dd62ed3e1461028b576100a9565b806306fdde03146100ae578063095ea7b31461012b57806318160ddd1461016b57806323b872dd14610185578063313ce567146101bb575b600080fd5b6100b66102b9565b6040805160208082528351818301528351919283929083019185019080838360005b838110156100f05781810151838201526020016100d8565b50505050905090810190601f16801561011d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101576004803603604081101561014157600080fd5b506001600160a01b03813516906020013561034f565b604080519115158252519081900360200190f35b61017361036c565b60408051918252519081900360200190f35b6101576004803603606081101561019b57600080fd5b506001600160a01b03813581169160208101359091169060400135610372565b6101c36103f9565b6040805160ff9092168252519081900360200190f35b610157600480360360408110156101ef57600080fd5b506001600160a01b038135169060200135610402565b6101736004803603602081101561021b57600080fd5b50356001600160a01b0316610450565b6100b661046b565b6101576004803603604081101561024957600080fd5b506001600160a01b0381351690602001356104cc565b6101576004803603604081101561027557600080fd5b506001600160a01b038135169060200135610534565b610173600480360360408110156102a157600080fd5b506001600160a01b0381358116916020013516610548565b60038054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103455780601f1061031a57610100808354040283529160200191610345565b820191906000526020600020905b81548152906001019060200180831161032857829003601f168201915b5050505050905090565b600061036361035c6105d4565b84846105d8565b50600192915050565b60025490565b600061037f8484846106c4565b6103ef8461038b6105d4565b6103ea85604051806060016040528060288152602001610927602891396001600160a01b038a166000908152600160205260408120906103c96105d4565b6001600160a01b03168152602081019190915260400160002054919061081f565b6105d8565b5060019392505050565b60055460ff1690565b600061036361040f6105d4565b846103ea85600160006104206105d4565b6001600160a01b03908116825260208083019390935260409182016000908120918c168152925290205490610573565b6001600160a01b031660009081526020819052604090205490565b60048054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103455780601f1061031a57610100808354040283529160200191610345565b60006103636104d96105d4565b846103ea8560405180606001604052806025815260200161099860259139600160006105036105d4565b6001600160a01b03908116825260208083019390935260409182016000908120918d1681529252902054919061081f565b60006103636105416105d4565b84846106c4565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6000828201838110156105cd576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b3390565b6001600160a01b03831661061d5760405162461bcd60e51b81526004018080602001828103825260248152602001806109746024913960400191505060405180910390fd5b6001600160a01b0382166106625760405162461bcd60e51b81526004018080602001828103825260228152602001806108df6022913960400191505060405180910390fd5b6001600160a01b03808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b0383166107095760405162461bcd60e51b815260040180806020018281038252602581526020018061094f6025913960400191505060405180910390fd5b6001600160a01b03821661074e5760405162461bcd60e51b81526004018080602001828103825260238152602001806108bc6023913960400191505060405180910390fd5b6107598383836108b6565b61079681604051806060016040528060268152602001610901602691396001600160a01b038616600090815260208190526040902054919061081f565b6001600160a01b0380851660009081526020819052604080822093909355908416815220546107c59082610573565b6001600160a01b038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600081848411156108ae5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561087357818101518382015260200161085b565b50505050905090810190601f1680156108a05780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b50505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa264697066735822122077c110b83655fc51968e38fdc606b94ac5819151c022a8cf8ebfabe88559128464736f6c63430007060033",
+    "linkReferences": {},
+    "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/THXERC1155.json b/apps/api/src/app/hardhat/export/THXERC1155.json
new file mode 100644
index 000000000..614ddf887
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/THXERC1155.json
@@ -0,0 +1,688 @@
+{
+    "_format": "hh-sol-artifact-1",
+    "contractName": "THX_ERC1155",
+    "sourceName": "contracts/utils/ERC1155/THX_ERC1155.sol",
+    "abi": [
+        {
+            "inputs": [
+                {
+                    "internalType": "string",
+                    "name": "URI_",
+                    "type": "string"
+                },
+                {
+                    "internalType": "address",
+                    "name": "owner_",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "constructor"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "account",
+                    "type": "address"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "operator",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "bool",
+                    "name": "approved",
+                    "type": "bool"
+                }
+            ],
+            "name": "ApprovalForAll",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "previousOwner",
+                    "type": "address"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "newOwner",
+                    "type": "address"
+                }
+            ],
+            "name": "OwnershipTransferred",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "bytes32",
+                    "name": "role",
+                    "type": "bytes32"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "bytes32",
+                    "name": "previousAdminRole",
+                    "type": "bytes32"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "bytes32",
+                    "name": "newAdminRole",
+                    "type": "bytes32"
+                }
+            ],
+            "name": "RoleAdminChanged",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "bytes32",
+                    "name": "role",
+                    "type": "bytes32"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "account",
+                    "type": "address"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "sender",
+                    "type": "address"
+                }
+            ],
+            "name": "RoleGranted",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "bytes32",
+                    "name": "role",
+                    "type": "bytes32"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "account",
+                    "type": "address"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "sender",
+                    "type": "address"
+                }
+            ],
+            "name": "RoleRevoked",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "operator",
+                    "type": "address"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "from",
+                    "type": "address"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "to",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256[]",
+                    "name": "ids",
+                    "type": "uint256[]"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256[]",
+                    "name": "values",
+                    "type": "uint256[]"
+                }
+            ],
+            "name": "TransferBatch",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "operator",
+                    "type": "address"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "from",
+                    "type": "address"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "to",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "id",
+                    "type": "uint256"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "value",
+                    "type": "uint256"
+                }
+            ],
+            "name": "TransferSingle",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": false,
+                    "internalType": "string",
+                    "name": "value",
+                    "type": "string"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "uint256",
+                    "name": "id",
+                    "type": "uint256"
+                }
+            ],
+            "name": "URI",
+            "type": "event"
+        },
+        {
+            "inputs": [],
+            "name": "DEFAULT_ADMIN_ROLE",
+            "outputs": [
+                {
+                    "internalType": "bytes32",
+                    "name": "",
+                    "type": "bytes32"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "MINTER_ROLE",
+            "outputs": [
+                {
+                    "internalType": "bytes32",
+                    "name": "",
+                    "type": "bytes32"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "account",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "id",
+                    "type": "uint256"
+                }
+            ],
+            "name": "balanceOf",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address[]",
+                    "name": "accounts",
+                    "type": "address[]"
+                },
+                {
+                    "internalType": "uint256[]",
+                    "name": "ids",
+                    "type": "uint256[]"
+                }
+            ],
+            "name": "balanceOfBatch",
+            "outputs": [
+                {
+                    "internalType": "uint256[]",
+                    "name": "",
+                    "type": "uint256[]"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "bytes32",
+                    "name": "role",
+                    "type": "bytes32"
+                }
+            ],
+            "name": "getRoleAdmin",
+            "outputs": [
+                {
+                    "internalType": "bytes32",
+                    "name": "",
+                    "type": "bytes32"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "bytes32",
+                    "name": "role",
+                    "type": "bytes32"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "index",
+                    "type": "uint256"
+                }
+            ],
+            "name": "getRoleMember",
+            "outputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "bytes32",
+                    "name": "role",
+                    "type": "bytes32"
+                }
+            ],
+            "name": "getRoleMemberCount",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "bytes32",
+                    "name": "role",
+                    "type": "bytes32"
+                },
+                {
+                    "internalType": "address",
+                    "name": "account",
+                    "type": "address"
+                }
+            ],
+            "name": "grantRole",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "bytes32",
+                    "name": "role",
+                    "type": "bytes32"
+                },
+                {
+                    "internalType": "address",
+                    "name": "account",
+                    "type": "address"
+                }
+            ],
+            "name": "hasRole",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "account",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "operator",
+                    "type": "address"
+                }
+            ],
+            "name": "isApprovedForAll",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "to",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "id",
+                    "type": "uint256"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                },
+                {
+                    "internalType": "bytes",
+                    "name": "data",
+                    "type": "bytes"
+                }
+            ],
+            "name": "mint",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "to",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256[]",
+                    "name": "ids",
+                    "type": "uint256[]"
+                },
+                {
+                    "internalType": "uint256[]",
+                    "name": "amounts",
+                    "type": "uint256[]"
+                },
+                {
+                    "internalType": "bytes",
+                    "name": "data",
+                    "type": "bytes"
+                }
+            ],
+            "name": "mintBatch",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "owner",
+            "outputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "renounceOwnership",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "bytes32",
+                    "name": "role",
+                    "type": "bytes32"
+                },
+                {
+                    "internalType": "address",
+                    "name": "account",
+                    "type": "address"
+                }
+            ],
+            "name": "renounceRole",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "bytes32",
+                    "name": "role",
+                    "type": "bytes32"
+                },
+                {
+                    "internalType": "address",
+                    "name": "account",
+                    "type": "address"
+                }
+            ],
+            "name": "revokeRole",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "from",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "to",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256[]",
+                    "name": "ids",
+                    "type": "uint256[]"
+                },
+                {
+                    "internalType": "uint256[]",
+                    "name": "amounts",
+                    "type": "uint256[]"
+                },
+                {
+                    "internalType": "bytes",
+                    "name": "data",
+                    "type": "bytes"
+                }
+            ],
+            "name": "safeBatchTransferFrom",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "from",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "to",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "id",
+                    "type": "uint256"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                },
+                {
+                    "internalType": "bytes",
+                    "name": "data",
+                    "type": "bytes"
+                }
+            ],
+            "name": "safeTransferFrom",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "operator",
+                    "type": "address"
+                },
+                {
+                    "internalType": "bool",
+                    "name": "approved",
+                    "type": "bool"
+                }
+            ],
+            "name": "setApprovalForAll",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "bytes4",
+                    "name": "interfaceId",
+                    "type": "bytes4"
+                }
+            ],
+            "name": "supportsInterface",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "newOwner",
+                    "type": "address"
+                }
+            ],
+            "name": "transferOwnership",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "name": "uri",
+            "outputs": [
+                {
+                    "internalType": "string",
+                    "name": "",
+                    "type": "string"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        }
+    ],
+    "bytecode": "0x60806040523480156200001157600080fd5b5060405162002b6838038062002b68833981810160405260408110156200003757600080fd5b81019080805160405193929190846401000000008211156200005857600080fd5b9083019060208201858111156200006e57600080fd5b82516401000000008111828201881017156200008957600080fd5b82525081516020918201929091019080838360005b83811015620000b85781810151838201526020016200009e565b50505050905090810190601f168015620000e65780820380516001836020036101000a031916815260200191505b50604052602001519150829050620001056301ffc9a760e01b620001ca565b62000110816200024f565b62000122636cdb3d1360e11b620001ca565b620001346303a24d0760e21b620001ca565b5060006200014162000268565b600580546001600160a01b0319166001600160a01b0383169081179091556040519192509060009060008051602062002b48833981519152908290a35062000189816200026c565b6200019660008262000377565b620001c27f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a68262000377565b505062000540565b6001600160e01b031980821614156200022a576040805162461bcd60e51b815260206004820152601c60248201527f4552433136353a20696e76616c696420696e7465726661636520696400000000604482015290519081900360640190fd5b6001600160e01b0319166000908152602081905260409020805460ff19166001179055565b80516200026490600390602084019062000494565b5050565b3390565b6200027662000268565b6001600160a01b03166200028962000383565b6001600160a01b031614620002e5576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b6001600160a01b0381166200032c5760405162461bcd60e51b815260040180806020018281038252602681526020018062002b226026913960400191505060405180910390fd5b6005546040516001600160a01b0380841692169060008051602062002b4883398151915290600090a3600580546001600160a01b0319166001600160a01b0392909216919091179055565b62000264828262000392565b6005546001600160a01b031690565b6000828152600460209081526040909120620003b9918390620017116200040d821b17901c565b156200026457620003c962000268565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b600062000424836001600160a01b0384166200042d565b90505b92915050565b60006200043b83836200047c565b620004735750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915562000427565b50600062000427565b60009081526001919091016020526040902054151590565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282620004cc576000855562000517565b82601f10620004e757805160ff191683800117855562000517565b8280016001018555821562000517579182015b8281111562000517578251825591602001919060010190620004fa565b506200052592915062000529565b5090565b5b808211156200052557600081556001016200052a565b6125d280620005506000396000f3fe608060405234801561001057600080fd5b50600436106101415760003560e01c80638da5cb5b116100b8578063ca15c8731161007c578063ca15c87314610925578063d539139314610942578063d547741f1461094a578063e985e9c514610976578063f242432a146109a4578063f2fde38b14610a6d57610141565b80638da5cb5b1461087c5780639010d07c146108a057806391d14854146108c3578063a217fddf146108ef578063a22cb465146108f757610141565b80632eb2c2d61161010a5780632eb2c2d6146104285780632f2ff15d146105e957806336568abe146106155780634e1273f414610641578063715018a6146107b4578063731133e9146107bc57610141565b8062fdd58e1461014657806301ffc9a7146101845780630e89341c146101bf5780631f7fdffa14610251578063248a9ca31461040b575b600080fd5b6101726004803603604081101561015c57600080fd5b506001600160a01b038135169060200135610a93565b60408051918252519081900360200190f35b6101ab6004803603602081101561019a57600080fd5b50356001600160e01b031916610b05565b604080519115158252519081900360200190f35b6101dc600480360360208110156101d557600080fd5b5035610b24565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102165781810151838201526020016101fe565b50505050905090810190601f1680156102435780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6104096004803603608081101561026757600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561029157600080fd5b8201836020820111156102a357600080fd5b803590602001918460208302840111600160201b831117156102c457600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b81111561031357600080fd5b82018360208201111561032557600080fd5b803590602001918460208302840111600160201b8311171561034657600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b81111561039557600080fd5b8201836020820111156103a757600080fd5b803590602001918460018302840111600160201b831117156103c857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610bbc945050505050565b005b6101726004803603602081101561042157600080fd5b5035610c36565b610409600480360360a081101561043e57600080fd5b6001600160a01b038235811692602081013590911691810190606081016040820135600160201b81111561047157600080fd5b82018360208201111561048357600080fd5b803590602001918460208302840111600160201b831117156104a457600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b8111156104f357600080fd5b82018360208201111561050557600080fd5b803590602001918460208302840111600160201b8311171561052657600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b81111561057557600080fd5b82018360208201111561058757600080fd5b803590602001918460018302840111600160201b831117156105a857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610c4b945050505050565b610409600480360360408110156105ff57600080fd5b50803590602001356001600160a01b0316610f49565b6104096004803603604081101561062b57600080fd5b50803590602001356001600160a01b0316610fb5565b6107646004803603604081101561065757600080fd5b810190602081018135600160201b81111561067157600080fd5b82018360208201111561068357600080fd5b803590602001918460208302840111600160201b831117156106a457600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b8111156106f357600080fd5b82018360208201111561070557600080fd5b803590602001918460208302840111600160201b8311171561072657600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550611016945050505050565b60408051602080825283518183015283519192839290830191858101910280838360005b838110156107a0578181015183820152602001610788565b505050509050019250505060405180910390f35b610409611102565b610409600480360360808110156107d257600080fd5b6001600160a01b038235169160208101359160408201359190810190608081016060820135600160201b81111561080857600080fd5b82018360208201111561081a57600080fd5b803590602001918460018302840111600160201b8311171561083b57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506111c0945050505050565b610884611234565b604080516001600160a01b039092168252519081900360200190f35b610884600480360360408110156108b657600080fd5b5080359060200135611244565b6101ab600480360360408110156108d957600080fd5b50803590602001356001600160a01b0316611263565b61017261127b565b6104096004803603604081101561090d57600080fd5b506001600160a01b0381351690602001351515611280565b6101726004803603602081101561093b57600080fd5b503561136f565b610172611386565b6104096004803603604081101561096057600080fd5b50803590602001356001600160a01b03166113aa565b6101ab6004803603604081101561098c57600080fd5b506001600160a01b0381358116916020013516611403565b610409600480360360a08110156109ba57600080fd5b6001600160a01b03823581169260208101359091169160408201359160608101359181019060a081016080820135600160201b8111156109f957600080fd5b820183602082011115610a0b57600080fd5b803590602001918460018302840111600160201b83111715610a2c57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611431945050505050565b61040960048036036020811015610a8357600080fd5b50356001600160a01b03166115fc565b60006001600160a01b038316610ada5760405162461bcd60e51b815260040180806020018281038252602b8152602001806123a8602b913960400191505060405180910390fd5b5060008181526001602090815260408083206001600160a01b03861684529091529020545b92915050565b6001600160e01b03191660009081526020819052604090205460ff1690565b60038054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815260609390929091830182828015610bb05780601f10610b8557610100808354040283529160200191610bb0565b820191906000526020600020905b815481529060010190602001808311610b9357829003601f168201915b50505050509050919050565b610be67f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a633611263565b610c24576040805162461bcd60e51b815260206004820152600a6024820152692727aa2fa6a4a72a22a960b11b604482015290519081900360640190fd5b610c3084848484611726565b50505050565b60009081526004602052604090206002015490565b8151835114610c8b5760405162461bcd60e51b81526004018080602001828103825260288152602001806125256028913960400191505060405180910390fd5b6001600160a01b038416610cd05760405162461bcd60e51b81526004018080602001828103825260258152602001806124526025913960400191505060405180910390fd5b610cd861197b565b6001600160a01b0316856001600160a01b03161480610d035750610d0385610cfe61197b565b611403565b610d3e5760405162461bcd60e51b81526004018080602001828103825260328152602001806124776032913960400191505060405180910390fd5b6000610d4861197b565b9050610d58818787878787610f41565b60005b8451811015610e59576000858281518110610d7257fe5b602002602001015190506000858381518110610d8a57fe5b60200260200101519050610df7816040518060600160405280602a81526020016124a9602a91396001600086815260200190815260200160002060008d6001600160a01b03166001600160a01b031681526020019081526020016000205461197f9092919063ffffffff16565b60008381526001602090815260408083206001600160a01b038e811685529252808320939093558a1681522054610e2e9082611a16565b60009283526001602081815260408086206001600160a01b038d168752909152909320555001610d5b565b50846001600160a01b0316866001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8787604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b83811015610edf578181015183820152602001610ec7565b50505050905001838103825284818151815260200191508051906020019060200280838360005b83811015610f1e578181015183820152602001610f06565b5050505090500194505050505060405180910390a4610f41818787878787611a70565b505050505050565b600082815260046020526040902060020154610f6c90610f6761197b565b611263565b610fa75760405162461bcd60e51b815260040180806020018281038252602f815260200180612379602f913960400191505060405180910390fd5b610fb18282611cef565b5050565b610fbd61197b565b6001600160a01b0316816001600160a01b03161461100c5760405162461bcd60e51b815260040180806020018281038252602f81526020018061256e602f913960400191505060405180910390fd5b610fb18282611d58565b606081518351146110585760405162461bcd60e51b81526004018080602001828103825260298152602001806124fc6029913960400191505060405180910390fd5b6000835167ffffffffffffffff8111801561107257600080fd5b5060405190808252806020026020018201604052801561109c578160200160208202803683370190505b50905060005b84518110156110fa576110db8582815181106110ba57fe5b60200260200101518583815181106110ce57fe5b6020026020010151610a93565b8282815181106110e757fe5b60209081029190910101526001016110a2565b509392505050565b61110a61197b565b6001600160a01b031661111b611234565b6001600160a01b031614611176576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b6005546040516000916001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600580546001600160a01b0319169055565b6111ea7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a633611263565b611228576040805162461bcd60e51b815260206004820152600a6024820152692727aa2fa6a4a72a22a960b11b604482015290519081900360640190fd5b610c3084848484611dc1565b6005546001600160a01b03165b90565b600082815260046020526040812061125c9083611ec2565b9392505050565b600082815260046020526040812061125c9083611ece565b600081565b816001600160a01b031661129261197b565b6001600160a01b031614156112d85760405162461bcd60e51b81526004018080602001828103825260298152602001806124d36029913960400191505060405180910390fd5b80600260006112e561197b565b6001600160a01b03908116825260208083019390935260409182016000908120918716808252919093529120805460ff19169215159290921790915561132961197b565b6001600160a01b03167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c318360405180821515815260200191505060405180910390a35050565b6000818152600460205260408120610aff90611ee3565b7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a681565b6000828152600460205260409020600201546113c890610f6761197b565b61100c5760405162461bcd60e51b81526004018080602001828103825260308152602001806124226030913960400191505060405180910390fd5b6001600160a01b03918216600090815260026020908152604080832093909416825291909152205460ff1690565b6001600160a01b0384166114765760405162461bcd60e51b81526004018080602001828103825260258152602001806124526025913960400191505060405180910390fd5b61147e61197b565b6001600160a01b0316856001600160a01b031614806114a457506114a485610cfe61197b565b6114df5760405162461bcd60e51b81526004018080602001828103825260298152602001806123f96029913960400191505060405180910390fd5b60006114e961197b565b90506115098187876114fa88611eee565b61150388611eee565b87610f41565b611550836040518060600160405280602a81526020016124a9602a913960008781526001602090815260408083206001600160a01b038d168452909152902054919061197f565b60008581526001602090815260408083206001600160a01b038b811685529252808320939093558716815220546115879084611a16565b60008581526001602090815260408083206001600160a01b03808b168086529184529382902094909455805188815291820187905280518a8416938616927fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6292908290030190a4610f41818787878787611f33565b61160461197b565b6001600160a01b0316611615611234565b6001600160a01b031614611670576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b6001600160a01b0381166116b55760405162461bcd60e51b81526004018080602001828103825260268152602001806123d36026913960400191505060405180910390fd5b6005546040516001600160a01b038084169216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3600580546001600160a01b0319166001600160a01b0392909216919091179055565b600061125c836001600160a01b0384166120a4565b6001600160a01b03841661176b5760405162461bcd60e51b815260040180806020018281038252602181526020018061254d6021913960400191505060405180910390fd5b81518351146117ab5760405162461bcd60e51b81526004018080602001828103825260288152602001806125256028913960400191505060405180910390fd5b60006117b561197b565b90506117c681600087878787610f41565b60005b845181101561188a57611841600160008784815181106117e557fe5b602002602001015181526020019081526020016000206000886001600160a01b03166001600160a01b031681526020019081526020016000205485838151811061182b57fe5b6020026020010151611a1690919063ffffffff16565b6001600087848151811061185157fe5b602090810291909101810151825281810192909252604090810160009081206001600160a01b038b1682529092529020556001016117c9565b50846001600160a01b031660006001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8787604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b838110156119115781810151838201526020016118f9565b50505050905001838103825284818151815260200191508051906020019060200280838360005b83811015611950578181015183820152602001611938565b5050505090500194505050505060405180910390a461197481600087878787611a70565b5050505050565b3390565b60008184841115611a0e5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156119d35781810151838201526020016119bb565b50505050905090810190601f168015611a005780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b60008282018381101561125c576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b611a82846001600160a01b03166120ee565b15610f4157836001600160a01b031663bc197c8187878686866040518663ffffffff1660e01b815260040180866001600160a01b03168152602001856001600160a01b03168152602001806020018060200180602001848103845287818151815260200191508051906020019060200280838360005b83811015611b10578181015183820152602001611af8565b50505050905001848103835286818151815260200191508051906020019060200280838360005b83811015611b4f578181015183820152602001611b37565b50505050905001848103825285818151815260200191508051906020019080838360005b83811015611b8b578181015183820152602001611b73565b50505050905090810190601f168015611bb85780820380516001836020036101000a031916815260200191505b5098505050505050505050602060405180830381600087803b158015611bdd57600080fd5b505af1925050508015611c0257506040513d6020811015611bfd57600080fd5b505160015b611c9757611c0e612255565b80611c195750611c60565b60405162461bcd60e51b81526020600482018181528351602484015283518493919283926044019190850190808383600083156119d35781810151838201526020016119bb565b60405162461bcd60e51b81526004018080602001828103825260348152602001806122fb6034913960400191505060405180910390fd5b6001600160e01b0319811663bc197c8160e01b14611ce65760405162461bcd60e51b81526004018080602001828103825260288152602001806123516028913960400191505060405180910390fd5b50505050505050565b6000828152600460205260409020611d079082611711565b15610fb157611d1461197b565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000828152600460205260409020611d7090826120f4565b15610fb157611d7d61197b565b6001600160a01b0316816001600160a01b0316837ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b60405160405180910390a45050565b6001600160a01b038416611e065760405162461bcd60e51b815260040180806020018281038252602181526020018061254d6021913960400191505060405180910390fd5b6000611e1061197b565b9050611e22816000876114fa88611eee565b60008481526001602090815260408083206001600160a01b0389168452909152902054611e4f9084611a16565b60008581526001602090815260408083206001600160a01b03808b16808652918452828520959095558151898152928301889052815190948616927fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6292908290030190a461197481600087878787611f33565b600061125c8383612109565b600061125c836001600160a01b03841661216d565b6000610aff82612185565b60408051600180825281830190925260609160009190602080830190803683370190505090508281600081518110611f2257fe5b602090810291909101015292915050565b611f45846001600160a01b03166120ee565b15610f4157836001600160a01b031663f23a6e6187878686866040518663ffffffff1660e01b815260040180866001600160a01b03168152602001856001600160a01b0316815260200184815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015611fd4578181015183820152602001611fbc565b50505050905090810190601f1680156120015780820380516001836020036101000a031916815260200191505b509650505050505050602060405180830381600087803b15801561202457600080fd5b505af192505050801561204957506040513d602081101561204457600080fd5b505160015b61205557611c0e612255565b6001600160e01b0319811663f23a6e6160e01b14611ce65760405162461bcd60e51b81526004018080602001828103825260288152602001806123516028913960400191505060405180910390fd5b60006120b0838361216d565b6120e657508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610aff565b506000610aff565b3b151590565b600061125c836001600160a01b038416612189565b8154600090821061214b5760405162461bcd60e51b815260040180806020018281038252602281526020018061232f6022913960400191505060405180910390fd5b82600001828154811061215a57fe5b9060005260206000200154905092915050565b60009081526001919091016020526040902054151590565b5490565b6000818152600183016020526040812054801561224557835460001980830191908101906000908790839081106121bc57fe5b90600052602060002001549050808760000184815481106121d957fe5b60009182526020808320909101929092558281526001898101909252604090209084019055865487908061220957fe5b60019003818190600052602060002001600090559055866001016000878152602001908152602001600020600090556001945050505050610aff565b6000915050610aff565b60e01c90565b600060443d101561226557611241565b600481823e6308c379a0612279825161224f565b1461228357611241565b6040513d600319016004823e80513d67ffffffffffffffff81602484011181841117156122b35750505050611241565b828401925082519150808211156122cd5750505050611241565b503d830160208284010111156122e557505050611241565b601f01601f191681016020016040529150509056fe455243313135353a207472616e7366657220746f206e6f6e2045524331313535526563656976657220696d706c656d656e746572456e756d657261626c655365743a20696e646578206f7574206f6620626f756e6473455243313135353a204552433131353552656365697665722072656a656374656420746f6b656e73416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f206772616e74455243313135353a2062616c616e636520717565727920666f7220746865207a65726f20616464726573734f776e61626c653a206e6577206f776e657220697320746865207a65726f2061646472657373455243313135353a2063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f766564416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f207265766f6b65455243313135353a207472616e7366657220746f20746865207a65726f2061646472657373455243313135353a207472616e736665722063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f766564455243313135353a20696e73756666696369656e742062616c616e636520666f72207472616e73666572455243313135353a2073657474696e6720617070726f76616c2073746174757320666f722073656c66455243313135353a206163636f756e747320616e6420696473206c656e677468206d69736d61746368455243313135353a2069647320616e6420616d6f756e7473206c656e677468206d69736d61746368455243313135353a206d696e7420746f20746865207a65726f2061646472657373416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636520726f6c657320666f722073656c66a26469706673582212206b93266a963cefb5dcc7161758be463edca28a8dc8337e55beefbbabc25f14ee64736f6c634300070600334f776e61626c653a206e6577206f776e657220697320746865207a65726f20616464726573738be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0",
+    "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106101415760003560e01c80638da5cb5b116100b8578063ca15c8731161007c578063ca15c87314610925578063d539139314610942578063d547741f1461094a578063e985e9c514610976578063f242432a146109a4578063f2fde38b14610a6d57610141565b80638da5cb5b1461087c5780639010d07c146108a057806391d14854146108c3578063a217fddf146108ef578063a22cb465146108f757610141565b80632eb2c2d61161010a5780632eb2c2d6146104285780632f2ff15d146105e957806336568abe146106155780634e1273f414610641578063715018a6146107b4578063731133e9146107bc57610141565b8062fdd58e1461014657806301ffc9a7146101845780630e89341c146101bf5780631f7fdffa14610251578063248a9ca31461040b575b600080fd5b6101726004803603604081101561015c57600080fd5b506001600160a01b038135169060200135610a93565b60408051918252519081900360200190f35b6101ab6004803603602081101561019a57600080fd5b50356001600160e01b031916610b05565b604080519115158252519081900360200190f35b6101dc600480360360208110156101d557600080fd5b5035610b24565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102165781810151838201526020016101fe565b50505050905090810190601f1680156102435780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6104096004803603608081101561026757600080fd5b6001600160a01b038235169190810190604081016020820135600160201b81111561029157600080fd5b8201836020820111156102a357600080fd5b803590602001918460208302840111600160201b831117156102c457600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b81111561031357600080fd5b82018360208201111561032557600080fd5b803590602001918460208302840111600160201b8311171561034657600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b81111561039557600080fd5b8201836020820111156103a757600080fd5b803590602001918460018302840111600160201b831117156103c857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610bbc945050505050565b005b6101726004803603602081101561042157600080fd5b5035610c36565b610409600480360360a081101561043e57600080fd5b6001600160a01b038235811692602081013590911691810190606081016040820135600160201b81111561047157600080fd5b82018360208201111561048357600080fd5b803590602001918460208302840111600160201b831117156104a457600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b8111156104f357600080fd5b82018360208201111561050557600080fd5b803590602001918460208302840111600160201b8311171561052657600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b81111561057557600080fd5b82018360208201111561058757600080fd5b803590602001918460018302840111600160201b831117156105a857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610c4b945050505050565b610409600480360360408110156105ff57600080fd5b50803590602001356001600160a01b0316610f49565b6104096004803603604081101561062b57600080fd5b50803590602001356001600160a01b0316610fb5565b6107646004803603604081101561065757600080fd5b810190602081018135600160201b81111561067157600080fd5b82018360208201111561068357600080fd5b803590602001918460208302840111600160201b831117156106a457600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295949360208101935035915050600160201b8111156106f357600080fd5b82018360208201111561070557600080fd5b803590602001918460208302840111600160201b8311171561072657600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550611016945050505050565b60408051602080825283518183015283519192839290830191858101910280838360005b838110156107a0578181015183820152602001610788565b505050509050019250505060405180910390f35b610409611102565b610409600480360360808110156107d257600080fd5b6001600160a01b038235169160208101359160408201359190810190608081016060820135600160201b81111561080857600080fd5b82018360208201111561081a57600080fd5b803590602001918460018302840111600160201b8311171561083b57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506111c0945050505050565b610884611234565b604080516001600160a01b039092168252519081900360200190f35b610884600480360360408110156108b657600080fd5b5080359060200135611244565b6101ab600480360360408110156108d957600080fd5b50803590602001356001600160a01b0316611263565b61017261127b565b6104096004803603604081101561090d57600080fd5b506001600160a01b0381351690602001351515611280565b6101726004803603602081101561093b57600080fd5b503561136f565b610172611386565b6104096004803603604081101561096057600080fd5b50803590602001356001600160a01b03166113aa565b6101ab6004803603604081101561098c57600080fd5b506001600160a01b0381358116916020013516611403565b610409600480360360a08110156109ba57600080fd5b6001600160a01b03823581169260208101359091169160408201359160608101359181019060a081016080820135600160201b8111156109f957600080fd5b820183602082011115610a0b57600080fd5b803590602001918460018302840111600160201b83111715610a2c57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611431945050505050565b61040960048036036020811015610a8357600080fd5b50356001600160a01b03166115fc565b60006001600160a01b038316610ada5760405162461bcd60e51b815260040180806020018281038252602b8152602001806123a8602b913960400191505060405180910390fd5b5060008181526001602090815260408083206001600160a01b03861684529091529020545b92915050565b6001600160e01b03191660009081526020819052604090205460ff1690565b60038054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815260609390929091830182828015610bb05780601f10610b8557610100808354040283529160200191610bb0565b820191906000526020600020905b815481529060010190602001808311610b9357829003601f168201915b50505050509050919050565b610be67f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a633611263565b610c24576040805162461bcd60e51b815260206004820152600a6024820152692727aa2fa6a4a72a22a960b11b604482015290519081900360640190fd5b610c3084848484611726565b50505050565b60009081526004602052604090206002015490565b8151835114610c8b5760405162461bcd60e51b81526004018080602001828103825260288152602001806125256028913960400191505060405180910390fd5b6001600160a01b038416610cd05760405162461bcd60e51b81526004018080602001828103825260258152602001806124526025913960400191505060405180910390fd5b610cd861197b565b6001600160a01b0316856001600160a01b03161480610d035750610d0385610cfe61197b565b611403565b610d3e5760405162461bcd60e51b81526004018080602001828103825260328152602001806124776032913960400191505060405180910390fd5b6000610d4861197b565b9050610d58818787878787610f41565b60005b8451811015610e59576000858281518110610d7257fe5b602002602001015190506000858381518110610d8a57fe5b60200260200101519050610df7816040518060600160405280602a81526020016124a9602a91396001600086815260200190815260200160002060008d6001600160a01b03166001600160a01b031681526020019081526020016000205461197f9092919063ffffffff16565b60008381526001602090815260408083206001600160a01b038e811685529252808320939093558a1681522054610e2e9082611a16565b60009283526001602081815260408086206001600160a01b038d168752909152909320555001610d5b565b50846001600160a01b0316866001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8787604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b83811015610edf578181015183820152602001610ec7565b50505050905001838103825284818151815260200191508051906020019060200280838360005b83811015610f1e578181015183820152602001610f06565b5050505090500194505050505060405180910390a4610f41818787878787611a70565b505050505050565b600082815260046020526040902060020154610f6c90610f6761197b565b611263565b610fa75760405162461bcd60e51b815260040180806020018281038252602f815260200180612379602f913960400191505060405180910390fd5b610fb18282611cef565b5050565b610fbd61197b565b6001600160a01b0316816001600160a01b03161461100c5760405162461bcd60e51b815260040180806020018281038252602f81526020018061256e602f913960400191505060405180910390fd5b610fb18282611d58565b606081518351146110585760405162461bcd60e51b81526004018080602001828103825260298152602001806124fc6029913960400191505060405180910390fd5b6000835167ffffffffffffffff8111801561107257600080fd5b5060405190808252806020026020018201604052801561109c578160200160208202803683370190505b50905060005b84518110156110fa576110db8582815181106110ba57fe5b60200260200101518583815181106110ce57fe5b6020026020010151610a93565b8282815181106110e757fe5b60209081029190910101526001016110a2565b509392505050565b61110a61197b565b6001600160a01b031661111b611234565b6001600160a01b031614611176576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b6005546040516000916001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600580546001600160a01b0319169055565b6111ea7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a633611263565b611228576040805162461bcd60e51b815260206004820152600a6024820152692727aa2fa6a4a72a22a960b11b604482015290519081900360640190fd5b610c3084848484611dc1565b6005546001600160a01b03165b90565b600082815260046020526040812061125c9083611ec2565b9392505050565b600082815260046020526040812061125c9083611ece565b600081565b816001600160a01b031661129261197b565b6001600160a01b031614156112d85760405162461bcd60e51b81526004018080602001828103825260298152602001806124d36029913960400191505060405180910390fd5b80600260006112e561197b565b6001600160a01b03908116825260208083019390935260409182016000908120918716808252919093529120805460ff19169215159290921790915561132961197b565b6001600160a01b03167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c318360405180821515815260200191505060405180910390a35050565b6000818152600460205260408120610aff90611ee3565b7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a681565b6000828152600460205260409020600201546113c890610f6761197b565b61100c5760405162461bcd60e51b81526004018080602001828103825260308152602001806124226030913960400191505060405180910390fd5b6001600160a01b03918216600090815260026020908152604080832093909416825291909152205460ff1690565b6001600160a01b0384166114765760405162461bcd60e51b81526004018080602001828103825260258152602001806124526025913960400191505060405180910390fd5b61147e61197b565b6001600160a01b0316856001600160a01b031614806114a457506114a485610cfe61197b565b6114df5760405162461bcd60e51b81526004018080602001828103825260298152602001806123f96029913960400191505060405180910390fd5b60006114e961197b565b90506115098187876114fa88611eee565b61150388611eee565b87610f41565b611550836040518060600160405280602a81526020016124a9602a913960008781526001602090815260408083206001600160a01b038d168452909152902054919061197f565b60008581526001602090815260408083206001600160a01b038b811685529252808320939093558716815220546115879084611a16565b60008581526001602090815260408083206001600160a01b03808b168086529184529382902094909455805188815291820187905280518a8416938616927fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6292908290030190a4610f41818787878787611f33565b61160461197b565b6001600160a01b0316611615611234565b6001600160a01b031614611670576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b6001600160a01b0381166116b55760405162461bcd60e51b81526004018080602001828103825260268152602001806123d36026913960400191505060405180910390fd5b6005546040516001600160a01b038084169216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3600580546001600160a01b0319166001600160a01b0392909216919091179055565b600061125c836001600160a01b0384166120a4565b6001600160a01b03841661176b5760405162461bcd60e51b815260040180806020018281038252602181526020018061254d6021913960400191505060405180910390fd5b81518351146117ab5760405162461bcd60e51b81526004018080602001828103825260288152602001806125256028913960400191505060405180910390fd5b60006117b561197b565b90506117c681600087878787610f41565b60005b845181101561188a57611841600160008784815181106117e557fe5b602002602001015181526020019081526020016000206000886001600160a01b03166001600160a01b031681526020019081526020016000205485838151811061182b57fe5b6020026020010151611a1690919063ffffffff16565b6001600087848151811061185157fe5b602090810291909101810151825281810192909252604090810160009081206001600160a01b038b1682529092529020556001016117c9565b50846001600160a01b031660006001600160a01b0316826001600160a01b03167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8787604051808060200180602001838103835285818151815260200191508051906020019060200280838360005b838110156119115781810151838201526020016118f9565b50505050905001838103825284818151815260200191508051906020019060200280838360005b83811015611950578181015183820152602001611938565b5050505090500194505050505060405180910390a461197481600087878787611a70565b5050505050565b3390565b60008184841115611a0e5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156119d35781810151838201526020016119bb565b50505050905090810190601f168015611a005780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b60008282018381101561125c576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b611a82846001600160a01b03166120ee565b15610f4157836001600160a01b031663bc197c8187878686866040518663ffffffff1660e01b815260040180866001600160a01b03168152602001856001600160a01b03168152602001806020018060200180602001848103845287818151815260200191508051906020019060200280838360005b83811015611b10578181015183820152602001611af8565b50505050905001848103835286818151815260200191508051906020019060200280838360005b83811015611b4f578181015183820152602001611b37565b50505050905001848103825285818151815260200191508051906020019080838360005b83811015611b8b578181015183820152602001611b73565b50505050905090810190601f168015611bb85780820380516001836020036101000a031916815260200191505b5098505050505050505050602060405180830381600087803b158015611bdd57600080fd5b505af1925050508015611c0257506040513d6020811015611bfd57600080fd5b505160015b611c9757611c0e612255565b80611c195750611c60565b60405162461bcd60e51b81526020600482018181528351602484015283518493919283926044019190850190808383600083156119d35781810151838201526020016119bb565b60405162461bcd60e51b81526004018080602001828103825260348152602001806122fb6034913960400191505060405180910390fd5b6001600160e01b0319811663bc197c8160e01b14611ce65760405162461bcd60e51b81526004018080602001828103825260288152602001806123516028913960400191505060405180910390fd5b50505050505050565b6000828152600460205260409020611d079082611711565b15610fb157611d1461197b565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000828152600460205260409020611d7090826120f4565b15610fb157611d7d61197b565b6001600160a01b0316816001600160a01b0316837ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b60405160405180910390a45050565b6001600160a01b038416611e065760405162461bcd60e51b815260040180806020018281038252602181526020018061254d6021913960400191505060405180910390fd5b6000611e1061197b565b9050611e22816000876114fa88611eee565b60008481526001602090815260408083206001600160a01b0389168452909152902054611e4f9084611a16565b60008581526001602090815260408083206001600160a01b03808b16808652918452828520959095558151898152928301889052815190948616927fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6292908290030190a461197481600087878787611f33565b600061125c8383612109565b600061125c836001600160a01b03841661216d565b6000610aff82612185565b60408051600180825281830190925260609160009190602080830190803683370190505090508281600081518110611f2257fe5b602090810291909101015292915050565b611f45846001600160a01b03166120ee565b15610f4157836001600160a01b031663f23a6e6187878686866040518663ffffffff1660e01b815260040180866001600160a01b03168152602001856001600160a01b0316815260200184815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015611fd4578181015183820152602001611fbc565b50505050905090810190601f1680156120015780820380516001836020036101000a031916815260200191505b509650505050505050602060405180830381600087803b15801561202457600080fd5b505af192505050801561204957506040513d602081101561204457600080fd5b505160015b61205557611c0e612255565b6001600160e01b0319811663f23a6e6160e01b14611ce65760405162461bcd60e51b81526004018080602001828103825260288152602001806123516028913960400191505060405180910390fd5b60006120b0838361216d565b6120e657508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610aff565b506000610aff565b3b151590565b600061125c836001600160a01b038416612189565b8154600090821061214b5760405162461bcd60e51b815260040180806020018281038252602281526020018061232f6022913960400191505060405180910390fd5b82600001828154811061215a57fe5b9060005260206000200154905092915050565b60009081526001919091016020526040902054151590565b5490565b6000818152600183016020526040812054801561224557835460001980830191908101906000908790839081106121bc57fe5b90600052602060002001549050808760000184815481106121d957fe5b60009182526020808320909101929092558281526001898101909252604090209084019055865487908061220957fe5b60019003818190600052602060002001600090559055866001016000878152602001908152602001600020600090556001945050505050610aff565b6000915050610aff565b60e01c90565b600060443d101561226557611241565b600481823e6308c379a0612279825161224f565b1461228357611241565b6040513d600319016004823e80513d67ffffffffffffffff81602484011181841117156122b35750505050611241565b828401925082519150808211156122cd5750505050611241565b503d830160208284010111156122e557505050611241565b601f01601f191681016020016040529150509056fe455243313135353a207472616e7366657220746f206e6f6e2045524331313535526563656976657220696d706c656d656e746572456e756d657261626c655365743a20696e646578206f7574206f6620626f756e6473455243313135353a204552433131353552656365697665722072656a656374656420746f6b656e73416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f206772616e74455243313135353a2062616c616e636520717565727920666f7220746865207a65726f20616464726573734f776e61626c653a206e6577206f776e657220697320746865207a65726f2061646472657373455243313135353a2063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f766564416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f207265766f6b65455243313135353a207472616e7366657220746f20746865207a65726f2061646472657373455243313135353a207472616e736665722063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f766564455243313135353a20696e73756666696369656e742062616c616e636520666f72207472616e73666572455243313135353a2073657474696e6720617070726f76616c2073746174757320666f722073656c66455243313135353a206163636f756e747320616e6420696473206c656e677468206d69736d61746368455243313135353a2069647320616e6420616d6f756e7473206c656e677468206d69736d61746368455243313135353a206d696e7420746f20746865207a65726f2061646472657373416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636520726f6c657320666f722073656c66a26469706673582212206b93266a963cefb5dcc7161758be463edca28a8dc8337e55beefbbabc25f14ee64736f6c63430007060033",
+    "linkReferences": {},
+    "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/THXERC20_LimitedSupply.json b/apps/api/src/app/hardhat/export/THXERC20_LimitedSupply.json
new file mode 100644
index 000000000..3667d557e
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/THXERC20_LimitedSupply.json
@@ -0,0 +1,307 @@
+{
+  "_format": "hh-sol-artifact-1",
+  "contractName": "THX_ERC20_LimitedSupply",
+  "sourceName": "contracts/utils/ERC20/THX_ERC20.sol",
+  "abi": [
+    {
+      "inputs": [
+        {
+          "internalType": "string",
+          "name": "_name",
+          "type": "string"
+        },
+        {
+          "internalType": "string",
+          "name": "_symbol",
+          "type": "string"
+        },
+        {
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "constructor"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        }
+      ],
+      "name": "Approval",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "from",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        }
+      ],
+      "name": "Transfer",
+      "type": "event"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        }
+      ],
+      "name": "allowance",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "approve",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        }
+      ],
+      "name": "balanceOf",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "decimals",
+      "outputs": [
+        {
+          "internalType": "uint8",
+          "name": "",
+          "type": "uint8"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "subtractedValue",
+          "type": "uint256"
+        }
+      ],
+      "name": "decreaseAllowance",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "addedValue",
+          "type": "uint256"
+        }
+      ],
+      "name": "increaseAllowance",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "name",
+      "outputs": [
+        {
+          "internalType": "string",
+          "name": "",
+          "type": "string"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "symbol",
+      "outputs": [
+        {
+          "internalType": "string",
+          "name": "",
+          "type": "string"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "totalSupply",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "recipient",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "transfer",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "sender",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "recipient",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "transferFrom",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    }
+  ],
+  "bytecode": "0x60806040523480156200001157600080fd5b5060405162000e2538038062000e25833981810160405260808110156200003757600080fd5b81019080805160405193929190846401000000008211156200005857600080fd5b9083019060208201858111156200006e57600080fd5b82516401000000008111828201881017156200008957600080fd5b82525081516020918201929091019080838360005b83811015620000b85781810151838201526020016200009e565b50505050905090810190601f168015620000e65780820380516001836020036101000a031916815260200191505b50604052602001805160405193929190846401000000008211156200010a57600080fd5b9083019060208201858111156200012057600080fd5b82516401000000008111828201881017156200013b57600080fd5b82525081516020918201929091019080838360005b838110156200016a57818101518382015260200162000150565b50505050905090810190601f168015620001985780820380516001836020036101000a031916815260200191505b50604090815260208281015192909101518651929450925085918591620001c59160039185019062000377565b508051620001db90600490602084019062000377565b50506005805460ff1916601217905550620001f7828262000201565b5050505062000423565b6001600160a01b0382166200025d576040805162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b6200026b6000838362000310565b62000287816002546200031560201b620005731790919060201c565b6002556001600160a01b03821660009081526020818152604090912054620002ba9183906200057362000315821b17901c565b6001600160a01b0383166000818152602081815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b505050565b60008282018381101562000370576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282620003af5760008555620003fa565b82601f10620003ca57805160ff1916838001178555620003fa565b82800160010185558215620003fa579182015b82811115620003fa578251825591602001919060010190620003dd565b50620004089291506200040c565b5090565b5b808211156200040857600081556001016200040d565b6109f280620004336000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c8063395093511161007157806339509351146101d957806370a082311461020557806395d89b411461022b578063a457c2d714610233578063a9059cbb1461025f578063dd62ed3e1461028b576100a9565b806306fdde03146100ae578063095ea7b31461012b57806318160ddd1461016b57806323b872dd14610185578063313ce567146101bb575b600080fd5b6100b66102b9565b6040805160208082528351818301528351919283929083019185019080838360005b838110156100f05781810151838201526020016100d8565b50505050905090810190601f16801561011d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101576004803603604081101561014157600080fd5b506001600160a01b03813516906020013561034f565b604080519115158252519081900360200190f35b61017361036c565b60408051918252519081900360200190f35b6101576004803603606081101561019b57600080fd5b506001600160a01b03813581169160208101359091169060400135610372565b6101c36103f9565b6040805160ff9092168252519081900360200190f35b610157600480360360408110156101ef57600080fd5b506001600160a01b038135169060200135610402565b6101736004803603602081101561021b57600080fd5b50356001600160a01b0316610450565b6100b661046b565b6101576004803603604081101561024957600080fd5b506001600160a01b0381351690602001356104cc565b6101576004803603604081101561027557600080fd5b506001600160a01b038135169060200135610534565b610173600480360360408110156102a157600080fd5b506001600160a01b0381358116916020013516610548565b60038054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103455780601f1061031a57610100808354040283529160200191610345565b820191906000526020600020905b81548152906001019060200180831161032857829003601f168201915b5050505050905090565b600061036361035c6105d4565b84846105d8565b50600192915050565b60025490565b600061037f8484846106c4565b6103ef8461038b6105d4565b6103ea85604051806060016040528060288152602001610927602891396001600160a01b038a166000908152600160205260408120906103c96105d4565b6001600160a01b03168152602081019190915260400160002054919061081f565b6105d8565b5060019392505050565b60055460ff1690565b600061036361040f6105d4565b846103ea85600160006104206105d4565b6001600160a01b03908116825260208083019390935260409182016000908120918c168152925290205490610573565b6001600160a01b031660009081526020819052604090205490565b60048054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103455780601f1061031a57610100808354040283529160200191610345565b60006103636104d96105d4565b846103ea8560405180606001604052806025815260200161099860259139600160006105036105d4565b6001600160a01b03908116825260208083019390935260409182016000908120918d1681529252902054919061081f565b60006103636105416105d4565b84846106c4565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6000828201838110156105cd576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b3390565b6001600160a01b03831661061d5760405162461bcd60e51b81526004018080602001828103825260248152602001806109746024913960400191505060405180910390fd5b6001600160a01b0382166106625760405162461bcd60e51b81526004018080602001828103825260228152602001806108df6022913960400191505060405180910390fd5b6001600160a01b03808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b0383166107095760405162461bcd60e51b815260040180806020018281038252602581526020018061094f6025913960400191505060405180910390fd5b6001600160a01b03821661074e5760405162461bcd60e51b81526004018080602001828103825260238152602001806108bc6023913960400191505060405180910390fd5b6107598383836108b6565b61079681604051806060016040528060268152602001610901602691396001600160a01b038616600090815260208190526040902054919061081f565b6001600160a01b0380851660009081526020819052604080822093909355908416815220546107c59082610573565b6001600160a01b038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600081848411156108ae5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561087357818101518382015260200161085b565b50505050905090810190601f1680156108a05780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b50505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa264697066735822122092555631e3c671c3ad0e9398fc7e4198120e26816633db409891a7e02b19db9464736f6c63430007060033",
+  "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100a95760003560e01c8063395093511161007157806339509351146101d957806370a082311461020557806395d89b411461022b578063a457c2d714610233578063a9059cbb1461025f578063dd62ed3e1461028b576100a9565b806306fdde03146100ae578063095ea7b31461012b57806318160ddd1461016b57806323b872dd14610185578063313ce567146101bb575b600080fd5b6100b66102b9565b6040805160208082528351818301528351919283929083019185019080838360005b838110156100f05781810151838201526020016100d8565b50505050905090810190601f16801561011d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101576004803603604081101561014157600080fd5b506001600160a01b03813516906020013561034f565b604080519115158252519081900360200190f35b61017361036c565b60408051918252519081900360200190f35b6101576004803603606081101561019b57600080fd5b506001600160a01b03813581169160208101359091169060400135610372565b6101c36103f9565b6040805160ff9092168252519081900360200190f35b610157600480360360408110156101ef57600080fd5b506001600160a01b038135169060200135610402565b6101736004803603602081101561021b57600080fd5b50356001600160a01b0316610450565b6100b661046b565b6101576004803603604081101561024957600080fd5b506001600160a01b0381351690602001356104cc565b6101576004803603604081101561027557600080fd5b506001600160a01b038135169060200135610534565b610173600480360360408110156102a157600080fd5b506001600160a01b0381358116916020013516610548565b60038054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103455780601f1061031a57610100808354040283529160200191610345565b820191906000526020600020905b81548152906001019060200180831161032857829003601f168201915b5050505050905090565b600061036361035c6105d4565b84846105d8565b50600192915050565b60025490565b600061037f8484846106c4565b6103ef8461038b6105d4565b6103ea85604051806060016040528060288152602001610927602891396001600160a01b038a166000908152600160205260408120906103c96105d4565b6001600160a01b03168152602081019190915260400160002054919061081f565b6105d8565b5060019392505050565b60055460ff1690565b600061036361040f6105d4565b846103ea85600160006104206105d4565b6001600160a01b03908116825260208083019390935260409182016000908120918c168152925290205490610573565b6001600160a01b031660009081526020819052604090205490565b60048054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103455780601f1061031a57610100808354040283529160200191610345565b60006103636104d96105d4565b846103ea8560405180606001604052806025815260200161099860259139600160006105036105d4565b6001600160a01b03908116825260208083019390935260409182016000908120918d1681529252902054919061081f565b60006103636105416105d4565b84846106c4565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6000828201838110156105cd576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b3390565b6001600160a01b03831661061d5760405162461bcd60e51b81526004018080602001828103825260248152602001806109746024913960400191505060405180910390fd5b6001600160a01b0382166106625760405162461bcd60e51b81526004018080602001828103825260228152602001806108df6022913960400191505060405180910390fd5b6001600160a01b03808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b0383166107095760405162461bcd60e51b815260040180806020018281038252602581526020018061094f6025913960400191505060405180910390fd5b6001600160a01b03821661074e5760405162461bcd60e51b81526004018080602001828103825260238152602001806108bc6023913960400191505060405180910390fd5b6107598383836108b6565b61079681604051806060016040528060268152602001610901602691396001600160a01b038616600090815260208190526040902054919061081f565b6001600160a01b0380851660009081526020819052604080822093909355908416815220546107c59082610573565b6001600160a01b038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600081848411156108ae5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561087357818101518382015260200161085b565b50505050905090810190601f1680156108a05780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b50505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa264697066735822122092555631e3c671c3ad0e9398fc7e4198120e26816633db409891a7e02b19db9464736f6c63430007060033",
+  "linkReferences": {},
+  "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/THXERC20_UnlimitedSupply.json b/apps/api/src/app/hardhat/export/THXERC20_UnlimitedSupply.json
new file mode 100644
index 000000000..bf15f47d1
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/THXERC20_UnlimitedSupply.json
@@ -0,0 +1,595 @@
+{
+  "_format": "hh-sol-artifact-1",
+  "contractName": "THX_ERC20_UnlimitedSupply",
+  "sourceName": "contracts/utils/ERC20/THX_ERC20_UnlimitedSupply.sol",
+  "abi": [
+    {
+      "inputs": [
+        {
+          "internalType": "string",
+          "name": "name_",
+          "type": "string"
+        },
+        {
+          "internalType": "string",
+          "name": "symbol_",
+          "type": "string"
+        },
+        {
+          "internalType": "address",
+          "name": "owner_",
+          "type": "address"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "constructor"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        }
+      ],
+      "name": "Approval",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "previousOwner",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "newOwner",
+          "type": "address"
+        }
+      ],
+      "name": "OwnershipTransferred",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "bytes32",
+          "name": "role",
+          "type": "bytes32"
+        },
+        {
+          "indexed": true,
+          "internalType": "bytes32",
+          "name": "previousAdminRole",
+          "type": "bytes32"
+        },
+        {
+          "indexed": true,
+          "internalType": "bytes32",
+          "name": "newAdminRole",
+          "type": "bytes32"
+        }
+      ],
+      "name": "RoleAdminChanged",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "bytes32",
+          "name": "role",
+          "type": "bytes32"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "sender",
+          "type": "address"
+        }
+      ],
+      "name": "RoleGranted",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "bytes32",
+          "name": "role",
+          "type": "bytes32"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "sender",
+          "type": "address"
+        }
+      ],
+      "name": "RoleRevoked",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "from",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "uint256",
+          "name": "value",
+          "type": "uint256"
+        }
+      ],
+      "name": "Transfer",
+      "type": "event"
+    },
+    {
+      "inputs": [],
+      "name": "DEFAULT_ADMIN_ROLE",
+      "outputs": [
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "MINTER_ROLE",
+      "outputs": [
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        }
+      ],
+      "name": "allowance",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "approve",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        }
+      ],
+      "name": "balanceOf",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "decimals",
+      "outputs": [
+        {
+          "internalType": "uint8",
+          "name": "",
+          "type": "uint8"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "subtractedValue",
+          "type": "uint256"
+        }
+      ],
+      "name": "decreaseAllowance",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "role",
+          "type": "bytes32"
+        }
+      ],
+      "name": "getRoleAdmin",
+      "outputs": [
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "role",
+          "type": "bytes32"
+        },
+        {
+          "internalType": "uint256",
+          "name": "index",
+          "type": "uint256"
+        }
+      ],
+      "name": "getRoleMember",
+      "outputs": [
+        {
+          "internalType": "address",
+          "name": "",
+          "type": "address"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "role",
+          "type": "bytes32"
+        }
+      ],
+      "name": "getRoleMemberCount",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "role",
+          "type": "bytes32"
+        },
+        {
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        }
+      ],
+      "name": "grantRole",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "role",
+          "type": "bytes32"
+        },
+        {
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        }
+      ],
+      "name": "hasRole",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "spender",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "addedValue",
+          "type": "uint256"
+        }
+      ],
+      "name": "increaseAllowance",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "name",
+      "outputs": [
+        {
+          "internalType": "string",
+          "name": "",
+          "type": "string"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "owner",
+      "outputs": [
+        {
+          "internalType": "address",
+          "name": "",
+          "type": "address"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "renounceOwnership",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "role",
+          "type": "bytes32"
+        },
+        {
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        }
+      ],
+      "name": "renounceRole",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "role",
+          "type": "bytes32"
+        },
+        {
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        }
+      ],
+      "name": "revokeRole",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "symbol",
+      "outputs": [
+        {
+          "internalType": "string",
+          "name": "",
+          "type": "string"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "totalSupply",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "recipient",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "transfer",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "sender",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "recipient",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "amount",
+          "type": "uint256"
+        }
+      ],
+      "name": "transferFrom",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "newOwner",
+          "type": "address"
+        }
+      ],
+      "name": "transferOwnership",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    }
+  ],
+  "bytecode": "0x60806040523480156200001157600080fd5b50604051620019f7380380620019f7833981810160405260608110156200003757600080fd5b81019080805160405193929190846401000000008211156200005857600080fd5b9083019060208201858111156200006e57600080fd5b82516401000000008111828201881017156200008957600080fd5b82525081516020918201929091019080838360005b83811015620000b85781810151838201526020016200009e565b50505050905090810190601f168015620000e65780820380516001836020036101000a031916815260200191505b50604052602001805160405193929190846401000000008211156200010a57600080fd5b9083019060208201858111156200012057600080fd5b82516401000000008111828201881017156200013b57600080fd5b82525081516020918201929091019080838360005b838110156200016a57818101518382015260200162000150565b50505050905090810190601f168015620001985780820380516001836020036101000a031916815260200191505b5060405260209081015185519093508592508491620001bd91600391850190620004a9565b508051620001d3906004906020840190620004a9565b50506005805460ff19166012179055506000620001ef62000279565b600780546001600160a01b0319166001600160a01b03831690811790915560405191925090600090600080516020620019d7833981519152908290a35062000237816200027d565b6200024460008262000388565b620002707f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a68262000388565b50505062000555565b3390565b6200028762000279565b6001600160a01b03166200029a62000398565b6001600160a01b031614620002f6576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b6001600160a01b0381166200033d5760405162461bcd60e51b8152600401808060200182810382526026815260200180620019b16026913960400191505060405180910390fd5b6007546040516001600160a01b03808416921690600080516020620019d783398151915290600090a3600780546001600160a01b0319166001600160a01b0392909216919091179055565b620003948282620003a7565b5050565b6007546001600160a01b031690565b6000828152600660209081526040909120620003ce91839062000b1d62000422821b17901c565b156200039457620003de62000279565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b600062000439836001600160a01b03841662000442565b90505b92915050565b600062000450838362000491565b62000488575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556200043c565b5060006200043c565b60009081526001919091016020526040902054151590565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282620004e157600085556200052c565b82601f10620004fc57805160ff19168380011785556200052c565b828001600101855582156200052c579182015b828111156200052c5782518255916020019190600101906200050f565b506200053a9291506200053e565b5090565b5b808211156200053a57600081556001016200053f565b61144c80620005656000396000f3fe608060405234801561001057600080fd5b506004361061014d5760003560e01c80638da5cb5b116100c3578063a9059cbb1161007c578063a9059cbb146103fd578063ca15c87314610429578063d539139314610446578063d547741f1461044e578063dd62ed3e1461047a578063f2fde38b146104a85761014d565b80638da5cb5b1461034e5780639010d07c1461037257806391d148541461039557806395d89b41146103c1578063a217fddf146103c9578063a457c2d7146103d15761014d565b80632f2ff15d116101155780632f2ff15d1461027c578063313ce567146102aa57806336568abe146102c857806339509351146102f457806370a0823114610320578063715018a6146103465761014d565b806306fdde0314610152578063095ea7b3146101cf57806318160ddd1461020f57806323b872dd14610229578063248a9ca31461025f575b600080fd5b61015a6104ce565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561019457818101518382015260200161017c565b50505050905090810190601f1680156101c15780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101fb600480360360408110156101e557600080fd5b506001600160a01b038135169060200135610564565b604080519115158252519081900360200190f35b610217610582565b60408051918252519081900360200190f35b6101fb6004803603606081101561023f57600080fd5b506001600160a01b03813581169160208101359091169060400135610588565b6102176004803603602081101561027557600080fd5b503561060f565b6102a86004803603604081101561029257600080fd5b50803590602001356001600160a01b0316610624565b005b6102b2610690565b6040805160ff9092168252519081900360200190f35b6102a8600480360360408110156102de57600080fd5b50803590602001356001600160a01b0316610699565b6101fb6004803603604081101561030a57600080fd5b506001600160a01b0381351690602001356106fa565b6102176004803603602081101561033657600080fd5b50356001600160a01b0316610748565b6102a8610763565b610356610821565b604080516001600160a01b039092168252519081900360200190f35b6103566004803603604081101561038857600080fd5b5080359060200135610830565b6101fb600480360360408110156103ab57600080fd5b50803590602001356001600160a01b031661084f565b61015a610867565b6102176108c8565b6101fb600480360360408110156103e757600080fd5b506001600160a01b0381351690602001356108cd565b6101fb6004803603604081101561041357600080fd5b506001600160a01b038135169060200135610935565b6102176004803603602081101561043f57600080fd5b5035610949565b610217610960565b6102a86004803603604081101561046457600080fd5b50803590602001356001600160a01b0316610984565b6102176004803603604081101561049057600080fd5b506001600160a01b03813581169160200135166109dd565b6102a8600480360360208110156104be57600080fd5b50356001600160a01b0316610a08565b60038054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526060939092909183018282801561055a5780601f1061052f5761010080835404028352916020019161055a565b820191906000526020600020905b81548152906001019060200180831161053d57829003601f168201915b5050505050905090565b6000610578610571610b32565b8484610b36565b5060015b92915050565b60025490565b6000610595848484610c22565b610605846105a1610b32565b61060085604051806060016040528060288152602001611352602891396001600160a01b038a166000908152600160205260408120906105df610b32565b6001600160a01b031681526020810191909152604001600020549190610d7d565b610b36565b5060019392505050565b60009081526006602052604090206002015490565b60008281526006602052604090206002015461064790610642610b32565b61084f565b6106825760405162461bcd60e51b815260040180806020018281038252602f815260200180611285602f913960400191505060405180910390fd5b61068c8282610e14565b5050565b60055460ff1690565b6106a1610b32565b6001600160a01b0316816001600160a01b0316146106f05760405162461bcd60e51b815260040180806020018281038252602f8152602001806113e8602f913960400191505060405180910390fd5b61068c8282610e7d565b6000610578610707610b32565b846106008560016000610718610b32565b6001600160a01b03908116825260208083019390935260409182016000908120918c168152925290205490610ee6565b6001600160a01b031660009081526020819052604090205490565b61076b610b32565b6001600160a01b031661077c610821565b6001600160a01b0316146107d7576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b6007546040516000916001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600780546001600160a01b0319169055565b6007546001600160a01b031690565b60008281526006602052604081206108489083610f40565b9392505050565b60008281526006602052604081206108489083610f4c565b60048054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526060939092909183018282801561055a5780601f1061052f5761010080835404028352916020019161055a565b600081565b60006105786108da610b32565b84610600856040518060600160405280602581526020016113c36025913960016000610904610b32565b6001600160a01b03908116825260208083019390935260409182016000908120918d16815292529020549190610d7d565b6000610578610942610b32565b8484610c22565b600081815260066020526040812061057c90610f61565b7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a681565b6000828152600660205260409020600201546109a290610642610b32565b6106f05760405162461bcd60e51b81526004018080602001828103825260308152602001806113226030913960400191505060405180910390fd5b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b610a10610b32565b6001600160a01b0316610a21610821565b6001600160a01b031614610a7c576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b6001600160a01b038116610ac15760405162461bcd60e51b81526004018080602001828103825260268152602001806112b46026913960400191505060405180910390fd5b6007546040516001600160a01b038084169216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3600780546001600160a01b0319166001600160a01b0392909216919091179055565b6000610848836001600160a01b038416610f6c565b3390565b6001600160a01b038316610b7b5760405162461bcd60e51b815260040180806020018281038252602481526020018061139f6024913960400191505060405180910390fd5b6001600160a01b038216610bc05760405162461bcd60e51b81526004018080602001828103825260228152602001806112da6022913960400191505060405180910390fd5b6001600160a01b03808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b038316610c675760405162461bcd60e51b815260040180806020018281038252602581526020018061137a6025913960400191505060405180910390fd5b6001600160a01b038216610cac5760405162461bcd60e51b81526004018080602001828103825260238152602001806112626023913960400191505060405180910390fd5b610cb7838383610fb6565b610cf4816040518060600160405280602681526020016112fc602691396001600160a01b0386166000908152602081905260409020549190610d7d565b6001600160a01b038085166000908152602081905260408082209390935590841681522054610d239082610ee6565b6001600160a01b038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b60008184841115610e0c5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015610dd1578181015183820152602001610db9565b50505050905090810190601f168015610dfe5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b6000828152600660205260409020610e2c9082610b1d565b1561068c57610e39610b32565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000828152600660205260409020610e959082610ff4565b1561068c57610ea2610b32565b6001600160a01b0316816001600160a01b0316837ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b60405160405180910390a45050565b600082820183811015610848576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b60006108488383611009565b6000610848836001600160a01b03841661106d565b600061057c82611085565b6000610f78838361106d565b610fae5750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915561057c565b50600061057c565b610fe07f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a68461084f565b15610fef57610fef8382611089565b505050565b6000610848836001600160a01b038416611179565b8154600090821061104b5760405162461bcd60e51b81526004018080602001828103825260228152602001806112406022913960400191505060405180910390fd5b82600001828154811061105a57fe5b9060005260206000200154905092915050565b60009081526001919091016020526040902054151590565b5490565b6001600160a01b0382166110e4576040805162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b6110f060008383610fb6565b6002546110fd9082610ee6565b6002556001600160a01b0382166000908152602081905260409020546111239082610ee6565b6001600160a01b0383166000818152602081815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b6000818152600183016020526040812054801561123557835460001980830191908101906000908790839081106111ac57fe5b90600052602060002001549050808760000184815481106111c957fe5b6000918252602080832090910192909255828152600189810190925260409020908401905586548790806111f957fe5b6001900381819060005260206000200160009055905586600101600087815260200190815260200160002060009055600194505050505061057c565b600091505061057c56fe456e756d657261626c655365743a20696e646578206f7574206f6620626f756e647345524332303a207472616e7366657220746f20746865207a65726f2061646472657373416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f206772616e744f776e61626c653a206e6577206f776e657220697320746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e6365416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f207265766f6b6545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636520726f6c657320666f722073656c66a2646970667358221220339e6347a85131212ad2bbc765e33c8918a3dd7d9e29b1fed5ada0541b2f1c2764736f6c634300070600334f776e61626c653a206e6577206f776e657220697320746865207a65726f20616464726573738be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0",
+  "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061014d5760003560e01c80638da5cb5b116100c3578063a9059cbb1161007c578063a9059cbb146103fd578063ca15c87314610429578063d539139314610446578063d547741f1461044e578063dd62ed3e1461047a578063f2fde38b146104a85761014d565b80638da5cb5b1461034e5780639010d07c1461037257806391d148541461039557806395d89b41146103c1578063a217fddf146103c9578063a457c2d7146103d15761014d565b80632f2ff15d116101155780632f2ff15d1461027c578063313ce567146102aa57806336568abe146102c857806339509351146102f457806370a0823114610320578063715018a6146103465761014d565b806306fdde0314610152578063095ea7b3146101cf57806318160ddd1461020f57806323b872dd14610229578063248a9ca31461025f575b600080fd5b61015a6104ce565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561019457818101518382015260200161017c565b50505050905090810190601f1680156101c15780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101fb600480360360408110156101e557600080fd5b506001600160a01b038135169060200135610564565b604080519115158252519081900360200190f35b610217610582565b60408051918252519081900360200190f35b6101fb6004803603606081101561023f57600080fd5b506001600160a01b03813581169160208101359091169060400135610588565b6102176004803603602081101561027557600080fd5b503561060f565b6102a86004803603604081101561029257600080fd5b50803590602001356001600160a01b0316610624565b005b6102b2610690565b6040805160ff9092168252519081900360200190f35b6102a8600480360360408110156102de57600080fd5b50803590602001356001600160a01b0316610699565b6101fb6004803603604081101561030a57600080fd5b506001600160a01b0381351690602001356106fa565b6102176004803603602081101561033657600080fd5b50356001600160a01b0316610748565b6102a8610763565b610356610821565b604080516001600160a01b039092168252519081900360200190f35b6103566004803603604081101561038857600080fd5b5080359060200135610830565b6101fb600480360360408110156103ab57600080fd5b50803590602001356001600160a01b031661084f565b61015a610867565b6102176108c8565b6101fb600480360360408110156103e757600080fd5b506001600160a01b0381351690602001356108cd565b6101fb6004803603604081101561041357600080fd5b506001600160a01b038135169060200135610935565b6102176004803603602081101561043f57600080fd5b5035610949565b610217610960565b6102a86004803603604081101561046457600080fd5b50803590602001356001600160a01b0316610984565b6102176004803603604081101561049057600080fd5b506001600160a01b03813581169160200135166109dd565b6102a8600480360360208110156104be57600080fd5b50356001600160a01b0316610a08565b60038054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526060939092909183018282801561055a5780601f1061052f5761010080835404028352916020019161055a565b820191906000526020600020905b81548152906001019060200180831161053d57829003601f168201915b5050505050905090565b6000610578610571610b32565b8484610b36565b5060015b92915050565b60025490565b6000610595848484610c22565b610605846105a1610b32565b61060085604051806060016040528060288152602001611352602891396001600160a01b038a166000908152600160205260408120906105df610b32565b6001600160a01b031681526020810191909152604001600020549190610d7d565b610b36565b5060019392505050565b60009081526006602052604090206002015490565b60008281526006602052604090206002015461064790610642610b32565b61084f565b6106825760405162461bcd60e51b815260040180806020018281038252602f815260200180611285602f913960400191505060405180910390fd5b61068c8282610e14565b5050565b60055460ff1690565b6106a1610b32565b6001600160a01b0316816001600160a01b0316146106f05760405162461bcd60e51b815260040180806020018281038252602f8152602001806113e8602f913960400191505060405180910390fd5b61068c8282610e7d565b6000610578610707610b32565b846106008560016000610718610b32565b6001600160a01b03908116825260208083019390935260409182016000908120918c168152925290205490610ee6565b6001600160a01b031660009081526020819052604090205490565b61076b610b32565b6001600160a01b031661077c610821565b6001600160a01b0316146107d7576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b6007546040516000916001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600780546001600160a01b0319169055565b6007546001600160a01b031690565b60008281526006602052604081206108489083610f40565b9392505050565b60008281526006602052604081206108489083610f4c565b60048054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526060939092909183018282801561055a5780601f1061052f5761010080835404028352916020019161055a565b600081565b60006105786108da610b32565b84610600856040518060600160405280602581526020016113c36025913960016000610904610b32565b6001600160a01b03908116825260208083019390935260409182016000908120918d16815292529020549190610d7d565b6000610578610942610b32565b8484610c22565b600081815260066020526040812061057c90610f61565b7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a681565b6000828152600660205260409020600201546109a290610642610b32565b6106f05760405162461bcd60e51b81526004018080602001828103825260308152602001806113226030913960400191505060405180910390fd5b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b610a10610b32565b6001600160a01b0316610a21610821565b6001600160a01b031614610a7c576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b6001600160a01b038116610ac15760405162461bcd60e51b81526004018080602001828103825260268152602001806112b46026913960400191505060405180910390fd5b6007546040516001600160a01b038084169216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3600780546001600160a01b0319166001600160a01b0392909216919091179055565b6000610848836001600160a01b038416610f6c565b3390565b6001600160a01b038316610b7b5760405162461bcd60e51b815260040180806020018281038252602481526020018061139f6024913960400191505060405180910390fd5b6001600160a01b038216610bc05760405162461bcd60e51b81526004018080602001828103825260228152602001806112da6022913960400191505060405180910390fd5b6001600160a01b03808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b038316610c675760405162461bcd60e51b815260040180806020018281038252602581526020018061137a6025913960400191505060405180910390fd5b6001600160a01b038216610cac5760405162461bcd60e51b81526004018080602001828103825260238152602001806112626023913960400191505060405180910390fd5b610cb7838383610fb6565b610cf4816040518060600160405280602681526020016112fc602691396001600160a01b0386166000908152602081905260409020549190610d7d565b6001600160a01b038085166000908152602081905260408082209390935590841681522054610d239082610ee6565b6001600160a01b038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b60008184841115610e0c5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015610dd1578181015183820152602001610db9565b50505050905090810190601f168015610dfe5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b6000828152600660205260409020610e2c9082610b1d565b1561068c57610e39610b32565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000828152600660205260409020610e959082610ff4565b1561068c57610ea2610b32565b6001600160a01b0316816001600160a01b0316837ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b60405160405180910390a45050565b600082820183811015610848576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b60006108488383611009565b6000610848836001600160a01b03841661106d565b600061057c82611085565b6000610f78838361106d565b610fae5750815460018181018455600084815260208082209093018490558454848252828601909352604090209190915561057c565b50600061057c565b610fe07f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a68461084f565b15610fef57610fef8382611089565b505050565b6000610848836001600160a01b038416611179565b8154600090821061104b5760405162461bcd60e51b81526004018080602001828103825260228152602001806112406022913960400191505060405180910390fd5b82600001828154811061105a57fe5b9060005260206000200154905092915050565b60009081526001919091016020526040902054151590565b5490565b6001600160a01b0382166110e4576040805162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b6110f060008383610fb6565b6002546110fd9082610ee6565b6002556001600160a01b0382166000908152602081905260409020546111239082610ee6565b6001600160a01b0383166000818152602081815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b6000818152600183016020526040812054801561123557835460001980830191908101906000908790839081106111ac57fe5b90600052602060002001549050808760000184815481106111c957fe5b6000918252602080832090910192909255828152600189810190925260409020908401905586548790806111f957fe5b6001900381819060005260206000200160009055905586600101600087815260200190815260200160002060009055600194505050505061057c565b600091505061057c56fe456e756d657261626c655365743a20696e646578206f7574206f6620626f756e647345524332303a207472616e7366657220746f20746865207a65726f2061646472657373416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f206772616e744f776e61626c653a206e6577206f776e657220697320746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e6365416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f207265766f6b6545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636520726f6c657320666f722073656c66a2646970667358221220339e6347a85131212ad2bbc765e33c8918a3dd7d9e29b1fed5ada0541b2f1c2764736f6c63430007060033",
+  "linkReferences": {},
+  "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/THXERC721.json b/apps/api/src/app/hardhat/export/THXERC721.json
new file mode 100644
index 000000000..d2c1e7a57
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/THXERC721.json
@@ -0,0 +1,753 @@
+{
+  "_format": "hh-sol-artifact-1",
+  "contractName": "THX_ERC721",
+  "sourceName": "contracts/utils/ERC721/THX_ERC721.sol",
+  "abi": [
+    {
+      "inputs": [
+        {
+          "internalType": "string",
+          "name": "name_",
+          "type": "string"
+        },
+        {
+          "internalType": "string",
+          "name": "symbol_",
+          "type": "string"
+        },
+        {
+          "internalType": "string",
+          "name": "baseURI_",
+          "type": "string"
+        },
+        {
+          "internalType": "address",
+          "name": "owner_",
+          "type": "address"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "constructor"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "approved",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "uint256",
+          "name": "tokenId",
+          "type": "uint256"
+        }
+      ],
+      "name": "Approval",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "operator",
+          "type": "address"
+        },
+        {
+          "indexed": false,
+          "internalType": "bool",
+          "name": "approved",
+          "type": "bool"
+        }
+      ],
+      "name": "ApprovalForAll",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "previousOwner",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "newOwner",
+          "type": "address"
+        }
+      ],
+      "name": "OwnershipTransferred",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "bytes32",
+          "name": "role",
+          "type": "bytes32"
+        },
+        {
+          "indexed": true,
+          "internalType": "bytes32",
+          "name": "previousAdminRole",
+          "type": "bytes32"
+        },
+        {
+          "indexed": true,
+          "internalType": "bytes32",
+          "name": "newAdminRole",
+          "type": "bytes32"
+        }
+      ],
+      "name": "RoleAdminChanged",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "bytes32",
+          "name": "role",
+          "type": "bytes32"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "sender",
+          "type": "address"
+        }
+      ],
+      "name": "RoleGranted",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "bytes32",
+          "name": "role",
+          "type": "bytes32"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "sender",
+          "type": "address"
+        }
+      ],
+      "name": "RoleRevoked",
+      "type": "event"
+    },
+    {
+      "anonymous": false,
+      "inputs": [
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "from",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "indexed": true,
+          "internalType": "uint256",
+          "name": "tokenId",
+          "type": "uint256"
+        }
+      ],
+      "name": "Transfer",
+      "type": "event"
+    },
+    {
+      "inputs": [],
+      "name": "DEFAULT_ADMIN_ROLE",
+      "outputs": [
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "MINTER_ROLE",
+      "outputs": [
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "tokenId",
+          "type": "uint256"
+        }
+      ],
+      "name": "approve",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        }
+      ],
+      "name": "balanceOf",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "baseURI",
+      "outputs": [
+        {
+          "internalType": "string",
+          "name": "",
+          "type": "string"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint256",
+          "name": "tokenId",
+          "type": "uint256"
+        }
+      ],
+      "name": "getApproved",
+      "outputs": [
+        {
+          "internalType": "address",
+          "name": "",
+          "type": "address"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "role",
+          "type": "bytes32"
+        }
+      ],
+      "name": "getRoleAdmin",
+      "outputs": [
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "role",
+          "type": "bytes32"
+        },
+        {
+          "internalType": "uint256",
+          "name": "index",
+          "type": "uint256"
+        }
+      ],
+      "name": "getRoleMember",
+      "outputs": [
+        {
+          "internalType": "address",
+          "name": "",
+          "type": "address"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "role",
+          "type": "bytes32"
+        }
+      ],
+      "name": "getRoleMemberCount",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "role",
+          "type": "bytes32"
+        },
+        {
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        }
+      ],
+      "name": "grantRole",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "role",
+          "type": "bytes32"
+        },
+        {
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        }
+      ],
+      "name": "hasRole",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "operator",
+          "type": "address"
+        }
+      ],
+      "name": "isApprovedForAll",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "_recipient",
+          "type": "address"
+        },
+        {
+          "internalType": "string",
+          "name": "_tokenURI",
+          "type": "string"
+        }
+      ],
+      "name": "mint",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "name",
+      "outputs": [
+        {
+          "internalType": "string",
+          "name": "",
+          "type": "string"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "owner",
+      "outputs": [
+        {
+          "internalType": "address",
+          "name": "",
+          "type": "address"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint256",
+          "name": "tokenId",
+          "type": "uint256"
+        }
+      ],
+      "name": "ownerOf",
+      "outputs": [
+        {
+          "internalType": "address",
+          "name": "",
+          "type": "address"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "renounceOwnership",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "role",
+          "type": "bytes32"
+        },
+        {
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        }
+      ],
+      "name": "renounceRole",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "role",
+          "type": "bytes32"
+        },
+        {
+          "internalType": "address",
+          "name": "account",
+          "type": "address"
+        }
+      ],
+      "name": "revokeRole",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "from",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "tokenId",
+          "type": "uint256"
+        }
+      ],
+      "name": "safeTransferFrom",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "from",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "tokenId",
+          "type": "uint256"
+        },
+        {
+          "internalType": "bytes",
+          "name": "_data",
+          "type": "bytes"
+        }
+      ],
+      "name": "safeTransferFrom",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "operator",
+          "type": "address"
+        },
+        {
+          "internalType": "bool",
+          "name": "approved",
+          "type": "bool"
+        }
+      ],
+      "name": "setApprovalForAll",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes4",
+          "name": "interfaceId",
+          "type": "bytes4"
+        }
+      ],
+      "name": "supportsInterface",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "symbol",
+      "outputs": [
+        {
+          "internalType": "string",
+          "name": "",
+          "type": "string"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint256",
+          "name": "index",
+          "type": "uint256"
+        }
+      ],
+      "name": "tokenByIndex",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "owner",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "index",
+          "type": "uint256"
+        }
+      ],
+      "name": "tokenOfOwnerByIndex",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint256",
+          "name": "tokenId",
+          "type": "uint256"
+        }
+      ],
+      "name": "tokenURI",
+      "outputs": [
+        {
+          "internalType": "string",
+          "name": "",
+          "type": "string"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "totalSupply",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "from",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "to",
+          "type": "address"
+        },
+        {
+          "internalType": "uint256",
+          "name": "tokenId",
+          "type": "uint256"
+        }
+      ],
+      "name": "transferFrom",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "address",
+          "name": "newOwner",
+          "type": "address"
+        }
+      ],
+      "name": "transferOwnership",
+      "outputs": [],
+      "stateMutability": "nonpayable",
+      "type": "function"
+    }
+  ],
+  "bytecode": "0x60806040523480156200001157600080fd5b5060405162002bf438038062002bf4833981810160405260808110156200003757600080fd5b81019080805160405193929190846401000000008211156200005857600080fd5b9083019060208201858111156200006e57600080fd5b82516401000000008111828201881017156200008957600080fd5b82525081516020918201929091019080838360005b83811015620000b85781810151838201526020016200009e565b50505050905090810190601f168015620000e65780820380516001836020036101000a031916815260200191505b50604052602001805160405193929190846401000000008211156200010a57600080fd5b9083019060208201858111156200012057600080fd5b82516401000000008111828201881017156200013b57600080fd5b82525081516020918201929091019080838360005b838110156200016a57818101518382015260200162000150565b50505050905090810190601f168015620001985780820380516001836020036101000a031916815260200191505b5060405260200180516040519392919084640100000000821115620001bc57600080fd5b908301906020820185811115620001d257600080fd5b8251640100000000811182820188101715620001ed57600080fd5b82525081516020918201929091019080838360005b838110156200021c57818101518382015260200162000202565b50505050905090810190601f1680156200024a5780820380516001836020036101000a031916815260200191505b50604052602001519150849050836200026a6301ffc9a760e01b62000371565b81516200027f9060069060208501906200063b565b508051620002959060079060208401906200063b565b50620002a86380ac58cd60e01b62000371565b620002ba635b5e139f60e01b62000371565b620002cc63780e9d6360e01b62000371565b5060009050620002db620003f6565b600b80546001600160a01b0319166001600160a01b0383169081179091556040519192509060009060008051602062002bd4833981519152908290a3506200032381620003fa565b6200033060008262000505565b6200035c7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a68262000505565b620003678262000515565b50505050620006e7565b6001600160e01b03198082161415620003d1576040805162461bcd60e51b815260206004820152601c60248201527f4552433136353a20696e76616c696420696e7465726661636520696400000000604482015290519081900360640190fd5b6001600160e01b0319166000908152602081905260409020805460ff19166001179055565b3390565b62000404620003f6565b6001600160a01b0316620004176200052a565b6001600160a01b03161462000473576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b6001600160a01b038116620004ba5760405162461bcd60e51b815260040180806020018281038252602681526020018062002bae6026913960400191505060405180910390fd5b600b546040516001600160a01b0380841692169060008051602062002bd483398151915290600090a3600b80546001600160a01b0319166001600160a01b0392909216919091179055565b62000511828262000539565b5050565b8051620005119060099060208401906200063b565b600b546001600160a01b031690565b6000828152600a6020908152604090912062000560918390620013a9620005b4821b17901c565b15620005115762000570620003f6565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000620005cb836001600160a01b038416620005d4565b90505b92915050565b6000620005e2838362000623565b6200061a57508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155620005ce565b506000620005ce565b60009081526001919091016020526040902054151590565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282620006735760008555620006be565b82601f106200068e57805160ff1916838001178555620006be565b82800160010185558215620006be579182015b82811115620006be578251825591602001919060010190620006a1565b50620006cc929150620006d0565b5090565b5b80821115620006cc5760008155600101620006d1565b6124b780620006f76000396000f3fe608060405234801561001057600080fd5b50600436106101da5760003560e01c8063715018a611610104578063b88d4fde116100a2578063d539139311610071578063d5391393146106e0578063d547741f146106e8578063e985e9c514610714578063f2fde38b14610742576101da565b8063b88d4fde1461052a578063c87b56dd146105f0578063ca15c8731461060d578063d0def5211461062a576101da565b806391d14854116100de57806391d14854146104c057806395d89b41146104ec578063a217fddf146104f4578063a22cb465146104fc576101da565b8063715018a61461048d5780638da5cb5b146104955780639010d07c1461049d576101da565b80632f2ff15d1161017c5780634f6ccce71161014b5780634f6ccce7146104255780636352211e146104425780636c0360eb1461045f57806370a0823114610467576101da565b80632f2ff15d1461036b5780632f745c591461039757806336568abe146103c357806342842e0e146103ef576101da565b8063095ea7b3116101b8578063095ea7b3146102d057806318160ddd146102fe57806323b872dd14610318578063248a9ca31461034e576101da565b806301ffc9a7146101df57806306fdde031461021a578063081812fc14610297575b600080fd5b610206600480360360208110156101f557600080fd5b50356001600160e01b031916610768565b604080519115158252519081900360200190f35b61022261078b565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561025c578181015183820152602001610244565b50505050905090810190601f1680156102895780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102b4600480360360208110156102ad57600080fd5b5035610821565b604080516001600160a01b039092168252519081900360200190f35b6102fc600480360360408110156102e657600080fd5b506001600160a01b038135169060200135610883565b005b61030661095e565b60408051918252519081900360200190f35b6102fc6004803603606081101561032e57600080fd5b506001600160a01b0381358116916020810135909116906040013561096f565b6103066004803603602081101561036457600080fd5b50356109c6565b6102fc6004803603604081101561038157600080fd5b50803590602001356001600160a01b03166109db565b610306600480360360408110156103ad57600080fd5b506001600160a01b038135169060200135610a47565b6102fc600480360360408110156103d957600080fd5b50803590602001356001600160a01b0316610a72565b6102fc6004803603606081101561040557600080fd5b506001600160a01b03813581169160208101359091169060400135610ad3565b6103066004803603602081101561043b57600080fd5b5035610aee565b6102b46004803603602081101561045857600080fd5b5035610b04565b610222610b2c565b6103066004803603602081101561047d57600080fd5b50356001600160a01b0316610b8d565b6102fc610bf5565b6102b4610cb3565b6102b4600480360360408110156104b357600080fd5b5080359060200135610cc2565b610206600480360360408110156104d657600080fd5b50803590602001356001600160a01b0316610cda565b610222610cf2565b610306610d53565b6102fc6004803603604081101561051257600080fd5b506001600160a01b0381351690602001351515610d58565b6102fc6004803603608081101561054057600080fd5b6001600160a01b0382358116926020810135909116916040820135919081019060808101606082013564010000000081111561057b57600080fd5b82018360208201111561058d57600080fd5b803590602001918460018302840111640100000000831117156105af57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610e5d945050505050565b6102226004803603602081101561060657600080fd5b5035610ebb565b6103066004803603602081101561062357600080fd5b503561113c565b6103066004803603604081101561064057600080fd5b6001600160a01b03823516919081019060408101602082013564010000000081111561066b57600080fd5b82018360208201111561067d57600080fd5b8035906020019184600183028401116401000000008311171561069f57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611153945050505050565b6103066111e9565b6102fc600480360360408110156106fe57600080fd5b50803590602001356001600160a01b031661120d565b6102066004803603604081101561072a57600080fd5b506001600160a01b0381358116916020013516611266565b6102fc6004803603602081101561075857600080fd5b50356001600160a01b0316611294565b6001600160e01b0319811660009081526020819052604090205460ff165b919050565b60068054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156108175780601f106107ec57610100808354040283529160200191610817565b820191906000526020600020905b8154815290600101906020018083116107fa57829003601f168201915b5050505050905090565b600061082c826113be565b6108675760405162461bcd60e51b815260040180806020018281038252602c815260200180612351602c913960400191505060405180910390fd5b506000908152600460205260409020546001600160a01b031690565b600061088e82610b04565b9050806001600160a01b0316836001600160a01b031614156108e15760405162461bcd60e51b81526004018080602001828103825260218152602001806124016021913960400191505060405180910390fd5b806001600160a01b03166108f36113cb565b6001600160a01b0316148061091457506109148161090f6113cb565b611266565b61094f5760405162461bcd60e51b81526004018080602001828103825260388152602001806122a46038913960400191505060405180910390fd5b61095983836113cf565b505050565b600061096a600261143d565b905090565b61098061097a6113cb565b82611448565b6109bb5760405162461bcd60e51b81526004018080602001828103825260318152602001806124226031913960400191505060405180910390fd5b6109598383836114ec565b6000908152600a602052604090206002015490565b6000828152600a60205260409020600201546109fe906109f96113cb565b610cda565b610a395760405162461bcd60e51b815260040180806020018281038252602f81526020018061219d602f913960400191505060405180910390fd5b610a438282611638565b5050565b6001600160a01b0382166000908152600160205260408120610a6990836116a1565b90505b92915050565b610a7a6113cb565b6001600160a01b0316816001600160a01b031614610ac95760405162461bcd60e51b815260040180806020018281038252602f815260200180612453602f913960400191505060405180910390fd5b610a4382826116ad565b61095983838360405180602001604052806000815250610e5d565b600080610afc600284611716565b509392505050565b6000610a6c826040518060600160405280602981526020016123066029913960029190611732565b60098054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156108175780601f106107ec57610100808354040283529160200191610817565b60006001600160a01b038216610bd45760405162461bcd60e51b815260040180806020018281038252602a8152602001806122dc602a913960400191505060405180910390fd5b6001600160a01b0382166000908152600160205260409020610a6c9061143d565b610bfd6113cb565b6001600160a01b0316610c0e610cb3565b6001600160a01b031614610c69576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b600b546040516000916001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600b80546001600160a01b0319169055565b600b546001600160a01b031690565b6000828152600a60205260408120610a6990836116a1565b6000828152600a60205260408120610a699083611749565b60078054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156108175780601f106107ec57610100808354040283529160200191610817565b600081565b610d606113cb565b6001600160a01b0316826001600160a01b03161415610dc6576040805162461bcd60e51b815260206004820152601960248201527f4552433732313a20617070726f766520746f2063616c6c657200000000000000604482015290519081900360640190fd5b8060056000610dd36113cb565b6001600160a01b03908116825260208083019390935260409182016000908120918716808252919093529120805460ff191692151592909217909155610e176113cb565b6001600160a01b03167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c318360405180821515815260200191505060405180910390a35050565b610e6e610e686113cb565b83611448565b610ea95760405162461bcd60e51b81526004018080602001828103825260318152602001806124226031913960400191505060405180910390fd5b610eb58484848461175e565b50505050565b6060610ec6826113be565b610f015760405162461bcd60e51b815260040180806020018281038252602f8152602001806123d2602f913960400191505060405180910390fd5b60008281526008602090815260408083208054825160026001831615610100026000190190921691909104601f810185900485028201850190935282815292909190830182828015610f945780601f10610f6957610100808354040283529160200191610f94565b820191906000526020600020905b815481529060010190602001808311610f7757829003601f168201915b505050505090506000610fa5610b2c565b9050805160001415610fb957509050610786565b81511561107a5780826040516020018083805190602001908083835b60208310610ff45780518252601f199092019160209182019101610fd5565b51815160209384036101000a600019018019909216911617905285519190930192850191508083835b6020831061103c5780518252601f19909201916020918201910161101d565b6001836020036101000a0380198251168184511680821785525050505050509050019250505060405160208183030381529060405292505050610786565b80611084856117b0565b6040516020018083805190602001908083835b602083106110b65780518252601f199092019160209182019101611097565b51815160209384036101000a600019018019909216911617905285519190930192850191508083835b602083106110fe5780518252601f1990920191602091820191016110df565b6001836020036101000a0380198251168184511680821785525050505050509050019250505060405160208183030381529060405292505050919050565b6000818152600a60205260408120610a6c9061143d565b600061117f7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a633610cda565b6111bd576040805162461bcd60e51b815260206004820152600a6024820152692727aa2fa6a4a72a22a960b11b604482015290519081900360640190fd5b6111c7600c61188b565b60006111d3600c611894565b90506111df8482611898565b610a6981846119c6565b7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a681565b6000828152600a602052604090206002015461122b906109f96113cb565b610ac95760405162461bcd60e51b81526004018080602001828103825260308152602001806122746030913960400191505060405180910390fd5b6001600160a01b03918216600090815260056020908152604080832093909416825291909152205460ff1690565b61129c6113cb565b6001600160a01b03166112ad610cb3565b6001600160a01b031614611308576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b6001600160a01b03811661134d5760405162461bcd60e51b81526004018080602001828103825260268152602001806121fe6026913960400191505060405180910390fd5b600b546040516001600160a01b038084169216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3600b80546001600160a01b0319166001600160a01b0392909216919091179055565b6000610a69836001600160a01b038416611a29565b6000610a6c600283611a73565b3390565b600081815260046020526040902080546001600160a01b0319166001600160a01b038416908117909155819061140482610b04565b6001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b6000610a6c82611894565b6000611453826113be565b61148e5760405162461bcd60e51b815260040180806020018281038252602c815260200180612248602c913960400191505060405180910390fd5b600061149983610b04565b9050806001600160a01b0316846001600160a01b031614806114d45750836001600160a01b03166114c984610821565b6001600160a01b0316145b806114e457506114e48185611266565b949350505050565b826001600160a01b03166114ff82610b04565b6001600160a01b0316146115445760405162461bcd60e51b81526004018080602001828103825260298152602001806123a96029913960400191505060405180910390fd5b6001600160a01b0382166115895760405162461bcd60e51b81526004018080602001828103825260248152602001806122246024913960400191505060405180910390fd5b611594838383610959565b61159f6000826113cf565b6001600160a01b03831660009081526001602052604090206115c19082611a7f565b506001600160a01b03821660009081526001602052604090206115e49082611a8b565b506115f160028284611a97565b5080826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4505050565b6000828152600a6020526040902061165090826113a9565b15610a435761165d6113cb565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000610a698383611aad565b6000828152600a602052604090206116c59082611b11565b15610a43576116d26113cb565b6001600160a01b0316816001600160a01b0316837ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b60405160405180910390a45050565b60008080806117258686611b26565b9097909650945050505050565b600061173f848484611ba1565b90505b9392505050565b6000610a69836001600160a01b038416611c6b565b6117698484846114ec565b61177584848484611c83565b610eb55760405162461bcd60e51b81526004018080602001828103825260328152602001806121cc6032913960400191505060405180910390fd5b6060816117d557506040805180820190915260018152600360fc1b6020820152610786565b8160005b81156117ed57600101600a820491506117d9565b60008167ffffffffffffffff8111801561180657600080fd5b506040519080825280601f01601f191660200182016040528015611831576020820181803683370190505b50859350905060001982015b831561188257600a840660300160f81b8282806001900393508151811061186057fe5b60200101906001600160f81b031916908160001a905350600a8404935061183d565b50949350505050565b80546001019055565b5490565b6001600160a01b0382166118f3576040805162461bcd60e51b815260206004820181905260248201527f4552433732313a206d696e7420746f20746865207a65726f2061646472657373604482015290519081900360640190fd5b6118fc816113be565b1561194e576040805162461bcd60e51b815260206004820152601c60248201527f4552433732313a20746f6b656e20616c7265616479206d696e74656400000000604482015290519081900360640190fd5b61195a60008383610959565b6001600160a01b038216600090815260016020526040902061197c9082611a8b565b5061198960028284611a97565b5060405181906001600160a01b038416906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a45050565b6119cf826113be565b611a0a5760405162461bcd60e51b815260040180806020018281038252602c81526020018061237d602c913960400191505060405180910390fd5b60008281526008602090815260409091208251610959928401906120d9565b6000611a358383611c6b565b611a6b57508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610a6c565b506000610a6c565b6000610a698383611c6b565b6000610a698383611deb565b6000610a698383611a29565b600061173f84846001600160a01b038516611eb1565b81546000908210611aef5760405162461bcd60e51b815260040180806020018281038252602281526020018061217b6022913960400191505060405180910390fd5b826000018281548110611afe57fe5b9060005260206000200154905092915050565b6000610a69836001600160a01b038416611deb565b815460009081908310611b6a5760405162461bcd60e51b815260040180806020018281038252602281526020018061232f6022913960400191505060405180910390fd5b6000846000018481548110611b7b57fe5b906000526020600020906002020190508060000154816001015492509250509250929050565b60008281526001840160205260408120548281611c3c5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015611c01578181015183820152602001611be9565b50505050905090810190601f168015611c2e5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b50846000016001820381548110611c4f57fe5b9060005260206000209060020201600101549150509392505050565b60009081526001919091016020526040902054151590565b6000611c97846001600160a01b0316611f48565b611ca3575060016114e4565b6000611db1630a85bd0160e11b611cb86113cb565b88878760405160240180856001600160a01b03168152602001846001600160a01b0316815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015611d1f578181015183820152602001611d07565b50505050905090810190601f168015611d4c5780820380516001836020036101000a031916815260200191505b5095505050505050604051602081830303815290604052906001600160e01b0319166020820180516001600160e01b0383818316178352505050506040518060600160405280603281526020016121cc603291396001600160a01b0388169190611f4e565b90506000818060200190516020811015611dca57600080fd5b50516001600160e01b031916630a85bd0160e11b1492505050949350505050565b60008181526001830160205260408120548015611ea75783546000198083019190810190600090879083908110611e1e57fe5b9060005260206000200154905080876000018481548110611e3b57fe5b600091825260208083209091019290925582815260018981019092526040902090840190558654879080611e6b57fe5b60019003818190600052602060002001600090559055866001016000878152602001908152602001600020600090556001945050505050610a6c565b6000915050610a6c565b600082815260018401602052604081205480611f16575050604080518082018252838152602080820184815286546001818101895560008981528481209551600290930290950191825591519082015586548684528188019092529290912055611742565b82856000016001830381548110611f2957fe5b9060005260206000209060020201600101819055506000915050611742565b3b151590565b606061173f848460008585611f6285611f48565b611fb3576040805162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015290519081900360640190fd5b600080866001600160a01b031685876040518082805190602001908083835b60208310611ff15780518252601f199092019160209182019101611fd2565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d8060008114612053576040519150601f19603f3d011682016040523d82523d6000602084013e612058565b606091505b5091509150612068828286612073565b979650505050505050565b60608315612082575081611742565b8251156120925782518084602001fd5b60405162461bcd60e51b8152602060048201818152845160248401528451859391928392604401919085019080838360008315611c01578181015183820152602001611be9565b828054600181600116156101000203166002900490600052602060002090601f01602090048101928261210f5760008555612155565b82601f1061212857805160ff1916838001178555612155565b82800160010185558215612155579182015b8281111561215557825182559160200191906001019061213a565b50612161929150612165565b5090565b5b80821115612161576000815560010161216656fe456e756d657261626c655365743a20696e646578206f7574206f6620626f756e6473416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f206772616e744552433732313a207472616e7366657220746f206e6f6e20455243373231526563656976657220696d706c656d656e7465724f776e61626c653a206e6577206f776e657220697320746865207a65726f20616464726573734552433732313a207472616e7366657220746f20746865207a65726f20616464726573734552433732313a206f70657261746f7220717565727920666f72206e6f6e6578697374656e7420746f6b656e416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f207265766f6b654552433732313a20617070726f76652063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f76656420666f7220616c6c4552433732313a2062616c616e636520717565727920666f7220746865207a65726f20616464726573734552433732313a206f776e657220717565727920666f72206e6f6e6578697374656e7420746f6b656e456e756d657261626c654d61703a20696e646578206f7574206f6620626f756e64734552433732313a20617070726f76656420717565727920666f72206e6f6e6578697374656e7420746f6b656e4552433732314d657461646174613a2055524920736574206f66206e6f6e6578697374656e7420746f6b656e4552433732313a207472616e73666572206f6620746f6b656e2074686174206973206e6f74206f776e4552433732314d657461646174613a2055524920717565727920666f72206e6f6e6578697374656e7420746f6b656e4552433732313a20617070726f76616c20746f2063757272656e74206f776e65724552433732313a207472616e736665722063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f766564416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636520726f6c657320666f722073656c66a2646970667358221220824b5c0669df2c3d081e845da5e34644edc08bd41f5f0a02b09c19b0a3c1329164736f6c634300070600334f776e61626c653a206e6577206f776e657220697320746865207a65726f20616464726573738be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0",
+  "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106101da5760003560e01c8063715018a611610104578063b88d4fde116100a2578063d539139311610071578063d5391393146106e0578063d547741f146106e8578063e985e9c514610714578063f2fde38b14610742576101da565b8063b88d4fde1461052a578063c87b56dd146105f0578063ca15c8731461060d578063d0def5211461062a576101da565b806391d14854116100de57806391d14854146104c057806395d89b41146104ec578063a217fddf146104f4578063a22cb465146104fc576101da565b8063715018a61461048d5780638da5cb5b146104955780639010d07c1461049d576101da565b80632f2ff15d1161017c5780634f6ccce71161014b5780634f6ccce7146104255780636352211e146104425780636c0360eb1461045f57806370a0823114610467576101da565b80632f2ff15d1461036b5780632f745c591461039757806336568abe146103c357806342842e0e146103ef576101da565b8063095ea7b3116101b8578063095ea7b3146102d057806318160ddd146102fe57806323b872dd14610318578063248a9ca31461034e576101da565b806301ffc9a7146101df57806306fdde031461021a578063081812fc14610297575b600080fd5b610206600480360360208110156101f557600080fd5b50356001600160e01b031916610768565b604080519115158252519081900360200190f35b61022261078b565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561025c578181015183820152602001610244565b50505050905090810190601f1680156102895780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102b4600480360360208110156102ad57600080fd5b5035610821565b604080516001600160a01b039092168252519081900360200190f35b6102fc600480360360408110156102e657600080fd5b506001600160a01b038135169060200135610883565b005b61030661095e565b60408051918252519081900360200190f35b6102fc6004803603606081101561032e57600080fd5b506001600160a01b0381358116916020810135909116906040013561096f565b6103066004803603602081101561036457600080fd5b50356109c6565b6102fc6004803603604081101561038157600080fd5b50803590602001356001600160a01b03166109db565b610306600480360360408110156103ad57600080fd5b506001600160a01b038135169060200135610a47565b6102fc600480360360408110156103d957600080fd5b50803590602001356001600160a01b0316610a72565b6102fc6004803603606081101561040557600080fd5b506001600160a01b03813581169160208101359091169060400135610ad3565b6103066004803603602081101561043b57600080fd5b5035610aee565b6102b46004803603602081101561045857600080fd5b5035610b04565b610222610b2c565b6103066004803603602081101561047d57600080fd5b50356001600160a01b0316610b8d565b6102fc610bf5565b6102b4610cb3565b6102b4600480360360408110156104b357600080fd5b5080359060200135610cc2565b610206600480360360408110156104d657600080fd5b50803590602001356001600160a01b0316610cda565b610222610cf2565b610306610d53565b6102fc6004803603604081101561051257600080fd5b506001600160a01b0381351690602001351515610d58565b6102fc6004803603608081101561054057600080fd5b6001600160a01b0382358116926020810135909116916040820135919081019060808101606082013564010000000081111561057b57600080fd5b82018360208201111561058d57600080fd5b803590602001918460018302840111640100000000831117156105af57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610e5d945050505050565b6102226004803603602081101561060657600080fd5b5035610ebb565b6103066004803603602081101561062357600080fd5b503561113c565b6103066004803603604081101561064057600080fd5b6001600160a01b03823516919081019060408101602082013564010000000081111561066b57600080fd5b82018360208201111561067d57600080fd5b8035906020019184600183028401116401000000008311171561069f57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611153945050505050565b6103066111e9565b6102fc600480360360408110156106fe57600080fd5b50803590602001356001600160a01b031661120d565b6102066004803603604081101561072a57600080fd5b506001600160a01b0381358116916020013516611266565b6102fc6004803603602081101561075857600080fd5b50356001600160a01b0316611294565b6001600160e01b0319811660009081526020819052604090205460ff165b919050565b60068054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156108175780601f106107ec57610100808354040283529160200191610817565b820191906000526020600020905b8154815290600101906020018083116107fa57829003601f168201915b5050505050905090565b600061082c826113be565b6108675760405162461bcd60e51b815260040180806020018281038252602c815260200180612351602c913960400191505060405180910390fd5b506000908152600460205260409020546001600160a01b031690565b600061088e82610b04565b9050806001600160a01b0316836001600160a01b031614156108e15760405162461bcd60e51b81526004018080602001828103825260218152602001806124016021913960400191505060405180910390fd5b806001600160a01b03166108f36113cb565b6001600160a01b0316148061091457506109148161090f6113cb565b611266565b61094f5760405162461bcd60e51b81526004018080602001828103825260388152602001806122a46038913960400191505060405180910390fd5b61095983836113cf565b505050565b600061096a600261143d565b905090565b61098061097a6113cb565b82611448565b6109bb5760405162461bcd60e51b81526004018080602001828103825260318152602001806124226031913960400191505060405180910390fd5b6109598383836114ec565b6000908152600a602052604090206002015490565b6000828152600a60205260409020600201546109fe906109f96113cb565b610cda565b610a395760405162461bcd60e51b815260040180806020018281038252602f81526020018061219d602f913960400191505060405180910390fd5b610a438282611638565b5050565b6001600160a01b0382166000908152600160205260408120610a6990836116a1565b90505b92915050565b610a7a6113cb565b6001600160a01b0316816001600160a01b031614610ac95760405162461bcd60e51b815260040180806020018281038252602f815260200180612453602f913960400191505060405180910390fd5b610a4382826116ad565b61095983838360405180602001604052806000815250610e5d565b600080610afc600284611716565b509392505050565b6000610a6c826040518060600160405280602981526020016123066029913960029190611732565b60098054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156108175780601f106107ec57610100808354040283529160200191610817565b60006001600160a01b038216610bd45760405162461bcd60e51b815260040180806020018281038252602a8152602001806122dc602a913960400191505060405180910390fd5b6001600160a01b0382166000908152600160205260409020610a6c9061143d565b610bfd6113cb565b6001600160a01b0316610c0e610cb3565b6001600160a01b031614610c69576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b600b546040516000916001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600b80546001600160a01b0319169055565b600b546001600160a01b031690565b6000828152600a60205260408120610a6990836116a1565b6000828152600a60205260408120610a699083611749565b60078054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156108175780601f106107ec57610100808354040283529160200191610817565b600081565b610d606113cb565b6001600160a01b0316826001600160a01b03161415610dc6576040805162461bcd60e51b815260206004820152601960248201527f4552433732313a20617070726f766520746f2063616c6c657200000000000000604482015290519081900360640190fd5b8060056000610dd36113cb565b6001600160a01b03908116825260208083019390935260409182016000908120918716808252919093529120805460ff191692151592909217909155610e176113cb565b6001600160a01b03167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c318360405180821515815260200191505060405180910390a35050565b610e6e610e686113cb565b83611448565b610ea95760405162461bcd60e51b81526004018080602001828103825260318152602001806124226031913960400191505060405180910390fd5b610eb58484848461175e565b50505050565b6060610ec6826113be565b610f015760405162461bcd60e51b815260040180806020018281038252602f8152602001806123d2602f913960400191505060405180910390fd5b60008281526008602090815260408083208054825160026001831615610100026000190190921691909104601f810185900485028201850190935282815292909190830182828015610f945780601f10610f6957610100808354040283529160200191610f94565b820191906000526020600020905b815481529060010190602001808311610f7757829003601f168201915b505050505090506000610fa5610b2c565b9050805160001415610fb957509050610786565b81511561107a5780826040516020018083805190602001908083835b60208310610ff45780518252601f199092019160209182019101610fd5565b51815160209384036101000a600019018019909216911617905285519190930192850191508083835b6020831061103c5780518252601f19909201916020918201910161101d565b6001836020036101000a0380198251168184511680821785525050505050509050019250505060405160208183030381529060405292505050610786565b80611084856117b0565b6040516020018083805190602001908083835b602083106110b65780518252601f199092019160209182019101611097565b51815160209384036101000a600019018019909216911617905285519190930192850191508083835b602083106110fe5780518252601f1990920191602091820191016110df565b6001836020036101000a0380198251168184511680821785525050505050509050019250505060405160208183030381529060405292505050919050565b6000818152600a60205260408120610a6c9061143d565b600061117f7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a633610cda565b6111bd576040805162461bcd60e51b815260206004820152600a6024820152692727aa2fa6a4a72a22a960b11b604482015290519081900360640190fd5b6111c7600c61188b565b60006111d3600c611894565b90506111df8482611898565b610a6981846119c6565b7f9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a681565b6000828152600a602052604090206002015461122b906109f96113cb565b610ac95760405162461bcd60e51b81526004018080602001828103825260308152602001806122746030913960400191505060405180910390fd5b6001600160a01b03918216600090815260056020908152604080832093909416825291909152205460ff1690565b61129c6113cb565b6001600160a01b03166112ad610cb3565b6001600160a01b031614611308576040805162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b6001600160a01b03811661134d5760405162461bcd60e51b81526004018080602001828103825260268152602001806121fe6026913960400191505060405180910390fd5b600b546040516001600160a01b038084169216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3600b80546001600160a01b0319166001600160a01b0392909216919091179055565b6000610a69836001600160a01b038416611a29565b6000610a6c600283611a73565b3390565b600081815260046020526040902080546001600160a01b0319166001600160a01b038416908117909155819061140482610b04565b6001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b6000610a6c82611894565b6000611453826113be565b61148e5760405162461bcd60e51b815260040180806020018281038252602c815260200180612248602c913960400191505060405180910390fd5b600061149983610b04565b9050806001600160a01b0316846001600160a01b031614806114d45750836001600160a01b03166114c984610821565b6001600160a01b0316145b806114e457506114e48185611266565b949350505050565b826001600160a01b03166114ff82610b04565b6001600160a01b0316146115445760405162461bcd60e51b81526004018080602001828103825260298152602001806123a96029913960400191505060405180910390fd5b6001600160a01b0382166115895760405162461bcd60e51b81526004018080602001828103825260248152602001806122246024913960400191505060405180910390fd5b611594838383610959565b61159f6000826113cf565b6001600160a01b03831660009081526001602052604090206115c19082611a7f565b506001600160a01b03821660009081526001602052604090206115e49082611a8b565b506115f160028284611a97565b5080826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4505050565b6000828152600a6020526040902061165090826113a9565b15610a435761165d6113cb565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000610a698383611aad565b6000828152600a602052604090206116c59082611b11565b15610a43576116d26113cb565b6001600160a01b0316816001600160a01b0316837ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b60405160405180910390a45050565b60008080806117258686611b26565b9097909650945050505050565b600061173f848484611ba1565b90505b9392505050565b6000610a69836001600160a01b038416611c6b565b6117698484846114ec565b61177584848484611c83565b610eb55760405162461bcd60e51b81526004018080602001828103825260328152602001806121cc6032913960400191505060405180910390fd5b6060816117d557506040805180820190915260018152600360fc1b6020820152610786565b8160005b81156117ed57600101600a820491506117d9565b60008167ffffffffffffffff8111801561180657600080fd5b506040519080825280601f01601f191660200182016040528015611831576020820181803683370190505b50859350905060001982015b831561188257600a840660300160f81b8282806001900393508151811061186057fe5b60200101906001600160f81b031916908160001a905350600a8404935061183d565b50949350505050565b80546001019055565b5490565b6001600160a01b0382166118f3576040805162461bcd60e51b815260206004820181905260248201527f4552433732313a206d696e7420746f20746865207a65726f2061646472657373604482015290519081900360640190fd5b6118fc816113be565b1561194e576040805162461bcd60e51b815260206004820152601c60248201527f4552433732313a20746f6b656e20616c7265616479206d696e74656400000000604482015290519081900360640190fd5b61195a60008383610959565b6001600160a01b038216600090815260016020526040902061197c9082611a8b565b5061198960028284611a97565b5060405181906001600160a01b038416906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a45050565b6119cf826113be565b611a0a5760405162461bcd60e51b815260040180806020018281038252602c81526020018061237d602c913960400191505060405180910390fd5b60008281526008602090815260409091208251610959928401906120d9565b6000611a358383611c6b565b611a6b57508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610a6c565b506000610a6c565b6000610a698383611c6b565b6000610a698383611deb565b6000610a698383611a29565b600061173f84846001600160a01b038516611eb1565b81546000908210611aef5760405162461bcd60e51b815260040180806020018281038252602281526020018061217b6022913960400191505060405180910390fd5b826000018281548110611afe57fe5b9060005260206000200154905092915050565b6000610a69836001600160a01b038416611deb565b815460009081908310611b6a5760405162461bcd60e51b815260040180806020018281038252602281526020018061232f6022913960400191505060405180910390fd5b6000846000018481548110611b7b57fe5b906000526020600020906002020190508060000154816001015492509250509250929050565b60008281526001840160205260408120548281611c3c5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015611c01578181015183820152602001611be9565b50505050905090810190601f168015611c2e5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b50846000016001820381548110611c4f57fe5b9060005260206000209060020201600101549150509392505050565b60009081526001919091016020526040902054151590565b6000611c97846001600160a01b0316611f48565b611ca3575060016114e4565b6000611db1630a85bd0160e11b611cb86113cb565b88878760405160240180856001600160a01b03168152602001846001600160a01b0316815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015611d1f578181015183820152602001611d07565b50505050905090810190601f168015611d4c5780820380516001836020036101000a031916815260200191505b5095505050505050604051602081830303815290604052906001600160e01b0319166020820180516001600160e01b0383818316178352505050506040518060600160405280603281526020016121cc603291396001600160a01b0388169190611f4e565b90506000818060200190516020811015611dca57600080fd5b50516001600160e01b031916630a85bd0160e11b1492505050949350505050565b60008181526001830160205260408120548015611ea75783546000198083019190810190600090879083908110611e1e57fe5b9060005260206000200154905080876000018481548110611e3b57fe5b600091825260208083209091019290925582815260018981019092526040902090840190558654879080611e6b57fe5b60019003818190600052602060002001600090559055866001016000878152602001908152602001600020600090556001945050505050610a6c565b6000915050610a6c565b600082815260018401602052604081205480611f16575050604080518082018252838152602080820184815286546001818101895560008981528481209551600290930290950191825591519082015586548684528188019092529290912055611742565b82856000016001830381548110611f2957fe5b9060005260206000209060020201600101819055506000915050611742565b3b151590565b606061173f848460008585611f6285611f48565b611fb3576040805162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015290519081900360640190fd5b600080866001600160a01b031685876040518082805190602001908083835b60208310611ff15780518252601f199092019160209182019101611fd2565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d8060008114612053576040519150601f19603f3d011682016040523d82523d6000602084013e612058565b606091505b5091509150612068828286612073565b979650505050505050565b60608315612082575081611742565b8251156120925782518084602001fd5b60405162461bcd60e51b8152602060048201818152845160248401528451859391928392604401919085019080838360008315611c01578181015183820152602001611be9565b828054600181600116156101000203166002900490600052602060002090601f01602090048101928261210f5760008555612155565b82601f1061212857805160ff1916838001178555612155565b82800160010185558215612155579182015b8281111561215557825182559160200191906001019061213a565b50612161929150612165565b5090565b5b80821115612161576000815560010161216656fe456e756d657261626c655365743a20696e646578206f7574206f6620626f756e6473416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f206772616e744552433732313a207472616e7366657220746f206e6f6e20455243373231526563656976657220696d706c656d656e7465724f776e61626c653a206e6577206f776e657220697320746865207a65726f20616464726573734552433732313a207472616e7366657220746f20746865207a65726f20616464726573734552433732313a206f70657261746f7220717565727920666f72206e6f6e6578697374656e7420746f6b656e416363657373436f6e74726f6c3a2073656e646572206d75737420626520616e2061646d696e20746f207265766f6b654552433732313a20617070726f76652063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f76656420666f7220616c6c4552433732313a2062616c616e636520717565727920666f7220746865207a65726f20616464726573734552433732313a206f776e657220717565727920666f72206e6f6e6578697374656e7420746f6b656e456e756d657261626c654d61703a20696e646578206f7574206f6620626f756e64734552433732313a20617070726f76656420717565727920666f72206e6f6e6578697374656e7420746f6b656e4552433732314d657461646174613a2055524920736574206f66206e6f6e6578697374656e7420746f6b656e4552433732313a207472616e73666572206f6620746f6b656e2074686174206973206e6f74206f776e4552433732314d657461646174613a2055524920717565727920666f72206e6f6e6578697374656e7420746f6b656e4552433732313a20617070726f76616c20746f2063757272656e74206f776e65724552433732313a207472616e736665722063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f766564416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636520726f6c657320666f722073656c66a2646970667358221220824b5c0669df2c3d081e845da5e34644edc08bd41f5f0a02b09c19b0a3c1329164736f6c63430007060033",
+  "linkReferences": {},
+  "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/THXPaymentSplitter.json b/apps/api/src/app/hardhat/export/THXPaymentSplitter.json
new file mode 100644
index 000000000..db415a73b
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/THXPaymentSplitter.json
@@ -0,0 +1,184 @@
+{
+    "_format": "hh-sol-artifact-1",
+    "contractName": "THXPaymentSplitter",
+    "sourceName": "contracts/utils/THXPaymentSplitter.sol",
+    "abi": [
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "_admin",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "_registry",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "constructor"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "_owner",
+                    "type": "address"
+                }
+            ],
+            "name": "balanceOf",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "bpt",
+            "outputs": [
+                {
+                    "internalType": "contract IWeightedPool",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "_owner",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "_amount",
+                    "type": "uint256"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "_minAmountOut",
+                    "type": "uint256"
+                }
+            ],
+            "name": "deposit",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "gauge",
+            "outputs": [
+                {
+                    "internalType": "contract IGauge",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "paymentToken",
+            "outputs": [
+                {
+                    "internalType": "contract IERC20",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "name": "rates",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "registry",
+            "outputs": [
+                {
+                    "internalType": "contract ITHXRegistry",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "_owner",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "_rate",
+                    "type": "uint256"
+                }
+            ],
+            "name": "setRate",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "_registry",
+                    "type": "address"
+                }
+            ],
+            "name": "setRegistry",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "vault",
+            "outputs": [
+                {
+                    "internalType": "contract BalancerVault",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        }
+    ],
+    "bytecode": "0x60806040523480156200001157600080fd5b506040516200148138038062001481833981016040819052620000349162000304565b6001600081905580546001600160a01b038085166001600160a01b03199283161790925560028054848416921691909117908190556040805163d41c3a6560e01b81529051919092169163d41c3a65916004808301926020929190829003018186803b158015620000a457600080fd5b505afa158015620000b9573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620000df9190620002e0565b600680546001600160a01b0319166001600160a01b039283161790556002546040805163f2803f0360e01b81529051919092169163f2803f03916004808301926020929190829003018186803b1580156200013957600080fd5b505afa1580156200014e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620001749190620002e0565b600380546001600160a01b0319166001600160a01b03928316179081905560408051634163183360e11b8152905191909216916382c63066916004808301926020929190829003018186803b158015620001cd57600080fd5b505afa158015620001e2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620002089190620002e0565b600480546001600160a01b0319166001600160a01b0392831617808255604080516311b2515f60e31b815290519190931692638d928af89281810192602092909190829003018186803b1580156200025f57600080fd5b505afa15801562000274573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200029a9190620002e0565b600580546001600160a01b0319166001600160a01b0392909216919091179055506200033b9050565b80516001600160a01b0381168114620002db57600080fd5b919050565b600060208284031215620002f2578081fd5b620002fd82620002c3565b9392505050565b6000806040838503121562000317578081fd5b6200032283620002c3565b91506200033260208401620002c3565b90509250929050565b611136806200034b6000396000f3fe608060405234801561001057600080fd5b506004361061009e5760003560e01c80637b103999116100665780637b10399914610111578063a6f19c8414610119578063a8734f0b14610121578063a91ee0dc14610134578063fbfa77cf146101475761009e565b80630efe6a8b146100a35780632bdb7097146100b85780633013ce29146100cb578063546af3c3146100e957806370a08231146100f1575b600080fd5b6100b66100b1366004610de6565b61014f565b005b6100b66100c6366004610dbb565b610a46565b6100d3610a9c565b6040516100e09190610edd565b60405180910390f35b6100d3610aab565b6101046100ff366004610d83565b610aba565b6040516100e091906110be565b6100d3610b64565b6100d3610b73565b61010461012f366004610d83565b610b82565b6100b6610142366004610d83565b610b94565b6100d3610bf0565b600260005414156101a7576040805162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015290519081900360640190fd5b600260009081556001600160a01b0384168152600760205260409020546101e95760405162461bcd60e51b81526004016101e090611057565b60405180910390fd5b6006546040516323b872dd60e01b81526001600160a01b03909116906323b872dd9061021d90869030908790600401610ef1565b602060405180830381600087803b15801561023757600080fd5b505af115801561024b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061026f9190610e1a565b61028b5760405162461bcd60e51b81526004016101e090611022565b60025460408051637a3d22ad60e11b815290516000926001600160a01b03169163f47a455a916004808301926020929190829003018186803b1580156102d057600080fd5b505afa1580156102e4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103089190610e3a565b9050600061032261271061031c8685610bff565b90610c61565b6006546002546040805163cd44673560e01b815290519394506001600160a01b039283169363a9059cbb939092169163cd44673591600480820192602092909190829003018186803b15801561037757600080fd5b505afa15801561038b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103af9190610d9f565b836040518363ffffffff1660e01b81526004016103cd929190610f15565b602060405180830381600087803b1580156103e757600080fd5b505af11580156103fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061041f9190610e1a565b61043b5760405162461bcd60e51b81526004016101e090611022565b604080516002808252606082018352600092602083019080368337505060065482519293506001600160a01b03169183915060009061047657fe5b60200260200101906001600160a01b031690816001600160a01b0316815250506000816001815181106104a557fe5b6001600160a01b039290921660209283029190910182015260408051600280825260608201835260009391929091830190803683370190505090506104ea8684610cc8565b816000815181106104f757fe5b60200260200101818152505060008160018151811061051257fe5b602090810291909101015260065460055482516001600160a01b039283169263095ea7b3921690849060009061054457fe5b60200260200101516040518363ffffffff1660e01b8152600401610569929190610f15565b602060405180830381600087803b15801561058357600080fd5b505af1158015610597573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105bb9190610e1a565b50600554600480546040805163038fff2d60e41b815290516001600160a01b039485169463b95cac28949316926338fff2d092808201926020929091829003018186803b15801561060b57600080fd5b505afa15801561061f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106439190610e3a565b303060405180608001604052808881526020018781526020016001888d60405160200161067293929190610ff6565b6040516020818303038152906040528152602001600015158152506040518563ffffffff1660e01b81526004016106ac9493929190610f2e565b600060405180830381600087803b1580156106c657600080fd5b505af11580156106da573d6000803e3d6000fd5b5050600480546040516370a0823160e01b8152600094506001600160a01b0390911692506370a082319161071091309101610edd565b60206040518083038186803b15801561072857600080fd5b505afa15801561073c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107609190610e3a565b6004805460035460405163095ea7b360e01b81529394506001600160a01b039182169363095ea7b3936107999390921691869101610f15565b602060405180830381600087803b1580156107b357600080fd5b505af11580156107c7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107eb9190610e1a565b5060035460405163b6b55f2560e01b81526001600160a01b039091169063b6b55f259061081c9084906004016110be565b600060405180830381600087803b15801561083657600080fd5b505af115801561084a573d6000803e3d6000fd5b50506003546040516370a0823160e01b8152600093506001600160a01b0390911691506370a0823190610881903090600401610edd565b60206040518083038186803b15801561089957600080fd5b505afa1580156108ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108d19190610e3a565b600354600254604080516367906c3960e11b815290519394506001600160a01b039283169363a9059cbb939092169163cf20d87291600480820192602092909190829003018186803b15801561092657600080fd5b505afa15801561093a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061095e9190610d9f565b836040518363ffffffff1660e01b815260040161097c929190610f15565b602060405180830381600087803b15801561099657600080fd5b505af11580156109aa573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109ce9190610e1a565b6109ea5760405162461bcd60e51b81526004016101e090611022565b6001600160a01b038916600090815260086020526040902054610a0d9089610d25565b6001600160a01b0390991660009081526008602090815260408083209b909b556009905298892042905550506001909655505050505050565b6001546001600160a01b0316610a5a610d7f565b6001600160a01b031614610a805760405162461bcd60e51b81526004016101e090611087565b6001600160a01b03909116600090815260076020526040902055565b6006546001600160a01b031681565b6004546001600160a01b031681565b6001600160a01b038116600090815260076020908152604080832054600990925282205480610aee57600092505050610b5f565b426000610b0584610aff8486610cc8565b90610bff565b6001600160a01b038716600090815260086020526040902054909150811115610b35576000945050505050610b5f565b6001600160a01b038616600090815260086020526040902054610b589082610cc8565b9450505050505b919050565b6002546001600160a01b031681565b6003546001600160a01b031681565b60076020526000908152604090205481565b6001546001600160a01b0316610ba8610d7f565b6001600160a01b031614610bce5760405162461bcd60e51b81526004016101e090611087565b600280546001600160a01b0319166001600160a01b0392909216919091179055565b6005546001600160a01b031681565b600082610c0e57506000610c5b565b82820282848281610c1b57fe5b0414610c585760405162461bcd60e51b81526004018080602001828103825260218152602001806110e06021913960400191505060405180910390fd5b90505b92915050565b6000808211610cb7576040805162461bcd60e51b815260206004820152601a60248201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604482015290519081900360640190fd5b818381610cc057fe5b049392505050565b600082821115610d1f576040805162461bcd60e51b815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604482015290519081900360640190fd5b50900390565b600082820183811015610c58576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b3390565b600060208284031215610d94578081fd5b8135610c58816110c7565b600060208284031215610db0578081fd5b8151610c58816110c7565b60008060408385031215610dcd578081fd5b8235610dd8816110c7565b946020939093013593505050565b600080600060608486031215610dfa578081fd5b8335610e05816110c7565b95602085013595506040909401359392505050565b600060208284031215610e2b578081fd5b81518015158114610c58578182fd5b600060208284031215610e4b578081fd5b5051919050565b6000815180845260208085019450808401835b83811015610e8157815187529582019590820190600101610e65565b509495945050505050565b15159052565b60008151808452815b81811015610eb757602081850181015186830182015201610e9b565b81811115610ec85782602083870101525b50601f01601f19169290920160200192915050565b6001600160a01b0391909116815260200190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b03929092168252602082015260400190565b6000858252602060018060a01b0380871682850152808616604085015260806060850152610100840185516080808701528181518084526101208801915085830193508692505b80831015610f9757835185168252928501926001929092019190850190610f75565b50848801519450607f199350838782030160a0880152610fb78186610e52565b94505050506040850151818584030160c0860152610fd58382610e92565b925050506060840151610feb60e0850182610e8c565b509695505050505050565b600060ff85168252606060208301526110126060830185610e52565b9050826040830152949350505050565b6020808252818101527f5061796d656e7453706c69747465723a207472616e73666572206661696c6564604082015260600190565b6020808252601690820152755061796d656e7453706c69747465723a20217261746560501b604082015260600190565b60208082526017908201527f5061796d656e7453706c69747465723a202161646d696e000000000000000000604082015260600190565b90815260200190565b6001600160a01b03811681146110dc57600080fd5b5056fe536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f77a2646970667358221220eba06cbcd528d8589e50089c90e52f7dc3fa5e7165ae1e40435ece99c19f65c564736f6c63430007060033",
+    "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061009e5760003560e01c80637b103999116100665780637b10399914610111578063a6f19c8414610119578063a8734f0b14610121578063a91ee0dc14610134578063fbfa77cf146101475761009e565b80630efe6a8b146100a35780632bdb7097146100b85780633013ce29146100cb578063546af3c3146100e957806370a08231146100f1575b600080fd5b6100b66100b1366004610de6565b61014f565b005b6100b66100c6366004610dbb565b610a46565b6100d3610a9c565b6040516100e09190610edd565b60405180910390f35b6100d3610aab565b6101046100ff366004610d83565b610aba565b6040516100e091906110be565b6100d3610b64565b6100d3610b73565b61010461012f366004610d83565b610b82565b6100b6610142366004610d83565b610b94565b6100d3610bf0565b600260005414156101a7576040805162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604482015290519081900360640190fd5b600260009081556001600160a01b0384168152600760205260409020546101e95760405162461bcd60e51b81526004016101e090611057565b60405180910390fd5b6006546040516323b872dd60e01b81526001600160a01b03909116906323b872dd9061021d90869030908790600401610ef1565b602060405180830381600087803b15801561023757600080fd5b505af115801561024b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061026f9190610e1a565b61028b5760405162461bcd60e51b81526004016101e090611022565b60025460408051637a3d22ad60e11b815290516000926001600160a01b03169163f47a455a916004808301926020929190829003018186803b1580156102d057600080fd5b505afa1580156102e4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103089190610e3a565b9050600061032261271061031c8685610bff565b90610c61565b6006546002546040805163cd44673560e01b815290519394506001600160a01b039283169363a9059cbb939092169163cd44673591600480820192602092909190829003018186803b15801561037757600080fd5b505afa15801561038b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103af9190610d9f565b836040518363ffffffff1660e01b81526004016103cd929190610f15565b602060405180830381600087803b1580156103e757600080fd5b505af11580156103fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061041f9190610e1a565b61043b5760405162461bcd60e51b81526004016101e090611022565b604080516002808252606082018352600092602083019080368337505060065482519293506001600160a01b03169183915060009061047657fe5b60200260200101906001600160a01b031690816001600160a01b0316815250506000816001815181106104a557fe5b6001600160a01b039290921660209283029190910182015260408051600280825260608201835260009391929091830190803683370190505090506104ea8684610cc8565b816000815181106104f757fe5b60200260200101818152505060008160018151811061051257fe5b602090810291909101015260065460055482516001600160a01b039283169263095ea7b3921690849060009061054457fe5b60200260200101516040518363ffffffff1660e01b8152600401610569929190610f15565b602060405180830381600087803b15801561058357600080fd5b505af1158015610597573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105bb9190610e1a565b50600554600480546040805163038fff2d60e41b815290516001600160a01b039485169463b95cac28949316926338fff2d092808201926020929091829003018186803b15801561060b57600080fd5b505afa15801561061f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106439190610e3a565b303060405180608001604052808881526020018781526020016001888d60405160200161067293929190610ff6565b6040516020818303038152906040528152602001600015158152506040518563ffffffff1660e01b81526004016106ac9493929190610f2e565b600060405180830381600087803b1580156106c657600080fd5b505af11580156106da573d6000803e3d6000fd5b5050600480546040516370a0823160e01b8152600094506001600160a01b0390911692506370a082319161071091309101610edd565b60206040518083038186803b15801561072857600080fd5b505afa15801561073c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107609190610e3a565b6004805460035460405163095ea7b360e01b81529394506001600160a01b039182169363095ea7b3936107999390921691869101610f15565b602060405180830381600087803b1580156107b357600080fd5b505af11580156107c7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107eb9190610e1a565b5060035460405163b6b55f2560e01b81526001600160a01b039091169063b6b55f259061081c9084906004016110be565b600060405180830381600087803b15801561083657600080fd5b505af115801561084a573d6000803e3d6000fd5b50506003546040516370a0823160e01b8152600093506001600160a01b0390911691506370a0823190610881903090600401610edd565b60206040518083038186803b15801561089957600080fd5b505afa1580156108ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108d19190610e3a565b600354600254604080516367906c3960e11b815290519394506001600160a01b039283169363a9059cbb939092169163cf20d87291600480820192602092909190829003018186803b15801561092657600080fd5b505afa15801561093a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061095e9190610d9f565b836040518363ffffffff1660e01b815260040161097c929190610f15565b602060405180830381600087803b15801561099657600080fd5b505af11580156109aa573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109ce9190610e1a565b6109ea5760405162461bcd60e51b81526004016101e090611022565b6001600160a01b038916600090815260086020526040902054610a0d9089610d25565b6001600160a01b0390991660009081526008602090815260408083209b909b556009905298892042905550506001909655505050505050565b6001546001600160a01b0316610a5a610d7f565b6001600160a01b031614610a805760405162461bcd60e51b81526004016101e090611087565b6001600160a01b03909116600090815260076020526040902055565b6006546001600160a01b031681565b6004546001600160a01b031681565b6001600160a01b038116600090815260076020908152604080832054600990925282205480610aee57600092505050610b5f565b426000610b0584610aff8486610cc8565b90610bff565b6001600160a01b038716600090815260086020526040902054909150811115610b35576000945050505050610b5f565b6001600160a01b038616600090815260086020526040902054610b589082610cc8565b9450505050505b919050565b6002546001600160a01b031681565b6003546001600160a01b031681565b60076020526000908152604090205481565b6001546001600160a01b0316610ba8610d7f565b6001600160a01b031614610bce5760405162461bcd60e51b81526004016101e090611087565b600280546001600160a01b0319166001600160a01b0392909216919091179055565b6005546001600160a01b031681565b600082610c0e57506000610c5b565b82820282848281610c1b57fe5b0414610c585760405162461bcd60e51b81526004018080602001828103825260218152602001806110e06021913960400191505060405180910390fd5b90505b92915050565b6000808211610cb7576040805162461bcd60e51b815260206004820152601a60248201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604482015290519081900360640190fd5b818381610cc057fe5b049392505050565b600082821115610d1f576040805162461bcd60e51b815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604482015290519081900360640190fd5b50900390565b600082820183811015610c58576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b3390565b600060208284031215610d94578081fd5b8135610c58816110c7565b600060208284031215610db0578081fd5b8151610c58816110c7565b60008060408385031215610dcd578081fd5b8235610dd8816110c7565b946020939093013593505050565b600080600060608486031215610dfa578081fd5b8335610e05816110c7565b95602085013595506040909401359392505050565b600060208284031215610e2b578081fd5b81518015158114610c58578182fd5b600060208284031215610e4b578081fd5b5051919050565b6000815180845260208085019450808401835b83811015610e8157815187529582019590820190600101610e65565b509495945050505050565b15159052565b60008151808452815b81811015610eb757602081850181015186830182015201610e9b565b81811115610ec85782602083870101525b50601f01601f19169290920160200192915050565b6001600160a01b0391909116815260200190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b03929092168252602082015260400190565b6000858252602060018060a01b0380871682850152808616604085015260806060850152610100840185516080808701528181518084526101208801915085830193508692505b80831015610f9757835185168252928501926001929092019190850190610f75565b50848801519450607f199350838782030160a0880152610fb78186610e52565b94505050506040850151818584030160c0860152610fd58382610e92565b925050506060840151610feb60e0850182610e8c565b509695505050505050565b600060ff85168252606060208301526110126060830185610e52565b9050826040830152949350505050565b6020808252818101527f5061796d656e7453706c69747465723a207472616e73666572206661696c6564604082015260600190565b6020808252601690820152755061796d656e7453706c69747465723a20217261746560501b604082015260600190565b60208082526017908201527f5061796d656e7453706c69747465723a202161646d696e000000000000000000604082015260600190565b90815260200190565b6001600160a01b03811681146110dc57600080fd5b5056fe536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f77a2646970667358221220eba06cbcd528d8589e50089c90e52f7dc3fa5e7165ae1e40435ece99c19f65c564736f6c63430007060033",
+    "linkReferences": {},
+    "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/THXRegistry.json b/apps/api/src/app/hardhat/export/THXRegistry.json
new file mode 100644
index 000000000..c91fd4daa
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/THXRegistry.json
@@ -0,0 +1,180 @@
+{
+    "_format": "hh-sol-artifact-1",
+    "contractName": "THXRegistry",
+    "sourceName": "contracts/utils/THXRegistry.sol",
+    "abi": [
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "_paymentToken",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "_payee",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "_rewardDistributor",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "_gauge",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "constructor"
+        },
+        {
+            "inputs": [],
+            "name": "gauge",
+            "outputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "getGauge",
+            "outputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "getPayee",
+            "outputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "getPaymentToken",
+            "outputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "getPayoutRate",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "getRewardDistributor",
+            "outputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "payee",
+            "outputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "paymentToken",
+            "outputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "payoutRate",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "rewardDistributor",
+            "outputs": [
+                {
+                    "internalType": "address",
+                    "name": "",
+                    "type": "address"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "_rate",
+                    "type": "uint256"
+                }
+            ],
+            "name": "setPayoutRate",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        }
+    ],
+    "bytecode": "0x608060405234801561001057600080fd5b506040516103683803806103688339818101604052608081101561003357600080fd5b50805160208201516040830151606090930151600080546001600160a01b039485166001600160a01b03199182161790915560018054938516938216939093179092556003805494841694831694909417909355600480549290931691161790556102c5806100a36000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c8063ae90b21311610071578063ae90b2131461011b578063cd44673514610123578063cf20d8721461012b578063d41c3a6514610133578063f2803f031461013b578063f47a455a14610143576100a9565b80632b4353f2146100ae5780633013ce29146100c857806348eaee66146100ec578063a6f19c841461010b578063acc2166a14610113575b600080fd5b6100b661014b565b60408051918252519081900360200190f35b6100d0610151565b604080516001600160a01b039092168252519081900360200190f35b6101096004803603602081101561010257600080fd5b5035610160565b005b6100d06101fb565b6100d061020a565b6100d0610219565b6100d0610228565b6100d0610237565b6100d0610246565b6100d0610255565b6100b6610264565b60025481565b6000546001600160a01b031681565b6001546001600160a01b031633146101b5576040805162461bcd60e51b815260206004820152601360248201527254485852656769737472793a2021706179656560681b604482015290519081900360640190fd5b6127108111156101f65760405162461bcd60e51b815260040180806020018281038252602581526020018061026b6025913960400191505060405180910390fd5b600255565b6004546001600160a01b031681565b6003546001600160a01b031681565b6001546001600160a01b031681565b6001546001600160a01b031690565b6003546001600160a01b031690565b6000546001600160a01b031690565b6004546001600160a01b031690565b6002549056fe54485852656769737472793a207061796f757452617465206f7574206f6620626f756e6473a26469706673582212209766fd3b31b5070d7ebcdb80534a3fdd12da7399c3e94c56888c415f0e1ef47064736f6c63430007060033",
+    "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100a95760003560e01c8063ae90b21311610071578063ae90b2131461011b578063cd44673514610123578063cf20d8721461012b578063d41c3a6514610133578063f2803f031461013b578063f47a455a14610143576100a9565b80632b4353f2146100ae5780633013ce29146100c857806348eaee66146100ec578063a6f19c841461010b578063acc2166a14610113575b600080fd5b6100b661014b565b60408051918252519081900360200190f35b6100d0610151565b604080516001600160a01b039092168252519081900360200190f35b6101096004803603602081101561010257600080fd5b5035610160565b005b6100d06101fb565b6100d061020a565b6100d0610219565b6100d0610228565b6100d0610237565b6100d0610246565b6100d0610255565b6100b6610264565b60025481565b6000546001600160a01b031681565b6001546001600160a01b031633146101b5576040805162461bcd60e51b815260206004820152601360248201527254485852656769737472793a2021706179656560681b604482015290519081900360640190fd5b6127108111156101f65760405162461bcd60e51b815260040180806020018281038252602581526020018061026b6025913960400191505060405180910390fd5b600255565b6004546001600160a01b031681565b6003546001600160a01b031681565b6001546001600160a01b031681565b6001546001600160a01b031690565b6003546001600160a01b031690565b6000546001600160a01b031690565b6004546001600160a01b031690565b6002549056fe54485852656769737472793a207061796f757452617465206f7574206f6620626f756e6473a26469706673582212209766fd3b31b5070d7ebcdb80534a3fdd12da7399c3e94c56888c415f0e1ef47064736f6c63430007060033",
+    "linkReferences": {},
+    "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/TestToken.json b/apps/api/src/app/hardhat/export/TestToken.json
new file mode 100644
index 000000000..0ea496224
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/TestToken.json
@@ -0,0 +1,304 @@
+{
+    "_format": "hh-sol-artifact-1",
+    "contractName": "TestToken",
+    "sourceName": "contracts/mock/Token.sol",
+    "abi": [
+        {
+            "inputs": [],
+            "stateMutability": "nonpayable",
+            "type": "constructor"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "owner",
+                    "type": "address"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "value",
+                    "type": "uint256"
+                }
+            ],
+            "name": "Approval",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "from",
+                    "type": "address"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "to",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "value",
+                    "type": "uint256"
+                }
+            ],
+            "name": "Transfer",
+            "type": "event"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "owner",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                }
+            ],
+            "name": "allowance",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "approve",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "account",
+                    "type": "address"
+                }
+            ],
+            "name": "balanceOf",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "decimals",
+            "outputs": [
+                {
+                    "internalType": "uint8",
+                    "name": "",
+                    "type": "uint8"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "subtractedValue",
+                    "type": "uint256"
+                }
+            ],
+            "name": "decreaseAllowance",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "addedValue",
+                    "type": "uint256"
+                }
+            ],
+            "name": "increaseAllowance",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "to",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "mint",
+            "outputs": [],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "name",
+            "outputs": [
+                {
+                    "internalType": "string",
+                    "name": "",
+                    "type": "string"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "symbol",
+            "outputs": [
+                {
+                    "internalType": "string",
+                    "name": "",
+                    "type": "string"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "totalSupply",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "to",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "transfer",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "from",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "to",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "transferFrom",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        }
+    ],
+    "bytecode": "0x608060405234801561001057600080fd5b5060405180604001604052806006815260200165546f6b656e3160d01b8152506040518060400160405280600681526020016553796d626c3160d01b815250816003908161005e9190610112565b50600461006b8282610112565b5050506101d1565b634e487b7160e01b600052604160045260246000fd5b600181811c9082168061009d57607f821691505b6020821081036100bd57634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111561010d57600081815260208120601f850160051c810160208610156100ea5750805b601f850160051c820191505b81811015610109578281556001016100f6565b5050505b505050565b81516001600160401b0381111561012b5761012b610073565b61013f816101398454610089565b846100c3565b602080601f831160018114610174576000841561015c5750858301515b600019600386901b1c1916600185901b178555610109565b600085815260208120601f198616915b828110156101a357888601518255948401946001909101908401610184565b50858210156101c15787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b61093f806101e06000396000f3fe608060405234801561001057600080fd5b50600436106100b45760003560e01c806340c10f191161007157806340c10f191461014157806370a082311461015657806395d89b411461017f578063a457c2d714610187578063a9059cbb1461019a578063dd62ed3e146101ad57600080fd5b806306fdde03146100b9578063095ea7b3146100d757806318160ddd146100fa57806323b872dd1461010c578063313ce5671461011f578063395093511461012e575b600080fd5b6100c16101c0565b6040516100ce9190610789565b60405180910390f35b6100ea6100e53660046107f3565b610252565b60405190151581526020016100ce565b6002545b6040519081526020016100ce565b6100ea61011a36600461081d565b61026c565b604051601281526020016100ce565b6100ea61013c3660046107f3565b610290565b61015461014f3660046107f3565b6102b2565b005b6100fe610164366004610859565b6001600160a01b031660009081526020819052604090205490565b6100c16102c0565b6100ea6101953660046107f3565b6102cf565b6100ea6101a83660046107f3565b61034f565b6100fe6101bb36600461087b565b61035d565b6060600380546101cf906108ae565b80601f01602080910402602001604051908101604052809291908181526020018280546101fb906108ae565b80156102485780601f1061021d57610100808354040283529160200191610248565b820191906000526020600020905b81548152906001019060200180831161022b57829003601f168201915b5050505050905090565b600033610260818585610388565b60019150505b92915050565b60003361027a8582856104ac565b610285858585610526565b506001949350505050565b6000336102608185856102a3838361035d565b6102ad91906108e8565b610388565b6102bc82826106ca565b5050565b6060600480546101cf906108ae565b600033816102dd828661035d565b9050838110156103425760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084015b60405180910390fd5b6102858286868403610388565b600033610260818585610526565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6001600160a01b0383166103ea5760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610339565b6001600160a01b03821661044b5760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610339565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b60006104b8848461035d565b9050600019811461052057818110156105135760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610339565b6105208484848403610388565b50505050565b6001600160a01b03831661058a5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610339565b6001600160a01b0382166105ec5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610339565b6001600160a01b038316600090815260208190526040902054818110156106645760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610339565b6001600160a01b03848116600081815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a3610520565b6001600160a01b0382166107205760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610339565b806002600082825461073291906108e8565b90915550506001600160a01b038216600081815260208181526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b600060208083528351808285015260005b818110156107b65785810183015185820160400152820161079a565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b03811681146107ee57600080fd5b919050565b6000806040838503121561080657600080fd5b61080f836107d7565b946020939093013593505050565b60008060006060848603121561083257600080fd5b61083b846107d7565b9250610849602085016107d7565b9150604084013590509250925092565b60006020828403121561086b57600080fd5b610874826107d7565b9392505050565b6000806040838503121561088e57600080fd5b610897836107d7565b91506108a5602084016107d7565b90509250929050565b600181811c908216806108c257607f821691505b6020821081036108e257634e487b7160e01b600052602260045260246000fd5b50919050565b8082018082111561026657634e487b7160e01b600052601160045260246000fdfea264697066735822122061db66c492e91059f7307777f5295b8df5b39f9055c2a3cb521f545cfe1b69c764736f6c63430008120033",
+    "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100b45760003560e01c806340c10f191161007157806340c10f191461014157806370a082311461015657806395d89b411461017f578063a457c2d714610187578063a9059cbb1461019a578063dd62ed3e146101ad57600080fd5b806306fdde03146100b9578063095ea7b3146100d757806318160ddd146100fa57806323b872dd1461010c578063313ce5671461011f578063395093511461012e575b600080fd5b6100c16101c0565b6040516100ce9190610789565b60405180910390f35b6100ea6100e53660046107f3565b610252565b60405190151581526020016100ce565b6002545b6040519081526020016100ce565b6100ea61011a36600461081d565b61026c565b604051601281526020016100ce565b6100ea61013c3660046107f3565b610290565b61015461014f3660046107f3565b6102b2565b005b6100fe610164366004610859565b6001600160a01b031660009081526020819052604090205490565b6100c16102c0565b6100ea6101953660046107f3565b6102cf565b6100ea6101a83660046107f3565b61034f565b6100fe6101bb36600461087b565b61035d565b6060600380546101cf906108ae565b80601f01602080910402602001604051908101604052809291908181526020018280546101fb906108ae565b80156102485780601f1061021d57610100808354040283529160200191610248565b820191906000526020600020905b81548152906001019060200180831161022b57829003601f168201915b5050505050905090565b600033610260818585610388565b60019150505b92915050565b60003361027a8582856104ac565b610285858585610526565b506001949350505050565b6000336102608185856102a3838361035d565b6102ad91906108e8565b610388565b6102bc82826106ca565b5050565b6060600480546101cf906108ae565b600033816102dd828661035d565b9050838110156103425760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084015b60405180910390fd5b6102858286868403610388565b600033610260818585610526565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6001600160a01b0383166103ea5760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610339565b6001600160a01b03821661044b5760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610339565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b60006104b8848461035d565b9050600019811461052057818110156105135760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610339565b6105208484848403610388565b50505050565b6001600160a01b03831661058a5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610339565b6001600160a01b0382166105ec5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610339565b6001600160a01b038316600090815260208190526040902054818110156106645760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610339565b6001600160a01b03848116600081815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a3610520565b6001600160a01b0382166107205760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610339565b806002600082825461073291906108e8565b90915550506001600160a01b038216600081815260208181526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b600060208083528351808285015260005b818110156107b65785810183015185820160400152820161079a565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b03811681146107ee57600080fd5b919050565b6000806040838503121561080657600080fd5b61080f836107d7565b946020939093013593505050565b60008060006060848603121561083257600080fd5b61083b846107d7565b9250610849602085016107d7565b9150604084013590509250925092565b60006020828403121561086b57600080fd5b610874826107d7565b9392505050565b6000806040838503121561088e57600080fd5b610897836107d7565b91506108a5602084016107d7565b90509250929050565b600181811c908216806108c257607f821691505b6020821081036108e257634e487b7160e01b600052602260045260246000fd5b50919050565b8082018082111561026657634e487b7160e01b600052601160045260246000fdfea264697066735822122061db66c492e91059f7307777f5295b8df5b39f9055c2a3cb521f545cfe1b69c764736f6c63430008120033",
+    "linkReferences": {},
+    "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/USDC.json b/apps/api/src/app/hardhat/export/USDC.json
new file mode 100644
index 000000000..56669e317
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/USDC.json
@@ -0,0 +1,297 @@
+{
+    "_format": "hh-sol-artifact-1",
+    "contractName": "USDC",
+    "sourceName": "contracts/mock/USDC.sol",
+    "abi": [
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "to",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "constructor"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "owner",
+                    "type": "address"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "value",
+                    "type": "uint256"
+                }
+            ],
+            "name": "Approval",
+            "type": "event"
+        },
+        {
+            "anonymous": false,
+            "inputs": [
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "from",
+                    "type": "address"
+                },
+                {
+                    "indexed": true,
+                    "internalType": "address",
+                    "name": "to",
+                    "type": "address"
+                },
+                {
+                    "indexed": false,
+                    "internalType": "uint256",
+                    "name": "value",
+                    "type": "uint256"
+                }
+            ],
+            "name": "Transfer",
+            "type": "event"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "owner",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                }
+            ],
+            "name": "allowance",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "approve",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "account",
+                    "type": "address"
+                }
+            ],
+            "name": "balanceOf",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "decimals",
+            "outputs": [
+                {
+                    "internalType": "uint8",
+                    "name": "",
+                    "type": "uint8"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "subtractedValue",
+                    "type": "uint256"
+                }
+            ],
+            "name": "decreaseAllowance",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "spender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "addedValue",
+                    "type": "uint256"
+                }
+            ],
+            "name": "increaseAllowance",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "name",
+            "outputs": [
+                {
+                    "internalType": "string",
+                    "name": "",
+                    "type": "string"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "symbol",
+            "outputs": [
+                {
+                    "internalType": "string",
+                    "name": "",
+                    "type": "string"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [],
+            "name": "totalSupply",
+            "outputs": [
+                {
+                    "internalType": "uint256",
+                    "name": "",
+                    "type": "uint256"
+                }
+            ],
+            "stateMutability": "view",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "recipient",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "transfer",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        },
+        {
+            "inputs": [
+                {
+                    "internalType": "address",
+                    "name": "sender",
+                    "type": "address"
+                },
+                {
+                    "internalType": "address",
+                    "name": "recipient",
+                    "type": "address"
+                },
+                {
+                    "internalType": "uint256",
+                    "name": "amount",
+                    "type": "uint256"
+                }
+            ],
+            "name": "transferFrom",
+            "outputs": [
+                {
+                    "internalType": "bool",
+                    "name": "",
+                    "type": "bool"
+                }
+            ],
+            "stateMutability": "nonpayable",
+            "type": "function"
+        }
+    ],
+    "bytecode": "0x60806040523480156200001157600080fd5b5060405162000d1938038062000d19833981810160405260408110156200003757600080fd5b508051602091820151604080518082018252600e81526d55534420436f696e2028506f532960901b81860190815282518084019093526006835265555344432e6560d01b9583019590955280519394929390926200009991600391906200026b565b508051620000af9060049060208401906200026b565b50506005805460ff1916601217905550620000cb6006620000df565b620000d78282620000f5565b505062000317565b6005805460ff191660ff92909216919091179055565b6001600160a01b03821662000151576040805162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b6200015f6000838362000204565b6200017b816002546200020960201b620005731790919060201c565b6002556001600160a01b03821660009081526020818152604090912054620001ae9183906200057362000209821b17901c565b6001600160a01b0383166000818152602081815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b505050565b60008282018381101562000264576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282620002a35760008555620002ee565b82601f10620002be57805160ff1916838001178555620002ee565b82800160010185558215620002ee579182015b82811115620002ee578251825591602001919060010190620002d1565b50620002fc92915062000300565b5090565b5b80821115620002fc576000815560010162000301565b6109f280620003276000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c8063395093511161007157806339509351146101d957806370a082311461020557806395d89b411461022b578063a457c2d714610233578063a9059cbb1461025f578063dd62ed3e1461028b576100a9565b806306fdde03146100ae578063095ea7b31461012b57806318160ddd1461016b57806323b872dd14610185578063313ce567146101bb575b600080fd5b6100b66102b9565b6040805160208082528351818301528351919283929083019185019080838360005b838110156100f05781810151838201526020016100d8565b50505050905090810190601f16801561011d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101576004803603604081101561014157600080fd5b506001600160a01b03813516906020013561034f565b604080519115158252519081900360200190f35b61017361036c565b60408051918252519081900360200190f35b6101576004803603606081101561019b57600080fd5b506001600160a01b03813581169160208101359091169060400135610372565b6101c36103f9565b6040805160ff9092168252519081900360200190f35b610157600480360360408110156101ef57600080fd5b506001600160a01b038135169060200135610402565b6101736004803603602081101561021b57600080fd5b50356001600160a01b0316610450565b6100b661046b565b6101576004803603604081101561024957600080fd5b506001600160a01b0381351690602001356104cc565b6101576004803603604081101561027557600080fd5b506001600160a01b038135169060200135610534565b610173600480360360408110156102a157600080fd5b506001600160a01b0381358116916020013516610548565b60038054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103455780601f1061031a57610100808354040283529160200191610345565b820191906000526020600020905b81548152906001019060200180831161032857829003601f168201915b5050505050905090565b600061036361035c6105d4565b84846105d8565b50600192915050565b60025490565b600061037f8484846106c4565b6103ef8461038b6105d4565b6103ea85604051806060016040528060288152602001610927602891396001600160a01b038a166000908152600160205260408120906103c96105d4565b6001600160a01b03168152602081019190915260400160002054919061081f565b6105d8565b5060019392505050565b60055460ff1690565b600061036361040f6105d4565b846103ea85600160006104206105d4565b6001600160a01b03908116825260208083019390935260409182016000908120918c168152925290205490610573565b6001600160a01b031660009081526020819052604090205490565b60048054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103455780601f1061031a57610100808354040283529160200191610345565b60006103636104d96105d4565b846103ea8560405180606001604052806025815260200161099860259139600160006105036105d4565b6001600160a01b03908116825260208083019390935260409182016000908120918d1681529252902054919061081f565b60006103636105416105d4565b84846106c4565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6000828201838110156105cd576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b3390565b6001600160a01b03831661061d5760405162461bcd60e51b81526004018080602001828103825260248152602001806109746024913960400191505060405180910390fd5b6001600160a01b0382166106625760405162461bcd60e51b81526004018080602001828103825260228152602001806108df6022913960400191505060405180910390fd5b6001600160a01b03808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b0383166107095760405162461bcd60e51b815260040180806020018281038252602581526020018061094f6025913960400191505060405180910390fd5b6001600160a01b03821661074e5760405162461bcd60e51b81526004018080602001828103825260238152602001806108bc6023913960400191505060405180910390fd5b6107598383836108b6565b61079681604051806060016040528060268152602001610901602691396001600160a01b038616600090815260208190526040902054919061081f565b6001600160a01b0380851660009081526020819052604080822093909355908416815220546107c59082610573565b6001600160a01b038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600081848411156108ae5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561087357818101518382015260200161085b565b50505050905090810190601f1680156108a05780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b50505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa26469706673582212201f8cdee0bd0e245ffd8ccb83c246c0b366118d610a54acf6eeed476dca2dfa1964736f6c63430007060033",
+    "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100a95760003560e01c8063395093511161007157806339509351146101d957806370a082311461020557806395d89b411461022b578063a457c2d714610233578063a9059cbb1461025f578063dd62ed3e1461028b576100a9565b806306fdde03146100ae578063095ea7b31461012b57806318160ddd1461016b57806323b872dd14610185578063313ce567146101bb575b600080fd5b6100b66102b9565b6040805160208082528351818301528351919283929083019185019080838360005b838110156100f05781810151838201526020016100d8565b50505050905090810190601f16801561011d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101576004803603604081101561014157600080fd5b506001600160a01b03813516906020013561034f565b604080519115158252519081900360200190f35b61017361036c565b60408051918252519081900360200190f35b6101576004803603606081101561019b57600080fd5b506001600160a01b03813581169160208101359091169060400135610372565b6101c36103f9565b6040805160ff9092168252519081900360200190f35b610157600480360360408110156101ef57600080fd5b506001600160a01b038135169060200135610402565b6101736004803603602081101561021b57600080fd5b50356001600160a01b0316610450565b6100b661046b565b6101576004803603604081101561024957600080fd5b506001600160a01b0381351690602001356104cc565b6101576004803603604081101561027557600080fd5b506001600160a01b038135169060200135610534565b610173600480360360408110156102a157600080fd5b506001600160a01b0381358116916020013516610548565b60038054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103455780601f1061031a57610100808354040283529160200191610345565b820191906000526020600020905b81548152906001019060200180831161032857829003601f168201915b5050505050905090565b600061036361035c6105d4565b84846105d8565b50600192915050565b60025490565b600061037f8484846106c4565b6103ef8461038b6105d4565b6103ea85604051806060016040528060288152602001610927602891396001600160a01b038a166000908152600160205260408120906103c96105d4565b6001600160a01b03168152602081019190915260400160002054919061081f565b6105d8565b5060019392505050565b60055460ff1690565b600061036361040f6105d4565b846103ea85600160006104206105d4565b6001600160a01b03908116825260208083019390935260409182016000908120918c168152925290205490610573565b6001600160a01b031660009081526020819052604090205490565b60048054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156103455780601f1061031a57610100808354040283529160200191610345565b60006103636104d96105d4565b846103ea8560405180606001604052806025815260200161099860259139600160006105036105d4565b6001600160a01b03908116825260208083019390935260409182016000908120918d1681529252902054919061081f565b60006103636105416105d4565b84846106c4565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6000828201838110156105cd576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b9392505050565b3390565b6001600160a01b03831661061d5760405162461bcd60e51b81526004018080602001828103825260248152602001806109746024913960400191505060405180910390fd5b6001600160a01b0382166106625760405162461bcd60e51b81526004018080602001828103825260228152602001806108df6022913960400191505060405180910390fd5b6001600160a01b03808416600081815260016020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b0383166107095760405162461bcd60e51b815260040180806020018281038252602581526020018061094f6025913960400191505060405180910390fd5b6001600160a01b03821661074e5760405162461bcd60e51b81526004018080602001828103825260238152602001806108bc6023913960400191505060405180910390fd5b6107598383836108b6565b61079681604051806060016040528060268152602001610901602691396001600160a01b038616600090815260208190526040902054919061081f565b6001600160a01b0380851660009081526020819052604080822093909355908416815220546107c59082610573565b6001600160a01b038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600081848411156108ae5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561087357818101518382015260200161085b565b50505050905090810190601f1680156108a05780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b50505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa26469706673582212201f8cdee0bd0e245ffd8ccb83c246c0b366118d610a54acf6eeed476dca2dfa1964736f6c63430007060033",
+    "linkReferences": {},
+    "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/export/VotingEscrow.json b/apps/api/src/app/hardhat/export/VotingEscrow.json
new file mode 100644
index 000000000..4f72b7728
--- /dev/null
+++ b/apps/api/src/app/hardhat/export/VotingEscrow.json
@@ -0,0 +1,1028 @@
+{
+    "_format": "hh-vyper-artifact-1",
+    "contractName": "VotingEscrow",
+    "sourceName": "contracts/VotingEscrow.vy",
+    "abi": [
+        {
+            "name": "CommitOwnership",
+            "inputs": [
+                {
+                    "name": "admin",
+                    "type": "address",
+                    "indexed": false
+                }
+            ],
+            "anonymous": false,
+            "type": "event"
+        },
+        {
+            "name": "ApplyOwnership",
+            "inputs": [
+                {
+                    "name": "admin",
+                    "type": "address",
+                    "indexed": false
+                }
+            ],
+            "anonymous": false,
+            "type": "event"
+        },
+        {
+            "name": "EarlyUnlock",
+            "inputs": [
+                {
+                    "name": "status",
+                    "type": "bool",
+                    "indexed": false
+                }
+            ],
+            "anonymous": false,
+            "type": "event"
+        },
+        {
+            "name": "PenaltySpeed",
+            "inputs": [
+                {
+                    "name": "penalty_k",
+                    "type": "uint256",
+                    "indexed": false
+                }
+            ],
+            "anonymous": false,
+            "type": "event"
+        },
+        {
+            "name": "PenaltyTreasury",
+            "inputs": [
+                {
+                    "name": "penalty_treasury",
+                    "type": "address",
+                    "indexed": false
+                }
+            ],
+            "anonymous": false,
+            "type": "event"
+        },
+        {
+            "name": "TotalUnlock",
+            "inputs": [
+                {
+                    "name": "status",
+                    "type": "bool",
+                    "indexed": false
+                }
+            ],
+            "anonymous": false,
+            "type": "event"
+        },
+        {
+            "name": "RewardReceiver",
+            "inputs": [
+                {
+                    "name": "newReceiver",
+                    "type": "address",
+                    "indexed": false
+                }
+            ],
+            "anonymous": false,
+            "type": "event"
+        },
+        {
+            "name": "Deposit",
+            "inputs": [
+                {
+                    "name": "provider",
+                    "type": "address",
+                    "indexed": true
+                },
+                {
+                    "name": "value",
+                    "type": "uint256",
+                    "indexed": false
+                },
+                {
+                    "name": "locktime",
+                    "type": "uint256",
+                    "indexed": true
+                },
+                {
+                    "name": "type",
+                    "type": "int128",
+                    "indexed": false
+                },
+                {
+                    "name": "ts",
+                    "type": "uint256",
+                    "indexed": false
+                }
+            ],
+            "anonymous": false,
+            "type": "event"
+        },
+        {
+            "name": "Withdraw",
+            "inputs": [
+                {
+                    "name": "provider",
+                    "type": "address",
+                    "indexed": true
+                },
+                {
+                    "name": "value",
+                    "type": "uint256",
+                    "indexed": false
+                },
+                {
+                    "name": "ts",
+                    "type": "uint256",
+                    "indexed": false
+                }
+            ],
+            "anonymous": false,
+            "type": "event"
+        },
+        {
+            "name": "WithdrawEarly",
+            "inputs": [
+                {
+                    "name": "provider",
+                    "type": "address",
+                    "indexed": true
+                },
+                {
+                    "name": "penalty",
+                    "type": "uint256",
+                    "indexed": false
+                },
+                {
+                    "name": "time_left",
+                    "type": "uint256",
+                    "indexed": false
+                }
+            ],
+            "anonymous": false,
+            "type": "event"
+        },
+        {
+            "name": "Supply",
+            "inputs": [
+                {
+                    "name": "prevSupply",
+                    "type": "uint256",
+                    "indexed": false
+                },
+                {
+                    "name": "supply",
+                    "type": "uint256",
+                    "indexed": false
+                }
+            ],
+            "anonymous": false,
+            "type": "event"
+        },
+        {
+            "stateMutability": "nonpayable",
+            "type": "function",
+            "name": "initialize",
+            "inputs": [
+                {
+                    "name": "_token_addr",
+                    "type": "address"
+                },
+                {
+                    "name": "_name",
+                    "type": "string"
+                },
+                {
+                    "name": "_symbol",
+                    "type": "string"
+                },
+                {
+                    "name": "_admin_addr",
+                    "type": "address"
+                },
+                {
+                    "name": "_admin_unlock_all",
+                    "type": "address"
+                },
+                {
+                    "name": "_admin_early_unlock",
+                    "type": "address"
+                },
+                {
+                    "name": "_max_time",
+                    "type": "uint256"
+                },
+                {
+                    "name": "_balToken",
+                    "type": "address"
+                },
+                {
+                    "name": "_balMinter",
+                    "type": "address"
+                },
+                {
+                    "name": "_rewardReceiver",
+                    "type": "address"
+                },
+                {
+                    "name": "_rewardReceiverChangeable",
+                    "type": "bool"
+                },
+                {
+                    "name": "_rewardDistributor",
+                    "type": "address"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "token",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "address"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "name",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "string"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "symbol",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "string"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "decimals",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "uint256"
+                }
+            ]
+        },
+        {
+            "stateMutability": "nonpayable",
+            "type": "function",
+            "name": "commit_transfer_ownership",
+            "inputs": [
+                {
+                    "name": "addr",
+                    "type": "address"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "stateMutability": "nonpayable",
+            "type": "function",
+            "name": "apply_transfer_ownership",
+            "inputs": [],
+            "outputs": []
+        },
+        {
+            "stateMutability": "nonpayable",
+            "type": "function",
+            "name": "commit_smart_wallet_checker",
+            "inputs": [
+                {
+                    "name": "addr",
+                    "type": "address"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "stateMutability": "nonpayable",
+            "type": "function",
+            "name": "apply_smart_wallet_checker",
+            "inputs": [],
+            "outputs": []
+        },
+        {
+            "stateMutability": "nonpayable",
+            "type": "function",
+            "name": "set_early_unlock",
+            "inputs": [
+                {
+                    "name": "_early_unlock",
+                    "type": "bool"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "stateMutability": "nonpayable",
+            "type": "function",
+            "name": "set_early_unlock_penalty_speed",
+            "inputs": [
+                {
+                    "name": "_penalty_k",
+                    "type": "uint256"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "stateMutability": "nonpayable",
+            "type": "function",
+            "name": "set_penalty_treasury",
+            "inputs": [
+                {
+                    "name": "_penalty_treasury",
+                    "type": "address"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "stateMutability": "nonpayable",
+            "type": "function",
+            "name": "set_all_unlock",
+            "inputs": [],
+            "outputs": []
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "get_last_user_slope",
+            "inputs": [
+                {
+                    "name": "addr",
+                    "type": "address"
+                }
+            ],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "int128"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "user_point_history__ts",
+            "inputs": [
+                {
+                    "name": "_addr",
+                    "type": "address"
+                },
+                {
+                    "name": "_idx",
+                    "type": "uint256"
+                }
+            ],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "uint256"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "locked__end",
+            "inputs": [
+                {
+                    "name": "_addr",
+                    "type": "address"
+                }
+            ],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "uint256"
+                }
+            ]
+        },
+        {
+            "stateMutability": "nonpayable",
+            "type": "function",
+            "name": "checkpoint",
+            "inputs": [],
+            "outputs": []
+        },
+        {
+            "stateMutability": "nonpayable",
+            "type": "function",
+            "name": "deposit_for",
+            "inputs": [
+                {
+                    "name": "_addr",
+                    "type": "address"
+                },
+                {
+                    "name": "_value",
+                    "type": "uint256"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "stateMutability": "nonpayable",
+            "type": "function",
+            "name": "create_lock",
+            "inputs": [
+                {
+                    "name": "_value",
+                    "type": "uint256"
+                },
+                {
+                    "name": "_unlock_time",
+                    "type": "uint256"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "stateMutability": "nonpayable",
+            "type": "function",
+            "name": "increase_amount",
+            "inputs": [
+                {
+                    "name": "_value",
+                    "type": "uint256"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "stateMutability": "nonpayable",
+            "type": "function",
+            "name": "increase_unlock_time",
+            "inputs": [
+                {
+                    "name": "_unlock_time",
+                    "type": "uint256"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "stateMutability": "nonpayable",
+            "type": "function",
+            "name": "withdraw",
+            "inputs": [],
+            "outputs": []
+        },
+        {
+            "stateMutability": "nonpayable",
+            "type": "function",
+            "name": "withdraw_early",
+            "inputs": [],
+            "outputs": []
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "balanceOf",
+            "inputs": [
+                {
+                    "name": "addr",
+                    "type": "address"
+                }
+            ],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "uint256"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "balanceOf",
+            "inputs": [
+                {
+                    "name": "addr",
+                    "type": "address"
+                },
+                {
+                    "name": "_t",
+                    "type": "uint256"
+                }
+            ],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "uint256"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "balanceOfAt",
+            "inputs": [
+                {
+                    "name": "addr",
+                    "type": "address"
+                },
+                {
+                    "name": "_block",
+                    "type": "uint256"
+                }
+            ],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "uint256"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "totalSupply",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "uint256"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "totalSupply",
+            "inputs": [
+                {
+                    "name": "t",
+                    "type": "uint256"
+                }
+            ],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "uint256"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "totalSupplyAt",
+            "inputs": [
+                {
+                    "name": "_block",
+                    "type": "uint256"
+                }
+            ],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "uint256"
+                }
+            ]
+        },
+        {
+            "stateMutability": "nonpayable",
+            "type": "function",
+            "name": "claimExternalRewards",
+            "inputs": [],
+            "outputs": []
+        },
+        {
+            "stateMutability": "nonpayable",
+            "type": "function",
+            "name": "changeRewardReceiver",
+            "inputs": [
+                {
+                    "name": "newReceiver",
+                    "type": "address"
+                }
+            ],
+            "outputs": []
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "MAXTIME",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "uint256"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "TOKEN",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "address"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "supply",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "uint256"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "locked",
+            "inputs": [
+                {
+                    "name": "arg0",
+                    "type": "address"
+                }
+            ],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "tuple",
+                    "components": [
+                        {
+                            "name": "amount",
+                            "type": "int128"
+                        },
+                        {
+                            "name": "end",
+                            "type": "uint256"
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "epoch",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "uint256"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "point_history",
+            "inputs": [
+                {
+                    "name": "arg0",
+                    "type": "uint256"
+                }
+            ],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "tuple",
+                    "components": [
+                        {
+                            "name": "bias",
+                            "type": "int128"
+                        },
+                        {
+                            "name": "slope",
+                            "type": "int128"
+                        },
+                        {
+                            "name": "ts",
+                            "type": "uint256"
+                        },
+                        {
+                            "name": "blk",
+                            "type": "uint256"
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "user_point_history",
+            "inputs": [
+                {
+                    "name": "arg0",
+                    "type": "address"
+                },
+                {
+                    "name": "arg1",
+                    "type": "uint256"
+                }
+            ],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "tuple",
+                    "components": [
+                        {
+                            "name": "bias",
+                            "type": "int128"
+                        },
+                        {
+                            "name": "slope",
+                            "type": "int128"
+                        },
+                        {
+                            "name": "ts",
+                            "type": "uint256"
+                        },
+                        {
+                            "name": "blk",
+                            "type": "uint256"
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "user_point_epoch",
+            "inputs": [
+                {
+                    "name": "arg0",
+                    "type": "address"
+                }
+            ],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "uint256"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "slope_changes",
+            "inputs": [
+                {
+                    "name": "arg0",
+                    "type": "uint256"
+                }
+            ],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "int128"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "future_smart_wallet_checker",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "address"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "smart_wallet_checker",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "address"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "admin",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "address"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "admin_unlock_all",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "address"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "admin_early_unlock",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "address"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "future_admin",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "address"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "is_initialized",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "bool"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "early_unlock",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "bool"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "penalty_k",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "uint256"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "prev_penalty_k",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "uint256"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "penalty_upd_ts",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "uint256"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "penalty_treasury",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "address"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "balMinter",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "address"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "balToken",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "address"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "rewardReceiver",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "address"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "rewardReceiverChangeable",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "bool"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "rewardDistributor",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "address"
+                }
+            ]
+        },
+        {
+            "stateMutability": "view",
+            "type": "function",
+            "name": "all_unlock",
+            "inputs": [],
+            "outputs": [
+                {
+                    "name": "",
+                    "type": "bool"
+                }
+            ]
+        }
+    ],
+    "bytecode": "0x613bea61001161000039613bea610000f36003361161000c57612d19565b60003560e01c34613bd857639bf6abf3811861047c576101c43610613bd8576004358060a01c613bd8576040526024356004016040813511613bd8578035806060526020820181816080375050506044356004016020813511613bd85780358060c05260208201803560e0525050506064358060a01c613bd857610100526084358060a01c613bd8576101205260a4358060a01c613bd8576101405260e4358060a01c613bd85761016052610104358060a01c613bd85761018052610124358060a01c613bd8576101a052610144358060011c613bd8576101c052610164358060a01c613bd8576101e0526c050c783eb9b5c84000000000155415610171576009610200527f6f6e6c79206f6e636500000000000000000000000000000000000000000000006102205261020050610200518061022001601f826000031636823750506308c379a06101c05260206101e052601f19601f6102005101166044016101dcfd5b60016c050c783eb9b5c840000000001555610100516101f0576006610200527f21656d70747900000000000000000000000000000000000000000000000000006102205261020050610200518061022001601f826000031636823750506308c379a06101c05260206101e052601f19601f6102005101166044016101dcfd5b610100516c050c783eb9b5c840000000001155600a6c050c783eb9b5c840000000001755600a6c050c783eb9b5c840000000001855426c050c783eb9b5c840000000001955610100516c050c783eb9b5c840000000001a5560405160025543600f5542600e5560405163313ce567610220526020610220600461023c845afa61027e573d600060003e3d6000fd5b60203d10613bd8576102209050516102005260066102005110156102a35760006102ac565b60ff6102005111155b610316576009610220527f21646563696d616c7300000000000000000000000000000000000000000000006102405261022050610220518061024001601f826000031636823750506308c379a06101e052602061020052601f19601f6102205101166044016101fcfd5b60605180600355600081601f0160051c60028111613bd857801561034e57905b8060051b608001518160040155600101818118610336575b50505060c0518060065560e051600755506102005160085562093a8060c435101561037a576000610385565b63095f6a0060c43511155b6103ef576008610220527f216d61786c6f636b0000000000000000000000000000000000000000000000006102405261022050610220518061024001601f826000031636823750506308c379a06101e052602061020052601f19601f6102205101166044016101fcfd5b60c435600155610120516c050c783eb9b5c840000000001255610140516c050c783eb9b5c840000000001355610160516c050c783eb9b5c840000000001c55610180516c050c783eb9b5c840000000001b556101a0516c050c783eb9b5c840000000001d556101c0516c050c783eb9b5c840000000001e556101e0516c050c783eb9b5c840000000001f55005b63fc0c546a811861049b5760043610613bd85760025460405260206040f35b6306fdde0381186105205760043610613bd8576020806040528060400160035480825260208201600082601f0160051c60028111613bd85780156104f257905b80600401548160051b8401526001018181186104db575b505050508051806020830101601f82600003163682375050601f19601f825160200101169050810190506040f35b6395d89b4181186105785760043610613bd8576020806040528060400160065480825260208201600754815250508051806020830101601f82600003163682375050601f19601f825160200101169050810190506040f35b63313ce56781186105975760043610613bd85760085460405260206040f35b636b441a40811861060d5760243610613bd8576004358060a01c613bd8576040526c050c783eb9b5c8400000000011543318613bd8576040516c050c783eb9b5c8400000000014557f2f56810a6bf40af059b96d3aea4db54081f378029a518390491093a7b67032e960405160605260206060a1005b636a1c05ae811861068f5760043610613bd8576c050c783eb9b5c8400000000011543318613bd8576c050c783eb9b5c84000000000145460405260405115613bd8576040516c050c783eb9b5c8400000000011557febee2d5739011062cb4f14113f3b36bf0ffe3da5c0568f64189d1012a118910560405160605260206060a1005b6357f901e281186106d95760243610613bd8576004358060a01c613bd8576040526c050c783eb9b5c8400000000011543318613bd8576040516c050c783eb9b5c840000000000f55005b638e5b490f81186107215760043610613bd8576c050c783eb9b5c8400000000011543318613bd8576c050c783eb9b5c840000000000f546c050c783eb9b5c840000000001055005b6355a3323581186108695760243610613bd8576004358060011c613bd8576040526c050c783eb9b5c8400000000013543318156107b55760066060527f2161646d696e000000000000000000000000000000000000000000000000000060805260605060605180608001601f826000031636823750506308c379a06020526020604052601f19601f6060510116604401603cfd5b6c050c783eb9b5c840000000001654604051186108295760076060527f616c72656164790000000000000000000000000000000000000000000000000060805260605060605180608001601f826000031636823750506308c379a06020526020604052601f19601f6060510116604401603cfd5b6040516c050c783eb9b5c8400000000016557f66515f71c349ef0ad8c6981cedaa58746200512e6e12754c5ac5cc701d5cf41860405160605260206060a1005b63655317ae8118610a445760243610613bd8576c050c783eb9b5c8400000000013543318156108ef5760066040527f2161646d696e000000000000000000000000000000000000000000000000000060605260405060405180606001601f826000031636823750506308c379a06000526020602052601f19601f6040510116604401601cfd5b603260043511156109575760026040527f216b00000000000000000000000000000000000000000000000000000000000060605260405060405180606001601f826000031636823750506308c379a06000526020602052601f19601f6040510116604401601cfd5b6c050c783eb9b5c840000000001954603c8101818110613bd857905042116109d65760056040527f6561726c7900000000000000000000000000000000000000000000000000000060605260405060405180606001601f826000031636823750506308c379a06000526020602052601f19601f6040510116604401601cfd5b6c050c783eb9b5c8400000000017546c050c783eb9b5c8400000000018556004356c050c783eb9b5c840000000001755426c050c783eb9b5c8400000000019557f9c04360e3c3de1eeca25bbd4a21cdc22c4c192f4b91f91d2a0c59dcd042f8ba860043560405260206040a1005b63af3097af8118610b7c5760243610613bd8576004358060a01c613bd8576040526c050c783eb9b5c840000000001354331815610ad85760066060527f2161646d696e000000000000000000000000000000000000000000000000000060805260605060605180608001601f826000031636823750506308c379a06020526020604052601f19601f6060510116604401603cfd5b604051610b3c5760056060527f217a65726f00000000000000000000000000000000000000000000000000000060805260605060605180608001601f826000031636823750506308c379a06020526020604052601f19601f6060510116604401603cfd5b6040516c050c783eb9b5c840000000001a557ff51c492eafc918620c2d49b196e6a4e0cac71709d348224a8e7fc231ee973e5960405160605260206060a1005b6366e01ca98118610c405760043610613bd8576c050c783eb9b5c840000000001254331815610c025760066040527f2161646d696e000000000000000000000000000000000000000000000000000060605260405060405180606001601f826000031636823750506308c379a06000526020602052601f19601f6040510116604401601cfd5b60016c050c783eb9b5c8400000000020557f7ca88488f569b55d1fb073429f75839e505182c1f6d26e5caed22e9828df430c600160405260206040a1005b637c74a1748118610cc25760243610613bd8576004358060a01c613bd8576040526c050c783eb9b5c840000000000d6040516020526000526040600020546060526c050c783eb9b5c840000000000c6040516020526000526040600020606051633b9ac9ff8111613bd85760021b810190506001810190505460805260206080f35b63da020a188118610d245760443610613bd8576004358060a01c613bd8576040526c050c783eb9b5c840000000000c6040516020526000526040600020602435633b9ac9ff8111613bd85760021b810190506002810190505460605260206060f35b63adc635898118610d655760243610613bd8576004358060a01c613bd857604052600a60405160205260005260406000206001810190505460605260206060f35b63c2c4c5c18118610d885760043610613bd85760a036604037610d86612e17565b005b633a46273e8118610f345760443610613bd8576004358060a01c613bd8576105e052600054600214613bd8576002600055600a6105e05160205260005260406000208054610600526001810154610620525060243515613bd8576001610600511215610e54576016610640527f4e6f206578697374696e67206c6f636b20666f756e64000000000000000000006106605261064050610640518061066001601f826000031636823750506308c379a061060052602061062052601f19601f61064051011660440161061cfd5b426106205111610ee9576024610640527f43616e6e6f742061646420746f2065787069726564206c6f636b2e2057697468610660527f64726177000000000000000000000000000000000000000000000000000000006106805261064050610640518061066001601f826000031636823750506308c379a061060052602061062052601f19601f61064051011660440161061cfd5b6105e0516103e05260243561040052600061042052600a6105e051602052600052604060002080546104405260018101546104605250600061048052610f2d61352a565b6003600055005b6365fc3873811861116a5760443610613bd857600054600214613bd857600260005533604052610f62612d1f565b60243562093a808104905062093a8081028162093a80820418613bd85790506105e052600a3360205260005260406000208054610600526001810154610620525060043515613bd857610600511561101a576019610640527f5769746864726177206f6c6420746f6b656e73206669727374000000000000006106605261064050610640518061066001601f826000031636823750506308c379a061060052602061062052601f19601f61064051011660440161061cfd5b426105e051116110af576026610640527f43616e206f6e6c79206c6f636b20756e74696c2074696d6520696e2074686520610660527f66757475726500000000000000000000000000000000000000000000000000006106805261064050610640518061066001601f826000031636823750506308c379a061060052602061062052601f19601f61064051011660440161061cfd5b42600154808201828110613bd857905090506105e0511115611131576014610640527f566f74696e67206c6f636b20746f6f206c6f6e670000000000000000000000006106605261064050610640518061066001601f826000031636823750506308c379a061060052602061062052601f19601f61064051011660440161061cfd5b336103e052600435610400526105e051610420526106005161044052610620516104605260016104805261116361352a565b6003600055005b634957677c81186112fc5760243610613bd857600054600214613bd857600260005533604052611198612d1f565b600a33602052600052604060002080546105e0526001810154610600525060043515613bd85760016105e0511215611230576016610620527f4e6f206578697374696e67206c6f636b20666f756e64000000000000000000006106405261062050610620518061064001601f826000031636823750506308c379a06105e052602061060052601f19601f6106205101166044016105fcfd5b4261060051116112c5576024610620527f43616e6e6f742061646420746f2065787069726564206c6f636b2e2057697468610640527f64726177000000000000000000000000000000000000000000000000000000006106605261062050610620518061064001601f826000031636823750506308c379a06105e052602061060052601f19601f6106205101166044016105fcfd5b336103e052600435610400526000610420526105e0516104405261060051610460526002610480526112f561352a565b6003600055005b63eff7a612811861157a5760243610613bd857600054600214613bd85760026000553360405261132a612d1f565b600a33602052600052604060002080546105e0526001810154610600525060043562093a808104905062093a8081028162093a80820418613bd8579050610620524261060051116113db57600c610640527f4c6f636b206578706972656400000000000000000000000000000000000000006106605261064050610640518061066001601f826000031636823750506308c379a061060052602061062052601f19601f61064051011660440161061cfd5b60016105e051121561144d576011610640527f4e6f7468696e67206973206c6f636b65640000000000000000000000000000006106605261064050610640518061066001601f826000031636823750506308c379a061060052602061062052601f19601f61064051011660440161061cfd5b6106005161062051116114c057601f610640527f43616e206f6e6c7920696e637265617365206c6f636b206475726174696f6e006106605261064050610640518061066001601f826000031636823750506308c379a061060052602061062052601f19601f61064051011660440161061cfd5b42600154808201828110613bd85790509050610620511115611542576014610640527f566f74696e67206c6f636b20746f6f206c6f6e670000000000000000000000006106605261064050610640518061066001601f826000031636823750506308c379a061060052602061062052601f19601f61064051011660440161061cfd5b336103e05260006104005261062051610420526105e05161044052610600516104605260036104805261157361352a565b6003600055005b633ccfd60b81186117cf5760043610613bd857600054600214613bd8576002600055600a33602052600052604060002080546103e05260018101546104005250610400514210156115d9576c050c783eb9b5c8400000000020546115dc565b60015b611646576017610420527f6c6f636b2021657870697265206f722021756e6c6f636b0000000000000000006104405261042050610420518061044001601f826000031636823750506308c379a06103e052602061040052601f19601f6104205101166044016103fcfd5b6103e05160008112613bd857610420526103e05161044052610400516104605260006104005260006103e052600a3360205260005260406000206103e051815561040051600182015550600954610480526104805161042051808203828111613bd857905090506009553360405261044051606052610460516080526103e05160a0526104005160c0526116d8612e17565b60025463a9059cbb6104a052336104c052610420516104e05260206104a060446104bc6000855af161170f573d600060003e3d6000fd5b3d61172657803b15613bd85760016105005261173f565b60203d10613bd8576104a0518060011c613bd857610500525b61050090505115613bd857337ff279e6a1f5e320cca91135676d9cb6e44ca8a08c0b88342bcdb1144f6511b568610420516104a052426104c05260406104a0a27f5e2aa66efd74cce82b21852e317e5490d9ecc9e6bb953ae24d90851258cc2f5c610480516104a0526104805161042051808203828111613bd857905090506104c05260406104a0a16003600055005b638239f0648118611c6e5760043610613bd857600054600214613bd857600260005560016c050c783eb9b5c840000000001654181561186e57600d6103e0527f216561726c7920756e6c6f636b00000000000000000000000000000000000000610400526103e0506103e0518061040001601f826000031636823750506308c379a06103a05260206103c052601f19601f6103e05101166044016103bcfd5b600a33602052600052604060002080546103e052600181015461040052506104005142106118fc57600c610420527f6c6f636b206578706972656400000000000000000000000000000000000000006104405261042050610420518061044001601f826000031636823750506308c379a06103e052602061040052601f19601f6104205101166044016103fcfd5b6103e05160008112613bd857610420526104005142808203828111613bd85790509050610440526000610460526c050c783eb9b5c840000000001954603c8101818110613bd85790504211611963576c050c783eb9b5c84000000000185461046052611977565b6c050c783eb9b5c840000000001754610460525b61044051670de0b6b3a7640000810281670de0b6b3a7640000820418613bd85790506001548015613bd8578082049050905061046051808202811583838304141715613bd85790509050610480526104205161048051808202811583838304141715613bd85790509050670de0b6b3a764000081049050600a810490506104a052610420516104a0511115611a0f57610420516104a0525b610420516104a051808203828111613bd857905090506104c0526103e0516104e052610400516105005260006104005260006103e052600a3360205260005260406000206103e051815561040051600182015550600954610520526105205161042051808203828111613bd85790509050600955336040526104e051606052610500516080526103e05160a0526104005160c052611aab612e17565b6104a05115611b355760025463a9059cbb610540526c050c783eb9b5c840000000001a54610560526104a051610580526020610540604461055c6000855af1611af9573d600060003e3d6000fd5b3d611b1057803b15613bd85760016105a052611b29565b60203d10613bd857610540518060011c613bd8576105a0525b6105a090505115613bd8575b6104c05115611bb15760025463a9059cbb6105405233610560526104c051610580526020610540604461055c6000855af1611b75573d600060003e3d6000fd5b3d611b8c57803b15613bd85760016105a052611ba5565b60203d10613bd857610540518060011c613bd8576105a0525b6105a090505115613bd8575b337ff279e6a1f5e320cca91135676d9cb6e44ca8a08c0b88342bcdb1144f6511b568610420516105405242610560526040610540a27f5e2aa66efd74cce82b21852e317e5490d9ecc9e6bb953ae24d90851258cc2f5c61052051610540526105205161042051808203828111613bd85790509050610560526040610540a1337ff11a1a5a36ad286d4f77c166e8d87e5bfe87d1e74f9c46654ef21bd811c6784f6104a0516105405261044051610560526040610540a26003600055005b6370a082318118611c8b5760243610613bd8574261014052611ca5565b62fdd58e8118611e345760443610613bd857602435610140525b6004358060a01c613bd85761012052600061016052426101405118611ceb576c050c783eb9b5c840000000000d6101205160205260005260406000205461016052611d2e565b61012051604052610140516060526c050c783eb9b5c840000000000d61012051602052600052604060002054608052611d256101806139a2565b61018051610160525b61016051611d4a576000610180526020610180611e3256611e32565b6c050c783eb9b5c840000000000c61012051602052600052604060002061016051633b9ac9ff8111613bd85760021b8101905080546101805260018101546101a05260028101546101c05260038101546101e05250610180516101a051610140516101c051808203828111613bd8579050905080607f1c613bd85780820280600f0b8118613bd8579050905080820380600f0b8118613bd85790509050610180527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6101805113611e1c576000610180525b6101805160008112613bd8576102005260206102005bf35b634ee2cd7e811861210f5760443610613bd8576004358060a01c613bd857610120524360243511613bd857610120516040526024356060526c050c783eb9b5c840000000000d61012051602052600052604060002054608052611e986101606138da565b61016051610140526c050c783eb9b5c840000000000c61012051602052600052604060002061014051633b9ac9ff8111613bd85760021b8101905080546101605260018101546101805260028101546101a05260038101546101c05250600b546101e0526024356040526101e051606052611f1461022061377a565b6102205161020052610200516c01431e0fae6d7217ca9fffffff8111613bd85760021b600c01805461022052600181015461024052600281015461026052600381015461028052506040366102a0376101e0516102005110611fa3574361028051808203828111613bd857905090506102a0524261026051808203828111613bd857905090506102c052612025565b6102005160018101818110613bd85790506c01431e0fae6d7217ca9fffffff8111613bd85760021b600c0180546102e052600181015461030052600281015461032052600381015461034052506103405161028051808203828111613bd857905090506102a0526103205161026051808203828111613bd857905090506102c0525b610260516102e0526102a0511561208b576102e0516102c05160243561028051808203828111613bd85790509050808202811583838304141715613bd857905090506102a0518015613bd85780820490509050808201828110613bd857905090506102e0525b61016051610180516102e0516101a051808203828111613bd8579050905080607f1c613bd85780820280600f0b8118613bd8579050905080820380600f0b8118613bd857905090506101605260006101605112156120f757600061030052602061030061210d5661210d565b6101605160008112613bd8576103005260206103005bf35b6318160ddd811861212c5760043610613bd857426101c052612147565b63bd85b03981186122165760243610613bd8576004356101c0525b60006101e052426101c0511861216357600b546101e052612184565b6101c051604052600b5460605261217b61020061382a565b610200516101e0525b6101e0516121a057600061020052602061020061221456612214565b6101e0516c01431e0fae6d7217ca9fffffff8111613bd85760021b600c018054610200526001810154610220526002810154610240526003810154610260525060206102005160405261022051606052610240516080526102605160a0526101c05160c052612210610280613a6a565b6102805bf35b63981b24d081186124235760243610613bd8574360043511613bd857600b546101c0526004356040526101c05160605261225161020061377a565b610200516101e0526101e0516c01431e0fae6d7217ca9fffffff8111613bd85760021b600c01805461020052600181015461022052600281015461024052600381015461026052506000610280526101c0516101e0511061231b574361026051146123dc5760043561026051808203828111613bd857905090504261024051808203828111613bd85790509050808202811583838304141715613bd857905090504361026051808203828111613bd857905090508015613bd85780820490509050610280526123dc565b6101e05160018101818110613bd85790506c01431e0fae6d7217ca9fffffff8111613bd85760021b600c0180546102a05260018101546102c05260028101546102e052600381015461030052506103005161026051146123dc5760043561026051808203828111613bd857905090506102e05161024051808203828111613bd85790509050808202811583838304141715613bd857905090506103005161026051808203828111613bd857905090508015613bd85780820490509050610280525b60206102005160405261022051606052610240516080526102605160a0526102405161028051808201828110613bd8579050905060c05261241e6102a0613a6a565b6102a0f35b63db93cc7a811861265e5760043610613bd857600054600214613bd85760026000556c050c783eb9b5c840000000001b54636a627842604052600254606052602060406024605c6000855af161247e573d600060003e3d6000fd5b60203d10613bd857604050506c050c783eb9b5c840000000001c546370a0823160605230608052602060606024607c845afa6124bf573d600060003e3d6000fd5b60203d10613bd857606090505160405260405115612657576c050c783eb9b5c840000000001f546c050c783eb9b5c840000000001d54186125d4576c050c783eb9b5c840000000001c5463095ea7b36060526c050c783eb9b5c840000000001f5460805260405160a052602060606044607c6000855af1612545573d600060003e3d6000fd5b3d61255b57803b15613bd857600160c052612572565b60203d10613bd8576060518060011c613bd85760c0525b60c090505115613bd8576c050c783eb9b5c840000000001f5463338b5dea6060526c050c783eb9b5c840000000001c5460805260405160a052803b15613bd857600060606044607c6000855af16125ce573d600060003e3d6000fd5b50612657565b6c050c783eb9b5c840000000001c5463a9059cbb6060526c050c783eb9b5c840000000001d5460805260405160a052602060606044607c6000855af161261f573d600060003e3d6000fd5b3d61263557803b15613bd857600160c05261264c565b60203d10613bd8576060518060011c613bd85760c0525b60c090505115613bd8575b6003600055005b63cb8e090d81186128065760243610613bd8576004358060a01c613bd8576040526c050c783eb9b5c8400000000011543318156126f25760066060527f2161646d696e000000000000000000000000000000000000000000000000000060805260605060605180608001601f826000031636823750506308c379a06020526020604052601f19601f6060510116604401603cfd5b6c050c783eb9b5c840000000001e5461276257600a6060527f21617661696c61626c650000000000000000000000000000000000000000000060805260605060605180608001601f826000031636823750506308c379a06020526020604052601f19601f6060510116604401603cfd5b6040516127c65760066060527f21656d707479000000000000000000000000000000000000000000000000000060805260605060605180608001601f826000031636823750506308c379a06020526020604052601f19601f6060510116604401603cfd5b6040516c050c783eb9b5c840000000001d557f454ccf21cdac2e344d64173597f5922657c25c5b1e6c3f733791637513d2063760405160605260206060a1005b63ee00ef3a81186128255760043610613bd85760015460405260206040f35b6382bfefc881186128445760043610613bd85760025460405260206040f35b63047fc9aa81186128635760043610613bd85760095460405260206040f35b63cbf9fe5f81186128a85760243610613bd8576004358060a01c613bd857604052600a6040516020526000526040600020805460605260018101546080525060406060f35b63900cf0cf81186128c75760043610613bd857600b5460405260206040f35b63d1febfb9811861291b5760243610613bd8576004356c01431e0fae6d7217ca9fffffff8111613bd85760021b600c01805460405260018101546060526002810154608052600381015460a0525060806040f35b6328d09d4781186129915760443610613bd8576004358060a01c613bd8576040526c050c783eb9b5c840000000000c6040516020526000526040600020602435633b9ac9ff8111613bd85760021b8101905080546060526001810154608052600281015460a052600381015460c0525060806060f35b63010ae75781186129d85760243610613bd8576004358060a01c613bd8576040526c050c783eb9b5c840000000000d60405160205260005260406000205460605260206060f35b63711974848118612a115760243610613bd8576c050c783eb9b5c840000000000e60043560205260005260406000205460405260206040f35b638ff36fd18118612a3c5760043610613bd8576c050c783eb9b5c840000000000f5460405260206040f35b637175d4f78118612a675760043610613bd8576c050c783eb9b5c84000000000105460405260206040f35b63f851a4408118612a925760043610613bd8576c050c783eb9b5c84000000000115460405260206040f35b63142614258118612abd5760043610613bd8576c050c783eb9b5c84000000000125460405260206040f35b6322cf35f58118612ae85760043610613bd8576c050c783eb9b5c84000000000135460405260206040f35b6317f7182a8118612b135760043610613bd8576c050c783eb9b5c84000000000145460405260206040f35b639a01873c8118612b3e5760043610613bd8576c050c783eb9b5c84000000000155460405260206040f35b63f68467278118612b695760043610613bd8576c050c783eb9b5c84000000000165460405260206040f35b63cd8c79aa8118612b945760043610613bd8576c050c783eb9b5c84000000000175460405260206040f35b63094cda238118612bbf5760043610613bd8576c050c783eb9b5c84000000000185460405260206040f35b63205ad4088118612bea5760043610613bd8576c050c783eb9b5c84000000000195460405260206040f35b635836ec3a8118612c155760043610613bd8576c050c783eb9b5c840000000001a5460405260206040f35b6373f43d6d8118612c405760043610613bd8576c050c783eb9b5c840000000001b5460405260206040f35b6338d546458118612c6b5760043610613bd8576c050c783eb9b5c840000000001c5460405260206040f35b631dac30b08118612c965760043610613bd8576c050c783eb9b5c840000000001d5460405260206040f35b63b027651a8118612cc15760043610613bd8576c050c783eb9b5c840000000001e5460405260206040f35b63acc2166a8118612cec5760043610613bd8576c050c783eb9b5c840000000001f5460405260206040f35b63b3d8f7e78118612d175760043610613bd8576c050c783eb9b5c84000000000205460405260206040f35b505b60006000fd5b3260405114612e15576c050c783eb9b5c84000000000105460605260605115612d945760605163c23697a860805260405160a052602060806024609c6000855af1612d6f573d600060003e3d6000fd5b60203d10613bd8576080518060011c613bd85760c05260c090505115612d9457612e15565b60256080527f536d61727420636f6e7472616374206465706f7369746f7273206e6f7420616c60a0527f6c6f77656400000000000000000000000000000000000000000000000000000060c0526080506080518060a001601f826000031636823750506308c379a06040526020606052601f19601f6080510116604401605cfd5b565b6101403660e037600b546102205260405115612f83574260805111612e3d576000612e45565b600160605112155b15612ea65760605160015480607f1c613bd8578015613bd85780820580600f0b8118613bd85790509050610100526101005160805142808203828111613bd8579050905080607f1c613bd85780820280600f0b8118613bd8579050905060e0525b4260c05111612eb6576000612ebe565b600160a05112155b15612f205760a05160015480607f1c613bd8578015613bd85780820580600f0b8118613bd85790509050610180526101805160c05142808203828111613bd8579050905080607f1c613bd85780820280600f0b8118613bd85790509050610160525b6c050c783eb9b5c840000000000e6080516020526000526040600020546101e05260c05115612f835760805160c05118612f61576101e05161020052612f83565b6c050c783eb9b5c840000000000e60c051602052600052604060002054610200525b604036610240374261028052436102a0526102205115612fde57610220516c01431e0fae6d7217ca9fffffff8111613bd85760021b600c0180546102405260018101546102605260028101546102805260038101546102a052505b610280516102c052610240516102e052610260516103005261028051610320526102a051610340526000610360526102805142111561306d57436102a051808203828111613bd85790509050670de0b6b3a7640000810281670de0b6b3a7640000820418613bd85790504261028051808203828111613bd857905090508015613bd85780820490509050610360525b6102c05162093a808104905062093a8081028162093a80820418613bd857905061038052600060ff905b806103a0526103805162093a808101818110613bd85790506103805260006103c0524261038051116130ea576c050c783eb9b5c840000000000e610380516020526000526040600020546103c0526130f0565b42610380525b6102405161026051610380516102c051808203828111613bd8579050905080607f1c613bd85780820280600f0b8118613bd8579050905080820380600f0b8118613bd8579050905061024052610260516103c05180820180600f0b8118613bd85790509050610260527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610240511361318a576000610240525b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61026051136131bb576000610260525b610380516102c052610380516102805261034051610360516103805161032051808203828111613bd85790509050808202811583838304141715613bd85790509050670de0b6b3a764000081049050808201828110613bd857905090506102a0526102205160018101818110613bd85790506102205242610380511861324957436102a0526132955661328a565b610220516c01431e0fae6d7217ca9fffffff8111613bd85760021b600c016102405181556102605160018201556102805160028201556102a0516003820155505b600101818118613097575b505061022051600b556040511561336b5761026051610180516101005180820380600f0b8118613bd8579050905080820180600f0b8118613bd8579050905061026052610240516101605160e05180820380600f0b8118613bd8579050905080820180600f0b8118613bd85790509050610240527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610260511361333a576000610260525b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610240511361336b576000610240525b610220516c01431e0fae6d7217ca9fffffff8111613bd85760021b600c016102405181556102605160018201556102805160028201556102a0516003820155506040511561352857426080511115613425576101e0516101005180820180600f0b8118613bd857905090506101e05260805160c05118613403576101e0516101805180820380600f0b8118613bd857905090506101e0525b6101e0516c050c783eb9b5c840000000000e6080516020526000526040600020555b4260c051111561347a5760805160c051111561347a57610200516101805180820380600f0b8118613bd8579050905061020052610200516c050c783eb9b5c840000000000e60c0516020526000526040600020555b6c050c783eb9b5c840000000000d60405160205260005260406000205460018101818110613bd85790506103a0526103a0516c050c783eb9b5c840000000000d604051602052600052604060002055426101a052436101c0526c050c783eb9b5c840000000000c60405160205260005260406000206103a051633b9ac9ff8111613bd85760021b810190506101605181556101805160018201556101a05160028201556101c0516003820155505b565b6c050c783eb9b5c840000000002054156135a45760156104a0527f616c6c20756e6c6f636b65642c6e6f2073656e736500000000000000000000006104c0526104a0506104a051806104c001601f826000031636823750506308c379a061046052602061048052601f19601f6104a051011660440161047cfd5b610440516104a052610460516104c0526009546104e0526104e05161040051808201828110613bd857905090506009556104a051610500526104c051610520526104a0516104005180607f1c613bd85780820180600f0b8118613bd857905090506104a052610420511561361b57610420516104c0525b600a6103e05160205260005260406000206104a05181556104c0516001820155506103e05160405261050051606052610520516080526104a05160a0526104c05160c052613667612e17565b61040051156136eb576002546323b872dd610540526103e051610560523061058052610400516105a0526020610540606461055c6000855af16136af573d600060003e3d6000fd5b3d6136c657803b15613bd85760016105c0526136df565b60203d10613bd857610540518060011c613bd8576105c0525b6105c090505115613bd8575b6104c0516103e0517f4566dfc29f6f11d13a418c26a02bef7c28bae749d4de47e4e6a7cddea6730d596104005161054052610480516105605242610580526060610540a37f5e2aa66efd74cce82b21852e317e5490d9ecc9e6bb953ae24d90851258cc2f5c6104e051610540526104e05161040051808201828110613bd85790509050610560526040610540a1565b600060805260605160a05260006080905b8060c05260a0516080511061379f57613820565b60805160a051808201828110613bd8579050905060018101818110613bd85790508060011c905060e05260405160e0516c01431e0fae6d7217ca9fffffff8111613bd85760021b600c0160038101905054111561380e5760e05160018103818111613bd857905060a052613815565b60e0516080525b60010181811861378b575b5050608051815250565b600060805260605160a05260006080905b8060c05260a0516080511061384f576138d0565b60805160a051808201828110613bd8579050905060018101818110613bd85790508060011c905060e05260405160e0516c01431e0fae6d7217ca9fffffff8111613bd85760021b600c016002810190505411156138be5760e05160018103818111613bd857905060a0526138c5565b60e0516080525b60010181811861383b575b5050608051815250565b600060a05260805160c05260006080905b8060e05260c05160a051106138ff57613998565b60a05160c051808201828110613bd8579050905060018101818110613bd85790508060011c9050610100526060516c050c783eb9b5c840000000000c604051602052600052604060002061010051633b9ac9ff8111613bd85760021b81019050600381019050541115613985576101005160018103818111613bd857905060c05261398d565b6101005160a0525b6001018181186138eb575b505060a051815250565b600060a05260805160c05260006080905b8060e05260c05160a051106139c757613a60565b60a05160c051808201828110613bd8579050905060018101818110613bd85790508060011c9050610100526060516c050c783eb9b5c840000000000c604051602052600052604060002061010051633b9ac9ff8111613bd85760021b81019050600281019050541115613a4d576101005160018103818111613bd857905060c052613a55565b6101005160a0525b6001018181186139b3575b505060a051815250565b60405160e052606051610100526080516101205260a051610140526101205162093a808104905062093a8081028162093a80820418613bd857905061016052600060ff905b80610180526101605162093a808101818110613bd85790506101605260006101a05260c0516101605111613b04576c050c783eb9b5c840000000000e610160516020526000526040600020546101a052613b0c565b60c051610160525b60e051610100516101605161012051808203828111613bd8579050905080607f1c613bd85780820280600f0b8118613bd8579050905080820380600f0b8118613bd8579050905060e05260c0516101605118613b6757613b97565b610100516101a05180820180600f0b8118613bd85790509050610100526101605161012052600101818118613aaf575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60e05113613bc857600060e0525b60e05160008112613bd857815250565b600080fda165767970657283000307000b",
+    "deployedBytecode": "0x6003361161000c57612d19565b60003560e01c34613bd857639bf6abf3811861047c576101c43610613bd8576004358060a01c613bd8576040526024356004016040813511613bd8578035806060526020820181816080375050506044356004016020813511613bd85780358060c05260208201803560e0525050506064358060a01c613bd857610100526084358060a01c613bd8576101205260a4358060a01c613bd8576101405260e4358060a01c613bd85761016052610104358060a01c613bd85761018052610124358060a01c613bd8576101a052610144358060011c613bd8576101c052610164358060a01c613bd8576101e0526c050c783eb9b5c84000000000155415610171576009610200527f6f6e6c79206f6e636500000000000000000000000000000000000000000000006102205261020050610200518061022001601f826000031636823750506308c379a06101c05260206101e052601f19601f6102005101166044016101dcfd5b60016c050c783eb9b5c840000000001555610100516101f0576006610200527f21656d70747900000000000000000000000000000000000000000000000000006102205261020050610200518061022001601f826000031636823750506308c379a06101c05260206101e052601f19601f6102005101166044016101dcfd5b610100516c050c783eb9b5c840000000001155600a6c050c783eb9b5c840000000001755600a6c050c783eb9b5c840000000001855426c050c783eb9b5c840000000001955610100516c050c783eb9b5c840000000001a5560405160025543600f5542600e5560405163313ce567610220526020610220600461023c845afa61027e573d600060003e3d6000fd5b60203d10613bd8576102209050516102005260066102005110156102a35760006102ac565b60ff6102005111155b610316576009610220527f21646563696d616c7300000000000000000000000000000000000000000000006102405261022050610220518061024001601f826000031636823750506308c379a06101e052602061020052601f19601f6102205101166044016101fcfd5b60605180600355600081601f0160051c60028111613bd857801561034e57905b8060051b608001518160040155600101818118610336575b50505060c0518060065560e051600755506102005160085562093a8060c435101561037a576000610385565b63095f6a0060c43511155b6103ef576008610220527f216d61786c6f636b0000000000000000000000000000000000000000000000006102405261022050610220518061024001601f826000031636823750506308c379a06101e052602061020052601f19601f6102205101166044016101fcfd5b60c435600155610120516c050c783eb9b5c840000000001255610140516c050c783eb9b5c840000000001355610160516c050c783eb9b5c840000000001c55610180516c050c783eb9b5c840000000001b556101a0516c050c783eb9b5c840000000001d556101c0516c050c783eb9b5c840000000001e556101e0516c050c783eb9b5c840000000001f55005b63fc0c546a811861049b5760043610613bd85760025460405260206040f35b6306fdde0381186105205760043610613bd8576020806040528060400160035480825260208201600082601f0160051c60028111613bd85780156104f257905b80600401548160051b8401526001018181186104db575b505050508051806020830101601f82600003163682375050601f19601f825160200101169050810190506040f35b6395d89b4181186105785760043610613bd8576020806040528060400160065480825260208201600754815250508051806020830101601f82600003163682375050601f19601f825160200101169050810190506040f35b63313ce56781186105975760043610613bd85760085460405260206040f35b636b441a40811861060d5760243610613bd8576004358060a01c613bd8576040526c050c783eb9b5c8400000000011543318613bd8576040516c050c783eb9b5c8400000000014557f2f56810a6bf40af059b96d3aea4db54081f378029a518390491093a7b67032e960405160605260206060a1005b636a1c05ae811861068f5760043610613bd8576c050c783eb9b5c8400000000011543318613bd8576c050c783eb9b5c84000000000145460405260405115613bd8576040516c050c783eb9b5c8400000000011557febee2d5739011062cb4f14113f3b36bf0ffe3da5c0568f64189d1012a118910560405160605260206060a1005b6357f901e281186106d95760243610613bd8576004358060a01c613bd8576040526c050c783eb9b5c8400000000011543318613bd8576040516c050c783eb9b5c840000000000f55005b638e5b490f81186107215760043610613bd8576c050c783eb9b5c8400000000011543318613bd8576c050c783eb9b5c840000000000f546c050c783eb9b5c840000000001055005b6355a3323581186108695760243610613bd8576004358060011c613bd8576040526c050c783eb9b5c8400000000013543318156107b55760066060527f2161646d696e000000000000000000000000000000000000000000000000000060805260605060605180608001601f826000031636823750506308c379a06020526020604052601f19601f6060510116604401603cfd5b6c050c783eb9b5c840000000001654604051186108295760076060527f616c72656164790000000000000000000000000000000000000000000000000060805260605060605180608001601f826000031636823750506308c379a06020526020604052601f19601f6060510116604401603cfd5b6040516c050c783eb9b5c8400000000016557f66515f71c349ef0ad8c6981cedaa58746200512e6e12754c5ac5cc701d5cf41860405160605260206060a1005b63655317ae8118610a445760243610613bd8576c050c783eb9b5c8400000000013543318156108ef5760066040527f2161646d696e000000000000000000000000000000000000000000000000000060605260405060405180606001601f826000031636823750506308c379a06000526020602052601f19601f6040510116604401601cfd5b603260043511156109575760026040527f216b00000000000000000000000000000000000000000000000000000000000060605260405060405180606001601f826000031636823750506308c379a06000526020602052601f19601f6040510116604401601cfd5b6c050c783eb9b5c840000000001954603c8101818110613bd857905042116109d65760056040527f6561726c7900000000000000000000000000000000000000000000000000000060605260405060405180606001601f826000031636823750506308c379a06000526020602052601f19601f6040510116604401601cfd5b6c050c783eb9b5c8400000000017546c050c783eb9b5c8400000000018556004356c050c783eb9b5c840000000001755426c050c783eb9b5c8400000000019557f9c04360e3c3de1eeca25bbd4a21cdc22c4c192f4b91f91d2a0c59dcd042f8ba860043560405260206040a1005b63af3097af8118610b7c5760243610613bd8576004358060a01c613bd8576040526c050c783eb9b5c840000000001354331815610ad85760066060527f2161646d696e000000000000000000000000000000000000000000000000000060805260605060605180608001601f826000031636823750506308c379a06020526020604052601f19601f6060510116604401603cfd5b604051610b3c5760056060527f217a65726f00000000000000000000000000000000000000000000000000000060805260605060605180608001601f826000031636823750506308c379a06020526020604052601f19601f6060510116604401603cfd5b6040516c050c783eb9b5c840000000001a557ff51c492eafc918620c2d49b196e6a4e0cac71709d348224a8e7fc231ee973e5960405160605260206060a1005b6366e01ca98118610c405760043610613bd8576c050c783eb9b5c840000000001254331815610c025760066040527f2161646d696e000000000000000000000000000000000000000000000000000060605260405060405180606001601f826000031636823750506308c379a06000526020602052601f19601f6040510116604401601cfd5b60016c050c783eb9b5c8400000000020557f7ca88488f569b55d1fb073429f75839e505182c1f6d26e5caed22e9828df430c600160405260206040a1005b637c74a1748118610cc25760243610613bd8576004358060a01c613bd8576040526c050c783eb9b5c840000000000d6040516020526000526040600020546060526c050c783eb9b5c840000000000c6040516020526000526040600020606051633b9ac9ff8111613bd85760021b810190506001810190505460805260206080f35b63da020a188118610d245760443610613bd8576004358060a01c613bd8576040526c050c783eb9b5c840000000000c6040516020526000526040600020602435633b9ac9ff8111613bd85760021b810190506002810190505460605260206060f35b63adc635898118610d655760243610613bd8576004358060a01c613bd857604052600a60405160205260005260406000206001810190505460605260206060f35b63c2c4c5c18118610d885760043610613bd85760a036604037610d86612e17565b005b633a46273e8118610f345760443610613bd8576004358060a01c613bd8576105e052600054600214613bd8576002600055600a6105e05160205260005260406000208054610600526001810154610620525060243515613bd8576001610600511215610e54576016610640527f4e6f206578697374696e67206c6f636b20666f756e64000000000000000000006106605261064050610640518061066001601f826000031636823750506308c379a061060052602061062052601f19601f61064051011660440161061cfd5b426106205111610ee9576024610640527f43616e6e6f742061646420746f2065787069726564206c6f636b2e2057697468610660527f64726177000000000000000000000000000000000000000000000000000000006106805261064050610640518061066001601f826000031636823750506308c379a061060052602061062052601f19601f61064051011660440161061cfd5b6105e0516103e05260243561040052600061042052600a6105e051602052600052604060002080546104405260018101546104605250600061048052610f2d61352a565b6003600055005b6365fc3873811861116a5760443610613bd857600054600214613bd857600260005533604052610f62612d1f565b60243562093a808104905062093a8081028162093a80820418613bd85790506105e052600a3360205260005260406000208054610600526001810154610620525060043515613bd857610600511561101a576019610640527f5769746864726177206f6c6420746f6b656e73206669727374000000000000006106605261064050610640518061066001601f826000031636823750506308c379a061060052602061062052601f19601f61064051011660440161061cfd5b426105e051116110af576026610640527f43616e206f6e6c79206c6f636b20756e74696c2074696d6520696e2074686520610660527f66757475726500000000000000000000000000000000000000000000000000006106805261064050610640518061066001601f826000031636823750506308c379a061060052602061062052601f19601f61064051011660440161061cfd5b42600154808201828110613bd857905090506105e0511115611131576014610640527f566f74696e67206c6f636b20746f6f206c6f6e670000000000000000000000006106605261064050610640518061066001601f826000031636823750506308c379a061060052602061062052601f19601f61064051011660440161061cfd5b336103e052600435610400526105e051610420526106005161044052610620516104605260016104805261116361352a565b6003600055005b634957677c81186112fc5760243610613bd857600054600214613bd857600260005533604052611198612d1f565b600a33602052600052604060002080546105e0526001810154610600525060043515613bd85760016105e0511215611230576016610620527f4e6f206578697374696e67206c6f636b20666f756e64000000000000000000006106405261062050610620518061064001601f826000031636823750506308c379a06105e052602061060052601f19601f6106205101166044016105fcfd5b4261060051116112c5576024610620527f43616e6e6f742061646420746f2065787069726564206c6f636b2e2057697468610640527f64726177000000000000000000000000000000000000000000000000000000006106605261062050610620518061064001601f826000031636823750506308c379a06105e052602061060052601f19601f6106205101166044016105fcfd5b336103e052600435610400526000610420526105e0516104405261060051610460526002610480526112f561352a565b6003600055005b63eff7a612811861157a5760243610613bd857600054600214613bd85760026000553360405261132a612d1f565b600a33602052600052604060002080546105e0526001810154610600525060043562093a808104905062093a8081028162093a80820418613bd8579050610620524261060051116113db57600c610640527f4c6f636b206578706972656400000000000000000000000000000000000000006106605261064050610640518061066001601f826000031636823750506308c379a061060052602061062052601f19601f61064051011660440161061cfd5b60016105e051121561144d576011610640527f4e6f7468696e67206973206c6f636b65640000000000000000000000000000006106605261064050610640518061066001601f826000031636823750506308c379a061060052602061062052601f19601f61064051011660440161061cfd5b6106005161062051116114c057601f610640527f43616e206f6e6c7920696e637265617365206c6f636b206475726174696f6e006106605261064050610640518061066001601f826000031636823750506308c379a061060052602061062052601f19601f61064051011660440161061cfd5b42600154808201828110613bd85790509050610620511115611542576014610640527f566f74696e67206c6f636b20746f6f206c6f6e670000000000000000000000006106605261064050610640518061066001601f826000031636823750506308c379a061060052602061062052601f19601f61064051011660440161061cfd5b336103e05260006104005261062051610420526105e05161044052610600516104605260036104805261157361352a565b6003600055005b633ccfd60b81186117cf5760043610613bd857600054600214613bd8576002600055600a33602052600052604060002080546103e05260018101546104005250610400514210156115d9576c050c783eb9b5c8400000000020546115dc565b60015b611646576017610420527f6c6f636b2021657870697265206f722021756e6c6f636b0000000000000000006104405261042050610420518061044001601f826000031636823750506308c379a06103e052602061040052601f19601f6104205101166044016103fcfd5b6103e05160008112613bd857610420526103e05161044052610400516104605260006104005260006103e052600a3360205260005260406000206103e051815561040051600182015550600954610480526104805161042051808203828111613bd857905090506009553360405261044051606052610460516080526103e05160a0526104005160c0526116d8612e17565b60025463a9059cbb6104a052336104c052610420516104e05260206104a060446104bc6000855af161170f573d600060003e3d6000fd5b3d61172657803b15613bd85760016105005261173f565b60203d10613bd8576104a0518060011c613bd857610500525b61050090505115613bd857337ff279e6a1f5e320cca91135676d9cb6e44ca8a08c0b88342bcdb1144f6511b568610420516104a052426104c05260406104a0a27f5e2aa66efd74cce82b21852e317e5490d9ecc9e6bb953ae24d90851258cc2f5c610480516104a0526104805161042051808203828111613bd857905090506104c05260406104a0a16003600055005b638239f0648118611c6e5760043610613bd857600054600214613bd857600260005560016c050c783eb9b5c840000000001654181561186e57600d6103e0527f216561726c7920756e6c6f636b00000000000000000000000000000000000000610400526103e0506103e0518061040001601f826000031636823750506308c379a06103a05260206103c052601f19601f6103e05101166044016103bcfd5b600a33602052600052604060002080546103e052600181015461040052506104005142106118fc57600c610420527f6c6f636b206578706972656400000000000000000000000000000000000000006104405261042050610420518061044001601f826000031636823750506308c379a06103e052602061040052601f19601f6104205101166044016103fcfd5b6103e05160008112613bd857610420526104005142808203828111613bd85790509050610440526000610460526c050c783eb9b5c840000000001954603c8101818110613bd85790504211611963576c050c783eb9b5c84000000000185461046052611977565b6c050c783eb9b5c840000000001754610460525b61044051670de0b6b3a7640000810281670de0b6b3a7640000820418613bd85790506001548015613bd8578082049050905061046051808202811583838304141715613bd85790509050610480526104205161048051808202811583838304141715613bd85790509050670de0b6b3a764000081049050600a810490506104a052610420516104a0511115611a0f57610420516104a0525b610420516104a051808203828111613bd857905090506104c0526103e0516104e052610400516105005260006104005260006103e052600a3360205260005260406000206103e051815561040051600182015550600954610520526105205161042051808203828111613bd85790509050600955336040526104e051606052610500516080526103e05160a0526104005160c052611aab612e17565b6104a05115611b355760025463a9059cbb610540526c050c783eb9b5c840000000001a54610560526104a051610580526020610540604461055c6000855af1611af9573d600060003e3d6000fd5b3d611b1057803b15613bd85760016105a052611b29565b60203d10613bd857610540518060011c613bd8576105a0525b6105a090505115613bd8575b6104c05115611bb15760025463a9059cbb6105405233610560526104c051610580526020610540604461055c6000855af1611b75573d600060003e3d6000fd5b3d611b8c57803b15613bd85760016105a052611ba5565b60203d10613bd857610540518060011c613bd8576105a0525b6105a090505115613bd8575b337ff279e6a1f5e320cca91135676d9cb6e44ca8a08c0b88342bcdb1144f6511b568610420516105405242610560526040610540a27f5e2aa66efd74cce82b21852e317e5490d9ecc9e6bb953ae24d90851258cc2f5c61052051610540526105205161042051808203828111613bd85790509050610560526040610540a1337ff11a1a5a36ad286d4f77c166e8d87e5bfe87d1e74f9c46654ef21bd811c6784f6104a0516105405261044051610560526040610540a26003600055005b6370a082318118611c8b5760243610613bd8574261014052611ca5565b62fdd58e8118611e345760443610613bd857602435610140525b6004358060a01c613bd85761012052600061016052426101405118611ceb576c050c783eb9b5c840000000000d6101205160205260005260406000205461016052611d2e565b61012051604052610140516060526c050c783eb9b5c840000000000d61012051602052600052604060002054608052611d256101806139a2565b61018051610160525b61016051611d4a576000610180526020610180611e3256611e32565b6c050c783eb9b5c840000000000c61012051602052600052604060002061016051633b9ac9ff8111613bd85760021b8101905080546101805260018101546101a05260028101546101c05260038101546101e05250610180516101a051610140516101c051808203828111613bd8579050905080607f1c613bd85780820280600f0b8118613bd8579050905080820380600f0b8118613bd85790509050610180527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6101805113611e1c576000610180525b6101805160008112613bd8576102005260206102005bf35b634ee2cd7e811861210f5760443610613bd8576004358060a01c613bd857610120524360243511613bd857610120516040526024356060526c050c783eb9b5c840000000000d61012051602052600052604060002054608052611e986101606138da565b61016051610140526c050c783eb9b5c840000000000c61012051602052600052604060002061014051633b9ac9ff8111613bd85760021b8101905080546101605260018101546101805260028101546101a05260038101546101c05250600b546101e0526024356040526101e051606052611f1461022061377a565b6102205161020052610200516c01431e0fae6d7217ca9fffffff8111613bd85760021b600c01805461022052600181015461024052600281015461026052600381015461028052506040366102a0376101e0516102005110611fa3574361028051808203828111613bd857905090506102a0524261026051808203828111613bd857905090506102c052612025565b6102005160018101818110613bd85790506c01431e0fae6d7217ca9fffffff8111613bd85760021b600c0180546102e052600181015461030052600281015461032052600381015461034052506103405161028051808203828111613bd857905090506102a0526103205161026051808203828111613bd857905090506102c0525b610260516102e0526102a0511561208b576102e0516102c05160243561028051808203828111613bd85790509050808202811583838304141715613bd857905090506102a0518015613bd85780820490509050808201828110613bd857905090506102e0525b61016051610180516102e0516101a051808203828111613bd8579050905080607f1c613bd85780820280600f0b8118613bd8579050905080820380600f0b8118613bd857905090506101605260006101605112156120f757600061030052602061030061210d5661210d565b6101605160008112613bd8576103005260206103005bf35b6318160ddd811861212c5760043610613bd857426101c052612147565b63bd85b03981186122165760243610613bd8576004356101c0525b60006101e052426101c0511861216357600b546101e052612184565b6101c051604052600b5460605261217b61020061382a565b610200516101e0525b6101e0516121a057600061020052602061020061221456612214565b6101e0516c01431e0fae6d7217ca9fffffff8111613bd85760021b600c018054610200526001810154610220526002810154610240526003810154610260525060206102005160405261022051606052610240516080526102605160a0526101c05160c052612210610280613a6a565b6102805bf35b63981b24d081186124235760243610613bd8574360043511613bd857600b546101c0526004356040526101c05160605261225161020061377a565b610200516101e0526101e0516c01431e0fae6d7217ca9fffffff8111613bd85760021b600c01805461020052600181015461022052600281015461024052600381015461026052506000610280526101c0516101e0511061231b574361026051146123dc5760043561026051808203828111613bd857905090504261024051808203828111613bd85790509050808202811583838304141715613bd857905090504361026051808203828111613bd857905090508015613bd85780820490509050610280526123dc565b6101e05160018101818110613bd85790506c01431e0fae6d7217ca9fffffff8111613bd85760021b600c0180546102a05260018101546102c05260028101546102e052600381015461030052506103005161026051146123dc5760043561026051808203828111613bd857905090506102e05161024051808203828111613bd85790509050808202811583838304141715613bd857905090506103005161026051808203828111613bd857905090508015613bd85780820490509050610280525b60206102005160405261022051606052610240516080526102605160a0526102405161028051808201828110613bd8579050905060c05261241e6102a0613a6a565b6102a0f35b63db93cc7a811861265e5760043610613bd857600054600214613bd85760026000556c050c783eb9b5c840000000001b54636a627842604052600254606052602060406024605c6000855af161247e573d600060003e3d6000fd5b60203d10613bd857604050506c050c783eb9b5c840000000001c546370a0823160605230608052602060606024607c845afa6124bf573d600060003e3d6000fd5b60203d10613bd857606090505160405260405115612657576c050c783eb9b5c840000000001f546c050c783eb9b5c840000000001d54186125d4576c050c783eb9b5c840000000001c5463095ea7b36060526c050c783eb9b5c840000000001f5460805260405160a052602060606044607c6000855af1612545573d600060003e3d6000fd5b3d61255b57803b15613bd857600160c052612572565b60203d10613bd8576060518060011c613bd85760c0525b60c090505115613bd8576c050c783eb9b5c840000000001f5463338b5dea6060526c050c783eb9b5c840000000001c5460805260405160a052803b15613bd857600060606044607c6000855af16125ce573d600060003e3d6000fd5b50612657565b6c050c783eb9b5c840000000001c5463a9059cbb6060526c050c783eb9b5c840000000001d5460805260405160a052602060606044607c6000855af161261f573d600060003e3d6000fd5b3d61263557803b15613bd857600160c05261264c565b60203d10613bd8576060518060011c613bd85760c0525b60c090505115613bd8575b6003600055005b63cb8e090d81186128065760243610613bd8576004358060a01c613bd8576040526c050c783eb9b5c8400000000011543318156126f25760066060527f2161646d696e000000000000000000000000000000000000000000000000000060805260605060605180608001601f826000031636823750506308c379a06020526020604052601f19601f6060510116604401603cfd5b6c050c783eb9b5c840000000001e5461276257600a6060527f21617661696c61626c650000000000000000000000000000000000000000000060805260605060605180608001601f826000031636823750506308c379a06020526020604052601f19601f6060510116604401603cfd5b6040516127c65760066060527f21656d707479000000000000000000000000000000000000000000000000000060805260605060605180608001601f826000031636823750506308c379a06020526020604052601f19601f6060510116604401603cfd5b6040516c050c783eb9b5c840000000001d557f454ccf21cdac2e344d64173597f5922657c25c5b1e6c3f733791637513d2063760405160605260206060a1005b63ee00ef3a81186128255760043610613bd85760015460405260206040f35b6382bfefc881186128445760043610613bd85760025460405260206040f35b63047fc9aa81186128635760043610613bd85760095460405260206040f35b63cbf9fe5f81186128a85760243610613bd8576004358060a01c613bd857604052600a6040516020526000526040600020805460605260018101546080525060406060f35b63900cf0cf81186128c75760043610613bd857600b5460405260206040f35b63d1febfb9811861291b5760243610613bd8576004356c01431e0fae6d7217ca9fffffff8111613bd85760021b600c01805460405260018101546060526002810154608052600381015460a0525060806040f35b6328d09d4781186129915760443610613bd8576004358060a01c613bd8576040526c050c783eb9b5c840000000000c6040516020526000526040600020602435633b9ac9ff8111613bd85760021b8101905080546060526001810154608052600281015460a052600381015460c0525060806060f35b63010ae75781186129d85760243610613bd8576004358060a01c613bd8576040526c050c783eb9b5c840000000000d60405160205260005260406000205460605260206060f35b63711974848118612a115760243610613bd8576c050c783eb9b5c840000000000e60043560205260005260406000205460405260206040f35b638ff36fd18118612a3c5760043610613bd8576c050c783eb9b5c840000000000f5460405260206040f35b637175d4f78118612a675760043610613bd8576c050c783eb9b5c84000000000105460405260206040f35b63f851a4408118612a925760043610613bd8576c050c783eb9b5c84000000000115460405260206040f35b63142614258118612abd5760043610613bd8576c050c783eb9b5c84000000000125460405260206040f35b6322cf35f58118612ae85760043610613bd8576c050c783eb9b5c84000000000135460405260206040f35b6317f7182a8118612b135760043610613bd8576c050c783eb9b5c84000000000145460405260206040f35b639a01873c8118612b3e5760043610613bd8576c050c783eb9b5c84000000000155460405260206040f35b63f68467278118612b695760043610613bd8576c050c783eb9b5c84000000000165460405260206040f35b63cd8c79aa8118612b945760043610613bd8576c050c783eb9b5c84000000000175460405260206040f35b63094cda238118612bbf5760043610613bd8576c050c783eb9b5c84000000000185460405260206040f35b63205ad4088118612bea5760043610613bd8576c050c783eb9b5c84000000000195460405260206040f35b635836ec3a8118612c155760043610613bd8576c050c783eb9b5c840000000001a5460405260206040f35b6373f43d6d8118612c405760043610613bd8576c050c783eb9b5c840000000001b5460405260206040f35b6338d546458118612c6b5760043610613bd8576c050c783eb9b5c840000000001c5460405260206040f35b631dac30b08118612c965760043610613bd8576c050c783eb9b5c840000000001d5460405260206040f35b63b027651a8118612cc15760043610613bd8576c050c783eb9b5c840000000001e5460405260206040f35b63acc2166a8118612cec5760043610613bd8576c050c783eb9b5c840000000001f5460405260206040f35b63b3d8f7e78118612d175760043610613bd8576c050c783eb9b5c84000000000205460405260206040f35b505b60006000fd5b3260405114612e15576c050c783eb9b5c84000000000105460605260605115612d945760605163c23697a860805260405160a052602060806024609c6000855af1612d6f573d600060003e3d6000fd5b60203d10613bd8576080518060011c613bd85760c05260c090505115612d9457612e15565b60256080527f536d61727420636f6e7472616374206465706f7369746f7273206e6f7420616c60a0527f6c6f77656400000000000000000000000000000000000000000000000000000060c0526080506080518060a001601f826000031636823750506308c379a06040526020606052601f19601f6080510116604401605cfd5b565b6101403660e037600b546102205260405115612f83574260805111612e3d576000612e45565b600160605112155b15612ea65760605160015480607f1c613bd8578015613bd85780820580600f0b8118613bd85790509050610100526101005160805142808203828111613bd8579050905080607f1c613bd85780820280600f0b8118613bd8579050905060e0525b4260c05111612eb6576000612ebe565b600160a05112155b15612f205760a05160015480607f1c613bd8578015613bd85780820580600f0b8118613bd85790509050610180526101805160c05142808203828111613bd8579050905080607f1c613bd85780820280600f0b8118613bd85790509050610160525b6c050c783eb9b5c840000000000e6080516020526000526040600020546101e05260c05115612f835760805160c05118612f61576101e05161020052612f83565b6c050c783eb9b5c840000000000e60c051602052600052604060002054610200525b604036610240374261028052436102a0526102205115612fde57610220516c01431e0fae6d7217ca9fffffff8111613bd85760021b600c0180546102405260018101546102605260028101546102805260038101546102a052505b610280516102c052610240516102e052610260516103005261028051610320526102a051610340526000610360526102805142111561306d57436102a051808203828111613bd85790509050670de0b6b3a7640000810281670de0b6b3a7640000820418613bd85790504261028051808203828111613bd857905090508015613bd85780820490509050610360525b6102c05162093a808104905062093a8081028162093a80820418613bd857905061038052600060ff905b806103a0526103805162093a808101818110613bd85790506103805260006103c0524261038051116130ea576c050c783eb9b5c840000000000e610380516020526000526040600020546103c0526130f0565b42610380525b6102405161026051610380516102c051808203828111613bd8579050905080607f1c613bd85780820280600f0b8118613bd8579050905080820380600f0b8118613bd8579050905061024052610260516103c05180820180600f0b8118613bd85790509050610260527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610240511361318a576000610240525b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61026051136131bb576000610260525b610380516102c052610380516102805261034051610360516103805161032051808203828111613bd85790509050808202811583838304141715613bd85790509050670de0b6b3a764000081049050808201828110613bd857905090506102a0526102205160018101818110613bd85790506102205242610380511861324957436102a0526132955661328a565b610220516c01431e0fae6d7217ca9fffffff8111613bd85760021b600c016102405181556102605160018201556102805160028201556102a0516003820155505b600101818118613097575b505061022051600b556040511561336b5761026051610180516101005180820380600f0b8118613bd8579050905080820180600f0b8118613bd8579050905061026052610240516101605160e05180820380600f0b8118613bd8579050905080820180600f0b8118613bd85790509050610240527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610260511361333a576000610260525b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610240511361336b576000610240525b610220516c01431e0fae6d7217ca9fffffff8111613bd85760021b600c016102405181556102605160018201556102805160028201556102a0516003820155506040511561352857426080511115613425576101e0516101005180820180600f0b8118613bd857905090506101e05260805160c05118613403576101e0516101805180820380600f0b8118613bd857905090506101e0525b6101e0516c050c783eb9b5c840000000000e6080516020526000526040600020555b4260c051111561347a5760805160c051111561347a57610200516101805180820380600f0b8118613bd8579050905061020052610200516c050c783eb9b5c840000000000e60c0516020526000526040600020555b6c050c783eb9b5c840000000000d60405160205260005260406000205460018101818110613bd85790506103a0526103a0516c050c783eb9b5c840000000000d604051602052600052604060002055426101a052436101c0526c050c783eb9b5c840000000000c60405160205260005260406000206103a051633b9ac9ff8111613bd85760021b810190506101605181556101805160018201556101a05160028201556101c0516003820155505b565b6c050c783eb9b5c840000000002054156135a45760156104a0527f616c6c20756e6c6f636b65642c6e6f2073656e736500000000000000000000006104c0526104a0506104a051806104c001601f826000031636823750506308c379a061046052602061048052601f19601f6104a051011660440161047cfd5b610440516104a052610460516104c0526009546104e0526104e05161040051808201828110613bd857905090506009556104a051610500526104c051610520526104a0516104005180607f1c613bd85780820180600f0b8118613bd857905090506104a052610420511561361b57610420516104c0525b600a6103e05160205260005260406000206104a05181556104c0516001820155506103e05160405261050051606052610520516080526104a05160a0526104c05160c052613667612e17565b61040051156136eb576002546323b872dd610540526103e051610560523061058052610400516105a0526020610540606461055c6000855af16136af573d600060003e3d6000fd5b3d6136c657803b15613bd85760016105c0526136df565b60203d10613bd857610540518060011c613bd8576105c0525b6105c090505115613bd8575b6104c0516103e0517f4566dfc29f6f11d13a418c26a02bef7c28bae749d4de47e4e6a7cddea6730d596104005161054052610480516105605242610580526060610540a37f5e2aa66efd74cce82b21852e317e5490d9ecc9e6bb953ae24d90851258cc2f5c6104e051610540526104e05161040051808201828110613bd85790509050610560526040610540a1565b600060805260605160a05260006080905b8060c05260a0516080511061379f57613820565b60805160a051808201828110613bd8579050905060018101818110613bd85790508060011c905060e05260405160e0516c01431e0fae6d7217ca9fffffff8111613bd85760021b600c0160038101905054111561380e5760e05160018103818111613bd857905060a052613815565b60e0516080525b60010181811861378b575b5050608051815250565b600060805260605160a05260006080905b8060c05260a0516080511061384f576138d0565b60805160a051808201828110613bd8579050905060018101818110613bd85790508060011c905060e05260405160e0516c01431e0fae6d7217ca9fffffff8111613bd85760021b600c016002810190505411156138be5760e05160018103818111613bd857905060a0526138c5565b60e0516080525b60010181811861383b575b5050608051815250565b600060a05260805160c05260006080905b8060e05260c05160a051106138ff57613998565b60a05160c051808201828110613bd8579050905060018101818110613bd85790508060011c9050610100526060516c050c783eb9b5c840000000000c604051602052600052604060002061010051633b9ac9ff8111613bd85760021b81019050600381019050541115613985576101005160018103818111613bd857905060c05261398d565b6101005160a0525b6001018181186138eb575b505060a051815250565b600060a05260805160c05260006080905b8060e05260c05160a051106139c757613a60565b60a05160c051808201828110613bd8579050905060018101818110613bd85790508060011c9050610100526060516c050c783eb9b5c840000000000c604051602052600052604060002061010051633b9ac9ff8111613bd85760021b81019050600281019050541115613a4d576101005160018103818111613bd857905060c052613a55565b6101005160a0525b6001018181186139b3575b505060a051815250565b60405160e052606051610100526080516101205260a051610140526101205162093a808104905062093a8081028162093a80820418613bd857905061016052600060ff905b80610180526101605162093a808101818110613bd85790506101605260006101a05260c0516101605111613b04576c050c783eb9b5c840000000000e610160516020526000526040600020546101a052613b0c565b60c051610160525b60e051610100516101605161012051808203828111613bd8579050905080607f1c613bd85780820280600f0b8118613bd8579050905080820380600f0b8118613bd8579050905060e05260c0516101605118613b6757613b97565b610100516101a05180820180600f0b8118613bd85790509050610100526101605161012052600101818118613aaf575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60e05113613bc857600060e0525b60e05160008112613bd857815250565b600080fda165767970657283000307000b",
+    "linkReferences": {},
+    "deployedLinkReferences": {}
+}
diff --git a/apps/api/src/app/hardhat/hardhat.config.ts b/apps/api/src/app/hardhat/hardhat.config.ts
new file mode 100644
index 000000000..c30262176
--- /dev/null
+++ b/apps/api/src/app/hardhat/hardhat.config.ts
@@ -0,0 +1,114 @@
+import dotenv from 'dotenv';
+import { HardhatUserConfig } from 'hardhat/config';
+import '@nomicfoundation/hardhat-toolbox';
+
+dotenv.config();
+
+const INFURA_PROJECT_ID = process.env.INFURA_PROJECT_ID || '';
+const POLYGON_PRIVATE_KEY = process.env.POLYGON_PRIVATE_KEY || '';
+
+const config: HardhatUserConfig = {
+    defaultNetwork: 'hardhat',
+    networks: {
+        hardhat: {
+            accounts: [
+                {
+                    balance: '100000000000000000000',
+                    privateKey: '0x873c254263b17925b686f971d7724267710895f1585bb0533db8e693a2af32ff',
+                },
+                {
+                    balance: '100000000000000000000',
+                    privateKey: '0x97093724e1748ebfa6aa2d2ec4ec68df8678423ab9a12eb2d27ddc74e35e5db9',
+                },
+                {
+                    balance: '100000000000000000000',
+                    privateKey: '5a05e38394194379795422d2e8c1d33e90033d90defec4880174c39198f707e3',
+                },
+                {
+                    balance: '100000000000000000000',
+                    privateKey: 'eea0247bd059ac4d2528adb36bb0de003d62ba568e3197984b61c41d9a132df0',
+                },
+            ],
+        },
+    },
+    paths: {
+        sources: 'contracts',
+    },
+    solidity: {
+        compilers: [
+            {
+                version: '0.5.17',
+                settings: {
+                    optimizer: {
+                        enabled: true,
+                        runs: 200,
+                    },
+                },
+            },
+            {
+                version: '0.7.6',
+                settings: {
+                    optimizer: {
+                        enabled: true,
+                        runs: 200,
+                    },
+                },
+            },
+            {
+                version: '0.8.0',
+                settings: {
+                    optimizer: {
+                        enabled: true,
+                        runs: 200,
+                    },
+                },
+            },
+            {
+                version: '0.8.1',
+                settings: {
+                    optimizer: {
+                        enabled: true,
+                        runs: 200,
+                    },
+                },
+            },
+            {
+                version: '0.8.2',
+                settings: {
+                    optimizer: {
+                        enabled: true,
+                        runs: 200,
+                    },
+                },
+            },
+            {
+                version: '0.8.6',
+                settings: {
+                    optimizer: {
+                        enabled: true,
+                        runs: 200,
+                    },
+                },
+            },
+            {
+                version: '0.8.24', // Remove when Lock is removed
+                settings: {
+                    optimizer: {
+                        enabled: true,
+                        runs: 200,
+                    },
+                },
+            },
+        ],
+    },
+};
+
+if (POLYGON_PRIVATE_KEY && INFURA_PROJECT_ID && config.networks) {
+    config.networks.matic = {
+        url: `https://polygon-mainnet.infura.io/v3/${INFURA_PROJECT_ID}`,
+        accounts: [POLYGON_PRIVATE_KEY],
+        timeout: 2483647,
+    };
+}
+
+export default config;
diff --git a/apps/api/src/app/hardhat/index.ts b/apps/api/src/app/hardhat/index.ts
new file mode 100644
index 000000000..577f7074e
--- /dev/null
+++ b/apps/api/src/app/hardhat/index.ts
@@ -0,0 +1,172 @@
+import { ContractNetworksConfig } from '@safe-global/protocol-kit';
+// Safe
+import DefaultCallbackHandler from './export/DefaultCallbackHandler.json';
+import CreateCall from './export/CreateCall.json';
+import GnosisSafeL2 from './export/GnosisSafeL2.json';
+import GnosisSafeProxyFactory from './export/GnosisSafeProxyFactory.json';
+import MultiSend from './export/MultiSend.json';
+import MultiSendCallOnly from './export/MultiSendCallOnly.json';
+import SignMessageLib from './export/SignMessageLib.json';
+import SimulateTxAccessor from './export/SimulateTxAccessor.json';
+// Balancer
+import USDC from './export/USDC.json';
+import THX from './export/THX.json';
+import BPT from './export/BPT.json';
+import BPTGauge from './export/BPTGauge.json';
+import BalancerVault from './export/BalancerVault.json';
+import BalancerMinter from './export/BalancerMinter.json';
+import BalancerGaugeController from './export/BalancerGaugeController.json';
+import BAL from './export/BAL.json';
+import Launchpad from './export/Launchpad.json';
+// Tokens
+import THXERC20_LimitedSupply from './export/THXERC20_LimitedSupply.json';
+import THXERC20_UnlimitedSupply from './export/THXERC20_UnlimitedSupply.json';
+import THXERC721 from './export/THXERC721.json';
+import THXERC1155 from './export/THXERC1155.json';
+// Protocol
+import THXPaymentSplitter from './export/THXPaymentSplitter.json';
+import THXRegistry from './export/THXRegistry.json';
+import VotingEscrow from './export/VotingEscrow.json';
+import RewardDistributor from './export/RewardDistributor.json';
+import RewardFaucet from './export/RewardFaucet.json';
+import SmartWalletWhitelist from './export/SmartWalletWhitelist.json';
+import LensReward from './export/LensReward.json';
+
+export const getArtifact = (contractName: TContractName) => {
+    if (!contractArtifacts[contractName]) {
+        throw new Error(`Contract ${contractName} not found in contractArtifacts`);
+    }
+    return contractArtifacts[contractName];
+};
+
+export const contractNetworks = {
+    '31337': {
+        // Safe
+        simulateTxAccessorAddress: '0x278Ff6d33826D906070eE938CDc9788003749e93',
+        safeProxyFactoryAddress: '0xEAB9a65eB0F098f822033192802B53EE159De5F0',
+        fallbackHandlerAddress: '0x055cBfeD6df4AFE2452b18fd3D2592D1795592b4',
+        createCallAddress: '0xb63564A81D5d4004F4f22E9aB074cE25540B0C26',
+        multiSendAddress: '0x50aF0922d65D04D87d810048Dc640E2474eBfbd9',
+        multiSendCallOnlyAddress: '0x15FC0878406CcF4d2963235A5B1EF68C67F17Ee5',
+        signMessageLibAddress: '0xa4E84979c95cD4f12C53E73d63E0A8634A1f44Ae',
+        safeMasterCopyAddress: '0xd916a690676e925Ac9Faf2d01869c13Fd9757ef2',
+
+        // Tokens
+        THX: '0xB952d9b5de7804691e7936E88915A669B15822ef',
+        USDC: '0x7150A3CC09429583471020A6CE5228A57736180a',
+        BAL: '0xe1c01805a21ee0DC535afa93172a5F21CE160649',
+        BPT: '0xf228ADAa4c3D07C8285C1025421afe2c4F320C59',
+        BPTGauge: '0x8613B8E442219e4349fa5602C69431131a7ED114',
+        BalancerVault: '0x8B219D3d1FC64e03F6cF3491E7C7A732bF253EC8',
+
+        // veTHX
+        VotingEscrow: '0x1280809d06C42E68063305235813e52c8Bb03a58',
+        RewardDistributor: '0xd0507c5363AeCfe8231FF4110e05AFf611d7F7B6',
+        RewardFaucet: '0x33599eaec2752DB3242323483A7313bA3b1111cd',
+        SmartWalletWhitelist: '0xb3B2b0fc5ce12aE58EEb13E19547Eb2Dd61A79D5',
+        LensReward: '0x774442713f32fa98bf27bEc78c96fb7186f7C223',
+
+        // Company
+        THXRegistry: '0x0Bb5Cb54566cEEf9dF1F60d8D7d2Fd01eA88279e',
+        THXPaymentSplitter: '0x58C0e64cBB7E5C7D0201A3a5c2D899cC70B0dc4c',
+
+        CompanyMultiSig: '0xaf9d56684466fcFcEA0a2B7fC137AB864d642946',
+    },
+    '137': {
+        // Tokens
+        BPT: '0xb204BF10bc3a5435017D3db247f56dA601dFe08A',
+        BPTGauge: '0xf16BECC1Bcaf0fF0b865024a644a4da1A2f8585c',
+        BalancerVault: '0xBA12222222228d8Ba445958a75a0704d566BF2C8',
+        BAL: '0x9a71012B13CA4d3D0Cdc72A177DF3ef03b0E76A3',
+        USDC: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174',
+        THX: '0x2934b36ca9A4B31E633C5BE670C8C8b28b6aA015',
+
+        // veTHX
+        VotingEscrow: '0xE3B8E734e7BCcB64B63e032795896CC57012A51D',
+        RewardDistributor: '0xCc62c812EfF9cA4c35623103B2Bb63E22f465E09',
+        RewardFaucet: '0xA1D7671f73FbcB5e079d4dC4Cffb7dDD0967EA7E',
+        SmartWalletWhitelist: '0x876625a92cEAa7f1Bddd40908B8eb5C6080cB83C',
+        LensReward: '0xE8D9624E0B7f839540E7c13577550E3Eff3FC8aA',
+
+        // Company
+        THXRegistry: '',
+        THXPaymentSplitter: '',
+        CompanyMultiSig: '0x0b8e0aAF940cc99EDA5DA5Ab0a8d6Ed798eDc08A',
+    },
+    '1': {
+        BalancerGaugeController: '0xC128468b7Ce63eA702C1f104D55A2566b13D3ABD',
+        BalancerRootGauge: '0x9902913ce5439d667774c8f9526064b2bc103b4a',
+    },
+} as ContractNetworksConfig & any;
+
+export type TContractName = (typeof contractNames)[number];
+
+export const contractArtifacts: { [contractName: string]: { abi: any; bytecode: string } } = {
+    // Safe
+    DefaultCallbackHandler,
+    CreateCall,
+    GnosisSafeL2,
+    GnosisSafeProxyFactory,
+    MultiSend,
+    MultiSendCallOnly,
+    SignMessageLib,
+    SimulateTxAccessor,
+    // Balancer
+    USDC,
+    THX,
+    BPT,
+    BPTGauge,
+    BalancerVault,
+    BalancerMinter,
+    BalancerGaugeController,
+    BAL,
+    Launchpad,
+    // Tokens
+    THXERC20_LimitedSupply,
+    THXERC20_UnlimitedSupply,
+    THXERC721,
+    THXERC1155,
+    // Protocol
+    THXPaymentSplitter,
+    THXRegistry,
+    VotingEscrow,
+    RewardDistributor,
+    RewardFaucet,
+    SmartWalletWhitelist,
+    LensReward,
+};
+
+export const contractNames = [
+    // Safe
+    'DefaultCallbackHandler',
+    'CreateCall',
+    'GnosisSafeL2',
+    'GnosisSafeProxyFactory',
+    'MultiSend',
+    'MultiSendCallOnly',
+    'SignMessageLib',
+    'SimulateTxAccessor',
+    // Balancer
+    'USDC',
+    'THX',
+    'BPT',
+    'BPTGauge',
+    'BalancerVault',
+    'BalancerMinter',
+    'BalancerGaugeController',
+    'BAL',
+    'Launchpad',
+    // Tokens
+    'THXERC20_LimitedSupply',
+    'THXERC20_UnlimitedSupply',
+    'THXERC721',
+    'THXERC1155',
+    // Protocol
+    'THXPaymentSplitter',
+    'THXRegistry',
+    'VotingEscrow',
+    'RewardDistributor',
+    'RewardFaucet',
+    'SmartWalletWhitelist',
+    'LensReward',
+] as const;
diff --git a/apps/api/src/app/hardhat/scripts/deploy.ts b/apps/api/src/app/hardhat/scripts/deploy.ts
new file mode 100644
index 000000000..a84ddbdd1
--- /dev/null
+++ b/apps/api/src/app/hardhat/scripts/deploy.ts
@@ -0,0 +1,116 @@
+import hre from 'hardhat';
+import { parseUnits } from 'ethers/lib/utils';
+import { contractArtifacts, getArtifact, TContractName } from '..';
+import { Signer } from '@ethersproject/abstract-signer';
+
+const deploy = async (contractName: TContractName, args: string[], signer: Signer) => {
+    const artifact = getArtifact(contractName);
+    const factory = new hre.ethers.ContractFactory(artifact.abi, artifact.bytecode, signer);
+    const contract = await factory.deploy(...args);
+    console.log(`${contractName} ${contract.address}`);
+    return contract;
+};
+
+async function main() {
+    const [signer] = (await hre.ethers.getSigners()) as unknown as Signer[];
+    // Deploy Safe infrastructure
+    for (const contractName of [
+        'SimulateTxAccessor',
+        'GnosisSafeProxyFactory',
+        'DefaultCallbackHandler',
+        'CreateCall',
+        'MultiSend',
+        'MultiSendCallOnly',
+        'SignMessageLib',
+        'GnosisSafeL2',
+    ] as TContractName[]) {
+        await deploy(contractName, [], signer);
+    }
+    // Deploy Balancer infrastructure
+    const totalSupply = parseUnits('1000000', 'ether').toString();
+    const thx = await deploy('THX', [await signer.getAddress(), totalSupply], signer);
+    const usdc = await deploy('USDC', [await signer.getAddress(), totalSupply], signer);
+    const bal = await deploy('BAL', [await signer.getAddress(), totalSupply], signer);
+    const bpt = await deploy('BPT', [await signer.getAddress(), totalSupply], signer);
+    const gauge = await deploy('BPTGauge', [bpt.address], signer);
+    const vault = await deploy('BalancerVault', [bpt.address, usdc.address, thx.address], signer);
+
+    await bpt.setVault(vault.address);
+    await bpt.transfer(vault.address, parseUnits('500000', 'ether').toString());
+
+    // Deploy Balancer Launchpad Implementations
+    const VotingEscrow = await deploy('VotingEscrow', [], signer);
+    const RewardDistributor = await deploy('RewardDistributor', [], signer);
+    const RewardFaucet = await deploy('RewardFaucet', [], signer);
+
+    // Deploy Balancer Launchpad
+    const BalMinter = await deploy('BalancerMinter', [bal.address], signer);
+    const Launchpad = await deploy(
+        'Launchpad',
+        [VotingEscrow.address, RewardDistributor.address, RewardFaucet.address, bal.address, BalMinter.address],
+        signer,
+    );
+
+    /*
+    @notice Deploys new VotingEscrow, RewardDistributor and RewardFaucet contracts
+    @param tokenBptAddr The address of the token to be used for locking
+    @param name The name for the new VotingEscrow contract
+    @param symbol The symbol for the new VotingEscrow contract
+    @param maxLockTime A constraint for the maximum lock time in the new VotingEscrow contract
+    @param rewardDistributorStartTime The start time for reward distribution
+    @param admin_unlock_all Admin address to enable unlock-all feature in VotingEscrow (zero-address to disable forever)
+    @param admin_early_unlock Admin address to enable eraly-unlock feature in VotingEscrow (zero-address to disable forever)
+    @param rewardReceiver The receiver address of claimed BAL-token rewards
+    */
+    const lp = new hre.ethers.Contract(Launchpad.address, contractArtifacts['Launchpad'].abi, signer);
+    let tx = await lp.deploy(
+        gauge.address,
+        'Voted Escrow 20USDC-80THX-gauge',
+        'veTHX',
+        7776000, // 90 days
+        Math.ceil(Date.now() / 1000) + 60 * 60 * 24 * 7, // 7 days from now
+        await signer.getAddress(), // admin_unlock_all
+        await signer.getAddress(), // admin_early_unlock
+        '0x0000000000000000000000000000000000000000', // empty will set it to the rewardDistributor
+    );
+    tx = await tx.wait();
+
+    const event = tx.events.find((event: any) => event.event == 'VESystemCreated');
+    const { votingEscrow, rewardDistributor, rewardFaucet } = event.args;
+    console.log(`VotingEscrow ${votingEscrow}`);
+    console.log(`RewardDistributor ${rewardDistributor}`);
+    console.log(`RewardFaucet ${rewardFaucet}`);
+
+    // Configure VeTHX
+    const vethx = new hre.ethers.Contract(votingEscrow, contractArtifacts['VotingEscrow'].abi, signer);
+    const rdthx = new hre.ethers.Contract(rewardDistributor, contractArtifacts['RewardDistributor'].abi, signer);
+    const smartCheckerList = await deploy('SmartWalletWhitelist', [await signer.getAddress()], signer);
+    const lensReward = await deploy('LensReward', [], signer);
+
+    // Configure reward tokens in reward distributor
+    await rdthx.addAllowedRewardTokens([bal.address, bpt.address]);
+
+    // Add smart wallet whitelist checker
+    await vethx.commit_smart_wallet_checker(smartCheckerList.address);
+    await vethx.apply_smart_wallet_checker();
+    await vethx.set_early_unlock(true);
+    // await vethx.set_early_unlock_penalty_speed(1); //Default
+    // Set early exit penalty treasury to reward distributor
+    await vethx.set_penalty_treasury(rewardDistributor);
+    // Allow all contract wallets
+    await smartCheckerList.setAllowAll(true);
+
+    // Deploy THXRegistry
+    const registry = await deploy(
+        'THXRegistry',
+        [usdc.address, await signer.getAddress(), rdthx.address, gauge.address],
+        signer,
+    );
+    await registry.setPayoutRate('3000'); // 30%
+
+    // Deploy PaymentSplitter
+    const splitter = await deploy('THXPaymentSplitter', [await signer.getAddress(), registry.address], signer);
+    await splitter.setRegistry(registry.address);
+}
+
+main().catch(console.error);
diff --git a/apps/api/src/app/hardhat/tsconfig.json b/apps/api/src/app/hardhat/tsconfig.json
new file mode 100644
index 000000000..fe59b369b
--- /dev/null
+++ b/apps/api/src/app/hardhat/tsconfig.json
@@ -0,0 +1,13 @@
+{
+    "extends": "../../../tsconfig.json",
+    "compilerOptions": {
+        "target": "es2020",
+        "module": "commonjs",
+        "esModuleInterop": true,
+        "forceConsistentCasingInFileNames": true,
+        "strict": true,
+        "skipLibCheck": true,
+        "resolveJsonModule": true
+    },
+    "include": ["./**/*.ts", "./**/*.json"]
+}
diff --git a/apps/api/src/app/index.ts b/apps/api/src/app/index.ts
new file mode 100644
index 000000000..fac02b9bb
--- /dev/null
+++ b/apps/api/src/app/index.ts
@@ -0,0 +1,53 @@
+import 'express-async-errors';
+import axios from 'axios';
+import axiosBetterStacktrace from 'axios-better-stacktrace';
+import compression from 'compression';
+import express, { Express, Request } from 'express';
+import lusca from 'lusca';
+import db from '@thxnetwork/api/util/database';
+import morganBody from 'morgan-body';
+import { router } from '@thxnetwork/api/controllers/index';
+import { MONGODB_URI, NODE_ENV, PORT, VERSION } from '@thxnetwork/api/config/secrets';
+import { corsHandler, errorLogger, errorNormalizer, errorOutput, notFoundHandler } from '@thxnetwork/api/middlewares';
+import { assetsPath } from './util/path';
+import morgan from './middlewares/morgan';
+
+axiosBetterStacktrace(axios);
+
+const app: Express = express();
+
+db.connect(MONGODB_URI);
+
+app.set('trust proxy', true);
+app.set('port', PORT);
+app.use(lusca.xframe('SAMEORIGIN'));
+app.use(lusca.xssProtection(true));
+app.use(express.static(assetsPath));
+app.use(
+    express.json({
+        verify(req: Request, res, buf, encoding: BufferEncoding) {
+            if (buf && buf.length) {
+                req.rawBody = buf.toString(encoding || 'utf8');
+            }
+        },
+    }),
+);
+
+app.use(morgan);
+
+morganBody(app, {
+    logRequestBody: NODE_ENV === 'development',
+    logResponseBody: false,
+    skip: () => ['test', 'production'].includes(NODE_ENV),
+});
+
+app.use(express.urlencoded({ extended: true }));
+app.use(corsHandler);
+app.use(`/${VERSION}`, router);
+app.use(notFoundHandler);
+app.use(errorLogger);
+app.use(errorNormalizer);
+app.use(errorOutput);
+app.use(compression());
+
+export default app;
diff --git a/apps/api/src/app/jobs/createTwitterQuests.ts b/apps/api/src/app/jobs/createTwitterQuests.ts
new file mode 100644
index 000000000..27e02c0b3
--- /dev/null
+++ b/apps/api/src/app/jobs/createTwitterQuests.ts
@@ -0,0 +1,99 @@
+import { AccessTokenKind, QuestVariant, QuestSocialRequirement, OAuthRequiredScopes } from '@thxnetwork/common/enums';
+import { Pool, QuestSocial } from '@thxnetwork/api/models';
+import { logger } from '../util/logger';
+import { DASHBOARD_URL } from '../config/secrets';
+import TwitterDataProxy from '../proxies/TwitterDataProxy';
+import AccountProxy from '../proxies/AccountProxy';
+import MailService from '../services/MailService';
+import QuestService from '../services/QuestService';
+
+export async function createTwitterQuests() {
+    for await (const pool of Pool.find({ 'settings.isTwitterSyncEnabled': true })) {
+        try {
+            const { hashtag, title, description, amount, locks, isPublished } =
+                pool.settings.defaults.conditionalRewards;
+
+            const account = await AccountProxy.findById(pool.sub);
+            if (!account) {
+                logger.error(`Account not found for ${pool.sub}.`);
+                continue;
+            }
+
+            const token = await AccountProxy.getToken(
+                account,
+                AccessTokenKind.Twitter,
+                OAuthRequiredScopes.TwitterAutoQuest,
+            );
+            if (!token) {
+                logger.error(`Could not find Twitter accounts for ${pool.sub} in ${pool.settings.title}`);
+                continue;
+            }
+
+            const tweets = await TwitterDataProxy.searchTweets(account, `#${hashtag}`);
+            if (!tweets || !tweets.length) continue;
+            logger.info(`Found tweets matching the hashtag in the last 7 days!`);
+            logger.info(JSON.stringify(tweets));
+
+            const promises = tweets.map(async (tweet: any) => {
+                const isExistingQuest = !!(await QuestSocial.exists({
+                    poolId: String(pool._id),
+                    content: tweet.id,
+                }));
+                return { ...tweet, isExistingQuest };
+            });
+            const recentTweets = await Promise.all(promises);
+            logger.info(`Found ${recentTweets.length} posts for ${pool.sub} in ${pool.settings.title}`);
+
+            const newTweets = recentTweets.filter(
+                (tweet) => !tweet.isExistingQuest && tweet.text.includes(`#${hashtag}`),
+            );
+            if (!newTweets.length) {
+                logger.info(`Found no new autoquests for ${pool.sub} in ${pool.settings.title}`);
+                continue;
+            }
+            logger.info(`Found ${newTweets.length} new autoquest for ${pool.sub} in ${pool.settings.title}`);
+
+            const quests = await Promise.all(
+                newTweets.map(async (tweet) => {
+                    try {
+                        const contentMetadata = JSON.stringify({
+                            url: `https://twitter.com/${token.metadata.username}/status/${tweet.id}`,
+                            username: token.metadata.username,
+                            text: tweet.text,
+                            minFollowersCount: 0,
+                        });
+                        return await QuestService.create(QuestVariant.Twitter, pool._id, {
+                            index: 0,
+                            title,
+                            description,
+                            amount,
+                            locks,
+                            kind: AccessTokenKind.Twitter,
+                            interaction: QuestSocialRequirement.TwitterLikeRetweet,
+                            content: tweet.id,
+                            contentMetadata,
+                            isPublished,
+                        });
+                    } catch (error) {
+                        logger.error(error.message);
+                    }
+                }),
+            );
+
+            const subject = `Created ${quests.length} Twitter Quest${quests.length && 's'}!`;
+            const message = `We have detected ${quests.length} new tweet${
+                quests.length && 's'
+            } in <a href="https://www.twitter.com/${token.metadata.username}">@${
+                token.metadata.username
+            }</a>. A Twitter Quest ${quests.length && 'for each'} has been ${
+                isPublished ? 'published' : 'prepared'
+            } for you in <a href="${DASHBOARD_URL}/pool/${pool._id}/quests">${pool.settings.title}</a>.`;
+
+            await MailService.send(account.email, subject, message);
+
+            logger.info(`Created ${quests.length} Twitter Quests in ${pool.settings.title}`);
+        } catch (error) {
+            logger.info(error);
+        }
+    }
+}
diff --git a/apps/api/src/app/jobs/sendPoolAnalyticsReport.ts b/apps/api/src/app/jobs/sendPoolAnalyticsReport.ts
new file mode 100644
index 000000000..ad1c04109
--- /dev/null
+++ b/apps/api/src/app/jobs/sendPoolAnalyticsReport.ts
@@ -0,0 +1,110 @@
+import { Pool } from '@thxnetwork/api/models';
+import { DASHBOARD_URL } from '../config/secrets';
+import { logger } from '../util/logger';
+import PoolService from '../services/PoolService';
+import AccountProxy from '../proxies/AccountProxy';
+import MailService from '../services/MailService';
+import AnalyticsService from '../services/AnalyticsService';
+
+const emojiMap = ['🥇', '🥈', '🥉'];
+const oneDay = 86400000; // one day in milliseconds
+
+export async function sendPoolAnalyticsReport() {
+    const endDate = new Date();
+    endDate.setHours(0, 0, 0, 0);
+
+    const startDate = new Date(new Date(endDate).getTime() - oneDay * 7);
+    const dateRange = { startDate, endDate };
+
+    let account: TAccount;
+
+    for await (const pool of Pool.find({ 'settings.isWeeklyDigestEnabled': true })) {
+        try {
+            if (!account || account.sub != pool.sub) account = await AccountProxy.findById(pool.sub);
+            if (!account.email) continue;
+
+            const { dailyQuest, inviteQuest, socialQuest, customQuest, coinReward, nftReward } =
+                await AnalyticsService.getPoolMetrics(pool, dateRange);
+            const leaderboard = await PoolService.findParticipants(pool, 1, 10);
+            const subs = leaderboard.results.map((entry) => entry.sub);
+            const accounts = await AccountProxy.find({ subs });
+
+            const totalPointsClaimed =
+                dailyQuest.totalAmount + inviteQuest.totalAmount + socialQuest.totalAmount + customQuest.totalAmount;
+            const totalPointsSpent = coinReward.totalAmount + nftReward.totalAmount;
+
+            // Skip if nothing happened.
+            if (!totalPointsClaimed && !totalPointsSpent) continue;
+
+            let html = `<p style="font-size: 18px">Hi there!👋</p>`;
+            html += `<p>We're pleased to bring you the <strong>Weekly Digest</strong> for "${pool.settings.title}".</p>`;
+            html += `<hr />`;
+
+            html += `<p><strong>🏆 Quests: </strong> ${totalPointsClaimed} points claimed</p>`;
+            html += `<table width="100%" role="presentation" border="0" cellpadding="0" cellspacing="0">`;
+            if (dailyQuest.totalCreated) {
+                html += `<tr>
+                <td><strong>${dailyQuest.totalCreated}x</strong> Daily - ${dailyQuest.totalAmount} pts</td>
+                <td align="right"><a href="${DASHBOARD_URL}/pool/${pool._id}/quests">Manage</a></td>
+                `;
+            }
+            if (inviteQuest.totalCreated) {
+                html += `<tr>
+                <td><strong>${inviteQuest.totalCreated}x</strong> Invite - ${inviteQuest.totalAmount} pts)</td>
+                <td align="right"><a href="${DASHBOARD_URL}/pool/${pool._id}/quests">Manage</a></td>
+                </tr>`;
+            }
+            if (socialQuest.totalCreated) {
+                html += `<tr>
+                <td><strong>${socialQuest.totalCreated}x</strong> Social - ${socialQuest.totalAmount} pts</td>
+                <td align="right"><a href="${DASHBOARD_URL}/pool/${pool._id}/quests">Manage</a></td>
+                </tr>`;
+            }
+            if (customQuest.totalCreated) {
+                html += `<tr>
+                <td><strong>${customQuest.totalCreated}x</strong> Custom - ${customQuest.totalAmount} pts</td>
+                <td align="right"><a href="${DASHBOARD_URL}/pool/${pool._id}/quests">Manage</a></td>
+                </tr>`;
+            }
+            html += `</table>`;
+            html += `<hr />`;
+
+            html += `<p><strong>🎁 Rewards: </strong> ${totalPointsSpent} points spent</p>`;
+            html += `<table width="100%" role="presentation" border="0" cellpadding="0" cellspacing="0">`;
+            if (coinReward.totalCreated) {
+                html += `<tr>
+                <td><strong>${coinReward.totalCreated}x</strong> Coin Rewards (${coinReward.totalAmount} points)</td>
+                <td align="right" ><a href="${DASHBOARD_URL}/pool/${pool._id}/rewards">Manage</a></td>
+                </tr>`;
+            }
+            if (nftReward.totalCreated) {
+                html += `<tr>
+                <td><strong>${nftReward.totalCreated}x</strong> NFT Rewards (${nftReward.totalAmount} points)</td>
+                <td align="right"><a href="${DASHBOARD_URL}/pool/${pool._id}/rewards">Manage</a></td>
+                </tr>`;
+            }
+            html += `</table>`;
+            html += `<hr />`;
+
+            html += `<p style="font-size:16px"><strong>Top 3</strong></p>`;
+            html += `<table role="presentation" border="0" cellpadding="0" cellspacing="0">`;
+
+            for (const index in leaderboard.results) {
+                const entry = leaderboard[index];
+                const account = accounts.find((a) => a.sub === entry.sub);
+
+                html += `<tr>
+                <td width="5%">${emojiMap[index]}</td>
+                <td><strong>${account.firstName || '...'}</strong> ${entry.wallet.address.substring(0, 8)}...</td>
+                <td align="right" width="25%"><strong>${entry.score} Points</strong></td>
+                </tr>`;
+            }
+            html += '</table>';
+            html += `<a href="${DASHBOARD_URL}/pool/${pool._id}/participants">All participants</a>`;
+
+            await MailService.send(account.email, `🎁 Weekly Digest: "${pool.settings.title}"`, html);
+        } catch (error) {
+            logger.error(error);
+        }
+    }
+}
diff --git a/apps/api/src/app/jobs/updateCampaignRanks.ts b/apps/api/src/app/jobs/updateCampaignRanks.ts
new file mode 100644
index 000000000..a34e1fe71
--- /dev/null
+++ b/apps/api/src/app/jobs/updateCampaignRanks.ts
@@ -0,0 +1,100 @@
+import {
+    Pool,
+    RewardCoin,
+    RewardNFT,
+    RewardCustom,
+    RewardCoupon,
+    RewardDiscordRole,
+    RewardGalachain,
+    QuestDaily,
+    QuestInvite,
+    QuestSocial,
+    QuestCustom,
+    QuestWeb3,
+    QuestGitcoin,
+    Participant,
+} from '@thxnetwork/api/models';
+import { logger } from '../util/logger';
+
+export async function updateCampaignRanks() {
+    try {
+        const questModels = [QuestDaily, QuestInvite, QuestSocial, QuestCustom, QuestWeb3, QuestGitcoin];
+        const rewardModels = [RewardCoin, RewardNFT, RewardCustom, RewardCoupon, RewardDiscordRole, RewardGalachain];
+        const questLookupStages = questModels.map((model) => {
+            return {
+                $lookup: {
+                    from: model.collection.name,
+                    localField: 'id',
+                    foreignField: 'poolId',
+                    as: model.collection.name,
+                },
+            };
+        });
+        const rewardLookupStages = rewardModels.map((model) => {
+            return {
+                $lookup: {
+                    from: model.collection.name,
+                    localField: 'id',
+                    foreignField: 'poolId',
+                    as: model.collection.name,
+                },
+            };
+        });
+        const campaigns = await Pool.aggregate([
+            {
+                $addFields: {
+                    id: { $toString: '$_id' },
+                },
+            },
+            {
+                $lookup: {
+                    from: Participant.collection.name,
+                    localField: 'id',
+                    foreignField: 'poolId',
+                    as: Participant.collection.name,
+                },
+            },
+            // Rewards
+            ...questLookupStages,
+            ...rewardLookupStages,
+            {
+                $addFields: {
+                    participantCount: { $size: `$${Participant.collection.name}` },
+                    totalQuestCount: {
+                        $size: {
+                            $concatArrays: questModels.map((model) => `$${model.collection.name}`),
+                        },
+                    },
+                    totalRewardsCount: {
+                        $size: {
+                            $concatArrays: rewardModels.map((model) => `$${model.collection.name}`),
+                        },
+                    },
+                },
+            },
+            {
+                $match: {
+                    'settings.isPublished': true,
+                    'totalQuestCount': { $gt: 0 },
+                    'totalRewardsCount': { $gt: 0 },
+                },
+            },
+            {
+                $sort: { participantCount: -1 },
+            },
+        ]).exec();
+
+        await Pool.bulkWrite(
+            campaigns.map((campaign, index) => {
+                return {
+                    updateOne: {
+                        filter: { _id: campaign._id },
+                        update: { $set: { rank: Number(index) + 1 } },
+                    },
+                };
+            }),
+        );
+    } catch (error) {
+        logger.error(error);
+    }
+}
diff --git a/apps/api/src/app/jobs/updateParticipantRanks.ts b/apps/api/src/app/jobs/updateParticipantRanks.ts
new file mode 100644
index 000000000..94da0c021
--- /dev/null
+++ b/apps/api/src/app/jobs/updateParticipantRanks.ts
@@ -0,0 +1,20 @@
+import { Pool } from '@thxnetwork/api/models';
+import { logger } from '../util/logger';
+import { Job } from '@hokify/agenda';
+import AnalyticsService from '../services/AnalyticsService';
+
+export async function updateParticipantRanks(job: Job) {
+    if (!job.attrs.data) return;
+
+    try {
+        const { poolId } = job.attrs.data as { poolId: string };
+        const pool = await Pool.findById(poolId);
+        if (!pool) throw new Error('Could not find campaign');
+
+        await AnalyticsService.createLeaderboard(pool);
+
+        logger.info('Updated participant ranks.');
+    } catch (error) {
+        logger.error(error);
+    }
+}
diff --git a/apps/api/src/app/jobs/updatePendingTransactions.ts b/apps/api/src/app/jobs/updatePendingTransactions.ts
new file mode 100644
index 000000000..337cf18b8
--- /dev/null
+++ b/apps/api/src/app/jobs/updatePendingTransactions.ts
@@ -0,0 +1,56 @@
+import { TransactionState, TransactionType } from '@thxnetwork/common/enums';
+import { Transaction, TransactionDocument } from '@thxnetwork/api/models/Transaction';
+import { Wallet } from '../models/Wallet';
+import TransactionService from '@thxnetwork/api/services/TransactionService';
+import SafeService from '../services/SafeService';
+import { logger } from '../util/logger';
+
+export async function updatePendingTransactions() {
+    const transactions: TransactionDocument[] = await Transaction.find({
+        $or: [{ state: TransactionState.Confirmed }, { state: TransactionState.Sent }],
+    }).sort({ createdAt: 'asc' });
+
+    // Iterate over all tx sent to or proposed and confirmed by the relayer
+    for (const tx of transactions) {
+        switch (tx.state) {
+            // Legacy tx will not have this state
+            // Transactions is proposed and confirmed by the relayer, awaiting user wallet confirmation
+            case TransactionState.Confirmed: {
+                if (!tx.walletId) continue;
+
+                const wallet = await Wallet.findById(tx.walletId);
+
+                let pendingTx;
+                try {
+                    pendingTx = await SafeService.getTransaction(wallet, tx.safeTxHash);
+                    logger.debug(`Safe TX Found: ${tx.safeTxHash}`);
+                } catch (error) {
+                    logger.error(error);
+                }
+
+                // Safes for pools have a single signer (relayer) while safes for end users
+                // have 2 (relayer + web3auth mpc key)
+                const threshold = wallet.poolId ? 1 : 2;
+                if (pendingTx && pendingTx.confirmations.length >= threshold) {
+                    logger.debug(`Safe TX Confirmed: ${tx.safeTxHash}`);
+
+                    try {
+                        await SafeService.executeTransaction(wallet, tx.safeTxHash);
+                        logger.debug(`Safe TX Executed: ${tx.safeTxHash}`);
+                    } catch (error) {
+                        await tx.updateOne({ state: TransactionState.Failed });
+                        logger.error(error);
+                    }
+                }
+                break;
+            }
+            // TransactionType.Default is handled in tx service send methods
+            case TransactionState.Sent: {
+                if (tx.type == TransactionType.Relayed) {
+                    TransactionService.queryTransactionStatusDefender(tx).catch((error) => logger.error(error));
+                }
+                break;
+            }
+        }
+    }
+}
diff --git a/apps/api/src/app/middlewares/assertAccount.ts b/apps/api/src/app/middlewares/assertAccount.ts
new file mode 100644
index 000000000..f63167119
--- /dev/null
+++ b/apps/api/src/app/middlewares/assertAccount.ts
@@ -0,0 +1,21 @@
+import { Response, Request, NextFunction } from 'express';
+import { NotFoundError } from '../util/errors';
+import { Pool } from '@thxnetwork/api/models';
+import AccountProxy from '../proxies/AccountProxy';
+
+const assertAccount = async (req: Request, res: Response, next: NextFunction) => {
+    const account = await AccountProxy.findById(req.auth.sub);
+    if (!account) throw new Error('Account not found.');
+    req.account = account;
+
+    const poolId = req.header('X-PoolId');
+    if (poolId) {
+        const pool = await Pool.findById(poolId);
+        if (!pool) throw new NotFoundError('Could not find campaign');
+        req.campaign = pool;
+    }
+
+    next();
+};
+
+export { assertAccount };
diff --git a/apps/api/src/app/middlewares/assertPayment.ts b/apps/api/src/app/middlewares/assertPayment.ts
new file mode 100644
index 000000000..27aca009d
--- /dev/null
+++ b/apps/api/src/app/middlewares/assertPayment.ts
@@ -0,0 +1,26 @@
+import { Response, Request, NextFunction } from 'express';
+import { BigNumber } from 'alchemy-sdk';
+import { logger } from '../util/logger';
+import SafeService from '../services/SafeService';
+import PoolService from '../services/PoolService';
+import PaymentService from '../services/PaymentService';
+
+/*
+ * This middleware function is used to assert payments of the pool owner.
+ * @dev Assumes that the poolId is available as 'id' param in the request
+ */
+export async function assertPayment(req: Request, res: Response, next: NextFunction) {
+    const pool = await PoolService.getById(req.params.id);
+    const safe = await SafeService.findOneByPool(pool);
+    const balanceInWei = await PaymentService.balanceOf(safe);
+
+    // If pool.createdAt + 2 weeks is larger than now there should be a payment
+    const isPostTrial = Date.now() > new Date(pool.trialEndsAt).getTime();
+    if (isPostTrial && BigNumber.from(balanceInWei).eq(0)) {
+        // @dev Disable until we agree on a better notification flow
+        // throw new ForbiddenError('Payment is required.');
+        logger.info(JSON.stringify({ poolId: pool._id, safeAddress: safe.address, isPostTrial, balanceInWei }));
+    }
+
+    next();
+}
diff --git a/apps/api/src/app/middlewares/assertPoolAccess.ts b/apps/api/src/app/middlewares/assertPoolAccess.ts
new file mode 100644
index 000000000..d4d3bb93d
--- /dev/null
+++ b/apps/api/src/app/middlewares/assertPoolAccess.ts
@@ -0,0 +1,25 @@
+import { Response, Request, NextFunction } from 'express';
+import PoolService from '@thxnetwork/api/services/PoolService';
+import { AudienceUnauthorizedError, ForbiddenError, SubjectUnauthorizedError } from '@thxnetwork/api/util/errors';
+import { ALLOWED_API_CLIENT_ID } from '../config/secrets';
+
+export async function assertPoolAccess(
+    req: Request & { user: { sub: string; aud: string } },
+    res: Response,
+    next: NextFunction,
+) {
+    if (req.auth.aud === ALLOWED_API_CLIENT_ID && ALLOWED_API_CLIENT_ID) return next();
+
+    const poolId = req.header('X-PoolId') || req.params.id; // Deprecate the header non pool child resources are tested
+    if (!poolId) throw new ForbiddenError('Missing id param or X-PoolId header');
+
+    // If there is a sub check if the user is an owner or collaborator
+    const isSubjectAllowed = await PoolService.isSubjectAllowed(req.auth.sub, poolId);
+    if (req.auth.sub !== req.auth.aud && !isSubjectAllowed) throw new SubjectUnauthorizedError();
+
+    const isAudienceAllowed = await PoolService.isAudienceAllowed(req.auth.aud, poolId);
+    // Equal sub and aud are client_credentials grants
+    if (req.auth.sub === req.auth.aud && !isAudienceAllowed) throw new AudienceUnauthorizedError();
+
+    next();
+}
diff --git a/apps/api/src/app/middlewares/assertQuestAccess.ts b/apps/api/src/app/middlewares/assertQuestAccess.ts
new file mode 100644
index 000000000..fb9dd7378
--- /dev/null
+++ b/apps/api/src/app/middlewares/assertQuestAccess.ts
@@ -0,0 +1,19 @@
+import { ForbiddenError } from '@thxnetwork/api/util/errors';
+import { QuestVariant } from '@thxnetwork/common/enums';
+import { Response, Request, NextFunction } from 'express';
+import { serviceMap } from '../services/interfaces/IQuestService';
+
+// @dev For all social quests use QuestVariant.Twitter by default as we can not access the
+// quest interaction type yet but have to assign the db collection
+// in order to check quest access
+export function assertQuestAccess(variant: QuestVariant) {
+    return async (req: Request, res: Response, next: NextFunction) => {
+        const poolId = req.header('X-PoolId');
+        const Quest = serviceMap[variant].models.quest;
+        const quest = await Quest.findById(req.params.id);
+        if (poolId !== quest.poolId) {
+            throw new ForbiddenError(`Not your quest!`);
+        }
+        next();
+    };
+}
diff --git a/apps/api/src/app/middlewares/assertRequestInput.ts b/apps/api/src/app/middlewares/assertRequestInput.ts
new file mode 100644
index 000000000..dcc0fe481
--- /dev/null
+++ b/apps/api/src/app/middlewares/assertRequestInput.ts
@@ -0,0 +1,13 @@
+import { Response, Request, NextFunction } from 'express';
+import { validationResult } from 'express-validator';
+
+export function assertRequestInput(validations: any) {
+    return async (req: Request, res: Response, next: NextFunction) => {
+        await Promise.all(validations.map((validation: any) => validation.run(req)));
+
+        const errors = validationResult(req);
+        if (errors.isEmpty()) return next();
+
+        res.status(400).json({ errors: errors.array() });
+    };
+}
diff --git a/apps/api/src/app/middlewares/assertUUID.ts b/apps/api/src/app/middlewares/assertUUID.ts
new file mode 100644
index 000000000..9fa05c645
--- /dev/null
+++ b/apps/api/src/app/middlewares/assertUUID.ts
@@ -0,0 +1,11 @@
+import { Request } from 'express';
+import { ForbiddenError } from '../util/errors';
+import WalletService from '../services/WalletService';
+
+const assertUUID = async (req: Request) => {
+    const sub = await WalletService.findOne({ uuid: req.body.uuid });
+    if (!sub) throw new ForbiddenError('Sub not found for this uuid.');
+    req.auth = { sub };
+};
+
+export { assertUUID };
diff --git a/apps/api/src/app/middlewares/assertWallet.ts b/apps/api/src/app/middlewares/assertWallet.ts
new file mode 100644
index 000000000..5585dbdbd
--- /dev/null
+++ b/apps/api/src/app/middlewares/assertWallet.ts
@@ -0,0 +1,19 @@
+import { Response, Request, NextFunction } from 'express';
+import { ForbiddenError, NotFoundError } from '../util/errors';
+import WalletService from '../services/WalletService';
+
+const assertWallet = async (req: Request, res: Response, next: NextFunction) => {
+    const walletId = req.query.walletId as string;
+
+    if (walletId) {
+        const wallet = await WalletService.findById(walletId);
+        if (!wallet) throw new NotFoundError('Wallet not found');
+        if (wallet.sub !== req.auth.sub) throw new ForbiddenError('Wallet not owned by sub.');
+
+        req.wallet = wallet;
+    }
+
+    next();
+};
+
+export { assertWallet };
diff --git a/apps/api/src/app/middlewares/checkJwt.ts b/apps/api/src/app/middlewares/checkJwt.ts
new file mode 100644
index 000000000..7a12f3d04
--- /dev/null
+++ b/apps/api/src/app/middlewares/checkJwt.ts
@@ -0,0 +1,20 @@
+import jwksRsa from 'jwks-rsa';
+import expressJwtPermissions from 'express-jwt-permissions';
+import { expressjwt } from 'express-jwt';
+import { AUTH_URL } from '@thxnetwork/api/config/secrets';
+
+export const checkJwt: any = expressjwt({
+    secret: jwksRsa.expressJwtSecret({
+        cache: true,
+        rateLimit: true,
+        jwksRequestsPerMinute: 10,
+        jwksUri: `${AUTH_URL}/jwks`,
+    }),
+    issuer: AUTH_URL,
+    algorithms: ['RS256'],
+});
+
+export const guard: any = expressJwtPermissions({
+    requestProperty: 'auth',
+    permissionsProperty: 'scope',
+});
diff --git a/apps/api/src/app/middlewares/corsHandler.ts b/apps/api/src/app/middlewares/corsHandler.ts
new file mode 100644
index 000000000..482968f9c
--- /dev/null
+++ b/apps/api/src/app/middlewares/corsHandler.ts
@@ -0,0 +1,29 @@
+import cors from 'cors';
+import { AUTH_URL, API_URL, WALLET_URL, DASHBOARD_URL, WIDGET_URL, PUBLIC_URL } from '@thxnetwork/api/config/secrets';
+import ClientProxy from '@thxnetwork/api/proxies/ClientProxy';
+
+export const corsHandler = cors(async (req: any, callback: any) => {
+    const origin = req.header('Origin');
+    const allowedOrigins = [
+        AUTH_URL,
+        API_URL,
+        WALLET_URL,
+        DASHBOARD_URL,
+        WIDGET_URL,
+        PUBLIC_URL,
+        'https://app.thx.network',
+        'https://dev-app.thx.network',
+    ];
+    const isAllowedOrigin = await ClientProxy.isAllowedOrigin(origin);
+
+    if (isAllowedOrigin) {
+        allowedOrigins.push(origin);
+    }
+
+    if (!origin || allowedOrigins.includes(origin)) {
+        allowedOrigins.push(origin);
+        callback(null, { credentials: true, origin: allowedOrigins });
+    } else {
+        callback(new Error(`${origin} is not allowed by CORS`));
+    }
+});
diff --git a/apps/api/src/app/middlewares/errorLogger.ts b/apps/api/src/app/middlewares/errorLogger.ts
new file mode 100644
index 000000000..e51bb8bc9
--- /dev/null
+++ b/apps/api/src/app/middlewares/errorLogger.ts
@@ -0,0 +1,11 @@
+import { THXHttpError } from '@thxnetwork/api/util/errors';
+import { logger } from '@thxnetwork/api/util/logger';
+import { NextFunction, Request, Response } from 'express';
+
+export const errorLogger = (error: Error, req: Request, res: Response, next: NextFunction) => {
+    if (!(error instanceof THXHttpError)) {
+        logger.error('Error caught:', error);
+    }
+
+    next(error);
+};
diff --git a/apps/api/src/app/middlewares/errorNormalizer.ts b/apps/api/src/app/middlewares/errorNormalizer.ts
new file mode 100644
index 000000000..fd2308ecf
--- /dev/null
+++ b/apps/api/src/app/middlewares/errorNormalizer.ts
@@ -0,0 +1,11 @@
+import { NextFunction, Request, Response } from 'express';
+import { UnauthorizedError } from '@thxnetwork/api/util/errors';
+import { UnauthorizedError as JWTUnauthorizedError } from 'express-jwt';
+
+export const errorNormalizer = (error: Error, _req: Request, _res: Response, next: NextFunction) => {
+    if (error instanceof JWTUnauthorizedError) {
+        return next(new UnauthorizedError(error.message));
+    }
+
+    next(error);
+};
diff --git a/apps/api/src/app/middlewares/errorOutput.ts b/apps/api/src/app/middlewares/errorOutput.ts
new file mode 100644
index 000000000..72ad710d9
--- /dev/null
+++ b/apps/api/src/app/middlewares/errorOutput.ts
@@ -0,0 +1,28 @@
+import { NextFunction, Request, Response } from 'express';
+import { THXHttpError } from '@thxnetwork/api/util/errors';
+import { NODE_ENV } from '@thxnetwork/api/config/secrets';
+
+interface ErrorResponse {
+    error: {
+        message: string;
+        error?: Error;
+        rootMessage?: string;
+        stack?: string;
+    };
+}
+
+// Error handler needs to have 4 arguments.
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export const errorOutput = (error: any, req: Request, res: Response, next: NextFunction) => {
+    let status = 500;
+    const response: ErrorResponse = { error: { message: 'Unable to fulfill request' } };
+    if (error instanceof THXHttpError || error.status) {
+        status = error.status;
+        response.error.message = error.message;
+    } else if (NODE_ENV !== 'production') {
+        response.error.error = error;
+        response.error.stack = error.stack;
+    }
+
+    res.status(status).json(response);
+};
diff --git a/apps/api/src/app/middlewares/index.ts b/apps/api/src/app/middlewares/index.ts
new file mode 100644
index 000000000..5d0f6b48d
--- /dev/null
+++ b/apps/api/src/app/middlewares/index.ts
@@ -0,0 +1,12 @@
+export * from './assertPoolAccess';
+export * from './assertRequestInput';
+export * from './errorOutput';
+export * from './errorLogger';
+export * from './errorNormalizer';
+export * from './notFoundHandler';
+export * from './corsHandler';
+export * from './checkJwt';
+export * from './assertPayment';
+export * from './assertQuestAccess';
+export * from './assertAccount';
+export * from './assertWallet';
diff --git a/apps/api/src/app/middlewares/morgan.ts b/apps/api/src/app/middlewares/morgan.ts
new file mode 100644
index 000000000..994de436b
--- /dev/null
+++ b/apps/api/src/app/middlewares/morgan.ts
@@ -0,0 +1,16 @@
+import morgan from 'morgan';
+import { logger } from '../util/logger';
+import { NODE_ENV } from '../config/secrets';
+
+const stream = {
+    write: (message) => logger.http(message),
+};
+
+const skip = () => {
+    return ['development', 'test'].includes(NODE_ENV || 'development');
+};
+
+export default morgan(
+    ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent" - :response-time ms',
+    { stream, skip },
+);
diff --git a/apps/api/src/app/middlewares/notFoundHandler.ts b/apps/api/src/app/middlewares/notFoundHandler.ts
new file mode 100644
index 000000000..95b5beb28
--- /dev/null
+++ b/apps/api/src/app/middlewares/notFoundHandler.ts
@@ -0,0 +1,6 @@
+import { NextFunction, Request, Response } from 'express';
+import { NotFoundError } from '@thxnetwork/api/util/errors';
+
+export const notFoundHandler = (req: Request, res: Response, next: NextFunction) => {
+    next(new NotFoundError());
+};
diff --git a/apps/api/src/app/migrations/20240321144151-galachain-pkey.js b/apps/api/src/app/migrations/20240321144151-galachain-pkey.js
new file mode 100644
index 000000000..ebae8457a
--- /dev/null
+++ b/apps/api/src/app/migrations/20240321144151-galachain-pkey.js
@@ -0,0 +1,24 @@
+const { ethers } = require('ethers');
+
+module.exports = {
+    async up(db, client) {
+        const poolsColl = db.collection('pool');
+        const pools = await (await poolsColl.find({})).toArray();
+        const operations = pools.map((pool) => {
+            const privateKey = ethers.Wallet.createRandom().privateKey;
+            return {
+                updateOne: {
+                    filter: { _id: pool._id },
+                    update: {
+                        $set: { 'settings.galachainPrivateKey': privateKey },
+                    },
+                },
+            };
+        });
+        await poolsColl.bulkWrite(operations);
+    },
+
+    async down(db, client) {
+        //
+    },
+};
diff --git a/apps/api/src/app/migrations/20240329123915-quest-metadata.js b/apps/api/src/app/migrations/20240329123915-quest-metadata.js
new file mode 100644
index 000000000..8b9c03556
--- /dev/null
+++ b/apps/api/src/app/migrations/20240329123915-quest-metadata.js
@@ -0,0 +1,29 @@
+module.exports = {
+    async up(db, client) {
+        await db.collection('questdailyentry').updateMany(
+            {},
+            {
+                $rename: {
+                    ip: 'metadata.ip',
+                },
+            },
+        );
+        await db.collection('questsocialentry').updateMany(
+            {},
+            {
+                $rename: {
+                    platformUserId: 'metadata.platformUserId',
+                    publicMetrics: 'metadata.twitter',
+                },
+            },
+        );
+        await db.collection('questweb3entry').updateMany({}, { $rename: { address: 'metadata.address' } });
+        await db.collection('questgitcoinentry').updateMany({}, { $rename: { address: 'metadata.address' } });
+    },
+
+    async down(db, client) {
+        // TODO write the statements to rollback your migration (if possible)
+        // Example:
+        // await db.collection('albums').updateOne({artist: 'The Beatles'}, {$set: {blacklisted: false}});
+    },
+};
diff --git a/apps/api/src/app/migrations/20240430124026-message-query.js b/apps/api/src/app/migrations/20240430124026-message-query.js
new file mode 100644
index 000000000..b81f2abad
--- /dev/null
+++ b/apps/api/src/app/migrations/20240430124026-message-query.js
@@ -0,0 +1,84 @@
+const { ObjectId } = require('mongodb');
+
+const createQuery = (operators) => {
+    const operatorKeys = Object.keys(operators);
+    if (!operatorKeys) return;
+    const media = !operators['media'] || ['ignore', null].includes(operators['media']) ? '' : ` ${operators['media']}`;
+    const query = operatorKeys
+        .map((key) => {
+            switch (key) {
+                case 'from': {
+                    const items = operators[key];
+                    if (!items) return;
+                    const authors = items.map((author) => `from:${author}`).join(' OR ');
+                    return items.length > 1 ? `(${authors})` : authors;
+                }
+                case 'to': {
+                    const items = operators[key];
+                    if (!items) return;
+                    const authors = items.map((author) => `to:${author}`).join(' OR ');
+                    return items.length > 1 ? `(${authors})` : authors;
+                }
+                case 'text': {
+                    const items = operators[key];
+                    if (!items) return;
+                    const texts = items.map((value) => `"${value}"`).join(' OR ');
+                    return items.length > 1 ? `(${texts})` : texts;
+                }
+                case 'url': {
+                    const items = operators[key];
+                    if (!items) return;
+                    const urls = items.map((value) => `url:${value}`).join(' OR ');
+                    return items.length > 1 ? `(${urls})` : urls;
+                }
+                case 'hashtags': {
+                    const items = operators[key];
+                    if (!items) return;
+                    const hashtags = items.map((tag) => `#${tag}`).join(' OR ');
+                    return (items.length > 1 ? `(${hashtags})` : hashtags) + media;
+                }
+                case 'mentions': {
+                    const items = operators[key];
+                    if (!items) return;
+                    const mentions = items.map((tag) => `@${tag}`).join(' OR ');
+                    return (items.length > 1 ? `(${mentions})` : mentions) + media;
+                }
+            }
+            return;
+        })
+        .filter((query) => !!query)
+        .join(' ');
+
+    return `${query} -is:retweet`;
+};
+
+module.exports = {
+    async up(db, client) {
+        const questColl = db.collection('questsocial');
+        const quests = await questColl.find({ interaction: 6 }).toArray();
+
+        for (const quest of quests) {
+            if (!quest.contentMetadata) continue;
+            try {
+                const metadata = JSON.parse(quest.contentMetadata);
+                const operators = {
+                    text: [quest.content],
+                };
+                const query = createQuery(operators);
+                const contentMetadata = JSON.stringify({
+                    ...metadata,
+                    query,
+                    operators,
+                });
+
+                await questColl.updateOne({ _id: quest._id }, { $set: { content: query, contentMetadata } });
+            } catch (error) {
+                console.log(error);
+            }
+        }
+    },
+
+    async down(db, client) {
+        //
+    },
+};
diff --git a/apps/api/src/app/models/Brand.ts b/apps/api/src/app/models/Brand.ts
new file mode 100644
index 000000000..c8ebb880f
--- /dev/null
+++ b/apps/api/src/app/models/Brand.ts
@@ -0,0 +1,15 @@
+import mongoose from 'mongoose';
+
+export type TBrandUpdate = Partial<TBrand>;
+
+export const Brand = mongoose.model<TBrand>(
+    'Brand',
+    new mongoose.Schema({
+        poolId: String,
+        previewImgUrl: String,
+        logoImgUrl: String,
+        backgroundImgUrl: String,
+        widgetPreviewImgUrl: String,
+    }),
+    'brand',
+);
diff --git a/apps/api/src/app/models/Client.ts b/apps/api/src/app/models/Client.ts
new file mode 100644
index 000000000..dd98c556c
--- /dev/null
+++ b/apps/api/src/app/models/Client.ts
@@ -0,0 +1,42 @@
+import mongoose from 'mongoose';
+
+export type TClient = {
+    sub: string;
+    name: string;
+    poolId: string;
+    grantType: string;
+    clientId: string;
+    clientSecret: string;
+    requestUris: string[];
+    registrationAccessToken: string;
+    origins?: string[];
+};
+export type TClientPayload = {
+    application_type: string;
+    grant_types: string[];
+    scope: string;
+    response_types: string[];
+    request_uris?: string[];
+    redirect_uris?: string[];
+    post_logout_redirect_uris?: string[];
+};
+
+export type ClientDocument = mongoose.Document & TClient;
+
+export const Client = mongoose.model<ClientDocument>(
+    'Client',
+    new mongoose.Schema(
+        {
+            sub: String,
+            name: String,
+            poolId: String,
+            clientId: String,
+            clientSecret: String,
+            grantType: String,
+            registrationAccessToken: String,
+            origins: [String],
+        },
+        { timestamps: true },
+    ),
+    'client',
+);
diff --git a/apps/api/src/app/models/Collaborator.ts b/apps/api/src/app/models/Collaborator.ts
new file mode 100644
index 000000000..160aed12a
--- /dev/null
+++ b/apps/api/src/app/models/Collaborator.ts
@@ -0,0 +1,18 @@
+import mongoose from 'mongoose';
+
+export type CollaboratorDocument = mongoose.Document & TCollaborator;
+
+export const Collaborator = mongoose.model<CollaboratorDocument>(
+    'Collaborator',
+    new mongoose.Schema(
+        {
+            sub: String,
+            poolId: String,
+            email: String,
+            uuid: String,
+            state: Number,
+        },
+        { timestamps: true },
+    ),
+    'collaborator',
+);
diff --git a/apps/api/src/app/models/CouponCode.ts b/apps/api/src/app/models/CouponCode.ts
new file mode 100644
index 000000000..885bc539a
--- /dev/null
+++ b/apps/api/src/app/models/CouponCode.ts
@@ -0,0 +1,17 @@
+import mongoose from 'mongoose';
+
+export type CouponCodeDocument = mongoose.Document & TCouponCode;
+
+export const CouponCode = mongoose.model<CouponCodeDocument>(
+    'CouponCode',
+    new mongoose.Schema(
+        {
+            poolId: String,
+            couponRewardId: String,
+            code: String,
+            sub: String,
+        },
+        { timestamps: true },
+    ),
+    'couponcode',
+);
diff --git a/apps/api/src/app/models/DiscordGuild.ts b/apps/api/src/app/models/DiscordGuild.ts
new file mode 100644
index 000000000..0d3a3b27f
--- /dev/null
+++ b/apps/api/src/app/models/DiscordGuild.ts
@@ -0,0 +1,22 @@
+import mongoose from 'mongoose';
+
+export type DiscordGuildDocument = mongoose.Document & TDiscordGuild;
+
+export const DiscordGuild = mongoose.model<DiscordGuildDocument>(
+    'DiscordGuild',
+    new mongoose.Schema(
+        {
+            sub: String,
+            poolId: String,
+            guildId: String,
+            channelId: String,
+            adminRoleId: String,
+            name: String,
+            secret: String,
+        },
+        {
+            timestamps: true,
+        },
+    ),
+    'discordguild',
+);
diff --git a/apps/api/src/app/models/DiscordMessage.ts b/apps/api/src/app/models/DiscordMessage.ts
new file mode 100644
index 000000000..f6bef8dc4
--- /dev/null
+++ b/apps/api/src/app/models/DiscordMessage.ts
@@ -0,0 +1,18 @@
+import mongoose from 'mongoose';
+
+export type DiscordMessageDocument = mongoose.Document & TDiscordMessage;
+
+export const DiscordMessage = mongoose.model<DiscordMessageDocument>(
+    'DiscordMessage',
+    new mongoose.Schema(
+        {
+            guildId: String,
+            messageId: String,
+            memberId: String,
+        },
+        {
+            timestamps: true,
+        },
+    ),
+    'discordmessage',
+);
diff --git a/apps/api/src/app/models/DiscordReaction.ts b/apps/api/src/app/models/DiscordReaction.ts
new file mode 100644
index 000000000..fda975859
--- /dev/null
+++ b/apps/api/src/app/models/DiscordReaction.ts
@@ -0,0 +1,19 @@
+import mongoose from 'mongoose';
+
+export type DiscordReactionDocument = mongoose.Document & TDiscordReaction;
+
+export const DiscordReaction = mongoose.model<DiscordReactionDocument>(
+    'DiscordReaction',
+    new mongoose.Schema(
+        {
+            guildId: String,
+            messageId: String,
+            memberId: String,
+            content: String,
+        },
+        {
+            timestamps: true,
+        },
+    ),
+    'discordreaction',
+);
diff --git a/apps/api/src/app/models/DiscordUser.ts b/apps/api/src/app/models/DiscordUser.ts
new file mode 100644
index 000000000..7c24def95
--- /dev/null
+++ b/apps/api/src/app/models/DiscordUser.ts
@@ -0,0 +1,22 @@
+import mongoose from 'mongoose';
+
+export type DiscordUserDocument = mongoose.Document & TDiscordUser;
+
+export const DiscordUser = mongoose.model<DiscordUserDocument>(
+    'DiscordUser',
+    new mongoose.Schema(
+        {
+            userId: String,
+            guildId: String,
+            profileImgUrl: String,
+            username: String,
+            publicMetrics: {
+                joinedAt: Date,
+                messageCount: Number,
+                reactionCount: Number,
+            },
+        },
+        { timestamps: true },
+    ),
+    'discorduser',
+);
diff --git a/apps/api/src/app/models/ERC1155.ts b/apps/api/src/app/models/ERC1155.ts
new file mode 100644
index 000000000..bf461c98a
--- /dev/null
+++ b/apps/api/src/app/models/ERC1155.ts
@@ -0,0 +1,30 @@
+import mongoose from 'mongoose';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { getArtifact } from '../hardhat';
+
+export type ERC1155Document = mongoose.Document & TERC1155;
+
+const schema = new mongoose.Schema(
+    {
+        variant: String,
+        chainId: Number,
+        sub: String,
+        name: String,
+        description: String,
+        transactions: [String],
+        address: String,
+        baseURL: String,
+        archived: Boolean,
+        logoImgUrl: String,
+    },
+    { timestamps: true },
+);
+
+schema.virtual('contract').get(function () {
+    if (!this.address) return;
+    const { web3, defaultAccount } = getProvider(this.chainId);
+    const { abi } = getArtifact('THXERC1155');
+    return new web3.eth.Contract(abi, this.address, { from: defaultAccount });
+});
+
+export const ERC1155 = mongoose.model<ERC1155Document>('ERC1155', schema, 'erc1155');
diff --git a/apps/api/src/app/models/ERC1155Metadata.ts b/apps/api/src/app/models/ERC1155Metadata.ts
new file mode 100644
index 000000000..989e1ca42
--- /dev/null
+++ b/apps/api/src/app/models/ERC1155Metadata.ts
@@ -0,0 +1,20 @@
+import mongoose from 'mongoose';
+
+export type ERC1155MetadataDocument = mongoose.Document & TERC1155Metadata;
+
+export const ERC1155Metadata = mongoose.model<ERC1155MetadataDocument>(
+    'ERC1155Metadata',
+    new mongoose.Schema(
+        {
+            erc1155Id: String,
+            imageUrl: String,
+            name: String,
+            image: String,
+            description: String,
+            externalUrl: String,
+            tokenId: Number,
+        },
+        { timestamps: true },
+    ),
+    'erc1155metadata',
+);
diff --git a/apps/api/src/app/models/ERC1155Token.ts b/apps/api/src/app/models/ERC1155Token.ts
new file mode 100644
index 000000000..fac51b990
--- /dev/null
+++ b/apps/api/src/app/models/ERC1155Token.ts
@@ -0,0 +1,22 @@
+import mongoose from 'mongoose';
+
+export type ERC1155TokenDocument = mongoose.Document & TERC1155Token;
+
+export const ERC1155Token = mongoose.model<ERC1155TokenDocument>(
+    'ERC1155Token',
+    new mongoose.Schema(
+        {
+            sub: String,
+            state: Number,
+            erc1155Id: String,
+            metadataId: String,
+            tokenId: Number,
+            tokenUri: String,
+            recipient: String,
+            transactions: [String],
+            walletId: { type: String, index: 'hashed' },
+        },
+        { timestamps: true },
+    ),
+    'erc1155token',
+);
diff --git a/apps/api/src/app/models/ERC20.ts b/apps/api/src/app/models/ERC20.ts
new file mode 100644
index 000000000..90c635f5c
--- /dev/null
+++ b/apps/api/src/app/models/ERC20.ts
@@ -0,0 +1,38 @@
+import mongoose from 'mongoose';
+import { ERC20Type } from '@thxnetwork/common/enums';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { getArtifact, TContractName } from '../hardhat';
+
+export type ERC20Document = mongoose.Document & TERC20;
+
+const schema = new mongoose.Schema(
+    {
+        sub: String,
+        type: Number,
+        address: String,
+        chainId: Number,
+        name: String,
+        symbol: String,
+        transactions: [String],
+        archived: Boolean,
+        logoImgUrl: String,
+    },
+    { timestamps: true },
+);
+
+schema.virtual('contractName').get(function () {
+    return getContractName(this.type) as TContractName;
+});
+
+schema.virtual('contract').get(function () {
+    if (!this.address) return;
+    const { web3, defaultAccount } = getProvider(this.chainId);
+    const { abi } = getArtifact(getContractName(this.type));
+    return new web3.eth.Contract(abi, this.address, { from: defaultAccount });
+});
+
+function getContractName(type: ERC20Type) {
+    return type === ERC20Type.Unlimited ? 'THXERC20_UnlimitedSupply' : 'THXERC20_LimitedSupply';
+}
+
+export const ERC20 = mongoose.model<ERC20Document>('ERC20', schema, 'erc20');
diff --git a/apps/api/src/app/models/ERC20Token.ts b/apps/api/src/app/models/ERC20Token.ts
new file mode 100644
index 000000000..a26153c2c
--- /dev/null
+++ b/apps/api/src/app/models/ERC20Token.ts
@@ -0,0 +1,16 @@
+import mongoose from 'mongoose';
+
+export type ERC20TokenDocument = mongoose.Document & TERC20Token;
+
+export const ERC20Token = mongoose.model<ERC20TokenDocument>(
+    'ERC20Token',
+    new mongoose.Schema(
+        {
+            sub: String,
+            erc20Id: String,
+            walletId: { type: String, index: 'hashed' },
+        },
+        { timestamps: true },
+    ),
+    'erc20token',
+);
diff --git a/apps/api/src/app/models/ERC20Transfer.ts b/apps/api/src/app/models/ERC20Transfer.ts
new file mode 100644
index 000000000..ab4a80301
--- /dev/null
+++ b/apps/api/src/app/models/ERC20Transfer.ts
@@ -0,0 +1,19 @@
+import mongoose from 'mongoose';
+
+export type ERC20TransferDocument = mongoose.Document & TERC20Transfer;
+
+export const ERC20Transfer = mongoose.model<ERC20TransferDocument>(
+    'ERC20Transfer',
+    new mongoose.Schema(
+        {
+            erc20Id: String,
+            from: String,
+            to: String,
+            chainId: Number,
+            transactionId: String,
+            sub: String,
+        },
+        { timestamps: true },
+    ),
+    'erc20transfer',
+);
diff --git a/apps/api/src/app/models/ERC721.ts b/apps/api/src/app/models/ERC721.ts
new file mode 100644
index 000000000..91945cff0
--- /dev/null
+++ b/apps/api/src/app/models/ERC721.ts
@@ -0,0 +1,31 @@
+import mongoose from 'mongoose';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { getArtifact } from '../hardhat';
+
+export type ERC721Document = mongoose.Document & TERC721;
+
+const schema = new mongoose.Schema(
+    {
+        chainId: Number,
+        sub: String,
+        name: String,
+        symbol: String,
+        description: String,
+        transactions: [String],
+        address: String,
+        baseURL: String,
+        archived: Boolean,
+        logoImgUrl: String,
+        variant: String,
+    },
+    { timestamps: true },
+);
+
+schema.virtual('contract').get(function () {
+    if (!this.address) return;
+    const { web3 } = getProvider(this.chainId);
+    const { abi } = getArtifact('THXERC721');
+    return new web3.eth.Contract(abi, this.address);
+});
+
+export const ERC721 = mongoose.model<ERC721Document>('ERC721', schema, 'erc721');
diff --git a/apps/api/src/app/models/ERC721Metadata.ts b/apps/api/src/app/models/ERC721Metadata.ts
new file mode 100644
index 000000000..9eba455b4
--- /dev/null
+++ b/apps/api/src/app/models/ERC721Metadata.ts
@@ -0,0 +1,19 @@
+import mongoose from 'mongoose';
+
+export type ERC721MetadataDocument = mongoose.Document & TERC721Metadata;
+
+export const ERC721Metadata = mongoose.model<ERC721MetadataDocument>(
+    'ERC721Metadata',
+    new mongoose.Schema(
+        {
+            erc721Id: String,
+            imageUrl: String,
+            name: String,
+            image: String,
+            description: String,
+            externalUrl: String,
+        },
+        { timestamps: true },
+    ),
+    'erc721metadata',
+);
diff --git a/apps/api/src/app/models/ERC721Token.ts b/apps/api/src/app/models/ERC721Token.ts
new file mode 100644
index 000000000..fdee045bb
--- /dev/null
+++ b/apps/api/src/app/models/ERC721Token.ts
@@ -0,0 +1,23 @@
+import mongoose from 'mongoose';
+
+export type ERC721TokenDocument = mongoose.Document & TERC721Token;
+
+export const ERC721Token = mongoose.model<ERC721TokenDocument>(
+    'ERC721Token',
+    new mongoose.Schema(
+        {
+            erc721Id: String,
+            walletId: String,
+            metadataId: String,
+            tokenId: Number,
+            sub: String,
+            state: Number,
+            tokenUri: String,
+            recipient: String,
+            failReason: String,
+            transactions: [String],
+        },
+        { timestamps: true },
+    ),
+    'erc721token',
+);
diff --git a/apps/api/src/app/models/ERC721Transfer.ts b/apps/api/src/app/models/ERC721Transfer.ts
new file mode 100644
index 000000000..43a47e75c
--- /dev/null
+++ b/apps/api/src/app/models/ERC721Transfer.ts
@@ -0,0 +1,20 @@
+import mongoose from 'mongoose';
+
+export type ERC721TransferDocument = mongoose.Document & TERC721Transfer;
+
+export const ERC721Transfer = mongoose.model<ERC721TransferDocument>(
+    'ERC721Transfer',
+    new mongoose.Schema(
+        {
+            erc721Id: String,
+            erc721TokenId: String,
+            from: String,
+            to: String,
+            chainId: Number,
+            transactionId: String,
+            sub: String,
+        },
+        { timestamps: true },
+    ),
+    'erc721transfer',
+);
diff --git a/apps/api/src/app/models/Event.ts b/apps/api/src/app/models/Event.ts
new file mode 100644
index 000000000..cb8fc33d7
--- /dev/null
+++ b/apps/api/src/app/models/Event.ts
@@ -0,0 +1,16 @@
+import mongoose from 'mongoose';
+
+export type EventDocument = mongoose.Document & TEvent;
+
+export const Event = mongoose.model<EventDocument>(
+    'Event',
+    new mongoose.Schema(
+        {
+            identityId: String,
+            poolId: String,
+            name: String,
+        },
+        { timestamps: true },
+    ),
+    'event',
+);
diff --git a/apps/api/src/app/models/Identity.ts b/apps/api/src/app/models/Identity.ts
new file mode 100644
index 000000000..937bcb4eb
--- /dev/null
+++ b/apps/api/src/app/models/Identity.ts
@@ -0,0 +1,16 @@
+import mongoose from 'mongoose';
+
+export type IdentityDocument = mongoose.Document & TIdentity;
+
+export const Identity = mongoose.model<IdentityDocument>(
+    'Identity',
+    new mongoose.Schema(
+        {
+            poolId: String,
+            uuid: { unique: true, type: String },
+            sub: String,
+        },
+        { timestamps: true },
+    ),
+    'identity',
+);
diff --git a/apps/api/src/app/models/Invoice.ts b/apps/api/src/app/models/Invoice.ts
new file mode 100644
index 000000000..6870dbfad
--- /dev/null
+++ b/apps/api/src/app/models/Invoice.ts
@@ -0,0 +1,24 @@
+import mongoose from 'mongoose';
+
+export type InvoiceDocument = mongoose.Document & TInvoice;
+
+export const Invoice = mongoose.model<InvoiceDocument>(
+    'Invoice',
+    new mongoose.Schema(
+        {
+            poolId: String,
+            additionalUnitCount: Number,
+            costPerUnit: Number,
+            costSubscription: Number,
+            costTotal: Number,
+            currency: String,
+            plan: Number,
+            mapCount: Number,
+            mapLimit: Number,
+            periodStartDate: Date,
+            periodEndDate: Date,
+        },
+        { timestamps: true },
+    ),
+    'invoice',
+);
diff --git a/apps/api/src/app/models/Job.ts b/apps/api/src/app/models/Job.ts
new file mode 100644
index 000000000..a105ce448
--- /dev/null
+++ b/apps/api/src/app/models/Job.ts
@@ -0,0 +1,18 @@
+import mongoose from 'mongoose';
+
+export type JobDocument = mongoose.Document & TJob;
+
+export const Job = mongoose.model<JobDocument>(
+    'Job',
+    new mongoose.Schema(
+        {
+            name: String,
+            data: Object,
+            lastRunAt: Date,
+            failedAt: Date,
+            failReason: String,
+        },
+        { timestamps: true },
+    ),
+    'jobs',
+);
diff --git a/apps/api/src/app/models/Notification.ts b/apps/api/src/app/models/Notification.ts
new file mode 100644
index 000000000..81e2c040e
--- /dev/null
+++ b/apps/api/src/app/models/Notification.ts
@@ -0,0 +1,18 @@
+import mongoose from 'mongoose';
+
+export type NotificationDocument = mongoose.Document & TNotification;
+
+export const Notification = mongoose.model<NotificationDocument>(
+    'Notifications',
+    new mongoose.Schema(
+        {
+            sub: String,
+            subjectId: String,
+            poolId: String,
+            subject: String,
+            message: String,
+        },
+        { timestamps: true },
+    ),
+    'notification',
+);
diff --git a/apps/api/src/app/models/Participant.ts b/apps/api/src/app/models/Participant.ts
new file mode 100644
index 000000000..e18c1c764
--- /dev/null
+++ b/apps/api/src/app/models/Participant.ts
@@ -0,0 +1,21 @@
+import mongoose from 'mongoose';
+
+export type ParticipantDocument = mongoose.Document & TParticipant;
+
+export const Participant = mongoose.model<ParticipantDocument>(
+    'Participant',
+    new mongoose.Schema(
+        {
+            sub: String,
+            poolId: String,
+            rank: Number,
+            score: Number,
+            balance: { type: Number, default: 0 },
+            questEntryCount: Number,
+            riskAnalysis: { score: Number, reasons: [String] },
+            isSubscribed: { type: Boolean, default: false },
+        },
+        { timestamps: true },
+    ),
+    'participant',
+);
diff --git a/apps/api/src/app/models/Payment.ts b/apps/api/src/app/models/Payment.ts
new file mode 100644
index 000000000..b6d9188c6
--- /dev/null
+++ b/apps/api/src/app/models/Payment.ts
@@ -0,0 +1,15 @@
+import mongoose from 'mongoose';
+
+export type PaymentDocument = mongoose.Document & TPayment;
+
+export const Payment = mongoose.model<PaymentDocument>(
+    'Payment',
+    new mongoose.Schema(
+        {
+            sub: String,
+            poolId: String,
+        },
+        { timestamps: true },
+    ),
+    'payment',
+);
diff --git a/apps/api/src/app/models/Pool.ts b/apps/api/src/app/models/Pool.ts
new file mode 100644
index 000000000..80e885ff7
--- /dev/null
+++ b/apps/api/src/app/models/Pool.ts
@@ -0,0 +1,52 @@
+import mongoose from 'mongoose';
+import { WIDGET_URL } from '../config/secrets';
+
+export type PoolDocument = mongoose.Document & TPool;
+
+const schema = new mongoose.Schema(
+    {
+        sub: String,
+        chainId: Number,
+        transactions: [String],
+        version: String,
+        token: String,
+        signingSecret: String,
+        rank: Number,
+        safeAddress: String,
+        trialEndsAt: Date,
+        settings: {
+            title: String,
+            slug: String,
+            description: String,
+            startDate: Date,
+            endDate: Date,
+            isArchived: Boolean,
+            isPublished: { type: Boolean, default: false },
+            isWeeklyDigestEnabled: { type: Boolean, default: true },
+            isTwitterSyncEnabled: { type: Boolean, default: false },
+            discordWebhookUrl: String,
+            galachainPrivateKey: String,
+            defaults: {
+                discordMessage: String,
+                conditionalRewards: {
+                    title: String,
+                    description: String,
+                    amount: Number,
+                    hashtag: String,
+                    isPublished: { type: Boolean, default: false },
+                    locks: { type: [{ questId: String, variant: Number }], default: [] },
+                },
+            },
+            authenticationMethods: [Number],
+        },
+    },
+    { timestamps: true },
+);
+
+schema.virtual('campaignURL').get(function (this: PoolDocument) {
+    const url = new URL(WIDGET_URL);
+    url.pathname = `/c/${this.settings.slug}`;
+    return url.toString();
+});
+
+export const Pool = mongoose.model<PoolDocument>('Pool', schema, 'pool');
diff --git a/apps/api/src/app/models/QRCodeEntry.ts b/apps/api/src/app/models/QRCodeEntry.ts
new file mode 100644
index 000000000..d02453829
--- /dev/null
+++ b/apps/api/src/app/models/QRCodeEntry.ts
@@ -0,0 +1,19 @@
+import mongoose from 'mongoose';
+
+export type QRCodeEntryDocument = mongoose.Document & TQRCodeEntry;
+
+export const QRCodeEntry = mongoose.model<QRCodeEntryDocument>(
+    'QRCodeEntry',
+    new mongoose.Schema(
+        {
+            sub: String,
+            uuid: String,
+            redirectURL: String,
+            rewardId: String,
+            amount: Number,
+            claimedAt: Date,
+        },
+        { timestamps: true },
+    ),
+    'qrcodeentry',
+);
diff --git a/apps/api/src/app/models/Quest.ts b/apps/api/src/app/models/Quest.ts
new file mode 100644
index 000000000..9950b8481
--- /dev/null
+++ b/apps/api/src/app/models/Quest.ts
@@ -0,0 +1,13 @@
+export const questSchema = {
+    uuid: String,
+    poolId: String,
+    variant: Number,
+    title: String,
+    description: String,
+    image: String,
+    index: Number,
+    expiryDate: Date,
+    infoLinks: [{ label: String, url: String }],
+    isPublished: { type: Boolean, default: false },
+    locks: { type: [{ questId: String, variant: Number }], default: [] },
+};
diff --git a/apps/api/src/app/models/QuestCustom.ts b/apps/api/src/app/models/QuestCustom.ts
new file mode 100644
index 000000000..99b09ada8
--- /dev/null
+++ b/apps/api/src/app/models/QuestCustom.ts
@@ -0,0 +1,18 @@
+import mongoose from 'mongoose';
+import { questSchema } from '@thxnetwork/api/models/Quest';
+
+export type QuestCustomDocument = mongoose.Document & TQuestCustom;
+
+export const QuestCustom = mongoose.model<QuestCustomDocument>(
+    'QuestCustom',
+    new mongoose.Schema(
+        {
+            ...(questSchema as any),
+            amount: Number,
+            limit: Number,
+            eventName: String,
+        },
+        { timestamps: true },
+    ),
+    'questcustom',
+);
diff --git a/apps/api/src/app/models/QuestCustomEntry.ts b/apps/api/src/app/models/QuestCustomEntry.ts
new file mode 100644
index 000000000..0c046bd34
--- /dev/null
+++ b/apps/api/src/app/models/QuestCustomEntry.ts
@@ -0,0 +1,19 @@
+import mongoose from 'mongoose';
+
+export type QuestCustomEntryDocument = mongoose.Document & TQuestCustomEntry;
+
+export const QuestCustomEntry = mongoose.model<QuestCustomEntryDocument>(
+    'QuestCustomEntry',
+    new mongoose.Schema(
+        {
+            questId: String,
+            sub: String,
+            uuid: String,
+            amount: Number,
+            isClaimed: Boolean,
+            poolId: String,
+        },
+        { timestamps: true },
+    ),
+    'questcustomentry',
+);
diff --git a/apps/api/src/app/models/QuestDaily.ts b/apps/api/src/app/models/QuestDaily.ts
new file mode 100644
index 000000000..adc438142
--- /dev/null
+++ b/apps/api/src/app/models/QuestDaily.ts
@@ -0,0 +1,17 @@
+import mongoose from 'mongoose';
+import { questSchema } from './Quest';
+
+export type QuestDailyDocument = mongoose.Document & TQuestDaily;
+
+export const QuestDaily = mongoose.model<QuestDailyDocument>(
+    'QuestDaily',
+    new mongoose.Schema(
+        {
+            ...(questSchema as any),
+            amounts: [Number],
+            eventName: String,
+        },
+        { timestamps: true },
+    ),
+    'questdaily',
+);
diff --git a/apps/api/src/app/models/QuestDailyEntry.ts b/apps/api/src/app/models/QuestDailyEntry.ts
new file mode 100644
index 000000000..ef4a4c0f7
--- /dev/null
+++ b/apps/api/src/app/models/QuestDailyEntry.ts
@@ -0,0 +1,22 @@
+import mongoose from 'mongoose';
+
+export type QuestDailyEntryDocument = mongoose.Document & TQuestDailyEntry;
+
+export const QuestDailyEntry = mongoose.model<QuestDailyEntryDocument>(
+    'QuestDailyEntry',
+    new mongoose.Schema(
+        {
+            questId: String,
+            sub: String,
+            uuid: String,
+            amount: Number,
+            poolId: String,
+            metadata: {
+                state: Number,
+                ip: String,
+            },
+        },
+        { timestamps: true },
+    ),
+    'questdailyentry',
+);
diff --git a/apps/api/src/app/models/QuestGitcoin.ts b/apps/api/src/app/models/QuestGitcoin.ts
new file mode 100644
index 000000000..7fbf75630
--- /dev/null
+++ b/apps/api/src/app/models/QuestGitcoin.ts
@@ -0,0 +1,18 @@
+import mongoose from 'mongoose';
+import { questSchema } from '@thxnetwork/api/models/Quest';
+
+export type QuestGitcoinDocument = mongoose.Document & TQuestGitcoin;
+
+export const QuestGitcoin = mongoose.model<QuestGitcoinDocument>(
+    'QuestGitcoin',
+    new mongoose.Schema(
+        {
+            ...(questSchema as any),
+            amount: Number,
+            scorerId: Number,
+            score: Number,
+        },
+        { timestamps: true },
+    ),
+    'questgitcoin',
+);
diff --git a/apps/api/src/app/models/QuestGitcoinEntry.ts b/apps/api/src/app/models/QuestGitcoinEntry.ts
new file mode 100644
index 000000000..aad3fe0c4
--- /dev/null
+++ b/apps/api/src/app/models/QuestGitcoinEntry.ts
@@ -0,0 +1,21 @@
+import mongoose from 'mongoose';
+
+export type QuestGitcoinEntryDocument = mongoose.Document & TQuestGitcoinEntry;
+
+export const QuestGitcoinEntry = mongoose.model<QuestGitcoinEntryDocument>(
+    'QuestGitcoinEntry',
+    new mongoose.Schema(
+        {
+            poolId: String,
+            questId: String,
+            sub: String,
+            amount: Number,
+            metadata: {
+                address: String,
+                score: Number,
+            },
+        },
+        { timestamps: true },
+    ),
+    'questgitcoinentry',
+);
diff --git a/apps/api/src/app/models/QuestInvite.ts b/apps/api/src/app/models/QuestInvite.ts
new file mode 100644
index 000000000..4e6cd17c3
--- /dev/null
+++ b/apps/api/src/app/models/QuestInvite.ts
@@ -0,0 +1,20 @@
+import mongoose from 'mongoose';
+import { questSchema } from '@thxnetwork/api/models/Quest';
+
+export type QuestInviteDocument = mongoose.Document & TQuestInvite;
+
+export const QuestInvite = mongoose.model<QuestInviteDocument>(
+    'QuestInvite',
+    new mongoose.Schema(
+        {
+            ...(questSchema as any),
+            amount: Number,
+            pathname: String,
+            successUrl: String,
+            token: String,
+            isMandatoryReview: Boolean,
+        },
+        { timestamps: true },
+    ),
+    'questinvite',
+);
diff --git a/apps/api/src/app/models/QuestInviteEntry.ts b/apps/api/src/app/models/QuestInviteEntry.ts
new file mode 100644
index 000000000..363b99ca4
--- /dev/null
+++ b/apps/api/src/app/models/QuestInviteEntry.ts
@@ -0,0 +1,20 @@
+import mongoose from 'mongoose';
+
+export type QuestInviteEntryDocument = mongoose.Document & TQuestInviteEntry;
+
+export const QuestInviteEntry = mongoose.model<QuestInviteEntryDocument>(
+    'QuestInviteEntry',
+    new mongoose.Schema(
+        {
+            questId: String,
+            sub: String,
+            uuid: String,
+            amount: String,
+            isApproved: Boolean,
+            poolId: String,
+            metadata: String,
+        },
+        { timestamps: true },
+    ),
+    'questinviteentry',
+);
diff --git a/apps/api/src/app/models/QuestSocial.ts b/apps/api/src/app/models/QuestSocial.ts
new file mode 100644
index 000000000..f130cadbd
--- /dev/null
+++ b/apps/api/src/app/models/QuestSocial.ts
@@ -0,0 +1,21 @@
+import mongoose from 'mongoose';
+import { questSchema } from './Quest';
+
+export type QuestSocialDocument = mongoose.Document & TQuestSocial;
+
+export const QuestSocial = mongoose.model<QuestSocialDocument>(
+    'QuestSocial',
+    new mongoose.Schema(
+        {
+            ...(questSchema as any),
+            amount: Number,
+            platform: Number,
+            kind: String,
+            interaction: Number,
+            content: String,
+            contentMetadata: String,
+        },
+        { timestamps: true },
+    ),
+    'questsocial',
+);
diff --git a/apps/api/src/app/models/QuestSocialEntry.ts b/apps/api/src/app/models/QuestSocialEntry.ts
new file mode 100644
index 000000000..91776d4e5
--- /dev/null
+++ b/apps/api/src/app/models/QuestSocialEntry.ts
@@ -0,0 +1,34 @@
+import mongoose from 'mongoose';
+
+export type QuestSocialEntryDocument = mongoose.Document & TQuestSocialEntry;
+
+export const QuestSocialEntry = mongoose.model<QuestSocialEntryDocument>(
+    'QuestSocialEntry',
+    new mongoose.Schema(
+        {
+            questId: String,
+            sub: String,
+            amount: Number,
+            poolId: String,
+            metadata: {
+                platformUserId: String,
+                twitter: {
+                    followersCount: Number,
+                    followingCount: Number,
+                    tweetCount: Number,
+                    listedCount: Number,
+                    likeCount: Number,
+                },
+                discord: {
+                    guildId: String,
+                    username: String,
+                    joinedAt: Date,
+                    messageCount: Number,
+                    reactionCount: Number,
+                },
+            },
+        },
+        { timestamps: true },
+    ),
+    'questsocialentry',
+);
diff --git a/apps/api/src/app/models/QuestWeb3.ts b/apps/api/src/app/models/QuestWeb3.ts
new file mode 100644
index 000000000..a74b95dd1
--- /dev/null
+++ b/apps/api/src/app/models/QuestWeb3.ts
@@ -0,0 +1,22 @@
+import mongoose from 'mongoose';
+import { questSchema } from '@thxnetwork/api/models/Quest';
+
+export type QuestWeb3Document = mongoose.Document & TQuestWeb3;
+
+export const QuestWeb3 = mongoose.model<QuestWeb3Document>(
+    'QuestWeb3',
+    new mongoose.Schema(
+        {
+            ...(questSchema as any),
+            amount: Number,
+            contracts: Array({
+                chainId: Number,
+                address: String,
+            }),
+            methodName: String,
+            threshold: String,
+        },
+        { timestamps: true },
+    ),
+    'questweb3',
+);
diff --git a/apps/api/src/app/models/QuestWeb3Entry.ts b/apps/api/src/app/models/QuestWeb3Entry.ts
new file mode 100644
index 000000000..ef5c44f87
--- /dev/null
+++ b/apps/api/src/app/models/QuestWeb3Entry.ts
@@ -0,0 +1,22 @@
+import mongoose from 'mongoose';
+
+export type QuestWeb3EntryDocument = mongoose.Document & TQuestWeb3Entry;
+
+export const QuestWeb3Entry = mongoose.model<QuestWeb3EntryDocument>(
+    'QuestWeb3Entry',
+    new mongoose.Schema(
+        {
+            poolId: String,
+            questId: String,
+            sub: String,
+            amount: Number,
+            metadata: {
+                chainId: Number,
+                callResult: String,
+                address: String,
+            },
+        },
+        { timestamps: true },
+    ),
+    'questweb3entry',
+);
diff --git a/apps/api/src/app/models/QuestWebhook.ts b/apps/api/src/app/models/QuestWebhook.ts
new file mode 100644
index 000000000..31ff80b15
--- /dev/null
+++ b/apps/api/src/app/models/QuestWebhook.ts
@@ -0,0 +1,18 @@
+import mongoose from 'mongoose';
+import { questSchema } from './Quest';
+
+export type QuestWebhookDocument = mongoose.Document & TQuestWebhook;
+
+export const QuestWebhook = mongoose.model<QuestWebhookDocument>(
+    'QuestWebhook',
+    new mongoose.Schema(
+        {
+            ...(questSchema as any),
+            amount: Number,
+            webhookId: String,
+            metadata: String,
+        },
+        { timestamps: true },
+    ),
+    'questwebhook',
+);
diff --git a/apps/api/src/app/models/QuestWebhookEntry.ts b/apps/api/src/app/models/QuestWebhookEntry.ts
new file mode 100644
index 000000000..7968c5c68
--- /dev/null
+++ b/apps/api/src/app/models/QuestWebhookEntry.ts
@@ -0,0 +1,19 @@
+import mongoose from 'mongoose';
+
+export type QuestWebhookEntryDocument = mongoose.Document & TQuestWebhookEntry;
+
+export const QuestWebhookEntry = mongoose.model<QuestWebhookEntryDocument>(
+    'QuestWebhookEntry',
+    new mongoose.Schema(
+        {
+            questId: String,
+            poolId: String,
+            webhookId: String,
+            identityId: String,
+            sub: String,
+            amount: Number,
+        },
+        { timestamps: true },
+    ),
+    'questwebhookentry',
+);
diff --git a/apps/api/src/app/models/Reward.ts b/apps/api/src/app/models/Reward.ts
new file mode 100644
index 000000000..7301a1d31
--- /dev/null
+++ b/apps/api/src/app/models/Reward.ts
@@ -0,0 +1,22 @@
+export const rewardSchema = {
+    uuid: String,
+    poolId: String,
+    title: String,
+    description: String,
+    image: String,
+    pointPrice: Number,
+    expiryDate: Date,
+    limit: Number,
+    isPromoted: { type: Boolean, default: false },
+    isPublished: { type: Boolean, default: false },
+    locks: { type: [{ questId: String, variant: Number }], default: [] },
+    claimAmount: Number,
+    claimLimit: Number,
+};
+
+export const rewardPaymentSchema = {
+    poolId: String,
+    rewardId: String,
+    sub: String,
+    amount: Number,
+};
diff --git a/apps/api/src/app/models/RewardCoin.ts b/apps/api/src/app/models/RewardCoin.ts
new file mode 100644
index 000000000..a54d6108d
--- /dev/null
+++ b/apps/api/src/app/models/RewardCoin.ts
@@ -0,0 +1,19 @@
+import mongoose from 'mongoose';
+import { RewardVariant } from '@thxnetwork/common/enums';
+import { rewardSchema } from './Reward';
+
+export type RewardCoinDocument = mongoose.Document & TRewardCoin;
+
+export const RewardCoin = mongoose.model<RewardCoinDocument>(
+    'RewardCoin',
+    new mongoose.Schema(
+        {
+            ...rewardSchema,
+            variant: { type: Number, default: RewardVariant.Coin },
+            erc20Id: String,
+            amount: String,
+        },
+        { timestamps: true },
+    ),
+    'rewardcoin',
+);
diff --git a/apps/api/src/app/models/RewardCoinPayment.ts b/apps/api/src/app/models/RewardCoinPayment.ts
new file mode 100644
index 000000000..5d9500190
--- /dev/null
+++ b/apps/api/src/app/models/RewardCoinPayment.ts
@@ -0,0 +1,17 @@
+import mongoose from 'mongoose';
+
+export type RewardCoinPaymentDocument = mongoose.Document & TRewardCoinPayment;
+
+export const RewardCoinPayment = mongoose.model<RewardCoinPaymentDocument>(
+    'RewardCoinPayment',
+    new mongoose.Schema(
+        {
+            rewardId: String,
+            sub: String,
+            poolId: String,
+            amount: Number,
+        },
+        { timestamps: true },
+    ),
+    'rewardcoinpayment',
+);
diff --git a/apps/api/src/app/models/RewardCoupon.ts b/apps/api/src/app/models/RewardCoupon.ts
new file mode 100644
index 000000000..badc6d39a
--- /dev/null
+++ b/apps/api/src/app/models/RewardCoupon.ts
@@ -0,0 +1,18 @@
+import mongoose from 'mongoose';
+import { rewardSchema } from './Reward';
+import { RewardVariant } from '@thxnetwork/common/enums';
+
+export type RewardCouponDocument = mongoose.Document & TRewardCoupon;
+
+export const RewardCoupon = mongoose.model<RewardCouponDocument>(
+    'RewardCoupon',
+    new mongoose.Schema(
+        {
+            ...rewardSchema,
+            variant: { type: Number, default: RewardVariant.Coupon },
+            webshopURL: String,
+        },
+        { timestamps: true },
+    ),
+    'rewardcoupon',
+);
diff --git a/apps/api/src/app/models/RewardCouponPayment.ts b/apps/api/src/app/models/RewardCouponPayment.ts
new file mode 100644
index 000000000..025e9e52e
--- /dev/null
+++ b/apps/api/src/app/models/RewardCouponPayment.ts
@@ -0,0 +1,16 @@
+import mongoose from 'mongoose';
+import { rewardPaymentSchema } from './Reward';
+
+export type RewardCouponPaymentDocument = mongoose.Document & TRewardCouponPayment;
+
+export const RewardCouponPayment = mongoose.model<RewardCouponPaymentDocument>(
+    'RewardCouponPayment',
+    new mongoose.Schema(
+        {
+            ...rewardPaymentSchema,
+            couponCodeId: String,
+        },
+        { timestamps: true },
+    ),
+    'rewardcouponpayment',
+);
diff --git a/apps/api/src/app/models/RewardCustom.ts b/apps/api/src/app/models/RewardCustom.ts
new file mode 100644
index 000000000..f7b8e42a9
--- /dev/null
+++ b/apps/api/src/app/models/RewardCustom.ts
@@ -0,0 +1,19 @@
+import mongoose from 'mongoose';
+import { rewardSchema } from './Reward';
+import { RewardVariant } from '@thxnetwork/common/enums';
+
+export type RewardCustomDocument = mongoose.Document & TRewardCustom;
+
+export const RewardCustom = mongoose.model<RewardCustomDocument>(
+    'RewardCustom',
+    new mongoose.Schema(
+        {
+            ...rewardSchema,
+            variant: { type: Number, default: RewardVariant.Custom },
+            metadata: String,
+            webhookId: String,
+        },
+        { timestamps: true },
+    ),
+    'rewardcustom',
+);
diff --git a/apps/api/src/app/models/RewardCustomPayment.ts b/apps/api/src/app/models/RewardCustomPayment.ts
new file mode 100644
index 000000000..459b79a16
--- /dev/null
+++ b/apps/api/src/app/models/RewardCustomPayment.ts
@@ -0,0 +1,15 @@
+import mongoose from 'mongoose';
+import { rewardPaymentSchema } from './Reward';
+
+export type RewardCustomPaymentDocument = mongoose.Document & TRewardCustomPayment;
+
+export const RewardCustomPayment = mongoose.model<RewardCustomPaymentDocument>(
+    'RewardCustomPayment',
+    new mongoose.Schema(
+        {
+            ...rewardPaymentSchema,
+        },
+        { timestamps: true },
+    ),
+    'rewardcustompayment',
+);
diff --git a/apps/api/src/app/models/RewardDiscordRole.ts b/apps/api/src/app/models/RewardDiscordRole.ts
new file mode 100644
index 000000000..f38ecd684
--- /dev/null
+++ b/apps/api/src/app/models/RewardDiscordRole.ts
@@ -0,0 +1,18 @@
+import mongoose from 'mongoose';
+import { rewardSchema } from './Reward';
+import { RewardVariant } from '@thxnetwork/common/enums';
+
+export type RewardDiscordRoleDocument = mongoose.Document & TRewardDiscordRole;
+
+export const RewardDiscordRole = mongoose.model<RewardDiscordRoleDocument>(
+    'RewardDiscordRole',
+    new mongoose.Schema(
+        {
+            ...rewardSchema,
+            variant: { type: Number, default: RewardVariant.DiscordRole },
+            discordRoleId: String,
+        },
+        { timestamps: true },
+    ),
+    'rewarddiscordrole',
+);
diff --git a/apps/api/src/app/models/RewardDiscordRolePayment.ts b/apps/api/src/app/models/RewardDiscordRolePayment.ts
new file mode 100644
index 000000000..fccdf8ee8
--- /dev/null
+++ b/apps/api/src/app/models/RewardDiscordRolePayment.ts
@@ -0,0 +1,16 @@
+import mongoose from 'mongoose';
+import { rewardPaymentSchema } from './Reward';
+
+export type RewardDiscordRolePaymentDocument = mongoose.Document & TRewardDiscordRolePayment;
+
+export const RewardDiscordRolePayment = mongoose.model<RewardDiscordRolePaymentDocument>(
+    'RewardDiscordRolePayment',
+    new mongoose.Schema(
+        {
+            ...rewardPaymentSchema,
+            discordRoleId: String,
+        },
+        { timestamps: true },
+    ),
+    'rewarddiscordrolepayment',
+);
diff --git a/apps/api/src/app/models/RewardGalachain.ts b/apps/api/src/app/models/RewardGalachain.ts
new file mode 100644
index 000000000..75aec01c8
--- /dev/null
+++ b/apps/api/src/app/models/RewardGalachain.ts
@@ -0,0 +1,26 @@
+import mongoose from 'mongoose';
+import { rewardSchema } from './Reward';
+import { RewardVariant } from '@thxnetwork/common/enums';
+
+export type RewardGalachainDocument = mongoose.Document & TRewardGalachain;
+
+export const RewardGalachain = mongoose.model<RewardGalachainDocument>(
+    'RewardGalachain',
+    new mongoose.Schema(
+        {
+            ...rewardSchema,
+            variant: { type: Number, default: RewardVariant.Galachain },
+            amount: String,
+            contractChannelName: { type: String, required: true },
+            contractChaincodeName: { type: String, required: true },
+            contractContractName: { type: String, required: true },
+            tokenCollection: { type: String, required: true },
+            tokenCategory: { type: String, required: true },
+            tokenType: { type: String, required: true },
+            tokenAdditionalKey: { type: String, required: true },
+            tokenInstance: { type: Number, required: true },
+        },
+        { timestamps: true },
+    ),
+    'rewardgalachain',
+);
diff --git a/apps/api/src/app/models/RewardGalachainPayment.ts b/apps/api/src/app/models/RewardGalachainPayment.ts
new file mode 100644
index 000000000..de4bf79e6
--- /dev/null
+++ b/apps/api/src/app/models/RewardGalachainPayment.ts
@@ -0,0 +1,16 @@
+import mongoose from 'mongoose';
+import { rewardPaymentSchema } from './Reward';
+
+export type RewardGalachainPaymentDocument = mongoose.Document & TRewardGalachainPayment;
+
+export const RewardGalachainPayment = mongoose.model<RewardGalachainPaymentDocument>(
+    'RewardGalachainPayment',
+    new mongoose.Schema(
+        {
+            ...rewardPaymentSchema,
+            amount: String,
+        },
+        { timestamps: true },
+    ),
+    'rewardgalachainpayment',
+);
diff --git a/apps/api/src/app/models/RewardNFT.ts b/apps/api/src/app/models/RewardNFT.ts
new file mode 100644
index 000000000..2ad9763ef
--- /dev/null
+++ b/apps/api/src/app/models/RewardNFT.ts
@@ -0,0 +1,25 @@
+import mongoose from 'mongoose';
+import { RewardVariant } from '@thxnetwork/common/enums';
+import { rewardSchema } from './Reward';
+
+export type RewardNFTDocument = mongoose.Document & TRewardNFT;
+
+export const RewardNFT = mongoose.model<RewardNFTDocument>(
+    'RewardNFT',
+    new mongoose.Schema(
+        {
+            ...rewardSchema,
+            variant: { type: Number, default: RewardVariant.NFT },
+            erc721Id: String,
+            erc1155Id: String,
+            erc1155Amount: String,
+            metadataId: String,
+            tokenId: String,
+            price: Number,
+            priceCurrency: String,
+            redirectUrl: String,
+        },
+        { timestamps: true },
+    ),
+    'rewardnft',
+);
diff --git a/apps/api/src/app/models/RewardNFTPayment.ts b/apps/api/src/app/models/RewardNFTPayment.ts
new file mode 100644
index 000000000..d7d49b5fb
--- /dev/null
+++ b/apps/api/src/app/models/RewardNFTPayment.ts
@@ -0,0 +1,15 @@
+import mongoose from 'mongoose';
+import { rewardPaymentSchema } from './Reward';
+
+export type RewardNFTPaymentDocument = mongoose.Document & TRewardNFTPayment;
+
+export const RewardNFTPayment = mongoose.model<RewardNFTPaymentDocument>(
+    'RewardNFTPayment',
+    new mongoose.Schema(
+        {
+            ...rewardPaymentSchema,
+        },
+        { timestamps: true },
+    ),
+    'rewardnftpayment',
+);
diff --git a/apps/api/src/app/models/Transaction.ts b/apps/api/src/app/models/Transaction.ts
new file mode 100644
index 000000000..53ccef3ad
--- /dev/null
+++ b/apps/api/src/app/models/Transaction.ts
@@ -0,0 +1,30 @@
+import mongoose from 'mongoose';
+
+export type TransactionDocument = mongoose.Document & TTransaction;
+
+export const Transaction = mongoose.model<TransactionDocument>(
+    'Transaction',
+    new mongoose.Schema(
+        {
+            from: String,
+            to: String,
+            nonce: Number,
+            walletId: String,
+            transactionId: String,
+            transactionHash: String,
+            safeTxHash: String,
+            gas: String,
+            baseFee: String,
+            maxFeePerGas: String,
+            maxPriorityFeePerGas: String,
+            type: Number,
+            state: { type: Number, index: { sparse: true } },
+            call: { fn: String, args: String },
+            chainId: Number,
+            failReason: String,
+            callback: {},
+        },
+        { timestamps: true },
+    ),
+    'transaction',
+);
diff --git a/apps/api/src/app/models/TwitterFollower.ts b/apps/api/src/app/models/TwitterFollower.ts
new file mode 100644
index 000000000..21a1e5b10
--- /dev/null
+++ b/apps/api/src/app/models/TwitterFollower.ts
@@ -0,0 +1,17 @@
+import mongoose from 'mongoose';
+
+export type TwitterFollowerDocument = mongoose.Document & TTwitterFollower;
+
+const twitterFollowerSchema = new mongoose.Schema(
+    {
+        userId: String,
+        targetUserId: String,
+    },
+    { timestamps: true },
+);
+
+export const TwitterFollower = mongoose.model<TwitterFollowerDocument>(
+    'TwitterFollower',
+    twitterFollowerSchema,
+    'twitterfollower',
+);
diff --git a/apps/api/src/app/models/TwitterLike.ts b/apps/api/src/app/models/TwitterLike.ts
new file mode 100644
index 000000000..f759fef3d
--- /dev/null
+++ b/apps/api/src/app/models/TwitterLike.ts
@@ -0,0 +1,15 @@
+import mongoose from 'mongoose';
+
+export type TwitterLikeDocument = mongoose.Document & TTwitterLike;
+
+export const TwitterLike = mongoose.model<TwitterLikeDocument>(
+    'TwitterLike',
+    new mongoose.Schema(
+        {
+            userId: String,
+            postId: String,
+        },
+        { timestamps: true },
+    ),
+    'twitterlike',
+);
diff --git a/apps/api/src/app/models/TwitterPost.ts b/apps/api/src/app/models/TwitterPost.ts
new file mode 100644
index 000000000..6f5c09e95
--- /dev/null
+++ b/apps/api/src/app/models/TwitterPost.ts
@@ -0,0 +1,25 @@
+import mongoose from 'mongoose';
+
+export type TwitterPostDocument = mongoose.Document & TTwitterPost;
+
+export const TwitterPost = mongoose.model<TwitterPostDocument>(
+    'TwitterPost',
+    new mongoose.Schema(
+        {
+            userId: String,
+            postId: String,
+            queryId: String,
+            text: String,
+            publicMetrics: {
+                retweetCount: Number,
+                replyCount: Number,
+                likeCount: Number,
+                quoteCount: Number,
+                bookmarkCount: Number,
+                impressionCount: Number,
+            },
+        },
+        { timestamps: true },
+    ),
+    'twitterpost',
+);
diff --git a/apps/api/src/app/models/TwitterQuery.ts b/apps/api/src/app/models/TwitterQuery.ts
new file mode 100644
index 000000000..43ce2f1e9
--- /dev/null
+++ b/apps/api/src/app/models/TwitterQuery.ts
@@ -0,0 +1,33 @@
+import mongoose from 'mongoose';
+
+export type TwitterQueryDocument = mongoose.Document & TTwitterQuery;
+
+export const TwitterQuery = mongoose.model<TwitterQueryDocument>(
+    'TwitterQuery',
+    new mongoose.Schema(
+        {
+            poolId: String,
+            query: String,
+            operators: {
+                from: [String],
+                to: [String],
+                text: [String],
+                url: [String],
+                hashtags: [String],
+                mentions: [String],
+                media: String,
+                excludes: [String],
+            },
+            defaults: {
+                title: String,
+                description: String,
+                amount: Number,
+                isPublished: Boolean,
+                expiryInDays: Number,
+                locks: [{ questId: String, variant: Number }],
+            },
+        },
+        { timestamps: true },
+    ),
+    'twitterquery',
+);
diff --git a/apps/api/src/app/models/TwitterRepost.ts b/apps/api/src/app/models/TwitterRepost.ts
new file mode 100644
index 000000000..8d3b307c7
--- /dev/null
+++ b/apps/api/src/app/models/TwitterRepost.ts
@@ -0,0 +1,15 @@
+import mongoose from 'mongoose';
+
+export type TwitterRepostDocument = mongoose.Document & TTwitterRepost;
+
+export const TwitterRepost = mongoose.model<TwitterRepostDocument>(
+    'TwitterRepost',
+    new mongoose.Schema(
+        {
+            userId: String,
+            postId: String,
+        },
+        { timestamps: true },
+    ),
+    'twitterrepost',
+);
diff --git a/apps/api/src/app/models/TwitterUser.ts b/apps/api/src/app/models/TwitterUser.ts
new file mode 100644
index 000000000..c012f7b6b
--- /dev/null
+++ b/apps/api/src/app/models/TwitterUser.ts
@@ -0,0 +1,24 @@
+import mongoose from 'mongoose';
+
+export type TwitterUserDocument = mongoose.Document & TTwitterUser;
+
+export const TwitterUser = mongoose.model<TwitterUserDocument>(
+    'TwitterUser',
+    new mongoose.Schema(
+        {
+            userId: String,
+            profileImgUrl: String,
+            name: String,
+            username: String,
+            publicMetrics: {
+                followersCount: Number,
+                followingCount: Number,
+                tweetCount: Number,
+                listedCount: Number,
+                likeCount: Number,
+            },
+        },
+        { timestamps: true },
+    ),
+    'twitteruser',
+);
diff --git a/apps/api/src/app/models/Wallet.ts b/apps/api/src/app/models/Wallet.ts
new file mode 100644
index 000000000..6a0eb30e8
--- /dev/null
+++ b/apps/api/src/app/models/Wallet.ts
@@ -0,0 +1,22 @@
+import mongoose from 'mongoose';
+
+export type WalletDocument = mongoose.Document & TWallet;
+
+export const Wallet = mongoose.model<WalletDocument>(
+    'Wallet',
+    new mongoose.Schema(
+        {
+            uuid: String,
+            expiresAt: Date,
+            poolId: String,
+            address: String,
+            sub: { type: String, index: 'hashed' },
+            chainId: Number,
+            version: String,
+            safeVersion: String,
+            variant: String,
+        },
+        { timestamps: true },
+    ),
+    'wallet',
+);
diff --git a/apps/api/src/app/models/Webhook.ts b/apps/api/src/app/models/Webhook.ts
new file mode 100644
index 000000000..43841317d
--- /dev/null
+++ b/apps/api/src/app/models/Webhook.ts
@@ -0,0 +1,17 @@
+import mongoose from 'mongoose';
+
+export type WebhookDocument = mongoose.Document & TWebhook;
+
+export const Webhook = mongoose.model<WebhookDocument>(
+    'Webhook',
+    new mongoose.Schema(
+        {
+            sub: String,
+            poolId: String,
+            url: String,
+            active: { default: false, type: Boolean },
+        },
+        { timestamps: true },
+    ),
+    'webhook',
+);
diff --git a/apps/api/src/app/models/WebhookRequest.ts b/apps/api/src/app/models/WebhookRequest.ts
new file mode 100644
index 000000000..0d2d06228
--- /dev/null
+++ b/apps/api/src/app/models/WebhookRequest.ts
@@ -0,0 +1,19 @@
+import mongoose from 'mongoose';
+
+export type WebhookRequestDocument = mongoose.Document & TWebhookRequest;
+
+export const WebhookRequest = mongoose.model<WebhookRequestDocument>(
+    'WebhookRequest',
+    new mongoose.Schema(
+        {
+            webhookId: String,
+            payload: String,
+            attempts: { type: Number, default: 0 },
+            state: Number,
+            httpStatus: Number,
+            failReason: String,
+        },
+        { timestamps: true },
+    ),
+    'webhookrequest',
+);
diff --git a/apps/api/src/app/models/Widget.ts b/apps/api/src/app/models/Widget.ts
new file mode 100644
index 000000000..49f6bd0ab
--- /dev/null
+++ b/apps/api/src/app/models/Widget.ts
@@ -0,0 +1,23 @@
+import mongoose from 'mongoose';
+
+export type WidgetDocument = mongoose.Document & TWidget;
+
+export const Widget = mongoose.model<WidgetDocument>(
+    'Widget',
+    new mongoose.Schema(
+        {
+            uuid: String,
+            poolId: String,
+            iconImg: String,
+            align: String,
+            message: String,
+            domain: String,
+            theme: String,
+            cssSelector: String,
+            active: { default: false, type: Boolean },
+            isPublished: { type: Boolean, default: true },
+        },
+        { timestamps: true },
+    ),
+    'widget',
+);
diff --git a/apps/api/src/app/models/index.ts b/apps/api/src/app/models/index.ts
new file mode 100644
index 000000000..004dd492d
--- /dev/null
+++ b/apps/api/src/app/models/index.ts
@@ -0,0 +1,61 @@
+export { Brand } from './Brand';
+export { QRCodeEntry, QRCodeEntryDocument } from './QRCodeEntry';
+export { Client, ClientDocument } from './Client';
+export { Collaborator, CollaboratorDocument } from './Collaborator';
+export { CouponCode, CouponCodeDocument } from './CouponCode';
+export { DiscordGuild, DiscordGuildDocument } from './DiscordGuild';
+export { DiscordMessage, DiscordMessageDocument } from './DiscordMessage';
+export { DiscordReaction, DiscordReactionDocument } from './DiscordReaction';
+export { ERC1155, ERC1155Document } from './ERC1155';
+export { ERC1155Metadata, ERC1155MetadataDocument } from './ERC1155Metadata';
+export { ERC1155Token, ERC1155TokenDocument } from './ERC1155Token';
+export { ERC20, ERC20Document } from './ERC20';
+export { ERC20Token, ERC20TokenDocument } from './ERC20Token';
+export { ERC20Transfer, ERC20TransferDocument } from './ERC20Transfer';
+export { ERC721, ERC721Document } from './ERC721';
+export { ERC721Metadata, ERC721MetadataDocument } from './ERC721Metadata';
+export { ERC721Token, ERC721TokenDocument } from './ERC721Token';
+export { ERC721Transfer, ERC721TransferDocument } from './ERC721Transfer';
+export { Event, EventDocument } from './Event';
+export { Identity, IdentityDocument } from './Identity';
+export { Invoice, InvoiceDocument } from './Invoice';
+export { Job, JobDocument } from './Job';
+export { Notification, NotificationDocument } from './Notification';
+export { Participant, ParticipantDocument } from './Participant';
+export { Pool, PoolDocument } from './Pool';
+export { QuestCustom, QuestCustomDocument } from './QuestCustom';
+export { QuestCustomEntry, QuestCustomEntryDocument } from './QuestCustomEntry';
+export { QuestDaily, QuestDailyDocument } from './QuestDaily';
+export { QuestDailyEntry, QuestDailyEntryDocument } from './QuestDailyEntry';
+export { QuestWebhook, QuestWebhookDocument } from './QuestWebhook';
+export { QuestWebhookEntry, QuestWebhookEntryDocument } from './QuestWebhookEntry';
+export { QuestGitcoin, QuestGitcoinDocument } from './QuestGitcoin';
+export { QuestGitcoinEntry, QuestGitcoinEntryDocument } from './QuestGitcoinEntry';
+export { QuestInvite, QuestInviteDocument } from './QuestInvite';
+export { QuestInviteEntry, QuestInviteEntryDocument } from './QuestInviteEntry';
+export { QuestSocial, QuestSocialDocument } from './QuestSocial';
+export { QuestSocialEntry, QuestSocialEntryDocument } from './QuestSocialEntry';
+export { QuestWeb3, QuestWeb3Document } from './QuestWeb3';
+export { QuestWeb3Entry, QuestWeb3EntryDocument } from './QuestWeb3Entry';
+export { RewardCoin, RewardCoinDocument } from './RewardCoin';
+export { RewardCoinPayment, RewardCoinPaymentDocument } from './RewardCoinPayment';
+export { RewardCoupon, RewardCouponDocument } from './RewardCoupon';
+export { RewardCouponPayment, RewardCouponPaymentDocument } from './RewardCouponPayment';
+export { RewardCustom, RewardCustomDocument } from './RewardCustom';
+export { RewardCustomPayment, RewardCustomPaymentDocument } from './RewardCustomPayment';
+export { RewardDiscordRole, RewardDiscordRoleDocument } from './RewardDiscordRole';
+export { RewardDiscordRolePayment, RewardDiscordRolePaymentDocument } from './RewardDiscordRolePayment';
+export { RewardNFT, RewardNFTDocument } from './RewardNFT';
+export { RewardNFTPayment, RewardNFTPaymentDocument } from './RewardNFTPayment';
+export { RewardGalachain, RewardGalachainDocument } from './RewardGalachain';
+export { RewardGalachainPayment, RewardGalachainPaymentDocument } from './RewardGalachainPayment';
+export { Transaction, TransactionDocument } from './Transaction';
+export { TwitterFollower, TwitterFollowerDocument } from './TwitterFollower';
+export { TwitterLike, TwitterLikeDocument } from './TwitterLike';
+export { TwitterRepost, TwitterRepostDocument } from './TwitterRepost';
+export { TwitterUser, TwitterUserDocument } from './TwitterUser';
+export { TwitterQuery, TwitterQueryDocument } from './TwitterQuery';
+export { Wallet, WalletDocument } from './Wallet';
+export { Webhook, WebhookDocument } from './Webhook';
+export { WebhookRequest, WebhookRequestDocument } from './WebhookRequest';
+export { Widget, WidgetDocument } from './Widget';
diff --git a/apps/api/src/app/proxies/AccountProxy.ts b/apps/api/src/app/proxies/AccountProxy.ts
new file mode 100644
index 000000000..097c2f33b
--- /dev/null
+++ b/apps/api/src/app/proxies/AccountProxy.ts
@@ -0,0 +1,113 @@
+import { authClient, getAuthAccessToken } from '@thxnetwork/api/util/auth';
+import { BadRequestError } from '../util/errors';
+import { AccessTokenKind, OAuthScope } from '@thxnetwork/common/enums';
+import { AxiosRequestConfig } from 'axios';
+
+export default class AccountProxy {
+    static async request(config: AxiosRequestConfig) {
+        const { status, data } = await authClient({
+            ...config,
+            headers: {
+                Authorization: await getAuthAccessToken(),
+            },
+        });
+
+        if (status >= 400 && status <= 500 && data.error) {
+            throw new BadRequestError(data.error.message);
+        }
+
+        return data;
+    }
+
+    static async getToken(account: TAccount, kind: AccessTokenKind, requiredScopes: OAuthScope[] = []) {
+        const token = await this.request({
+            method: 'GET',
+            url: `/accounts/${account.sub}/tokens/${kind}`,
+        });
+        if (token && requiredScopes.every((scope) => token.scopes.includes(scope))) return token;
+    }
+
+    static disconnect(account: TAccount, kind: AccessTokenKind) {
+        return this.request({
+            method: 'DELETE',
+            url: `/accounts/${account.sub}/tokens/${kind}`,
+        });
+    }
+
+    static findById(sub: string): Promise<TAccount> {
+        return this.request({
+            method: 'GET',
+            url: `/accounts/${sub}`,
+        });
+    }
+
+    static update(sub: string, updates: Partial<TAccount>): Promise<TAccount> {
+        return this.request({
+            method: 'PATCH',
+            url: `/accounts/${sub}`,
+            data: updates,
+        });
+    }
+
+    static remove(sub: string) {
+        return this.request({
+            method: 'DELETE',
+            url: `/accounts/${sub}`,
+        });
+    }
+
+    static find({ subs, query }: Partial<{ subs: string[]; query: string }>): Promise<TAccount[]> {
+        return this.request({
+            method: 'POST',
+            url: '/accounts',
+            data: { subs: JSON.stringify(subs), query },
+        });
+    }
+
+    static getByDiscordId(discordId: string): Promise<TAccount> {
+        return this.request({
+            method: 'GET',
+            url: `/accounts/discord/${discordId}`,
+        });
+    }
+
+    static getByEmail(email: string): Promise<TAccount> {
+        return this.request({
+            method: 'GET',
+            url: `/accounts/email/${email}`,
+        });
+    }
+
+    static getByAddress(address: string): Promise<TAccount> {
+        return this.request({
+            method: 'GET',
+            url: `/accounts/address/${address}`,
+        });
+    }
+
+    static getByIdentity(identity: string): Promise<TAccount> {
+        return this.request({
+            method: 'GET',
+            url: `/accounts/identity/${identity}`,
+        });
+    }
+
+    static async isEmailDuplicate(email: string) {
+        try {
+            await authClient({
+                method: 'GET',
+                url: `/accounts/email/${email}`,
+                headers: {
+                    Authorization: await getAuthAccessToken(),
+                },
+            });
+
+            return true;
+        } catch (error) {
+            if (error.response.status === 404) {
+                return false;
+            }
+            throw error;
+        }
+    }
+}
diff --git a/apps/api/src/app/proxies/ClientProxy.ts b/apps/api/src/app/proxies/ClientProxy.ts
new file mode 100644
index 000000000..812a5d3f0
--- /dev/null
+++ b/apps/api/src/app/proxies/ClientProxy.ts
@@ -0,0 +1,87 @@
+import { INITIAL_ACCESS_TOKEN } from '@thxnetwork/api/config/secrets';
+import { Client, ClientDocument, TClient, TClientPayload } from '@thxnetwork/api/models/Client';
+import { authClient } from '@thxnetwork/api/util/auth';
+import { paginatedResults } from '@thxnetwork/api/util/pagination';
+
+export default class ClientProxy {
+    static async getCredentials(client: ClientDocument) {
+        const { data } = await authClient({
+            method: 'GET',
+            url: `/reg/${client.clientId}?access_token=${client.registrationAccessToken}`,
+        });
+
+        client.clientSecret = data['client_secret'];
+        client.requestUris = data['request_uris'];
+
+        return client;
+    }
+
+    static async get(id: string): Promise<TClient> {
+        const client = await Client.findById(id);
+        return await this.getCredentials(client);
+    }
+
+    static async findByClientId(clientId: string): Promise<TClient> {
+        const client = await Client.findOne({ clientId });
+        return await this.getCredentials(client);
+    }
+
+    static async isAllowedOrigin(origin: string) {
+        return Client.exists({ origins: origin });
+    }
+
+    static async findByQuery(query: { poolId: string }, page = 1, limit = 10) {
+        return paginatedResults(Client, page, limit, query);
+    }
+
+    static async create(sub: string, poolId: string, payload: TClientPayload, name?: string) {
+        const { data } = await authClient({
+            method: 'POST',
+            url: '/reg',
+            headers: {
+                Authorization: `Bearer ${INITIAL_ACCESS_TOKEN}`,
+            },
+            data: payload,
+        });
+
+        const client = await Client.create({
+            sub,
+            name,
+            poolId,
+            grantType: payload.grant_types[0],
+            clientId: data.client_id,
+            registrationAccessToken: data.registration_access_token,
+        });
+
+        if (payload.request_uris && payload.request_uris.length) {
+            const origins = payload.request_uris.map((uri: string) => new URL(uri));
+            await client.updateOne({ origins });
+        }
+
+        client.clientSecret = data['client_secret'];
+        client.requestUris = data['request_uris'];
+
+        return client;
+    }
+
+    static async remove(clientId: string) {
+        const client = await Client.findOne({ clientId });
+
+        await authClient({
+            method: 'DELETE',
+            url: `/reg/${client.clientId}?access_token=${client.registrationAccessToken}`,
+        });
+
+        await client.deleteOne();
+    }
+
+    static async update(clientId: string, updates: TClientUpdatePayload) {
+        const client = await Client.findOne({ clientId });
+        await client.updateOne(updates);
+        return this.getCredentials(client);
+    }
+}
+
+export type TClientUpdatePayload = Partial<{
+    name: string;
+}>;
diff --git a/apps/api/src/app/proxies/DiscordDataProxy.ts b/apps/api/src/app/proxies/DiscordDataProxy.ts
new file mode 100644
index 000000000..f5a074ffd
--- /dev/null
+++ b/apps/api/src/app/proxies/DiscordDataProxy.ts
@@ -0,0 +1,156 @@
+import axios, { AxiosRequestConfig } from 'axios';
+import { client, PermissionFlagsBits } from '../../discord';
+import { DiscordGuild, DiscordGuildDocument, PoolDocument } from '@thxnetwork/api/models';
+import { ActionRowBuilder, ButtonBuilder, Guild } from 'discord.js';
+import { WIDGET_URL } from '../config/secrets';
+import { logger } from '../util/logger';
+import { AccessTokenKind, OAuthRequiredScopes } from '@thxnetwork/common/enums';
+import { DISCORD_API_ENDPOINT } from '@thxnetwork/common/constants';
+import AccountProxy from './AccountProxy';
+import { discordColorToHex } from '../util/discord';
+
+export enum NotificationVariant {
+    QuestDaily = 0,
+    QuestInvite = 1,
+    QuestYouTube = 3,
+    QuestTwitter = 4,
+    QuestDiscord = 5,
+    QuestCustom = 6,
+    QuestWeb3 = 7,
+}
+
+export async function discordClient(config: AxiosRequestConfig) {
+    try {
+        const client = axios.create({ ...config, baseURL: DISCORD_API_ENDPOINT });
+        return await client(config);
+    } catch (error) {
+        console.error(error);
+    }
+}
+
+export default class DiscordDataProxy {
+    static async sendChannelMessage(
+        pool: PoolDocument,
+        content: string,
+        embeds: TDiscordEmbed[] = [],
+        buttons?: TDiscordButton[],
+    ) {
+        try {
+            const discordGuild = await DiscordGuild.findOne({ poolId: String(pool._id) });
+            const url = WIDGET_URL + `/c/${pool.settings.slug}/quests`;
+
+            if (discordGuild && discordGuild.channelId) {
+                const channel: any = await client.channels.fetch(discordGuild.channelId);
+                const components = [];
+                if (buttons) components.push(this.createButtonActionRow(buttons));
+
+                const botMember = channel.guild.members.cache.get(client.user.id);
+                if (!botMember.permissionsIn(channel).has(PermissionFlagsBits.SendMessages)) {
+                    throw new Error('Insufficient channel permissions for bot to send messages.');
+                }
+
+                channel.send({ content, embeds, components });
+            } else if (pool.settings.discordWebhookUrl) {
+                // Extending the content with a link as we're not allowed to send button components over webhooks
+                content += ` [Complete Quest ▸](<${url}>)`;
+                axios.post(pool.settings.discordWebhookUrl, { content, embeds });
+            }
+        } catch (error) {
+            logger.error(error);
+        }
+    }
+
+    static createButtonActionRow(buttons: TDiscordButton[]) {
+        const components = buttons.map((btn: TDiscordButton) => {
+            const button = new ButtonBuilder().setLabel(btn.label).setStyle(btn.style);
+            if (btn.customId) button.setCustomId(btn.customId);
+            if (btn.url) button.setURL(btn.url);
+            if (btn.emoji) button.setEmoji(btn.emoji);
+            if (btn.disabled) button.setDisabled(true);
+            return button;
+        });
+        return new ActionRowBuilder().addComponents(components);
+    }
+
+    static async getGuilds(token: TToken) {
+        const r = await discordClient({
+            method: 'GET',
+            url: '/users/@me/guilds',
+            headers: {
+                'Accept': 'application/json',
+                'Content-Type': 'application/json',
+                'Authorization': `Bearer ${token.accessToken}`,
+            },
+        });
+        return r.data;
+    }
+
+    static async validateGuildJoined(account: TAccount, guildId: string) {
+        const token = await AccountProxy.getToken(
+            account,
+            AccessTokenKind.Discord,
+            OAuthRequiredScopes.DiscordValidateGuild,
+        );
+        if (!token) return { result: false, reason: 'Could not find a Discord access_token for this account.' };
+
+        const guilds = await this.getGuilds(token);
+        const isUserJoinedGuild = guilds.find((guild) => guild.id === guildId);
+        if (isUserJoinedGuild) return { result: true, reason: '' };
+
+        return { result: false, reason: 'Discord: Your Discord account is not a member of this server.' };
+    }
+
+    static async validateGuildRole(account: TAccount, guildId: string, roleId: string) {
+        const token = await AccountProxy.getToken(
+            account,
+            AccessTokenKind.Discord,
+            OAuthRequiredScopes.DiscordValidateGuild,
+        );
+        if (!token) return { result: false, reason: 'Could not find a Discord access_token for this account.' };
+
+        // Fetch guild from bot
+        const guild = await this.fetchGuild(guildId);
+        if (!guild) return { result: false, reason: 'THX Bot is not in the server.' };
+
+        // Check role for guild member
+        const member = await guild.members.fetch(token.userId);
+        console.log(member.roles.cache.has(roleId), roleId);
+        if (member.roles.cache.has(roleId)) return { result: true, reason: '' };
+
+        return { result: false, reason: 'You do not have the required role.' };
+    }
+
+    static async getGuildRoles(guild: Guild) {
+        return guild.roles.cache.map((role) => ({
+            id: role.id,
+            name: role.name,
+            color: discordColorToHex(role.color),
+        }));
+    }
+
+    static async getGuildChannels(guild: Guild) {
+        const channels = await guild.channels.fetch();
+        return channels.map((c) => ({ name: c.name, channelId: c.id }));
+    }
+
+    static async fetchGuild(guildId: string) {
+        try {
+            return await client.guilds.fetch(guildId);
+        } catch (error) {
+            return;
+        }
+    }
+
+    static async getGuild(guild: DiscordGuildDocument) {
+        const botGuild = await this.fetchGuild(guild.guildId);
+        const roles = botGuild ? await this.getGuildRoles(botGuild) : [];
+        const channels = botGuild ? await this.getGuildChannels(botGuild) : [];
+
+        return {
+            ...guild,
+            roles,
+            channels,
+            isInvited: !!botGuild,
+        };
+    }
+}
diff --git a/apps/api/src/app/proxies/TwitterDataProxy.ts b/apps/api/src/app/proxies/TwitterDataProxy.ts
new file mode 100644
index 000000000..8b2780adc
--- /dev/null
+++ b/apps/api/src/app/proxies/TwitterDataProxy.ts
@@ -0,0 +1,442 @@
+import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
+import { AccessTokenKind, OAuthRequiredScopes, OAuthTwitterScope } from '@thxnetwork/common/enums';
+import { TWITTER_API_ENDPOINT } from '@thxnetwork/common/constants';
+import { formatDistance } from 'date-fns';
+import { logger } from '../util/logger';
+import { TwitterLike } from '../models/TwitterLike';
+import { TwitterRepost } from '../models/TwitterRepost';
+import { TwitterUser } from '../models/TwitterUser';
+import { TwitterFollower } from '../models/TwitterFollower';
+import TwitterCacheService from '../services/TwitterCacheService';
+import AccountProxy from './AccountProxy';
+
+async function twitterClient(config: AxiosRequestConfig) {
+    const client = axios.create({ ...config, baseURL: TWITTER_API_ENDPOINT });
+    return await client(config);
+}
+
+export default class TwitterDataProxy {
+    static async getUserByUsername(account: TAccount, username: string) {
+        const token = await AccountProxy.getToken(account, AccessTokenKind.Twitter, [
+            OAuthTwitterScope.UsersRead,
+            OAuthTwitterScope.TweetRead,
+        ]);
+        if (!token) return { result: false, reason: 'X: Could not find a connection for this account.' };
+
+        const data = await this.request(token, {
+            method: 'GET',
+            url: `/users/by/username/${username}`,
+            params: {
+                'user.fields': 'profile_image_url,public_metrics',
+            },
+        });
+
+        return data.data;
+    }
+
+    static async getUser(account: TAccount, userId: string) {
+        const token = await AccountProxy.getToken(account, AccessTokenKind.Twitter, [
+            OAuthTwitterScope.UsersRead,
+            OAuthTwitterScope.TweetRead,
+        ]);
+        if (!token) return { result: false, reason: 'X: Could not find a connection for this account.' };
+
+        try {
+            const data = await this.request(token, {
+                method: 'GET',
+                url: `/users/${userId}`,
+                params: {
+                    'user.fields': 'profile_image_url,public_metrics',
+                },
+            });
+
+            // Cache TwitterUser
+            await TwitterUser.findOneAndUpdate(
+                { userId: data.data.id },
+                {
+                    userId: data.data.id,
+                    profileImgUrl: data.data.profile_image_url,
+                    name: data.data.name,
+                    username: data.data.username,
+                    publicMetrics: {
+                        followersCount: data.data.public_metrics.followers_count,
+                        followingCount: data.data.public_metrics.following_count,
+                        tweetCount: data.data.public_metrics.tweet_count,
+                        listedCount: data.data.public_metrics.listed_count,
+                        likeCount: data.data.public_metrics.like_count,
+                    },
+                },
+                { upsert: true },
+            );
+
+            return data.data;
+        } catch (res) {
+            return this.handleError(account, token, res);
+        }
+    }
+
+    static async getTweet(account: TAccount, tweetId: string) {
+        const token = await AccountProxy.getToken(account, AccessTokenKind.Twitter, [
+            OAuthTwitterScope.UsersRead,
+            OAuthTwitterScope.TweetRead,
+        ]);
+        if (!token) return { result: false, reason: 'X: Could not find a connection for this account.' };
+
+        const data = await this.request(token, {
+            method: 'GET',
+            url: `/tweets`,
+            params: {
+                ids: tweetId,
+                expansions: 'author_id',
+            },
+        });
+
+        return { tweet: data.data[0], user: data.includes.users[0] };
+    }
+
+    /**
+     * '/tweets/search/recent'
+     *
+     * Rate Limits:
+     * Application-only: 450 requests per 15-minute window shared among all users of your app
+     * User context: 180 requests per 15-minute window per each authenticated user
+     */
+    static async search(
+        account: TAccount,
+        query: string,
+        options: {
+            'max_results': number;
+            'media.fields': string;
+            'tweet.fields': string;
+            'user.fields': string;
+            'expansions': string;
+            'sort_order': string;
+        } = {
+            'max_results': 10,
+            'sort_order': 'recency', // relevancy / recency
+            'expansions': 'author_id,attachments.media_keys',
+            'tweet.fields': 'public_metrics,possibly_sensitive,created_at,attachments',
+            'user.fields': 'public_metrics,username,profile_image_url,verified,verified_type',
+            'media.fields': 'public_metrics,url,preview_image_url,width,height,alt_text',
+        },
+    ) {
+        const token = await AccountProxy.getToken(account, AccessTokenKind.Twitter, [
+            OAuthTwitterScope.UsersRead,
+            OAuthTwitterScope.TweetRead,
+        ]);
+        try {
+            logger.info(`Twitter Query: "${query}"`);
+
+            const data = await this.request(token, {
+                url: '/tweets/search/recent',
+                method: 'GET',
+                params: { query, ...options },
+            });
+            logger.info(`Twitter Query Results: ${data.meta.result_count}`);
+            if (!data.meta.result_count) return [];
+
+            return data.data.map((post) => {
+                const user = data.includes.users.find((user) => user.id === post.author_id);
+                const media =
+                    post.attachments &&
+                    post.attachments.media_keys &&
+                    post.attachments.media_keys.map((key) => data.includes.media.find((m) => m.media_key === key));
+                return {
+                    user,
+                    media,
+                    ...post,
+                };
+            });
+        } catch (error) {
+            logger.error(error);
+            return [];
+        }
+    }
+
+    static async searchTweets(account: TAccount, query: string) {
+        const token = await AccountProxy.getToken(account, AccessTokenKind.Twitter, [
+            OAuthTwitterScope.UsersRead,
+            OAuthTwitterScope.TweetRead,
+        ]);
+        const startTime = new Date(Date.now() - 60 * 60 * 24).toISOString(); // 24h ago
+        const data = await this.request(token, {
+            url: '/tweets/search/recent',
+            method: 'GET',
+            params: {
+                query: `from:${token.userId} ${query}`,
+                start_time: startTime,
+            },
+        });
+        return data.data;
+    }
+
+    static async validateUser(account: TAccount, quest: TQuestSocial) {
+        const token = await AccountProxy.getToken(
+            account,
+            AccessTokenKind.Twitter,
+            OAuthRequiredScopes.TwitterValidateUser,
+        );
+        if (!token) return { result: false, reason: 'X: Could not find a connection for this account.' };
+
+        const metadata = JSON.parse(quest.contentMetadata);
+        const minFollowersCount = metadata.minFollowersCount ? Number(metadata.minFollowersCount) : 0;
+
+        try {
+            const user = await this.getUser(account, token.userId);
+
+            // Validate the follower count for this user
+            const followersCount = user.public_metrics.followers_count;
+            if (followersCount >= minFollowersCount) return { result: true, reason: '' };
+
+            return {
+                result: false,
+                reason: `X: Your account does not meet the threshold of ${minFollowersCount} followers.`,
+            };
+        } catch (res) {
+            return this.handleError(account, token, res);
+        }
+    }
+
+    static async validateFollow(account: TAccount, userId: string) {
+        const token = await AccountProxy.getToken(
+            account,
+            AccessTokenKind.Twitter,
+            OAuthRequiredScopes.TwitterValidateFollow,
+        );
+        if (!token) return { result: false, reason: 'X: Could not find a connection for this account.' };
+        try {
+            if (token.userId === userId) {
+                return { result: false, reason: 'X: Can not validate a follow for your account with your account.' };
+            }
+
+            const data = await this.request(token, {
+                url: `/users/${token.userId}/following`,
+                method: 'POST',
+                data: {
+                    target_user_id: userId,
+                },
+            });
+
+            // Cache TwitterFollower here if isFollowing is true
+            await TwitterFollower.findOneAndUpdate(
+                { userId: token.userId, targetUserId: userId },
+                { userId: token.userId, targetUserId: userId },
+                { upsert: true },
+            );
+
+            if (data.data.following) {
+                return { result: true, reason: '' };
+            }
+
+            return { result: false, reason: 'X: Account is not found as a follower.' };
+        } catch (res) {
+            return this.handleError(account, token, res);
+        }
+    }
+
+    static async validateLike(account: TAccount, quest: TQuestSocial) {
+        const postId = quest.content;
+        const token = await AccountProxy.getToken(
+            account,
+            AccessTokenKind.Twitter,
+            OAuthRequiredScopes.TwitterValidateLike,
+        );
+        if (!token) return { result: false, reason: 'X: Could not find a connection for this account.' };
+
+        // Search for TwitterLikes with this userId and postId in the cache
+        const like = await TwitterLike.findOne({ userId: token.userId, postId });
+        if (like) {
+            logger.info(
+                `[${quest.poolId}][${account.sub}] X Quest ${quest._id} Like verification resolves from cache.`,
+            );
+            return { result: true, reason: '' };
+        }
+
+        try {
+            // No cache result means we should update the cache.
+            await TwitterCacheService.updateLikeCache(account, quest, token);
+
+            // Search the database again after a complete cache update that is not rate limited
+            const like = await this.findLike(token.userId, postId);
+            if (like) return { result: true, reason: '' };
+
+            // Fail if nothing is found
+            return { result: false, reason: 'X: Post has not been not liked.' };
+        } catch (res) {
+            // Search the database again after a partial cache update that threw an error
+            const like = await this.findLike(token.userId, postId);
+            if (like) return { result: true, reason: '' };
+
+            // If not found amongst the latest cache update then we show the rate limit error
+            return this.handleError(account, token, res);
+        }
+    }
+
+    static async validateRetweet(account: TAccount, quest: TQuestSocial) {
+        const postId = quest.content;
+        const token = await AccountProxy.getToken(
+            account,
+            AccessTokenKind.Twitter,
+            OAuthRequiredScopes.TwitterValidateRepost,
+        );
+        if (!token) return { result: false, reason: 'X: Could not find a connection for this account.' };
+
+        // Query cached TwitterReposts for this tweetId and userId
+        const repost = await TwitterRepost.findOne({ userId: token.userId, postId });
+        if (repost) {
+            logger.info(
+                `[${quest.poolId}][${account.sub}] X Quest ${quest._id} Repost verification resolves from cache.`,
+            );
+            return { result: true, reason: '' };
+        }
+
+        try {
+            // No cache result means we should update the cache.
+            await TwitterCacheService.updateRepostCache(account, quest, token);
+
+            // Search the database again after a complete cache update that is not rate limited
+            const repost = await this.findRepost(token.userId, postId);
+            if (repost) return { result: true, reason: '' };
+
+            // Fail if nothing is found
+            return { result: false, reason: 'X: Post has not been not reposted.' };
+        } catch (res) {
+            // Search the database again after a partial cache update that threw an error
+            const repost = await this.findRepost(token.userId, postId);
+            if (repost) return { result: true, reason: '' };
+
+            return this.handleError(account, token, res);
+        }
+    }
+
+    static async validateQuery(account: TAccount, quest: TQuestSocial) {
+        const token = await AccountProxy.getToken(
+            account,
+            AccessTokenKind.Twitter,
+            OAuthRequiredScopes.TwitterValidateMessage,
+        );
+        if (!token) return { result: false, reason: 'X: Could not find a connection for this account.' };
+        if (!token.metadata || !token.metadata.username) {
+            return { result: false, reason: 'X: Could not find your username. Please reconnect your X account.' };
+        }
+
+        // Check connected X account username is known
+        const { operators } = JSON.parse(quest.contentMetadata);
+        if (!token.metadata || !token.metadata.username) {
+            return { result: false, reason: 'X: Could not find your username. Please reconnect your X account.' };
+        }
+
+        // Check if account username is among the required post authors if this operator is available
+        const authorWhitelist = operators.from.map((author) => author);
+        const username = token.metadata.username.toLowerCase();
+        if (operators.from.length && !authorWhitelist.includes(username)) {
+            return {
+                result: false,
+                reason: `X: Your X account @${token.metadata.username} is not whitelisted for this quest.`,
+            };
+        }
+
+        try {
+            // If there is an author requirement we do not add the from operator for the current user
+            // and search for the given list instead
+            const query = operators.from.length ? quest.content : `from:${username} ${quest.content}`;
+
+            // Not checking the cache here on purpose as we would need to reverse engineer
+            // the query logic in order to find matched similar to how X would
+            const posts = await this.search(account, query);
+            if (!posts.length) {
+                return {
+                    result: false,
+                    reason: `X: Could not find a post matching the requirements in the last 7 days.`,
+                };
+            }
+
+            // Cache the posts for future reference and display in UI
+            await TwitterCacheService.savePosts(posts);
+
+            // Check if account username is among the results
+            const authorUsernames = posts.map((result) => result.user.username.toLowerCase());
+            if (!authorUsernames.includes(token.metadata.username.toLowerCase())) {
+                return {
+                    result: false,
+                    reason: `X: Your X account @${token.metadata.username} is not found among the matched posts.`,
+                };
+            }
+
+            return { result: true, reason: '' };
+        } catch (res) {
+            return this.handleError(account, token, res);
+        }
+    }
+
+    static findLike(userId: string, postId: string) {
+        return TwitterLike.findOne({ userId, postId });
+    }
+
+    static findRepost(userId: string, postId: string) {
+        return TwitterRepost.findOne({ userId, postId });
+    }
+
+    static async request(token: TToken, config: AxiosRequestConfig) {
+        try {
+            const { data } = await twitterClient({
+                ...config,
+                headers: { Authorization: `Bearer ${token.accessToken}` },
+            });
+            return data;
+        } catch (error) {
+            if (error.response) {
+                // Rethrow if this is an axios error
+                throw error.response;
+            } else {
+                logger.error(error);
+            }
+        }
+    }
+
+    static async handleError(account: TAccount, token: TToken, res: AxiosResponse) {
+        if (res.status === 429) {
+            logger.info(`[429] X-RateLimit is hit by account ${account.sub} with X UserId ${token.userId}.`);
+            return this.handleRateLimitError(res);
+        }
+
+        if (res.status === 401) {
+            logger.info(`[401] Token for ${account.sub} with X UserId ${token.userId} is invalid and disconnected.`);
+            await AccountProxy.disconnect(account, token.kind);
+            return { result: false, reason: 'Your X account connection has been removed, please reconnect!' };
+        }
+
+        if (res.status === 403) {
+            logger.info(`[403] Token for ${account.sub} with X UserId ${token.userId} has insufficient permissions.`);
+            return { result: false, reason: 'Your X account access level is insufficient, please reconnect!' };
+        }
+
+        logger.error(res);
+
+        return { result: false, reason: 'X: An unexpected issue occured during your request.' };
+    }
+
+    private static handleRateLimitError(res: AxiosResponse) {
+        const limit = res.headers['x-rate-limit-limit'];
+        const resetTime = Number(res.headers['x-rate-limit-reset']);
+        const seconds = resetTime - Math.ceil(Date.now() / 1000);
+
+        return {
+            result: false,
+            reason: `Quest requirement not found yet! We can only check ${
+                limit * 100
+            } items every 15 minutes. Please wait ${formatDistance(0, seconds * 1000, {
+                includeSeconds: true,
+            })} before retrying. Thank you!`,
+        };
+    }
+
+    private static parseSearchQuery(content: string) {
+        const emojiRegex = /<a?:.+?:\d{18}>|\p{Extended_Pictographic}/gu;
+        return content
+            .split(emojiRegex)
+            .filter((text) => text && text.length > 1 && !text.match(emojiRegex))
+            .map((text) => `"${text}"`)
+            .join(' ');
+    }
+}
diff --git a/apps/api/src/app/proxies/YoutubeDataProxy.ts b/apps/api/src/app/proxies/YoutubeDataProxy.ts
new file mode 100644
index 000000000..482c7e7c8
--- /dev/null
+++ b/apps/api/src/app/proxies/YoutubeDataProxy.ts
@@ -0,0 +1,53 @@
+import { google } from 'googleapis';
+import { AccessTokenKind, OAuthRequiredScopes, OAuthScope } from '@thxnetwork/common/enums';
+import { AUTH_URL, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET } from '../config/secrets';
+import AccountProxy from './AccountProxy';
+
+const client = new google.auth.OAuth2(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, AUTH_URL + '/oidc/callback/google');
+
+google.options({ auth: client });
+
+async function getClient(account: TAccount, requiredScopes: OAuthScope[]) {
+    const token = await AccountProxy.getToken(account, AccessTokenKind.Google, requiredScopes);
+    client.setCredentials({
+        access_token: token.accessToken,
+        refresh_token: token.refreshToken,
+    });
+    return google.youtube({ version: 'v3' });
+}
+
+export default class YoutubeDataProxy {
+    static async validateLike(account: TAccount, videoId: string, nextPageToken?: string) {
+        const youtube = await getClient(account, OAuthRequiredScopes.GoogleYoutubeLike);
+        const { data } = await youtube.videos.list({
+            part: ['snippet'],
+            myRating: 'like',
+            maxResults: 50,
+            pageToken: nextPageToken,
+        });
+
+        const isLiked = data.items.find((item) => item.id === videoId);
+        if (isLiked) return { result: true, reason: '' };
+
+        // NOTE Disabled paging as we hit rate limits when searching
+        // through all liked videos of a user.
+        // if (data.nextPageToken) {
+        //     return await this.validateLike(account, content, nextPageToken);
+        // }
+
+        return { result: false, reason: 'YouTube: Could not find your like for this video.' };
+    }
+
+    static async validateSubscribe(account: TAccount, channelId: string) {
+        const youtube = await getClient(account, OAuthRequiredScopes.GoogleYoutubeLike);
+        const { data } = await youtube.subscriptions.list({
+            forChannelId: channelId,
+            part: ['snippet'],
+            mine: true,
+        });
+        const isSubscribed = data.items.length > 0;
+        if (isSubscribed) return { result: true, reason: '' };
+
+        return { result: false, reason: 'Could not find your subscription for this channel.' };
+    }
+}
diff --git a/apps/api/src/app/services/AnalyticsService.ts b/apps/api/src/app/services/AnalyticsService.ts
new file mode 100644
index 000000000..297f57ce8
--- /dev/null
+++ b/apps/api/src/app/services/AnalyticsService.ts
@@ -0,0 +1,552 @@
+import mongoose from 'mongoose';
+import { RewardCoinDocument, RewardCoin } from '@thxnetwork/api/models/RewardCoin';
+import { RewardNFTDocument, RewardNFT } from '@thxnetwork/api/models/RewardNFT';
+import { QuestInviteDocument, QuestInvite } from '@thxnetwork/api/models/QuestInvite';
+import {
+    PoolDocument,
+    RewardCustomDocument,
+    QuestCustomDocument,
+    QuestSocialDocument,
+    QuestWeb3Document,
+    QuestWeb3,
+    QuestSocialEntry,
+    QuestInviteEntry,
+    QuestWeb3Entry,
+    RewardCoinPayment,
+    RewardNFTPayment,
+    WalletDocument,
+    Participant,
+    RewardCouponDocument,
+    RewardCustom,
+    RewardDiscordRoleDocument,
+    RewardCouponPayment,
+    RewardCustomPayment,
+    RewardDiscordRolePayment,
+    RewardCoupon,
+    RewardDiscordRole,
+    QuestCustomEntry,
+    QuestDailyEntry,
+    QuestCustom,
+    QuestSocial,
+    QuestDailyDocument,
+    QuestDaily,
+    QuestGitcoin,
+    QuestGitcoinEntry,
+    Wallet,
+    RewardGalachainDocument,
+    RewardGalachain,
+    QuestGitcoinDocument,
+    RewardGalachainPayment,
+} from '@thxnetwork/api/models';
+
+async function getPoolAnalyticsForChart(pool: PoolDocument, startDate: Date, endDate: Date) {
+    // Rewards
+    const [
+        erc20PerksQueryResult,
+        erc721PerksQueryResult,
+        customRewardsQueryResult,
+        couponRewardsQueryResult,
+        discordRoleRewardsQueryResult,
+        galachainRewardsQueryResult,
+    ] = await Promise.all([
+        queryRewardRedemptions<RewardCoinDocument>({
+            collectionName: 'rewardcoinpayment',
+            key: 'rewardId',
+            model: RewardCoin,
+            poolId: String(pool._id),
+            startDate,
+            endDate,
+        }),
+        queryRewardRedemptions<RewardNFTDocument>({
+            collectionName: 'rewardnftpayment',
+            key: 'rewardId',
+            model: RewardNFT,
+            poolId: String(pool._id),
+            startDate,
+            endDate,
+        }),
+        queryRewardRedemptions<RewardCustomDocument>({
+            collectionName: 'rewardcustompayment',
+            key: 'rewardId',
+            model: RewardCustom,
+            poolId: String(pool._id),
+            startDate,
+            endDate,
+        }),
+        queryRewardRedemptions<RewardCouponDocument>({
+            collectionName: 'rewardcouponpayment',
+            key: 'rewardId',
+            model: RewardCoupon,
+            poolId: String(pool._id),
+            startDate,
+            endDate,
+        }),
+        queryRewardRedemptions<RewardDiscordRoleDocument>({
+            collectionName: 'rewarddiscordrolepayment',
+            key: 'rewardId',
+            model: RewardDiscordRole,
+            poolId: String(pool._id),
+            startDate,
+            endDate,
+        }),
+        queryRewardRedemptions<RewardGalachainDocument>({
+            collectionName: 'rewargalachainpayment',
+            key: 'rewardId',
+            model: RewardGalachain,
+            poolId: String(pool._id),
+            startDate,
+            endDate,
+        }),
+    ]);
+
+    // Quests
+    const [
+        milestoneRewardsQueryResult,
+        referralRewardsQueryResult,
+        pointRewardsQueryResult,
+        dailyRewardsQueryResult,
+        web3QuestsQueryResult,
+        gitcoinQuestsQueryResult,
+    ] = await Promise.all([
+        queryQuestEntries<QuestCustomDocument>({
+            collectionName: 'questcustomentry',
+            key: 'questId',
+            model: QuestCustom,
+            poolId: String(pool._id),
+            startDate,
+            endDate,
+            extraFilter: { isClaimed: true },
+        }),
+        queryQuestEntries<QuestInviteDocument>({
+            collectionName: 'questinviteentry',
+            key: 'questId',
+            model: QuestInvite,
+            poolId: String(pool._id),
+            startDate,
+            endDate,
+            extraFilter: { isApproved: true },
+        }),
+        queryQuestEntries<QuestSocialDocument>({
+            collectionName: 'questsocialentry',
+            key: 'questId',
+            model: QuestSocial,
+            poolId: String(pool._id),
+            startDate,
+            endDate,
+        }),
+        queryQuestEntries<QuestDailyDocument>({
+            collectionName: 'questdailyentry',
+            key: 'questId',
+            model: QuestDaily,
+            poolId: String(pool._id),
+            startDate,
+            endDate,
+        }),
+        queryQuestEntries<QuestWeb3Document>({
+            collectionName: 'questweb3entry',
+            key: 'questId',
+            model: QuestWeb3,
+            poolId: String(pool._id),
+            startDate,
+            endDate,
+        }),
+        queryQuestEntries<QuestGitcoinDocument>({
+            collectionName: 'questgitcoinentry',
+            key: 'questId',
+            model: QuestGitcoin,
+            poolId: String(pool._id),
+            startDate,
+            endDate,
+        }),
+    ]);
+
+    const result = {
+        _id: pool._id,
+        erc20Perks: erc20PerksQueryResult.map((x) => {
+            return {
+                day: x._id,
+                totalAmount: x.total_amount,
+            };
+        }),
+        erc721Perks: erc721PerksQueryResult.map((x) => {
+            return {
+                day: x._id,
+                totalAmount: x.total_amount,
+            };
+        }),
+        customRewards: customRewardsQueryResult.map((x) => {
+            return {
+                day: x._id,
+                totalAmount: x.total_amount,
+            };
+        }),
+        couponRewards: couponRewardsQueryResult.map((x) => {
+            return {
+                day: x._id,
+                totalAmount: x.total_amount,
+            };
+        }),
+        discordRoleRewards: discordRoleRewardsQueryResult.map((x) => {
+            return {
+                day: x._id,
+                totalAmount: x.total_amount,
+            };
+        }),
+        galachainRewards: galachainRewardsQueryResult.map((x) => {
+            return {
+                day: x._id,
+                totalAmount: x.total_amount,
+            };
+        }),
+        //
+        dailyRewards: dailyRewardsQueryResult.map((x) => {
+            return {
+                day: x._id,
+                totalAmount: x.total_amount,
+            };
+        }),
+        milestoneRewards: milestoneRewardsQueryResult.map((x) => {
+            return {
+                day: x._id,
+                totalAmount: x.total_amount,
+            };
+        }),
+        referralRewards: referralRewardsQueryResult.map((x) => {
+            return {
+                day: x._id,
+                totalAmount: x.total_amount,
+            };
+        }),
+        pointRewards: pointRewardsQueryResult.map((x) => {
+            return {
+                day: x._id,
+                totalAmount: x.total_amount,
+            };
+        }),
+        web3Quests: web3QuestsQueryResult.map((x) => {
+            return {
+                day: x._id,
+                totalAmount: x.total_amount,
+            };
+        }),
+        gitcoinQuests: gitcoinQuestsQueryResult.map((x) => {
+            return {
+                day: x._id,
+                totalAmount: x.total_amount,
+            };
+        }),
+    };
+    return result;
+}
+
+async function getPoolMetrics(pool: PoolDocument, dateRange?: { startDate: Date; endDate: Date }) {
+    const collections = [
+        QuestDailyEntry,
+        QuestSocialEntry,
+        QuestInviteEntry,
+        QuestCustomEntry,
+        QuestWeb3Entry,
+        QuestGitcoinEntry,
+        RewardCoinPayment,
+        RewardNFTPayment,
+        RewardCustomPayment,
+        RewardCouponPayment,
+        RewardDiscordRolePayment,
+        RewardGalachainPayment,
+    ];
+    const [
+        dailyQuest,
+        socialQuest,
+        inviteQuest,
+        customQuest,
+        web3Quest,
+        gitcoinQuest,
+        coinReward,
+        nftReward,
+        customReward,
+        couponReward,
+        discordRoleReward,
+        galachainReward,
+    ] = await Promise.all(
+        collections.map(async (Model) => {
+            const $match = { poolId: String(pool._id) };
+            if (dateRange) {
+                $match['createdAt'] = { $gte: dateRange.startDate, $lte: dateRange.endDate };
+            }
+
+            // Extend the $match filter with model specific properties
+            switch (Model) {
+                case QuestDailyEntry:
+                    $match['state'] = 1;
+                    break;
+                case QuestCustomEntry:
+                    $match['isClaimed'] = true;
+                    break;
+            }
+
+            const [result] = await Model.aggregate([
+                { $match },
+                {
+                    $group: {
+                        _id: '$poolId',
+                        totalCompleted: { $sum: 1 },
+                        totalAmount: { $sum: { $convert: { input: '$amount', to: 'int' } } },
+                    },
+                },
+            ]);
+
+            const query = { poolId: String(pool._id) };
+            if (dateRange) {
+                query['createdAt'] = { $gte: dateRange.startDate, $lte: dateRange.endDate };
+            }
+            const totalCreated = await Model.countDocuments(query as any);
+
+            return {
+                totalCompleted: result && result.totalCompleted ? result.totalCompleted : 0,
+                totalAmount: result && result.totalAmount ? result.totalAmount : 0,
+                totalCreated,
+            };
+        }),
+    );
+
+    return {
+        dailyQuest,
+        socialQuest,
+        inviteQuest,
+        customQuest,
+        web3Quest,
+        gitcoinQuest,
+        coinReward,
+        nftReward,
+        customReward,
+        couponReward,
+        discordRoleReward,
+        galachainReward,
+    };
+}
+
+async function createLeaderboard(pool: PoolDocument, dateRange?: { startDate: Date; endDate: Date }) {
+    const collections = [
+        QuestDailyEntry,
+        QuestSocialEntry,
+        QuestInviteEntry,
+        QuestCustomEntry,
+        QuestWeb3Entry,
+        QuestGitcoinEntry,
+    ];
+    const result = await Promise.all(
+        collections.map(async (Model) => {
+            const $match = { poolId: String(pool._id) };
+
+            // Extend the $match filter with optional dateRange
+            if (dateRange) {
+                $match['createdAt'] = { $gte: dateRange.startDate, $lte: dateRange.endDate };
+            }
+
+            // Extend the $match filter with model specific properties
+            switch (Model) {
+                case QuestDailyEntry:
+                    $match['state'] = 1;
+                    break;
+                case QuestCustomEntry:
+                    $match['isClaimed'] = true;
+                    break;
+            }
+
+            const $group = {
+                _id: '$sub',
+                totalCompleted: { $sum: 1 },
+                totalAmount: { $sum: { $convert: { input: '$amount', to: 'int' } } },
+            };
+
+            return await Model.aggregate([{ $match }, { $group }]);
+        }),
+    );
+
+    // Combine results from all collections and calculate overall totals
+    const walletTotals = {};
+    for (const collectionResults of result) {
+        for (const r of collectionResults) {
+            if (!r) continue;
+            if (walletTotals[r._id]) {
+                walletTotals[r._id].totalCompleted += r.totalCompleted;
+                walletTotals[r._id].totalAmount += r.totalAmount;
+            } else {
+                walletTotals[r._id] = {
+                    totalCompleted: r.totalCompleted,
+                    totalAmount: r.totalAmount,
+                };
+            }
+        }
+    }
+
+    const wallets = await Wallet.find({ _id: Object.keys(walletTotals), sub: { $exists: true } });
+    const leaderboard = wallets
+        .map((wallet: WalletDocument) => ({
+            score: walletTotals[wallet._id].totalAmount || 0,
+            questEntryCount: walletTotals[wallet._id].totalCompleted || 0,
+            sub: wallet.sub,
+        }))
+        .filter((entry) => entry.score > 0)
+        .sort((a: any, b: any) => b.score - a.score);
+
+    const updates = leaderboard.map(
+        (entry: { sub: string; score: number; questEntryCount: number }, index: number) => ({
+            updateOne: {
+                filter: { poolId: String(pool._id), sub: entry.sub },
+                update: {
+                    $set: {
+                        rank: Number(index) + 1,
+                        score: entry.score,
+                        questEntryCount: entry.questEntryCount,
+                    },
+                },
+            },
+        }),
+    );
+
+    await Participant.bulkWrite(updates);
+}
+
+async function queryQuestEntries<T>(args: {
+    model: mongoose.Model<T>;
+    poolId: string;
+    collectionName: string;
+    key: string;
+    startDate: Date;
+    endDate: Date;
+    extraFilter?: object;
+}) {
+    const extraFilter = args.extraFilter ? { ...args.extraFilter } : {};
+    const queryResult = await args.model.aggregate([
+        {
+            $match: {
+                poolId: args.poolId,
+            },
+        },
+        {
+            $lookup: {
+                from: args.collectionName,
+                let: {
+                    id: {
+                        $convert: {
+                            input: '$_id',
+                            to: 'string',
+                        },
+                    },
+                },
+                pipeline: [
+                    {
+                        $match: {
+                            $and: [
+                                {
+                                    $expr: {
+                                        $eq: ['$$id', `$${args.key}`],
+                                    },
+                                },
+                                {
+                                    createdAt: {
+                                        $gte: args.startDate,
+                                        $lte: args.endDate,
+                                    },
+                                },
+                                extraFilter,
+                            ],
+                        },
+                    },
+                ],
+                as: 'entries',
+            },
+        },
+        {
+            $unwind: '$entries',
+        },
+        {
+            $group: {
+                _id: {
+                    $dateToString: {
+                        format: '%Y-%m-%d',
+                        date: { $toDate: '$entries.createdAt' },
+                    },
+                },
+                total_amount: {
+                    $sum: 1,
+                },
+            },
+        },
+    ]);
+
+    return queryResult;
+}
+
+async function queryRewardRedemptions<T>(args: {
+    model: mongoose.Model<T>;
+    poolId: string;
+    collectionName: string;
+    key: string;
+    startDate: Date;
+    endDate: Date;
+    extraFilter?: object;
+}) {
+    const extraFilter = args.extraFilter ? { ...args.extraFilter } : {};
+    const queryResult = await args.model.aggregate([
+        {
+            $match: {
+                poolId: args.poolId,
+            },
+        },
+        {
+            $lookup: {
+                from: args.collectionName,
+                let: {
+                    id: {
+                        $convert: {
+                            input: '$_id',
+                            to: 'string',
+                        },
+                    },
+                },
+                pipeline: [
+                    {
+                        $match: {
+                            $and: [
+                                {
+                                    $expr: {
+                                        $eq: ['$$id', `$${args.key}`],
+                                    },
+                                },
+                                {
+                                    createdAt: {
+                                        $gte: args.startDate,
+                                        $lte: args.endDate,
+                                    },
+                                },
+                                extraFilter,
+                            ],
+                        },
+                    },
+                ],
+                as: 'entries',
+            },
+        },
+        {
+            $unwind: '$entries',
+        },
+        {
+            $group: {
+                _id: {
+                    $dateToString: {
+                        format: '%Y-%m-%d',
+                        date: { $toDate: '$entries.createdAt' },
+                    },
+                },
+                total_amount: {
+                    $sum: 1,
+                },
+            },
+        },
+    ]);
+
+    return queryResult;
+}
+export default { getPoolMetrics, createLeaderboard, getPoolAnalyticsForChart };
diff --git a/apps/api/src/app/services/BalancerService.ts b/apps/api/src/app/services/BalancerService.ts
new file mode 100644
index 000000000..d9e6f10d2
--- /dev/null
+++ b/apps/api/src/app/services/BalancerService.ts
@@ -0,0 +1,246 @@
+import axios from 'axios';
+import { ethers } from 'ethers';
+import { BalancerSDK, Network } from '@balancer-labs/sdk';
+import { BALANCER_POOL_ID, ETHEREUM_RPC, HARDHAT_RPC, NODE_ENV, POLYGON_RPC } from '../config/secrets';
+import { logger } from '../util/logger';
+import { WalletDocument } from '../models';
+import { ChainId } from '@thxnetwork/common/enums';
+import { contractArtifacts, contractNetworks } from '@thxnetwork/api/hardhat';
+import { formatUnits } from 'ethers/lib/utils';
+import { BigNumber } from 'alchemy-sdk';
+
+class BalancerService {
+    pricing = {};
+    apr = {
+        [ChainId.Hardhat]: {
+            balancer: {
+                min: 0,
+                max: 0,
+                swapFees: 0,
+            },
+            thx: 0,
+        },
+        [ChainId.Polygon]: {
+            balancer: {
+                min: 0,
+                max: 0,
+                swapFees: 0,
+            },
+            thx: 0,
+        },
+    };
+    tvl = {
+        [ChainId.Hardhat]: { liquidity: '0', staked: '0', tvl: '0' },
+        [ChainId.Polygon]: { liquidity: '0', staked: '0', tvl: '0' },
+    };
+    rewards = {
+        [ChainId.Hardhat]: { bal: '0', bpt: '0' },
+        [ChainId.Polygon]: { bal: '0', bpt: '0' },
+    };
+    schedule = {
+        [ChainId.Hardhat]: { bal: [], bpt: [] },
+        [ChainId.Polygon]: { bal: [], bpt: [] },
+    };
+    balancer = new BalancerSDK({
+        network: Network.POLYGON,
+        rpcUrl: POLYGON_RPC,
+    });
+
+    constructor() {
+        this.updatePricesJob().then(() => {
+            this.updateMetricsJob();
+        });
+    }
+
+    async buildJoin(
+        wallet: WalletDocument,
+        usdcAmountInWei: string,
+        thxAmountInWei: string,
+        slippage: string,
+    ): Promise<JoinPoolAttributes> {
+        const pool = await this.balancer.pools.find(BALANCER_POOL_ID);
+        const [usdc, thx] = pool.tokens as {
+            address: string;
+        }[];
+
+        return pool.buildJoin(
+            wallet.address,
+            [usdc.address, thx.address],
+            [usdcAmountInWei, thxAmountInWei],
+            slippage,
+        ) as JoinPoolAttributes;
+    }
+
+    getPricing() {
+        return this.pricing;
+    }
+
+    getMetrics(chainId: ChainId) {
+        return {
+            apr: this.apr[chainId],
+            tvl: this.tvl[chainId],
+            rewards: this.rewards[chainId],
+            schedule: this.schedule[chainId],
+        };
+    }
+
+    async fetchPrice(symbolIn: string, symbolOut: string) {
+        try {
+            const { data } = await axios({
+                method: 'GET',
+                url: `https://api.coinbase.com/v2/exchange-rates?currency=${symbolIn}`,
+            });
+
+            return data.data.rates[symbolOut];
+        } catch (error) {
+            logger.error(error);
+            return 0;
+        }
+    }
+
+    async updatePricesJob() {
+        const pool = await this.balancer.pools.find(BALANCER_POOL_ID);
+        const [usdc, thx] = pool.tokens as unknown as {
+            symbol: string;
+            balance: number;
+            token: { latestUSDPrice: number };
+        }[];
+        const totalShares = pool.totalShares as unknown as number;
+        const thxValue = thx.balance * thx.token.latestUSDPrice;
+        const usdcValue = usdc.balance * usdc.token.latestUSDPrice;
+        const btpPrice = (thxValue + usdcValue) / totalShares;
+        const balPrice = await this.fetchPrice('BAL', 'USDC');
+
+        this.pricing = {
+            '20USDC-80THX': btpPrice,
+            'BAL': Number(balPrice),
+            'USDC': Number(usdc.token.latestUSDPrice),
+            'THX': Number(thx.token.latestUSDPrice),
+        };
+    }
+
+    async updateMetricsJob() {
+        try {
+            const rpcMap = { [ChainId.Hardhat]: HARDHAT_RPC, [ChainId.Polygon]: POLYGON_RPC };
+            const priceOfBAL = this.pricing['BAL'];
+            const pricePerBPT = this.pricing['20USDC-80THX'];
+
+            // Amount of bpt-gauge locked in veTHX in wei
+            for (const chainId of [ChainId.Hardhat, ChainId.Polygon]) {
+                if (NODE_ENV === 'production' && chainId === ChainId.Hardhat) continue;
+                const provider = new ethers.providers.JsonRpcProvider(rpcMap[chainId]);
+                const gaugeAddress = contractNetworks[chainId].BPTGauge;
+                const bptAddress = contractNetworks[chainId].BPT;
+                const gauge = new ethers.Contract(gaugeAddress, contractArtifacts.BPTGauge.abi, provider);
+                const bpt = new ethers.Contract(bptAddress, contractArtifacts.BPT.abi, provider);
+
+                // veTHX contract on Polygon
+                const veTHXAddress = contractNetworks[chainId].VotingEscrow;
+                const veTHX = new ethers.Contract(veTHXAddress, contractArtifacts.VotingEscrow.abi, provider);
+
+                const { rewards, schedule } = await this.getRewards(chainId);
+                this.rewards[chainId] = rewards;
+                logger.debug(this.rewards[chainId]);
+
+                this.schedule[chainId] = schedule;
+                logger.debug(this.schedule[chainId]);
+
+                // TVL is measured as the total amount of BPT-gauge locked in veTHX
+                const liquidity = (await bpt.totalSupply()).toString();
+                const staked = (await bpt.balanceOf(gauge.address)).toString();
+                const tvl = (await gauge.balanceOf(veTHXAddress)).toString();
+                this.tvl[chainId] = { liquidity, staked, tvl };
+                logger.debug(this.tvl[chainId]);
+
+                // Calc APR
+                const apr = await this.calculateBalancerAPR(gauge, priceOfBAL, pricePerBPT);
+                const balancer = { apr, swapFees: 0.2 }; // TODO Fetch swapFees from SDK or contract
+                const rewardsInBPT = this.rewards[chainId].bpt;
+                const thx = await this.calculateTHXAPR(gauge, veTHX, rewardsInBPT, pricePerBPT);
+                this.apr[chainId] = { balancer, thx };
+                logger.debug(this.apr[chainId]);
+            }
+
+            // Log pricing here because job interval creates less logging clutter
+            logger.debug(this.pricing);
+        } catch (error) {
+            console.log(error);
+        }
+    }
+
+    async calculateTHXAPR(gauge: ethers.Contract, veTHX: ethers.Contract, rewardsInBPT: string, pricePerBPT: number) {
+        const monthlyEmissions = Number(formatUnits(rewardsInBPT, 18));
+        const totalShares = Number(formatUnits(await gauge.balanceOf(veTHX.address), 18));
+        const pricePerShare = pricePerBPT;
+        return ((monthlyEmissions * 12) / totalShares / pricePerShare) * 100;
+    }
+
+    async calculateBalancerAPR(gauge: ethers.Contract, priceOfBAL: number, pricePerBPT: number) {
+        // Balancer Gauge contracts on Ethereum
+        const ethereumProvider = new ethers.providers.JsonRpcProvider(ETHEREUM_RPC);
+        const gaugeControllerAddress = contractNetworks[ChainId.Ethereum].BalancerGaugeController;
+        const gaugeController = new ethers.Contract(
+            gaugeControllerAddress,
+            contractArtifacts.BalancerGaugeController.abi,
+            ethereumProvider,
+        );
+        const rootGaugeAddress = contractNetworks[ChainId.Ethereum].BalancerRootGauge;
+
+        // APR formula inputs
+        const gaugeRelWeight = Number((await gaugeController.gauge_relative_weight(rootGaugeAddress)).toString());
+        const workingSupply = Number((await gauge.working_supply()).toString());
+
+        // Take Balancer inflation schedule into account. Started at 140000 BAL per week
+        // https://docs.balancer.fi/concepts/governance/bal-token.html#supply-inflation-schedule
+        const weeklyBALemissions = 102530.48; // TODO add formula to calculate weekly emissions
+
+        // APR formula as per
+        // https://docs.balancer.fi/reference/vebal-and-gauges/apr-calculation.html
+
+        // Example data May 4th 2024
+        // const workingSupply = 8.102148903933154e23;
+        // const gaugeRelWeight = 1518354055844830;
+        // const weeklyBALemissions = 102530.48;
+        // const priceOfBAL = 3.655;
+        // const pricePerBPT = 0.04489925552408662;
+
+        return (
+            (((0.4 / (workingSupply + 0.4)) * gaugeRelWeight * weeklyBALemissions * 52 * priceOfBAL) / pricePerBPT) *
+            100
+        );
+    }
+
+    async getRewards(chainId: ChainId) {
+        const rpcMap = {
+            [ChainId.Hardhat]: HARDHAT_RPC,
+            [ChainId.Polygon]: POLYGON_RPC,
+        };
+        const { BAL, BPT, RewardFaucet, RewardDistributor } = contractNetworks[chainId];
+        const provider = new ethers.providers.JsonRpcProvider(rpcMap[chainId]);
+        const rewardFaucet = new ethers.Contract(RewardFaucet, contractArtifacts['RewardFaucet'].abi, provider);
+        const amountOfWeeks = '4';
+        const [balSchedule, bptSchedule] = await Promise.all(
+            [BAL, BPT].map(async (tokenAddress: string) => {
+                const upcoming = await rewardFaucet.getUpcomingRewardsForNWeeks(tokenAddress, amountOfWeeks);
+                return upcoming.map((amount: BigNumber) => amount.toString());
+            }),
+        );
+        const [balTotal, bptTotal] = await Promise.all(
+            [BAL, BPT].map(async (tokenAddress) => await rewardFaucet.totalTokenRewards(tokenAddress)),
+        );
+
+        // Add reward distributor BAL balance to the current week
+        const balContract = new ethers.Contract(BAL, contractArtifacts['BAL'].abi, provider);
+        const balBalance = await balContract.balanceOf(RewardDistributor);
+        balSchedule[0] = BigNumber.from(balSchedule[0]).add(balBalance).toString();
+
+        return {
+            schedule: { bal: balSchedule, bpt: bptSchedule },
+            rewards: { bal: balTotal.add(balBalance).toString(), bpt: bptTotal.toString() },
+        };
+    }
+}
+
+const service = new BalancerService();
+
+export default service;
diff --git a/apps/api/src/app/services/BrandService.ts b/apps/api/src/app/services/BrandService.ts
new file mode 100644
index 000000000..bbcc60637
--- /dev/null
+++ b/apps/api/src/app/services/BrandService.ts
@@ -0,0 +1,10 @@
+import { Brand } from '@thxnetwork/api/models/Brand';
+
+export default {
+    get: async (poolId: string) => {
+        return Brand.findOne({ poolId });
+    },
+    update: async (filter: Partial<TBrand>, updates: Partial<TBrand>) => {
+        return Brand.findOneAndUpdate(filter, updates, { upsert: true, new: true });
+    },
+};
diff --git a/apps/api/src/app/services/CanvasService.ts b/apps/api/src/app/services/CanvasService.ts
new file mode 100644
index 000000000..e60be467c
--- /dev/null
+++ b/apps/api/src/app/services/CanvasService.ts
@@ -0,0 +1,79 @@
+import path from 'path';
+import { createCanvas, loadImage, registerFont } from 'canvas';
+import { assetsPath } from '@thxnetwork/api/util/path';
+
+// Load on boot as registration on runtime results in font not being loaded in time
+const fontPath = path.resolve(assetsPath, 'fa-solid-900.ttf');
+const family = 'Font Awesome 5 Pro Solid';
+const defaultBackgroundImgPath = path.resolve(assetsPath, 'bg.png');
+const defaultLogoImgPath = path.resolve(assetsPath, 'logo.png');
+
+registerFont(fontPath, { family, style: 'normal', weight: '900' });
+
+function drawImageBg(canvas, ctx, image) {
+    const imageAspectRatio = image.width / image.height;
+    let scaledWidth = canvas.width + 1,
+        scaledHeight = canvas.width / imageAspectRatio;
+
+    if (scaledHeight < canvas.height) {
+        scaledHeight = canvas.height;
+        scaledWidth = canvas.height * imageAspectRatio;
+    }
+
+    const offsetX = Math.floor((canvas.width - scaledWidth) / 2);
+    const offsetY = Math.floor((canvas.height - scaledHeight) / 2);
+
+    // Draw mask
+    ctx.beginPath();
+    ctx.moveTo(0, 0);
+    ctx.lineTo(canvas.width, 0);
+    ctx.lineTo(canvas.width, canvas.height);
+    ctx.lineTo(0, canvas.height);
+    ctx.lineTo(0, 0);
+    ctx.closePath();
+
+    ctx.clip();
+    ctx.drawImage(image, offsetX, offsetY, scaledWidth, scaledHeight);
+    ctx.restore();
+}
+
+async function dataUrlToFile(dataUrl: string) {
+    return await loadImage(dataUrl);
+}
+
+async function createPreviewImage({ logoImgUrl, backgroundImgUrl }: TBrand) {
+    const bg = await loadImage(backgroundImgUrl || defaultBackgroundImgPath);
+    const logo = await loadImage(logoImgUrl || defaultLogoImgPath);
+
+    // Create a canvas with the desired dimensions
+    // https://www.linkedin.com/help/linkedin/answer/a521928/make-your-website-shareable-on-linkedin
+    const canvasWidth = 1200;
+    const canvasHeight = 627;
+    const canvas = createCanvas(canvasWidth, canvasHeight);
+
+    const ctx = canvas.getContext('2d');
+
+    // Draw the loaded image onto the canvas
+    drawImageBg(canvas, ctx, bg);
+
+    // Draw the logo
+    const logoRatio = logo.width / logo.height;
+    const logoWidth = 200;
+    const logoHeight = logoWidth / logoRatio;
+
+    ctx.drawImage(logo, canvasWidth / 2 - logoWidth / 2, canvasHeight / 2 - logoHeight / 2, logoWidth, logoHeight);
+
+    // Convert the canvas content to a buffer
+    // const dataUrl = canvas.toDataURL('image/png');
+    const buffer = canvas.toBuffer('image/png');
+
+    return buffer;
+}
+
+export default {
+    dataUrlToFile,
+    createPreviewImage,
+    defaultBackgroundImgPath,
+    defaultLogoImgPath,
+    drawImageBg,
+};
diff --git a/apps/api/src/app/services/ClaimService.ts b/apps/api/src/app/services/ClaimService.ts
new file mode 100644
index 000000000..5a8eeb73e
--- /dev/null
+++ b/apps/api/src/app/services/ClaimService.ts
@@ -0,0 +1,50 @@
+import { QRCodeEntry } from '@thxnetwork/api/models';
+import { v4 } from 'uuid';
+
+export default class QRCodeService {
+    static create(data: Partial<TQRCodeEntry>, claimAmount: number) {
+        if (!claimAmount) return;
+        return QRCodeEntry.create(Array.from({ length: Number(claimAmount) }).map(() => ({ uuid: v4(), ...data })));
+    }
+
+    static findByReward(reward: TRewardNFT, page: number, limit: number) {
+        //
+    }
+}
+
+// function create(
+//     data: { poolId: string; rewardUuid: string; erc20Id?: string; erc721Id?: string; erc1155Id?: string },
+//     claimAmount: number,
+// ) {
+//     if (!claimAmount) return;
+//     return QRCodeEntry.create(
+//         Array.from({ length: Number(claimAmount) }).map(() => ({ uuid: db.createUUID(), ...data })),
+//     );
+// }
+
+// function findByUuid(uuid: string) {
+//     return QRCodeEntry.findOne({ uuid });
+// }
+
+// function findByPool(pool: PoolDocument) {
+//     return QRCodeEntry.find({ poolId: String(pool._id) });
+// }
+
+// async function findByPerk(reward: TReward) {
+//     const claims = await QRCodeEntry.find({ rewardUuid: reward.uuid, poolId: reward.poolId });
+//     const subs = claims.filter((c) => c.sub).map(({ sub }) => sub);
+//     const accounts = await AccountProxy.find({ subs });
+
+//     return claims
+//         .map((claim) => {
+//             return { ...claim.toJSON(), account: accounts.find((a) => claim.sub === a.sub) };
+//         })
+//         .sort((a, b) => {
+//             if (!a.sub && !b.sub) return 0; // Both are undefined, no change in order
+//             if (!a.sub) return -1; // a.sub is undefined, move it to the bottom
+//             if (!b.sub) return 1; // b.sub is undefined, move it to the bottom
+//             return a.sub.localeCompare(b.sub); // Sort by sub values
+//         });
+// }
+
+// export default { create, findByUuid, findByPool, findByPerk };
diff --git a/apps/api/src/app/services/ContractService.ts b/apps/api/src/app/services/ContractService.ts
new file mode 100644
index 000000000..bc85cfe03
--- /dev/null
+++ b/apps/api/src/app/services/ContractService.ts
@@ -0,0 +1,26 @@
+import { ChainId } from '@thxnetwork/common/enums';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { SafeVersion } from '@safe-global/safe-core-sdk-types';
+import { TContractName, getArtifact } from '@thxnetwork/api/hardhat';
+import { ethers } from 'ethers';
+
+export const safeVersion: SafeVersion = '1.3.0';
+
+export default class ContractService {
+    static getChainId() {
+        return process.env.NODE_ENV !== 'production' ? ChainId.Hardhat : ChainId.Polygon;
+    }
+
+    static getContract(contractName: TContractName, chainId: ChainId, address: string) {
+        const { signer } = getProvider(chainId);
+        const artifact = getArtifact(contractName);
+        return new ethers.Contract(address, artifact.abi, signer);
+    }
+
+    static async deploy(contractName: TContractName, args: any[], signer: ethers.Signer): Promise<ethers.Contract> {
+        const { abi, bytecode } = getArtifact(contractName);
+        const factory = new ethers.ContractFactory(abi, bytecode, signer);
+        const tx = await factory.deploy(...args);
+        return new ethers.Contract(await tx.getAddress(), abi, signer);
+    }
+}
diff --git a/apps/api/src/app/services/DiscordService.ts b/apps/api/src/app/services/DiscordService.ts
new file mode 100644
index 000000000..5a1e270e7
--- /dev/null
+++ b/apps/api/src/app/services/DiscordService.ts
@@ -0,0 +1,57 @@
+import { client } from '../../discord';
+import { DiscordGuild, DiscordMessage, DiscordReaction } from '../models';
+import { DiscordUser } from '../models/DiscordUser';
+import { logger } from '../util/logger';
+
+export default class DiscordService {
+    static async getGuild(poolId: string) {
+        const discordGuild = await DiscordGuild.findOne({ poolId });
+        if (!discordGuild) return;
+        try {
+            // Might fail if bot is removed from the guild
+            return await client.guilds.fetch(discordGuild.guildId);
+        } catch (error) {
+            logger.error(error);
+        }
+    }
+
+    static async getMember(guildId: string, userId: string) {
+        try {
+            // Might fail if bot is removed from the guild
+            return await client.guilds.fetch(guildId).then((guild) => guild.members.fetch(userId));
+        } catch (error) {
+            logger.error(error);
+        }
+    }
+
+    static async getRole(guildId: string, roleId: string) {
+        try {
+            return await client.guilds.fetch(guildId).then((guild) => guild.roles.fetch(roleId));
+        } catch (error) {
+            logger.error(error);
+        }
+    }
+
+    static async getUserMetrics(poolId: string, userId: string) {
+        const guild = await this.getGuild(poolId);
+        if (!guild) return;
+
+        const member = await this.getMember(guild.id, userId);
+        if (!member) return;
+
+        const profileImgUrl = member.user.displayAvatarURL({ forceStatic: true });
+        const query = { guildId: guild.id, userId };
+
+        return await DiscordUser.create({
+            userId,
+            guildId: guild.id,
+            profileImgUrl,
+            username: member.user.username,
+            publicMetrics: {
+                joinedAt: new Date(member.joinedTimestamp).toISOString(),
+                reactionCount: guild ? await DiscordReaction.countDocuments(query) : 0,
+                messageCount: guild ? await DiscordMessage.countDocuments(query) : 0,
+            },
+        });
+    }
+}
diff --git a/apps/api/src/app/services/ERC1155Service.ts b/apps/api/src/app/services/ERC1155Service.ts
new file mode 100644
index 000000000..3d9be0081
--- /dev/null
+++ b/apps/api/src/app/services/ERC1155Service.ts
@@ -0,0 +1,348 @@
+import { keccak256, toUtf8Bytes } from 'ethers/lib/utils';
+import { TransactionReceipt } from 'web3-core';
+import { ChainId, TransactionState, ERC1155TokenState } from '@thxnetwork/common/enums';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { paginatedResults } from '@thxnetwork/api/util/pagination';
+import { assertEvent, ExpectedEventNotFound, findEvent, parseLogs } from '@thxnetwork/api/util/events';
+import { API_URL, VERSION } from '../config/secrets';
+import TransactionService from './TransactionService';
+import PoolService from './PoolService';
+import IPFSService from './IPFSService';
+import SafeService from './SafeService';
+import {
+    Transaction,
+    ERC1155Document,
+    ERC1155,
+    PoolDocument,
+    RewardNFT,
+    ERC1155MetadataDocument,
+    ERC1155Metadata,
+    WalletDocument,
+    ERC1155TokenDocument,
+    ERC1155Token,
+} from '@thxnetwork/api/models';
+import { getArtifact } from '../hardhat';
+
+const contractName = 'THXERC1155';
+
+async function deploy(data: TERC1155, forceSync = true): Promise<ERC1155Document> {
+    const { web3, defaultAccount } = getProvider(data.chainId);
+    const { abi, bytecode } = getArtifact(contractName);
+    const contract = new web3.eth.Contract(abi);
+    const erc1155 = await ERC1155.create(data);
+    const baseURL = getBaseURL(erc1155);
+    const fn = contract.deploy({
+        data: bytecode,
+        arguments: [baseURL, defaultAccount],
+    });
+
+    const txId = await TransactionService.sendAsync(null, fn, erc1155.chainId, forceSync, {
+        type: 'ERC1155DeployCallback',
+        args: { erc1155Id: String(erc1155._id) },
+    });
+
+    return ERC1155.findByIdAndUpdate(erc1155._id, { transactions: [txId], baseURL }, { new: true });
+}
+
+async function deployCallback({ erc1155Id }: TERC1155DeployCallbackArgs, receipt: TransactionReceipt) {
+    const { abi } = getArtifact(contractName);
+    const events = parseLogs(abi, receipt.logs);
+
+    if (!findEvent('OwnershipTransferred', events) && !findEvent('Transfer', events)) {
+        throw new ExpectedEventNotFound('Transfer or OwnershipTransferred');
+    }
+
+    await ERC1155.findByIdAndUpdate(erc1155Id, { address: receipt.contractAddress });
+}
+
+export async function queryDeployTransaction(erc1155: ERC1155Document): Promise<ERC1155Document> {
+    if (!erc1155.address && erc1155.transactions[0]) {
+        const tx = await Transaction.findById(erc1155.transactions[0]);
+        const txResult = await TransactionService.queryTransactionStatusReceipt(tx);
+        if (txResult === TransactionState.Mined) {
+            erc1155 = await findById(erc1155._id);
+        }
+    }
+
+    return erc1155;
+}
+
+function getBaseURL(erc1155: ERC1155Document) {
+    return `${API_URL}/${VERSION}/metadata/erc1155/${String(erc1155._id)}/{id}`;
+}
+
+const initialize = async (pool: PoolDocument, address: string) => {
+    const erc1155 = await findByQuery({ address, chainId: pool.chainId });
+    await addMinter(erc1155, pool.safeAddress);
+};
+
+export async function findById(id: string): Promise<ERC1155Document> {
+    const erc1155 = await ERC1155.findById(id);
+    if (!erc1155) return;
+    erc1155.logoImgUrl || erc1155.logoImgUrl || `https://api.dicebear.com/7.x/identicon/svg?seed=${erc1155.address}`;
+    return erc1155;
+}
+
+export async function findBySub(sub: string): Promise<ERC1155Document[]> {
+    const pools = await PoolService.getAllBySub(sub);
+    const nftRewards = await RewardNFT.find({ poolId: pools.map((p) => String(p._id)) });
+    const erc1155Ids = nftRewards.map((c) => c.erc1155Id);
+    const erc1155s = await ERC1155.find({ sub });
+
+    return erc1155s.concat(await ERC1155.find({ _id: erc1155Ids }));
+}
+
+export async function createMetadata(erc1155: ERC1155Document, attributes: any): Promise<ERC1155MetadataDocument> {
+    return ERC1155Metadata.create({
+        erc1155: String(erc1155._id),
+        attributes,
+    });
+}
+
+export async function deleteMetadata(id: string) {
+    return ERC1155Metadata.findOneAndDelete({ _id: id });
+}
+
+export async function mint(
+    safe: WalletDocument,
+    erc1155: ERC1155Document,
+    wallet: WalletDocument,
+    metadata: ERC1155MetadataDocument,
+    amount: string,
+): Promise<ERC1155TokenDocument> {
+    const tokenUri = await IPFSService.getTokenURI(erc1155, String(metadata._id), String(metadata.tokenId));
+    const erc1155token = await ERC1155Token.findOneAndUpdate(
+        {
+            erc1155Id: String(erc1155._id),
+            tokenId: metadata.tokenId,
+            sub: wallet.sub,
+            walletId: String(wallet._id),
+        },
+        {
+            sub: wallet.sub,
+            tokenUri: erc1155.baseURL.replace('{id}', tokenUri),
+            recipient: wallet.address,
+            state: ERC1155TokenState.Pending,
+            erc1155Id: String(erc1155._id),
+            metadataId: String(metadata._id),
+            walletId: String(wallet._id),
+            tokenId: metadata.tokenId,
+        },
+        { upsert: true, new: true },
+    );
+
+    const tx = await TransactionService.sendSafeAsync(
+        safe,
+        erc1155.address,
+        erc1155.contract.methods.mint(wallet.address, metadata.tokenId, amount, '0x'),
+        {
+            type: 'erc1155TokenMintCallback',
+            args: { erc1155tokenId: String(erc1155token._id) },
+        },
+    );
+
+    return await ERC1155Token.findByIdAndUpdate(
+        erc1155token._id,
+        { transactions: [tx._id], state: ERC1155TokenState.Transferring },
+        { new: true },
+    );
+}
+
+export async function mintCallback(args: TERC1155TokenMintCallbackArgs, receipt: TransactionReceipt) {
+    const { erc1155tokenId } = args;
+    const { abi } = getArtifact(contractName);
+    const events = parseLogs(abi, receipt.logs);
+    const event = assertEvent('TransferSingle', events);
+
+    await ERC1155Token.findByIdAndUpdate(erc1155tokenId, {
+        state: ERC1155TokenState.Minted,
+        tokenId: event.args.id,
+        recipient: event.args.recipient,
+    });
+}
+
+export async function queryMintTransaction(erc1155Token: ERC1155TokenDocument): Promise<ERC1155TokenDocument> {
+    if (erc1155Token.state === ERC1155TokenState.Pending && erc1155Token.transactions[0]) {
+        const tx = await Transaction.findById(erc1155Token.transactions[0]);
+        const txResult = await TransactionService.queryTransactionStatusReceipt(tx);
+        if (txResult === TransactionState.Mined) {
+            erc1155Token = await findTokenById(erc1155Token._id);
+        }
+    }
+    return erc1155Token;
+}
+
+export async function transferFrom(
+    erc1155: ERC1155Document,
+    wallet: WalletDocument,
+    to: string,
+    erc1155Token: ERC1155TokenDocument,
+    amount: string,
+): Promise<ERC1155TokenDocument> {
+    const toWallet = await SafeService.findOne({ address: to, chainId: erc1155.chainId });
+    const tx = await TransactionService.sendSafeAsync(
+        wallet,
+        erc1155.address,
+        erc1155.contract.methods.safeTransferFrom(wallet.address, to, erc1155Token.tokenId, amount, '0x'),
+        {
+            type: 'erc1155TransferFromCallback',
+            args: {
+                erc1155Id: String(erc1155._id),
+                erc1155TokenId: String(erc1155Token._id),
+                walletId: toWallet && toWallet.id,
+            },
+        },
+    );
+    const metadata = await ERC1155Metadata.findById(erc1155Token.metadataId);
+
+    await ERC1155Token.findOneAndUpdate(
+        {
+            erc1155Id: String(erc1155._id),
+            tokenId: metadata.tokenId,
+            sub: wallet.sub,
+            walletId: String(wallet._id),
+        },
+        {
+            sub: wallet.sub,
+            tokenUri: erc1155.baseURL.replace('{id}', erc1155Token.tokenUri),
+            recipient: wallet.address,
+            state: ERC1155TokenState.Pending,
+            erc1155Id: String(erc1155._id),
+            metadataId: String(metadata._id),
+            walletId: String(wallet._id),
+            tokenId: metadata.tokenId,
+        },
+        { upsert: true, new: true },
+    );
+
+    return await ERC1155Token.findByIdAndUpdate(
+        erc1155Token._id,
+        { transactions: [String(tx._id)], state: ERC1155TokenState.Transferring },
+        { new: true },
+    );
+}
+
+export async function transferFromCallback(args: TERC1155TransferFromCallbackArgs, receipt: TransactionReceipt) {
+    const { erc1155TokenId, walletId } = args;
+    const { abi } = getArtifact(contractName);
+    const events = parseLogs(abi, receipt.logs);
+    const event = assertEvent('TransferSingle', events);
+    const wallet = await SafeService.findById(walletId);
+
+    await ERC1155Token.findByIdAndUpdate(erc1155TokenId, {
+        state: ERC1155TokenState.Transferred,
+        tokenId: event.args.id,
+        recipient: event.args.to,
+        sub: wallet && wallet.sub,
+        walletId: wallet && wallet.id,
+    });
+}
+
+async function isMinter(erc1155: ERC1155Document, address: string) {
+    return await erc1155.contract.methods.hasRole(keccak256(toUtf8Bytes('MINTER_ROLE')), address).call();
+}
+
+async function addMinter(erc1155: ERC1155Document, address: string) {
+    const receipt = await TransactionService.send(
+        erc1155.address,
+        erc1155.contract.methods.grantRole(keccak256(toUtf8Bytes('MINTER_ROLE')), address),
+        erc1155.chainId,
+    );
+
+    assertEvent('RoleGranted', parseLogs(erc1155.contract.options.jsonInterface, receipt.logs));
+}
+
+async function findMetadataByToken(token: TERC1155Token) {
+    return ERC1155Metadata.findById(token.metadataId);
+}
+
+async function findTokenById(id: string): Promise<ERC1155TokenDocument> {
+    return ERC1155Token.findById(id);
+}
+
+async function findTokensByMetadataAndSub(metadataId: string, account: TAccount): Promise<ERC1155TokenDocument[]> {
+    return ERC1155Token.find({ sub: account.sub, metadataId });
+}
+
+async function findTokensBySub(sub: string): Promise<ERC1155TokenDocument[]> {
+    return ERC1155Token.find({ sub });
+}
+
+async function findTokensByWallet(wallet: WalletDocument): Promise<ERC1155TokenDocument[]> {
+    return ERC1155Token.find({ walletId: wallet._id });
+}
+
+async function findMetadataById(id: string) {
+    return ERC1155Metadata.findById(id);
+}
+
+async function findTokensByRecipient(recipient: string, erc1155Id: string): Promise<TERC1155Token[]> {
+    const result = [];
+    for await (const token of ERC1155Token.find({ recipient, erc1155Id })) {
+        const metadata = await ERC1155Metadata.findById(token.metadataId);
+        result.push({ ...(token.toJSON() as TERC1155Token), metadata });
+    }
+    return result;
+}
+
+async function findTokensByMetadata(metadata: ERC1155MetadataDocument): Promise<TERC1155Token[]> {
+    return ERC1155Token.find({ metadataId: String(metadata._id) });
+}
+
+async function findMetadataByNFT(erc1155Id: string, page = 1, limit = 10) {
+    const paginatedResult = await paginatedResults(ERC1155Metadata, page, limit, { erc1155Id });
+    const results: TERC1155Metadata[] = [];
+    for (const metadata of paginatedResult.results) {
+        const tokens = (await this.findTokensByMetadata(metadata)).map((m: ERC1155MetadataDocument) => m.toJSON());
+        results.push({ ...metadata.toJSON(), tokens });
+    }
+    paginatedResult.results = results;
+    return paginatedResult;
+}
+
+async function findByQuery(query: { poolAddress?: string; address?: string; chainId?: ChainId }) {
+    return ERC1155.findOne(query);
+}
+
+export const update = (erc1155: ERC1155Document, updates: Partial<TERC1155>) => {
+    return ERC1155.findByIdAndUpdate(erc1155._id, updates, { new: true });
+};
+
+export const getOnChainERC1155Token = async (chainId: number, address: string) => {
+    const { web3 } = getProvider(chainId);
+    const { abi } = getArtifact(contractName);
+    const contract = new web3.eth.Contract(abi, address);
+    const uri = await contract.methods.uri(1).call();
+
+    return { uri };
+};
+
+export default {
+    deploy,
+    deployCallback,
+    findById,
+    createMetadata,
+    deleteMetadata,
+    mint,
+    mintCallback,
+    queryMintTransaction,
+    findBySub,
+    findTokenById,
+    findTokensByMetadataAndSub,
+    findTokensByMetadata,
+    findTokensBySub,
+    findMetadataById,
+    findMetadataByNFT,
+    findTokensByRecipient,
+    findByQuery,
+    addMinter,
+    isMinter,
+    update,
+    initialize,
+    transferFrom,
+    transferFromCallback,
+    queryDeployTransaction,
+    getOnChainERC1155Token,
+    findTokensByWallet,
+    findMetadataByToken,
+};
diff --git a/apps/api/src/app/services/ERC20Service.ts b/apps/api/src/app/services/ERC20Service.ts
new file mode 100644
index 000000000..d21afb21d
--- /dev/null
+++ b/apps/api/src/app/services/ERC20Service.ts
@@ -0,0 +1,341 @@
+import { toChecksumAddress } from 'web3-utils';
+import { assertEvent, ExpectedEventNotFound, findEvent, parseLogs } from '@thxnetwork/api/util/events';
+import { ChainId, ERC20Type, TransactionState } from '@thxnetwork/common/enums';
+import { keccak256, toUtf8Bytes } from 'ethers/lib/utils';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { TransactionReceipt } from 'web3-core';
+import { contractNetworks, getArtifact } from '@thxnetwork/api/hardhat';
+import {
+    ERC20,
+    ERC20Document,
+    ERC20Token,
+    ERC20TokenDocument,
+    ERC20Transfer,
+    PoolDocument,
+    RewardCoin,
+    Transaction,
+    Wallet,
+    WalletDocument,
+} from '@thxnetwork/api/models';
+import TransactionService from './TransactionService';
+import PoolService from './PoolService';
+import { fromWei } from 'web3-utils';
+
+async function decorate(token: ERC20TokenDocument, wallet: WalletDocument) {
+    const erc20 = await getById(token.erc20Id);
+    if (!erc20 || erc20.chainId !== wallet.chainId) return;
+
+    const walletBalanceInWei = await erc20.contract.methods.balanceOf(wallet.address).call();
+    const walletBalance = fromWei(walletBalanceInWei, 'ether');
+
+    return Object.assign(token.toJSON() as TERC20Token, {
+        walletBalance,
+        erc20,
+    });
+}
+
+function getDeployArgs(erc20: ERC20Document, totalSupply?: string) {
+    const { defaultAccount } = getProvider(erc20.chainId);
+
+    switch (erc20.type) {
+        case ERC20Type.Limited: {
+            return [erc20.name, erc20.symbol, defaultAccount, totalSupply];
+        }
+        case ERC20Type.Unlimited: {
+            return [erc20.name, erc20.symbol, defaultAccount];
+        }
+    }
+}
+
+export async function findBySub(sub: string) {
+    const pools = await PoolService.getAllBySub(sub);
+    const coinRewards = await RewardCoin.find({ poolId: pools.map((p) => String(p._id)) });
+    const erc20Ids = coinRewards.map((c) => c.erc20Id);
+    const erc20s = await ERC20.find({ sub });
+
+    return erc20s.concat(await ERC20.find({ _id: erc20Ids }));
+}
+
+export const deploy = async (params: Partial<TERC20>, forceSync = true) => {
+    const erc20 = await ERC20.create({
+        name: params.name,
+        symbol: params.symbol,
+        chainId: params.chainId,
+        type: params.type,
+        sub: params.sub,
+        logoImgUrl: params.logoImgUrl,
+    });
+    const { web3 } = getProvider(erc20.chainId);
+    const { abi, bytecode } = getArtifact(erc20.contractName);
+    const contract = new web3.eth.Contract(abi);
+    const fn = contract.deploy({
+        data: bytecode,
+        arguments: getDeployArgs(erc20, String(params.totalSupply)),
+    });
+
+    const txId = await TransactionService.sendAsync(null, fn, erc20.chainId, forceSync, {
+        type: 'Erc20DeployCallback',
+        args: { erc20Id: String(erc20._id) },
+    });
+
+    return ERC20.findByIdAndUpdate(erc20._id, { transactions: [txId] }, { new: true });
+};
+
+export async function deployCallback({ erc20Id }: TERC20DeployCallbackArgs, receipt: TransactionReceipt) {
+    const erc20 = await ERC20.findById(erc20Id);
+    const { abi } = getArtifact(erc20.contractName);
+    const events = parseLogs(abi, receipt.logs);
+
+    // Limited and unlimited tokes emit different events. Check if one of the two is emitted.
+    if (!findEvent('OwnershipTransferred', events) && !findEvent('Transfer', events)) {
+        throw new ExpectedEventNotFound('Transfer or OwnershipTransferred');
+    }
+
+    await ERC20.findByIdAndUpdate(erc20Id, {
+        address: receipt.contractAddress,
+    });
+}
+
+export async function queryDeployTransaction(erc20: ERC20Document): Promise<ERC20Document> {
+    if (!erc20.address && erc20.transactions[0]) {
+        const tx = await Transaction.findById(erc20.transactions[0]);
+        const txResult = await TransactionService.queryTransactionStatusReceipt(tx);
+        if (txResult === TransactionState.Mined) {
+            erc20 = await getById(erc20._id);
+        }
+    }
+
+    return erc20;
+}
+
+const initialize = async (pool: PoolDocument, erc20: ERC20Document) => {
+    if (erc20 && erc20.type === ERC20Type.Unlimited) {
+        await addMinter(erc20, pool.safeAddress);
+    }
+};
+
+const addMinter = async (erc20: ERC20Document, address: string) => {
+    const receipt = await TransactionService.send(
+        erc20.address,
+        erc20.contract.methods.grantRole(keccak256(toUtf8Bytes('MINTER_ROLE')), address),
+        erc20.chainId,
+    );
+
+    assertEvent('RoleGranted', parseLogs(erc20.contract.options.jsonInterface, receipt.logs));
+};
+
+const addToken = async (wallet: WalletDocument, erc20: ERC20Document) => {
+    const query = { sub: wallet.sub, walletId: wallet.id, erc20Id: erc20._id };
+    if (!(await ERC20Token.exists(query))) {
+        await createERC20Token(erc20, wallet);
+    }
+};
+
+export const getAll = (sub: string) => {
+    return ERC20.find({ sub });
+};
+
+export const getTokensForSub = (sub: string) => {
+    return ERC20Token.find({ sub });
+};
+
+export const getTokensForWallet = async (wallet: WalletDocument) => {
+    const tokens = await ERC20Token.find({ walletId: wallet.id });
+
+    const result = [];
+    for (const token of tokens) {
+        try {
+            const decorated = await decorate(token, wallet);
+            result.push(decorated);
+        } catch (error) {
+            console.log(error);
+        }
+    }
+
+    const defaultTokens = (await findDefaultTokens(wallet)).filter(({ walletBalance }) => walletBalance > 0);
+
+    return result.concat(defaultTokens);
+};
+
+export const getById = async (id: string) => {
+    const erc20 = await ERC20.findById(id);
+    if (!erc20) return;
+
+    erc20.logoImgUrl = erc20.logoImgUrl || `https://api.dicebear.com/7.x/identicon/svg?seed=${erc20.address}`;
+    return erc20;
+};
+
+export const getTokenById = (id: string) => {
+    return ERC20Token.findById(id);
+};
+
+export const findBy = (query: { address: string; chainId: ChainId; sub?: string }) => {
+    return ERC20.findOne(query);
+};
+
+export const addTokenForWallet = async (erc20: ERC20Document, wallet: WalletDocument) => {
+    const hasToken = await ERC20Token.exists({
+        sub: wallet.sub,
+        walletId: wallet.id,
+        erc20Id: erc20.id,
+    });
+
+    if (!hasToken) {
+        await createERC20Token(erc20, wallet);
+    }
+};
+
+export const importToken = async (chainId: number, address: string, sub: string, logoImgUrl: string) => {
+    const { web3 } = getProvider(chainId);
+    const { abi } = getArtifact('THXERC20_LimitedSupply');
+    const contract = new web3.eth.Contract(abi);
+    const [name, symbol] = await Promise.all([contract.methods.name().call(), contract.methods.symbol().call()]);
+    const erc20 = await ERC20.create({
+        name,
+        symbol,
+        address: toChecksumAddress(address),
+        chainId,
+        type: ERC20Type.Unknown,
+        sub,
+        logoImgUrl,
+    });
+
+    const wallets = await Wallet.find({ sub });
+    for (const wallet of wallets) {
+        await addTokenForWallet(erc20, wallet);
+    }
+
+    return erc20;
+};
+
+export const update = (erc20: ERC20Document, updates: Partial<TERC20>) => {
+    return ERC20.findByIdAndUpdate(erc20._id, updates, { new: true });
+};
+
+export const approve = async (erc20: ERC20Document, wallet: WalletDocument, amountInWei: string) => {
+    return await TransactionService.sendSafeAsync(
+        wallet,
+        erc20.address,
+        erc20.contract.methods.approve(wallet.address, amountInWei),
+    );
+};
+
+export const transferFrom = async (erc20: ERC20Document, wallet: WalletDocument, to: string, amountInWei: string) => {
+    const erc20Transfer = await ERC20Transfer.create({
+        erc20Id: erc20._id,
+        from: wallet.address,
+        to,
+        amount: amountInWei,
+        chainId: wallet.chainId,
+        sub: wallet.sub,
+    });
+
+    // Check if an erc20Token exists for a known receiving wallet and create one if not
+    const toWallet = await Wallet.findOne({ chainId: wallet.chainId, address: toChecksumAddress(to) });
+    if (toWallet && !(await ERC20Token.exists({ walletId: toWallet._id, erc20Id: erc20._id }))) {
+        await createERC20Token(erc20, toWallet);
+    }
+
+    const tx = await TransactionService.sendSafeAsync(
+        wallet,
+        erc20.address,
+        erc20.contract.methods.transfer(to, amountInWei),
+        { type: 'transferFromCallBack', args: { erc20Id: String(erc20._id) } },
+    );
+
+    await erc20Transfer.updateOne({ transactionId: String(tx._id) });
+
+    return tx;
+};
+
+export const transferFromCallBack = async (args: TERC20TransferFromCallBackArgs, receipt: TransactionReceipt) => {
+    const erc20 = await ERC20.findById(args.erc20Id);
+    const events = parseLogs(erc20.contract.options.jsonInterface, receipt.logs);
+
+    assertEvent('ERC20ProxyTransferFrom', events);
+};
+
+async function isMinter(erc20: ERC20Document, address: string) {
+    return await erc20.contract.methods.hasRole(keccak256(toUtf8Bytes('MINTER_ROLE')), address).call();
+}
+
+async function createERC20Token(erc20: ERC20Document, wallet: WalletDocument) {
+    await ERC20Token.create({
+        sub: wallet.sub,
+        walletId: wallet.id,
+        erc20Id: erc20.id,
+    });
+}
+
+async function findDefaultTokens(wallet: WalletDocument) {
+    const defaultContracts = [
+        {
+            type: ERC20Type.Unknown,
+            name: '20USDC-80THX',
+            symbol: '20USDC-80THX',
+            decimals: 18,
+            chainId: wallet.chainId,
+            address: contractNetworks[wallet.chainId].BPT,
+            logoImgUrl: 'https://assets.coingecko.com/coins/images/21323/standard/logo-thx-resized-200-200.png',
+        },
+        {
+            type: ERC20Type.Unknown,
+            name: '20USDC-80THX (staked)',
+            symbol: '20USDC-80THX-gauge',
+            decimals: 18,
+            chainId: wallet.chainId,
+            address: contractNetworks[wallet.chainId].BPTGauge,
+            logoImgUrl: 'https://assets.coingecko.com/coins/images/21323/standard/logo-thx-resized-200-200.png',
+        },
+        {
+            type: ERC20Type.Unknown,
+            name: 'Voting Escrow 20USDC-80THX-gauge',
+            symbol: 'veTHX',
+            decimals: 18,
+            chainId: wallet.chainId,
+            address: contractNetworks[wallet.chainId].VotingEscrow,
+            logoImgUrl: 'https://assets.coingecko.com/coins/images/21323/standard/logo-thx-resized-200-200.png',
+        },
+    ];
+
+    const promises = defaultContracts.map(async (erc20) => {
+        const { web3 } = getProvider(erc20.chainId);
+        const { abi } = getArtifact('THXERC20_LimitedSupply');
+        const contract = new web3.eth.Contract(abi, erc20.address);
+        const walletBalanceInWei = await contract.methods.balanceOf(wallet.address).call();
+        const walletBalance = Number(fromWei(walletBalanceInWei));
+        return {
+            sub: wallet.sub,
+            erc20Id: '',
+            walletId: wallet.id,
+            walletBalance,
+            erc20,
+        };
+    });
+
+    return await Promise.all(promises);
+}
+
+export default {
+    findDefaultTokens,
+    decorate,
+    findBySub,
+    createERC20Token,
+    deploy,
+    getAll,
+    findBy,
+    getById,
+    addToken,
+    addMinter,
+    isMinter,
+    importToken,
+    getTokensForSub,
+    getTokenById,
+    update,
+    initialize,
+    queryDeployTransaction,
+    transferFrom,
+    transferFromCallBack,
+    getTokensForWallet,
+    approve,
+};
diff --git a/apps/api/src/app/services/ERC721Service.ts b/apps/api/src/app/services/ERC721Service.ts
new file mode 100644
index 000000000..21a529a29
--- /dev/null
+++ b/apps/api/src/app/services/ERC721Service.ts
@@ -0,0 +1,289 @@
+import { keccak256, toUtf8Bytes } from 'ethers/lib/utils';
+import { TransactionReceipt } from 'web3-core';
+import { ERC721, ERC721Document } from '@thxnetwork/api/models/ERC721';
+import { ERC721Metadata, ERC721MetadataDocument } from '@thxnetwork/api/models/ERC721Metadata';
+import { ERC721Token, ERC721TokenDocument } from '@thxnetwork/api/models/ERC721Token';
+import { Transaction } from '@thxnetwork/api/models/Transaction';
+import { ERC721TokenState, TransactionState } from '@thxnetwork/common/enums';
+import { assertEvent, ExpectedEventNotFound, findEvent, parseLogs } from '@thxnetwork/api/util/events';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { paginatedResults } from '@thxnetwork/api/util/pagination';
+import { WalletDocument } from '../models/Wallet';
+import { RewardNFT } from '../models/RewardNFT';
+import { getArtifact } from '../hardhat';
+import PoolService from './PoolService';
+import TransactionService from './TransactionService';
+import IPFSService from './IPFSService';
+import WalletService from './WalletService';
+
+const contractName = 'THXERC721';
+
+async function deploy(data: TERC721, forceSync = true): Promise<ERC721Document> {
+    const { web3, defaultAccount } = getProvider(data.chainId);
+    const { abi, bytecode } = getArtifact(contractName);
+    const contract = new web3.eth.Contract(abi);
+    const erc721 = await ERC721.create(data);
+    const fn = contract.deploy({
+        data: bytecode,
+        arguments: [erc721.name, erc721.symbol, erc721.baseURL, defaultAccount],
+    });
+    const txId = await TransactionService.sendAsync(null, fn, erc721.chainId, forceSync, {
+        type: 'Erc721DeployCallback',
+        args: { erc721Id: String(erc721._id) },
+    });
+
+    return await ERC721.findByIdAndUpdate(erc721._id, { transactions: [txId] }, { new: true });
+}
+
+async function deployCallback({ erc721Id }: TERC721DeployCallbackArgs, receipt: TransactionReceipt) {
+    const { abi } = getArtifact(contractName);
+    const events = parseLogs(abi, receipt.logs);
+
+    if (!findEvent('OwnershipTransferred', events) && !findEvent('Transfer', events)) {
+        throw new ExpectedEventNotFound('Transfer or OwnershipTransferred');
+    }
+
+    await ERC721.findByIdAndUpdate(erc721Id, { address: receipt.contractAddress });
+}
+
+export async function queryDeployTransaction(erc721: ERC721Document): Promise<ERC721Document> {
+    if (!erc721.address && erc721.transactions[0]) {
+        const tx = await Transaction.findById(erc721.transactions[0]);
+        const txResult = await TransactionService.queryTransactionStatusReceipt(tx);
+        if (txResult === TransactionState.Mined) {
+            erc721 = await findById(erc721._id);
+        }
+    }
+
+    return erc721;
+}
+
+export async function findById(id: string): Promise<ERC721Document> {
+    const erc721 = await ERC721.findById(id);
+    if (!erc721) return;
+    erc721.logoImgUrl = erc721.logoImgUrl || `https://api.dicebear.com/7.x/identicon/svg?seed=${erc721.address}`;
+    return erc721;
+}
+
+export async function findBySub(sub: string): Promise<ERC721Document[]> {
+    const pools = await PoolService.getAllBySub(sub);
+    const nftRewards = await RewardNFT.find({ poolId: pools.map((p) => String(p._id)) });
+    const erc721Ids = nftRewards.map((c) => c.erc721Id);
+    const erc721s = await ERC721.find({ sub });
+
+    return erc721s.concat(await ERC721.find({ _id: erc721Ids }));
+}
+
+export async function deleteMetadata(id: string) {
+    return ERC721Metadata.findOneAndDelete({ _id: id });
+}
+
+export async function mint(
+    safe: WalletDocument,
+    erc721: ERC721Document,
+    wallet: WalletDocument,
+    metadata: ERC721MetadataDocument,
+): Promise<ERC721TokenDocument> {
+    const tokenUri = await IPFSService.getTokenURI(erc721, String(metadata._id));
+    const erc721token = await ERC721Token.create({
+        sub: wallet.sub,
+        tokenUri: erc721.baseURL + tokenUri,
+        recipient: wallet.address,
+        state: ERC721TokenState.Pending,
+        erc721Id: String(erc721._id),
+        metadataId: String(metadata._id),
+        walletId: wallet._id,
+    });
+
+    const tx = await TransactionService.sendSafeAsync(
+        safe,
+        erc721.address,
+        erc721.contract.methods.mint(wallet.address, tokenUri),
+        {
+            type: 'erc721TokenMintCallback',
+            args: { erc721tokenId: String(erc721token._id) },
+        },
+    );
+
+    return await ERC721Token.findByIdAndUpdate(
+        erc721token._id,
+        { transactions: [String(tx._id)], state: ERC721TokenState.Transferring },
+        { new: true },
+    );
+}
+
+export async function mintCallback(args: TERC721TokenMintCallbackArgs, receipt: TransactionReceipt) {
+    const token = await ERC721Token.findById(args.erc721tokenId);
+    const { contract } = await ERC721.findById(token.erc721Id);
+    const events = parseLogs(contract.options.jsonInterface, receipt.logs);
+    const event = assertEvent('Transfer', events);
+
+    await token.updateOne({
+        state: ERC721TokenState.Minted,
+        tokenId: Number(event.args.tokenId),
+        recipient: event.args.to,
+    });
+}
+
+export async function queryMintTransaction(erc721Token: ERC721TokenDocument): Promise<ERC721TokenDocument> {
+    if (erc721Token.state === ERC721TokenState.Pending && erc721Token.transactions[0]) {
+        const tx = await Transaction.findById(erc721Token.transactions[0]);
+        const txResult = await TransactionService.queryTransactionStatusReceipt(tx);
+        if (txResult === TransactionState.Mined) {
+            erc721Token = await ERC721Token.findById(erc721Token._id);
+        }
+    }
+
+    return erc721Token;
+}
+
+export function parseAttributes(entry: ERC721MetadataDocument) {
+    return {
+        name: entry.name,
+        description: entry.description,
+        image: entry.image,
+        external_url: entry.externalUrl,
+    };
+}
+
+async function isMinter(erc721: ERC721Document, address: string) {
+    return await erc721.contract.methods.hasRole(keccak256(toUtf8Bytes('MINTER_ROLE')), address).call();
+}
+
+async function addMinter(erc721: ERC721Document, address: string) {
+    const receipt = await TransactionService.send(
+        erc721.address,
+        erc721.contract.methods.grantRole(keccak256(toUtf8Bytes('MINTER_ROLE')), address),
+        erc721.chainId,
+    );
+
+    assertEvent('RoleGranted', parseLogs(erc721.contract.options.jsonInterface, receipt.logs));
+}
+
+async function findTokensByRecipient(recipient: string, erc721Id: string): Promise<TERC721Token[]> {
+    const result = [];
+    for await (const token of ERC721Token.find({ recipient, erc721Id })) {
+        const metadata = await ERC721Metadata.findById(token.metadataId);
+        result.push({ ...(token.toJSON() as TERC721Token), metadata });
+    }
+    return result;
+}
+
+async function findMetadataByToken(token: TERC721Token) {
+    return ERC721Metadata.findById(token.metadataId);
+}
+
+async function findTokenById(id: string) {
+    return await ERC721Token.findById(id);
+}
+
+async function findMetadataById(id: string) {
+    return await ERC721Metadata.findById(id);
+}
+
+async function findMetadataByNFT(erc721Id: string, page = 1, limit = 10) {
+    const paginatedResult = await paginatedResults(ERC721Metadata, page, limit, { erc721Id });
+    const results: TERC721Metadata[] = [];
+    for (const metadata of paginatedResult.results) {
+        const tokens = await ERC721Token.find({ erc721Id, metadataId: metadata._id });
+        results.push({ ...metadata.toJSON(), tokens });
+    }
+    paginatedResult.results = results;
+    return paginatedResult;
+}
+
+export const getOnChainERC721Token = async (chainId: number, address: string) => {
+    const { web3 } = getProvider(chainId);
+    const { abi } = getArtifact(contractName);
+    const contract = new web3.eth.Contract(abi, address);
+    const [name, symbol, totalSupply] = await Promise.all([
+        contract.methods.name().call(),
+        contract.methods.symbol().call(),
+        contract.methods.totalSupply().call(),
+    ]);
+
+    return { name, symbol, totalSupply };
+};
+
+export async function transferFrom(
+    erc721: ERC721Document,
+    wallet: WalletDocument,
+    to: string,
+    erc721Token: ERC721TokenDocument,
+): Promise<ERC721TokenDocument> {
+    const toWallet = await WalletService.findOne({ address: to, chainId: erc721.chainId });
+    const tx = await TransactionService.sendSafeAsync(
+        wallet,
+        erc721.address,
+        erc721.contract.methods.transferFrom(wallet.address, to, erc721Token.tokenId),
+        {
+            type: 'erc721nTransferFromCallback',
+            args: {
+                erc721Id: String(erc721._id),
+                erc721TokenId: String(erc721Token._id),
+                walletId: toWallet && toWallet._id,
+            },
+        },
+    );
+    return await ERC721Token.findByIdAndUpdate(
+        erc721Token._id,
+        {
+            transactions: [String(tx._id)],
+            state: ERC721TokenState.Transferring,
+        },
+        { new: true },
+    );
+}
+
+export async function transferFromCallback(args: TERC721TransferFromCallBackArgs, receipt: TransactionReceipt) {
+    const { erc721TokenId, walletId } = args;
+    const erc721Token = await ERC721Token.findById(erc721TokenId);
+    const erc721 = await ERC721.findById(erc721Token.erc721Id);
+    const events = parseLogs(erc721.contract.options.jsonInterface, receipt.logs);
+    const event = assertEvent('Transfer', events);
+    const wallet = await WalletService.findById(walletId);
+
+    await erc721Token.updateOne({
+        state: ERC721TokenState.Transferred,
+        tokenId: Number(event.args.tokenId),
+        recipient: event.args.to,
+        sub: wallet && wallet.sub,
+        walletId: wallet && String(wallet._id),
+    });
+}
+
+export async function queryTransferFromTransaction(erc721Token: ERC721TokenDocument): Promise<ERC721TokenDocument> {
+    if (erc721Token.state === ERC721TokenState.Transferring) {
+        const tx = await Transaction.findById(erc721Token.transactions[erc721Token.transactions.length - 1]);
+        const txResult = await TransactionService.queryTransactionStatusReceipt(tx);
+        if (txResult === TransactionState.Mined) {
+            erc721Token = await ERC721Token.findById(erc721Token._id);
+        }
+    }
+
+    return erc721Token;
+}
+
+export default {
+    deploy,
+    deployCallback,
+    findById,
+    deleteMetadata,
+    mint,
+    mintCallback,
+    queryMintTransaction,
+    findBySub,
+    findMetadataByNFT,
+    findTokensByRecipient,
+    addMinter,
+    isMinter,
+    parseAttributes,
+    queryDeployTransaction,
+    getOnChainERC721Token,
+    transferFrom,
+    transferFromCallback,
+    queryTransferFromTransaction,
+    findMetadataById,
+    findTokenById,
+    findMetadataByToken,
+};
diff --git a/apps/api/src/app/services/GalachainService.ts b/apps/api/src/app/services/GalachainService.ts
new file mode 100644
index 000000000..228f55a20
--- /dev/null
+++ b/apps/api/src/app/services/GalachainService.ts
@@ -0,0 +1,205 @@
+import axios from 'axios';
+import { instanceToPlain, plainToInstance } from 'class-transformer';
+import { BigNumber } from 'bignumber.js';
+import { logger } from '../util/logger';
+import { BadRequestError } from '../util/errors';
+import {
+    ChainCallDTO,
+    TokenInstance,
+    TokenClassKey,
+    TokenInstanceKey,
+    createValidDTO,
+    CreateTokenClassDto,
+    GalaChainResponse,
+    GetMyProfileDto,
+    MintTokenDto,
+    GrantAllowanceDto,
+    AllowanceType,
+    FetchBalancesDto,
+    TransferTokenDto,
+    RegisterUserDto,
+} from '@gala-chain/api';
+import { GalachainRole, getClient } from '../util/galachain';
+import { Wallet } from 'ethers';
+import { NODE_ENV } from '../config/secrets';
+
+const GALACHAIN_URL = 'https://gateway.stage.galachain.com/api';
+const identityKey = (address: string) => `eth|${address.replace(/^0x/, '')}`;
+
+export default class GalachainService {
+    static evaluateTransaction(
+        methodName: string,
+        contract: TGalachainContract,
+        dto: ChainCallDTO,
+        privateKey: string,
+    ) {
+        const methodMap = {
+            development: this.evaluateTransactionLocal.bind(this),
+            production: this.submitTransactonREST.bind(this),
+        };
+        return methodMap[NODE_ENV](methodName, contract, dto, privateKey);
+    }
+
+    static submitTransaction(methodName: string, contract: TGalachainContract, dto: ChainCallDTO, privateKey: string) {
+        const methodMap = {
+            development: this.submitTransactionLocal.bind(this),
+            production: this.submitTransactonREST.bind(this),
+        };
+        return methodMap[NODE_ENV](methodName, contract, dto, privateKey);
+    }
+
+    static async evaluateTransactionLocal(
+        methodName: string,
+        contract: TGalachainContract,
+        dto: ChainCallDTO,
+        privateKey: string,
+    ) {
+        const client = getClient(GalachainRole.Curator); // TODO Make this dynamic
+        const response = await client.forContract(contract).evaluateTransaction(methodName, dto.signed(privateKey));
+
+        if (GalaChainResponse.isError(response)) {
+            throw new Error(`${response.Message} (${response.ErrorKey})`);
+        } else {
+            return response.Data;
+        }
+    }
+
+    static async submitTransactionLocal(
+        methodName: string,
+        contract: TGalachainContract,
+        dto: ChainCallDTO,
+        privateKey: string,
+    ) {
+        const client = getClient(GalachainRole.Curator); // TODO Make this dynamic
+        const response = await client.forContract(contract).submitTransaction(methodName, dto.signed(privateKey));
+
+        if (GalaChainResponse.isError(response)) {
+            throw new BadRequestError(`${response.Message} (${response.ErrorKey})`);
+        } else {
+            return response.Data;
+        }
+    }
+
+    static async submitTransactonREST(
+        methodName: string,
+        contract: TGalachainContract,
+        dto: ChainCallDTO,
+        privateKey: string,
+    ) {
+        const signedDto = dto.signed(privateKey);
+        const url = new URL(GALACHAIN_URL);
+        url.pathname = `${url.pathname}/${contract.channelName}/${contract.chaincodeName}-${contract.contractName}/${methodName}`;
+
+        try {
+            const res = await axios({
+                method: 'POST',
+                url: url.toString(),
+                headers: {},
+                data: instanceToPlain(signedDto),
+            });
+            return res.data;
+        } catch (error) {
+            logger.error(error.response.data);
+            throw new BadRequestError(error.response.data.message);
+        }
+    }
+
+    static getProfile(contract: TGalachainContract, privateKey: string) {
+        const dto = new GetMyProfileDto().signed(privateKey, false);
+        return this.evaluateTransaction('GetMyProfile', contract, dto, privateKey);
+    }
+
+    static registerUser(contract: TGalachainContract, publicKey: string, privateKey: string) {
+        const dto = new RegisterUserDto();
+        dto.publicKey = publicKey;
+        dto.sign(privateKey, false);
+
+        return this.submitTransaction('RegisterEthUser', contract, dto, privateKey);
+    }
+
+    static async balanceOf(contract: TGalachainContract, tokenClassKey: TGalachainToken, privateKey: string) {
+        const tokenClass = plainToInstance(TokenInstanceKey, tokenClassKey);
+        const owner = new Wallet(privateKey).address;
+        const dto = await createValidDTO(FetchBalancesDto, {
+            owner: identityKey(owner),
+            ...tokenClass,
+        });
+        return this.evaluateTransaction('FetchBalances', contract, dto, privateKey);
+    }
+
+    static async create(
+        contract: TGalachainContract,
+        tokenInfo: {
+            image: string;
+            name: string;
+            description: string;
+            symbol: string;
+            decimals: number;
+            maxSupply: any;
+        },
+        tokenClassKey: TGalachainToken,
+        privateKey: string,
+    ) {
+        const tokenClass = plainToInstance(TokenClassKey, tokenClassKey);
+        const dto = await createValidDTO<CreateTokenClassDto>(CreateTokenClassDto, {
+            tokenClass,
+            ...tokenInfo,
+        });
+
+        return this.submitTransaction('CreateTokenClass', contract, dto, privateKey);
+    }
+
+    static async mint(
+        contract: TGalachainContract,
+        tokenClassKey: TGalachainToken,
+        to: string,
+        amount: number,
+        privateKey: string,
+    ) {
+        const tokenClass = plainToInstance(TokenClassKey, tokenClassKey);
+        const dto = await createValidDTO<MintTokenDto>(MintTokenDto, {
+            owner: identityKey(to),
+            tokenClass,
+            quantity: new BigNumber(amount) as any,
+        });
+
+        return this.submitTransaction('MintToken', contract, dto, privateKey);
+    }
+
+    static async approve(
+        contract: TGalachainContract,
+        tokenClassKey: TGalachainToken,
+        spender: string,
+        amount: number,
+        allowanceType: AllowanceType,
+        privateKey: string,
+    ) {
+        const dto = await createValidDTO<GrantAllowanceDto>(GrantAllowanceDto, {
+            tokenInstance: TokenInstanceKey.nftKey(tokenClassKey, TokenInstance.FUNGIBLE_TOKEN_INSTANCE).toQueryKey(),
+            allowanceType,
+            quantities: [{ user: identityKey(spender), quantity: new BigNumber(amount) as any }],
+            uses: new BigNumber(amount) as any,
+        });
+
+        return this.submitTransaction('GrantAllowance', contract, dto, privateKey);
+    }
+
+    static async transfer(
+        contract: TGalachainContract,
+        tokenClassKey: TGalachainToken,
+        to: string,
+        amount: number,
+        instance: BigNumber,
+        privateKey: string,
+    ) {
+        const tokenInstance = plainToInstance(TokenInstanceKey, { ...tokenClassKey, instance });
+        const dto = await createValidDTO(TransferTokenDto, {
+            from: identityKey(new Wallet(privateKey).address),
+            to: identityKey(to),
+            tokenInstance,
+            quantity: new BigNumber(amount) as any,
+        });
+
+        return this.submitTransaction('TransferToken', contract, dto, privateKey);
+    }
+}
diff --git a/apps/api/src/app/services/GitcoinService.ts b/apps/api/src/app/services/GitcoinService.ts
new file mode 100644
index 000000000..721975b27
--- /dev/null
+++ b/apps/api/src/app/services/GitcoinService.ts
@@ -0,0 +1,32 @@
+import axios from 'axios';
+import { GITCOIN_API_KEY } from '../config/secrets';
+import { logger } from '../util/logger';
+export default class GitcoinService {
+    static async submitPassport(scorerId: number, address: string) {
+        await axios({
+            method: 'POST',
+            url: 'https://api.scorer.gitcoin.co/registry/submit-passport',
+            headers: { 'X-API-KEY': GITCOIN_API_KEY },
+            data: {
+                address,
+                scorer_id: scorerId,
+            },
+        });
+    }
+
+    static async getScoreUniqueHumanity(scorerId: number, address: string) {
+        try {
+            await this.submitPassport(scorerId, address);
+
+            const { data } = await axios({
+                method: 'GET',
+                url: `https://api.scorer.gitcoin.co/registry/score/${scorerId}/${address}`,
+                headers: { 'X-API-KEY': GITCOIN_API_KEY },
+            });
+            return { score: data.score === '0E-9' ? 0 : data.score };
+        } catch (error) {
+            logger.error(error.message);
+            return { error: `Could not get a score for ${address}.` };
+        }
+    }
+}
diff --git a/apps/api/src/app/services/IPFSService.ts b/apps/api/src/app/services/IPFSService.ts
new file mode 100644
index 000000000..be61d081d
--- /dev/null
+++ b/apps/api/src/app/services/IPFSService.ts
@@ -0,0 +1,44 @@
+import { API_URL, NODE_ENV } from '../config/secrets';
+import { NFTVariant } from '@thxnetwork/common/enums';
+import { ERC721Document, ERC1155Document } from '@thxnetwork/api/models';
+import axios from 'axios';
+import pinataSDK from '@pinata/sdk';
+import https from 'https';
+
+const pinata = new pinataSDK({ pinataJWTKey: process.env.PINATA_API_JWT });
+
+if (NODE_ENV !== 'production') {
+    const httpsAgent = new https.Agent({
+        rejectUnauthorized: false,
+    });
+    axios.defaults.httpsAgent = httpsAgent;
+}
+
+export async function addUrlSource(url: string) {
+    const response = await axios.get(url, { responseType: 'stream' });
+    const urlParts = url.split('/');
+    const name = urlParts[urlParts.length - 1];
+    const { IpfsHash } = await pinata.pinFileToIPFS(response.data, {
+        pinataMetadata: { name },
+        pinataOptions: { cidVersion: 0 },
+    });
+    return IpfsHash;
+}
+
+async function getTokenURI(nft: ERC721Document | ERC1155Document, metadataId: string, tokenId?: string) {
+    const tokenUri = {
+        [NFTVariant.ERC721]: metadataId,
+        [NFTVariant.ERC1155]: tokenId,
+    };
+    // During tests we can not grab data from an url due to TLS issues, hence we return the internally used tokenUri
+    if (NODE_ENV === 'test') return tokenUri[nft.variant];
+
+    const metadataUrl = {
+        [NFTVariant.ERC721]: `${API_URL}/v1/metadata/${metadataId}`,
+        [NFTVariant.ERC1155]: `${API_URL}/v1/metadata/erc1155/${nft._id}/${tokenId}`,
+    };
+
+    return await addUrlSource(metadataUrl[nft.variant]);
+}
+
+export default { addUrlSource, getTokenURI };
diff --git a/apps/api/src/app/services/IdentityService.ts b/apps/api/src/app/services/IdentityService.ts
new file mode 100644
index 000000000..95c22ee79
--- /dev/null
+++ b/apps/api/src/app/services/IdentityService.ts
@@ -0,0 +1,33 @@
+import { WalletVariant } from '@thxnetwork/common/enums';
+import { Wallet, Identity, PoolDocument } from '@thxnetwork/api/models';
+import { uuidV1 } from '../util/uuid';
+
+export default class IdentityService {
+    static getUUID(pool: PoolDocument, salt: string) {
+        const poolId = String(pool._id);
+        return uuidV1(`${poolId}${salt}`);
+    }
+
+    // Derive uuid v1 from poolId + salt. Using uuid v1 format so we can
+    // validate the input using express-validator
+    static getIdentityForSalt(pool: PoolDocument, salt: string) {
+        const uuid = this.getUUID(pool, salt);
+        return Identity.findOneAndUpdate(
+            { poolId: pool._id, uuid },
+            { poolId: pool._id, uuid },
+            { new: true, upsert: true },
+        );
+    }
+
+    static async forceConnect(pool: PoolDocument, account: TAccount) {
+        // Search for WalletConnect wallets for this sub
+        const wallets = await Wallet.find({ sub: account.sub, variant: WalletVariant.WalletConnect });
+        if (!wallets.length) return;
+
+        // Create a list of uuids for these wallets
+        const uuids = wallets.map((wallet) => this.getUUID(pool, wallet.address));
+
+        // Find any identity for these uuids and update
+        await Identity.findOneAndUpdate({ uuid: { $in: uuids } }, { sub: account.sub });
+    }
+}
diff --git a/apps/api/src/app/services/ImageService.ts b/apps/api/src/app/services/ImageService.ts
new file mode 100644
index 000000000..137330c27
--- /dev/null
+++ b/apps/api/src/app/services/ImageService.ts
@@ -0,0 +1,29 @@
+import short from 'short-uuid';
+import { AWS_S3_PUBLIC_BUCKET_NAME, AWS_S3_PUBLIC_BUCKET_REGION } from '@thxnetwork/api/config/secrets';
+import { s3Client } from '@thxnetwork/api/util/s3';
+import { PutObjectCommand } from '@aws-sdk/client-s3';
+
+async function upload(file: Express.Multer.File) {
+    const [originalname, extension] = file.originalname.split('.');
+    const filename =
+        originalname.toLowerCase().split(' ').join('-').split('.') + '-' + short.generate() + `.${extension}`;
+    const type = extension === 'svg' ? 'image/svg+xml' : 'image/*';
+    return this.uploadToS3(file.buffer, filename, type);
+}
+
+async function uploadToS3(fileBuffer: Buffer, filename, type) {
+    await s3Client.send(
+        new PutObjectCommand({
+            Key: filename,
+            Bucket: AWS_S3_PUBLIC_BUCKET_NAME,
+            ACL: 'public-read',
+            Body: fileBuffer,
+            ContentType: type,
+            ContentDisposition: 'inline',
+        }),
+    );
+
+    return `https://${AWS_S3_PUBLIC_BUCKET_NAME}.s3.${AWS_S3_PUBLIC_BUCKET_REGION}.amazonaws.com/${filename}`;
+}
+
+export default { upload, uploadToS3 };
diff --git a/apps/api/src/app/services/InvoiceService.ts b/apps/api/src/app/services/InvoiceService.ts
new file mode 100644
index 000000000..7e1f78297
--- /dev/null
+++ b/apps/api/src/app/services/InvoiceService.ts
@@ -0,0 +1,141 @@
+import { planPricingMap } from '@thxnetwork/common/constants';
+import {
+    Pool,
+    Invoice,
+    QuestDailyEntry,
+    QuestInviteEntry,
+    QuestSocialEntry,
+    QuestCustomEntry,
+    QuestWeb3Entry,
+    QuestGitcoinEntry,
+} from '../models';
+import AccountProxy from '../proxies/AccountProxy';
+import { logger } from '../util/logger';
+import { AccountPlanType } from '@thxnetwork/common/enums';
+import { startOfMonth, endOfMonth } from 'date-fns';
+
+export default class InvoiceService {
+    /**
+     * Upsert invoices for the current month. Periodically (daily) invoked by the agenda job scheduler.
+     */
+    static async upsertJob() {
+        const currentDate = new Date();
+        // Define the start and end dates for the month range
+        const invoicePeriodstartDate = startOfMonth(currentDate);
+        const invoicePeriodEndDate = endOfMonth(currentDate);
+
+        await this.upsertInvoices(invoicePeriodstartDate, invoicePeriodEndDate);
+    }
+
+    /**
+     * Upsert invoices for a given period. Used independently for testing and backfills.
+     * @param invoicePeriodstartDate
+     * @param invoicePeriodEndDate
+     */
+    static async upsertInvoices(invoicePeriodstartDate: Date, invoicePeriodEndDate: Date) {
+        // Determine the lookup stages for the quest entries in the pools pipeline
+        const questEntryModels = [
+            QuestDailyEntry,
+            QuestInviteEntry,
+            QuestSocialEntry,
+            QuestCustomEntry,
+            QuestWeb3Entry,
+            QuestGitcoinEntry,
+        ];
+
+        // Get all relevant pools
+        const pools = await Pool.find({ 'settings.isPublished': true });
+        const questEntriesByCampaign = await Promise.all(
+            pools.map(async (pool) => {
+                const uniqueEntriesByVariant = await Promise.all(
+                    questEntryModels.map(async (model) => {
+                        return await model
+                            .countDocuments({
+                                poolId: pool.id,
+                                createdAt: { $gte: invoicePeriodstartDate, $lte: invoicePeriodEndDate },
+                            })
+                            .distinct('sub');
+                    }),
+                );
+                const flattenedArray = uniqueEntriesByVariant.flat();
+
+                return { poolId: pool.id, poolSub: pool.sub, mapCount: new Set(flattenedArray).size };
+            }),
+        );
+
+        // Get the pool owner accounts to send the invoices
+        const subs = questEntriesByCampaign.map(({ poolSub }) => poolSub);
+        const accounts = await AccountProxy.find({ subs });
+
+        // Build operations array for the current month metrics
+        const operations = questEntriesByCampaign.map(({ poolId, poolSub, mapCount }) => {
+            try {
+                const account = accounts.find((a) => a.sub === poolSub);
+                // If the account can not be found, has no email or plan then notify admin.
+                // Continue with invoice generation for future reference
+                // @todo: notify admin
+                if (!account) {
+                    logger.info(`Account ${account.sub} not found for invoicing.`);
+                }
+                if (!account.email) {
+                    logger.info(`Account ${account.sub} has no email for invoicing.`);
+                }
+                if (![AccountPlanType.Lite, AccountPlanType.Premium].includes(account.plan)) {
+                    logger.info(`Account ${account.sub} has no plan for invoicing.`);
+                }
+
+                return {
+                    updateOne: {
+                        filter: {
+                            poolId,
+                            periodStartDate: invoicePeriodstartDate,
+                            periodEndDate: invoicePeriodEndDate,
+                        },
+                        update: {
+                            $set: {
+                                poolId,
+                                periodStartDate: invoicePeriodstartDate,
+                                periodEndDate: invoicePeriodEndDate,
+                                mapCount,
+                                mapLimit: planPricingMap[account.plan].subscriptionLimit,
+                                ...this.createInvoiceDetails(account, mapCount),
+                            },
+                        },
+                        upsert: true,
+                    },
+                };
+            } catch (error) {
+                logger.error(error);
+            }
+        });
+
+        // Remove empty ops and bulk write the invoices
+        await Invoice.bulkWrite(operations.filter((op) => !!op));
+    }
+
+    /**
+     * Create invoice details for the given account and monthly active participant count
+     * @param account
+     * @param mapCount
+     * @returns invoice details used for upsert in db
+     */
+    static createInvoiceDetails(account: TAccount, mapCount: number) {
+        const countAdditionalUnits = (mapCount: number, limit: number) => {
+            return Math.max(0, mapCount - limit);
+        };
+        const plan = account.plan || AccountPlanType.Lite;
+        const { subscriptionLimit, costPerUnit, costSubscription } = planPricingMap[plan];
+
+        // Plan limit is subtracted from unit count as costs are included in subscription costs
+        const additionalUnitCount = countAdditionalUnits(mapCount, subscriptionLimit);
+
+        return {
+            additionalUnitCount,
+            costPerUnit,
+            costSubscription,
+            costTotal: costSubscription + additionalUnitCount * costPerUnit,
+            currency: 'USDC',
+            plan: account.plan,
+        };
+    }
+}
diff --git a/apps/api/src/app/services/LiquidityService.ts b/apps/api/src/app/services/LiquidityService.ts
new file mode 100644
index 000000000..704099d96
--- /dev/null
+++ b/apps/api/src/app/services/LiquidityService.ts
@@ -0,0 +1,23 @@
+import { contractNetworks, getArtifact } from '@thxnetwork/api/hardhat';
+import { WalletDocument } from '../models/Wallet';
+import { getProvider } from '../util/network';
+import TransactionService from './TransactionService';
+import BalancerService from './BalancerService';
+
+export default class LiquidityService {
+    static async create(wallet: WalletDocument, usdcAmountInWei: string, thxAmountInWei: string, slippage: string) {
+        const { to, data } = await BalancerService.buildJoin(wallet, usdcAmountInWei, thxAmountInWei, slippage);
+        return await TransactionService.proposeSafeAsync(wallet, to, data);
+    }
+
+    static async stake(wallet: WalletDocument, amountInWei: string) {
+        const { web3 } = getProvider(wallet.chainId);
+
+        // Deposit the BPT into the gauge
+        const bptGauge = new web3.eth.Contract(getArtifact('BPTGauge').abi, contractNetworks[wallet.chainId].BPTGauge);
+        const fn = bptGauge.methods.deposit(amountInWei);
+
+        // Propose tx data to relayer and return safeTxHash to client to sign
+        return await TransactionService.sendSafeAsync(wallet, bptGauge.options.address, fn);
+    }
+}
diff --git a/apps/api/src/app/services/LockService.ts b/apps/api/src/app/services/LockService.ts
new file mode 100644
index 000000000..951387b6f
--- /dev/null
+++ b/apps/api/src/app/services/LockService.ts
@@ -0,0 +1,50 @@
+import { QuestSocialDocument } from '../models';
+import QuestService from './QuestService';
+import { serviceMap } from './interfaces/IQuestService';
+
+async function getIsUnlocked(lock: TQuestLock, account: TAccount): Promise<boolean> {
+    const ids: any = [{ sub: account.sub }];
+
+    // For these social quests we also search for existing entries by platformUserId
+    const quest = (await QuestService.findById(lock.variant, lock.questId)) as QuestSocialDocument;
+    if (quest.interaction) {
+        const platformUserId = QuestService.findUserIdForInteraction(account, quest.interaction);
+        if (platformUserId) ids.push({ platformUserId });
+    }
+
+    const Entry = serviceMap[lock.variant].models.entry;
+    const exists = await Entry.exists({ questId: lock.questId, $or: ids });
+
+    return !!exists;
+}
+
+async function getIsLocked(locks: TQuestLock[], account: TAccount) {
+    if (!locks.length || !account) return false;
+
+    // Check if there are entries for the remaining quests
+    const promises = locks.map((lock) => getIsUnlocked(lock, account));
+    const results = await Promise.allSettled(promises);
+    const anyRejected = results.some((result) => result.status === 'rejected');
+    if (anyRejected) return true;
+
+    return results
+        .filter((result) => result.status === 'fulfilled')
+        .map((result: any & { value: boolean }) => result.value)
+        .includes(false);
+}
+
+async function removeAllLocks(questId: string) {
+    for (const variant in Object.keys(serviceMap)) {
+        const Quest = serviceMap[variant].models.quest;
+        const lockedQuests = await Quest.find({ 'locks.questId': questId });
+
+        for (const lockedQuest of lockedQuests) {
+            const index = lockedQuest.locks.findIndex((lock: TQuestLock) => lock.questId === questId);
+            const locks = lockedQuest.locks.splice(index, 1);
+
+            await lockedQuest.updateOne({ locks });
+        }
+    }
+}
+
+export default { getIsLocked, removeAllLocks };
diff --git a/apps/api/src/app/services/MailService.ts b/apps/api/src/app/services/MailService.ts
new file mode 100644
index 000000000..0aafd427e
--- /dev/null
+++ b/apps/api/src/app/services/MailService.ts
@@ -0,0 +1,33 @@
+import {
+    AUTH_URL,
+    NODE_ENV,
+    CYPRESS_EMAIL,
+    AWS_ACCESS_KEY_ID,
+    AWS_SECRET_ACCESS_KEY,
+} from '@thxnetwork/api/config/secrets';
+import path from 'path';
+import { assetsPath } from '../util/path';
+import ejs from 'ejs';
+import { sendMail } from '@thxnetwork/common/mail';
+import { logger } from '../util/logger';
+
+const mailTemplatePath = path.join(assetsPath, 'views', 'email');
+
+const send = async (to: string, subject: string, htmlContent: string, link = { src: '', text: '' }) => {
+    if (!to) return;
+
+    const html = await ejs.renderFile(
+        path.join(mailTemplatePath, 'base-template.ejs'),
+        { link, subject, htmlContent, baseUrl: AUTH_URL },
+        { async: true },
+    );
+
+    if (!AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY || NODE_ENV === 'test' || CYPRESS_EMAIL === to) {
+        logger.debug({ message: 'Not sending e-mail', link });
+        return;
+    }
+
+    return sendMail(to, subject, html);
+};
+
+export default { send };
diff --git a/apps/api/src/app/services/NotificationService.ts b/apps/api/src/app/services/NotificationService.ts
new file mode 100644
index 000000000..782794e62
--- /dev/null
+++ b/apps/api/src/app/services/NotificationService.ts
@@ -0,0 +1,142 @@
+import { QuestVariant } from '@thxnetwork/common/enums';
+import { PoolDocument } from '@thxnetwork/api/models';
+import { logger } from '../util/logger';
+import { sleep } from '../util';
+import { Notification, Widget, Participant } from '@thxnetwork/api/models';
+import { DiscordButtonVariant } from '../events/InteractionCreated';
+import { ButtonStyle } from 'discord.js';
+import { WIDGET_URL } from '../config/secrets';
+import { celebratoryWords } from '../util/dictionaries';
+import AccountProxy from '../proxies/AccountProxy';
+import MailService from './MailService';
+import PoolService from './PoolService';
+import BrandService from './BrandService';
+import DiscordDataProxy from '../proxies/DiscordDataProxy';
+
+const MAIL_CHUNK_SIZE = 600;
+
+async function send(
+    pool: PoolDocument,
+    { subjectId, subject, message, link }: Partial<TNotification> & { link?: { src: string; text: string } },
+) {
+    const participants = await Participant.find({ poolId: pool._id, isSubscribed: true });
+    const subs = participants.map((p) => p.sub);
+    const accounts = (await AccountProxy.find({ subs })).filter((a) => a.email);
+
+    // Create chunks for bulk email sending to avoid hitting Sendgrit rate limits
+    for (let i = 0; i < subs.length; i += MAIL_CHUNK_SIZE) {
+        const chunk = subs.slice(i, i + MAIL_CHUNK_SIZE);
+        await Promise.all(
+            chunk.map(async (sub) => {
+                try {
+                    // Make sure to not sent duplicate notifications
+                    // for the same subjectId
+                    const isNotifiedAlready = await Notification.exists({ sub, subjectId });
+                    if (isNotifiedAlready) return;
+
+                    const account = accounts.find((a) => a.sub === sub);
+                    await MailService.send(account.email, subject, message, link);
+
+                    await Notification.create({ sub, poolId: pool._id, subjectId, subject, message });
+                } catch (error) {
+                    logger.error(error);
+                }
+            }),
+        );
+
+        // Sleep 60 seconds before sending the next chunk
+        await sleep(60);
+    }
+}
+
+async function notify(variant: QuestVariant, quest: TQuest) {
+    const [pool, brand, widget] = await Promise.all([
+        PoolService.getById(quest.poolId),
+        BrandService.get(quest.poolId),
+        Widget.findOne({ poolId: quest.poolId }),
+    ]);
+
+    sendQuestPublishEmail(pool, variant, quest as TQuest, widget);
+    sendQuestPublishNotification(pool, variant, quest as TQuest, widget, brand);
+}
+
+async function sendQuestPublishEmail(pool: PoolDocument, variant: QuestVariant, quest: TQuest, widget: TWidget) {
+    const { amount, amounts } = quest as any;
+    const subject = `🎁 New ${QuestVariant[variant]} Quest: Earn ${amount || amounts[0]} pts!"`;
+    const message = `<p style="font-size: 18px">Earn ${amount || amounts[0]} points!🔔</p>
+    <p>Hi! <strong>${pool.settings.title}</strong> just published a new ${QuestVariant[variant]} Quest.
+    <p><strong>${quest.title}</strong><br />${quest.description}.</p>`;
+    const src = WIDGET_URL + `/c/${pool.settings.slug}`;
+
+    send(pool, {
+        subjectId: quest.uuid,
+        subject,
+        message,
+        link: { text: `Complete ${QuestVariant[variant]} Quest`, src },
+    });
+}
+
+async function sendQuestPublishNotification(
+    pool: PoolDocument,
+    variant: QuestVariant,
+    quest: TQuest,
+    widget: TWidget,
+    brand?: TBrand,
+) {
+    const theme = JSON.parse(widget.theme);
+    const { amount, amounts } = quest as any;
+
+    const embed = {
+        title: quest.title,
+        description: quest.description,
+        author: {
+            name: pool.settings.title,
+            icon_url: brand ? brand.logoImgUrl : '',
+            url: widget.domain,
+        },
+        image: { url: quest.image },
+        color: parseInt(theme.elements.btnBg.color.replace(/^#/, ''), 16),
+        fields: [
+            {
+                name: 'Points',
+                value: `${amount || amounts[0]}`,
+                inline: true,
+            },
+            {
+                name: 'Type',
+                value: `${QuestVariant[quest.variant]}`,
+                inline: true,
+            },
+        ],
+    };
+
+    await DiscordDataProxy.sendChannelMessage(
+        pool,
+        `Hi @everyone! We published a **${QuestVariant[variant]} Quest**.`,
+        [embed],
+        [
+            {
+                customId: `${DiscordButtonVariant.QuestComplete}:${quest.variant}:${quest._id}`,
+                label: 'Complete Quest!',
+                style: ButtonStyle.Success,
+            },
+            { label: 'More Info', style: ButtonStyle.Link, url: WIDGET_URL + `/c/${pool.settings.slug}` },
+        ],
+    );
+}
+
+async function sendQuestEntryNotification(pool: PoolDocument, quest: TQuest, account: TAccount, amount: number) {
+    const index = Math.floor(Math.random() * celebratoryWords.length);
+    const discord = account.tokens && account.tokens.find((a) => a.kind === 'discord');
+    const user = discord && discord.userId ? `<@${discord.userId}>` : `**${account.username}**`;
+    const button = {
+        customId: `${DiscordButtonVariant.QuestComplete}:${quest.variant}:${quest._id}`,
+        label: 'Complete Quest',
+        style: ButtonStyle.Primary,
+    };
+    const content = `${celebratoryWords[index]} ${user} completed the **${quest.title}** quest and earned **${amount} points.**`;
+
+    await DiscordDataProxy.sendChannelMessage(pool, content, [], [button]);
+}
+
+export default { send, notify, sendQuestEntryNotification };
diff --git a/apps/api/src/app/services/ParticipantService.ts b/apps/api/src/app/services/ParticipantService.ts
new file mode 100644
index 000000000..34973b370
--- /dev/null
+++ b/apps/api/src/app/services/ParticipantService.ts
@@ -0,0 +1,60 @@
+import { Document } from 'mongoose';
+import { DiscordReaction, Participant, TwitterUser } from '../models';
+import ReCaptchaService from '@thxnetwork/api/services/ReCaptchaService';
+import { AccessTokenKind } from '@thxnetwork/common/enums';
+import DiscordService from './DiscordService';
+import { DiscordUser } from '../models/DiscordUser';
+
+export default class ParticipantService {
+    static async decorate(
+        data: Document & (TQuestEntry | TRewardPayment),
+        { accounts, participants }: { accounts: TAccount[]; participants: TParticipant[] },
+    ) {
+        const account = accounts.find((a) => a.sub === data.sub);
+        const pointBalance = participants.find((p) => account.sub === String(p.sub));
+        const tokens = await Promise.all(
+            account.tokens.map(async (token: TToken) => {
+                if (token.kind !== 'twitter') return token;
+                const user = await TwitterUser.findOne({ userId: token.userId });
+                return { ...token, user };
+            }),
+        );
+
+        return {
+            ...data.toJSON(),
+            account: { ...account, tokens },
+            pointBalance: pointBalance ? pointBalance.balance : 0,
+        };
+    }
+
+    static async updateRiskScore(
+        account: TAccount,
+        poolId: string,
+        { token, recaptchaAction }: { token: string; recaptchaAction: string },
+    ) {
+        // Get risk score from Google
+        const riskAnalysis = await ReCaptchaService.getRiskAnalysis({
+            token,
+            recaptchaAction,
+        });
+
+        // Update the participant's risk score
+        return await Participant.findOneAndUpdate({ sub: account.sub, poolId }, { riskAnalysis }, { new: true });
+    }
+
+    static async findUser(token: TToken, { userId, guildId }: { userId: string; guildId?: string }) {
+        const userModelMap = {
+            [AccessTokenKind.Twitter]: () => TwitterUser.findOne({ userId }),
+            [AccessTokenKind.Discord]: () => DiscordUser.findOne({ userId, guildId }),
+        };
+
+        const user = userModelMap[token.kind] && (await userModelMap[token.kind]());
+
+        return {
+            kind: token.kind,
+            userId: token.userId,
+            metadata: token.metadata,
+            user,
+        } as unknown as TToken;
+    }
+}
diff --git a/apps/api/src/app/services/PaymentService.ts b/apps/api/src/app/services/PaymentService.ts
new file mode 100644
index 000000000..bb74d1a51
--- /dev/null
+++ b/apps/api/src/app/services/PaymentService.ts
@@ -0,0 +1,112 @@
+import { contractArtifacts, contractNetworks } from '@thxnetwork/api/hardhat';
+import { Pool, WalletDocument } from '../models';
+import { getProvider } from '../util/network';
+import { BigNumber } from 'alchemy-sdk';
+import { differenceInSeconds, isBefore, subWeeks } from 'date-fns';
+import { AccountPlanType, ChainId } from '@thxnetwork/common/enums';
+import { Payment } from '../models/Payment';
+import { planPricingMap } from '@thxnetwork/common/constants';
+import { parseUnits } from 'ethers/lib/utils';
+import ContractService from './ContractService';
+import TransactionService from './TransactionService';
+import SafeService from './SafeService';
+import BalancerService from './BalancerService';
+
+const ONE_DAY = 60 * 60 * 24;
+
+export default class PaymentService {
+    static async deposit(safe: WalletDocument, sub: string, amountInWei: BigNumber) {
+        const { web3 } = getProvider(safe.chainId);
+        const addresses = contractNetworks[safe.chainId];
+        const contract = new web3.eth.Contract(
+            contractArtifacts['THXPaymentSplitter'].abi,
+            addresses.THXPaymentSplitter,
+        );
+
+        // @dev Using default slippage value here as payments
+        const { minBPTOut } = await BalancerService.buildJoin(safe, amountInWei.toString(), '0', '50');
+        const fn = contract.methods.deposit(safe.address, amountInWei, minBPTOut);
+
+        await Payment.create({ poolId: safe.poolId, sub, amountInWei });
+
+        return await TransactionService.sendSafeAsync(safe, addresses.THXPaymentSplitter, fn);
+    }
+
+    static async balanceOf(wallet: WalletDocument) {
+        // TODO Deploy Polygon PaymentSplitter before using this middleware
+        const { THXPaymentSplitter } = contractNetworks[wallet.chainId];
+        if (!THXPaymentSplitter && wallet.chainId === ChainId.Polygon) {
+            return '0';
+        }
+
+        const splitter = ContractService.getContract('THXPaymentSplitter', wallet.chainId, THXPaymentSplitter);
+        const balance = await splitter.balanceOf(wallet.address);
+        return balance.toString();
+    }
+
+    static async getRate(wallet: WalletDocument) {
+        const splitter = ContractService.getContract(
+            'THXPaymentSplitter',
+            wallet.chainId,
+            contractNetworks[wallet.chainId].THXPaymentSplitter,
+        );
+        const rate = await splitter.rates(wallet.address);
+        return rate.toString();
+    }
+
+    static async setRate(safe: WalletDocument, plan: AccountPlanType) {
+        const { web3 } = getProvider(safe.chainId);
+        const addresses = contractNetworks[safe.chainId];
+        const contract = new web3.eth.Contract(
+            contractArtifacts['THXPaymentSplitter'].abi,
+            addresses.THXPaymentSplitter,
+        );
+        // Convert plan pricing to rate in wei per second
+        const pricing = planPricingMap[plan];
+        // Using 6 decimals for USDC
+        const costInWeiPerThirtyDays = BigNumber.from(parseUnits(pricing.costSubscription.toString(), 6));
+        // Plan pricing is determined on a per 4 week basis
+        const rateInWeiPerSecond = costInWeiPerThirtyDays.div(4 * 7 * 24 * 60 * 60);
+        const fn = contract.methods.setRate(rateInWeiPerSecond);
+
+        return await TransactionService.sendSafeAsync(safe, addresses.PaymentSplitter, fn);
+    }
+
+    static async getTimeLeftInSeconds(safe: WalletDocument, pool: TPool) {
+        const now = new Date();
+        const isTrial = isBefore(pool.trialEndsAt, now);
+        if (isTrial) return BigNumber.from(differenceInSeconds(pool.trialEndsAt, now));
+
+        // Devide balance by rate and calculate time left for the pool
+        const balanceInWei = await this.balanceOf(safe);
+        const rateInWeiPerSecond = await this.getRate(safe);
+        return BigNumber.from(balanceInWei).div(rateInWeiPerSecond);
+    }
+
+    static async assertPaymentsJob() {
+        // Skip pools that have no trialEnd date (legacy) or are still in the first week of their trial
+        const pools = await Pool.find({ trialEndsAt: { $exists: true, $lt: subWeeks(new Date(), 1) } });
+        for (const pool of pools) {
+            // Get campaing safe
+            const safe = await SafeService.findOneByPool(pool);
+            const timeLeftInSeconds = await this.getTimeLeftInSeconds(safe, pool);
+
+            // Insufficient payments
+            if (timeLeftInSeconds.eq(0)) {
+                // Send a reminder to make payment and inform about campaign pause change
+            }
+            // 1 day before balance hitting zero send a reminder to make payment
+            else if (timeLeftInSeconds.lt(ONE_DAY)) {
+                // Send reminder to make payment
+            }
+            // 3 days before balance hitting zero send a reminder to make payment
+            else if (timeLeftInSeconds.lt(ONE_DAY * 3)) {
+                // Send reminder to make payment
+            }
+            // 1 week before balance hitting zero send a reminder to make payment
+            else if (timeLeftInSeconds.lt(ONE_DAY * 7)) {
+                // Send reminder to make payment
+            }
+        }
+    }
+}
diff --git a/apps/api/src/app/services/PointBalanceService.ts b/apps/api/src/app/services/PointBalanceService.ts
new file mode 100644
index 000000000..3463cc14e
--- /dev/null
+++ b/apps/api/src/app/services/PointBalanceService.ts
@@ -0,0 +1,29 @@
+import { Participant, PoolDocument } from '@thxnetwork/api/models';
+
+async function add(pool: PoolDocument, account: TAccount, amount: number) {
+    const participant = await Participant.findOne({ poolId: pool._id, sub: account.sub });
+    const balance = participant ? Number(participant.balance) + Number(amount) : Number(amount);
+
+    await Participant.updateOne(
+        { poolId: String(pool._id), sub: account.sub },
+        { poolId: String(pool._id), sub: account.sub, balance },
+        { upsert: true },
+    );
+}
+
+async function subtract(pool: PoolDocument, account: TAccount, price: number) {
+    if (!price) return;
+
+    const participant = await Participant.findOne({ poolId: pool._id, sub: account.sub });
+    if (!participant) return;
+
+    const balance = Number(participant.balance) >= price ? Number(participant.balance) - price : 0;
+
+    await Participant.updateOne(
+        { poolId: String(pool._id), sub: account.sub },
+        { poolId: String(pool._id), sub: account.sub, balance },
+        { upsert: true },
+    );
+}
+
+export default { add, subtract };
diff --git a/apps/api/src/app/services/PoolService.ts b/apps/api/src/app/services/PoolService.ts
new file mode 100644
index 000000000..9d63ce88c
--- /dev/null
+++ b/apps/api/src/app/services/PoolService.ts
@@ -0,0 +1,406 @@
+import { AccessTokenKind, ChainId, CollaboratorInviteState, OAuthDiscordScope } from '@thxnetwork/common/enums';
+import { v4 } from 'uuid';
+import { AccountVariant } from '@thxnetwork/common/enums';
+import { DASHBOARD_URL } from '../config/secrets';
+import { DEFAULT_COLORS, DEFAULT_ELEMENTS } from '@thxnetwork/common/constants';
+import { logger } from '../util/logger';
+import { getsigningSecret } from '../util/signingsecret';
+import {
+    Pool,
+    PoolDocument,
+    RewardCoin,
+    RewardNFT,
+    Collaborator,
+    CollaboratorDocument,
+    Client,
+    DiscordGuild,
+    Identity,
+    Participant,
+    QuestInvite,
+    QuestWeb3,
+    QuestCustom,
+    RewardCustom,
+    Widget,
+    QuestSocial,
+    QuestDaily,
+    CouponCode,
+    WalletDocument,
+} from '@thxnetwork/api/models';
+
+import AccountProxy from '../proxies/AccountProxy';
+import DiscordDataProxy from '../proxies/DiscordDataProxy';
+import MailService from './MailService';
+import SafeService from './SafeService';
+import ParticipantService from './ParticipantService';
+import DiscordService from './DiscordService';
+import ContractService from './ContractService';
+
+export const ADMIN_ROLE = '0x0000000000000000000000000000000000000000000000000000000000000000';
+
+async function isAudienceAllowed(aud: string, poolId: string) {
+    return !!(await Client.exists({ clientId: aud, poolId }));
+}
+
+async function isSubjectAllowed(sub: string, poolId: string) {
+    const isOwner = await Pool.exists({
+        _id: poolId,
+        sub,
+    });
+    const isCollaborator = await Collaborator.exists({ sub, poolId, state: CollaboratorInviteState.Accepted });
+    return isOwner || isCollaborator;
+}
+
+async function getById(id: string) {
+    const pool = await Pool.findById(id);
+    const safe = await SafeService.findOneByPool(pool, pool.chainId);
+    pool.safe = safe;
+    return pool;
+}
+
+function getByAddress(address: string) {
+    return Pool.findOne({ address });
+}
+
+async function deploy(sub: string, title: string): Promise<PoolDocument> {
+    const pool = await Pool.create({
+        sub,
+        token: v4(),
+        signingSecret: getsigningSecret(64),
+        settings: {
+            title,
+            description: '',
+            isArchived: false,
+            isWeeklyDigestEnabled: true,
+            isTwitterSyncEnabled: false,
+            defaults: {
+                conditionalRewards: { title: '', description: '', amount: 50 },
+            },
+            authenticationMethods: [
+                AccountVariant.EmailPassword,
+                AccountVariant.Metamask,
+                AccountVariant.SSOGoogle,
+                AccountVariant.SSODiscord,
+            ],
+        },
+    });
+
+    await Widget.create({
+        uuid: v4(),
+        poolId: pool._id,
+        align: 'right',
+        message: 'Hi there!👋 Click me to complete quests and earn rewards...',
+        domain: 'https://www.example.com',
+        theme: JSON.stringify({ elements: DEFAULT_ELEMENTS, colors: DEFAULT_COLORS }),
+    });
+
+    return Pool.findByIdAndUpdate(pool._id, { 'settings.slug': String(pool._id) }, { new: true });
+}
+
+async function getAllBySub(sub: string): Promise<PoolDocument[]> {
+    let pools = await Pool.find({ sub });
+
+    // Only query for collabs of not already owned pools
+    const collaborations = await Collaborator.find({ sub, poolId: { $nin: pools.map(({ _id }) => String(_id)) } });
+    const poolIds = collaborations.map((c) => c.poolId);
+    if (poolIds.length) {
+        const collaborationPools = await Pool.find({ _id: poolIds });
+        pools = pools.concat(collaborationPools);
+    }
+
+    // Add Safes to pools
+    return await Promise.all(
+        pools.map(async (pool) => {
+            const safe = await SafeService.findOneByPool(pool);
+            return { ...pool.toJSON(), safe };
+        }),
+    );
+}
+
+function getAll() {
+    return Pool.find({});
+}
+
+async function balanceOf(safe: WalletDocument) {
+    const contract = ContractService.getContract('USDC', safe.chainId, safe.address);
+    return await contract.balanceOf(safe.address);
+}
+
+async function countByNetwork(chainId: ChainId) {
+    return Pool.countDocuments({ chainId });
+}
+
+async function find(model: any, pool: PoolDocument) {
+    return await model.find({ poolId: String(pool._id) });
+}
+
+async function findOwner(pool: PoolDocument) {
+    const account = await AccountProxy.findById(pool.sub);
+    account.tokens = account.tokens.map(({ kind, expiry, scopes }) => ({ kind, expiry, scopes } as TToken));
+    return account;
+}
+
+async function getQuestCount(pool: PoolDocument) {
+    const result = await Promise.all(
+        [QuestDaily, QuestInvite, QuestSocial, QuestCustom, QuestWeb3].map(async (model) => await find(model, pool)),
+    );
+    return Array.from(new Set(result.flat(1)));
+}
+
+async function getRewardCount(pool: PoolDocument) {
+    const result = await Promise.all(
+        [RewardCoin, RewardNFT, RewardCustom].map(async (model) => await find(model, pool)),
+    );
+    return Array.from(new Set(result.flat(1)));
+}
+
+async function findIdentities(pool: PoolDocument, page: number, limit: number) {
+    const startIndex = (page - 1) * limit;
+    const endIndex = page * limit;
+    const total = await Identity.find({ poolId: pool._id }).countDocuments().exec();
+
+    const identities = {
+        previous: startIndex > 0 && {
+            page: page - 1,
+        },
+        next: endIndex < total && {
+            page: page + 1,
+        },
+        limit,
+        total,
+        results: await Identity.aggregate([
+            { $match: { poolId: String(pool._id) } },
+            { $skip: startIndex },
+            { $limit: limit },
+        ]).exec(),
+    };
+
+    const subs = identities.results.filter(({ sub }) => !!sub).map(({ sub }) => sub);
+    const accounts = await AccountProxy.find({ subs });
+
+    identities.results = identities.results.map((identity: TIdentity) => ({
+        ...identity,
+        account: accounts.find(({ sub }) => sub === identity.sub),
+    }));
+
+    return identities;
+}
+
+async function findCouponCodes(
+    { couponRewardId, query }: { couponRewardId: string; query: string },
+    page: number,
+    limit: number,
+) {
+    const startIndex = (page - 1) * limit;
+    const endIndex = page * limit;
+    const $match = { couponRewardId };
+    if (query && query.length > 1) {
+        $match['code'] = { $regex: query, $options: 'i' };
+    }
+
+    const total = await CouponCode.find($match).countDocuments();
+    const results = await CouponCode.aggregate([{ $match }, { $skip: startIndex }, { $limit: limit }]).exec();
+    // Get subs for results
+    const subs = results.map(({ sub }) => sub);
+    // Get accounts for subs
+    const accounts = await AccountProxy.find({ subs });
+
+    return {
+        previous: startIndex > 0 && {
+            page: page - 1,
+        },
+        next: endIndex < total && {
+            page: page + 1,
+        },
+        total,
+        results: results.map((result) => {
+            const account = accounts.find(({ sub }) => sub === result.sub);
+            result.account = account;
+            return result;
+        }),
+    };
+}
+
+async function findParticipants(pool: PoolDocument, page: number, limit: number, query = '') {
+    const startIndex = (page - 1) * limit;
+    const endIndex = page * limit;
+    const poolId = String(pool._id);
+    const total = await Participant.countDocuments({ poolId });
+    const $match = { poolId };
+
+    let accounts = [],
+        subs = [];
+
+    // If a query is provided first get the accounts and subs based on the query
+    if (query) {
+        accounts = await AccountProxy.find({ query });
+        subs = accounts.map((a) => a.sub);
+        $match['sub'] = { $in: subs };
+    }
+
+    const results = await Participant.aggregate([
+        { $match },
+        {
+            $addFields: {
+                rankSort: {
+                    $cond: {
+                        if: { $gt: ['$rank', 0] },
+                        then: '$rank',
+                        else: Number.MAX_SAFE_INTEGER,
+                    },
+                },
+            },
+        },
+        { $sort: { rankSort: 1 } },
+        { $skip: startIndex },
+        { $limit: limit },
+    ]).exec();
+
+    // If a query was provided dont get accounts and subs based on participants
+    if (!query) {
+        subs = results.map((p) => p.sub);
+        accounts = await AccountProxy.find({ subs });
+    }
+
+    // Format the output
+    const participants = {
+        previous: startIndex > 0 && {
+            page: page - 1,
+        },
+        next: endIndex < total && {
+            page: page + 1,
+        },
+        total,
+        results,
+    };
+
+    const guild = await DiscordService.getGuild(poolId);
+
+    participants.results = await Promise.all(
+        participants.results.map(async (participant) => {
+            let account: TAccount;
+
+            try {
+                account = accounts.find((a) => a.sub === participant.sub);
+                account.tokens = await Promise.all(
+                    account.tokens.map(async (token: TToken) =>
+                        ParticipantService.findUser(token, { userId: token.userId, guildId: guild && guild.id }),
+                    ),
+                );
+            } catch (error) {
+                logger.error(error);
+            }
+
+            return {
+                ...participant,
+                account: account && {
+                    email: account.email,
+                    username: account.username,
+                    profileImg: account.profileImg,
+                    variant: account.variant,
+                    tokens: account.tokens,
+                },
+            };
+        }),
+    );
+
+    return participants;
+}
+
+async function getParticipantCount(pool: PoolDocument) {
+    return await Participant.countDocuments({ poolId: pool._id });
+}
+
+async function inviteCollaborator(pool: PoolDocument, email: string) {
+    const uuid = v4();
+    let collaborator = await Collaborator.findOne({ email, poolId: pool._id });
+
+    if (collaborator) {
+        collaborator = await Collaborator.findByIdAndUpdate(collaborator._id, { uuid }, { new: true });
+    } else {
+        collaborator = await Collaborator.create({
+            email,
+            uuid,
+            poolId: pool._id,
+            state: CollaboratorInviteState.Pending,
+        });
+    }
+
+    const url = new URL(DASHBOARD_URL);
+    url.pathname = 'collaborator';
+    url.searchParams.append('poolId', pool._id);
+    url.searchParams.append('collaboratorRequestToken', collaborator.uuid);
+
+    await MailService.send(
+        email,
+        `👋 Collaboration Request: ${pool.settings.title}`,
+        `<p>Hi!👋</p><p>You have received a collaboration request for Quest &amp; Reward campaign: <strong>${pool.settings.title}</strong></p>`,
+        { src: url.href, text: 'Accept Request' },
+    );
+
+    return collaborator;
+}
+
+async function getAccountGuilds(account: TAccount) {
+    // Try as this is potentially rate limited due to subsequent GET pool for id requests
+    try {
+        const token = await AccountProxy.getToken(account, AccessTokenKind.Discord, [
+            OAuthDiscordScope.Identify,
+            OAuthDiscordScope.Guilds,
+        ]);
+        return DiscordDataProxy.getGuilds(token);
+    } catch (error) {
+        return [];
+    }
+}
+
+async function findGuilds(pool: PoolDocument) {
+    const account = await AccountProxy.findById(pool.sub);
+    const userGuilds = await getAccountGuilds(account);
+    const guilds = await DiscordGuild.find({ poolId: pool._id });
+    const promises = userGuilds.map(async (userGuild: { id: string; name: string }) => {
+        const guild = guilds.find(({ guildId }) => guildId === userGuild.id);
+        return await DiscordDataProxy.getGuild({
+            ...(guild && guild.toJSON()),
+            ...userGuild,
+            guildId: userGuild.id,
+            poolId: pool._id,
+            isConnected: !!guild,
+        });
+    });
+
+    return await Promise.all(promises);
+}
+
+async function findCollaborators(pool: PoolDocument) {
+    const collabs = await Collaborator.find({ poolId: pool._id });
+    const promises = collabs.map(async (collaborator: CollaboratorDocument) => {
+        if (collaborator.sub) {
+            const account = await AccountProxy.findById(collaborator.sub);
+            return { ...collaborator.toJSON(), account };
+        }
+        return collaborator;
+    });
+    return await Promise.all(promises);
+}
+
+export default {
+    isAudienceAllowed,
+    isSubjectAllowed,
+    getById,
+    getByAddress,
+    deploy,
+    balanceOf,
+    getAllBySub,
+    getAll,
+    countByNetwork,
+    getParticipantCount,
+    getQuestCount,
+    getRewardCount,
+    findOwner,
+    findIdentities,
+    findParticipants,
+    findGuilds,
+    findCollaborators,
+    findCouponCodes,
+    inviteCollaborator,
+};
diff --git a/apps/api/src/app/services/QuestCustomService.ts b/apps/api/src/app/services/QuestCustomService.ts
new file mode 100644
index 000000000..cc54a5ca4
--- /dev/null
+++ b/apps/api/src/app/services/QuestCustomService.ts
@@ -0,0 +1,126 @@
+import {
+    QuestCustom,
+    QuestCustomDocument,
+    QuestCustomEntry,
+    Identity,
+    IdentityDocument,
+    Event,
+    WalletDocument,
+} from '@thxnetwork/api/models';
+import { IQuestService } from './interfaces/IQuestService';
+
+export default class QuestCustomService implements IQuestService {
+    models = {
+        quest: QuestCustom,
+        entry: QuestCustomEntry,
+    };
+
+    async findEntryMetadata({ quest }: { quest: QuestCustomDocument }) {
+        const uniqueParticipantIds = await QuestCustomEntry.countDocuments({
+            questId: String(quest._id),
+        }).distinct('sub');
+
+        return { participantCount: uniqueParticipantIds.length };
+    }
+
+    async isAvailable({
+        quest,
+        account,
+    }: {
+        quest: QuestCustomDocument;
+        wallet?: WalletDocument;
+        account?: TAccount;
+        data: Partial<TQuestCustomEntry>;
+    }): Promise<TValidationResult> {
+        const entries = await this.findAllEntries({ quest, account });
+        if (quest.limit && entries.length >= quest.limit) {
+            return { result: false, reason: 'Quest entry limit has been reached.' };
+        }
+
+        return { result: true, reason: '' };
+    }
+
+    async getAmount({ quest }: { quest: QuestCustomDocument; wallet: WalletDocument; account: TAccount }) {
+        return quest.amount;
+    }
+
+    async decorate({
+        quest,
+        account,
+        data,
+    }: {
+        quest: QuestCustomDocument;
+        account?: TAccount;
+        data: Partial<TQuestCustomEntry>;
+    }) {
+        const entries = await this.findAllEntries({ quest, account });
+        const identities = await this.findIdentities({ quest, account });
+        const events = await this.findEvents({ quest, identities });
+        const isAvailable = await this.isAvailable({ quest, account, data });
+        const pointsAvailable = quest.limit ? (quest.limit - entries.length) * quest.amount : quest.amount;
+
+        return {
+            ...quest,
+            eventName: '', // FK Deprecrates March 15th 2024
+            isAvailable: isAvailable.result,
+            pointsAvailable,
+            entries,
+            events,
+        };
+    }
+
+    async getValidationResult({
+        quest,
+        account,
+    }: {
+        quest: QuestCustomDocument;
+        account: TAccount;
+        data: Partial<TQuestCustomEntry>;
+    }): Promise<{ reason: string; result: boolean }> {
+        // See if there are identities
+        const identities = await this.findIdentities({ quest, account });
+        if (!identities.length) {
+            return {
+                result: false,
+                reason: 'No identity connected to this account. Please ask for this in your community!',
+            };
+        }
+
+        // Find existing entries for this quest and check optional limit
+        const entries = await this.findAllEntries({ quest, account });
+        if (quest.limit && entries.length >= quest.limit) {
+            return { result: false, reason: 'Quest entry limit has been reached' };
+        }
+
+        // Find events for this quest and the identities connected to the account
+        const events = await this.findEvents({ quest, identities });
+        if (entries.length >= events.length) {
+            return { result: false, reason: 'Insufficient custom events found for this quest' };
+        }
+
+        if (entries.length < events.length) return { result: true, reason: '' };
+    }
+
+    private async findAllEntries({ quest, account }: { quest: QuestCustomDocument; account: TAccount }) {
+        if (!account) return [];
+        return await this.models.entry.find({
+            questId: quest._id,
+            sub: account.sub,
+            isClaimed: true,
+        });
+    }
+
+    private async findIdentities({ quest, account }: { quest: QuestCustomDocument; account: TAccount }) {
+        if (!account || !account.sub) return [];
+        return await Identity.find({ poolId: quest.poolId, sub: account.sub });
+    }
+
+    private async findEvents({ quest, identities }: { quest: QuestCustomDocument; identities: IdentityDocument[] }) {
+        if (!identities.length) return [];
+        return await Event.find({
+            identityId: { $in: identities.map(({ _id }) => String(_id)) },
+            poolId: quest.poolId,
+            name: quest.eventName,
+        }).limit(quest.limit || null);
+    }
+}
diff --git a/apps/api/src/app/services/QuestDailyService.ts b/apps/api/src/app/services/QuestDailyService.ts
new file mode 100644
index 000000000..ae0c488be
--- /dev/null
+++ b/apps/api/src/app/services/QuestDailyService.ts
@@ -0,0 +1,206 @@
+import { Event, Identity, QuestDaily, QuestDailyEntry } from '@thxnetwork/api/models';
+import { IQuestService } from './interfaces/IQuestService';
+
+const ONE_DAY_MS = 86400 * 1000; // 24 hours in milliseconds
+
+export default class QuestDailyService implements IQuestService {
+    models = {
+        quest: QuestDaily,
+        entry: QuestDailyEntry,
+    };
+
+    async findEntryMetadata({ quest }: { quest: TQuestDaily }) {
+        const uniqueParticipantIds = await this.models.entry
+            .countDocuments({
+                questId: String(quest._id),
+            })
+            .distinct('sub');
+
+        return { participantCount: uniqueParticipantIds.length };
+    }
+
+    async decorate({
+        quest,
+        account,
+        data,
+    }: {
+        quest: TQuestDaily;
+        data: Partial<TQuestDailyEntry>;
+        account?: TAccount;
+    }): Promise<
+        TQuestDaily & {
+            isAvailable: boolean;
+            amount: number;
+            entries: TQuestDailyEntry[];
+            claimAgainDuration: number;
+        }
+    > {
+        const amount = await this.getAmount({ quest, account });
+        const entries = account ? await this.findEntries({ quest, account }) : [];
+        const claimAgainTime = entries.length ? new Date(entries[0].createdAt).getTime() + ONE_DAY_MS : null;
+        const now = Date.now();
+        const isAvailable = await this.isAvailable({ quest, account, data });
+
+        return {
+            ...quest,
+            isAvailable: isAvailable.result,
+            amount,
+            entries,
+            claimAgainDuration:
+                claimAgainTime && claimAgainTime - now > 0 ? Math.floor((claimAgainTime - now) / 1000) : null, // Convert and floor to S,
+        };
+    }
+
+    async isAvailable({
+        quest,
+        account,
+        data,
+    }: {
+        quest: TQuestDaily;
+        account: TAccount;
+        data: Partial<TQuestDailyEntry>;
+    }): Promise<TValidationResult> {
+        if (!account) return { result: true, reason: '' };
+
+        const now = Date.now(),
+            start = now - ONE_DAY_MS,
+            end = now;
+
+        // Check for IP as we limit to 1 per IP per day (if an ip is passed)
+        if (data.metadata && data.metadata.ip) {
+            const isCompletedForIP = !!(await QuestDailyEntry.exists({
+                'questId': quest._id,
+                'createdAt': { $gt: new Date(start), $lt: new Date(end) },
+                'metadata.ip': data.metadata.ip,
+            }));
+            if (isCompletedForIP) {
+                return {
+                    result: false,
+                    reason: 'You have completed this quest from this IP within the last 24 hours.',
+                };
+            }
+        }
+
+        const isCompleted = await QuestDailyEntry.findOne({
+            questId: quest._id,
+            sub: account.sub,
+            createdAt: { $gt: new Date(start), $lt: new Date(end) },
+        });
+        if (!isCompleted) return { result: true, reason: '' };
+
+        return { result: false, reason: 'You have completed this quest within the last 24 hours.' };
+    }
+
+    async getAmount({ quest, account }: { quest: TQuestDaily; account: TAccount }): Promise<number> {
+        if (!account) return quest.amounts[0];
+
+        const claims = await this.findEntries({ quest, account });
+        const amountIndex =
+            claims.length >= quest.amounts.length ? claims.length % quest.amounts.length : claims.length;
+        return quest.amounts[amountIndex];
+    }
+
+    async getValidationResult({
+        quest,
+        account,
+    }: {
+        quest: TQuestDaily;
+        account: TAccount;
+        data: Partial<TQuestDailyEntry>;
+    }): Promise<TValidationResult> {
+        const now = Date.now(),
+            start = now - ONE_DAY_MS,
+            end = now;
+
+        const entry = await QuestDailyEntry.findOne({
+            questId: quest._id,
+            sub: account.sub,
+            createdAt: { $gt: new Date(start), $lt: new Date(end) },
+        });
+
+        // If an entry has been found the user needs to wait
+        if (entry) {
+            return { result: false, reason: `Already completed within the last 24 hours.` };
+        }
+
+        // If no entry has been found and no event is required the entry is allowed to be created
+        if (!quest.eventName) {
+            return { result: true, reason: '' };
+        }
+
+        // If an event is required we check if there is an event found within the time window
+        const identities = await this.findIdentities({ quest, account });
+        if (!identities.length) {
+            return {
+                result: false,
+                reason: 'No identity connected to this account. Please ask for this in your community!',
+            };
+        }
+
+        const identityIds = identities.map(({ _id }) => String(_id));
+        const events = await Event.find({
+            name: quest.eventName,
+            poolId: quest.poolId,
+            identityId: { $in: identityIds },
+            createdAt: { $gt: new Date(start), $lt: new Date(end) },
+        });
+
+        // If no events are found we invalidate
+        if (!events.length) {
+            return { result: false, reason: 'No events found for this account' };
+        }
+
+        // If events are found we validate true
+        else {
+            return { result: true, reason: '' };
+        }
+    }
+
+    private async findIdentities({ quest, account }: { quest: TQuestDaily; account: TAccount }) {
+        return await Identity.find({ sub: account.sub, poolId: quest.poolId });
+    }
+
+    private async findEntries({ account, quest }: { account: TAccount; quest: TQuestDaily }) {
+        const claims = [];
+        const now = Date.now(),
+            start = now - ONE_DAY_MS,
+            end = now;
+
+        let lastEntry = await this.getLastEntry(account, quest, start, end);
+        if (!lastEntry) return [];
+        claims.push(lastEntry);
+
+        while (lastEntry) {
+            const timestamp = new Date(lastEntry.createdAt).getTime();
+            lastEntry = await QuestDailyEntry.findOne({
+                questId: quest._id,
+                sub: account.sub,
+                createdAt: {
+                    $gt: new Date(timestamp - ONE_DAY_MS * 2),
+                    $lt: new Date(timestamp - ONE_DAY_MS),
+                },
+            });
+            if (!lastEntry) break;
+            claims.push(lastEntry);
+        }
+
+        return claims;
+    }
+
+    private async getLastEntry(account: TAccount, quest: TQuestDaily, start: number, end: number) {
+        let lastEntry = await QuestDailyEntry.findOne({
+            questId: quest._id,
+            sub: account.sub,
+            createdAt: { $gt: new Date(start), $lt: new Date(end) },
+        });
+
+        if (!lastEntry) {
+            lastEntry = await QuestDailyEntry.findOne({
+                questId: quest._id,
+                sub: account.sub,
+                createdAt: { $gt: new Date(start - ONE_DAY_MS), $lt: new Date(end - ONE_DAY_MS) },
+            });
+        }
+        return lastEntry;
+    }
+}
diff --git a/apps/api/src/app/services/QuestDiscordService.ts b/apps/api/src/app/services/QuestDiscordService.ts
new file mode 100644
index 000000000..d746ff5b4
--- /dev/null
+++ b/apps/api/src/app/services/QuestDiscordService.ts
@@ -0,0 +1,193 @@
+import { QuestSocialEntry, QuestSocial, DiscordMessage } from '@thxnetwork/api/models';
+import { QuestSocialRequirement } from '@thxnetwork/common/enums';
+import { IQuestService } from './interfaces/IQuestService';
+import { requirementMap } from './maps/quests';
+import QuestSocialService from './QuestSocialService';
+import QuestService from './QuestService';
+
+type TRestartDates = { now: Date; start: Date; endDay: Date; end: Date };
+
+export default class QuestDiscordService implements IQuestService {
+    models = {
+        quest: QuestSocial,
+        entry: QuestSocialEntry,
+    };
+
+    findEntryMetadata(options: { quest: TQuestSocial }) {
+        return {};
+    }
+
+    async decorate({
+        quest,
+        account,
+        data,
+    }: {
+        quest: TQuestSocial;
+        account: TAccount;
+        data: Partial<TQuestSocialEntry>;
+    }): Promise<
+        TQuestSocial & {
+            messages: TDiscordMessage[];
+            restartDates: TRestartDates;
+            amount: number;
+            isAvailable: boolean;
+        }
+    > {
+        const amount = await this.getAmount({ quest, account });
+        const isAvailable = await this.isAvailable({ quest, account, data });
+        const interactionMap = {
+            [QuestSocialRequirement.DiscordMessage]: this.getDiscordMessageParams.bind(this),
+            [QuestSocialRequirement.DiscordGuildJoined]: this.getDiscordParams.bind(this),
+            [QuestSocialRequirement.DiscordGuildRole]: this.getDiscordParams.bind(this),
+        };
+        const extraParams = await interactionMap[quest.interaction]({ quest, account });
+
+        return {
+            ...quest,
+            amount,
+            isAvailable: isAvailable.result,
+            contentMetadata: quest.contentMetadata && JSON.parse(quest.contentMetadata),
+            ...extraParams,
+        };
+    }
+
+    async isAvailable({
+        quest,
+        account,
+    }: {
+        quest: TQuestSocial;
+        account?: TAccount;
+        data: Partial<TQuestSocialEntry>;
+    }): Promise<TValidationResult> {
+        const map = {
+            [QuestSocialRequirement.DiscordMessage]: this.isAvailableMessage.bind(this),
+            [QuestSocialRequirement.DiscordGuildJoined]: this.isAvailableDefault.bind(this),
+            [QuestSocialRequirement.DiscordGuildRole]: this.isAvailableDefault.bind(this),
+        };
+        return await map[quest.interaction]({ quest, account });
+    }
+
+    private async isAvailableDefault({
+        quest,
+        account,
+        data,
+    }: {
+        quest: TQuestSocial;
+        account?: TAccount;
+        data: Partial<TQuestSocialEntry>;
+    }) {
+        if (!account) return { result: true, reason: '' };
+
+        // We use the default more generic QuestSocialService here since we want to
+        // validate for platformUserIds as well
+        return await new QuestSocialService().isAvailable({ quest, account, data });
+    }
+
+    private async isAvailableMessage({ quest, account }: { quest: TQuestSocial; account?: TAccount }) {
+        const { pointsAvailable } = await this.getMessagePoints({ quest, account });
+        const isAvailable = pointsAvailable > 0;
+        if (isAvailable) return { result: true, reason: '' };
+
+        return { result: false, reason: 'You have not earned any points with messages yet.' };
+    }
+
+    async getAmount({ account, quest }: { quest: TQuestSocial; account?: TAccount }): Promise<number> {
+        const interactionMap = {
+            [QuestSocialRequirement.DiscordMessage]: this.getMessagePoints.bind(this),
+            [QuestSocialRequirement.DiscordGuildJoined]: this.getPoints.bind(this),
+            [QuestSocialRequirement.DiscordGuildRole]: this.getPoints.bind(this),
+        };
+        const { pointsAvailable } = await interactionMap[quest.interaction]({ quest, account });
+        return pointsAvailable;
+    }
+
+    async getValidationResult(options: {
+        quest: TQuestSocial;
+        account: TAccount;
+        data: Partial<TQuestSocialEntry>;
+    }): Promise<TValidationResult> {
+        if (!options.quest.interaction) return { result: false, reason: '' };
+        return await requirementMap[options.quest.interaction](options.account, options.quest);
+    }
+
+    private getRestartDates(quest: TQuestSocial) {
+        const { days } = JSON.parse(quest.contentMetadata);
+        const now = new Date();
+        const questCreatedAt = new Date(quest.createdAt);
+        const totalDaysRunning = Math.floor(
+            Math.ceil(now.getTime() / 1000 - questCreatedAt.getTime() / 1000) / 60 / 60 / 24,
+        );
+        const daysRunning = totalDaysRunning % days;
+        const msRunning = daysRunning * 24 * 60 * 60 * 1000;
+
+        const start = new Date(now.getTime() - msRunning);
+        start.setUTCHours(0, 0, 0, 0);
+
+        const end = new Date(start.getTime() + days * 24 * 60 * 60 * 1000);
+        const endDay = new Date(now);
+        endDay.setUTCHours(23, 59, 59, 999);
+
+        return { now, start, endDay, end };
+    }
+
+    private async getDiscordParams({ quest }: { quest: TQuestSocial; account: TAccount }) {
+        return { pointsAvailable: quest.amount };
+    }
+
+    private async getDiscordMessageParams({ quest, account }: { quest: TQuestSocial; account: TAccount }) {
+        const restartDates = this.getRestartDates(quest);
+        const messages = await this.getMessages({ account, quest, start: restartDates.start });
+        const points = await this.getMessagePoints({
+            quest,
+            account,
+        });
+
+        return {
+            restartDates,
+            messages,
+            ...points,
+        };
+    }
+
+    private async getMessages({ quest, account, start }: { quest: TQuestSocial; account: TAccount; start: Date }) {
+        if (!account) return [];
+
+        const userId = QuestService.findUserIdForInteraction(account, quest.interaction);
+        return await DiscordMessage.find({
+            guildId: quest.content,
+            memberId: userId,
+            createdAt: { $gte: new Date(start).toISOString() },
+        });
+    }
+
+    private async getPoints({ quest }) {
+        return { pointsAvailable: quest.amount };
+    }
+
+    private async getMessagePoints({ quest, account }) {
+        if (!account) return { pointsAvailable: 0, pointsClaimed: 0 };
+
+        const { start, end } = this.getRestartDates(quest);
+        const platformUserId = QuestService.findUserIdForInteraction(account, quest.interaction);
+        const claims = await QuestSocialEntry.find({
+            'questId': String(quest._id),
+            'metadata.platformUserId': platformUserId,
+            'createdAt': {
+                $gte: start,
+                $lt: end,
+            },
+        }).sort({ createdAt: -1 });
+        const [claim] = claims;
+        const pointsClaimed = claims.reduce((total, claim) => total + Number(claim.amount), 0);
+
+        // Only find messages created after the last claim if one exists
+        const messages = await DiscordMessage.find({
+            guildId: quest.content,
+            memberId: platformUserId,
+            createdAt: { $gte: claim ? claim.createdAt : start, $lt: end },
+        });
+        const pointsAvailable = messages.length * quest.amount;
+
+        return { pointsClaimed, pointsAvailable };
+    }
+}
diff --git a/apps/api/src/app/services/QuestGitcoinService.ts b/apps/api/src/app/services/QuestGitcoinService.ts
new file mode 100644
index 000000000..73825fe5b
--- /dev/null
+++ b/apps/api/src/app/services/QuestGitcoinService.ts
@@ -0,0 +1,75 @@
+import { QuestGitcoin, QuestGitcoinEntry } from '@thxnetwork/api/models';
+import { IQuestService } from './interfaces/IQuestService';
+import GitcoinService from './GitcoinService';
+
+export default class QuestGitcoinService implements IQuestService {
+    models = {
+        quest: QuestGitcoin,
+        entry: QuestGitcoinEntry,
+    };
+
+    findEntryMetadata(options: { quest: TQuestGitcoin }) {
+        return {};
+    }
+
+    async decorate({
+        quest,
+        account,
+        data,
+    }: {
+        quest: TQuestGitcoin;
+        account?: TAccount;
+        data: Partial<TQuestGitcoinEntry>;
+    }): Promise<TQuestGitcoin & { isAvailable: boolean }> {
+        const isAvailable = await this.isAvailable({ quest, account, data });
+        return { ...quest, isAvailable: isAvailable.result };
+    }
+
+    async isAvailable({
+        quest,
+        account,
+        data,
+    }: {
+        quest: TQuestGitcoin;
+        account?: TAccount;
+        data: Partial<TQuestGitcoinEntry>;
+    }): Promise<TValidationResult> {
+        if (!account) return { result: true, reason: '' };
+
+        const ids: { [key: string]: string }[] = [{ sub: account.sub }];
+        if (data.metadata && data.metadata.address) ids.push({ 'metadata.address': data.metadata.address });
+
+        const isCompleted = await QuestGitcoinEntry.exists({
+            questId: quest._id,
+            $or: ids,
+        });
+        if (!isCompleted) return { result: true, reason: '' };
+
+        return { result: false, reason: 'You have completed this quest with this account and/or address already.' };
+    }
+
+    async getAmount({ quest }: { quest: TQuestGitcoin; account: TAccount }): Promise<number> {
+        return quest.amount;
+    }
+
+    async getValidationResult({
+        quest,
+        data,
+    }: {
+        quest: TQuestGitcoin;
+        account: TAccount;
+        data: Partial<TQuestGitcoinEntry>;
+    }): Promise<TValidationResult> {
+        if (!data.metadata.address) return { result: false, reason: 'Could not find an address during validation.' };
+        if (data.metadata.score < quest.score) {
+            const score = data.metadata.score.toString() || 0;
+            const reason = `Your score ${score}/100 does not meet the minimum of ${quest.score}/100.`;
+            return { result: false, reason };
+        }
+        if (data.metadata.score >= quest.score) return { result: true, reason: '' };
+    }
+
+    async getScore(scorerId: number, address: string) {
+        return await GitcoinService.getScoreUniqueHumanity(scorerId, address);
+    }
+}
diff --git a/apps/api/src/app/services/QuestInviteService.ts b/apps/api/src/app/services/QuestInviteService.ts
new file mode 100644
index 000000000..61c31393b
--- /dev/null
+++ b/apps/api/src/app/services/QuestInviteService.ts
@@ -0,0 +1,44 @@
+import { QuestInvite, QuestInviteEntry } from '@thxnetwork/api/models';
+import { IQuestService } from './interfaces/IQuestService';
+
+export default class QuestInviteService implements IQuestService {
+    models = {
+        quest: QuestInvite,
+        entry: QuestInviteEntry,
+    };
+
+    findEntryMetadata(options: { quest: TQuestInvite }) {
+        return {};
+    }
+
+    async decorate({ quest }: { quest: TQuestInvite; data: Partial<TQuestInviteEntry> }): Promise<TQuestInvite> {
+        return {
+            ...quest,
+            pathname: quest.pathname,
+            successUrl: quest.successUrl,
+        };
+    }
+
+    async isAvailable(options: {
+        quest: TQuestInvite;
+        account?: TAccount;
+        data: Partial<TQuestInviteEntry>;
+    }): Promise<TValidationResult> {
+        return { result: false, reason: 'Not implemented' };
+    }
+
+    async getAmount({ quest }: { quest: TQuestInvite; account: TAccount }): Promise<number> {
+        return quest.amount;
+    }
+
+    async getValidationResult(options: {
+        quest: TQuestInvite;
+        account: TAccount;
+        data: Partial<TQuestInviteEntry>;
+    }): Promise<TValidationResult> {
+        return {
+            result: false,
+            reason: 'Sorry, support not yet implemented...',
+        };
+    }
+}
diff --git a/apps/api/src/app/services/QuestService.ts b/apps/api/src/app/services/QuestService.ts
new file mode 100644
index 000000000..e33ee0b33
--- /dev/null
+++ b/apps/api/src/app/services/QuestService.ts
@@ -0,0 +1,260 @@
+import { JobType, QuestSocialRequirement, QuestVariant } from '@thxnetwork/common/enums';
+import { PoolDocument, Participant } from '@thxnetwork/api/models';
+import { v4 } from 'uuid';
+import { agenda } from '../util/agenda';
+import { logger } from '../util/logger';
+import { Job } from '@hokify/agenda';
+import { serviceMap } from './interfaces/IQuestService';
+import { tokenInteractionMap } from './maps/quests';
+import { NODE_ENV } from '../config/secrets';
+import PoolService from './PoolService';
+import NotificationService from './NotificationService';
+import PointBalanceService from './PointBalanceService';
+import LockService from './LockService';
+import ImageService from './ImageService';
+import AccountProxy from '../proxies/AccountProxy';
+import ParticipantService from './ParticipantService';
+import THXService from './THXService';
+
+export default class QuestService {
+    static async count({ poolId }) {
+        const variants = Object.keys(QuestVariant).filter((v) => !isNaN(Number(v)));
+        const counts = await Promise.all(
+            variants.map(async (variant: string) => {
+                const Quest = serviceMap[variant].models.quest;
+                return await Quest.countDocuments({ poolId, isPublished: true });
+            }),
+        );
+        return counts.reduce((acc, count) => acc + count, 0);
+    }
+
+    static async list({ pool, data, account }: { pool: PoolDocument; data: Partial<TQuestEntry>; account?: TAccount }) {
+        const questVariants = Object.keys(QuestVariant).filter((v) => !isNaN(Number(v)));
+        const author = await AccountProxy.findById(pool.sub);
+        const callback: any = async (variant: QuestVariant) => {
+            const Quest = serviceMap[variant].models.quest;
+            const quests = await Quest.find({
+                poolId: pool._id,
+                variant,
+                isPublished: true,
+                $or: [
+                    // Include quests with expiryDate less than or equal to now
+                    { expiryDate: { $exists: true, $gte: new Date() } },
+                    // Include quests with no expiryDate
+                    { expiryDate: { $exists: false } },
+                ],
+            });
+
+            return await Promise.all(
+                quests.map(async (q) => {
+                    try {
+                        const quest = q.toJSON() as TQuest;
+                        const decorated = await serviceMap[variant].decorate({ quest, account, data });
+                        const isLocked = await LockService.getIsLocked(quest.locks, account);
+                        const isExpired = this.isExpired(quest);
+                        const QuestEntry = serviceMap[variant].models.entry;
+                        const distinctSubs = await QuestEntry.countDocuments({ questId: q.id }).distinct('sub');
+                        return {
+                            ...decorated,
+                            entryCount: distinctSubs.length,
+                            author: { username: author.username },
+                            isLocked,
+                            isExpired,
+                        };
+                    } catch (error) {
+                        logger.error(error);
+                    }
+                }),
+            );
+        };
+
+        return await Promise.all(questVariants.map(callback));
+    }
+
+    static async update(quest: TQuest, updates: Partial<TQuest>, file?: Express.Multer.File) {
+        if (file) {
+            updates.image = await ImageService.upload(file);
+        }
+
+        // We only want to notify when the quest is set to published (and not updated while published already)
+        if (updates.isPublished && Boolean(updates.isPublished) !== quest.isPublished) {
+            await NotificationService.notify(quest.variant, {
+                ...quest,
+                ...updates,
+                image: updates.image || quest.image,
+            });
+        }
+
+        return await this.updateById(quest.variant, quest._id, updates);
+    }
+
+    static async create(variant: QuestVariant, poolId: string, data: Partial<TQuest>, file?: Express.Multer.File) {
+        if (file) {
+            data.image = await ImageService.upload(file);
+        }
+
+        const Quest = serviceMap[variant].models.quest;
+        const quest = await Quest.create({ ...data, poolId, variant, uuid: v4() });
+
+        if (data.isPublished) {
+            await NotificationService.notify(variant, quest);
+        }
+
+        return quest;
+    }
+
+    static findById(variant: QuestVariant, questId: string) {
+        const Quest = serviceMap[variant].models.quest;
+        return Quest.findById(questId);
+    }
+
+    static updateById(variant: QuestVariant, questId: string, options: Partial<TQuest>) {
+        const Quest = serviceMap[variant].models.quest;
+        return Quest.findByIdAndUpdate(questId, options, { new: true });
+    }
+
+    static getAmount(variant: QuestVariant, quest: TQuest, account: TAccount) {
+        return serviceMap[variant].getAmount({ quest, account });
+    }
+
+    static isExpired(quest: TQuest) {
+        return quest.expiryDate ? new Date(quest.expiryDate).getTime() < Date.now() : false;
+    }
+
+    static async isAvailable(
+        variant: QuestVariant,
+        options: {
+            quest: TQuest;
+            account?: TAccount;
+            data: Partial<TQuestEntry & { rpc: string }>;
+        },
+    ): Promise<TValidationResult> {
+        if (!options.quest.isPublished) {
+            return { result: false, reason: 'Quest has not been published.' };
+        }
+
+        const isExpired = this.isExpired(options.quest);
+        if (isExpired) return { result: false, reason: 'Quest has expired.' };
+
+        const isLocked = await LockService.getIsLocked(options.quest.locks, options.account);
+        if (isLocked) return { result: false, reason: 'Quest is locked.' };
+
+        return await serviceMap[variant].isAvailable(options);
+    }
+
+    static async isRealUser(
+        variant: QuestVariant,
+        options: { quest: TQuest; account: TAccount; data: Partial<TQuestEntry & { recaptcha: string }> },
+    ) {
+        // Skip recaptcha check in test environment
+        if (NODE_ENV === 'test') return { result: true, reasons: '' };
+
+        // Define the recaptcha action for this quest variant
+        const recaptchaAction = `QUEST_${QuestVariant[variant].toUpperCase()}_ENTRY_CREATE`;
+
+        // Update the participant's risk score
+        const { riskAnalysis } = await ParticipantService.updateRiskScore(options.account, options.quest.poolId, {
+            token: options.data.recaptcha,
+            recaptchaAction,
+        });
+
+        logger.info(
+            'ReCaptcha result' +
+                JSON.stringify({
+                    sub: options.account.sub,
+                    poolId: options.quest.poolId,
+                    riskAnalysis,
+                    recaptchaAction,
+                }),
+        );
+
+        // Defaults: 0.1, 0.3, 0.7 and 0.9. Ranges from 0 (Bot) to 1 (User)
+        if (riskAnalysis.score >= 0.9) {
+            return { result: true, reasons: '' };
+        }
+
+        return { result: false, reason: 'This request has been indentified as potentially automated.' };
+    }
+
+    static async getValidationResult(
+        variant: QuestVariant,
+        options: {
+            quest: TQuest;
+            account: TAccount;
+            data: Partial<TQuestEntry>;
+        },
+    ) {
+        const isAvailable = await this.isAvailable(variant, options);
+        if (!isAvailable.result) return isAvailable;
+
+        return await serviceMap[variant].getValidationResult(options);
+    }
+
+    static async createEntryJob(job: Job) {
+        try {
+            const { variant, questId, sub, data } = job.attrs.data as any;
+            const Entry = serviceMap[Number(variant)].models.entry;
+            const account = await AccountProxy.findById(sub);
+            const quest = await this.findById(variant, questId);
+            const pool = await PoolService.getById(quest.poolId);
+            const amount = await this.getAmount(variant, quest, account);
+
+            // Test availabily of quest once more as it could be completed by a job that was scheduled already
+            // if the jobs were created in parallel.
+            const isAvailable = await this.isAvailable(variant, { quest, account, data });
+            if (!isAvailable.result) throw new Error(isAvailable.reason);
+
+            // Create the quest entry
+            const entry = await Entry.create({
+                ...data,
+                sub: account.sub,
+                amount,
+                questId: String(quest._id),
+                poolId: pool._id,
+                uuid: v4(),
+            } as TQuestEntry);
+            if (!entry) throw new Error('Entry creation failed.');
+
+            // Should make sure quest entry is properly created
+            await PointBalanceService.add(pool, account, amount);
+            await NotificationService.sendQuestEntryNotification(pool, quest, account, amount);
+
+            // Register THX onboarding campaign event
+            await THXService.createEvent(account, 'quest_entry_created');
+
+            // Update participant ranks async
+            agenda.now(JobType.UpdateParticipantRanks, { poolId: pool._id });
+        } catch (error) {
+            logger.error(error);
+        }
+    }
+
+    static findUserIdForInteraction(account: TAccount, interaction: QuestSocialRequirement) {
+        if (typeof interaction === 'undefined') return;
+        const { kind } = tokenInteractionMap[interaction];
+        const token = account.tokens.find((token) => token.kind === kind);
+
+        return token && token.userId;
+    }
+
+    static async findEntries(quest: TQuest, { page = 1, limit = 25 }: { page: number; limit: number }) {
+        const skip = (page - 1) * limit;
+        const Entry = serviceMap[quest.variant].models.entry;
+        const total = await Entry.countDocuments({ questId: quest._id });
+        const entries = await Entry.find({ questId: quest._id }).limit(limit).skip(skip);
+        const subs = entries.map((entry) => entry.sub);
+        const accounts = await AccountProxy.find({ subs });
+        const participants = await Participant.find({ poolId: quest.poolId });
+        const promises = entries.map(async (entry) => ParticipantService.decorate(entry, { accounts, participants }));
+        const results = await Promise.allSettled(promises);
+        const meta = await serviceMap[quest.variant].findEntryMetadata({ quest });
+
+        return {
+            total,
+            limit,
+            page,
+            meta,
+            results: results.filter((result) => result.status === 'fulfilled').map((result: any) => result.value),
+        };
+    }
+}
diff --git a/apps/api/src/app/services/QuestSocialService.ts b/apps/api/src/app/services/QuestSocialService.ts
new file mode 100644
index 000000000..289f6e03c
--- /dev/null
+++ b/apps/api/src/app/services/QuestSocialService.ts
@@ -0,0 +1,100 @@
+import { QuestSocial, QuestSocialDocument, QuestSocialEntry } from '@thxnetwork/api/models';
+import { WalletDocument } from '@thxnetwork/api/models/Wallet';
+import { IQuestService } from './interfaces/IQuestService';
+import { requirementMap } from './maps/quests';
+import { logger } from '../util/logger';
+import { QuestVariant } from '@thxnetwork/common/enums';
+
+export default class QuestSocialService implements IQuestService {
+    models = {
+        quest: QuestSocial,
+        entry: QuestSocialEntry,
+    };
+
+    async decorate({
+        quest,
+        account,
+        data,
+    }: {
+        quest: TQuestSocial;
+        account?: TAccount;
+        data: Partial<TQuestSocialEntry>;
+    }): Promise<TQuestSocial & { isAvailable: boolean }> {
+        const isAvailable = await this.isAvailable({ quest, account, data });
+
+        return {
+            ...quest,
+            isAvailable: isAvailable.result,
+            contentMetadata: quest.contentMetadata && JSON.parse(quest.contentMetadata),
+        };
+    }
+
+    async isAvailable({
+        quest,
+        account,
+        data,
+    }: {
+        quest: TQuestSocial;
+        account: TAccount;
+        data: Partial<TQuestSocialEntry>;
+    }): Promise<TValidationResult> {
+        if (!account) return { result: true, reason: '' };
+
+        // We validate for both here since there are entries that only contain a sub
+        // and should not be claimed again.
+        const ids: any[] = [{ sub: account.sub }];
+        if (data && data.metadata && data.metadata.platformUserId)
+            ids.push({ platformUserId: data.metadata.platformUserId });
+
+        // If no entry exist the quest is available
+        const isCompleted = await QuestSocialEntry.exists({
+            questId: quest._id,
+            $or: ids,
+        });
+        if (!isCompleted) return { result: true, reason: '' };
+
+        return { result: false, reason: 'You have completed this quest with this (connected) account already.' };
+    }
+
+    async getAmount({ quest }: { quest: TQuestSocial; wallet: WalletDocument; account: TAccount }): Promise<number> {
+        return quest.amount;
+    }
+
+    async getValidationResult({
+        quest,
+        account,
+    }: {
+        quest: TQuestSocial;
+        account: TAccount;
+        data: Partial<TQuestSocialEntry>;
+    }): Promise<TValidationResult> {
+        try {
+            // Check quest requirements
+            const validationResult = await requirementMap[quest.interaction](account, quest);
+            return validationResult || { result: true, reason: '' };
+        } catch (error) {
+            logger.error(error);
+            return { result: false, reason: 'We were unable to confirm the requirements for this quest.' };
+        }
+    }
+
+    async findEntryMetadata({ quest }: { quest: QuestSocialDocument }) {
+        const reachTotal = await this.getTwitterFollowerCount(quest);
+        const uniqueParticipantIds = await QuestSocialEntry.find({
+            questId: String(quest._id),
+        }).distinct('sub');
+
+        return { reachTotal, participantCount: uniqueParticipantIds.length };
+    }
+
+    async getTwitterFollowerCount(quest: QuestSocialDocument) {
+        if (quest.variant !== QuestVariant.Twitter) return;
+
+        const [result] = await QuestSocialEntry.aggregate([
+            { $match: { questId: String(quest._id) } },
+            { $group: { _id: null, totalFollowersCount: { $sum: '$publicMetrics.followersCount' } } },
+        ]);
+
+        return result ? result.totalFollowersCount : 0;
+    }
+}
diff --git a/apps/api/src/app/services/QuestWeb3Service.ts b/apps/api/src/app/services/QuestWeb3Service.ts
new file mode 100644
index 000000000..d38ea484b
--- /dev/null
+++ b/apps/api/src/app/services/QuestWeb3Service.ts
@@ -0,0 +1,116 @@
+import { ethers } from 'ethers';
+import { QuestWeb3Entry, QuestWeb3 } from '@thxnetwork/api/models';
+import { logger } from '@thxnetwork/api/util/logger';
+import { IQuestService } from './interfaces/IQuestService';
+import { BigNumber } from 'alchemy-sdk';
+
+export default class QuestWeb3Service implements IQuestService {
+    models = {
+        quest: QuestWeb3,
+        entry: QuestWeb3Entry,
+    };
+
+    findEntryMetadata(options: { quest: TQuestWeb3 }) {
+        return {};
+    }
+
+    async decorate({
+        quest,
+        account,
+        data,
+    }: {
+        quest: TQuestWeb3;
+        data: Partial<TQuestWeb3Entry>;
+        account?: TAccount;
+    }): Promise<TQuestWeb3 & { isAvailable: boolean }> {
+        const isAvailable = await this.isAvailable({ quest, account, data });
+
+        return {
+            ...quest,
+            isAvailable: isAvailable.result,
+            amount: quest.amount,
+            contracts: quest.contracts,
+            methodName: quest.methodName,
+            threshold: quest.threshold,
+        };
+    }
+
+    async isAvailable({
+        quest,
+        account,
+        data,
+    }: {
+        quest: TQuestWeb3;
+        account: TAccount;
+        data: Partial<TQuestWeb3Entry>;
+    }): Promise<TValidationResult> {
+        if (!account) return { result: true, reason: '' };
+
+        const ids: any[] = [{ sub: account.sub }];
+        if (data.metadata && data.metadata.address) ids.push({ 'metadata.address': data.metadata.address });
+
+        const isCompleted = await QuestWeb3Entry.exists({
+            questId: quest._id,
+            $or: ids,
+        });
+        if (!isCompleted) return { result: true, reason: '' };
+
+        return { result: false, reason: 'You have completed this quest with this account and/or address already.' };
+    }
+
+    async getAmount({ quest }: { quest: TQuestWeb3; account: TAccount }): Promise<number> {
+        return quest.amount;
+    }
+
+    async getValidationResult({
+        quest,
+        account,
+        data,
+    }: {
+        quest: TQuestWeb3;
+        account: TAccount;
+        data: Partial<TQuestWeb3Entry>;
+    }): Promise<TValidationResult> {
+        const isCompleted = await QuestWeb3Entry.exists({
+            questId: quest._id,
+            $or: [{ sub: account.sub }, { 'metadata.address': data.metadata.address }],
+        });
+        if (isCompleted) return { result: false, reason: 'You have claimed this quest already' };
+
+        const threshold = BigNumber.from(quest.threshold);
+        const result = BigNumber.from(data.metadata.callResult);
+        if (result.lt(threshold)) {
+            return { result: false, reason: 'Result does not meet the threshold' };
+        }
+
+        return { result: true, reason: '' };
+    }
+
+    static async getCallResult({
+        quest,
+        data,
+    }: {
+        quest: TQuestWeb3;
+        account: TAccount;
+        data: Partial<TQuestWeb3Entry>;
+    }) {
+        const { rpc, chainId, address } = data.metadata;
+        const contract = quest.contracts.find((c) => c.chainId === chainId);
+        if (!contract) return { result: false, reason: 'Smart contract not found.' };
+
+        const contractInstance = new ethers.Contract(
+            contract.address,
+            ['function ' + quest.methodName + '(address) view returns (uint256)'],
+            new ethers.providers.JsonRpcProvider(rpc),
+        );
+
+        try {
+            const value = await contractInstance[quest.methodName](address);
+
+            return { result: true, reason: '', value };
+        } catch (error) {
+            logger.error(error);
+            return { result: false, reason: `Smart contract call on ${quest.methodName} failed` };
+        }
+    }
+}
diff --git a/apps/api/src/app/services/QuestWebhookService.ts b/apps/api/src/app/services/QuestWebhookService.ts
new file mode 100644
index 000000000..798d4e0e3
--- /dev/null
+++ b/apps/api/src/app/services/QuestWebhookService.ts
@@ -0,0 +1,114 @@
+import {
+    QuestWebhook,
+    QuestWebhookDocument,
+    QuestWebhookEntry,
+    Identity,
+    WalletDocument,
+    Webhook,
+} from '@thxnetwork/api/models';
+import { IQuestService } from './interfaces/IQuestService';
+import WebhookService from './WebhookService';
+
+export default class QuestWebhookService implements IQuestService {
+    models = {
+        quest: QuestWebhook,
+        entry: QuestWebhookEntry,
+    };
+
+    async findEntryMetadata({ quest }: { quest: QuestWebhookDocument }) {
+        const uniqueParticipantIds = await QuestWebhookEntry.countDocuments({
+            questId: String(quest._id),
+        }).distinct('sub');
+
+        return { participantCount: uniqueParticipantIds.length };
+    }
+
+    async isAvailable({
+        quest,
+        account,
+    }: {
+        quest: QuestWebhookDocument;
+        wallet?: WalletDocument;
+        account?: TAccount;
+        data: Partial<TQuestWebhookEntry>;
+    }): Promise<TValidationResult> {
+        const entries = await this.findAllEntries({ quest, account });
+        if (entries.length) {
+            return { result: false, reason: 'Quest entry limit has been reached.' };
+        }
+
+        return { result: true, reason: '' };
+    }
+
+    async getAmount({ quest }: { quest: QuestWebhookDocument; wallet: WalletDocument; account: TAccount }) {
+        return quest.amount;
+    }
+
+    async decorate({
+        quest,
+        account,
+        data,
+    }: {
+        quest: QuestWebhookDocument;
+        account?: TAccount;
+        data: Partial<TQuestWebhookEntry>;
+    }) {
+        const entries = await this.findAllEntries({ quest, account });
+        const identities = await this.findIdentities({ quest, account });
+        const isAvailable = await this.isAvailable({ quest, account, data });
+
+        return {
+            ...quest,
+            identities,
+            isAvailable: isAvailable.result,
+            entries,
+        };
+    }
+
+    async getValidationResult({
+        quest,
+        account,
+    }: {
+        quest: QuestWebhookDocument;
+        account: TAccount;
+        data: Partial<TQuestWebhookEntry>;
+    }): Promise<{ reason: string; result: boolean; data?: any }> {
+        // See if there are identities
+        const identities = await this.findIdentities({ quest, account });
+        if (!identities.length) {
+            return {
+                result: false,
+                reason: 'No identity connected to this account. Please ask for this in your community!',
+            };
+        }
+
+        const webhook = await Webhook.findById(quest.webhookId);
+        if (!webhook) return { result: false, reason: 'Webhook no longer available.' };
+
+        const data = await WebhookService.request(webhook, account, quest.metadata);
+        if (!data) return { result: false, reason: 'Webhook validation returned nothing.' };
+        if (!data.result) return { result: false, reason: 'Webhook validation was negative.' };
+        if (data.result) {
+            return {
+                result: true,
+                reason: '',
+                data,
+            };
+        }
+
+        return { result: false, reason: 'Webhook validation request failed.' };
+    }
+
+    private async findAllEntries({ quest, account }: { quest: QuestWebhookDocument; account: TAccount }) {
+        if (!account) return [];
+        return await this.models.entry.find({
+            questId: quest._id,
+            sub: account.sub,
+        });
+    }
+
+    private async findIdentities({ quest, account }: { quest: QuestWebhookDocument; account: TAccount }) {
+        if (!account || !account.sub) return [];
+        return await Identity.find({ poolId: quest.poolId, sub: account.sub });
+    }
+}
diff --git a/apps/api/src/app/services/ReCaptchaService.ts b/apps/api/src/app/services/ReCaptchaService.ts
new file mode 100644
index 000000000..7af44d027
--- /dev/null
+++ b/apps/api/src/app/services/ReCaptchaService.ts
@@ -0,0 +1,39 @@
+import axios from 'axios';
+import { GCLOUD_PROJECT_ID, GCLOUD_RECAPTCHA_API_KEY, GCLOUD_RECAPTCHA_SITE_KEY } from '../config/secrets';
+import { BadRequestError } from '../util/errors';
+
+export default class ReCaptchaService {
+    static async getRiskAnalysis({ token, recaptchaAction }) {
+        const url = new URL('https://recaptchaenterprise.googleapis.com');
+        url.pathname = `/v1/projects/${GCLOUD_PROJECT_ID}/assessments`;
+
+        const { data } = await axios({
+            method: 'POST',
+            url: url.toString(),
+            data: {
+                event: {
+                    token,
+                    expectedAction: recaptchaAction,
+                    siteKey: GCLOUD_RECAPTCHA_SITE_KEY,
+                },
+            },
+            params: {
+                key: GCLOUD_RECAPTCHA_API_KEY,
+            },
+        });
+
+        // Check if the token is valid.
+        if (!data.tokenProperties.valid) {
+            throw new BadRequestError('Invalid ReCAPTCHA token.');
+        }
+
+        // Check if the expected action was executed.
+        if (data.tokenProperties.action !== recaptchaAction) {
+            throw new BadRequestError('Invalid ReCAPTCHA action.');
+        }
+
+        // Get the risk score and the reason(s).
+        // https://cloud.google.com/recaptcha-enterprise/docs/interpret-assessment
+        return data.riskAnalysis;
+    }
+}
diff --git a/apps/api/src/app/services/RewardCoinService.ts b/apps/api/src/app/services/RewardCoinService.ts
new file mode 100644
index 000000000..405895c06
--- /dev/null
+++ b/apps/api/src/app/services/RewardCoinService.ts
@@ -0,0 +1,140 @@
+import {
+    ERC20,
+    ERC20Document,
+    RewardCoin,
+    RewardCoinDocument,
+    Transaction,
+    WalletDocument,
+} from '@thxnetwork/api/models';
+import { RewardCoinPayment } from '@thxnetwork/api/models';
+import { IRewardService } from './interfaces/IRewardService';
+import { ChainId, ERC20Type, TransactionState } from '@thxnetwork/common/enums';
+import { BigNumber } from 'alchemy-sdk';
+import AccountProxy from '../proxies/AccountProxy';
+import ERC20Service from './ERC20Service';
+import MailService from './MailService';
+import PoolService from './PoolService';
+import { toWei } from 'web3-utils';
+
+export default class RewardCoinService implements IRewardService {
+    models = {
+        reward: RewardCoin,
+        payment: RewardCoinPayment,
+    };
+
+    async decorate({ reward }) {
+        const erc20 = await ERC20.findById(reward.erc20Id);
+        return { ...reward.toJSON(), erc20 };
+    }
+
+    async decoratePayment(payment: TBaseRewardPayment) {
+        return payment;
+    }
+
+    findById(id: string) {
+        return this.models.reward.findById(id);
+    }
+
+    async create(data: Partial<TRewardCoin>) {
+        const erc20 = await this.getERC20(data.erc20Id);
+        await this.addMinter(erc20, data.poolId);
+
+        return await this.models.reward.create(data);
+    }
+
+    async update(reward: RewardCoinDocument, updates: Partial<TRewardCoin>) {
+        const erc20 = await this.getERC20(updates.erc20Id);
+        await this.addMinter(erc20, reward.poolId);
+
+        return await this.models.reward.findByIdAndUpdate(reward._id, updates, { new: true });
+    }
+
+    async remove(reward: RewardCoinDocument) {
+        await this.models.reward.findOneAndDelete(reward._id);
+    }
+
+    async createPayment({
+        reward,
+        safe,
+        wallet,
+    }: {
+        reward: TRewardCoin;
+        safe: WalletDocument;
+        wallet?: WalletDocument;
+    }) {
+        if (!wallet) return { result: false, reason: 'Wallet not found' };
+
+        const erc20 = await ERC20.findById(reward.erc20Id);
+        if (!erc20) return { result: false, reason: 'ERC20 not found' };
+
+        // TODO Wei should be determined in the FE
+        const amount = toWei(reward.amount as string);
+
+        // Transfer ERC20 from safe to wallet
+        await ERC20Service.transferFrom(erc20, safe, wallet.address, amount);
+
+        // Register the payment
+        await RewardCoinPayment.create({
+            rewardId: reward._id,
+            sub: wallet.sub,
+            walletId: wallet._id,
+            poolId: reward.poolId,
+            amount: reward.pointPrice,
+        });
+    }
+
+    async getValidationResult({ reward, safe }: { reward: RewardCoinDocument; safe: WalletDocument }) {
+        const erc20 = await ERC20.findById(reward.erc20Id);
+        if (!erc20) throw new Error('ERC20 not found');
+
+        // Check if there are pending transactions that are not mined or failed.
+        const txs = await Transaction.find({
+            walletId: safe.id,
+            $or: [
+                { state: TransactionState.Confirmed },
+                { state: TransactionState.Sent },
+                { state: TransactionState.Queued },
+            ],
+        }).sort({ createdAt: 'asc' });
+        if (txs.length) {
+            return { result: false, reason: `Found ${txs.length} pending transactions, please try again later.` };
+        }
+
+        // Check balances
+        const balanceOfPool = await erc20.contract.methods.balanceOf(safe.address).call();
+        const isTransferable = [ERC20Type.Unknown, ERC20Type.Limited].includes(erc20.type);
+        const isBalanceInsufficient = BigNumber.from(balanceOfPool).lt(BigNumber.from(toWei(reward.amount)));
+
+        // Notifiy the campaign owner if token is transferrable and balance is insufficient
+        if (isTransferable && isBalanceInsufficient) {
+            const owner = await AccountProxy.findById(safe.sub);
+            const html = `Not enough ${erc20.symbol} available in campaign contract ${safe.address}. Please top up on ${
+                ChainId[erc20.chainId]
+            }`;
+
+            // Send email to campaign owner
+            await MailService.send(owner.email, `⚠️ Out of ${erc20.symbol}!"`, html);
+
+            return {
+                result: false,
+                reason: `We have notified the campaign owner that there is insufficient ${erc20.symbol} in the campaign wallet. Please try again later!`,
+            };
+        }
+
+        return { result: true, reason: '' };
+    }
+
+    private getERC20(erc20Id: TERC20) {
+        return ERC20.findById(erc20Id);
+    }
+
+    private async addMinter(erc20: ERC20Document, poolId: string) {
+        if (erc20.type !== ERC20Type.Unlimited) return;
+
+        const { safe } = await PoolService.getById(poolId);
+        const isMinter = await ERC20Service.isMinter(erc20, safe.address);
+        if (!isMinter) {
+            await ERC20Service.addMinter(erc20, safe.address);
+        }
+    }
+}
diff --git a/apps/api/src/app/services/RewardCouponService.ts b/apps/api/src/app/services/RewardCouponService.ts
new file mode 100644
index 000000000..403b70151
--- /dev/null
+++ b/apps/api/src/app/services/RewardCouponService.ts
@@ -0,0 +1,75 @@
+import { CouponCode, RewardCoupon, RewardCouponPayment } from '../models';
+import { IRewardService } from './interfaces/IRewardService';
+
+export default class RewardCouponService implements IRewardService {
+    models = {
+        reward: RewardCoupon,
+        payment: RewardCouponPayment,
+    };
+
+    async decorate({ reward }) {
+        const couponCodes = await CouponCode.find({ couponRewardId: reward._id });
+        const progress = {
+            count: await this.models.payment.countDocuments({
+                rewardId: reward._id,
+            }),
+            limit: couponCodes.length,
+        };
+
+        return { ...reward.toJSON(), progress, limit: couponCodes.length };
+    }
+
+    async decoratePayment(payment: TRewardPayment) {
+        const code = await CouponCode.findById(payment.couponCodeId);
+        return { ...payment.toJSON(), code: code && code.code };
+    }
+
+    async getValidationResult({ reward }: { reward: TReward; account?: TAccount }) {
+        const couponCode = await CouponCode.findOne({ couponRewardId: String(reward._id), sub: { $exists: false } });
+        if (!couponCode) return { result: false, reason: 'No more coupon codes available' };
+
+        return { result: true, reason: '' };
+    }
+
+    async create(data: Partial<TReward>) {
+        const reward = await this.models.reward.create(data);
+        await this.createCouponCodes(reward, data.codes);
+        return reward;
+    }
+
+    private async createCouponCodes(reward: TRewardCoupon, codes: string[]) {
+        await Promise.all(
+            codes.map(async (code: string) => await CouponCode.create({ code, couponRewardId: reward._id })),
+        );
+    }
+
+    async update(reward: TReward, updates: Partial<TReward>): Promise<TReward> {
+        await this.createCouponCodes(reward, updates.codes);
+        return this.models.reward.findByIdAndUpdate(reward, updates, { new: true });
+    }
+
+    remove(reward: TReward): Promise<void> {
+        return this.models.reward.findByIdAndDelete(reward._id);
+    }
+
+    findById(id: string): Promise<TReward> {
+        return this.models.reward.findById(id);
+    }
+
+    async createPayment({ reward, account }: { reward: TRewardNFT; account: TAccount }) {
+        const couponCode = await CouponCode.findOne({ couponRewardId: reward._id, sub: { $exists: false } });
+        if (!couponCode) return { result: false, reason: 'No more coupon codes available' };
+
+        // Change owner of couponCode
+        await couponCode.updateOne({ sub: account.sub });
+
+        // Register payment
+        await this.models.payment.create({
+            couponCodeId: couponCode._id,
+            rewardId: reward.id,
+            sub: account.sub,
+            poolId: reward.poolId,
+            amount: reward.pointPrice,
+        });
+    }
+}
diff --git a/apps/api/src/app/services/RewardCustomService.ts b/apps/api/src/app/services/RewardCustomService.ts
new file mode 100644
index 000000000..5937849e2
--- /dev/null
+++ b/apps/api/src/app/services/RewardCustomService.ts
@@ -0,0 +1,68 @@
+import { Identity, RewardCustom, RewardCustomPayment, Webhook } from '../models';
+import { IRewardService } from './interfaces/IRewardService';
+import { Event } from '@thxnetwork/common/enums';
+import WebhookService from './WebhookService';
+
+export default class RewardCustomService implements IRewardService {
+    models = {
+        reward: RewardCustom,
+        payment: RewardCustomPayment,
+    };
+
+    async decorate({ reward, account }) {
+        const identities = account ? await Identity.find({ poolId: reward.poolId, sub: account.sub }) : [];
+        return { ...reward.toJSON(), isDisabled: !identities.length };
+    }
+
+    async decoratePayment(payment: TRewardPayment): Promise<TRewardPayment> {
+        return payment;
+    }
+
+    async getValidationResult({ reward, account }: { reward: TReward; account?: TAccount }) {
+        const identities = account ? await Identity.find({ poolId: reward.poolId, sub: account.sub }) : [];
+        if (!identities.length) return { result: false, reason: 'No identity connected for this campaign.' };
+
+        return { result: true, reason: '' };
+    }
+
+    create(data: Partial<TReward>) {
+        return this.models.reward.create(data);
+    }
+
+    update(reward: TReward, updates: Partial<TReward>): Promise<TReward> {
+        return this.models.reward.findByIdAndUpdate(reward._id, updates, { new: true });
+    }
+
+    remove(reward: TReward): Promise<void> {
+        return this.models.reward.findByIdAndDelete(reward._id);
+    }
+
+    findById(id: string): Promise<TReward> {
+        return this.models.reward.findById(id);
+    }
+
+    async createPayment({
+        reward,
+        account,
+    }: {
+        reward: TReward;
+        account: TAccount;
+    }): Promise<TValidationResult | void> {
+        const webhook = await Webhook.findById(reward.webhookId);
+        if (!webhook) return { result: false, reason: 'Webhook not found.' };
+
+        // Call the webhook with known account identities for this campaign and optional metadata
+        await WebhookService.requestAsync(webhook, account.sub, {
+            type: Event.RewardCustomPayment,
+            data: { customRewardId: reward._id, metadata: reward.metadata },
+        });
+
+        // Register the payment
+        await this.models.payment.create({
+            rewardId: reward.id,
+            poolId: reward.poolId,
+            sub: account.sub,
+            amount: reward.pointPrice,
+        });
+    }
+}
diff --git a/apps/api/src/app/services/RewardDiscordRoleService.ts b/apps/api/src/app/services/RewardDiscordRoleService.ts
new file mode 100644
index 000000000..1fe5297b7
--- /dev/null
+++ b/apps/api/src/app/services/RewardDiscordRoleService.ts
@@ -0,0 +1,95 @@
+import { AccessTokenKind } from '@thxnetwork/common/enums';
+import { RewardDiscordRole, RewardDiscordRolePayment } from '../models';
+import { IRewardService } from './interfaces/IRewardService';
+import { discordColorToHex } from '../util/discord';
+import DiscordService from './DiscordService';
+
+export default class RewardDiscordRoleService implements IRewardService {
+    models = {
+        reward: RewardDiscordRole,
+        payment: RewardDiscordRolePayment,
+    };
+
+    async decorate({ reward, account }) {
+        const token = account && account.tokens.find(({ kind }) => kind === AccessTokenKind.Discord);
+        return { ...reward.toJSON(), isDisabled: !token };
+    }
+
+    async decoratePayment(payment: TRewardPayment): Promise<TRewardDiscordRolePayment> {
+        const reward = await this.models.reward.findById(payment.rewardId);
+        const guild = reward && (await DiscordService.getGuild(reward.poolId));
+        const role = guild && reward && (await DiscordService.getRole(guild.id, reward.discordRoleId));
+        const discordServerURL = guild && `https://discordapp.com/channels/${guild.id}/`;
+
+        return {
+            ...payment.toJSON(),
+            discordServerURL,
+            guild: guild && {
+                name: guild.name,
+                icon: guild.icon && `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.png`,
+            },
+            role: role && {
+                name: role.name,
+                color: discordColorToHex(role.color),
+            },
+        };
+    }
+
+    async getValidationResult({ reward, account }: { reward: TReward; account?: TAccount }) {
+        const token = this.getToken(account);
+        if (!token) {
+            return { result: false, reason: 'Your account is not connected to a Discord account' };
+        }
+
+        const guild = await DiscordService.getGuild(reward.poolId);
+        if (!guild) {
+            return { result: false, reason: `THX Bot is not invited to the ${guild.name} Discord server` };
+        }
+
+        const member = await DiscordService.getMember(guild.id, token.userId);
+        if (!member) {
+            return { result: false, reason: `You are not a member of the ${guild.name} Discord server` };
+        }
+
+        return { result: true, reason: '' };
+    }
+
+    create(data: Partial<TReward>) {
+        return this.models.reward.create(data);
+    }
+
+    update(reward: TReward, updates: Partial<TReward>): Promise<TReward> {
+        return this.models.reward.findByIdAndUpdate(reward, updates, { new: true });
+    }
+
+    remove(reward: TReward): Promise<void> {
+        return this.models.reward.findByIdAndDelete(reward._id);
+    }
+
+    findById(id: string) {
+        return this.models.reward.findById(id);
+    }
+
+    async createPayment({ reward, account }: { reward: TRewardNFT; account: TAccount }) {
+        const token = this.getToken(account);
+        const guild = await DiscordService.getGuild(reward.poolId);
+        const role = await DiscordService.getRole(guild.id, reward.discordRoleId);
+        const member = await DiscordService.getMember(guild.id, token.userId);
+
+        // Add role to discord user
+        await member.roles.add(role);
+
+        // Register the payment
+        await this.models.payment.create({
+            rewardId: reward._id,
+            discordRoleId: reward.discordRoleId,
+            sub: account.sub,
+            poolId: reward.poolId,
+            amount: reward.pointPrice,
+        });
+    }
+
+    private getToken(account: TAccount) {
+        return account.tokens.find(({ kind }) => kind === AccessTokenKind.Discord);
+    }
+}
diff --git a/apps/api/src/app/services/RewardGalachainService.ts b/apps/api/src/app/services/RewardGalachainService.ts
new file mode 100644
index 000000000..11df2f49e
--- /dev/null
+++ b/apps/api/src/app/services/RewardGalachainService.ts
@@ -0,0 +1,156 @@
+import { RewardGalachain, RewardGalachainPayment, WalletDocument } from '../models';
+import GalachainService from './GalachainService';
+import { IRewardService } from './interfaces/IRewardService';
+import { BigNumber } from 'bignumber.js';
+import PoolService from './PoolService';
+
+export default class RewardGalachainService implements IRewardService {
+    models = {
+        reward: RewardGalachain,
+        payment: RewardGalachainPayment,
+    };
+
+    async decorate({ reward }: { reward: TRewardGalachain; account?: TAccount }): Promise<any> {
+        const contract = {
+            channelName: reward.contractChannelName,
+            chaincodeName: reward.contractChaincodeName,
+            contractName: reward.contractContractName,
+        };
+        const token = {
+            collection: reward.tokenCollection,
+            category: reward.tokenCategory,
+            type: reward.tokenType,
+            additionalKey: reward.tokenAdditionalKey,
+            instance: new BigNumber(0),
+        };
+        const pool = await PoolService.getById(reward.poolId);
+        const [balance] = (await GalachainService.balanceOf(
+            contract,
+            token,
+            pool.settings.galachainPrivateKey,
+        )) as any[];
+        const paymentCount = await this.models.payment.countDocuments({
+            rewardId: reward._id,
+        });
+        const progress = {
+            count: paymentCount,
+            limit: Number(balance.quantity) + paymentCount,
+        };
+
+        return { ...reward.toJSON(), progress, limit: balance.quantity };
+    }
+
+    async decoratePayment(payment: TRewardPayment): Promise<TRewardGalachainPayment> {
+        const reward = await this.models.reward.findById(payment.rewardId);
+        return { reward, ...payment.toJSON() };
+    }
+
+    async getValidationResult({ reward }: { reward: any; account?: TAccount }): Promise<TValidationResult> {
+        const { tokenCollection, tokenCategory, tokenType, tokenAdditionalKey } = reward;
+        const token = {
+            collection: tokenCollection,
+            category: tokenCategory,
+            type: tokenType,
+            additionalKey: tokenAdditionalKey,
+            instance: new BigNumber(0),
+        };
+        const contract = {
+            channelName: reward.contractChannelName,
+            chaincodeName: reward.contractChaincodeName,
+            contractName: reward.contractContractName,
+            methodName: 'TransferToken',
+        };
+        const pool = await PoolService.getById(reward.poolId);
+
+        // Check balance of the distributor
+        const [balance] = (await GalachainService.balanceOf(
+            contract,
+            token,
+            pool.settings.galachainPrivateKey,
+        )) as any[];
+
+        if (Number(balance.quantity) < reward.amount) {
+            return { result: false, reason: 'Distributor has an insufficient balance' };
+        }
+
+        return { result: true, reason: '' };
+    }
+
+    create(data: any): Promise<any> {
+        return this.models.reward.create(data);
+    }
+
+    update(reward: TReward, updates: Partial<TReward>): Promise<TReward> {
+        return this.models.reward.findByIdAndUpdate(reward, updates, { new: true });
+    }
+
+    remove(reward: TReward): Promise<void> {
+        return this.models.reward.findByIdAndDelete(reward._id);
+    }
+
+    findById(id: string) {
+        return this.models.reward.findById(id);
+    }
+
+    async createPayment({
+        reward,
+        wallet,
+    }: {
+        reward: TRewardGalachain;
+        account: TAccount;
+        safe: WalletDocument;
+        wallet?: WalletDocument;
+    }): Promise<void | TValidationResult> {
+        const token = this.getToken(reward);
+        const contract = this.getContract(reward);
+        const pool = await PoolService.getById(reward.poolId);
+
+        // Get first token from balance
+        const instanceId = await this.getInstance(contract, token, pool);
+
+        // Transfer token to user wallet
+        await GalachainService.transfer(
+            contract,
+            token,
+            wallet.address,
+            Number(reward.amount),
+            instanceId,
+            pool.settings.galachainPrivateKey,
+        );
+
+        // Register payment
+        await this.models.payment.create({
+            sub: wallet.sub,
+            walletId: wallet._id,
+            rewardId: reward._id,
+            amount: reward.amount,
+        });
+    }
+
+    private async getInstance(contract: TGalachainContract, token: TGalachainToken, pool: TPool) {
+        const [balance] = (await GalachainService.balanceOf(
+            contract,
+            token,
+            pool.settings.galachainPrivateKey,
+        )) as any[];
+        const [instanceId] = balance.instanceIds;
+        return instanceId;
+    }
+
+    private getToken(reward: TRewardGalachain) {
+        return {
+            collection: reward.tokenCollection,
+            category: reward.tokenCategory,
+            type: reward.tokenType,
+            additionalKey: reward.tokenAdditionalKey,
+        };
+    }
+
+    private getContract(reward: TRewardGalachain) {
+        return {
+            channelName: reward.contractChannelName,
+            chaincodeName: reward.contractChaincodeName,
+            contractName: reward.contractContractName,
+        };
+    }
+}
diff --git a/apps/api/src/app/services/RewardNFTService.ts b/apps/api/src/app/services/RewardNFTService.ts
new file mode 100644
index 000000000..d6c64139a
--- /dev/null
+++ b/apps/api/src/app/services/RewardNFTService.ts
@@ -0,0 +1,179 @@
+import {
+    ERC1155MetadataDocument,
+    ERC1155TokenDocument,
+    ERC721MetadataDocument,
+    ERC721TokenDocument,
+    RewardNFT,
+    RewardNFTDocument,
+    RewardNFTPayment,
+    WalletDocument,
+} from '@thxnetwork/api/models';
+import { NFTVariant } from '@thxnetwork/common/enums';
+import { IRewardService } from './interfaces/IRewardService';
+import ERC721Service from './ERC721Service';
+import ERC1155Service from './ERC1155Service';
+import PoolService from './PoolService';
+import SafeService from './SafeService';
+
+export default class RewardNFTService implements IRewardService {
+    models = {
+        reward: RewardNFT,
+        payment: RewardNFTPayment,
+    };
+    services = {
+        [NFTVariant.ERC721]: ERC721Service,
+        [NFTVariant.ERC1155]: ERC1155Service,
+    };
+
+    async decorate({ reward, account }: { reward: TRewardNFT; account?: TAccount }) {
+        const nft = await this.findNFT(reward);
+        const token = reward.tokenId && (await this.findTokenById(nft, reward.tokenId));
+        const metadataId = token ? token.metadataId : reward.metadataId ? reward.metadataId : null;
+        const metadata = metadataId ? await this.findMetadataById(nft, metadataId) : null;
+        const expiry = reward.expiryDate && {
+            date: reward.expiryDate,
+            now: new Date(),
+        };
+        return { ...reward.toJSON(), chainId: nft.chainId, nft, metadata, token, expiry };
+    }
+
+    async decoratePayment(payment: TRewardPayment): Promise<TRewardPayment> {
+        return payment;
+    }
+
+    async getValidationResult({
+        reward,
+        safe,
+        wallet,
+    }: {
+        reward: TRewardNFT;
+        safe?: WalletDocument;
+        wallet?: WalletDocument;
+        account?: TAccount;
+    }) {
+        const nft = await this.findNFT(reward);
+        if (!nft) return { result: false, reason: 'NFT contract is no longer available' };
+
+        // This will require a transfer
+        if (reward.tokenId) {
+            // Check if Safe is the owner
+            const { contract } = nft;
+            const token = await this.findTokenById(nft, reward.tokenId);
+            if (!token) return { result: false, reason: 'Token not found' };
+
+            const owner = await contract.methods.ownerOf(token.tokenId).call();
+            if (owner.toLowerCase() !== safe.address.toLowerCase()) {
+                return { result: false, reason: 'Token is no longer owner by campaign Safe.' };
+            }
+        }
+
+        // Will require a mint
+        if (reward.metadataId) {
+            const isMinter = await this.services[nft.variant].isMinter(nft, safe.address);
+            if (!isMinter) return { result: false, reason: 'Campaign Safe is not a minter of the NFT contract.' };
+        }
+
+        // Check receiving wallet for chain compatibility
+        if (wallet.chainId !== nft.chainId) {
+            return { result: false, reason: 'Your wallet is not on the same chain as the NFT contract.' };
+        }
+
+        return { result: true, reason: '' };
+    }
+
+    async create(data: Partial<TRewardNFT>) {
+        // If erc721Id or erc1155Id, check if campaign safe is minter
+        if (data.metadataId) {
+            const pool = await PoolService.getById(data.poolId);
+            const safe = await SafeService.findOneByPool(pool, pool.chainId);
+            await this.addMinter(data, safe.address);
+        }
+
+        return await this.models.reward.create(data);
+    }
+
+    update(reward: TRewardNFT, updates: Partial<TRewardNFT>) {
+        return this.models.reward.findByIdAndUpdate(reward._id, updates, { new: true });
+    }
+
+    async remove(reward: RewardNFTDocument) {
+        await this.models.reward.findOneAndDelete(reward._id);
+    }
+
+    async createPayment({
+        reward,
+        safe,
+        wallet,
+    }: {
+        reward: RewardNFTDocument;
+        safe: WalletDocument;
+        wallet?: WalletDocument;
+    }) {
+        const erc1155Amount = reward.erc1155Amount && String(reward.erc1155Amount);
+        const nft = await this.findNFT(reward);
+        if (!nft) throw new Error('NFT not found');
+
+        // Get token and metadata for either ERC721 or ERC1155 based contracts
+        // and mint if metadataId is present or transfer if tokenId is present
+        let token: ERC721TokenDocument | ERC1155TokenDocument,
+            metadata: ERC721MetadataDocument | ERC1155MetadataDocument;
+
+        // Mint a token if metadataId is present
+        if (reward.metadataId) {
+            metadata = await this.findMetadataById(nft, reward.metadataId);
+
+            // Mint the token to wallet address
+            token = await this.services[nft.variant].mint(safe, nft, wallet, metadata, erc1155Amount);
+        }
+
+        // Transfer a token if tokenId is present
+        if (reward.tokenId) {
+            token = await this.findTokenById(nft, reward.tokenId);
+            metadata = await this.findMetadataByToken(nft, token);
+
+            // Transfer the token from safe to wallet address
+            token = await this.services[nft.variant].transferFrom(nft, safe, wallet.address, token, erc1155Amount);
+        }
+
+        // Register the payment
+        await RewardNFTPayment.create({
+            rewardId: reward._id,
+            sub: wallet.sub,
+            walletId: wallet._id,
+            poolId: reward.poolId,
+            amount: reward.pointPrice,
+        });
+    }
+
+    findById(id: string) {
+        return this.models.reward.findById(id);
+    }
+
+    findMetadataByToken(nft: TERC721 | TERC1155, token: TERC721Token | TERC1155Token) {
+        return this.services[nft.variant].findMetadataByToken(token);
+    }
+
+    findTokenById(nft: TERC721 | TERC1155, tokenId: string) {
+        return this.services[nft.variant].findTokenById(tokenId);
+    }
+
+    findMetadataById(nft: TERC721 | TERC1155, metadataId: string) {
+        return this.services[nft.variant].findMetadataById(metadataId);
+    }
+
+    findNFT({ erc721Id, erc1155Id }: { erc721Id?: string; erc1155Id?: string }) {
+        if (erc721Id) {
+            return ERC721Service.findById(erc721Id);
+        }
+
+        if (erc1155Id) {
+            return ERC1155Service.findById(erc1155Id);
+        }
+    }
+
+    private async addMinter({ erc721Id, erc1155Id }: { erc721Id?: string; erc1155Id?: string }, address: string) {
+        const nft = await this.findNFT({ erc721Id, erc1155Id });
+        const isMinter = await this.services[nft.variant].isMinter(nft, address);
+        if (!isMinter) await this.services[nft.variant].addMinter(nft, address);
+    }
+}
diff --git a/apps/api/src/app/services/RewardService.ts b/apps/api/src/app/services/RewardService.ts
new file mode 100644
index 000000000..aa6560118
--- /dev/null
+++ b/apps/api/src/app/services/RewardService.ts
@@ -0,0 +1,292 @@
+import { Document } from 'mongoose';
+import { RewardVariant } from '@thxnetwork/common/enums';
+import { Participant, QRCodeEntry, WalletDocument } from '@thxnetwork/api/models';
+import { v4 } from 'uuid';
+import { logger } from '../util/logger';
+import { Job } from '@hokify/agenda';
+import RewardCoinService from './RewardCoinService';
+import LockService from './LockService';
+import AccountProxy from '../proxies/AccountProxy';
+import ParticipantService from './ParticipantService';
+import RewardNFTService from './RewardNFTService';
+import RewardCouponService from './RewardCouponService';
+import ImageService from './ImageService';
+import PointBalanceService from './PointBalanceService';
+import MailService from './MailService';
+import RewardDiscordRoleService from './RewardDiscordRoleService';
+import RewardCustomService from './RewardCustomService';
+import RewardGalachainService from './RewardGalachainService';
+import PoolService from './PoolService';
+import WalletService from './WalletService';
+import THXService from './THXService';
+
+const serviceMap = {
+    [RewardVariant.Coin]: new RewardCoinService(),
+    [RewardVariant.NFT]: new RewardNFTService(),
+    [RewardVariant.Custom]: new RewardCustomService(),
+    [RewardVariant.Coupon]: new RewardCouponService(),
+    [RewardVariant.DiscordRole]: new RewardDiscordRoleService(),
+    [RewardVariant.Galachain]: new RewardGalachainService(),
+};
+
+export default class RewardService {
+    static async count({ poolId }) {
+        const variants = Object.keys(RewardVariant).filter((v) => !isNaN(Number(v)));
+        const counts = await Promise.all(
+            variants.map(async (variant: string) => {
+                const Reward = serviceMap[variant].models.reward;
+                return await Reward.countDocuments({ poolId, isPublished: true });
+            }),
+        );
+        return counts.reduce((acc, count) => acc + count, 0);
+    }
+
+    static async list({ pool, account }) {
+        const rewardVariants = Object.keys(RewardVariant).filter((v) => !isNaN(Number(v)));
+        const callback: any = async (variant: RewardVariant) => {
+            const Reward = serviceMap[variant].models.reward;
+            // Filter out rewards that have QR codes (RDM)
+            const qrCodeRewardIds = await QRCodeEntry.find().distinct('rewardId');
+            const rewards = await Reward.find({
+                _id: { $nin: qrCodeRewardIds },
+                poolId: pool._id,
+                variant,
+                isPublished: true,
+                $or: [
+                    // Include quests with expiryDate less than or equal to now
+                    { expiryDate: { $exists: true, $gte: new Date() } },
+                    // Include quests with no expiryDate
+                    { expiryDate: { $exists: false } },
+                ],
+            });
+            return await Promise.all(
+                rewards.map(async (reward) => {
+                    try {
+                        const decorated = await serviceMap[reward.variant].decorate({ reward, account });
+                        const isLocked = await this.isLocked({ reward, account });
+                        const isStocked = await this.isStocked(reward);
+                        const isExpired = this.isExpired(reward);
+                        const isAvailable = await this.isAvailable({ reward, account });
+                        const progress = {
+                            count: await serviceMap[reward.variant].models.payment.countDocuments({
+                                rewardId: reward._id,
+                            }),
+                            limit: reward.limit,
+                        };
+
+                        // Decorated properties may override generic properties
+                        return { progress, isLocked, isStocked, isExpired, isAvailable, ...decorated };
+                    } catch (error) {
+                        logger.error(error);
+                    }
+                }),
+            );
+        };
+
+        return await Promise.all(rewardVariants.map(callback));
+    }
+
+    static async findPaymentsBySub(
+        reward: TReward,
+        { skip, limit, query }: { skip: number; limit: number; query: string },
+    ) {
+        const Payment = serviceMap[reward.variant].models.payment;
+        // Get all matching accounts by email and username first
+        const accounts = await AccountProxy.find({ query });
+        // We then fetch the payments for the list of subs
+        const subs = accounts.map(({ sub }) => sub);
+        // Then we fetch the participants for the poolId and the list of subs
+        const participants = await Participant.find({ poolId: reward.poolId, sub: { $in: subs } });
+        const payments = await Payment.find({ rewardId: reward._id, sub: { $in: subs } })
+            .limit(limit)
+            .skip(skip);
+
+        return { payments, accounts, participants };
+    }
+
+    static async findPaymentsByReward(
+        reward: TReward,
+        { skip, limit }: { skip: number; limit: number; query: string },
+    ) {
+        const Payment = serviceMap[reward.variant].models.payment;
+        // If there is no query we fetch the payments for the reward
+        const payments = await Payment.find({ rewardId: reward._id }).limit(limit).skip(skip);
+        const subs = payments.map(({ sub }) => sub);
+        const accounts = await AccountProxy.find({ subs });
+        const participants = await Participant.find({ poolId: reward.poolId, sub: { $in: subs } });
+
+        return { payments, accounts, participants };
+    }
+
+    static async findPayments(reward: TReward, { page, limit, query }: { page: number; limit: number; query: string }) {
+        const skip = (page - 1) * limit;
+        const Payment = serviceMap[reward.variant].models.payment;
+        const total = await Payment.countDocuments({ rewardId: reward._id });
+
+        // If there is a query we fetch accounts by username first
+        const { payments, accounts, participants } =
+            query.length > 3
+                ? await this.findPaymentsBySub(reward, { skip, limit, query })
+                : await this.findPaymentsByReward(reward, { skip, limit, query });
+        const promises = payments.map(async (payment: Document & TRewardPayment) =>
+            ParticipantService.decorate(payment, { accounts, participants }),
+        );
+        const results = await Promise.allSettled(promises);
+
+        return {
+            total,
+            limit,
+            page,
+            results: results.filter((result) => result.status === 'fulfilled').map((result: any) => result.value),
+        };
+    }
+
+    static async findPaymentsForSub(sub: string) {
+        const rewardVariants: string[] = Object.keys(RewardVariant).filter((v) => !isNaN(Number(v)));
+        const payments = await Promise.allSettled(
+            rewardVariants.map(async (variant: string) => {
+                const rewardVariant = Number(variant);
+                const payments = await serviceMap[rewardVariant].models.payment.find({ sub });
+                const callback = payments.map(async (p: Document & TRewardPayment) => {
+                    const decorated = await serviceMap[rewardVariant].decoratePayment(p);
+                    return { ...decorated, rewardVariant };
+                });
+                return await Promise.all(callback);
+            }),
+        );
+
+        return payments
+            .filter((result) => result.status === 'fulfilled')
+            .map((result: any) => result.value)
+            .flat();
+    }
+
+    static async createPaymentJob(job: Job) {
+        try {
+            const { variant, sub, rewardId, walletId } = job.attrs.data as any;
+            const account = await AccountProxy.findById(sub);
+            const reward = await this.findById(variant, rewardId);
+            const pool = await PoolService.getById(reward.poolId);
+            const wallet = walletId && (await WalletService.findById(walletId));
+
+            // Validate supply, expiry, locked and reward specific validation
+            const validationResult = await this.getValidationResult({ reward, account, safe: pool.safe });
+            if (!validationResult.result) return validationResult.reason;
+
+            // Subtract points for account
+            await PointBalanceService.subtract(pool, account, reward.pointPrice);
+
+            // Send email notification
+            let html = `<p style="font-size: 18px">Congratulations!🚀</p>`;
+            html += `<p>Your payment has been received! <strong>${reward.title}</strong> is available in your account.</p>`;
+            html += `<p class="btn"><a href="${pool.campaignURL}">View Wallet</a></p>`;
+            await MailService.send(account.email, `🎁 Reward Received!`, html);
+
+            const payment = await serviceMap[variant].createPayment({ reward, account, safe: pool.safe, wallet });
+
+            // Register THX onboarding campaign event
+            await THXService.createEvent(account, 'reward_payment_created');
+
+            // Register the payment for the account
+            return payment;
+        } catch (error) {
+            console.log(error);
+            logger.error(error);
+        }
+    }
+
+    static async create(variant: RewardVariant, poolId: string, data: Partial<TReward>, file: Express.Multer.File) {
+        if (file) {
+            data.image = await ImageService.upload(file);
+        }
+
+        const reward = await serviceMap[variant].create({ ...data, poolId, variant, uuid: v4() });
+
+        // TODO Implement publish notification flow for rewards
+        // if (data.isPublished) {
+        //     await NotificationService.notify(variant, quest);
+        // }
+
+        return reward;
+    }
+
+    static async update(reward: TReward, updates: Partial<TReward>, file?: Express.Multer.File) {
+        if (file) {
+            updates.image = await ImageService.upload(file);
+        }
+
+        reward = await serviceMap[reward.variant].update(reward, updates);
+
+        // TODO Implement publish notification flow for rewards
+        // if (data.isPublished) {
+        //     await NotificationService.notify(variant, quest);
+        // }
+
+        return reward;
+    }
+
+    static async remove(reward: TReward) {
+        return await serviceMap[reward.variant].remove(reward);
+    }
+
+    static findById(variant: RewardVariant, rewardId: string) {
+        return serviceMap[variant].findById(rewardId);
+    }
+
+    static async getValidationResult({
+        reward,
+        account,
+        safe,
+        wallet,
+    }: {
+        reward: TReward;
+        account?: TAccount;
+        safe?: WalletDocument;
+        wallet?: WalletDocument;
+    }) {
+        const participant = await Participant.findOne({ sub: account.sub, poolId: reward.poolId });
+        if (Number(participant.balance) < Number(reward.pointPrice)) {
+            return { result: false, reason: 'Participant has insufficient points.' };
+        }
+
+        const isLocked = await this.isLocked({ reward, account });
+        if (isLocked) return { result: false, reason: 'This reward is locked.' };
+
+        const isExpired = this.isExpired(reward);
+        if (isExpired) return { result: false, reason: 'This reward claim has expired.' };
+
+        const isStocked = await this.isStocked(reward);
+        if (!isStocked) return { result: false, reason: 'This reward is out of stock.' };
+
+        return serviceMap[reward.variant].getValidationResult({ reward, account, wallet, safe });
+    }
+
+    static async isLocked({ reward, account }) {
+        if (!account || !reward.locks.length) return false;
+        return await LockService.getIsLocked(reward.locks, account);
+    }
+
+    static isExpired(reward: TReward) {
+        if (!reward.expiryDate) return false;
+        return Date.now() > new Date(reward.expiryDate).getTime();
+    }
+
+    static async isStocked(reward) {
+        if (!reward.limit) return true;
+        // Check if reward has a limit and if limit has been reached
+        const amountOfPayments = await serviceMap[reward.variant].models.payment.countDocuments({
+            rewardId: reward._id,
+        });
+        return amountOfPayments < reward.limit;
+    }
+
+    static async isAvailable({ reward, account }: { reward: TReward; account?: TAccount }) {
+        if (!account) return true;
+
+        const isLocked = await this.isLocked({ reward, account });
+        const isStocked = await this.isStocked(reward);
+        const isExpired = this.isExpired(reward);
+
+        return !isLocked && !isExpired && isStocked;
+    }
+}
diff --git a/apps/api/src/app/services/SafeService.ts b/apps/api/src/app/services/SafeService.ts
new file mode 100644
index 000000000..608b2476e
--- /dev/null
+++ b/apps/api/src/app/services/SafeService.ts
@@ -0,0 +1,254 @@
+import { Wallet, WalletDocument, Pool, PoolDocument, Transaction } from '@thxnetwork/api/models';
+import { ChainId, WalletVariant } from '@thxnetwork/common/enums';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { contractNetworks } from '@thxnetwork/api/hardhat';
+import ContractService, { safeVersion } from '@thxnetwork/api/services/ContractService';
+import { toChecksumAddress } from 'web3-utils';
+import Safe, { SafeAccountConfig, SafeFactory } from '@safe-global/protocol-kit';
+import SafeApiKit from '@safe-global/api-kit';
+import {
+    SafeMultisigTransactionResponse,
+    SafeTransactionData,
+    SafeTransactionDataPartial,
+    SafeVersion,
+} from '@safe-global/safe-core-sdk-types';
+import { logger } from '@thxnetwork/api/util/logger';
+import { agenda, JobType } from '@thxnetwork/api/util/agenda';
+import { Job } from '@hokify/agenda';
+import { convertObjectIdToNumber } from '../util';
+import TransactionService from './TransactionService';
+
+function getSafeApiKit(chainId: ChainId) {
+    const { txServiceUrl, ethAdapter } = getProvider(chainId);
+    return new SafeApiKit({ txServiceUrl, ethAdapter });
+}
+
+function reset(wallet: WalletDocument, userWalletAddress: string) {
+    const { defaultAccount } = getProvider(wallet.chainId);
+    return deploy(wallet, [toChecksumAddress(defaultAccount), toChecksumAddress(userWalletAddress)]);
+}
+
+async function create(
+    data: { chainId: ChainId; sub: string; safeVersion?: SafeVersion; address?: string; poolId?: string },
+    userWalletAddress?: string,
+) {
+    const { safeVersion, chainId, sub, address, poolId } = data;
+    const { defaultAccount } = getProvider(chainId);
+    const wallet = await Wallet.create({ variant: WalletVariant.Safe, sub, chainId, address, safeVersion, poolId });
+
+    // Concerns a Metamask account so we do not deploy and return early
+    if (!safeVersion && address) return wallet;
+
+    // Add relayer address and consider this a campaign safe
+    const owners = [toChecksumAddress(defaultAccount)];
+    // Add user address as a signer and consider this a participant safe
+    if (userWalletAddress) owners.push(toChecksumAddress(userWalletAddress));
+
+    // If campaign safe we provide a nonce based on the timestamp in the MongoID the pool (poolId value)
+    const nonce = wallet.poolId && String(convertObjectIdToNumber(wallet.poolId));
+
+    return await deploy(wallet, owners, nonce);
+}
+
+async function deploy(wallet: WalletDocument, owners: string[], nonce?: string) {
+    const { ethAdapter } = getProvider(wallet.chainId);
+    const safeFactory = await SafeFactory.create({
+        safeVersion: wallet.safeVersion as SafeVersion,
+        ethAdapter,
+        contractNetworks,
+    });
+    const safeAccountConfig: SafeAccountConfig = {
+        owners,
+        threshold: owners.length,
+    };
+    const safeAddress = toChecksumAddress(await safeFactory.predictSafeAddress(safeAccountConfig, nonce));
+
+    try {
+        await Safe.create({
+            ethAdapter,
+            safeAddress,
+            contractNetworks,
+        });
+    } catch (error) {
+        await agenda.now(JobType.DeploySafe, {
+            safeAccountConfig,
+            safeVersion: wallet.safeVersion,
+            safeAddress,
+            safeWalletId: String(wallet._id),
+        });
+    }
+
+    return await Wallet.findByIdAndUpdate(wallet._id, { address: safeAddress }, { new: true });
+}
+
+async function createJob(job: Job) {
+    const { safeAccountConfig, safeVersion, safeAddress, safeWalletId } = job.attrs.data as any;
+    if (!safeAccountConfig || !safeVersion || !safeAddress || !safeWalletId) return;
+
+    const wallet = await Wallet.findById(safeWalletId);
+    const { ethAdapter } = getProvider(wallet.chainId);
+    const safeFactory = await SafeFactory.create({
+        safeVersion,
+        ethAdapter,
+        contractNetworks,
+    });
+
+    // If campaign safe we provide a nonce based on the timestamp in the MongoID the pool (poolId value)
+    const nonce = wallet.poolId && String(convertObjectIdToNumber(wallet.poolId));
+    const config = { safeAccountConfig, options: { gasLimit: '3000000' } };
+    if (nonce) config['saltNonce'] = nonce;
+
+    await safeFactory.deploySafe(config);
+    logger.debug(`[${wallet.sub}] Deployed Safe: ${safeAddress}`);
+
+    // Set safeAddress for campaign to keep address available for potential regression
+    if (wallet.poolId) {
+        await Pool.findByIdAndUpdate(wallet.poolId, { safeAddress: toChecksumAddress(safeAddress) });
+    }
+}
+
+function findById(id: string) {
+    return Wallet.findById(id);
+}
+
+function findOne(query) {
+    return Wallet.findOne({ ...query, variant: WalletVariant.Safe, poolId: { $exists: false } });
+}
+
+function findOneByAddress(address: string) {
+    return Wallet.findOne({ address: toChecksumAddress(address) });
+}
+
+async function findOneByPool(pool: PoolDocument, chainId?: ChainId) {
+    return await Wallet.findOne({
+        poolId: pool.id,
+        chainId: chainId || ContractService.getChainId(),
+        sub: pool.sub,
+        safeVersion,
+    });
+}
+
+async function getOwners(wallet: WalletDocument) {
+    const { ethAdapter } = getProvider(wallet.chainId);
+    const safeSdk = await Safe.create({
+        ethAdapter,
+        safeAddress: wallet.address,
+        contractNetworks,
+    });
+
+    return await safeSdk.getOwners();
+}
+
+async function createSwapOwnerTransaction(wallet: WalletDocument, oldOwnerAddress: string, newOwnerAddress: string) {
+    const { ethAdapter } = getProvider(wallet.chainId);
+    const safeSdk = await Safe.create({
+        ethAdapter,
+        safeAddress: wallet.address,
+        contractNetworks,
+    });
+
+    return await safeSdk.createSwapOwnerTx({ oldOwnerAddress, newOwnerAddress });
+}
+
+async function proposeTransaction(wallet: WalletDocument, safeTransactionData: SafeTransactionDataPartial) {
+    const { ethAdapter, signer } = getProvider(wallet.chainId);
+    const safe = await Safe.create({
+        ethAdapter,
+        safeAddress: wallet.address,
+        contractNetworks,
+    });
+
+    // Get nonce for this Safes transaction
+    const nonce = await safe.getNonce();
+    const safeTransaction = await safe.createTransaction({
+        safeTransactionData,
+        options: { nonce: nonce + 1 },
+    });
+
+    // Create hash for this transaction
+    const safeTxHash = await safe.getTransactionHash(safeTransaction);
+    const signature = await safe.signTransactionHash(safeTxHash);
+    const apiKit = getSafeApiKit(wallet.chainId);
+
+    logger.info({ safeTxHash, nonce });
+
+    try {
+        await apiKit.proposeTransaction({
+            safeAddress: wallet.address,
+            safeTxHash,
+            safeTransactionData: safeTransaction.data as any,
+            senderAddress: toChecksumAddress(await signer.getAddress()),
+            senderSignature: signature.data,
+        });
+
+        logger.info(`Safe TX Proposed: ${safeTxHash}`);
+        return safeTxHash;
+    } catch (error) {
+        logger.error(error);
+    }
+}
+
+async function confirmTransaction(wallet: WalletDocument, safeTxHash: string) {
+    const { ethAdapter } = getProvider(wallet.chainId);
+    const safe = await Safe.create({
+        ethAdapter,
+        safeAddress: wallet.address,
+        contractNetworks,
+    });
+    const signature = await safe.signTransactionHash(safeTxHash);
+    return await confirm(wallet, safeTxHash, signature.data);
+}
+
+async function confirm(wallet: WalletDocument, safeTxHash: string, signatureData: string) {
+    const { txServiceUrl, ethAdapter } = getProvider(wallet.chainId);
+    const apiKit = new SafeApiKit({ ethAdapter, txServiceUrl });
+    return await apiKit.confirmTransaction(safeTxHash, signatureData);
+}
+
+async function executeTransaction(wallet: WalletDocument, safeTxHash: string) {
+    const { ethAdapter } = getProvider(wallet.chainId);
+    const apiKit = getSafeApiKit(wallet.chainId);
+    const safe = await Safe.create({
+        ethAdapter,
+        safeAddress: wallet.address,
+        contractNetworks,
+    });
+    const safeTransaction = await apiKit.getTransaction(safeTxHash);
+    const executeTxResponse = await safe.executeTransaction(safeTransaction as any);
+    const receipt = await executeTxResponse.transactionResponse?.wait();
+    const tx = await Transaction.findOne({ safeTxHash });
+
+    await TransactionService.executeCallback(tx, receipt as any);
+
+    return receipt;
+}
+
+async function getLastPendingTransactions(wallet: WalletDocument) {
+    const apiKit = getSafeApiKit(wallet.chainId);
+    const { results }: any = await apiKit.getPendingTransactions(wallet.address);
+
+    return results as unknown as SafeMultisigTransactionResponse[];
+}
+
+async function getTransaction(wallet: WalletDocument, safeTxHash: string): Promise<SafeMultisigTransactionResponse> {
+    const apiKit = getSafeApiKit(wallet.chainId);
+    return (await apiKit.getTransaction(safeTxHash)) as unknown as SafeMultisigTransactionResponse;
+}
+
+export default {
+    reset,
+    findById,
+    createSwapOwnerTransaction,
+    proposeTransaction,
+    confirmTransaction,
+    confirm,
+    getLastPendingTransactions,
+    getOwners,
+    create,
+    createJob,
+    findOneByAddress,
+    findOne,
+    getTransaction,
+    executeTransaction,
+    findOneByPool,
+};
diff --git a/apps/api/src/app/services/THXService.ts b/apps/api/src/app/services/THXService.ts
new file mode 100644
index 000000000..cdc75e67b
--- /dev/null
+++ b/apps/api/src/app/services/THXService.ts
@@ -0,0 +1,37 @@
+import { THXAPIClient } from '@thxnetwork/sdk/clients';
+import { THX_CLIENT_ID, THX_CLIENT_SECRET } from '../config/secrets';
+import { Identity } from '../models';
+import AccountProxy from '../proxies/AccountProxy';
+
+class THXService {
+    thx!: THXAPIClient;
+
+    constructor() {
+        if (THX_CLIENT_ID && THX_CLIENT_SECRET) {
+            this.thx = new THXAPIClient({
+                clientId: THX_CLIENT_ID,
+                clientSecret: THX_CLIENT_SECRET,
+            });
+        }
+    }
+
+    async connect(account: TAccount) {
+        if (!this.thx) return;
+
+        if (!account.identity) {
+            account.identity = await this.thx.identity.create();
+            await AccountProxy.update(account.sub, { identity: account.identity });
+        }
+
+        await Identity.updateOne({ uuid: account.identity }, { sub: account.sub });
+    }
+
+    async createEvent(account: TAccount, event: string) {
+        if (!this.thx || !account.identity) return;
+        await this.thx.events.create({ identity: account.identity, event });
+    }
+}
+
+const THXServiceInstance = new THXService();
+
+export default THXServiceInstance;
diff --git a/apps/api/src/app/services/TransactionService.ts b/apps/api/src/app/services/TransactionService.ts
new file mode 100644
index 000000000..701df1db5
--- /dev/null
+++ b/apps/api/src/app/services/TransactionService.ts
@@ -0,0 +1,366 @@
+import { getProvider } from '@thxnetwork/api/util/network';
+import { ChainId, TransactionState, TransactionType } from '@thxnetwork/common/enums';
+import { MINIMUM_GAS_LIMIT, RELAYER_SPEED } from '@thxnetwork/api/config/secrets';
+import { paginatedResults } from '@thxnetwork/api/util/pagination';
+import { toChecksumAddress } from 'web3-utils';
+import { poll } from '@thxnetwork/api/util/polling';
+import { deployCallback as erc20DeployCallback } from './ERC20Service';
+import { RelayerTransactionPayload } from '@openzeppelin/defender-relay-client';
+import { Contract } from 'web3-eth-contract';
+import { Transaction, TransactionDocument, WalletDocument } from '@thxnetwork/api/models';
+import { TransactionReceipt } from 'web3-core';
+import ERC721Service from './ERC721Service';
+import ERC1155Service from './ERC1155Service';
+import SafeService from './SafeService';
+
+function getById(id: string) {
+    return Transaction.findById(id);
+}
+
+async function sendValue(to: string, value: string, chainId: ChainId) {
+    const { web3, defaultAccount } = getProvider(chainId);
+    const from = defaultAccount;
+    const gas = '21000';
+
+    let tx = await Transaction.create({
+        state: TransactionState.Queued,
+        chainId,
+        from,
+        to,
+        gas,
+    });
+
+    const receipt = await web3.eth.sendTransaction({
+        from,
+        to,
+        value,
+        gas,
+    });
+
+    if (receipt.transactionHash) {
+        tx.transactionHash = receipt.transactionHash;
+        tx.state = TransactionState.Mined;
+        tx = await tx.save();
+    }
+
+    return { tx, receipt };
+}
+
+async function send(to: string, fn: any, chainId: ChainId) {
+    const { web3, defaultAccount } = getProvider(chainId);
+    const from = defaultAccount;
+    const data = fn.encodeABI();
+    const estimate = await fn.estimateGas({ from });
+    const gas = estimate < MINIMUM_GAS_LIMIT ? MINIMUM_GAS_LIMIT : estimate;
+
+    return web3.eth.sendTransaction({
+        from,
+        to,
+        data,
+        gas,
+    });
+}
+
+/**
+ * Creates a transaction in the db and either executes or schedules a web3 transaction.
+ *
+ * When the chain has a relayer configured the transaction is scheduled through it instead of directly executed.
+ *
+ * By setting the forceSync bool to true you can force the call to behave synchronously. It will poll for the transaction to be executed and only return after the transaction and its callback are executed.
+ *
+ * @param to Recipient
+ * @param fn Web3 contract method
+ * @param chainId Chainid to execute on
+ * @param forceSync Boolean to force synchronous execution, this waits for the transaction to be processed before returning.
+ * @param callback Callback configuration.
+ * @returns The transaction ID. This can be stored so the status of the transaction can be queried.
+ */
+async function sendAsync(
+    to: string | null,
+    fn: any,
+    chainId: ChainId,
+    forceSync = true,
+    callback?: TTransactionCallback,
+) {
+    const { web3, relayer, defaultAccount } = getProvider(chainId);
+    const data = fn.encodeABI();
+
+    const estimate = await fn.estimateGas({ from: defaultAccount });
+    const gas = estimate < MINIMUM_GAS_LIMIT ? MINIMUM_GAS_LIMIT : estimate;
+
+    const tx = await Transaction.create({
+        type: relayer && !forceSync ? TransactionType.Relayed : TransactionType.Default,
+        state: TransactionState.Queued,
+        from: defaultAccount,
+        to,
+        chainId,
+        callback,
+    });
+    if (relayer) {
+        const args: RelayerTransactionPayload = {
+            data,
+            speed: RELAYER_SPEED,
+            gasLimit: gas,
+        };
+        if (to) args.to = to;
+
+        const defenderTx = await relayer.sendTransaction(args);
+
+        Object.assign(tx, {
+            transactionId: defenderTx.transactionId,
+            transactionHash: defenderTx.hash,
+            state: TransactionState.Sent,
+        });
+
+        await tx.save();
+
+        if (forceSync) {
+            await poll(
+                async () => {
+                    const transaction = await getById(tx._id);
+                    return queryTransactionStatusReceipt(transaction);
+                },
+                (state: TransactionState) => state === TransactionState.Sent,
+                500,
+            );
+        }
+    } else {
+        const receipt = await web3.eth.sendTransaction({
+            from: defaultAccount,
+            to,
+            data,
+            gas: gas + 100000, // This was originally added for relayed transactions, not sure if still  needed
+        });
+
+        await transactionMined(tx, receipt);
+    }
+
+    // We return the id because the transaction might be out of date and the transaction is not used by callers anyway.
+    return String(tx._id);
+}
+
+async function execSafeAsync(wallet: WalletDocument, tx: TransactionDocument) {
+    const { relayer } = getProvider(wallet.chainId);
+    const safeTransaction = await SafeService.getTransaction(wallet, tx.safeTxHash);
+
+    // If there is no relayer for the network the safe executes immediately
+    if (!relayer) {
+        const receipt = await SafeService.executeTransaction(wallet, tx.safeTxHash);
+        await transactionMined(tx, receipt as any);
+        return;
+    }
+
+    // If there is a relayer the transaction is sent to Defender and the job
+    // processor polls for the receipt and invokes callback
+    const defenderTx = await relayer.sendTransaction({
+        to: safeTransaction.to,
+        data: safeTransaction.data,
+        gasLimit: safeTransaction.safeTxGas || '196000',
+        speed: RELAYER_SPEED,
+    });
+
+    await tx.updateOne({
+        transactionId: defenderTx.transactionId,
+        transactionHash: defenderTx.hash,
+        state: TransactionState.Sent,
+    });
+}
+
+async function proposeSafeAsync(
+    wallet: WalletDocument,
+    to: string | null,
+    data: string,
+    callback?: TTransactionCallback,
+) {
+    const { relayer, defaultAccount } = getProvider(wallet.chainId);
+    const safeTxHash = await SafeService.proposeTransaction(wallet, {
+        to,
+        data,
+        value: '0',
+    });
+    if (!safeTxHash) throw new Error("Couldn't propose transaction.");
+
+    await SafeService.confirmTransaction(wallet, safeTxHash);
+
+    return await Transaction.create({
+        type: relayer ? TransactionType.Relayed : TransactionType.Default,
+        state: TransactionState.Confirmed,
+        safeTxHash,
+        chainId: wallet.chainId,
+        walletId: String(wallet._id),
+        from: defaultAccount,
+        to,
+        callback,
+    });
+}
+
+async function sendSafeAsync(wallet: WalletDocument, to: string | null, fn: any, callback?: TTransactionCallback) {
+    const data = fn.encodeABI();
+    return proposeSafeAsync(wallet, to, data, callback);
+}
+
+async function deploy(abi: any, bytecode: any, arg: any[], chainId: ChainId) {
+    const { web3, defaultAccount } = getProvider(chainId);
+    const contract = new web3.eth.Contract(abi) as unknown as Contract;
+    const gas = await contract
+        .deploy({
+            data: bytecode,
+            arguments: arg,
+        })
+        .estimateGas();
+    const data = contract
+        .deploy({
+            data: bytecode,
+            arguments: arg,
+        })
+        .encodeABI();
+
+    const tx = await Transaction.create({
+        type: TransactionType.Default,
+        state: TransactionState.Queued,
+        from: defaultAccount,
+        chainId,
+        gas,
+    });
+
+    const receipt = await web3.eth.sendTransaction({
+        from: defaultAccount,
+        data,
+        gas,
+    });
+
+    if (receipt.transactionHash) {
+        await tx.updateOne({
+            to: receipt.to,
+            transactionHash: receipt.transactionHash,
+            state: TransactionState.Mined,
+        });
+    }
+
+    contract.options.address = receipt.contractAddress;
+
+    return contract;
+}
+
+async function transactionMined(tx: TransactionDocument, receipt: TransactionReceipt) {
+    Object.assign(tx, {
+        transactionHash: receipt.transactionHash,
+        state: TransactionState.Failed,
+    });
+
+    if (receipt.to) {
+        Object.assign(tx, { to: toChecksumAddress(receipt.to) });
+    }
+
+    if (tx.callback) {
+        try {
+            await executeCallback(tx, receipt);
+            tx.state = TransactionState.Mined;
+        } catch (e) {
+            tx.failReason = e.message;
+        }
+    }
+
+    await tx.save();
+}
+
+async function executeCallback(tx: TransactionDocument, receipt: TransactionReceipt) {
+    if (!tx || !tx.callback) return;
+    switch (tx.callback.type) {
+        case 'Erc20DeployCallback':
+            await erc20DeployCallback(tx.callback.args, receipt);
+            break;
+        case 'Erc721DeployCallback':
+            await ERC721Service.deployCallback(tx.callback.args, receipt);
+            break;
+        case 'ERC1155DeployCallback':
+            await ERC1155Service.deployCallback(tx.callback.args, receipt);
+            break;
+        case 'erc721TokenMintCallback':
+            await ERC721Service.mintCallback(tx.callback.args, receipt);
+            break;
+        case 'erc1155TokenMintCallback':
+            await ERC1155Service.mintCallback(tx.callback.args, receipt);
+            break;
+        case 'erc721nTransferFromCallback':
+            await ERC721Service.transferFromCallback(tx.callback.args, receipt);
+            break;
+        case 'erc1155TransferFromCallback':
+            await ERC1155Service.transferFromCallback(tx.callback.args, receipt);
+            break;
+    }
+}
+
+async function queryTransactionStatusDefender(tx: TransactionDocument) {
+    if ([TransactionState.Mined, TransactionState.Failed].includes(tx.state)) {
+        return tx;
+    }
+    const { web3, relayer } = getProvider(tx.chainId);
+
+    const defenderTx = await relayer.query(tx.transactionId);
+
+    // Hash has been updated
+    if (tx.transactionHash != defenderTx.hash) {
+        tx.transactionHash = defenderTx.hash;
+        await tx.save();
+    }
+
+    if (['mined', 'confirmed'].includes(defenderTx.status)) {
+        const receipt = await web3.eth.getTransactionReceipt(tx.transactionHash);
+        await transactionMined(tx, receipt);
+    } else if (defenderTx.status === 'failed') {
+        tx.state = TransactionState.Failed;
+        await tx.save();
+    }
+
+    return tx.state;
+}
+
+async function queryTransactionStatusReceipt(tx: TransactionDocument) {
+    if ([TransactionState.Mined, TransactionState.Failed].includes(tx.state)) {
+        return tx;
+    }
+    const { web3 } = getProvider(tx.chainId);
+
+    const receipt = await web3.eth.getTransactionReceipt(tx.transactionHash);
+
+    if (receipt) {
+        // Wait 500 ms for transactions to be propagated to all nodes.
+        // Since we use multiple RPCs it happens we already have the receipt but the other RPC
+        // doesn't have the block available yet.
+        await new Promise((done) => setTimeout(done, 500));
+
+        await transactionMined(tx, receipt);
+    }
+
+    return tx.state;
+}
+
+async function findByQuery(poolAddress: string, page = 1, limit = 10, startDate?: Date, endDate?: Date) {
+    const query: Record<string, any> = { to: poolAddress };
+
+    if (startDate || endDate) query.createdAt = {};
+    if (startDate) {
+        query.createdAt['$gte'] = startDate;
+    }
+    if (endDate) {
+        query.createdAt['$lt'] = endDate;
+    }
+
+    return paginatedResults(Transaction, page, limit, query);
+}
+
+export default {
+    getById,
+    send,
+    sendAsync,
+    deploy,
+    sendValue,
+    findByQuery,
+    sendSafeAsync,
+    execSafeAsync,
+    queryTransactionStatusDefender,
+    queryTransactionStatusReceipt,
+    executeCallback,
+    proposeSafeAsync,
+};
diff --git a/apps/api/src/app/services/TwitterCacheService.ts b/apps/api/src/app/services/TwitterCacheService.ts
new file mode 100644
index 000000000..cdd79d4ee
--- /dev/null
+++ b/apps/api/src/app/services/TwitterCacheService.ts
@@ -0,0 +1,264 @@
+import { AccessTokenKind, JobType, OAuthRequiredScopes, OAuthTwitterScope } from '@thxnetwork/common/enums';
+import { agenda } from '../util/agenda';
+import { Job, QuestSocial, TwitterLike, TwitterQueryDocument, TwitterRepost, TwitterUser } from '../models';
+import { AxiosResponse } from 'axios';
+import { logger } from '../util/logger';
+import AccountProxy from '../proxies/AccountProxy';
+import TwitterDataProxy from '../proxies/TwitterDataProxy';
+import { TwitterPost } from '../models/TwitterPost';
+
+function findUserById(users: { id: string }[], userId: string) {
+    return users.find((user: { id: string }) => user.id === userId);
+}
+
+export default class TwitterCacheService {
+    static savePosts(posts: TTwitterPostWithUserAndMedia[] = [], query?: TwitterQueryDocument) {
+        return Promise.all(
+            posts.map(async (post) => {
+                await this.savePost(post, post.media, query);
+                await this.saveUser(post.user);
+            }),
+        );
+    }
+
+    static savePost(post: TTwitterPostResponse, media: TTwitterMediaResponse[] = [], query?: TwitterQueryDocument) {
+        return TwitterPost.findOneAndUpdate(
+            {
+                postId: post.id,
+                queryId: query && query.id,
+            },
+            {
+                postId: post.id,
+                queryId: query && query.id,
+                userId: post.author_id,
+                text: post.text,
+                media: media.map((m: TTwitterMediaResponse) => ({
+                    url: m.url,
+                    type: m.type,
+                    previewImageUrl: m.preview_image_url,
+                    width: m.width,
+                    height: m.height,
+                })),
+            },
+            { upsert: true, new: true },
+        );
+    }
+
+    static saveUser(user: TTwitterUserResponse) {
+        if (!user) return;
+        return TwitterUser.findOneAndUpdate(
+            { userId: user.id },
+            {
+                userId: user.id,
+                profileImgUrl: user.profile_image_url,
+                name: user.name,
+                username: user.username,
+                publicMetrics: {
+                    followersCount: user.public_metrics.followers_count,
+                    followingCount: user.public_metrics.following_count,
+                    tweetCount: user.public_metrics.tweet_count,
+                    listedCount: user.public_metrics.listed_count,
+                    likeCount: user.public_metrics.like_count,
+                },
+            },
+            { upsert: true, new: true },
+        );
+    }
+
+    static async updatePostCache(
+        account: TAccount,
+        quest: TQuestSocial,
+        token: TToken,
+        params: TTwitterRequestParams = { max_results: 100 },
+    ) {
+        try {
+            logger.info(`[${quest.poolId}][${account.sub}] X Quest ${quest._id} Post verification calls X API.`);
+            const data = await TwitterDataProxy.request(token, {
+                url: `/tweets/search/recent`,
+                method: 'GET',
+                params,
+            });
+            logger.info(`Fetched ${data.meta.result_count} reposts from X.`);
+
+            // If no results return early
+            if (!data.meta.result_count) return;
+        } catch (res) {
+            await this.handleRateLimitError(res, account, quest, params, JobType.UpdateTwitterRepostCache);
+        }
+    }
+
+    static async updateRepostCache(
+        account: TAccount,
+        quest: TQuestSocial,
+        token: TToken,
+        params: TTwitterRequestParams = { max_results: 100 },
+    ) {
+        const postId = quest.content;
+        try {
+            logger.info(`[${quest.poolId}][${account.sub}] X Quest ${quest._id} Repost verification calls X API.`);
+            const data = await TwitterDataProxy.request(token, {
+                url: `/tweets/${postId}/retweeted_by`,
+                method: 'GET',
+                params,
+            });
+            logger.info(`Fetched ${data.meta.result_count} reposts from X.`);
+
+            // If no results return early
+            if (!data.meta.result_count) return;
+
+            // If not then we upsert all TwitterReposts into the database
+            const operations = data.data.map((user: { id: string }) => ({
+                updateOne: {
+                    filter: { userId: user.id, postId },
+                    update: { userId: user.id, postId },
+                    upsert: true,
+                },
+            }));
+            await TwitterRepost.bulkWrite(operations);
+
+            // If the user has reposted the post, we return early
+            if (findUserById(data.data, token.userId)) return;
+
+            // If there is a next_token, we store the next_token in case we get rate limited
+            // and continue on the next page
+            if (data.meta.next_token) {
+                // Start with caching the next 100 results
+                await this.updateRepostCache(account, quest, token, {
+                    ...params,
+                    pagination_token: data.meta.next_token,
+                });
+            }
+        } catch (res) {
+            await this.handleRateLimitError(res, account, quest, params, JobType.UpdateTwitterRepostCache);
+        }
+    }
+
+    static async updateLikeCache(
+        account: TAccount,
+        quest: TQuestSocial,
+        token: TToken,
+        params: TTwitterRequestParams = { max_results: 100 },
+    ) {
+        const postId = quest.content;
+
+        try {
+            logger.info(`[${quest.poolId}][${account.sub}] X Quest ${quest._id} Like verification calls X API.`);
+            const data = await TwitterDataProxy.request(token, {
+                url: `/tweets/${postId}/liking_users`,
+                method: 'GET',
+                params,
+            });
+            logger.info(`Fetched ${data.meta.result_count} likes from X.`);
+
+            // If no results return early
+            if (!data.meta.result_count) return;
+
+            // If not then we upsert all TwitterLikes into the database
+            const operations = data.data.map((user: { id: string }) => ({
+                updateOne: {
+                    filter: { userId: user.id, postId },
+                    update: { userId: user.id, postId },
+                    upsert: true,
+                },
+            }));
+            await TwitterLike.bulkWrite(operations);
+
+            // If the user has liked the post, we return early
+            if (findUserById(data.data, token.userId)) return;
+
+            // If there is a next_token, we store the next_token in case we get rate limited
+            // and continue on the next page
+            if (data.meta.next_token) {
+                // Start with caching the next 100 results
+                await this.updateLikeCache(account, quest, token, {
+                    ...params,
+                    pagination_token: data.meta.next_token,
+                });
+            }
+        } catch (res) {
+            await this.handleRateLimitError(res, account, quest, params, JobType.UpdateTwitterLikeCache);
+        }
+    }
+
+    static async handleRateLimitError(
+        res: AxiosResponse,
+        account: TAccount,
+        quest: TQuestSocial,
+        params: TTwitterRequestParams,
+        jobType: JobType,
+    ) {
+        // Retrow the error if it's not a rate limit error
+        if (res.status === 429) {
+            const sub = account.sub;
+            const questId = String(quest._id);
+            const job = await Job.findOne({
+                'name': jobType,
+                'data.sub': sub,
+                'data.questId': questId,
+            });
+
+            if (!job) {
+                const resetTime = Number(res.headers['x-rate-limit-reset']);
+                const seconds = resetTime - Math.ceil(Date.now() / 1000);
+                const minutes = Math.ceil(seconds / 60);
+
+                // Resume caching when rate limit is reset
+                await agenda.schedule(`in ${minutes} minutes`, jobType, {
+                    sub,
+                    questId,
+                    params,
+                });
+
+                logger.info('Scheduled updateLikeCacheJob', { questId, sub, params });
+            }
+        }
+
+        throw res;
+    }
+
+    static async updateRepostCacheJob(job: TJob) {
+        await this.updateCacheJob(job, OAuthRequiredScopes.TwitterValidateRepost, this.updateRepostCache.bind(this));
+    }
+
+    static async updateLikeCacheJob(job: TJob) {
+        await this.updateCacheJob(job, OAuthRequiredScopes.TwitterValidateLike, this.updateLikeCache.bind(this));
+    }
+
+    static async updateCacheJob(
+        job: TJob,
+        scopes: OAuthTwitterScope[],
+        updateCacheCallback: (
+            account: TAccount,
+            quest: TQuestSocial,
+            token: TToken,
+            params: TTwitterRequestParams,
+        ) => Promise<void>,
+    ) {
+        const { questId, sub, params } = job.attrs.data as {
+            sub: string;
+            questId: string;
+            params: TTwitterRequestParams;
+        };
+        logger.info(`Starting ${job.attrs.name}`, params);
+
+        try {
+            const quest = await QuestSocial.findById(questId);
+            if (!quest) throw new Error(`No token found for questId ${questId}.`);
+
+            const account = await AccountProxy.findById(sub);
+            if (!account) throw new Error(`No account found for sub ${sub}.`);
+
+            const token = await AccountProxy.getToken(account, AccessTokenKind.Twitter, scopes);
+            if (!token) throw new Error(`No token found for sub ${sub}.`);
+
+            // Remove this job so it can be recreated if another rate limit is hit
+            await job.remove();
+
+            // Continue cache update for likes or reposts until the last page is reached
+            // or the next rate limit is hit
+            await updateCacheCallback(account, quest, token, params);
+        } catch (error) {
+            logger.error(error.response ? error.response : error);
+        }
+    }
+}
diff --git a/apps/api/src/app/services/TwitterQueryService.ts b/apps/api/src/app/services/TwitterQueryService.ts
new file mode 100644
index 000000000..c88aae3c7
--- /dev/null
+++ b/apps/api/src/app/services/TwitterQueryService.ts
@@ -0,0 +1,131 @@
+import QuestService from './QuestService';
+import TwitterDataProxy from '../proxies/TwitterDataProxy';
+import TwitterCacheService from './TwitterCacheService';
+import MailService from './MailService';
+import AccountProxy from '../proxies/AccountProxy';
+import { Pool, PoolDocument, QuestSocial, TwitterQuery, TwitterQueryDocument } from '../models';
+import { DASHBOARD_URL } from '../config/secrets';
+import { QuestSocialRequirement, QuestVariant } from '@thxnetwork/common/enums';
+import { logger } from '../util/logger';
+import { TwitterPost } from '../models/TwitterPost';
+
+const ONE_DAY_IN_MS = 24 * 60 * 60 * 1000;
+
+export default class TwitterQueryService {
+    static async searchJob() {
+        const queries = await TwitterQuery.find();
+        await this.run(queries);
+    }
+
+    static async list(query: { poolId: string }): Promise<TwitterQueryDocument[]> {
+        const queries = await TwitterQuery.find(query);
+        return await Promise.all(
+            queries.map(async (query) => {
+                const posts = await TwitterPost.find({ queryId: query.id });
+                return { ...query.toJSON(), posts };
+            }),
+        );
+    }
+
+    static async run(queries: TwitterQueryDocument[]) {
+        const poolIds = queries.map((query) => query.poolId);
+        const pools = await Pool.find({ _id: { $in: poolIds } });
+        const subs = pools.map((pool) => pool.sub);
+        const accounts = await AccountProxy.find({ subs });
+
+        for (const query of queries) {
+            try {
+                const pool = pools.find((pool) => pool.id === query.poolId);
+                if (!pool) continue;
+
+                const account = accounts.find((account) => account.sub === pool.sub);
+                if (!account) continue;
+
+                const posts = await this.search(account, query);
+                if (!posts.length) continue;
+
+                // Send notification to campaign owner if new quests are created
+                await this.sendMail(account, pool, posts);
+            } catch (error) {
+                logger.error(error);
+            }
+        }
+    }
+
+    static async search(account: TAccount, query: TwitterQueryDocument) {
+        const posts = await TwitterDataProxy.search(account, query.query);
+
+        // Filter out the posts that already have a quest
+        const postIds = posts.map((post) => post.id);
+        const quests = await QuestSocial.find({ poolId: query.poolId, content: { $in: postIds } });
+        const postsWithoutQuest = posts.filter((post) => {
+            return !quests.some((quest) => quest.content === post.id);
+        });
+
+        // Iterate over posts and create quests
+        for (const post of postsWithoutQuest) {
+            await this.createQuest(
+                query,
+                await TwitterCacheService.savePost(post, post.media, query),
+                await TwitterCacheService.saveUser(post.user),
+            );
+        }
+
+        return postsWithoutQuest;
+    }
+
+    static async sendMail(account: TAccount, pool: PoolDocument, posts: TTwitterPostWithUserAndMedia[]) {
+        const src = new URL(DASHBOARD_URL);
+        src.pathname = `/pool/${pool.id}/quests`;
+        src.searchParams.append('isPublished', 'false');
+
+        await MailService.send(
+            account.email,
+            '👀 New matches for your query!',
+            `<p>Hi!</p>
+            <p>We found matches for your X query!</p>
+            ${posts
+                .map(
+                    (post) =>
+                        `<div><strong>${post.user.username}</strong> <span>(${
+                            post.public_metrics.impression_count
+                        } views)</span></div>
+                    <p>${post.text.substring(0, 100)}... <a href="https://x.com/${post.user.username}/status/${
+                            post.id
+                        }" target="_blank">
+                    View Post    
+                    </a></p>`,
+                )
+                .join('<br />')}
+            `,
+            { src: src.toString(), text: 'Publish Quests' },
+        );
+    }
+
+    static async createQuest(query: TTwitterQuery, post: TTwitterPost, user: TTwitterUser) {
+        const file = null; // TODO Download buffer for the media first URL and upload with quest
+        const quest = {
+            kind: 'twitter',
+            interaction: QuestSocialRequirement.TwitterLikeRetweet,
+            title: 'Repost & Like',
+            description: query.defaults.description,
+            amount: query.defaults.amount,
+            locks: query.defaults.locks,
+            isPublished: query.defaults.isPublished,
+            content: post.postId,
+            contentMetadata: JSON.stringify({
+                url: `https://twitter.com/${user.username.toLowerCase()}/status/${post.postId}`,
+                username: user.username,
+                name: user.name,
+                text: post.text,
+                minFollowersCount: query.defaults.minFollowersCount,
+            }),
+        };
+
+        if (query.defaults.expiryInDays > 0) {
+            quest['expiryDate'] = new Date(Date.now() + query.defaults.expiryInDays * ONE_DAY_IN_MS);
+        }
+
+        await QuestService.create(QuestVariant.Twitter, query.poolId, quest, file);
+    }
+}
diff --git a/apps/api/src/app/services/VoteEscrowService.ts b/apps/api/src/app/services/VoteEscrowService.ts
new file mode 100644
index 000000000..a8c8c67b9
--- /dev/null
+++ b/apps/api/src/app/services/VoteEscrowService.ts
@@ -0,0 +1,198 @@
+import { getProvider } from '@thxnetwork/api/util/network';
+import { contractArtifacts, contractNetworks } from '@thxnetwork/api/hardhat';
+import { ChainId } from '@thxnetwork/common/enums';
+import { WalletDocument } from '@thxnetwork/api/models';
+import { toChecksumAddress } from 'web3-utils';
+import TransactionService from '@thxnetwork/api/services/TransactionService';
+import { logger } from '../util/logger';
+import { NODE_ENV } from '../config/secrets';
+
+async function isApprovedAddress(address: string, chainId: ChainId) {
+    const { web3 } = getProvider(chainId);
+    const whitelist = new web3.eth.Contract(
+        contractArtifacts['SmartWalletWhitelist'].abi,
+        contractNetworks[chainId].SmartWalletWhitelist,
+    );
+    return await whitelist.methods.check(address).call();
+}
+
+async function list(wallet: WalletDocument) {
+    const { web3 } = getProvider(wallet.chainId);
+    const ve = new web3.eth.Contract(
+        contractArtifacts['VotingEscrow'].abi,
+        contractNetworks[wallet.chainId].VotingEscrow,
+    );
+    return await ve.methods.locked(wallet.address).call();
+}
+
+async function getAllowance(wallet: WalletDocument, tokenAddress: string, spender: string) {
+    const { web3 } = getProvider(wallet.chainId);
+    const bpt = new web3.eth.Contract(contractArtifacts['BPT'].abi, tokenAddress);
+    return await bpt.methods.allowance(wallet.address, spender).call();
+}
+
+async function approve(wallet: WalletDocument, tokenAddress: string, spender: string, amount: string) {
+    const { web3 } = getProvider(wallet.chainId);
+    const bpt = new web3.eth.Contract(contractArtifacts['BPT'].abi, tokenAddress);
+    const fn = bpt.methods.approve(spender, amount);
+
+    // Propose tx data to relayer and return safeTxHash to client to sign
+    return await TransactionService.sendSafeAsync(wallet, bpt.options.address, fn);
+}
+
+async function increaseAmount(wallet: WalletDocument, amountInWei: string) {
+    const { web3 } = getProvider(wallet.chainId);
+    const ve = new web3.eth.Contract(
+        contractArtifacts['VotingEscrow'].abi,
+        contractNetworks[wallet.chainId].VotingEscrow,
+    );
+    const fn = ve.methods.increase_amount(amountInWei);
+    return TransactionService.sendSafeAsync(wallet, contractNetworks[wallet.chainId].VotingEscrow, fn);
+}
+
+async function increaseUnlockTime(wallet: WalletDocument, endTimestamp: number) {
+    const { web3 } = getProvider(wallet.chainId);
+    const ve = new web3.eth.Contract(
+        contractArtifacts['VotingEscrow'].abi,
+        contractNetworks[wallet.chainId].VotingEscrow,
+    );
+    const fn = ve.methods.increase_unlock_time(endTimestamp);
+    return TransactionService.sendSafeAsync(wallet, contractNetworks[wallet.chainId].VotingEscrow, fn);
+}
+
+async function deposit(wallet: WalletDocument, amountInWei: string, endTimestamp: number) {
+    const { web3 } = getProvider(wallet.chainId);
+    const ve = new web3.eth.Contract(
+        contractArtifacts['VotingEscrow'].abi,
+        contractNetworks[wallet.chainId].VotingEscrow,
+    );
+    const fn = ve.methods.create_lock(amountInWei, endTimestamp);
+    return TransactionService.sendSafeAsync(wallet, contractNetworks[wallet.chainId].VotingEscrow, fn);
+}
+
+async function withdraw(wallet: WalletDocument, isEarlyWithdraw: boolean) {
+    const { web3 } = getProvider(wallet.chainId);
+    const ve = new web3.eth.Contract(
+        contractArtifacts['VotingEscrow'].abi,
+        contractNetworks[wallet.chainId].VotingEscrow,
+    );
+
+    // Check for lock and determine ve function to call
+    const fn = isEarlyWithdraw ? ve.methods.withdraw_early() : ve.methods.withdraw();
+
+    // Propose tx data to relayer and return safeTxHash to client to sign
+    const tx = await TransactionService.sendSafeAsync(wallet, ve.options.address, fn);
+
+    return [tx];
+}
+
+async function listRewards(wallet: WalletDocument) {
+    const { web3 } = getProvider(wallet.chainId);
+
+    // Get reward tokens
+    const lr = new web3.eth.Contract(contractArtifacts['LensReward'].abi, contractNetworks[wallet.chainId].LensReward);
+
+    // Call static
+    const rewardTokens = [contractNetworks[wallet.chainId].BAL, contractNetworks[wallet.chainId].BPT];
+    const callStatic = async (fn) => {
+        const result = await web3.eth.call({
+            to: contractNetworks[wallet.chainId].LensReward,
+            data: fn.encodeABI(),
+            from: toChecksumAddress(wallet.address),
+        });
+        return web3.eth.abi.decodeParameters(
+            [
+                {
+                    type: 'tuple[]',
+                    components: [
+                        { type: 'address', name: 'tokenAddress' },
+                        { type: 'uint256', name: 'amount' },
+                    ],
+                },
+            ],
+            result,
+        );
+    };
+
+    // Call static on rewards
+    const rewards = await callStatic(
+        lr.methods.getUserClaimableRewardsAll(
+            contractNetworks[wallet.chainId].RewardDistributor,
+            toChecksumAddress(wallet.address),
+            rewardTokens,
+        ),
+    );
+
+    // Util functions to get amount for tokenAddress from call
+    const getAmount = (tokenAddress: string) => {
+        return rewards['0'].find((r) => r.tokenAddress === tokenAddress)?.amount;
+    };
+    const { BAL, BPT } = contractNetworks[wallet.chainId];
+
+    return [
+        {
+            tokenAddress: BAL,
+            amount: getAmount(BAL),
+            symbol: 'BAL',
+        },
+        {
+            tokenAddress: BPT,
+            amount: getAmount(BPT),
+            symbol: '20USDC-80THX',
+        },
+    ];
+}
+
+async function claimTokens(wallet: WalletDocument) {
+    const { web3 } = getProvider(wallet.chainId);
+    const rewardDistributor = new web3.eth.Contract(
+        contractArtifacts['RewardDistributor'].abi,
+        contractNetworks[wallet.chainId].RewardDistributor,
+    );
+
+    // List reward tokens and build function call
+    const rewardTokens = await rewardDistributor.methods.getAllowedRewardTokens().call();
+    const fn = rewardDistributor.methods.claimTokens(wallet.address, rewardTokens);
+
+    // Propose tx data to relayer and return safeTxHash to client to sign
+    const tx = await TransactionService.sendSafeAsync(wallet, rewardDistributor.options.address, fn);
+
+    return [tx];
+}
+
+async function claimExternalRewardsJob() {
+    for (const chainId of [ChainId.Hardhat, ChainId.Polygon]) {
+        try {
+            if (NODE_ENV === 'production' && chainId === ChainId.Hardhat) continue;
+            const { web3 } = getProvider(chainId);
+            const ve = new web3.eth.Contract(
+                contractArtifacts['VotingEscrow'].abi,
+                contractNetworks[chainId].VotingEscrow,
+            );
+
+            // Execute directly using the relayer
+            const receipt = await TransactionService.send(
+                ve.options.address,
+                ve.methods.claimExternalRewards(),
+                chainId,
+            );
+            logger.info(`ClaimExternalRewards: ${receipt.transactionHash}`);
+        } catch (error) {
+            logger.error(`ClaimExternalRewards: ${error && error.message}`);
+        }
+    }
+}
+
+export default {
+    claimExternalRewardsJob,
+    list,
+    isApprovedAddress,
+    approve,
+    getAllowance,
+    deposit,
+    withdraw,
+    listRewards,
+    claimTokens,
+    increaseAmount,
+    increaseUnlockTime,
+};
diff --git a/apps/api/src/app/services/WalletService.ts b/apps/api/src/app/services/WalletService.ts
new file mode 100644
index 000000000..b8ede9c58
--- /dev/null
+++ b/apps/api/src/app/services/WalletService.ts
@@ -0,0 +1,66 @@
+import { Wallet } from '@thxnetwork/api/models/Wallet';
+import { TransactionState, WalletVariant } from '@thxnetwork/common/enums';
+import { Transaction } from '@thxnetwork/api/models/Transaction';
+import ContractService, { safeVersion } from './ContractService';
+import SafeService from './SafeService';
+
+export default class WalletService {
+    static findById(id: string) {
+        if (!id) return;
+        return Wallet.findById(id);
+    }
+
+    static async list(account: TAccount): Promise<TWallet[]> {
+        // List all wallets owned by the account but filter out wallets used for the campaign
+        const wallets = await Wallet.find({
+            sub: account.sub,
+            variant: { $in: [WalletVariant.Safe, WalletVariant.WalletConnect] },
+            address: { $exists: true, $ne: null },
+            poolId: { $exists: false },
+        });
+
+        return await Promise.all(
+            wallets.map(async (wallet) => {
+                const pendingTransactions = await Transaction.find({
+                    walletId: String(wallet._id),
+                    state: TransactionState.Confirmed,
+                });
+                const short = wallet.address && WalletService.formatAddress(wallet.address);
+
+                return { ...wallet.toJSON(), short, pendingTransactions };
+            }),
+        );
+    }
+
+    static findOne(query: Partial<TWallet>) {
+        return Wallet.findOne(query);
+    }
+
+    static formatAddress(address: string) {
+        return `${address.slice(0, 5)}...${address.slice(-3)}`;
+    }
+
+    static create(variant: WalletVariant, data: Partial<TWallet>) {
+        const chainId = ContractService.getChainId();
+        const map = {
+            [WalletVariant.Safe]: WalletService.createSafe,
+            [WalletVariant.WalletConnect]: WalletService.createWalletConnect,
+        };
+        return map[variant]({ ...(data as TWallet), chainId });
+    }
+
+    static async createSafe({ sub, address, chainId }) {
+        const safeWallet = await SafeService.findOne({ sub });
+        // An account can have max 1 Safe
+        if (safeWallet) throw new Error('Already has a Safe.');
+
+        // Deploy a Safe with Web3Auth address and relayer as signers
+        await SafeService.create({ sub, chainId, safeVersion }, address);
+    }
+
+    static async createWalletConnect({ sub, address, chainId }) {
+        const data: Partial<TWallet> = { variant: WalletVariant.WalletConnect, sub, address, chainId };
+
+        await Wallet.findOneAndUpdate({ sub, address, chainId }, data, { upsert: true });
+    }
+}
diff --git a/apps/api/src/app/services/WebhookService.ts b/apps/api/src/app/services/WebhookService.ts
new file mode 100644
index 000000000..48cb7b940
--- /dev/null
+++ b/apps/api/src/app/services/WebhookService.ts
@@ -0,0 +1,94 @@
+import axios from 'axios';
+import { Pool } from '@thxnetwork/api/models';
+import { Webhook, WebhookDocument } from '@thxnetwork/api/models/Webhook';
+import { Identity } from '@thxnetwork/api/models/Identity';
+import { WebhookRequest, WebhookRequestDocument } from '@thxnetwork/api/models/WebhookRequest';
+import { Job } from '@hokify/agenda';
+import { agenda } from '@thxnetwork/api/util/agenda';
+import { signPayload } from '@thxnetwork/api/util/signingsecret';
+import { JobType, Event, WebhookRequestState } from '@thxnetwork/common/enums';
+
+export default class WebhookService {
+    static async request(webhook: WebhookDocument, account: TAccount, metadata?: string) {
+        const identities = (await Identity.find({ poolId: webhook.poolId, sub: account.sub })).map((i) => i.uuid);
+        const webhookRequest = await WebhookRequest.create({
+            webhookId: webhook._id,
+            payload: JSON.stringify({ type: 'quest_entry.create', identities, metadata }),
+            state: WebhookRequestState.Pending,
+        });
+
+        return await this.executeRequest(webhook, webhookRequest);
+    }
+
+    static async requestAsync(
+        webhook: WebhookDocument,
+        sub: string,
+        payload: { type: Event; data: any & { metadata: any } },
+    ) {
+        const identities = (await Identity.find({ poolId: webhook.poolId, sub })).map((i) => i.uuid);
+        const webhookRequest = await WebhookRequest.create({
+            webhookId: webhook._id,
+            payload: JSON.stringify({ ...payload, identities }),
+            state: WebhookRequestState.Pending,
+        });
+
+        await agenda.now(JobType.RequestAttemp, {
+            webhookRequestId: String(webhookRequest._id),
+            poolId: webhook.poolId,
+        });
+    }
+
+    static async requestAttemptJob(job: Job) {
+        const { webhookRequestId } = job.attrs.data as any;
+
+        const webhookRequest = await WebhookRequest.findById(webhookRequestId);
+        if (!webhookRequest) throw new Error('No webhook request object found');
+
+        const webhook = await Webhook.findById(webhookRequest.webhookId);
+        if (!webhook) throw new Error('No webhook object found');
+
+        await this.executeRequest(webhook, webhookRequest);
+    }
+
+    static async executeRequest(webhook: WebhookDocument, webhookRequest: WebhookRequestDocument) {
+        try {
+            const pool = await Pool.findById(webhook.poolId);
+            if (!pool.signingSecret) throw new Error('No signing secret found');
+
+            const signature = signPayload(webhookRequest.payload, pool.signingSecret);
+            webhookRequest.state = WebhookRequestState.Sent;
+
+            const response = await axios({
+                method: 'POST',
+                url: webhook.url,
+                data: { signature, payload: webhookRequest.payload },
+                headers: {
+                    'Content-Type': 'application/json',
+                },
+            });
+
+            webhookRequest.state = WebhookRequestState.Received;
+            webhookRequest.httpStatus = response.status;
+
+            console.debug(`[${response.status}], ${JSON.stringify(response.data)}`);
+
+            return response && response.data;
+        } catch (error) {
+            console.log(error);
+
+            webhookRequest.state = WebhookRequestState.Failed;
+            webhookRequest.failReason = error && error.toString();
+
+            // If there is an HTTP response we store the HTTP error and status code
+            if (error && error.response) {
+                webhookRequest.httpStatus = error.response.status;
+                webhookRequest.failReason = JSON.stringify(error.response.data);
+            }
+
+            console.error(error);
+        } finally {
+            webhookRequest.attempts = webhookRequest.attempts++;
+            await webhookRequest.save();
+        }
+    }
+}
diff --git a/apps/api/src/app/services/index.ts b/apps/api/src/app/services/index.ts
new file mode 100644
index 000000000..bdf4232e9
--- /dev/null
+++ b/apps/api/src/app/services/index.ts
@@ -0,0 +1,49 @@
+export * as AnalyticsService from './AnalyticsService';
+export * as BalancerService from './BalancerService';
+export * as BrandService from './BrandService';
+export * as CanvasService from './CanvasService';
+export * as ClaimService from './ClaimService';
+export * as ContractService from './ContractService';
+export * as DiscordService from './DiscordService';
+export * as ERC1155Service from './ERC1155Service';
+export * as ERC20Service from './ERC20Service';
+export * as ERC721Service from './ERC721Service';
+export * as GalachainService from './GalachainService';
+export * as GitcoinService from './GitcoinService';
+export * as IPFSService from './IPFSService';
+export * as IdentityService from './IdentityService';
+export * as ImageService from './ImageService';
+export * as InvoiceService from './InvoiceService';
+export * as LiquidityService from './LiquidityService';
+export * as LockService from './LockService';
+export * as MailService from './MailService';
+export * as NotificationService from './NotificationService';
+export * as ParticipantService from './ParticipantService';
+export * as PaymentService from './PaymentService';
+export * as PointBalanceService from './PointBalanceService';
+export * as PoolService from './PoolService';
+export * as QuestCustomService from './QuestCustomService';
+export * as QuestDailyService from './QuestDailyService';
+export * as QuestDiscordService from './QuestDiscordService';
+export * as QuestGitcoinService from './QuestGitcoinService';
+export * as QuestInviteService from './QuestInviteService';
+export * as QuestService from './QuestService';
+export * as QuestSocialService from './QuestSocialService';
+export * as QuestWeb3Service from './QuestWeb3Service';
+export * as QuestWebhookService from './QuestWebhookService';
+export * as ReCaptchaService from './ReCaptchaService';
+export * as RewardCoinService from './RewardCoinService';
+export * as RewardCouponService from './RewardCouponService';
+export * as RewardCustomService from './RewardCustomService';
+export * as RewardDiscordRoleService from './RewardDiscordRoleService';
+export * as RewardGalachainService from './RewardGalachainService';
+export * as RewardNFTService from './RewardNFTService';
+export * as RewardService from './RewardService';
+export * as SafeService from './SafeService';
+export * as THXService from './THXService';
+export * as TransactionService from './TransactionService';
+export * as TwitterCacheService from './TwitterCacheService';
+export * as TwitterQueryService from './TwitterQueryService';
+export * as VoteEscrowService from './VoteEscrowService';
+export * as WalletService from './WalletService';
+export * as WebhookService from './WebhookService';
diff --git a/apps/api/src/app/services/interfaces/IGalaService.ts b/apps/api/src/app/services/interfaces/IGalaService.ts
new file mode 100644
index 000000000..67731f3cd
--- /dev/null
+++ b/apps/api/src/app/services/interfaces/IGalaService.ts
@@ -0,0 +1,26 @@
+import { TokenInstanceKey, TokenClassKey, RegisterUserDto, UserProfile } from '@gala-chain/api';
+
+interface CustomProfileAPI {
+    GetProfile(privateKey: string): Promise<UserProfile>;
+    RegisterEthUser(publicKey: string): Promise<RegisterUserDto>;
+}
+
+interface CustomTokenAPI {
+    CoinBalanceOf({ tokenInstance, owner }: { tokenInstance: TokenInstanceKey; owner: string }): Promise<any>;
+    CoinCreate(
+        tokenInfo: {
+            image: string;
+            name: string;
+            description: string;
+            symbol: string;
+            decimals: number;
+            maxSupply: any;
+        },
+        privateKey: string,
+    ): Promise<TokenClassKey>;
+    CoinApprove(options: { spender: string; amount: number }, privateKey: string): Promise<any>;
+    CoinMint(options: { to: string; amount: number }, privateKey: string): Promise<TokenClassKey>;
+    CoinTransfer(options: { to: string; amount: number }, privateKey: string): Promise<any>;
+}
+
+export { CustomProfileAPI, CustomTokenAPI };
diff --git a/apps/api/src/app/services/interfaces/IQuestService.ts b/apps/api/src/app/services/interfaces/IQuestService.ts
new file mode 100644
index 000000000..777650f9c
--- /dev/null
+++ b/apps/api/src/app/services/interfaces/IQuestService.ts
@@ -0,0 +1,38 @@
+import { Model } from 'mongoose';
+import QuestInviteService from '../QuestInviteService';
+import QuestDiscordService from '../QuestDiscordService';
+import QuestTwitterService from '../QuestSocialService'; // Split
+import QuestYouTubeService from '../QuestSocialService'; // Split
+import QuestDailyService from '../QuestDailyService';
+import QuestCustomService from '../QuestCustomService';
+import QuestGitcoinService from '../QuestGitcoinService';
+import QuestWeb3Service from '../QuestWeb3Service';
+import QuestWebhookService from '../QuestWebhookService';
+import { QuestVariant } from '@thxnetwork/common/enums';
+
+export interface IQuestService {
+    models: { quest: Model<TQuest>; entry: Model<TQuestEntry> };
+    decorate(options: { quest: TQuest; account?: TAccount; data: Partial<TQuestEntry> }): Promise<TQuest>;
+    isAvailable(options: { quest: TQuest; account?: TAccount; data: Partial<TQuestEntry> }): Promise<TValidationResult>;
+    getAmount(options: { quest: TQuest; account?: TAccount }): Promise<number>;
+    getValidationResult(options: {
+        quest: TQuest;
+        account: TAccount;
+        data: Partial<TQuestEntry>;
+    }): Promise<TValidationResult>;
+    findEntryMetadata(options: { quest: TQuest });
+}
+
+export const serviceMap: {
+    [variant: number]: IQuestService;
+} = {
+    [QuestVariant.Daily]: new QuestDailyService(),
+    [QuestVariant.Invite]: new QuestInviteService(),
+    [QuestVariant.Discord]: new QuestDiscordService(),
+    [QuestVariant.Twitter]: new QuestTwitterService(),
+    [QuestVariant.YouTube]: new QuestYouTubeService(),
+    [QuestVariant.Custom]: new QuestCustomService(),
+    [QuestVariant.Web3]: new QuestWeb3Service(),
+    [QuestVariant.Gitcoin]: new QuestGitcoinService(),
+    [QuestVariant.Webhook]: new QuestWebhookService(),
+};
diff --git a/apps/api/src/app/services/interfaces/IRewardService.ts b/apps/api/src/app/services/interfaces/IRewardService.ts
new file mode 100644
index 000000000..0ffd58ac3
--- /dev/null
+++ b/apps/api/src/app/services/interfaces/IRewardService.ts
@@ -0,0 +1,32 @@
+import { Model } from 'mongoose';
+import { WalletDocument } from '@thxnetwork/api/models';
+
+export interface IRewardService {
+    models: {
+        reward: Model<TReward>;
+        payment: Model<TRewardPayment>;
+    };
+    decorate(data: { reward: TReward; account?: TAccount }): Promise<TReward>;
+    decoratePayment(payment: TRewardPayment): Promise<TRewardPayment>;
+    getValidationResult(data: {
+        reward: TReward;
+        wallet?: WalletDocument;
+        safe?: WalletDocument;
+        account?: TAccount;
+    }): Promise<TValidationResult>;
+    create(data: Partial<TReward>): Promise<TReward>;
+    update(reward: TReward, updates: Partial<TReward>): Promise<TReward>;
+    remove(reward: TReward): Promise<void>;
+    findById(id: string): Promise<TReward>;
+    createPayment({
+        reward,
+        account,
+        safe,
+        wallet,
+    }: {
+        reward: TReward;
+        account: TAccount;
+        safe: WalletDocument;
+        wallet?: WalletDocument;
+    }): Promise<TValidationResult | void>;
+}
diff --git a/apps/api/src/app/services/maps/quests.ts b/apps/api/src/app/services/maps/quests.ts
new file mode 100644
index 000000000..bd13ebc3d
--- /dev/null
+++ b/apps/api/src/app/services/maps/quests.ts
@@ -0,0 +1,114 @@
+import { AccessTokenKind, QuestSocialRequirement, OAuthScope, OAuthRequiredScopes } from '@thxnetwork/common/enums';
+import { logger } from '@thxnetwork/api/util/logger';
+import DiscordDataProxy from '@thxnetwork/api/proxies/DiscordDataProxy';
+import TwitterDataProxy from '@thxnetwork/api/proxies/TwitterDataProxy';
+import YouTubeDataProxy from '@thxnetwork/api/proxies/YoutubeDataProxy';
+
+export const requirementMap: {
+    [interaction: number]: (account: TAccount, quest: TQuestSocial) => Promise<TValidationResult>;
+} = {
+    [QuestSocialRequirement.YouTubeLike]: async (account, quest) => {
+        return await YouTubeDataProxy.validateLike(account, quest.content);
+    },
+    [QuestSocialRequirement.YouTubeSubscribe]: async (account, quest) => {
+        return await YouTubeDataProxy.validateSubscribe(account, quest.content);
+    },
+    [QuestSocialRequirement.TwitterLike]: async (account, quest) => {
+        logger.info(`[${quest.poolId}][${account.sub}] X Quest ${quest._id} Like verification started`);
+
+        const validationResultUser = await TwitterDataProxy.validateUser(account, quest);
+        if (!validationResultUser.result) return validationResultUser;
+        const validationResultLike = await TwitterDataProxy.validateLike(account, quest);
+        if (!validationResultLike.result) return validationResultLike;
+    },
+    [QuestSocialRequirement.TwitterRetweet]: async (account, quest) => {
+        logger.info(`[${quest.poolId}][${account.sub}] X Quest ${quest._id} Repost verification started`);
+
+        const validationResultUser = await TwitterDataProxy.validateUser(account, quest);
+        if (!validationResultUser.result) return validationResultUser;
+        const validationResultRepost = await TwitterDataProxy.validateRetweet(account, quest);
+        if (!validationResultRepost.result) return validationResultRepost;
+    },
+    [QuestSocialRequirement.TwitterLikeRetweet]: async (account, quest) => {
+        logger.info(`[${quest.poolId}][${account.sub}] X Quest ${quest._id} LikeRepost verification started`);
+
+        const validationResultUser = await TwitterDataProxy.validateUser(account, quest);
+        if (!validationResultUser.result) return validationResultUser;
+        const validationResultLike = await TwitterDataProxy.validateLike(account, quest);
+        if (!validationResultLike.result) return validationResultLike;
+        const validationResultRepost = await TwitterDataProxy.validateRetweet(account, quest);
+        if (!validationResultRepost.result) return validationResultRepost;
+    },
+    [QuestSocialRequirement.TwitterFollow]: async (account, quest) => {
+        logger.info(`[${quest.poolId}][${account.sub}] X Quest ${quest._id} Follow verification started`);
+
+        const resultUser = await TwitterDataProxy.validateUser(account, quest);
+        if (!resultUser.result) return resultUser;
+        const validationResultFollow = await TwitterDataProxy.validateFollow(account, quest.content);
+        if (!validationResultFollow.result) return validationResultFollow;
+    },
+    [QuestSocialRequirement.TwitterQuery]: async (account, quest) => {
+        logger.info(`[${quest.poolId}][${account.sub}] X Quest ${quest._id} Message verification started`);
+        const resultUser = await TwitterDataProxy.validateUser(account, quest);
+        if (!resultUser.result) return resultUser;
+        const validationResultMessage = await TwitterDataProxy.validateQuery(account, quest);
+        if (!validationResultMessage.result) return validationResultMessage;
+    },
+    [QuestSocialRequirement.DiscordGuildJoined]: async (account, quest) => {
+        return await DiscordDataProxy.validateGuildJoined(account, quest.content);
+    },
+    [QuestSocialRequirement.DiscordGuildRole]: async (account, quest) => {
+        const { roleId } = JSON.parse(quest.contentMetadata);
+        return await DiscordDataProxy.validateGuildRole(account, quest.content, roleId);
+    },
+    [QuestSocialRequirement.DiscordMessage]: async (account, quest) => {
+        return { result: true, reason: '' };
+    },
+    [QuestSocialRequirement.DiscordMessageReaction]: async (account, quest) => {
+        return { result: true, reason: '' };
+    },
+};
+
+export const tokenInteractionMap: { [interaction: number]: { kind: AccessTokenKind; scopes: OAuthScope[] } } = {
+    [QuestSocialRequirement.YouTubeLike]: {
+        kind: AccessTokenKind.Google,
+        scopes: OAuthRequiredScopes.GoogleYoutubeLike,
+    },
+    [QuestSocialRequirement.YouTubeSubscribe]: {
+        kind: AccessTokenKind.Google,
+        scopes: OAuthRequiredScopes.GoogleYoutubeSubscribe,
+    },
+    [QuestSocialRequirement.TwitterLike]: {
+        kind: AccessTokenKind.Twitter,
+        scopes: OAuthRequiredScopes.TwitterValidateLike,
+    },
+    [QuestSocialRequirement.TwitterRetweet]: {
+        kind: AccessTokenKind.Twitter,
+        scopes: OAuthRequiredScopes.TwitterValidateRepost,
+    },
+    [QuestSocialRequirement.TwitterFollow]: {
+        kind: AccessTokenKind.Twitter,
+        scopes: OAuthRequiredScopes.TwitterValidateFollow,
+    },
+    [QuestSocialRequirement.TwitterQuery]: { kind: AccessTokenKind.Twitter, scopes: OAuthRequiredScopes.TwitterAuth },
+    [QuestSocialRequirement.TwitterLikeRetweet]: {
+        kind: AccessTokenKind.Twitter,
+        scopes: OAuthRequiredScopes.TwitterValidateLike,
+    },
+    [QuestSocialRequirement.DiscordGuildJoined]: {
+        kind: AccessTokenKind.Discord,
+        scopes: OAuthRequiredScopes.DiscordValidateGuild,
+    },
+    [QuestSocialRequirement.DiscordGuildRole]: {
+        kind: AccessTokenKind.Discord,
+        scopes: OAuthRequiredScopes.DiscordAuth,
+    },
+    [QuestSocialRequirement.DiscordMessage]: {
+        kind: AccessTokenKind.Discord,
+        scopes: OAuthRequiredScopes.DiscordAuth,
+    },
+    [QuestSocialRequirement.DiscordMessageReaction]: {
+        kind: AccessTokenKind.Discord,
+        scopes: OAuthRequiredScopes.DiscordAuth,
+    },
+};
diff --git a/apps/api/src/app/types/augment-express-request.d.ts b/apps/api/src/app/types/augment-express-request.d.ts
new file mode 100644
index 000000000..de7385dff
--- /dev/null
+++ b/apps/api/src/app/types/augment-express-request.d.ts
@@ -0,0 +1,11 @@
+namespace Express {
+    interface Request {
+        origin?: string;
+        auth?: any;
+        rawBody?: string;
+        account?: TAccount;
+        wallet?: WalletDocument;
+        campaign?: PoolDocument;
+        quest?: TQuest;
+    }
+}
diff --git a/apps/api/src/app/types/cors.d.ts b/apps/api/src/app/types/cors.d.ts
new file mode 100644
index 000000000..8524732d2
--- /dev/null
+++ b/apps/api/src/app/types/cors.d.ts
@@ -0,0 +1 @@
+declare module 'cors';
diff --git a/apps/api/src/app/types/jsonwebtoken.d.ts b/apps/api/src/app/types/jsonwebtoken.d.ts
new file mode 100644
index 000000000..0493c0083
--- /dev/null
+++ b/apps/api/src/app/types/jsonwebtoken.d.ts
@@ -0,0 +1 @@
+declare module 'jsonwebtoken';
diff --git a/apps/api/src/app/types/migrate-mongo.d.ts b/apps/api/src/app/types/migrate-mongo.d.ts
new file mode 100644
index 000000000..4e6e50f6d
--- /dev/null
+++ b/apps/api/src/app/types/migrate-mongo.d.ts
@@ -0,0 +1,110 @@
+// Copied type definitions for migrate-mongo here since the @types/migrate-mongo
+// lags behind the current version. Once @types/migrate-mongo@^8.2.3 is available
+// we can install that again and remove this file.
+//
+// Project: https://github.com/seppevs/migrate-mongo#readme
+// Definitions by: Amit Beckenstein <https://github.com/amitbeck>
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
+// Minimum TypeScript Version: 3.2
+
+import * as mongo from 'mongodb';
+declare module 'migrate-mongo' {
+    export function init(): Promise<void>;
+    export function create(description: string): Promise<string>;
+    export namespace database {
+        function connect(): Promise<{
+            client: mongo.MongoClient;
+            db: mongo.Db & { close: mongo.MongoClient['close'] };
+        }>;
+    }
+    export namespace config {
+        /**
+         * @internal
+         */
+        const DEFAULT_CONFIG_FILE_NAME: string;
+        /**
+         * @internal
+         */
+        function shouldExist(): Promise<void>;
+        /**
+         * @internal
+         */
+        function shouldNotExist(): Promise<void>;
+        /**
+         * @internal
+         */
+        function getConfigFilename(): string;
+        /**
+         * Read the `migrate-mongo-config.js` file.
+         */
+        function read(): Promise<Config>;
+        /**
+         * Set the passed config object.
+         * @param config The config object.
+         */
+        function set(config: Partial<Config>): void;
+
+        interface Config {
+            mongodb: {
+                url: Parameters<typeof mongo.MongoClient['connect']>[0];
+                databaseName?: mongo.Db['databaseName'];
+                options?: mongo.MongoClientOptions;
+            };
+            /**
+             * The migrations dir, can be an relative or absolute path.
+             */
+            migrationsDir?: string | undefined;
+            /**
+             * The MongoDB collection where the applied changes are stored.
+             */
+            changelogCollectionName: string;
+        }
+    }
+
+    /**
+     * Apply all pending migrations.
+     * @example
+     * ```js
+     * const db = await database.connect();
+     * const migrated = await up(db);
+     * migrated.forEach(fileName => console.log('Migrated:', fileName));
+     * ```
+     * If an an error occurred, the promise will reject and won't continue with the rest of the pending migrations.
+     */
+    export function up(db: mongo.Db, client: mongo.MongoClient): Promise<string[]>;
+    /**
+     * Revert (only) the last applied migration.
+     * @example
+     * ```js
+     * const db = await database.connect();
+     * const migratedDown = await down(db);
+     * migratedDown.forEach(fileName => console.log('Migrated Down:', fileName));
+     * ```
+     */
+    export function down(db: mongo.Db, client: mongo.MongoClient): Promise<string[]>;
+    export function status(db: mongo.Db): Promise<MigrationStatus[]>;
+
+    export interface MigrationStatus {
+        fileName: string;
+        /**
+         * Either "PENDING" or a JSON date.
+         */
+        appliedAt: string;
+    }
+
+    /**
+     * Type of `up()` and `down()` functions for migration scripts.
+     */
+    export type MigrationFunction =
+        | ((db: mongo.Db, client: mongo.MongoClient) => Promise<void>)
+        /**
+         * @deprecated Callbacks are supported for backwards compatibility.
+         * New migration scripts should be written using `Promise`s and/or `async` & `await`. It's easier to read and write.
+         */
+        | ((db: mongo.Db, next: mongo.MongoCallback<mongo.UpdateWriteOpResult>) => void)
+        /**
+         * @deprecated Callbacks are supported for backwards compatibility.
+         * New migration scripts should be written using `Promise`s and/or `async` & `await`. It's easier to read and write.
+         */
+        | ((db: mongo.Db, client: mongo.MongoClient, next: mongo.MongoCallback<mongo.UpdateWriteOpResult>) => void);
+}
diff --git a/apps/api/src/app/types/morgan-json.d.ts b/apps/api/src/app/types/morgan-json.d.ts
new file mode 100644
index 000000000..2b861c8c3
--- /dev/null
+++ b/apps/api/src/app/types/morgan-json.d.ts
@@ -0,0 +1 @@
+declare module 'morgan-json';
diff --git a/apps/api/src/app/types/morgan.d.ts b/apps/api/src/app/types/morgan.d.ts
new file mode 100644
index 000000000..0b6637ede
--- /dev/null
+++ b/apps/api/src/app/types/morgan.d.ts
@@ -0,0 +1 @@
+declare module 'morgan';
diff --git a/apps/api/src/app/types/swagger-autogen.d.ts b/apps/api/src/app/types/swagger-autogen.d.ts
new file mode 100644
index 000000000..061dbe38b
--- /dev/null
+++ b/apps/api/src/app/types/swagger-autogen.d.ts
@@ -0,0 +1 @@
+declare module 'swagger-autogen';
diff --git a/apps/api/src/app/types/swagger-ui-express.d.ts b/apps/api/src/app/types/swagger-ui-express.d.ts
new file mode 100644
index 000000000..d7910fe9e
--- /dev/null
+++ b/apps/api/src/app/types/swagger-ui-express.d.ts
@@ -0,0 +1 @@
+declare module 'swagger-ui-express';
diff --git a/apps/api/src/app/util/agenda.ts b/apps/api/src/app/util/agenda.ts
new file mode 100644
index 000000000..c1ba701f2
--- /dev/null
+++ b/apps/api/src/app/util/agenda.ts
@@ -0,0 +1,64 @@
+import db from './database';
+import { Agenda, Job } from '@hokify/agenda';
+import { updatePendingTransactions } from '@thxnetwork/api/jobs/updatePendingTransactions';
+import { sendPoolAnalyticsReport } from '@thxnetwork/api/jobs/sendPoolAnalyticsReport';
+import { updateCampaignRanks } from '@thxnetwork/api/jobs/updateCampaignRanks';
+import { updateParticipantRanks } from '@thxnetwork/api/jobs/updateParticipantRanks';
+import { logger } from './logger';
+import { MONGODB_URI } from '../config/secrets';
+import { JobType } from '@thxnetwork/common/enums';
+import SafeService from '@thxnetwork/api/services/SafeService';
+import WebhookService from '../services/WebhookService';
+import QuestService from '../services/QuestService';
+import TwitterCacheService from '../services/TwitterCacheService';
+import InvoiceService from '../services/InvoiceService';
+import BalancerService from '../services/BalancerService';
+import RewardService from '../services/RewardService';
+import PaymentService from '../services/PaymentService';
+import TwitterQueryService from '../services/TwitterQueryService';
+import VoteEscrowService from '../services/VoteEscrowService';
+
+const agenda = new Agenda({
+    db: {
+        address: MONGODB_URI,
+        collection: 'jobs',
+    },
+    maxConcurrency: 1,
+    lockLimit: 1,
+    processEvery: '1 second',
+});
+
+agenda.define(JobType.UpdateCampaignRanks, updateCampaignRanks);
+agenda.define(JobType.UpdateParticipantRanks, updateParticipantRanks);
+agenda.define(JobType.UpdatePendingTransactions, updatePendingTransactions);
+agenda.define(JobType.CreateTwitterQuests, () => TwitterQueryService.searchJob());
+agenda.define(JobType.CreateQuestEntry, (job: Job) => QuestService.createEntryJob(job));
+agenda.define(JobType.CreateRewardPayment, (job: Job) => RewardService.createPaymentJob(job));
+agenda.define(JobType.DeploySafe, (job: Job) => SafeService.createJob(job));
+agenda.define(JobType.SendCampaignReport, sendPoolAnalyticsReport);
+agenda.define(JobType.RequestAttemp, (job: Job) => WebhookService.requestAttemptJob(job));
+agenda.define(JobType.UpdateTwitterLikeCache, (job: Job) => TwitterCacheService.updateLikeCacheJob(job));
+agenda.define(JobType.UpdateTwitterRepostCache, (job: Job) => TwitterCacheService.updateRepostCacheJob(job));
+agenda.define(JobType.UpsertInvoices, () => InvoiceService.upsertJob());
+agenda.define(JobType.UpdatePrices, () => BalancerService.updatePricesJob());
+agenda.define(JobType.UpdateAPR, () => BalancerService.updateMetricsJob());
+agenda.define(JobType.AssertPayments, () => PaymentService.assertPaymentsJob());
+agenda.define(JobType.ClaimExternalRewards, () => VoteEscrowService.claimExternalRewardsJob());
+
+db.connection.once('open', async () => {
+    await agenda.start();
+
+    await agenda.every('10 seconds', JobType.UpdatePrices);
+    await agenda.every('10 seconds', JobType.UpdatePendingTransactions);
+    await agenda.every('5 minutes', JobType.UpdateCampaignRanks);
+    await agenda.every('15 minutes', JobType.UpsertInvoices);
+    await agenda.every('15 minutes', JobType.UpdateAPR);
+    await agenda.every('1 day', JobType.CreateTwitterQuests);
+    await agenda.every('1 day', JobType.AssertPayments);
+    await agenda.every('1 day', JobType.ClaimExternalRewards);
+    await agenda.every('0 9 * * MON', JobType.SendCampaignReport);
+
+    logger.info('AgendaJS started job processor');
+});
+
+export { agenda, JobType };
diff --git a/apps/api/src/app/util/alchemy.ts b/apps/api/src/app/util/alchemy.ts
new file mode 100644
index 000000000..1f2c20387
--- /dev/null
+++ b/apps/api/src/app/util/alchemy.ts
@@ -0,0 +1,48 @@
+import { Network, Alchemy, OwnedNft } from 'alchemy-sdk';
+import { ALCHEMY_API_KEY } from '../config/secrets';
+import { logger } from './logger';
+import { IPFS_BASE_URL } from '@thxnetwork/api/config/secrets';
+
+export const alchemy = new Alchemy({
+    apiKey: ALCHEMY_API_KEY,
+    network: Network.MATIC_MAINNET,
+});
+
+export async function getNFTsForOwner(owner: string, contractAddress: string) {
+    const pageSize = 100;
+    let pageKey = 0,
+        pageCount = 1,
+        ownedNfts: OwnedNft[] = [];
+
+    while (pageKey < pageCount) {
+        try {
+            const key = String(++pageKey);
+            const result = await alchemy.nft.getNftsForOwner(owner, {
+                contractAddresses: [contractAddress],
+                omitMetadata: false,
+                pageSize,
+                pageKey: key,
+            });
+            const totalCount = Number(result.totalCount);
+
+            // If total is less than size there will only be 1 page, if not round up total / size
+            // to get the max amount of pages
+            pageCount = totalCount < pageSize ? 1 : Math.ceil(totalCount / pageSize);
+
+            ownedNfts = ownedNfts.concat(result.ownedNfts);
+        } catch (error) {
+            logger.error(error);
+        }
+    }
+
+    return ownedNfts;
+}
+
+export function parseIPFSImageUrl(url = 'ipfs://QmdnhWN8VjX45BfX7sJuUnwcr7HU9YcDPPLCPQhSuuyjZ3/0.png') {
+    const ipfsPrefix = 'ipfs://';
+    if (url.startsWith(ipfsPrefix)) {
+        const ipfsPath = url.substring(ipfsPrefix.length);
+        return IPFS_BASE_URL + ipfsPath;
+    }
+    return url;
+}
diff --git a/apps/api/src/app/util/auth.ts b/apps/api/src/app/util/auth.ts
new file mode 100644
index 000000000..af39da296
--- /dev/null
+++ b/apps/api/src/app/util/auth.ts
@@ -0,0 +1,52 @@
+import axios, { AxiosRequestConfig } from 'axios';
+import { THXError } from './errors';
+import { AUTH_CLIENT_ID, AUTH_CLIENT_SECRET, AUTH_URL } from '@thxnetwork/api/config/secrets';
+import { AxiosResponse } from 'axios';
+
+class AuthAccesTokenRequestError extends THXError {
+    message = 'Auth access token request failed';
+}
+
+let authAccessToken = '';
+let authAccessTokenExpires = 0;
+
+export const authClient = async (options: AxiosRequestConfig) => {
+    try {
+        axios.defaults.baseURL = AUTH_URL;
+        return await axios(options);
+    } catch (error) {
+        if (error && error.response && error.response.status >= 400 && error.response.status <= 600) {
+            return error.response as AxiosResponse;
+        }
+    }
+};
+
+async function requestAuthAccessToken() {
+    const data = new URLSearchParams();
+    data.append('grant_type', 'client_credentials');
+    data.append('scope', 'openid accounts:read accounts:write');
+
+    const r = await authClient({
+        url: '/token',
+        method: 'POST',
+        headers: {
+            'Content-Type': 'application/x-www-form-urlencoded',
+            'Authorization': 'Basic ' + Buffer.from(`${AUTH_CLIENT_ID}:${AUTH_CLIENT_SECRET}`).toString('base64'),
+        },
+        data,
+    });
+
+    if (r.status !== 200) throw new AuthAccesTokenRequestError();
+
+    return r.data;
+}
+
+export async function getAuthAccessToken() {
+    if (!authAccessTokenExpires || Date.now() > authAccessTokenExpires) {
+        const { access_token, expires_in } = await requestAuthAccessToken();
+        authAccessToken = access_token;
+        authAccessTokenExpires = Date.now() + expires_in * 1000;
+    }
+
+    return `Bearer ${authAccessToken}`;
+}
diff --git a/apps/api/src/app/util/code.ts b/apps/api/src/app/util/code.ts
new file mode 100644
index 000000000..ec31c91bd
--- /dev/null
+++ b/apps/api/src/app/util/code.ts
@@ -0,0 +1,12 @@
+import { ChainId } from '@thxnetwork/common/enums';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { NoDataAtAddressError } from '@thxnetwork/api/util/errors';
+
+export async function getCodeForAddressOnNetwork(address: string, chainId: ChainId) {
+    const { web3 } = getProvider(chainId);
+    const code = await web3.eth.getCode(address);
+
+    if (code === '0x') {
+        throw new NoDataAtAddressError(address);
+    }
+}
diff --git a/apps/campaign/src/assets/.gitkeep b/apps/api/src/app/util/condition.ts
similarity index 100%
rename from apps/campaign/src/assets/.gitkeep
rename to apps/api/src/app/util/condition.ts
diff --git a/apps/api/src/app/util/database.ts b/apps/api/src/app/util/database.ts
new file mode 100644
index 000000000..d4192af88
--- /dev/null
+++ b/apps/api/src/app/util/database.ts
@@ -0,0 +1,59 @@
+import mongoose from 'mongoose';
+import { logger } from './logger';
+import { v4 } from 'uuid';
+
+const connect = async (url: string) => {
+    mongoose.connection.on('error', (err) => {
+        logger.error(`MongoDB connection error. Please make sure MongoDB is running. ${err}`);
+    });
+
+    mongoose.connection.on('reconnectFailed', () => {
+        logger.error('Unable to reconnect to MongoDB');
+        process.exit();
+    });
+
+    mongoose.connection.on('open', () => {
+        logger.info(`MongoDB successfully connected to ${url.split('@')[1]}`);
+    });
+
+    mongoose.connection.on('close', () => {
+        logger.info(`MongoDB successfully closed connection`);
+    });
+
+    if (mongoose.connection.readyState === 0) {
+        await mongoose.connect(url);
+    }
+};
+
+const truncate = async () => {
+    if (mongoose.connection.readyState !== 0) {
+        const { collections } = mongoose.connection;
+        const promises = Object.keys(collections).map((collection) => {
+            return mongoose.connection.collection(collection).deleteMany({});
+        });
+        await Promise.all(promises);
+    }
+};
+
+const readyState = () => {
+    return mongoose.connection.readyState;
+};
+
+const disconnect = async () => {
+    if (mongoose.connection.readyState !== 0) {
+        await mongoose.disconnect();
+    }
+};
+
+const createUUID = () => {
+    return v4();
+};
+
+export default {
+    connect,
+    truncate,
+    disconnect,
+    readyState,
+    connection: mongoose.connection,
+    createUUID,
+};
diff --git a/apps/api/src/app/util/date.ts b/apps/api/src/app/util/date.ts
new file mode 100644
index 000000000..c4a1c04f1
--- /dev/null
+++ b/apps/api/src/app/util/date.ts
@@ -0,0 +1,18 @@
+export function addMinutes(date: Date, minutes: number) {
+    return new Date(date.getTime() + minutes * 60000);
+}
+
+export function subMinutes(date: Date, minutes: number) {
+    return new Date(date.getTime() - minutes * 60000);
+}
+
+export function formatDate(date: Date) {
+    const yyyy = date.getFullYear();
+    let mm: any = date.getMonth() + 1; // Months start at 0!
+    let dd: any = date.getDate();
+
+    if (dd < 10) dd = '0' + dd;
+    if (mm < 10) mm = '0' + mm;
+
+    return yyyy + '-' + mm + '-' + dd;
+}
diff --git a/apps/api/src/app/util/dictionaries.ts b/apps/api/src/app/util/dictionaries.ts
new file mode 100644
index 000000000..456c775fc
--- /dev/null
+++ b/apps/api/src/app/util/dictionaries.ts
@@ -0,0 +1,52 @@
+export const celebratoryWords = [
+    'Huray!',
+    'Yay!',
+    'Whoop!',
+    'Hoorah!',
+    'Woo-hoo!',
+    'Yippee!',
+    'Bravo!',
+    'Cheers!',
+    'Yee-haw!',
+    'Hip-hip-hooray!',
+    'Awesome!',
+    'Fantastic!',
+    'Celebrate!',
+    'Triumph!',
+    'Victory!',
+    'Delight!',
+    'Excelsior!',
+    'Amazing!',
+    'Outstanding!',
+    'Ecstatic!',
+    'Thrilling!',
+    'Glorious!',
+    'Exhilarating!',
+    'Jubilation!',
+    'Eureka!',
+    'Huzzah!',
+    'Applause!',
+    'Delirious!',
+    'Phenomenal!',
+    'Splendid!',
+    'Magnificent!',
+    'Terrific!',
+    'Superb!',
+    'Incredible!',
+    'Astounding!',
+    'Majestic!',
+    'Wonderful!',
+    'Marvelous!',
+    'Electrifying!',
+    'Fabulous!',
+    'Glittering!',
+    'Splendiferous!',
+    'Stupendous!',
+    'Ecstasy!',
+    'Triumphant!',
+    'Brilliant!',
+    'Radiant!',
+    'Euphoria!',
+    'Unbelievable!',
+    'Spectacular!',
+];
diff --git a/apps/api/src/app/util/discord.ts b/apps/api/src/app/util/discord.ts
new file mode 100644
index 000000000..9295f557d
--- /dev/null
+++ b/apps/api/src/app/util/discord.ts
@@ -0,0 +1,33 @@
+import { Client, SlashCommandBuilder } from 'discord.js';
+import { REST, Routes } from 'discord.js';
+import { DISCORD_CLIENT_ID, BOT_TOKEN } from '../config/secrets';
+import { logger } from './logger';
+import { onAutoComplete } from '../events/InteractionCreated';
+import { Events } from 'discord.js';
+
+const rest = new REST({ version: '10' }).setToken(BOT_TOKEN);
+
+export const commandRegister = async (commandList: SlashCommandBuilder[]) => {
+    try {
+        const commands = commandList.map((cmd) => cmd.toJSON());
+        await rest.put(Routes.applicationCommands(DISCORD_CLIENT_ID), { body: commands });
+        logger.info(`Successfully reloaded ${commands.length} application (/) commands.`);
+    } catch (error) {
+        logger.error(`Some error happened while loading application (/) commands.`);
+        logger.error(error);
+    }
+};
+
+export const eventRegister = (client: Client<boolean>, router: { [key: string]: any }) => {
+    Object.keys(router).forEach((key) => {
+        client.on(key, router[key]);
+    });
+    client.on(Events.InteractionCreate, onAutoComplete);
+    client.on('error', (error) => {
+        console.error('Discord.js error:', error);
+    });
+};
+
+export function discordColorToHex(discordColorCode) {
+    return `#${discordColorCode.toString(16).padStart(6, '0')}`;
+}
diff --git a/apps/api/src/app/util/errors.ts b/apps/api/src/app/util/errors.ts
new file mode 100644
index 000000000..e52f9e978
--- /dev/null
+++ b/apps/api/src/app/util/errors.ts
@@ -0,0 +1,187 @@
+class THXError extends Error {
+    message: string;
+
+    constructor(message?: string) {
+        super(message);
+        this.name = this.constructor.name;
+        this.message = message || 'No error message';
+        Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
+    }
+}
+
+class NoUserFound extends THXError {
+    constructor() {
+        super('Could not find a user for this address');
+    }
+}
+
+class NotAMemberError extends THXError {
+    constructor(address: string, assetPool: string) {
+        super(`${address} is not a member of assetPool ${assetPool}`);
+    }
+}
+class AlreadyAMemberError extends THXError {
+    constructor(address: string, assetPool: string) {
+        super(`${address} is already a member of assetPool ${assetPool}`);
+    }
+}
+
+class NoDataAtAddressError extends THXError {
+    constructor(address: string) {
+        super(`No data found at address ${address}`);
+    }
+}
+
+class THXHttpError extends THXError {
+    status: number;
+    constructor(message?: string, status?: number) {
+        super(message);
+        if (status) {
+            this.status = status;
+        }
+    }
+}
+
+class BadRequestError extends THXHttpError {
+    status = 400;
+    constructor(message?: string) {
+        super(message || 'Bad Request');
+    }
+}
+
+class UnauthorizedError extends THXHttpError {
+    status = 401;
+    constructor(message?: string) {
+        super(message || 'Unauthorized');
+    }
+}
+
+class ForbiddenError extends THXHttpError {
+    status = 403;
+    constructor(message?: string) {
+        super(message || 'Forbidden');
+    }
+}
+
+class NotFoundError extends THXHttpError {
+    status = 404;
+    constructor(message?: string) {
+        super(message || 'Not Found');
+    }
+}
+
+class ConflictError extends THXHttpError {
+    status = 409;
+    constructor(message?: string) {
+        super(message || 'Conflict');
+    }
+}
+
+class InternalServerError extends THXHttpError {
+    status = 500;
+    constructor(message?: string) {
+        super(message || 'Internal Server Error');
+    }
+}
+
+class NotImplementedError extends THXHttpError {
+    status = 501;
+    constructor(message?: string) {
+        super(message || 'Not Implemented');
+    }
+}
+
+class BadGatewayError extends THXHttpError {
+    status = 502;
+    constructor(message?: string) {
+        super(message || 'Bad Gateway');
+    }
+}
+
+class PromoCodeNotFoundError extends NotFoundError {
+    message = 'Could not find this promo code';
+}
+
+class SubjectUnauthorizedError extends ForbiddenError {
+    message = 'Not authorized for subject of access token';
+}
+
+class AudienceUnauthorizedError extends UnauthorizedError {
+    message = 'Not authorized for audience of access token';
+}
+
+class AmountExceedsAllowanceError extends BadRequestError {
+    message = 'Transfer amount exceeds allowance';
+}
+
+class InsufficientBalanceError extends BadRequestError {
+    message = 'Transfer amount exceeds balance';
+}
+
+class InsufficientAllowanceError extends BadRequestError {
+    message = 'Requested amount exceeds allowance';
+}
+
+class TokenPaymentFailedError extends InternalServerError {
+    message = 'Transfer did not succeed';
+}
+class GetPastTransferEventsError extends InternalServerError {
+    message = 'GetPastEvents for Transfer event failed in callback.';
+}
+class GetPastWithdrawnEventsError extends InternalServerError {
+    message = 'GetPastEvents for Withdrawn event failed in callback.';
+}
+
+class DuplicateEmailError extends BadRequestError {
+    message = 'An account with this e-mail address already exists.';
+}
+
+class GetPastWithdrawPollCreatedEventsError extends InternalServerError {
+    message = 'GetPastEvents for WithdrawPollCreated event failed in callback.';
+}
+class MaxFeePerGasExceededError extends THXError {
+    message = 'MaxFeePerGas from oracle exceeds configured cap';
+}
+class NoFeeDataError extends THXError {
+    message = 'Could not get fee data from oracle';
+}
+
+class DiscordDisconnected extends THXError {
+    message = 'Please sign in to your THX account and connect your Discord account.';
+}
+
+class DiscordSafeNotFound extends THXError {
+    message = 'Please sign in to your THX account so we can deploy your Safe multisig wallet.';
+}
+
+export {
+    DiscordSafeNotFound,
+    DiscordDisconnected,
+    THXError,
+    NoUserFound,
+    THXHttpError,
+    BadRequestError,
+    UnauthorizedError,
+    ForbiddenError,
+    NotFoundError,
+    ConflictError,
+    NotImplementedError,
+    BadGatewayError,
+    InternalServerError,
+    PromoCodeNotFoundError,
+    SubjectUnauthorizedError,
+    AudienceUnauthorizedError,
+    AmountExceedsAllowanceError,
+    InsufficientBalanceError,
+    TokenPaymentFailedError,
+    GetPastTransferEventsError,
+    GetPastWithdrawnEventsError,
+    DuplicateEmailError,
+    GetPastWithdrawPollCreatedEventsError,
+    NotAMemberError,
+    AlreadyAMemberError,
+    NoDataAtAddressError,
+    MaxFeePerGasExceededError,
+    InsufficientAllowanceError,
+    NoFeeDataError,
+};
diff --git a/apps/api/src/app/util/events.ts b/apps/api/src/app/util/events.ts
new file mode 100644
index 000000000..8946815f2
--- /dev/null
+++ b/apps/api/src/app/util/events.ts
@@ -0,0 +1,74 @@
+import { ethers } from 'ethers';
+import { THXError } from './errors';
+import { logger } from './logger';
+
+export class ExpectedEventNotFound extends THXError {
+    constructor(event: string) {
+        super(`Event ${event} expected in eventlog but not found.`);
+    }
+}
+
+export function parseArgs(args: any) {
+    const returnValues: any = {};
+    for (const key of Object.keys(args)) {
+        if (isNaN(Number(key))) {
+            returnValues[key] = args[key];
+        }
+    }
+    return returnValues;
+}
+
+export function parseLog(abi: any, log: any) {
+    const contractInterface = new ethers.utils.Interface(abi);
+    try {
+        return contractInterface.parseLog(log);
+    } catch (e) {
+        logger.error(e.toString());
+        return;
+    }
+}
+
+export function hex2a(hex: any) {
+    let str = '';
+    for (let i = 0; i < hex.length; i += 2) {
+        const v = parseInt(hex.substr(i, 2), 16);
+        if (v == 8) continue; // http://www.fileformat.info/info/unicode/char/0008/index.htm
+        if (v == 15) continue;
+        if (v == 16) continue; // http://www.fileformat.info/info/unicode/char/0010/index.htm
+        if (v == 14) continue; // https://www.fileformat.info/info/unicode/char/000e/index.htm
+        if (v) str += String.fromCharCode(v);
+    }
+    return str.trim();
+}
+
+export function findEvent(eventName: string, events: CustomEventLog[]): CustomEventLog {
+    return events.find((ev: any) => ev && ev.name === eventName);
+}
+
+export function assertEvent(eventName: string, events: CustomEventLog[]): CustomEventLog {
+    const event = findEvent(eventName, events);
+
+    if (!event) {
+        throw new ExpectedEventNotFound(eventName);
+    }
+
+    return event;
+}
+
+export interface CustomEventLog {
+    name: string;
+    args: any;
+    blockNumber: number;
+    transactionHash: string;
+}
+
+export function parseLogs(abi: any, logs: any = []): CustomEventLog[] {
+    const contractInterface = new ethers.utils.Interface(abi);
+    return logs.map((log: any) => {
+        try {
+            return { ...log, ...contractInterface.parseLog(log) };
+        } catch (e) {
+            return;
+        }
+    });
+}
diff --git a/apps/api/src/app/util/galachain.ts b/apps/api/src/app/util/galachain.ts
new file mode 100644
index 000000000..1f6593422
--- /dev/null
+++ b/apps/api/src/app/util/galachain.ts
@@ -0,0 +1,48 @@
+import path from 'path';
+import { CWD } from '../config/secrets';
+import { gcclient, HFClientConfig } from '@gala-chain/client';
+
+enum GalachainRole {
+    Partner = 0,
+    Curator = 1,
+    User = 2,
+}
+
+enum GalachainContract {
+    PublicKeyContract = 'PublicKeyContract',
+    GalaChainToken = 'GalaChainToken',
+}
+
+const credentials: { [role: number]: HFClientConfig } = {
+    [GalachainRole.Partner]: {
+        orgMsp: 'PartnerOrg',
+        userId: 'admin',
+        userSecret: 'adminpw',
+        connectionProfilePath: path.resolve(CWD, 'app/connection-profiles/cpp-partner.json'),
+    },
+    [GalachainRole.Curator]: {
+        orgMsp: 'CuratorOrg',
+        userId: 'admin',
+        userSecret: 'adminpw',
+        connectionProfilePath: path.resolve(CWD, 'app/connection-profiles/cpp-curator.json'),
+    },
+    [GalachainRole.User]: {
+        orgMsp: 'UserOrg',
+        userId: 'admin',
+        userSecret: 'adminpw',
+        connectionProfilePath: path.resolve(CWD, 'app/connection-profiles/cpp-user.json'),
+    },
+};
+
+const getClient = (role: GalachainRole) => {
+    const params = credentials[role];
+    return gcclient.forConnectionProfile(params);
+};
+
+const getContract = (variant: GalachainContract) => ({
+    channelName: 'product-channel',
+    chaincodeName: 'basic-product',
+    contractName: variant,
+});
+
+export { getContract, getClient, GalachainRole, GalachainContract };
diff --git a/apps/api/src/app/util/healthcheck.ts b/apps/api/src/app/util/healthcheck.ts
new file mode 100644
index 000000000..12bdd0bcd
--- /dev/null
+++ b/apps/api/src/app/util/healthcheck.ts
@@ -0,0 +1,34 @@
+import newrelic from 'newrelic';
+import { config, status } from 'migrate-mongo';
+import { connection } from 'mongoose';
+import { HealthCheck } from '@godaddy/terminus';
+
+import migrateMongoConfig from '../config/migrate-mongo';
+
+const dbConnected = async () => {
+    // https://mongoosejs.com/docs/api.html#connection_Connection-readyState
+    const { readyState } = connection;
+    // ERR_CONNECTING_TO_MONGO
+    if (readyState === 0 || readyState === 3) {
+        throw new Error('Mongoose has disconnected');
+    }
+    // CONNECTING_TO_MONGO
+    if (readyState === 2) {
+        throw new Error('Mongoose is connecting');
+    }
+};
+
+const migrationsApplied = async () => {
+    config.set(migrateMongoConfig);
+    const pendingMigrations = (await status(connection.db as any)).filter(
+        (migration) => migration.appliedAt === 'PENDING',
+    );
+    if (pendingMigrations.length > 0) {
+        throw new Error('Not all migrations applied');
+    }
+};
+
+export const healthCheck: HealthCheck = async () => {
+    newrelic.getTransaction().ignore();
+    return Promise.all([dbConnected(), migrationsApplied()]);
+};
diff --git a/apps/api/src/app/util/helpers.ts b/apps/api/src/app/util/helpers.ts
new file mode 100644
index 000000000..788236765
--- /dev/null
+++ b/apps/api/src/app/util/helpers.ts
@@ -0,0 +1,23 @@
+export const pick = <T, K extends keyof T>(object: T, keys: K[]): Pick<T, K> => {
+    return Object.assign(
+        {},
+        ...keys.map((key) => {
+            if (object && Object.prototype.hasOwnProperty.call(object, key)) {
+                return { [key]: object[key] };
+            }
+        }),
+    );
+};
+
+export const uniq = <T>(array: T[]): T[] => {
+    return [...new Set(array)];
+};
+
+export const sleep = async (seconds: number) => {
+    await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
+};
+
+export const convertObjectIdToNumber = (objectId: string) => {
+    // Extract the timestamp part of the ObjectId (first 4 bytes)
+    return parseInt(objectId.toString().substring(0, 8), 16);
+};
diff --git a/apps/api/src/app/util/index.ts b/apps/api/src/app/util/index.ts
new file mode 100644
index 000000000..c5f595cf9
--- /dev/null
+++ b/apps/api/src/app/util/index.ts
@@ -0,0 +1 @@
+export * from './helpers';
diff --git a/apps/api/src/app/util/ip.ts b/apps/api/src/app/util/ip.ts
new file mode 100644
index 000000000..ae0b16e77
--- /dev/null
+++ b/apps/api/src/app/util/ip.ts
@@ -0,0 +1,7 @@
+import { Request } from 'express';
+
+function getIP(req: Request) {
+    return req.ip || req.header('x-forwarded-for');
+}
+
+export { getIP };
diff --git a/apps/api/src/app/util/jest/config.ts b/apps/api/src/app/util/jest/config.ts
new file mode 100644
index 000000000..996f4e360
--- /dev/null
+++ b/apps/api/src/app/util/jest/config.ts
@@ -0,0 +1,91 @@
+import db from '@thxnetwork/api/util/database';
+import { mockStart } from './mock';
+import { safeVersion } from '@thxnetwork/api/services/ContractService';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { ChainId, WalletVariant } from '@thxnetwork/common/enums';
+import {
+    sub,
+    sub2,
+    sub3,
+    sub4,
+    userWalletAddress,
+    userWalletAddress2,
+    userWalletAddress3,
+    userWalletAddress4,
+} from './constants';
+import { Wallet } from '@thxnetwork/api/models';
+import Safe, { SafeFactory } from '@safe-global/protocol-kit';
+import { contractNetworks } from '@thxnetwork/api/hardhat';
+import { poll } from '../polling';
+import { agenda } from '../agenda';
+
+export async function beforeAllCallback(options = { skipWalletCreation: false }) {
+    mockStart();
+
+    const { web3, defaultAccount, ethAdapter } = getProvider(ChainId.Hardhat);
+    // Wait for this hardhat log:
+    const lastDeployedContractAddress = '0x58C0e64cBB7E5C7D0201A3a5c2D899cC70B0dc4c';
+    const fn = () => web3.eth.getCode(lastDeployedContractAddress);
+    const fnCondition = (result: string) => result === '0x';
+
+    await poll(fn, fnCondition, 500);
+
+    if (!options.skipWalletCreation) {
+        const chainId = ChainId.Hardhat;
+        const safeFactory = await SafeFactory.create({
+            safeVersion,
+            ethAdapter,
+            contractNetworks,
+        });
+        for (const entry of [
+            { sub, userWalletAddress },
+            { sub: sub2, userWalletAddress: userWalletAddress2 },
+            { sub: sub3, userWalletAddress: userWalletAddress3 },
+        ]) {
+            const safeAccountConfig = {
+                owners: [defaultAccount, entry.userWalletAddress],
+                threshold: 2,
+            };
+            const safeAddress = await safeFactory.predictSafeAddress(safeAccountConfig);
+
+            await Wallet.create({
+                sub: entry.sub,
+                safeVersion,
+                address: safeAddress,
+                chainId,
+                variant: WalletVariant.Safe,
+            });
+
+            try {
+                await Safe.create({
+                    ethAdapter,
+                    safeAddress,
+                    contractNetworks,
+                });
+            } catch (error) {
+                await safeFactory.deploySafe({ safeAccountConfig, options: { gasLimit: '3000000' } });
+            }
+        }
+
+        // Create wallet for metamask account
+        await Wallet.create({
+            chainId: ChainId.Hardhat,
+            sub: sub4,
+            address: userWalletAddress4,
+            variant: WalletVariant.WalletConnect,
+        });
+    }
+}
+
+export async function afterAllCallback() {
+    await new Promise<void>((resolve) => {
+        // Listen for 'complete' event
+        agenda.on('complete', () => {
+            resolve();
+        });
+    });
+    await agenda.stop();
+    await agenda.cancel({});
+    await agenda.purge();
+    await db.truncate();
+}
diff --git a/apps/api/src/app/util/jest/constants.ts b/apps/api/src/app/util/jest/constants.ts
new file mode 100644
index 000000000..656144f62
--- /dev/null
+++ b/apps/api/src/app/util/jest/constants.ts
@@ -0,0 +1,89 @@
+import { AccountPlanType, AccountVariant } from '@thxnetwork/common/enums';
+import { getToken } from './jwt';
+import { toWei } from 'web3-utils';
+import { CYPRESS_EMAIL } from '@thxnetwork/api/config/secrets';
+
+export const tokenName = 'Volunteers United';
+export const tokenSymbol = 'VUT';
+export const tokenTotalSupply = toWei('100000000');
+export const rewardWithdrawAmount = '1000';
+export const rewardWithdrawDuration = 60;
+export const rewardWithdrawUnlockDate = '2022-04-20';
+export const COLLECTOR_PK = '0x794a8efb7e73278907197b0f65e1c32724810f0399e1a12feb1e6af6fb77dbff';
+export const VOTER_PK = '0x97093724e1748ebfa6aa2d2ec4ec68df8678423ab9a12eb2d27ddc74e35e5db9';
+export const DEPOSITOR_PK = '0x5a05e38394194379795422d2e8c1d33e90033d90defec4880174c39198f707e3';
+export const userEmail = CYPRESS_EMAIL;
+export const userEmail2 = CYPRESS_EMAIL;
+export const userPassword = 'mellonmellonmellon';
+export const userPassword2 = 'mellonmellonmellon';
+export const voterEmail = CYPRESS_EMAIL;
+export const newAddress = '0x253cA584af3E458392982EF246066A6750Fa0735';
+export const MaxUint256 = '115792089237316195423570985008687907853269984665640564039457584007913129639935';
+export const sub = '6074cbdd1459355fae4b6a14';
+export const sub2 = '6074cbdd1459355fae4b6a15';
+export const sub3 = '6074cbdd1459355fae4b6a16';
+export const sub4 = '6074cbdd1459355fae4b6a17';
+export const userWalletAddress = '0x960911a62FdDf7BA84D0d3aD016EF7D15966F7Dc';
+export const userWalletAddress2 = '0xaf9d56684466fcFcEA0a2B7fC137AB864d642946';
+export const userWalletAddress3 = '0x861EFc0989DF42d793e3147214FfFcA4D124cAE8';
+export const userWalletAddress4 = '0x6e781b0af6204c3dc23b4a7fe049202125a4f849';
+export const userWalletPrivateKey = '0x794a8efb7e73278907197b0f65e1c32724810f0399e1a12feb1e6af6fb77dbff';
+export const userWalletPrivateKey2 = '0x97093724e1748ebfa6aa2d2ec4ec68df8678423ab9a12eb2d27ddc74e35e5db9';
+export const userWalletPrivateKey3 = '0x5a05e38394194379795422d2e8c1d33e90033d90defec4880174c39198f707e3';
+export const userWalletPrivateKey4 = '0x3b7fdd74a6c50a03d6e37f2d2c54e6fc73d67ff7d32858e55d80b2a5c9946b79';
+
+export const account = {
+    sub,
+    plan: AccountPlanType.Lite,
+    email: CYPRESS_EMAIL,
+    address: userWalletAddress,
+    tokens: [],
+};
+export const account2 = {
+    sub: sub2,
+    plan: AccountPlanType.Lite,
+    email: CYPRESS_EMAIL,
+    address: userWalletAddress2,
+    tokens: [],
+};
+
+export const account3 = {
+    sub: sub3,
+    plan: AccountPlanType.Lite,
+    variant: AccountVariant.EmailPassword,
+    address: userWalletAddress3,
+    tokens: [],
+};
+
+export const account4 = {
+    sub: sub4,
+    plan: AccountPlanType.Lite,
+    variant: AccountVariant.Metamask,
+    address: userWalletAddress4,
+};
+
+export const rewardId = 1;
+export const requestUris = ['http://localhost:8080'];
+export const redirectUris = ['http://localhost:8080'];
+export const postLogoutRedirectUris = ['http://localhost:8080'];
+export const clientId = 'xxxxxxx';
+export const clientSecret = 'xxxxxxxxxxxxxx';
+export const registrationAccessToken = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
+
+export const authScopes = 'wallets:read wallets:write';
+export const dashboardScopes =
+    'openid pools:read pools:write erc20:write erc20:read erc721:write erc721:read erc1155:write erc1155:read rewards:read rewards:write deposits:read deposits:write promotions:read promotions:write widgets:write widgets:read transactions:read swaprule:read swaprule:write claims:read  erc20_rewards:read erc20_rewards:write erc721_rewards:read erc721_rewards:write referral_rewards:read referral_rewards:write pool_subscription:read custom_rewards:write custom_rewards:read';
+export const dashboardAccessToken = getToken(dashboardScopes);
+export const dashboardAccessToken2 = getToken(dashboardScopes, sub2);
+export const walletScopes =
+    'openid rewards:read erc20:read erc721:read erc1155:read withdrawals:read withdrawals:write deposits:read deposits:write account:read account:write memberships:read memberships:write promotions:read payments:write payments:read relay:write transactions:read transactions:write swap:read swap:write swaprule:read claims:read wallets:read wallets:write erc20_rewards:read erc721_rewards:read referral_rewards:read point_balances:read';
+export const widgetScopes =
+    'openid offline_access account:read account:write erc20:read erc721:read erc1155:read point_balances:read referral_rewards:read point_rewards:read wallets:read wallets:write pool_subscription:read pool_subscription:write claims:read';
+export const walletAccessToken = getToken(walletScopes);
+export const walletAccessToken2 = getToken(walletScopes, sub);
+export const walletAccessToken3 = getToken(walletScopes, sub2);
+export const widgetAccessToken = getToken(widgetScopes, sub);
+export const widgetAccessToken2 = getToken(widgetScopes, sub2);
+export const widgetAccessToken3 = getToken(widgetScopes, sub3);
+export const widgetAccessToken4 = getToken(widgetScopes, sub4);
+export const authAccessToken = getToken(authScopes);
diff --git a/apps/api/src/app/util/jest/erc1155.ts b/apps/api/src/app/util/jest/erc1155.ts
new file mode 100644
index 000000000..bcd689b4e
--- /dev/null
+++ b/apps/api/src/app/util/jest/erc1155.ts
@@ -0,0 +1,58 @@
+import { API_URL, MINIMUM_GAS_LIMIT, VERSION } from '@thxnetwork/api/config/secrets';
+import { ChainId } from '@thxnetwork/common/enums';
+import { getProvider } from '../network';
+import { getArtifact } from '@thxnetwork/api/hardhat';
+
+export async function deployERC1155(chainId = ChainId.Hardhat) {
+    const { web3, defaultAccount } = getProvider(chainId);
+    const contractName = 'THXERC1155';
+    const { abi, bytecode } = getArtifact(contractName);
+    const contract = new web3.eth.Contract(abi);
+    const baseURL = `${API_URL}/${VERSION}/erc1155/metadata/{id}`;
+    const fn = contract.deploy({
+        data: bytecode,
+        arguments: [baseURL, defaultAccount],
+    });
+    const data = fn.encodeABI();
+    const estimate = await fn.estimateGas({ from: defaultAccount });
+    const gas = estimate < Number(MINIMUM_GAS_LIMIT) ? MINIMUM_GAS_LIMIT : estimate;
+    const receipt = await web3.eth.sendTransaction({
+        from: defaultAccount,
+        to: null,
+        data,
+        gas,
+    });
+
+    contract.options.address = receipt.contractAddress;
+
+    return contract;
+}
+
+export const mockGetNftsForOwner = (contractAddress: string) => {
+    return {
+        ownedNfts: [
+            {
+                name: 'Test Collection',
+                description: 'Test Collection description',
+                image: {
+                    originalUrl: 'ipfs://QmRvCinGkzqDdmSZ3PzQRyHbQVqaFLTDyfyMMD54Bwcjsi/1.png',
+                },
+                contract: {
+                    address: contractAddress,
+                },
+                tokenId: '1',
+                tokenUri: 'https://ipfs.io/ipfs/QmRvCinGkzqDdmSZ3PzQRyHbQVqaFLTDyfyMMD54Bwcjsi',
+                collection: {
+                    externalUrl: 'https://example.com',
+                },
+                rawMetadata: {
+                    name: '#1',
+                    description: 'image description piece #1',
+                    image: 'https://gateway.pinata.cloud/ipfs/QmemtAVJMkfUj3bAXee1H7vccbX6nC6Vbkbu6gBjdn1Kdh/1.png',
+                },
+            },
+        ],
+        pageKey: 1,
+        totalCount: 1,
+    };
+};
diff --git a/apps/api/src/app/util/jest/erc721.ts b/apps/api/src/app/util/jest/erc721.ts
new file mode 100644
index 000000000..75e130cfe
--- /dev/null
+++ b/apps/api/src/app/util/jest/erc721.ts
@@ -0,0 +1,60 @@
+import { API_URL, MINIMUM_GAS_LIMIT, VERSION } from '@thxnetwork/api/config/secrets';
+import { ChainId } from '@thxnetwork/common/enums';
+import { getProvider } from '../network';
+import { getArtifact } from '@thxnetwork/api/hardhat';
+
+export async function deployERC721(nftName: string, nftSymbol: string) {
+    const { web3, defaultAccount } = getProvider(ChainId.Hardhat);
+    const contractName = 'THXERC721';
+    const { abi, bytecode } = getArtifact(contractName);
+    const contract = new web3.eth.Contract(abi);
+    const baseURL = `${API_URL}/${VERSION}/erc721/metadata/`;
+    const fn = contract.deploy({
+        data: bytecode,
+        arguments: [nftName, nftSymbol, baseURL, defaultAccount],
+    });
+    const data = fn.encodeABI();
+    const estimate = await fn.estimateGas({ from: defaultAccount });
+    const gas = estimate < Number(MINIMUM_GAS_LIMIT) ? MINIMUM_GAS_LIMIT : estimate;
+    const receipt = await web3.eth.sendTransaction({
+        from: defaultAccount,
+        to: null,
+        data,
+        gas,
+    });
+
+    contract.options.address = receipt.contractAddress;
+
+    return contract;
+}
+
+export const mockGetNftsForOwner = (contractAddress: string, nftName: string, nftSymbol: string) => {
+    return {
+        ownedNfts: [
+            {
+                contract: {
+                    address: contractAddress,
+                    name: nftName,
+                    symbol: nftSymbol,
+                },
+                tokenId: '1',
+                tokenUri: 'https://ipfs.io/ipfs/QmRvCinGkzqDdmSZ3PzQRyHbQVqaFLTDyfyMMD54Bwcjsi',
+                rawMetadata: {
+                    name: '#1',
+                    description: 'image description piece #1',
+                    image: 'https://gateway.pinata.cloud/ipfs/QmemtAVJMkfUj3bAXee1H7vccbX6nC6Vbkbu6gBjdn1Kdh/1.png',
+                },
+                name: 'Test Collection',
+                description: 'Test Collection description',
+                image: {
+                    originalUrl: 'ipfs://QmRvCinGkzqDdmSZ3PzQRyHbQVqaFLTDyfyMMD54Bwcjsi/1.png',
+                },
+                collection: {
+                    externalUrl: 'https://example.com',
+                },
+            },
+        ],
+        pageKey: 1,
+        totalCount: 1,
+    };
+};
diff --git a/apps/api/src/app/util/jest/images.ts b/apps/api/src/app/util/jest/images.ts
new file mode 100644
index 000000000..886b3090c
--- /dev/null
+++ b/apps/api/src/app/util/jest/images.ts
@@ -0,0 +1,6 @@
+import path from 'path';
+import fs from 'fs';
+
+export function createImage() {
+    return fs.readFileSync(path.resolve(__dirname, 'test.jpg'));
+}
diff --git a/apps/api/src/app/util/jest/jwt.ts b/apps/api/src/app/util/jest/jwt.ts
new file mode 100644
index 000000000..05c88cfe5
--- /dev/null
+++ b/apps/api/src/app/util/jest/jwt.ts
@@ -0,0 +1,77 @@
+import jwt from 'jsonwebtoken';
+import { AUTH_URL } from '../../config/secrets';
+import { dashboardScopes, sub, sub2, walletScopes } from './constants';
+
+const privateKey = `-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAwaZ3afW0/zYy3HfJwAAr83PDdZvADuSJ6jTZk1+jprdHdG6P
+zH9XaB6xhzvwTIJFcWuREkNSC06MDLCuvmZ8fj93FcNaZ2ZJ0LFvY4SODMDqFekE
+5vD2Y15aSI2Y69qwKlVLphvEEXJ/FRqIHQX9wwCtwVsnqcLt/f5aNWRHyk2jwhz7
+IBm+dLu9/CV8AsvE5ddgOYYbNk+SMCjznESZcMg1KRzbdawnOklzloc+Q0iCxQK7
+022ukVxFbmT7U1hTVOTOzrruqBxptPDiutkKfOXebzYyZodlFFL5MWcatCWS3XL5
+1KBIeKWny5mExZPzIf1ofGuJe0zxllw8olgMqQIDAQABAoIBAB6x2DO/cpURbjZr
+9lqsrErGirDVoze5GfM5tVMa0cHXQ0g9TiXH+X7TfqhE4+38qC02M6SFbzfDl4db
+ahdb/1ezj5ivgmDpYcHmnhVUKX/0BCa87L3+a8+MYRsm9ppL66iKJJeLxyRM1b/u
+mKyhCnwiW2hOnpbWAwtDieD0qDx0kzkYLevG3MivaAe1rDD6gS2LhPy6tGtmemRv
+uT03a1X9DEO0U45oDvhi0V6dm8jz/eBBybHt0WWNNi2c5PVjhIL3HfT2UMiNa0xf
+O3g1eGc8bdVPW4z0Kz1g+H/uvguTEqKVq4yT8Y2LuF0Vm2MxptqAmxxJTyL5q0ZI
+V7PDg0ECgYEAziWa0BWbX+cMfIJXNi89aMpgBqBLdvQt9rE/TGjVeViPBcG8AoRr
+Osxzct6tMd+DQO3uzx5DES2FiM7gbOtpGKnLWjJImmCq2gPCtWXo74I8il5egJNh
+vq03eP7wdcm+bFta4+ScGv2wAbx28y3+5fQZPqzUISz+2dM8Hxx9GO0CgYEA8Hs0
+ap+FKD/nbRFfYMVLeP0WfJYuSpn9MF8FT73RSvCva4Ql0a3WbaZ5AMgLFGjm5nRp
+idf5vPMaXbleDG1xZnkhhBXidwFmn4TCVvG/fiDjXOULuJ5qjKLv+5dTG72GDHGG
+onNUwn1LQ3bJpGZ3VHIFJXOcQHl2Dxa6Cn7G9y0CgYBn0gyL67XasNRbCJG/mj8F
+PZbq/2PCPuu/KDlG1C1e9bjiH1X+to4CiOFD4t27FmRWGP6ClS0Vw6VS502jzVOa
+tjjR7i0egrzJG8e979tGdILk9O4HNzKtAzPC3jJgQACFNeUqjQIJneY8mZwWkP2k
+9jCYnhYftzeKoJXQ3VoraQKBgQDiprxYYdDWhqRQH7eNNWZUufSfp8wpc8k19djD
+t1uzDfXHl90tKnKXFfelzOTkb5pwSffOe0hd1aJcA4GopN3kfvYfz6CKGT/nyPCB
+kYeyEL05qIbLkkNKGaelsJIb6xyUTctfAOQ6Cm0NQL/7urdtV6mSCsyR1+h1gC4I
+BkTwYQKBgC7s9J9rRT23bx0NKyyHKu7/akAdC8m0YCohU5L7NNw0UZql0p8EQguh
+bKhNO4+rlwh2VzIi5tVMQTYoUbaab8n17fdNxtfTsG0h4vj8q+7ab59GYf1TKn0R
+JEn/NS0gRKfNh6bwZaSTfhFxALmKApVNTPm2UT9G5hADTcw4xTQf
+-----END RSA PRIVATE KEY-----`;
+
+export const jwksResponse = {
+    keys: [
+        {
+            alg: 'RS256',
+            kty: 'RSA',
+            use: 'sig',
+            n: 'waZ3afW0_zYy3HfJwAAr83PDdZvADuSJ6jTZk1-jprdHdG6PzH9XaB6xhzvwTIJFcWuREkNSC06MDLCuvmZ8fj93FcNaZ2ZJ0LFvY4SODMDqFekE5vD2Y15aSI2Y69qwKlVLphvEEXJ_FRqIHQX9wwCtwVsnqcLt_f5aNWRHyk2jwhz7IBm-dLu9_CV8AsvE5ddgOYYbNk-SMCjznESZcMg1KRzbdawnOklzloc-Q0iCxQK7022ukVxFbmT7U1hTVOTOzrruqBxptPDiutkKfOXebzYyZodlFFL5MWcatCWS3XL51KBIeKWny5mExZPzIf1ofGuJe0zxllw8olgMqQ', //eslint-disable-line max-len
+            e: 'AQAB',
+            kid: '0',
+        },
+    ],
+};
+
+export const getToken = (scope: string, outerSub?: string) => {
+    const payload: any = {
+        scope,
+    };
+
+    if (scope === dashboardScopes) {
+        payload.sub = sub;
+    } else if (scope === walletScopes) {
+        payload.sub = sub2;
+    }
+
+    if (outerSub) {
+        payload.sub = outerSub;
+    }
+
+    const options = {
+        header: { kid: '0' },
+        algorithm: 'RS256',
+        expiresIn: '1d',
+        issuer: AUTH_URL,
+    };
+
+    let token;
+    try {
+        token = jwt.sign(payload, privateKey, options);
+    } catch (err) {
+        console.log(err);
+        throw err;
+    }
+
+    return `Bearer ${token}`;
+};
diff --git a/apps/api/src/app/util/jest/mock.ts b/apps/api/src/app/util/jest/mock.ts
new file mode 100644
index 000000000..8f8f7dafa
--- /dev/null
+++ b/apps/api/src/app/util/jest/mock.ts
@@ -0,0 +1,78 @@
+import nock from 'nock';
+import {
+    userEmail2,
+    clientId,
+    clientSecret,
+    registrationAccessToken,
+    requestUris,
+    userWalletAddress2,
+    account2,
+    sub2,
+    account,
+    sub,
+    userEmail,
+    userWalletAddress,
+    sub3,
+    account3,
+    sub4,
+    account4,
+} from './constants';
+import { getToken, jwksResponse } from './jwt';
+import { AUTH_URL } from '@thxnetwork/api/config/secrets';
+
+export function mockAuthPath(method: string, path: string, status: number, callback: any = {}) {
+    const n = nock(AUTH_URL).persist() as any;
+    return n[method](path).reply(status, callback);
+}
+
+export function mockUrl(method: string, baseUrl: string, path: string, status: number, callback: any = {}) {
+    const n = nock(baseUrl).persist() as any;
+    return n[method](path).reply(status, callback);
+}
+
+export function mockStart() {
+    mockClear();
+    mockAuthPath('get', '/jwks', 200, jwksResponse);
+    mockAuthPath('post', '/token', 200, async () => {
+        return {
+            access_token: getToken('openid accounts:read accounts:write'),
+        };
+    });
+    mockAuthPath('post', '/reg', 201, {
+        client_id: clientId,
+        registration_access_token: registrationAccessToken,
+    });
+    mockAuthPath('delete', `/reg/${clientId}?access_token=${registrationAccessToken}`, 204, {});
+    mockAuthPath('get', `/reg/${clientId}?access_token=${registrationAccessToken}`, 200, {
+        client_id: clientId,
+        client_secret: clientSecret,
+        request_uris: requestUris,
+    });
+
+    // mockAuthPath('get', `https://local.auth.thx.network/account?subs=${sub}`, 200, account);
+
+    // Account 1 (Dashboard)
+    mockAuthPath('get', `/accounts/${sub}`, 200, account);
+    mockAuthPath('patch', `/accounts/${sub}`, 204, {});
+    mockAuthPath('get', `/accounts/email/${userEmail}`, 200, account);
+    mockAuthPath('get', `/accounts/address/${userWalletAddress}`, 200, account);
+
+    // Account 2 (Web Wallet)
+    mockAuthPath('get', `/accounts/${sub2}`, 200, account2);
+    mockAuthPath('patch', `/accounts/${sub2}`, 204, {});
+    mockAuthPath('post', '/accounts', 200, [account2]);
+    mockAuthPath('get', `/accounts/address/${userWalletAddress2}`, 200, account2);
+    mockAuthPath('get', `/accounts/email/${userEmail2}`, 404, {});
+
+    // Account 3
+    mockAuthPath('get', `/accounts/${sub3}`, 200, account3);
+    mockAuthPath('patch', `/accounts/${sub3}`, 204, account3);
+
+    // Account 4
+    mockAuthPath('get', `/accounts/${sub4}`, 200, account4);
+    mockAuthPath('patch', `/accounts/${sub4}`, 204, account4);
+}
+
+export function mockClear() {
+    return nock.cleanAll();
+}
diff --git a/apps/api/src/app/util/jest/network.ts b/apps/api/src/app/util/jest/network.ts
new file mode 100644
index 000000000..8c311da17
--- /dev/null
+++ b/apps/api/src/app/util/jest/network.ts
@@ -0,0 +1,57 @@
+import { ethers } from 'ethers';
+import { VOTER_PK, DEPOSITOR_PK } from './constants';
+import { getProvider } from '@thxnetwork/api/util/network';
+import { ChainId } from '@thxnetwork/common/enums';
+import { contractNetworks } from '@thxnetwork/api/hardhat';
+import { HARDHAT_RPC, SAFE_TXS_SERVICE } from '@thxnetwork/api/config/secrets';
+import Safe, { EthersAdapter } from '@safe-global/protocol-kit';
+import SafeApiKit from '@safe-global/api-kit';
+
+const { web3 } = getProvider(ChainId.Hardhat);
+
+const voter = web3.eth.accounts.privateKeyToAccount(VOTER_PK) as any;
+const depositor = web3.eth.accounts.privateKeyToAccount(DEPOSITOR_PK) as any;
+
+function createWallet(privateKey: string): any {
+    return web3.eth.accounts.privateKeyToAccount(privateKey);
+}
+
+export const timeTravel = async (seconds: number) => {
+    web3.extend({
+        methods: [
+            {
+                name: 'increaseTime',
+                call: 'evm_increaseTime',
+                params: 1,
+            },
+            {
+                name: 'mine',
+                call: 'evm_mine',
+            },
+        ],
+    });
+    await (web3 as any).increaseTime(seconds);
+};
+
+export const signMessage = (privateKey: string, message: string) => {
+    const wallet = createWallet(privateKey);
+    return wallet.sign(message).signature;
+};
+
+export async function signTxHash(safeAddress: string, safeTxHash: string, privateKey: string) {
+    const provider = new ethers.providers.JsonRpcProvider(HARDHAT_RPC);
+    const signer = new ethers.Wallet(privateKey, provider);
+    const ethAdapter = new EthersAdapter({ ethers, signerOrProvider: signer as any }) as any;
+    const safe = await Safe.create({
+        ethAdapter,
+        safeAddress,
+        contractNetworks,
+    });
+    const signedTx = await safe.signTransactionHash(safeTxHash);
+    const apiKit = new SafeApiKit({ txServiceUrl: SAFE_TXS_SERVICE, ethAdapter });
+    const { signature } = await apiKit.confirmTransaction(safeTxHash, signedTx.data);
+
+    return { safeTxHash, signature };
+}
+
+export { voter, depositor, createWallet };
diff --git a/apps/api/src/app/util/jest/test.jpg b/apps/api/src/app/util/jest/test.jpg
new file mode 100644
index 000000000..96a94a982
Binary files /dev/null and b/apps/api/src/app/util/jest/test.jpg differ
diff --git a/apps/api/src/app/util/jwt.ts b/apps/api/src/app/util/jwt.ts
new file mode 100644
index 000000000..63ba995e5
--- /dev/null
+++ b/apps/api/src/app/util/jwt.ts
@@ -0,0 +1,6 @@
+import jwt_decode from 'jwt-decode';
+
+export const parseToken = (header: string): { sub: string } => {
+    if (!header || !header.startsWith('Bearer ')) return;
+    return jwt_decode(header.split(' ')[1]);
+};
diff --git a/apps/api/src/app/util/logger.ts b/apps/api/src/app/util/logger.ts
new file mode 100644
index 000000000..e43d81206
--- /dev/null
+++ b/apps/api/src/app/util/logger.ts
@@ -0,0 +1,14 @@
+import winston from 'winston';
+import { NODE_ENV } from '@thxnetwork/api/config/secrets';
+
+const formatWinston = winston.format.combine(
+    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
+    winston.format.errors({ stack: true }),
+    winston.format.json(),
+);
+
+export const logger = winston.createLogger({
+    level: NODE_ENV === 'test' ? 'warn' : 'debug',
+    format: formatWinston,
+    transports: [new winston.transports.Console()],
+});
diff --git a/apps/api/src/app/util/multer.ts b/apps/api/src/app/util/multer.ts
new file mode 100644
index 000000000..5eefec84e
--- /dev/null
+++ b/apps/api/src/app/util/multer.ts
@@ -0,0 +1,3 @@
+import multer from 'multer';
+
+export const upload = multer({});
diff --git a/apps/api/src/app/util/network.ts b/apps/api/src/app/util/network.ts
new file mode 100644
index 000000000..c348c585f
--- /dev/null
+++ b/apps/api/src/app/util/network.ts
@@ -0,0 +1,99 @@
+import {
+    HARDHAT_RPC,
+    POLYGON_RELAYER,
+    POLYGON_RELAYER_API_KEY,
+    POLYGON_RELAYER_API_SECRET,
+    POLYGON_RPC,
+    PRIVATE_KEY,
+    RELAYER_SPEED,
+    SAFE_TXS_SERVICE,
+} from '@thxnetwork/api/config/secrets';
+import Web3 from 'web3';
+import { ethers, Signer, Wallet } from 'ethers';
+import { Contract } from 'web3-eth-contract';
+import { recoverAddress, hashMessage } from 'ethers/lib/utils';
+import { EthersAdapter } from '@safe-global/protocol-kit';
+import { DefenderRelaySigner } from '@openzeppelin/defender-relay-client/lib/ethers';
+import { Relayer } from '@openzeppelin/defender-relay-client';
+import { DefenderRelayProvider } from '@openzeppelin/defender-relay-client/lib/web3';
+import { ChainId } from '@thxnetwork/common/enums';
+import ContractService from '@thxnetwork/api/services/ContractService';
+
+export const MaxUint256 = '115792089237316195423570985008687907853269984665640564039457584007913129639935';
+
+const web3 = new Web3();
+const networks: {
+    [chainId: number]: {
+        web3: Web3;
+        txServiceUrl: string;
+        signer: Signer;
+        ethAdapter: any;
+        defaultAccount: string;
+        relayer?: Relayer;
+    };
+} = {};
+
+if (HARDHAT_RPC) {
+    networks[ChainId.Hardhat] = (() => {
+        const web3 = new Web3(HARDHAT_RPC);
+        const signer = new Wallet(PRIVATE_KEY, new ethers.providers.JsonRpcProvider(HARDHAT_RPC));
+        const methods = [
+            { name: 'setAutomine', call: 'evm_setAutomine', params: 1 },
+            { name: 'setIntervalMining', call: 'evm_setIntervalMining', params: 1 },
+        ];
+        web3.extend({ property: 'hardhat', methods });
+        return {
+            web3,
+            txServiceUrl: SAFE_TXS_SERVICE,
+            ethAdapter: new EthersAdapter({ ethers, signerOrProvider: signer as any }),
+            signer,
+            defaultAccount: web3.eth.accounts.privateKeyToAccount(PRIVATE_KEY).address,
+        };
+    })();
+}
+
+if (POLYGON_RELAYER) {
+    networks[ChainId.Polygon] = (() => {
+        const provider = new DefenderRelayProvider(
+            { apiKey: POLYGON_RELAYER_API_KEY, apiSecret: POLYGON_RELAYER_API_SECRET },
+            { speed: RELAYER_SPEED },
+        );
+        const relayer = new Relayer({ apiKey: POLYGON_RELAYER_API_KEY, apiSecret: POLYGON_RELAYER_API_SECRET });
+        const signer = new DefenderRelaySigner(
+            { apiKey: POLYGON_RELAYER_API_KEY, apiSecret: POLYGON_RELAYER_API_SECRET },
+            new ethers.providers.JsonRpcProvider(POLYGON_RPC),
+            { speed: RELAYER_SPEED },
+        ) as unknown as Signer;
+
+        return {
+            web3: new Web3(provider),
+            txServiceUrl: SAFE_TXS_SERVICE,
+            ethAdapter: new EthersAdapter({
+                ethers,
+                signerOrProvider: signer as any,
+            }),
+            signer,
+            relayer,
+            defaultAccount: POLYGON_RELAYER,
+        };
+    })();
+}
+
+export function getProvider(chainId?: ChainId) {
+    if (!chainId) chainId = ContractService.getChainId();
+    if (!networks[chainId]) throw new Error(`Network with chainId ${chainId} is not available`);
+    return networks[chainId];
+}
+
+export const recoverSigner = (message: string, sig: string) => {
+    return recoverAddress(hashMessage(message), sig);
+};
+
+export function getSelectors(contract: Contract) {
+    const signatures: string[] = [];
+    for (const sig of Object.keys(contract.methods)) {
+        if (sig.indexOf('(') === -1) continue; // Only add selectors for full function signatures.
+        signatures.push(web3.eth.abi.encodeFunctionSignature(sig));
+    }
+    return signatures;
+}
diff --git a/apps/api/src/app/util/newrelic.ts b/apps/api/src/app/util/newrelic.ts
new file mode 100644
index 000000000..4f3beb035
--- /dev/null
+++ b/apps/api/src/app/util/newrelic.ts
@@ -0,0 +1,12 @@
+import newrelic from 'newrelic';
+
+export const wrapBackgroundTransaction = (name: string, group: string, promise: Promise<unknown>): Promise<unknown> => {
+    return newrelic.startBackgroundTransaction(name, group, async () => {
+        try {
+            return await promise;
+        } catch (error) {
+            newrelic.noticeError(error);
+            throw error;
+        }
+    });
+};
diff --git a/apps/api/src/app/util/pagination.ts b/apps/api/src/app/util/pagination.ts
new file mode 100644
index 000000000..62c9c9ed4
--- /dev/null
+++ b/apps/api/src/app/util/pagination.ts
@@ -0,0 +1,45 @@
+export interface PaginationResult {
+    results: any[];
+    next?: { page: number };
+    previous?: { page: number };
+    limit: number;
+    total: number;
+}
+
+export const paginatedResults = async (
+    model: any,
+    page: number,
+    limit: number,
+    query: any,
+    sorts: any = [['createdAt', -1]],
+): Promise<PaginationResult> => {
+    const startIndex = (page - 1) * limit;
+    const endIndex = page * limit;
+    const total = await model.find(query).countDocuments().exec();
+    let next, previous;
+
+    if (endIndex < total) {
+        next = {
+            next: {
+                page: page + 1,
+            },
+        };
+    }
+    if (startIndex > 0) {
+        previous = {
+            previous: {
+                page: page - 1,
+            },
+        };
+    }
+
+    const results = await model.find(query).sort(sorts).limit(limit).skip(startIndex).exec();
+
+    return {
+        results,
+        limit,
+        total,
+        ...next,
+        ...previous,
+    };
+};
diff --git a/apps/api/src/app/util/path.ts b/apps/api/src/app/util/path.ts
new file mode 100644
index 000000000..98377c9a6
--- /dev/null
+++ b/apps/api/src/app/util/path.ts
@@ -0,0 +1,4 @@
+import path from 'path';
+import { CWD } from '../config/secrets';
+
+export const assetsPath = path.resolve(CWD, 'assets');
diff --git a/apps/api/src/app/util/polling.ts b/apps/api/src/app/util/polling.ts
new file mode 100644
index 000000000..87f5adc8e
--- /dev/null
+++ b/apps/api/src/app/util/polling.ts
@@ -0,0 +1,14 @@
+export async function poll<T>(fn: () => Promise<T>, fnCondition: (result: T) => boolean, ms: number) {
+    let result = await fn();
+    while (fnCondition(result)) {
+        await wait(ms);
+        result = await fn();
+    }
+    return result;
+}
+
+function wait(ms = 1000) {
+    return new Promise((resolve) => {
+        setTimeout(resolve, ms);
+    });
+}
diff --git a/apps/api/src/app/util/random.ts b/apps/api/src/app/util/random.ts
new file mode 100644
index 000000000..2179cba15
--- /dev/null
+++ b/apps/api/src/app/util/random.ts
@@ -0,0 +1,3 @@
+export function generateRandomString(length: number) {
+    return (+new Date() * Math.random()).toString(36).substring(0, length);
+}
diff --git a/apps/api/src/app/util/ratelimiter.ts b/apps/api/src/app/util/ratelimiter.ts
new file mode 100644
index 000000000..4900c2131
--- /dev/null
+++ b/apps/api/src/app/util/ratelimiter.ts
@@ -0,0 +1,6 @@
+import rateLimit from 'express-rate-limit';
+import { NODE_ENV } from '../config/secrets';
+
+const limitInSeconds = (seconds: number) => rateLimit({ windowMs: NODE_ENV !== 'test' && seconds * 1000, max: 1 });
+
+export { limitInSeconds };
diff --git a/apps/api/src/app/util/s3.ts b/apps/api/src/app/util/s3.ts
new file mode 100644
index 000000000..57a6c78b5
--- /dev/null
+++ b/apps/api/src/app/util/s3.ts
@@ -0,0 +1,26 @@
+import { S3, S3Client } from '@aws-sdk/client-s3';
+import {
+    AWS_ACCESS_KEY_ID,
+    AWS_SECRET_ACCESS_KEY,
+    AWS_S3_PRIVATE_BUCKET_REGION,
+    AWS_S3_PUBLIC_BUCKET_REGION,
+} from '@thxnetwork/api/config/secrets';
+
+const credentials = {
+    accessKeyId: AWS_ACCESS_KEY_ID,
+    secretAccessKey: AWS_SECRET_ACCESS_KEY,
+};
+
+const s3Client = new S3Client({
+    region: AWS_S3_PUBLIC_BUCKET_REGION,
+    credentials,
+});
+
+const s3PrivateClient = new S3Client({
+    region: AWS_S3_PRIVATE_BUCKET_REGION,
+    credentials,
+});
+
+const s3Public = new S3({ region: AWS_S3_PUBLIC_BUCKET_REGION, credentials });
+
+export { s3Public, s3Client, s3PrivateClient };
diff --git a/apps/api/src/app/util/scopes.ts b/apps/api/src/app/util/scopes.ts
new file mode 100644
index 000000000..024bb03b7
--- /dev/null
+++ b/apps/api/src/app/util/scopes.ts
@@ -0,0 +1,63 @@
+export const openId = 'openid';
+export const adminScopes = [
+    'account:read',
+    'account:write',
+    'members:read',
+    'members:write',
+    'withdrawals:write',
+    'rewards:read',
+    'wallets:read',
+    'wallets:write',
+    'erc20_rewards:read',
+    'erc721_rewards:read',
+    'referral_rewards:read',
+];
+export const dashboardScopes = [
+    'asset_pools:read',
+    'asset_pools:write',
+    'rewards:read',
+    'rewards:write',
+    'deposits:read',
+    'deposits:write',
+    'promotions:read',
+    'promotions:write',
+    'transactions:read',
+    'claims:read',
+    'swaprule:read',
+    'swaprule:write',
+    'erc20_rewards:read',
+    'erc20_rewards:write',
+    'erc721_rewards:read',
+    'erc721_rewards:write',
+    'referral_rewards:read',
+    'referral_rewards:write',
+    'pool_subscription:read',
+];
+export const userScopes = [
+    'asset_pools:read',
+    'asset_pools:write',
+    'rewards:read',
+    'withdrawals:read',
+    'deposits:read',
+    'deposits:write',
+    'transactions:read',
+    'transactions:write',
+    'claims:read',
+    'swaprule:read',
+    'swap:read',
+    'swap:write',
+    'erc20_rewards:read',
+    'erc721_rewards:read',
+    'referral_rewards:read',
+    'referal_reward_claims:read',
+    'referal_reward_claims:write',
+];
+
+export const opneIdAdminScopes = `${openId} ${adminScopes.join(' ')}`;
+export const openIdDashboardScopes = `${openId} ${dashboardScopes.join(' ')}`;
+export const openIdUserScopes = `${openId} ${userScopes.join(' ')}`;
+
+export const adminDashboardScopes = Array.from(new Set([...adminScopes, ...dashboardScopes]));
+export const userDashboardScopes = Array.from(new Set([...userScopes, ...dashboardScopes]));
+export const userAdminScopes = Array.from(new Set([...adminScopes, ...userScopes]));
+export const userAdminDashboardScopes = Array.from(new Set([...adminScopes, ...userScopes, ...dashboardScopes]));
diff --git a/apps/api/src/app/util/signingsecret.ts b/apps/api/src/app/util/signingsecret.ts
new file mode 100644
index 000000000..736149ad2
--- /dev/null
+++ b/apps/api/src/app/util/signingsecret.ts
@@ -0,0 +1,11 @@
+import crypto from 'crypto';
+
+export function getsigningSecret(length: number) {
+    return crypto.randomBytes(length).toString('base64');
+}
+
+export function signPayload(payload: string, secret: string): string {
+    const hmac = crypto.createHmac('sha256', secret);
+    hmac.update(payload);
+    return hmac.digest('base64');
+}
diff --git a/apps/api/src/app/util/token.ts b/apps/api/src/app/util/token.ts
new file mode 100644
index 000000000..786573023
--- /dev/null
+++ b/apps/api/src/app/util/token.ts
@@ -0,0 +1,6 @@
+import crypto from 'crypto';
+
+export function createRandomToken() {
+    const buf = crypto.randomBytes(16);
+    return buf.toString('hex');
+}
diff --git a/apps/api/src/app/util/twitter.ts b/apps/api/src/app/util/twitter.ts
new file mode 100644
index 000000000..ea07f182a
--- /dev/null
+++ b/apps/api/src/app/util/twitter.ts
@@ -0,0 +1,8 @@
+import { TWITTER_API_TOKEN } from '@thxnetwork/api/config/secrets';
+import axios, { AxiosRequestConfig } from 'axios';
+
+export function twitterClient(config: AxiosRequestConfig) {
+    axios.defaults.headers['Authorization'] = `Bearer ${TWITTER_API_TOKEN}`;
+    axios.defaults.baseURL = 'https://api.twitter.com/2';
+    return axios(config);
+}
diff --git a/apps/api/src/app/util/url.ts b/apps/api/src/app/util/url.ts
new file mode 100644
index 000000000..4b292f77d
--- /dev/null
+++ b/apps/api/src/app/util/url.ts
@@ -0,0 +1,2 @@
+const URL_REGEX = /^(https?):\/\/[^\s/$.?#].[^\s]*$/i;
+export const isValidUrl = (url: string) => url.match(URL_REGEX);
diff --git a/apps/api/src/app/util/uuid.ts b/apps/api/src/app/util/uuid.ts
new file mode 100644
index 000000000..b50ed5c80
--- /dev/null
+++ b/apps/api/src/app/util/uuid.ts
@@ -0,0 +1,23 @@
+import crypto from 'crypto';
+import { v1 } from 'uuid';
+
+export function uuidV1(salt?: string) {
+    if (!salt) return v1();
+
+    // Use a cryptographic hash function (SHA-256 in this example)
+    const hash = crypto.createHash('sha256');
+
+    // Update the hash with the salt
+    hash.update(salt);
+
+    // Get the hashed data in hexadecimal format
+    const hashedData = hash.digest('hex');
+
+    // Create a UUID from the first 16 bytes of the hashed data
+    const uuid = `${hashedData.slice(0, 8)}-${hashedData.slice(8, 12)}-${hashedData.slice(12, 16)}-${hashedData.slice(
+        16,
+        20,
+    )}-${hashedData.slice(20, 32)}`;
+
+    return uuid;
+}
diff --git a/apps/api/src/app/util/validation.ts b/apps/api/src/app/util/validation.ts
new file mode 100644
index 000000000..4b6f306c5
--- /dev/null
+++ b/apps/api/src/app/util/validation.ts
@@ -0,0 +1,65 @@
+import { body, check, validationResult } from 'express-validator';
+import { Response, Request, NextFunction } from 'express';
+import { isValidUrl } from './url';
+
+export const validate = (validations: any) => {
+    return async (req: Request, res: Response, next: NextFunction) => {
+        await Promise.all(validations.map((validation: any) => validation.run(req)));
+        const errors = validationResult(req);
+        if (errors.isEmpty()) return next();
+        res.status(400).json({ errors: errors.array() });
+    };
+};
+
+export const defaults = {
+    quest: [
+        body('index').optional().isInt(),
+        body('title').optional().isString().trim().escape(),
+        body('description').optional().isString().trim().escape(),
+        body('expiryDate').optional().isISO8601(),
+        body('isPublished')
+            .optional()
+            .isBoolean()
+            .customSanitizer((value) => JSON.parse(value)),
+        check('file')
+            .optional()
+            .custom((value, { req }) => {
+                return ['jpg', 'jpeg', 'gif', 'png'].includes(req.file.mimetype);
+            }),
+        body('infoLinks')
+            .optional()
+            .customSanitizer((infoLinks) => {
+                return JSON.parse(infoLinks).filter((link: TInfoLink) => link.label.length && isValidUrl(link.url));
+            }),
+        body('locks')
+            .optional()
+            .custom((value) => {
+                const locks = value && JSON.parse(value);
+                return Array.isArray(locks);
+            })
+            .customSanitizer((locks) => locks && JSON.parse(locks)),
+    ],
+    reward: [
+        body('title').optional().isString().trim().escape(),
+        body('description').optional().isString().trim().escape(),
+        body('expiryDate').optional().isISO8601(),
+        body('limit').optional().isNumeric(),
+        body('pointPrice').optional().isNumeric(),
+        body('isPublished')
+            .optional()
+            .isBoolean()
+            .customSanitizer((value) => JSON.parse(value)),
+        check('file')
+            .optional()
+            .custom((value) => {
+                return ['jpg', 'jpeg', 'gif', 'png'].includes(value.mimetype);
+            }),
+        body('locks')
+            .optional()
+            .custom((value) => {
+                const locks = value && JSON.parse(value);
+                return Array.isArray(locks);
+            })
+            .customSanitizer((locks) => locks && JSON.parse(locks)),
+    ],
+};
diff --git a/apps/api/src/app/util/zip.ts b/apps/api/src/app/util/zip.ts
new file mode 100644
index 000000000..28c845e99
--- /dev/null
+++ b/apps/api/src/app/util/zip.ts
@@ -0,0 +1,9 @@
+import JSZip from 'jszip';
+
+export const createArchiver = () => {
+    const jsZip = new JSZip();
+    return {
+        jsZip,
+        archive: jsZip.folder('qrcodes'),
+    };
+};
diff --git a/apps/api/src/assets/.gitkeep b/apps/api/src/assets/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/api/src/assets/bg.png b/apps/api/src/assets/bg.png
new file mode 100644
index 000000000..e58c94831
Binary files /dev/null and b/apps/api/src/assets/bg.png differ
diff --git a/apps/api/src/assets/fa-solid-900.ttf b/apps/api/src/assets/fa-solid-900.ttf
new file mode 100644
index 000000000..e6330e6aa
Binary files /dev/null and b/apps/api/src/assets/fa-solid-900.ttf differ
diff --git a/apps/campaign/src/assets/logo.png b/apps/api/src/assets/logo.png
similarity index 100%
rename from apps/campaign/src/assets/logo.png
rename to apps/api/src/assets/logo.png
diff --git a/apps/api/src/assets/qr-logo.jpg b/apps/api/src/assets/qr-logo.jpg
new file mode 100644
index 000000000..96a94a982
Binary files /dev/null and b/apps/api/src/assets/qr-logo.jpg differ
diff --git a/apps/api/src/assets/views/email/base-template.ejs b/apps/api/src/assets/views/email/base-template.ejs
new file mode 100644
index 000000000..ed8f7ba46
--- /dev/null
+++ b/apps/api/src/assets/views/email/base-template.ejs
@@ -0,0 +1,407 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <title><%= subject %></title>
+  <style>
+    /* -------------------------------------
+          GLOBAL RESETS
+      ------------------------------------- */
+
+    img {
+      border: none;
+      -ms-interpolation-mode: bicubic;
+      max-width: 100%;
+    }
+
+    body {
+      background-color: #f6f6f6;
+      font-family: sans-serif;
+      -webkit-font-smoothing: antialiased;
+      font-size: 14px;
+      line-height: 1.4;
+      margin: 0;
+      padding: 0;
+      -ms-text-size-adjust: 100%;
+      -webkit-text-size-adjust: 100%;
+    }
+
+    table {
+      border-collapse: separate;
+      mso-table-lspace: 0pt;
+      mso-table-rspace: 0pt;
+      width: 100%;
+    }
+
+    table td {
+      font-family: sans-serif;
+      font-size: 14px;
+      vertical-align: top;
+    }
+
+    /* -------------------------------------
+          BODY & CONTAINER
+      ------------------------------------- */
+
+    .body {
+      background-color: #f6f6f6;
+      width: 100%;
+    }
+
+    /* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
+    .container {
+      display: block;
+      margin: 0 auto !important;
+      /* makes it centered */
+      max-width: 580px;
+      padding: 10px;
+      width: 580px;
+    }
+
+    /* This should also be a block element, so that it will fill 100% of the .container */
+    .content {
+      box-sizing: border-box;
+      display: block;
+      margin: 0 auto;
+      max-width: 580px;
+      padding: 10px;
+    }
+
+    /* -------------------------------------
+          HEADER, FOOTER, MAIN
+      ------------------------------------- */
+    .main {
+      background: #ffffff;
+      border-radius: 3px;
+      width: 100%;
+    }
+
+    .wrapper {
+      box-sizing: border-box;
+      padding: 20px;
+    }
+
+    .content-block {
+      padding-bottom: 10px;
+      padding-top: 10px;
+    }
+
+    .footer {
+      clear: both;
+      margin-top: 10px;
+      text-align: center;
+      width: 100%;
+    }
+
+    .footer td,
+    .footer p,
+    .footer span,
+    .footer a {
+      color: #999999;
+      font-size: 12px;
+      text-align: center;
+    }
+
+    /* -------------------------------------
+          TYPOGRAPHY
+      ------------------------------------- */
+    h1,
+    h2,
+    h3,
+    h4 {
+      color: #000000;
+      font-family: sans-serif;
+      font-weight: 400;
+      line-height: 1.4;
+      margin: 0;
+      margin-bottom: 30px;
+    }
+
+    h1 {
+      font-size: 35px;
+      font-weight: 300;
+      text-align: center;
+      text-transform: capitalize;
+    }
+
+    p,
+    ul,
+    ol {
+      font-family: sans-serif;
+      font-size: 16px;
+      font-weight: normal;
+      margin: 0;
+      margin-bottom: 15px;
+    }
+
+    p li,
+    ul li,
+    ol li {
+      list-style-position: inside;
+      margin-left: 5px;
+    }
+
+    a {
+      color: #3498db;
+      text-decoration: underline;
+    }
+
+    /* -------------------------------------
+          BUTTONS
+      ------------------------------------- */
+    .btn {
+      box-sizing: border-box;
+      width: 100%;
+    }
+
+    .btn>tbody>tr>td {
+      padding-bottom: 15px;
+    }
+
+    .btn table {
+      width: auto;
+    }
+
+    .btn table td {
+      background-color: #ffffff;
+      border-radius: 5px;
+      text-align: center;
+    }
+
+    .btn a {
+      display: block;
+      font-size: 16px !important;
+      width: 100%;
+      text-align: center;
+      color: #ffffff;
+      background-color: #5942c1;
+      border: 0;
+      border-radius: 50rem;
+      box-sizing: border-box;
+      cursor: pointer;
+      text-decoration: none;
+      font-size: 12px;
+      margin: 0;
+      padding: 12px 25px;
+    }
+
+    .btn-primary table td {
+      background-color: #3498db;
+    }
+
+    .btn-primary a {
+      background-color: #3498db;
+      border-color: #3498db;
+      color: #ffffff;
+    }
+
+    /* -------------------------------------
+          OTHER STYLES THAT MIGHT BE USEFUL
+      ------------------------------------- */
+    .last {
+      margin-bottom: 0;
+    }
+
+    .first {
+      margin-top: 0;
+    }
+
+    .align-center {
+      text-align: center;
+    }
+
+    .align-right {
+      text-align: right;
+    }
+
+    .align-left {
+      text-align: left;
+    }
+
+    .clear {
+      clear: both;
+    }
+
+    .mt0 {
+      margin-top: 0;
+    }
+
+    .mb0 {
+      margin-bottom: 0;
+    }
+
+    .preheader {
+      color: transparent;
+      display: none;
+      height: 0;
+      max-height: 0;
+      max-width: 0;
+      opacity: 0;
+      overflow: hidden;
+      mso-hide: all;
+      visibility: hidden;
+      width: 0;
+    }
+
+    .powered-by a {
+      text-decoration: none;
+    }
+
+    hr {
+      border: 0;
+      border-bottom: 1px solid #f6f6f6;
+      margin: 20px 0;
+    }
+
+    /* -------------------------------------
+          RESPONSIVE AND MOBILE FRIENDLY STYLES
+      ------------------------------------- */
+    @media only screen and (max-width: 620px) {
+      table.body h1 {
+        font-size: 28px !important;
+        margin-bottom: 10px !important;
+      }
+
+      table.body p,
+      table.body ul,
+      table.body ol,
+      table.body td,
+      table.body span,
+      table.body a {
+        font-size: 16px !important;
+      }
+
+      table.body .wrapper,
+      table.body .article {
+        padding: 10px !important;
+      }
+
+      table.body .content {
+        padding: 0 !important;
+      }
+
+      table.body .container {
+        padding: 0 !important;
+        width: 100% !important;
+      }
+
+      table.body .main {
+        border-left-width: 0 !important;
+        border-radius: 0 !important;
+        border-right-width: 0 !important;
+      }
+
+      table.body .btn table {
+        width: 100% !important;
+      }
+
+      table.body .btn a {
+        width: 100% !important;
+      }
+
+      table.body .img-responsive {
+        height: auto !important;
+        max-width: 100% !important;
+        width: auto !important;
+      }
+    }
+
+    /* -------------------------------------
+          PRESERVE THESE STYLES IN THE HEAD
+      ------------------------------------- */
+    @media all {
+      .ExternalClass {
+        width: 100%;
+      }
+
+      .ExternalClass,
+      .ExternalClass p,
+      .ExternalClass span,
+      .ExternalClass font,
+      .ExternalClass td,
+      .ExternalClass div {
+        line-height: 100%;
+      }
+
+      .apple-link a {
+        color: inherit !important;
+        font-family: inherit !important;
+        font-size: inherit !important;
+        font-weight: inherit !important;
+        line-height: inherit !important;
+        text-decoration: none !important;
+      }
+
+      #MessageViewBody a {
+        color: inherit;
+        text-decoration: none;
+        font-size: inherit;
+        font-family: inherit;
+        font-weight: inherit;
+        line-height: inherit;
+      }
+
+      .btn-primary table td:hover {
+        background-color: #34495e !important;
+      }
+
+      .btn-primary a:hover {
+        background-color: #34495e !important;
+        border-color: #34495e !important;
+      }
+    }
+  </style>
+</head>
+
+<body>
+  <span class="preheader"><%- htmlContent %></span>
+  <table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body">
+    <tr>
+      <td>&nbsp;</td>
+      <td class="container">
+        <div class="content">
+          <!-- START CENTERED WHITE CONTAINER -->
+          <table role="presentation" class="main">
+            <!-- START MAIN CONTENT AREA -->
+            <tr>
+              <td class="wrapper">
+                <table role="presentation" border="0" cellpadding="0" cellspacing="0">
+                  <tr>
+                    <td>
+                      <%- htmlContent %>
+                      <% if (link.src && link.text) { %>
+                      <div class="btn">
+                        <a href="<%= link.src %>" target="_blank"><%= link.text %></a>
+                      </div>
+                      <% } %>
+                    </td>
+                  </tr>
+                </table>
+              </td>
+            </tr>
+
+            <!-- END MAIN CONTENT AREA -->
+          </table>
+          <!-- END CENTERED WHITE CONTAINER -->
+
+          <!-- START FOOTER -->
+          <div class="footer">
+            <table role="presentation" border="0" cellpadding="0" cellspacing="0">
+              <tr>
+                <td class="content-block powered-by">
+                  <img src="<%= baseUrl %>/img/logo.png" width="60" alt="THX Logo">
+                </td>
+              </tr>
+            </table>
+          </div>
+          <!-- END FOOTER -->
+        </div>
+      </td>
+      <td>&nbsp;</td>
+    </tr>
+  </table>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/apps/api/src/discord.ts b/apps/api/src/discord.ts
new file mode 100644
index 000000000..400977fd0
--- /dev/null
+++ b/apps/api/src/discord.ts
@@ -0,0 +1,21 @@
+import { Client, GatewayIntentBits, Partials, PermissionFlagsBits } from 'discord.js';
+import { BOT_TOKEN } from '@thxnetwork/api/config/secrets';
+import { eventRegister } from '@thxnetwork/api/util/discord';
+import { logger } from './app/util/logger';
+import eventRouter from '@thxnetwork/api/events';
+
+export const client = new Client({
+    intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMessageReactions],
+    partials: [Partials.Message, Partials.Channel, Partials.Reaction],
+});
+
+export default async () => {
+    try {
+        eventRegister(client, eventRouter);
+        client.login(BOT_TOKEN);
+    } catch (error) {
+        logger.error(error);
+    }
+};
+
+export { PermissionFlagsBits };
diff --git a/apps/api/src/environments/environment.prod.ts b/apps/api/src/environments/environment.prod.ts
new file mode 100644
index 000000000..c9669790b
--- /dev/null
+++ b/apps/api/src/environments/environment.prod.ts
@@ -0,0 +1,3 @@
+export const environment = {
+  production: true,
+};
diff --git a/apps/api/src/environments/environment.ts b/apps/api/src/environments/environment.ts
new file mode 100644
index 000000000..a20cfe557
--- /dev/null
+++ b/apps/api/src/environments/environment.ts
@@ -0,0 +1,3 @@
+export const environment = {
+  production: false,
+};
diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts
new file mode 100644
index 000000000..cf031b58f
--- /dev/null
+++ b/apps/api/src/main.ts
@@ -0,0 +1,71 @@
+import 'newrelic';
+import http from 'http';
+import https from 'https';
+import httpProxy from 'http-proxy';
+import app from './app';
+import discordBot from './discord';
+import db from './app/util/database';
+import { createTerminus } from '@godaddy/terminus';
+import { healthCheck } from './app/util/healthcheck';
+import { logger } from './app/util/logger';
+import { agenda } from './app/util/agenda';
+import fs from 'fs';
+import { LOCAL_CERT, LOCAL_CERT_KEY, NODE_ENV } from './app/config/secrets';
+import path from 'path';
+
+let server: http.Server;
+
+if (LOCAL_CERT && LOCAL_CERT_KEY) {
+    const ssl = {
+        key: fs.readFileSync(path.resolve(path.dirname(__dirname), LOCAL_CERT_KEY)),
+        cert: fs.readFileSync(path.resolve(path.dirname(__dirname), LOCAL_CERT)),
+    };
+    server = https.createServer(ssl, app);
+    httpProxy
+        .createProxyServer({
+            target: {
+                host: '127.0.0.1',
+                port: 8545,
+            },
+            ssl,
+        })
+        .listen(8547);
+} else {
+    server = http.createServer(app);
+}
+
+const options = {
+    healthChecks: {
+        '/healthcheck': healthCheck,
+        'verbatim': true,
+    },
+    onSignal: () => {
+        logger.info('Server shutting down gracefully');
+        return Promise.all([db.disconnect(), agenda.stop()]);
+    },
+    logger: logger.error,
+};
+
+createTerminus(server, options);
+
+process.on('uncaughtException', function (err: Error) {
+    if (err) {
+        logger.error({
+            message: 'Uncaught Exception was thrown, shutting down',
+            errorName: err.name,
+            errorMessage: err.message,
+            stack: err.stack,
+        });
+        process.exit(1);
+    }
+});
+
+logger.info({
+    message: `Server is starting on port: ${app.get('port')}, env: ${NODE_ENV}`,
+    port: app.get('port'),
+    env: NODE_ENV,
+});
+
+server.listen(app.get('port'));
+
+discordBot();
diff --git a/apps/api/tsconfig.app.json b/apps/api/tsconfig.app.json
new file mode 100644
index 000000000..12fae6ea6
--- /dev/null
+++ b/apps/api/tsconfig.app.json
@@ -0,0 +1,10 @@
+{
+    "extends": "./tsconfig.json",
+    "compilerOptions": {
+        "outDir": "../../dist/out-tsc",
+        "module": "commonjs",
+        "types": ["node"]
+    },
+    "exclude": ["jest.config.ts", "src/**/*.test.ts", "../../libs/common/src/lib/scss/**/*"],
+    "include": ["src/**/*.ts", "../../libs/common/src/**/*", "../../libs/sdk/src/**/*", "src/app/hardhat/export/*.json"]
+}
diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json
new file mode 100644
index 000000000..d2b6e8e03
--- /dev/null
+++ b/apps/api/tsconfig.json
@@ -0,0 +1,19 @@
+{
+    "extends": "../../tsconfig.base.json",
+    "compilerOptions": {
+        "esModuleInterop": true,
+        // Custom
+        "noImplicitAny": false,
+        "strict": false
+    },
+    "files": [],
+    "include": [],
+    "references": [
+        {
+            "path": "./tsconfig.app.json"
+        },
+        {
+            "path": "./tsconfig.spec.json"
+        }
+    ]
+}
diff --git a/apps/api/tsconfig.spec.json b/apps/api/tsconfig.spec.json
new file mode 100644
index 000000000..5692087fb
--- /dev/null
+++ b/apps/api/tsconfig.spec.json
@@ -0,0 +1,20 @@
+{
+    "extends": "./tsconfig.json",
+    "compilerOptions": {
+        "outDir": "../../dist/out-tsc",
+        "module": "commonjs",
+        "types": ["jest", "node"],
+        // Custom
+        "allowJs": true
+    },
+    "exclude": ["../../libs/common/src/lib/scss/**/*"],
+    "include": [
+        "jest.config.ts",
+        "**/*.test.ts",
+        "**/*.d.ts",
+        "src/**/*.ts",
+        "../../libs/common/src/**/*",
+        "../../libs/sdk/src/**/*",
+        "src/app/hardhat/export/*.json"
+    ]
+}
diff --git a/apps/api/webpack.config.js b/apps/api/webpack.config.js
new file mode 100644
index 000000000..29f653d71
--- /dev/null
+++ b/apps/api/webpack.config.js
@@ -0,0 +1,13 @@
+const { composePlugins, withNx } = require('@nx/webpack');
+
+// Nx plugins for webpack.
+module.exports = composePlugins(
+    withNx({
+        target: 'node',
+    }),
+    (config) => {
+        // Update the webpack config as needed here.
+        // e.g. `config.plugins.push(new MyPlugin())`
+        return config;
+    },
+);
diff --git a/apps/campaign/.build.env b/apps/app/.build.env
similarity index 100%
rename from apps/campaign/.build.env
rename to apps/app/.build.env
diff --git a/apps/campaign/.env.example b/apps/app/.env.example
similarity index 100%
rename from apps/campaign/.env.example
rename to apps/app/.env.example
diff --git a/apps/campaign/.eslintrc.json b/apps/app/.eslintrc.json
similarity index 100%
rename from apps/campaign/.eslintrc.json
rename to apps/app/.eslintrc.json
diff --git a/apps/app/.gitkeep b/apps/app/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/campaign/components.d.ts b/apps/app/components.d.ts
similarity index 100%
rename from apps/campaign/components.d.ts
rename to apps/app/components.d.ts
diff --git a/apps/campaign/index.html b/apps/app/index.html
similarity index 100%
rename from apps/campaign/index.html
rename to apps/app/index.html
diff --git a/apps/campaign/project.json b/apps/app/project.json
similarity index 79%
rename from apps/campaign/project.json
rename to apps/app/project.json
index 1d8bc7951..4b9c0967f 100644
--- a/apps/campaign/project.json
+++ b/apps/app/project.json
@@ -1,20 +1,21 @@
 {
-    "name": "campaign",
+    "name": "app",
     "$schema": "../../node_modules/nx/schemas/project-schema.json",
-    "sourceRoot": "apps/campaign/src",
+    "sourceRoot": "apps/app/src",
     "projectType": "application",
+    "tags": [],
     "targets": {
         "build": {
             "executor": "@nx/vite:build",
             "options": {
-                "outputPath": "dist/apps/campaign"
+                "outputPath": "dist/apps/app"
             }
         },
         "serve": {
             "executor": "@nx/vite:dev-server",
             "defaultConfiguration": "development",
             "options": {
-                "buildTarget": "campaign:build",
+                "buildTarget": "app:build",
                 "port": 8080
             },
             "configurations": {}
@@ -34,9 +35,8 @@
         "test": {
             "executor": "@nxext/vitest:vitest",
             "options": {
-                "vitestConfig": "apps/campaign/vitest.config.ts"
+                "vitestConfig": "apps/app/vitest.config.ts"
             }
         }
-    },
-    "tags": []
+    }
 }
diff --git a/apps/campaign/public/favicon.ico b/apps/app/public/favicon.ico
similarity index 100%
rename from apps/campaign/public/favicon.ico
rename to apps/app/public/favicon.ico
diff --git a/apps/campaign/public/notify.js b/apps/app/public/notify.js
similarity index 84%
rename from apps/campaign/public/notify.js
rename to apps/app/public/notify.js
index 8c4a55f4f..508d6f05e 100644
--- a/apps/campaign/public/notify.js
+++ b/apps/app/public/notify.js
@@ -3,7 +3,7 @@ const args = process.argv.slice(2);
 const [branch, version, webhook] = args;
 
 if (!branch || !version || !webhook) {
-    console.error('Usage: nx run campaign:notify --branch=:branch --version=:version --webhook=:webhook');
+    console.error('Usage: nx run app:notify --branch=:branch --version=:version --webhook=:webhook');
     process.exit(0);
 }
 
diff --git a/apps/campaign/public/signin-popup.html b/apps/app/public/signin-popup.html
similarity index 100%
rename from apps/campaign/public/signin-popup.html
rename to apps/app/public/signin-popup.html
diff --git a/apps/campaign/public/signin-silent.html b/apps/app/public/signin-silent.html
similarity index 100%
rename from apps/campaign/public/signin-silent.html
rename to apps/app/public/signin-silent.html
diff --git a/apps/campaign/public/signout-popup.html b/apps/app/public/signout-popup.html
similarity index 100%
rename from apps/campaign/public/signout-popup.html
rename to apps/app/public/signout-popup.html
diff --git a/apps/campaign/src/App.vue b/apps/app/src/App.vue
similarity index 100%
rename from apps/campaign/src/App.vue
rename to apps/app/src/App.vue
diff --git a/apps/app/src/assets/.gitkeep b/apps/app/src/assets/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/campaign/src/assets/bg-overlay.png b/apps/app/src/assets/bg-overlay.png
similarity index 100%
rename from apps/campaign/src/assets/bg-overlay.png
rename to apps/app/src/assets/bg-overlay.png
diff --git a/apps/campaign/src/assets/bg-planet.png b/apps/app/src/assets/bg-planet.png
similarity index 100%
rename from apps/campaign/src/assets/bg-planet.png
rename to apps/app/src/assets/bg-planet.png
diff --git a/apps/campaign/src/assets/discord-logo.png b/apps/app/src/assets/discord-logo.png
similarity index 100%
rename from apps/campaign/src/assets/discord-logo.png
rename to apps/app/src/assets/discord-logo.png
diff --git a/apps/campaign/src/assets/gitcoin-logo.svg b/apps/app/src/assets/gitcoin-logo.svg
similarity index 100%
rename from apps/campaign/src/assets/gitcoin-logo.svg
rename to apps/app/src/assets/gitcoin-logo.svg
diff --git a/apps/campaign/src/assets/github-logo.png b/apps/app/src/assets/github-logo.png
similarity index 100%
rename from apps/campaign/src/assets/github-logo.png
rename to apps/app/src/assets/github-logo.png
diff --git a/apps/campaign/src/assets/google-logo.png b/apps/app/src/assets/google-logo.png
similarity index 100%
rename from apps/campaign/src/assets/google-logo.png
rename to apps/app/src/assets/google-logo.png
diff --git a/apps/campaign/src/assets/logo-eu.png b/apps/app/src/assets/logo-eu.png
similarity index 100%
rename from apps/campaign/src/assets/logo-eu.png
rename to apps/app/src/assets/logo-eu.png
diff --git a/apps/campaign/src/assets/logo-pv.png b/apps/app/src/assets/logo-pv.png
similarity index 100%
rename from apps/campaign/src/assets/logo-pv.png
rename to apps/app/src/assets/logo-pv.png
diff --git a/apps/campaign/src/assets/logo-ts.png b/apps/app/src/assets/logo-ts.png
similarity index 100%
rename from apps/campaign/src/assets/logo-ts.png
rename to apps/app/src/assets/logo-ts.png
diff --git a/apps/app/src/assets/logo.png b/apps/app/src/assets/logo.png
new file mode 100644
index 000000000..789f97fdc
Binary files /dev/null and b/apps/app/src/assets/logo.png differ
diff --git a/apps/campaign/src/assets/metamask-logo.png b/apps/app/src/assets/metamask-logo.png
similarity index 100%
rename from apps/campaign/src/assets/metamask-logo.png
rename to apps/app/src/assets/metamask-logo.png
diff --git a/apps/campaign/src/assets/safe-logo.jpg b/apps/app/src/assets/safe-logo.jpg
similarity index 100%
rename from apps/campaign/src/assets/safe-logo.jpg
rename to apps/app/src/assets/safe-logo.jpg
diff --git a/apps/campaign/src/assets/shopify-logo.png b/apps/app/src/assets/shopify-logo.png
similarity index 100%
rename from apps/campaign/src/assets/shopify-logo.png
rename to apps/app/src/assets/shopify-logo.png
diff --git a/apps/campaign/src/assets/thx_logo_arbitrum.svg b/apps/app/src/assets/thx_logo_arbitrum.svg
similarity index 100%
rename from apps/campaign/src/assets/thx_logo_arbitrum.svg
rename to apps/app/src/assets/thx_logo_arbitrum.svg
diff --git a/apps/campaign/src/assets/thx_logo_bsc.svg b/apps/app/src/assets/thx_logo_bsc.svg
similarity index 100%
rename from apps/campaign/src/assets/thx_logo_bsc.svg
rename to apps/app/src/assets/thx_logo_bsc.svg
diff --git a/apps/campaign/src/assets/thx_logo_ethereum.svg b/apps/app/src/assets/thx_logo_ethereum.svg
similarity index 100%
rename from apps/campaign/src/assets/thx_logo_ethereum.svg
rename to apps/app/src/assets/thx_logo_ethereum.svg
diff --git a/apps/campaign/src/assets/thx_logo_hardhat.svg b/apps/app/src/assets/thx_logo_hardhat.svg
similarity index 100%
rename from apps/campaign/src/assets/thx_logo_hardhat.svg
rename to apps/app/src/assets/thx_logo_hardhat.svg
diff --git a/apps/campaign/src/assets/thx_logo_linea.svg b/apps/app/src/assets/thx_logo_linea.svg
similarity index 100%
rename from apps/campaign/src/assets/thx_logo_linea.svg
rename to apps/app/src/assets/thx_logo_linea.svg
diff --git a/apps/campaign/src/assets/thx_logo_polygon.svg b/apps/app/src/assets/thx_logo_polygon.svg
similarity index 100%
rename from apps/campaign/src/assets/thx_logo_polygon.svg
rename to apps/app/src/assets/thx_logo_polygon.svg
diff --git a/apps/campaign/src/assets/thx_token_governance.png b/apps/app/src/assets/thx_token_governance.png
similarity index 100%
rename from apps/campaign/src/assets/thx_token_governance.png
rename to apps/app/src/assets/thx_token_governance.png
diff --git a/apps/campaign/src/assets/thx_token_subscribe.webp b/apps/app/src/assets/thx_token_subscribe.webp
similarity index 100%
rename from apps/campaign/src/assets/thx_token_subscribe.webp
rename to apps/app/src/assets/thx_token_subscribe.webp
diff --git a/apps/campaign/src/assets/twitter-logo.png b/apps/app/src/assets/twitter-logo.png
similarity index 100%
rename from apps/campaign/src/assets/twitter-logo.png
rename to apps/app/src/assets/twitter-logo.png
diff --git a/apps/campaign/src/assets/walletconnect-logo.png b/apps/app/src/assets/walletconnect-logo.png
similarity index 100%
rename from apps/campaign/src/assets/walletconnect-logo.png
rename to apps/app/src/assets/walletconnect-logo.png
diff --git a/apps/campaign/src/assets/web3auth-logo.jpeg b/apps/app/src/assets/web3auth-logo.jpeg
similarity index 100%
rename from apps/campaign/src/assets/web3auth-logo.jpeg
rename to apps/app/src/assets/web3auth-logo.jpeg
diff --git a/apps/campaign/src/assets/youtube-logo.png b/apps/app/src/assets/youtube-logo.png
similarity index 100%
rename from apps/campaign/src/assets/youtube-logo.png
rename to apps/app/src/assets/youtube-logo.png
diff --git a/apps/campaign/src/components/BaseFooter.vue b/apps/app/src/components/BaseFooter.vue
similarity index 100%
rename from apps/campaign/src/components/BaseFooter.vue
rename to apps/app/src/components/BaseFooter.vue
diff --git a/apps/campaign/src/components/BaseQuestLeaderboard.vue b/apps/app/src/components/BaseQuestLeaderboard.vue
similarity index 100%
rename from apps/campaign/src/components/BaseQuestLeaderboard.vue
rename to apps/app/src/components/BaseQuestLeaderboard.vue
diff --git a/apps/campaign/src/components/BaseSidebar.vue b/apps/app/src/components/BaseSidebar.vue
similarity index 100%
rename from apps/campaign/src/components/BaseSidebar.vue
rename to apps/app/src/components/BaseSidebar.vue
diff --git a/apps/campaign/src/components/alert/BaseAlertErrorList.vue b/apps/app/src/components/alert/BaseAlertErrorList.vue
similarity index 100%
rename from apps/campaign/src/components/alert/BaseAlertErrorList.vue
rename to apps/app/src/components/alert/BaseAlertErrorList.vue
diff --git a/apps/campaign/src/components/blockquote/BaseBlockquoteDiscordInviteUsed.vue b/apps/app/src/components/blockquote/BaseBlockquoteDiscordInviteUsed.vue
similarity index 100%
rename from apps/campaign/src/components/blockquote/BaseBlockquoteDiscordInviteUsed.vue
rename to apps/app/src/components/blockquote/BaseBlockquoteDiscordInviteUsed.vue
diff --git a/apps/campaign/src/components/blockquote/BaseBlockquoteDiscordMessage.vue b/apps/app/src/components/blockquote/BaseBlockquoteDiscordMessage.vue
similarity index 100%
rename from apps/campaign/src/components/blockquote/BaseBlockquoteDiscordMessage.vue
rename to apps/app/src/components/blockquote/BaseBlockquoteDiscordMessage.vue
diff --git a/apps/campaign/src/components/blockquote/BaseBlockquoteDiscordServerJoin.vue b/apps/app/src/components/blockquote/BaseBlockquoteDiscordServerJoin.vue
similarity index 100%
rename from apps/campaign/src/components/blockquote/BaseBlockquoteDiscordServerJoin.vue
rename to apps/app/src/components/blockquote/BaseBlockquoteDiscordServerJoin.vue
diff --git a/apps/campaign/src/components/blockquote/BaseBlockquoteDiscordServerRole.vue b/apps/app/src/components/blockquote/BaseBlockquoteDiscordServerRole.vue
similarity index 100%
rename from apps/campaign/src/components/blockquote/BaseBlockquoteDiscordServerRole.vue
rename to apps/app/src/components/blockquote/BaseBlockquoteDiscordServerRole.vue
diff --git a/apps/campaign/src/components/blockquote/BaseBlockquoteTwitterQuery.vue b/apps/app/src/components/blockquote/BaseBlockquoteTwitterQuery.vue
similarity index 100%
rename from apps/campaign/src/components/blockquote/BaseBlockquoteTwitterQuery.vue
rename to apps/app/src/components/blockquote/BaseBlockquoteTwitterQuery.vue
diff --git a/apps/campaign/src/components/blockquote/BaseBlockquoteTwitterTweet.vue b/apps/app/src/components/blockquote/BaseBlockquoteTwitterTweet.vue
similarity index 100%
rename from apps/campaign/src/components/blockquote/BaseBlockquoteTwitterTweet.vue
rename to apps/app/src/components/blockquote/BaseBlockquoteTwitterTweet.vue
diff --git a/apps/campaign/src/components/blockquote/BaseBlockquoteTwitterUser.vue b/apps/app/src/components/blockquote/BaseBlockquoteTwitterUser.vue
similarity index 100%
rename from apps/campaign/src/components/blockquote/BaseBlockquoteTwitterUser.vue
rename to apps/app/src/components/blockquote/BaseBlockquoteTwitterUser.vue
diff --git a/apps/campaign/src/components/blockquote/BaseBlockquoteVideo.vue b/apps/app/src/components/blockquote/BaseBlockquoteVideo.vue
similarity index 100%
rename from apps/campaign/src/components/blockquote/BaseBlockquoteVideo.vue
rename to apps/app/src/components/blockquote/BaseBlockquoteVideo.vue
diff --git a/apps/campaign/src/components/blockquote/BaseBlockquoteYoutubeChannelSubscription.vue b/apps/app/src/components/blockquote/BaseBlockquoteYoutubeChannelSubscription.vue
similarity index 100%
rename from apps/campaign/src/components/blockquote/BaseBlockquoteYoutubeChannelSubscription.vue
rename to apps/app/src/components/blockquote/BaseBlockquoteYoutubeChannelSubscription.vue
diff --git a/apps/campaign/src/components/button/BaseBtnShareEmail.vue b/apps/app/src/components/button/BaseBtnShareEmail.vue
similarity index 100%
rename from apps/campaign/src/components/button/BaseBtnShareEmail.vue
rename to apps/app/src/components/button/BaseBtnShareEmail.vue
diff --git a/apps/campaign/src/components/button/BaseBtnShareLinkedin.vue b/apps/app/src/components/button/BaseBtnShareLinkedin.vue
similarity index 100%
rename from apps/campaign/src/components/button/BaseBtnShareLinkedin.vue
rename to apps/app/src/components/button/BaseBtnShareLinkedin.vue
diff --git a/apps/campaign/src/components/button/BaseBtnShareTelegram.vue b/apps/app/src/components/button/BaseBtnShareTelegram.vue
similarity index 100%
rename from apps/campaign/src/components/button/BaseBtnShareTelegram.vue
rename to apps/app/src/components/button/BaseBtnShareTelegram.vue
diff --git a/apps/campaign/src/components/button/BaseBtnShareTwitter.vue b/apps/app/src/components/button/BaseBtnShareTwitter.vue
similarity index 100%
rename from apps/campaign/src/components/button/BaseBtnShareTwitter.vue
rename to apps/app/src/components/button/BaseBtnShareTwitter.vue
diff --git a/apps/campaign/src/components/button/BaseBtnShareWhatsapp.vue b/apps/app/src/components/button/BaseBtnShareWhatsapp.vue
similarity index 100%
rename from apps/campaign/src/components/button/BaseBtnShareWhatsapp.vue
rename to apps/app/src/components/button/BaseBtnShareWhatsapp.vue
diff --git a/apps/campaign/src/components/button/BaseButtonApprove.vue b/apps/app/src/components/button/BaseButtonApprove.vue
similarity index 94%
rename from apps/campaign/src/components/button/BaseButtonApprove.vue
rename to apps/app/src/components/button/BaseButtonApprove.vue
index 22237a296..d7219f170 100644
--- a/apps/campaign/src/components/button/BaseButtonApprove.vue
+++ b/apps/app/src/components/button/BaseButtonApprove.vue
@@ -9,10 +9,10 @@
 import { defineComponent, PropType } from 'vue';
 import { BigNumber } from 'ethers/lib/ethers';
 import { formatUnits, parseUnits } from 'ethers/lib/utils';
-import { useWalletStore } from '@thxnetwork/campaign/stores/Wallet';
+import { useWalletStore } from '@thxnetwork/app/stores/Wallet';
 import { mapStores } from 'pinia';
-import { contractNetworks } from '@thxnetwork/campaign/config/constants';
-import { ChainId } from '@thxnetwork/sdk';
+import { contractNetworks } from '@thxnetwork/app/config/constants';
+import { ChainId } from '@thxnetwork/common/enums';
 import poll from 'promise-poller';
 
 export default defineComponent({
diff --git a/apps/campaign/src/components/button/BaseButtonLiquidityCreate.vue b/apps/app/src/components/button/BaseButtonLiquidityCreate.vue
similarity index 90%
rename from apps/campaign/src/components/button/BaseButtonLiquidityCreate.vue
rename to apps/app/src/components/button/BaseButtonLiquidityCreate.vue
index b361c5d30..e42e0cd97 100644
--- a/apps/campaign/src/components/button/BaseButtonLiquidityCreate.vue
+++ b/apps/app/src/components/button/BaseButtonLiquidityCreate.vue
@@ -8,13 +8,13 @@
 <script lang="ts">
 import { defineComponent, PropType } from 'vue';
 import { BigNumber } from 'ethers/lib/ethers';
-import { useWalletStore } from '@thxnetwork/campaign/stores/Wallet';
+import { useWalletStore } from '@thxnetwork/app/stores/Wallet';
 import { mapStores } from 'pinia';
-import { BALANCER_POOL_ID, contractNetworks } from '@thxnetwork/campaign/config/constants';
-import { ChainId } from '@thxnetwork/sdk';
+import { BALANCER_POOL_ID, contractNetworks } from '@thxnetwork/app/config/constants';
+import { ChainId } from '@thxnetwork/common/enums';
 import { BalancerSDK, Network } from '@balancer-labs/sdk';
-import { POLYGON_RPC } from '@thxnetwork/campaign/config/secrets';
-import { useLiquidityStore } from '@thxnetwork/campaign/stores/Liquidity';
+import { POLYGON_RPC } from '@thxnetwork/app/config/secrets';
+import { useLiquidityStore } from '@thxnetwork/app/stores/Liquidity';
 
 export default defineComponent({
     name: 'BaseButtonLiquidityCreate',
diff --git a/apps/campaign/src/components/button/BaseButtonLiquidityLock.vue b/apps/app/src/components/button/BaseButtonLiquidityLock.vue
similarity index 92%
rename from apps/campaign/src/components/button/BaseButtonLiquidityLock.vue
rename to apps/app/src/components/button/BaseButtonLiquidityLock.vue
index 08f42ed2a..624fd5cc8 100644
--- a/apps/campaign/src/components/button/BaseButtonLiquidityLock.vue
+++ b/apps/app/src/components/button/BaseButtonLiquidityLock.vue
@@ -8,9 +8,9 @@
 <script lang="ts">
 import { defineComponent, PropType } from 'vue';
 import { BigNumber } from 'ethers/lib/ethers';
-import { useWalletStore } from '@thxnetwork/campaign/stores/Wallet';
+import { useWalletStore } from '@thxnetwork/app/stores/Wallet';
 import { mapStores } from 'pinia';
-import { useVeStore } from '@thxnetwork/campaign/stores/VE';
+import { useVeStore } from '@thxnetwork/app/stores/VE';
 
 export default defineComponent({
     name: 'BaseButtonLiquidityLock',
diff --git a/apps/campaign/src/components/button/BaseButtonLiquidityStake.vue b/apps/app/src/components/button/BaseButtonLiquidityStake.vue
similarity index 87%
rename from apps/campaign/src/components/button/BaseButtonLiquidityStake.vue
rename to apps/app/src/components/button/BaseButtonLiquidityStake.vue
index 2ef44dc29..ad416942e 100644
--- a/apps/campaign/src/components/button/BaseButtonLiquidityStake.vue
+++ b/apps/app/src/components/button/BaseButtonLiquidityStake.vue
@@ -8,11 +8,11 @@
 <script lang="ts">
 import { defineComponent, PropType } from 'vue';
 import { BigNumber } from 'ethers/lib/ethers';
-import { useWalletStore } from '@thxnetwork/campaign/stores/Wallet';
+import { useWalletStore } from '@thxnetwork/app/stores/Wallet';
 import { mapStores } from 'pinia';
-import { contractNetworks } from '@thxnetwork/campaign/config/constants';
-import { ChainId } from '@thxnetwork/sdk';
-import { useLiquidityStore } from '@thxnetwork/campaign/stores/Liquidity';
+import { contractNetworks } from '@thxnetwork/app/config/constants';
+import { ChainId } from '@thxnetwork/common/enums';
+import { useLiquidityStore } from '@thxnetwork/app/stores/Liquidity';
 
 export default defineComponent({
     name: 'BaseButtonLiquidityStake',
diff --git a/apps/campaign/src/components/button/BaseButtonQuestLocked.vue b/apps/app/src/components/button/BaseButtonQuestLocked.vue
similarity index 100%
rename from apps/campaign/src/components/button/BaseButtonQuestLocked.vue
rename to apps/app/src/components/button/BaseButtonQuestLocked.vue
diff --git a/apps/campaign/src/components/button/BaseButtonWalletConnect.vue b/apps/app/src/components/button/BaseButtonWalletConnect.vue
similarity index 100%
rename from apps/campaign/src/components/button/BaseButtonWalletConnect.vue
rename to apps/app/src/components/button/BaseButtonWalletConnect.vue
diff --git a/apps/campaign/src/components/card/BaseCardAccount.vue b/apps/app/src/components/card/BaseCardAccount.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardAccount.vue
rename to apps/app/src/components/card/BaseCardAccount.vue
diff --git a/apps/campaign/src/components/card/BaseCardAccountRank.vue b/apps/app/src/components/card/BaseCardAccountRank.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardAccountRank.vue
rename to apps/app/src/components/card/BaseCardAccountRank.vue
diff --git a/apps/campaign/src/components/card/BaseCardCampaign.vue b/apps/app/src/components/card/BaseCardCampaign.vue
similarity index 98%
rename from apps/campaign/src/components/card/BaseCardCampaign.vue
rename to apps/app/src/components/card/BaseCardCampaign.vue
index 4d88ff507..0e78456e9 100644
--- a/apps/campaign/src/components/card/BaseCardCampaign.vue
+++ b/apps/app/src/components/card/BaseCardCampaign.vue
@@ -91,7 +91,7 @@
 import { defineComponent, PropType } from 'vue';
 import { decodeHTML } from '../../utils/decode-html';
 import { mapStores } from 'pinia';
-import { useAccountStore } from '@thxnetwork/campaign/stores/Account';
+import { useAccountStore } from '@thxnetwork/app/stores/Account';
 
 type TCampaignProps = {
     id: string;
diff --git a/apps/campaign/src/components/card/BaseCardCouponCode.vue b/apps/app/src/components/card/BaseCardCouponCode.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardCouponCode.vue
rename to apps/app/src/components/card/BaseCardCouponCode.vue
diff --git a/apps/campaign/src/components/card/BaseCardDiscordRole.vue b/apps/app/src/components/card/BaseCardDiscordRole.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardDiscordRole.vue
rename to apps/app/src/components/card/BaseCardDiscordRole.vue
diff --git a/apps/campaign/src/components/card/BaseCardERC20.vue b/apps/app/src/components/card/BaseCardERC20.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardERC20.vue
rename to apps/app/src/components/card/BaseCardERC20.vue
diff --git a/apps/campaign/src/components/card/BaseCardERC721.vue b/apps/app/src/components/card/BaseCardERC721.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardERC721.vue
rename to apps/app/src/components/card/BaseCardERC721.vue
diff --git a/apps/campaign/src/components/card/BaseCardGalachain.vue b/apps/app/src/components/card/BaseCardGalachain.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardGalachain.vue
rename to apps/app/src/components/card/BaseCardGalachain.vue
diff --git a/apps/campaign/src/components/card/BaseCardHeader.vue b/apps/app/src/components/card/BaseCardHeader.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardHeader.vue
rename to apps/app/src/components/card/BaseCardHeader.vue
diff --git a/apps/campaign/src/components/card/BaseCardHeaderHome.vue b/apps/app/src/components/card/BaseCardHeaderHome.vue
similarity index 87%
rename from apps/campaign/src/components/card/BaseCardHeaderHome.vue
rename to apps/app/src/components/card/BaseCardHeaderHome.vue
index bc63925ca..d2a187541 100644
--- a/apps/campaign/src/components/card/BaseCardHeaderHome.vue
+++ b/apps/app/src/components/card/BaseCardHeaderHome.vue
@@ -39,12 +39,12 @@
 </template>
 
 <script lang="ts">
-import { useLiquidityStore } from '@thxnetwork/campaign/stores/Liquidity';
+import { useLiquidityStore } from '@thxnetwork/app/stores/Liquidity';
 import { mapStores } from 'pinia';
 import { defineComponent } from 'vue';
-import imgLogoEU from '@thxnetwork/campaign/assets/logo-eu.png';
-import imgLogoTS from '@thxnetwork/campaign/assets/logo-ts.png';
-import imgLogoPV from '@thxnetwork/campaign/assets/logo-pv.png';
+import imgLogoEU from '@thxnetwork/app/assets/logo-eu.png';
+import imgLogoTS from '@thxnetwork/app/assets/logo-ts.png';
+import imgLogoPV from '@thxnetwork/app/assets/logo-pv.png';
 
 export default defineComponent({
     name: 'BaseCardHeaderHome',
diff --git a/apps/app/src/components/card/BaseCardMembership.vue b/apps/app/src/components/card/BaseCardMembership.vue
new file mode 100644
index 000000000..bb193615e
--- /dev/null
+++ b/apps/app/src/components/card/BaseCardMembership.vue
@@ -0,0 +1,56 @@
+<template>
+    <b-card class="border-0 gradient-shadow-xl" style="min-height: 415px">
+        <b-tabs v-model="index" pills justified content-class="mt-3" nav-wrapper-class="text-white">
+            <b-tab>
+                <template #title>
+                    <i class="fas fa-balance-scale me-1" />
+                    Liquidity
+                </template>
+                <hr />
+                <BaseTabLiquidity @change-tab="index = $event" />
+            </b-tab>
+            <b-tab>
+                <template #title>
+                    <i class="fas fa-id-card me-1" />
+                    Membership
+                </template>
+                <hr />
+                <BaseTabDeposit v-if="veStore.lock && !Number(veStore.lock.amount)" @change-tab="index = $event" />
+                <BaseTabWithdraw v-else @change-tab="index = $event" />
+            </b-tab>
+        </b-tabs>
+    </b-card>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+import { mapStores } from 'pinia';
+import { useVeStore } from '../../stores/VE';
+
+export default defineComponent({
+    name: 'BaseCardMembership',
+    props: {
+        tabIndex: {
+            type: Number,
+            default: 0,
+        },
+    },
+    data() {
+        return {
+            index: 0,
+        };
+    },
+    computed: {
+        ...mapStores(useVeStore),
+    },
+    watch: {
+        tabIndex: {
+            handler(value: number) {
+                this.index = value;
+            },
+            immediate: true,
+        },
+    },
+    methods: {},
+});
+</script>
diff --git a/apps/campaign/src/components/card/BaseCardMembershipOnboarding.vue b/apps/app/src/components/card/BaseCardMembershipOnboarding.vue
similarity index 96%
rename from apps/campaign/src/components/card/BaseCardMembershipOnboarding.vue
rename to apps/app/src/components/card/BaseCardMembershipOnboarding.vue
index 1fbc53d72..9c12ad186 100644
--- a/apps/campaign/src/components/card/BaseCardMembershipOnboarding.vue
+++ b/apps/app/src/components/card/BaseCardMembershipOnboarding.vue
@@ -88,11 +88,11 @@ import { mapStores } from 'pinia';
 import { useWalletStore } from '../../stores/Wallet';
 import { useLiquidityStore } from '../../stores/Liquidity';
 import { useVeStore } from '../../stores/VE';
-import { contractNetworks } from '@thxnetwork/campaign/config/constants';
-import { chainList } from '@thxnetwork/campaign/utils/chains';
-import { ChainId } from '@thxnetwork/sdk';
+import { contractNetworks } from '@thxnetwork/app/config/constants';
+import { chainList } from '@thxnetwork/app/utils/chains';
+import { ChainId } from '@thxnetwork/common/enums';
 import { formatUnits } from 'ethers/lib/utils';
-import { useAuthStore } from '@thxnetwork/campaign/stores/Auth';
+import { useAuthStore } from '@thxnetwork/app/stores/Auth';
 import { useAccountStore } from '../../stores/Account';
 
 export default defineComponent({
diff --git a/apps/campaign/src/components/card/BaseCardPrices.vue b/apps/app/src/components/card/BaseCardPrices.vue
similarity index 96%
rename from apps/campaign/src/components/card/BaseCardPrices.vue
rename to apps/app/src/components/card/BaseCardPrices.vue
index 525fee560..f41b3d86a 100644
--- a/apps/campaign/src/components/card/BaseCardPrices.vue
+++ b/apps/app/src/components/card/BaseCardPrices.vue
@@ -23,7 +23,7 @@ import { useAuthStore } from '../../stores/Auth';
 import { useWalletStore } from '../../stores/Wallet';
 import { useLiquidityStore } from '../../stores/Liquidity';
 import { useVeStore } from '../../stores/VE';
-import { roundUpFixed, toFiatPrice } from '@thxnetwork/campaign/utils/price';
+import { roundUpFixed, toFiatPrice } from '@thxnetwork/app/utils/price';
 
 export default defineComponent({
     name: 'BaseCardPrices',
diff --git a/apps/campaign/src/components/card/BaseCardQuest.vue b/apps/app/src/components/card/BaseCardQuest.vue
similarity index 98%
rename from apps/campaign/src/components/card/BaseCardQuest.vue
rename to apps/app/src/components/card/BaseCardQuest.vue
index 526f05dfa..25def26c9 100644
--- a/apps/campaign/src/components/card/BaseCardQuest.vue
+++ b/apps/app/src/components/card/BaseCardQuest.vue
@@ -117,11 +117,11 @@
 <script lang="ts">
 import { PropType, defineComponent } from 'vue';
 import { format, formatDistance } from 'date-fns';
-import { QuestVariant } from '@thxnetwork/sdk';
 import { mapStores } from 'pinia';
 import { useAccountStore } from '../../stores/Account';
 import { useQuestStore } from '../../stores/Quest';
-import { decodeHTML } from '@thxnetwork/campaign/utils/decode-html';
+import { decodeHTML } from '@thxnetwork/app/utils/decode-html';
+import { QuestVariant } from '@thxnetwork/sdk/types/enums';
 
 export default defineComponent({
     name: 'BaseCardQuest',
diff --git a/apps/campaign/src/components/card/BaseCardQuestCustom.vue b/apps/app/src/components/card/BaseCardQuestCustom.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardQuestCustom.vue
rename to apps/app/src/components/card/BaseCardQuestCustom.vue
diff --git a/apps/campaign/src/components/card/BaseCardQuestDaily.vue b/apps/app/src/components/card/BaseCardQuestDaily.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardQuestDaily.vue
rename to apps/app/src/components/card/BaseCardQuestDaily.vue
diff --git a/apps/campaign/src/components/card/BaseCardQuestGitcoin.vue b/apps/app/src/components/card/BaseCardQuestGitcoin.vue
similarity index 96%
rename from apps/campaign/src/components/card/BaseCardQuestGitcoin.vue
rename to apps/app/src/components/card/BaseCardQuestGitcoin.vue
index bf372e06f..f9bf004cd 100644
--- a/apps/campaign/src/components/card/BaseCardQuestGitcoin.vue
+++ b/apps/app/src/components/card/BaseCardQuestGitcoin.vue
@@ -57,8 +57,8 @@ import { useAccountStore } from '../../stores/Account';
 import { useAuthStore } from '../../stores/Auth';
 import { useQuestStore } from '../../stores/Quest';
 import { useWalletStore } from '../../stores/Wallet';
-import { ChainId } from '@thxnetwork/sdk';
-import { WalletVariant } from '@thxnetwork/campaign/types/enums/accountVariant';
+import { WalletVariant } from '../../types/enums/accountVariant';
+import { ChainId } from '@thxnetwork/common/enums';
 import imgLogoGitcoin from '../../assets/gitcoin-logo.svg';
 
 export default defineComponent({
diff --git a/apps/campaign/src/components/card/BaseCardQuestInvite.vue b/apps/app/src/components/card/BaseCardQuestInvite.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardQuestInvite.vue
rename to apps/app/src/components/card/BaseCardQuestInvite.vue
diff --git a/apps/campaign/src/components/card/BaseCardQuestSocial.vue b/apps/app/src/components/card/BaseCardQuestSocial.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardQuestSocial.vue
rename to apps/app/src/components/card/BaseCardQuestSocial.vue
diff --git a/apps/campaign/src/components/card/BaseCardQuestSpotlight.vue b/apps/app/src/components/card/BaseCardQuestSpotlight.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardQuestSpotlight.vue
rename to apps/app/src/components/card/BaseCardQuestSpotlight.vue
diff --git a/apps/campaign/src/components/card/BaseCardQuestWeb3.vue b/apps/app/src/components/card/BaseCardQuestWeb3.vue
similarity index 95%
rename from apps/campaign/src/components/card/BaseCardQuestWeb3.vue
rename to apps/app/src/components/card/BaseCardQuestWeb3.vue
index f3f7e3412..46644a481 100644
--- a/apps/campaign/src/components/card/BaseCardQuestWeb3.vue
+++ b/apps/app/src/components/card/BaseCardQuestWeb3.vue
@@ -66,9 +66,9 @@ import { useAccountStore } from '../../stores/Account';
 import { useAuthStore } from '../../stores/Auth';
 import { useQuestStore } from '../../stores/Quest';
 import { chainList, getAddressURL } from '../../utils/chains';
-import { ChainId } from '@thxnetwork/sdk/src/lib/types/enums/ChainId';
-import { useWalletStore } from '@thxnetwork/campaign/stores/Wallet';
-import { WalletVariant } from '@thxnetwork/campaign/types/enums/accountVariant';
+import { ChainId } from '@thxnetwork/common/enums';
+import { useWalletStore } from '@thxnetwork/app/stores/Wallet';
+import { WalletVariant } from '@thxnetwork/app/types/enums/accountVariant';
 
 export default defineComponent({
     name: 'BaseCardQuestWeb3',
diff --git a/apps/campaign/src/components/card/BaseCardQuestWebhook.vue b/apps/app/src/components/card/BaseCardQuestWebhook.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardQuestWebhook.vue
rename to apps/app/src/components/card/BaseCardQuestWebhook.vue
diff --git a/apps/campaign/src/components/card/BaseCardReward.vue b/apps/app/src/components/card/BaseCardReward.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardReward.vue
rename to apps/app/src/components/card/BaseCardReward.vue
diff --git a/apps/campaign/src/components/card/BaseCardRewardCoin.vue b/apps/app/src/components/card/BaseCardRewardCoin.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardRewardCoin.vue
rename to apps/app/src/components/card/BaseCardRewardCoin.vue
diff --git a/apps/campaign/src/components/card/BaseCardRewardCoupon.vue b/apps/app/src/components/card/BaseCardRewardCoupon.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardRewardCoupon.vue
rename to apps/app/src/components/card/BaseCardRewardCoupon.vue
diff --git a/apps/campaign/src/components/card/BaseCardRewardCustom.vue b/apps/app/src/components/card/BaseCardRewardCustom.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardRewardCustom.vue
rename to apps/app/src/components/card/BaseCardRewardCustom.vue
diff --git a/apps/campaign/src/components/card/BaseCardRewardDiscordRole.vue b/apps/app/src/components/card/BaseCardRewardDiscordRole.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardRewardDiscordRole.vue
rename to apps/app/src/components/card/BaseCardRewardDiscordRole.vue
diff --git a/apps/campaign/src/components/card/BaseCardRewardGalachain.vue b/apps/app/src/components/card/BaseCardRewardGalachain.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardRewardGalachain.vue
rename to apps/app/src/components/card/BaseCardRewardGalachain.vue
diff --git a/apps/campaign/src/components/card/BaseCardRewardNFT.vue b/apps/app/src/components/card/BaseCardRewardNFT.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardRewardNFT.vue
rename to apps/app/src/components/card/BaseCardRewardNFT.vue
diff --git a/apps/campaign/src/components/card/BaseCardRewards.vue b/apps/app/src/components/card/BaseCardRewards.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardRewards.vue
rename to apps/app/src/components/card/BaseCardRewards.vue
diff --git a/apps/campaign/src/components/card/BaseCardSnapshotProposal.vue b/apps/app/src/components/card/BaseCardSnapshotProposal.vue
similarity index 98%
rename from apps/campaign/src/components/card/BaseCardSnapshotProposal.vue
rename to apps/app/src/components/card/BaseCardSnapshotProposal.vue
index bc7b16ff5..a4e0af0c3 100644
--- a/apps/campaign/src/components/card/BaseCardSnapshotProposal.vue
+++ b/apps/app/src/components/card/BaseCardSnapshotProposal.vue
@@ -64,7 +64,7 @@
 import { mapStores } from 'pinia';
 import { defineComponent } from 'vue';
 import { useSnapshotStore } from '../../stores/Snapshot';
-import { useVeStore } from '@thxnetwork/campaign/stores/VE';
+import { useVeStore } from '@thxnetwork/app/stores/VE';
 import { format } from 'date-fns';
 
 export default defineComponent({
diff --git a/apps/campaign/src/components/card/BaseCardStatusCheck.vue b/apps/app/src/components/card/BaseCardStatusCheck.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardStatusCheck.vue
rename to apps/app/src/components/card/BaseCardStatusCheck.vue
diff --git a/apps/campaign/src/components/card/BaseCardWallets.vue b/apps/app/src/components/card/BaseCardWallets.vue
similarity index 100%
rename from apps/campaign/src/components/card/BaseCardWallets.vue
rename to apps/app/src/components/card/BaseCardWallets.vue
diff --git a/apps/campaign/src/components/dropdown/BaseDropdownMetric.vue b/apps/app/src/components/dropdown/BaseDropdownMetric.vue
similarity index 100%
rename from apps/campaign/src/components/dropdown/BaseDropdownMetric.vue
rename to apps/app/src/components/dropdown/BaseDropdownMetric.vue
diff --git a/apps/campaign/src/components/dropdown/BaseDropdownMetricAPR.vue b/apps/app/src/components/dropdown/BaseDropdownMetricAPR.vue
similarity index 100%
rename from apps/campaign/src/components/dropdown/BaseDropdownMetricAPR.vue
rename to apps/app/src/components/dropdown/BaseDropdownMetricAPR.vue
diff --git a/apps/campaign/src/components/dropdown/BaseDropdownMetricReward.vue b/apps/app/src/components/dropdown/BaseDropdownMetricReward.vue
similarity index 89%
rename from apps/campaign/src/components/dropdown/BaseDropdownMetricReward.vue
rename to apps/app/src/components/dropdown/BaseDropdownMetricReward.vue
index ef1bccff6..ab04a3d46 100644
--- a/apps/campaign/src/components/dropdown/BaseDropdownMetricReward.vue
+++ b/apps/app/src/components/dropdown/BaseDropdownMetricReward.vue
@@ -31,10 +31,10 @@ import { defineComponent, PropType } from 'vue';
 import { useLiquidityStore } from '../../stores/Liquidity';
 import { useWalletStore } from '../../stores/Wallet';
 import { formatUnits } from 'ethers/lib/utils';
-import { toFiatPrice } from '@thxnetwork/campaign/utils/price';
-import { contractNetworks } from '@thxnetwork/campaign/config/constants';
-import { ChainId } from '@thxnetwork/sdk';
-import { chainList } from '@thxnetwork/campaign/utils/chains';
+import { toFiatPrice } from '@thxnetwork/app/utils/price';
+import { contractNetworks } from '@thxnetwork/app/config/constants';
+import { ChainId } from '@thxnetwork/common/enums';
+import { chainList } from '@thxnetwork/app/utils/chains';
 
 type TMetricReward = {
     week: string;
diff --git a/apps/campaign/src/components/dropdown/BaseDropdownMetricRewards.vue b/apps/app/src/components/dropdown/BaseDropdownMetricRewards.vue
similarity index 89%
rename from apps/campaign/src/components/dropdown/BaseDropdownMetricRewards.vue
rename to apps/app/src/components/dropdown/BaseDropdownMetricRewards.vue
index c40266ff8..c4fd38a26 100644
--- a/apps/campaign/src/components/dropdown/BaseDropdownMetricRewards.vue
+++ b/apps/app/src/components/dropdown/BaseDropdownMetricRewards.vue
@@ -31,12 +31,12 @@ import { mapStores } from 'pinia';
 import { defineComponent } from 'vue';
 import { useLiquidityStore } from '../../stores/Liquidity';
 import { formatUnits, parseUnits } from 'ethers/lib/utils';
-import { toFiatPrice } from '@thxnetwork/campaign/utils/price';
+import { toFiatPrice } from '@thxnetwork/app/utils/price';
 import { BigNumber } from 'ethers/lib/ethers';
-import { chainList } from '@thxnetwork/campaign/utils/chains';
-import { useWalletStore } from '@thxnetwork/campaign/stores/Wallet';
-import { ChainId } from '@thxnetwork/sdk';
-import { contractNetworks } from '@thxnetwork/campaign/config/constants';
+import { chainList } from '@thxnetwork/app/utils/chains';
+import { useWalletStore } from '@thxnetwork/app/stores/Wallet';
+import { ChainId } from '@thxnetwork/common/enums';
+import { contractNetworks } from '@thxnetwork/app/config/constants';
 
 export default defineComponent({
     name: 'BaseDropdownMetricAPR',
diff --git a/apps/campaign/src/components/dropdown/BaseDropdownMetricTVL.vue b/apps/app/src/components/dropdown/BaseDropdownMetricTVL.vue
similarity index 92%
rename from apps/campaign/src/components/dropdown/BaseDropdownMetricTVL.vue
rename to apps/app/src/components/dropdown/BaseDropdownMetricTVL.vue
index 963341551..a1e4b4d30 100644
--- a/apps/campaign/src/components/dropdown/BaseDropdownMetricTVL.vue
+++ b/apps/app/src/components/dropdown/BaseDropdownMetricTVL.vue
@@ -40,10 +40,10 @@ import { defineComponent } from 'vue';
 import { useLiquidityStore } from '../../stores/Liquidity';
 import { useWalletStore } from '../../stores/Wallet';
 import { formatUnits } from 'ethers/lib/utils';
-import { toFiatPrice } from '@thxnetwork/campaign/utils/price';
-import { contractNetworks } from '@thxnetwork/campaign/config/constants';
-import { ChainId } from '@thxnetwork/sdk';
-import { chainList } from '@thxnetwork/campaign/utils/chains';
+import { toFiatPrice } from '@thxnetwork/app/utils/price';
+import { contractNetworks } from '@thxnetwork/app/config/constants';
+import { ChainId } from '@thxnetwork/common/enums';
+import { chainList } from '@thxnetwork/app/utils/chains';
 
 export default defineComponent({
     name: 'BaseDropdownMetricAPR',
diff --git a/apps/campaign/src/components/dropdown/BaseDropdownUserMenu.vue b/apps/app/src/components/dropdown/BaseDropdownUserMenu.vue
similarity index 100%
rename from apps/campaign/src/components/dropdown/BaseDropdownUserMenu.vue
rename to apps/app/src/components/dropdown/BaseDropdownUserMenu.vue
diff --git a/apps/campaign/src/components/dropdown/BaseDropdownWallets.vue b/apps/app/src/components/dropdown/BaseDropdownWallets.vue
similarity index 99%
rename from apps/campaign/src/components/dropdown/BaseDropdownWallets.vue
rename to apps/app/src/components/dropdown/BaseDropdownWallets.vue
index ea5f82f86..40ef5b77a 100644
--- a/apps/campaign/src/components/dropdown/BaseDropdownWallets.vue
+++ b/apps/app/src/components/dropdown/BaseDropdownWallets.vue
@@ -153,7 +153,7 @@ import { mapStores } from 'pinia';
 import { useWalletStore, walletLogoMap } from '../../stores/Wallet';
 import { useAccountStore } from '../../stores/Account';
 import { WalletVariant } from '../../types/enums/accountVariant';
-import { chainList } from '@thxnetwork/campaign/utils/chains';
+import { chainList } from '@thxnetwork/app/utils/chains';
 
 export default defineComponent({
     name: 'BaseDropdownWallets',
diff --git a/apps/campaign/src/components/formgroup/BaseFormGroupAccountVariant.vue b/apps/app/src/components/formgroup/BaseFormGroupAccountVariant.vue
similarity index 100%
rename from apps/campaign/src/components/formgroup/BaseFormGroupAccountVariant.vue
rename to apps/app/src/components/formgroup/BaseFormGroupAccountVariant.vue
diff --git a/apps/campaign/src/components/formgroup/BaseFormGroupAvatar.vue b/apps/app/src/components/formgroup/BaseFormGroupAvatar.vue
similarity index 100%
rename from apps/campaign/src/components/formgroup/BaseFormGroupAvatar.vue
rename to apps/app/src/components/formgroup/BaseFormGroupAvatar.vue
diff --git a/apps/campaign/src/components/formgroup/BaseFormGroupConnected.vue b/apps/app/src/components/formgroup/BaseFormGroupConnected.vue
similarity index 100%
rename from apps/campaign/src/components/formgroup/BaseFormGroupConnected.vue
rename to apps/app/src/components/formgroup/BaseFormGroupConnected.vue
diff --git a/apps/campaign/src/components/formgroup/BaseFormGroupEmail.vue b/apps/app/src/components/formgroup/BaseFormGroupEmail.vue
similarity index 100%
rename from apps/campaign/src/components/formgroup/BaseFormGroupEmail.vue
rename to apps/app/src/components/formgroup/BaseFormGroupEmail.vue
diff --git a/apps/campaign/src/components/formgroup/BaseFormGroupInputDate.vue b/apps/app/src/components/formgroup/BaseFormGroupInputDate.vue
similarity index 100%
rename from apps/campaign/src/components/formgroup/BaseFormGroupInputDate.vue
rename to apps/app/src/components/formgroup/BaseFormGroupInputDate.vue
diff --git a/apps/campaign/src/components/formgroup/BaseFormGroupInputTokenAmount.vue b/apps/app/src/components/formgroup/BaseFormGroupInputTokenAmount.vue
similarity index 100%
rename from apps/campaign/src/components/formgroup/BaseFormGroupInputTokenAmount.vue
rename to apps/app/src/components/formgroup/BaseFormGroupInputTokenAmount.vue
diff --git a/apps/campaign/src/components/formgroup/BaseFormGroupLockAmount.vue b/apps/app/src/components/formgroup/BaseFormGroupLockAmount.vue
similarity index 88%
rename from apps/campaign/src/components/formgroup/BaseFormGroupLockAmount.vue
rename to apps/app/src/components/formgroup/BaseFormGroupLockAmount.vue
index b3186681f..a96a0f58a 100644
--- a/apps/campaign/src/components/formgroup/BaseFormGroupLockAmount.vue
+++ b/apps/app/src/components/formgroup/BaseFormGroupLockAmount.vue
@@ -32,11 +32,11 @@
 </template>
 
 <script lang="ts">
-import { contractNetworks } from '@thxnetwork/campaign/config/constants';
-import { useLiquidityStore } from '@thxnetwork/campaign/stores/Liquidity';
-import { useVeStore } from '@thxnetwork/campaign/stores/VE';
-import { useWalletStore } from '@thxnetwork/campaign/stores/Wallet';
-import { ChainId } from '@thxnetwork/sdk';
+import { contractNetworks } from '@thxnetwork/app/config/constants';
+import { useLiquidityStore } from '@thxnetwork/app/stores/Liquidity';
+import { useVeStore } from '@thxnetwork/app/stores/VE';
+import { useWalletStore } from '@thxnetwork/app/stores/Wallet';
+import { ChainId } from '@thxnetwork/common/enums';
 import { formatUnits, parseUnits } from 'ethers/lib/utils';
 import { mapStores } from 'pinia';
 import { defineComponent } from 'vue';
diff --git a/apps/campaign/src/components/formgroup/BaseFormGroupLockEnd.vue b/apps/app/src/components/formgroup/BaseFormGroupLockEnd.vue
similarity index 95%
rename from apps/campaign/src/components/formgroup/BaseFormGroupLockEnd.vue
rename to apps/app/src/components/formgroup/BaseFormGroupLockEnd.vue
index 4a5626620..1a282215a 100644
--- a/apps/campaign/src/components/formgroup/BaseFormGroupLockEnd.vue
+++ b/apps/app/src/components/formgroup/BaseFormGroupLockEnd.vue
@@ -29,10 +29,10 @@
 
 <script lang="ts">
 import { defineComponent } from 'vue';
-import { NinetyDaysInMs, getThursdaysUntilTimestamp } from '@thxnetwork/campaign/utils/date';
+import { NinetyDaysInMs, getThursdaysUntilTimestamp } from '@thxnetwork/app/utils/date';
 import { mapStores } from 'pinia';
-import { useVeStore } from '@thxnetwork/campaign/stores/VE';
-import { BigNumber } from 'ethers';
+import { useVeStore } from '@thxnetwork/app/stores/VE';
+import { BigNumber } from 'alchemy-sdk';
 
 export default defineComponent({
     name: 'BaseFormGroupLockEnd',
diff --git a/apps/campaign/src/components/formgroup/BaseFormGroupUsername.vue b/apps/app/src/components/formgroup/BaseFormGroupUsername.vue
similarity index 100%
rename from apps/campaign/src/components/formgroup/BaseFormGroupUsername.vue
rename to apps/app/src/components/formgroup/BaseFormGroupUsername.vue
diff --git a/apps/campaign/src/components/formgroup/BaseFormGroupWalletAddress.vue b/apps/app/src/components/formgroup/BaseFormGroupWalletAddress.vue
similarity index 100%
rename from apps/campaign/src/components/formgroup/BaseFormGroupWalletAddress.vue
rename to apps/app/src/components/formgroup/BaseFormGroupWalletAddress.vue
diff --git a/apps/campaign/src/components/formgroup/BaseFormGroupWalletSelect.vue b/apps/app/src/components/formgroup/BaseFormGroupWalletSelect.vue
similarity index 100%
rename from apps/campaign/src/components/formgroup/BaseFormGroupWalletSelect.vue
rename to apps/app/src/components/formgroup/BaseFormGroupWalletSelect.vue
diff --git a/apps/campaign/src/components/modal/BaseModalAccount.vue b/apps/app/src/components/modal/BaseModalAccount.vue
similarity index 100%
rename from apps/campaign/src/components/modal/BaseModalAccount.vue
rename to apps/app/src/components/modal/BaseModalAccount.vue
diff --git a/apps/campaign/src/components/modal/BaseModalClaimTokens.vue b/apps/app/src/components/modal/BaseModalClaimTokens.vue
similarity index 93%
rename from apps/campaign/src/components/modal/BaseModalClaimTokens.vue
rename to apps/app/src/components/modal/BaseModalClaimTokens.vue
index 0528c9b61..19af3f7b1 100644
--- a/apps/campaign/src/components/modal/BaseModalClaimTokens.vue
+++ b/apps/app/src/components/modal/BaseModalClaimTokens.vue
@@ -70,11 +70,11 @@ import { mapStores } from 'pinia';
 import { useVeStore } from '../../stores/VE';
 import { useWalletStore } from '../../stores/Wallet';
 import { formatUnits } from 'ethers/lib/utils';
-import { roundDownFixed, toFiatPrice } from '@thxnetwork/campaign/utils/price';
-import { ChainId } from '@thxnetwork/sdk';
-import { chainList } from '@thxnetwork/campaign/utils/chains';
-import { useLiquidityStore } from '@thxnetwork/campaign/stores/Liquidity';
-import { WalletVariant } from '@thxnetwork/campaign/types/enums/accountVariant';
+import { roundDownFixed, toFiatPrice } from '@thxnetwork/app/utils/price';
+import { ChainId } from '@thxnetwork/common/enums';
+import { chainList } from '@thxnetwork/app/utils/chains';
+import { useLiquidityStore } from '@thxnetwork/app/stores/Liquidity';
+import { WalletVariant } from '@thxnetwork/app/types/enums/accountVariant';
 
 export default defineComponent({
     name: 'BaseModalClaimTokens',
diff --git a/apps/campaign/src/components/modal/BaseModalCreateLiquidity.vue b/apps/app/src/components/modal/BaseModalCreateLiquidity.vue
similarity index 98%
rename from apps/campaign/src/components/modal/BaseModalCreateLiquidity.vue
rename to apps/app/src/components/modal/BaseModalCreateLiquidity.vue
index 08513051e..6c27abc4a 100644
--- a/apps/campaign/src/components/modal/BaseModalCreateLiquidity.vue
+++ b/apps/app/src/components/modal/BaseModalCreateLiquidity.vue
@@ -125,10 +125,10 @@ import { useWalletStore } from '../../stores/Wallet';
 import { useLiquidityStore } from '../../stores/Liquidity';
 import { BALANCER_POOL_ID, contractNetworks } from '../../config/constants';
 import { parseUnits } from 'ethers/lib/utils';
-import { ChainId } from '@thxnetwork/sdk';
-import { WalletVariant } from '@thxnetwork/campaign/types/enums/accountVariant';
+import { ChainId } from '@thxnetwork/common/enums';
+import { WalletVariant } from '@thxnetwork/app/types/enums/accountVariant';
 import { BalancerSDK, Network } from '@balancer-labs/sdk';
-import { POLYGON_RPC } from '@thxnetwork/campaign/config/secrets';
+import { POLYGON_RPC } from '@thxnetwork/app/config/secrets';
 
 export default defineComponent({
     name: 'BaseModalCreateLiquidity',
diff --git a/apps/campaign/src/components/modal/BaseModalDeposit.vue b/apps/app/src/components/modal/BaseModalDeposit.vue
similarity index 96%
rename from apps/campaign/src/components/modal/BaseModalDeposit.vue
rename to apps/app/src/components/modal/BaseModalDeposit.vue
index dcfed23be..2f2f9991b 100644
--- a/apps/campaign/src/components/modal/BaseModalDeposit.vue
+++ b/apps/app/src/components/modal/BaseModalDeposit.vue
@@ -52,11 +52,11 @@ import { useVeStore } from '../../stores/VE';
 import { useWalletStore } from '../../stores/Wallet';
 import { useLiquidityStore } from '../../stores/Liquidity';
 import { contractNetworks } from '../../config/constants';
-import { ChainId } from '@thxnetwork/sdk';
+import { ChainId } from '@thxnetwork/common/enums';
 import { formatUnits, parseUnits } from 'ethers/lib/utils';
-import { roundDownFixed } from '@thxnetwork/campaign/utils/price';
-import { WalletVariant } from '@thxnetwork/campaign/types/enums/accountVariant';
-import { BigNumber } from 'ethers';
+import { roundDownFixed } from '@thxnetwork/app/utils/price';
+import { WalletVariant } from '@thxnetwork/app/types/enums/accountVariant';
+import { BigNumber } from 'alchemy-sdk';
 
 export default defineComponent({
     name: 'BaseModalDeposit',
diff --git a/apps/campaign/src/components/modal/BaseModalERC20Transfer.vue b/apps/app/src/components/modal/BaseModalERC20Transfer.vue
similarity index 100%
rename from apps/campaign/src/components/modal/BaseModalERC20Transfer.vue
rename to apps/app/src/components/modal/BaseModalERC20Transfer.vue
diff --git a/apps/campaign/src/components/modal/BaseModalERC721Transfer.vue b/apps/app/src/components/modal/BaseModalERC721Transfer.vue
similarity index 100%
rename from apps/campaign/src/components/modal/BaseModalERC721Transfer.vue
rename to apps/app/src/components/modal/BaseModalERC721Transfer.vue
diff --git a/apps/campaign/src/components/modal/BaseModalExternalURL.vue b/apps/app/src/components/modal/BaseModalExternalURL.vue
similarity index 100%
rename from apps/campaign/src/components/modal/BaseModalExternalURL.vue
rename to apps/app/src/components/modal/BaseModalExternalURL.vue
diff --git a/apps/campaign/src/components/modal/BaseModalIncreaseAmount.vue b/apps/app/src/components/modal/BaseModalIncreaseAmount.vue
similarity index 95%
rename from apps/campaign/src/components/modal/BaseModalIncreaseAmount.vue
rename to apps/app/src/components/modal/BaseModalIncreaseAmount.vue
index 226d8dcbf..91c57019d 100644
--- a/apps/campaign/src/components/modal/BaseModalIncreaseAmount.vue
+++ b/apps/app/src/components/modal/BaseModalIncreaseAmount.vue
@@ -35,11 +35,11 @@ import { defineComponent } from 'vue';
 import { mapStores } from 'pinia';
 import { useVeStore } from '../../stores/VE';
 import { useWalletStore } from '../../stores/Wallet';
-import { useLiquidityStore } from '@thxnetwork/campaign/stores/Liquidity';
+import { useLiquidityStore } from '@thxnetwork/app/stores/Liquidity';
 import { contractNetworks } from '../../config/constants';
 import { formatUnits, parseUnits } from 'ethers/lib/utils';
-import { ChainId } from '@thxnetwork/sdk';
-import { WalletVariant } from '@thxnetwork/campaign/types/enums/accountVariant';
+import { ChainId } from '@thxnetwork/common/enums';
+import { WalletVariant } from '@thxnetwork/app/types/enums/accountVariant';
 import { BigNumber } from 'ethers/lib/ethers';
 
 export default defineComponent({
diff --git a/apps/campaign/src/components/modal/BaseModalIncreaseLockEnd.vue b/apps/app/src/components/modal/BaseModalIncreaseLockEnd.vue
similarity index 94%
rename from apps/campaign/src/components/modal/BaseModalIncreaseLockEnd.vue
rename to apps/app/src/components/modal/BaseModalIncreaseLockEnd.vue
index 5e3ade64f..b1e422e2c 100644
--- a/apps/campaign/src/components/modal/BaseModalIncreaseLockEnd.vue
+++ b/apps/app/src/components/modal/BaseModalIncreaseLockEnd.vue
@@ -26,8 +26,8 @@ import { defineComponent } from 'vue';
 import { mapStores } from 'pinia';
 import { useVeStore } from '../../stores/VE';
 import { useWalletStore } from '../../stores/Wallet';
-import { useLiquidityStore } from '@thxnetwork/campaign/stores/Liquidity';
-import { WalletVariant } from '@thxnetwork/campaign/types/enums/accountVariant';
+import { useLiquidityStore } from '@thxnetwork/app/stores/Liquidity';
+import { WalletVariant } from '@thxnetwork/app/types/enums/accountVariant';
 
 export default defineComponent({
     name: 'BaseModalIncreaseLockEnd',
diff --git a/apps/campaign/src/components/modal/BaseModalLogin.vue b/apps/app/src/components/modal/BaseModalLogin.vue
similarity index 100%
rename from apps/campaign/src/components/modal/BaseModalLogin.vue
rename to apps/app/src/components/modal/BaseModalLogin.vue
diff --git a/apps/campaign/src/components/modal/BaseModalMembershipCreate.vue b/apps/app/src/components/modal/BaseModalMembershipCreate.vue
similarity index 98%
rename from apps/campaign/src/components/modal/BaseModalMembershipCreate.vue
rename to apps/app/src/components/modal/BaseModalMembershipCreate.vue
index 0c1dddd05..8db612185 100644
--- a/apps/campaign/src/components/modal/BaseModalMembershipCreate.vue
+++ b/apps/app/src/components/modal/BaseModalMembershipCreate.vue
@@ -135,13 +135,13 @@ import { useVeStore } from '../../stores/VE';
 import { useWalletStore } from '../../stores/Wallet';
 import { useLiquidityStore } from '../../stores/Liquidity';
 import { contractNetworks } from '../../config/constants';
-import { ChainId } from '@thxnetwork/sdk';
+import { ChainId } from '@thxnetwork/common/enums';
 import { formatUnits, parseUnits } from 'ethers/lib/utils';
-import { chainList } from '@thxnetwork/campaign/utils/chains';
+import { chainList } from '@thxnetwork/app/utils/chains';
 import { BigNumber } from 'ethers/lib/ethers';
 import { format } from 'date-fns';
-import { roundDownFixed, toFiatPrice } from '@thxnetwork/campaign/utils/price';
-import { parseError } from '@thxnetwork/campaign/utils/toast';
+import { roundDownFixed, toFiatPrice } from '@thxnetwork/app/utils/price';
+import { parseError } from '@thxnetwork/app/utils/toast';
 
 export default defineComponent({
     name: 'BaseModalDeposit',
diff --git a/apps/campaign/src/components/modal/BaseModalQuestEntry.vue b/apps/app/src/components/modal/BaseModalQuestEntry.vue
similarity index 100%
rename from apps/campaign/src/components/modal/BaseModalQuestEntry.vue
rename to apps/app/src/components/modal/BaseModalQuestEntry.vue
diff --git a/apps/campaign/src/components/modal/BaseModalRewardPayment.vue b/apps/app/src/components/modal/BaseModalRewardPayment.vue
similarity index 98%
rename from apps/campaign/src/components/modal/BaseModalRewardPayment.vue
rename to apps/app/src/components/modal/BaseModalRewardPayment.vue
index 18fae2c2f..64b10e8cd 100644
--- a/apps/campaign/src/components/modal/BaseModalRewardPayment.vue
+++ b/apps/app/src/components/modal/BaseModalRewardPayment.vue
@@ -63,7 +63,7 @@ import { mapStores } from 'pinia';
 import { useRewardStore } from '../../stores/Reward';
 import { useAccountStore } from '../../stores/Account';
 import { useWalletStore } from '../../stores/Wallet';
-import { RewardVariant } from '@thxnetwork/campaign/types/enums/rewards';
+import { RewardVariant } from '@thxnetwork/app/types/enums/rewards';
 
 export default defineComponent({
     name: 'BaseModalRewardPayment',
diff --git a/apps/campaign/src/components/modal/BaseModalStake.vue b/apps/app/src/components/modal/BaseModalStake.vue
similarity index 97%
rename from apps/campaign/src/components/modal/BaseModalStake.vue
rename to apps/app/src/components/modal/BaseModalStake.vue
index c9cf23153..4c719a747 100644
--- a/apps/campaign/src/components/modal/BaseModalStake.vue
+++ b/apps/app/src/components/modal/BaseModalStake.vue
@@ -45,8 +45,8 @@ import { useWalletStore } from '../../stores/Wallet';
 import { useLiquidityStore } from '../../stores/Liquidity';
 import { contractNetworks } from '../../config/constants';
 import { formatUnits, parseUnits } from 'ethers/lib/utils';
-import { ChainId } from '@thxnetwork/sdk';
-import { WalletVariant } from '@thxnetwork/campaign/types/enums/accountVariant';
+import { ChainId } from '@thxnetwork/common/enums';
+import { WalletVariant } from '@thxnetwork/app/types/enums/accountVariant';
 
 export default defineComponent({
     name: 'BaseModalStake',
diff --git a/apps/campaign/src/components/modal/BaseModalTokenSelect.vue b/apps/app/src/components/modal/BaseModalTokenSelect.vue
similarity index 89%
rename from apps/campaign/src/components/modal/BaseModalTokenSelect.vue
rename to apps/app/src/components/modal/BaseModalTokenSelect.vue
index b0a8b8f9c..e9177a0ad 100644
--- a/apps/campaign/src/components/modal/BaseModalTokenSelect.vue
+++ b/apps/app/src/components/modal/BaseModalTokenSelect.vue
@@ -40,11 +40,11 @@ import { defineComponent, PropType } from 'vue';
 import { mapStores } from 'pinia';
 import { useVeStore } from '../../stores/VE';
 import { useWalletStore } from '../../stores/Wallet';
-import { toFiatPrice } from '@thxnetwork/campaign/utils/price';
-import { useLiquidityStore } from '@thxnetwork/campaign/stores/Liquidity';
-import { contractNetworks } from '@thxnetwork/campaign/config/constants';
-import { ChainId } from '@thxnetwork/sdk';
-import { chainList } from '@thxnetwork/campaign/utils/chains';
+import { toFiatPrice } from '@thxnetwork/app/utils/price';
+import { useLiquidityStore } from '@thxnetwork/app/stores/Liquidity';
+import { contractNetworks } from '@thxnetwork/app/config/constants';
+import { ChainId } from '@thxnetwork/common/enums';
+import { chainList } from '@thxnetwork/app/utils/chains';
 import { formatUnits } from 'ethers/lib/utils';
 
 export default defineComponent({
diff --git a/apps/campaign/src/components/modal/BaseModalWallet.vue b/apps/app/src/components/modal/BaseModalWallet.vue
similarity index 100%
rename from apps/campaign/src/components/modal/BaseModalWallet.vue
rename to apps/app/src/components/modal/BaseModalWallet.vue
diff --git a/apps/campaign/src/components/modal/BaseModalWalletConnect.vue b/apps/app/src/components/modal/BaseModalWalletConnect.vue
similarity index 95%
rename from apps/campaign/src/components/modal/BaseModalWalletConnect.vue
rename to apps/app/src/components/modal/BaseModalWalletConnect.vue
index 7ad6c3abb..7986f0741 100644
--- a/apps/campaign/src/components/modal/BaseModalWalletConnect.vue
+++ b/apps/app/src/components/modal/BaseModalWalletConnect.vue
@@ -46,9 +46,9 @@ import { chainList } from '../../utils/chains';
 import { useWalletStore } from '../../stores/Wallet';
 import { mapStores } from 'pinia';
 import { defineComponent } from 'vue';
-import { useAccountStore } from '@thxnetwork/campaign/stores/Account';
-import { ChainId } from '@thxnetwork/sdk';
-import { shortenAddress } from '@thxnetwork/campaign/utils/address';
+import { useAccountStore } from '@thxnetwork/app/stores/Account';
+import { ChainId } from '@thxnetwork/common/enums';
+import { shortenAddress } from '@thxnetwork/app/utils/address';
 
 export default defineComponent({
     name: 'BaseModalWalletConnect',
diff --git a/apps/campaign/src/components/modal/BaseModalWalletCreate.vue b/apps/app/src/components/modal/BaseModalWalletCreate.vue
similarity index 100%
rename from apps/campaign/src/components/modal/BaseModalWalletCreate.vue
rename to apps/app/src/components/modal/BaseModalWalletCreate.vue
diff --git a/apps/campaign/src/components/modal/BaseModalWalletRecovery.vue b/apps/app/src/components/modal/BaseModalWalletRecovery.vue
similarity index 100%
rename from apps/campaign/src/components/modal/BaseModalWalletRecovery.vue
rename to apps/app/src/components/modal/BaseModalWalletRecovery.vue
diff --git a/apps/campaign/src/components/modal/BaseModalWalletSafe.vue b/apps/app/src/components/modal/BaseModalWalletSafe.vue
similarity index 100%
rename from apps/campaign/src/components/modal/BaseModalWalletSafe.vue
rename to apps/app/src/components/modal/BaseModalWalletSafe.vue
diff --git a/apps/campaign/src/components/modal/BaseModalWithdraw.vue b/apps/app/src/components/modal/BaseModalWithdraw.vue
similarity index 94%
rename from apps/campaign/src/components/modal/BaseModalWithdraw.vue
rename to apps/app/src/components/modal/BaseModalWithdraw.vue
index 21c20a63a..e52f7b79e 100644
--- a/apps/campaign/src/components/modal/BaseModalWithdraw.vue
+++ b/apps/app/src/components/modal/BaseModalWithdraw.vue
@@ -41,10 +41,10 @@ import { mapStores } from 'pinia';
 import { useVeStore } from '../../stores/VE';
 import { useWalletStore } from '../../stores/Wallet';
 import { MAX_LOCK_TIME, contractNetworks } from '../../config/constants';
-import { useLiquidityStore } from '@thxnetwork/campaign/stores/Liquidity';
-import { calculatePenalty, toFiatPrice } from '@thxnetwork/campaign/utils/price';
+import { useLiquidityStore } from '@thxnetwork/app/stores/Liquidity';
+import { calculatePenalty, toFiatPrice } from '@thxnetwork/app/utils/price';
 import { formatUnits } from 'ethers/lib/utils';
-import { WalletVariant } from '@thxnetwork/campaign/types/enums/accountVariant';
+import { WalletVariant } from '@thxnetwork/app/types/enums/accountVariant';
 
 export default defineComponent({
     name: 'BaseModalWithdraw',
diff --git a/apps/campaign/src/components/navbar/BaseNavbar.vue b/apps/app/src/components/navbar/BaseNavbar.vue
similarity index 100%
rename from apps/campaign/src/components/navbar/BaseNavbar.vue
rename to apps/app/src/components/navbar/BaseNavbar.vue
diff --git a/apps/campaign/src/components/navbar/BaseNavbarPrimary.vue b/apps/app/src/components/navbar/BaseNavbarPrimary.vue
similarity index 100%
rename from apps/campaign/src/components/navbar/BaseNavbarPrimary.vue
rename to apps/app/src/components/navbar/BaseNavbarPrimary.vue
diff --git a/apps/campaign/src/components/navbar/BaseNavbarSecondary.vue b/apps/app/src/components/navbar/BaseNavbarSecondary.vue
similarity index 100%
rename from apps/campaign/src/components/navbar/BaseNavbarSecondary.vue
rename to apps/app/src/components/navbar/BaseNavbarSecondary.vue
diff --git a/apps/campaign/src/components/navbar/BaseNavbarTicker.vue b/apps/app/src/components/navbar/BaseNavbarTicker.vue
similarity index 100%
rename from apps/campaign/src/components/navbar/BaseNavbarTicker.vue
rename to apps/app/src/components/navbar/BaseNavbarTicker.vue
diff --git a/apps/campaign/src/components/navbar/BaseNavbarTop.vue b/apps/app/src/components/navbar/BaseNavbarTop.vue
similarity index 100%
rename from apps/campaign/src/components/navbar/BaseNavbarTop.vue
rename to apps/app/src/components/navbar/BaseNavbarTop.vue
diff --git a/apps/campaign/src/components/tabs/BaseTabDeposit.vue b/apps/app/src/components/tabs/BaseTabDeposit.vue
similarity index 95%
rename from apps/campaign/src/components/tabs/BaseTabDeposit.vue
rename to apps/app/src/components/tabs/BaseTabDeposit.vue
index 12da1a2f2..56f09000f 100644
--- a/apps/campaign/src/components/tabs/BaseTabDeposit.vue
+++ b/apps/app/src/components/tabs/BaseTabDeposit.vue
@@ -41,11 +41,11 @@ import { useLiquidityStore } from '../../stores/Liquidity';
 import { useVeStore } from '../../stores/VE';
 import { contractNetworks } from '../../config/constants';
 import { NinetyDaysInMs, getThursdaysUntilTimestamp } from '../../utils/date';
-import { ChainId } from '@thxnetwork/sdk';
+import { ChainId } from '@thxnetwork/common/enums';
 import { formatUnits, parseUnits } from 'ethers/lib/utils';
-import { useAuthStore } from '@thxnetwork/campaign/stores/Auth';
+import { useAuthStore } from '@thxnetwork/app/stores/Auth';
 import { BigNumber } from 'ethers/lib/ethers';
-import { parseError } from '@thxnetwork/campaign/utils/toast';
+import { parseError } from '@thxnetwork/app/utils/toast';
 
 export default defineComponent({
     name: 'BaseTabDeposit',
diff --git a/apps/campaign/src/components/tabs/BaseTabLiquidity.vue b/apps/app/src/components/tabs/BaseTabLiquidity.vue
similarity index 98%
rename from apps/campaign/src/components/tabs/BaseTabLiquidity.vue
rename to apps/app/src/components/tabs/BaseTabLiquidity.vue
index a646f2768..1f2ed1686 100644
--- a/apps/campaign/src/components/tabs/BaseTabLiquidity.vue
+++ b/apps/app/src/components/tabs/BaseTabLiquidity.vue
@@ -217,12 +217,12 @@ import { useWalletStore } from '../../stores/Wallet';
 import { useLiquidityStore } from '../../stores/Liquidity';
 import { useVeStore } from '../../stores/VE';
 import { contractNetworks } from '../../config/constants';
-import { ChainId } from '@thxnetwork/sdk';
+import { ChainId } from '@thxnetwork/common/enums';
 import { formatUnits, parseUnits } from 'ethers/lib/utils';
-import { useAuthStore } from '@thxnetwork/campaign/stores/Auth';
-import { chainList } from '@thxnetwork/campaign/utils/chains';
+import { useAuthStore } from '@thxnetwork/app/stores/Auth';
+import { chainList } from '@thxnetwork/app/utils/chains';
 import { BigNumber } from 'ethers/lib/ethers';
-import { parseError } from '@thxnetwork/campaign/utils/toast';
+import { parseError } from '@thxnetwork/app/utils/toast';
 
 export default defineComponent({
     name: 'BaseTabLiquidity',
diff --git a/apps/campaign/src/components/tabs/BaseTabWalletVariant.vue b/apps/app/src/components/tabs/BaseTabWalletVariant.vue
similarity index 100%
rename from apps/campaign/src/components/tabs/BaseTabWalletVariant.vue
rename to apps/app/src/components/tabs/BaseTabWalletVariant.vue
diff --git a/apps/campaign/src/components/tabs/BaseTabWalletWalletConnect.vue b/apps/app/src/components/tabs/BaseTabWalletWalletConnect.vue
similarity index 98%
rename from apps/campaign/src/components/tabs/BaseTabWalletWalletConnect.vue
rename to apps/app/src/components/tabs/BaseTabWalletWalletConnect.vue
index e7605fba3..c4aa1f357 100644
--- a/apps/campaign/src/components/tabs/BaseTabWalletWalletConnect.vue
+++ b/apps/app/src/components/tabs/BaseTabWalletWalletConnect.vue
@@ -24,7 +24,7 @@ import { useWalletStore, walletLogoMap } from '../../stores/Wallet';
 import { useAccountStore } from '../../stores/Account';
 import { useAuthStore } from '../../stores/Auth';
 import { WalletVariant } from '../../types/enums/accountVariant';
-import { shortenAddress } from '@thxnetwork/campaign/utils/address';
+import { shortenAddress } from '@thxnetwork/app/utils/address';
 import poll from 'promise-poller';
 
 export default defineComponent({
diff --git a/apps/campaign/src/components/tabs/BaseTabWalletWeb3Auth.vue b/apps/app/src/components/tabs/BaseTabWalletWeb3Auth.vue
similarity index 100%
rename from apps/campaign/src/components/tabs/BaseTabWalletWeb3Auth.vue
rename to apps/app/src/components/tabs/BaseTabWalletWeb3Auth.vue
diff --git a/apps/campaign/src/components/tabs/BaseTabWithdraw.vue b/apps/app/src/components/tabs/BaseTabWithdraw.vue
similarity index 98%
rename from apps/campaign/src/components/tabs/BaseTabWithdraw.vue
rename to apps/app/src/components/tabs/BaseTabWithdraw.vue
index 465a7e0f4..25544c89f 100644
--- a/apps/campaign/src/components/tabs/BaseTabWithdraw.vue
+++ b/apps/app/src/components/tabs/BaseTabWithdraw.vue
@@ -61,7 +61,7 @@ import { useVeStore } from '../../stores/VE';
 import { format, differenceInDays } from 'date-fns';
 import { contractNetworks } from '../../config/constants';
 import { fromWei } from 'web3-utils';
-import { ChainId } from '@thxnetwork/sdk';
+import { ChainId } from '@thxnetwork/common/enums';
 
 export default defineComponent({
     name: 'BaseTabWithdraw',
diff --git a/apps/campaign/src/config/constants.ts b/apps/app/src/config/constants.ts
similarity index 100%
rename from apps/campaign/src/config/constants.ts
rename to apps/app/src/config/constants.ts
diff --git a/apps/campaign/src/config/secrets.ts b/apps/app/src/config/secrets.ts
similarity index 100%
rename from apps/campaign/src/config/secrets.ts
rename to apps/app/src/config/secrets.ts
diff --git a/apps/campaign/src/environments/environment.prod.ts b/apps/app/src/environments/environment.prod.ts
similarity index 100%
rename from apps/campaign/src/environments/environment.prod.ts
rename to apps/app/src/environments/environment.prod.ts
diff --git a/apps/campaign/src/environments/environment.ts b/apps/app/src/environments/environment.ts
similarity index 100%
rename from apps/campaign/src/environments/environment.ts
rename to apps/app/src/environments/environment.ts
diff --git a/apps/campaign/src/main.ts b/apps/app/src/main.ts
similarity index 92%
rename from apps/campaign/src/main.ts
rename to apps/app/src/main.ts
index e99b9c5d2..d2e3b05b6 100644
--- a/apps/campaign/src/main.ts
+++ b/apps/app/src/main.ts
@@ -1,13 +1,13 @@
 import { BootstrapVueNext, vBTooltip } from 'bootstrap-vue-next';
 import { createApp } from 'vue';
 import { createPinia } from 'pinia';
-import { Sentry } from '@thxnetwork/common/lib/sentry';
+import { Sentry } from '@thxnetwork/common/sentry';
 import { GCLOUD_RECAPTCHA_SITE_KEY, MODE, API_URL, MIXPANEL_TOKEN, AUTH_URL, WIDGET_URL } from './config/secrets';
 import App from './App.vue';
 import VueClipboard from 'vue3-clipboard';
 import Vue3Toastify from 'vue3-toastify';
 import router from './router';
-import Mixpanel from '@thxnetwork/common/lib/mixpanel';
+import Mixpanel from '@thxnetwork/common/mixpanel';
 
 import './scss/main.scss';
 
diff --git a/apps/campaign/src/polyfills.ts b/apps/app/src/polyfills.ts
similarity index 100%
rename from apps/campaign/src/polyfills.ts
rename to apps/app/src/polyfills.ts
diff --git a/apps/campaign/src/router/index.ts b/apps/app/src/router/index.ts
similarity index 100%
rename from apps/campaign/src/router/index.ts
rename to apps/app/src/router/index.ts
diff --git a/apps/campaign/src/scss/_badge.scss b/apps/app/src/scss/_badge.scss
similarity index 100%
rename from apps/campaign/src/scss/_badge.scss
rename to apps/app/src/scss/_badge.scss
diff --git a/apps/campaign/src/scss/_blockquote.scss b/apps/app/src/scss/_blockquote.scss
similarity index 100%
rename from apps/campaign/src/scss/_blockquote.scss
rename to apps/app/src/scss/_blockquote.scss
diff --git a/apps/campaign/src/scss/_card.scss b/apps/app/src/scss/_card.scss
similarity index 100%
rename from apps/campaign/src/scss/_card.scss
rename to apps/app/src/scss/_card.scss
diff --git a/apps/campaign/src/scss/_forms.scss b/apps/app/src/scss/_forms.scss
similarity index 100%
rename from apps/campaign/src/scss/_forms.scss
rename to apps/app/src/scss/_forms.scss
diff --git a/apps/campaign/src/scss/_gradients.scss b/apps/app/src/scss/_gradients.scss
similarity index 100%
rename from apps/campaign/src/scss/_gradients.scss
rename to apps/app/src/scss/_gradients.scss
diff --git a/apps/campaign/src/scss/_modals.scss b/apps/app/src/scss/_modals.scss
similarity index 100%
rename from apps/campaign/src/scss/_modals.scss
rename to apps/app/src/scss/_modals.scss
diff --git a/apps/campaign/src/scss/_navbar.scss b/apps/app/src/scss/_navbar.scss
similarity index 100%
rename from apps/campaign/src/scss/_navbar.scss
rename to apps/app/src/scss/_navbar.scss
diff --git a/apps/campaign/src/scss/_reset.scss b/apps/app/src/scss/_reset.scss
similarity index 100%
rename from apps/campaign/src/scss/_reset.scss
rename to apps/app/src/scss/_reset.scss
diff --git a/apps/campaign/src/scss/_tables.scss b/apps/app/src/scss/_tables.scss
similarity index 100%
rename from apps/campaign/src/scss/_tables.scss
rename to apps/app/src/scss/_tables.scss
diff --git a/apps/campaign/src/scss/_tabs.scss b/apps/app/src/scss/_tabs.scss
similarity index 100%
rename from apps/campaign/src/scss/_tabs.scss
rename to apps/app/src/scss/_tabs.scss
diff --git a/apps/campaign/src/scss/_theme.scss b/apps/app/src/scss/_theme.scss
similarity index 100%
rename from apps/campaign/src/scss/_theme.scss
rename to apps/app/src/scss/_theme.scss
diff --git a/apps/campaign/src/scss/_toasts.scss b/apps/app/src/scss/_toasts.scss
similarity index 100%
rename from apps/campaign/src/scss/_toasts.scss
rename to apps/app/src/scss/_toasts.scss
diff --git a/apps/campaign/src/scss/_variables.scss b/apps/app/src/scss/_variables.scss
similarity index 100%
rename from apps/campaign/src/scss/_variables.scss
rename to apps/app/src/scss/_variables.scss
diff --git a/apps/campaign/src/scss/main.scss b/apps/app/src/scss/main.scss
similarity index 100%
rename from apps/campaign/src/scss/main.scss
rename to apps/app/src/scss/main.scss
diff --git a/apps/campaign/src/shims-vue.d.ts b/apps/app/src/shims-vue.d.ts
similarity index 100%
rename from apps/campaign/src/shims-vue.d.ts
rename to apps/app/src/shims-vue.d.ts
diff --git a/apps/campaign/src/stores/Account.ts b/apps/app/src/stores/Account.ts
similarity index 99%
rename from apps/campaign/src/stores/Account.ts
rename to apps/app/src/stores/Account.ts
index a0d9eab20..743fe426c 100644
--- a/apps/campaign/src/stores/Account.ts
+++ b/apps/app/src/stores/Account.ts
@@ -1,6 +1,6 @@
 import { defineStore } from 'pinia';
-import { track } from '@thxnetwork/common/lib/mixpanel';
-import { THXBrowserClient } from '@thxnetwork/sdk';
+import { track } from '@thxnetwork/common/mixpanel';
+import { THXBrowserClient } from '@thxnetwork/sdk/clients';
 import { API_URL, AUTH_URL, CLIENT_ID, WIDGET_URL } from '../config/secrets';
 import { DEFAULT_COLORS, DEFAULT_ELEMENTS, getStyles } from '../utils/theme';
 import { BREAKPOINT_LG } from '../config/constants';
diff --git a/apps/campaign/src/stores/Auth.ts b/apps/app/src/stores/Auth.ts
similarity index 99%
rename from apps/campaign/src/stores/Auth.ts
rename to apps/app/src/stores/Auth.ts
index daae0bca0..cadae11a5 100644
--- a/apps/campaign/src/stores/Auth.ts
+++ b/apps/app/src/stores/Auth.ts
@@ -5,7 +5,7 @@ import { tKey } from '../utils/tkey';
 import { useAccountStore } from './Account';
 import { User, UserManager, WebStorageStateStore } from 'oidc-client-ts';
 import { Wallet } from '@ethersproject/wallet';
-import { track } from '@thxnetwork/common/lib/mixpanel';
+import { track } from '@thxnetwork/common/mixpanel';
 import poll from 'promise-poller';
 import { useVeStore } from './VE';
 import { useWalletStore } from './Wallet';
diff --git a/apps/campaign/src/stores/Liquidity.ts b/apps/app/src/stores/Liquidity.ts
similarity index 99%
rename from apps/campaign/src/stores/Liquidity.ts
rename to apps/app/src/stores/Liquidity.ts
index 7450e1ab5..dcbf4b933 100644
--- a/apps/campaign/src/stores/Liquidity.ts
+++ b/apps/app/src/stores/Liquidity.ts
@@ -4,9 +4,9 @@ import { useAccountStore } from './Account';
 import { useWalletStore } from './Wallet';
 import { BALANCER_POOL_ID, contractNetworks } from '../config/constants';
 import { WalletVariant } from '../types/enums/accountVariant';
-import { BigNumber } from 'ethers';
+import { BigNumber } from 'alchemy-sdk';
 import { PoolWithMethods } from '@balancer-labs/sdk';
-import { ChainId } from '@thxnetwork/sdk';
+import { ChainId } from '@thxnetwork/common/enums';
 
 type TCreateLiquidityOptions = {
     thxAmountInWei: string;
diff --git a/apps/campaign/src/stores/QRCode.ts b/apps/app/src/stores/QRCode.ts
similarity index 95%
rename from apps/campaign/src/stores/QRCode.ts
rename to apps/app/src/stores/QRCode.ts
index a3ab6a086..121ab2eca 100644
--- a/apps/campaign/src/stores/QRCode.ts
+++ b/apps/app/src/stores/QRCode.ts
@@ -1,6 +1,6 @@
 import { defineStore } from 'pinia';
 import { useAccountStore } from './Account';
-import { track } from '@thxnetwork/common/lib/mixpanel';
+import { track } from '@thxnetwork/common/mixpanel';
 
 export const useQRCodeStore = defineStore('qrcode', {
     state: (): TQRCodeState => ({
diff --git a/apps/campaign/src/stores/Quest.ts b/apps/app/src/stores/Quest.ts
similarity index 97%
rename from apps/campaign/src/stores/Quest.ts
rename to apps/app/src/stores/Quest.ts
index dd73fcf9a..068c4a802 100644
--- a/apps/campaign/src/stores/Quest.ts
+++ b/apps/app/src/stores/Quest.ts
@@ -1,7 +1,7 @@
 import { defineStore } from 'pinia';
 import { useAccountStore } from './Account';
-import { track } from '@thxnetwork/common/lib/mixpanel';
-import { QuestVariant } from '@thxnetwork/sdk';
+import { track } from '@thxnetwork/common/mixpanel';
+import { QuestVariant } from '@thxnetwork/common/enums';
 import { GCLOUD_RECAPTCHA_SITE_KEY } from '../config/secrets';
 
 export const useQuestStore = defineStore('quest', {
diff --git a/apps/campaign/src/stores/Reward.ts b/apps/app/src/stores/Reward.ts
similarity index 97%
rename from apps/campaign/src/stores/Reward.ts
rename to apps/app/src/stores/Reward.ts
index 37c12da7d..d18479c1e 100644
--- a/apps/campaign/src/stores/Reward.ts
+++ b/apps/app/src/stores/Reward.ts
@@ -1,5 +1,5 @@
 import { defineStore } from 'pinia';
-import { track } from '@thxnetwork/common/lib/mixpanel';
+import { track } from '@thxnetwork/common/mixpanel';
 import { toNumber } from '../utils/quests';
 import { useAccountStore } from './Account';
 import { RewardVariant } from '../types/enums/rewards';
diff --git a/apps/campaign/src/stores/Snapshot.ts b/apps/app/src/stores/Snapshot.ts
similarity index 100%
rename from apps/campaign/src/stores/Snapshot.ts
rename to apps/app/src/stores/Snapshot.ts
diff --git a/apps/campaign/src/stores/VE.ts b/apps/app/src/stores/VE.ts
similarity index 99%
rename from apps/campaign/src/stores/VE.ts
rename to apps/app/src/stores/VE.ts
index d1ac069d4..beed6dbd5 100644
--- a/apps/campaign/src/stores/VE.ts
+++ b/apps/app/src/stores/VE.ts
@@ -1,11 +1,11 @@
 import { defineStore } from 'pinia';
 import { useAccountStore } from './Account';
 import { useWalletStore } from './Wallet';
-import { ChainId } from '@thxnetwork/sdk';
+import { ChainId } from '@thxnetwork/common/enums';
 import { MODE } from '../config/secrets';
 import { WalletVariant } from '../types/enums/accountVariant';
 import { contractNetworks } from '../config/constants';
-import { BigNumber } from 'ethers';
+import { BigNumber } from 'alchemy-sdk';
 import poll, { CANCEL_TOKEN } from 'promise-poller';
 
 export function getChainId() {
diff --git a/apps/campaign/src/stores/Wallet.ts b/apps/app/src/stores/Wallet.ts
similarity index 99%
rename from apps/campaign/src/stores/Wallet.ts
rename to apps/app/src/stores/Wallet.ts
index 59d3b4bf1..18f4f86a7 100644
--- a/apps/campaign/src/stores/Wallet.ts
+++ b/apps/app/src/stores/Wallet.ts
@@ -1,11 +1,11 @@
 import { defineStore } from 'pinia';
 import { useAccountStore } from './Account';
-import { track } from '@thxnetwork/common/lib/mixpanel';
+import { track } from '@thxnetwork/common/mixpanel';
 import { HARDHAT_RPC, POLYGON_RPC } from '../config/secrets';
 import { useAuthStore } from './Auth';
 import { EthersAdapter, SafeConfig } from '@safe-global/protocol-kit';
 import { ethers } from 'ethers';
-import { ChainId } from '@thxnetwork/sdk/src/lib/types/enums/ChainId';
+import { ChainId } from '@thxnetwork/common/enums';
 import { WalletVariant } from '../types/enums/accountVariant';
 import { AUTH_URL, WALLET_CONNECT_PROJECT_ID, WIDGET_URL } from '../config/secrets';
 import { createWeb3Modal, defaultWagmiConfig } from '@web3modal/wagmi';
diff --git a/apps/campaign/src/types/enums/accessTokenKind.ts b/apps/app/src/types/enums/accessTokenKind.ts
similarity index 100%
rename from apps/campaign/src/types/enums/accessTokenKind.ts
rename to apps/app/src/types/enums/accessTokenKind.ts
diff --git a/apps/campaign/src/types/enums/accountVariant.ts b/apps/app/src/types/enums/accountVariant.ts
similarity index 100%
rename from apps/campaign/src/types/enums/accountVariant.ts
rename to apps/app/src/types/enums/accountVariant.ts
diff --git a/apps/campaign/src/types/enums/nft.ts b/apps/app/src/types/enums/nft.ts
similarity index 100%
rename from apps/campaign/src/types/enums/nft.ts
rename to apps/app/src/types/enums/nft.ts
diff --git a/apps/campaign/src/types/enums/rewards.ts b/apps/app/src/types/enums/rewards.ts
similarity index 100%
rename from apps/campaign/src/types/enums/rewards.ts
rename to apps/app/src/types/enums/rewards.ts
diff --git a/apps/campaign/src/types/interfaces/account.d.ts b/apps/app/src/types/interfaces/account.d.ts
similarity index 100%
rename from apps/campaign/src/types/interfaces/account.d.ts
rename to apps/app/src/types/interfaces/account.d.ts
diff --git a/apps/campaign/src/types/interfaces/chains.d.ts b/apps/app/src/types/interfaces/chains.d.ts
similarity index 100%
rename from apps/campaign/src/types/interfaces/chains.d.ts
rename to apps/app/src/types/interfaces/chains.d.ts
diff --git a/apps/campaign/src/types/interfaces/qrcodes.d.ts b/apps/app/src/types/interfaces/qrcodes.d.ts
similarity index 100%
rename from apps/campaign/src/types/interfaces/qrcodes.d.ts
rename to apps/app/src/types/interfaces/qrcodes.d.ts
diff --git a/apps/campaign/src/types/interfaces/quests.d.ts b/apps/app/src/types/interfaces/quests.d.ts
similarity index 100%
rename from apps/campaign/src/types/interfaces/quests.d.ts
rename to apps/app/src/types/interfaces/quests.d.ts
diff --git a/apps/campaign/src/types/interfaces/rewards.d.ts b/apps/app/src/types/interfaces/rewards.d.ts
similarity index 100%
rename from apps/campaign/src/types/interfaces/rewards.d.ts
rename to apps/app/src/types/interfaces/rewards.d.ts
diff --git a/apps/campaign/src/types/interfaces/wallet.d.ts b/apps/app/src/types/interfaces/wallet.d.ts
similarity index 100%
rename from apps/campaign/src/types/interfaces/wallet.d.ts
rename to apps/app/src/types/interfaces/wallet.d.ts
diff --git a/apps/campaign/src/types/snapshot.d.ts b/apps/app/src/types/snapshot.d.ts
similarity index 100%
rename from apps/campaign/src/types/snapshot.d.ts
rename to apps/app/src/types/snapshot.d.ts
diff --git a/apps/campaign/src/utils/address.ts b/apps/app/src/utils/address.ts
similarity index 100%
rename from apps/campaign/src/utils/address.ts
rename to apps/app/src/utils/address.ts
diff --git a/apps/campaign/src/utils/chains.ts b/apps/app/src/utils/chains.ts
similarity index 97%
rename from apps/campaign/src/utils/chains.ts
rename to apps/app/src/utils/chains.ts
index 2a3ebb9cf..d303a6b58 100644
--- a/apps/campaign/src/utils/chains.ts
+++ b/apps/app/src/utils/chains.ts
@@ -5,7 +5,7 @@ import ImgLogoPolygon from '../assets/thx_logo_polygon.svg';
 import ImgLogoHardhat from '../assets/thx_logo_hardhat.svg';
 import ImgLogoLinea from '../assets/thx_logo_linea.svg';
 import { arbitrum, mainnet, bsc, polygon, hardhat, polygonZkEvm, linea } from '@wagmi/core/chains';
-import { ChainId } from '@thxnetwork/sdk';
+import { ChainId } from '@thxnetwork/common/enums';
 
 const chainList: { [chainId: number]: ChainInfo } = {
     [ChainId.Ethereum]: {
diff --git a/apps/campaign/src/utils/date.ts b/apps/app/src/utils/date.ts
similarity index 100%
rename from apps/campaign/src/utils/date.ts
rename to apps/app/src/utils/date.ts
diff --git a/apps/campaign/src/utils/decode-html.ts b/apps/app/src/utils/decode-html.ts
similarity index 100%
rename from apps/campaign/src/utils/decode-html.ts
rename to apps/app/src/utils/decode-html.ts
diff --git a/apps/campaign/src/utils/ga.ts b/apps/app/src/utils/ga.ts
similarity index 100%
rename from apps/campaign/src/utils/ga.ts
rename to apps/app/src/utils/ga.ts
diff --git a/apps/campaign/src/utils/popup.ts b/apps/app/src/utils/popup.ts
similarity index 100%
rename from apps/campaign/src/utils/popup.ts
rename to apps/app/src/utils/popup.ts
diff --git a/apps/campaign/src/utils/price.ts b/apps/app/src/utils/price.ts
similarity index 100%
rename from apps/campaign/src/utils/price.ts
rename to apps/app/src/utils/price.ts
diff --git a/apps/campaign/src/utils/quests.ts b/apps/app/src/utils/quests.ts
similarity index 100%
rename from apps/campaign/src/utils/quests.ts
rename to apps/app/src/utils/quests.ts
diff --git a/apps/campaign/src/utils/rand-hex.ts b/apps/app/src/utils/rand-hex.ts
similarity index 100%
rename from apps/campaign/src/utils/rand-hex.ts
rename to apps/app/src/utils/rand-hex.ts
diff --git a/apps/campaign/src/utils/social.ts b/apps/app/src/utils/social.ts
similarity index 100%
rename from apps/campaign/src/utils/social.ts
rename to apps/app/src/utils/social.ts
diff --git a/apps/campaign/src/utils/sort.ts b/apps/app/src/utils/sort.ts
similarity index 100%
rename from apps/campaign/src/utils/sort.ts
rename to apps/app/src/utils/sort.ts
diff --git a/apps/campaign/src/utils/theme.ts b/apps/app/src/utils/theme.ts
similarity index 100%
rename from apps/campaign/src/utils/theme.ts
rename to apps/app/src/utils/theme.ts
diff --git a/apps/campaign/src/utils/tkey.ts b/apps/app/src/utils/tkey.ts
similarity index 100%
rename from apps/campaign/src/utils/tkey.ts
rename to apps/app/src/utils/tkey.ts
diff --git a/apps/campaign/src/utils/toast.ts b/apps/app/src/utils/toast.ts
similarity index 100%
rename from apps/campaign/src/utils/toast.ts
rename to apps/app/src/utils/toast.ts
diff --git a/apps/campaign/src/views/Campaign.vue b/apps/app/src/views/Campaign.vue
similarity index 98%
rename from apps/campaign/src/views/Campaign.vue
rename to apps/app/src/views/Campaign.vue
index f0bda3073..07f656304 100644
--- a/apps/campaign/src/views/Campaign.vue
+++ b/apps/app/src/views/Campaign.vue
@@ -17,7 +17,7 @@
 </template>
 <script lang="ts">
 import { mapStores } from 'pinia';
-import { track } from '@thxnetwork/common/lib/mixpanel';
+import { track } from '@thxnetwork/common/mixpanel';
 import { defineComponent } from 'vue';
 import { GTM } from '../config/secrets';
 import { useAuthStore } from '../stores/Auth';
diff --git a/apps/campaign/src/views/Discovery.vue b/apps/app/src/views/Discovery.vue
similarity index 79%
rename from apps/campaign/src/views/Discovery.vue
rename to apps/app/src/views/Discovery.vue
index b817a0e8f..52d51b27c 100644
--- a/apps/campaign/src/views/Discovery.vue
+++ b/apps/app/src/views/Discovery.vue
@@ -12,9 +12,9 @@
 <script lang="ts">
 import { mapStores } from 'pinia';
 import { defineComponent } from 'vue';
-import { useLiquidityStore } from '@thxnetwork/campaign/stores/Liquidity';
-import { useWalletStore } from '@thxnetwork/campaign/stores/Wallet';
-import { useVeStore } from '@thxnetwork/campaign/stores/VE';
+import { useLiquidityStore } from '@thxnetwork/app/stores/Liquidity';
+import { useWalletStore } from '@thxnetwork/app/stores/Wallet';
+import { useVeStore } from '@thxnetwork/app/stores/VE';
 
 export default defineComponent({
     name: 'Discovery',
diff --git a/apps/campaign/src/views/Wallets.vue b/apps/app/src/views/Wallets.vue
similarity index 100%
rename from apps/campaign/src/views/Wallets.vue
rename to apps/app/src/views/Wallets.vue
diff --git a/apps/campaign/src/views/campaign/About.vue b/apps/app/src/views/campaign/About.vue
similarity index 100%
rename from apps/campaign/src/views/campaign/About.vue
rename to apps/app/src/views/campaign/About.vue
diff --git a/apps/campaign/src/views/campaign/Collect.vue b/apps/app/src/views/campaign/Collect.vue
similarity index 100%
rename from apps/campaign/src/views/campaign/Collect.vue
rename to apps/app/src/views/campaign/Collect.vue
diff --git a/apps/campaign/src/views/campaign/Identities.vue b/apps/app/src/views/campaign/Identities.vue
similarity index 100%
rename from apps/campaign/src/views/campaign/Identities.vue
rename to apps/app/src/views/campaign/Identities.vue
diff --git a/apps/campaign/src/views/campaign/Quests.vue b/apps/app/src/views/campaign/Quests.vue
similarity index 100%
rename from apps/campaign/src/views/campaign/Quests.vue
rename to apps/app/src/views/campaign/Quests.vue
diff --git a/apps/campaign/src/views/campaign/Ranking.vue b/apps/app/src/views/campaign/Ranking.vue
similarity index 100%
rename from apps/campaign/src/views/campaign/Ranking.vue
rename to apps/app/src/views/campaign/Ranking.vue
diff --git a/apps/campaign/src/views/campaign/Rewards.vue b/apps/app/src/views/campaign/Rewards.vue
similarity index 100%
rename from apps/campaign/src/views/campaign/Rewards.vue
rename to apps/app/src/views/campaign/Rewards.vue
diff --git a/apps/campaign/src/views/campaign/Signin.vue b/apps/app/src/views/campaign/Signin.vue
similarity index 100%
rename from apps/campaign/src/views/campaign/Signin.vue
rename to apps/app/src/views/campaign/Signin.vue
diff --git a/apps/campaign/src/views/discovery/Community.vue b/apps/app/src/views/discovery/Community.vue
similarity index 98%
rename from apps/campaign/src/views/discovery/Community.vue
rename to apps/app/src/views/discovery/Community.vue
index c1494a799..93022719e 100644
--- a/apps/campaign/src/views/discovery/Community.vue
+++ b/apps/app/src/views/discovery/Community.vue
@@ -47,7 +47,7 @@ import { mapStores } from 'pinia';
 import { defineComponent } from 'vue';
 import { useSnapshotStore } from '../../stores/Snapshot';
 import { marked } from 'marked';
-import { useVeStore } from '@thxnetwork/campaign/stores/VE';
+import { useVeStore } from '@thxnetwork/app/stores/VE';
 import { formatUnits } from 'ethers/lib/utils';
 import imgJumbotron from '../../assets/thx_token_governance.png';
 
diff --git a/apps/campaign/src/views/discovery/Earn.vue b/apps/app/src/views/discovery/Earn.vue
similarity index 81%
rename from apps/campaign/src/views/discovery/Earn.vue
rename to apps/app/src/views/discovery/Earn.vue
index b9a530598..693d79182 100644
--- a/apps/campaign/src/views/discovery/Earn.vue
+++ b/apps/app/src/views/discovery/Earn.vue
@@ -39,30 +39,7 @@
             </div>
         </template>
         <template #secondary>
-            <b-card class="border-0 gradient-shadow-xl" style="min-height: 415px">
-                <b-tabs v-model="tabIndex" pills justified content-class="mt-3" nav-wrapper-class="text-white">
-                    <b-tab>
-                        <template #title>
-                            <i class="fas fa-balance-scale me-1" />
-                            Liquidity
-                        </template>
-                        <hr />
-                        <BaseTabLiquidity @change-tab="tabIndex = $event" />
-                    </b-tab>
-                    <b-tab>
-                        <template #title>
-                            <i class="fas fa-id-card me-1" />
-                            Membership
-                        </template>
-                        <hr />
-                        <BaseTabDeposit
-                            v-if="veStore.lock && !Number(veStore.lock.amount)"
-                            @change-tab="tabIndex = $event"
-                        />
-                        <BaseTabWithdraw v-else @change-tab="tabIndex = $event" />
-                    </b-tab>
-                </b-tabs>
-            </b-card>
+            <BaseCardMembership :tab-index="0" />
         </template>
     </BaseCardHeader>
     <b-container class="mb-5">
@@ -84,9 +61,9 @@ import { useAccountStore } from '../../stores/Account';
 import { useWalletStore } from '../../stores/Wallet';
 import { useLiquidityStore } from '../../stores/Liquidity';
 import { useVeStore } from '../../stores/VE';
-import { roundUpFixed, toFiatPrice } from '@thxnetwork/campaign/utils/price';
+import { roundUpFixed, toFiatPrice } from '@thxnetwork/app/utils/price';
 import { formatUnits, parseUnits } from 'ethers/lib/utils';
-import { BigNumber } from 'ethers';
+import { BigNumber } from 'alchemy-sdk';
 import { startOfWeek, addWeeks, format, eachWeekOfInterval } from 'date-fns';
 
 export default defineComponent({
diff --git a/apps/campaign/src/views/discovery/Home.vue b/apps/app/src/views/discovery/Home.vue
similarity index 100%
rename from apps/campaign/src/views/discovery/Home.vue
rename to apps/app/src/views/discovery/Home.vue
diff --git a/apps/campaign/src/views/discovery/Learn.vue b/apps/app/src/views/discovery/Learn.vue
similarity index 95%
rename from apps/campaign/src/views/discovery/Learn.vue
rename to apps/app/src/views/discovery/Learn.vue
index ef321da7f..ec89a014e 100644
--- a/apps/campaign/src/views/discovery/Learn.vue
+++ b/apps/app/src/views/discovery/Learn.vue
@@ -50,7 +50,7 @@
 </template>
 
 <script lang="ts">
-import { useQuestStore } from '@thxnetwork/campaign/stores/Quest';
+import { useQuestStore } from '@thxnetwork/app/stores/Quest';
 import { mapStores } from 'pinia';
 import { defineComponent } from 'vue';
 import { questComponentMap } from '../../utils/quests';
@@ -62,8 +62,8 @@ import BaseCardQuestDaily from '../../components/card/BaseCardQuestDaily.vue';
 import BaseCardQuestWeb3 from '../../components/card/BaseCardQuestWeb3.vue';
 import BaseCardQuestGitcoin from '../../components/card/BaseCardQuestGitcoin.vue';
 import BaseCardQuestWebhook from '../../components/card/BaseCardQuestWebhook.vue';
-import { useAccountStore } from '@thxnetwork/campaign/stores/Account';
-import { useAuthStore } from '@thxnetwork/campaign/stores/Auth';
+import { useAccountStore } from '@thxnetwork/app/stores/Account';
+import { useAuthStore } from '@thxnetwork/app/stores/Auth';
 
 export default defineComponent({
     name: 'Learn',
diff --git a/apps/campaign/src/views/discovery/Members.vue b/apps/app/src/views/discovery/Members.vue
similarity index 93%
rename from apps/campaign/src/views/discovery/Members.vue
rename to apps/app/src/views/discovery/Members.vue
index 5704b3514..8424e21be 100644
--- a/apps/campaign/src/views/discovery/Members.vue
+++ b/apps/app/src/views/discovery/Members.vue
@@ -11,10 +11,7 @@
             </p>
         </template>
         <template #secondary>
-            <BaseCardMembershipOnboarding v-if="!Number(formatUnits(veStore.lock.amount, 18))" />
-            <div v-else class="h-100 d-flex align-items-center justify-content-center">
-                <BaseCardMembership />
-            </div>
+            <BaseCardMembership :tab-index="1" />
         </template>
     </BaseCardHeader>
     <b-container class="mt-5">
@@ -70,8 +67,8 @@
 </template>
 
 <script lang="ts">
-import { useLiquidityStore } from '@thxnetwork/campaign/stores/Liquidity';
-import { useVeStore } from '@thxnetwork/campaign/stores/VE';
+import { useLiquidityStore } from '@thxnetwork/app/stores/Liquidity';
+import { useVeStore } from '@thxnetwork/app/stores/VE';
 import { formatUnits } from 'ethers/lib/utils';
 import { mapStores } from 'pinia';
 import { defineComponent } from 'vue';
diff --git a/apps/campaign/src/vite-env.d.ts b/apps/app/src/vite-env.d.ts
similarity index 100%
rename from apps/campaign/src/vite-env.d.ts
rename to apps/app/src/vite-env.d.ts
diff --git a/apps/campaign/tsconfig.json b/apps/app/tsconfig.json
similarity index 80%
rename from apps/campaign/tsconfig.json
rename to apps/app/tsconfig.json
index 152b68801..a29abe707 100644
--- a/apps/campaign/tsconfig.json
+++ b/apps/app/tsconfig.json
@@ -6,7 +6,8 @@
         "components.d.ts",
         "src/types/**/*.d.ts",
         "node_modules/**/*.d.ts",
-        "../../libs/common/**/*"
+        "../../libs/common/**/*",
+        "../../libs/sdk/**/*"
     ],
     "exclude": ["**/*.spec.ts", "**/*.test.ts"]
 }
diff --git a/apps/campaign/vite.config.ts b/apps/app/vite.config.ts
similarity index 89%
rename from apps/campaign/vite.config.ts
rename to apps/app/vite.config.ts
index c0d169f25..544ac22c3 100644
--- a/apps/campaign/vite.config.ts
+++ b/apps/app/vite.config.ts
@@ -33,8 +33,9 @@ const config: UserConfigExport = {
     },
     resolve: {
         alias: [
-            { find: '@thxnetwork/campaign', replacement: path.resolve(__dirname, './src') },
-            { find: '@thxnetwork/common', replacement: path.resolve(__dirname, '../../libs/common/src') },
+            { find: '@thxnetwork/app', replacement: path.resolve(__dirname, './src') },
+            { find: '@thxnetwork/common', replacement: path.resolve(__dirname, '../../libs/common/src/lib') },
+            { find: '@thxnetwork/sdk', replacement: path.resolve(__dirname, '../../libs/sdk/src/lib') },
         ],
     },
     optimizeDeps: {
diff --git a/apps/auth/.env.ci b/apps/auth/.env.ci
new file mode 100644
index 000000000..d50e39363
--- /dev/null
+++ b/apps/auth/.env.ci
@@ -0,0 +1,7 @@
+MONGODB_URI=mongodb://root:root@mongo:27017/auth?authSource=admin&ssl=false
+MONGODB_URI_TEST_OVERRIDE=mongodb://root:root@mongo:27017/auth_test?authSource=admin&ssl=false
+AUTH_URL=http://localhost:3030
+AUTH_URL_TEST_OVERRIDE=http://localhost:3030
+CWD="/usr/src/app/apps/auth/src/"
+LOCAL_CERT=
+LOCAL_CERT_KEY=
\ No newline at end of file
diff --git a/apps/auth/.env.example b/apps/auth/.env.example
new file mode 100644
index 000000000..98601cce1
--- /dev/null
+++ b/apps/auth/.env.example
@@ -0,0 +1,46 @@
+# Docker MongoDB URI
+MONGODB_URI=mongodb://root:root@localhost:27017/auth?authSource=admin&ssl=false
+MONGODB_URI_TEST_OVERRIDE=mongodb://root:root@localhost:27017/auth_test?authSource=admin&ssl=false
+MONGODB_NAME=auth
+MONGODB_USER=root
+MONGODB_PASSWORD=root
+
+# Origins
+API_URL=https://localhost:3000
+AUTH_URL=https://local.auth.thx.network
+AUTH_URL_TEST_OVERRIDE=http://localhost:3030
+PUBLIC_URL=https://localhost:8081
+DASHBOARD_URL=https://localhost:8082
+WALLET_URL=https://localhost:8083
+WIDGETS_URL=https://localhost:8085
+ 
+# AWS
+AWS_ACCESS_KEY_ID="xxxxxxxxxxxxxxxxxxxxxxxx"
+AWS_SECRET_ACCESS_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+
+# OAuth
+SECURE_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+INITIAL_ACCESS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+AUTH_CLIENT_ID=P4bLsMzQIwWlr_DOhH_qE
+AUTH_CLIENT_SECRET=o7RMGeLieULzp0bAiuq6nfukFBCLKxo8N867SzeZNrinshSb15W9NggU93CdtrE_0jTWLUl3U2YkcXYtD3hdcQ
+
+# Others
+PORT=3030
+GTM=xxxxxxxxx
+CWD="/usr/src/app/"
+JWKS_JSON={"keys":[{"e":"AQAB","n":"oGGYqftZswJRU_vfKDi7jFNgH20LOVfOgSYBu39GEvYWeygBTGuHmf2Ict7m70IKiFAY24kOhMnNjFJ6B0dxdBt5Hf3Kw-VoTKp1MDuy6_wao51WbR5G6II7ND3_2jucyNLeHpYwKHmk00yo4XTBlihNB_ogLCsblupHFceULfq5DN1whTGp-ZHTdYZaSZGXBC_v046vzqIqdKF785AzN56fBDlWhJ0CB9PdLSqj5hd91mxIbJqarHHWR844R5gOLE5ZMJMwX7SUgirFXhDo_VPVKJsP06SxGWBHe8HaYYi6DSXA7i59yBWyJy80F4t3OBTX-AWkzSfJ-_O9zPEBXQ","d":"Y3BfsXTwhrb3KfVOxad3UWgYfyOjA-jXVufzxwcAsZz3D2EcfP9m0imKQn7F_K6bzSysXOG7qMVetpQkqQK4615lWB0VbSR96Jr_kepR41MqSSuyfHF7UYn9n0SZr_uqGDH2GkzQiyfw9Dnhs_gqVymhqUKCVCBeONIFRRG7ZE3Gythc9WxSpahmX4hvSBSzU_DkfHyitPpCAqitJBEZCp5Ik7BmCYfXQ_5pozARKh4g1pj3cvyZ1FS7t_6wjyG3yP1DEkFb0Di3tscIYxL2EK3lTo2IDL-J53bT9dL_Vs3FsXi1iU_lzn8P7wmMi0auCoL15FJYfT3kZOXfBo8ZWQ","p":"zK6sr6VUTsqwqD-xULZ2SP6h7JC6yvLsNoRp8-r4sdkyj4CI0KHd9iBDQHgpuEMkwM7YpWD1eHPK3R-fnz8AAo2QsP_uaAEEIt7nzBmzLBWOzXcrFEOrtWQ4ojwF9y3nWfdbWZc7CH3uJ1cF7TnzaqO9zChT9jNFhY0o1ECuKis","q":"yJeAegY_6kjhRPc3XWnnYfn8fUYFZ9P7Dylq-seZGT3q6anP1h_yVlXh5h5rYMe7nmTLeyc9RuhPLJY0hDTYKZd5-wCLsfGrEGEa_FQITQElmUYkBjuqqxjoHlVmgCopGjvg9uUCm_hV2HfS6scLWJbnd9NSSvKza_euMly7Zpc","dp":"hiIiQKkT6t6hjmDPDpnEQmm8K49dGgrACaoU1Sgy-jngDHKrNi4di2HxMJqOnJZDy6bCCv7TXrBjTS2gKXfbxdCH9baCwd2InGF_fh2JcWZfQv7JWGpQaHrZMlgrgKSwbCDR2JBmu1XrcBRzadcEUeokQ1paS4mmEbNEAnSwrik","dq":"YpMBEfYsRqfV_Bw42vEGQgGlcLKOSX3ErKi_58lalSr2XCmU9zbv0jmWL43XWtIMg0QpMrYPyN60ucZ-vFFzwMytpwmXnLSUShJOPp3VDnJ31aDAZ0e_ESHGP9Hb8zPEyx2N6gaUh608Eoqf3bw--SP_T8VLdYVbYr1un1UuyrU","qi":"bm0rMfUEAfDXvsb2hUNesL55cWU5soCCn9A2xYyKyRRVIQO3dIp-iPxI0pv0U0JPl0sqQ8fa8A1xmdOvkL71pQDzBco4mAfA1IE38srnWKhbM9XMsJChwB6jCJAtsqHBwTgbqwZNDouVr9ZE2CbP7DVG66FUkqV8NXNb9zXEUv4","kty":"RSA","kid":"qSjSTujaClXGGkwW4zdC1zpRHxAq099krc8TTFfXYlg","alg":"RS256","use":"sig"}]}
+
+GOOGLE_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxx
+GOOGLE_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+GOOGLE_REDIRECT_URI=https://localhost:3030/oidc/callback/youtube
+
+TWITTER_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxx
+TWITTER_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+TWITTER_REDIRECT_URI=https://localhost:3030/oidc/callback/twitter
+
+GITHUB_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxx
+GITHUB_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+GITHUB_REDIRECT_URI=https://localhost:3030/oidc/callback/github
+
+LOCAL_CERT=../../../certs/localhost.crt
+LOCAL_CERT_KEY=../../../certs/localhost.key
\ No newline at end of file
diff --git a/apps/auth/.eslintrc.json b/apps/auth/.eslintrc.json
new file mode 100644
index 000000000..6156d669d
--- /dev/null
+++ b/apps/auth/.eslintrc.json
@@ -0,0 +1,20 @@
+{
+    "extends": ["../../.eslintrc.json"],
+    "ignorePatterns": ["!**/*"],
+    "overrides": [
+        {
+            "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
+            "rules": {}
+        },
+        {
+            "files": ["*.ts", "*.tsx"],
+            "rules": {
+                "@typescript-eslint/no-explicit-any": "off"
+            }
+        },
+        {
+            "files": ["*.js", "*.jsx"],
+            "rules": {}
+        }
+    ]
+}
diff --git a/apps/auth/Dockerfile b/apps/auth/Dockerfile
new file mode 100644
index 000000000..f25a91b05
--- /dev/null
+++ b/apps/auth/Dockerfile
@@ -0,0 +1,62 @@
+#####################################################################################################
+## Develop stage
+#####################################################################################################
+FROM node:18-slim as develop
+
+WORKDIR /usr/src/app
+
+ENV NODE_OPTIONS="--max_old_space_size=4096"
+
+RUN apt-get update && apt-get install -y g++ make python3-pip build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev
+
+COPY package.json yarn.lock ./
+RUN yarn
+COPY . .
+
+CMD [ "npx", "nx", "serve", "auth" ]
+
+#####################################################################################################
+## Build stage
+#####################################################################################################
+FROM node:18-slim as build
+
+WORKDIR /usr/src/app
+COPY --from=develop ./usr/src/app/ ./
+RUN npx nx build auth --prod
+COPY ./newrelic.js ./yarn.lock ./dist/apps/auth/
+
+
+#####################################################################################################
+## Production stage
+#####################################################################################################
+FROM node:18-slim as production
+
+ENV NODE_ENV=production
+
+WORKDIR /usr/src/app
+COPY --from=build ./usr/src/app/dist/apps/auth/package.json ./usr/src/app/dist/apps/auth/yarn.lock  ./
+
+# Install dependencies and packages
+RUN apt-get update && apt-get install -y \
+    g++ \
+    make \
+    python3-pip \
+    build-essential \ 
+    libcairo2-dev \
+    libpango1.0-dev \
+    libjpeg-dev \
+    libgif-dev \
+    librsvg2-dev \
+    && rm -rf /var/lib/apt/lists/*
+
+# Install your application dependencies (assuming it uses Node.js)
+RUN yarn
+
+# Clean up unnecessary packages and files
+RUN apt-get purge -y --auto-remove build-essential && \
+    apt-get clean && \
+    rm -rf /var/lib/apt/lists/*
+
+COPY --from=build ./usr/src/app/dist/apps/auth ./
+
+CMD [ "main.js" ]
\ No newline at end of file
diff --git a/apps/auth/jest.config.ts b/apps/auth/jest.config.ts
new file mode 100644
index 000000000..9dcc1b44c
--- /dev/null
+++ b/apps/auth/jest.config.ts
@@ -0,0 +1,11 @@
+/* eslint-disable */
+export default {
+    displayName: 'auth',
+    preset: '../../jest.preset.js',
+    testEnvironment: 'node',
+    transform: {
+        '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
+    },
+    moduleFileExtensions: ['ts', 'js', 'html'],
+    coverageDirectory: '../../coverage/apps/auth',
+};
diff --git a/apps/auth/package.json b/apps/auth/package.json
new file mode 100644
index 000000000..20b8ea4fa
--- /dev/null
+++ b/apps/auth/package.json
@@ -0,0 +1,16 @@
+{
+    "name": "@thxnetwork/auth",
+    "contributors": [
+        "Peter Polman <peter@thx.network>",
+        "GarfDev <garfdev.13@gmail.com>",
+        "Valeria Grazzini <vgrazzini@gmail.com>",
+        "Bram Rongen <mail@bramrongen.nl>",
+        "Justina Mary <justinamary27@gmail.com>"
+    ],
+    "license": "AGPL-3.0",
+    "version": "1.41.175",
+    "scripts": {
+        "migrate": "node migrate-mongo.js up",
+        "migrate:down": "node migrate-mongo.js down"
+    }
+}
diff --git a/apps/auth/project.json b/apps/auth/project.json
new file mode 100644
index 000000000..94c6a61fc
--- /dev/null
+++ b/apps/auth/project.json
@@ -0,0 +1,99 @@
+{
+    "name": "auth",
+    "$schema": "../../node_modules/nx/schemas/project-schema.json",
+    "sourceRoot": "apps/auth/src",
+    "projectType": "application",
+    "tags": [],
+    "targets": {
+        "build": {
+            "executor": "@nx/webpack:webpack",
+            "outputs": ["{options.outputPath}"],
+            "defaultConfiguration": "production",
+            "options": {
+                "target": "node",
+                "compiler": "tsc",
+                "outputPath": "dist/apps/auth",
+                "main": "apps/auth/src/main.ts",
+                "tsConfig": "apps/auth/tsconfig.app.json",
+                "assets": ["apps/auth/src/assets", "apps/auth/src/app/migrations"],
+                "webpackConfig": "apps/api/webpack.config.js",
+                "generatePackageJson": true,
+                "additionalEntryPoints": [
+                    {
+                        "entryPath": "apps/auth/scripts/migrate-mongo.ts",
+                        "entryName": "migrate-mongo"
+                    },
+                    {
+                        "entryPath": "apps/auth/scripts/script.ts",
+                        "entryName": "script"
+                    }
+                ]
+            },
+            "configurations": {
+                "development": {},
+                "production": {
+                    "optimization": true,
+                    "extractLicenses": true,
+                    "inspect": false
+                }
+            }
+        },
+        "serve": {
+            "executor": "@nx/js:node",
+            "options": {
+                "buildTarget": "auth:build",
+                "host": "localhost",
+                "port": 3030,
+                "inspect": false,
+                "watch": true
+            },
+            "configurations": {
+                "production": {
+                    "buildTarget": "auth:build:production"
+                }
+            }
+        },
+        "lint": {
+            "executor": "@nx/eslint:lint",
+            "outputs": ["{options.outputFile}"],
+            "options": {
+                "lintFilePatterns": ["apps/auth/**/*.ts"]
+            }
+        },
+        "test": {
+            "executor": "@nx/jest:jest",
+            "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
+            "options": {
+                "jestConfig": "apps/auth/jest.config.ts",
+                "testTimeout": 60000,
+                "passWithNoTests": false,
+                "bail": false,
+                "runInBand": true,
+                "logHeapUsage": true
+            }
+        },
+        "script": {
+            "dependsOn": ["^build"],
+            "executor": "nx:run-commands",
+            "options": {
+                "command": "node script.js",
+                "cwd": "dist/apps/auth"
+            }
+        },
+        "migrate-db": {
+            "dependsOn": ["^build"],
+            "executor": "nx:run-commands",
+            "options": {
+                "command": "node migrate-mongo.js up",
+                "cwd": "dist/apps/auth"
+            }
+        },
+        "migrate-db-create": {
+            "executor": "nx:run-commands",
+            "options": {
+                "command": "migrate-mongo create -f src/app/config/migrate-mongo-create-only.json",
+                "cwd": "apps/auth"
+            }
+        }
+    }
+}
diff --git a/apps/auth/scripts/migrate-mongo.ts b/apps/auth/scripts/migrate-mongo.ts
new file mode 100644
index 000000000..46bdae37b
--- /dev/null
+++ b/apps/auth/scripts/migrate-mongo.ts
@@ -0,0 +1,4 @@
+import { migrateMongoScript } from '@thxnetwork/common/migrate-mongo';
+import migrateMongoConfig from '../src/app/config/migrate-mongo';
+
+migrateMongoScript(migrateMongoConfig);
diff --git a/apps/auth/scripts/script.ts b/apps/auth/scripts/script.ts
new file mode 100644
index 000000000..c5fdb64b2
--- /dev/null
+++ b/apps/auth/scripts/script.ts
@@ -0,0 +1,14 @@
+import db from '@thxnetwork/auth/util/database';
+
+db.connect(process.env.MONGODB_URI_PROD);
+
+async function main() {
+    //
+}
+
+main()
+    .then(() => process.exit(0))
+    .catch((error) => {
+        console.error(error);
+        process.exit(1);
+    });
diff --git a/apps/auth/src/app/app.ts b/apps/auth/src/app/app.ts
new file mode 100644
index 000000000..dac08c91b
--- /dev/null
+++ b/apps/auth/src/app/app.ts
@@ -0,0 +1,58 @@
+import 'express-async-errors';
+
+import axios from 'axios';
+import axiosBetterStacktrace from 'axios-better-stacktrace';
+import compression from 'compression';
+import express from 'express';
+import expressEJSLayouts from 'express-ejs-layouts';
+import path from 'path';
+import db from './util/database';
+import morgan from './middlewares/morgan';
+import morganBody from 'morgan-body';
+import { xssProtection } from 'lusca';
+import { DASHBOARD_URL, GTM, MONGODB_URI, NODE_ENV, PORT, PUBLIC_URL, WIDGET_URL } from './config/secrets';
+import RouterRoot from './controllers';
+import { corsHandler, errorLogger, errorNormalizer, errorOutput, notFoundHandler } from './middlewares';
+import { helmetInstance } from './util/helmet';
+import { assetsPath } from './util/path';
+
+axiosBetterStacktrace(axios);
+
+const app = express();
+
+db.connect(MONGODB_URI);
+
+app.set('port', PORT);
+app.set('trust proxy', true);
+app.set('layout', './layouts/default');
+app.set('view engine', 'ejs');
+app.set('views', path.join(assetsPath, 'views'));
+app.use(compression());
+app.use(helmetInstance);
+app.use(corsHandler);
+app.use(morgan);
+
+morganBody(app, {
+    logRequestBody: NODE_ENV === 'development',
+    logResponseBody: false, // NODE_ENV === 'development',
+    skip: () => NODE_ENV === 'test',
+});
+
+app.use(expressEJSLayouts);
+app.use(xssProtection(true));
+app.use(express.static(assetsPath));
+app.use('/', RouterRoot);
+app.use(notFoundHandler);
+app.use(errorLogger);
+app.use(errorNormalizer);
+app.use(errorOutput);
+
+app.locals = Object.assign(app.locals, {
+    gtm: GTM,
+    dashboardUrl: DASHBOARD_URL,
+    widgetUrl: WIDGET_URL,
+    publicUrl: PUBLIC_URL,
+    deployedAt: String(Date.now()),
+});
+
+export default app;
diff --git a/apps/auth/src/app/config/migrate-mongo-create-only.json b/apps/auth/src/app/config/migrate-mongo-create-only.json
new file mode 100644
index 000000000..f6776da66
--- /dev/null
+++ b/apps/auth/src/app/config/migrate-mongo-create-only.json
@@ -0,0 +1,4 @@
+{
+    "migrationsDir": "src/app/migrations",
+    "moduleSystem": "commonjs"
+}
diff --git a/apps/auth/src/app/config/migrate-mongo.ts b/apps/auth/src/app/config/migrate-mongo.ts
new file mode 100644
index 000000000..8ffe9b588
--- /dev/null
+++ b/apps/auth/src/app/config/migrate-mongo.ts
@@ -0,0 +1,13 @@
+import path from 'path';
+import { MONGODB_URI } from '@thxnetwork/auth/config/secrets';
+
+export default {
+    migrationFileExtension: '.js',
+    mongodb: {
+        url: MONGODB_URI,
+    },
+    migrationsDir: path.join(path.resolve(__dirname), 'app/migrations'),
+    changelogCollectionName: 'changelog',
+    useFileHash: false,
+    moduleSystem: 'commonjs',
+};
diff --git a/apps/auth/src/app/config/oidc.ts b/apps/auth/src/app/config/oidc.ts
new file mode 100644
index 000000000..cf28871b5
--- /dev/null
+++ b/apps/auth/src/app/config/oidc.ts
@@ -0,0 +1,221 @@
+import MongoAdapter from '../util/adapter';
+import { Account } from '../models/Account';
+import { AccountDocument } from '../models/Account';
+import { API_URL, INITIAL_ACCESS_TOKEN, NODE_ENV, SECURE_KEY } from '@thxnetwork/auth/config/secrets';
+import { Configuration, interactionPolicy } from 'oidc-provider';
+import { getJwks } from '../util/jwks';
+
+const basePolicy = interactionPolicy.base();
+const promptAuth = new interactionPolicy.Prompt({ name: 'auth', requestable: true });
+const promptVerifyEmail = new interactionPolicy.Prompt({ name: 'verify_email', requestable: true });
+const promptConnect = new interactionPolicy.Prompt({ name: 'connect', requestable: true });
+const promptAccount = new interactionPolicy.Prompt({ name: 'account-settings', requestable: true });
+basePolicy.add(promptAuth);
+basePolicy.add(promptVerifyEmail);
+basePolicy.add(promptConnect);
+basePolicy.add(promptAccount);
+
+// Consent prompt is a requirement for refresh_token grant
+// so we only remove the checks and not the prompt
+basePolicy.get('consent').checks.clear();
+
+const keys = [SECURE_KEY.split(',')[0], SECURE_KEY.split(',')[1]];
+const config: Configuration = {
+    jwks: getJwks(),
+    adapter: MongoAdapter,
+    loadExistingGrant: async (ctx) => {
+        const grant = new ctx.oidc.provider.Grant({
+            clientId: ctx.oidc.client.clientId,
+            accountId: ctx.oidc.session.accountId,
+        });
+
+        grant.addOIDCScope('openid offline_access');
+        grant.addOIDCClaims(['sub', 'email']);
+        grant.addResourceScope(API_URL, ctx.oidc.client.scope);
+        await grant.save();
+        return grant;
+    },
+    async findAccount(ctx: any, sub: string) {
+        const account: AccountDocument = await Account.findById(sub);
+
+        return {
+            accountId: sub,
+            claims: () => {
+                return {
+                    sub,
+                    ...account.toJSON(),
+                };
+            },
+        };
+    },
+    routes: {
+        authorization: '/authorize',
+    },
+    extraParams: [
+        'pool_id',
+        'claim_id',
+        'return_url',
+        'signup_email',
+        'signup_plan',
+        'signup_offer',
+        'verifyEmailToken',
+        'prompt',
+        'collaborator_request_token',
+        'referral_code',
+        'access_token_kind',
+        'provider_scope',
+        'auth_variant',
+        'auth_signature',
+        'auth_message',
+        'auth_email',
+    ],
+    scopes: [
+        'openid',
+        'offline_access',
+        'account:read',
+        'account:write',
+        'accounts:read',
+        'accounts:write',
+        'brands:read',
+        'brands:write',
+        'pools:read',
+        'pools:write',
+        'rewards:read',
+        'rewards:write',
+        'members:read',
+        'members:write',
+        'memberships:read',
+        'memberships:write',
+        'withdrawals:read',
+        'withdrawals:write',
+        'deposits:read',
+        'deposits:write',
+        'erc20:read',
+        'erc20:write',
+        'erc721:read',
+        'erc721:write',
+        'erc1155:read',
+        'erc1155:write',
+        'promotions:read',
+        'promotions:write',
+        'point_balances:read',
+        'point_balances:write',
+        'point_rewards:read',
+        'point_rewards:write',
+        'transactions:read',
+        'transactions:write',
+        'payments:read',
+        'payments:write',
+        'widgets:write',
+        'widgets:read',
+        'relay:write',
+        'metrics:read',
+        'swaprule:read',
+        'swaprule:write',
+        'swap:read',
+        'swap:write',
+        'claims:write',
+        'claims:read',
+        'clients:write',
+        'clients:read',
+        'wallets:read',
+        'wallets:write',
+        'webhooks:read',
+        'webhooks:write',
+        'web3_quests:read',
+        'web3_quests:write',
+        'custom_rewards:read',
+        'custom_rewards:write',
+        'coupon_rewards:read',
+        'coupon_rewards:write',
+        'discord_role_rewards:read',
+        'discord_role_rewards:write',
+        'erc20_rewards:read',
+        'erc20_rewards:write',
+        'erc721_rewards:read',
+        'erc721_rewards:write',
+        'referral_rewards:read',
+        'referral_rewards:write',
+        'referal_reward_claims:read',
+        'referal_reward_claims:write',
+        'pool_analytics:read',
+        'pool_subscription:read',
+        'pool_subscription:write',
+        'merchants:write',
+        'merchants:read',
+        'identities:write',
+        'identities:read',
+        'events:write',
+        'events:read',
+    ],
+    claims: {
+        openid: ['sub', 'email', 'variant', 'address'],
+    },
+    ttl: {
+        Interaction: 24 * 60 * 60, // 24 hours in seconds
+        Session: 24 * 60 * 60, // 24 hours in seconds
+        Grant: 24 * 60 * 60, // 24 hours in seconds
+        IdToken: 24 * 60 * 60, // 24 hours in seconds
+        RefreshToken: 24 * 60 * 60, // 24 hours in seconds
+        AccessToken: 24 * 60 * 60, // 24 hours in seconds,
+        AuthorizationCode: 10 * 60, // 10 minutes in seconds
+        ClientCredentials: 1 * 60 * 60, // 10 minutes in seconds
+    },
+    interactions: {
+        policy: basePolicy,
+        url(ctx: any, interaction: any) {
+            return `/oidc/${interaction.uid}`;
+        },
+    },
+    features: {
+        userinfo: { enabled: false },
+        devInteractions: { enabled: false },
+        clientCredentials: { enabled: true },
+        encryption: { enabled: true },
+        introspection: { enabled: true },
+        registration: { enabled: true, initialAccessToken: INITIAL_ACCESS_TOKEN },
+        registrationManagement: { enabled: true },
+        resourceIndicators: {
+            enabled: true,
+            defaultResource: () => API_URL,
+            getResourceServerInfo: async (ctx, resourceIndicator, client) => {
+                return {
+                    scope: client.scope,
+                    audience: client.clientId,
+                    accessTokenTTL: 1 * 60 * 60,
+                    accessTokenFormat: 'jwt',
+                };
+            },
+            useGrantedResource: () => true,
+        },
+        rpInitiatedLogout: {
+            enabled: true,
+            logoutSource: async (ctx: any, form: any) => {
+                ctx.body = `<!DOCTYPE html>
+                <head>
+                <title>Logout</title>
+                </head>
+                <body>
+                ${form}
+                <script src="/js/logout.js"></script>
+                </body>
+                </html>`;
+            },
+        },
+    },
+    cookies: {
+        long: { signed: true, secure: true, sameSite: 'none' },
+        short: { signed: true, secure: true, sameSite: 'none' },
+        keys,
+    },
+};
+
+if (NODE_ENV === 'test') {
+    config.pkce = {
+        methods: ['S256'],
+        required: () => false,
+    };
+    config.cookies.long = undefined;
+    config.cookies.short = undefined;
+}
+export default config;
diff --git a/apps/auth/src/app/config/secrets.ts b/apps/auth/src/app/config/secrets.ts
new file mode 100644
index 000000000..509f586ec
--- /dev/null
+++ b/apps/auth/src/app/config/secrets.ts
@@ -0,0 +1,80 @@
+import path from 'path';
+
+const required = [
+    'API_URL',
+    'AUTH_URL',
+    'WALLET_URL',
+    'PUBLIC_URL',
+    'DASHBOARD_URL',
+    'MONGODB_URI',
+    'PORT',
+    'SECURE_KEY',
+    'AWS_ACCESS_KEY_ID',
+    'AWS_SECRET_ACCESS_KEY',
+];
+
+// For production (docker containers) we should require JWKS_JSON to be set since otherwise each container
+// would generate their own jwks.json.
+if (process.env.NODE_ENV === 'production') {
+    required.push('JWKS_JSON');
+}
+
+required.forEach((value: string) => {
+    if (!process.env[value]) {
+        console.log(`Set ${value} environment variable.`);
+        process.exit(1);
+    }
+});
+
+// This allows you to use a single .env file with both regular and test configuration. This allows for an
+// easy to use setup locally without having hardcoded credentials during test runs.
+if (process.env.NODE_ENV === 'test') {
+    if (process.env.AUTH_URL_TEST_OVERRIDE !== undefined) process.env.AUTH_URL = process.env.AUTH_URL_TEST_OVERRIDE;
+    if (process.env.PORT_TEST_OVERRIDE !== undefined) process.env.AUTH_PORT = process.env.PORT_TEST_OVERRIDE;
+    if (process.env.MONGODB_URI_TEST_OVERRIDE !== undefined)
+        process.env.MONGODB_URI = process.env.MONGODB_URI_TEST_OVERRIDE;
+}
+
+export const VERSION = 'v1';
+export const GITHUB_API_ENDPOINT = 'https://api.github.com';
+export const TWITTER_API_ENDPOINT = 'https://api.twitter.com/2';
+export const GOOGLE_API_ENDPOINT = 'https://www.googleapis.com';
+export const DISCORD_API_ENDPOINT = 'https://discord.com/api/v10';
+export const TWITCH_API_ENDPOINT = 'https://api.twitch.tv/helix';
+
+export const CWD = process.env.CWD || path.resolve(__dirname, '../../../apps/auth/src');
+
+export const NODE_ENV = process.env.NODE_ENV;
+export const AUTH_URL = process.env.AUTH_URL;
+export const API_URL = process.env.API_URL;
+export const WALLET_URL = process.env.WALLET_URL;
+export const PUBLIC_URL = process.env.PUBLIC_URL;
+export const DASHBOARD_URL = process.env.DASHBOARD_URL;
+export const WIDGET_URL = process.env.WIDGET_URL;
+export const MONGODB_URI = String(process.env.MONGODB_URI);
+export const PORT = process.env.PORT;
+export const SECURE_KEY = process.env.SECURE_KEY;
+export const GTM = process.env.GTM;
+export const INITIAL_ACCESS_TOKEN = process.env.INITIAL_ACCESS_TOKEN;
+export const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID;
+export const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET;
+export const TWITTER_CLIENT_ID = process.env.TWITTER_CLIENT_ID;
+export const TWITTER_CLIENT_SECRET = process.env.TWITTER_CLIENT_SECRET;
+export const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID;
+export const GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET;
+export const DISCORD_CLIENT_ID = process.env.DISCORD_CLIENT_ID;
+export const DISCORD_CLIENT_SECRET = process.env.DISCORD_CLIENT_SECRET;
+export const TWITCH_CLIENT_ID = process.env.TWITCH_CLIENT_ID;
+export const TWITCH_CLIENT_SECRET = process.env.TWITCH_CLIENT_SECRET;
+export const AUTH_CLIENT_SECRET = process.env.AUTH_CLIENT_SECRET;
+export const AUTH_CLIENT_ID = process.env.AUTH_CLIENT_ID;
+export const JWKS_JSON = process.env.JWKS_JSON;
+export const LOCAL_CERT = process.env.LOCAL_CERT;
+export const LOCAL_CERT_KEY = process.env.LOCAL_CERT_KEY;
+export const MIXPANEL_TOKEN = process.env.MIXPANEL_TOKEN;
+export const CYPRESS_EMAIL = process.env.CYPRESS_EMAIL || 'cypress@thx.network';
+export const HUBSPOT_ACCESS_TOKEN = process.env.HUBSPOT_ACCESS_TOKEN;
+export const BOT_TOKEN = process.env.BOT_TOKEN;
+export const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID;
+export const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY;
+export const COOKIE_DOMAIN = NODE_ENV === 'production' ? '.thx.network' : undefined;
diff --git a/apps/auth/src/app/controllers/account/account.router.ts b/apps/auth/src/app/controllers/account/account.router.ts
new file mode 100644
index 000000000..0329349ac
--- /dev/null
+++ b/apps/auth/src/app/controllers/account/account.router.ts
@@ -0,0 +1,44 @@
+import express from 'express';
+import {
+    getAccount,
+    getAccountByAddress,
+    getAccountByEmail,
+    getAccountByDiscord,
+    getAccountByIdentity,
+} from './get.controller';
+import Patch from './patch.controller';
+import Delete from './delete.controller';
+import List from './list.controller';
+import TokenRead from './tokens/get.controller';
+import TokenRemove from './tokens/delete.controller';
+import { validate } from '../../util/validate';
+import { guard, validateJwt } from '../../middlewares';
+
+const router = express.Router({ mergeParams: true });
+
+router.use(validateJwt);
+router.get('/discord/:discordId', guard.check(['accounts:read']), validate([]), getAccountByDiscord);
+router.get('/address/:address', guard.check(['accounts:read']), validate([]), getAccountByAddress);
+router.get('/email/:email', guard.check(['accounts:read']), validate([]), getAccountByEmail);
+router.get('/identity/:identity', guard.check(['accounts:read']), validate([]), getAccountByIdentity);
+
+router.get('/:sub', guard.check(['accounts:read']), getAccount);
+router.patch('/:sub', guard.check(['accounts:read', 'accounts:write']), validate(Patch.validation), Patch.controller);
+router.delete('/:sub', guard.check(['accounts:write']), validate(Delete.validation), Delete.controller);
+router.post('/', guard.check(['accounts:read']), validate(List.validation), List.controller);
+
+router.get(
+    '/:sub/tokens/:kind',
+    guard.check(['accounts:read', 'accounts:write']),
+    validate(TokenRead.validation),
+    TokenRead.controller,
+);
+
+router.delete(
+    '/:sub/tokens/:kind',
+    guard.check(['accounts:read', 'accounts:write']),
+    validate(TokenRemove.validation),
+    TokenRemove.controller,
+);
+
+export default router;
diff --git a/apps/auth/src/app/controllers/account/account.test.ts b/apps/auth/src/app/controllers/account/account.test.ts
new file mode 100644
index 000000000..9a30a7279
--- /dev/null
+++ b/apps/auth/src/app/controllers/account/account.test.ts
@@ -0,0 +1,121 @@
+import request from 'supertest';
+import app from '../../app';
+import db from '../../util/database';
+import { accountAddress, accountEmail, jwksResponse } from '../../util/jest';
+import { INITIAL_ACCESS_TOKEN } from '@thxnetwork/auth/config/secrets';
+import { AccountVariant, AccountPlanType } from '@thxnetwork/common/enums';
+import { mockAuthPath } from '@thxnetwork/auth/util/jest/mock';
+import AuthService from '@thxnetwork/auth/services/AuthService';
+
+const http = request.agent(app);
+
+describe('Account Controller', () => {
+    let authHeader: string, basicAuthHeader: string, sub: string;
+
+    beforeAll(async () => {
+        await db.truncate();
+
+        // Mock jwks endpoint as jwks-rsa getKeyInterceptor does not work with supertest.
+        await mockAuthPath('get', '/jwks', 200, jwksResponse);
+
+        async function requestToken() {
+            const res = await http
+                .post('/token')
+                .set({
+                    'Content-Type': 'application/x-www-form-urlencoded',
+                    'Authorization': basicAuthHeader,
+                })
+                .send({
+                    grant_type: 'client_credentials',
+                    scope: 'openid accounts:read accounts:write',
+                });
+            return `Bearer ${res.body.access_token}`;
+        }
+
+        async function registerClient() {
+            const res = await http
+                .post('/reg')
+                .set({ Authorization: `Bearer ${INITIAL_ACCESS_TOKEN}` })
+                .send({
+                    application_type: 'web',
+                    client_name: 'THX API',
+                    grant_types: ['client_credentials'],
+                    redirect_uris: [],
+                    response_types: [],
+                    scope: 'openid accounts:read accounts:write',
+                });
+
+            return 'Basic ' + Buffer.from(`${res.body.client_id}:${res.body.client_secret}`).toString('base64');
+        }
+
+        basicAuthHeader = await registerClient();
+        authHeader = await requestToken();
+
+        const account = await AuthService.signup({
+            plan: AccountPlanType.Lite,
+            email: accountEmail,
+            variant: AccountVariant.EmailPassword,
+            active: true,
+        });
+        sub = String(account._id);
+    });
+
+    afterAll(async () => {
+        await db.disconnect();
+    });
+
+    describe('GET /account/:id', () => {
+        it('HTTP 200', async () => {
+            const res = await http
+                .get(`/accounts/${sub}`)
+                .set({
+                    Authorization: authHeader,
+                })
+                .send();
+            expect(res.status).toBe(200);
+            expect(res.body.email).toBe(accountEmail);
+            expect(res.body.variant).toBe(AccountVariant.EmailPassword);
+        });
+    });
+
+    describe('GET /accounts', () => {
+        it('HTTP 200', async () => {
+            const res = await http
+                .post(`/accounts`)
+                .send({
+                    subs: JSON.stringify([sub]),
+                })
+                .set({
+                    Authorization: authHeader,
+                });
+            expect(res.status).toBe(200);
+            expect(res.body.length).toBe(1);
+            expect(res.body[0].email).toBe(accountEmail);
+        });
+    });
+
+    describe('PATCH /accounts/:id', () => {
+        it('HTTP 200', async () => {
+            const res = await http
+                .patch(`/accounts/${sub}`)
+                .set({
+                    Authorization: authHeader,
+                })
+                .send({
+                    address: accountAddress,
+                });
+            expect(res.status).toBe(200);
+        });
+
+        it('HTTP 200', async () => {
+            const res = await http
+                .get(`/accounts/${sub}`)
+                .set({
+                    Authorization: authHeader,
+                })
+                .send();
+            expect(res.status).toBe(200);
+            expect(res.body.address).toBe(accountAddress);
+        });
+    });
+});
diff --git a/apps/auth/src/app/controllers/account/delete.controller.ts b/apps/auth/src/app/controllers/account/delete.controller.ts
new file mode 100644
index 000000000..c654ede0b
--- /dev/null
+++ b/apps/auth/src/app/controllers/account/delete.controller.ts
@@ -0,0 +1,12 @@
+import { Request, Response } from 'express';
+import { AccountService } from '../../services/AccountService';
+import { param } from 'express-validator';
+
+const validation = [param('sub').isMongoId()];
+
+const controller = async (req: Request, res: Response) => {
+    await AccountService.remove(req.auth.sub);
+    res.status(204).end();
+};
+
+export default { controller, validation };
diff --git a/apps/auth/src/app/controllers/account/get.controller.ts b/apps/auth/src/app/controllers/account/get.controller.ts
new file mode 100644
index 000000000..77af77d0c
--- /dev/null
+++ b/apps/auth/src/app/controllers/account/get.controller.ts
@@ -0,0 +1,85 @@
+import { Request, Response } from 'express';
+import { NotFoundError } from '../../util/errors';
+import { AccountService } from '../../services/AccountService';
+import { AccessTokenKind } from '@thxnetwork/common/enums';
+import { AccountDocument } from '@thxnetwork/auth/models/Account';
+import TokenService from '@thxnetwork/auth/services/TokenService';
+
+async function decorate(account: AccountDocument) {
+    const sub = String(account._id);
+    const kinds = [
+        AccessTokenKind.Google,
+        AccessTokenKind.Twitter,
+        AccessTokenKind.Discord,
+        AccessTokenKind.Twitch,
+        AccessTokenKind.Github,
+    ];
+    const tokens = (await Promise.all(kinds.map((kind) => TokenService.getToken(account, kind))))
+        .filter((token) => !!token)
+        .map(({ kind, scopes, userId, metadata }) => ({ kind, scopes, userId, metadata }));
+    const profileImg = account.profileImg || `https://api.dicebear.com/7.x/identicon/svg?seed=${sub}`;
+
+    return {
+        sub,
+        profileImg,
+        username: account.username,
+        address: account.address,
+        firstName: account.firstName,
+        lastName: account.lastName,
+        website: account.website,
+        organisation: account.organisation,
+        isEmailVerified: account.isEmailVerified,
+        plan: account.plan,
+        email: account.email,
+        variant: account.variant,
+        role: account.role,
+        goal: account.goal,
+        identity: account.identity,
+        tokens,
+    };
+}
+
+export const getMe = async (req: Request, res: Response) => {
+    const account = await AccountService.get(req.auth.sub);
+    if (!account) throw new NotFoundError('Could not find the account for this sub');
+
+    res.send(await decorate(account));
+};
+
+export const getAccount = async (req: Request, res: Response) => {
+    const account = await AccountService.get(req.params.sub);
+    if (!account) throw new NotFoundError('Could not find the account for this sub');
+
+    res.send(await decorate(account));
+};
+
+export const getAccountByAddress = async (req: Request, res: Response) => {
+    const account = await AccountService.getByAddress(req.params.address);
+    if (!account) return res.end();
+
+    res.send(await decorate(account));
+};
+
+export const getAccountByEmail = async (req: Request, res: Response) => {
+    const account = await AccountService.getByEmail(req.params.email);
+    if (!account) return res.end();
+
+    res.send(await decorate(account));
+};
+
+export const getAccountByIdentity = async (req: Request, res: Response) => {
+    const account = await AccountService.getByIdentity(req.params.identity);
+    if (!account) return res.end();
+
+    res.send(await decorate(account));
+};
+
+export const getAccountByDiscord = async (req: Request, res: Response) => {
+    const token = await TokenService.findTokenForUserId(req.params.discordId, AccessTokenKind.Discord);
+    if (!token) return res.end();
+
+    const account = await AccountService.get(token.sub);
+    if (!account) return res.end();
+
+    res.send(await decorate(account));
+};
diff --git a/apps/auth/src/app/controllers/account/list.controller.ts b/apps/auth/src/app/controllers/account/list.controller.ts
new file mode 100644
index 000000000..be68e2fb6
--- /dev/null
+++ b/apps/auth/src/app/controllers/account/list.controller.ts
@@ -0,0 +1,62 @@
+import { Request, Response } from 'express';
+import { body } from 'express-validator';
+import { Token } from '@thxnetwork/auth/models/Token';
+import { AccountService } from '@thxnetwork/auth/services/AccountService';
+import { AccessTokenKind } from '@thxnetwork/common/enums';
+
+const validation = [
+    body('subs')
+        .optional()
+        .custom((subs) => {
+            return Array.isArray(JSON.parse(subs));
+        })
+        .customSanitizer((subs) => subs && JSON.parse(subs)),
+    body('query').optional().isString(),
+];
+
+const controller = async (req: Request, res: Response) => {
+    let accounts = [];
+    const { subs, query } = req.body;
+    if (subs && subs.length) {
+        accounts = await AccountService.find({ _id: req.body.subs });
+    }
+    if (query) {
+        accounts = await AccountService.findByQuery({ query: req.body.query });
+    }
+
+    const result = await Promise.all(
+        accounts.map(async (account) => {
+            const sub = String(account._id);
+            const kinds = [
+                AccessTokenKind.Google,
+                AccessTokenKind.Twitter,
+                AccessTokenKind.Discord,
+                AccessTokenKind.Twitch,
+                AccessTokenKind.Github,
+            ];
+            const tokens = await Token.find({ sub, kind: { $in: kinds } });
+            const profileImg = account.profileImg || `https://api.dicebear.com/7.x/identicon/svg?seed=${sub}`;
+
+            return {
+                sub,
+                profileImg,
+                username: account.username,
+                address: account.address,
+                firstName: account.firstName,
+                lastName: account.lastName,
+                website: account.website,
+                organisation: account.organisation,
+                plan: account.plan,
+                email: account.email,
+                variant: account.variant,
+                role: account.role,
+                goal: account.goal,
+                tokens: tokens.map(({ kind, sub, userId, metadata }) => ({ kind, sub, userId, metadata })),
+            };
+        }),
+    );
+
+    res.send(result);
+};
+
+export default { validation, controller };
diff --git a/apps/auth/src/app/controllers/account/patch.controller.ts b/apps/auth/src/app/controllers/account/patch.controller.ts
new file mode 100644
index 000000000..d17d6c968
--- /dev/null
+++ b/apps/auth/src/app/controllers/account/patch.controller.ts
@@ -0,0 +1,23 @@
+import { Request, Response } from 'express';
+import { AccountService } from '../../services/AccountService';
+import { NotFoundError } from '../../util/errors';
+import { body, param } from 'express-validator';
+
+const validation = [
+    param('sub').isMongoId(),
+    body('email')
+        .optional()
+        .isEmail()
+        .customSanitizer((email) => email && email.toLowerCase()),
+];
+
+const controller = async (req: Request, res: Response) => {
+    let account = await AccountService.get(req.params.sub);
+    if (!account) throw new NotFoundError('Account not found.');
+
+    account = await AccountService.update(account, req.body);
+
+    res.json(account);
+};
+
+export default { controller, validation };
diff --git a/apps/auth/src/app/controllers/account/tokens/delete.controller.ts b/apps/auth/src/app/controllers/account/tokens/delete.controller.ts
new file mode 100644
index 000000000..271eb44c2
--- /dev/null
+++ b/apps/auth/src/app/controllers/account/tokens/delete.controller.ts
@@ -0,0 +1,15 @@
+import TokenService from '@thxnetwork/auth/services/TokenService';
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import { Account } from '@thxnetwork/auth/models/Account';
+import { AccessTokenKind } from '@thxnetwork/common/enums';
+
+const validation = [param('sub').isMongoId(), param('kind').isString()];
+
+export const controller = async (req: Request, res: Response) => {
+    const account = await Account.findById(req.params.sub);
+    await TokenService.unsetToken(account, req.params.kind as AccessTokenKind);
+
+    res.status(204).end();
+};
+export default { controller, validation };
diff --git a/apps/auth/src/app/controllers/account/tokens/get.controller.ts b/apps/auth/src/app/controllers/account/tokens/get.controller.ts
new file mode 100644
index 000000000..e81cb7c38
--- /dev/null
+++ b/apps/auth/src/app/controllers/account/tokens/get.controller.ts
@@ -0,0 +1,16 @@
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import { Account } from '@thxnetwork/auth/models/Account';
+import { AccessTokenKind } from '@thxnetwork/common/enums';
+import TokenService from '@thxnetwork/auth/services/TokenService';
+
+const validation = [param('sub').isMongoId(), param('kind').isString()];
+
+export const controller = async (req: Request, res: Response) => {
+    const account = await Account.findById(req.params.sub);
+    const kind = req.params.kind as AccessTokenKind;
+    const token = await TokenService.getToken(account, kind);
+
+    res.json(token);
+};
+export default { controller, validation };
diff --git a/apps/auth/src/app/controllers/get.controller.ts b/apps/auth/src/app/controllers/get.controller.ts
new file mode 100644
index 000000000..d731e0053
--- /dev/null
+++ b/apps/auth/src/app/controllers/get.controller.ts
@@ -0,0 +1,8 @@
+import { Request, Response } from 'express';
+import { WIDGET_URL } from '../config/secrets';
+
+const controller = (_req: Request, res: Response) => {
+    res.redirect(WIDGET_URL);
+};
+
+export default { controller };
diff --git a/apps/auth/src/app/controllers/health/get.controller.ts b/apps/auth/src/app/controllers/health/get.controller.ts
new file mode 100644
index 000000000..ac5da5f99
--- /dev/null
+++ b/apps/auth/src/app/controllers/health/get.controller.ts
@@ -0,0 +1,12 @@
+import { Response, Request } from 'express';
+import { name, version, license } from '../../../../package.json';
+
+export const getHealth = (_req: Request, res: Response) => {
+    const jsonData = {
+        name,
+        version,
+        license,
+    };
+
+    res.header('Content-Type', 'application/json').send(JSON.stringify(jsonData, null, 4));
+};
diff --git a/apps/auth/src/app/controllers/health/health.router.ts b/apps/auth/src/app/controllers/health/health.router.ts
new file mode 100644
index 000000000..f54c01ff6
--- /dev/null
+++ b/apps/auth/src/app/controllers/health/health.router.ts
@@ -0,0 +1,8 @@
+import express from 'express';
+import { getHealth } from './get.controller';
+
+const router = express.Router();
+
+router.get('/', getHealth);
+
+export default router;
diff --git a/apps/auth/src/app/controllers/index.ts b/apps/auth/src/app/controllers/index.ts
new file mode 100644
index 000000000..12944a247
--- /dev/null
+++ b/apps/auth/src/app/controllers/index.ts
@@ -0,0 +1,23 @@
+import express, { json, urlencoded } from 'express';
+import rateLimit from 'express-rate-limit';
+import { oidc } from '../util/oidc';
+import Root from './get.controller';
+import RouterOIDC from './oidc/oidc.router';
+import RouterMe from './me/me.router';
+import RouterAccounts from './account/account.router';
+import RouterHealth from './health/health.router';
+
+export const router = express.Router();
+
+// Rate limit these public endpoints to max 10 req per minute
+const rateLimiter = rateLimit({ windowMs: 60 * 1000, max: 10 });
+
+// Rate limit these public endpoints to max 10 req per minute
+router.get('/', rateLimiter, Root.controller);
+router.use('/health', rateLimiter, json(), urlencoded({ extended: true }), RouterHealth);
+router.use('/me', rateLimiter, json(), urlencoded({ extended: true }), RouterMe);
+router.use('/oidc', rateLimiter, RouterOIDC);
+router.use('/accounts', json(), urlencoded({ extended: true }), RouterAccounts);
+router.use('/', oidc.callback());
+
+export default router;
diff --git a/apps/auth/src/app/controllers/me/get.controller.ts b/apps/auth/src/app/controllers/me/get.controller.ts
new file mode 100644
index 000000000..889791578
--- /dev/null
+++ b/apps/auth/src/app/controllers/me/get.controller.ts
@@ -0,0 +1,8 @@
+import { Account } from '@thxnetwork/auth/models/Account';
+
+const controller = async (req, res) => {
+    const account = await Account.findById(req.auth.sub);
+    res.json(account);
+};
+
+export default { controller };
diff --git a/apps/auth/src/app/controllers/me/me.router.ts b/apps/auth/src/app/controllers/me/me.router.ts
new file mode 100644
index 000000000..6011c02b4
--- /dev/null
+++ b/apps/auth/src/app/controllers/me/me.router.ts
@@ -0,0 +1,9 @@
+import express from 'express';
+import { assertAuthorization, assertInteraction } from '@thxnetwork/auth/middlewares';
+import Read from './get.controller';
+
+const router = express.Router();
+
+router.get('/me', assertInteraction, assertAuthorization, Read.controller);
+
+export default router;
diff --git a/apps/auth/src/app/controllers/oidc/account/email/get.ts b/apps/auth/src/app/controllers/oidc/account/email/get.ts
new file mode 100644
index 000000000..e47ecd6b8
--- /dev/null
+++ b/apps/auth/src/app/controllers/oidc/account/email/get.ts
@@ -0,0 +1,18 @@
+import { Request, Response } from 'express';
+import AuthService from '@thxnetwork/auth/services/AuthService';
+
+async function controller(req: Request, res: Response) {
+    const { uid, params } = req.interaction;
+    const { error, result } = await AuthService.verifyEmailToken(params.verifyEmailToken);
+
+    return res.render('confirm', {
+        uid,
+        params,
+        alert: {
+            variant: error ? 'danger' : 'success',
+            message: error || result,
+        },
+    });
+}
+
+export default { controller };
diff --git a/apps/auth/src/app/controllers/oidc/account/get.ts b/apps/auth/src/app/controllers/oidc/account/get.ts
new file mode 100644
index 000000000..51c244f36
--- /dev/null
+++ b/apps/auth/src/app/controllers/oidc/account/get.ts
@@ -0,0 +1,50 @@
+import { Request, Response } from 'express';
+import { AccountService } from '../../../services/AccountService';
+import { AccessTokenKind, AccountPlanType, OAuthRequiredScopes } from '@thxnetwork/common/enums';
+import TokenService from '@thxnetwork/auth/services/TokenService';
+
+async function controller(req: Request, res: Response) {
+    const { uid, params, alert, session } = req.interaction;
+    const account = await AccountService.get(session.accountId);
+
+    const kinds = [
+        { kind: AccessTokenKind.Google, scopes: OAuthRequiredScopes.GoogleAuth },
+        { kind: AccessTokenKind.Twitter, scopes: OAuthRequiredScopes.TwitterAuth },
+        { kind: AccessTokenKind.Discord, scopes: OAuthRequiredScopes.DiscordAuth },
+        { kind: AccessTokenKind.Twitch, scopes: OAuthRequiredScopes.TwitchAuth },
+        { kind: AccessTokenKind.Github, scopes: OAuthRequiredScopes.GithubAuth },
+    ];
+    const [googleLoginUrl, twitterLoginUrl, discordLoginUrl, twitchLoginUrl, githubLoginUrl] = await Promise.all(
+        kinds.map(async ({ kind, scopes }) => {
+            const token = await TokenService.getToken(account, kind);
+            if (token) return;
+            return TokenService.getLoginURL({ kind, uid, scopes });
+        }),
+    );
+
+    return res.render('account', {
+        uid,
+        alert,
+        params: {
+            ...params,
+            email: account.email,
+            isEmailVerified: account.isEmailVerified,
+            firstName: account.firstName,
+            lastName: account.lastName,
+            profileImg: account.profileImg,
+            organisation: account.organisation,
+            website: account.website,
+            address: account.address,
+            plan: account.plan,
+            planType: AccountPlanType[account.plan],
+            variant: account.variant,
+            googleLoginUrl,
+            twitterLoginUrl,
+            discordLoginUrl,
+            twitchLoginUrl,
+            githubLoginUrl,
+        },
+    });
+}
+
+export default { controller };
diff --git a/apps/auth/src/app/controllers/oidc/account/post.ts b/apps/auth/src/app/controllers/oidc/account/post.ts
new file mode 100644
index 000000000..9a3877557
--- /dev/null
+++ b/apps/auth/src/app/controllers/oidc/account/post.ts
@@ -0,0 +1,64 @@
+import { Request, Response } from 'express';
+import { body } from 'express-validator';
+import { MailService } from '../../../services/MailService';
+import UploadProxy from '../../../proxies/UploadProxy';
+import { AccountService } from '../../../services/AccountService';
+import { ERROR_NO_ACCOUNT } from '../../../util/messages';
+import { createRandomToken } from '../../../util/tokens';
+import { AccessTokenKind } from '@thxnetwork/common/enums/AccessTokenKind';
+import { get24HoursExpiryTimestamp } from '@thxnetwork/auth/util/time';
+import { Account, AccountDocument } from '@thxnetwork/auth/models/Account';
+import TokenService from '@thxnetwork/auth/services/TokenService';
+
+export const validation = [
+    body('email').exists().isEmail(),
+    body('return_url').exists().isURL({ require_tld: false }),
+    body('firstName').optional().isString().isLength({ min: 0, max: 50 }),
+    body('lastName').optional().isString().isLength({ min: 0, max: 50 }),
+    body('organisation').optional().isString().isLength({ min: 0, max: 50 }),
+    body('website').optional().isURL({ require_tld: false }),
+    body().customSanitizer((val) => {
+        return {
+            email: val.email,
+            firstName: val.firstName,
+            lastName: val.lastName,
+            organisation: val.organisation,
+            website: val.website,
+            return_url: val.return_url,
+        };
+    }),
+];
+
+export async function controller(req: Request, res: Response) {
+    const { uid, session } = req.interaction;
+    let account: AccountDocument = await AccountService.get(session.accountId);
+    if (!account) throw new Error(ERROR_NO_ACCOUNT);
+
+    const file = (req.files as any)?.profile?.[0] as Express.Multer.File;
+    const isEmailChanged = req.body.email
+        ? account.email
+            ? account.email.toLowerCase()
+            : '' !== req.body.email
+            ? req.body.email.toLowerCase()
+            : ''
+        : false;
+    const profileImg = file ? await UploadProxy.post(file) : '';
+
+    account = await Account.findByIdAndUpdate(account._id, { ...req.body, profileImg }, { new: true });
+
+    if (isEmailChanged && account.email) {
+        account.isEmailVerified = false;
+
+        await TokenService.setToken(account, {
+            kind: AccessTokenKind.VerifyEmail,
+            accessToken: createRandomToken(),
+            expiry: get24HoursExpiryTimestamp(),
+        });
+        await account.save();
+        await MailService.sendVerificationEmail(account, account.email, req.body.return_url);
+    }
+
+    res.redirect(`/oidc/${uid}/account`);
+}
+
+export default { validation, controller };
diff --git a/apps/auth/src/app/controllers/oidc/callback/callback.router.ts b/apps/auth/src/app/controllers/oidc/callback/callback.router.ts
new file mode 100644
index 000000000..99cb81fc1
--- /dev/null
+++ b/apps/auth/src/app/controllers/oidc/callback/callback.router.ts
@@ -0,0 +1,8 @@
+import express from 'express';
+import Read from './get.controller';
+
+const router = express.Router({ mergeParams: true });
+
+router.get('/:kind', Read.controller);
+
+export default router;
diff --git a/apps/auth/src/app/controllers/oidc/callback/get.controller.ts b/apps/auth/src/app/controllers/oidc/callback/get.controller.ts
new file mode 100644
index 000000000..f26f6a696
--- /dev/null
+++ b/apps/auth/src/app/controllers/oidc/callback/get.controller.ts
@@ -0,0 +1,18 @@
+import { Request, Response } from 'express';
+import { providerAccountVariantMap } from '@thxnetwork/common/maps';
+import { AccessTokenKind } from '@thxnetwork/common/enums';
+import AuthService from '@thxnetwork/auth/services/AuthService';
+import TokenService from '@thxnetwork/auth/services/TokenService';
+
+export async function controller(req: Request, res: Response) {
+    const { code, interaction } = await AuthService.redirectCallback(req);
+    const kind = req.params.kind as AccessTokenKind;
+    const token = await TokenService.request({ kind, code });
+    const variant = providerAccountVariantMap[kind];
+    const account = await AuthService.connect(interaction, token, variant);
+    const returnUrl = await AuthService.getReturn(interaction, account);
+
+    res.redirect(returnUrl);
+}
+
+export default { controller };
diff --git a/apps/auth/src/app/controllers/oidc/connect/get.ts b/apps/auth/src/app/controllers/oidc/connect/get.ts
new file mode 100644
index 000000000..baedf0e2b
--- /dev/null
+++ b/apps/auth/src/app/controllers/oidc/connect/get.ts
@@ -0,0 +1,21 @@
+import { Request, Response } from 'express';
+import { oidc } from '../../../util/oidc';
+import TokenService from '../../../services/TokenService';
+
+async function controller(req: Request, res: Response) {
+    const { uid, params } = req.interaction;
+
+    // If no provider scopes are requested redirect to the return url directly
+    // as there is nothing to connect
+    if (!params.provider_scope) {
+        await oidc.interactionResult(req, res, {}, { mergeWithLastSubmission: true });
+        return res.redirect(params.return_url);
+    }
+
+    const scopes = params.provider_scope.split(' ');
+    const loginURL = TokenService.getLoginURL({ uid, kind: params.access_token_kind, scopes });
+
+    res.redirect(loginURL);
+}
+
+export default { controller };
diff --git a/apps/auth/src/app/controllers/oidc/disconnect/post.controller.ts b/apps/auth/src/app/controllers/oidc/disconnect/post.controller.ts
new file mode 100644
index 000000000..2bad1e664
--- /dev/null
+++ b/apps/auth/src/app/controllers/oidc/disconnect/post.controller.ts
@@ -0,0 +1,16 @@
+import TokenService from '@thxnetwork/auth/services/TokenService';
+import { Request, Response } from 'express';
+import { param } from 'express-validator';
+import { AccessTokenKind } from '@thxnetwork/common/enums';
+import { AccountService } from '@thxnetwork/auth/services/AccountService';
+
+const validation = [param('sub').isMongoId(), param('kind').isString()];
+
+export const controller = async (req: Request, res: Response) => {
+    const { session } = req.interaction;
+    const account = await AccountService.get(session.accountId);
+    await TokenService.unsetToken(account, req.params.kind as AccessTokenKind);
+
+    res.redirect(`/oidc/${req.params.uid}/account`);
+};
+export default { controller, validation };
diff --git a/apps/auth/src/app/controllers/oidc/get.controller.ts b/apps/auth/src/app/controllers/oidc/get.controller.ts
new file mode 100644
index 000000000..c3c007e43
--- /dev/null
+++ b/apps/auth/src/app/controllers/oidc/get.controller.ts
@@ -0,0 +1,42 @@
+import { Request, Response } from 'express';
+import { oidc } from '@thxnetwork/auth/util/oidc';
+import { AccountVariant } from '@thxnetwork/common/enums';
+import AuthService from '@thxnetwork/auth/services/AuthService';
+
+async function controller(req: Request, res: Response) {
+    const { uid, prompt, params } = await oidc.interactionDetails(req, res);
+
+    // If params.auth_variant is available, deeplink to auth_variant
+    if (params && params.auth_variant) {
+        const variant = Number(params.auth_variant) as AccountVariant;
+        const authVariantRedirectMap = {
+            [AccountVariant.EmailPassword]: () =>
+                AuthService.redirectOTP(req, res, { email: String(params.auth_email) }),
+            [AccountVariant.Metamask]: () =>
+                AuthService.redirectWalletConnect(req, res, {
+                    message: params.auth_message as string,
+                    signature: params.auth_signature as string,
+                }),
+            [AccountVariant.SSOGoogle]: () => AuthService.redirectSSO(req, res, { uid, variant }),
+            [AccountVariant.SSODiscord]: () => AuthService.redirectSSO(req, res, { uid, variant }),
+            [AccountVariant.SSOTwitter]: () => AuthService.redirectSSO(req, res, { uid, variant }),
+            [AccountVariant.SSOTwitch]: () => AuthService.redirectSSO(req, res, { uid, variant }),
+            [AccountVariant.SSOGithub]: () => AuthService.redirectSSO(req, res, { uid, variant }),
+        };
+        return authVariantRedirectMap[variant]();
+    }
+
+    // For other cases check the prompt or params.prompt values
+    const redirectMap = {
+        'verify_email': `/oidc/${uid}/account/email/verify`,
+        'account-settings': `/oidc/${uid}/account`,
+        'connect': `/oidc/${uid}/connect`,
+        'login': `/oidc/${uid}/signin`,
+    };
+    const paramsPrompt = params.prompt as string;
+    const url = redirectMap[paramsPrompt] ? redirectMap[paramsPrompt] : redirectMap[prompt.name];
+
+    res.redirect(url);
+}
+
+export default { controller };
diff --git a/apps/auth/src/app/controllers/oidc/oidc.router.ts b/apps/auth/src/app/controllers/oidc/oidc.router.ts
new file mode 100644
index 000000000..972dce360
--- /dev/null
+++ b/apps/auth/src/app/controllers/oidc/oidc.router.ts
@@ -0,0 +1,41 @@
+import multer from 'multer';
+import express, { urlencoded } from 'express';
+import { assertInput, assertAuthorization, assertInteraction } from '../../middlewares';
+
+import ReadConnect from './connect/get';
+import CreateDisconnect from './disconnect/post.controller';
+import ReadAccount from './account/get';
+import UpdateAccount from './account/post';
+import ReadAccountEmailVerify from './account/email/get';
+import ReadOIDC from './get.controller';
+
+import RouterCallback from './callback/callback.router';
+import RouterSignin from './signin/signin.router';
+
+const upload = multer();
+const router = express.Router();
+
+router.use('/callback', RouterCallback);
+router.use('/:uid/signin', RouterSignin);
+
+// Generic redirects from the OIDC router
+router.get('/:uid', assertInteraction, ReadOIDC.controller);
+
+// Our custom connect flow for external accounts
+router.get('/:uid/connect', assertInteraction, assertAuthorization, ReadConnect.controller);
+router.post('/:uid/tokens/:kind/disconnect', assertInteraction, assertAuthorization, CreateDisconnect.controller);
+
+// @peterpolman Should deprecate and let dashboard use the /account in the API for patching account data
+router.get('/:uid/account', assertInteraction, assertAuthorization, ReadAccount.controller);
+router.get('/:uid/account/email/verify', assertInteraction, assertAuthorization, ReadAccountEmailVerify.controller);
+router.post(
+    '/:uid/account',
+    urlencoded({ extended: false }),
+    upload.fields([{ name: 'profile', maxCount: 1 }]),
+    assertInteraction,
+    assertAuthorization,
+    assertInput(UpdateAccount.validation),
+    UpdateAccount.controller,
+);
+
+export default router;
diff --git a/apps/auth/src/app/controllers/oidc/signin/get.ts b/apps/auth/src/app/controllers/oidc/signin/get.ts
new file mode 100644
index 000000000..89830742d
--- /dev/null
+++ b/apps/auth/src/app/controllers/oidc/signin/get.ts
@@ -0,0 +1,98 @@
+import { Request, Response } from 'express';
+import { AUTH_URL, DASHBOARD_URL, WIDGET_URL } from '../../../config/secrets';
+import { AccountVariant, AccessTokenKind, OAuthRequiredScopes } from '@thxnetwork/common/enums';
+import ClaimProxy from '@thxnetwork/auth/proxies/ClaimProxy';
+import BrandProxy from '@thxnetwork/auth/proxies/BrandProxy';
+import PoolProxy from '@thxnetwork/auth/proxies/PoolProxy';
+import TokenService from '@thxnetwork/auth/services/TokenService';
+import EthereumService, { AUTH_REQUEST_TYPED_MESSAGE } from '@thxnetwork/auth/services/EthereumService';
+
+async function controller(req: Request, res: Response) {
+    const { uid, params } = req.interaction;
+    const alert = {};
+    const isWidget = params.return_url ? params.return_url.startsWith(WIDGET_URL) : false;
+    const isDashboard = params.return_url ? params.return_url.startsWith(DASHBOARD_URL) : false;
+    const isSignup = ['1', '2'].includes(params.signup_plan);
+
+    let pool,
+        claim,
+        brand,
+        authenticationMethods = Object.values(AccountVariant);
+
+    if (params.pool_id) {
+        brand = await BrandProxy.get(params.pool_id);
+        pool = await PoolProxy.getPool(params.pool_id);
+        if (pool.settings && pool.settings.authenticationMethods) {
+            authenticationMethods = pool.settings.authenticationMethods;
+        }
+    }
+
+    if (pool && params.collaborator_request_token) {
+        alert['variant'] = 'success';
+        alert[
+            'message'
+        ] = `<i class="fas fa-info-circle mr-2" aria-hidden="true"></i> Accept invite for <strong>${pool.settings.title}</strong>!`;
+    }
+
+    if (params.pool_transfer_token) {
+        alert['variant'] = 'success';
+        alert['message'] = `<i class="fas fa-gift mr-2" aria-hidden="true"></i>Sign in to access your campaign!`;
+    }
+
+    if (params.claim_id) {
+        claim = await ClaimProxy.get(params.claim_id);
+
+        alert['variant'] = 'success';
+        if (claim.erc20) {
+            alert[
+                'message'
+            ] = `<i class="fas fa-gift mr-2" aria-hidden="true"></i>Sign in and claim your <strong>${claim.reward.amount} ${claim.erc20.symbol}!</strong>`;
+        }
+        if (claim.erc721) {
+            alert[
+                'message'
+            ] = `<i class="fas fa-gift mr-2" aria-hidden="true"></i>Sign in and claim your <strong>${claim.erc721.symbol} NFT!</strong>`;
+        }
+    }
+
+    params.emailPasswordEnabled = authenticationMethods.includes(AccountVariant.EmailPassword);
+    params.metaMaskEnabled = authenticationMethods.includes(AccountVariant.Metamask);
+    params.trustedProviderAvailable = authenticationMethods.some((method: AccountVariant) =>
+        [
+            AccountVariant.SSOGoogle,
+            AccountVariant.SSOTwitter,
+            AccountVariant.SSOTwitch,
+            AccountVariant.SSOGithub,
+            AccountVariant.SSODiscord,
+        ].includes(method),
+    );
+
+    params.googleLoginUrl = authenticationMethods.includes(AccountVariant.SSOGoogle)
+        ? TokenService.getLoginURL({
+              kind: AccessTokenKind.Google,
+              uid,
+              scopes: OAuthRequiredScopes.GoogleAuth,
+          })
+        : null;
+    params.githubLoginUrl = authenticationMethods.includes(AccountVariant.SSOGithub)
+        ? TokenService.getLoginURL({ kind: AccessTokenKind.Github, uid, scopes: OAuthRequiredScopes.GithubAuth })
+        : null;
+    params.discordLoginUrl = authenticationMethods.includes(AccountVariant.SSODiscord)
+        ? TokenService.getLoginURL({ kind: AccessTokenKind.Discord, uid, scopes: OAuthRequiredScopes.DiscordAuth })
+        : null;
+    params.twitchLoginUrl = authenticationMethods.includes(AccountVariant.SSOTwitch)
+        ? TokenService.getLoginURL({ kind: AccessTokenKind.Twitch, uid, scopes: OAuthRequiredScopes.TwitchAuth })
+        : null;
+    params.twitterLoginUrl = authenticationMethods.includes(AccountVariant.SSOTwitter)
+        ? TokenService.getLoginURL({ kind: AccessTokenKind.Twitter, uid, scopes: OAuthRequiredScopes.TwitterAuth })
+        : null;
+    params.authRequestMessage = EthereumService.createTypedMessage(AUTH_REQUEST_TYPED_MESSAGE, AUTH_URL, uid);
+
+    res.render('signin', {
+        uid,
+        params: { ...params, ...brand, claim, isWidget, isDashboard, isSignup },
+        alert,
+    });
+}
+
+export default { controller };
diff --git a/apps/auth/src/app/controllers/oidc/signin/grants.test.ts b/apps/auth/src/app/controllers/oidc/signin/grants.test.ts
new file mode 100644
index 000000000..29074a643
--- /dev/null
+++ b/apps/auth/src/app/controllers/oidc/signin/grants.test.ts
@@ -0,0 +1,150 @@
+import request from 'supertest';
+import app from '../../../app';
+import db from '../../../util/database';
+import { AccountService } from '../../../services/AccountService';
+import { INITIAL_ACCESS_TOKEN } from '../../../config/secrets';
+import { accountEmail, jwksResponse } from '../../../util/jest';
+import { AccountVariant, AccountPlanType } from '@thxnetwork/common/enums';
+import { mockAuthPath } from '@thxnetwork/auth/util/jest/mock';
+
+const http = request.agent(app);
+
+describe('OAuth2 Grants', () => {
+    let authHeader: string, accessToken: string, sub: string;
+
+    beforeAll(async () => {
+        await db.truncate();
+
+        // Mock jwks endpoint as jwks-rsa getKeyInterceptor does not work with supertest.
+        await mockAuthPath('get', '/jwks', 200, jwksResponse);
+
+        const account = await AccountService.create({
+            plan: AccountPlanType.Lite,
+            email: accountEmail,
+            variant: AccountVariant.EmailPassword,
+            active: true,
+        });
+        sub = account.id;
+    });
+
+    afterAll(async () => {
+        await db.disconnect();
+    });
+
+    describe('GET /.well-known/openid-configuration', () => {
+        it('HTTP 200', async () => {
+            const res = await http.get('/.well-known/openid-configuration');
+            expect(res.status).toBe(200);
+        });
+    });
+
+    describe('GET /accounts', () => {
+        it('HTTP 401 Unauthorized', async () => {
+            const res = await http.get('/accounts');
+            expect(res.status).toBe(401);
+        });
+    });
+
+    describe('GET /reg', () => {
+        it('HTTP 201', async () => {
+            const res = await http
+                .post('/reg')
+                .set({ Authorization: `Bearer ${INITIAL_ACCESS_TOKEN}` })
+                .send({
+                    application_type: 'web',
+                    client_name: 'THX API',
+                    grant_types: ['client_credentials'],
+                    redirect_uris: [],
+                    response_types: [],
+                    scope: 'openid accounts:read accounts:write',
+                });
+            authHeader = 'Basic ' + Buffer.from(`${res.body.client_id}:${res.body.client_secret}`).toString('base64');
+
+            expect(res.status).toBe(201);
+        });
+    });
+
+    describe('GET /token', () => {
+        it('HTTP 401 (invalid access token)', async () => {
+            const res = await http
+                .post('/token')
+                .set({
+                    'Content-Type': 'application/x-www-form-urlencoded',
+                    'Authorization': 'incorrect authorization code',
+                })
+                .send({
+                    // resource: API_URL,
+                    grant_type: 'client_credentials',
+                    scope: 'openid accounts:read accounts:write',
+                });
+            expect(res.status).toBe(400);
+            expect(res.body).toMatchObject({
+                error: 'invalid_request',
+                error_description: 'invalid authorization header value format',
+            });
+        });
+        it('HTTP 401 (invalid grant)', async () => {
+            const res = await http
+                .post('/token')
+                .set({
+                    'Content-Type': 'application/x-www-form-urlencoded',
+                    'Authorization': authHeader,
+                })
+                .send({
+                    grant_type: 'authorization_code',
+                    scope: 'openid accounts:read accounts:write',
+                });
+            expect(res.body).toMatchObject({
+                error: 'unauthorized_client',
+                error_description: 'requested grant type is not allowed for this client',
+            });
+            expect(res.status).toBe(400);
+        });
+
+        it('HTTP 401 (invalid scope)', async () => {
+            const res = await http
+                .post('/token')
+                .set({
+                    'Content-Type': 'application/x-www-form-urlencoded',
+                    'Authorization': authHeader,
+                })
+                .send({
+                    grant_type: 'client_credentials',
+                    scope: 'openid account:read account:write members:read members:write withdrawals:write asset_pools:read asset_pools:write rewards:read withdrawals:read deposits:read deposits:write',
+                });
+            expect(res.body).toMatchObject({
+                error: 'invalid_scope',
+                error_description: 'requested scope is not allowed',
+                scope: 'account:read',
+            });
+            expect(res.status).toBe(400);
+        });
+
+        it('HTTP 200 (success)', async () => {
+            const res = await http
+                .post('/token')
+                .set({
+                    'Content-Type': 'application/x-www-form-urlencoded',
+                    'Authorization': authHeader,
+                })
+                .send({
+                    grant_type: 'client_credentials',
+                    scope: 'openid accounts:read accounts:write',
+                });
+            accessToken = res.body.access_token;
+
+            expect(res.status).toBe(200);
+            expect(accessToken).toBeDefined();
+        });
+    });
+
+    describe('GET /account/:id', () => {
+        it('HTTP 200', async () => {
+            const res = await http
+                .get(`/accounts/${sub}`)
+                .set({ Authorization: `Bearer ${accessToken}` })
+                .send();
+            expect(res.status).toBe(200);
+        });
+    });
+});
diff --git a/apps/auth/src/app/controllers/oidc/signin/otp/get.ts b/apps/auth/src/app/controllers/oidc/signin/otp/get.ts
new file mode 100644
index 000000000..1d13c0004
--- /dev/null
+++ b/apps/auth/src/app/controllers/oidc/signin/otp/get.ts
@@ -0,0 +1,27 @@
+import { Request, Response } from 'express';
+import ClaimProxy from '@thxnetwork/auth/proxies/ClaimProxy';
+import BrandProxy from '@thxnetwork/auth/proxies/BrandProxy';
+
+async function controller(req: Request, res: Response) {
+    const { jti, params } = req.interaction;
+    let claim, brand;
+
+    if (params.claim_id) {
+        claim = await ClaimProxy.get(params.claim_id);
+        brand = await BrandProxy.get(claim.pool._id);
+    }
+
+    if (params.pool_id) {
+        brand = await BrandProxy.get(params.pool_id);
+    }
+
+    const alert = {
+        variant: 'info',
+        icon: 'question-circle',
+        message: `We sent a password to <strong>${params.email}</strong>`,
+    };
+
+    res.render('otp', { uid: jti, alert, email: req.interaction.email, params: { ...params, ...brand, claim } });
+}
+
+export default { controller };
diff --git a/apps/auth/src/app/controllers/oidc/signin/otp/post.ts b/apps/auth/src/app/controllers/oidc/signin/otp/post.ts
new file mode 100644
index 000000000..ee5f1df0b
--- /dev/null
+++ b/apps/auth/src/app/controllers/oidc/signin/otp/post.ts
@@ -0,0 +1,52 @@
+import BrandProxy from '@thxnetwork/auth/proxies/BrandProxy';
+import ClaimProxy from '@thxnetwork/auth/proxies/ClaimProxy';
+import { AccountService } from '@thxnetwork/auth/services/AccountService';
+import { oidc } from '@thxnetwork/auth/util/oidc';
+import { AccessTokenKind } from '@thxnetwork/common/enums';
+import { Request, Response } from 'express';
+import { body } from 'express-validator';
+import TokenService from '@thxnetwork/auth/services/TokenService';
+import AuthService from '@thxnetwork/auth/services/AuthService';
+
+const validation = [
+    body('otp').exists().isString().isLength({ min: 5, max: 5 }),
+    body('returnUrl').exists().isURL({ require_tld: false }),
+];
+
+async function controller(req: Request, res: Response) {
+    let claim, brand;
+    const { uid, params } = req.interaction;
+
+    const attemptCount = await AuthService.getOTPAttempt(req);
+    if (attemptCount >= 5) throw new Error('You have reached the maximum number of attempts.');
+
+    if (params.claim_id) {
+        claim = await ClaimProxy.get(params.claim_id);
+        brand = await BrandProxy.get(claim.pool._id);
+    }
+
+    try {
+        const account = await AccountService.get(params.sub);
+        if (!account) throw new Error('No account could be found for this one-time password.');
+
+        const isValid = await AuthService.isOTPValid(account, req.body.otp);
+        if (!isValid) throw new Error('Your one-time password is incorrect.');
+
+        const token = await TokenService.getToken(account, AccessTokenKind.Auth);
+        if (token.expiry < Date.now()) throw new Error('One-time password expired');
+
+        await account.updateOne({ isEmailVerified: true });
+
+        await AuthService.getReturnUrl(account, req.interaction);
+
+        return await oidc.interactionFinished(req, res, { login: { accountId: String(account._id) } });
+    } catch (error) {
+        return res.render('otp', {
+            uid,
+            alert: { variant: 'danger', icon: 'exclamation-circle', message: error.message },
+            params: { ...params, ...brand, claim },
+        });
+    }
+}
+
+export default { controller, validation };
diff --git a/apps/auth/src/app/controllers/oidc/signin/post.ts b/apps/auth/src/app/controllers/oidc/signin/post.ts
new file mode 100644
index 000000000..44a0ed45e
--- /dev/null
+++ b/apps/auth/src/app/controllers/oidc/signin/post.ts
@@ -0,0 +1,15 @@
+import { Request, Response } from 'express';
+import { body, param } from 'express-validator';
+import { UnauthorizedError } from '@thxnetwork/auth/util/errors';
+import AuthService from '../../../services/AuthService';
+
+const validation = [body('email').optional().isEmail(), param('uid').isMongoId()];
+
+async function controller(req: Request, res: Response) {
+    const { email } = req.body;
+    if (!email) throw new UnauthorizedError('Email is required');
+
+    return await AuthService.redirectOTP(req, res, { email });
+}
+
+export default { controller, validation };
diff --git a/apps/auth/src/app/controllers/oidc/signin/retry/post.ts b/apps/auth/src/app/controllers/oidc/signin/retry/post.ts
new file mode 100644
index 000000000..0cc1b8670
--- /dev/null
+++ b/apps/auth/src/app/controllers/oidc/signin/retry/post.ts
@@ -0,0 +1,30 @@
+import { AccountService } from '../../../../services/AccountService';
+import { MailService } from '../../../../services/MailService';
+import { Request, Response } from 'express';
+import { AccountDocument } from '@thxnetwork/auth/models/Account';
+import { NotFoundError } from '@thxnetwork/auth/util/errors';
+
+async function controller(req: Request, res: Response) {
+    const { params } = req.interaction;
+
+    function renderSigninPage(variant: string, message: string) {
+        return res.render('signin', {
+            uid: req.params.uid,
+            params: { return_url: params.return_url },
+            alert: { variant, message },
+        });
+    }
+
+    try {
+        const account: AccountDocument = await AccountService.get(params.sub);
+        if (!account) throw new NotFoundError();
+
+        await MailService.sendOTPMail(account);
+
+        return res.redirect(`/oidc/${req.params.uid}/signin/otp`);
+    } catch (error) {
+        return renderSigninPage('danger', error.message);
+    }
+}
+
+export default { controller };
diff --git a/apps/auth/src/app/controllers/oidc/signin/signin.otp.test.ts b/apps/auth/src/app/controllers/oidc/signin/signin.otp.test.ts
new file mode 100644
index 000000000..39fa75062
--- /dev/null
+++ b/apps/auth/src/app/controllers/oidc/signin/signin.otp.test.ts
@@ -0,0 +1,95 @@
+import request from 'supertest';
+import app from '../../../app';
+import db from '../../../util/database';
+import bcrypt from 'bcrypt';
+import { API_URL, DASHBOARD_URL, INITIAL_ACCESS_TOKEN } from '../../../config/secrets';
+import { AccountService } from '../../../services/AccountService';
+import { mockWalletProxy } from '../../../util/jest/mock';
+import { AccessTokenKind } from '@thxnetwork/common/enums';
+import TokenService from '@thxnetwork/auth/services/TokenService';
+
+const http = request.agent(app);
+
+describe('Sign In', () => {
+    const redirectUri = 'https://localhost:8082/signin-oidc';
+    let uid = '',
+        clientId = '';
+
+    beforeAll(async () => {
+        await db.truncate();
+
+        const res = await http
+            .post('/reg')
+            .set({ Authorization: `Bearer ${INITIAL_ACCESS_TOKEN}` })
+            .send({
+                application_type: 'web',
+                client_name: 'THX Dashboard',
+                grant_types: ['authorization_code'],
+                redirect_uris: [redirectUri],
+                response_types: ['code'],
+                scope: 'openid pools:read pools:write withdrawals:read rewards:write deposits:read deposits:write wallets:read wallets:write',
+            });
+
+        clientId = res.body.client_id;
+
+        mockWalletProxy();
+    });
+
+    afterAll(async () => {
+        await db.disconnect();
+    });
+
+    describe('Signup OTP', () => {
+        const otp = '00000',
+            email = 'fake.user@thx.network';
+
+        it('GET /authorize', async () => {
+            const params = new URLSearchParams({
+                client_id: clientId,
+                redirect_uri: redirectUri,
+                resource: API_URL,
+                scope: 'openid pools:read pools:write withdrawals:read rewards:write deposits:read deposits:write wallets:read wallets:write',
+                response_type: 'code',
+                response_mode: 'query',
+                nonce: 'xun4kvy4mh',
+                return_url: DASHBOARD_URL,
+            });
+
+            const res = await http.get(`/authorize?${params.toString()}`).send();
+
+            expect(res.status).toEqual(303);
+            expect(res.header.location).toMatch(new RegExp('/oidc/.*'));
+
+            uid = (res.header.location as string).split('/')[2];
+        });
+
+        it('GET /oidc/:uid/signin', async () => {
+            const res = await http.get(`/oidc/${uid}/signin`).send();
+            expect(res.status).toEqual(200);
+            expect(res.text).toMatch(new RegExp('.*Send one-time password*'));
+        });
+
+        it('GET /oidc/:uid/signin/otp', async () => {
+            const res = await http.post(`/oidc/${uid}/signin`).send(`email=${email}`);
+            expect(res.status).toEqual(302);
+            expect(res.header.location).toBe(`/oidc/${uid}/signin/otp`);
+        });
+
+        it('POST /oidc/:uid/signin/otp (incorrect OTP)', async () => {
+            const res = await http.post(`/oidc/${uid}/signin/otp`).send(`otp=12345`);
+            expect(res.status).toEqual(200);
+            expect(res.text).toMatch(new RegExp('.*Your one-time password is incorrect.*'));
+        });
+
+        it('POST /oidc/:uid/signin/otp (correct OTP)', async () => {
+            // Override the hashed OTP in db to continue with a deterministic value
+            const hashedOtp = await bcrypt.hash(otp, 10);
+            const account = await AccountService.getByEmail(email);
+
+            await TokenService.setToken(account, { kind: AccessTokenKind.Auth, accessToken: hashedOtp });
+
+            const res = await http.post(`/oidc/${uid}/signin/otp`).send(`otp=${otp}`);
+            expect(res.status).toEqual(303);
+        });
+    });
+});
diff --git a/apps/auth/src/app/controllers/oidc/signin/signin.router.ts b/apps/auth/src/app/controllers/oidc/signin/signin.router.ts
new file mode 100644
index 000000000..f6800a367
--- /dev/null
+++ b/apps/auth/src/app/controllers/oidc/signin/signin.router.ts
@@ -0,0 +1,30 @@
+import express, { urlencoded } from 'express';
+import { assertInput, assertInteraction } from '@thxnetwork/auth/middlewares';
+import ReadOTP from './otp/get';
+import CreateOTP from './otp/post';
+import Read from './get';
+import Create from './post';
+import CreateORPRetry from './retry/post';
+import rateLimit from 'express-rate-limit';
+import { API_URL, NODE_ENV } from '@thxnetwork/auth/config/secrets';
+
+const router = express.Router({ mergeParams: true });
+
+// Apply rate limit in production only
+if (NODE_ENV === 'production' && API_URL.startsWith('https://api.')) {
+    router.use(rateLimit({ windowMs: 60 * 1000, max: 60 }));
+}
+
+router.get('/', assertInteraction, Read.controller);
+router.get('/otp', assertInteraction, ReadOTP.controller);
+router.post('/', urlencoded({ extended: false }), assertInteraction, assertInput(Create.validation), Create.controller);
+router.post(
+    '/otp',
+    urlencoded({ extended: false }),
+    assertInteraction,
+    assertInput(CreateOTP.validation),
+    CreateOTP.controller,
+);
+router.post('/resend-otp', urlencoded({ extended: false }), assertInteraction, CreateORPRetry.controller);
+
+export default router;
diff --git a/apps/auth/src/app/controllers/oidc/signin/signin.sso.test.ts b/apps/auth/src/app/controllers/oidc/signin/signin.sso.test.ts
new file mode 100644
index 000000000..23812cbc0
--- /dev/null
+++ b/apps/auth/src/app/controllers/oidc/signin/signin.sso.test.ts
@@ -0,0 +1,154 @@
+import { DASHBOARD_URL, GITHUB_API_ENDPOINT } from './../../../config/secrets';
+import nock from 'nock';
+import request from 'supertest';
+import { AccountVariant } from '@thxnetwork/common/enums';
+import app from '../../../app';
+import { AccountService } from '../../../services/AccountService';
+import { AccountPlanType } from '@thxnetwork/common/enums';
+import db from '../../../util/database';
+import { accountEmail } from '../../../util/jest';
+import { mockWalletProxy } from '../../../util/jest/mock';
+import { API_URL, INITIAL_ACCESS_TOKEN } from '../../../config/secrets';
+
+const http = request.agent(app);
+
+describe('SSO Sign In', () => {
+    let uid = '',
+        CLIENT_ID = '';
+    const REDIRECT_URL = DASHBOARD_URL + '/signin-oidc';
+
+    beforeAll(async () => {
+        mockWalletProxy();
+        await db.truncate();
+
+        const res = await http
+            .post('/reg')
+            .set({ Authorization: `Bearer ${INITIAL_ACCESS_TOKEN}` })
+            .send({
+                application_type: 'web',
+                client_name: 'THX Dashboard',
+                grant_types: ['authorization_code'],
+                redirect_uris: [REDIRECT_URL],
+                response_types: ['code'],
+                scope: 'openid pools:read pools:write withdrawals:read rewards:write deposits:read deposits:write wallets:read wallets:write',
+            });
+
+        CLIENT_ID = res.body.client_id;
+
+        const account = await AccountService.create({
+            plan: AccountPlanType.Lite,
+            email: accountEmail,
+            variant: AccountVariant.EmailPassword,
+        });
+        const params = new URLSearchParams({
+            client_id: CLIENT_ID,
+            redirect_uri: REDIRECT_URL,
+            resource: API_URL,
+            scope: 'openid pools:read pools:write withdrawals:read rewards:write deposits:read deposits:write wallets:read wallets:write',
+            response_type: 'code',
+            response_mode: 'query',
+            nonce: 'xun4kvy4mh',
+            return_url: DASHBOARD_URL,
+        });
+
+        const authRes = await http.get(`/authorize?${params.toString()}`).send();
+
+        expect(authRes.status).toEqual(303);
+        expect(authRes.header.location).toMatch(new RegExp('/oidc/.*'));
+
+        uid = (authRes.header.location as string).split('/')[2];
+
+        await account.save();
+    });
+
+    afterAll(async () => {
+        await db.disconnect();
+        nock.cleanAll();
+    });
+
+    // describe('Google SSO', () => {
+    //     beforeAll(async () => {
+    //         nock('https://oauth2.googleapis.com/token')
+    //             .post(/.*?/)
+    //             .reply(200, {
+    //                 id_token:
+    //                     'eyJhbGciOiJSUzI1NiIsImtpZCI6IjFiZDY4NWY1ZThmYzYyZDc1ODcwNWMxZWIwZThhNzUyNGM0NzU5NzUiLCJ0eXAiOiJKV1QifQ.' +
+    //                     btoa(
+    //                         JSON.stringify({
+    //                             iss: 'https://accounts.google.com',
+    //                             azp: '506948879165-0mkdoln16052qb4gb9318h5hv8rntnv3.apps.googleusercontent.com',
+    //                             aud: '506948879165-0mkdoln16052qb4gb9318h5hv8rntnv3.apps.googleusercontent.com',
+    //                             sub: '116780302581790032921',
+    //                             email: accountEmail,
+    //                             email_verified: true,
+    //                             at_hash: 'PGVG213L9h_mvf8AFAsVtQ',
+    //                             iat: 1657545884,
+    //                             exp: 1657549484,
+    //                         }),
+    //                     ),
+    //             }); // mock response for account create method
+    //     });
+    //     it('GET /oidc/callback/google', async () => {
+    //         const params = new URLSearchParams({
+    //             code: 'thisnotgonnawork',
+    //             state: Buffer.from(JSON.stringify({ uid })).toString('base64'),
+    //         });
+    //         const res = await http.get('/oidc/callback/google?' + params.toString());
+
+    //         expect(res.status).toBe(302);
+    //         expect(res.headers['location']).toContain('/authorize/');
+    //     });
+    // });
+
+    // describe('Twitter SSO', () => {
+    //     beforeAll(async () => {
+    //         nock(TWITTER_API_ENDPOINT + '/oauth2/token')
+    //             .post(/.*?/)
+    //             .reply(200, {
+    //                 accessToken: 'thisnotgonnawork',
+    //                 expires_in: 60000,
+    //             });
+    //         nock(TWITTER_API_ENDPOINT + '/users/me')
+    //             .get(/.*?/)
+    //             .reply(200, {
+    //                 data: {
+    //                     id: 'thisnotgonnawork',
+    //                 },
+    //             });
+    //     });
+
+    //     it('GET /oidc/callback/twitter', async () => {
+    //         const params = new URLSearchParams({
+    //             code: 'thisnotgonnawork',
+    //             state: Buffer.from(JSON.stringify({ uid })).toString('base64'),
+    //         });
+    //         const res = await http.get('/oidc/callback/twitter?' + params.toString());
+
+    //         expect(res.status).toBe(302);
+    //         expect(res.headers['location']).toContain('/authorize/');
+    //     });
+    // });
+
+    describe('Github SSO', () => {
+        beforeAll(async () => {
+            nock('https://github.com/login/oauth/access_token').post(/.*?/).reply(200, 'access_token=thisnotgonnawork');
+
+            nock(GITHUB_API_ENDPOINT + '/user')
+                .get(/.*?/)
+                .reply(200, {
+                    login: 'GarfDev',
+                });
+        });
+
+        it('GET /oidc/callback/github', async () => {
+            const params = new URLSearchParams({
+                code: 'thisnotgonnawork',
+                state: Buffer.from(JSON.stringify({ uid })).toString('base64'),
+            });
+            const res = await http.get('/oidc/callback/github?' + params.toString());
+
+            expect(res.status).toBe(302);
+            expect(res.headers['location']).toContain('/authorize/');
+        });
+    });
+});
diff --git a/apps/auth/src/app/middlewares/assertAuthorization.ts b/apps/auth/src/app/middlewares/assertAuthorization.ts
new file mode 100644
index 000000000..367a44eeb
--- /dev/null
+++ b/apps/auth/src/app/middlewares/assertAuthorization.ts
@@ -0,0 +1,6 @@
+import { Request, Response, NextFunction } from 'express';
+
+export async function assertAuthorization(req: Request, res: Response, next: NextFunction) {
+    if (req.interaction && !req.interaction.session) throw new Error('Not authorized');
+    next();
+}
diff --git a/apps/auth/src/app/middlewares/assertInput.ts b/apps/auth/src/app/middlewares/assertInput.ts
new file mode 100644
index 000000000..d0deb765f
--- /dev/null
+++ b/apps/auth/src/app/middlewares/assertInput.ts
@@ -0,0 +1,18 @@
+import { validationResult } from 'express-validator';
+import { Request, Response, NextFunction } from 'express';
+
+export function assertInput(validations: any) {
+    return async function (req: Request, res: Response, next: NextFunction) {
+        await Promise.all(validations.map((validation: any) => validation.run(req)));
+
+        const errors = validationResult(req);
+
+        if (errors.isEmpty()) return next();
+        if (!req.interaction) throw new Error('no interaction');
+
+        req.interaction.alert = { variant: 'danger', message: errors };
+        await req.interaction.save(Date.now() + 1000);
+
+        next();
+    };
+}
diff --git a/apps/auth/src/app/middlewares/assertInteraction.ts b/apps/auth/src/app/middlewares/assertInteraction.ts
new file mode 100644
index 000000000..508eeb6d7
--- /dev/null
+++ b/apps/auth/src/app/middlewares/assertInteraction.ts
@@ -0,0 +1,9 @@
+import { Request, Response, NextFunction } from 'express';
+import { oidc } from '../util/oidc';
+
+export async function assertInteraction(req: Request, res: Response, next: NextFunction) {
+    const interaction = await oidc.interactionDetails(req, res);
+    if (!interaction) throw new Error('Could not find the interaction.');
+    req.interaction = interaction;
+    next();
+}
diff --git a/apps/auth/src/app/middlewares/cors.ts b/apps/auth/src/app/middlewares/cors.ts
new file mode 100644
index 000000000..57e8eb9e1
--- /dev/null
+++ b/apps/auth/src/app/middlewares/cors.ts
@@ -0,0 +1,24 @@
+import cors from 'cors';
+import { AUTH_URL, WALLET_URL, DASHBOARD_URL, WIDGET_URL, PUBLIC_URL } from '../config/secrets';
+
+export const corsHandler = cors(async (req: any, callback: any) => {
+    const origin = req.header('Origin');
+    const allowedOrigins = [
+        AUTH_URL,
+        WALLET_URL,
+        DASHBOARD_URL,
+        WIDGET_URL,
+        PUBLIC_URL,
+        'https://app.thx.network',
+        'https://dev-app.thx.network',
+    ];
+
+    if (!origin || allowedOrigins.indexOf(origin) > -1) {
+        callback(null, {
+            credentials: true,
+            origin: '*',
+        });
+    } else {
+        callback(null);
+    }
+});
diff --git a/apps/auth/src/app/middlewares/errorLogger.ts b/apps/auth/src/app/middlewares/errorLogger.ts
new file mode 100644
index 000000000..0c190cc85
--- /dev/null
+++ b/apps/auth/src/app/middlewares/errorLogger.ts
@@ -0,0 +1,11 @@
+import { THXHttpError } from '../util/errors';
+import { logger } from '../util/logger';
+import { NextFunction, Request, Response } from 'express';
+
+export const errorLogger = (error: Error, req: Request, res: Response, next: NextFunction) => {
+    if (!(error instanceof THXHttpError)) {
+        logger.error('Error caught:', error);
+    }
+
+    next(error);
+};
diff --git a/apps/auth/src/app/middlewares/errorNormalizer.ts b/apps/auth/src/app/middlewares/errorNormalizer.ts
new file mode 100644
index 000000000..aebbb5a3f
--- /dev/null
+++ b/apps/auth/src/app/middlewares/errorNormalizer.ts
@@ -0,0 +1,11 @@
+import { NextFunction, Request, Response } from 'express';
+import { UnauthorizedError } from '../util/errors';
+import { UnauthorizedError as JWTUnauthorizedError } from 'express-jwt';
+
+export const errorNormalizer = (error: Error, _req: Request, _res: Response, next: NextFunction) => {
+    if (error instanceof JWTUnauthorizedError) {
+        return next(new UnauthorizedError(error.message));
+    }
+
+    next(error);
+};
diff --git a/apps/auth/src/app/middlewares/errorOutput.ts b/apps/auth/src/app/middlewares/errorOutput.ts
new file mode 100644
index 000000000..4b6c1309a
--- /dev/null
+++ b/apps/auth/src/app/middlewares/errorOutput.ts
@@ -0,0 +1,41 @@
+import { NextFunction, Request, Response } from 'express';
+import { THXHttpError } from '../util/errors';
+import { NODE_ENV } from '../config/secrets';
+
+interface ErrorResponse {
+    error: {
+        message: string;
+        error?: Error;
+        rootMessage?: string;
+        stack?: string;
+    };
+}
+
+const isJsonPath = (path: string): boolean => {
+    // This determines for which prefixes a json error is presented.
+    for (const prefix of ['/account', '/health']) {
+        if (path.startsWith(prefix)) return true;
+    }
+    return false;
+};
+
+// Error handler needs to have 4 arguments.
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export const errorOutput = (error: any, req: Request, res: Response, next: NextFunction) => {
+    let status = 500;
+    const response: ErrorResponse = { error: { message: error.message || 'Unable to fulfill request' } };
+    if (error instanceof THXHttpError || error.status) {
+        status = error.status;
+        response.error.message = error.message;
+    } else if (NODE_ENV !== 'production') {
+        response.error.error = error;
+        response.error.stack = error.stack;
+    }
+    res.status(status);
+    if (isJsonPath(req.path)) {
+        res.json(response);
+    } else {
+        const returnUrl = req.interaction ? req.interaction.params.return_url : '';
+        res.render('error', { returnUrl, alert: { variant: 'danger', message: response.error.message } });
+    }
+};
diff --git a/apps/auth/src/app/middlewares/index.ts b/apps/auth/src/app/middlewares/index.ts
new file mode 100644
index 000000000..5243b4241
--- /dev/null
+++ b/apps/auth/src/app/middlewares/index.ts
@@ -0,0 +1,10 @@
+export * from './errorOutput';
+export * from './errorLogger';
+export * from './errorNormalizer';
+export * from './notFoundHandler';
+export * from './cors';
+export * from './validateJwt';
+export * from './assertInteraction';
+export * from './assertAuthorization';
+export * from './assertInput';
+export * from './morgan';
diff --git a/apps/auth/src/app/middlewares/morgan.ts b/apps/auth/src/app/middlewares/morgan.ts
new file mode 100644
index 000000000..994de436b
--- /dev/null
+++ b/apps/auth/src/app/middlewares/morgan.ts
@@ -0,0 +1,16 @@
+import morgan from 'morgan';
+import { logger } from '../util/logger';
+import { NODE_ENV } from '../config/secrets';
+
+const stream = {
+    write: (message) => logger.http(message),
+};
+
+const skip = () => {
+    return ['development', 'test'].includes(NODE_ENV || 'development');
+};
+
+export default morgan(
+    ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent" - :response-time ms',
+    { stream, skip },
+);
diff --git a/apps/auth/src/app/middlewares/notFoundHandler.ts b/apps/auth/src/app/middlewares/notFoundHandler.ts
new file mode 100644
index 000000000..7ea7e3684
--- /dev/null
+++ b/apps/auth/src/app/middlewares/notFoundHandler.ts
@@ -0,0 +1,6 @@
+import { NextFunction, Request, Response } from 'express';
+import { NotFoundError } from '../util/errors';
+
+export const notFoundHandler = (req: Request, res: Response, next: NextFunction) => {
+    next(new NotFoundError());
+};
diff --git a/apps/auth/src/app/middlewares/validateJwt.ts b/apps/auth/src/app/middlewares/validateJwt.ts
new file mode 100644
index 000000000..69ddc1a6a
--- /dev/null
+++ b/apps/auth/src/app/middlewares/validateJwt.ts
@@ -0,0 +1,17 @@
+import jwksRsa from 'jwks-rsa';
+import expressJwtPermissions from 'express-jwt-permissions';
+import { expressjwt } from 'express-jwt';
+import { AUTH_URL } from '../config/secrets';
+
+export const validateJwt = expressjwt({
+    secret: jwksRsa.expressJwtSecret({
+        cache: true,
+        rateLimit: false,
+        jwksRequestsPerMinute: 10,
+        jwksUri: `${AUTH_URL}/jwks`, // Unnecessary, keys are provided through getKeysInterceptor.
+    }),
+    issuer: AUTH_URL,
+    algorithms: ['RS256'],
+});
+
+export const guard = expressJwtPermissions({ requestProperty: 'auth', permissionsProperty: 'scope' });
diff --git a/apps/auth/src/app/migrations/20240325102059-account-plans.js b/apps/auth/src/app/migrations/20240325102059-account-plans.js
new file mode 100644
index 000000000..6266315f3
--- /dev/null
+++ b/apps/auth/src/app/migrations/20240325102059-account-plans.js
@@ -0,0 +1,18 @@
+module.exports = {
+    async up(db) {
+        const accountColl = db.collection('accounts');
+
+        // Change all existing Free (0) and Basic (1) plans to Lite (0) and also make all undefined plans Lite (0)
+        await accountColl.updateOne(
+            { $or: [{ plan: 0 }, { plan: 1 }, { plan: null }, { plan: { $exists: false } }] },
+            { $set: { plan: 0 } },
+        );
+
+        // Change all existing Premium (2) plans to Premium (1)
+        await accountColl.updateOne({ $or: [{ plan: 2 }] }, { $set: { plan: 1 } });
+    },
+
+    async down() {
+        //
+    },
+};
diff --git a/apps/auth/src/app/models/Account.ts b/apps/auth/src/app/models/Account.ts
new file mode 100644
index 000000000..fb5ab41d5
--- /dev/null
+++ b/apps/auth/src/app/models/Account.ts
@@ -0,0 +1,31 @@
+import mongoose from 'mongoose';
+
+export type AccountDocument = mongoose.Document & TAccount;
+
+const accountSchema = new mongoose.Schema(
+    {
+        username: { type: String, maxLength: 255 },
+        active: Boolean,
+        isEmailVerified: Boolean,
+        firstName: { type: String, maxLength: 255 },
+        lastName: { type: String, maxLength: 255 },
+        profileImg: String,
+        website: { type: String, maxLength: 255 },
+        organisation: { type: String, maxLength: 255 },
+        plan: Number,
+        // email.sparse allows the value to be null and unique if defined
+        email: { type: String, unique: true, sparse: true, maxLength: 255 },
+        // address.sparse allows the value to be null and unique if defined
+        address: { type: String, unique: true, sparse: true },
+        variant: Number,
+        otpSecret: String,
+        acceptTermsPrivacy: Boolean,
+        acceptUpdates: Boolean,
+        role: String,
+        identity: String,
+        goal: [String],
+    },
+    { timestamps: true },
+);
+
+export const Account = mongoose.model<AccountDocument>('Account', accountSchema);
diff --git a/apps/auth/src/app/models/Client.ts b/apps/auth/src/app/models/Client.ts
new file mode 100644
index 000000000..2d70f9ea5
--- /dev/null
+++ b/apps/auth/src/app/models/Client.ts
@@ -0,0 +1,20 @@
+import mongoose from 'mongoose';
+
+export type ClientDocument = mongoose.Document & {
+    _id: string;
+    payload: {
+        request_uris: string[];
+    };
+};
+
+const clientSchema = new mongoose.Schema(
+    {
+        _id: String,
+        payload: {
+            request_uris: [String],
+        },
+    },
+    { timestamps: false },
+);
+
+export const Client = mongoose.model<ClientDocument>('Client', clientSchema, 'client');
diff --git a/apps/auth/src/app/models/Token.ts b/apps/auth/src/app/models/Token.ts
new file mode 100644
index 000000000..edf0b9cb9
--- /dev/null
+++ b/apps/auth/src/app/models/Token.ts
@@ -0,0 +1,44 @@
+import mongoose from 'mongoose';
+import { encryptString } from '../util/encrypt';
+import { SECURE_KEY } from '../config/secrets';
+import { decryptString } from '../util/decrypt';
+
+export type TokenDocument = mongoose.Document & TToken;
+
+const tokenSchema = new mongoose.Schema(
+    {
+        sub: String,
+        kind: String,
+        accessTokenEncrypted: String,
+        refreshTokenEncrypted: String,
+        expiry: Number,
+        userId: String,
+        scopes: [String],
+        metadata: Object,
+    },
+    { timestamps: true },
+);
+
+tokenSchema.pre(['save', 'findOneAndUpdate'], function (next) {
+    const accessToken = this.get('accessToken');
+    const refreshToken = this.get('refreshToken');
+    if (accessToken) {
+        const accessTokenEncrypted = encryptString(accessToken, SECURE_KEY);
+        this.set('accessTokenEncrypted', accessTokenEncrypted);
+    }
+    if (refreshToken) {
+        const refreshTokenEncrypted = encryptString(refreshToken, SECURE_KEY);
+        this.set('refreshTokenEncrypted', refreshTokenEncrypted);
+    }
+    next();
+});
+
+tokenSchema.virtual('accessToken').get(function () {
+    return this.accessTokenEncrypted && decryptString(this.accessTokenEncrypted, SECURE_KEY);
+});
+
+tokenSchema.virtual('refreshToken').get(function () {
+    return this.refreshTokenEncrypted && decryptString(this.refreshTokenEncrypted, SECURE_KEY);
+});
+
+export const Token = mongoose.model<TokenDocument>('Token', tokenSchema, 'tokens');
diff --git a/apps/auth/src/app/proxies/BrandProxy.ts b/apps/auth/src/app/proxies/BrandProxy.ts
new file mode 100644
index 000000000..7444527db
--- /dev/null
+++ b/apps/auth/src/app/proxies/BrandProxy.ts
@@ -0,0 +1,15 @@
+import { apiClient } from '../util/api';
+
+export default {
+    get: async (poolId: string) => {
+        const r = await apiClient({
+            method: 'GET',
+            url: '/v1/brands',
+            headers: {
+                'X-PoolId': poolId,
+            },
+        });
+
+        return r.data;
+    },
+};
diff --git a/apps/auth/src/app/proxies/ClaimProxy.ts b/apps/auth/src/app/proxies/ClaimProxy.ts
new file mode 100644
index 000000000..c0c590780
--- /dev/null
+++ b/apps/auth/src/app/proxies/ClaimProxy.ts
@@ -0,0 +1,14 @@
+import { apiClient, getAuthAccessToken } from '../util/api';
+
+export default {
+    get: async (uuid: string) => {
+        const { data } = await apiClient({
+            method: 'GET',
+            url: `/v1/qr-codes/${uuid}`,
+            headers: {
+                Authorization: await getAuthAccessToken(),
+            },
+        });
+        return data;
+    },
+};
diff --git a/apps/auth/src/app/proxies/PoolProxy.ts b/apps/auth/src/app/proxies/PoolProxy.ts
new file mode 100644
index 000000000..f08db30dc
--- /dev/null
+++ b/apps/auth/src/app/proxies/PoolProxy.ts
@@ -0,0 +1,21 @@
+import { AccountDocument } from '../models/Account';
+import { apiClient } from '../util/api';
+
+export default {
+    transferOwnership: async (account: AccountDocument, poolId: string, token: string) => {
+        const { data } = await apiClient({
+            method: 'POST',
+            url: `/v1/pools/${poolId}/transfers`,
+            data: { token, sub: String(account._id) },
+        });
+        return data;
+    },
+
+    getPool: async (poolId: string) => {
+        const { data } = await apiClient({
+            method: 'GET',
+            url: `/v1/pools/${poolId}`,
+        });
+        return data;
+    },
+};
diff --git a/apps/auth/src/app/proxies/UploadProxy.ts b/apps/auth/src/app/proxies/UploadProxy.ts
new file mode 100644
index 000000000..e2406af1f
--- /dev/null
+++ b/apps/auth/src/app/proxies/UploadProxy.ts
@@ -0,0 +1,25 @@
+import FormData from 'form-data';
+
+import { apiClient } from '../util/api';
+
+export default {
+    post: async (file: Express.Multer.File): Promise<string> => {
+        const form = new FormData({ readable: true, dataSize: file.size });
+
+        form.append('file', file.buffer, {
+            contentType: file.mimetype,
+            filename: file.originalname,
+        });
+
+        const r = await apiClient({
+            url: '/v1/upload',
+            method: 'PUT',
+            headers: {
+                'Content-Type': `multipart/form-data; boundary=${form.getBoundary()}`,
+            },
+            data: form,
+        });
+
+        return r.data.publicUrl as string;
+    },
+};
diff --git a/apps/auth/src/app/services/AccountService.ts b/apps/auth/src/app/services/AccountService.ts
new file mode 100644
index 000000000..de569de4c
--- /dev/null
+++ b/apps/auth/src/app/services/AccountService.ts
@@ -0,0 +1,115 @@
+import { Account, AccountDocument } from '../models/Account';
+import { AccountVariant, AccountPlanType } from '@thxnetwork/common/enums';
+import { generateUsername } from 'unique-username-generator';
+import { toChecksumAddress } from 'web3-utils';
+import { BadRequestError } from '../util/errors';
+import { MailService } from './MailService';
+import { WIDGET_URL } from '../config/secrets';
+import { accountVariantProviderMap } from '@thxnetwork/common/maps';
+import TokenService from './TokenService';
+
+export class AccountService {
+    static create(data: Partial<AccountDocument>) {
+        return Account.create({
+            plan: AccountPlanType.Lite,
+            username: generateUsername(),
+            ...data,
+            email: data.email && data.email.toLowerCase(),
+            isEmailVerified: false,
+            active: true,
+        });
+    }
+
+    static async update(account: AccountDocument, data: Partial<AccountDocument & { returnUrl?: string }>) {
+        // Checksum address
+        if (data.address) {
+            data.address = toChecksumAddress(data.address);
+        }
+
+        // Test username when changing username
+        if (data.username && account.username !== data.username) {
+            const isUsed = await Account.exists({
+                username: data.username,
+                _id: { $ne: data._id, $exists: true },
+            });
+            if (isUsed) throw new BadRequestError('Username already in use.');
+        }
+
+        // Send verification email when changing email
+        if (data.email) {
+            // Only check if email is different than the current one
+            if (data.email !== account.email) {
+                const isUsed = await Account.exists({
+                    email: data.email,
+                    _id: { $ne: String(account._id), $exists: true },
+                });
+                if (isUsed) throw new BadRequestError('Email already in use.');
+                data.isEmailVerified = false;
+            }
+
+            // Always send mail in case this is a retry
+            await MailService.sendVerificationEmail(account, data.email, WIDGET_URL);
+        }
+
+        return await Account.findByIdAndUpdate(account._id, data, { new: true });
+    }
+
+    static get(sub: string) {
+        return Account.findById(sub);
+    }
+
+    static find(query: { _id: string[] }) {
+        return Account.find({ _id: { $in: query._id } });
+    }
+
+    static findByQuery({ query }: { query: string }) {
+        return Account.find({ $or: [{ username: new RegExp(query, 'i') }, { email: new RegExp(query, 'i') }] });
+    }
+
+    static getByEmail(email: string) {
+        return Account.findOne({ email });
+    }
+
+    static getByIdentity(identity: string) {
+        return Account.findOne({ identity });
+    }
+
+    static getByAddress(address: string) {
+        return Account.findOne({ address });
+    }
+
+    static async remove(id: string) {
+        await Account.deleteOne({ _id: id });
+    }
+
+    static findAccountForSession(session: { accountId: string }) {
+        return Account.findById(session.accountId);
+    }
+
+    static findAccountForEmail(email: string) {
+        return Account.findOne({ email: email.toLowerCase() });
+    }
+
+    static async findAccountForToken(variant: AccountVariant, tokenInfo: Partial<{ userId: string }>) {
+        const kind = accountVariantProviderMap[variant];
+        const token = await TokenService.findTokenForUserId(tokenInfo.userId, kind);
+        if (!token) return;
+
+        return await Account.findById(token.sub);
+    }
+
+    static async findAccountForAddress(address: string) {
+        const checksummedAddress = toChecksumAddress(address);
+        // Checking for non checksummed as well in order to avoid issues with existing data in db
+        const account = await Account.findOne({
+            $or: [{ address: checksummedAddress }, { address }],
+            variant: AccountVariant.Metamask,
+        });
+        if (account) return account;
+        return await Account.create({
+            variant: AccountVariant.Metamask,
+            plan: AccountPlanType.Lite,
+            address,
+        });
+    }
+}
diff --git a/apps/auth/src/app/services/AuthService.ts b/apps/auth/src/app/services/AuthService.ts
new file mode 100644
index 000000000..f6abba5cf
--- /dev/null
+++ b/apps/auth/src/app/services/AuthService.ts
@@ -0,0 +1,216 @@
+import { Request, Response } from 'express';
+import { Account, AccountDocument } from '../models/Account';
+import { SUCCESS_SIGNUP_COMPLETED } from '../util/messages';
+import { AccessTokenKind, AccountVariant, AccountPlanType, OAuthRequiredScopes } from '@thxnetwork/common/enums';
+import bcrypt from 'bcrypt';
+import { AccountService } from './AccountService';
+import { Token } from '../models/Token';
+import { UnauthorizedError } from '@thxnetwork/auth/util/errors';
+import { oidc } from '@thxnetwork/auth/util/oidc';
+import { MailService } from './MailService';
+import TokenService from './TokenService';
+import EthereumService from './EthereumService';
+
+export default class AuthService {
+    static async connect(interaction: TInteraction, tokenInfo: Partial<TToken>, variant: AccountVariant) {
+        let account: AccountDocument;
+        const { session, params } = interaction;
+
+        // Find account for active session
+        if (session && session.accountId) {
+            account = await AccountService.findAccountForSession(session);
+        }
+
+        // Find account for userId
+        else if (tokenInfo) {
+            account = await AccountService.findAccountForToken(variant, tokenInfo);
+        }
+
+        // If no match, create the account
+        if (!account) {
+            account = await AccountService.create({ variant, plan: params.signup_plan });
+        }
+
+        // Connect token to account
+        await TokenService.connect(account, tokenInfo);
+
+        return account;
+    }
+
+    static async signup(data: { email?: string; plan: AccountPlanType; variant: AccountVariant; active: boolean }) {
+        let account: AccountDocument;
+
+        if (data.email) {
+            account = await Account.findOne({ email: data.email, active: false });
+        }
+
+        if (!account) {
+            account = await AccountService.create({
+                email: data.email,
+                plan: data.plan,
+            });
+        }
+
+        account.active = data.active;
+        account.email = data.email;
+        account.variant = data.variant;
+        account.plan = data.plan;
+
+        return await account.save();
+    }
+
+    static async redirectSSO(_req: Request, res: Response, { uid, variant }: { uid: string; variant: AccountVariant }) {
+        const map = {
+            [AccountVariant.SSOGoogle]: {
+                kind: AccessTokenKind.Google,
+                scopes: OAuthRequiredScopes.GoogleAuth,
+            },
+            [AccountVariant.SSODiscord]: {
+                kind: AccessTokenKind.Discord,
+                scopes: OAuthRequiredScopes.DiscordAuth,
+            },
+            [AccountVariant.SSOTwitter]: {
+                kind: AccessTokenKind.Twitter,
+                scopes: OAuthRequiredScopes.TwitterAuth,
+            },
+            [AccountVariant.SSOTwitch]: {
+                kind: AccessTokenKind.Twitch,
+                scopes: OAuthRequiredScopes.TwitchAuth,
+            },
+            [AccountVariant.SSOGithub]: {
+                kind: AccessTokenKind.Github,
+                scopes: OAuthRequiredScopes.GithubAuth,
+            },
+        };
+        const { kind, scopes } = map[variant];
+
+        const url = TokenService.getLoginURL({ uid, kind, scopes });
+
+        return res.redirect(url);
+    }
+    static async redirectWalletConnect(
+        req: Request,
+        res: Response,
+        { message, signature }: { message: string; signature: string },
+    ) {
+        // If signed auth request is available recover the address from the signature and lookup user
+        if (!message || !signature) {
+            throw new UnauthorizedError('Signed message and signature are required for Metamask login.');
+        }
+
+        const address = EthereumService.recoverSigner(decodeURIComponent(message), signature);
+        if (!address) throw new UnauthorizedError('Could not recover address from signed message.');
+
+        const account = await AccountService.findAccountForAddress(address);
+        if (!account) throw new UnauthorizedError('Account not found or created.');
+
+        return await oidc.interactionFinished(req, res, { login: { accountId: String(account._id) } });
+    }
+
+    static async getOTPAttempt(req: Request) {
+        const { params } = req.interaction;
+
+        // Store OTP attempt in interaction
+        req.interaction.params.otpAttempts = (params.otpAttempts || 0) + 1;
+
+        // Interaction TTL is set to 10min and will expire after
+        await req.interaction.save(Date.now() + 10 * 60 * 1000);
+
+        return req.interaction.params.otpAttempts;
+    }
+
+    static async redirectOTP(req: Request, res: Response, { email }: { email: string }) {
+        const { params } = req.interaction;
+        let account = await AccountService.findAccountForEmail(email);
+
+        // Create and return account if none found the given email
+        if (!account) {
+            const variant = AccountVariant.EmailPassword;
+            const plan = params.signup_plan ? Number(params.signup_plan) : AccountPlanType.Lite;
+
+            account = await AccountService.create({ email, plan, variant });
+        }
+
+        // Send email using SES
+        await MailService.sendOTPMail(account);
+
+        // Store the sub in the interaction so we can lookup the hashed OTP later
+        req.interaction.params.sub = String(account._id);
+        req.interaction.params.email = email;
+
+        // Interaction TTL is set to 10min and will expire after
+        await req.interaction.save(Date.now() + 10 * 60 * 1000);
+
+        const redirectURL = `/oidc/${req.params.uid}/signin/otp`;
+
+        return res.redirect(redirectURL);
+    }
+
+    static async isOTPValid(account: AccountDocument, otp: string): Promise<boolean> {
+        const token = await TokenService.getToken(account, AccessTokenKind.Auth);
+        if (!token) return;
+        return await bcrypt.compare(otp, token.accessToken);
+    }
+
+    static async verifyEmailToken(verifyEmailToken: string) {
+        const token = await Token.findOne({
+            kind: AccessTokenKind.VerifyEmail,
+            accessTokenEncrypted: verifyEmailToken,
+        });
+        if (!token) return { error: 'Verification request not found.' };
+        if (token && token.expiry < Date.now()) return { error: 'Verification request is expired.' };
+
+        const account = await Account.findById(token.sub);
+        if (!account) return { error: 'Account not found' };
+
+        await TokenService.unsetToken(account, AccessTokenKind.VerifyEmail);
+
+        await Account.findByIdAndUpdate(account._id, { isEmailVerified: true });
+
+        return { result: SUCCESS_SIGNUP_COMPLETED, account };
+    }
+
+    static async redirectCallback(req: Request) {
+        // Get code from url
+        const code = req.query.code as string;
+        // Throw error if not exists
+        if (!code) throw new UnauthorizedError('Could not find code in query');
+
+        const stateBase64String = req.query.state as string;
+        const stateSerialized = Buffer.from(stateBase64String, 'base64').toString();
+        const { uid } = JSON.parse(stateSerialized);
+
+        // Throw if no uid is present in state object
+        if (!uid) throw new UnauthorizedError('Could not find uid in state object');
+
+        // See if interaction still exists and throw if not
+        const interaction = await oidc.Interaction.find(uid);
+        if (!interaction) throw new UnauthorizedError('Your session has expired.');
+
+        return { interaction, code };
+    }
+
+    static async getReturn(interaction, account: AccountDocument) {
+        if (!account) throw new UnauthorizedError('Could not find or create an account');
+
+        // Update interaction with login state
+        interaction.result = { login: { accountId: String(account._id) } };
+        await interaction.save(Date.now() + 10000);
+
+        return await this.getReturnUrl(account, interaction);
+    }
+
+    static async getReturnUrl(
+        account: AccountDocument,
+        { params, returnTo, prompt }: { params: any; returnTo: string; prompt: any },
+    ) {
+        let returnUrl = returnTo;
+        // Connect prompts already have a session and will therefor not continue the
+        // regular auth signin flow used during SSO
+        if (prompt && prompt.name === 'connect') {
+            returnUrl = params.display === 'popup' ? params.redirect_uri : params.return_url;
+        }
+
+        return returnUrl;
+    }
+}
diff --git a/apps/auth/src/app/services/EthereumService.ts b/apps/auth/src/app/services/EthereumService.ts
new file mode 100644
index 000000000..c6746a428
--- /dev/null
+++ b/apps/auth/src/app/services/EthereumService.ts
@@ -0,0 +1,45 @@
+import { arrayify, computeAddress, hashMessage, recoverPublicKey } from 'ethers/lib/utils';
+import { SignTypedDataVersion, recoverTypedSignature } from '@metamask/eth-sig-util';
+
+export const AUTH_REQUEST_TYPED_MESSAGE =
+    "Welcome! Please make sure you have selected your preferred account and sign this message to verify it's ownership.";
+
+export default class EthereumService {
+    static createTypedMessage(message: string, app: string, nonce: string) {
+        return JSON.stringify({
+            types: {
+                EIP712Domain: [
+                    { name: 'name', type: 'string' },
+                    { name: 'version', type: 'string' },
+                ],
+                TypedMessage: [
+                    { name: 'message', type: 'string' },
+                    { name: 'app', type: 'string' },
+                    { name: 'nonce', type: 'string' },
+                ],
+            },
+            domain: {
+                name: 'THX Network',
+                version: '1',
+            },
+            primaryType: 'TypedMessage',
+            message: {
+                message,
+                app,
+                nonce,
+            },
+        });
+    }
+
+    static recoverAddress(message: string, signature: string) {
+        return recoverTypedSignature({
+            data: JSON.parse(message),
+            signature,
+            version: 'V3' as SignTypedDataVersion,
+        });
+    }
+
+    static recoverSigner(message: string, signature: string) {
+        return computeAddress(recoverPublicKey(arrayify(hashMessage(message)), signature));
+    }
+}
diff --git a/apps/auth/src/app/services/MailService.ts b/apps/auth/src/app/services/MailService.ts
new file mode 100644
index 000000000..95f1124bd
--- /dev/null
+++ b/apps/auth/src/app/services/MailService.ts
@@ -0,0 +1,85 @@
+import ejs from 'ejs';
+import path from 'path';
+import bcrypt from 'bcrypt';
+import crypto from 'crypto';
+import { AccountDocument } from '../models/Account';
+import { createRandomToken } from '../util/tokens';
+import { assetsPath } from '../util/path';
+import { AccessTokenKind } from '@thxnetwork/common/enums/AccessTokenKind';
+import { get24HoursExpiryTimestamp } from '../util/time';
+import {
+    AWS_ACCESS_KEY_ID,
+    AWS_SECRET_ACCESS_KEY,
+    AUTH_URL,
+    WALLET_URL,
+    NODE_ENV,
+    CYPRESS_EMAIL,
+} from '../config/secrets';
+import { sendMail } from '@thxnetwork/common/mail';
+import { logger } from '../util/logger';
+import TokenService from './TokenService';
+
+const mailTemplatePath = path.join(assetsPath, 'views', 'mail');
+
+function createOTP(account: AccountDocument) {
+    return account.email === CYPRESS_EMAIL
+        ? '00000'
+        : Array.from({ length: 5 })
+              .map(() => crypto.randomInt(0, 10))
+              .join('');
+}
+
+export class MailService {
+    static sendMail(to: string, subject: string, html: string, link = '') {
+        if (!AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY || NODE_ENV === 'test' || CYPRESS_EMAIL === to) {
+            logger.debug({ message: 'Not sending e-mail', link });
+            return;
+        }
+        sendMail(to, subject, html);
+    }
+
+    static async sendVerificationEmail(account: AccountDocument, email: string, returnUrl: string) {
+        const accessToken = createRandomToken();
+        const expiry = get24HoursExpiryTimestamp();
+        const token = await TokenService.setToken(account, {
+            kind: AccessTokenKind.VerifyEmail,
+            accessToken,
+            expiry,
+        });
+        const verifyURL = new URL(returnUrl);
+        verifyURL.pathname = '/verify_email';
+        verifyURL.searchParams.append('verifyEmailToken', token.accessTokenEncrypted);
+        verifyURL.searchParams.append('return_url', returnUrl);
+
+        const html = await ejs.renderFile(
+            path.join(mailTemplatePath, '/email-verify.ejs'),
+            { verifyURL: verifyURL.toString(), returnUrl, baseUrl: AUTH_URL },
+            { async: true },
+        );
+
+        this.sendMail(
+            email,
+            'Please complete the e-mail verification for your THX Account',
+            html,
+            verifyURL.toString(),
+        );
+    }
+
+    static async sendOTPMail(account: AccountDocument) {
+        const otp = createOTP(account);
+        const hashedOtp = await bcrypt.hash(otp, 10);
+        const html = await ejs.renderFile(
+            path.join(mailTemplatePath, 'email-otp.ejs'),
+            { otp, returnUrl: WALLET_URL, baseUrl: AUTH_URL },
+            { async: true },
+        );
+
+        this.sendMail(account.email, 'Request: Sign in', html);
+
+        await TokenService.setToken(account, {
+            kind: AccessTokenKind.Auth,
+            accessToken: hashedOtp,
+            expiry: Date.now() + 60 * 60 * 1000, // 60 minutes
+        });
+    }
+}
diff --git a/apps/auth/src/app/services/OAuthDiscordService.ts b/apps/auth/src/app/services/OAuthDiscordService.ts
new file mode 100644
index 000000000..c254d6f28
--- /dev/null
+++ b/apps/auth/src/app/services/OAuthDiscordService.ts
@@ -0,0 +1,103 @@
+import { AUTH_URL, DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET } from '../config/secrets';
+import { AccessTokenKind, OAuthDiscordScope } from '@thxnetwork/common/enums/AccessTokenKind';
+import { discordClient } from '../util/axios';
+import { Token, TokenDocument } from '../models/Token';
+import { IOAuthService } from './interfaces/IOAuthService';
+
+export default class DiscordService implements IOAuthService {
+    getLoginURL({ uid, scopes }: { uid: string; scopes: OAuthDiscordScope[] }): string {
+        const state = Buffer.from(JSON.stringify({ uid })).toString('base64');
+        const url = new URL('https://discord.com/oauth2/authorize');
+        url.searchParams.append('state', state);
+        url.searchParams.append('response_type', 'code');
+        url.searchParams.append('client_id', DISCORD_CLIENT_ID);
+        url.searchParams.append('redirect_uri', AUTH_URL + '/oidc/callback/discord');
+        url.searchParams.append('scope', scopes.join(' '));
+
+        return url.toString();
+    }
+
+    async requestToken(code: string) {
+        const body = new URLSearchParams();
+        body.append('code', code);
+        body.append('grant_type', 'authorization_code');
+        body.append('redirect_uri', AUTH_URL + '/oidc/callback/discord');
+        body.append('client_secret', DISCORD_CLIENT_SECRET);
+        body.append('client_id', DISCORD_CLIENT_ID);
+
+        const { data } = await discordClient({
+            url: 'https://discord.com/api/oauth2/token',
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/x-www-form-urlencoded',
+            },
+            data: body,
+        });
+        const user = await this.getUser(data.access_token);
+
+        return {
+            kind: AccessTokenKind.Discord,
+            accessToken: data.access_token,
+            refreshToken: data.refresh_token,
+            expiry: Date.now() + Number(data.expires_in) * 1000,
+            scopes: data.scope.split(' '),
+            userId: user.id,
+        };
+    }
+
+    async refreshToken(token: TokenDocument) {
+        const body = new URLSearchParams();
+        body.append('grant_type', 'refresh_token');
+        body.append('refresh_token', token.refreshToken);
+        body.append('client_secret', DISCORD_CLIENT_SECRET);
+        body.append('client_id', DISCORD_CLIENT_ID);
+
+        const { data } = await discordClient({
+            url: 'https://discord.com/api/oauth2/token',
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/x-www-form-urlencoded',
+            },
+            data: body,
+        });
+
+        return await Token.findByIdAndUpdate(
+            token._id,
+            {
+                accessToken: data.access_token,
+                refreshToken: data.refresh_token,
+                expiry: data.expires_in && Date.now() + Number(data.expires_in) * 1000,
+            },
+            { new: true },
+        );
+    }
+
+    async revokeToken(token: TokenDocument): Promise<void> {
+        const body = new URLSearchParams();
+        body.append('client_secret', DISCORD_CLIENT_SECRET);
+        body.append('client_id', DISCORD_CLIENT_ID);
+        body.append('token', token.accessToken);
+
+        await discordClient({
+            url: 'https://discord.com/api/oauth2/token/revoke',
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/x-www-form-urlencoded',
+            },
+            data: body,
+        });
+    }
+
+    private async getUser(accessToken: string) {
+        const { data } = await discordClient({
+            url: '/oauth2/@me',
+            method: 'GET',
+            headers: {
+                'Accept': 'application/json',
+                'Content-Type': 'application/json',
+                'Authorization': `Bearer ${accessToken}`,
+            },
+        });
+        return data.user;
+    }
+}
diff --git a/apps/auth/src/app/services/OAuthGithubService.ts b/apps/auth/src/app/services/OAuthGithubService.ts
new file mode 100644
index 000000000..b981e4b5a
--- /dev/null
+++ b/apps/auth/src/app/services/OAuthGithubService.ts
@@ -0,0 +1,77 @@
+import { URLSearchParams } from 'url';
+import { AUTH_URL, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET } from '@thxnetwork/auth/config/secrets';
+import { githubClient } from '../util/axios';
+import { AccessTokenKind, OAuthGithubScope } from '@thxnetwork/common/enums/AccessTokenKind';
+import { IOAuthService } from './interfaces/IOAuthService';
+import { TokenDocument } from '../models/Token';
+
+export default class GithubService implements IOAuthService {
+    getLoginURL({ uid, scopes }: { uid: string; scopes: OAuthGithubScope[] }) {
+        const state = Buffer.from(JSON.stringify({ uid })).toString('base64');
+        const url = new URL('https://github.com/login/oauth/authorize');
+        url.searchParams.append('state', state);
+        url.searchParams.append('allow_signup', 'true');
+        url.searchParams.append('client_id', GITHUB_CLIENT_ID);
+        url.searchParams.append('redirect_uri', AUTH_URL + '/oidc/callback/github');
+        url.searchParams.append('scope', scopes.join(' '));
+
+        return url.toString();
+    }
+
+    async requestToken(code: string) {
+        const { data } = await githubClient({
+            url: 'https://github.com/login/oauth/access_token',
+            method: 'POST',
+            data: {
+                code,
+                redirect_uri: AUTH_URL + '/oidc/callback/github',
+                client_secret: GITHUB_CLIENT_SECRET,
+                client_id: GITHUB_CLIENT_ID,
+            },
+        });
+
+        const search = new URLSearchParams(data);
+        const accessToken = search.get('access_token');
+        const refreshToken = search.get('refresh_token');
+        const expiresIn = search.get('expires_in');
+        const expiry = expiresIn && Date.now() + Number(expiresIn) * 1000;
+        const user = await this.getUser(accessToken);
+
+        return {
+            kind: AccessTokenKind.Github,
+            accessToken,
+            refreshToken,
+            expiry,
+            userId: user.id,
+        };
+    }
+
+    async refreshToken(token: TokenDocument) {
+        const { data } = await githubClient({
+            url: 'https://github.com/login/oauth/access_token',
+            method: 'POST',
+            data: {
+                refresh_token: token.refreshToken,
+                grant_type: 'authorization_code',
+                client_secret: GITHUB_CLIENT_SECRET,
+                client_id: GITHUB_CLIENT_ID,
+            },
+        });
+        return data;
+    }
+
+    revokeToken(token: TokenDocument): Promise<void> {
+        throw new Error('Method not implemented.');
+    }
+
+    private async getUser(accessToken: string) {
+        const { data } = await githubClient({
+            url: '/user',
+            method: 'GET',
+            headers: {
+                Authorization: 'Bearer ' + accessToken,
+            },
+        });
+        return data;
+    }
+}
diff --git a/apps/auth/src/app/services/OAuthTwitchService.ts b/apps/auth/src/app/services/OAuthTwitchService.ts
new file mode 100644
index 000000000..8787c9f32
--- /dev/null
+++ b/apps/auth/src/app/services/OAuthTwitchService.ts
@@ -0,0 +1,90 @@
+import { AUTH_URL, TWITCH_CLIENT_ID, TWITCH_CLIENT_SECRET } from '@thxnetwork/auth/config/secrets';
+import { AccessTokenKind, OAuthTwitchScope } from '@thxnetwork/common/enums/AccessTokenKind';
+import { twitchClient } from '../util/axios';
+import { IOAuthService } from './interfaces/IOAuthService';
+import { Token, TokenDocument } from '../models/Token';
+
+export default class TwitchService implements IOAuthService {
+    getLoginURL({ uid, scopes }: { uid: string; scopes: OAuthTwitchScope[] }) {
+        const state = Buffer.from(JSON.stringify({ uid })).toString('base64');
+        const url = new URL('https://id.twitch.tv/oauth2/authorize');
+        url.searchParams.append('state', state);
+        url.searchParams.append('response_type', 'code');
+        url.searchParams.append('force_verify', 'true');
+        url.searchParams.append('client_id', TWITCH_CLIENT_ID);
+        url.searchParams.append('redirect_uri', AUTH_URL + '/oidc/callback/twitch');
+        url.searchParams.append('scope', scopes.join(' '));
+
+        return url.toString();
+    }
+
+    async requestToken(code: string): Promise<Partial<TToken>> {
+        const body = new URLSearchParams();
+        body.append('code', code);
+        body.append('grant_type', 'authorization_code');
+        body.append('redirect_uri', AUTH_URL + '/oidc/callback/twitch');
+        body.append('client_secret', TWITCH_CLIENT_SECRET);
+        body.append('client_id', TWITCH_CLIENT_ID);
+
+        const { data } = await twitchClient({
+            url: 'https://id.twitch.tv/oauth2/token',
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/x-www-form-urlencoded',
+            },
+            data: body,
+        });
+
+        const user = await this.getUser(data.access_token);
+
+        return {
+            kind: AccessTokenKind.Twitch,
+            accessToken: data.access_token,
+            refreshToken: data.refresh_token,
+            expiry: Date.now() + Number(data.expires_in) * 1000,
+            userId: user.id,
+        };
+    }
+
+    async refreshToken(token: TokenDocument) {
+        const body = new URLSearchParams();
+        body.append('grant_type', 'refresh_token');
+        body.append('refresh_token', token.refreshToken);
+        body.append('client_secret', TWITCH_CLIENT_SECRET);
+        body.append('client_id', TWITCH_CLIENT_ID);
+
+        const { data } = await twitchClient({
+            url: 'https://id.twitch.tv/oauth2/token',
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/x-www-form-urlencoded',
+            },
+            data: body,
+        });
+
+        return await Token.findByIdAndUpdate(
+            token._id,
+            {
+                accessToken: data.access_token,
+                refreshToken: data.refresh_token,
+                expiry: Date.now() + Number(data.expires_in) * 1000,
+            },
+            { new: true },
+        );
+    }
+
+    revokeToken(token: TokenDocument): Promise<void> {
+        throw new Error('Method not implemented.');
+    }
+
+    private async getUser(accessToken: string) {
+        const { data } = await twitchClient({
+            url: 'https://id.twitch.tv/oauth2/userinfo',
+            method: 'GET',
+            headers: {
+                Authorization: `Bearer ${accessToken}`,
+            },
+        });
+        return data;
+    }
+}
diff --git a/apps/auth/src/app/services/OAuthTwitterService.ts b/apps/auth/src/app/services/OAuthTwitterService.ts
new file mode 100644
index 000000000..6b6082b1b
--- /dev/null
+++ b/apps/auth/src/app/services/OAuthTwitterService.ts
@@ -0,0 +1,122 @@
+import { URLSearchParams } from 'url';
+import { twitterClient } from '../util/axios';
+import { AUTH_URL, TWITTER_CLIENT_ID, TWITTER_CLIENT_SECRET } from '../config/secrets';
+import { AccessTokenKind, OAuthTwitterScope } from '@thxnetwork/common/enums/AccessTokenKind';
+import { IOAuthService } from '../services/interfaces/IOAuthService';
+import { Token, TokenDocument } from '../models/Token';
+import { logger } from '../util/logger';
+
+export default class TwitterService implements IOAuthService {
+    getLoginURL({ uid, scopes }: { uid: string; scopes: OAuthTwitterScope[] }): string {
+        const state = Buffer.from(JSON.stringify({ uid })).toString('base64');
+        const redirectURL = AUTH_URL + '/oidc/callback/twitter';
+        const authorizeURL = new URL('https://twitter.com/i/oauth2/authorize');
+
+        authorizeURL.searchParams.append('response_type', 'code');
+        authorizeURL.searchParams.append('client_id', TWITTER_CLIENT_ID);
+        authorizeURL.searchParams.append('redirect_uri', redirectURL);
+        authorizeURL.searchParams.append('scope', scopes.join(' '));
+        authorizeURL.searchParams.append('state', state);
+        authorizeURL.searchParams.append('code_challenge', 'challenge');
+        authorizeURL.searchParams.append('code_challenge_method', 'plain');
+
+        return authorizeURL.toString();
+    }
+
+    async requestToken(code: string): Promise<Partial<TToken>> {
+        const authHeader = 'Basic ' + Buffer.from(`${TWITTER_CLIENT_ID}:${TWITTER_CLIENT_SECRET}`).toString('base64');
+        const body = new URLSearchParams();
+        body.append('code', code);
+        body.append('grant_type', 'authorization_code');
+        body.append('client_id', TWITTER_CLIENT_ID);
+        body.append('redirect_uri', AUTH_URL + '/oidc/callback/twitter');
+        body.append('code_verifier', 'challenge');
+
+        const { data } = await twitterClient({
+            url: '/oauth2/token',
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/x-www-form-urlencoded',
+                'Authorization': authHeader,
+            },
+            data: body,
+        });
+        const expiry = data.expires_in ? Date.now() + Number(data.expires_in) * 1000 : undefined;
+        const user = await this.getUser(data.access_token);
+
+        return {
+            kind: AccessTokenKind.Twitter,
+            accessToken: data.access_token,
+            refreshToken: data.refresh_token,
+            expiry,
+            scopes: data.scope.split(' '),
+            userId: user.id,
+            metadata: {
+                name: user.name,
+                username: user.username,
+            },
+        };
+    }
+
+    async refreshToken(token: TokenDocument) {
+        const authHeader = 'Basic ' + Buffer.from(`${TWITTER_CLIENT_ID}:${TWITTER_CLIENT_SECRET}`).toString('base64');
+        const body = new URLSearchParams();
+        body.append('refresh_token', token.refreshToken);
+        body.append('grant_type', 'refresh_token');
+        body.append('client_id', TWITTER_CLIENT_ID);
+
+        const { data } = await twitterClient({
+            url: '/oauth2/token',
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/x-www-form-urlencoded',
+                'Authorization': authHeader,
+            },
+            data: body,
+        });
+
+        return await Token.findByIdAndUpdate(
+            token._id,
+            {
+                accessToken: data.access_token,
+                refreshToken: data.refresh_token,
+                expiry: Date.now() + Number(data.expires_in) * 1000,
+            },
+            { new: true },
+        );
+    }
+
+    async revokeToken(token: TokenDocument): Promise<void> {
+        const body = new URLSearchParams();
+        body.append('token', token.accessToken);
+        body.append('token_type_hint', 'access_token');
+        body.append('client_id', TWITTER_CLIENT_ID);
+        try {
+            await twitterClient({
+                url: '/oauth2/revoke',
+                method: 'POST',
+                headers: {
+                    'Content-Type': 'application/x-www-form-urlencoded',
+                },
+                data: body,
+            });
+        } catch (error) {
+            // Revocation request is failing with a 401, insufficient docs make it hard to
+            // gues what the exact payload should be, so for now we fail silently so the
+            // token can be removed from storage.
+            logger.error(error);
+        }
+    }
+
+    private async getUser(accessToken: string) {
+        const { data } = await twitterClient({
+            url: '/users/me',
+            method: 'GET',
+            headers: {
+                Authorization: `Bearer ${accessToken}`,
+            },
+        });
+
+        return data.data;
+    }
+}
diff --git a/apps/auth/src/app/services/OAuthYouTubeService.ts b/apps/auth/src/app/services/OAuthYouTubeService.ts
new file mode 100644
index 000000000..3bcf0e491
--- /dev/null
+++ b/apps/auth/src/app/services/OAuthYouTubeService.ts
@@ -0,0 +1,87 @@
+import axios from 'axios';
+import { google } from 'googleapis';
+import { AccessTokenKind, OAuthGoogleScope } from '@thxnetwork/common/enums/AccessTokenKind';
+import { AUTH_URL, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET } from '../config/secrets';
+import { parseJwt } from '../util/jwt';
+import { Token, TokenDocument } from '../models/Token';
+import { IOAuthService } from '../services/interfaces/IOAuthService';
+import { logger } from '../util/logger';
+
+const client = new google.auth.OAuth2(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, AUTH_URL + '/oidc/callback/google');
+
+google.options({ auth: client });
+
+export default class YouTubeService implements IOAuthService {
+    getLoginURL({ uid, scopes }: { uid: string; scopes: OAuthGoogleScope[] }) {
+        const state = Buffer.from(JSON.stringify({ uid })).toString('base64');
+        return client.generateAuthUrl({
+            state,
+            access_type: 'offline',
+            scope: scopes,
+        });
+    }
+
+    async requestToken(code: string) {
+        const { tokens } = await client.getToken(code);
+        const expiry = tokens.expiry_date ? Date.now() + Number(tokens.expiry_date) : undefined;
+        const claims = await parseJwt(tokens.id_token);
+
+        return {
+            kind: AccessTokenKind.Google,
+            expiry,
+            scopes: tokens.scope.split(' ') as OAuthGoogleScope[],
+            accessToken: tokens.access_token,
+            refreshToken: tokens.refresh_token,
+            userId: claims.sub,
+        };
+    }
+
+    async refreshToken(token: TokenDocument): Promise<TokenDocument> {
+        const refreshToken = await this.getRefreshToken(token);
+
+        client.setCredentials({
+            refresh_token: refreshToken,
+            access_token: token.accessToken,
+        });
+
+        const googleToken = await client.getAccessToken();
+        const { expiry_date, sub, scopes } = await client.getTokenInfo(googleToken.token);
+
+        return await Token.findByIdAndUpdate(
+            token._id,
+            {
+                accessToken: googleToken,
+                refreshToken,
+                expiry: expiry_date,
+                scope: scopes.join(' '),
+                userId: sub,
+            },
+            { new: true },
+        );
+    }
+
+    async revokeToken(token: TokenDocument): Promise<void> {
+        try {
+            const url = new URL('https://oauth2.googleapis.com/revoke');
+            if (token.accessToken) {
+                await axios({ url: url.toString(), method: 'POST', params: { token: token.accessToken } });
+            }
+            if (token.refreshToken) {
+                await axios({ url: url.toString(), method: 'POST', params: { token: token.refreshToken } });
+            }
+        } catch (error) {
+            logger.error(error);
+        }
+    }
+
+    // We only get one refreshToken from google and should use it for
+    // all token variations we store
+    private async getRefreshToken({ sub }: TokenDocument) {
+        const [token] = await Token.find({
+            scope: { $in: ['google', 'youtube-view', 'youtube-manage'] },
+            refreshToken: { $exists: true, $ne: '' },
+            sub,
+        });
+        return token && token.refreshToken;
+    }
+}
diff --git a/apps/auth/src/app/services/TokenService.ts b/apps/auth/src/app/services/TokenService.ts
new file mode 100644
index 000000000..2c568b73f
--- /dev/null
+++ b/apps/auth/src/app/services/TokenService.ts
@@ -0,0 +1,112 @@
+import { AccessTokenKind, OAuthScope } from '@thxnetwork/common/enums';
+import { Token, TokenDocument } from '../models/Token';
+import { AccountDocument } from '../models/Account';
+import { decryptString } from '../util/decrypt';
+import { SECURE_KEY } from '../config/secrets';
+import { IOAuthService } from './interfaces/IOAuthService';
+import DiscordService from './OAuthDiscordService';
+import TwitterService from './OAuthTwitterService';
+import TwitchService from './OAuthTwitchService';
+import YouTubeService from './OAuthYouTubeService';
+import GithubService from './OAuthGithubService';
+import { logger } from '../util/logger';
+
+const serviceMap: { [variant: string]: IOAuthService } = {
+    [AccessTokenKind.Twitter]: new TwitterService(),
+    [AccessTokenKind.Google]: new YouTubeService(),
+    [AccessTokenKind.Discord]: new DiscordService(),
+    [AccessTokenKind.Twitch]: new TwitchService(),
+    [AccessTokenKind.Github]: new GithubService(),
+};
+
+export default class TokenService {
+    static getLoginURL({ kind, uid, scopes }: { kind: AccessTokenKind; uid: string; scopes: OAuthScope[] }) {
+        return serviceMap[kind].getLoginURL({ uid, scopes });
+    }
+
+    static request({ kind, code }: { kind: AccessTokenKind; code: string }) {
+        return serviceMap[kind].requestToken(code);
+    }
+
+    static revoke(token: TokenDocument) {
+        return serviceMap[token.kind].revokeToken(token);
+    }
+
+    static async refresh(token: TokenDocument) {
+        // Return token if there is no expiry or no refreshtoken
+        if (!token || !token.expiry || !token.refreshToken) return token;
+
+        // Check if token is expired
+        const isExpired = Date.now() > token.expiry;
+        if (!isExpired) return token;
+
+        try {
+            // If so, refresh the token and return
+            return await serviceMap[token.kind].refreshToken(token);
+        } catch (error) {
+            logger.error(error);
+            logger.error('Token refresh failed');
+            return token;
+        }
+    }
+
+    static async getToken(account: AccountDocument, kind: AccessTokenKind): Promise<TokenDocument> {
+        const token = await Token.findOne({ sub: account._id, kind });
+        if (!token) return;
+
+        const { accessTokenEncrypted, refreshTokenEncrypted } = token;
+        const accessToken = accessTokenEncrypted && decryptString(accessTokenEncrypted, SECURE_KEY);
+        const refreshToken = refreshTokenEncrypted && decryptString(refreshTokenEncrypted, SECURE_KEY);
+        const refreshedToken = await this.refresh(token);
+
+        return { ...refreshedToken.toJSON(), accessToken, refreshToken };
+    }
+
+    static async connect(account: AccountDocument, token: Partial<TokenDocument>) {
+        // Check if any other accounts are using this token
+        const tokens = await Token.find({ kind: token.kind, userId: token.userId, sub: { $ne: String(account._id) } });
+        if (tokens.length) {
+            throw new Error('Already connect to another THX account! Please disconnect that account first.');
+        }
+
+        // Check if this account already has a token but with another userId
+        const existingToken = await Token.findOne({ sub: String(account._id), kind: token.kind });
+        if (existingToken && existingToken.userId !== token.userId) {
+            throw new Error(
+                'Already connected to a different account from this provider! Please disconnect that account first.',
+            );
+        }
+
+        // Store the token for the account
+        return await this.setToken(account, token);
+    }
+
+    static async setToken(account: AccountDocument, token: Partial<TokenDocument>) {
+        // Store the token for the new account
+        return Token.findOneAndUpdate(
+            { sub: account._id, kind: token.kind },
+            { ...token, sub: account._id },
+            { upsert: true, new: true },
+        );
+    }
+
+    static async unsetToken(account: AccountDocument, kind: AccessTokenKind) {
+        const token = await this.getToken(account, kind);
+
+        // Revoke access at token provider if token has scopes
+        if (token.scopes.length) {
+            await this.revoke(token);
+        }
+
+        // Remove from storage
+        return this.remove({ sub: account._id, kind });
+    }
+
+    static remove({ sub, kind }: { sub: string; kind: AccessTokenKind }) {
+        return Token.findOneAndDelete({ sub, kind });
+    }
+
+    static findTokenForUserId(userId: string, kind: AccessTokenKind) {
+        return Token.findOne({ userId, kind });
+    }
+}
diff --git a/apps/auth/src/app/services/interfaces/IOAuthService.ts b/apps/auth/src/app/services/interfaces/IOAuthService.ts
new file mode 100644
index 000000000..eae3538e9
--- /dev/null
+++ b/apps/auth/src/app/services/interfaces/IOAuthService.ts
@@ -0,0 +1,9 @@
+import { OAuthScope } from '@thxnetwork/common/enums';
+import { TokenDocument } from '../../models/Token';
+
+export interface IOAuthService {
+    getLoginURL(options: { uid: string; scopes: OAuthScope[] }): string;
+    requestToken(code: string): Promise<Partial<TokenDocument>>;
+    refreshToken(token: TokenDocument): Promise<TokenDocument>;
+    revokeToken(token: TokenDocument): Promise<void>;
+}
diff --git a/apps/auth/src/app/types/CommonOauthLoginOptions.ts b/apps/auth/src/app/types/CommonOauthLoginOptions.ts
new file mode 100644
index 000000000..018f5be92
--- /dev/null
+++ b/apps/auth/src/app/types/CommonOauthLoginOptions.ts
@@ -0,0 +1,6 @@
+type CommonOauthLoginOptions = Partial<{
+    scope?: string[];
+    redirectUrl?: string;
+}>;
+
+export default CommonOauthLoginOptions;
diff --git a/apps/auth/src/app/types/definitions/augment-express-request.d.ts b/apps/auth/src/app/types/definitions/augment-express-request.d.ts
new file mode 100644
index 000000000..d107d8f1d
--- /dev/null
+++ b/apps/auth/src/app/types/definitions/augment-express-request.d.ts
@@ -0,0 +1,11 @@
+export {};
+
+declare global {
+    namespace Express {
+        interface Request {
+            origin?: string;
+            auth?: any;
+            interaction?: any;
+        }
+    }
+}
diff --git a/apps/auth/src/app/types/definitions/cors.d.ts b/apps/auth/src/app/types/definitions/cors.d.ts
new file mode 100644
index 000000000..dff30ae18
--- /dev/null
+++ b/apps/auth/src/app/types/definitions/cors.d.ts
@@ -0,0 +1,2 @@
+declare module 'cors';
+declare module 'host-validation';
diff --git a/apps/auth/src/app/types/definitions/jsonwebtoken.d.ts b/apps/auth/src/app/types/definitions/jsonwebtoken.d.ts
new file mode 100644
index 000000000..0493c0083
--- /dev/null
+++ b/apps/auth/src/app/types/definitions/jsonwebtoken.d.ts
@@ -0,0 +1 @@
+declare module 'jsonwebtoken';
diff --git a/apps/auth/src/app/types/definitions/morgan-json.d.ts b/apps/auth/src/app/types/definitions/morgan-json.d.ts
new file mode 100644
index 000000000..2b861c8c3
--- /dev/null
+++ b/apps/auth/src/app/types/definitions/morgan-json.d.ts
@@ -0,0 +1 @@
+declare module 'morgan-json';
diff --git a/apps/auth/src/app/types/definitions/morgan.d.ts b/apps/auth/src/app/types/definitions/morgan.d.ts
new file mode 100644
index 000000000..0b6637ede
--- /dev/null
+++ b/apps/auth/src/app/types/definitions/morgan.d.ts
@@ -0,0 +1 @@
+declare module 'morgan';
diff --git a/apps/auth/src/app/types/definitions/qrcode.d.ts b/apps/auth/src/app/types/definitions/qrcode.d.ts
new file mode 100644
index 000000000..c9f8a23a7
--- /dev/null
+++ b/apps/auth/src/app/types/definitions/qrcode.d.ts
@@ -0,0 +1 @@
+declare module 'qrcode';
diff --git a/apps/auth/src/app/types/definitions/swagger-ui-express.d.ts b/apps/auth/src/app/types/definitions/swagger-ui-express.d.ts
new file mode 100644
index 000000000..d7910fe9e
--- /dev/null
+++ b/apps/auth/src/app/types/definitions/swagger-ui-express.d.ts
@@ -0,0 +1 @@
+declare module 'swagger-ui-express';
diff --git a/apps/auth/src/app/types/enums/chainId.ts b/apps/auth/src/app/types/enums/chainId.ts
new file mode 100644
index 000000000..1d52711a1
--- /dev/null
+++ b/apps/auth/src/app/types/enums/chainId.ts
@@ -0,0 +1,6 @@
+export enum ChainId {
+    Hardhat = 31337,
+    PolygonMumbai = 80001,
+    Polygon = 137,
+    PolygonZK = 1101,
+}
diff --git a/apps/auth/src/app/types/index.ts b/apps/auth/src/app/types/index.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/auth/src/app/util/adapter.ts b/apps/auth/src/app/util/adapter.ts
new file mode 100644
index 000000000..d1bb40bca
--- /dev/null
+++ b/apps/auth/src/app/util/adapter.ts
@@ -0,0 +1,106 @@
+import { snakeCase } from 'lodash';
+import db from './database';
+
+const grantable = new Set(['access_token', 'authorization_code', 'refresh_token', 'device_code']);
+
+let DB: any;
+
+db.connection.once('open', async () => {
+    DB = db.connection.getClient().db();
+});
+
+class CollectionSet extends Set {
+    add(name: string): any {
+        const nu = this.has(name);
+        super.add(name);
+        if (!nu) {
+            DB.collection(name)
+                .createIndexes([
+                    ...(grantable.has(name)
+                        ? [
+                              {
+                                  key: { 'payload.grantId': 1 },
+                              },
+                          ]
+                        : []),
+                    ...(name === 'device_code'
+                        ? [
+                              {
+                                  key: { 'payload.userCode': 1 },
+                                  unique: true,
+                              },
+                          ]
+                        : []),
+                    ...(name === 'session'
+                        ? [
+                              {
+                                  key: { 'payload.uid': 1 },
+                                  unique: true,
+                              },
+                          ]
+                        : []),
+                    {
+                        key: { expiresAt: 1 },
+                        expireAfterSeconds: 0,
+                    },
+                ])
+                .catch(console.error); // eslint-disable-line no-console
+        }
+    }
+}
+
+const collections = new CollectionSet();
+
+export default class MongoAdapter {
+    name: string;
+
+    constructor(name: string) {
+        this.name = snakeCase(name);
+
+        collections.add(this.name);
+    }
+
+    coll() {
+        return DB.collection(this.name);
+    }
+
+    async upsert(_id: string, payload: any, expiresIn: number) {
+        const expiresAt = expiresIn ? new Date(Date.now() + expiresIn * 1000) : null;
+        await this.coll().updateOne(
+            { _id },
+            { $set: { payload, ...(expiresAt ? { expiresAt } : undefined) } },
+            { upsert: true },
+        );
+    }
+
+    async find(_id: string) {
+        const result = await this.coll().find({ _id }, { payload: 1 }).limit(1).next();
+        if (!result) return undefined;
+        return result.payload;
+    }
+    async findByUserCode(userCode: string) {
+        const result = await this.coll().find({ 'payload.userCode': userCode }, { payload: 1 }).limit(1).next();
+
+        if (!result) return undefined;
+        return result.payload;
+    }
+
+    async findByUid(uid: string) {
+        const result = await this.coll().find({ 'payload.uid': uid }, { payload: 1 }).limit(1).next();
+
+        if (!result) return undefined;
+        return result.payload;
+    }
+
+    async destroy(_id: string) {
+        await this.coll().deleteOne({ _id });
+    }
+
+    async revokeByGrantId(grantId: string) {
+        await this.coll().deleteMany({ 'payload.grantId': grantId });
+    }
+
+    async consume(_id: string) {
+        await this.coll().findOneAndUpdate({ _id }, { $set: { 'payload.consumed': Math.floor(Date.now() / 1000) } });
+    }
+}
diff --git a/apps/auth/src/app/util/api.ts b/apps/auth/src/app/util/api.ts
new file mode 100644
index 000000000..e88119b52
--- /dev/null
+++ b/apps/auth/src/app/util/api.ts
@@ -0,0 +1,50 @@
+import axios, { AxiosRequestConfig } from 'axios';
+import { URLSearchParams } from 'url';
+
+import { THXError } from './errors';
+import { API_URL, AUTH_CLIENT_ID, AUTH_CLIENT_SECRET, AUTH_URL } from '../config/secrets';
+
+class ApiAccesTokenRequestError extends THXError {
+    message = 'API access token request failed';
+}
+
+let apiAccessToken = '';
+let apiAccessTokenExpired = 0;
+
+async function requestAuthAccessToken() {
+    const data = new URLSearchParams();
+    data.append('grant_type', 'client_credentials');
+    data.append('resource', API_URL);
+    data.append('scope', 'openid brands:read claims:read wallets:read wallets:write pools:write pools:read');
+    const r = await axios({
+        baseURL: AUTH_URL,
+        url: '/token',
+        method: 'POST',
+        headers: {
+            'Content-Type': 'application/x-www-form-urlencoded',
+            'Authorization': 'Basic ' + Buffer.from(`${AUTH_CLIENT_ID}:${AUTH_CLIENT_SECRET}`).toString('base64'),
+        },
+        data,
+    });
+
+    if (r.status !== 200) throw new ApiAccesTokenRequestError();
+    return r.data;
+}
+
+export async function getAuthAccessToken() {
+    if (Date.now() > apiAccessTokenExpired) {
+        const { access_token, expires_in } = await requestAuthAccessToken();
+        apiAccessToken = access_token;
+        apiAccessTokenExpired = Date.now() + expires_in * 1000;
+    }
+
+    return `Bearer ${apiAccessToken}`;
+}
+
+export async function apiClient(config: AxiosRequestConfig) {
+    const authHeader = await getAuthAccessToken();
+    if (!config.headers) config.headers = {};
+    config.headers['Authorization'] = authHeader;
+    config.baseURL = API_URL;
+    return axios(config);
+}
diff --git a/apps/auth/src/app/util/axios.ts b/apps/auth/src/app/util/axios.ts
new file mode 100644
index 000000000..5a240d9cb
--- /dev/null
+++ b/apps/auth/src/app/util/axios.ts
@@ -0,0 +1,43 @@
+import axios, { AxiosRequestConfig } from 'axios';
+import {
+    DISCORD_API_ENDPOINT,
+    GITHUB_API_ENDPOINT,
+    TWITCH_API_ENDPOINT,
+    TWITTER_API_ENDPOINT,
+} from '../config/secrets';
+
+export async function twitterClient(config: AxiosRequestConfig) {
+    try {
+        const client = axios.create({ ...config, baseURL: TWITTER_API_ENDPOINT });
+        return await client(config);
+    } catch (error) {
+        throw error.response;
+    }
+}
+
+export async function discordClient(config: AxiosRequestConfig) {
+    try {
+        const client = axios.create({ ...config, baseURL: DISCORD_API_ENDPOINT });
+        return await client(config);
+    } catch (error) {
+        throw error.response;
+    }
+}
+
+export async function githubClient(config: AxiosRequestConfig) {
+    try {
+        const client = axios.create({ ...config, baseURL: GITHUB_API_ENDPOINT });
+        return await client(config);
+    } catch (error) {
+        throw error.response;
+    }
+}
+
+export async function twitchClient(config: AxiosRequestConfig) {
+    try {
+        const client = axios.create({ ...config, baseURL: TWITCH_API_ENDPOINT });
+        return await client(config);
+    } catch (error) {
+        throw error.response;
+    }
+}
diff --git a/apps/auth/src/app/util/database.ts b/apps/auth/src/app/util/database.ts
new file mode 100644
index 000000000..a725f0bf2
--- /dev/null
+++ b/apps/auth/src/app/util/database.ts
@@ -0,0 +1,53 @@
+import mongoose from 'mongoose';
+import bluebird from 'bluebird';
+import { logger } from './logger';
+
+(mongoose as any).Promise = bluebird;
+
+const connect = async (url: string) => {
+    mongoose.connection.on('error', (err) => {
+        logger.error(`MongoDB connection error. Please make sure MongoDB is running. ${err}`);
+    });
+
+    mongoose.connection.on('reconnectFailed', () => {
+        logger.error('Unable to recoonect to MongoDB');
+        process.exit();
+    });
+
+    mongoose.connection.on('open', () => {
+        logger.info(`MongoDB successfully connected to ${url.split('@')[1]}`);
+    });
+
+    if (mongoose.connection.readyState === 0) {
+        await mongoose.connect(url);
+    }
+};
+
+const truncate = async () => {
+    if (mongoose.connection.readyState !== 0) {
+        const { collections } = mongoose.connection;
+        const promises = Object.keys(collections).map((collection) =>
+            mongoose.connection.collection(collection).deleteMany({}),
+        );
+
+        await Promise.all(promises);
+    }
+};
+
+const readyState = () => {
+    return mongoose.connection.readyState;
+};
+
+const disconnect = async () => {
+    if (mongoose.connection.readyState !== 0) {
+        await mongoose.disconnect();
+    }
+};
+
+export default {
+    connect,
+    truncate,
+    disconnect,
+    readyState,
+    connection: mongoose.connection,
+};
diff --git a/apps/auth/src/app/util/decrypt.ts b/apps/auth/src/app/util/decrypt.ts
new file mode 100644
index 000000000..021c34368
--- /dev/null
+++ b/apps/auth/src/app/util/decrypt.ts
@@ -0,0 +1,44 @@
+import crypto from 'crypto';
+
+const ALGORITHM_NAME = 'aes-128-gcm';
+const ALGORITHM_NONCE_SIZE = 12;
+const ALGORITHM_TAG_SIZE = 16;
+const ALGORITHM_KEY_SIZE = 16;
+const PBKDF2_NAME = 'sha256';
+const PBKDF2_SALT_SIZE = 16;
+const PBKDF2_ITERATIONS = 1000;
+
+function decrypt(ciphertextAndNonce: any, key: any) {
+    // Create buffers of nonce, ciphertext and tag.
+    const nonce = ciphertextAndNonce.slice(0, ALGORITHM_NONCE_SIZE);
+    const ciphertext = ciphertextAndNonce.slice(ALGORITHM_NONCE_SIZE, ciphertextAndNonce.length - ALGORITHM_TAG_SIZE);
+    const tag = ciphertextAndNonce.slice(ciphertext.length + ALGORITHM_NONCE_SIZE);
+
+    // Create the cipher instance.
+    const cipher = crypto.createDecipheriv(ALGORITHM_NAME, key, nonce);
+
+    // Decrypt and return result.
+    cipher.setAuthTag(tag);
+    return Buffer.concat([cipher.update(ciphertext), cipher.final()]);
+}
+
+export function decryptString(base64CiphertextAndNonceAndSalt: any, password: any) {
+    // Decode the base64.
+    const ciphertextAndNonceAndSalt = Buffer.from(base64CiphertextAndNonceAndSalt, 'base64');
+
+    // Create buffers of salt and ciphertextAndNonce.
+    const salt = ciphertextAndNonceAndSalt.slice(0, PBKDF2_SALT_SIZE);
+    const ciphertextAndNonce = ciphertextAndNonceAndSalt.slice(PBKDF2_SALT_SIZE);
+
+    // Derive the key using PBKDF2.
+    const key = crypto.pbkdf2Sync(
+        Buffer.from(password, 'utf8'),
+        salt,
+        PBKDF2_ITERATIONS,
+        ALGORITHM_KEY_SIZE,
+        PBKDF2_NAME,
+    );
+
+    // Decrypt and return result.
+    return decrypt(ciphertextAndNonce, key).toString('utf8');
+}
diff --git a/apps/auth/src/app/util/encrypt.ts b/apps/auth/src/app/util/encrypt.ts
new file mode 100644
index 000000000..831b470c0
--- /dev/null
+++ b/apps/auth/src/app/util/encrypt.ts
@@ -0,0 +1,42 @@
+// https://github.com/luke-park/SecureCompatibleEncryptionExamples/blob/master/JavaScript/SCEE-Node.js
+import crypto from 'crypto';
+
+const ALGORITHM_NAME = 'aes-128-gcm';
+const ALGORITHM_NONCE_SIZE = 12;
+const ALGORITHM_KEY_SIZE = 16;
+const PBKDF2_NAME = 'sha256';
+const PBKDF2_SALT_SIZE = 16;
+const PBKDF2_ITERATIONS = 1000;
+
+function encrypt(plaintext: any, key: any) {
+    // Generate a 96-bit nonce using a CSPRNG.
+    const nonce = crypto.randomBytes(ALGORITHM_NONCE_SIZE);
+
+    // Create the cipher instance.
+    const cipher = crypto.createCipheriv(ALGORITHM_NAME, key, nonce);
+
+    // Encrypt and prepend nonce.
+    const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
+
+    return Buffer.concat([nonce, ciphertext, cipher.getAuthTag()]);
+}
+
+export function encryptString(plaintext: string, password: string) {
+    // Generate a 128-bit salt using a CSPRNG.
+    const salt = crypto.randomBytes(PBKDF2_SALT_SIZE);
+
+    // Derive a key using PBKDF2.
+    const key = crypto.pbkdf2Sync(
+        Buffer.from(password, 'utf8'),
+        salt,
+        PBKDF2_ITERATIONS,
+        ALGORITHM_KEY_SIZE,
+        PBKDF2_NAME,
+    );
+
+    // Encrypt and prepend salt.
+    const ciphertextAndNonceAndSalt = Buffer.concat([salt, encrypt(Buffer.from(plaintext, 'utf8'), key)]);
+
+    // Return as base64 string.
+    return ciphertextAndNonceAndSalt.toString('base64');
+}
diff --git a/apps/auth/src/app/util/errors.ts b/apps/auth/src/app/util/errors.ts
new file mode 100644
index 000000000..c2c8a69a2
--- /dev/null
+++ b/apps/auth/src/app/util/errors.ts
@@ -0,0 +1,88 @@
+class THXError extends Error {
+    message: string;
+
+    constructor(message?: string) {
+        super(message);
+        this.name = this.constructor.name;
+        Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
+    }
+}
+
+class THXHttpError extends THXError {
+    status: number;
+    constructor(message?: string, status?: number) {
+        super(message);
+        if (status) {
+            this.status = status;
+        }
+    }
+}
+
+class BadRequestError extends THXHttpError {
+    status = 400;
+    constructor(message?: string) {
+        super(message || 'Bad Request');
+    }
+}
+
+class UnauthorizedError extends THXHttpError {
+    status = 401;
+    constructor(message?: string) {
+        super(message || 'Unauthorized');
+    }
+}
+
+class ForbiddenError extends THXHttpError {
+    status = 403;
+    constructor(message?: string) {
+        super(message || 'Forbidden');
+    }
+}
+
+class NotFoundError extends THXHttpError {
+    status = 404;
+    constructor(message?: string) {
+        super(message || 'Not Found');
+    }
+}
+
+class UnprocessableEntityError extends THXHttpError {
+    status = 422;
+    constructor(message?: string) {
+        super(message || 'Unprocessable Entity');
+    }
+}
+
+class InternalServerError extends THXHttpError {
+    status = 500;
+    constructor(message?: string) {
+        super(message || 'Internal Server Error');
+    }
+}
+
+class NotImplementedError extends THXHttpError {
+    status = 501;
+    constructor(message?: string) {
+        super(message || 'Not Implemented');
+    }
+}
+
+class BadGatewayError extends THXHttpError {
+    status = 502;
+    constructor(message?: string) {
+        super(message || 'Bad Gateway');
+    }
+}
+
+export {
+    THXError,
+    THXHttpError,
+    BadRequestError,
+    UnauthorizedError,
+    ForbiddenError,
+    NotFoundError,
+    UnprocessableEntityError,
+    NotImplementedError,
+    BadGatewayError,
+    InternalServerError,
+};
diff --git a/apps/auth/src/app/util/healthcheck.ts b/apps/auth/src/app/util/healthcheck.ts
new file mode 100644
index 000000000..5c1446e0c
--- /dev/null
+++ b/apps/auth/src/app/util/healthcheck.ts
@@ -0,0 +1,39 @@
+import newrelic from 'newrelic';
+import { HealthCheck } from '@godaddy/terminus';
+import { config, status } from 'migrate-mongo';
+import { connection } from 'mongoose';
+
+import migrateMongoConfig from '../config/migrate-mongo';
+
+const dbConnected = async () => {
+    // https://mongoosejs.com/docs/api.html#connection_Connection-readyState
+    const { readyState } = connection;
+    // ERR_CONNECTING_TO_MONGO
+    if (readyState === 0 || readyState === 3) {
+        throw new Error('Mongoose has disconnected');
+    }
+    // CONNECTING_TO_MONGO
+    if (readyState === 2) {
+        throw new Error('Mongoose is connecting');
+    }
+    // CONNECTED_TO_MONGO
+    return;
+};
+
+const migrationsApplied = async () => {
+    config.set(migrateMongoConfig);
+
+    const pendingMigrations = (await status(connection.db as any)).filter(
+        (migration) => migration.appliedAt === 'PENDING',
+    );
+    if (pendingMigrations.length > 0) {
+        throw new Error('Not all migrations applied');
+    }
+
+    return;
+};
+
+export const healthCheck: HealthCheck = () => {
+    newrelic.getTransaction().ignore();
+    return Promise.all([dbConnected(), migrationsApplied()]);
+};
diff --git a/apps/auth/src/app/util/helmet.ts b/apps/auth/src/app/util/helmet.ts
new file mode 100644
index 000000000..76aca8617
--- /dev/null
+++ b/apps/auth/src/app/util/helmet.ts
@@ -0,0 +1,41 @@
+import helmet from 'helmet';
+// import { AUTH_URL, DASHBOARD_URL, WALLET_URL } from '@thxnetwork/auth/config/secrets';
+
+export const helmetInstance = helmet({
+    // contentSecurityPolicy: {
+    //     directives: {
+    //         defaultSrc: [AUTH_URL, "'unsafe-eval'", "'unsafe-inline'"],
+    //         frameSrc: [AUTH_URL, WALLET_URL, DASHBOARD_URL],
+    //         frameAncestors: [WALLET_URL, DASHBOARD_URL],
+    //         fontSrc: ['https://fonts.gstatic.com', 'https://ka-f.fontawesome.com/'],
+    //         connectSrc: ['https://ka-f.fontawesome.com'],
+    //         scriptSrcElem: [
+    //             AUTH_URL,
+    //             'https://www.googletagmanager.com',
+    //             'https://kit.fontawesome.com',
+    //             'https://cdn.jsdelivr.net',
+    //             'https://unpkg.com/',
+    //             'https://cdnjs.cloudflare.com',
+    //             "'sha256-PEI/gdNohg23HbZboqauC7uLjfrpcON9Z4W9IurYRxk='",
+    //             "'sha256-jOpZSqrqP85EQ9xzce9PQ0EFR3DhpJcbc+vVR1OQLHQ='",
+    //             "'sha256-5+pexDB9ERu/BGJRRg/9bZuuZwHoAYgdA9L6UuOaIPY='",
+    //             "'sha256-tHn9v4E9xZmG7Eh4CSF7CHyPU7kSwiu32J8PimHwftU='",
+    //         ],
+    //         styleSrcElem: [
+    //             AUTH_URL,
+    //             'https://fonts.googleapis.com',
+    //             'https://ka-f.fontawesome.com',
+    //             "'sha256-uCITVBkyNmwuSQXzSNUuRx7G7+1kS2zWJ9SjHF0W2QA='",
+    //             "'sha256-bepHRYpM181zEsx4ClPGLgyLPMyNCxPBrA6m49/Ozqg='",
+    //             "'sha256-ZL58hL5KbUHBRnMK797rN7IR+Tg9Aw61ddJ/rmxn1KM='",
+    //             "'sha256-75mE4wfpMmhCBnDZSF3PLGDQFzUteIHYrgFoOGlCMQw='",
+    //         ],
+    //     },
+    // },
+    contentSecurityPolicy: false,
+    hidePoweredBy: true,
+    frameguard: false,
+    referrerPolicy: {
+        policy: ['origin'],
+    },
+});
diff --git a/apps/auth/src/app/util/jest/constants.ts b/apps/auth/src/app/util/jest/constants.ts
new file mode 100644
index 000000000..c53523f68
--- /dev/null
+++ b/apps/auth/src/app/util/jest/constants.ts
@@ -0,0 +1,91 @@
+import jwt from 'jsonwebtoken';
+import { AUTH_URL } from '../../config/secrets';
+
+const privateKey = `-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAwaZ3afW0/zYy3HfJwAAr83PDdZvADuSJ6jTZk1+jprdHdG6P
+zH9XaB6xhzvwTIJFcWuREkNSC06MDLCuvmZ8fj93FcNaZ2ZJ0LFvY4SODMDqFekE
+5vD2Y15aSI2Y69qwKlVLphvEEXJ/FRqIHQX9wwCtwVsnqcLt/f5aNWRHyk2jwhz7
+IBm+dLu9/CV8AsvE5ddgOYYbNk+SMCjznESZcMg1KRzbdawnOklzloc+Q0iCxQK7
+022ukVxFbmT7U1hTVOTOzrruqBxptPDiutkKfOXebzYyZodlFFL5MWcatCWS3XL5
+1KBIeKWny5mExZPzIf1ofGuJe0zxllw8olgMqQIDAQABAoIBAB6x2DO/cpURbjZr
+9lqsrErGirDVoze5GfM5tVMa0cHXQ0g9TiXH+X7TfqhE4+38qC02M6SFbzfDl4db
+ahdb/1ezj5ivgmDpYcHmnhVUKX/0BCa87L3+a8+MYRsm9ppL66iKJJeLxyRM1b/u
+mKyhCnwiW2hOnpbWAwtDieD0qDx0kzkYLevG3MivaAe1rDD6gS2LhPy6tGtmemRv
+uT03a1X9DEO0U45oDvhi0V6dm8jz/eBBybHt0WWNNi2c5PVjhIL3HfT2UMiNa0xf
+O3g1eGc8bdVPW4z0Kz1g+H/uvguTEqKVq4yT8Y2LuF0Vm2MxptqAmxxJTyL5q0ZI
+V7PDg0ECgYEAziWa0BWbX+cMfIJXNi89aMpgBqBLdvQt9rE/TGjVeViPBcG8AoRr
+Osxzct6tMd+DQO3uzx5DES2FiM7gbOtpGKnLWjJImmCq2gPCtWXo74I8il5egJNh
+vq03eP7wdcm+bFta4+ScGv2wAbx28y3+5fQZPqzUISz+2dM8Hxx9GO0CgYEA8Hs0
+ap+FKD/nbRFfYMVLeP0WfJYuSpn9MF8FT73RSvCva4Ql0a3WbaZ5AMgLFGjm5nRp
+idf5vPMaXbleDG1xZnkhhBXidwFmn4TCVvG/fiDjXOULuJ5qjKLv+5dTG72GDHGG
+onNUwn1LQ3bJpGZ3VHIFJXOcQHl2Dxa6Cn7G9y0CgYBn0gyL67XasNRbCJG/mj8F
+PZbq/2PCPuu/KDlG1C1e9bjiH1X+to4CiOFD4t27FmRWGP6ClS0Vw6VS502jzVOa
+tjjR7i0egrzJG8e979tGdILk9O4HNzKtAzPC3jJgQACFNeUqjQIJneY8mZwWkP2k
+9jCYnhYftzeKoJXQ3VoraQKBgQDiprxYYdDWhqRQH7eNNWZUufSfp8wpc8k19djD
+t1uzDfXHl90tKnKXFfelzOTkb5pwSffOe0hd1aJcA4GopN3kfvYfz6CKGT/nyPCB
+kYeyEL05qIbLkkNKGaelsJIb6xyUTctfAOQ6Cm0NQL/7urdtV6mSCsyR1+h1gC4I
+BkTwYQKBgC7s9J9rRT23bx0NKyyHKu7/akAdC8m0YCohU5L7NNw0UZql0p8EQguh
+bKhNO4+rlwh2VzIi5tVMQTYoUbaab8n17fdNxtfTsG0h4vj8q+7ab59GYf1TKn0R
+JEn/NS0gRKfNh6bwZaSTfhFxALmKApVNTPm2UT9G5hADTcw4xTQf
+-----END RSA PRIVATE KEY-----`;
+
+export const jwksResponse = {
+    keys: [
+        {
+            kty: 'RSA',
+            use: 'sig',
+            kid: 'qSjSTujaClXGGkwW4zdC1zpRHxAq099krc8TTFfXYlg',
+            alg: 'RS256',
+            e: 'AQAB',
+            n: 'oGGYqftZswJRU_vfKDi7jFNgH20LOVfOgSYBu39GEvYWeygBTGuHmf2Ict7m70IKiFAY24kOhMnNjFJ6B0dxdBt5Hf3Kw-VoTKp1MDuy6_wao51WbR5G6II7ND3_2jucyNLeHpYwKHmk00yo4XTBlihNB_ogLCsblupHFceULfq5DN1whTGp-ZHTdYZaSZGXBC_v046vzqIqdKF785AzN56fBDlWhJ0CB9PdLSqj5hd91mxIbJqarHHWR844R5gOLE5ZMJMwX7SUgirFXhDo_VPVKJsP06SxGWBHe8HaYYi6DSXA7i59yBWyJy80F4t3OBTX-AWkzSfJ-_O9zPEBXQ',
+        },
+    ],
+};
+
+export function getToken(scope: string, outerSub?: string) {
+    const payload: any = {
+        scope,
+    };
+
+    if (scope === dashboardScopes) {
+        payload.sub = sub;
+    } else if (scope === walletScopes) {
+        payload.sub = sub2;
+    }
+
+    if (outerSub) {
+        payload.sub = outerSub;
+    }
+
+    const options = {
+        header: { kid: '0' },
+        algorithm: 'RS256',
+        expiresIn: '1d',
+        issuer: AUTH_URL,
+    };
+
+    let token;
+    try {
+        token = jwt.sign(payload, privateKey, options);
+    } catch (err) {
+        console.log(err);
+        throw err;
+    }
+
+    return `Bearer ${token}`;
+}
+
+export const accountAddress = '0x287aAa0f0089069A115AF9D25f0adeB295b52964';
+export const accountEmail = 'test@test.com';
+export const accountSecret = 'mellon';
+export const clientId = 'xxxxxxx';
+export const sub = '6074cbdd1459355fae4b6a14';
+export const sub2 = '6074cbdd1459355fae4b6a15';
+
+export const dashboardScopes =
+    'openid pools:read pools:write erc20:write erc20:read erc721:write erc721:read rewards:read rewards:write deposits:read deposits:write promotions:read promotions:write widgets:write widgets:read transactions:read swaprule:read swaprule:write claims:read';
+export const dashboardAccessToken = getToken(dashboardScopes);
+export const walletScopes =
+    'openid rewards:read erc20:read erc721:read withdrawals:read withdrawals:write deposits:read deposits:write account:read account:write memberships:read memberships:write promotions:read payments:write payments:read relay:write transactions:read transactions:write swap:read swap:write swaprule:read claims:read wallets:read wallets:write';
+export const walletAccessToken = getToken(walletScopes);
+export const walletAccessToken2 = getToken(walletScopes, sub);
diff --git a/apps/auth/src/app/util/jest/index.ts b/apps/auth/src/app/util/jest/index.ts
new file mode 100644
index 000000000..3f51aca95
--- /dev/null
+++ b/apps/auth/src/app/util/jest/index.ts
@@ -0,0 +1,2 @@
+export * from './utils';
+export * from './constants';
diff --git a/apps/auth/src/app/util/jest/mock.ts b/apps/auth/src/app/util/jest/mock.ts
new file mode 100644
index 000000000..3e2bb3b79
--- /dev/null
+++ b/apps/auth/src/app/util/jest/mock.ts
@@ -0,0 +1,50 @@
+import nock from 'nock';
+import { getToken, jwksResponse } from './constants';
+import { API_URL, AUTH_URL } from '../../config/secrets';
+
+export function mockAuthPath(method: string, path: string, status: number, callback: any = {}) {
+    console.log(AUTH_URL);
+    const n = nock(AUTH_URL).persist() as any;
+    return n[method](path).reply(status, callback);
+}
+
+export function mockApiPath(method: string, path: string, status: number, callback: any = {}, query?: any) {
+    const n = nock(API_URL).persist() as any;
+
+    const interceptor = n[method](path);
+    if (query) {
+        interceptor.query(query);
+    }
+    return interceptor.reply(status, callback);
+}
+
+export function mockUrl(method: string, baseUrl: string, path: string, status: number, callback: any = {}) {
+    const n = nock(baseUrl).persist() as any;
+    return n[method](path).reply(status, callback);
+}
+
+export const mockWalletProxy = () => {
+    const token = getToken('openid account:read account:write');
+    mockAuthPath('get', '/jwks', 200, jwksResponse);
+    mockAuthPath('post', '/token', 200, async () => {
+        return { access_token: token };
+    });
+
+    mockApiPath(
+        'get',
+        `/v1/wallets`,
+        200,
+        async () => {
+            return [];
+        },
+        true, // mocks the entire url regardless of the passed query string:
+    );
+
+    mockApiPath('post', `/v1/wallets`, 200, async () => {
+        return true;
+    });
+};
+
+export function mockClear() {
+    return nock.cleanAll();
+}
diff --git a/apps/auth/src/app/util/jest/setup.ts b/apps/auth/src/app/util/jest/setup.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/auth/src/app/util/jest/utils.ts b/apps/auth/src/app/util/jest/utils.ts
new file mode 100644
index 000000000..be49d33e0
--- /dev/null
+++ b/apps/auth/src/app/util/jest/utils.ts
@@ -0,0 +1,3 @@
+export function getPath(url: string) {
+    return '/' + url.split('/')[3] + '/' + url.split('/')[4];
+}
diff --git a/apps/auth/src/app/util/jwks.ts b/apps/auth/src/app/util/jwks.ts
new file mode 100644
index 000000000..c585d4cec
--- /dev/null
+++ b/apps/auth/src/app/util/jwks.ts
@@ -0,0 +1,5 @@
+import { JWKS_JSON } from '../config/secrets';
+
+export function getJwks() {
+    if (JWKS_JSON) return JSON.parse(JWKS_JSON);
+}
diff --git a/apps/auth/src/app/util/jwt.ts b/apps/auth/src/app/util/jwt.ts
new file mode 100644
index 000000000..38f44c647
--- /dev/null
+++ b/apps/auth/src/app/util/jwt.ts
@@ -0,0 +1,15 @@
+export function parseJwt(token: string) {
+    const base64Url = token.split('.')[1];
+    const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
+    const jsonPayload = decodeURIComponent(
+        Buffer.from(base64, 'base64')
+            .toString('binary')
+            .split('')
+            .map(function (c) {
+                return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
+            })
+            .join(''),
+    );
+
+    return JSON.parse(jsonPayload);
+}
diff --git a/apps/auth/src/app/util/logger.ts b/apps/auth/src/app/util/logger.ts
new file mode 100644
index 000000000..de9f449e0
--- /dev/null
+++ b/apps/auth/src/app/util/logger.ts
@@ -0,0 +1,14 @@
+import winston from 'winston';
+import { NODE_ENV } from '../config/secrets';
+
+const formatWinston = winston.format.combine(
+    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
+    winston.format.errors({ stack: true }),
+    winston.format.json(),
+);
+
+export const logger = winston.createLogger({
+    level: NODE_ENV === 'test' ? 'warn' : 'debug',
+    format: formatWinston,
+    transports: [new winston.transports.Console()],
+});
diff --git a/apps/auth/src/app/util/messages.ts b/apps/auth/src/app/util/messages.ts
new file mode 100644
index 000000000..017257d8b
--- /dev/null
+++ b/apps/auth/src/app/util/messages.ts
@@ -0,0 +1,14 @@
+export const ERROR_ACCOUNT_NOT_ACTIVE = 'Your e-mail is not verified. We have re-sent the activation link.';
+// export const ERROR_NO_ACCOUNT = 'We could not find an account for this e-mail and password combination.';
+export const ERROR_AUTH_LINK = 'Your wallet is encrypted by another party. Please ask them to send you a login link.';
+export const ERROR_OTP_CODE_INVALID = 'Could not validate your MFA code.';
+export const ERROR_AUTHENTICATION_TOKEN_INVALID_OR_EXPIRED = 'Your authentication token is invalid or expired.';
+export const ERROR_PASSWORD_NOT_MATCHING = 'Your provided passwords do not match';
+export const ERROR_SIGNUP_TOKEN_INVALID = 'Could not find an account for this signup token.';
+export const ERROR_SIGNUP_TOKEN_EXPIRED = 'This signup_token has expired.';
+export const SUCCESS_SIGNUP_COMPLETED = 'Congratulations! Your e-mail address has been verified.';
+export const ERROR_NO_ACCOUNT = 'Could not find an account for this address';
+export const ERROR_PASSWORD_RESET_TOKEN_INVALID_OR_EXPIRED = 'Your password reset token is invalid or expired.';
+export const ERROR_PASSWORD_STRENGTH = 'Please enter a strong password.';
+export const ERROR_VERIFY_EMAIL_TOKEN_INVALID = 'Could not find an account for this verify_email_token.';
+export const ERROR_VERIFY_EMAIL_EXPIRED = 'This verify_email_token has expired.';
diff --git a/apps/auth/src/app/util/mixpanel.ts b/apps/auth/src/app/util/mixpanel.ts
new file mode 100644
index 000000000..97af80f54
--- /dev/null
+++ b/apps/auth/src/app/util/mixpanel.ts
@@ -0,0 +1,19 @@
+import mixpanel from 'mixpanel-browser';
+import { MIXPANEL_TOKEN } from '../config/secrets';
+
+export const mixpanelClient = () => {
+    mixpanel.init(MIXPANEL_TOKEN);
+    return mixpanel;
+};
+
+export const track = {
+    UserVisits: (distinctId: string, path: string, params: string[]) => {
+        if (!MIXPANEL_TOKEN) return;
+        mixpanelClient().track(`user visits ${path}`, { distinct_id: distinctId, params });
+    },
+    UserCreates: (distinctId: string, item: string) => {
+        if (!MIXPANEL_TOKEN) return;
+        mixpanelClient().track(`user creates ${item}`, { distinct_id: distinctId });
+    },
+};
+export default { mixpanelClient, track };
diff --git a/apps/auth/src/app/util/oidc.ts b/apps/auth/src/app/util/oidc.ts
new file mode 100644
index 000000000..21804516a
--- /dev/null
+++ b/apps/auth/src/app/util/oidc.ts
@@ -0,0 +1,17 @@
+import { Provider } from 'oidc-provider';
+import configuration from '../config/oidc';
+import { AUTH_URL, NODE_ENV } from '../config/secrets';
+
+const oidc = new Provider(AUTH_URL, configuration);
+
+oidc.proxy = true;
+
+if (NODE_ENV !== 'production') {
+    const { invalidate: orig } = (oidc.Client as any).Schema.prototype;
+    (oidc.Client as any).Schema.prototype.invalidate = function invalidate(message, code) {
+        if (code === 'implicit-force-https' || code === 'implicit-forbid-localhost') return;
+        orig.call(this, message);
+    };
+}
+
+export { oidc };
diff --git a/apps/auth/src/app/util/passwordcheck.ts b/apps/auth/src/app/util/passwordcheck.ts
new file mode 100644
index 000000000..3d91748a9
--- /dev/null
+++ b/apps/auth/src/app/util/passwordcheck.ts
@@ -0,0 +1,13 @@
+export const checkPasswordStrength = (password: string) => {
+    const strongPassword = new RegExp('(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^A-Za-z0-9])(?=.{8,})');
+    const mediumPassword = new RegExp(
+        '((?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^A-Za-z0-9])(?=.{6,}))|((?=.*[a-z])(?=.*[A-Z])(?=.*[^A-Za-z0-9])(?=.{8,}))',
+    );
+    if (strongPassword.test(password)) {
+        return 'strong';
+    } else if (mediumPassword.test(password)) {
+        return 'medium';
+    } else {
+        return 'weak';
+    }
+};
diff --git a/apps/auth/src/app/util/path.ts b/apps/auth/src/app/util/path.ts
new file mode 100644
index 000000000..98377c9a6
--- /dev/null
+++ b/apps/auth/src/app/util/path.ts
@@ -0,0 +1,4 @@
+import path from 'path';
+import { CWD } from '../config/secrets';
+
+export const assetsPath = path.resolve(CWD, 'assets');
diff --git a/apps/auth/src/app/util/ratelimiter.ts b/apps/auth/src/app/util/ratelimiter.ts
new file mode 100644
index 000000000..7ed5a76de
--- /dev/null
+++ b/apps/auth/src/app/util/ratelimiter.ts
@@ -0,0 +1,6 @@
+import rateLimit from 'express-rate-limit';
+
+export const rateLimitRewardGive = rateLimit({
+    windowMs: 3600 * 1000, // in seconds * 1000ms
+    max: 10, // limit each IP to n requests per windowMs
+});
diff --git a/apps/auth/src/app/util/time.ts b/apps/auth/src/app/util/time.ts
new file mode 100644
index 000000000..24fcae879
--- /dev/null
+++ b/apps/auth/src/app/util/time.ts
@@ -0,0 +1 @@
+export const get24HoursExpiryTimestamp = () => Date.now() + 1000 * 60 * 60 * 24;
diff --git a/apps/auth/src/app/util/tokens.ts b/apps/auth/src/app/util/tokens.ts
new file mode 100644
index 000000000..786573023
--- /dev/null
+++ b/apps/auth/src/app/util/tokens.ts
@@ -0,0 +1,6 @@
+import crypto from 'crypto';
+
+export function createRandomToken() {
+    const buf = crypto.randomBytes(16);
+    return buf.toString('hex');
+}
diff --git a/apps/auth/src/app/util/validate.ts b/apps/auth/src/app/util/validate.ts
new file mode 100644
index 000000000..488cdb5a4
--- /dev/null
+++ b/apps/auth/src/app/util/validate.ts
@@ -0,0 +1,24 @@
+import { ValidationChain, validationResult } from 'express-validator';
+import { Response, Request, NextFunction } from 'express';
+
+export const validate = (validations: ValidationChain[]) => {
+    return async (req: Request, res: Response, next: NextFunction) => {
+        await Promise.all(validations.map((validation) => validation.run(req)));
+
+        const errors = validationResult(req);
+
+        if (errors.isEmpty()) {
+            return next();
+        }
+
+        res.status(400).json({ errors: errors.array() });
+    };
+};
+
+export function validateEmail(email: string) {
+    return String(email)
+        .toLowerCase()
+        .match(
+            /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
+        );
+}
diff --git a/apps/auth/src/assets/browserconfig.xml b/apps/auth/src/assets/browserconfig.xml
new file mode 100644
index 000000000..b2f4bd3b8
--- /dev/null
+++ b/apps/auth/src/assets/browserconfig.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<browserconfig>
+    <msapplication>
+        <tile>
+            <square150x150logo src="/mstile-150x150.png?v=pgdmXmoa2w"/>
+            <TileColor>#da532c</TileColor>
+        </tile>
+    </msapplication>
+</browserconfig>
diff --git a/apps/auth/src/assets/css/bootstrap.min.css b/apps/auth/src/assets/css/bootstrap.min.css
new file mode 100644
index 000000000..86b6845bc
--- /dev/null
+++ b/apps/auth/src/assets/css/bootstrap.min.css
@@ -0,0 +1,7 @@
+/*!
+ * Bootstrap v4.4.1 (https://getbootstrap.com/)
+ * Copyright 2011-2019 The Bootstrap Authors
+ * Copyright 2011-2019 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]){color:inherit;text-decoration:none}a:not([href]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-sm-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-sm-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-md-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-md-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-md-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-md-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-md-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-md-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-lg-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-lg-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-xl-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-xl-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#0069d9;border-color:#0062cc;box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{color:#fff;background-color:#5a6268;border-color:#545b62;box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#218838;border-color:#1e7e34;box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#138496;border-color:#117a8b;box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{color:#212529;background-color:#e0a800;border-color:#d39e00;box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c82333;border-color:#bd2130;box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{color:#212529;background-color:#e2e6ea;border-color:#dae0e5;box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{color:#fff;background-color:#23272b;border-color:#1d2124;box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline;box-shadow:none}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 0%;flex:1 1 0%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.25rem;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before,.custom-control-input[disabled]~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{-moz-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;-ms-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{-ms-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(0, 0, 0, 0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(255, 255, 255, 0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img,.card-img-bottom,.card-img-top{-ms-flex-negative:0;flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{-ms-flex:1 0 0%;flex:1 0 0%;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.list-group-item:last-child{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal .list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal .list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal .list-group-item.active{margin-top:0}.list-group-horizontal .list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal .list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm .list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm .list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm .list-group-item.active{margin-top:0}.list-group-horizontal-sm .list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm .list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md .list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md .list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md .list-group-item.active{margin-top:0}.list-group-horizontal-md .list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md .list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg .list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg .list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg .list-group-item.active{margin-top:0}.list-group-horizontal-lg .list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg .list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl .list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl .list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl .list-group-item.active{margin-top:0}.list-group-horizontal-xl .list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl .list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush .list-group-item{border-right-width:0;border-left-width:0;border-radius:0}.list-group-flush .list-group-item:first-child{border-top-width:0}.list-group-flush:last-child .list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal.modal-static .modal-dialog{-webkit-transform:scale(1.02);transform:scale(1.02)}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:spinner-border .75s linear infinite;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:spinner-grow .75s linear infinite;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;overflow-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}}
+/*# sourceMappingURL=bootstrap.min.css.map */
\ No newline at end of file
diff --git a/apps/auth/src/assets/css/main.css b/apps/auth/src/assets/css/main.css
new file mode 100644
index 000000000..4ee3da5a6
--- /dev/null
+++ b/apps/auth/src/assets/css/main.css
@@ -0,0 +1,9109 @@
+@charset "UTF-8";
+@import url("https://fonts.googleapis.com/css?family=Exo+2:200,400,400i,700,700i,900,900i");
+@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@500&display=swap");
+*,
+*::before,
+*::after {
+  box-sizing: border-box;
+}
+
+html {
+  font-family: sans-serif;
+  line-height: 1.15;
+  -webkit-text-size-adjust: 100%;
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+
+article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
+  display: block;
+}
+
+body {
+  margin: 0;
+  font-family: "Exo 2", BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+  font-size: 1rem;
+  font-weight: 400;
+  line-height: 1.5;
+  color: #212529;
+  text-align: left;
+  background-color: #fff;
+}
+
+[tabindex="-1"]:focus:not(:focus-visible) {
+  outline: 0 !important;
+}
+
+hr {
+  box-sizing: content-box;
+  height: 0;
+  overflow: visible;
+}
+
+h1, h2, h3, h4, h5, h6 {
+  margin-top: 0;
+  margin-bottom: 0.5rem;
+}
+
+p {
+  margin-top: 0;
+  margin-bottom: 1rem;
+}
+
+abbr[title],
+abbr[data-original-title] {
+  text-decoration: underline;
+  text-decoration: underline dotted;
+  cursor: help;
+  border-bottom: 0;
+  text-decoration-skip-ink: none;
+}
+
+address {
+  margin-bottom: 1rem;
+  font-style: normal;
+  line-height: inherit;
+}
+
+ol,
+ul,
+dl {
+  margin-top: 0;
+  margin-bottom: 1rem;
+}
+
+ol ol,
+ul ul,
+ol ul,
+ul ol {
+  margin-bottom: 0;
+}
+
+dt {
+  font-weight: 700;
+}
+
+dd {
+  margin-bottom: 0.5rem;
+  margin-left: 0;
+}
+
+blockquote {
+  margin: 0 0 1rem;
+}
+
+b,
+strong {
+  font-weight: bolder;
+}
+
+small {
+  font-size: 80%;
+}
+
+sub,
+sup {
+  position: relative;
+  font-size: 75%;
+  line-height: 0;
+  vertical-align: baseline;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+sup {
+  top: -0.5em;
+}
+
+a {
+  color: #5942c1;
+  text-decoration: none;
+  background-color: transparent;
+}
+a:hover {
+  color: #3e2d89;
+  text-decoration: underline;
+}
+
+a:not([href]):not([class]) {
+  color: inherit;
+  text-decoration: none;
+}
+a:not([href]):not([class]):hover {
+  color: inherit;
+  text-decoration: none;
+}
+
+pre,
+code,
+kbd,
+samp {
+  font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+  font-size: 1em;
+}
+
+pre {
+  margin-top: 0;
+  margin-bottom: 1rem;
+  overflow: auto;
+  -ms-overflow-style: scrollbar;
+}
+
+figure {
+  margin: 0 0 1rem;
+}
+
+img {
+  vertical-align: middle;
+  border-style: none;
+}
+
+svg {
+  overflow: hidden;
+  vertical-align: middle;
+}
+
+table {
+  border-collapse: collapse;
+}
+
+caption {
+  padding-top: 0.75rem;
+  padding-bottom: 0.75rem;
+  color: #6c757d;
+  text-align: left;
+  caption-side: bottom;
+}
+
+th {
+  text-align: inherit;
+  text-align: -webkit-match-parent;
+}
+
+label {
+  display: inline-block;
+  margin-bottom: 0.5rem;
+}
+
+button {
+  border-radius: 0;
+}
+
+button:focus:not(:focus-visible) {
+  outline: 0;
+}
+
+input,
+button,
+select,
+optgroup,
+textarea {
+  margin: 0;
+  font-family: inherit;
+  font-size: inherit;
+  line-height: inherit;
+}
+
+button,
+input {
+  overflow: visible;
+}
+
+button,
+select {
+  text-transform: none;
+}
+
+[role=button] {
+  cursor: pointer;
+}
+
+select {
+  word-wrap: normal;
+}
+
+button,
+[type=button],
+[type=reset],
+[type=submit] {
+  -webkit-appearance: button;
+}
+
+button:not(:disabled),
+[type=button]:not(:disabled),
+[type=reset]:not(:disabled),
+[type=submit]:not(:disabled) {
+  cursor: pointer;
+}
+
+button::-moz-focus-inner,
+[type=button]::-moz-focus-inner,
+[type=reset]::-moz-focus-inner,
+[type=submit]::-moz-focus-inner {
+  padding: 0;
+  border-style: none;
+}
+
+input[type=radio],
+input[type=checkbox] {
+  box-sizing: border-box;
+  padding: 0;
+}
+
+textarea {
+  overflow: auto;
+  resize: vertical;
+}
+
+fieldset {
+  min-width: 0;
+  padding: 0;
+  margin: 0;
+  border: 0;
+}
+
+legend {
+  display: block;
+  width: 100%;
+  max-width: 100%;
+  padding: 0;
+  margin-bottom: 0.5rem;
+  font-size: 1.5rem;
+  line-height: inherit;
+  color: inherit;
+  white-space: normal;
+}
+@media (max-width: 1200px) {
+  legend {
+    font-size: calc(1.275rem + 0.3vw);
+  }
+}
+
+progress {
+  vertical-align: baseline;
+}
+
+[type=number]::-webkit-inner-spin-button,
+[type=number]::-webkit-outer-spin-button {
+  height: auto;
+}
+
+[type=search] {
+  outline-offset: -2px;
+  -webkit-appearance: none;
+}
+
+[type=search]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+::-webkit-file-upload-button {
+  font: inherit;
+  -webkit-appearance: button;
+}
+
+output {
+  display: inline-block;
+}
+
+summary {
+  display: list-item;
+  cursor: pointer;
+}
+
+template {
+  display: none;
+}
+
+[hidden] {
+  display: none !important;
+}
+
+.align-baseline {
+  vertical-align: baseline !important;
+}
+
+.align-top {
+  vertical-align: top !important;
+}
+
+.align-middle {
+  vertical-align: middle !important;
+}
+
+.align-bottom {
+  vertical-align: bottom !important;
+}
+
+.align-text-bottom {
+  vertical-align: text-bottom !important;
+}
+
+.align-text-top {
+  vertical-align: text-top !important;
+}
+
+.bg-primary {
+  background-color: #5942c1 !important;
+}
+
+a.bg-primary:hover, a.bg-primary:focus,
+button.bg-primary:hover,
+button.bg-primary:focus {
+  background-color: #46339d !important;
+}
+
+.bg-secondary {
+  background-color: #ffe500 !important;
+}
+
+a.bg-secondary:hover, a.bg-secondary:focus,
+button.bg-secondary:hover,
+button.bg-secondary:focus {
+  background-color: #ccb700 !important;
+}
+
+.bg-success {
+  background-color: #28a745 !important;
+}
+
+a.bg-success:hover, a.bg-success:focus,
+button.bg-success:hover,
+button.bg-success:focus {
+  background-color: #1e7e34 !important;
+}
+
+.bg-info {
+  background-color: #17a2b8 !important;
+}
+
+a.bg-info:hover, a.bg-info:focus,
+button.bg-info:hover,
+button.bg-info:focus {
+  background-color: #117a8b !important;
+}
+
+.bg-warning {
+  background-color: #ffe500 !important;
+}
+
+a.bg-warning:hover, a.bg-warning:focus,
+button.bg-warning:hover,
+button.bg-warning:focus {
+  background-color: #ccb700 !important;
+}
+
+.bg-danger {
+  background-color: #dc3545 !important;
+}
+
+a.bg-danger:hover, a.bg-danger:focus,
+button.bg-danger:hover,
+button.bg-danger:focus {
+  background-color: #bd2130 !important;
+}
+
+.bg-light {
+  background-color: #f8f9fa !important;
+}
+
+a.bg-light:hover, a.bg-light:focus,
+button.bg-light:hover,
+button.bg-light:focus {
+  background-color: #dae0e5 !important;
+}
+
+.bg-dark {
+  background-color: #343a40 !important;
+}
+
+a.bg-dark:hover, a.bg-dark:focus,
+button.bg-dark:hover,
+button.bg-dark:focus {
+  background-color: #1d2124 !important;
+}
+
+.bg-gray {
+  background-color: #9c9e9f !important;
+}
+
+a.bg-gray:hover, a.bg-gray:focus,
+button.bg-gray:hover,
+button.bg-gray:focus {
+  background-color: #828586 !important;
+}
+
+.bg-darker {
+  background-color: #212529 !important;
+}
+
+a.bg-darker:hover, a.bg-darker:focus,
+button.bg-darker:hover,
+button.bg-darker:focus {
+  background-color: #0a0c0d !important;
+}
+
+.bg-white {
+  background-color: #fff !important;
+}
+
+.bg-transparent {
+  background-color: transparent !important;
+}
+
+.border {
+  border: 1px solid #dee2e6 !important;
+}
+
+.border-top {
+  border-top: 1px solid #dee2e6 !important;
+}
+
+.border-right {
+  border-right: 1px solid #dee2e6 !important;
+}
+
+.border-bottom {
+  border-bottom: 1px solid #dee2e6 !important;
+}
+
+.border-left {
+  border-left: 1px solid #dee2e6 !important;
+}
+
+.border-0 {
+  border: 0 !important;
+}
+
+.border-top-0 {
+  border-top: 0 !important;
+}
+
+.border-right-0 {
+  border-right: 0 !important;
+}
+
+.border-bottom-0 {
+  border-bottom: 0 !important;
+}
+
+.border-left-0 {
+  border-left: 0 !important;
+}
+
+.border-primary {
+  border-color: #5942c1 !important;
+}
+
+.border-secondary {
+  border-color: #ffe500 !important;
+}
+
+.border-success {
+  border-color: #28a745 !important;
+}
+
+.border-info {
+  border-color: #17a2b8 !important;
+}
+
+.border-warning {
+  border-color: #ffe500 !important;
+}
+
+.border-danger {
+  border-color: #dc3545 !important;
+}
+
+.border-light {
+  border-color: #f8f9fa !important;
+}
+
+.border-dark {
+  border-color: #343a40 !important;
+}
+
+.border-gray {
+  border-color: #9c9e9f !important;
+}
+
+.border-darker {
+  border-color: #212529 !important;
+}
+
+.border-white {
+  border-color: #fff !important;
+}
+
+.rounded-sm {
+  border-radius: 0.2rem !important;
+}
+
+.rounded {
+  border-radius: 0.3rem !important;
+}
+
+.rounded-top {
+  border-top-left-radius: 0.3rem !important;
+  border-top-right-radius: 0.3rem !important;
+}
+
+.rounded-right {
+  border-top-right-radius: 0.3rem !important;
+  border-bottom-right-radius: 0.3rem !important;
+}
+
+.rounded-bottom {
+  border-bottom-right-radius: 0.3rem !important;
+  border-bottom-left-radius: 0.3rem !important;
+}
+
+.rounded-left {
+  border-top-left-radius: 0.3rem !important;
+  border-bottom-left-radius: 0.3rem !important;
+}
+
+.rounded-lg {
+  border-radius: 0.3rem !important;
+}
+
+.rounded-circle {
+  border-radius: 50% !important;
+}
+
+.rounded-pill {
+  border-radius: 50rem !important;
+}
+
+.rounded-0 {
+  border-radius: 0 !important;
+}
+
+.clearfix::after {
+  display: block;
+  clear: both;
+  content: "";
+}
+
+.d-none {
+  display: none !important;
+}
+
+.d-inline {
+  display: inline !important;
+}
+
+.d-inline-block {
+  display: inline-block !important;
+}
+
+.d-block {
+  display: block !important;
+}
+
+.d-table {
+  display: table !important;
+}
+
+.d-table-row {
+  display: table-row !important;
+}
+
+.d-table-cell {
+  display: table-cell !important;
+}
+
+.d-flex {
+  display: flex !important;
+}
+
+.d-inline-flex {
+  display: inline-flex !important;
+}
+
+@media (min-width: 576px) {
+  .d-sm-none {
+    display: none !important;
+  }
+
+  .d-sm-inline {
+    display: inline !important;
+  }
+
+  .d-sm-inline-block {
+    display: inline-block !important;
+  }
+
+  .d-sm-block {
+    display: block !important;
+  }
+
+  .d-sm-table {
+    display: table !important;
+  }
+
+  .d-sm-table-row {
+    display: table-row !important;
+  }
+
+  .d-sm-table-cell {
+    display: table-cell !important;
+  }
+
+  .d-sm-flex {
+    display: flex !important;
+  }
+
+  .d-sm-inline-flex {
+    display: inline-flex !important;
+  }
+}
+@media (min-width: 768px) {
+  .d-md-none {
+    display: none !important;
+  }
+
+  .d-md-inline {
+    display: inline !important;
+  }
+
+  .d-md-inline-block {
+    display: inline-block !important;
+  }
+
+  .d-md-block {
+    display: block !important;
+  }
+
+  .d-md-table {
+    display: table !important;
+  }
+
+  .d-md-table-row {
+    display: table-row !important;
+  }
+
+  .d-md-table-cell {
+    display: table-cell !important;
+  }
+
+  .d-md-flex {
+    display: flex !important;
+  }
+
+  .d-md-inline-flex {
+    display: inline-flex !important;
+  }
+}
+@media (min-width: 992px) {
+  .d-lg-none {
+    display: none !important;
+  }
+
+  .d-lg-inline {
+    display: inline !important;
+  }
+
+  .d-lg-inline-block {
+    display: inline-block !important;
+  }
+
+  .d-lg-block {
+    display: block !important;
+  }
+
+  .d-lg-table {
+    display: table !important;
+  }
+
+  .d-lg-table-row {
+    display: table-row !important;
+  }
+
+  .d-lg-table-cell {
+    display: table-cell !important;
+  }
+
+  .d-lg-flex {
+    display: flex !important;
+  }
+
+  .d-lg-inline-flex {
+    display: inline-flex !important;
+  }
+}
+@media (min-width: 1200px) {
+  .d-xl-none {
+    display: none !important;
+  }
+
+  .d-xl-inline {
+    display: inline !important;
+  }
+
+  .d-xl-inline-block {
+    display: inline-block !important;
+  }
+
+  .d-xl-block {
+    display: block !important;
+  }
+
+  .d-xl-table {
+    display: table !important;
+  }
+
+  .d-xl-table-row {
+    display: table-row !important;
+  }
+
+  .d-xl-table-cell {
+    display: table-cell !important;
+  }
+
+  .d-xl-flex {
+    display: flex !important;
+  }
+
+  .d-xl-inline-flex {
+    display: inline-flex !important;
+  }
+}
+@media print {
+  .d-print-none {
+    display: none !important;
+  }
+
+  .d-print-inline {
+    display: inline !important;
+  }
+
+  .d-print-inline-block {
+    display: inline-block !important;
+  }
+
+  .d-print-block {
+    display: block !important;
+  }
+
+  .d-print-table {
+    display: table !important;
+  }
+
+  .d-print-table-row {
+    display: table-row !important;
+  }
+
+  .d-print-table-cell {
+    display: table-cell !important;
+  }
+
+  .d-print-flex {
+    display: flex !important;
+  }
+
+  .d-print-inline-flex {
+    display: inline-flex !important;
+  }
+}
+.embed-responsive {
+  position: relative;
+  display: block;
+  width: 100%;
+  padding: 0;
+  overflow: hidden;
+}
+.embed-responsive::before {
+  display: block;
+  content: "";
+}
+.embed-responsive .embed-responsive-item,
+.embed-responsive iframe,
+.embed-responsive embed,
+.embed-responsive object,
+.embed-responsive video {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  border: 0;
+}
+
+.embed-responsive-21by9::before {
+  padding-top: 42.8571428571%;
+}
+
+.embed-responsive-16by9::before {
+  padding-top: 56.25%;
+}
+
+.embed-responsive-4by3::before {
+  padding-top: 75%;
+}
+
+.embed-responsive-1by1::before {
+  padding-top: 100%;
+}
+
+.embed-responsive-21by9::before {
+  padding-top: 42.8571428571%;
+}
+
+.embed-responsive-16by9::before {
+  padding-top: 56.25%;
+}
+
+.embed-responsive-4by3::before {
+  padding-top: 75%;
+}
+
+.embed-responsive-1by1::before {
+  padding-top: 100%;
+}
+
+.flex-row {
+  flex-direction: row !important;
+}
+
+.flex-column {
+  flex-direction: column !important;
+}
+
+.flex-row-reverse {
+  flex-direction: row-reverse !important;
+}
+
+.flex-column-reverse {
+  flex-direction: column-reverse !important;
+}
+
+.flex-wrap {
+  flex-wrap: wrap !important;
+}
+
+.flex-nowrap {
+  flex-wrap: nowrap !important;
+}
+
+.flex-wrap-reverse {
+  flex-wrap: wrap-reverse !important;
+}
+
+.flex-fill {
+  flex: 1 1 auto !important;
+}
+
+.flex-grow-0 {
+  flex-grow: 0 !important;
+}
+
+.flex-grow-1 {
+  flex-grow: 1 !important;
+}
+
+.flex-shrink-0 {
+  flex-shrink: 0 !important;
+}
+
+.flex-shrink-1 {
+  flex-shrink: 1 !important;
+}
+
+.justify-content-start {
+  justify-content: flex-start !important;
+}
+
+.justify-content-end {
+  justify-content: flex-end !important;
+}
+
+.justify-content-center {
+  justify-content: center !important;
+}
+
+.justify-content-between {
+  justify-content: space-between !important;
+}
+
+.justify-content-around {
+  justify-content: space-around !important;
+}
+
+.align-items-start {
+  align-items: flex-start !important;
+}
+
+.align-items-end {
+  align-items: flex-end !important;
+}
+
+.align-items-center {
+  align-items: center !important;
+}
+
+.align-items-baseline {
+  align-items: baseline !important;
+}
+
+.align-items-stretch {
+  align-items: stretch !important;
+}
+
+.align-content-start {
+  align-content: flex-start !important;
+}
+
+.align-content-end {
+  align-content: flex-end !important;
+}
+
+.align-content-center {
+  align-content: center !important;
+}
+
+.align-content-between {
+  align-content: space-between !important;
+}
+
+.align-content-around {
+  align-content: space-around !important;
+}
+
+.align-content-stretch {
+  align-content: stretch !important;
+}
+
+.align-self-auto {
+  align-self: auto !important;
+}
+
+.align-self-start {
+  align-self: flex-start !important;
+}
+
+.align-self-end {
+  align-self: flex-end !important;
+}
+
+.align-self-center {
+  align-self: center !important;
+}
+
+.align-self-baseline {
+  align-self: baseline !important;
+}
+
+.align-self-stretch {
+  align-self: stretch !important;
+}
+
+@media (min-width: 576px) {
+  .flex-sm-row {
+    flex-direction: row !important;
+  }
+
+  .flex-sm-column {
+    flex-direction: column !important;
+  }
+
+  .flex-sm-row-reverse {
+    flex-direction: row-reverse !important;
+  }
+
+  .flex-sm-column-reverse {
+    flex-direction: column-reverse !important;
+  }
+
+  .flex-sm-wrap {
+    flex-wrap: wrap !important;
+  }
+
+  .flex-sm-nowrap {
+    flex-wrap: nowrap !important;
+  }
+
+  .flex-sm-wrap-reverse {
+    flex-wrap: wrap-reverse !important;
+  }
+
+  .flex-sm-fill {
+    flex: 1 1 auto !important;
+  }
+
+  .flex-sm-grow-0 {
+    flex-grow: 0 !important;
+  }
+
+  .flex-sm-grow-1 {
+    flex-grow: 1 !important;
+  }
+
+  .flex-sm-shrink-0 {
+    flex-shrink: 0 !important;
+  }
+
+  .flex-sm-shrink-1 {
+    flex-shrink: 1 !important;
+  }
+
+  .justify-content-sm-start {
+    justify-content: flex-start !important;
+  }
+
+  .justify-content-sm-end {
+    justify-content: flex-end !important;
+  }
+
+  .justify-content-sm-center {
+    justify-content: center !important;
+  }
+
+  .justify-content-sm-between {
+    justify-content: space-between !important;
+  }
+
+  .justify-content-sm-around {
+    justify-content: space-around !important;
+  }
+
+  .align-items-sm-start {
+    align-items: flex-start !important;
+  }
+
+  .align-items-sm-end {
+    align-items: flex-end !important;
+  }
+
+  .align-items-sm-center {
+    align-items: center !important;
+  }
+
+  .align-items-sm-baseline {
+    align-items: baseline !important;
+  }
+
+  .align-items-sm-stretch {
+    align-items: stretch !important;
+  }
+
+  .align-content-sm-start {
+    align-content: flex-start !important;
+  }
+
+  .align-content-sm-end {
+    align-content: flex-end !important;
+  }
+
+  .align-content-sm-center {
+    align-content: center !important;
+  }
+
+  .align-content-sm-between {
+    align-content: space-between !important;
+  }
+
+  .align-content-sm-around {
+    align-content: space-around !important;
+  }
+
+  .align-content-sm-stretch {
+    align-content: stretch !important;
+  }
+
+  .align-self-sm-auto {
+    align-self: auto !important;
+  }
+
+  .align-self-sm-start {
+    align-self: flex-start !important;
+  }
+
+  .align-self-sm-end {
+    align-self: flex-end !important;
+  }
+
+  .align-self-sm-center {
+    align-self: center !important;
+  }
+
+  .align-self-sm-baseline {
+    align-self: baseline !important;
+  }
+
+  .align-self-sm-stretch {
+    align-self: stretch !important;
+  }
+}
+@media (min-width: 768px) {
+  .flex-md-row {
+    flex-direction: row !important;
+  }
+
+  .flex-md-column {
+    flex-direction: column !important;
+  }
+
+  .flex-md-row-reverse {
+    flex-direction: row-reverse !important;
+  }
+
+  .flex-md-column-reverse {
+    flex-direction: column-reverse !important;
+  }
+
+  .flex-md-wrap {
+    flex-wrap: wrap !important;
+  }
+
+  .flex-md-nowrap {
+    flex-wrap: nowrap !important;
+  }
+
+  .flex-md-wrap-reverse {
+    flex-wrap: wrap-reverse !important;
+  }
+
+  .flex-md-fill {
+    flex: 1 1 auto !important;
+  }
+
+  .flex-md-grow-0 {
+    flex-grow: 0 !important;
+  }
+
+  .flex-md-grow-1 {
+    flex-grow: 1 !important;
+  }
+
+  .flex-md-shrink-0 {
+    flex-shrink: 0 !important;
+  }
+
+  .flex-md-shrink-1 {
+    flex-shrink: 1 !important;
+  }
+
+  .justify-content-md-start {
+    justify-content: flex-start !important;
+  }
+
+  .justify-content-md-end {
+    justify-content: flex-end !important;
+  }
+
+  .justify-content-md-center {
+    justify-content: center !important;
+  }
+
+  .justify-content-md-between {
+    justify-content: space-between !important;
+  }
+
+  .justify-content-md-around {
+    justify-content: space-around !important;
+  }
+
+  .align-items-md-start {
+    align-items: flex-start !important;
+  }
+
+  .align-items-md-end {
+    align-items: flex-end !important;
+  }
+
+  .align-items-md-center {
+    align-items: center !important;
+  }
+
+  .align-items-md-baseline {
+    align-items: baseline !important;
+  }
+
+  .align-items-md-stretch {
+    align-items: stretch !important;
+  }
+
+  .align-content-md-start {
+    align-content: flex-start !important;
+  }
+
+  .align-content-md-end {
+    align-content: flex-end !important;
+  }
+
+  .align-content-md-center {
+    align-content: center !important;
+  }
+
+  .align-content-md-between {
+    align-content: space-between !important;
+  }
+
+  .align-content-md-around {
+    align-content: space-around !important;
+  }
+
+  .align-content-md-stretch {
+    align-content: stretch !important;
+  }
+
+  .align-self-md-auto {
+    align-self: auto !important;
+  }
+
+  .align-self-md-start {
+    align-self: flex-start !important;
+  }
+
+  .align-self-md-end {
+    align-self: flex-end !important;
+  }
+
+  .align-self-md-center {
+    align-self: center !important;
+  }
+
+  .align-self-md-baseline {
+    align-self: baseline !important;
+  }
+
+  .align-self-md-stretch {
+    align-self: stretch !important;
+  }
+}
+@media (min-width: 992px) {
+  .flex-lg-row {
+    flex-direction: row !important;
+  }
+
+  .flex-lg-column {
+    flex-direction: column !important;
+  }
+
+  .flex-lg-row-reverse {
+    flex-direction: row-reverse !important;
+  }
+
+  .flex-lg-column-reverse {
+    flex-direction: column-reverse !important;
+  }
+
+  .flex-lg-wrap {
+    flex-wrap: wrap !important;
+  }
+
+  .flex-lg-nowrap {
+    flex-wrap: nowrap !important;
+  }
+
+  .flex-lg-wrap-reverse {
+    flex-wrap: wrap-reverse !important;
+  }
+
+  .flex-lg-fill {
+    flex: 1 1 auto !important;
+  }
+
+  .flex-lg-grow-0 {
+    flex-grow: 0 !important;
+  }
+
+  .flex-lg-grow-1 {
+    flex-grow: 1 !important;
+  }
+
+  .flex-lg-shrink-0 {
+    flex-shrink: 0 !important;
+  }
+
+  .flex-lg-shrink-1 {
+    flex-shrink: 1 !important;
+  }
+
+  .justify-content-lg-start {
+    justify-content: flex-start !important;
+  }
+
+  .justify-content-lg-end {
+    justify-content: flex-end !important;
+  }
+
+  .justify-content-lg-center {
+    justify-content: center !important;
+  }
+
+  .justify-content-lg-between {
+    justify-content: space-between !important;
+  }
+
+  .justify-content-lg-around {
+    justify-content: space-around !important;
+  }
+
+  .align-items-lg-start {
+    align-items: flex-start !important;
+  }
+
+  .align-items-lg-end {
+    align-items: flex-end !important;
+  }
+
+  .align-items-lg-center {
+    align-items: center !important;
+  }
+
+  .align-items-lg-baseline {
+    align-items: baseline !important;
+  }
+
+  .align-items-lg-stretch {
+    align-items: stretch !important;
+  }
+
+  .align-content-lg-start {
+    align-content: flex-start !important;
+  }
+
+  .align-content-lg-end {
+    align-content: flex-end !important;
+  }
+
+  .align-content-lg-center {
+    align-content: center !important;
+  }
+
+  .align-content-lg-between {
+    align-content: space-between !important;
+  }
+
+  .align-content-lg-around {
+    align-content: space-around !important;
+  }
+
+  .align-content-lg-stretch {
+    align-content: stretch !important;
+  }
+
+  .align-self-lg-auto {
+    align-self: auto !important;
+  }
+
+  .align-self-lg-start {
+    align-self: flex-start !important;
+  }
+
+  .align-self-lg-end {
+    align-self: flex-end !important;
+  }
+
+  .align-self-lg-center {
+    align-self: center !important;
+  }
+
+  .align-self-lg-baseline {
+    align-self: baseline !important;
+  }
+
+  .align-self-lg-stretch {
+    align-self: stretch !important;
+  }
+}
+@media (min-width: 1200px) {
+  .flex-xl-row {
+    flex-direction: row !important;
+  }
+
+  .flex-xl-column {
+    flex-direction: column !important;
+  }
+
+  .flex-xl-row-reverse {
+    flex-direction: row-reverse !important;
+  }
+
+  .flex-xl-column-reverse {
+    flex-direction: column-reverse !important;
+  }
+
+  .flex-xl-wrap {
+    flex-wrap: wrap !important;
+  }
+
+  .flex-xl-nowrap {
+    flex-wrap: nowrap !important;
+  }
+
+  .flex-xl-wrap-reverse {
+    flex-wrap: wrap-reverse !important;
+  }
+
+  .flex-xl-fill {
+    flex: 1 1 auto !important;
+  }
+
+  .flex-xl-grow-0 {
+    flex-grow: 0 !important;
+  }
+
+  .flex-xl-grow-1 {
+    flex-grow: 1 !important;
+  }
+
+  .flex-xl-shrink-0 {
+    flex-shrink: 0 !important;
+  }
+
+  .flex-xl-shrink-1 {
+    flex-shrink: 1 !important;
+  }
+
+  .justify-content-xl-start {
+    justify-content: flex-start !important;
+  }
+
+  .justify-content-xl-end {
+    justify-content: flex-end !important;
+  }
+
+  .justify-content-xl-center {
+    justify-content: center !important;
+  }
+
+  .justify-content-xl-between {
+    justify-content: space-between !important;
+  }
+
+  .justify-content-xl-around {
+    justify-content: space-around !important;
+  }
+
+  .align-items-xl-start {
+    align-items: flex-start !important;
+  }
+
+  .align-items-xl-end {
+    align-items: flex-end !important;
+  }
+
+  .align-items-xl-center {
+    align-items: center !important;
+  }
+
+  .align-items-xl-baseline {
+    align-items: baseline !important;
+  }
+
+  .align-items-xl-stretch {
+    align-items: stretch !important;
+  }
+
+  .align-content-xl-start {
+    align-content: flex-start !important;
+  }
+
+  .align-content-xl-end {
+    align-content: flex-end !important;
+  }
+
+  .align-content-xl-center {
+    align-content: center !important;
+  }
+
+  .align-content-xl-between {
+    align-content: space-between !important;
+  }
+
+  .align-content-xl-around {
+    align-content: space-around !important;
+  }
+
+  .align-content-xl-stretch {
+    align-content: stretch !important;
+  }
+
+  .align-self-xl-auto {
+    align-self: auto !important;
+  }
+
+  .align-self-xl-start {
+    align-self: flex-start !important;
+  }
+
+  .align-self-xl-end {
+    align-self: flex-end !important;
+  }
+
+  .align-self-xl-center {
+    align-self: center !important;
+  }
+
+  .align-self-xl-baseline {
+    align-self: baseline !important;
+  }
+
+  .align-self-xl-stretch {
+    align-self: stretch !important;
+  }
+}
+.float-left {
+  float: left !important;
+}
+
+.float-right {
+  float: right !important;
+}
+
+.float-none {
+  float: none !important;
+}
+
+@media (min-width: 576px) {
+  .float-sm-left {
+    float: left !important;
+  }
+
+  .float-sm-right {
+    float: right !important;
+  }
+
+  .float-sm-none {
+    float: none !important;
+  }
+}
+@media (min-width: 768px) {
+  .float-md-left {
+    float: left !important;
+  }
+
+  .float-md-right {
+    float: right !important;
+  }
+
+  .float-md-none {
+    float: none !important;
+  }
+}
+@media (min-width: 992px) {
+  .float-lg-left {
+    float: left !important;
+  }
+
+  .float-lg-right {
+    float: right !important;
+  }
+
+  .float-lg-none {
+    float: none !important;
+  }
+}
+@media (min-width: 1200px) {
+  .float-xl-left {
+    float: left !important;
+  }
+
+  .float-xl-right {
+    float: right !important;
+  }
+
+  .float-xl-none {
+    float: none !important;
+  }
+}
+.user-select-all {
+  user-select: all !important;
+}
+
+.user-select-auto {
+  user-select: auto !important;
+}
+
+.user-select-none {
+  user-select: none !important;
+}
+
+.overflow-auto {
+  overflow: auto !important;
+}
+
+.overflow-hidden {
+  overflow: hidden !important;
+}
+
+.position-static {
+  position: static !important;
+}
+
+.position-relative {
+  position: relative !important;
+}
+
+.position-absolute {
+  position: absolute !important;
+}
+
+.position-fixed {
+  position: fixed !important;
+}
+
+.position-sticky {
+  position: sticky !important;
+}
+
+.fixed-top {
+  position: fixed;
+  top: 0;
+  right: 0;
+  left: 0;
+  z-index: 1030;
+}
+
+.fixed-bottom {
+  position: fixed;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1030;
+}
+
+@supports (position: sticky) {
+  .sticky-top {
+    position: sticky;
+    top: 0;
+    z-index: 1020;
+  }
+}
+
+.sr-only {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  padding: 0;
+  margin: -1px;
+  overflow: hidden;
+  clip: rect(0, 0, 0, 0);
+  white-space: nowrap;
+  border: 0;
+}
+
+.sr-only-focusable:active, .sr-only-focusable:focus {
+  position: static;
+  width: auto;
+  height: auto;
+  overflow: visible;
+  clip: auto;
+  white-space: normal;
+}
+
+.shadow-sm {
+  box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;
+}
+
+.shadow {
+  box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
+}
+
+.shadow-lg {
+  box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important;
+}
+
+.shadow-none {
+  box-shadow: none !important;
+}
+
+.w-25 {
+  width: 25% !important;
+}
+
+.w-50 {
+  width: 50% !important;
+}
+
+.w-75 {
+  width: 75% !important;
+}
+
+.w-100 {
+  width: 100% !important;
+}
+
+.w-auto {
+  width: auto !important;
+}
+
+.h-25 {
+  height: 25% !important;
+}
+
+.h-50 {
+  height: 50% !important;
+}
+
+.h-75 {
+  height: 75% !important;
+}
+
+.h-100 {
+  height: 100% !important;
+}
+
+.h-auto {
+  height: auto !important;
+}
+
+.mw-100 {
+  max-width: 100% !important;
+}
+
+.mh-100 {
+  max-height: 100% !important;
+}
+
+.min-vw-100 {
+  min-width: 100vw !important;
+}
+
+.min-vh-100 {
+  min-height: 100vh !important;
+}
+
+.vw-100 {
+  width: 100vw !important;
+}
+
+.vh-100 {
+  height: 100vh !important;
+}
+
+.m-0 {
+  margin: 0 !important;
+}
+
+.mt-0,
+.my-0 {
+  margin-top: 0 !important;
+}
+
+.mr-0,
+.mx-0 {
+  margin-right: 0 !important;
+}
+
+.mb-0,
+.my-0 {
+  margin-bottom: 0 !important;
+}
+
+.ml-0,
+.mx-0 {
+  margin-left: 0 !important;
+}
+
+.m-1 {
+  margin: 0.25rem !important;
+}
+
+.mt-1,
+.my-1 {
+  margin-top: 0.25rem !important;
+}
+
+.mr-1,
+.mx-1 {
+  margin-right: 0.25rem !important;
+}
+
+.mb-1,
+.my-1 {
+  margin-bottom: 0.25rem !important;
+}
+
+.ml-1,
+.mx-1 {
+  margin-left: 0.25rem !important;
+}
+
+.m-2 {
+  margin: 0.5rem !important;
+}
+
+.mt-2,
+.my-2 {
+  margin-top: 0.5rem !important;
+}
+
+.mr-2,
+.mx-2 {
+  margin-right: 0.5rem !important;
+}
+
+.mb-2,
+.my-2 {
+  margin-bottom: 0.5rem !important;
+}
+
+.ml-2,
+.mx-2 {
+  margin-left: 0.5rem !important;
+}
+
+.m-3 {
+  margin: 1rem !important;
+}
+
+.mt-3,
+.my-3 {
+  margin-top: 1rem !important;
+}
+
+.mr-3,
+.mx-3 {
+  margin-right: 1rem !important;
+}
+
+.mb-3,
+.my-3 {
+  margin-bottom: 1rem !important;
+}
+
+.ml-3,
+.mx-3 {
+  margin-left: 1rem !important;
+}
+
+.m-4 {
+  margin: 1.5rem !important;
+}
+
+.mt-4,
+.my-4 {
+  margin-top: 1.5rem !important;
+}
+
+.mr-4,
+.mx-4 {
+  margin-right: 1.5rem !important;
+}
+
+.mb-4,
+.my-4 {
+  margin-bottom: 1.5rem !important;
+}
+
+.ml-4,
+.mx-4 {
+  margin-left: 1.5rem !important;
+}
+
+.m-5 {
+  margin: 3rem !important;
+}
+
+.mt-5,
+.my-5 {
+  margin-top: 3rem !important;
+}
+
+.mr-5,
+.mx-5 {
+  margin-right: 3rem !important;
+}
+
+.mb-5,
+.my-5 {
+  margin-bottom: 3rem !important;
+}
+
+.ml-5,
+.mx-5 {
+  margin-left: 3rem !important;
+}
+
+.p-0 {
+  padding: 0 !important;
+}
+
+.pt-0,
+.py-0 {
+  padding-top: 0 !important;
+}
+
+.pr-0,
+.px-0 {
+  padding-right: 0 !important;
+}
+
+.pb-0,
+.py-0 {
+  padding-bottom: 0 !important;
+}
+
+.pl-0,
+.px-0 {
+  padding-left: 0 !important;
+}
+
+.p-1 {
+  padding: 0.25rem !important;
+}
+
+.pt-1,
+.py-1 {
+  padding-top: 0.25rem !important;
+}
+
+.pr-1,
+.px-1 {
+  padding-right: 0.25rem !important;
+}
+
+.pb-1,
+.py-1 {
+  padding-bottom: 0.25rem !important;
+}
+
+.pl-1,
+.px-1 {
+  padding-left: 0.25rem !important;
+}
+
+.p-2 {
+  padding: 0.5rem !important;
+}
+
+.pt-2,
+.py-2 {
+  padding-top: 0.5rem !important;
+}
+
+.pr-2,
+.px-2 {
+  padding-right: 0.5rem !important;
+}
+
+.pb-2,
+.py-2 {
+  padding-bottom: 0.5rem !important;
+}
+
+.pl-2,
+.px-2 {
+  padding-left: 0.5rem !important;
+}
+
+.p-3 {
+  padding: 1rem !important;
+}
+
+.pt-3,
+.py-3 {
+  padding-top: 1rem !important;
+}
+
+.pr-3,
+.px-3 {
+  padding-right: 1rem !important;
+}
+
+.pb-3,
+.py-3 {
+  padding-bottom: 1rem !important;
+}
+
+.pl-3,
+.px-3 {
+  padding-left: 1rem !important;
+}
+
+.p-4 {
+  padding: 1.5rem !important;
+}
+
+.pt-4,
+.py-4 {
+  padding-top: 1.5rem !important;
+}
+
+.pr-4,
+.px-4 {
+  padding-right: 1.5rem !important;
+}
+
+.pb-4,
+.py-4 {
+  padding-bottom: 1.5rem !important;
+}
+
+.pl-4,
+.px-4 {
+  padding-left: 1.5rem !important;
+}
+
+.p-5 {
+  padding: 3rem !important;
+}
+
+.pt-5,
+.py-5 {
+  padding-top: 3rem !important;
+}
+
+.pr-5,
+.px-5 {
+  padding-right: 3rem !important;
+}
+
+.pb-5,
+.py-5 {
+  padding-bottom: 3rem !important;
+}
+
+.pl-5,
+.px-5 {
+  padding-left: 3rem !important;
+}
+
+.m-n1 {
+  margin: -0.25rem !important;
+}
+
+.mt-n1,
+.my-n1 {
+  margin-top: -0.25rem !important;
+}
+
+.mr-n1,
+.mx-n1 {
+  margin-right: -0.25rem !important;
+}
+
+.mb-n1,
+.my-n1 {
+  margin-bottom: -0.25rem !important;
+}
+
+.ml-n1,
+.mx-n1 {
+  margin-left: -0.25rem !important;
+}
+
+.m-n2 {
+  margin: -0.5rem !important;
+}
+
+.mt-n2,
+.my-n2 {
+  margin-top: -0.5rem !important;
+}
+
+.mr-n2,
+.mx-n2 {
+  margin-right: -0.5rem !important;
+}
+
+.mb-n2,
+.my-n2 {
+  margin-bottom: -0.5rem !important;
+}
+
+.ml-n2,
+.mx-n2 {
+  margin-left: -0.5rem !important;
+}
+
+.m-n3 {
+  margin: -1rem !important;
+}
+
+.mt-n3,
+.my-n3 {
+  margin-top: -1rem !important;
+}
+
+.mr-n3,
+.mx-n3 {
+  margin-right: -1rem !important;
+}
+
+.mb-n3,
+.my-n3 {
+  margin-bottom: -1rem !important;
+}
+
+.ml-n3,
+.mx-n3 {
+  margin-left: -1rem !important;
+}
+
+.m-n4 {
+  margin: -1.5rem !important;
+}
+
+.mt-n4,
+.my-n4 {
+  margin-top: -1.5rem !important;
+}
+
+.mr-n4,
+.mx-n4 {
+  margin-right: -1.5rem !important;
+}
+
+.mb-n4,
+.my-n4 {
+  margin-bottom: -1.5rem !important;
+}
+
+.ml-n4,
+.mx-n4 {
+  margin-left: -1.5rem !important;
+}
+
+.m-n5 {
+  margin: -3rem !important;
+}
+
+.mt-n5,
+.my-n5 {
+  margin-top: -3rem !important;
+}
+
+.mr-n5,
+.mx-n5 {
+  margin-right: -3rem !important;
+}
+
+.mb-n5,
+.my-n5 {
+  margin-bottom: -3rem !important;
+}
+
+.ml-n5,
+.mx-n5 {
+  margin-left: -3rem !important;
+}
+
+.m-auto {
+  margin: auto !important;
+}
+
+.mt-auto,
+.my-auto {
+  margin-top: auto !important;
+}
+
+.mr-auto,
+.mx-auto {
+  margin-right: auto !important;
+}
+
+.mb-auto,
+.my-auto {
+  margin-bottom: auto !important;
+}
+
+.ml-auto,
+.mx-auto {
+  margin-left: auto !important;
+}
+
+@media (min-width: 576px) {
+  .m-sm-0 {
+    margin: 0 !important;
+  }
+
+  .mt-sm-0,
+.my-sm-0 {
+    margin-top: 0 !important;
+  }
+
+  .mr-sm-0,
+.mx-sm-0 {
+    margin-right: 0 !important;
+  }
+
+  .mb-sm-0,
+.my-sm-0 {
+    margin-bottom: 0 !important;
+  }
+
+  .ml-sm-0,
+.mx-sm-0 {
+    margin-left: 0 !important;
+  }
+
+  .m-sm-1 {
+    margin: 0.25rem !important;
+  }
+
+  .mt-sm-1,
+.my-sm-1 {
+    margin-top: 0.25rem !important;
+  }
+
+  .mr-sm-1,
+.mx-sm-1 {
+    margin-right: 0.25rem !important;
+  }
+
+  .mb-sm-1,
+.my-sm-1 {
+    margin-bottom: 0.25rem !important;
+  }
+
+  .ml-sm-1,
+.mx-sm-1 {
+    margin-left: 0.25rem !important;
+  }
+
+  .m-sm-2 {
+    margin: 0.5rem !important;
+  }
+
+  .mt-sm-2,
+.my-sm-2 {
+    margin-top: 0.5rem !important;
+  }
+
+  .mr-sm-2,
+.mx-sm-2 {
+    margin-right: 0.5rem !important;
+  }
+
+  .mb-sm-2,
+.my-sm-2 {
+    margin-bottom: 0.5rem !important;
+  }
+
+  .ml-sm-2,
+.mx-sm-2 {
+    margin-left: 0.5rem !important;
+  }
+
+  .m-sm-3 {
+    margin: 1rem !important;
+  }
+
+  .mt-sm-3,
+.my-sm-3 {
+    margin-top: 1rem !important;
+  }
+
+  .mr-sm-3,
+.mx-sm-3 {
+    margin-right: 1rem !important;
+  }
+
+  .mb-sm-3,
+.my-sm-3 {
+    margin-bottom: 1rem !important;
+  }
+
+  .ml-sm-3,
+.mx-sm-3 {
+    margin-left: 1rem !important;
+  }
+
+  .m-sm-4 {
+    margin: 1.5rem !important;
+  }
+
+  .mt-sm-4,
+.my-sm-4 {
+    margin-top: 1.5rem !important;
+  }
+
+  .mr-sm-4,
+.mx-sm-4 {
+    margin-right: 1.5rem !important;
+  }
+
+  .mb-sm-4,
+.my-sm-4 {
+    margin-bottom: 1.5rem !important;
+  }
+
+  .ml-sm-4,
+.mx-sm-4 {
+    margin-left: 1.5rem !important;
+  }
+
+  .m-sm-5 {
+    margin: 3rem !important;
+  }
+
+  .mt-sm-5,
+.my-sm-5 {
+    margin-top: 3rem !important;
+  }
+
+  .mr-sm-5,
+.mx-sm-5 {
+    margin-right: 3rem !important;
+  }
+
+  .mb-sm-5,
+.my-sm-5 {
+    margin-bottom: 3rem !important;
+  }
+
+  .ml-sm-5,
+.mx-sm-5 {
+    margin-left: 3rem !important;
+  }
+
+  .p-sm-0 {
+    padding: 0 !important;
+  }
+
+  .pt-sm-0,
+.py-sm-0 {
+    padding-top: 0 !important;
+  }
+
+  .pr-sm-0,
+.px-sm-0 {
+    padding-right: 0 !important;
+  }
+
+  .pb-sm-0,
+.py-sm-0 {
+    padding-bottom: 0 !important;
+  }
+
+  .pl-sm-0,
+.px-sm-0 {
+    padding-left: 0 !important;
+  }
+
+  .p-sm-1 {
+    padding: 0.25rem !important;
+  }
+
+  .pt-sm-1,
+.py-sm-1 {
+    padding-top: 0.25rem !important;
+  }
+
+  .pr-sm-1,
+.px-sm-1 {
+    padding-right: 0.25rem !important;
+  }
+
+  .pb-sm-1,
+.py-sm-1 {
+    padding-bottom: 0.25rem !important;
+  }
+
+  .pl-sm-1,
+.px-sm-1 {
+    padding-left: 0.25rem !important;
+  }
+
+  .p-sm-2 {
+    padding: 0.5rem !important;
+  }
+
+  .pt-sm-2,
+.py-sm-2 {
+    padding-top: 0.5rem !important;
+  }
+
+  .pr-sm-2,
+.px-sm-2 {
+    padding-right: 0.5rem !important;
+  }
+
+  .pb-sm-2,
+.py-sm-2 {
+    padding-bottom: 0.5rem !important;
+  }
+
+  .pl-sm-2,
+.px-sm-2 {
+    padding-left: 0.5rem !important;
+  }
+
+  .p-sm-3 {
+    padding: 1rem !important;
+  }
+
+  .pt-sm-3,
+.py-sm-3 {
+    padding-top: 1rem !important;
+  }
+
+  .pr-sm-3,
+.px-sm-3 {
+    padding-right: 1rem !important;
+  }
+
+  .pb-sm-3,
+.py-sm-3 {
+    padding-bottom: 1rem !important;
+  }
+
+  .pl-sm-3,
+.px-sm-3 {
+    padding-left: 1rem !important;
+  }
+
+  .p-sm-4 {
+    padding: 1.5rem !important;
+  }
+
+  .pt-sm-4,
+.py-sm-4 {
+    padding-top: 1.5rem !important;
+  }
+
+  .pr-sm-4,
+.px-sm-4 {
+    padding-right: 1.5rem !important;
+  }
+
+  .pb-sm-4,
+.py-sm-4 {
+    padding-bottom: 1.5rem !important;
+  }
+
+  .pl-sm-4,
+.px-sm-4 {
+    padding-left: 1.5rem !important;
+  }
+
+  .p-sm-5 {
+    padding: 3rem !important;
+  }
+
+  .pt-sm-5,
+.py-sm-5 {
+    padding-top: 3rem !important;
+  }
+
+  .pr-sm-5,
+.px-sm-5 {
+    padding-right: 3rem !important;
+  }
+
+  .pb-sm-5,
+.py-sm-5 {
+    padding-bottom: 3rem !important;
+  }
+
+  .pl-sm-5,
+.px-sm-5 {
+    padding-left: 3rem !important;
+  }
+
+  .m-sm-n1 {
+    margin: -0.25rem !important;
+  }
+
+  .mt-sm-n1,
+.my-sm-n1 {
+    margin-top: -0.25rem !important;
+  }
+
+  .mr-sm-n1,
+.mx-sm-n1 {
+    margin-right: -0.25rem !important;
+  }
+
+  .mb-sm-n1,
+.my-sm-n1 {
+    margin-bottom: -0.25rem !important;
+  }
+
+  .ml-sm-n1,
+.mx-sm-n1 {
+    margin-left: -0.25rem !important;
+  }
+
+  .m-sm-n2 {
+    margin: -0.5rem !important;
+  }
+
+  .mt-sm-n2,
+.my-sm-n2 {
+    margin-top: -0.5rem !important;
+  }
+
+  .mr-sm-n2,
+.mx-sm-n2 {
+    margin-right: -0.5rem !important;
+  }
+
+  .mb-sm-n2,
+.my-sm-n2 {
+    margin-bottom: -0.5rem !important;
+  }
+
+  .ml-sm-n2,
+.mx-sm-n2 {
+    margin-left: -0.5rem !important;
+  }
+
+  .m-sm-n3 {
+    margin: -1rem !important;
+  }
+
+  .mt-sm-n3,
+.my-sm-n3 {
+    margin-top: -1rem !important;
+  }
+
+  .mr-sm-n3,
+.mx-sm-n3 {
+    margin-right: -1rem !important;
+  }
+
+  .mb-sm-n3,
+.my-sm-n3 {
+    margin-bottom: -1rem !important;
+  }
+
+  .ml-sm-n3,
+.mx-sm-n3 {
+    margin-left: -1rem !important;
+  }
+
+  .m-sm-n4 {
+    margin: -1.5rem !important;
+  }
+
+  .mt-sm-n4,
+.my-sm-n4 {
+    margin-top: -1.5rem !important;
+  }
+
+  .mr-sm-n4,
+.mx-sm-n4 {
+    margin-right: -1.5rem !important;
+  }
+
+  .mb-sm-n4,
+.my-sm-n4 {
+    margin-bottom: -1.5rem !important;
+  }
+
+  .ml-sm-n4,
+.mx-sm-n4 {
+    margin-left: -1.5rem !important;
+  }
+
+  .m-sm-n5 {
+    margin: -3rem !important;
+  }
+
+  .mt-sm-n5,
+.my-sm-n5 {
+    margin-top: -3rem !important;
+  }
+
+  .mr-sm-n5,
+.mx-sm-n5 {
+    margin-right: -3rem !important;
+  }
+
+  .mb-sm-n5,
+.my-sm-n5 {
+    margin-bottom: -3rem !important;
+  }
+
+  .ml-sm-n5,
+.mx-sm-n5 {
+    margin-left: -3rem !important;
+  }
+
+  .m-sm-auto {
+    margin: auto !important;
+  }
+
+  .mt-sm-auto,
+.my-sm-auto {
+    margin-top: auto !important;
+  }
+
+  .mr-sm-auto,
+.mx-sm-auto {
+    margin-right: auto !important;
+  }
+
+  .mb-sm-auto,
+.my-sm-auto {
+    margin-bottom: auto !important;
+  }
+
+  .ml-sm-auto,
+.mx-sm-auto {
+    margin-left: auto !important;
+  }
+}
+@media (min-width: 768px) {
+  .m-md-0 {
+    margin: 0 !important;
+  }
+
+  .mt-md-0,
+.my-md-0 {
+    margin-top: 0 !important;
+  }
+
+  .mr-md-0,
+.mx-md-0 {
+    margin-right: 0 !important;
+  }
+
+  .mb-md-0,
+.my-md-0 {
+    margin-bottom: 0 !important;
+  }
+
+  .ml-md-0,
+.mx-md-0 {
+    margin-left: 0 !important;
+  }
+
+  .m-md-1 {
+    margin: 0.25rem !important;
+  }
+
+  .mt-md-1,
+.my-md-1 {
+    margin-top: 0.25rem !important;
+  }
+
+  .mr-md-1,
+.mx-md-1 {
+    margin-right: 0.25rem !important;
+  }
+
+  .mb-md-1,
+.my-md-1 {
+    margin-bottom: 0.25rem !important;
+  }
+
+  .ml-md-1,
+.mx-md-1 {
+    margin-left: 0.25rem !important;
+  }
+
+  .m-md-2 {
+    margin: 0.5rem !important;
+  }
+
+  .mt-md-2,
+.my-md-2 {
+    margin-top: 0.5rem !important;
+  }
+
+  .mr-md-2,
+.mx-md-2 {
+    margin-right: 0.5rem !important;
+  }
+
+  .mb-md-2,
+.my-md-2 {
+    margin-bottom: 0.5rem !important;
+  }
+
+  .ml-md-2,
+.mx-md-2 {
+    margin-left: 0.5rem !important;
+  }
+
+  .m-md-3 {
+    margin: 1rem !important;
+  }
+
+  .mt-md-3,
+.my-md-3 {
+    margin-top: 1rem !important;
+  }
+
+  .mr-md-3,
+.mx-md-3 {
+    margin-right: 1rem !important;
+  }
+
+  .mb-md-3,
+.my-md-3 {
+    margin-bottom: 1rem !important;
+  }
+
+  .ml-md-3,
+.mx-md-3 {
+    margin-left: 1rem !important;
+  }
+
+  .m-md-4 {
+    margin: 1.5rem !important;
+  }
+
+  .mt-md-4,
+.my-md-4 {
+    margin-top: 1.5rem !important;
+  }
+
+  .mr-md-4,
+.mx-md-4 {
+    margin-right: 1.5rem !important;
+  }
+
+  .mb-md-4,
+.my-md-4 {
+    margin-bottom: 1.5rem !important;
+  }
+
+  .ml-md-4,
+.mx-md-4 {
+    margin-left: 1.5rem !important;
+  }
+
+  .m-md-5 {
+    margin: 3rem !important;
+  }
+
+  .mt-md-5,
+.my-md-5 {
+    margin-top: 3rem !important;
+  }
+
+  .mr-md-5,
+.mx-md-5 {
+    margin-right: 3rem !important;
+  }
+
+  .mb-md-5,
+.my-md-5 {
+    margin-bottom: 3rem !important;
+  }
+
+  .ml-md-5,
+.mx-md-5 {
+    margin-left: 3rem !important;
+  }
+
+  .p-md-0 {
+    padding: 0 !important;
+  }
+
+  .pt-md-0,
+.py-md-0 {
+    padding-top: 0 !important;
+  }
+
+  .pr-md-0,
+.px-md-0 {
+    padding-right: 0 !important;
+  }
+
+  .pb-md-0,
+.py-md-0 {
+    padding-bottom: 0 !important;
+  }
+
+  .pl-md-0,
+.px-md-0 {
+    padding-left: 0 !important;
+  }
+
+  .p-md-1 {
+    padding: 0.25rem !important;
+  }
+
+  .pt-md-1,
+.py-md-1 {
+    padding-top: 0.25rem !important;
+  }
+
+  .pr-md-1,
+.px-md-1 {
+    padding-right: 0.25rem !important;
+  }
+
+  .pb-md-1,
+.py-md-1 {
+    padding-bottom: 0.25rem !important;
+  }
+
+  .pl-md-1,
+.px-md-1 {
+    padding-left: 0.25rem !important;
+  }
+
+  .p-md-2 {
+    padding: 0.5rem !important;
+  }
+
+  .pt-md-2,
+.py-md-2 {
+    padding-top: 0.5rem !important;
+  }
+
+  .pr-md-2,
+.px-md-2 {
+    padding-right: 0.5rem !important;
+  }
+
+  .pb-md-2,
+.py-md-2 {
+    padding-bottom: 0.5rem !important;
+  }
+
+  .pl-md-2,
+.px-md-2 {
+    padding-left: 0.5rem !important;
+  }
+
+  .p-md-3 {
+    padding: 1rem !important;
+  }
+
+  .pt-md-3,
+.py-md-3 {
+    padding-top: 1rem !important;
+  }
+
+  .pr-md-3,
+.px-md-3 {
+    padding-right: 1rem !important;
+  }
+
+  .pb-md-3,
+.py-md-3 {
+    padding-bottom: 1rem !important;
+  }
+
+  .pl-md-3,
+.px-md-3 {
+    padding-left: 1rem !important;
+  }
+
+  .p-md-4 {
+    padding: 1.5rem !important;
+  }
+
+  .pt-md-4,
+.py-md-4 {
+    padding-top: 1.5rem !important;
+  }
+
+  .pr-md-4,
+.px-md-4 {
+    padding-right: 1.5rem !important;
+  }
+
+  .pb-md-4,
+.py-md-4 {
+    padding-bottom: 1.5rem !important;
+  }
+
+  .pl-md-4,
+.px-md-4 {
+    padding-left: 1.5rem !important;
+  }
+
+  .p-md-5 {
+    padding: 3rem !important;
+  }
+
+  .pt-md-5,
+.py-md-5 {
+    padding-top: 3rem !important;
+  }
+
+  .pr-md-5,
+.px-md-5 {
+    padding-right: 3rem !important;
+  }
+
+  .pb-md-5,
+.py-md-5 {
+    padding-bottom: 3rem !important;
+  }
+
+  .pl-md-5,
+.px-md-5 {
+    padding-left: 3rem !important;
+  }
+
+  .m-md-n1 {
+    margin: -0.25rem !important;
+  }
+
+  .mt-md-n1,
+.my-md-n1 {
+    margin-top: -0.25rem !important;
+  }
+
+  .mr-md-n1,
+.mx-md-n1 {
+    margin-right: -0.25rem !important;
+  }
+
+  .mb-md-n1,
+.my-md-n1 {
+    margin-bottom: -0.25rem !important;
+  }
+
+  .ml-md-n1,
+.mx-md-n1 {
+    margin-left: -0.25rem !important;
+  }
+
+  .m-md-n2 {
+    margin: -0.5rem !important;
+  }
+
+  .mt-md-n2,
+.my-md-n2 {
+    margin-top: -0.5rem !important;
+  }
+
+  .mr-md-n2,
+.mx-md-n2 {
+    margin-right: -0.5rem !important;
+  }
+
+  .mb-md-n2,
+.my-md-n2 {
+    margin-bottom: -0.5rem !important;
+  }
+
+  .ml-md-n2,
+.mx-md-n2 {
+    margin-left: -0.5rem !important;
+  }
+
+  .m-md-n3 {
+    margin: -1rem !important;
+  }
+
+  .mt-md-n3,
+.my-md-n3 {
+    margin-top: -1rem !important;
+  }
+
+  .mr-md-n3,
+.mx-md-n3 {
+    margin-right: -1rem !important;
+  }
+
+  .mb-md-n3,
+.my-md-n3 {
+    margin-bottom: -1rem !important;
+  }
+
+  .ml-md-n3,
+.mx-md-n3 {
+    margin-left: -1rem !important;
+  }
+
+  .m-md-n4 {
+    margin: -1.5rem !important;
+  }
+
+  .mt-md-n4,
+.my-md-n4 {
+    margin-top: -1.5rem !important;
+  }
+
+  .mr-md-n4,
+.mx-md-n4 {
+    margin-right: -1.5rem !important;
+  }
+
+  .mb-md-n4,
+.my-md-n4 {
+    margin-bottom: -1.5rem !important;
+  }
+
+  .ml-md-n4,
+.mx-md-n4 {
+    margin-left: -1.5rem !important;
+  }
+
+  .m-md-n5 {
+    margin: -3rem !important;
+  }
+
+  .mt-md-n5,
+.my-md-n5 {
+    margin-top: -3rem !important;
+  }
+
+  .mr-md-n5,
+.mx-md-n5 {
+    margin-right: -3rem !important;
+  }
+
+  .mb-md-n5,
+.my-md-n5 {
+    margin-bottom: -3rem !important;
+  }
+
+  .ml-md-n5,
+.mx-md-n5 {
+    margin-left: -3rem !important;
+  }
+
+  .m-md-auto {
+    margin: auto !important;
+  }
+
+  .mt-md-auto,
+.my-md-auto {
+    margin-top: auto !important;
+  }
+
+  .mr-md-auto,
+.mx-md-auto {
+    margin-right: auto !important;
+  }
+
+  .mb-md-auto,
+.my-md-auto {
+    margin-bottom: auto !important;
+  }
+
+  .ml-md-auto,
+.mx-md-auto {
+    margin-left: auto !important;
+  }
+}
+@media (min-width: 992px) {
+  .m-lg-0 {
+    margin: 0 !important;
+  }
+
+  .mt-lg-0,
+.my-lg-0 {
+    margin-top: 0 !important;
+  }
+
+  .mr-lg-0,
+.mx-lg-0 {
+    margin-right: 0 !important;
+  }
+
+  .mb-lg-0,
+.my-lg-0 {
+    margin-bottom: 0 !important;
+  }
+
+  .ml-lg-0,
+.mx-lg-0 {
+    margin-left: 0 !important;
+  }
+
+  .m-lg-1 {
+    margin: 0.25rem !important;
+  }
+
+  .mt-lg-1,
+.my-lg-1 {
+    margin-top: 0.25rem !important;
+  }
+
+  .mr-lg-1,
+.mx-lg-1 {
+    margin-right: 0.25rem !important;
+  }
+
+  .mb-lg-1,
+.my-lg-1 {
+    margin-bottom: 0.25rem !important;
+  }
+
+  .ml-lg-1,
+.mx-lg-1 {
+    margin-left: 0.25rem !important;
+  }
+
+  .m-lg-2 {
+    margin: 0.5rem !important;
+  }
+
+  .mt-lg-2,
+.my-lg-2 {
+    margin-top: 0.5rem !important;
+  }
+
+  .mr-lg-2,
+.mx-lg-2 {
+    margin-right: 0.5rem !important;
+  }
+
+  .mb-lg-2,
+.my-lg-2 {
+    margin-bottom: 0.5rem !important;
+  }
+
+  .ml-lg-2,
+.mx-lg-2 {
+    margin-left: 0.5rem !important;
+  }
+
+  .m-lg-3 {
+    margin: 1rem !important;
+  }
+
+  .mt-lg-3,
+.my-lg-3 {
+    margin-top: 1rem !important;
+  }
+
+  .mr-lg-3,
+.mx-lg-3 {
+    margin-right: 1rem !important;
+  }
+
+  .mb-lg-3,
+.my-lg-3 {
+    margin-bottom: 1rem !important;
+  }
+
+  .ml-lg-3,
+.mx-lg-3 {
+    margin-left: 1rem !important;
+  }
+
+  .m-lg-4 {
+    margin: 1.5rem !important;
+  }
+
+  .mt-lg-4,
+.my-lg-4 {
+    margin-top: 1.5rem !important;
+  }
+
+  .mr-lg-4,
+.mx-lg-4 {
+    margin-right: 1.5rem !important;
+  }
+
+  .mb-lg-4,
+.my-lg-4 {
+    margin-bottom: 1.5rem !important;
+  }
+
+  .ml-lg-4,
+.mx-lg-4 {
+    margin-left: 1.5rem !important;
+  }
+
+  .m-lg-5 {
+    margin: 3rem !important;
+  }
+
+  .mt-lg-5,
+.my-lg-5 {
+    margin-top: 3rem !important;
+  }
+
+  .mr-lg-5,
+.mx-lg-5 {
+    margin-right: 3rem !important;
+  }
+
+  .mb-lg-5,
+.my-lg-5 {
+    margin-bottom: 3rem !important;
+  }
+
+  .ml-lg-5,
+.mx-lg-5 {
+    margin-left: 3rem !important;
+  }
+
+  .p-lg-0 {
+    padding: 0 !important;
+  }
+
+  .pt-lg-0,
+.py-lg-0 {
+    padding-top: 0 !important;
+  }
+
+  .pr-lg-0,
+.px-lg-0 {
+    padding-right: 0 !important;
+  }
+
+  .pb-lg-0,
+.py-lg-0 {
+    padding-bottom: 0 !important;
+  }
+
+  .pl-lg-0,
+.px-lg-0 {
+    padding-left: 0 !important;
+  }
+
+  .p-lg-1 {
+    padding: 0.25rem !important;
+  }
+
+  .pt-lg-1,
+.py-lg-1 {
+    padding-top: 0.25rem !important;
+  }
+
+  .pr-lg-1,
+.px-lg-1 {
+    padding-right: 0.25rem !important;
+  }
+
+  .pb-lg-1,
+.py-lg-1 {
+    padding-bottom: 0.25rem !important;
+  }
+
+  .pl-lg-1,
+.px-lg-1 {
+    padding-left: 0.25rem !important;
+  }
+
+  .p-lg-2 {
+    padding: 0.5rem !important;
+  }
+
+  .pt-lg-2,
+.py-lg-2 {
+    padding-top: 0.5rem !important;
+  }
+
+  .pr-lg-2,
+.px-lg-2 {
+    padding-right: 0.5rem !important;
+  }
+
+  .pb-lg-2,
+.py-lg-2 {
+    padding-bottom: 0.5rem !important;
+  }
+
+  .pl-lg-2,
+.px-lg-2 {
+    padding-left: 0.5rem !important;
+  }
+
+  .p-lg-3 {
+    padding: 1rem !important;
+  }
+
+  .pt-lg-3,
+.py-lg-3 {
+    padding-top: 1rem !important;
+  }
+
+  .pr-lg-3,
+.px-lg-3 {
+    padding-right: 1rem !important;
+  }
+
+  .pb-lg-3,
+.py-lg-3 {
+    padding-bottom: 1rem !important;
+  }
+
+  .pl-lg-3,
+.px-lg-3 {
+    padding-left: 1rem !important;
+  }
+
+  .p-lg-4 {
+    padding: 1.5rem !important;
+  }
+
+  .pt-lg-4,
+.py-lg-4 {
+    padding-top: 1.5rem !important;
+  }
+
+  .pr-lg-4,
+.px-lg-4 {
+    padding-right: 1.5rem !important;
+  }
+
+  .pb-lg-4,
+.py-lg-4 {
+    padding-bottom: 1.5rem !important;
+  }
+
+  .pl-lg-4,
+.px-lg-4 {
+    padding-left: 1.5rem !important;
+  }
+
+  .p-lg-5 {
+    padding: 3rem !important;
+  }
+
+  .pt-lg-5,
+.py-lg-5 {
+    padding-top: 3rem !important;
+  }
+
+  .pr-lg-5,
+.px-lg-5 {
+    padding-right: 3rem !important;
+  }
+
+  .pb-lg-5,
+.py-lg-5 {
+    padding-bottom: 3rem !important;
+  }
+
+  .pl-lg-5,
+.px-lg-5 {
+    padding-left: 3rem !important;
+  }
+
+  .m-lg-n1 {
+    margin: -0.25rem !important;
+  }
+
+  .mt-lg-n1,
+.my-lg-n1 {
+    margin-top: -0.25rem !important;
+  }
+
+  .mr-lg-n1,
+.mx-lg-n1 {
+    margin-right: -0.25rem !important;
+  }
+
+  .mb-lg-n1,
+.my-lg-n1 {
+    margin-bottom: -0.25rem !important;
+  }
+
+  .ml-lg-n1,
+.mx-lg-n1 {
+    margin-left: -0.25rem !important;
+  }
+
+  .m-lg-n2 {
+    margin: -0.5rem !important;
+  }
+
+  .mt-lg-n2,
+.my-lg-n2 {
+    margin-top: -0.5rem !important;
+  }
+
+  .mr-lg-n2,
+.mx-lg-n2 {
+    margin-right: -0.5rem !important;
+  }
+
+  .mb-lg-n2,
+.my-lg-n2 {
+    margin-bottom: -0.5rem !important;
+  }
+
+  .ml-lg-n2,
+.mx-lg-n2 {
+    margin-left: -0.5rem !important;
+  }
+
+  .m-lg-n3 {
+    margin: -1rem !important;
+  }
+
+  .mt-lg-n3,
+.my-lg-n3 {
+    margin-top: -1rem !important;
+  }
+
+  .mr-lg-n3,
+.mx-lg-n3 {
+    margin-right: -1rem !important;
+  }
+
+  .mb-lg-n3,
+.my-lg-n3 {
+    margin-bottom: -1rem !important;
+  }
+
+  .ml-lg-n3,
+.mx-lg-n3 {
+    margin-left: -1rem !important;
+  }
+
+  .m-lg-n4 {
+    margin: -1.5rem !important;
+  }
+
+  .mt-lg-n4,
+.my-lg-n4 {
+    margin-top: -1.5rem !important;
+  }
+
+  .mr-lg-n4,
+.mx-lg-n4 {
+    margin-right: -1.5rem !important;
+  }
+
+  .mb-lg-n4,
+.my-lg-n4 {
+    margin-bottom: -1.5rem !important;
+  }
+
+  .ml-lg-n4,
+.mx-lg-n4 {
+    margin-left: -1.5rem !important;
+  }
+
+  .m-lg-n5 {
+    margin: -3rem !important;
+  }
+
+  .mt-lg-n5,
+.my-lg-n5 {
+    margin-top: -3rem !important;
+  }
+
+  .mr-lg-n5,
+.mx-lg-n5 {
+    margin-right: -3rem !important;
+  }
+
+  .mb-lg-n5,
+.my-lg-n5 {
+    margin-bottom: -3rem !important;
+  }
+
+  .ml-lg-n5,
+.mx-lg-n5 {
+    margin-left: -3rem !important;
+  }
+
+  .m-lg-auto {
+    margin: auto !important;
+  }
+
+  .mt-lg-auto,
+.my-lg-auto {
+    margin-top: auto !important;
+  }
+
+  .mr-lg-auto,
+.mx-lg-auto {
+    margin-right: auto !important;
+  }
+
+  .mb-lg-auto,
+.my-lg-auto {
+    margin-bottom: auto !important;
+  }
+
+  .ml-lg-auto,
+.mx-lg-auto {
+    margin-left: auto !important;
+  }
+}
+@media (min-width: 1200px) {
+  .m-xl-0 {
+    margin: 0 !important;
+  }
+
+  .mt-xl-0,
+.my-xl-0 {
+    margin-top: 0 !important;
+  }
+
+  .mr-xl-0,
+.mx-xl-0 {
+    margin-right: 0 !important;
+  }
+
+  .mb-xl-0,
+.my-xl-0 {
+    margin-bottom: 0 !important;
+  }
+
+  .ml-xl-0,
+.mx-xl-0 {
+    margin-left: 0 !important;
+  }
+
+  .m-xl-1 {
+    margin: 0.25rem !important;
+  }
+
+  .mt-xl-1,
+.my-xl-1 {
+    margin-top: 0.25rem !important;
+  }
+
+  .mr-xl-1,
+.mx-xl-1 {
+    margin-right: 0.25rem !important;
+  }
+
+  .mb-xl-1,
+.my-xl-1 {
+    margin-bottom: 0.25rem !important;
+  }
+
+  .ml-xl-1,
+.mx-xl-1 {
+    margin-left: 0.25rem !important;
+  }
+
+  .m-xl-2 {
+    margin: 0.5rem !important;
+  }
+
+  .mt-xl-2,
+.my-xl-2 {
+    margin-top: 0.5rem !important;
+  }
+
+  .mr-xl-2,
+.mx-xl-2 {
+    margin-right: 0.5rem !important;
+  }
+
+  .mb-xl-2,
+.my-xl-2 {
+    margin-bottom: 0.5rem !important;
+  }
+
+  .ml-xl-2,
+.mx-xl-2 {
+    margin-left: 0.5rem !important;
+  }
+
+  .m-xl-3 {
+    margin: 1rem !important;
+  }
+
+  .mt-xl-3,
+.my-xl-3 {
+    margin-top: 1rem !important;
+  }
+
+  .mr-xl-3,
+.mx-xl-3 {
+    margin-right: 1rem !important;
+  }
+
+  .mb-xl-3,
+.my-xl-3 {
+    margin-bottom: 1rem !important;
+  }
+
+  .ml-xl-3,
+.mx-xl-3 {
+    margin-left: 1rem !important;
+  }
+
+  .m-xl-4 {
+    margin: 1.5rem !important;
+  }
+
+  .mt-xl-4,
+.my-xl-4 {
+    margin-top: 1.5rem !important;
+  }
+
+  .mr-xl-4,
+.mx-xl-4 {
+    margin-right: 1.5rem !important;
+  }
+
+  .mb-xl-4,
+.my-xl-4 {
+    margin-bottom: 1.5rem !important;
+  }
+
+  .ml-xl-4,
+.mx-xl-4 {
+    margin-left: 1.5rem !important;
+  }
+
+  .m-xl-5 {
+    margin: 3rem !important;
+  }
+
+  .mt-xl-5,
+.my-xl-5 {
+    margin-top: 3rem !important;
+  }
+
+  .mr-xl-5,
+.mx-xl-5 {
+    margin-right: 3rem !important;
+  }
+
+  .mb-xl-5,
+.my-xl-5 {
+    margin-bottom: 3rem !important;
+  }
+
+  .ml-xl-5,
+.mx-xl-5 {
+    margin-left: 3rem !important;
+  }
+
+  .p-xl-0 {
+    padding: 0 !important;
+  }
+
+  .pt-xl-0,
+.py-xl-0 {
+    padding-top: 0 !important;
+  }
+
+  .pr-xl-0,
+.px-xl-0 {
+    padding-right: 0 !important;
+  }
+
+  .pb-xl-0,
+.py-xl-0 {
+    padding-bottom: 0 !important;
+  }
+
+  .pl-xl-0,
+.px-xl-0 {
+    padding-left: 0 !important;
+  }
+
+  .p-xl-1 {
+    padding: 0.25rem !important;
+  }
+
+  .pt-xl-1,
+.py-xl-1 {
+    padding-top: 0.25rem !important;
+  }
+
+  .pr-xl-1,
+.px-xl-1 {
+    padding-right: 0.25rem !important;
+  }
+
+  .pb-xl-1,
+.py-xl-1 {
+    padding-bottom: 0.25rem !important;
+  }
+
+  .pl-xl-1,
+.px-xl-1 {
+    padding-left: 0.25rem !important;
+  }
+
+  .p-xl-2 {
+    padding: 0.5rem !important;
+  }
+
+  .pt-xl-2,
+.py-xl-2 {
+    padding-top: 0.5rem !important;
+  }
+
+  .pr-xl-2,
+.px-xl-2 {
+    padding-right: 0.5rem !important;
+  }
+
+  .pb-xl-2,
+.py-xl-2 {
+    padding-bottom: 0.5rem !important;
+  }
+
+  .pl-xl-2,
+.px-xl-2 {
+    padding-left: 0.5rem !important;
+  }
+
+  .p-xl-3 {
+    padding: 1rem !important;
+  }
+
+  .pt-xl-3,
+.py-xl-3 {
+    padding-top: 1rem !important;
+  }
+
+  .pr-xl-3,
+.px-xl-3 {
+    padding-right: 1rem !important;
+  }
+
+  .pb-xl-3,
+.py-xl-3 {
+    padding-bottom: 1rem !important;
+  }
+
+  .pl-xl-3,
+.px-xl-3 {
+    padding-left: 1rem !important;
+  }
+
+  .p-xl-4 {
+    padding: 1.5rem !important;
+  }
+
+  .pt-xl-4,
+.py-xl-4 {
+    padding-top: 1.5rem !important;
+  }
+
+  .pr-xl-4,
+.px-xl-4 {
+    padding-right: 1.5rem !important;
+  }
+
+  .pb-xl-4,
+.py-xl-4 {
+    padding-bottom: 1.5rem !important;
+  }
+
+  .pl-xl-4,
+.px-xl-4 {
+    padding-left: 1.5rem !important;
+  }
+
+  .p-xl-5 {
+    padding: 3rem !important;
+  }
+
+  .pt-xl-5,
+.py-xl-5 {
+    padding-top: 3rem !important;
+  }
+
+  .pr-xl-5,
+.px-xl-5 {
+    padding-right: 3rem !important;
+  }
+
+  .pb-xl-5,
+.py-xl-5 {
+    padding-bottom: 3rem !important;
+  }
+
+  .pl-xl-5,
+.px-xl-5 {
+    padding-left: 3rem !important;
+  }
+
+  .m-xl-n1 {
+    margin: -0.25rem !important;
+  }
+
+  .mt-xl-n1,
+.my-xl-n1 {
+    margin-top: -0.25rem !important;
+  }
+
+  .mr-xl-n1,
+.mx-xl-n1 {
+    margin-right: -0.25rem !important;
+  }
+
+  .mb-xl-n1,
+.my-xl-n1 {
+    margin-bottom: -0.25rem !important;
+  }
+
+  .ml-xl-n1,
+.mx-xl-n1 {
+    margin-left: -0.25rem !important;
+  }
+
+  .m-xl-n2 {
+    margin: -0.5rem !important;
+  }
+
+  .mt-xl-n2,
+.my-xl-n2 {
+    margin-top: -0.5rem !important;
+  }
+
+  .mr-xl-n2,
+.mx-xl-n2 {
+    margin-right: -0.5rem !important;
+  }
+
+  .mb-xl-n2,
+.my-xl-n2 {
+    margin-bottom: -0.5rem !important;
+  }
+
+  .ml-xl-n2,
+.mx-xl-n2 {
+    margin-left: -0.5rem !important;
+  }
+
+  .m-xl-n3 {
+    margin: -1rem !important;
+  }
+
+  .mt-xl-n3,
+.my-xl-n3 {
+    margin-top: -1rem !important;
+  }
+
+  .mr-xl-n3,
+.mx-xl-n3 {
+    margin-right: -1rem !important;
+  }
+
+  .mb-xl-n3,
+.my-xl-n3 {
+    margin-bottom: -1rem !important;
+  }
+
+  .ml-xl-n3,
+.mx-xl-n3 {
+    margin-left: -1rem !important;
+  }
+
+  .m-xl-n4 {
+    margin: -1.5rem !important;
+  }
+
+  .mt-xl-n4,
+.my-xl-n4 {
+    margin-top: -1.5rem !important;
+  }
+
+  .mr-xl-n4,
+.mx-xl-n4 {
+    margin-right: -1.5rem !important;
+  }
+
+  .mb-xl-n4,
+.my-xl-n4 {
+    margin-bottom: -1.5rem !important;
+  }
+
+  .ml-xl-n4,
+.mx-xl-n4 {
+    margin-left: -1.5rem !important;
+  }
+
+  .m-xl-n5 {
+    margin: -3rem !important;
+  }
+
+  .mt-xl-n5,
+.my-xl-n5 {
+    margin-top: -3rem !important;
+  }
+
+  .mr-xl-n5,
+.mx-xl-n5 {
+    margin-right: -3rem !important;
+  }
+
+  .mb-xl-n5,
+.my-xl-n5 {
+    margin-bottom: -3rem !important;
+  }
+
+  .ml-xl-n5,
+.mx-xl-n5 {
+    margin-left: -3rem !important;
+  }
+
+  .m-xl-auto {
+    margin: auto !important;
+  }
+
+  .mt-xl-auto,
+.my-xl-auto {
+    margin-top: auto !important;
+  }
+
+  .mr-xl-auto,
+.mx-xl-auto {
+    margin-right: auto !important;
+  }
+
+  .mb-xl-auto,
+.my-xl-auto {
+    margin-bottom: auto !important;
+  }
+
+  .ml-xl-auto,
+.mx-xl-auto {
+    margin-left: auto !important;
+  }
+}
+.stretched-link::after {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1;
+  pointer-events: auto;
+  content: "";
+  background-color: rgba(0, 0, 0, 0);
+}
+
+.text-monospace {
+  font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important;
+}
+
+.text-justify {
+  text-align: justify !important;
+}
+
+.text-wrap {
+  white-space: normal !important;
+}
+
+.text-nowrap {
+  white-space: nowrap !important;
+}
+
+.text-truncate {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.text-left {
+  text-align: left !important;
+}
+
+.text-right {
+  text-align: right !important;
+}
+
+.text-center {
+  text-align: center !important;
+}
+
+@media (min-width: 576px) {
+  .text-sm-left {
+    text-align: left !important;
+  }
+
+  .text-sm-right {
+    text-align: right !important;
+  }
+
+  .text-sm-center {
+    text-align: center !important;
+  }
+}
+@media (min-width: 768px) {
+  .text-md-left {
+    text-align: left !important;
+  }
+
+  .text-md-right {
+    text-align: right !important;
+  }
+
+  .text-md-center {
+    text-align: center !important;
+  }
+}
+@media (min-width: 992px) {
+  .text-lg-left {
+    text-align: left !important;
+  }
+
+  .text-lg-right {
+    text-align: right !important;
+  }
+
+  .text-lg-center {
+    text-align: center !important;
+  }
+}
+@media (min-width: 1200px) {
+  .text-xl-left {
+    text-align: left !important;
+  }
+
+  .text-xl-right {
+    text-align: right !important;
+  }
+
+  .text-xl-center {
+    text-align: center !important;
+  }
+}
+.text-lowercase {
+  text-transform: lowercase !important;
+}
+
+.text-uppercase {
+  text-transform: uppercase !important;
+}
+
+.text-capitalize {
+  text-transform: capitalize !important;
+}
+
+.font-weight-light {
+  font-weight: 300 !important;
+}
+
+.font-weight-lighter {
+  font-weight: lighter !important;
+}
+
+.font-weight-normal {
+  font-weight: 400 !important;
+}
+
+.font-weight-bold {
+  font-weight: 700 !important;
+}
+
+.font-weight-bolder {
+  font-weight: bolder !important;
+}
+
+.font-italic {
+  font-style: italic !important;
+}
+
+.text-white {
+  color: #fff !important;
+}
+
+.text-primary {
+  color: #5942c1 !important;
+}
+
+a.text-primary:hover, a.text-primary:focus {
+  color: #3e2d89 !important;
+}
+
+.text-secondary {
+  color: #ffe500 !important;
+}
+
+a.text-secondary:hover, a.text-secondary:focus {
+  color: #b3a000 !important;
+}
+
+.text-success {
+  color: #28a745 !important;
+}
+
+a.text-success:hover, a.text-success:focus {
+  color: #19692c !important;
+}
+
+.text-info {
+  color: #17a2b8 !important;
+}
+
+a.text-info:hover, a.text-info:focus {
+  color: #0f6674 !important;
+}
+
+.text-warning {
+  color: #ffe500 !important;
+}
+
+a.text-warning:hover, a.text-warning:focus {
+  color: #b3a000 !important;
+}
+
+.text-danger {
+  color: #dc3545 !important;
+}
+
+a.text-danger:hover, a.text-danger:focus {
+  color: #a71d2a !important;
+}
+
+.text-light {
+  color: #f8f9fa !important;
+}
+
+a.text-light:hover, a.text-light:focus {
+  color: #cbd3da !important;
+}
+
+.text-dark {
+  color: #343a40 !important;
+}
+
+a.text-dark:hover, a.text-dark:focus {
+  color: #121416 !important;
+}
+
+.text-gray {
+  color: #9c9e9f !important;
+}
+
+a.text-gray:hover, a.text-gray:focus {
+  color: #757879 !important;
+}
+
+.text-darker {
+  color: #212529 !important;
+}
+
+a.text-darker:hover, a.text-darker:focus {
+  color: black !important;
+}
+
+.text-body {
+  color: #212529 !important;
+}
+
+.text-muted {
+  color: #6c757d !important;
+}
+
+.text-black-50 {
+  color: rgba(0, 0, 0, 0.5) !important;
+}
+
+.text-white-50 {
+  color: rgba(255, 255, 255, 0.5) !important;
+}
+
+.text-hide {
+  font: 0/0 a;
+  color: transparent;
+  text-shadow: none;
+  background-color: transparent;
+  border: 0;
+}
+
+.text-decoration-none {
+  text-decoration: none !important;
+}
+
+.text-break {
+  word-break: break-word !important;
+  word-wrap: break-word !important;
+}
+
+.text-reset {
+  color: inherit !important;
+}
+
+.visible {
+  visibility: visible !important;
+}
+
+.invisible {
+  visibility: hidden !important;
+}
+
+:root {
+  --blue: #007bff;
+  --indigo: #6610f2;
+  --purple: #5942c1;
+  --pink: #e83e8c;
+  --red: #dc3545;
+  --orange: #fd7e14;
+  --yellow: #ffe500;
+  --green: #28a745;
+  --teal: #20c997;
+  --cyan: #17a2b8;
+  --white: #fff;
+  --gray: #6c757d;
+  --gray-dark: #343a40;
+  --primary: #5942c1;
+  --secondary: #ffe500;
+  --success: #28a745;
+  --info: #17a2b8;
+  --warning: #ffe500;
+  --danger: #dc3545;
+  --light: #f8f9fa;
+  --dark: #343a40;
+  --gray: #9c9e9f;
+  --darker: #212529;
+  --breakpoint-xs: 0;
+  --breakpoint-sm: 576px;
+  --breakpoint-md: 768px;
+  --breakpoint-lg: 992px;
+  --breakpoint-xl: 1200px;
+  --font-family-sans-serif: "Exo 2", BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+  --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+}
+
+h1, h2, h3, h4, h5, h6,
+.h1, .h2, .h3, .h4, .h5, .h6 {
+  margin-bottom: 0.5rem;
+  font-weight: 500;
+  line-height: 1.2;
+}
+
+h1, .h1 {
+  font-size: 3rem;
+}
+@media (max-width: 1200px) {
+  h1, .h1 {
+    font-size: calc(1.425rem + 2.1vw);
+  }
+}
+
+h2, .h2 {
+  font-size: 2rem;
+}
+@media (max-width: 1200px) {
+  h2, .h2 {
+    font-size: calc(1.325rem + 0.9vw);
+  }
+}
+
+h3, .h3 {
+  font-size: 1.75rem;
+}
+@media (max-width: 1200px) {
+  h3, .h3 {
+    font-size: calc(1.3rem + 0.6vw);
+  }
+}
+
+h4, .h4 {
+  font-size: 1.5rem;
+}
+@media (max-width: 1200px) {
+  h4, .h4 {
+    font-size: calc(1.275rem + 0.3vw);
+  }
+}
+
+h5, .h5 {
+  font-size: 1.25rem;
+}
+
+h6, .h6 {
+  font-size: 1rem;
+}
+
+.lead {
+  font-size: 1.25rem;
+  font-weight: 400;
+}
+
+.display-1 {
+  font-size: 6rem;
+  font-weight: 300;
+  line-height: 1.2;
+}
+@media (max-width: 1200px) {
+  .display-1 {
+    font-size: calc(1.725rem + 5.7vw);
+  }
+}
+
+.display-2 {
+  font-size: 5.5rem;
+  font-weight: 300;
+  line-height: 1.2;
+}
+@media (max-width: 1200px) {
+  .display-2 {
+    font-size: calc(1.675rem + 5.1vw);
+  }
+}
+
+.display-3 {
+  font-size: 4.5rem;
+  font-weight: 300;
+  line-height: 1.2;
+}
+@media (max-width: 1200px) {
+  .display-3 {
+    font-size: calc(1.575rem + 3.9vw);
+  }
+}
+
+.display-4 {
+  font-size: 3.5rem;
+  font-weight: 300;
+  line-height: 1.2;
+}
+@media (max-width: 1200px) {
+  .display-4 {
+    font-size: calc(1.475rem + 2.7vw);
+  }
+}
+
+hr {
+  margin-top: 1rem;
+  margin-bottom: 1rem;
+  border: 0;
+  border-top: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+small,
+.small {
+  font-size: 80%;
+  font-weight: 400;
+}
+
+mark,
+.mark {
+  padding: 0.2em;
+  background-color: #fcf8e3;
+}
+
+.list-unstyled {
+  padding-left: 0;
+  list-style: none;
+}
+
+.list-inline {
+  padding-left: 0;
+  list-style: none;
+}
+
+.list-inline-item {
+  display: inline-block;
+}
+.list-inline-item:not(:last-child) {
+  margin-right: 0.5rem;
+}
+
+.initialism {
+  font-size: 90%;
+  text-transform: uppercase;
+}
+
+.blockquote {
+  margin-bottom: 1rem;
+  font-size: 1.25rem;
+}
+
+.blockquote-footer {
+  display: block;
+  font-size: 80%;
+  color: #6c757d;
+}
+.blockquote-footer::before {
+  content: "— ";
+}
+
+.card {
+  position: relative;
+  display: flex;
+  flex-direction: column;
+  min-width: 0;
+  word-wrap: break-word;
+  background-color: #fff;
+  background-clip: border-box;
+  border: 1px solid rgba(0, 0, 0, 0.125);
+  border-radius: 0.3rem;
+}
+.card > hr {
+  margin-right: 0;
+  margin-left: 0;
+}
+.card > .list-group {
+  border-top: inherit;
+  border-bottom: inherit;
+}
+.card > .list-group:first-child {
+  border-top-width: 0;
+  border-top-left-radius: calc(0.3rem - 1px);
+  border-top-right-radius: calc(0.3rem - 1px);
+}
+.card > .list-group:last-child {
+  border-bottom-width: 0;
+  border-bottom-right-radius: calc(0.3rem - 1px);
+  border-bottom-left-radius: calc(0.3rem - 1px);
+}
+.card > .card-header + .list-group,
+.card > .list-group + .card-footer {
+  border-top: 0;
+}
+
+.card-body {
+  flex: 1 1 auto;
+  min-height: 1px;
+  padding: 1.25rem;
+}
+
+.card-title {
+  margin-bottom: 0.75rem;
+}
+
+.card-subtitle {
+  margin-top: -0.375rem;
+  margin-bottom: 0;
+}
+
+.card-text:last-child {
+  margin-bottom: 0;
+}
+
+.card-link:hover {
+  text-decoration: none;
+}
+.card-link + .card-link {
+  margin-left: 1.25rem;
+}
+
+.card-header {
+  padding: 0.75rem 1.25rem;
+  margin-bottom: 0;
+  background-color: rgba(0, 0, 0, 0.03);
+  border-bottom: 1px solid rgba(0, 0, 0, 0.125);
+}
+.card-header:first-child {
+  border-radius: calc(0.3rem - 1px) calc(0.3rem - 1px) 0 0;
+}
+
+.card-footer {
+  padding: 0.75rem 1.25rem;
+  background-color: rgba(0, 0, 0, 0.03);
+  border-top: 1px solid rgba(0, 0, 0, 0.125);
+}
+.card-footer:last-child {
+  border-radius: 0 0 calc(0.3rem - 1px) calc(0.3rem - 1px);
+}
+
+.card-header-tabs {
+  margin-right: -0.625rem;
+  margin-bottom: -0.75rem;
+  margin-left: -0.625rem;
+  border-bottom: 0;
+}
+
+.card-header-pills {
+  margin-right: -0.625rem;
+  margin-left: -0.625rem;
+}
+
+.card-img-overlay {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  padding: 1.25rem;
+  border-radius: calc(0.3rem - 1px);
+}
+
+.card-img,
+.card-img-top,
+.card-img-bottom {
+  flex-shrink: 0;
+  width: 100%;
+}
+
+.card-img,
+.card-img-top {
+  border-top-left-radius: calc(0.3rem - 1px);
+  border-top-right-radius: calc(0.3rem - 1px);
+}
+
+.card-img,
+.card-img-bottom {
+  border-bottom-right-radius: calc(0.3rem - 1px);
+  border-bottom-left-radius: calc(0.3rem - 1px);
+}
+
+.card-deck .card {
+  margin-bottom: 10px;
+}
+@media (min-width: 576px) {
+  .card-deck {
+    display: flex;
+    flex-flow: row wrap;
+    margin-right: -10px;
+    margin-left: -10px;
+  }
+  .card-deck .card {
+    flex: 1 0 0%;
+    margin-right: 10px;
+    margin-bottom: 0;
+    margin-left: 10px;
+  }
+}
+
+.card-group > .card {
+  margin-bottom: 10px;
+}
+@media (min-width: 576px) {
+  .card-group {
+    display: flex;
+    flex-flow: row wrap;
+  }
+  .card-group > .card {
+    flex: 1 0 0%;
+    margin-bottom: 0;
+  }
+  .card-group > .card + .card {
+    margin-left: 0;
+    border-left: 0;
+  }
+  .card-group > .card:not(:last-child) {
+    border-top-right-radius: 0;
+    border-bottom-right-radius: 0;
+  }
+  .card-group > .card:not(:last-child) .card-img-top,
+.card-group > .card:not(:last-child) .card-header {
+    border-top-right-radius: 0;
+  }
+  .card-group > .card:not(:last-child) .card-img-bottom,
+.card-group > .card:not(:last-child) .card-footer {
+    border-bottom-right-radius: 0;
+  }
+  .card-group > .card:not(:first-child) {
+    border-top-left-radius: 0;
+    border-bottom-left-radius: 0;
+  }
+  .card-group > .card:not(:first-child) .card-img-top,
+.card-group > .card:not(:first-child) .card-header {
+    border-top-left-radius: 0;
+  }
+  .card-group > .card:not(:first-child) .card-img-bottom,
+.card-group > .card:not(:first-child) .card-footer {
+    border-bottom-left-radius: 0;
+  }
+}
+
+.card-columns .card {
+  margin-bottom: 0.75rem;
+}
+@media (min-width: 576px) {
+  .card-columns {
+    column-count: 3;
+    column-gap: 1.25rem;
+    orphans: 1;
+    widows: 1;
+  }
+  .card-columns .card {
+    display: inline-block;
+    width: 100%;
+  }
+}
+
+.accordion {
+  overflow-anchor: none;
+}
+.accordion > .card {
+  overflow: hidden;
+}
+.accordion > .card:not(:last-of-type) {
+  border-bottom: 0;
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+.accordion > .card:not(:first-of-type) {
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+}
+.accordion > .card > .card-header {
+  border-radius: 0;
+  margin-bottom: -1px;
+}
+
+.badge {
+  display: inline-block;
+  padding: 0.25em 0.4em;
+  font-size: 75%;
+  font-weight: 700;
+  line-height: 1;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: baseline;
+  border-radius: 0.3rem;
+  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+  .badge {
+    transition: none;
+  }
+}
+a.badge:hover, a.badge:focus {
+  text-decoration: none;
+}
+
+.badge:empty {
+  display: none;
+}
+
+.btn .badge {
+  position: relative;
+  top: -1px;
+}
+
+.badge-pill {
+  padding-right: 0.6em;
+  padding-left: 0.6em;
+  border-radius: 10rem;
+}
+
+.badge-primary {
+  color: #fff;
+  background-color: #5942c1;
+}
+a.badge-primary:hover, a.badge-primary:focus {
+  color: #fff;
+  background-color: #46339d;
+}
+a.badge-primary:focus, a.badge-primary.focus {
+  outline: 0;
+  box-shadow: 0 0 0 0.2rem rgba(89, 66, 193, 0.5);
+}
+
+.badge-secondary {
+  color: #212529;
+  background-color: #ffe500;
+}
+a.badge-secondary:hover, a.badge-secondary:focus {
+  color: #212529;
+  background-color: #ccb700;
+}
+a.badge-secondary:focus, a.badge-secondary.focus {
+  outline: 0;
+  box-shadow: 0 0 0 0.2rem rgba(255, 229, 0, 0.5);
+}
+
+.badge-success {
+  color: #fff;
+  background-color: #28a745;
+}
+a.badge-success:hover, a.badge-success:focus {
+  color: #fff;
+  background-color: #1e7e34;
+}
+a.badge-success:focus, a.badge-success.focus {
+  outline: 0;
+  box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);
+}
+
+.badge-info {
+  color: #fff;
+  background-color: #17a2b8;
+}
+a.badge-info:hover, a.badge-info:focus {
+  color: #fff;
+  background-color: #117a8b;
+}
+a.badge-info:focus, a.badge-info.focus {
+  outline: 0;
+  box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);
+}
+
+.badge-warning {
+  color: #212529;
+  background-color: #ffe500;
+}
+a.badge-warning:hover, a.badge-warning:focus {
+  color: #212529;
+  background-color: #ccb700;
+}
+a.badge-warning:focus, a.badge-warning.focus {
+  outline: 0;
+  box-shadow: 0 0 0 0.2rem rgba(255, 229, 0, 0.5);
+}
+
+.badge-danger {
+  color: #fff;
+  background-color: #dc3545;
+}
+a.badge-danger:hover, a.badge-danger:focus {
+  color: #fff;
+  background-color: #bd2130;
+}
+a.badge-danger:focus, a.badge-danger.focus {
+  outline: 0;
+  box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);
+}
+
+.badge-light {
+  color: #212529;
+  background-color: #f8f9fa;
+}
+a.badge-light:hover, a.badge-light:focus {
+  color: #212529;
+  background-color: #dae0e5;
+}
+a.badge-light:focus, a.badge-light.focus {
+  outline: 0;
+  box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);
+}
+
+.badge-dark {
+  color: #fff;
+  background-color: #343a40;
+}
+a.badge-dark:hover, a.badge-dark:focus {
+  color: #fff;
+  background-color: #1d2124;
+}
+a.badge-dark:focus, a.badge-dark.focus {
+  outline: 0;
+  box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);
+}
+
+.badge-gray {
+  color: #212529;
+  background-color: #9c9e9f;
+}
+a.badge-gray:hover, a.badge-gray:focus {
+  color: #212529;
+  background-color: #828586;
+}
+a.badge-gray:focus, a.badge-gray.focus {
+  outline: 0;
+  box-shadow: 0 0 0 0.2rem rgba(156, 158, 159, 0.5);
+}
+
+.badge-darker {
+  color: #fff;
+  background-color: #212529;
+}
+a.badge-darker:hover, a.badge-darker:focus {
+  color: #fff;
+  background-color: #0a0c0d;
+}
+a.badge-darker:focus, a.badge-darker.focus {
+  outline: 0;
+  box-shadow: 0 0 0 0.2rem rgba(33, 37, 41, 0.5);
+}
+
+.form-control {
+  display: block;
+  width: 100%;
+  height: calc(1.5em + 1.5rem + 2px);
+  padding: 0.75rem 1.2rem;
+  font-size: 1rem;
+  font-weight: 400;
+  line-height: 1.5;
+  color: #495057;
+  background-color: #fff;
+  background-clip: padding-box;
+  border: 1px solid #ced4da;
+  border-radius: 0.3rem;
+  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+  .form-control {
+    transition: none;
+  }
+}
+.form-control::-ms-expand {
+  background-color: transparent;
+  border: 0;
+}
+.form-control:-moz-focusring {
+  color: transparent;
+  text-shadow: 0 0 0 #495057;
+}
+.form-control:focus {
+  color: #495057;
+  background-color: #fff;
+  border-color: #ada2e0;
+  outline: 0;
+  box-shadow: 0 0 0 0.2rem rgba(89, 66, 193, 0.25);
+}
+.form-control::placeholder {
+  color: #6c757d;
+  opacity: 1;
+}
+.form-control:disabled, .form-control[readonly] {
+  background-color: #e9ecef;
+  opacity: 1;
+}
+
+input[type=date].form-control,
+input[type=time].form-control,
+input[type=datetime-local].form-control,
+input[type=month].form-control {
+  appearance: none;
+}
+
+select.form-control:focus::-ms-value {
+  color: #495057;
+  background-color: #fff;
+}
+
+.form-control-file,
+.form-control-range {
+  display: block;
+  width: 100%;
+}
+
+.col-form-label {
+  padding-top: calc(0.75rem + 1px);
+  padding-bottom: calc(0.75rem + 1px);
+  margin-bottom: 0;
+  font-size: inherit;
+  line-height: 1.5;
+}
+
+.col-form-label-lg {
+  padding-top: calc(0.5rem + 1px);
+  padding-bottom: calc(0.5rem + 1px);
+  font-size: 1.25rem;
+  line-height: 1.5;
+}
+
+.col-form-label-sm {
+  padding-top: calc(0.25rem + 1px);
+  padding-bottom: calc(0.25rem + 1px);
+  font-size: 0.875rem;
+  line-height: 1.5;
+}
+
+.form-control-plaintext {
+  display: block;
+  width: 100%;
+  padding: 0.75rem 0;
+  margin-bottom: 0;
+  font-size: 1rem;
+  line-height: 1.5;
+  color: #212529;
+  background-color: transparent;
+  border: solid transparent;
+  border-width: 1px 0;
+}
+.form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg {
+  padding-right: 0;
+  padding-left: 0;
+}
+
+.form-control-sm {
+  height: calc(1.5em + 0.5rem + 2px);
+  padding: 0.25rem 0.5rem;
+  font-size: 0.875rem;
+  line-height: 1.5;
+  border-radius: 0.2rem;
+}
+
+.form-control-lg {
+  height: calc(1.5em + 1rem + 2px);
+  padding: 0.5rem 1rem;
+  font-size: 1.25rem;
+  line-height: 1.5;
+  border-radius: 0.3rem;
+}
+
+select.form-control[size], select.form-control[multiple] {
+  height: auto;
+}
+
+textarea.form-control {
+  height: auto;
+}
+
+.form-group {
+  margin-bottom: 1rem;
+}
+
+.form-text {
+  display: block;
+  margin-top: 0.25rem;
+}
+
+.form-row {
+  display: flex;
+  flex-wrap: wrap;
+  margin-right: -5px;
+  margin-left: -5px;
+}
+.form-row > .col,
+.form-row > [class*=col-] {
+  padding-right: 5px;
+  padding-left: 5px;
+}
+
+.form-check {
+  position: relative;
+  display: block;
+  padding-left: 1.25rem;
+}
+
+.form-check-input {
+  position: absolute;
+  margin-top: 0.3rem;
+  margin-left: -1.25rem;
+}
+.form-check-input[disabled] ~ .form-check-label, .form-check-input:disabled ~ .form-check-label {
+  color: #6c757d;
+}
+
+.form-check-label {
+  margin-bottom: 0;
+}
+
+.form-check-inline {
+  display: inline-flex;
+  align-items: center;
+  padding-left: 0;
+  margin-right: 0.75rem;
+}
+.form-check-inline .form-check-input {
+  position: static;
+  margin-top: 0;
+  margin-right: 0.3125rem;
+  margin-left: 0;
+}
+
+.valid-feedback {
+  display: none;
+  width: 100%;
+  margin-top: 0.25rem;
+  font-size: 80%;
+  color: #28a745;
+}
+
+.valid-tooltip {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  z-index: 5;
+  display: none;
+  max-width: 100%;
+  padding: 0.25rem 0.5rem;
+  margin-top: 0.1rem;
+  font-size: 0.875rem;
+  line-height: 1.5;
+  color: #fff;
+  background-color: rgba(40, 167, 69, 0.9);
+  border-radius: 0.3rem;
+}
+.form-row > .col > .valid-tooltip, .form-row > [class*=col-] > .valid-tooltip {
+  left: 5px;
+}
+
+.was-validated :valid ~ .valid-feedback,
+.was-validated :valid ~ .valid-tooltip,
+.is-valid ~ .valid-feedback,
+.is-valid ~ .valid-tooltip {
+  display: block;
+}
+
+.was-validated .form-control:valid, .form-control.is-valid {
+  border-color: #28a745;
+  padding-right: calc(1.5em + 1.5rem);
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");
+  background-repeat: no-repeat;
+  background-position: right calc(0.375em + 0.375rem) center;
+  background-size: calc(0.75em + 0.75rem) calc(0.75em + 0.75rem);
+}
+.was-validated .form-control:valid:focus, .form-control.is-valid:focus {
+  border-color: #28a745;
+  box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
+}
+
+.was-validated textarea.form-control:valid, textarea.form-control.is-valid {
+  padding-right: calc(1.5em + 1.5rem);
+  background-position: top calc(0.375em + 0.375rem) right calc(0.375em + 0.375rem);
+}
+
+.was-validated .custom-select:valid, .custom-select.is-valid {
+  border-color: #28a745;
+  padding-right: calc(0.75em + 3.325rem);
+  background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right 1.2rem center/8px 10px no-repeat, #fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") center right 2.2rem/calc(0.75em + 0.75rem) calc(0.75em + 0.75rem) no-repeat;
+}
+.was-validated .custom-select:valid:focus, .custom-select.is-valid:focus {
+  border-color: #28a745;
+  box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
+}
+
+.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label {
+  color: #28a745;
+}
+.was-validated .form-check-input:valid ~ .valid-feedback,
+.was-validated .form-check-input:valid ~ .valid-tooltip, .form-check-input.is-valid ~ .valid-feedback,
+.form-check-input.is-valid ~ .valid-tooltip {
+  display: block;
+}
+
+.was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label {
+  color: #28a745;
+}
+.was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before {
+  border-color: #28a745;
+}
+.was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before {
+  border-color: #34ce57;
+  background-color: #34ce57;
+}
+.was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before {
+  box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
+}
+.was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before {
+  border-color: #28a745;
+}
+
+.was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label {
+  border-color: #28a745;
+}
+.was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label {
+  border-color: #28a745;
+  box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
+}
+
+.invalid-feedback {
+  display: none;
+  width: 100%;
+  margin-top: 0.25rem;
+  font-size: 80%;
+  color: #dc3545;
+}
+
+.invalid-tooltip {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  z-index: 5;
+  display: none;
+  max-width: 100%;
+  padding: 0.25rem 0.5rem;
+  margin-top: 0.1rem;
+  font-size: 0.875rem;
+  line-height: 1.5;
+  color: #fff;
+  background-color: rgba(220, 53, 69, 0.9);
+  border-radius: 0.3rem;
+}
+.form-row > .col > .invalid-tooltip, .form-row > [class*=col-] > .invalid-tooltip {
+  left: 5px;
+}
+
+.was-validated :invalid ~ .invalid-feedback,
+.was-validated :invalid ~ .invalid-tooltip,
+.is-invalid ~ .invalid-feedback,
+.is-invalid ~ .invalid-tooltip {
+  display: block;
+}
+
+.was-validated .form-control:invalid, .form-control.is-invalid {
+  border-color: #dc3545;
+  padding-right: calc(1.5em + 1.5rem);
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
+  background-repeat: no-repeat;
+  background-position: right calc(0.375em + 0.375rem) center;
+  background-size: calc(0.75em + 0.75rem) calc(0.75em + 0.75rem);
+}
+.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus {
+  border-color: #dc3545;
+  box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
+}
+
+.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid {
+  padding-right: calc(1.5em + 1.5rem);
+  background-position: top calc(0.375em + 0.375rem) right calc(0.375em + 0.375rem);
+}
+
+.was-validated .custom-select:invalid, .custom-select.is-invalid {
+  border-color: #dc3545;
+  padding-right: calc(0.75em + 3.325rem);
+  background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right 1.2rem center/8px 10px no-repeat, #fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") center right 2.2rem/calc(0.75em + 0.75rem) calc(0.75em + 0.75rem) no-repeat;
+}
+.was-validated .custom-select:invalid:focus, .custom-select.is-invalid:focus {
+  border-color: #dc3545;
+  box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
+}
+
+.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label {
+  color: #dc3545;
+}
+.was-validated .form-check-input:invalid ~ .invalid-feedback,
+.was-validated .form-check-input:invalid ~ .invalid-tooltip, .form-check-input.is-invalid ~ .invalid-feedback,
+.form-check-input.is-invalid ~ .invalid-tooltip {
+  display: block;
+}
+
+.was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label {
+  color: #dc3545;
+}
+.was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before {
+  border-color: #dc3545;
+}
+.was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before {
+  border-color: #e4606d;
+  background-color: #e4606d;
+}
+.was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before {
+  box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
+}
+.was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before {
+  border-color: #dc3545;
+}
+
+.was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label {
+  border-color: #dc3545;
+}
+.was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label {
+  border-color: #dc3545;
+  box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
+}
+
+.form-inline {
+  display: flex;
+  flex-flow: row wrap;
+  align-items: center;
+}
+.form-inline .form-check {
+  width: 100%;
+}
+@media (min-width: 576px) {
+  .form-inline label {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-bottom: 0;
+  }
+  .form-inline .form-group {
+    display: flex;
+    flex: 0 0 auto;
+    flex-flow: row wrap;
+    align-items: center;
+    margin-bottom: 0;
+  }
+  .form-inline .form-control {
+    display: inline-block;
+    width: auto;
+    vertical-align: middle;
+  }
+  .form-inline .form-control-plaintext {
+    display: inline-block;
+  }
+  .form-inline .input-group,
+.form-inline .custom-select {
+    width: auto;
+  }
+  .form-inline .form-check {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: auto;
+    padding-left: 0;
+  }
+  .form-inline .form-check-input {
+    position: relative;
+    flex-shrink: 0;
+    margin-top: 0;
+    margin-right: 0.25rem;
+    margin-left: 0;
+  }
+  .form-inline .custom-control {
+    align-items: center;
+    justify-content: center;
+  }
+  .form-inline .custom-control-label {
+    margin-bottom: 0;
+  }
+}
+
+.custom-control {
+  position: relative;
+  z-index: 1;
+  display: block;
+  min-height: 1.5rem;
+  padding-left: 1.5rem;
+  color-adjust: exact;
+}
+
+.custom-control-inline {
+  display: inline-flex;
+  margin-right: 1rem;
+}
+
+.custom-control-input {
+  position: absolute;
+  left: 0;
+  z-index: -1;
+  width: 1rem;
+  height: 1.25rem;
+  opacity: 0;
+}
+.custom-control-input:checked ~ .custom-control-label::before {
+  color: #fff;
+  border-color: #5942c1;
+  background-color: #5942c1;
+}
+.custom-control-input:focus ~ .custom-control-label::before {
+  box-shadow: 0 0 0 0.2rem rgba(89, 66, 193, 0.25);
+}
+.custom-control-input:focus:not(:checked) ~ .custom-control-label::before {
+  border-color: #ada2e0;
+}
+.custom-control-input:not(:disabled):active ~ .custom-control-label::before {
+  color: #fff;
+  background-color: #cfc8ed;
+  border-color: #cfc8ed;
+}
+.custom-control-input[disabled] ~ .custom-control-label, .custom-control-input:disabled ~ .custom-control-label {
+  color: #6c757d;
+}
+.custom-control-input[disabled] ~ .custom-control-label::before, .custom-control-input:disabled ~ .custom-control-label::before {
+  background-color: #e9ecef;
+}
+
+.custom-control-label {
+  position: relative;
+  margin-bottom: 0;
+  vertical-align: top;
+}
+.custom-control-label::before {
+  position: absolute;
+  top: 0.25rem;
+  left: -1.5rem;
+  display: block;
+  width: 1rem;
+  height: 1rem;
+  pointer-events: none;
+  content: "";
+  background-color: #fff;
+  border: #adb5bd solid 1px;
+}
+.custom-control-label::after {
+  position: absolute;
+  top: 0.25rem;
+  left: -1.5rem;
+  display: block;
+  width: 1rem;
+  height: 1rem;
+  content: "";
+  background: 50%/50% 50% no-repeat;
+}
+
+.custom-checkbox .custom-control-label::before {
+  border-radius: 0.3rem;
+}
+.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after {
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e");
+}
+.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before {
+  border-color: #5942c1;
+  background-color: #5942c1;
+}
+.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after {
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e");
+}
+.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before {
+  background-color: rgba(89, 66, 193, 0.5);
+}
+.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before {
+  background-color: rgba(89, 66, 193, 0.5);
+}
+
+.custom-radio .custom-control-label::before {
+  border-radius: 50%;
+}
+.custom-radio .custom-control-input:checked ~ .custom-control-label::after {
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");
+}
+.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before {
+  background-color: rgba(89, 66, 193, 0.5);
+}
+
+.custom-switch {
+  padding-left: 2.25rem;
+}
+.custom-switch .custom-control-label::before {
+  left: -2.25rem;
+  width: 1.75rem;
+  pointer-events: all;
+  border-radius: 0.5rem;
+}
+.custom-switch .custom-control-label::after {
+  top: calc(0.25rem + 2px);
+  left: calc(-2.25rem + 2px);
+  width: calc(1rem - 4px);
+  height: calc(1rem - 4px);
+  background-color: #adb5bd;
+  border-radius: 0.5rem;
+  transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+  .custom-switch .custom-control-label::after {
+    transition: none;
+  }
+}
+.custom-switch .custom-control-input:checked ~ .custom-control-label::after {
+  background-color: #fff;
+  transform: translateX(0.75rem);
+}
+.custom-switch .custom-control-input:disabled:checked ~ .custom-control-label::before {
+  background-color: rgba(89, 66, 193, 0.5);
+}
+
+.custom-select {
+  display: inline-block;
+  width: 100%;
+  height: calc(1.5em + 1.5rem + 2px);
+  padding: 0.75rem 2.2rem 0.75rem 1.2rem;
+  font-size: 1rem;
+  font-weight: 400;
+  line-height: 1.5;
+  color: #495057;
+  vertical-align: middle;
+  background: #fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right 1.2rem center/8px 10px no-repeat;
+  border: 1px solid #ced4da;
+  border-radius: 0.3rem;
+  appearance: none;
+}
+.custom-select:focus {
+  border-color: #ada2e0;
+  outline: 0;
+  box-shadow: 0 0 0 0.2rem rgba(89, 66, 193, 0.25);
+}
+.custom-select:focus::-ms-value {
+  color: #495057;
+  background-color: #fff;
+}
+.custom-select[multiple], .custom-select[size]:not([size="1"]) {
+  height: auto;
+  padding-right: 1.2rem;
+  background-image: none;
+}
+.custom-select:disabled {
+  color: #6c757d;
+  background-color: #e9ecef;
+}
+.custom-select::-ms-expand {
+  display: none;
+}
+.custom-select:-moz-focusring {
+  color: transparent;
+  text-shadow: 0 0 0 #495057;
+}
+
+.custom-select-sm {
+  height: calc(1.5em + 0.5rem + 2px);
+  padding-top: 0.25rem;
+  padding-bottom: 0.25rem;
+  padding-left: 0.5rem;
+  font-size: 0.875rem;
+}
+
+.custom-select-lg {
+  height: calc(1.5em + 1rem + 2px);
+  padding-top: 0.5rem;
+  padding-bottom: 0.5rem;
+  padding-left: 1rem;
+  font-size: 1.25rem;
+}
+
+.custom-file {
+  position: relative;
+  display: inline-block;
+  width: 100%;
+  height: calc(1.5em + 1.5rem + 2px);
+  margin-bottom: 0;
+}
+
+.custom-file-input {
+  position: relative;
+  z-index: 2;
+  width: 100%;
+  height: calc(1.5em + 1.5rem + 2px);
+  margin: 0;
+  overflow: hidden;
+  opacity: 0;
+}
+.custom-file-input:focus ~ .custom-file-label {
+  border-color: #ada2e0;
+  box-shadow: 0 0 0 0.2rem rgba(89, 66, 193, 0.25);
+}
+.custom-file-input[disabled] ~ .custom-file-label, .custom-file-input:disabled ~ .custom-file-label {
+  background-color: #e9ecef;
+}
+.custom-file-input:lang(en) ~ .custom-file-label::after {
+  content: "Browse";
+}
+.custom-file-input ~ .custom-file-label[data-browse]::after {
+  content: attr(data-browse);
+}
+
+.custom-file-label {
+  position: absolute;
+  top: 0;
+  right: 0;
+  left: 0;
+  z-index: 1;
+  height: calc(1.5em + 1.5rem + 2px);
+  padding: 0.75rem 1.2rem;
+  overflow: hidden;
+  font-weight: 400;
+  line-height: 1.5;
+  color: #495057;
+  background-color: #fff;
+  border: 1px solid #ced4da;
+  border-radius: 0.3rem;
+}
+.custom-file-label::after {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 3;
+  display: block;
+  height: calc(1.5em + 1.5rem);
+  padding: 0.75rem 1.2rem;
+  line-height: 1.5;
+  color: #495057;
+  content: "Browse";
+  background-color: #e9ecef;
+  border-left: inherit;
+  border-radius: 0 0.3rem 0.3rem 0;
+}
+
+.custom-range {
+  width: 100%;
+  height: 1.4rem;
+  padding: 0;
+  background-color: transparent;
+  appearance: none;
+}
+.custom-range:focus {
+  outline: 0;
+}
+.custom-range:focus::-webkit-slider-thumb {
+  box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(89, 66, 193, 0.25);
+}
+.custom-range:focus::-moz-range-thumb {
+  box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(89, 66, 193, 0.25);
+}
+.custom-range:focus::-ms-thumb {
+  box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(89, 66, 193, 0.25);
+}
+.custom-range::-moz-focus-outer {
+  border: 0;
+}
+.custom-range::-webkit-slider-thumb {
+  width: 1rem;
+  height: 1rem;
+  margin-top: -0.25rem;
+  background-color: #5942c1;
+  border: 0;
+  border-radius: 1rem;
+  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+  appearance: none;
+}
+@media (prefers-reduced-motion: reduce) {
+  .custom-range::-webkit-slider-thumb {
+    transition: none;
+  }
+}
+.custom-range::-webkit-slider-thumb:active {
+  background-color: #cfc8ed;
+}
+.custom-range::-webkit-slider-runnable-track {
+  width: 100%;
+  height: 0.5rem;
+  color: transparent;
+  cursor: pointer;
+  background-color: #dee2e6;
+  border-color: transparent;
+  border-radius: 1rem;
+}
+.custom-range::-moz-range-thumb {
+  width: 1rem;
+  height: 1rem;
+  background-color: #5942c1;
+  border: 0;
+  border-radius: 1rem;
+  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+  appearance: none;
+}
+@media (prefers-reduced-motion: reduce) {
+  .custom-range::-moz-range-thumb {
+    transition: none;
+  }
+}
+.custom-range::-moz-range-thumb:active {
+  background-color: #cfc8ed;
+}
+.custom-range::-moz-range-track {
+  width: 100%;
+  height: 0.5rem;
+  color: transparent;
+  cursor: pointer;
+  background-color: #dee2e6;
+  border-color: transparent;
+  border-radius: 1rem;
+}
+.custom-range::-ms-thumb {
+  width: 1rem;
+  height: 1rem;
+  margin-top: 0;
+  margin-right: 0.2rem;
+  margin-left: 0.2rem;
+  background-color: #5942c1;
+  border: 0;
+  border-radius: 1rem;
+  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+  appearance: none;
+}
+@media (prefers-reduced-motion: reduce) {
+  .custom-range::-ms-thumb {
+    transition: none;
+  }
+}
+.custom-range::-ms-thumb:active {
+  background-color: #cfc8ed;
+}
+.custom-range::-ms-track {
+  width: 100%;
+  height: 0.5rem;
+  color: transparent;
+  cursor: pointer;
+  background-color: transparent;
+  border-color: transparent;
+  border-width: 0.5rem;
+}
+.custom-range::-ms-fill-lower {
+  background-color: #dee2e6;
+  border-radius: 1rem;
+}
+.custom-range::-ms-fill-upper {
+  margin-right: 15px;
+  background-color: #dee2e6;
+  border-radius: 1rem;
+}
+.custom-range:disabled::-webkit-slider-thumb {
+  background-color: #adb5bd;
+}
+.custom-range:disabled::-webkit-slider-runnable-track {
+  cursor: default;
+}
+.custom-range:disabled::-moz-range-thumb {
+  background-color: #adb5bd;
+}
+.custom-range:disabled::-moz-range-track {
+  cursor: default;
+}
+.custom-range:disabled::-ms-thumb {
+  background-color: #adb5bd;
+}
+
+.custom-control-label::before,
+.custom-file-label,
+.custom-select {
+  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+  .custom-control-label::before,
+.custom-file-label,
+.custom-select {
+    transition: none;
+  }
+}
+
+.list-group {
+  display: flex;
+  flex-direction: column;
+  padding-left: 0;
+  margin-bottom: 0;
+  border-radius: 0.3rem;
+}
+
+.list-group-item-action {
+  width: 100%;
+  color: #495057;
+  text-align: inherit;
+}
+.list-group-item-action:hover, .list-group-item-action:focus {
+  z-index: 1;
+  color: #495057;
+  text-decoration: none;
+  background-color: #f8f9fa;
+}
+.list-group-item-action:active {
+  color: #212529;
+  background-color: #e9ecef;
+}
+
+.list-group-item {
+  position: relative;
+  display: block;
+  padding: 0.75rem 1.25rem;
+  background-color: #fff;
+  border: 1px solid rgba(0, 0, 0, 0.125);
+}
+.list-group-item:first-child {
+  border-top-left-radius: inherit;
+  border-top-right-radius: inherit;
+}
+.list-group-item:last-child {
+  border-bottom-right-radius: inherit;
+  border-bottom-left-radius: inherit;
+}
+.list-group-item.disabled, .list-group-item:disabled {
+  color: #6c757d;
+  pointer-events: none;
+  background-color: #fff;
+}
+.list-group-item.active {
+  z-index: 2;
+  color: #fff;
+  background-color: #5942c1;
+  border-color: #5942c1;
+}
+.list-group-item + .list-group-item {
+  border-top-width: 0;
+}
+.list-group-item + .list-group-item.active {
+  margin-top: -1px;
+  border-top-width: 1px;
+}
+
+.list-group-horizontal {
+  flex-direction: row;
+}
+.list-group-horizontal > .list-group-item:first-child {
+  border-bottom-left-radius: 0.3rem;
+  border-top-right-radius: 0;
+}
+.list-group-horizontal > .list-group-item:last-child {
+  border-top-right-radius: 0.3rem;
+  border-bottom-left-radius: 0;
+}
+.list-group-horizontal > .list-group-item.active {
+  margin-top: 0;
+}
+.list-group-horizontal > .list-group-item + .list-group-item {
+  border-top-width: 1px;
+  border-left-width: 0;
+}
+.list-group-horizontal > .list-group-item + .list-group-item.active {
+  margin-left: -1px;
+  border-left-width: 1px;
+}
+
+@media (min-width: 576px) {
+  .list-group-horizontal-sm {
+    flex-direction: row;
+  }
+  .list-group-horizontal-sm > .list-group-item:first-child {
+    border-bottom-left-radius: 0.3rem;
+    border-top-right-radius: 0;
+  }
+  .list-group-horizontal-sm > .list-group-item:last-child {
+    border-top-right-radius: 0.3rem;
+    border-bottom-left-radius: 0;
+  }
+  .list-group-horizontal-sm > .list-group-item.active {
+    margin-top: 0;
+  }
+  .list-group-horizontal-sm > .list-group-item + .list-group-item {
+    border-top-width: 1px;
+    border-left-width: 0;
+  }
+  .list-group-horizontal-sm > .list-group-item + .list-group-item.active {
+    margin-left: -1px;
+    border-left-width: 1px;
+  }
+}
+@media (min-width: 768px) {
+  .list-group-horizontal-md {
+    flex-direction: row;
+  }
+  .list-group-horizontal-md > .list-group-item:first-child {
+    border-bottom-left-radius: 0.3rem;
+    border-top-right-radius: 0;
+  }
+  .list-group-horizontal-md > .list-group-item:last-child {
+    border-top-right-radius: 0.3rem;
+    border-bottom-left-radius: 0;
+  }
+  .list-group-horizontal-md > .list-group-item.active {
+    margin-top: 0;
+  }
+  .list-group-horizontal-md > .list-group-item + .list-group-item {
+    border-top-width: 1px;
+    border-left-width: 0;
+  }
+  .list-group-horizontal-md > .list-group-item + .list-group-item.active {
+    margin-left: -1px;
+    border-left-width: 1px;
+  }
+}
+@media (min-width: 992px) {
+  .list-group-horizontal-lg {
+    flex-direction: row;
+  }
+  .list-group-horizontal-lg > .list-group-item:first-child {
+    border-bottom-left-radius: 0.3rem;
+    border-top-right-radius: 0;
+  }
+  .list-group-horizontal-lg > .list-group-item:last-child {
+    border-top-right-radius: 0.3rem;
+    border-bottom-left-radius: 0;
+  }
+  .list-group-horizontal-lg > .list-group-item.active {
+    margin-top: 0;
+  }
+  .list-group-horizontal-lg > .list-group-item + .list-group-item {
+    border-top-width: 1px;
+    border-left-width: 0;
+  }
+  .list-group-horizontal-lg > .list-group-item + .list-group-item.active {
+    margin-left: -1px;
+    border-left-width: 1px;
+  }
+}
+@media (min-width: 1200px) {
+  .list-group-horizontal-xl {
+    flex-direction: row;
+  }
+  .list-group-horizontal-xl > .list-group-item:first-child {
+    border-bottom-left-radius: 0.3rem;
+    border-top-right-radius: 0;
+  }
+  .list-group-horizontal-xl > .list-group-item:last-child {
+    border-top-right-radius: 0.3rem;
+    border-bottom-left-radius: 0;
+  }
+  .list-group-horizontal-xl > .list-group-item.active {
+    margin-top: 0;
+  }
+  .list-group-horizontal-xl > .list-group-item + .list-group-item {
+    border-top-width: 1px;
+    border-left-width: 0;
+  }
+  .list-group-horizontal-xl > .list-group-item + .list-group-item.active {
+    margin-left: -1px;
+    border-left-width: 1px;
+  }
+}
+.list-group-flush {
+  border-radius: 0;
+}
+.list-group-flush > .list-group-item {
+  border-width: 0 0 1px;
+}
+.list-group-flush > .list-group-item:last-child {
+  border-bottom-width: 0;
+}
+
+.list-group-item-primary {
+  color: #2e2264;
+  background-color: #d1caee;
+}
+.list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus {
+  color: #2e2264;
+  background-color: #c0b7e8;
+}
+.list-group-item-primary.list-group-item-action.active {
+  color: #fff;
+  background-color: #2e2264;
+  border-color: #2e2264;
+}
+
+.list-group-item-secondary {
+  color: #857700;
+  background-color: #fff8b8;
+}
+.list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus {
+  color: #857700;
+  background-color: #fff59f;
+}
+.list-group-item-secondary.list-group-item-action.active {
+  color: #fff;
+  background-color: #857700;
+  border-color: #857700;
+}
+
+.list-group-item-success {
+  color: #155724;
+  background-color: #c3e6cb;
+}
+.list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus {
+  color: #155724;
+  background-color: #b1dfbb;
+}
+.list-group-item-success.list-group-item-action.active {
+  color: #fff;
+  background-color: #155724;
+  border-color: #155724;
+}
+
+.list-group-item-info {
+  color: #0c5460;
+  background-color: #bee5eb;
+}
+.list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus {
+  color: #0c5460;
+  background-color: #abdde5;
+}
+.list-group-item-info.list-group-item-action.active {
+  color: #fff;
+  background-color: #0c5460;
+  border-color: #0c5460;
+}
+
+.list-group-item-warning {
+  color: #857700;
+  background-color: #fff8b8;
+}
+.list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus {
+  color: #857700;
+  background-color: #fff59f;
+}
+.list-group-item-warning.list-group-item-action.active {
+  color: #fff;
+  background-color: #857700;
+  border-color: #857700;
+}
+
+.list-group-item-danger {
+  color: #721c24;
+  background-color: #f5c6cb;
+}
+.list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus {
+  color: #721c24;
+  background-color: #f1b0b7;
+}
+.list-group-item-danger.list-group-item-action.active {
+  color: #fff;
+  background-color: #721c24;
+  border-color: #721c24;
+}
+
+.list-group-item-light {
+  color: #818182;
+  background-color: #fdfdfe;
+}
+.list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus {
+  color: #818182;
+  background-color: #ececf6;
+}
+.list-group-item-light.list-group-item-action.active {
+  color: #fff;
+  background-color: #818182;
+  border-color: #818182;
+}
+
+.list-group-item-dark {
+  color: #1b1e21;
+  background-color: #c6c8ca;
+}
+.list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus {
+  color: #1b1e21;
+  background-color: #b9bbbe;
+}
+.list-group-item-dark.list-group-item-action.active {
+  color: #fff;
+  background-color: #1b1e21;
+  border-color: #1b1e21;
+}
+
+.list-group-item-gray {
+  color: #515253;
+  background-color: #e3e4e4;
+}
+.list-group-item-gray.list-group-item-action:hover, .list-group-item-gray.list-group-item-action:focus {
+  color: #515253;
+  background-color: #d6d7d7;
+}
+.list-group-item-gray.list-group-item-action.active {
+  color: #fff;
+  background-color: #515253;
+  border-color: #515253;
+}
+
+.list-group-item-darker {
+  color: #111315;
+  background-color: #c1c2c3;
+}
+.list-group-item-darker.list-group-item-action:hover, .list-group-item-darker.list-group-item-action:focus {
+  color: #111315;
+  background-color: #b4b5b6;
+}
+.list-group-item-darker.list-group-item-action.active {
+  color: #fff;
+  background-color: #111315;
+  border-color: #111315;
+}
+
+.alert {
+  position: relative;
+  padding: 0.75rem 1.25rem;
+  margin-bottom: 1rem;
+  border: 1px solid transparent;
+  border-radius: 0.3rem;
+}
+
+.alert-heading {
+  color: inherit;
+}
+
+.alert-link {
+  font-weight: 700;
+}
+
+.alert-dismissible {
+  padding-right: 4rem;
+}
+.alert-dismissible .close {
+  position: absolute;
+  top: 0;
+  right: 0;
+  z-index: 2;
+  padding: 0.75rem 1.25rem;
+  color: inherit;
+}
+
+.alert-primary {
+  color: #2e2264;
+  background-color: #ded9f3;
+  border-color: #d1caee;
+}
+.alert-primary hr {
+  border-top-color: #c0b7e8;
+}
+.alert-primary .alert-link {
+  color: #1c153e;
+}
+
+.alert-secondary {
+  color: #857700;
+  background-color: #fffacc;
+  border-color: #fff8b8;
+}
+.alert-secondary hr {
+  border-top-color: #fff59f;
+}
+.alert-secondary .alert-link {
+  color: #524900;
+}
+
+.alert-success {
+  color: #155724;
+  background-color: #d4edda;
+  border-color: #c3e6cb;
+}
+.alert-success hr {
+  border-top-color: #b1dfbb;
+}
+.alert-success .alert-link {
+  color: #0b2e13;
+}
+
+.alert-info {
+  color: #0c5460;
+  background-color: #d1ecf1;
+  border-color: #bee5eb;
+}
+.alert-info hr {
+  border-top-color: #abdde5;
+}
+.alert-info .alert-link {
+  color: #062c33;
+}
+
+.alert-warning {
+  color: #857700;
+  background-color: #fffacc;
+  border-color: #fff8b8;
+}
+.alert-warning hr {
+  border-top-color: #fff59f;
+}
+.alert-warning .alert-link {
+  color: #524900;
+}
+
+.alert-danger {
+  color: #721c24;
+  background-color: #f8d7da;
+  border-color: #f5c6cb;
+}
+.alert-danger hr {
+  border-top-color: #f1b0b7;
+}
+.alert-danger .alert-link {
+  color: #491217;
+}
+
+.alert-light {
+  color: #818182;
+  background-color: #fefefe;
+  border-color: #fdfdfe;
+}
+.alert-light hr {
+  border-top-color: #ececf6;
+}
+.alert-light .alert-link {
+  color: #686868;
+}
+
+.alert-dark {
+  color: #1b1e21;
+  background-color: #d6d8d9;
+  border-color: #c6c8ca;
+}
+.alert-dark hr {
+  border-top-color: #b9bbbe;
+}
+.alert-dark .alert-link {
+  color: #040505;
+}
+
+.alert-gray {
+  color: #515253;
+  background-color: #ebecec;
+  border-color: #e3e4e4;
+}
+.alert-gray hr {
+  border-top-color: #d6d7d7;
+}
+.alert-gray .alert-link {
+  color: #383939;
+}
+
+.alert-darker {
+  color: #111315;
+  background-color: #d3d3d4;
+  border-color: #c1c2c3;
+}
+.alert-darker hr {
+  border-top-color: #b4b5b6;
+}
+.alert-darker .alert-link {
+  color: black;
+}
+
+.container,
+.container-fluid,
+.container-xl,
+.container-lg,
+.container-md,
+.container-sm {
+  width: 100%;
+  padding-right: 10px;
+  padding-left: 10px;
+  margin-right: auto;
+  margin-left: auto;
+}
+
+@media (min-width: 576px) {
+  .container-sm, .container {
+    max-width: 540px;
+  }
+}
+@media (min-width: 768px) {
+  .container-md, .container-sm, .container {
+    max-width: 720px;
+  }
+}
+@media (min-width: 992px) {
+  .container-lg, .container-md, .container-sm, .container {
+    max-width: 960px;
+  }
+}
+@media (min-width: 1200px) {
+  .container-xl, .container-lg, .container-md, .container-sm, .container {
+    max-width: 1500px;
+  }
+}
+.row {
+  display: flex;
+  flex-wrap: wrap;
+  margin-right: -10px;
+  margin-left: -10px;
+}
+
+.no-gutters {
+  margin-right: 0;
+  margin-left: 0;
+}
+.no-gutters > .col,
+.no-gutters > [class*=col-] {
+  padding-right: 0;
+  padding-left: 0;
+}
+
+.col-xl,
+.col-xl-auto, .col-xl-12, .col-xl-11, .col-xl-10, .col-xl-9, .col-xl-8, .col-xl-7, .col-xl-6, .col-xl-5, .col-xl-4, .col-xl-3, .col-xl-2, .col-xl-1, .col-lg,
+.col-lg-auto, .col-lg-12, .col-lg-11, .col-lg-10, .col-lg-9, .col-lg-8, .col-lg-7, .col-lg-6, .col-lg-5, .col-lg-4, .col-lg-3, .col-lg-2, .col-lg-1, .col-md,
+.col-md-auto, .col-md-12, .col-md-11, .col-md-10, .col-md-9, .col-md-8, .col-md-7, .col-md-6, .col-md-5, .col-md-4, .col-md-3, .col-md-2, .col-md-1, .col-sm,
+.col-sm-auto, .col-sm-12, .col-sm-11, .col-sm-10, .col-sm-9, .col-sm-8, .col-sm-7, .col-sm-6, .col-sm-5, .col-sm-4, .col-sm-3, .col-sm-2, .col-sm-1, .col,
+.col-auto, .col-12, .col-11, .col-10, .col-9, .col-8, .col-7, .col-6, .col-5, .col-4, .col-3, .col-2, .col-1 {
+  position: relative;
+  width: 100%;
+  padding-right: 10px;
+  padding-left: 10px;
+}
+
+.col {
+  flex-basis: 0;
+  flex-grow: 1;
+  max-width: 100%;
+}
+
+.row-cols-1 > * {
+  flex: 0 0 100%;
+  max-width: 100%;
+}
+
+.row-cols-2 > * {
+  flex: 0 0 50%;
+  max-width: 50%;
+}
+
+.row-cols-3 > * {
+  flex: 0 0 33.3333333333%;
+  max-width: 33.3333333333%;
+}
+
+.row-cols-4 > * {
+  flex: 0 0 25%;
+  max-width: 25%;
+}
+
+.row-cols-5 > * {
+  flex: 0 0 20%;
+  max-width: 20%;
+}
+
+.row-cols-6 > * {
+  flex: 0 0 16.6666666667%;
+  max-width: 16.6666666667%;
+}
+
+.col-auto {
+  flex: 0 0 auto;
+  width: auto;
+  max-width: 100%;
+}
+
+.col-1 {
+  flex: 0 0 8.3333333333%;
+  max-width: 8.3333333333%;
+}
+
+.col-2 {
+  flex: 0 0 16.6666666667%;
+  max-width: 16.6666666667%;
+}
+
+.col-3 {
+  flex: 0 0 25%;
+  max-width: 25%;
+}
+
+.col-4 {
+  flex: 0 0 33.3333333333%;
+  max-width: 33.3333333333%;
+}
+
+.col-5 {
+  flex: 0 0 41.6666666667%;
+  max-width: 41.6666666667%;
+}
+
+.col-6 {
+  flex: 0 0 50%;
+  max-width: 50%;
+}
+
+.col-7 {
+  flex: 0 0 58.3333333333%;
+  max-width: 58.3333333333%;
+}
+
+.col-8 {
+  flex: 0 0 66.6666666667%;
+  max-width: 66.6666666667%;
+}
+
+.col-9 {
+  flex: 0 0 75%;
+  max-width: 75%;
+}
+
+.col-10 {
+  flex: 0 0 83.3333333333%;
+  max-width: 83.3333333333%;
+}
+
+.col-11 {
+  flex: 0 0 91.6666666667%;
+  max-width: 91.6666666667%;
+}
+
+.col-12 {
+  flex: 0 0 100%;
+  max-width: 100%;
+}
+
+.order-first {
+  order: -1;
+}
+
+.order-last {
+  order: 13;
+}
+
+.order-0 {
+  order: 0;
+}
+
+.order-1 {
+  order: 1;
+}
+
+.order-2 {
+  order: 2;
+}
+
+.order-3 {
+  order: 3;
+}
+
+.order-4 {
+  order: 4;
+}
+
+.order-5 {
+  order: 5;
+}
+
+.order-6 {
+  order: 6;
+}
+
+.order-7 {
+  order: 7;
+}
+
+.order-8 {
+  order: 8;
+}
+
+.order-9 {
+  order: 9;
+}
+
+.order-10 {
+  order: 10;
+}
+
+.order-11 {
+  order: 11;
+}
+
+.order-12 {
+  order: 12;
+}
+
+.offset-1 {
+  margin-left: 8.3333333333%;
+}
+
+.offset-2 {
+  margin-left: 16.6666666667%;
+}
+
+.offset-3 {
+  margin-left: 25%;
+}
+
+.offset-4 {
+  margin-left: 33.3333333333%;
+}
+
+.offset-5 {
+  margin-left: 41.6666666667%;
+}
+
+.offset-6 {
+  margin-left: 50%;
+}
+
+.offset-7 {
+  margin-left: 58.3333333333%;
+}
+
+.offset-8 {
+  margin-left: 66.6666666667%;
+}
+
+.offset-9 {
+  margin-left: 75%;
+}
+
+.offset-10 {
+  margin-left: 83.3333333333%;
+}
+
+.offset-11 {
+  margin-left: 91.6666666667%;
+}
+
+@media (min-width: 576px) {
+  .col-sm {
+    flex-basis: 0;
+    flex-grow: 1;
+    max-width: 100%;
+  }
+
+  .row-cols-sm-1 > * {
+    flex: 0 0 100%;
+    max-width: 100%;
+  }
+
+  .row-cols-sm-2 > * {
+    flex: 0 0 50%;
+    max-width: 50%;
+  }
+
+  .row-cols-sm-3 > * {
+    flex: 0 0 33.3333333333%;
+    max-width: 33.3333333333%;
+  }
+
+  .row-cols-sm-4 > * {
+    flex: 0 0 25%;
+    max-width: 25%;
+  }
+
+  .row-cols-sm-5 > * {
+    flex: 0 0 20%;
+    max-width: 20%;
+  }
+
+  .row-cols-sm-6 > * {
+    flex: 0 0 16.6666666667%;
+    max-width: 16.6666666667%;
+  }
+
+  .col-sm-auto {
+    flex: 0 0 auto;
+    width: auto;
+    max-width: 100%;
+  }
+
+  .col-sm-1 {
+    flex: 0 0 8.3333333333%;
+    max-width: 8.3333333333%;
+  }
+
+  .col-sm-2 {
+    flex: 0 0 16.6666666667%;
+    max-width: 16.6666666667%;
+  }
+
+  .col-sm-3 {
+    flex: 0 0 25%;
+    max-width: 25%;
+  }
+
+  .col-sm-4 {
+    flex: 0 0 33.3333333333%;
+    max-width: 33.3333333333%;
+  }
+
+  .col-sm-5 {
+    flex: 0 0 41.6666666667%;
+    max-width: 41.6666666667%;
+  }
+
+  .col-sm-6 {
+    flex: 0 0 50%;
+    max-width: 50%;
+  }
+
+  .col-sm-7 {
+    flex: 0 0 58.3333333333%;
+    max-width: 58.3333333333%;
+  }
+
+  .col-sm-8 {
+    flex: 0 0 66.6666666667%;
+    max-width: 66.6666666667%;
+  }
+
+  .col-sm-9 {
+    flex: 0 0 75%;
+    max-width: 75%;
+  }
+
+  .col-sm-10 {
+    flex: 0 0 83.3333333333%;
+    max-width: 83.3333333333%;
+  }
+
+  .col-sm-11 {
+    flex: 0 0 91.6666666667%;
+    max-width: 91.6666666667%;
+  }
+
+  .col-sm-12 {
+    flex: 0 0 100%;
+    max-width: 100%;
+  }
+
+  .order-sm-first {
+    order: -1;
+  }
+
+  .order-sm-last {
+    order: 13;
+  }
+
+  .order-sm-0 {
+    order: 0;
+  }
+
+  .order-sm-1 {
+    order: 1;
+  }
+
+  .order-sm-2 {
+    order: 2;
+  }
+
+  .order-sm-3 {
+    order: 3;
+  }
+
+  .order-sm-4 {
+    order: 4;
+  }
+
+  .order-sm-5 {
+    order: 5;
+  }
+
+  .order-sm-6 {
+    order: 6;
+  }
+
+  .order-sm-7 {
+    order: 7;
+  }
+
+  .order-sm-8 {
+    order: 8;
+  }
+
+  .order-sm-9 {
+    order: 9;
+  }
+
+  .order-sm-10 {
+    order: 10;
+  }
+
+  .order-sm-11 {
+    order: 11;
+  }
+
+  .order-sm-12 {
+    order: 12;
+  }
+
+  .offset-sm-0 {
+    margin-left: 0;
+  }
+
+  .offset-sm-1 {
+    margin-left: 8.3333333333%;
+  }
+
+  .offset-sm-2 {
+    margin-left: 16.6666666667%;
+  }
+
+  .offset-sm-3 {
+    margin-left: 25%;
+  }
+
+  .offset-sm-4 {
+    margin-left: 33.3333333333%;
+  }
+
+  .offset-sm-5 {
+    margin-left: 41.6666666667%;
+  }
+
+  .offset-sm-6 {
+    margin-left: 50%;
+  }
+
+  .offset-sm-7 {
+    margin-left: 58.3333333333%;
+  }
+
+  .offset-sm-8 {
+    margin-left: 66.6666666667%;
+  }
+
+  .offset-sm-9 {
+    margin-left: 75%;
+  }
+
+  .offset-sm-10 {
+    margin-left: 83.3333333333%;
+  }
+
+  .offset-sm-11 {
+    margin-left: 91.6666666667%;
+  }
+}
+@media (min-width: 768px) {
+  .col-md {
+    flex-basis: 0;
+    flex-grow: 1;
+    max-width: 100%;
+  }
+
+  .row-cols-md-1 > * {
+    flex: 0 0 100%;
+    max-width: 100%;
+  }
+
+  .row-cols-md-2 > * {
+    flex: 0 0 50%;
+    max-width: 50%;
+  }
+
+  .row-cols-md-3 > * {
+    flex: 0 0 33.3333333333%;
+    max-width: 33.3333333333%;
+  }
+
+  .row-cols-md-4 > * {
+    flex: 0 0 25%;
+    max-width: 25%;
+  }
+
+  .row-cols-md-5 > * {
+    flex: 0 0 20%;
+    max-width: 20%;
+  }
+
+  .row-cols-md-6 > * {
+    flex: 0 0 16.6666666667%;
+    max-width: 16.6666666667%;
+  }
+
+  .col-md-auto {
+    flex: 0 0 auto;
+    width: auto;
+    max-width: 100%;
+  }
+
+  .col-md-1 {
+    flex: 0 0 8.3333333333%;
+    max-width: 8.3333333333%;
+  }
+
+  .col-md-2 {
+    flex: 0 0 16.6666666667%;
+    max-width: 16.6666666667%;
+  }
+
+  .col-md-3 {
+    flex: 0 0 25%;
+    max-width: 25%;
+  }
+
+  .col-md-4 {
+    flex: 0 0 33.3333333333%;
+    max-width: 33.3333333333%;
+  }
+
+  .col-md-5 {
+    flex: 0 0 41.6666666667%;
+    max-width: 41.6666666667%;
+  }
+
+  .col-md-6 {
+    flex: 0 0 50%;
+    max-width: 50%;
+  }
+
+  .col-md-7 {
+    flex: 0 0 58.3333333333%;
+    max-width: 58.3333333333%;
+  }
+
+  .col-md-8 {
+    flex: 0 0 66.6666666667%;
+    max-width: 66.6666666667%;
+  }
+
+  .col-md-9 {
+    flex: 0 0 75%;
+    max-width: 75%;
+  }
+
+  .col-md-10 {
+    flex: 0 0 83.3333333333%;
+    max-width: 83.3333333333%;
+  }
+
+  .col-md-11 {
+    flex: 0 0 91.6666666667%;
+    max-width: 91.6666666667%;
+  }
+
+  .col-md-12 {
+    flex: 0 0 100%;
+    max-width: 100%;
+  }
+
+  .order-md-first {
+    order: -1;
+  }
+
+  .order-md-last {
+    order: 13;
+  }
+
+  .order-md-0 {
+    order: 0;
+  }
+
+  .order-md-1 {
+    order: 1;
+  }
+
+  .order-md-2 {
+    order: 2;
+  }
+
+  .order-md-3 {
+    order: 3;
+  }
+
+  .order-md-4 {
+    order: 4;
+  }
+
+  .order-md-5 {
+    order: 5;
+  }
+
+  .order-md-6 {
+    order: 6;
+  }
+
+  .order-md-7 {
+    order: 7;
+  }
+
+  .order-md-8 {
+    order: 8;
+  }
+
+  .order-md-9 {
+    order: 9;
+  }
+
+  .order-md-10 {
+    order: 10;
+  }
+
+  .order-md-11 {
+    order: 11;
+  }
+
+  .order-md-12 {
+    order: 12;
+  }
+
+  .offset-md-0 {
+    margin-left: 0;
+  }
+
+  .offset-md-1 {
+    margin-left: 8.3333333333%;
+  }
+
+  .offset-md-2 {
+    margin-left: 16.6666666667%;
+  }
+
+  .offset-md-3 {
+    margin-left: 25%;
+  }
+
+  .offset-md-4 {
+    margin-left: 33.3333333333%;
+  }
+
+  .offset-md-5 {
+    margin-left: 41.6666666667%;
+  }
+
+  .offset-md-6 {
+    margin-left: 50%;
+  }
+
+  .offset-md-7 {
+    margin-left: 58.3333333333%;
+  }
+
+  .offset-md-8 {
+    margin-left: 66.6666666667%;
+  }
+
+  .offset-md-9 {
+    margin-left: 75%;
+  }
+
+  .offset-md-10 {
+    margin-left: 83.3333333333%;
+  }
+
+  .offset-md-11 {
+    margin-left: 91.6666666667%;
+  }
+}
+@media (min-width: 992px) {
+  .col-lg {
+    flex-basis: 0;
+    flex-grow: 1;
+    max-width: 100%;
+  }
+
+  .row-cols-lg-1 > * {
+    flex: 0 0 100%;
+    max-width: 100%;
+  }
+
+  .row-cols-lg-2 > * {
+    flex: 0 0 50%;
+    max-width: 50%;
+  }
+
+  .row-cols-lg-3 > * {
+    flex: 0 0 33.3333333333%;
+    max-width: 33.3333333333%;
+  }
+
+  .row-cols-lg-4 > * {
+    flex: 0 0 25%;
+    max-width: 25%;
+  }
+
+  .row-cols-lg-5 > * {
+    flex: 0 0 20%;
+    max-width: 20%;
+  }
+
+  .row-cols-lg-6 > * {
+    flex: 0 0 16.6666666667%;
+    max-width: 16.6666666667%;
+  }
+
+  .col-lg-auto {
+    flex: 0 0 auto;
+    width: auto;
+    max-width: 100%;
+  }
+
+  .col-lg-1 {
+    flex: 0 0 8.3333333333%;
+    max-width: 8.3333333333%;
+  }
+
+  .col-lg-2 {
+    flex: 0 0 16.6666666667%;
+    max-width: 16.6666666667%;
+  }
+
+  .col-lg-3 {
+    flex: 0 0 25%;
+    max-width: 25%;
+  }
+
+  .col-lg-4 {
+    flex: 0 0 33.3333333333%;
+    max-width: 33.3333333333%;
+  }
+
+  .col-lg-5 {
+    flex: 0 0 41.6666666667%;
+    max-width: 41.6666666667%;
+  }
+
+  .col-lg-6 {
+    flex: 0 0 50%;
+    max-width: 50%;
+  }
+
+  .col-lg-7 {
+    flex: 0 0 58.3333333333%;
+    max-width: 58.3333333333%;
+  }
+
+  .col-lg-8 {
+    flex: 0 0 66.6666666667%;
+    max-width: 66.6666666667%;
+  }
+
+  .col-lg-9 {
+    flex: 0 0 75%;
+    max-width: 75%;
+  }
+
+  .col-lg-10 {
+    flex: 0 0 83.3333333333%;
+    max-width: 83.3333333333%;
+  }
+
+  .col-lg-11 {
+    flex: 0 0 91.6666666667%;
+    max-width: 91.6666666667%;
+  }
+
+  .col-lg-12 {
+    flex: 0 0 100%;
+    max-width: 100%;
+  }
+
+  .order-lg-first {
+    order: -1;
+  }
+
+  .order-lg-last {
+    order: 13;
+  }
+
+  .order-lg-0 {
+    order: 0;
+  }
+
+  .order-lg-1 {
+    order: 1;
+  }
+
+  .order-lg-2 {
+    order: 2;
+  }
+
+  .order-lg-3 {
+    order: 3;
+  }
+
+  .order-lg-4 {
+    order: 4;
+  }
+
+  .order-lg-5 {
+    order: 5;
+  }
+
+  .order-lg-6 {
+    order: 6;
+  }
+
+  .order-lg-7 {
+    order: 7;
+  }
+
+  .order-lg-8 {
+    order: 8;
+  }
+
+  .order-lg-9 {
+    order: 9;
+  }
+
+  .order-lg-10 {
+    order: 10;
+  }
+
+  .order-lg-11 {
+    order: 11;
+  }
+
+  .order-lg-12 {
+    order: 12;
+  }
+
+  .offset-lg-0 {
+    margin-left: 0;
+  }
+
+  .offset-lg-1 {
+    margin-left: 8.3333333333%;
+  }
+
+  .offset-lg-2 {
+    margin-left: 16.6666666667%;
+  }
+
+  .offset-lg-3 {
+    margin-left: 25%;
+  }
+
+  .offset-lg-4 {
+    margin-left: 33.3333333333%;
+  }
+
+  .offset-lg-5 {
+    margin-left: 41.6666666667%;
+  }
+
+  .offset-lg-6 {
+    margin-left: 50%;
+  }
+
+  .offset-lg-7 {
+    margin-left: 58.3333333333%;
+  }
+
+  .offset-lg-8 {
+    margin-left: 66.6666666667%;
+  }
+
+  .offset-lg-9 {
+    margin-left: 75%;
+  }
+
+  .offset-lg-10 {
+    margin-left: 83.3333333333%;
+  }
+
+  .offset-lg-11 {
+    margin-left: 91.6666666667%;
+  }
+}
+@media (min-width: 1200px) {
+  .col-xl {
+    flex-basis: 0;
+    flex-grow: 1;
+    max-width: 100%;
+  }
+
+  .row-cols-xl-1 > * {
+    flex: 0 0 100%;
+    max-width: 100%;
+  }
+
+  .row-cols-xl-2 > * {
+    flex: 0 0 50%;
+    max-width: 50%;
+  }
+
+  .row-cols-xl-3 > * {
+    flex: 0 0 33.3333333333%;
+    max-width: 33.3333333333%;
+  }
+
+  .row-cols-xl-4 > * {
+    flex: 0 0 25%;
+    max-width: 25%;
+  }
+
+  .row-cols-xl-5 > * {
+    flex: 0 0 20%;
+    max-width: 20%;
+  }
+
+  .row-cols-xl-6 > * {
+    flex: 0 0 16.6666666667%;
+    max-width: 16.6666666667%;
+  }
+
+  .col-xl-auto {
+    flex: 0 0 auto;
+    width: auto;
+    max-width: 100%;
+  }
+
+  .col-xl-1 {
+    flex: 0 0 8.3333333333%;
+    max-width: 8.3333333333%;
+  }
+
+  .col-xl-2 {
+    flex: 0 0 16.6666666667%;
+    max-width: 16.6666666667%;
+  }
+
+  .col-xl-3 {
+    flex: 0 0 25%;
+    max-width: 25%;
+  }
+
+  .col-xl-4 {
+    flex: 0 0 33.3333333333%;
+    max-width: 33.3333333333%;
+  }
+
+  .col-xl-5 {
+    flex: 0 0 41.6666666667%;
+    max-width: 41.6666666667%;
+  }
+
+  .col-xl-6 {
+    flex: 0 0 50%;
+    max-width: 50%;
+  }
+
+  .col-xl-7 {
+    flex: 0 0 58.3333333333%;
+    max-width: 58.3333333333%;
+  }
+
+  .col-xl-8 {
+    flex: 0 0 66.6666666667%;
+    max-width: 66.6666666667%;
+  }
+
+  .col-xl-9 {
+    flex: 0 0 75%;
+    max-width: 75%;
+  }
+
+  .col-xl-10 {
+    flex: 0 0 83.3333333333%;
+    max-width: 83.3333333333%;
+  }
+
+  .col-xl-11 {
+    flex: 0 0 91.6666666667%;
+    max-width: 91.6666666667%;
+  }
+
+  .col-xl-12 {
+    flex: 0 0 100%;
+    max-width: 100%;
+  }
+
+  .order-xl-first {
+    order: -1;
+  }
+
+  .order-xl-last {
+    order: 13;
+  }
+
+  .order-xl-0 {
+    order: 0;
+  }
+
+  .order-xl-1 {
+    order: 1;
+  }
+
+  .order-xl-2 {
+    order: 2;
+  }
+
+  .order-xl-3 {
+    order: 3;
+  }
+
+  .order-xl-4 {
+    order: 4;
+  }
+
+  .order-xl-5 {
+    order: 5;
+  }
+
+  .order-xl-6 {
+    order: 6;
+  }
+
+  .order-xl-7 {
+    order: 7;
+  }
+
+  .order-xl-8 {
+    order: 8;
+  }
+
+  .order-xl-9 {
+    order: 9;
+  }
+
+  .order-xl-10 {
+    order: 10;
+  }
+
+  .order-xl-11 {
+    order: 11;
+  }
+
+  .order-xl-12 {
+    order: 12;
+  }
+
+  .offset-xl-0 {
+    margin-left: 0;
+  }
+
+  .offset-xl-1 {
+    margin-left: 8.3333333333%;
+  }
+
+  .offset-xl-2 {
+    margin-left: 16.6666666667%;
+  }
+
+  .offset-xl-3 {
+    margin-left: 25%;
+  }
+
+  .offset-xl-4 {
+    margin-left: 33.3333333333%;
+  }
+
+  .offset-xl-5 {
+    margin-left: 41.6666666667%;
+  }
+
+  .offset-xl-6 {
+    margin-left: 50%;
+  }
+
+  .offset-xl-7 {
+    margin-left: 58.3333333333%;
+  }
+
+  .offset-xl-8 {
+    margin-left: 66.6666666667%;
+  }
+
+  .offset-xl-9 {
+    margin-left: 75%;
+  }
+
+  .offset-xl-10 {
+    margin-left: 83.3333333333%;
+  }
+
+  .offset-xl-11 {
+    margin-left: 91.6666666667%;
+  }
+}
+.fade {
+  transition: opacity 0.15s linear;
+}
+@media (prefers-reduced-motion: reduce) {
+  .fade {
+    transition: none;
+  }
+}
+.fade:not(.show) {
+  opacity: 0;
+}
+
+.collapse:not(.show) {
+  display: none;
+}
+
+.collapsing {
+  position: relative;
+  height: 0;
+  overflow: hidden;
+  transition: height 0.35s ease;
+}
+@media (prefers-reduced-motion: reduce) {
+  .collapsing {
+    transition: none;
+  }
+}
+
+.btn {
+  display: inline-block;
+  font-weight: 400;
+  color: #212529;
+  text-align: center;
+  vertical-align: middle;
+  user-select: none;
+  background-color: transparent;
+  border: 1px solid transparent;
+  padding: 0.75rem 1.2rem;
+  font-size: 1rem;
+  line-height: 1.5;
+  border-radius: 0.3rem;
+  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+}
+@media (prefers-reduced-motion: reduce) {
+  .btn {
+    transition: none;
+  }
+}
+.btn:hover {
+  color: #212529;
+  text-decoration: none;
+}
+.btn:focus, .btn.focus {
+  outline: 0;
+  box-shadow: 0 0 0 0.2rem rgba(89, 66, 193, 0.25);
+}
+.btn.disabled, .btn:disabled {
+  opacity: 0.65;
+}
+.btn:not(:disabled):not(.disabled) {
+  cursor: pointer;
+}
+a.btn.disabled,
+fieldset:disabled a.btn {
+  pointer-events: none;
+}
+
+.btn-primary {
+  color: #fff;
+  background-color: #5942c1;
+  border-color: #5942c1;
+}
+.btn-primary:hover {
+  color: #fff;
+  background-color: #4b37a6;
+  border-color: #46339d;
+}
+.btn-primary:focus, .btn-primary.focus {
+  color: #fff;
+  background-color: #4b37a6;
+  border-color: #46339d;
+  box-shadow: 0 0 0 0.2rem rgba(114, 94, 202, 0.5);
+}
+.btn-primary.disabled, .btn-primary:disabled {
+  color: #fff;
+  background-color: #5942c1;
+  border-color: #5942c1;
+}
+.btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active, .show > .btn-primary.dropdown-toggle {
+  color: #fff;
+  background-color: #46339d;
+  border-color: #423093;
+}
+.btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus, .show > .btn-primary.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(114, 94, 202, 0.5);
+}
+
+.btn-secondary {
+  color: #212529;
+  background-color: #ffe500;
+  border-color: #ffe500;
+}
+.btn-secondary:hover {
+  color: #212529;
+  background-color: #d9c300;
+  border-color: #ccb700;
+}
+.btn-secondary:focus, .btn-secondary.focus {
+  color: #212529;
+  background-color: #d9c300;
+  border-color: #ccb700;
+  box-shadow: 0 0 0 0.2rem rgba(222, 200, 6, 0.5);
+}
+.btn-secondary.disabled, .btn-secondary:disabled {
+  color: #212529;
+  background-color: #ffe500;
+  border-color: #ffe500;
+}
+.btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active, .show > .btn-secondary.dropdown-toggle {
+  color: #212529;
+  background-color: #ccb700;
+  border-color: #bfac00;
+}
+.btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus, .show > .btn-secondary.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(222, 200, 6, 0.5);
+}
+
+.btn-success {
+  color: #fff;
+  background-color: #28a745;
+  border-color: #28a745;
+}
+.btn-success:hover {
+  color: #fff;
+  background-color: #218838;
+  border-color: #1e7e34;
+}
+.btn-success:focus, .btn-success.focus {
+  color: #fff;
+  background-color: #218838;
+  border-color: #1e7e34;
+  box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5);
+}
+.btn-success.disabled, .btn-success:disabled {
+  color: #fff;
+  background-color: #28a745;
+  border-color: #28a745;
+}
+.btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active, .show > .btn-success.dropdown-toggle {
+  color: #fff;
+  background-color: #1e7e34;
+  border-color: #1c7430;
+}
+.btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus, .show > .btn-success.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5);
+}
+
+.btn-info {
+  color: #fff;
+  background-color: #17a2b8;
+  border-color: #17a2b8;
+}
+.btn-info:hover {
+  color: #fff;
+  background-color: #138496;
+  border-color: #117a8b;
+}
+.btn-info:focus, .btn-info.focus {
+  color: #fff;
+  background-color: #138496;
+  border-color: #117a8b;
+  box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);
+}
+.btn-info.disabled, .btn-info:disabled {
+  color: #fff;
+  background-color: #17a2b8;
+  border-color: #17a2b8;
+}
+.btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active, .show > .btn-info.dropdown-toggle {
+  color: #fff;
+  background-color: #117a8b;
+  border-color: #10707f;
+}
+.btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus, .show > .btn-info.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);
+}
+
+.btn-warning {
+  color: #212529;
+  background-color: #ffe500;
+  border-color: #ffe500;
+}
+.btn-warning:hover {
+  color: #212529;
+  background-color: #d9c300;
+  border-color: #ccb700;
+}
+.btn-warning:focus, .btn-warning.focus {
+  color: #212529;
+  background-color: #d9c300;
+  border-color: #ccb700;
+  box-shadow: 0 0 0 0.2rem rgba(222, 200, 6, 0.5);
+}
+.btn-warning.disabled, .btn-warning:disabled {
+  color: #212529;
+  background-color: #ffe500;
+  border-color: #ffe500;
+}
+.btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active, .show > .btn-warning.dropdown-toggle {
+  color: #212529;
+  background-color: #ccb700;
+  border-color: #bfac00;
+}
+.btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus, .show > .btn-warning.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(222, 200, 6, 0.5);
+}
+
+.btn-danger {
+  color: #fff;
+  background-color: #dc3545;
+  border-color: #dc3545;
+}
+.btn-danger:hover {
+  color: #fff;
+  background-color: #c82333;
+  border-color: #bd2130;
+}
+.btn-danger:focus, .btn-danger.focus {
+  color: #fff;
+  background-color: #c82333;
+  border-color: #bd2130;
+  box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);
+}
+.btn-danger.disabled, .btn-danger:disabled {
+  color: #fff;
+  background-color: #dc3545;
+  border-color: #dc3545;
+}
+.btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active, .show > .btn-danger.dropdown-toggle {
+  color: #fff;
+  background-color: #bd2130;
+  border-color: #b21f2d;
+}
+.btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus, .show > .btn-danger.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);
+}
+
+.btn-light {
+  color: #212529;
+  background-color: #f8f9fa;
+  border-color: #f8f9fa;
+}
+.btn-light:hover {
+  color: #212529;
+  background-color: #e2e6ea;
+  border-color: #dae0e5;
+}
+.btn-light:focus, .btn-light.focus {
+  color: #212529;
+  background-color: #e2e6ea;
+  border-color: #dae0e5;
+  box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5);
+}
+.btn-light.disabled, .btn-light:disabled {
+  color: #212529;
+  background-color: #f8f9fa;
+  border-color: #f8f9fa;
+}
+.btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active, .show > .btn-light.dropdown-toggle {
+  color: #212529;
+  background-color: #dae0e5;
+  border-color: #d3d9df;
+}
+.btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus, .show > .btn-light.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5);
+}
+
+.btn-dark {
+  color: #fff;
+  background-color: #343a40;
+  border-color: #343a40;
+}
+.btn-dark:hover {
+  color: #fff;
+  background-color: #23272b;
+  border-color: #1d2124;
+}
+.btn-dark:focus, .btn-dark.focus {
+  color: #fff;
+  background-color: #23272b;
+  border-color: #1d2124;
+  box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5);
+}
+.btn-dark.disabled, .btn-dark:disabled {
+  color: #fff;
+  background-color: #343a40;
+  border-color: #343a40;
+}
+.btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active, .show > .btn-dark.dropdown-toggle {
+  color: #fff;
+  background-color: #1d2124;
+  border-color: #171a1d;
+}
+.btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus, .show > .btn-dark.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5);
+}
+
+.btn-gray {
+  color: #212529;
+  background-color: #9c9e9f;
+  border-color: #9c9e9f;
+}
+.btn-gray:hover {
+  color: #fff;
+  background-color: #898b8c;
+  border-color: #828586;
+}
+.btn-gray:focus, .btn-gray.focus {
+  color: #fff;
+  background-color: #898b8c;
+  border-color: #828586;
+  box-shadow: 0 0 0 0.2rem rgba(138, 140, 141, 0.5);
+}
+.btn-gray.disabled, .btn-gray:disabled {
+  color: #212529;
+  background-color: #9c9e9f;
+  border-color: #9c9e9f;
+}
+.btn-gray:not(:disabled):not(.disabled):active, .btn-gray:not(:disabled):not(.disabled).active, .show > .btn-gray.dropdown-toggle {
+  color: #fff;
+  background-color: #828586;
+  border-color: #7c7e80;
+}
+.btn-gray:not(:disabled):not(.disabled):active:focus, .btn-gray:not(:disabled):not(.disabled).active:focus, .show > .btn-gray.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(138, 140, 141, 0.5);
+}
+
+.btn-darker {
+  color: #fff;
+  background-color: #212529;
+  border-color: #212529;
+}
+.btn-darker:hover {
+  color: #fff;
+  background-color: #101214;
+  border-color: #0a0c0d;
+}
+.btn-darker:focus, .btn-darker.focus {
+  color: #fff;
+  background-color: #101214;
+  border-color: #0a0c0d;
+  box-shadow: 0 0 0 0.2rem rgba(66, 70, 73, 0.5);
+}
+.btn-darker.disabled, .btn-darker:disabled {
+  color: #fff;
+  background-color: #212529;
+  border-color: #212529;
+}
+.btn-darker:not(:disabled):not(.disabled):active, .btn-darker:not(:disabled):not(.disabled).active, .show > .btn-darker.dropdown-toggle {
+  color: #fff;
+  background-color: #0a0c0d;
+  border-color: #050506;
+}
+.btn-darker:not(:disabled):not(.disabled):active:focus, .btn-darker:not(:disabled):not(.disabled).active:focus, .show > .btn-darker.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(66, 70, 73, 0.5);
+}
+
+.btn-outline-primary {
+  color: #5942c1;
+  border-color: #5942c1;
+}
+.btn-outline-primary:hover {
+  color: #fff;
+  background-color: #5942c1;
+  border-color: #5942c1;
+}
+.btn-outline-primary:focus, .btn-outline-primary.focus {
+  box-shadow: 0 0 0 0.2rem rgba(89, 66, 193, 0.5);
+}
+.btn-outline-primary.disabled, .btn-outline-primary:disabled {
+  color: #5942c1;
+  background-color: transparent;
+}
+.btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active, .show > .btn-outline-primary.dropdown-toggle {
+  color: #fff;
+  background-color: #5942c1;
+  border-color: #5942c1;
+}
+.btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-primary.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(89, 66, 193, 0.5);
+}
+
+.btn-outline-secondary {
+  color: #ffe500;
+  border-color: #ffe500;
+}
+.btn-outline-secondary:hover {
+  color: #212529;
+  background-color: #ffe500;
+  border-color: #ffe500;
+}
+.btn-outline-secondary:focus, .btn-outline-secondary.focus {
+  box-shadow: 0 0 0 0.2rem rgba(255, 229, 0, 0.5);
+}
+.btn-outline-secondary.disabled, .btn-outline-secondary:disabled {
+  color: #ffe500;
+  background-color: transparent;
+}
+.btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active, .show > .btn-outline-secondary.dropdown-toggle {
+  color: #212529;
+  background-color: #ffe500;
+  border-color: #ffe500;
+}
+.btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-secondary.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(255, 229, 0, 0.5);
+}
+
+.btn-outline-success {
+  color: #28a745;
+  border-color: #28a745;
+}
+.btn-outline-success:hover {
+  color: #fff;
+  background-color: #28a745;
+  border-color: #28a745;
+}
+.btn-outline-success:focus, .btn-outline-success.focus {
+  box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);
+}
+.btn-outline-success.disabled, .btn-outline-success:disabled {
+  color: #28a745;
+  background-color: transparent;
+}
+.btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active, .show > .btn-outline-success.dropdown-toggle {
+  color: #fff;
+  background-color: #28a745;
+  border-color: #28a745;
+}
+.btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-success.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);
+}
+
+.btn-outline-info {
+  color: #17a2b8;
+  border-color: #17a2b8;
+}
+.btn-outline-info:hover {
+  color: #fff;
+  background-color: #17a2b8;
+  border-color: #17a2b8;
+}
+.btn-outline-info:focus, .btn-outline-info.focus {
+  box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);
+}
+.btn-outline-info.disabled, .btn-outline-info:disabled {
+  color: #17a2b8;
+  background-color: transparent;
+}
+.btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active, .show > .btn-outline-info.dropdown-toggle {
+  color: #fff;
+  background-color: #17a2b8;
+  border-color: #17a2b8;
+}
+.btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-info.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);
+}
+
+.btn-outline-warning {
+  color: #ffe500;
+  border-color: #ffe500;
+}
+.btn-outline-warning:hover {
+  color: #212529;
+  background-color: #ffe500;
+  border-color: #ffe500;
+}
+.btn-outline-warning:focus, .btn-outline-warning.focus {
+  box-shadow: 0 0 0 0.2rem rgba(255, 229, 0, 0.5);
+}
+.btn-outline-warning.disabled, .btn-outline-warning:disabled {
+  color: #ffe500;
+  background-color: transparent;
+}
+.btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active, .show > .btn-outline-warning.dropdown-toggle {
+  color: #212529;
+  background-color: #ffe500;
+  border-color: #ffe500;
+}
+.btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-warning.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(255, 229, 0, 0.5);
+}
+
+.btn-outline-danger {
+  color: #dc3545;
+  border-color: #dc3545;
+}
+.btn-outline-danger:hover {
+  color: #fff;
+  background-color: #dc3545;
+  border-color: #dc3545;
+}
+.btn-outline-danger:focus, .btn-outline-danger.focus {
+  box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);
+}
+.btn-outline-danger.disabled, .btn-outline-danger:disabled {
+  color: #dc3545;
+  background-color: transparent;
+}
+.btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active, .show > .btn-outline-danger.dropdown-toggle {
+  color: #fff;
+  background-color: #dc3545;
+  border-color: #dc3545;
+}
+.btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-danger.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);
+}
+
+.btn-outline-light {
+  color: #f8f9fa;
+  border-color: #f8f9fa;
+}
+.btn-outline-light:hover {
+  color: #212529;
+  background-color: #f8f9fa;
+  border-color: #f8f9fa;
+}
+.btn-outline-light:focus, .btn-outline-light.focus {
+  box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);
+}
+.btn-outline-light.disabled, .btn-outline-light:disabled {
+  color: #f8f9fa;
+  background-color: transparent;
+}
+.btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active, .show > .btn-outline-light.dropdown-toggle {
+  color: #212529;
+  background-color: #f8f9fa;
+  border-color: #f8f9fa;
+}
+.btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-light.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);
+}
+
+.btn-outline-dark {
+  color: #343a40;
+  border-color: #343a40;
+}
+.btn-outline-dark:hover {
+  color: #fff;
+  background-color: #343a40;
+  border-color: #343a40;
+}
+.btn-outline-dark:focus, .btn-outline-dark.focus {
+  box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);
+}
+.btn-outline-dark.disabled, .btn-outline-dark:disabled {
+  color: #343a40;
+  background-color: transparent;
+}
+.btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active, .show > .btn-outline-dark.dropdown-toggle {
+  color: #fff;
+  background-color: #343a40;
+  border-color: #343a40;
+}
+.btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-dark.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);
+}
+
+.btn-outline-gray {
+  color: #9c9e9f;
+  border-color: #9c9e9f;
+}
+.btn-outline-gray:hover {
+  color: #212529;
+  background-color: #9c9e9f;
+  border-color: #9c9e9f;
+}
+.btn-outline-gray:focus, .btn-outline-gray.focus {
+  box-shadow: 0 0 0 0.2rem rgba(156, 158, 159, 0.5);
+}
+.btn-outline-gray.disabled, .btn-outline-gray:disabled {
+  color: #9c9e9f;
+  background-color: transparent;
+}
+.btn-outline-gray:not(:disabled):not(.disabled):active, .btn-outline-gray:not(:disabled):not(.disabled).active, .show > .btn-outline-gray.dropdown-toggle {
+  color: #212529;
+  background-color: #9c9e9f;
+  border-color: #9c9e9f;
+}
+.btn-outline-gray:not(:disabled):not(.disabled):active:focus, .btn-outline-gray:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-gray.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(156, 158, 159, 0.5);
+}
+
+.btn-outline-darker {
+  color: #212529;
+  border-color: #212529;
+}
+.btn-outline-darker:hover {
+  color: #fff;
+  background-color: #212529;
+  border-color: #212529;
+}
+.btn-outline-darker:focus, .btn-outline-darker.focus {
+  box-shadow: 0 0 0 0.2rem rgba(33, 37, 41, 0.5);
+}
+.btn-outline-darker.disabled, .btn-outline-darker:disabled {
+  color: #212529;
+  background-color: transparent;
+}
+.btn-outline-darker:not(:disabled):not(.disabled):active, .btn-outline-darker:not(:disabled):not(.disabled).active, .show > .btn-outline-darker.dropdown-toggle {
+  color: #fff;
+  background-color: #212529;
+  border-color: #212529;
+}
+.btn-outline-darker:not(:disabled):not(.disabled):active:focus, .btn-outline-darker:not(:disabled):not(.disabled).active:focus, .show > .btn-outline-darker.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(33, 37, 41, 0.5);
+}
+
+.btn-link {
+  font-weight: 400;
+  color: #5942c1;
+  text-decoration: none;
+}
+.btn-link:hover {
+  color: #3e2d89;
+  text-decoration: underline;
+}
+.btn-link:focus, .btn-link.focus {
+  text-decoration: underline;
+}
+.btn-link:disabled, .btn-link.disabled {
+  color: #6c757d;
+  pointer-events: none;
+}
+
+.btn-lg {
+  padding: 0.5rem 1rem;
+  font-size: 1.25rem;
+  line-height: 1.5;
+  border-radius: 0.3rem;
+}
+
+.btn-sm {
+  padding: 0.25rem 0.5rem;
+  font-size: 0.875rem;
+  line-height: 1.5;
+  border-radius: 0.2rem;
+}
+
+.btn-block {
+  display: block;
+  width: 100%;
+}
+.btn-block + .btn-block {
+  margin-top: 0.5rem;
+}
+
+input[type=submit].btn-block,
+input[type=reset].btn-block,
+input[type=button].btn-block {
+  width: 100%;
+}
+
+@keyframes spinner-border {
+  to {
+    transform: rotate(360deg);
+  }
+}
+.spinner-border {
+  display: inline-block;
+  width: 2rem;
+  height: 2rem;
+  vertical-align: text-bottom;
+  border: 0.25em solid currentColor;
+  border-right-color: transparent;
+  border-radius: 50%;
+  animation: 0.75s linear infinite spinner-border;
+}
+
+.spinner-border-sm {
+  width: 1rem;
+  height: 1rem;
+  border-width: 0.2em;
+}
+
+@keyframes spinner-grow {
+  0% {
+    transform: scale(0);
+  }
+  50% {
+    opacity: 1;
+    transform: none;
+  }
+}
+.spinner-grow {
+  display: inline-block;
+  width: 2rem;
+  height: 2rem;
+  vertical-align: text-bottom;
+  background-color: currentColor;
+  border-radius: 50%;
+  opacity: 0;
+  animation: 0.75s linear infinite spinner-grow;
+}
+
+.spinner-grow-sm {
+  width: 1rem;
+  height: 1rem;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .spinner-border,
+.spinner-grow {
+    animation-duration: 1.5s;
+  }
+}
+.nav {
+  display: flex;
+  flex-wrap: wrap;
+  padding-left: 0;
+  margin-bottom: 0;
+  list-style: none;
+}
+
+.nav-link {
+  display: block;
+  padding: 1rem 2rem;
+}
+.nav-link:hover, .nav-link:focus {
+  text-decoration: none;
+}
+.nav-link.disabled {
+  color: #6c757d;
+  pointer-events: none;
+  cursor: default;
+}
+
+.nav-tabs {
+  border-bottom: 1px solid #dee2e6;
+}
+.nav-tabs .nav-link {
+  margin-bottom: -1px;
+  border: 1px solid transparent;
+  border-top-left-radius: 0.3rem;
+  border-top-right-radius: 0.3rem;
+}
+.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {
+  border-color: #e9ecef #e9ecef #dee2e6;
+}
+.nav-tabs .nav-link.disabled {
+  color: #6c757d;
+  background-color: transparent;
+  border-color: transparent;
+}
+.nav-tabs .nav-link.active,
+.nav-tabs .nav-item.show .nav-link {
+  color: #495057;
+  background-color: #fff;
+  border-color: #dee2e6 #dee2e6 #fff;
+}
+.nav-tabs .dropdown-menu {
+  margin-top: -1px;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+}
+
+.nav-pills .nav-link {
+  border-radius: 0.3rem;
+}
+.nav-pills .nav-link.active,
+.nav-pills .show > .nav-link {
+  color: #fff;
+  background-color: #5942c1;
+}
+
+.nav-fill > .nav-link,
+.nav-fill .nav-item {
+  flex: 1 1 auto;
+  text-align: center;
+}
+
+.nav-justified > .nav-link,
+.nav-justified .nav-item {
+  flex-basis: 0;
+  flex-grow: 1;
+  text-align: center;
+}
+
+.tab-content > .tab-pane {
+  display: none;
+}
+.tab-content > .active {
+  display: block;
+}
+
+.navbar {
+  position: relative;
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  justify-content: space-between;
+  padding: 0.5rem 1rem;
+}
+.navbar .container,
+.navbar .container-fluid,
+.navbar .container-sm,
+.navbar .container-md,
+.navbar .container-lg,
+.navbar .container-xl {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  justify-content: space-between;
+}
+.navbar-brand {
+  display: inline-block;
+  padding-top: 0.8125rem;
+  padding-bottom: 0.8125rem;
+  margin-right: 1rem;
+  font-size: 1.25rem;
+  line-height: inherit;
+  white-space: nowrap;
+}
+.navbar-brand:hover, .navbar-brand:focus {
+  text-decoration: none;
+}
+
+.navbar-nav {
+  display: flex;
+  flex-direction: column;
+  padding-left: 0;
+  margin-bottom: 0;
+  list-style: none;
+}
+.navbar-nav .nav-link {
+  padding-right: 0;
+  padding-left: 0;
+}
+.navbar-nav .dropdown-menu {
+  position: static;
+  float: none;
+}
+
+.navbar-text {
+  display: inline-block;
+  padding-top: 1rem;
+  padding-bottom: 1rem;
+}
+
+.navbar-collapse {
+  flex-basis: 100%;
+  flex-grow: 1;
+  align-items: center;
+}
+
+.navbar-toggler {
+  padding: 0.25rem 0.75rem;
+  font-size: 1.25rem;
+  line-height: 1;
+  background-color: transparent;
+  border: 1px solid transparent;
+  border-radius: 0.3rem;
+}
+.navbar-toggler:hover, .navbar-toggler:focus {
+  text-decoration: none;
+}
+
+.navbar-toggler-icon {
+  display: inline-block;
+  width: 1.5em;
+  height: 1.5em;
+  vertical-align: middle;
+  content: "";
+  background: 50%/100% 100% no-repeat;
+}
+
+.navbar-nav-scroll {
+  max-height: 75vh;
+  overflow-y: auto;
+}
+
+@media (max-width: 575.98px) {
+  .navbar-expand-sm > .container,
+.navbar-expand-sm > .container-fluid,
+.navbar-expand-sm > .container-sm,
+.navbar-expand-sm > .container-md,
+.navbar-expand-sm > .container-lg,
+.navbar-expand-sm > .container-xl {
+    padding-right: 0;
+    padding-left: 0;
+  }
+}
+@media (min-width: 576px) {
+  .navbar-expand-sm {
+    flex-flow: row nowrap;
+    justify-content: flex-start;
+  }
+  .navbar-expand-sm .navbar-nav {
+    flex-direction: row;
+  }
+  .navbar-expand-sm .navbar-nav .dropdown-menu {
+    position: absolute;
+  }
+  .navbar-expand-sm .navbar-nav .nav-link {
+    padding-right: 0.5rem;
+    padding-left: 0.5rem;
+  }
+  .navbar-expand-sm > .container,
+.navbar-expand-sm > .container-fluid,
+.navbar-expand-sm > .container-sm,
+.navbar-expand-sm > .container-md,
+.navbar-expand-sm > .container-lg,
+.navbar-expand-sm > .container-xl {
+    flex-wrap: nowrap;
+  }
+  .navbar-expand-sm .navbar-nav-scroll {
+    overflow: visible;
+  }
+  .navbar-expand-sm .navbar-collapse {
+    display: flex !important;
+    flex-basis: auto;
+  }
+  .navbar-expand-sm .navbar-toggler {
+    display: none;
+  }
+}
+@media (max-width: 767.98px) {
+  .navbar-expand-md > .container,
+.navbar-expand-md > .container-fluid,
+.navbar-expand-md > .container-sm,
+.navbar-expand-md > .container-md,
+.navbar-expand-md > .container-lg,
+.navbar-expand-md > .container-xl {
+    padding-right: 0;
+    padding-left: 0;
+  }
+}
+@media (min-width: 768px) {
+  .navbar-expand-md {
+    flex-flow: row nowrap;
+    justify-content: flex-start;
+  }
+  .navbar-expand-md .navbar-nav {
+    flex-direction: row;
+  }
+  .navbar-expand-md .navbar-nav .dropdown-menu {
+    position: absolute;
+  }
+  .navbar-expand-md .navbar-nav .nav-link {
+    padding-right: 0.5rem;
+    padding-left: 0.5rem;
+  }
+  .navbar-expand-md > .container,
+.navbar-expand-md > .container-fluid,
+.navbar-expand-md > .container-sm,
+.navbar-expand-md > .container-md,
+.navbar-expand-md > .container-lg,
+.navbar-expand-md > .container-xl {
+    flex-wrap: nowrap;
+  }
+  .navbar-expand-md .navbar-nav-scroll {
+    overflow: visible;
+  }
+  .navbar-expand-md .navbar-collapse {
+    display: flex !important;
+    flex-basis: auto;
+  }
+  .navbar-expand-md .navbar-toggler {
+    display: none;
+  }
+}
+@media (max-width: 991.98px) {
+  .navbar-expand-lg > .container,
+.navbar-expand-lg > .container-fluid,
+.navbar-expand-lg > .container-sm,
+.navbar-expand-lg > .container-md,
+.navbar-expand-lg > .container-lg,
+.navbar-expand-lg > .container-xl {
+    padding-right: 0;
+    padding-left: 0;
+  }
+}
+@media (min-width: 992px) {
+  .navbar-expand-lg {
+    flex-flow: row nowrap;
+    justify-content: flex-start;
+  }
+  .navbar-expand-lg .navbar-nav {
+    flex-direction: row;
+  }
+  .navbar-expand-lg .navbar-nav .dropdown-menu {
+    position: absolute;
+  }
+  .navbar-expand-lg .navbar-nav .nav-link {
+    padding-right: 0.5rem;
+    padding-left: 0.5rem;
+  }
+  .navbar-expand-lg > .container,
+.navbar-expand-lg > .container-fluid,
+.navbar-expand-lg > .container-sm,
+.navbar-expand-lg > .container-md,
+.navbar-expand-lg > .container-lg,
+.navbar-expand-lg > .container-xl {
+    flex-wrap: nowrap;
+  }
+  .navbar-expand-lg .navbar-nav-scroll {
+    overflow: visible;
+  }
+  .navbar-expand-lg .navbar-collapse {
+    display: flex !important;
+    flex-basis: auto;
+  }
+  .navbar-expand-lg .navbar-toggler {
+    display: none;
+  }
+}
+@media (max-width: 1199.98px) {
+  .navbar-expand-xl > .container,
+.navbar-expand-xl > .container-fluid,
+.navbar-expand-xl > .container-sm,
+.navbar-expand-xl > .container-md,
+.navbar-expand-xl > .container-lg,
+.navbar-expand-xl > .container-xl {
+    padding-right: 0;
+    padding-left: 0;
+  }
+}
+@media (min-width: 1200px) {
+  .navbar-expand-xl {
+    flex-flow: row nowrap;
+    justify-content: flex-start;
+  }
+  .navbar-expand-xl .navbar-nav {
+    flex-direction: row;
+  }
+  .navbar-expand-xl .navbar-nav .dropdown-menu {
+    position: absolute;
+  }
+  .navbar-expand-xl .navbar-nav .nav-link {
+    padding-right: 0.5rem;
+    padding-left: 0.5rem;
+  }
+  .navbar-expand-xl > .container,
+.navbar-expand-xl > .container-fluid,
+.navbar-expand-xl > .container-sm,
+.navbar-expand-xl > .container-md,
+.navbar-expand-xl > .container-lg,
+.navbar-expand-xl > .container-xl {
+    flex-wrap: nowrap;
+  }
+  .navbar-expand-xl .navbar-nav-scroll {
+    overflow: visible;
+  }
+  .navbar-expand-xl .navbar-collapse {
+    display: flex !important;
+    flex-basis: auto;
+  }
+  .navbar-expand-xl .navbar-toggler {
+    display: none;
+  }
+}
+.navbar-expand {
+  flex-flow: row nowrap;
+  justify-content: flex-start;
+}
+.navbar-expand > .container,
+.navbar-expand > .container-fluid,
+.navbar-expand > .container-sm,
+.navbar-expand > .container-md,
+.navbar-expand > .container-lg,
+.navbar-expand > .container-xl {
+  padding-right: 0;
+  padding-left: 0;
+}
+.navbar-expand .navbar-nav {
+  flex-direction: row;
+}
+.navbar-expand .navbar-nav .dropdown-menu {
+  position: absolute;
+}
+.navbar-expand .navbar-nav .nav-link {
+  padding-right: 0.5rem;
+  padding-left: 0.5rem;
+}
+.navbar-expand > .container,
+.navbar-expand > .container-fluid,
+.navbar-expand > .container-sm,
+.navbar-expand > .container-md,
+.navbar-expand > .container-lg,
+.navbar-expand > .container-xl {
+  flex-wrap: nowrap;
+}
+.navbar-expand .navbar-nav-scroll {
+  overflow: visible;
+}
+.navbar-expand .navbar-collapse {
+  display: flex !important;
+  flex-basis: auto;
+}
+.navbar-expand .navbar-toggler {
+  display: none;
+}
+
+.navbar-light .navbar-brand {
+  color: rgba(0, 0, 0, 0.9);
+}
+.navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus {
+  color: rgba(0, 0, 0, 0.9);
+}
+.navbar-light .navbar-nav .nav-link {
+  color: rgba(0, 0, 0, 0.5);
+}
+.navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus {
+  color: rgba(0, 0, 0, 0.7);
+}
+.navbar-light .navbar-nav .nav-link.disabled {
+  color: rgba(0, 0, 0, 0.3);
+}
+.navbar-light .navbar-nav .show > .nav-link,
+.navbar-light .navbar-nav .active > .nav-link,
+.navbar-light .navbar-nav .nav-link.show,
+.navbar-light .navbar-nav .nav-link.active {
+  color: rgba(0, 0, 0, 0.9);
+}
+.navbar-light .navbar-toggler {
+  color: rgba(0, 0, 0, 0.5);
+  border-color: rgba(0, 0, 0, 0.1);
+}
+.navbar-light .navbar-toggler-icon {
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
+}
+.navbar-light .navbar-text {
+  color: rgba(0, 0, 0, 0.5);
+}
+.navbar-light .navbar-text a {
+  color: rgba(0, 0, 0, 0.9);
+}
+.navbar-light .navbar-text a:hover, .navbar-light .navbar-text a:focus {
+  color: rgba(0, 0, 0, 0.9);
+}
+
+.navbar-dark .navbar-brand {
+  color: #fff;
+}
+.navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus {
+  color: #fff;
+}
+.navbar-dark .navbar-nav .nav-link {
+  color: rgba(255, 255, 255, 0.5);
+}
+.navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus {
+  color: rgba(255, 255, 255, 0.75);
+}
+.navbar-dark .navbar-nav .nav-link.disabled {
+  color: rgba(255, 255, 255, 0.25);
+}
+.navbar-dark .navbar-nav .show > .nav-link,
+.navbar-dark .navbar-nav .active > .nav-link,
+.navbar-dark .navbar-nav .nav-link.show,
+.navbar-dark .navbar-nav .nav-link.active {
+  color: #fff;
+}
+.navbar-dark .navbar-toggler {
+  color: rgba(255, 255, 255, 0.5);
+  border-color: rgba(255, 255, 255, 0.1);
+}
+.navbar-dark .navbar-toggler-icon {
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
+}
+.navbar-dark .navbar-text {
+  color: rgba(255, 255, 255, 0.5);
+}
+.navbar-dark .navbar-text a {
+  color: #fff;
+}
+.navbar-dark .navbar-text a:hover, .navbar-dark .navbar-text a:focus {
+  color: #fff;
+}
+
+.img-fluid {
+  max-width: 100%;
+  height: auto;
+}
+
+.img-thumbnail {
+  padding: 0.25rem;
+  background-color: #fff;
+  border: 1px solid #dee2e6;
+  border-radius: 0.3rem;
+  max-width: 100%;
+  height: auto;
+}
+
+.figure {
+  display: inline-block;
+}
+
+.figure-img {
+  margin-bottom: 0.5rem;
+  line-height: 1;
+}
+
+.figure-caption {
+  font-size: 90%;
+  color: #6c757d;
+}
+
+.tooltip {
+  position: absolute;
+  z-index: 1070;
+  display: block;
+  margin: 0;
+  font-family: "Exo 2", BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+  font-style: normal;
+  font-weight: 400;
+  line-height: 1.5;
+  text-align: left;
+  text-align: start;
+  text-decoration: none;
+  text-shadow: none;
+  text-transform: none;
+  letter-spacing: normal;
+  word-break: normal;
+  word-spacing: normal;
+  white-space: normal;
+  line-break: auto;
+  font-size: 0.875rem;
+  word-wrap: break-word;
+  opacity: 0;
+}
+.tooltip.show {
+  opacity: 0.9;
+}
+.tooltip .arrow {
+  position: absolute;
+  display: block;
+  width: 0.8rem;
+  height: 0.4rem;
+}
+.tooltip .arrow::before {
+  position: absolute;
+  content: "";
+  border-color: transparent;
+  border-style: solid;
+}
+
+.bs-tooltip-top, .bs-tooltip-auto[x-placement^=top] {
+  padding: 0.4rem 0;
+}
+.bs-tooltip-top .arrow, .bs-tooltip-auto[x-placement^=top] .arrow {
+  bottom: 0;
+}
+.bs-tooltip-top .arrow::before, .bs-tooltip-auto[x-placement^=top] .arrow::before {
+  top: 0;
+  border-width: 0.4rem 0.4rem 0;
+  border-top-color: #000;
+}
+
+.bs-tooltip-right, .bs-tooltip-auto[x-placement^=right] {
+  padding: 0 0.4rem;
+}
+.bs-tooltip-right .arrow, .bs-tooltip-auto[x-placement^=right] .arrow {
+  left: 0;
+  width: 0.4rem;
+  height: 0.8rem;
+}
+.bs-tooltip-right .arrow::before, .bs-tooltip-auto[x-placement^=right] .arrow::before {
+  right: 0;
+  border-width: 0.4rem 0.4rem 0.4rem 0;
+  border-right-color: #000;
+}
+
+.bs-tooltip-bottom, .bs-tooltip-auto[x-placement^=bottom] {
+  padding: 0.4rem 0;
+}
+.bs-tooltip-bottom .arrow, .bs-tooltip-auto[x-placement^=bottom] .arrow {
+  top: 0;
+}
+.bs-tooltip-bottom .arrow::before, .bs-tooltip-auto[x-placement^=bottom] .arrow::before {
+  bottom: 0;
+  border-width: 0 0.4rem 0.4rem;
+  border-bottom-color: #000;
+}
+
+.bs-tooltip-left, .bs-tooltip-auto[x-placement^=left] {
+  padding: 0 0.4rem;
+}
+.bs-tooltip-left .arrow, .bs-tooltip-auto[x-placement^=left] .arrow {
+  right: 0;
+  width: 0.4rem;
+  height: 0.8rem;
+}
+.bs-tooltip-left .arrow::before, .bs-tooltip-auto[x-placement^=left] .arrow::before {
+  left: 0;
+  border-width: 0.4rem 0 0.4rem 0.4rem;
+  border-left-color: #000;
+}
+
+.tooltip-inner {
+  max-width: 200px;
+  padding: 0.25rem 0.5rem;
+  color: #fff;
+  text-align: center;
+  background-color: #000;
+  border-radius: 0.3rem;
+}
+
+.popover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 1060;
+  display: block;
+  max-width: 276px;
+  font-family: "Exo 2", BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+  font-style: normal;
+  font-weight: 400;
+  line-height: 1.5;
+  text-align: left;
+  text-align: start;
+  text-decoration: none;
+  text-shadow: none;
+  text-transform: none;
+  letter-spacing: normal;
+  word-break: normal;
+  word-spacing: normal;
+  white-space: normal;
+  line-break: auto;
+  font-size: 0.875rem;
+  word-wrap: break-word;
+  background-color: #fff;
+  background-clip: padding-box;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  border-radius: 0.3rem;
+}
+.popover .arrow {
+  position: absolute;
+  display: block;
+  width: 1rem;
+  height: 0.5rem;
+  margin: 0 0.3rem;
+}
+.popover .arrow::before, .popover .arrow::after {
+  position: absolute;
+  display: block;
+  content: "";
+  border-color: transparent;
+  border-style: solid;
+}
+
+.bs-popover-top, .bs-popover-auto[x-placement^=top] {
+  margin-bottom: 0.5rem;
+}
+.bs-popover-top > .arrow, .bs-popover-auto[x-placement^=top] > .arrow {
+  bottom: calc(-0.5rem - 1px);
+}
+.bs-popover-top > .arrow::before, .bs-popover-auto[x-placement^=top] > .arrow::before {
+  bottom: 0;
+  border-width: 0.5rem 0.5rem 0;
+  border-top-color: rgba(0, 0, 0, 0.25);
+}
+.bs-popover-top > .arrow::after, .bs-popover-auto[x-placement^=top] > .arrow::after {
+  bottom: 1px;
+  border-width: 0.5rem 0.5rem 0;
+  border-top-color: #fff;
+}
+
+.bs-popover-right, .bs-popover-auto[x-placement^=right] {
+  margin-left: 0.5rem;
+}
+.bs-popover-right > .arrow, .bs-popover-auto[x-placement^=right] > .arrow {
+  left: calc(-0.5rem - 1px);
+  width: 0.5rem;
+  height: 1rem;
+  margin: 0.3rem 0;
+}
+.bs-popover-right > .arrow::before, .bs-popover-auto[x-placement^=right] > .arrow::before {
+  left: 0;
+  border-width: 0.5rem 0.5rem 0.5rem 0;
+  border-right-color: rgba(0, 0, 0, 0.25);
+}
+.bs-popover-right > .arrow::after, .bs-popover-auto[x-placement^=right] > .arrow::after {
+  left: 1px;
+  border-width: 0.5rem 0.5rem 0.5rem 0;
+  border-right-color: #fff;
+}
+
+.bs-popover-bottom, .bs-popover-auto[x-placement^=bottom] {
+  margin-top: 0.5rem;
+}
+.bs-popover-bottom > .arrow, .bs-popover-auto[x-placement^=bottom] > .arrow {
+  top: calc(-0.5rem - 1px);
+}
+.bs-popover-bottom > .arrow::before, .bs-popover-auto[x-placement^=bottom] > .arrow::before {
+  top: 0;
+  border-width: 0 0.5rem 0.5rem 0.5rem;
+  border-bottom-color: rgba(0, 0, 0, 0.25);
+}
+.bs-popover-bottom > .arrow::after, .bs-popover-auto[x-placement^=bottom] > .arrow::after {
+  top: 1px;
+  border-width: 0 0.5rem 0.5rem 0.5rem;
+  border-bottom-color: #fff;
+}
+.bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^=bottom] .popover-header::before {
+  position: absolute;
+  top: 0;
+  left: 50%;
+  display: block;
+  width: 1rem;
+  margin-left: -0.5rem;
+  content: "";
+  border-bottom: 1px solid #f7f7f7;
+}
+
+.bs-popover-left, .bs-popover-auto[x-placement^=left] {
+  margin-right: 0.5rem;
+}
+.bs-popover-left > .arrow, .bs-popover-auto[x-placement^=left] > .arrow {
+  right: calc(-0.5rem - 1px);
+  width: 0.5rem;
+  height: 1rem;
+  margin: 0.3rem 0;
+}
+.bs-popover-left > .arrow::before, .bs-popover-auto[x-placement^=left] > .arrow::before {
+  right: 0;
+  border-width: 0.5rem 0 0.5rem 0.5rem;
+  border-left-color: rgba(0, 0, 0, 0.25);
+}
+.bs-popover-left > .arrow::after, .bs-popover-auto[x-placement^=left] > .arrow::after {
+  right: 1px;
+  border-width: 0.5rem 0 0.5rem 0.5rem;
+  border-left-color: #fff;
+}
+
+.popover-header {
+  padding: 0.5rem 0.75rem;
+  margin-bottom: 0;
+  font-size: 1rem;
+  background-color: #f7f7f7;
+  border-bottom: 1px solid #ebebeb;
+  border-top-left-radius: calc(0.3rem - 1px);
+  border-top-right-radius: calc(0.3rem - 1px);
+}
+.popover-header:empty {
+  display: none;
+}
+
+.popover-body {
+  padding: 0.5rem 0.75rem;
+  color: #212529;
+}
+
+html,
+body {
+  min-height: 100%;
+  font-size: 16px;
+  text-rendering: geometricPrecision !important;
+}
+
+#app {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+}
+
+@media (min-width: 992px) {
+  .pt-lg-10 {
+    padding-top: 10rem !important;
+  }
+}
+
+@media (min-width: 992px) {
+  .pb-lg-10 {
+    padding-bottom: 10rem !important;
+  }
+}
+
+@media (min-width: 992px) {
+  .container-pricing-max-height {
+    max-height: 800px;
+  }
+}
+
+h1,
+.h1 {
+  font-family: "Exo 2", BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+  font-weight: 800 !important;
+  text-transform: uppercase;
+  line-height: 1;
+  font-size: 2rem !important;
+}
+@media (min-width: 992px) {
+  h1,
+.h1 {
+    font-size: 2.8rem !important;
+  }
+}
+
+h3,
+.h3 {
+  color: #fff;
+  font-weight: 700;
+  font-size: 1.5rem;
+}
+
+.h4 {
+  color: #5942c1;
+  text-transform: uppercase;
+  font-weight: bold;
+  font-size: 1rem;
+  border-left: 3px solid #5942c1;
+  padding-left: 1rem;
+  margin-bottom: 1rem;
+}
+
+.h5 {
+  display: inline-block;
+  font-size: 1.1rem;
+  padding: 0.65rem 1rem;
+  border-radius: 5px;
+  background-color: #ffe500;
+}
+.h5 + .lead {
+  font-size: 2.2rem;
+  font-weight: 700;
+  margin-bottom: 1rem;
+  line-height: 1.3;
+}
+
+.font-size-l {
+  font-size: 3.2rem !important;
+}
+
+.font-size-xl {
+  font-size: 4rem !important;
+}
+
+.line-height-2 {
+  line-height: 2;
+}
+
+.center-center {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.icon {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  width: 50px;
+  height: 50px;
+  background: #343a40;
+}
+.icon i {
+  font-size: 1.2rem;
+}
+@media (min-width: 992px) {
+  .icon {
+    width: 80px;
+    height: 80px;
+  }
+  .icon i {
+    font-size: 2rem;
+  }
+}
+
+.bg-before-dark {
+  position: relative;
+}
+.bg-before-dark:before {
+  content: "";
+  background-color: #212529;
+  position: absolute;
+  width: 100%;
+  height: 325px;
+  top: -1px;
+  display: block;
+}
+
+.bg-before-dark-contact {
+  position: relative;
+}
+.bg-before-dark-contact:before {
+  background-color: #212529;
+  position: absolute;
+  width: 100%;
+  left: 0;
+  bottom: 0;
+  height: 100px;
+  display: block;
+}
+@media (min-width: 992px) {
+  .bg-before-dark-contact:before {
+    content: "";
+  }
+}
+
+.btn-icon {
+  width: 50px !important;
+  height: 50px !important;
+  align-items: center;
+  justify-content: center;
+  display: inline-flex;
+  margin: 0 1rem;
+}
+.btn-icon i {
+  margin: 0 !important;
+  font-size: 1.3rem !important;
+}
+
+.list-disc {
+  padding: 0;
+}
+.list-disc li {
+  margin-bottom: 0.5rem;
+}
+.list-disc li a {
+  color: #fff;
+}
+.list-disc li a:before {
+  content: "";
+  display: inline-block;
+  width: 3px;
+  height: 3px;
+  background-color: #ffe500;
+  margin-right: 0.5rem;
+  margin-bottom: 0.2rem;
+}
+
+.card-about-us .card-header {
+  height: 460px;
+  background-size: cover;
+}
+
+.card-contact .card-header {
+  height: 200px;
+  border-top-right-radius: 10px;
+}
+@media (min-width: 992px) {
+  .card-contact .card-header {
+    height: 400px;
+  }
+}
+
+.form-control-underlined,
+.form-control-underlined:focus {
+  background: transparent;
+  box-shadow: none !important;
+  border: 0;
+  border-radius: 0;
+  border-bottom: 1px solid #9c9e9f;
+  color: #fff;
+  padding-left: 0;
+  padding-right: 0;
+}
+
+.form-control-underlined:focus {
+  border-color: #ffe500;
+}
+
+textarea.form-control-underlined {
+  resize: none;
+  height: 150px;
+}
+
+.cursor-pointer {
+  cursor: pointer;
+}
+.cursor-pointer:hover {
+  cursor: pointer;
+}
+
+[class*=btn-outline-] {
+  border-width: 3px;
+}
+
+.btn {
+  font-family: "Exo 2", BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+  font-weight: 400 !important;
+}
+.btn i {
+  font-size: 0.8rem;
+  margin-left: 0.5rem;
+}
+
+.btn-chat {
+  position: fixed;
+  width: auto;
+  font-size: 1.2rem;
+  bottom: 1rem;
+  right: 1rem;
+}
+
+.btn-nav {
+  display: block;
+  color: #000;
+  font-size: 18px;
+  font-weight: 700;
+  text-transform: uppercase;
+  margin: 0;
+  position: relative;
+}
+.btn-nav:focus {
+  box-shadow: none;
+}
+.btn-nav i {
+  margin-left: 0.5rem;
+  font-size: 0.875rem;
+}
+
+.btn-sm {
+  font-size: 14px !important;
+}
+
+@media (max-width: 768px) {
+  .btn-nav {
+    margin: auto;
+    color: black !important;
+  }
+
+  .header-menu--right .btn-primary {
+    margin-top: 1rem;
+  }
+}
+.btn-remove {
+  height: 31px;
+  width: 31px;
+  background-color: transparent;
+  color: #ced4da;
+  border: 1px solid #ced4da;
+}
+.btn-remove:hover, .btn-remove:focus, .btn-remove:focus:hover {
+  background-color: transparent;
+  color: #dc3545;
+  border-color: #dc3545;
+}
+.btn-remove i {
+  margin: 0;
+}
+
+.btn-tab {
+  flex: 1;
+  color: white;
+  background-color: #5942c1;
+  margin: 0 1rem;
+  background-size: contain;
+}
+.btn-tab img {
+  height: 20px;
+}
+@media (min-width: 768px) {
+  .btn-tab {
+    padding: 1.5rem 2rem;
+  }
+  .btn-tab img {
+    margin-bottom: 0.5rem;
+    height: 30px;
+  }
+}
+.btn-tab:not(.router-link-exact-active) {
+  background-image: none !important;
+  background-color: white;
+  color: #5942c1;
+}
+.btn-tab.router-link-exact-active:hover {
+  color: white;
+}
+.btn-tab:first-of-type {
+  margin-left: 0;
+}
+.btn-tab:last-of-type {
+  margin-right: 0;
+}
+
+.btn-menu {
+  position: absolute;
+  z-index: 1;
+  color: white;
+  background: #212529;
+  border-radius: 0;
+  border-top-right-radius: 50%;
+  border-bottom-right-radius: 50%;
+  width: 40px;
+  height: 50px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  left: calc(260px);
+  top: 50%;
+  margin-top: -25px;
+}
+.btn-menu.collapsed {
+  left: 0;
+  margin-left: 0;
+}
+.btn-menu:hover, .btn-menu:focus, .btn-menu:hover:focus {
+  color: white;
+  text-decoration: none;
+}
+.btn-menu i {
+  margin: 0;
+  font-size: 1.1rem;
+  margin-right: 10px;
+}
+
+.btn-sign-in {
+  display: inline-flex;
+  align-items: center;
+  border: 1px solid #ced4da;
+  padding: 0;
+}
+.btn-sign-in .logo {
+  margin-right: 0.5rem;
+  background: white;
+  border-radius: 3px;
+  margin: 1px;
+  padding: 10px;
+  flex: 0 38px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 15px;
+}
+.btn-sign-in .logo img {
+  height: 22px;
+  width: 22px;
+}
+.btn-sign-in .label {
+  font-size: 16px;
+  color: white;
+  flex: 1;
+  text-align: left;
+}
+.btn-discord {
+  background-color: #7289da;
+}
+.btn-discord:active {
+  background-color: #7289da;
+}
+.btn-google {
+  background-color: #4285f4;
+}
+.btn-google:active {
+  background-color: #3367d6;
+}
+.btn-twitch {
+  background-color: #6441a5;
+}
+.btn-twitch:active {
+  background-color: #b9a3e3;
+}
+.btn-twitter {
+  background-color: #0098ff;
+}
+.btn-twitter:active {
+  background-color: #3367d6;
+}
+
+.btn-spotify {
+  background-color: #1d75de;
+}
+.btn-spotify:active {
+  background-color: #1865c2;
+}
+.btn-github {
+  background-color: #171515;
+}
+.btn-github:active {
+  background-color: #171515;
+}
+.btn-metamask {
+  background-color: #037dd6;
+}
+.btn-metamask:active {
+  background-color: #037dd6;
+}
+.btn-shopify {
+  background-color: #96bf48;
+}
+.btn-shopify:active {
+  background-color: #3367d6;
+}
+
+.card-logo {
+  display: flex;
+  border: 0;
+  flex: 0 0 120px;
+  background-color: #f8f9fa;
+  height: 120px;
+  width: 120px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  border-radius: 10px;
+}
+.card-logo:nth-child(3n) {
+  margin-right: 0;
+}
+.card-logo img {
+  margin: auto;
+  display: block;
+}
+
+.card-logo-lg {
+  width: 200px;
+  height: 200px;
+  border-radius: 15px;
+}
+
+a i.fa-question-circle {
+  color: #ced4da;
+}
+
+body {
+  background-image: url("../img/bg.jpg") !important;
+  background-position: center center;
+  background-size: cover;
+  background-attachment: fixed;
+}
+
+/*# sourceMappingURL=main.css.map */
diff --git a/apps/auth/src/assets/css/main.css.map b/apps/auth/src/assets/css/main.css.map
new file mode 100644
index 000000000..42d1f4dbd
--- /dev/null
+++ b/apps/auth/src/assets/css/main.css.map
@@ -0,0 +1 @@
+{"version":3,"sourceRoot":"","sources":["../main.scss","../scss/_buttons.scss","../../node_modules/bootstrap/scss/_reboot.scss","../scss/_variables.scss","../../node_modules/bootstrap/scss/vendor/_rfs.scss","../../node_modules/bootstrap/scss/mixins/_hover.scss","../../node_modules/bootstrap/scss/utilities/_align.scss","../../node_modules/bootstrap/scss/mixins/_background-variant.scss","../../node_modules/bootstrap/scss/utilities/_background.scss","../../node_modules/bootstrap/scss/utilities/_borders.scss","../../node_modules/bootstrap/scss/mixins/_clearfix.scss","../../node_modules/bootstrap/scss/utilities/_display.scss","../../node_modules/bootstrap/scss/mixins/_breakpoints.scss","../../node_modules/bootstrap/scss/utilities/_embed.scss","../../node_modules/bootstrap/scss/utilities/_flex.scss","../../node_modules/bootstrap/scss/utilities/_float.scss","../../node_modules/bootstrap/scss/utilities/_interactions.scss","../../node_modules/bootstrap/scss/utilities/_overflow.scss","../../node_modules/bootstrap/scss/utilities/_position.scss","../../node_modules/bootstrap/scss/utilities/_screenreaders.scss","../../node_modules/bootstrap/scss/mixins/_screen-reader.scss","../../node_modules/bootstrap/scss/utilities/_shadows.scss","../../node_modules/bootstrap/scss/utilities/_sizing.scss","../../node_modules/bootstrap/scss/utilities/_spacing.scss","../../node_modules/bootstrap/scss/utilities/_stretched-link.scss","../../node_modules/bootstrap/scss/utilities/_text.scss","../../node_modules/bootstrap/scss/mixins/_text-truncate.scss","../../node_modules/bootstrap/scss/mixins/_text-emphasis.scss","../../node_modules/bootstrap/scss/mixins/_text-hide.scss","../../node_modules/bootstrap/scss/utilities/_visibility.scss","../../node_modules/bootstrap/scss/_root.scss","../../node_modules/bootstrap/scss/_type.scss","../../node_modules/bootstrap/scss/mixins/_lists.scss","../../node_modules/bootstrap/scss/_card.scss","../../node_modules/bootstrap/scss/mixins/_border-radius.scss","../../node_modules/bootstrap/scss/_badge.scss","../../node_modules/bootstrap/scss/mixins/_transition.scss","../../node_modules/bootstrap/scss/mixins/_badge.scss","../../node_modules/bootstrap/scss/_variables.scss","../../node_modules/bootstrap/scss/_forms.scss","../../node_modules/bootstrap/scss/mixins/_forms.scss","../../node_modules/bootstrap/scss/mixins/_gradients.scss","../../node_modules/bootstrap/scss/_custom-forms.scss","../../node_modules/bootstrap/scss/_list-group.scss","../../node_modules/bootstrap/scss/mixins/_list-group.scss","../../node_modules/bootstrap/scss/_alert.scss","../../node_modules/bootstrap/scss/mixins/_alert.scss","../../node_modules/bootstrap/scss/_grid.scss","../../node_modules/bootstrap/scss/mixins/_grid.scss","../../node_modules/bootstrap/scss/mixins/_grid-framework.scss","../../node_modules/bootstrap/scss/_transitions.scss","../../node_modules/bootstrap/scss/_buttons.scss","../../node_modules/bootstrap/scss/mixins/_buttons.scss","../../node_modules/bootstrap/scss/_spinners.scss","../../node_modules/bootstrap/scss/_nav.scss","../../node_modules/bootstrap/scss/_navbar.scss","../../node_modules/bootstrap/scss/_images.scss","../../node_modules/bootstrap/scss/mixins/_image.scss","../../node_modules/bootstrap/scss/_tooltip.scss","../../node_modules/bootstrap/scss/mixins/_reset-text.scss","../../node_modules/bootstrap/scss/_popover.scss","../scss/_root.scss","../scss/_card.scss","../scss/_forms.scss"],"names":[],"mappings":";AAAQ;ACAA;ACkBR;AAAA;AAAA;EAGE;;;AAGF;EACE;EACA;EACA;EACA;;;AAMF;EACE;;;AAUF;EACE;EACA,aC2NuB;EC3InB,WAtCa;EFxCjB,aCqOmB;EDpOnB,aCyOiB;EDxOjB,OCnCS;EDoCT;EACA,kBC9CM;;;AD0DR;EACE;;;AASF;EACE;EACA;EACA;;;AAaF;EACE;EACA,eCuMuB;;;ADhMzB;EACE;EACA,eCsFwB;;;AD3E1B;AAAA;EAEE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;AAAA;AAAA;EAGE;EACA;;;AAGF;AAAA;AAAA;AAAA;EAIE;;;AAGF;EACE,aCwIiB;;;ADrInB;EACE;EACA;;;AAGF;EACE;;;AAGF;AAAA;EAEE,aC2HmB;;;ADxHrB;EExFI;;;AFiGJ;AAAA;EAEE;EEnGE;EFqGF;EACA;;;AAGF;EAAM;;;AACN;EAAM;;;AAON;EACE,OCTW;EDUX,iBCTgB;EDUhB;;AGhLA;EHmLE,OCZe;EDaf,iBCZoB;;;ADqBxB;EACE;EACA;;AG/LA;EHkME;EACA;;;AASJ;AAAA;AAAA;AAAA;EAIE,aCgDsB;ECpMpB;;;AFwJJ;EAEE;EAEA;EAEA;EAGA;;;AAQF;EAEE;;;AAQF;EACE;EACA;;;AAGF;EAGE;EACA;;;AAQF;EACE;;;AAGF;EACE,aCmEmB;EDlEnB,gBCkEmB;EDjEnB,OCtQS;EDuQT;EACA;;;AAOF;EAEE;EACA;;;AAQF;EAEE;EACA,eC+IoB;;;ADzItB;EAEE;;;AAQF;EACE;;;AAGF;AAAA;AAAA;AAAA;AAAA;EAKE;EACA;EE5PE;EF8PF;;;AAGF;AAAA;EAEE;;;AAGF;AAAA;EAEE;;;AAMF;EACE;;;AAMF;EACE;;;AAOF;AAAA;AAAA;AAAA;EAIE;;;AASE;AAAA;AAAA;AAAA;EACE;;;AAMN;AAAA;AAAA;AAAA;EAIE;EACA;;;AAGF;AAAA;EAEE;EACA;;;AAIF;EACE;EAEA;;;AAGF;EAME;EAEA;EACA;EACA;;;AAKF;EACE;EACA;EACA;EACA;EACA;EEzQM,WAhEW;EF2UjB;EACA;EACA;;AEvPM;EF8OR;IEtOY,WA9DM;;;;AFgTlB;EACE;;;AAIF;AAAA;EAEE;;;AAGF;EAKE;EACA;;;AAOF;EACE;;;AAQF;EACE;EACA;;;AAOF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAKF;EACE;;;AIheF;EAAqB;;;AACrB;EAAqB;;;AACrB;EAAqB;;;AACrB;EAAqB;;;AACrB;EAAqB;;;AACrB;EAAqB;;;ACFnB;EACE;;;AFUF;AAAA;AAAA;EELI;;;AANJ;EACE;;;AFUF;AAAA;AAAA;EELI;;;AANJ;EACE;;;AFUF;AAAA;AAAA;EELI;;;AANJ;EACE;;;AFUF;AAAA;AAAA;EELI;;;AANJ;EACE;;;AFUF;AAAA;AAAA;EELI;;;AANJ;EACE;;;AFUF;AAAA;AAAA;EELI;;;AANJ;EACE;;;AFUF;AAAA;AAAA;EELI;;;AANJ;EACE;;;AFUF;AAAA;AAAA;EELI;;;AANJ;EACE;;;AFUF;AAAA;AAAA;EELI;;;AANJ;EACE;;;AFUF;AAAA;AAAA;EELI;;;ACCN;EACE;;;AAGF;EACE;;;ACXF;EAAkB;;;AAClB;EAAkB;;;AAClB;EAAkB;;;AAClB;EAAkB;;;AAClB;EAAkB;;;AAElB;EAAmB;;;AACnB;EAAmB;;;AACnB;EAAmB;;;AACnB;EAAmB;;;AACnB;EAAmB;;;AAGjB;EACE;;;AADF;EACE;;;AADF;EACE;;;AADF;EACE;;;AADF;EACE;;;AADF;EACE;;;AADF;EACE;;;AADF;EACE;;;AADF;EACE;;;AADF;EACE;;;AAIJ;EACE;;;AAOF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;ACxEA;EACE;EACA;EACA;;;ACOE;EAAwB;;;AAAxB;EAAwB;;;AAAxB;EAAwB;;;AAAxB;EAAwB;;;AAAxB;EAAwB;;;AAAxB;EAAwB;;;AAAxB;EAAwB;;;AAAxB;EAAwB;;;AAAxB;EAAwB;;;ACiD1B;EDjDE;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;ACiD1B;EDjDE;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;ACiD1B;EDjDE;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;ACiD1B;EDjDE;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;EAAxB;IAAwB;;;AAU9B;EAEI;IAAqB;;;EAArB;IAAqB;;;EAArB;IAAqB;;;EAArB;IAAqB;;;EAArB;IAAqB;;;EAArB;IAAqB;;;EAArB;IAAqB;;;EAArB;IAAqB;;;EAArB;IAAqB;;;AErBzB;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;AAAA;AAAA;AAAA;AAAA;EAKE;EACA;EACA;EACA;EACA;EACA;EACA;;;AASA;EACE;;;AADF;EACE;;;AADF;EACE;;;AADF;EACE;;;AADF;EACE;;;AADF;EACE;;;AADF;EACE;;;AADF;EACE;;;ACzBF;EAAgC;;;AAChC;EAAgC;;;AAChC;EAAgC;;;AAChC;EAAgC;;;AAEhC;EAA8B;;;AAC9B;EAA8B;;;AAC9B;EAA8B;;;AAC9B;EAA8B;;;AAC9B;EAA8B;;;AAC9B;EAA8B;;;AAC9B;EAA8B;;;AAC9B;EAA8B;;;AAE9B;EAAoC;;;AACpC;EAAoC;;;AACpC;EAAoC;;;AACpC;EAAoC;;;AACpC;EAAoC;;;AAEpC;EAAiC;;;AACjC;EAAiC;;;AACjC;EAAiC;;;AACjC;EAAiC;;;AACjC;EAAiC;;;AAEjC;EAAkC;;;AAClC;EAAkC;;;AAClC;EAAkC;;;AAClC;EAAkC;;;AAClC;EAAkC;;;AAClC;EAAkC;;;AAElC;EAAgC;;;AAChC;EAAgC;;;AAChC;EAAgC;;;AAChC;EAAgC;;;AAChC;EAAgC;;;AAChC;EAAgC;;;AFYhC;EElDA;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;EAEhC;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAE9B;IAAoC;;;EACpC;IAAoC;;;EACpC;IAAoC;;;EACpC;IAAoC;;;EACpC;IAAoC;;;EAEpC;IAAiC;;;EACjC;IAAiC;;;EACjC;IAAiC;;;EACjC;IAAiC;;;EACjC;IAAiC;;;EAEjC;IAAkC;;;EAClC;IAAkC;;;EAClC;IAAkC;;;EAClC;IAAkC;;;EAClC;IAAkC;;;EAClC;IAAkC;;;EAElC;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;AFYhC;EElDA;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;EAEhC;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAE9B;IAAoC;;;EACpC;IAAoC;;;EACpC;IAAoC;;;EACpC;IAAoC;;;EACpC;IAAoC;;;EAEpC;IAAiC;;;EACjC;IAAiC;;;EACjC;IAAiC;;;EACjC;IAAiC;;;EACjC;IAAiC;;;EAEjC;IAAkC;;;EAClC;IAAkC;;;EAClC;IAAkC;;;EAClC;IAAkC;;;EAClC;IAAkC;;;EAClC;IAAkC;;;EAElC;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;AFYhC;EElDA;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;EAEhC;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAE9B;IAAoC;;;EACpC;IAAoC;;;EACpC;IAAoC;;;EACpC;IAAoC;;;EACpC;IAAoC;;;EAEpC;IAAiC;;;EACjC;IAAiC;;;EACjC;IAAiC;;;EACjC;IAAiC;;;EACjC;IAAiC;;;EAEjC;IAAkC;;;EAClC;IAAkC;;;EAClC;IAAkC;;;EAClC;IAAkC;;;EAClC;IAAkC;;;EAClC;IAAkC;;;EAElC;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;AFYhC;EElDA;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;EAEhC;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAC9B;IAA8B;;;EAE9B;IAAoC;;;EACpC;IAAoC;;;EACpC;IAAoC;;;EACpC;IAAoC;;;EACpC;IAAoC;;;EAEpC;IAAiC;;;EACjC;IAAiC;;;EACjC;IAAiC;;;EACjC;IAAiC;;;EACjC;IAAiC;;;EAEjC;IAAkC;;;EAClC;IAAkC;;;EAClC;IAAkC;;;EAClC;IAAkC;;;EAClC;IAAkC;;;EAClC;IAAkC;;;EAElC;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;EAChC;IAAgC;;;AC1ChC;EAAwB;;;AACxB;EAAwB;;;AACxB;EAAwB;;;AHoDxB;EGtDA;IAAwB;;;EACxB;IAAwB;;;EACxB;IAAwB;;;AHoDxB;EGtDA;IAAwB;;;EACxB;IAAwB;;;EACxB;IAAwB;;;AHoDxB;EGtDA;IAAwB;;;EACxB;IAAwB;;;EACxB;IAAwB;;;AHoDxB;EGtDA;IAAwB;;;EACxB;IAAwB;;;EACxB;IAAwB;;;ACL1B;EAAyB;;;AAAzB;EAAyB;;;AAAzB;EAAyB;;;ACAzB;EAAsB;;;AAAtB;EAAsB;;;ACCtB;EAAyB;;;AAAzB;EAAyB;;;AAAzB;EAAyB;;;AAAzB;EAAyB;;;AAAzB;EAAyB;;;AAK3B;EACE;EACA;EACA;EACA;EACA,Sf2pBa;;;AexpBf;EACE;EACA;EACA;EACA;EACA,SfmpBa;;;Ae/oBb;EADF;IAEI;IACA;IACA,Sf2oBY;;;;AgBpqBhB;ECEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAUA;EAEE;EACA;EACA;EACA;EACA;EACA;;;AC7BJ;EAAa;;;AACb;EAAU;;;AACV;EAAa;;;AACb;EAAe;;;ACCX;EAAuB;;;AAAvB;EAAuB;;;AAAvB;EAAuB;;;AAAvB;EAAuB;;;AAAvB;EAAuB;;;AAAvB;EAAuB;;;AAAvB;EAAuB;;;AAAvB;EAAuB;;;AAAvB;EAAuB;;;AAAvB;EAAuB;;;AAI3B;EAAU;;;AACV;EAAU;;;AAIV;EAAc;;;AACd;EAAc;;;AAEd;EAAU;;;AACV;EAAU;;;ACTF;EAAgC;;;AAChC;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAfF;EAAgC;;;AAChC;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAfF;EAAgC;;;AAChC;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAfF;EAAgC;;;AAChC;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAfF;EAAgC;;;AAChC;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAfF;EAAgC;;;AAChC;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAfF;EAAgC;;;AAChC;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAfF;EAAgC;;;AAChC;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAfF;EAAgC;;;AAChC;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAfF;EAAgC;;;AAChC;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAfF;EAAgC;;;AAChC;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAfF;EAAgC;;;AAChC;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAQF;EAAwB;;;AACxB;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAfF;EAAwB;;;AACxB;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAfF;EAAwB;;;AACxB;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAfF;EAAwB;;;AACxB;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAfF;EAAwB;;;AACxB;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAMN;EAAmB;;;AACnB;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AAEF;AAAA;EAEE;;;AXTF;EWlDI;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAQF;IAAwB;;;EACxB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAwB;;;EACxB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAwB;;;EACxB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAwB;;;EACxB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAwB;;;EACxB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAMN;IAAmB;;;EACnB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;AXTF;EWlDI;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAQF;IAAwB;;;EACxB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAwB;;;EACxB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAwB;;;EACxB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAwB;;;EACxB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAwB;;;EACxB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAMN;IAAmB;;;EACnB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;AXTF;EWlDI;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAQF;IAAwB;;;EACxB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAwB;;;EACxB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAwB;;;EACxB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAwB;;;EACxB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAwB;;;EACxB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAMN;IAAmB;;;EACnB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;AXTF;EWlDI;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAgC;;;EAChC;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAQF;IAAwB;;;EACxB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAwB;;;EACxB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAwB;;;EACxB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAwB;;;EACxB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAfF;IAAwB;;;EACxB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAMN;IAAmB;;;EACnB;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;EAEF;AAAA;IAEE;;;AChEJ;EACE;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EAEA;;;ACVJ;EAAkB;;;AAIlB;EAAiB;;;AACjB;EAAiB;;;AACjB;EAAiB;;;AACjB;ECTE;EACA;EACA;;;ADeE;EAAwB;;;AACxB;EAAwB;;;AACxB;EAAwB;;;AbqCxB;EavCA;IAAwB;;;EACxB;IAAwB;;;EACxB;IAAwB;;;AbqCxB;EavCA;IAAwB;;;EACxB;IAAwB;;;EACxB;IAAwB;;;AbqCxB;EavCA;IAAwB;;;EACxB;IAAwB;;;EACxB;IAAwB;;;AbqCxB;EavCA;IAAwB;;;EACxB;IAAwB;;;EACxB;IAAwB;;;AAM5B;EAAmB;;;AACnB;EAAmB;;;AACnB;EAAmB;;;AAInB;EAAuB;;;AACvB;EAAuB;;;AACvB;EAAuB;;;AACvB;EAAuB;;;AACvB;EAAuB;;;AACvB;EAAuB;;;AAIvB;EAAc;;;AEvCZ;EACE;;;AtBUF;EsBLM;;;AANN;EACE;;;AtBUF;EsBLM;;;AANN;EACE;;;AtBUF;EsBLM;;;AANN;EACE;;;AtBUF;EsBLM;;;AANN;EACE;;;AtBUF;EsBLM;;;AANN;EACE;;;AtBUF;EsBLM;;;AANN;EACE;;;AtBUF;EsBLM;;;AANN;EACE;;;AtBUF;EsBLM;;;AANN;EACE;;;AtBUF;EsBLM;;;AANN;EACE;;;AtBUF;EsBLM;;;AFuCR;EAAa;;;AACb;EAAc;;;AAEd;EAAiB;;;AACjB;EAAiB;;;AAIjB;EGvDE;EACA;EACA;EACA;EACA;;;AHuDF;EAAwB;;;AAExB;EACE;EACA;;;AAKF;EAAc;;;AIjEd;EACE;;;AAGF;EACE;;;ACXF;EAGI;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAIA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAIA;EAAA;EAAA;EAAA;EAAA;EAKF;EACA;;;ACXF;AAAA;EAEE,e5B4RuB;E4B1RvB,a5B4RqB;E4B3RrB,a5B4RqB;;;A4BxRvB;E3B0IQ,WAhEW;;AAsFX;E2BhKR;I3BwKY,WA9DM;;;;A2BzGlB;E3ByIQ,WAhEW;;AAsFX;E2B/JR;I3BuKY,WA9DM;;;;A2BxGlB;E3BwIQ,WAhEW;;AAsFX;E2B9JR;I3BsKY,WA9DM;;;;A2BvGlB;E3BuIQ,WAhEW;;AAsFX;E2B7JR;I3BqKY,WA9DM;;;;A2BtGlB;E3B4GM,WAtCa;;;A2BrEnB;E3B2GM,WAtCa;;;A2BnEnB;E3ByGM,WAtCa;E2BjEjB,a5B8PmB;;;A4B1PrB;E3B6HQ,WAhEW;E2B3DjB,a5BiRgB;E4BhRhB,a5BwQqB;;ACxHf;E2BnJR;I3B2JY,WA9DM;;;;A2BxFlB;E3BwHQ,WAhEW;E2BtDjB,a5B6QgB;E4B5QhB,a5BmQqB;;ACxHf;E2B9IR;I3BsJY,WA9DM;;;;A2BnFlB;E3BmHQ,WAhEW;E2BjDjB,a5ByQgB;E4BxQhB,a5B8PqB;;ACxHf;E2BzIR;I3BiJY,WA9DM;;;;A2B9ElB;E3B8GQ,WAhEW;E2B5CjB,a5BqQgB;E4BpQhB,a5ByPqB;;ACxHf;E2BpIR;I3B4IY,WA9DM;;;;A2BnElB;EACE,Y5B4EO;E4B3EP,e5B2EO;E4B1EP;EACA;;;AAQF;AAAA;E3BMI;E2BHF,a5BiNmB;;;A4B9MrB;AAAA;EAEE,S5ByPa;E4BxPb,kB5BiQQ;;;A4BzPV;EC/EE;EACA;;;ADmFF;ECpFE;EACA;;;ADsFF;EACE;;AAEA;EACE,c5B2OkB;;;A4BjOtB;E3BjCI;E2BmCF;;;AAIF;EACE,e5BmBO;ECJH,WAtCa;;;A2B2BnB;EACE;E3B7CE;E2B+CF,O5B1GS;;A4B4GT;EACE;;;AEtHJ;EACE;EACA;EACA;EACA;EAEA;EACA,kB9BJM;E8BKN;EACA;ECKE;;ADFF;EACE;EACA;;AAGF;EACE;EACA;;AAEA;EACE;ECCF;EACA;;ADEA;EACE;ECUF;EACA;;ADJF;AAAA;EAEE;;;AAIJ;EAGE;EAGA;EACA,S9BowBc;;;A8BhwBhB;EACE,e9B8vBc;;;A8B3vBhB;EACE;EACA;;;AAGF;EACE;;;A5BrDA;E4B0DE;;AAGF;EACE,a9B6uBY;;;A8BruBhB;EACE;EACA;EAEA,kB9BsuBY;E8BruBZ;;AAEA;ECvEE;;;AD4EJ;EACE;EAEA,kB9B2tBY;E8B1tBZ;;AAEA;EClFE;;;AD4FJ;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAIF;EACE;EACA;EACA;EACA;EACA;EACA,S9BisByB;E+BhzBvB;;;ADmHJ;AAAA;AAAA;EAGE;EACA;;;AAGF;AAAA;ECjHI;EACA;;;ADqHJ;AAAA;ECxGI;EACA;;;ADgHF;EACE,e9ByqBgB;;ASxwBhB;EqB6FJ;IAMI;IACA;IACA;IACA;;EAEA;IAEE;IACA,c9B6pBc;I8B5pBd;IACA,a9B2pBc;;;;A8B9oBlB;EACE,e9B6oBgB;;ASxwBhB;EqBuHJ;IAQI;IACA;;EAGA;IAEE;IACA;;EAEA;IACE;IACA;;EAKA;ICzKJ;IACA;;ED2KM;AAAA;IAGE;;EAEF;AAAA;IAGE;;EAIJ;IC1KJ;IACA;;ED4KM;AAAA;IAGE;;EAEF;AAAA;IAGE;;;;AAcV;EACE,e9BkkBY;;AS1vBZ;EqBsLJ;IAMI,c9B+kBiB;I8B9kBjB,Y9B+kBe;I8B9kBf;IACA;;EAEA;IACE;IACA;;;;AAUN;EACE;;AAEA;EACE;;AAEA;EACE;ECvOF;EACA;;AD0OA;ECzPA;EACA;;AD4PA;ECtQA;EDwQE;;;AErRN;EACE;EACA;E/BiEE;E+B/DF,ahC8QiB;EgC7QjB;EACA;EACA;EACA;EDKE;EEFE,YDDJ;;ACKI;EDfN;ICgBQ;;;A/BLN;E8BGI;;;AAKJ;EACE;;;AAKJ;EACE;EACA;;;AAOF;EACE,ehCi3BqB;EgCh3BrB,chCg3BqB;E+Bv4BnB;;;ACgCF;EEjDA;EACA,kBC0Ea;;AjC5Db;EgCVI;EACA;;AAGF;EAEE;EACA;;;AFqCJ;EEjDA;EACA,kBC0Ea;;AjC5Db;EgCVI;EACA;;AAGF;EAEE;EACA;;;AFqCJ;EEjDA;EACA,kBC0Ea;;AjC5Db;EgCVI;EACA;;AAGF;EAEE;EACA;;;AFqCJ;EEjDA;EACA,kBC0Ea;;AjC5Db;EgCVI;EACA;;AAGF;EAEE;EACA;;;AFqCJ;EEjDA;EACA,kBC0Ea;;AjC5Db;EgCVI;EACA;;AAGF;EAEE;EACA;;;AFqCJ;EEjDA;EACA,kBC0Ea;;AjC5Db;EgCVI;EACA;;AAGF;EAEE;EACA;;;AFqCJ;EEjDA;EACA,kBC0Ea;;AjC5Db;EgCVI;EACA;;AAGF;EAEE;EACA;;;AFqCJ;EEjDA;EACA,kBC0Ea;;AjC5Db;EgCVI;EACA;;AAGF;EAEE;EACA;;;AFqCJ;EEjDA;EACA,kBC0Ea;;AjC5Db;EgCVI;EACA;;AAGF;EAEE;EACA;;;AFqCJ;EEjDA;EACA,kBC0Ea;;AjC5Db;EgCVI;EACA;;AAGF;EAEE;EACA;;;AEPN;EACE;EACA;EACA,QpC8da;EoC7db;EnCqHI,WAtCa;EmC5EjB,apCyQmB;EoCxQnB,apC6QiB;EoC5QjB,OpCDS;EoCET,kBpCTM;EoCUN;EACA;ELAE;EEFE,YGQJ;;AHJI;EGdN;IHeQ;;;AGMN;EACE;EACA;;AAIF;EACE;EACA;;ACtBF;EACE;EACA,kBrCRI;EqCSJ,crCycuB;EqCxcvB;EAKE,YrCyWuB;;AoCrV3B;EACE,OpC9BO;EoCgCP;;AAQF;EAEE,kBpC9CO;EoCgDP;;;AAQF;AAAA;AAAA;AAAA;EACE;;;AAKF;EAME,OpC/DO;EoCgEP,kBpCvEI;;;AoC4ER;AAAA;EAEE;EACA;;;AAUF;EACE;EACA;EACA;EnC3BE;EmC6BF,apCsLiB;;;AoCnLnB;EACE;EACA;EnCqBI,WAtCa;EmCmBjB,apC2He;;;AoCxHjB;EACE;EACA;EnCcI,WAtCa;EmC0BjB,apCqHe;;;AoC5GjB;EACE;EACA;EACA;EACA;EnCDI,WAtCa;EmCyCjB,apCyJiB;EoCxJjB,OpCnHS;EoCoHT;EACA;EACA;;AAEA;EAEE;EACA;;;AAYJ;EACE,QpCgVgB;EoC/UhB;EnC1BI,WAtCa;EmCkEjB,apC6Ee;E+BtNb;;;AK6IJ;EACE,QpCyUgB;EoCxUhB;EnClCI,WAtCa;EmC0EjB,apCoEe;E+BrNb;;;AKuJF;EAEE;;;AAIJ;EACE;;;AAQF;EACE,epC8TyB;;;AoC3T3B;EACE;EACA,YpC+SqB;;;AoCvSvB;EACE;EACA;EACA;EACA;;AAEA;AAAA;EAEE;EACA;;;AASJ;EACE;EACA;EACA,cpCoRwB;;;AoCjR1B;EACE;EACA,YpCgR0B;EoC/Q1B;;AAGA;EAEE,OpCzNO;;;AoC6NX;EACE;;;AAGF;EACE;EACA;EACA;EACA,cpCiQ2B;;AoC9P3B;EACE;EACA;EACA,cpC4P+B;EoC3P/B;;;AC7MF;EACE;EACA;EACA,YrC8bmB;ECranB;EoCvBA,ODqNqC;;;AClNvC;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EpCmEE,WAtCa;EoC3Bf,arC6Ne;EqC5Nf;EACA;EN9CA;;AMmDA;EAEE;;;AAKF;AAAA;AAAA;AAAA;EAEE;;;AA9CF;EAoDE,cDkLmC;EC/KjC,erC4Ya;EqC3Yb;EACA;EACA;EACA;;AAGF;EACE,cDuKiC;ECtKjC;;;AAhEJ;EAyEI,erC0Xa;EqCzXb;;;AA1EJ;EAiFE,cDqJmC;EClJjC,erCgdoC;EqC/cpC;;AAGF;EACE,cD6IiC;EC5IjC;;;AAOF;EACE,ODoIiC;;ACjInC;AAAA;AAAA;EAEE;;;AAOF;EACE,ODuHiC;;ACrHjC;EACE,cDoH+B;;AC/GjC;EACE;EClJN,kBDmJ2B;;AAKvB;EACE;;AAGF;EACE,cAVqB;;;AAmBzB;EACE,cApBuB;;AAwBvB;EACE,cAzBqB;EA0BrB;;;AAvIR;EACE;EACA;EACA,YrC8bmB;ECranB;EoCvBA,ODqNqC;;;AClNvC;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EpCmEE,WAtCa;EoC3Bf,arC6Ne;EqC5Nf;EACA;EN9CA;;AMmDA;EAEE;;;AAKF;AAAA;AAAA;AAAA;EAEE;;;AA9CF;EAoDE,cDkLmC;EC/KjC,erC4Ya;EqC3Yb;EACA;EACA;EACA;;AAGF;EACE,cDuKiC;ECtKjC;;;AAhEJ;EAyEI,erC0Xa;EqCzXb;;;AA1EJ;EAiFE,cDqJmC;EClJjC,erCgdoC;EqC/cpC;;AAGF;EACE,cD6IiC;EC5IjC;;;AAOF;EACE,ODoIiC;;ACjInC;AAAA;AAAA;EAEE;;;AAOF;EACE,ODuHiC;;ACrHjC;EACE,cDoH+B;;AC/GjC;EACE;EClJN,kBDmJ2B;;AAKvB;EACE;;AAGF;EACE,cAVqB;;;AAmBzB;EACE,cApBuB;;AAwBvB;EACE,cAzBqB;EA0BrB;;;AD+FV;EACE;EACA;EACA;;AAKA;EACE;;A3B/NA;E2BoOA;IACE;IACA;IACA;IACA;;EAIF;IACE;IACA;IACA;IACA;IACA;;EAIF;IACE;IACA;IACA;;EAIF;IACE;;EAGF;AAAA;IAEE;;EAKF;IACE;IACA;IACA;IACA;IACA;;EAEF;IACE;IACA;IACA;IACA,cpCmKsB;IoClKtB;;EAGF;IACE;IACA;;EAEF;IACE;;;;AG9UN;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA,cvC6ewB;;;AuC1e1B;EACE;EACA;EACA;EACA,OvCye8B;EuCxe9B;EACA;;AAEA;EACE,OvCzBI;EuC0BJ,cvCuNkB;EsClPlB,kBtCkPkB;;AuClNpB;EAKI,YvCoVuB;;AuChV3B;EACE,cvCyauB;;AuCtazB;EACE,OvC7CI;EuC8CJ,kBvCseiC;EuCrejC,cvCqeiC;;AuC9djC;EACE,OvCjDK;;AuCmDL;EACE,kBvCxDG;;;AuCkEX;EACE;EACA;EAEA;;AAIA;EACE;EACA;EACA;EACA;EACA,OvC4a4B;EuC3a5B,QvC2a4B;EuC1a5B;EACA;EACA,kBvCrFI;EuCsFJ;;AAKF;EACE;EACA;EACA;EACA;EACA,OvC6Z4B;EuC5Z5B,QvC4Z4B;EuC3Z5B;EACA;;;AAUF;ERlGE;;AQuGA;EACE;;AAKF;EACE,cvCwHgB;EsClPlB,kBtCkPkB;;AuCpHlB;EACE;;AAKF;EDpIA,kBtC6gB2C;;AuCtY3C;EDvIA,kBtC6gB2C;;;AuC3X7C;EAEE,evC8YmC;;AuC1YnC;EACE;;AAKF;ED9JA,kBtC6gB2C;;;AuCpW/C;EACE;;AAGE;EACE;EACA,OvCsXgB;EuCrXhB;EAEA,evCoXkC;;AuCjXpC;EACE;EACA;EACA,OvC+WyB;EuC9WzB,QvC8WyB;EuC7WzB,kBvCpLK;EuCsLL,evC0WkC;EiC5hBlC,YMmLA;;AN/KA;EMuKF;INtKI;;;AMmLJ;EACE,kBvClME;EuCmMF;;AAKF;EDzMA,kBtC6gB2C;;;AuCvT/C;EACE;EACA;EACA,QvCwQa;EuCvQb;EtCjGI,WAtCa;EsC0IjB,avCmDmB;EuClDnB,avCuDiB;EuCtDjB,OvCvNS;EuCwNT;EACA;EACA;ERtNE;EQyNF;;AAEA;EACE,cvC2OuB;EuC1OvB;EAKE,YvC8V2B;;AuC3V7B;EAME,OvC/OK;EuCgPL,kBvCvPE;;AuC2PN;EAEE;EACA,evCmHkB;EuClHlB;;AAGF;EACE,OvC7PO;EuC8PP,kBvClQO;;AuCsQT;EACE;;AAIF;EACE;EACA;;;AAIJ;EACE,QvC6MgB;EuC5MhB,avCqGuB;EuCpGvB,gBvCoGuB;EuCnGvB,cvCoGuB;ECnQnB,WAtCa;;;AsCyMnB;EACE,QvCsMgB;EuCrMhB,avCkGuB;EuCjGvB,gBvCiGuB;EuChGvB,cvCiGuB;ECxQnB,WAtCa;;;AsCsNnB;EACE;EACA;EACA;EACA,QvCoLa;EuCnLb;;;AAGF;EACE;EACA;EACA;EACA,QvC4Ka;EuC3Kb;EACA;EACA;;AAEA;EACE,cvCwJuB;EuCvJvB,YvC6DyB;;AuCzD3B;EAEE,kBvC/TO;;AuCmUP;EACE,SvCsTa;;AuClTjB;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA,QvC2Ia;EuC1Ib;EACA;EAEA,avC1EmB;EuC2EnB,avCtEiB;EuCuEjB,OvCpVS;EuCqVT,kBvC5VM;EuC6VN;ERlVE;;AQsVF;EACE;EACA;EACA;EACA;EACA;EACA;EACA,QvCoHiB;EuCnHjB;EACA,avCtFe;EuCuFf,OvCpWO;EuCqWP;ED7WA,kBtCGO;EuC4WP;ERnWA;;;AQ8WJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIA;EAA0B,YvC+NQ;;AuC9NlC;EAA0B,YvC8NQ;;AuC7NlC;EAA0B,YvC6NQ;;AuC1NpC;EACE;;AAGF;EACE,OvC+MuB;EuC9MvB,QvC8MuB;EuC7MvB;EDlZA,kBtCkPkB;EuCkKlB,QvC8MwB;E+BtlBxB;EEFE,YM6YF;EACA;;AN1YE;EMiYJ;INhYM;;;AM2YJ;ED1ZA,kBtCumB2B;;AuCxM7B;EACE,OvCwLuB;EuCvLvB,QvCwLwB;EuCvLxB;EACA,QvCuLwB;EuCtLxB,kBvChaO;EuCiaP;ERzZA;;AQ8ZF;EACE,OvCoLuB;EuCnLvB,QvCmLuB;EsC/lBvB,kBtCkPkB;EuC4LlB,QvCoLwB;E+BtlBxB;EEFE,YMuaF;EACA;;ANpaE;EM4ZJ;IN3ZM;;;AMqaJ;EDpbA,kBtCumB2B;;AuC9K7B;EACE,OvC8JuB;EuC7JvB,QvC8JwB;EuC7JxB;EACA,QvC6JwB;EuC5JxB,kBvC1bO;EuC2bP;ERnbA;;AQwbF;EACE,OvC0JuB;EuCzJvB,QvCyJuB;EuCxJvB;EACA,cvCjFoB;EuCkFpB,avClFoB;EsCvXpB,kBtCkPkB;EuCyNlB,QvCuJwB;E+BtlBxB;EEFE,YMocF;EACA;;ANjcE;EMsbJ;INrbM;;;AMkcJ;EDjdA,kBtCumB2B;;AuCjJ7B;EACE,OvCiIuB;EuChIvB,QvCiIwB;EuChIxB;EACA,QvCgIwB;EuC/HxB;EACA;EACA;;AAIF;EACE,kBvC9dO;E+BQP;;AQ0dF;EACE;EACA,kBvCpeO;E+BQP;;AQieA;EACE,kBvCxeK;;AuC2eP;EACE;;AAGF;EACE,kBvChfK;;AuCmfP;EACE;;AAGF;EACE,kBvCxfK;;;AuC6fX;AAAA;AAAA;ENzfM,YM4fJ;;ANxfI;EMqfN;AAAA;AAAA;INpfQ;;;;AOjBR;EACE;EACA;EAGA;EACA;ETQE;;;ASEJ;EACE;EACA,OxCRS;EwCST;;AtCPA;EsCWE;EACA,OxCdO;EwCeP;EACA,kBxCtBO;;AwCyBT;EACE,OxClBO;EwCmBP,kBxC1BO;;;AwCmCX;EACE;EACA;EACA;EAGA,kBxC3CM;EwC4CN;;AAEA;ET1BE;EACA;;AS6BF;EThBE;EACA;;ASmBF;EAEE,OxClDO;EwCmDP;EACA,kBxC1DI;;AwC8DN;EACE;EACA,OxChEI;EwCiEJ,kBxCgLkB;EwC/KlB,cxC+KkB;;AwC5KpB;EACE;;AAEA;EACE;EACA,kBxCyJS;;;AwC3IX;EACE;;AAGE;ET1BJ;EAZA;;AS2CI;ET3CJ;EAYA;;ASoCI;EACE;;AAGF;EACE,kBxCwHK;EwCvHL;;AAEA;EACE;EACA,mBxCmHG;;;AS9KX;E+BmCA;IACE;;EAGE;IT1BJ;IAZA;;ES2CI;IT3CJ;IAYA;;ESoCI;IACE;;EAGF;IACE,kBxCwHK;IwCvHL;;EAEA;IACE;IACA,mBxCmHG;;;AS9KX;E+BmCA;IACE;;EAGE;IT1BJ;IAZA;;ES2CI;IT3CJ;IAYA;;ESoCI;IACE;;EAGF;IACE,kBxCwHK;IwCvHL;;EAEA;IACE;IACA,mBxCmHG;;;AS9KX;E+BmCA;IACE;;EAGE;IT1BJ;IAZA;;ES2CI;IT3CJ;IAYA;;ESoCI;IACE;;EAGF;IACE,kBxCwHK;IwCvHL;;EAEA;IACE;IACA,mBxCmHG;;;AS9KX;E+BmCA;IACE;;EAGE;IT1BJ;IAZA;;ES2CI;IT3CJ;IAYA;;ESoCI;IACE;;EAGF;IACE,kBxCwHK;IwCvHL;;EAEA;IACE;IACA,mBxCmHG;;;AwCrGf;ETnHI;;ASsHF;EACE;;AAEA;EACE;;;ACzIJ;EACE,ODoJsE;ECnJtE,kBDmJuC;;AtCxIzC;EuCPM,OD+IkE;EC9IlE;;AAGF;EACE,OzCPA;EyCQA,kBDyIkE;ECxIlE,cDwIkE;;;ACrJxE;EACE,ODoJsE;ECnJtE,kBDmJuC;;AtCxIzC;EuCPM,OD+IkE;EC9IlE;;AAGF;EACE,OzCPA;EyCQA,kBDyIkE;ECxIlE,cDwIkE;;;ACrJxE;EACE,ODoJsE;ECnJtE,kBDmJuC;;AtCxIzC;EuCPM,OD+IkE;EC9IlE;;AAGF;EACE,OzCPA;EyCQA,kBDyIkE;ECxIlE,cDwIkE;;;ACrJxE;EACE,ODoJsE;ECnJtE,kBDmJuC;;AtCxIzC;EuCPM,OD+IkE;EC9IlE;;AAGF;EACE,OzCPA;EyCQA,kBDyIkE;ECxIlE,cDwIkE;;;ACrJxE;EACE,ODoJsE;ECnJtE,kBDmJuC;;AtCxIzC;EuCPM,OD+IkE;EC9IlE;;AAGF;EACE,OzCPA;EyCQA,kBDyIkE;ECxIlE,cDwIkE;;;ACrJxE;EACE,ODoJsE;ECnJtE,kBDmJuC;;AtCxIzC;EuCPM,OD+IkE;EC9IlE;;AAGF;EACE,OzCPA;EyCQA,kBDyIkE;ECxIlE,cDwIkE;;;ACrJxE;EACE,ODoJsE;ECnJtE,kBDmJuC;;AtCxIzC;EuCPM,OD+IkE;EC9IlE;;AAGF;EACE,OzCPA;EyCQA,kBDyIkE;ECxIlE,cDwIkE;;;ACrJxE;EACE,ODoJsE;ECnJtE,kBDmJuC;;AtCxIzC;EuCPM,OD+IkE;EC9IlE;;AAGF;EACE,OzCPA;EyCQA,kBDyIkE;ECxIlE,cDwIkE;;;ACrJxE;EACE,ODoJsE;ECnJtE,kBDmJuC;;AtCxIzC;EuCPM,OD+IkE;EC9IlE;;AAGF;EACE,OzCPA;EyCQA,kBDyIkE;ECxIlE,cDwIkE;;;ACrJxE;EACE,ODoJsE;ECnJtE,kBDmJuC;;AtCxIzC;EuCPM,OD+IkE;EC9IlE;;AAGF;EACE,OzCPA;EyCQA,kBDyIkE;ECxIlE,cDwIkE;;;AEpJ1E;EACE;EACA;EACA,e1Cu8BoB;E0Ct8BpB;EXUE;;;AWLJ;EAEE;;;AAIF;EACE,a1CmQiB;;;A0C3PnB;EACE;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;;;AAUF;EC/CA,ODgDqH;EJ3CnH,kBI2CuB;EC9CzB,cD8CqE;;AC5CrE;EACE;;AAGF;EACE;;;ADsCF;EC/CA,ODgDqH;EJ3CnH,kBI2CuB;EC9CzB,cD8CqE;;AC5CrE;EACE;;AAGF;EACE;;;ADsCF;EC/CA,ODgDqH;EJ3CnH,kBI2CuB;EC9CzB,cD8CqE;;AC5CrE;EACE;;AAGF;EACE;;;ADsCF;EC/CA,ODgDqH;EJ3CnH,kBI2CuB;EC9CzB,cD8CqE;;AC5CrE;EACE;;AAGF;EACE;;;ADsCF;EC/CA,ODgDqH;EJ3CnH,kBI2CuB;EC9CzB,cD8CqE;;AC5CrE;EACE;;AAGF;EACE;;;ADsCF;EC/CA,ODgDqH;EJ3CnH,kBI2CuB;EC9CzB,cD8CqE;;AC5CrE;EACE;;AAGF;EACE;;;ADsCF;EC/CA,ODgDqH;EJ3CnH,kBI2CuB;EC9CzB,cD8CqE;;AC5CrE;EACE;;AAGF;EACE;;;ADsCF;EC/CA,ODgDqH;EJ3CnH,kBI2CuB;EC9CzB,cD8CqE;;AC5CrE;EACE;;AAGF;EACE;;;ADsCF;EC/CA,ODgDqH;EJ3CnH,kBI2CuB;EC9CzB,cD8CqE;;AC5CrE;EACE;;AAGF;EACE;;;ADsCF;EC/CA,ODgDqH;EJ3CnH,kBI2CuB;EC9CzB,cD8CqE;;AC5CrE;EACE;;AAGF;EACE;;;ACJF;AAAA;AAAA;AAAA;AAAA;AAAA;ECDA;EACA;EACA;EACA;EACA;;;ApCmDE;EmCzCE;IACE,W5C8Le;;;AStJnB;EmCzCE;IACE,W5C8Le;;;AStJnB;EmCzCE;IACE,W5C8Le;;;AStJnB;EmCzCE;IACE,W5C8Le;;;A4ClKrB;ECnCA;EACA;EACA;EACA;;;ADsCA;EACE;EACA;;AAEA;AAAA;EAEE;EACA;;;AEtDJ;AAAA;AAAA;AAAA;AAAA;AAAA;EACE;EACA;EACA;EACA;;;AAsBE;EACE;EACA;EACA;;;AD4BN;EACE;EACA;;;AAFF;EACE;EACA;;;AAFF;EACE;EACA;;;AAFF;EACE;EACA;;;AAFF;EACE;EACA;;;AAFF;EACE;EACA;;;ACnBE;EDCJ;EACA;EACA;;;ACGQ;EDbR;EAIA;;;ACSQ;EDbR;EAIA;;;ACSQ;EDbR;EAIA;;;ACSQ;EDbR;EAIA;;;ACSQ;EDbR;EAIA;;;ACSQ;EDbR;EAIA;;;ACSQ;EDbR;EAIA;;;ACSQ;EDbR;EAIA;;;ACSQ;EDbR;EAIA;;;ACSQ;EDbR;EAIA;;;ACSQ;EDbR;EAIA;;;ACSQ;EDbR;EAIA;;;ACeI;EAAwB;;;AAExB;EAAuB;;;AAGrB;EAAwB,OADb;;;AACX;EAAwB,OADb;;;AACX;EAAwB,OADb;;;AACX;EAAwB,OADb;;;AACX;EAAwB,OADb;;;AACX;EAAwB,OADb;;;AACX;EAAwB,OADb;;;AACX;EAAwB,OADb;;;AACX;EAAwB,OADb;;;AACX;EAAwB,OADb;;;AACX;EAAwB,OADb;;;AACX;EAAwB,OADb;;;AACX;EAAwB,OADb;;;AAQP;EDhBV;;;ACgBU;EDhBV;;;ACgBU;EDhBV;;;ACgBU;EDhBV;;;ACgBU;EDhBV;;;ACgBU;EDhBV;;;ACgBU;EDhBV;;;ACgBU;EDhBV;;;ACgBU;EDhBV;;;ACgBU;EDhBV;;;ACgBU;EDhBV;;;ApCKE;EqC3BE;IACE;IACA;IACA;;;ED4BN;IACE;IACA;;;EAFF;IACE;IACA;;;EAFF;IACE;IACA;;;EAFF;IACE;IACA;;;EAFF;IACE;IACA;;;EAFF;IACE;IACA;;;ECnBE;IDCJ;IACA;IACA;;;ECGQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECeI;IAAwB;;;EAExB;IAAuB;;;EAGrB;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EAQP;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ApCKE;EqC3BE;IACE;IACA;IACA;;;ED4BN;IACE;IACA;;;EAFF;IACE;IACA;;;EAFF;IACE;IACA;;;EAFF;IACE;IACA;;;EAFF;IACE;IACA;;;EAFF;IACE;IACA;;;ECnBE;IDCJ;IACA;IACA;;;ECGQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECeI;IAAwB;;;EAExB;IAAuB;;;EAGrB;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EAQP;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ApCKE;EqC3BE;IACE;IACA;IACA;;;ED4BN;IACE;IACA;;;EAFF;IACE;IACA;;;EAFF;IACE;IACA;;;EAFF;IACE;IACA;;;EAFF;IACE;IACA;;;EAFF;IACE;IACA;;;ECnBE;IDCJ;IACA;IACA;;;ECGQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECeI;IAAwB;;;EAExB;IAAuB;;;EAGrB;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EAQP;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ApCKE;EqC3BE;IACE;IACA;IACA;;;ED4BN;IACE;IACA;;;EAFF;IACE;IACA;;;EAFF;IACE;IACA;;;EAFF;IACE;IACA;;;EAFF;IACE;IACA;;;EAFF;IACE;IACA;;;ECnBE;IDCJ;IACA;IACA;;;ECGQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECSQ;IDbR;IAIA;;;ECeI;IAAwB;;;EAExB;IAAuB;;;EAGrB;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EACX;IAAwB,OADb;;;EAQP;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;ECgBU;IDhBV;;;AEvDF;EdgBM,YcfJ;;AdmBI;EcpBN;IdqBQ;;;AclBN;EACE;;;AAKF;EACE;;;AAIJ;EACE;EACA;EACA;EdDI,YcEJ;;AdEI;EcNN;IdOQ;;;;AefR;EACE;EAEA,ahD6QmB;EgD5QnB,OhDMS;EgDLT;EAGA;EACA;EACA;EACA;ECuFA;EhDuBI,WAtCa;EgDiBjB,ajDiLiB;E+BzQf;EEFE,YeGJ;;AfCI;EedN;IfeQ;;;A/BTN;E8CUE,OhDNO;EgDOP;;AAGF;EAEE;EACA,YhDkWyB;;AgD9V3B;EAEE,ShDqYmB;;AgDjYrB;EACE;;AAcJ;AAAA;EAEE;;;AASA;EC3DA;EXAE,kBHsEW;EcpEb,cdoEa;;AjChEb;E+CAE;EXNA,kBWD2D;EAS3D,cATqG;;AAYvG;EAEE;EXbA,kBWD2D;EAgB3D,cAhBqG;EAqBnG;;AAKJ;EAEE;EACA,kBd0CW;EczCX,cdyCW;;AclCb;EAGE;EACA,kBAzC+I;EA6C/I,cA7CyL;;AA+CzL;EAKI;;;ADQN;EC3DA;EXAE,kBHsEW;EcpEb,cdoEa;;AjChEb;E+CAE;EXNA,kBWD2D;EAS3D,cATqG;;AAYvG;EAEE;EXbA,kBWD2D;EAgB3D,cAhBqG;EAqBnG;;AAKJ;EAEE;EACA,kBd0CW;EczCX,cdyCW;;AclCb;EAGE;EACA,kBAzC+I;EA6C/I,cA7CyL;;AA+CzL;EAKI;;;ADQN;EC3DA;EXAE,kBHsEW;EcpEb,cdoEa;;AjChEb;E+CAE;EXNA,kBWD2D;EAS3D,cATqG;;AAYvG;EAEE;EXbA,kBWD2D;EAgB3D,cAhBqG;EAqBnG;;AAKJ;EAEE;EACA,kBd0CW;EczCX,cdyCW;;AclCb;EAGE;EACA,kBAzC+I;EA6C/I,cA7CyL;;AA+CzL;EAKI;;;ADQN;EC3DA;EXAE,kBHsEW;EcpEb,cdoEa;;AjChEb;E+CAE;EXNA,kBWD2D;EAS3D,cATqG;;AAYvG;EAEE;EXbA,kBWD2D;EAgB3D,cAhBqG;EAqBnG;;AAKJ;EAEE;EACA,kBd0CW;EczCX,cdyCW;;AclCb;EAGE;EACA,kBAzC+I;EA6C/I,cA7CyL;;AA+CzL;EAKI;;;ADQN;EC3DA;EXAE,kBHsEW;EcpEb,cdoEa;;AjChEb;E+CAE;EXNA,kBWD2D;EAS3D,cATqG;;AAYvG;EAEE;EXbA,kBWD2D;EAgB3D,cAhBqG;EAqBnG;;AAKJ;EAEE;EACA,kBd0CW;EczCX,cdyCW;;AclCb;EAGE;EACA,kBAzC+I;EA6C/I,cA7CyL;;AA+CzL;EAKI;;;ADQN;EC3DA;EXAE,kBHsEW;EcpEb,cdoEa;;AjChEb;E+CAE;EXNA,kBWD2D;EAS3D,cATqG;;AAYvG;EAEE;EXbA,kBWD2D;EAgB3D,cAhBqG;EAqBnG;;AAKJ;EAEE;EACA,kBd0CW;EczCX,cdyCW;;AclCb;EAGE;EACA,kBAzC+I;EA6C/I,cA7CyL;;AA+CzL;EAKI;;;ADQN;EC3DA;EXAE,kBHsEW;EcpEb,cdoEa;;AjChEb;E+CAE;EXNA,kBWD2D;EAS3D,cATqG;;AAYvG;EAEE;EXbA,kBWD2D;EAgB3D,cAhBqG;EAqBnG;;AAKJ;EAEE;EACA,kBd0CW;EczCX,cdyCW;;AclCb;EAGE;EACA,kBAzC+I;EA6C/I,cA7CyL;;AA+CzL;EAKI;;;ADQN;EC3DA;EXAE,kBHsEW;EcpEb,cdoEa;;AjChEb;E+CAE;EXNA,kBWD2D;EAS3D,cATqG;;AAYvG;EAEE;EXbA,kBWD2D;EAgB3D,cAhBqG;EAqBnG;;AAKJ;EAEE;EACA,kBd0CW;EczCX,cdyCW;;AclCb;EAGE;EACA,kBAzC+I;EA6C/I,cA7CyL;;AA+CzL;EAKI;;;ADQN;EC3DA;EXAE,kBHsEW;EcpEb,cdoEa;;AjChEb;E+CAE;EXNA,kBWD2D;EAS3D,cATqG;;AAYvG;EAEE;EXbA,kBWD2D;EAgB3D,cAhBqG;EAqBnG;;AAKJ;EAEE;EACA,kBd0CW;EczCX,cdyCW;;AclCb;EAGE;EACA,kBAzC+I;EA6C/I,cA7CyL;;AA+CzL;EAKI;;;ADQN;EC3DA;EXAE,kBHsEW;EcpEb,cdoEa;;AjChEb;E+CAE;EXNA,kBWD2D;EAS3D,cATqG;;AAYvG;EAEE;EXbA,kBWD2D;EAgB3D,cAhBqG;EAqBnG;;AAKJ;EAEE;EACA,kBd0CW;EczCX,cdyCW;;AclCb;EAGE;EACA,kBAzC+I;EA6C/I,cA7CyL;;AA+CzL;EAKI;;;ADcN;ECPA,OdYa;EcXb,cdWa;;AjChEb;E+CwDE,OALgD;EAMhD,kBdOW;EcNX,cdMW;;AcHb;EAEE;;AAGF;EAEE,OdJW;EcKX;;AAGF;EAGE;EACA,kBdZW;EcaX,cdbW;;AceX;EAKI;;;ADzBN;ECPA,OdYa;EcXb,cdWa;;AjChEb;E+CwDE,OALgD;EAMhD,kBdOW;EcNX,cdMW;;AcHb;EAEE;;AAGF;EAEE,OdJW;EcKX;;AAGF;EAGE;EACA,kBdZW;EcaX,cdbW;;AceX;EAKI;;;ADzBN;ECPA,OdYa;EcXb,cdWa;;AjChEb;E+CwDE,OALgD;EAMhD,kBdOW;EcNX,cdMW;;AcHb;EAEE;;AAGF;EAEE,OdJW;EcKX;;AAGF;EAGE;EACA,kBdZW;EcaX,cdbW;;AceX;EAKI;;;ADzBN;ECPA,OdYa;EcXb,cdWa;;AjChEb;E+CwDE,OALgD;EAMhD,kBdOW;EcNX,cdMW;;AcHb;EAEE;;AAGF;EAEE,OdJW;EcKX;;AAGF;EAGE;EACA,kBdZW;EcaX,cdbW;;AceX;EAKI;;;ADzBN;ECPA,OdYa;EcXb,cdWa;;AjChEb;E+CwDE,OALgD;EAMhD,kBdOW;EcNX,cdMW;;AcHb;EAEE;;AAGF;EAEE,OdJW;EcKX;;AAGF;EAGE;EACA,kBdZW;EcaX,cdbW;;AceX;EAKI;;;ADzBN;ECPA,OdYa;EcXb,cdWa;;AjChEb;E+CwDE,OALgD;EAMhD,kBdOW;EcNX,cdMW;;AcHb;EAEE;;AAGF;EAEE,OdJW;EcKX;;AAGF;EAGE;EACA,kBdZW;EcaX,cdbW;;AceX;EAKI;;;ADzBN;ECPA,OdYa;EcXb,cdWa;;AjChEb;E+CwDE,OALgD;EAMhD,kBdOW;EcNX,cdMW;;AcHb;EAEE;;AAGF;EAEE,OdJW;EcKX;;AAGF;EAGE;EACA,kBdZW;EcaX,cdbW;;AceX;EAKI;;;ADzBN;ECPA,OdYa;EcXb,cdWa;;AjChEb;E+CwDE,OALgD;EAMhD,kBdOW;EcNX,cdMW;;AcHb;EAEE;;AAGF;EAEE,OdJW;EcKX;;AAGF;EAGE;EACA,kBdZW;EcaX,cdbW;;AceX;EAKI;;;ADzBN;ECPA,OdYa;EcXb,cdWa;;AjChEb;E+CwDE,OALgD;EAMhD,kBdOW;EcNX,cdMW;;AcHb;EAEE;;AAGF;EAEE,OdJW;EcKX;;AAGF;EAGE;EACA,kBdZW;EcaX,cdbW;;AceX;EAKI;;;ADzBN;ECPA,OdYa;EcXb,cdWa;;AjChEb;E+CwDE,OALgD;EAMhD,kBdOW;EcNX,cdMW;;AcHb;EAEE;;AAGF;EAEE,OdJW;EcKX;;AAGF;EAGE;EACA,kBdZW;EcaX,cdbW;;AceX;EAKI;;;ADdR;EACE,ahDmMmB;EgDlMnB,OhD6FW;EgD5FX,iBhD6FgB;;AEtKhB;E8C4EE,OhD2Fe;EgD1Ff,iBhD2FoB;;AgDxFtB;EAEE,iBhDsFoB;;AgDnFtB;EAEE,OhDtFO;EgDuFP;;;AAWJ;ECPE;EhDuBI,WAtCa;EgDiBjB,ajD6He;E+BrNb;;;AiBiGJ;ECXE;EhDuBI,WAtCa;EgDiBjB,ajD8He;E+BtNb;;;AiB0GJ;EACE;EACA;;AAGA;EACE,YhD2SkB;;;AgDnSpB;AAAA;AAAA;EACE;;;AEvIJ;EACE;IAAK;;;AAGP;EACE;EACA,OlD6iCc;EkD5iCd,QlD4iCc;EkD3iCd;EACA;EACA;EAEA;EACA;;;AAGF;EACE,OlDsiCiB;EkDriCjB,QlDqiCiB;EkDpiCjB,clDsiCwB;;;AkD/hC1B;EACE;IACE;;EAEF;IACE;IACA;;;AAIJ;EACE;EACA,OlD6gCc;EkD5gCd,QlD4gCc;EkD3gCd;EACA;EAEA;EACA;EACA;;;AAGF;EACE,OlDsgCiB;EkDrgCjB,QlDqgCiB;;;AkDjgCjB;EACE;AAAA;IAEE;;;ACxDN;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;;AjDCA;EiDGE;;AAIF;EACE,OnDXO;EmDYP;EACA;;;AAQJ;EACE;;AAEA;EACE;EACA;EpBZA;EACA;;A7BZF;EiD2BI,cnD6oB6B;;AmD1oB/B;EACE,OnDlCK;EmDmCL;EACA;;AAIJ;AAAA;EAEE,OnDzCO;EmD0CP,kBnDjDI;EmDkDJ,cnDkoBgC;;AmD/nBlC;EAEE;EpBnCA;EACA;;;AoB8CF;EpBxDE;;AoB4DF;AAAA;EAEE,OnDzEI;EmD0EJ,kBnDuKkB;;;AmD7JpB;AAAA;EAEE;EACA;;;AAKF;AAAA;EAEE;EACA;EACA;;;AAUF;EACE;;AAEF;EACE;;;ACpGJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAIA;AAAA;AAAA;AAAA;AAAA;AAAA;EACE;EACA;EACA;EACA;;AAoBJ;EACE;EACA,apD0pBuB;EoDzpBvB,gBpDypBuB;EoDxpBvB,cpD4EO;ECJH,WAtCa;EmDhCjB;EACA;;AlD1CA;EkD6CE;;;AASJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AASJ;EACE;EACA,apDklBmB;EoDjlBnB,gBpDilBmB;;;AoDrkBrB;EACE;EACA;EAGA;;;AAIF;EACE;EnDSI,WAtCa;EmD+BjB;EACA;EACA;ErBxGE;;A7BFF;EkD8GE;;;AAMJ;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE,YpDwkB6B;EoDvkB7B;;;A3CtEE;E2CkFI;AAAA;AAAA;AAAA;AAAA;AAAA;IACE;IACA;;;A3CjGN;E2C6FA;IAoBI;IACA;;EAEA;IACE;;EAEA;IACE;;EAGF;IACE,epDihBgB;IoDhhBhB,cpDghBgB;;EoD3gBpB;AAAA;AAAA;AAAA;AAAA;AAAA;IACE;;EAcF;IACE;;EAGF;IACE;IAGA;;EAGF;IACE;;;A3ChJN;E2CkFI;AAAA;AAAA;AAAA;AAAA;AAAA;IACE;IACA;;;A3CjGN;E2C6FA;IAoBI;IACA;;EAEA;IACE;;EAEA;IACE;;EAGF;IACE,epDihBgB;IoDhhBhB,cpDghBgB;;EoD3gBpB;AAAA;AAAA;AAAA;AAAA;AAAA;IACE;;EAcF;IACE;;EAGF;IACE;IAGA;;EAGF;IACE;;;A3ChJN;E2CkFI;AAAA;AAAA;AAAA;AAAA;AAAA;IACE;IACA;;;A3CjGN;E2C6FA;IAoBI;IACA;;EAEA;IACE;;EAEA;IACE;;EAGF;IACE,epDihBgB;IoDhhBhB,cpDghBgB;;EoD3gBpB;AAAA;AAAA;AAAA;AAAA;AAAA;IACE;;EAcF;IACE;;EAGF;IACE;IAGA;;EAGF;IACE;;;A3ChJN;E2CkFI;AAAA;AAAA;AAAA;AAAA;AAAA;IACE;IACA;;;A3CjGN;E2C6FA;IAoBI;IACA;;EAEA;IACE;;EAEA;IACE;;EAGF;IACE,epDihBgB;IoDhhBhB,cpDghBgB;;EoD3gBpB;AAAA;AAAA;AAAA;AAAA;AAAA;IACE;;EAcF;IACE;;EAGF;IACE;IAGA;;EAGF;IACE;;;AAhEN;EAoBI;EACA;;AAnBA;AAAA;AAAA;AAAA;AAAA;AAAA;EACE;EACA;;AAmBF;EACE;;AAEA;EACE;;AAGF;EACE,epDihBgB;EoDhhBhB,cpDghBgB;;AoD3gBpB;AAAA;AAAA;AAAA;AAAA;AAAA;EACE;;AAcF;EACE;;AAGF;EACE;EAGA;;AAGF;EACE;;;AAcR;EACE,OpDyfwB;;AEjtB1B;EkD2NI,OpDsfsB;;AoDjfxB;EACE,OpD8ee;;AE/sBnB;EkDoOM,OpD4emB;;AoDzerB;EACE,OpD0esB;;AoDte1B;AAAA;AAAA;AAAA;EAIE,OpDiesB;;AoD7d1B;EACE,OpD0diB;EoDzdjB,cpD8dgC;;AoD3dlC;EACE;;AAGF;EACE,OpDidiB;;AoDhdjB;EACE,OpDidsB;;AEjtB1B;EkDmQM,OpD8coB;;;AoDtc1B;EACE,OpDrRI;;AESN;EkD+QI,OpDxRE;;AoD6RJ;EACE,OpDmbc;;AExsBlB;EkDwRM,OpDibkB;;AoD9apB;EACE,OpD+aqB;;AoD3azB;AAAA;AAAA;AAAA;EAIE,OpD7SE;;AoDiTN;EACE,OpD+ZgB;EoD9ZhB,cpDma+B;;AoDhajC;EACE;;AAGF;EACE,OpDsZgB;;AoDrZhB;EACE,OpD7TE;;AESN;EkDuTM,OpDhUA;;;AqDAR;ECIE;EAGA;;;ADDF;EACE,SrDg/BkB;EqD/+BlB,kBrDRM;EqDSN;EtBEE;EuBPF;EAGA;;;ADcF;EAEE;;;AAGF;EACE;EACA;;;AAGF;EpDkCI;EoDhCF,OrD3BS;;;AuDZX;EACE;EACA,SvD0qBe;EuDzqBf;EACA,QvDg1Be;EwDp1Bf,axDyQuB;EwDvQvB;EACA,axDkRmB;EwDjRnB,axDsRiB;EwDrRjB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EvDgHI,WAtCa;EsD9EjB;EACA;;AAEA;EAAS,SvDo0BO;;AuDl0BhB;EACE;EACA;EACA,OvDo0BkB;EuDn0BlB,QvDo0BmB;;AuDl0BnB;EACE;EACA;EACA;EACA;;;AAKN;EACE;;AAEA;EACE;;AAEA;EACE;EACA;EACA,kBvDvBE;;;AuD4BR;EACE;;AAEA;EACE;EACA,OvDsyBmB;EuDryBnB,QvDoyBkB;;AuDlyBlB;EACE;EACA;EACA,oBvDvCE;;;AuD4CR;EACE;;AAEA;EACE;;AAEA;EACE;EACA;EACA,qBvDrDE;;;AuD0DR;EACE;;AAEA;EACE;EACA,OvDwwBmB;EuDvwBnB,QvDswBkB;;AuDpwBlB;EACE;EACA;EACA,mBvDrEE;;;AuD0FR;EACE,WvDkuBkB;EuDjuBlB;EACA,OvDvGM;EuDwGN;EACA,kBvD/FM;E+BCJ;;;A0BlBJ;EACE;EACA;EACA;EACA,SzDwqBe;EyDvqBf;EACA,WzDi2BkB;EwDt2BlB,axDyQuB;EwDvQvB;EACA,axDkRmB;EwDjRnB,axDsRiB;EwDrRjB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EvDgHI,WAtCa;EwD7EjB;EACA,kBzDNM;EyDON;EACA;E1BGE;;A0BCF;EACE;EACA;EACA,OzDi2BkB;EyDh2BlB,QzDi2BmB;EyDh2BnB;;AAEA;EAEE;EACA;EACA;EACA;EACA;;;AAKN;EACE,ezDk1BqB;;AyDh1BrB;EACE;;AAEA;EACE;EACA;EACA,kBzD60BsB;;AyD10BxB;EACE,QzDwLS;EyDvLT;EACA,kBzD7CE;;;AyDkDR;EACE,azD8zBqB;;AyD5zBrB;EACE;EACA,OzD0zBmB;EyDzzBnB,QzDwzBkB;EyDvzBlB;;AAEA;EACE;EACA;EACA,oBzDszBsB;;AyDnzBxB;EACE,MzDiKS;EyDhKT;EACA,oBzDpEE;;;AyDyER;EACE,YzDuyBqB;;AyDryBrB;EACE;;AAEA;EACE;EACA;EACA,qBzDkyBsB;;AyD/xBxB;EACE,KzD6IS;EyD5IT;EACA,qBzDxFE;;AyD6FN;EACE;EACA;EACA;EACA;EACA,OzD8wBkB;EyD7wBlB;EACA;EACA;;;AAIJ;EACE,czDuwBqB;;AyDrwBrB;EACE;EACA,OzDmwBmB;EyDlwBnB,QzDiwBkB;EyDhwBlB;;AAEA;EACE;EACA;EACA,mBzD+vBsB;;AyD5vBxB;EACE,OzD0GS;EyDzGT;EACA,mBzD3HE;;;AyDiJR;EACE;EACA;ExD3BI,WAtCa;EwDoEjB,kBzDitBkB;EyDhtBlB;E1BnIE;EACA;;A0BqIF;EACE;;;AAIJ;EACE;EACA,OzDxJS;;;A0DhBX;AAAA;EAEI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;;;AAIA;EADJ;IAEQ;;;;AAKJ;EADJ;IAEQ;;;;AAKJ;EADJ;IAEQ;;;;AAIR;AAAA;EAEI,a1DyOqB;E0DxOrB;EACA;EACA;EACA;;AAEA;EARJ;AAAA;IASQ;;;;AAIR;AAAA;EAEI,O1DvCI;E0DwCJ,a1DwOe;E0DvOf;;;AAGJ;EACI,O1DfK;E0DgBL;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA,kB1DzBK;;A0D2BL;EACI;EACA,a1DiNW;E0DhNX;EACA;;;AAIR;EACI;;;AAGJ;EACI;;;AAGJ;EACI;;;AAGJ;EACI;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA,Y1DzBG;;A0D2BH;EACI;;AAGJ;EAZJ;IAaQ;IACA;;EAEA;IACI;;;;AAKZ;EACI;;AAEA;EACI;EACA,kB1D7CC;E0D8CD;EACA;EACA;EACA;EACA;;;AAIR;EACI;;AAEA;EACI,kB1D1DC;E0D2DD;EACA;EACA;EACA;EACA;EACA;;AAEA;EATJ;IAUQ;;;;AAIZ;EACI;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;;;AAIR;EACI;;AAEA;EACI;;AAEA;EACI,O1DjKJ;;A0DmKI;EACI;EACA;EACA;EACA;EACA,kB1DtIP;E0DuIO;EACA;;;AAOZ;EACI;EACA;;;AAKJ;EACI;EACA;;AAEA;EAJJ;IAKQ;;;;AAKZ;AAAA;EAEI;EACA;EACA;EACA;EACA;EACA,O1DzMI;E0D0MJ;EACA;;;AAEJ;EACI,c1D5KK;;;A0D+KT;EACI;EACA;;;AAGJ;EACI;;AAEA;EACI;;;A5D/NR;EACI;;;AAGJ;EACI,aEmQqB;EFlQrB;;AAEA;EACI;EACA;;;AAIR;EACI;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;EACA,WE2OO;;;AFvOf;EACI;;;AAGJ;EACI;IACI;IACA;;;EAEJ;IACI;;;AAIR;EACI;EACA;EACA;EACA,OElDO;EFmDP;;AAEA;EAGI;EACA,OE7BF;EF8BE,cE9BF;;AFiCF;EACI;;;AAIR;EACI;EACA;EACA,kBE3CK;EF4CL;EACA;;AAEA;EACI;;AAGJ;EAXJ;IAYQ;;EAEA;IACI;IACA;;;AAIR;EACI;EACA;EACA,OE/DC;;AFkEL;EACI;;AAGJ;EACI;;AAEJ;EACI;;;AAIR;EACI;EACA;EACA;EACA,YE3CK;EF4CL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;;AAGJ;EAGI;EACA;;AAGJ;EACI;EACA;EACA;;;AAIR;EACI;EACA;EACA;EACA;;AAEA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;EACA;;AAIR;EACI;EACA;EACA;EACA;EACA;;;AAIR;EACI;;AAEA;EACI;;;AAIR;EACI;;AAEA;EACI;;;AAIR;EACI;;AAEA;EACI;;;AAIR;EACI;;AAEA;EACI;;;A6DpNR;EACI;EACA;EACA;EACA,kB3DIO;E2DHP;EACA;EACA;EACA;EACA;EACA;;AAEA;EACI;;AAGJ;EACI;EACA;;;AAIR;EACI;EACA;EACA;;;ACzBJ;EACI,O5DUO;;;AH4BX;EACI;EACA;EACA","file":"main.css"}
\ No newline at end of file
diff --git a/apps/auth/src/assets/img/bg.jpg b/apps/auth/src/assets/img/bg.jpg
new file mode 100644
index 000000000..a72edc556
Binary files /dev/null and b/apps/auth/src/assets/img/bg.jpg differ
diff --git a/apps/auth/src/assets/img/discord-logo.png b/apps/auth/src/assets/img/discord-logo.png
new file mode 100644
index 000000000..fb6a4b4ba
Binary files /dev/null and b/apps/auth/src/assets/img/discord-logo.png differ
diff --git a/apps/auth/src/assets/img/g-logo.png b/apps/auth/src/assets/img/g-logo.png
new file mode 100644
index 000000000..3a2e0c537
Binary files /dev/null and b/apps/auth/src/assets/img/g-logo.png differ
diff --git a/apps/auth/src/assets/img/github-logo.png b/apps/auth/src/assets/img/github-logo.png
new file mode 100644
index 000000000..56b2531f1
Binary files /dev/null and b/apps/auth/src/assets/img/github-logo.png differ
diff --git a/apps/auth/src/assets/img/icons/android-chrome-192x192.png b/apps/auth/src/assets/img/icons/android-chrome-192x192.png
new file mode 100644
index 000000000..6cda98d12
Binary files /dev/null and b/apps/auth/src/assets/img/icons/android-chrome-192x192.png differ
diff --git a/apps/auth/src/assets/img/icons/android-chrome-512x512.png b/apps/auth/src/assets/img/icons/android-chrome-512x512.png
new file mode 100644
index 000000000..b939a3554
Binary files /dev/null and b/apps/auth/src/assets/img/icons/android-chrome-512x512.png differ
diff --git a/apps/auth/src/assets/img/icons/apple-touch-icon.png b/apps/auth/src/assets/img/icons/apple-touch-icon.png
new file mode 100644
index 000000000..8647b6bb6
Binary files /dev/null and b/apps/auth/src/assets/img/icons/apple-touch-icon.png differ
diff --git a/apps/auth/src/assets/img/icons/favicon-16x16.png b/apps/auth/src/assets/img/icons/favicon-16x16.png
new file mode 100644
index 000000000..14611c47a
Binary files /dev/null and b/apps/auth/src/assets/img/icons/favicon-16x16.png differ
diff --git a/apps/auth/src/assets/img/icons/favicon-32x32.png b/apps/auth/src/assets/img/icons/favicon-32x32.png
new file mode 100644
index 000000000..8cf9c6c4c
Binary files /dev/null and b/apps/auth/src/assets/img/icons/favicon-32x32.png differ
diff --git a/apps/auth/src/assets/img/icons/mstile-150x150.png b/apps/auth/src/assets/img/icons/mstile-150x150.png
new file mode 100644
index 000000000..9e1cf551e
Binary files /dev/null and b/apps/auth/src/assets/img/icons/mstile-150x150.png differ
diff --git a/apps/auth/src/assets/img/icons/safari-pinned-tab.svg b/apps/auth/src/assets/img/icons/safari-pinned-tab.svg
new file mode 100644
index 000000000..629c49373
--- /dev/null
+++ b/apps/auth/src/assets/img/icons/safari-pinned-tab.svg
@@ -0,0 +1,58 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
+ "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
+ width="1182.000000pt" height="1182.000000pt" viewBox="0 0 1182.000000 1182.000000"
+ preserveAspectRatio="xMidYMid meet">
+<metadata>
+Created by potrace 1.11, written by Peter Selinger 2001-2013
+</metadata>
+<g transform="translate(0.000000,1182.000000) scale(0.100000,-0.100000)"
+fill="#000000" stroke="none">
+<path d="M5618 10424 c-2 -2 -40 -6 -83 -9 -44 -3 -91 -7 -105 -10 -14 -2 -52
+-6 -85 -10 -76 -8 -115 -13 -190 -27 -33 -5 -71 -12 -85 -14 -14 -3 -37 -7
+-52 -10 -15 -3 -30 -5 -33 -5 -2 0 -19 -5 -37 -10 -18 -5 -46 -12 -61 -14 -61
+-11 -259 -67 -387 -108 -464 -152 -895 -376 -1300 -677 -69 -51 -127 -96 -130
+-99 -3 -3 -32 -28 -65 -56 -33 -27 -75 -63 -94 -80 -58 -50 -334 -327 -382
+-381 -91 -105 -100 -116 -186 -224 -129 -161 -403 -582 -403 -618 0 -6 -3 -12
+-7 -14 -8 -3 -102 -189 -146 -288 -59 -132 -69 -156 -71 -165 -1 -5 -10 -28
+-20 -50 -18 -43 -125 -353 -131 -382 -2 -10 -17 -71 -34 -135 -17 -65 -33
+-128 -35 -140 -17 -79 -57 -296 -60 -331 -3 -23 -7 -58 -10 -77 -25 -173 -30
+-270 -30 -575 -1 -257 6 -422 18 -492 2 -10 7 -49 11 -88 4 -38 9 -79 11 -90
+3 -11 7 -38 10 -60 15 -103 85 -429 101 -465 3 -8 8 -25 10 -36 4 -23 88 -285
+102 -319 5 -11 18 -47 31 -80 23 -63 34 -89 69 -167 12 -26 21 -49 21 -52 0
+-13 143 -292 207 -406 115 -202 326 -502 467 -665 34 -38 72 -83 86 -99 80
+-95 381 -385 475 -457 17 -13 37 -30 45 -38 8 -7 57 -46 110 -85 52 -39 97
+-74 100 -77 20 -24 412 -269 430 -269 5 0 10 -3 12 -8 3 -8 300 -159 363 -184
+22 -9 58 -24 80 -34 22 -10 42 -18 45 -19 9 -3 45 -17 65 -25 57 -25 102 -42
+120 -46 11 -2 29 -8 40 -13 37 -18 287 -93 355 -107 14 -2 39 -9 55 -14 17 -5
+41 -12 55 -15 14 -2 45 -9 70 -15 25 -5 63 -12 85 -16 22 -3 56 -9 75 -14 19
+-5 60 -12 90 -15 30 -4 62 -9 71 -10 8 -2 42 -6 75 -10 32 -4 73 -8 89 -10
+232 -28 758 -25 977 5 37 6 88 12 115 15 26 3 59 7 73 10 14 3 54 10 90 16 36
+5 81 14 100 19 19 5 49 11 65 13 180 29 723 205 866 282 14 8 29 12 33 8 3 -3
+6 -1 6 5 0 7 5 12 11 12 12 0 211 98 304 150 105 58 119 66 185 107 79 48 253
+165 260 174 3 3 14 11 25 17 16 8 36 3 103 -29 46 -21 89 -39 96 -39 6 0 16
+-4 21 -9 8 -7 125 -60 450 -206 17 -7 65 -29 108 -49 43 -20 80 -36 82 -36 2
+0 47 -20 99 -45 53 -25 98 -45 100 -45 2 0 37 -15 78 -34 111 -51 270 -123
+367 -166 47 -21 115 -51 151 -67 36 -17 94 -43 130 -59 36 -15 68 -33 72 -39
+5 -7 8 -6 8 1 0 12 -27 66 -104 209 -24 44 -57 105 -73 135 -16 30 -36 66 -45
+80 -8 14 -38 68 -65 120 -28 52 -65 122 -83 155 -93 170 -151 277 -185 340
+-21 39 -55 101 -77 138 -21 38 -38 72 -38 76 0 3 -11 23 -25 44 -14 20 -23 37
+-20 37 4 0 -2 12 -12 28 -10 15 -32 52 -47 82 -16 30 -38 67 -48 83 -11 15
+-17 27 -14 27 4 0 -4 17 -18 38 -13 20 -35 60 -49 88 -23 46 -24 53 -11 70 7
+11 27 36 42 56 39 50 214 313 245 368 66 116 111 198 124 225 8 17 18 37 23
+45 21 33 94 190 126 270 19 47 39 94 44 105 9 18 53 133 55 145 1 3 4 12 8 20
+26 66 111 351 122 411 2 12 7 29 10 39 3 10 8 31 10 46 2 16 9 47 14 69 13 53
+21 93 27 135 2 19 8 49 13 65 4 17 7 42 7 58 -1 15 1 27 4 27 3 0 6 10 7 23 1
+29 13 145 18 177 24 141 24 685 1 900 -3 19 -7 62 -10 95 -4 33 -9 73 -12 90
+-3 16 -7 44 -9 60 -4 30 -10 69 -20 115 -3 14 -8 41 -11 60 -3 19 -14 73 -26
+120 -20 87 -26 110 -33 145 -3 11 -14 52 -25 90 -12 39 -23 77 -25 85 -21 86
+-136 397 -190 515 -40 87 -151 311 -187 375 -119 218 -349 549 -503 726 -157
+180 -353 377 -501 504 -35 30 -135 112 -149 122 -5 4 -44 34 -85 65 -94 73
+-410 283 -426 283 -3 0 -19 10 -36 21 -28 20 -381 203 -403 209 -5 1 -35 14
+-65 27 -30 14 -59 26 -65 27 -5 1 -27 10 -49 19 -21 9 -44 17 -50 17 -7 0 -16
+4 -22 9 -9 8 -218 81 -239 82 -5 1 -17 5 -26 10 -13 6 -190 56 -269 74 -52 13
+-252 54 -285 60 -71 12 -79 13 -130 20 -27 4 -61 8 -75 10 -14 2 -52 7 -85 10
+-33 4 -82 9 -110 12 -56 6 -662 13 -667 7z"/>
+</g>
+</svg>
diff --git a/apps/auth/src/assets/img/logo-padding.png b/apps/auth/src/assets/img/logo-padding.png
new file mode 100644
index 000000000..f84f50ea8
Binary files /dev/null and b/apps/auth/src/assets/img/logo-padding.png differ
diff --git a/apps/auth/src/assets/img/logo.png b/apps/auth/src/assets/img/logo.png
new file mode 100644
index 000000000..789f97fdc
Binary files /dev/null and b/apps/auth/src/assets/img/logo.png differ
diff --git a/apps/auth/src/assets/img/mail/icons/chevron-right-solid.png b/apps/auth/src/assets/img/mail/icons/chevron-right-solid.png
new file mode 100644
index 000000000..a0b2581eb
Binary files /dev/null and b/apps/auth/src/assets/img/mail/icons/chevron-right-solid.png differ
diff --git a/apps/auth/src/assets/img/mail/icons/discord-brands.png b/apps/auth/src/assets/img/mail/icons/discord-brands.png
new file mode 100644
index 000000000..1c48d038a
Binary files /dev/null and b/apps/auth/src/assets/img/mail/icons/discord-brands.png differ
diff --git a/apps/auth/src/assets/img/mail/icons/envelope-solid.png b/apps/auth/src/assets/img/mail/icons/envelope-solid.png
new file mode 100644
index 000000000..0bc4903bc
Binary files /dev/null and b/apps/auth/src/assets/img/mail/icons/envelope-solid.png differ
diff --git a/apps/auth/src/assets/img/mail/icons/sign-in-alt-solid.png b/apps/auth/src/assets/img/mail/icons/sign-in-alt-solid.png
new file mode 100644
index 000000000..13ef380b5
Binary files /dev/null and b/apps/auth/src/assets/img/mail/icons/sign-in-alt-solid.png differ
diff --git a/apps/auth/src/assets/img/mail/icons/slack-brands.png b/apps/auth/src/assets/img/mail/icons/slack-brands.png
new file mode 100644
index 000000000..c4f2f5c83
Binary files /dev/null and b/apps/auth/src/assets/img/mail/icons/slack-brands.png differ
diff --git a/apps/auth/src/assets/img/mm-logo.svg b/apps/auth/src/assets/img/mm-logo.svg
new file mode 100644
index 000000000..a6cffef03
--- /dev/null
+++ b/apps/auth/src/assets/img/mm-logo.svg
@@ -0,0 +1 @@
+<svg fill="none" height="33" viewBox="0 0 35 33" width="35" xmlns="http://www.w3.org/2000/svg"><g stroke-linecap="round" stroke-linejoin="round" stroke-width=".25"><path d="m32.9582 1-13.1341 9.7183 2.4424-5.72731z" fill="#e17726" stroke="#e17726"/><g fill="#e27625" stroke="#e27625"><path d="m2.66296 1 13.01714 9.809-2.3254-5.81802z"/><path d="m28.2295 23.5335-3.4947 5.3386 7.4829 2.0603 2.1436-7.2823z"/><path d="m1.27281 23.6501 2.13055 7.2823 7.46994-2.0603-3.48166-5.3386z"/><path d="m10.4706 14.5149-2.0786 3.1358 7.405.3369-.2469-7.969z"/><path d="m25.1505 14.5149-5.1575-4.58704-.1688 8.05974 7.4049-.3369z"/><path d="m10.8733 28.8721 4.4819-2.1639-3.8583-3.0062z"/><path d="m20.2659 26.7082 4.4689 2.1639-.6105-5.1701z"/></g><path d="m24.7348 28.8721-4.469-2.1639.3638 2.9025-.039 1.231z" fill="#d5bfb2" stroke="#d5bfb2"/><path d="m10.8732 28.8721 4.1572 1.9696-.026-1.231.3508-2.9025z" fill="#d5bfb2" stroke="#d5bfb2"/><path d="m15.1084 21.7842-3.7155-1.0884 2.6243-1.2051z" fill="#233447" stroke="#233447"/><path d="m20.5126 21.7842 1.0913-2.2935 2.6372 1.2051z" fill="#233447" stroke="#233447"/><path d="m10.8733 28.8721.6495-5.3386-4.13117.1167z" fill="#cc6228" stroke="#cc6228"/><path d="m24.0982 23.5335.6366 5.3386 3.4946-5.2219z" fill="#cc6228" stroke="#cc6228"/><path d="m27.2291 17.6507-7.405.3369.6885 3.7966 1.0913-2.2935 2.6372 1.2051z" fill="#cc6228" stroke="#cc6228"/><path d="m11.3929 20.6958 2.6242-1.2051 1.0913 2.2935.6885-3.7966-7.40495-.3369z" fill="#cc6228" stroke="#cc6228"/><path d="m8.392 17.6507 3.1049 6.0513-.1039-3.0062z" fill="#e27525" stroke="#e27525"/><path d="m24.2412 20.6958-.1169 3.0062 3.1049-6.0513z" fill="#e27525" stroke="#e27525"/><path d="m15.797 17.9876-.6886 3.7967.8704 4.4833.1949-5.9087z" fill="#e27525" stroke="#e27525"/><path d="m19.8242 17.9876-.3638 2.3584.1819 5.9216.8704-4.4833z" fill="#e27525" stroke="#e27525"/><path d="m20.5127 21.7842-.8704 4.4834.6236.4406 3.8584-3.0062.1169-3.0062z" fill="#f5841f" stroke="#f5841f"/><path d="m11.3929 20.6958.104 3.0062 3.8583 3.0062.6236-.4406-.8704-4.4834z" fill="#f5841f" stroke="#f5841f"/><path d="m20.5906 30.8417.039-1.231-.3378-.2851h-4.9626l-.3248.2851.026 1.231-4.1572-1.9696 1.4551 1.1921 2.9489 2.0344h5.0536l2.962-2.0344 1.442-1.1921z" fill="#c0ac9d" stroke="#c0ac9d"/><path d="m20.2659 26.7082-.6236-.4406h-3.6635l-.6236.4406-.3508 2.9025.3248-.2851h4.9626l.3378.2851z" fill="#161616" stroke="#161616"/><path d="m33.5168 11.3532 1.1043-5.36447-1.6629-4.98873-12.6923 9.3944 4.8846 4.1205 6.8983 2.0085 1.52-1.7752-.6626-.4795 1.0523-.9588-.8054-.622 1.0523-.8034z" fill="#763e1a" stroke="#763e1a"/><path d="m1 5.98873 1.11724 5.36447-.71451.5313 1.06527.8034-.80545.622 1.05228.9588-.66255.4795 1.51997 1.7752 6.89835-2.0085 4.8846-4.1205-12.69233-9.3944z" fill="#763e1a" stroke="#763e1a"/><path d="m32.0489 16.5234-6.8983-2.0085 2.0786 3.1358-3.1049 6.0513 4.1052-.0519h6.1318z" fill="#f5841f" stroke="#f5841f"/><path d="m10.4705 14.5149-6.89828 2.0085-2.29944 7.1267h6.11883l4.10519.0519-3.10487-6.0513z" fill="#f5841f" stroke="#f5841f"/><path d="m19.8241 17.9876.4417-7.5932 2.0007-5.4034h-8.9119l2.0006 5.4034.4417 7.5932.1689 2.3842.013 5.8958h3.6635l.013-5.8958z" fill="#f5841f" stroke="#f5841f"/></g></svg>
\ No newline at end of file
diff --git a/apps/auth/src/assets/img/s-logo.png b/apps/auth/src/assets/img/s-logo.png
new file mode 100644
index 000000000..7bb2f2bb4
Binary files /dev/null and b/apps/auth/src/assets/img/s-logo.png differ
diff --git a/apps/auth/src/assets/img/shopify-logo.png b/apps/auth/src/assets/img/shopify-logo.png
new file mode 100644
index 000000000..91f963a86
Binary files /dev/null and b/apps/auth/src/assets/img/shopify-logo.png differ
diff --git a/apps/auth/src/assets/img/t-logo.png b/apps/auth/src/assets/img/t-logo.png
new file mode 100644
index 000000000..c24595e84
Binary files /dev/null and b/apps/auth/src/assets/img/t-logo.png differ
diff --git a/apps/auth/src/assets/img/thx_landing_crater.webp b/apps/auth/src/assets/img/thx_landing_crater.webp
new file mode 100644
index 000000000..6a8a97f8e
Binary files /dev/null and b/apps/auth/src/assets/img/thx_landing_crater.webp differ
diff --git a/apps/auth/src/assets/img/twitch-logo.png b/apps/auth/src/assets/img/twitch-logo.png
new file mode 100644
index 000000000..bcc5b1024
Binary files /dev/null and b/apps/auth/src/assets/img/twitch-logo.png differ
diff --git a/apps/auth/src/assets/js/logout.js b/apps/auth/src/assets/js/logout.js
new file mode 100644
index 000000000..f3d9b94b2
--- /dev/null
+++ b/apps/auth/src/assets/js/logout.js
@@ -0,0 +1,10 @@
+function logout() {
+    const form = document.forms[0];
+    const input = document.createElement('input');
+    input.type = 'hidden';
+    input.name = 'logout';
+    input.value = 'yes';
+    form.appendChild(input);
+    form.submit();
+}
+logout();
diff --git a/apps/auth/src/assets/js/otp.js b/apps/auth/src/assets/js/otp.js
new file mode 100644
index 000000000..3eca585a5
--- /dev/null
+++ b/apps/auth/src/assets/js/otp.js
@@ -0,0 +1,14 @@
+import { createApp } from 'https://cdnjs.cloudflare.com/ajax/libs/petite-vue/0.4.1/petite-vue.es.js';
+
+createApp({
+    otp: '',
+    isLoading: null,
+    onInput() {
+        if (this.otp.length === 5) {
+            this.isLoading = true;
+            setTimeout(() => {
+                this.$refs.submit.click();
+            }, 1000);
+        }
+    },
+}).mount();
diff --git a/apps/auth/src/assets/js/signin.js b/apps/auth/src/assets/js/signin.js
new file mode 100644
index 000000000..f88bc1983
--- /dev/null
+++ b/apps/auth/src/assets/js/signin.js
@@ -0,0 +1,42 @@
+import { createApp } from 'https://cdnjs.cloudflare.com/ajax/libs/petite-vue/0.4.1/petite-vue.es.min.js';
+
+/* eslint-disable no-undef */
+
+createApp({
+    isMounted: false,
+    alert: { variant: 'warning', message: '' },
+    email: '',
+    isIframe: window.matchMedia('(pointer:coarse)').matches,
+    isMobile: window.matchMedia('(pointer:coarse)').matches,
+    isLoading: false,
+    get isDisabled() {
+        return this.email
+            ? !this.email.match(
+                  /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
+              )
+            : true;
+    },
+    onMounted() {
+        const isWidgetInput = document.getElementsByName('isWidget');
+        const returnUrlInput = document.getElementsByName('returnUrl');
+        const claimUrlInput = document.getElementsByName('claimUrl');
+        const signupEmailInput = document.getElementsByName('signupEmail');
+        this.email = signupEmailInput.length ? signupEmailInput[0].value : '';
+        this.isWidget = isWidgetInput.length ? JSON.parse(isWidgetInput[0].value) : false;
+        this.returnUrl = returnUrlInput.length ? returnUrlInput[0].value : '';
+        this.claimUrl = claimUrlInput.length ? claimUrlInput[0].value : '';
+        this.isMounted = true;
+    },
+    onClickReturn() {
+        if (window.opener) {
+            window.close();
+        } else {
+            window.location.href = this.returnUrl;
+        }
+    },
+    onClickSubmit() {
+        this.isLoading = true;
+    },
+}).mount();
+
+/* eslint-enable no-undef */
diff --git a/apps/auth/src/assets/js/vendors/bootstrap.bundle.min.js b/apps/auth/src/assets/js/vendors/bootstrap.bundle.min.js
new file mode 100644
index 000000000..3af2dc364
--- /dev/null
+++ b/apps/auth/src/assets/js/vendors/bootstrap.bundle.min.js
@@ -0,0 +1,7 @@
+/*!
+  * Bootstrap v4.6.1 (https://getbootstrap.com/)
+  * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
+  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+  */
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery")):"function"==typeof define&&define.amd?define(["exports","jquery"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap={},t.jQuery)}(this,(function(t,e){"use strict";function n(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var i=n(e);function o(t,e){for(var n=0;n<e.length;n++){var i=e[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,i.key,i)}}function r(t,e,n){return e&&o(t.prototype,e),n&&o(t,n),t}function a(){return a=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(t[i]=n[i])}return t},a.apply(this,arguments)}function s(t,e){return s=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t},s(t,e)}var l="transitionend";var u={TRANSITION_END:"bsTransitionEnd",getUID:function(t){do{t+=~~(1e6*Math.random())}while(document.getElementById(t));return t},getSelectorFromElement:function(t){var e=t.getAttribute("data-target");if(!e||"#"===e){var n=t.getAttribute("href");e=n&&"#"!==n?n.trim():""}try{return document.querySelector(e)?e:null}catch(t){return null}},getTransitionDurationFromElement:function(t){if(!t)return 0;var e=i.default(t).css("transition-duration"),n=i.default(t).css("transition-delay"),o=parseFloat(e),r=parseFloat(n);return o||r?(e=e.split(",")[0],n=n.split(",")[0],1e3*(parseFloat(e)+parseFloat(n))):0},reflow:function(t){return t.offsetHeight},triggerTransitionEnd:function(t){i.default(t).trigger(l)},supportsTransitionEnd:function(){return Boolean(l)},isElement:function(t){return(t[0]||t).nodeType},typeCheckConfig:function(t,e,n){for(var i in n)if(Object.prototype.hasOwnProperty.call(n,i)){var o=n[i],r=e[i],a=r&&u.isElement(r)?"element":null===(s=r)||"undefined"==typeof s?""+s:{}.toString.call(s).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(o).test(a))throw new Error(t.toUpperCase()+': Option "'+i+'" provided type "'+a+'" but expected type "'+o+'".')}var s},findShadowRoot:function(t){if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){var e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?u.findShadowRoot(t.parentNode):null},jQueryDetection:function(){if("undefined"==typeof i.default)throw new TypeError("Bootstrap's JavaScript requires jQuery. jQuery must be included before Bootstrap's JavaScript.");var t=i.default.fn.jquery.split(" ")[0].split(".");if(t[0]<2&&t[1]<9||1===t[0]&&9===t[1]&&t[2]<1||t[0]>=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}};u.jQueryDetection(),i.default.fn.emulateTransitionEnd=function(t){var e=this,n=!1;return i.default(this).one(u.TRANSITION_END,(function(){n=!0})),setTimeout((function(){n||u.triggerTransitionEnd(e)}),t),this},i.default.event.special[u.TRANSITION_END]={bindType:l,delegateType:l,handle:function(t){if(i.default(t.target).is(this))return t.handleObj.handler.apply(this,arguments)}};var f="bs.alert",d=i.default.fn.alert,c=function(){function t(t){this._element=t}var e=t.prototype;return e.close=function(t){var e=this._element;t&&(e=this._getRootElement(t)),this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},e.dispose=function(){i.default.removeData(this._element,f),this._element=null},e._getRootElement=function(t){var e=u.getSelectorFromElement(t),n=!1;return e&&(n=document.querySelector(e)),n||(n=i.default(t).closest(".alert")[0]),n},e._triggerCloseEvent=function(t){var e=i.default.Event("close.bs.alert");return i.default(t).trigger(e),e},e._removeElement=function(t){var e=this;if(i.default(t).removeClass("show"),i.default(t).hasClass("fade")){var n=u.getTransitionDurationFromElement(t);i.default(t).one(u.TRANSITION_END,(function(n){return e._destroyElement(t,n)})).emulateTransitionEnd(n)}else this._destroyElement(t)},e._destroyElement=function(t){i.default(t).detach().trigger("closed.bs.alert").remove()},t._jQueryInterface=function(e){return this.each((function(){var n=i.default(this),o=n.data(f);o||(o=new t(this),n.data(f,o)),"close"===e&&o[e](this)}))},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},r(t,null,[{key:"VERSION",get:function(){return"4.6.1"}}]),t}();i.default(document).on("click.bs.alert.data-api",'[data-dismiss="alert"]',c._handleDismiss(new c)),i.default.fn.alert=c._jQueryInterface,i.default.fn.alert.Constructor=c,i.default.fn.alert.noConflict=function(){return i.default.fn.alert=d,c._jQueryInterface};var h="bs.button",p=i.default.fn.button,m="active",g='[data-toggle^="button"]',_='input:not([type="hidden"])',v=".btn",b=function(){function t(t){this._element=t,this.shouldAvoidTriggerChange=!1}var e=t.prototype;return e.toggle=function(){var t=!0,e=!0,n=i.default(this._element).closest('[data-toggle="buttons"]')[0];if(n){var o=this._element.querySelector(_);if(o){if("radio"===o.type)if(o.checked&&this._element.classList.contains(m))t=!1;else{var r=n.querySelector(".active");r&&i.default(r).removeClass(m)}t&&("checkbox"!==o.type&&"radio"!==o.type||(o.checked=!this._element.classList.contains(m)),this.shouldAvoidTriggerChange||i.default(o).trigger("change")),o.focus(),e=!1}}this._element.hasAttribute("disabled")||this._element.classList.contains("disabled")||(e&&this._element.setAttribute("aria-pressed",!this._element.classList.contains(m)),t&&i.default(this._element).toggleClass(m))},e.dispose=function(){i.default.removeData(this._element,h),this._element=null},t._jQueryInterface=function(e,n){return this.each((function(){var o=i.default(this),r=o.data(h);r||(r=new t(this),o.data(h,r)),r.shouldAvoidTriggerChange=n,"toggle"===e&&r[e]()}))},r(t,null,[{key:"VERSION",get:function(){return"4.6.1"}}]),t}();i.default(document).on("click.bs.button.data-api",g,(function(t){var e=t.target,n=e;if(i.default(e).hasClass("btn")||(e=i.default(e).closest(v)[0]),!e||e.hasAttribute("disabled")||e.classList.contains("disabled"))t.preventDefault();else{var o=e.querySelector(_);if(o&&(o.hasAttribute("disabled")||o.classList.contains("disabled")))return void t.preventDefault();"INPUT"!==n.tagName&&"LABEL"===e.tagName||b._jQueryInterface.call(i.default(e),"toggle","INPUT"===n.tagName)}})).on("focus.bs.button.data-api blur.bs.button.data-api",g,(function(t){var e=i.default(t.target).closest(v)[0];i.default(e).toggleClass("focus",/^focus(in)?$/.test(t.type))})),i.default(window).on("load.bs.button.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-toggle="buttons"] .btn')),e=0,n=t.length;e<n;e++){var i=t[e],o=i.querySelector(_);o.checked||o.hasAttribute("checked")?i.classList.add(m):i.classList.remove(m)}for(var r=0,a=(t=[].slice.call(document.querySelectorAll('[data-toggle="button"]'))).length;r<a;r++){var s=t[r];"true"===s.getAttribute("aria-pressed")?s.classList.add(m):s.classList.remove(m)}})),i.default.fn.button=b._jQueryInterface,i.default.fn.button.Constructor=b,i.default.fn.button.noConflict=function(){return i.default.fn.button=p,b._jQueryInterface};var y="carousel",E="bs.carousel",w=i.default.fn[y],T="active",C="next",S="prev",N="slid.bs.carousel",D=".active.carousel-item",A={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},k={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},I={TOUCH:"touch",PEN:"pen"},O=function(){function t(t,e){this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(e),this._element=t,this._indicatorsElement=this._element.querySelector(".carousel-indicators"),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent||window.MSPointerEvent),this._addEventListeners()}var e=t.prototype;return e.next=function(){this._isSliding||this._slide(C)},e.nextWhenVisible=function(){var t=i.default(this._element);!document.hidden&&t.is(":visible")&&"hidden"!==t.css("visibility")&&this.next()},e.prev=function(){this._isSliding||this._slide(S)},e.pause=function(t){t||(this._isPaused=!0),this._element.querySelector(".carousel-item-next, .carousel-item-prev")&&(u.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},e.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},e.to=function(t){var e=this;this._activeElement=this._element.querySelector(D);var n=this._getItemIndex(this._activeElement);if(!(t>this._items.length-1||t<0))if(this._isSliding)i.default(this._element).one(N,(function(){return e.to(t)}));else{if(n===t)return this.pause(),void this.cycle();var o=t>n?C:S;this._slide(o,this._items[t])}},e.dispose=function(){i.default(this._element).off(".bs.carousel"),i.default.removeData(this._element,E),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},e._getConfig=function(t){return t=a({},A,t),u.typeCheckConfig(y,t,k),t},e._handleSwipe=function(){var t=Math.abs(this.touchDeltaX);if(!(t<=40)){var e=t/this.touchDeltaX;this.touchDeltaX=0,e>0&&this.prev(),e<0&&this.next()}},e._addEventListeners=function(){var t=this;this._config.keyboard&&i.default(this._element).on("keydown.bs.carousel",(function(e){return t._keydown(e)})),"hover"===this._config.pause&&i.default(this._element).on("mouseenter.bs.carousel",(function(e){return t.pause(e)})).on("mouseleave.bs.carousel",(function(e){return t.cycle(e)})),this._config.touch&&this._addTouchEventListeners()},e._addTouchEventListeners=function(){var t=this;if(this._touchSupported){var e=function(e){t._pointerEvent&&I[e.originalEvent.pointerType.toUpperCase()]?t.touchStartX=e.originalEvent.clientX:t._pointerEvent||(t.touchStartX=e.originalEvent.touches[0].clientX)},n=function(e){t._pointerEvent&&I[e.originalEvent.pointerType.toUpperCase()]&&(t.touchDeltaX=e.originalEvent.clientX-t.touchStartX),t._handleSwipe(),"hover"===t._config.pause&&(t.pause(),t.touchTimeout&&clearTimeout(t.touchTimeout),t.touchTimeout=setTimeout((function(e){return t.cycle(e)}),500+t._config.interval))};i.default(this._element.querySelectorAll(".carousel-item img")).on("dragstart.bs.carousel",(function(t){return t.preventDefault()})),this._pointerEvent?(i.default(this._element).on("pointerdown.bs.carousel",(function(t){return e(t)})),i.default(this._element).on("pointerup.bs.carousel",(function(t){return n(t)})),this._element.classList.add("pointer-event")):(i.default(this._element).on("touchstart.bs.carousel",(function(t){return e(t)})),i.default(this._element).on("touchmove.bs.carousel",(function(e){return function(e){t.touchDeltaX=e.originalEvent.touches&&e.originalEvent.touches.length>1?0:e.originalEvent.touches[0].clientX-t.touchStartX}(e)})),i.default(this._element).on("touchend.bs.carousel",(function(t){return n(t)})))}},e._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case 37:t.preventDefault(),this.prev();break;case 39:t.preventDefault(),this.next()}},e._getItemIndex=function(t){return this._items=t&&t.parentNode?[].slice.call(t.parentNode.querySelectorAll(".carousel-item")):[],this._items.indexOf(t)},e._getItemByDirection=function(t,e){var n=t===C,i=t===S,o=this._getItemIndex(e),r=this._items.length-1;if((i&&0===o||n&&o===r)&&!this._config.wrap)return e;var a=(o+(t===S?-1:1))%this._items.length;return-1===a?this._items[this._items.length-1]:this._items[a]},e._triggerSlideEvent=function(t,e){var n=this._getItemIndex(t),o=this._getItemIndex(this._element.querySelector(D)),r=i.default.Event("slide.bs.carousel",{relatedTarget:t,direction:e,from:o,to:n});return i.default(this._element).trigger(r),r},e._setActiveIndicatorElement=function(t){if(this._indicatorsElement){var e=[].slice.call(this._indicatorsElement.querySelectorAll(".active"));i.default(e).removeClass(T);var n=this._indicatorsElement.children[this._getItemIndex(t)];n&&i.default(n).addClass(T)}},e._updateInterval=function(){var t=this._activeElement||this._element.querySelector(D);if(t){var e=parseInt(t.getAttribute("data-interval"),10);e?(this._config.defaultInterval=this._config.defaultInterval||this._config.interval,this._config.interval=e):this._config.interval=this._config.defaultInterval||this._config.interval}},e._slide=function(t,e){var n,o,r,a=this,s=this._element.querySelector(D),l=this._getItemIndex(s),f=e||s&&this._getItemByDirection(t,s),d=this._getItemIndex(f),c=Boolean(this._interval);if(t===C?(n="carousel-item-left",o="carousel-item-next",r="left"):(n="carousel-item-right",o="carousel-item-prev",r="right"),f&&i.default(f).hasClass(T))this._isSliding=!1;else if(!this._triggerSlideEvent(f,r).isDefaultPrevented()&&s&&f){this._isSliding=!0,c&&this.pause(),this._setActiveIndicatorElement(f),this._activeElement=f;var h=i.default.Event(N,{relatedTarget:f,direction:r,from:l,to:d});if(i.default(this._element).hasClass("slide")){i.default(f).addClass(o),u.reflow(f),i.default(s).addClass(n),i.default(f).addClass(n);var p=u.getTransitionDurationFromElement(s);i.default(s).one(u.TRANSITION_END,(function(){i.default(f).removeClass(n+" "+o).addClass(T),i.default(s).removeClass("active "+o+" "+n),a._isSliding=!1,setTimeout((function(){return i.default(a._element).trigger(h)}),0)})).emulateTransitionEnd(p)}else i.default(s).removeClass(T),i.default(f).addClass(T),this._isSliding=!1,i.default(this._element).trigger(h);c&&this.cycle()}},t._jQueryInterface=function(e){return this.each((function(){var n=i.default(this).data(E),o=a({},A,i.default(this).data());"object"==typeof e&&(o=a({},o,e));var r="string"==typeof e?e:o.slide;if(n||(n=new t(this,o),i.default(this).data(E,n)),"number"==typeof e)n.to(e);else if("string"==typeof r){if("undefined"==typeof n[r])throw new TypeError('No method named "'+r+'"');n[r]()}else o.interval&&o.ride&&(n.pause(),n.cycle())}))},t._dataApiClickHandler=function(e){var n=u.getSelectorFromElement(this);if(n){var o=i.default(n)[0];if(o&&i.default(o).hasClass("carousel")){var r=a({},i.default(o).data(),i.default(this).data()),s=this.getAttribute("data-slide-to");s&&(r.interval=!1),t._jQueryInterface.call(i.default(o),r),s&&i.default(o).data(E).to(s),e.preventDefault()}}},r(t,null,[{key:"VERSION",get:function(){return"4.6.1"}},{key:"Default",get:function(){return A}}]),t}();i.default(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",O._dataApiClickHandler),i.default(window).on("load.bs.carousel.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-ride="carousel"]')),e=0,n=t.length;e<n;e++){var o=i.default(t[e]);O._jQueryInterface.call(o,o.data())}})),i.default.fn[y]=O._jQueryInterface,i.default.fn[y].Constructor=O,i.default.fn[y].noConflict=function(){return i.default.fn[y]=w,O._jQueryInterface};var x="collapse",j="bs.collapse",L=i.default.fn[x],P="show",F="collapse",R="collapsing",H="collapsed",M="width",q='[data-toggle="collapse"]',B={toggle:!0,parent:""},Q={toggle:"boolean",parent:"(string|element)"},W=function(){function t(t,e){this._isTransitioning=!1,this._element=t,this._config=this._getConfig(e),this._triggerArray=[].slice.call(document.querySelectorAll('[data-toggle="collapse"][href="#'+t.id+'"],[data-toggle="collapse"][data-target="#'+t.id+'"]'));for(var n=[].slice.call(document.querySelectorAll(q)),i=0,o=n.length;i<o;i++){var r=n[i],a=u.getSelectorFromElement(r),s=[].slice.call(document.querySelectorAll(a)).filter((function(e){return e===t}));null!==a&&s.length>0&&(this._selector=a,this._triggerArray.push(r))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var e=t.prototype;return e.toggle=function(){i.default(this._element).hasClass(P)?this.hide():this.show()},e.show=function(){var e,n,o=this;if(!(this._isTransitioning||i.default(this._element).hasClass(P)||(this._parent&&0===(e=[].slice.call(this._parent.querySelectorAll(".show, .collapsing")).filter((function(t){return"string"==typeof o._config.parent?t.getAttribute("data-parent")===o._config.parent:t.classList.contains(F)}))).length&&(e=null),e&&(n=i.default(e).not(this._selector).data(j))&&n._isTransitioning))){var r=i.default.Event("show.bs.collapse");if(i.default(this._element).trigger(r),!r.isDefaultPrevented()){e&&(t._jQueryInterface.call(i.default(e).not(this._selector),"hide"),n||i.default(e).data(j,null));var a=this._getDimension();i.default(this._element).removeClass(F).addClass(R),this._element.style[a]=0,this._triggerArray.length&&i.default(this._triggerArray).removeClass(H).attr("aria-expanded",!0),this.setTransitioning(!0);var s="scroll"+(a[0].toUpperCase()+a.slice(1)),l=u.getTransitionDurationFromElement(this._element);i.default(this._element).one(u.TRANSITION_END,(function(){i.default(o._element).removeClass(R).addClass("collapse show"),o._element.style[a]="",o.setTransitioning(!1),i.default(o._element).trigger("shown.bs.collapse")})).emulateTransitionEnd(l),this._element.style[a]=this._element[s]+"px"}}},e.hide=function(){var t=this;if(!this._isTransitioning&&i.default(this._element).hasClass(P)){var e=i.default.Event("hide.bs.collapse");if(i.default(this._element).trigger(e),!e.isDefaultPrevented()){var n=this._getDimension();this._element.style[n]=this._element.getBoundingClientRect()[n]+"px",u.reflow(this._element),i.default(this._element).addClass(R).removeClass("collapse show");var o=this._triggerArray.length;if(o>0)for(var r=0;r<o;r++){var a=this._triggerArray[r],s=u.getSelectorFromElement(a);null!==s&&(i.default([].slice.call(document.querySelectorAll(s))).hasClass(P)||i.default(a).addClass(H).attr("aria-expanded",!1))}this.setTransitioning(!0),this._element.style[n]="";var l=u.getTransitionDurationFromElement(this._element);i.default(this._element).one(u.TRANSITION_END,(function(){t.setTransitioning(!1),i.default(t._element).removeClass(R).addClass(F).trigger("hidden.bs.collapse")})).emulateTransitionEnd(l)}}},e.setTransitioning=function(t){this._isTransitioning=t},e.dispose=function(){i.default.removeData(this._element,j),this._config=null,this._parent=null,this._element=null,this._triggerArray=null,this._isTransitioning=null},e._getConfig=function(t){return(t=a({},B,t)).toggle=Boolean(t.toggle),u.typeCheckConfig(x,t,Q),t},e._getDimension=function(){return i.default(this._element).hasClass(M)?M:"height"},e._getParent=function(){var e,n=this;u.isElement(this._config.parent)?(e=this._config.parent,"undefined"!=typeof this._config.parent.jquery&&(e=this._config.parent[0])):e=document.querySelector(this._config.parent);var o='[data-toggle="collapse"][data-parent="'+this._config.parent+'"]',r=[].slice.call(e.querySelectorAll(o));return i.default(r).each((function(e,i){n._addAriaAndCollapsedClass(t._getTargetFromElement(i),[i])})),e},e._addAriaAndCollapsedClass=function(t,e){var n=i.default(t).hasClass(P);e.length&&i.default(e).toggleClass(H,!n).attr("aria-expanded",n)},t._getTargetFromElement=function(t){var e=u.getSelectorFromElement(t);return e?document.querySelector(e):null},t._jQueryInterface=function(e){return this.each((function(){var n=i.default(this),o=n.data(j),r=a({},B,n.data(),"object"==typeof e&&e?e:{});if(!o&&r.toggle&&"string"==typeof e&&/show|hide/.test(e)&&(r.toggle=!1),o||(o=new t(this,r),n.data(j,o)),"string"==typeof e){if("undefined"==typeof o[e])throw new TypeError('No method named "'+e+'"');o[e]()}}))},r(t,null,[{key:"VERSION",get:function(){return"4.6.1"}},{key:"Default",get:function(){return B}}]),t}();i.default(document).on("click.bs.collapse.data-api",q,(function(t){"A"===t.currentTarget.tagName&&t.preventDefault();var e=i.default(this),n=u.getSelectorFromElement(this),o=[].slice.call(document.querySelectorAll(n));i.default(o).each((function(){var t=i.default(this),n=t.data(j)?"toggle":e.data();W._jQueryInterface.call(t,n)}))})),i.default.fn[x]=W._jQueryInterface,i.default.fn[x].Constructor=W,i.default.fn[x].noConflict=function(){return i.default.fn[x]=L,W._jQueryInterface};var U="undefined"!=typeof window&&"undefined"!=typeof document&&"undefined"!=typeof navigator,V=function(){for(var t=["Edge","Trident","Firefox"],e=0;e<t.length;e+=1)if(U&&navigator.userAgent.indexOf(t[e])>=0)return 1;return 0}(),Y=U&&window.Promise?function(t){var e=!1;return function(){e||(e=!0,window.Promise.resolve().then((function(){e=!1,t()})))}}:function(t){var e=!1;return function(){e||(e=!0,setTimeout((function(){e=!1,t()}),V))}};function z(t){return t&&"[object Function]"==={}.toString.call(t)}function K(t,e){if(1!==t.nodeType)return[];var n=t.ownerDocument.defaultView.getComputedStyle(t,null);return e?n[e]:n}function X(t){return"HTML"===t.nodeName?t:t.parentNode||t.host}function G(t){if(!t)return document.body;switch(t.nodeName){case"HTML":case"BODY":return t.ownerDocument.body;case"#document":return t.body}var e=K(t),n=e.overflow,i=e.overflowX,o=e.overflowY;return/(auto|scroll|overlay)/.test(n+o+i)?t:G(X(t))}function $(t){return t&&t.referenceNode?t.referenceNode:t}var J=U&&!(!window.MSInputMethodContext||!document.documentMode),Z=U&&/MSIE 10/.test(navigator.userAgent);function tt(t){return 11===t?J:10===t?Z:J||Z}function et(t){if(!t)return document.documentElement;for(var e=tt(10)?document.body:null,n=t.offsetParent||null;n===e&&t.nextElementSibling;)n=(t=t.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&"BODY"!==i&&"HTML"!==i?-1!==["TH","TD","TABLE"].indexOf(n.nodeName)&&"static"===K(n,"position")?et(n):n:t?t.ownerDocument.documentElement:document.documentElement}function nt(t){return null!==t.parentNode?nt(t.parentNode):t}function it(t,e){if(!(t&&t.nodeType&&e&&e.nodeType))return document.documentElement;var n=t.compareDocumentPosition(e)&Node.DOCUMENT_POSITION_FOLLOWING,i=n?t:e,o=n?e:t,r=document.createRange();r.setStart(i,0),r.setEnd(o,0);var a,s,l=r.commonAncestorContainer;if(t!==l&&e!==l||i.contains(o))return"BODY"===(s=(a=l).nodeName)||"HTML"!==s&&et(a.firstElementChild)!==a?et(l):l;var u=nt(t);return u.host?it(u.host,e):it(t,nt(e).host)}function ot(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"top",n="top"===e?"scrollTop":"scrollLeft",i=t.nodeName;if("BODY"===i||"HTML"===i){var o=t.ownerDocument.documentElement,r=t.ownerDocument.scrollingElement||o;return r[n]}return t[n]}function rt(t,e){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],i=ot(e,"top"),o=ot(e,"left"),r=n?-1:1;return t.top+=i*r,t.bottom+=i*r,t.left+=o*r,t.right+=o*r,t}function at(t,e){var n="x"===e?"Left":"Top",i="Left"===n?"Right":"Bottom";return parseFloat(t["border"+n+"Width"])+parseFloat(t["border"+i+"Width"])}function st(t,e,n,i){return Math.max(e["offset"+t],e["scroll"+t],n["client"+t],n["offset"+t],n["scroll"+t],tt(10)?parseInt(n["offset"+t])+parseInt(i["margin"+("Height"===t?"Top":"Left")])+parseInt(i["margin"+("Height"===t?"Bottom":"Right")]):0)}function lt(t){var e=t.body,n=t.documentElement,i=tt(10)&&getComputedStyle(n);return{height:st("Height",e,n,i),width:st("Width",e,n,i)}}var ut=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")},ft=function(){function t(t,e){for(var n=0;n<e.length;n++){var i=e[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,i.key,i)}}return function(e,n,i){return n&&t(e.prototype,n),i&&t(e,i),e}}(),dt=function(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t},ct=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(t[i]=n[i])}return t};function ht(t){return ct({},t,{right:t.left+t.width,bottom:t.top+t.height})}function pt(t){var e={};try{if(tt(10)){e=t.getBoundingClientRect();var n=ot(t,"top"),i=ot(t,"left");e.top+=n,e.left+=i,e.bottom+=n,e.right+=i}else e=t.getBoundingClientRect()}catch(t){}var o={left:e.left,top:e.top,width:e.right-e.left,height:e.bottom-e.top},r="HTML"===t.nodeName?lt(t.ownerDocument):{},a=r.width||t.clientWidth||o.width,s=r.height||t.clientHeight||o.height,l=t.offsetWidth-a,u=t.offsetHeight-s;if(l||u){var f=K(t);l-=at(f,"x"),u-=at(f,"y"),o.width-=l,o.height-=u}return ht(o)}function mt(t,e){var n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],i=tt(10),o="HTML"===e.nodeName,r=pt(t),a=pt(e),s=G(t),l=K(e),u=parseFloat(l.borderTopWidth),f=parseFloat(l.borderLeftWidth);n&&o&&(a.top=Math.max(a.top,0),a.left=Math.max(a.left,0));var d=ht({top:r.top-a.top-u,left:r.left-a.left-f,width:r.width,height:r.height});if(d.marginTop=0,d.marginLeft=0,!i&&o){var c=parseFloat(l.marginTop),h=parseFloat(l.marginLeft);d.top-=u-c,d.bottom-=u-c,d.left-=f-h,d.right-=f-h,d.marginTop=c,d.marginLeft=h}return(i&&!n?e.contains(s):e===s&&"BODY"!==s.nodeName)&&(d=rt(d,e)),d}function gt(t){var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=t.ownerDocument.documentElement,i=mt(t,n),o=Math.max(n.clientWidth,window.innerWidth||0),r=Math.max(n.clientHeight,window.innerHeight||0),a=e?0:ot(n),s=e?0:ot(n,"left"),l={top:a-i.top+i.marginTop,left:s-i.left+i.marginLeft,width:o,height:r};return ht(l)}function _t(t){var e=t.nodeName;if("BODY"===e||"HTML"===e)return!1;if("fixed"===K(t,"position"))return!0;var n=X(t);return!!n&&_t(n)}function vt(t){if(!t||!t.parentElement||tt())return document.documentElement;for(var e=t.parentElement;e&&"none"===K(e,"transform");)e=e.parentElement;return e||document.documentElement}function bt(t,e,n,i){var o=arguments.length>4&&void 0!==arguments[4]&&arguments[4],r={top:0,left:0},a=o?vt(t):it(t,$(e));if("viewport"===i)r=gt(a,o);else{var s=void 0;"scrollParent"===i?"BODY"===(s=G(X(e))).nodeName&&(s=t.ownerDocument.documentElement):s="window"===i?t.ownerDocument.documentElement:i;var l=mt(s,a,o);if("HTML"!==s.nodeName||_t(a))r=l;else{var u=lt(t.ownerDocument),f=u.height,d=u.width;r.top+=l.top-l.marginTop,r.bottom=f+l.top,r.left+=l.left-l.marginLeft,r.right=d+l.left}}var c="number"==typeof(n=n||0);return r.left+=c?n:n.left||0,r.top+=c?n:n.top||0,r.right-=c?n:n.right||0,r.bottom-=c?n:n.bottom||0,r}function yt(t){return t.width*t.height}function Et(t,e,n,i,o){var r=arguments.length>5&&void 0!==arguments[5]?arguments[5]:0;if(-1===t.indexOf("auto"))return t;var a=bt(n,i,r,o),s={top:{width:a.width,height:e.top-a.top},right:{width:a.right-e.right,height:a.height},bottom:{width:a.width,height:a.bottom-e.bottom},left:{width:e.left-a.left,height:a.height}},l=Object.keys(s).map((function(t){return ct({key:t},s[t],{area:yt(s[t])})})).sort((function(t,e){return e.area-t.area})),u=l.filter((function(t){var e=t.width,i=t.height;return e>=n.clientWidth&&i>=n.clientHeight})),f=u.length>0?u[0].key:l[0].key,d=t.split("-")[1];return f+(d?"-"+d:"")}function wt(t,e,n){var i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,o=i?vt(e):it(e,$(n));return mt(n,o,i)}function Tt(t){var e=t.ownerDocument.defaultView.getComputedStyle(t),n=parseFloat(e.marginTop||0)+parseFloat(e.marginBottom||0),i=parseFloat(e.marginLeft||0)+parseFloat(e.marginRight||0);return{width:t.offsetWidth+i,height:t.offsetHeight+n}}function Ct(t){var e={left:"right",right:"left",bottom:"top",top:"bottom"};return t.replace(/left|right|bottom|top/g,(function(t){return e[t]}))}function St(t,e,n){n=n.split("-")[0];var i=Tt(t),o={width:i.width,height:i.height},r=-1!==["right","left"].indexOf(n),a=r?"top":"left",s=r?"left":"top",l=r?"height":"width",u=r?"width":"height";return o[a]=e[a]+e[l]/2-i[l]/2,o[s]=n===s?e[s]-i[u]:e[Ct(s)],o}function Nt(t,e){return Array.prototype.find?t.find(e):t.filter(e)[0]}function Dt(t,e,n){return(void 0===n?t:t.slice(0,function(t,e,n){if(Array.prototype.findIndex)return t.findIndex((function(t){return t.name===n}));var i=Nt(t,(function(t){return t.name===n}));return t.indexOf(i)}(t,0,n))).forEach((function(t){t.function&&console.warn("`modifier.function` is deprecated, use `modifier.fn`!");var n=t.function||t.fn;t.enabled&&z(n)&&(e.offsets.popper=ht(e.offsets.popper),e.offsets.reference=ht(e.offsets.reference),e=n(e,t))})),e}function At(){if(!this.state.isDestroyed){var t={instance:this,styles:{},arrowStyles:{},attributes:{},flipped:!1,offsets:{}};t.offsets.reference=wt(this.state,this.popper,this.reference,this.options.positionFixed),t.placement=Et(this.options.placement,t.offsets.reference,this.popper,this.reference,this.options.modifiers.flip.boundariesElement,this.options.modifiers.flip.padding),t.originalPlacement=t.placement,t.positionFixed=this.options.positionFixed,t.offsets.popper=St(this.popper,t.offsets.reference,t.placement),t.offsets.popper.position=this.options.positionFixed?"fixed":"absolute",t=Dt(this.modifiers,t),this.state.isCreated?this.options.onUpdate(t):(this.state.isCreated=!0,this.options.onCreate(t))}}function kt(t,e){return t.some((function(t){var n=t.name;return t.enabled&&n===e}))}function It(t){for(var e=[!1,"ms","Webkit","Moz","O"],n=t.charAt(0).toUpperCase()+t.slice(1),i=0;i<e.length;i++){var o=e[i],r=o?""+o+n:t;if("undefined"!=typeof document.body.style[r])return r}return null}function Ot(){return this.state.isDestroyed=!0,kt(this.modifiers,"applyStyle")&&(this.popper.removeAttribute("x-placement"),this.popper.style.position="",this.popper.style.top="",this.popper.style.left="",this.popper.style.right="",this.popper.style.bottom="",this.popper.style.willChange="",this.popper.style[It("transform")]=""),this.disableEventListeners(),this.options.removeOnDestroy&&this.popper.parentNode.removeChild(this.popper),this}function xt(t){var e=t.ownerDocument;return e?e.defaultView:window}function jt(t,e,n,i){var o="BODY"===t.nodeName,r=o?t.ownerDocument.defaultView:t;r.addEventListener(e,n,{passive:!0}),o||jt(G(r.parentNode),e,n,i),i.push(r)}function Lt(t,e,n,i){n.updateBound=i,xt(t).addEventListener("resize",n.updateBound,{passive:!0});var o=G(t);return jt(o,"scroll",n.updateBound,n.scrollParents),n.scrollElement=o,n.eventsEnabled=!0,n}function Pt(){this.state.eventsEnabled||(this.state=Lt(this.reference,this.options,this.state,this.scheduleUpdate))}function Ft(){var t,e;this.state.eventsEnabled&&(cancelAnimationFrame(this.scheduleUpdate),this.state=(t=this.reference,e=this.state,xt(t).removeEventListener("resize",e.updateBound),e.scrollParents.forEach((function(t){t.removeEventListener("scroll",e.updateBound)})),e.updateBound=null,e.scrollParents=[],e.scrollElement=null,e.eventsEnabled=!1,e))}function Rt(t){return""!==t&&!isNaN(parseFloat(t))&&isFinite(t)}function Ht(t,e){Object.keys(e).forEach((function(n){var i="";-1!==["width","height","top","right","bottom","left"].indexOf(n)&&Rt(e[n])&&(i="px"),t.style[n]=e[n]+i}))}var Mt=U&&/Firefox/i.test(navigator.userAgent);function qt(t,e,n){var i=Nt(t,(function(t){return t.name===e})),o=!!i&&t.some((function(t){return t.name===n&&t.enabled&&t.order<i.order}));if(!o){var r="`"+e+"`",a="`"+n+"`";console.warn(a+" modifier is required by "+r+" modifier in order to work, be sure to include it before "+r+"!")}return o}var Bt=["auto-start","auto","auto-end","top-start","top","top-end","right-start","right","right-end","bottom-end","bottom","bottom-start","left-end","left","left-start"],Qt=Bt.slice(3);function Wt(t){var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=Qt.indexOf(t),i=Qt.slice(n+1).concat(Qt.slice(0,n));return e?i.reverse():i}var Ut={placement:"bottom",positionFixed:!1,eventsEnabled:!0,removeOnDestroy:!1,onCreate:function(){},onUpdate:function(){},modifiers:{shift:{order:100,enabled:!0,fn:function(t){var e=t.placement,n=e.split("-")[0],i=e.split("-")[1];if(i){var o=t.offsets,r=o.reference,a=o.popper,s=-1!==["bottom","top"].indexOf(n),l=s?"left":"top",u=s?"width":"height",f={start:dt({},l,r[l]),end:dt({},l,r[l]+r[u]-a[u])};t.offsets.popper=ct({},a,f[i])}return t}},offset:{order:200,enabled:!0,fn:function(t,e){var n,i=e.offset,o=t.placement,r=t.offsets,a=r.popper,s=r.reference,l=o.split("-")[0];return n=Rt(+i)?[+i,0]:function(t,e,n,i){var o=[0,0],r=-1!==["right","left"].indexOf(i),a=t.split(/(\+|\-)/).map((function(t){return t.trim()})),s=a.indexOf(Nt(a,(function(t){return-1!==t.search(/,|\s/)})));a[s]&&-1===a[s].indexOf(",")&&console.warn("Offsets separated by white space(s) are deprecated, use a comma (,) instead.");var l=/\s*,\s*|\s+/,u=-1!==s?[a.slice(0,s).concat([a[s].split(l)[0]]),[a[s].split(l)[1]].concat(a.slice(s+1))]:[a];return u=u.map((function(t,i){var o=(1===i?!r:r)?"height":"width",a=!1;return t.reduce((function(t,e){return""===t[t.length-1]&&-1!==["+","-"].indexOf(e)?(t[t.length-1]=e,a=!0,t):a?(t[t.length-1]+=e,a=!1,t):t.concat(e)}),[]).map((function(t){return function(t,e,n,i){var o=t.match(/((?:\-|\+)?\d*\.?\d*)(.*)/),r=+o[1],a=o[2];return r?0===a.indexOf("%")?ht("%p"===a?n:i)[e]/100*r:"vh"===a||"vw"===a?("vh"===a?Math.max(document.documentElement.clientHeight,window.innerHeight||0):Math.max(document.documentElement.clientWidth,window.innerWidth||0))/100*r:r:t}(t,o,e,n)}))})),u.forEach((function(t,e){t.forEach((function(n,i){Rt(n)&&(o[e]+=n*("-"===t[i-1]?-1:1))}))})),o}(i,a,s,l),"left"===l?(a.top+=n[0],a.left-=n[1]):"right"===l?(a.top+=n[0],a.left+=n[1]):"top"===l?(a.left+=n[0],a.top-=n[1]):"bottom"===l&&(a.left+=n[0],a.top+=n[1]),t.popper=a,t},offset:0},preventOverflow:{order:300,enabled:!0,fn:function(t,e){var n=e.boundariesElement||et(t.instance.popper);t.instance.reference===n&&(n=et(n));var i=It("transform"),o=t.instance.popper.style,r=o.top,a=o.left,s=o[i];o.top="",o.left="",o[i]="";var l=bt(t.instance.popper,t.instance.reference,e.padding,n,t.positionFixed);o.top=r,o.left=a,o[i]=s,e.boundaries=l;var u=e.priority,f=t.offsets.popper,d={primary:function(t){var n=f[t];return f[t]<l[t]&&!e.escapeWithReference&&(n=Math.max(f[t],l[t])),dt({},t,n)},secondary:function(t){var n="right"===t?"left":"top",i=f[n];return f[t]>l[t]&&!e.escapeWithReference&&(i=Math.min(f[n],l[t]-("right"===t?f.width:f.height))),dt({},n,i)}};return u.forEach((function(t){var e=-1!==["left","top"].indexOf(t)?"primary":"secondary";f=ct({},f,d[e](t))})),t.offsets.popper=f,t},priority:["left","right","top","bottom"],padding:5,boundariesElement:"scrollParent"},keepTogether:{order:400,enabled:!0,fn:function(t){var e=t.offsets,n=e.popper,i=e.reference,o=t.placement.split("-")[0],r=Math.floor,a=-1!==["top","bottom"].indexOf(o),s=a?"right":"bottom",l=a?"left":"top",u=a?"width":"height";return n[s]<r(i[l])&&(t.offsets.popper[l]=r(i[l])-n[u]),n[l]>r(i[s])&&(t.offsets.popper[l]=r(i[s])),t}},arrow:{order:500,enabled:!0,fn:function(t,e){var n;if(!qt(t.instance.modifiers,"arrow","keepTogether"))return t;var i=e.element;if("string"==typeof i){if(!(i=t.instance.popper.querySelector(i)))return t}else if(!t.instance.popper.contains(i))return console.warn("WARNING: `arrow.element` must be child of its popper element!"),t;var o=t.placement.split("-")[0],r=t.offsets,a=r.popper,s=r.reference,l=-1!==["left","right"].indexOf(o),u=l?"height":"width",f=l?"Top":"Left",d=f.toLowerCase(),c=l?"left":"top",h=l?"bottom":"right",p=Tt(i)[u];s[h]-p<a[d]&&(t.offsets.popper[d]-=a[d]-(s[h]-p)),s[d]+p>a[h]&&(t.offsets.popper[d]+=s[d]+p-a[h]),t.offsets.popper=ht(t.offsets.popper);var m=s[d]+s[u]/2-p/2,g=K(t.instance.popper),_=parseFloat(g["margin"+f]),v=parseFloat(g["border"+f+"Width"]),b=m-t.offsets.popper[d]-_-v;return b=Math.max(Math.min(a[u]-p,b),0),t.arrowElement=i,t.offsets.arrow=(dt(n={},d,Math.round(b)),dt(n,c,""),n),t},element:"[x-arrow]"},flip:{order:600,enabled:!0,fn:function(t,e){if(kt(t.instance.modifiers,"inner"))return t;if(t.flipped&&t.placement===t.originalPlacement)return t;var n=bt(t.instance.popper,t.instance.reference,e.padding,e.boundariesElement,t.positionFixed),i=t.placement.split("-")[0],o=Ct(i),r=t.placement.split("-")[1]||"",a=[];switch(e.behavior){case"flip":a=[i,o];break;case"clockwise":a=Wt(i);break;case"counterclockwise":a=Wt(i,!0);break;default:a=e.behavior}return a.forEach((function(s,l){if(i!==s||a.length===l+1)return t;i=t.placement.split("-")[0],o=Ct(i);var u=t.offsets.popper,f=t.offsets.reference,d=Math.floor,c="left"===i&&d(u.right)>d(f.left)||"right"===i&&d(u.left)<d(f.right)||"top"===i&&d(u.bottom)>d(f.top)||"bottom"===i&&d(u.top)<d(f.bottom),h=d(u.left)<d(n.left),p=d(u.right)>d(n.right),m=d(u.top)<d(n.top),g=d(u.bottom)>d(n.bottom),_="left"===i&&h||"right"===i&&p||"top"===i&&m||"bottom"===i&&g,v=-1!==["top","bottom"].indexOf(i),b=!!e.flipVariations&&(v&&"start"===r&&h||v&&"end"===r&&p||!v&&"start"===r&&m||!v&&"end"===r&&g),y=!!e.flipVariationsByContent&&(v&&"start"===r&&p||v&&"end"===r&&h||!v&&"start"===r&&g||!v&&"end"===r&&m),E=b||y;(c||_||E)&&(t.flipped=!0,(c||_)&&(i=a[l+1]),E&&(r=function(t){return"end"===t?"start":"start"===t?"end":t}(r)),t.placement=i+(r?"-"+r:""),t.offsets.popper=ct({},t.offsets.popper,St(t.instance.popper,t.offsets.reference,t.placement)),t=Dt(t.instance.modifiers,t,"flip"))})),t},behavior:"flip",padding:5,boundariesElement:"viewport",flipVariations:!1,flipVariationsByContent:!1},inner:{order:700,enabled:!1,fn:function(t){var e=t.placement,n=e.split("-")[0],i=t.offsets,o=i.popper,r=i.reference,a=-1!==["left","right"].indexOf(n),s=-1===["top","left"].indexOf(n);return o[a?"left":"top"]=r[n]-(s?o[a?"width":"height"]:0),t.placement=Ct(e),t.offsets.popper=ht(o),t}},hide:{order:800,enabled:!0,fn:function(t){if(!qt(t.instance.modifiers,"hide","preventOverflow"))return t;var e=t.offsets.reference,n=Nt(t.instance.modifiers,(function(t){return"preventOverflow"===t.name})).boundaries;if(e.bottom<n.top||e.left>n.right||e.top>n.bottom||e.right<n.left){if(!0===t.hide)return t;t.hide=!0,t.attributes["x-out-of-boundaries"]=""}else{if(!1===t.hide)return t;t.hide=!1,t.attributes["x-out-of-boundaries"]=!1}return t}},computeStyle:{order:850,enabled:!0,fn:function(t,e){var n=e.x,i=e.y,o=t.offsets.popper,r=Nt(t.instance.modifiers,(function(t){return"applyStyle"===t.name})).gpuAcceleration;void 0!==r&&console.warn("WARNING: `gpuAcceleration` option moved to `computeStyle` modifier and will not be supported in future versions of Popper.js!");var a,s,l=void 0!==r?r:e.gpuAcceleration,u=et(t.instance.popper),f=pt(u),d={position:o.position},c=function(t,e){var n=t.offsets,i=n.popper,o=n.reference,r=Math.round,a=Math.floor,s=function(t){return t},l=r(o.width),u=r(i.width),f=-1!==["left","right"].indexOf(t.placement),d=-1!==t.placement.indexOf("-"),c=e?f||d||l%2==u%2?r:a:s,h=e?r:s;return{left:c(l%2==1&&u%2==1&&!d&&e?i.left-1:i.left),top:h(i.top),bottom:h(i.bottom),right:c(i.right)}}(t,window.devicePixelRatio<2||!Mt),h="bottom"===n?"top":"bottom",p="right"===i?"left":"right",m=It("transform");if(s="bottom"===h?"HTML"===u.nodeName?-u.clientHeight+c.bottom:-f.height+c.bottom:c.top,a="right"===p?"HTML"===u.nodeName?-u.clientWidth+c.right:-f.width+c.right:c.left,l&&m)d[m]="translate3d("+a+"px, "+s+"px, 0)",d[h]=0,d[p]=0,d.willChange="transform";else{var g="bottom"===h?-1:1,_="right"===p?-1:1;d[h]=s*g,d[p]=a*_,d.willChange=h+", "+p}var v={"x-placement":t.placement};return t.attributes=ct({},v,t.attributes),t.styles=ct({},d,t.styles),t.arrowStyles=ct({},t.offsets.arrow,t.arrowStyles),t},gpuAcceleration:!0,x:"bottom",y:"right"},applyStyle:{order:900,enabled:!0,fn:function(t){var e,n;return Ht(t.instance.popper,t.styles),e=t.instance.popper,n=t.attributes,Object.keys(n).forEach((function(t){!1!==n[t]?e.setAttribute(t,n[t]):e.removeAttribute(t)})),t.arrowElement&&Object.keys(t.arrowStyles).length&&Ht(t.arrowElement,t.arrowStyles),t},onLoad:function(t,e,n,i,o){var r=wt(o,e,t,n.positionFixed),a=Et(n.placement,r,e,t,n.modifiers.flip.boundariesElement,n.modifiers.flip.padding);return e.setAttribute("x-placement",a),Ht(e,{position:n.positionFixed?"fixed":"absolute"}),n},gpuAcceleration:void 0}}},Vt=function(){function t(e,n){var i=this,o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};ut(this,t),this.scheduleUpdate=function(){return requestAnimationFrame(i.update)},this.update=Y(this.update.bind(this)),this.options=ct({},t.Defaults,o),this.state={isDestroyed:!1,isCreated:!1,scrollParents:[]},this.reference=e&&e.jquery?e[0]:e,this.popper=n&&n.jquery?n[0]:n,this.options.modifiers={},Object.keys(ct({},t.Defaults.modifiers,o.modifiers)).forEach((function(e){i.options.modifiers[e]=ct({},t.Defaults.modifiers[e]||{},o.modifiers?o.modifiers[e]:{})})),this.modifiers=Object.keys(this.options.modifiers).map((function(t){return ct({name:t},i.options.modifiers[t])})).sort((function(t,e){return t.order-e.order})),this.modifiers.forEach((function(t){t.enabled&&z(t.onLoad)&&t.onLoad(i.reference,i.popper,i.options,t,i.state)})),this.update();var r=this.options.eventsEnabled;r&&this.enableEventListeners(),this.state.eventsEnabled=r}return ft(t,[{key:"update",value:function(){return At.call(this)}},{key:"destroy",value:function(){return Ot.call(this)}},{key:"enableEventListeners",value:function(){return Pt.call(this)}},{key:"disableEventListeners",value:function(){return Ft.call(this)}}]),t}();Vt.Utils=("undefined"!=typeof window?window:global).PopperUtils,Vt.placements=Bt,Vt.Defaults=Ut;var Yt=Vt,zt="dropdown",Kt="bs.dropdown",Xt=i.default.fn[zt],Gt=new RegExp("38|40|27"),$t="disabled",Jt="show",Zt="dropdown-menu-right",te="hide.bs.dropdown",ee="hidden.bs.dropdown",ne="click.bs.dropdown.data-api",ie="keydown.bs.dropdown.data-api",oe='[data-toggle="dropdown"]',re=".dropdown-menu",ae={offset:0,flip:!0,boundary:"scrollParent",reference:"toggle",display:"dynamic",popperConfig:null},se={offset:"(number|string|function)",flip:"boolean",boundary:"(string|element)",reference:"(string|element)",display:"string",popperConfig:"(null|object)"},le=function(){function t(t,e){this._element=t,this._popper=null,this._config=this._getConfig(e),this._menu=this._getMenuElement(),this._inNavbar=this._detectNavbar(),this._addEventListeners()}var e=t.prototype;return e.toggle=function(){if(!this._element.disabled&&!i.default(this._element).hasClass($t)){var e=i.default(this._menu).hasClass(Jt);t._clearMenus(),e||this.show(!0)}},e.show=function(e){if(void 0===e&&(e=!1),!(this._element.disabled||i.default(this._element).hasClass($t)||i.default(this._menu).hasClass(Jt))){var n={relatedTarget:this._element},o=i.default.Event("show.bs.dropdown",n),r=t._getParentFromElement(this._element);if(i.default(r).trigger(o),!o.isDefaultPrevented()){if(!this._inNavbar&&e){if("undefined"==typeof Yt)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");var a=this._element;"parent"===this._config.reference?a=r:u.isElement(this._config.reference)&&(a=this._config.reference,"undefined"!=typeof this._config.reference.jquery&&(a=this._config.reference[0])),"scrollParent"!==this._config.boundary&&i.default(r).addClass("position-static"),this._popper=new Yt(a,this._menu,this._getPopperConfig())}"ontouchstart"in document.documentElement&&0===i.default(r).closest(".navbar-nav").length&&i.default(document.body).children().on("mouseover",null,i.default.noop),this._element.focus(),this._element.setAttribute("aria-expanded",!0),i.default(this._menu).toggleClass(Jt),i.default(r).toggleClass(Jt).trigger(i.default.Event("shown.bs.dropdown",n))}}},e.hide=function(){if(!this._element.disabled&&!i.default(this._element).hasClass($t)&&i.default(this._menu).hasClass(Jt)){var e={relatedTarget:this._element},n=i.default.Event(te,e),o=t._getParentFromElement(this._element);i.default(o).trigger(n),n.isDefaultPrevented()||(this._popper&&this._popper.destroy(),i.default(this._menu).toggleClass(Jt),i.default(o).toggleClass(Jt).trigger(i.default.Event(ee,e)))}},e.dispose=function(){i.default.removeData(this._element,Kt),i.default(this._element).off(".bs.dropdown"),this._element=null,this._menu=null,null!==this._popper&&(this._popper.destroy(),this._popper=null)},e.update=function(){this._inNavbar=this._detectNavbar(),null!==this._popper&&this._popper.scheduleUpdate()},e._addEventListeners=function(){var t=this;i.default(this._element).on("click.bs.dropdown",(function(e){e.preventDefault(),e.stopPropagation(),t.toggle()}))},e._getConfig=function(t){return t=a({},this.constructor.Default,i.default(this._element).data(),t),u.typeCheckConfig(zt,t,this.constructor.DefaultType),t},e._getMenuElement=function(){if(!this._menu){var e=t._getParentFromElement(this._element);e&&(this._menu=e.querySelector(re))}return this._menu},e._getPlacement=function(){var t=i.default(this._element.parentNode),e="bottom-start";return t.hasClass("dropup")?e=i.default(this._menu).hasClass(Zt)?"top-end":"top-start":t.hasClass("dropright")?e="right-start":t.hasClass("dropleft")?e="left-start":i.default(this._menu).hasClass(Zt)&&(e="bottom-end"),e},e._detectNavbar=function(){return i.default(this._element).closest(".navbar").length>0},e._getOffset=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=a({},e.offsets,t._config.offset(e.offsets,t._element)),e}:e.offset=this._config.offset,e},e._getPopperConfig=function(){var t={placement:this._getPlacement(),modifiers:{offset:this._getOffset(),flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}};return"static"===this._config.display&&(t.modifiers.applyStyle={enabled:!1}),a({},t,this._config.popperConfig)},t._jQueryInterface=function(e){return this.each((function(){var n=i.default(this).data(Kt);if(n||(n=new t(this,"object"==typeof e?e:null),i.default(this).data(Kt,n)),"string"==typeof e){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}}))},t._clearMenus=function(e){if(!e||3!==e.which&&("keyup"!==e.type||9===e.which))for(var n=[].slice.call(document.querySelectorAll(oe)),o=0,r=n.length;o<r;o++){var a=t._getParentFromElement(n[o]),s=i.default(n[o]).data(Kt),l={relatedTarget:n[o]};if(e&&"click"===e.type&&(l.clickEvent=e),s){var u=s._menu;if(i.default(a).hasClass(Jt)&&!(e&&("click"===e.type&&/input|textarea/i.test(e.target.tagName)||"keyup"===e.type&&9===e.which)&&i.default.contains(a,e.target))){var f=i.default.Event(te,l);i.default(a).trigger(f),f.isDefaultPrevented()||("ontouchstart"in document.documentElement&&i.default(document.body).children().off("mouseover",null,i.default.noop),n[o].setAttribute("aria-expanded","false"),s._popper&&s._popper.destroy(),i.default(u).removeClass(Jt),i.default(a).removeClass(Jt).trigger(i.default.Event(ee,l)))}}}},t._getParentFromElement=function(t){var e,n=u.getSelectorFromElement(t);return n&&(e=document.querySelector(n)),e||t.parentNode},t._dataApiKeydownHandler=function(e){if(!(/input|textarea/i.test(e.target.tagName)?32===e.which||27!==e.which&&(40!==e.which&&38!==e.which||i.default(e.target).closest(re).length):!Gt.test(e.which))&&!this.disabled&&!i.default(this).hasClass($t)){var n=t._getParentFromElement(this),o=i.default(n).hasClass(Jt);if(o||27!==e.which){if(e.preventDefault(),e.stopPropagation(),!o||27===e.which||32===e.which)return 27===e.which&&i.default(n.querySelector(oe)).trigger("focus"),void i.default(this).trigger("click");var r=[].slice.call(n.querySelectorAll(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)")).filter((function(t){return i.default(t).is(":visible")}));if(0!==r.length){var a=r.indexOf(e.target);38===e.which&&a>0&&a--,40===e.which&&a<r.length-1&&a++,a<0&&(a=0),r[a].focus()}}}},r(t,null,[{key:"VERSION",get:function(){return"4.6.1"}},{key:"Default",get:function(){return ae}},{key:"DefaultType",get:function(){return se}}]),t}();i.default(document).on(ie,oe,le._dataApiKeydownHandler).on(ie,re,le._dataApiKeydownHandler).on(ne+" keyup.bs.dropdown.data-api",le._clearMenus).on(ne,oe,(function(t){t.preventDefault(),t.stopPropagation(),le._jQueryInterface.call(i.default(this),"toggle")})).on(ne,".dropdown form",(function(t){t.stopPropagation()})),i.default.fn[zt]=le._jQueryInterface,i.default.fn[zt].Constructor=le,i.default.fn[zt].noConflict=function(){return i.default.fn[zt]=Xt,le._jQueryInterface};var ue="bs.modal",fe=i.default.fn.modal,de="modal-open",ce="fade",he="show",pe="modal-static",me="hidden.bs.modal",ge="show.bs.modal",_e="focusin.bs.modal",ve="resize.bs.modal",be="click.dismiss.bs.modal",ye="keydown.dismiss.bs.modal",Ee="mousedown.dismiss.bs.modal",we=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",Te={backdrop:!0,keyboard:!0,focus:!0,show:!0},Ce={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean",show:"boolean"},Se=function(){function t(t,e){this._config=this._getConfig(e),this._element=t,this._dialog=t.querySelector(".modal-dialog"),this._backdrop=null,this._isShown=!1,this._isBodyOverflowing=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollbarWidth=0}var e=t.prototype;return e.toggle=function(t){return this._isShown?this.hide():this.show(t)},e.show=function(t){var e=this;if(!this._isShown&&!this._isTransitioning){var n=i.default.Event(ge,{relatedTarget:t});i.default(this._element).trigger(n),n.isDefaultPrevented()||(this._isShown=!0,i.default(this._element).hasClass(ce)&&(this._isTransitioning=!0),this._checkScrollbar(),this._setScrollbar(),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),i.default(this._element).on(be,'[data-dismiss="modal"]',(function(t){return e.hide(t)})),i.default(this._dialog).on(Ee,(function(){i.default(e._element).one("mouseup.dismiss.bs.modal",(function(t){i.default(t.target).is(e._element)&&(e._ignoreBackdropClick=!0)}))})),this._showBackdrop((function(){return e._showElement(t)})))}},e.hide=function(t){var e=this;if(t&&t.preventDefault(),this._isShown&&!this._isTransitioning){var n=i.default.Event("hide.bs.modal");if(i.default(this._element).trigger(n),this._isShown&&!n.isDefaultPrevented()){this._isShown=!1;var o=i.default(this._element).hasClass(ce);if(o&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),i.default(document).off(_e),i.default(this._element).removeClass(he),i.default(this._element).off(be),i.default(this._dialog).off(Ee),o){var r=u.getTransitionDurationFromElement(this._element);i.default(this._element).one(u.TRANSITION_END,(function(t){return e._hideModal(t)})).emulateTransitionEnd(r)}else this._hideModal()}}},e.dispose=function(){[window,this._element,this._dialog].forEach((function(t){return i.default(t).off(".bs.modal")})),i.default(document).off(_e),i.default.removeData(this._element,ue),this._config=null,this._element=null,this._dialog=null,this._backdrop=null,this._isShown=null,this._isBodyOverflowing=null,this._ignoreBackdropClick=null,this._isTransitioning=null,this._scrollbarWidth=null},e.handleUpdate=function(){this._adjustDialog()},e._getConfig=function(t){return t=a({},Te,t),u.typeCheckConfig("modal",t,Ce),t},e._triggerBackdropTransition=function(){var t=this,e=i.default.Event("hidePrevented.bs.modal");if(i.default(this._element).trigger(e),!e.isDefaultPrevented()){var n=this._element.scrollHeight>document.documentElement.clientHeight;n||(this._element.style.overflowY="hidden"),this._element.classList.add(pe);var o=u.getTransitionDurationFromElement(this._dialog);i.default(this._element).off(u.TRANSITION_END),i.default(this._element).one(u.TRANSITION_END,(function(){t._element.classList.remove(pe),n||i.default(t._element).one(u.TRANSITION_END,(function(){t._element.style.overflowY=""})).emulateTransitionEnd(t._element,o)})).emulateTransitionEnd(o),this._element.focus()}},e._showElement=function(t){var e=this,n=i.default(this._element).hasClass(ce),o=this._dialog?this._dialog.querySelector(".modal-body"):null;this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),i.default(this._dialog).hasClass("modal-dialog-scrollable")&&o?o.scrollTop=0:this._element.scrollTop=0,n&&u.reflow(this._element),i.default(this._element).addClass(he),this._config.focus&&this._enforceFocus();var r=i.default.Event("shown.bs.modal",{relatedTarget:t}),a=function(){e._config.focus&&e._element.focus(),e._isTransitioning=!1,i.default(e._element).trigger(r)};if(n){var s=u.getTransitionDurationFromElement(this._dialog);i.default(this._dialog).one(u.TRANSITION_END,a).emulateTransitionEnd(s)}else a()},e._enforceFocus=function(){var t=this;i.default(document).off(_e).on(_e,(function(e){document!==e.target&&t._element!==e.target&&0===i.default(t._element).has(e.target).length&&t._element.focus()}))},e._setEscapeEvent=function(){var t=this;this._isShown?i.default(this._element).on(ye,(function(e){t._config.keyboard&&27===e.which?(e.preventDefault(),t.hide()):t._config.keyboard||27!==e.which||t._triggerBackdropTransition()})):this._isShown||i.default(this._element).off(ye)},e._setResizeEvent=function(){var t=this;this._isShown?i.default(window).on(ve,(function(e){return t.handleUpdate(e)})):i.default(window).off(ve)},e._hideModal=function(){var t=this;this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._showBackdrop((function(){i.default(document.body).removeClass(de),t._resetAdjustments(),t._resetScrollbar(),i.default(t._element).trigger(me)}))},e._removeBackdrop=function(){this._backdrop&&(i.default(this._backdrop).remove(),this._backdrop=null)},e._showBackdrop=function(t){var e=this,n=i.default(this._element).hasClass(ce)?ce:"";if(this._isShown&&this._config.backdrop){if(this._backdrop=document.createElement("div"),this._backdrop.className="modal-backdrop",n&&this._backdrop.classList.add(n),i.default(this._backdrop).appendTo(document.body),i.default(this._element).on(be,(function(t){e._ignoreBackdropClick?e._ignoreBackdropClick=!1:t.target===t.currentTarget&&("static"===e._config.backdrop?e._triggerBackdropTransition():e.hide())})),n&&u.reflow(this._backdrop),i.default(this._backdrop).addClass(he),!t)return;if(!n)return void t();var o=u.getTransitionDurationFromElement(this._backdrop);i.default(this._backdrop).one(u.TRANSITION_END,t).emulateTransitionEnd(o)}else if(!this._isShown&&this._backdrop){i.default(this._backdrop).removeClass(he);var r=function(){e._removeBackdrop(),t&&t()};if(i.default(this._element).hasClass(ce)){var a=u.getTransitionDurationFromElement(this._backdrop);i.default(this._backdrop).one(u.TRANSITION_END,r).emulateTransitionEnd(a)}else r()}else t&&t()},e._adjustDialog=function(){var t=this._element.scrollHeight>document.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},e._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},e._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=Math.round(t.left+t.right)<window.innerWidth,this._scrollbarWidth=this._getScrollbarWidth()},e._setScrollbar=function(){var t=this;if(this._isBodyOverflowing){var e=[].slice.call(document.querySelectorAll(we)),n=[].slice.call(document.querySelectorAll(".sticky-top"));i.default(e).each((function(e,n){var o=n.style.paddingRight,r=i.default(n).css("padding-right");i.default(n).data("padding-right",o).css("padding-right",parseFloat(r)+t._scrollbarWidth+"px")})),i.default(n).each((function(e,n){var o=n.style.marginRight,r=i.default(n).css("margin-right");i.default(n).data("margin-right",o).css("margin-right",parseFloat(r)-t._scrollbarWidth+"px")}));var o=document.body.style.paddingRight,r=i.default(document.body).css("padding-right");i.default(document.body).data("padding-right",o).css("padding-right",parseFloat(r)+this._scrollbarWidth+"px")}i.default(document.body).addClass(de)},e._resetScrollbar=function(){var t=[].slice.call(document.querySelectorAll(we));i.default(t).each((function(t,e){var n=i.default(e).data("padding-right");i.default(e).removeData("padding-right"),e.style.paddingRight=n||""}));var e=[].slice.call(document.querySelectorAll(".sticky-top"));i.default(e).each((function(t,e){var n=i.default(e).data("margin-right");"undefined"!=typeof n&&i.default(e).css("margin-right",n).removeData("margin-right")}));var n=i.default(document.body).data("padding-right");i.default(document.body).removeData("padding-right"),document.body.style.paddingRight=n||""},e._getScrollbarWidth=function(){var t=document.createElement("div");t.className="modal-scrollbar-measure",document.body.appendChild(t);var e=t.getBoundingClientRect().width-t.clientWidth;return document.body.removeChild(t),e},t._jQueryInterface=function(e,n){return this.each((function(){var o=i.default(this).data(ue),r=a({},Te,i.default(this).data(),"object"==typeof e&&e?e:{});if(o||(o=new t(this,r),i.default(this).data(ue,o)),"string"==typeof e){if("undefined"==typeof o[e])throw new TypeError('No method named "'+e+'"');o[e](n)}else r.show&&o.show(n)}))},r(t,null,[{key:"VERSION",get:function(){return"4.6.1"}},{key:"Default",get:function(){return Te}}]),t}();i.default(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',(function(t){var e,n=this,o=u.getSelectorFromElement(this);o&&(e=document.querySelector(o));var r=i.default(e).data(ue)?"toggle":a({},i.default(e).data(),i.default(this).data());"A"!==this.tagName&&"AREA"!==this.tagName||t.preventDefault();var s=i.default(e).one(ge,(function(t){t.isDefaultPrevented()||s.one(me,(function(){i.default(n).is(":visible")&&n.focus()}))}));Se._jQueryInterface.call(i.default(e),r,this)})),i.default.fn.modal=Se._jQueryInterface,i.default.fn.modal.Constructor=Se,i.default.fn.modal.noConflict=function(){return i.default.fn.modal=fe,Se._jQueryInterface};var Ne=["background","cite","href","itemtype","longdesc","poster","src","xlink:href"],De=/^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i,Ae=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i;function ke(t,e,n){if(0===t.length)return t;if(n&&"function"==typeof n)return n(t);for(var i=(new window.DOMParser).parseFromString(t,"text/html"),o=Object.keys(e),r=[].slice.call(i.body.querySelectorAll("*")),a=function(t,n){var i=r[t],a=i.nodeName.toLowerCase();if(-1===o.indexOf(i.nodeName.toLowerCase()))return i.parentNode.removeChild(i),"continue";var s=[].slice.call(i.attributes),l=[].concat(e["*"]||[],e[a]||[]);s.forEach((function(t){(function(t,e){var n=t.nodeName.toLowerCase();if(-1!==e.indexOf(n))return-1===Ne.indexOf(n)||Boolean(De.test(t.nodeValue)||Ae.test(t.nodeValue));for(var i=e.filter((function(t){return t instanceof RegExp})),o=0,r=i.length;o<r;o++)if(i[o].test(n))return!0;return!1})(t,l)||i.removeAttribute(t.nodeName)}))},s=0,l=r.length;s<l;s++)a(s);return i.body.innerHTML}var Ie="tooltip",Oe="bs.tooltip",xe=i.default.fn.tooltip,je=new RegExp("(^|\\s)bs-tooltip\\S+","g"),Le=["sanitize","whiteList","sanitizeFn"],Pe="fade",Fe="show",Re="show",He="out",Me="hover",qe="focus",Be={AUTO:"auto",TOP:"top",RIGHT:"right",BOTTOM:"bottom",LEFT:"left"},Qe={animation:!0,template:'<div class="tooltip" role="tooltip"><div class="arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",customClass:"",sanitize:!0,sanitizeFn:null,whiteList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},We={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(number|string|function)",container:"(string|element|boolean)",fallbackPlacement:"(string|array)",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",whiteList:"object",popperConfig:"(null|object)"},Ue={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},Ve=function(){function t(t,e){if("undefined"==typeof Yt)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var e=t.prototype;return e.enable=function(){this._isEnabled=!0},e.disable=function(){this._isEnabled=!1},e.toggleEnabled=function(){this._isEnabled=!this._isEnabled},e.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=i.default(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),i.default(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(i.default(this.getTipElement()).hasClass(Fe))return void this._leave(null,this);this._enter(null,this)}},e.dispose=function(){clearTimeout(this._timeout),i.default.removeData(this.element,this.constructor.DATA_KEY),i.default(this.element).off(this.constructor.EVENT_KEY),i.default(this.element).closest(".modal").off("hide.bs.modal",this._hideModalHandler),this.tip&&i.default(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},e.show=function(){var t=this;if("none"===i.default(this.element).css("display"))throw new Error("Please use show on visible elements");var e=i.default.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){i.default(this.element).trigger(e);var n=u.findShadowRoot(this.element),o=i.default.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(e.isDefaultPrevented()||!o)return;var r=this.getTipElement(),a=u.getUID(this.constructor.NAME);r.setAttribute("id",a),this.element.setAttribute("aria-describedby",a),this.setContent(),this.config.animation&&i.default(r).addClass(Pe);var s="function"==typeof this.config.placement?this.config.placement.call(this,r,this.element):this.config.placement,l=this._getAttachment(s);this.addAttachmentClass(l);var f=this._getContainer();i.default(r).data(this.constructor.DATA_KEY,this),i.default.contains(this.element.ownerDocument.documentElement,this.tip)||i.default(r).appendTo(f),i.default(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new Yt(this.element,r,this._getPopperConfig(l)),i.default(r).addClass(Fe),i.default(r).addClass(this.config.customClass),"ontouchstart"in document.documentElement&&i.default(document.body).children().on("mouseover",null,i.default.noop);var d=function(){t.config.animation&&t._fixTransition();var e=t._hoverState;t._hoverState=null,i.default(t.element).trigger(t.constructor.Event.SHOWN),e===He&&t._leave(null,t)};if(i.default(this.tip).hasClass(Pe)){var c=u.getTransitionDurationFromElement(this.tip);i.default(this.tip).one(u.TRANSITION_END,d).emulateTransitionEnd(c)}else d()}},e.hide=function(t){var e=this,n=this.getTipElement(),o=i.default.Event(this.constructor.Event.HIDE),r=function(){e._hoverState!==Re&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),i.default(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(i.default(this.element).trigger(o),!o.isDefaultPrevented()){if(i.default(n).removeClass(Fe),"ontouchstart"in document.documentElement&&i.default(document.body).children().off("mouseover",null,i.default.noop),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1,i.default(this.tip).hasClass(Pe)){var a=u.getTransitionDurationFromElement(n);i.default(n).one(u.TRANSITION_END,r).emulateTransitionEnd(a)}else r();this._hoverState=""}},e.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},e.isWithContent=function(){return Boolean(this.getTitle())},e.addAttachmentClass=function(t){i.default(this.getTipElement()).addClass("bs-tooltip-"+t)},e.getTipElement=function(){return this.tip=this.tip||i.default(this.config.template)[0],this.tip},e.setContent=function(){var t=this.getTipElement();this.setElementContent(i.default(t.querySelectorAll(".tooltip-inner")),this.getTitle()),i.default(t).removeClass("fade show")},e.setElementContent=function(t,e){"object"!=typeof e||!e.nodeType&&!e.jquery?this.config.html?(this.config.sanitize&&(e=ke(e,this.config.whiteList,this.config.sanitizeFn)),t.html(e)):t.text(e):this.config.html?i.default(e).parent().is(t)||t.empty().append(e):t.text(i.default(e).text())},e.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},e._getPopperConfig=function(t){var e=this;return a({},{placement:t,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:".arrow"},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}},this.config.popperConfig)},e._getOffset=function(){var t=this,e={};return"function"==typeof this.config.offset?e.fn=function(e){return e.offsets=a({},e.offsets,t.config.offset(e.offsets,t.element)),e}:e.offset=this.config.offset,e},e._getContainer=function(){return!1===this.config.container?document.body:u.isElement(this.config.container)?i.default(this.config.container):i.default(document).find(this.config.container)},e._getAttachment=function(t){return Be[t.toUpperCase()]},e._setListeners=function(){var t=this;this.config.trigger.split(" ").forEach((function(e){if("click"===e)i.default(t.element).on(t.constructor.Event.CLICK,t.config.selector,(function(e){return t.toggle(e)}));else if("manual"!==e){var n=e===Me?t.constructor.Event.MOUSEENTER:t.constructor.Event.FOCUSIN,o=e===Me?t.constructor.Event.MOUSELEAVE:t.constructor.Event.FOCUSOUT;i.default(t.element).on(n,t.config.selector,(function(e){return t._enter(e)})).on(o,t.config.selector,(function(e){return t._leave(e)}))}})),this._hideModalHandler=function(){t.element&&t.hide()},i.default(this.element).closest(".modal").on("hide.bs.modal",this._hideModalHandler),this.config.selector?this.config=a({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},e._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},e._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||i.default(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),i.default(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?qe:Me]=!0),i.default(e.getTipElement()).hasClass(Fe)||e._hoverState===Re?e._hoverState=Re:(clearTimeout(e._timeout),e._hoverState=Re,e.config.delay&&e.config.delay.show?e._timeout=setTimeout((function(){e._hoverState===Re&&e.show()}),e.config.delay.show):e.show())},e._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||i.default(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),i.default(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?qe:Me]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=He,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout((function(){e._hoverState===He&&e.hide()}),e.config.delay.hide):e.hide())},e._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},e._getConfig=function(t){var e=i.default(this.element).data();return Object.keys(e).forEach((function(t){-1!==Le.indexOf(t)&&delete e[t]})),"number"==typeof(t=a({},this.constructor.Default,e,"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),u.typeCheckConfig(Ie,t,this.constructor.DefaultType),t.sanitize&&(t.template=ke(t.template,t.whiteList,t.sanitizeFn)),t},e._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},e._cleanTipClass=function(){var t=i.default(this.getTipElement()),e=t.attr("class").match(je);null!==e&&e.length&&t.removeClass(e.join(""))},e._handlePopperPlacementChange=function(t){this.tip=t.instance.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},e._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(i.default(t).removeClass(Pe),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},t._jQueryInterface=function(e){return this.each((function(){var n=i.default(this),o=n.data(Oe),r="object"==typeof e&&e;if((o||!/dispose|hide/.test(e))&&(o||(o=new t(this,r),n.data(Oe,o)),"string"==typeof e)){if("undefined"==typeof o[e])throw new TypeError('No method named "'+e+'"');o[e]()}}))},r(t,null,[{key:"VERSION",get:function(){return"4.6.1"}},{key:"Default",get:function(){return Qe}},{key:"NAME",get:function(){return Ie}},{key:"DATA_KEY",get:function(){return Oe}},{key:"Event",get:function(){return Ue}},{key:"EVENT_KEY",get:function(){return".bs.tooltip"}},{key:"DefaultType",get:function(){return We}}]),t}();i.default.fn.tooltip=Ve._jQueryInterface,i.default.fn.tooltip.Constructor=Ve,i.default.fn.tooltip.noConflict=function(){return i.default.fn.tooltip=xe,Ve._jQueryInterface};var Ye="bs.popover",ze=i.default.fn.popover,Ke=new RegExp("(^|\\s)bs-popover\\S+","g"),Xe=a({},Ve.Default,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-header"></h3><div class="popover-body"></div></div>'}),Ge=a({},Ve.DefaultType,{content:"(string|element|function)"}),$e={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"},Je=function(t){var e,n;function o(){return t.apply(this,arguments)||this}n=t,(e=o).prototype=Object.create(n.prototype),e.prototype.constructor=e,s(e,n);var a=o.prototype;return a.isWithContent=function(){return this.getTitle()||this._getContent()},a.addAttachmentClass=function(t){i.default(this.getTipElement()).addClass("bs-popover-"+t)},a.getTipElement=function(){return this.tip=this.tip||i.default(this.config.template)[0],this.tip},a.setContent=function(){var t=i.default(this.getTipElement());this.setElementContent(t.find(".popover-header"),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(".popover-body"),e),t.removeClass("fade show")},a._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},a._cleanTipClass=function(){var t=i.default(this.getTipElement()),e=t.attr("class").match(Ke);null!==e&&e.length>0&&t.removeClass(e.join(""))},o._jQueryInterface=function(t){return this.each((function(){var e=i.default(this).data(Ye),n="object"==typeof t?t:null;if((e||!/dispose|hide/.test(t))&&(e||(e=new o(this,n),i.default(this).data(Ye,e)),"string"==typeof t)){if("undefined"==typeof e[t])throw new TypeError('No method named "'+t+'"');e[t]()}}))},r(o,null,[{key:"VERSION",get:function(){return"4.6.1"}},{key:"Default",get:function(){return Xe}},{key:"NAME",get:function(){return"popover"}},{key:"DATA_KEY",get:function(){return Ye}},{key:"Event",get:function(){return $e}},{key:"EVENT_KEY",get:function(){return".bs.popover"}},{key:"DefaultType",get:function(){return Ge}}]),o}(Ve);i.default.fn.popover=Je._jQueryInterface,i.default.fn.popover.Constructor=Je,i.default.fn.popover.noConflict=function(){return i.default.fn.popover=ze,Je._jQueryInterface};var Ze="scrollspy",tn="bs.scrollspy",en=i.default.fn[Ze],nn="active",on="position",rn=".nav, .list-group",an={offset:10,method:"auto",target:""},sn={offset:"number",method:"string",target:"(string|element)"},ln=function(){function t(t,e){var n=this;this._element=t,this._scrollElement="BODY"===t.tagName?window:t,this._config=this._getConfig(e),this._selector=this._config.target+" .nav-link,"+this._config.target+" .list-group-item,"+this._config.target+" .dropdown-item",this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,i.default(this._scrollElement).on("scroll.bs.scrollspy",(function(t){return n._process(t)})),this.refresh(),this._process()}var e=t.prototype;return e.refresh=function(){var t=this,e=this._scrollElement===this._scrollElement.window?"offset":on,n="auto"===this._config.method?e:this._config.method,o=n===on?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),[].slice.call(document.querySelectorAll(this._selector)).map((function(t){var e,r=u.getSelectorFromElement(t);if(r&&(e=document.querySelector(r)),e){var a=e.getBoundingClientRect();if(a.width||a.height)return[i.default(e)[n]().top+o,r]}return null})).filter((function(t){return t})).sort((function(t,e){return t[0]-e[0]})).forEach((function(e){t._offsets.push(e[0]),t._targets.push(e[1])}))},e.dispose=function(){i.default.removeData(this._element,tn),i.default(this._scrollElement).off(".bs.scrollspy"),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},e._getConfig=function(t){if("string"!=typeof(t=a({},an,"object"==typeof t&&t?t:{})).target&&u.isElement(t.target)){var e=i.default(t.target).attr("id");e||(e=u.getUID(Ze),i.default(t.target).attr("id",e)),t.target="#"+e}return u.typeCheckConfig(Ze,t,sn),t},e._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},e._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},e._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},e._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t<this._offsets[0]&&this._offsets[0]>0)return this._activeTarget=null,void this._clear();for(var o=this._offsets.length;o--;)this._activeTarget!==this._targets[o]&&t>=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||t<this._offsets[o+1])&&this._activate(this._targets[o])}},e._activate=function(t){this._activeTarget=t,this._clear();var e=this._selector.split(",").map((function(e){return e+'[data-target="'+t+'"],'+e+'[href="'+t+'"]'})),n=i.default([].slice.call(document.querySelectorAll(e.join(","))));n.hasClass("dropdown-item")?(n.closest(".dropdown").find(".dropdown-toggle").addClass(nn),n.addClass(nn)):(n.addClass(nn),n.parents(rn).prev(".nav-link, .list-group-item").addClass(nn),n.parents(rn).prev(".nav-item").children(".nav-link").addClass(nn)),i.default(this._scrollElement).trigger("activate.bs.scrollspy",{relatedTarget:t})},e._clear=function(){[].slice.call(document.querySelectorAll(this._selector)).filter((function(t){return t.classList.contains(nn)})).forEach((function(t){return t.classList.remove(nn)}))},t._jQueryInterface=function(e){return this.each((function(){var n=i.default(this).data(tn);if(n||(n=new t(this,"object"==typeof e&&e),i.default(this).data(tn,n)),"string"==typeof e){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}}))},r(t,null,[{key:"VERSION",get:function(){return"4.6.1"}},{key:"Default",get:function(){return an}}]),t}();i.default(window).on("load.bs.scrollspy.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-spy="scroll"]')),e=t.length;e--;){var n=i.default(t[e]);ln._jQueryInterface.call(n,n.data())}})),i.default.fn[Ze]=ln._jQueryInterface,i.default.fn[Ze].Constructor=ln,i.default.fn[Ze].noConflict=function(){return i.default.fn[Ze]=en,ln._jQueryInterface};var un="bs.tab",fn=i.default.fn.tab,dn="active",cn="fade",hn="show",pn=".active",mn="> li > .active",gn=function(){function t(t){this._element=t}var e=t.prototype;return e.show=function(){var t=this;if(!(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&i.default(this._element).hasClass(dn)||i.default(this._element).hasClass("disabled"))){var e,n,o=i.default(this._element).closest(".nav, .list-group")[0],r=u.getSelectorFromElement(this._element);if(o){var a="UL"===o.nodeName||"OL"===o.nodeName?mn:pn;n=(n=i.default.makeArray(i.default(o).find(a)))[n.length-1]}var s=i.default.Event("hide.bs.tab",{relatedTarget:this._element}),l=i.default.Event("show.bs.tab",{relatedTarget:n});if(n&&i.default(n).trigger(s),i.default(this._element).trigger(l),!l.isDefaultPrevented()&&!s.isDefaultPrevented()){r&&(e=document.querySelector(r)),this._activate(this._element,o);var f=function(){var e=i.default.Event("hidden.bs.tab",{relatedTarget:t._element}),o=i.default.Event("shown.bs.tab",{relatedTarget:n});i.default(n).trigger(e),i.default(t._element).trigger(o)};e?this._activate(e,e.parentNode,f):f()}}},e.dispose=function(){i.default.removeData(this._element,un),this._element=null},e._activate=function(t,e,n){var o=this,r=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?i.default(e).children(pn):i.default(e).find(mn))[0],a=n&&r&&i.default(r).hasClass(cn),s=function(){return o._transitionComplete(t,r,n)};if(r&&a){var l=u.getTransitionDurationFromElement(r);i.default(r).removeClass(hn).one(u.TRANSITION_END,s).emulateTransitionEnd(l)}else s()},e._transitionComplete=function(t,e,n){if(e){i.default(e).removeClass(dn);var o=i.default(e.parentNode).find("> .dropdown-menu .active")[0];o&&i.default(o).removeClass(dn),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}i.default(t).addClass(dn),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),u.reflow(t),t.classList.contains(cn)&&t.classList.add(hn);var r=t.parentNode;if(r&&"LI"===r.nodeName&&(r=r.parentNode),r&&i.default(r).hasClass("dropdown-menu")){var a=i.default(t).closest(".dropdown")[0];if(a){var s=[].slice.call(a.querySelectorAll(".dropdown-toggle"));i.default(s).addClass(dn)}t.setAttribute("aria-expanded",!0)}n&&n()},t._jQueryInterface=function(e){return this.each((function(){var n=i.default(this),o=n.data(un);if(o||(o=new t(this),n.data(un,o)),"string"==typeof e){if("undefined"==typeof o[e])throw new TypeError('No method named "'+e+'"');o[e]()}}))},r(t,null,[{key:"VERSION",get:function(){return"4.6.1"}}]),t}();i.default(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',(function(t){t.preventDefault(),gn._jQueryInterface.call(i.default(this),"show")})),i.default.fn.tab=gn._jQueryInterface,i.default.fn.tab.Constructor=gn,i.default.fn.tab.noConflict=function(){return i.default.fn.tab=fn,gn._jQueryInterface};var _n="bs.toast",vn=i.default.fn.toast,bn="hide",yn="show",En="showing",wn="click.dismiss.bs.toast",Tn={animation:!0,autohide:!0,delay:500},Cn={animation:"boolean",autohide:"boolean",delay:"number"},Sn=function(){function t(t,e){this._element=t,this._config=this._getConfig(e),this._timeout=null,this._setListeners()}var e=t.prototype;return e.show=function(){var t=this,e=i.default.Event("show.bs.toast");if(i.default(this._element).trigger(e),!e.isDefaultPrevented()){this._clearTimeout(),this._config.animation&&this._element.classList.add("fade");var n=function(){t._element.classList.remove(En),t._element.classList.add(yn),i.default(t._element).trigger("shown.bs.toast"),t._config.autohide&&(t._timeout=setTimeout((function(){t.hide()}),t._config.delay))};if(this._element.classList.remove(bn),u.reflow(this._element),this._element.classList.add(En),this._config.animation){var o=u.getTransitionDurationFromElement(this._element);i.default(this._element).one(u.TRANSITION_END,n).emulateTransitionEnd(o)}else n()}},e.hide=function(){if(this._element.classList.contains(yn)){var t=i.default.Event("hide.bs.toast");i.default(this._element).trigger(t),t.isDefaultPrevented()||this._close()}},e.dispose=function(){this._clearTimeout(),this._element.classList.contains(yn)&&this._element.classList.remove(yn),i.default(this._element).off(wn),i.default.removeData(this._element,_n),this._element=null,this._config=null},e._getConfig=function(t){return t=a({},Tn,i.default(this._element).data(),"object"==typeof t&&t?t:{}),u.typeCheckConfig("toast",t,this.constructor.DefaultType),t},e._setListeners=function(){var t=this;i.default(this._element).on(wn,'[data-dismiss="toast"]',(function(){return t.hide()}))},e._close=function(){var t=this,e=function(){t._element.classList.add(bn),i.default(t._element).trigger("hidden.bs.toast")};if(this._element.classList.remove(yn),this._config.animation){var n=u.getTransitionDurationFromElement(this._element);i.default(this._element).one(u.TRANSITION_END,e).emulateTransitionEnd(n)}else e()},e._clearTimeout=function(){clearTimeout(this._timeout),this._timeout=null},t._jQueryInterface=function(e){return this.each((function(){var n=i.default(this),o=n.data(_n);if(o||(o=new t(this,"object"==typeof e&&e),n.data(_n,o)),"string"==typeof e){if("undefined"==typeof o[e])throw new TypeError('No method named "'+e+'"');o[e](this)}}))},r(t,null,[{key:"VERSION",get:function(){return"4.6.1"}},{key:"DefaultType",get:function(){return Cn}},{key:"Default",get:function(){return Tn}}]),t}();i.default.fn.toast=Sn._jQueryInterface,i.default.fn.toast.Constructor=Sn,i.default.fn.toast.noConflict=function(){return i.default.fn.toast=vn,Sn._jQueryInterface},t.Alert=c,t.Button=b,t.Carousel=O,t.Collapse=W,t.Dropdown=le,t.Modal=Se,t.Popover=Je,t.Scrollspy=ln,t.Tab=gn,t.Toast=Sn,t.Tooltip=Ve,t.Util=u,Object.defineProperty(t,"__esModule",{value:!0})}));
+//# sourceMappingURL=bootstrap.bundle.min.js.map
\ No newline at end of file
diff --git a/apps/auth/src/assets/js/vendors/detect.min.js b/apps/auth/src/assets/js/vendors/detect.min.js
new file mode 100644
index 000000000..71cedfebd
--- /dev/null
+++ b/apps/auth/src/assets/js/vendors/detect.min.js
@@ -0,0 +1,43 @@
+!(function (e) {
+    if ('object' == typeof exports && 'undefined' != typeof module) module.exports = e();
+    else if ('function' == typeof define && define.amd) define([], e);
+    else {
+        ('undefined' != typeof window
+            ? window
+            : 'undefined' != typeof global
+            ? global
+            : 'undefined' != typeof self
+            ? self
+            : this
+        ).detectEthereumProvider = e();
+    }
+})(function () {
+    return function ({ mustBeMetaMask: e = !1, silent: t = !1, timeout: o = 3e3 } = {}) {
+        !(function () {
+            if ('boolean' != typeof e)
+                throw new Error("@metamask/detect-provider: Expected option 'mustBeMetaMask' to be a boolean.");
+            if ('boolean' != typeof t)
+                throw new Error("@metamask/detect-provider: Expected option 'silent' to be a boolean.");
+            if ('number' != typeof o)
+                throw new Error("@metamask/detect-provider: Expected option 'timeout' to be a number.");
+        })();
+        let n = !1;
+        return new Promise((i) => {
+            function r() {
+                if (n) return;
+                (n = !0), window.removeEventListener('ethereum#initialized', r);
+                const { ethereum: o } = window;
+                if (!o || (e && !o.isMetaMask)) {
+                    const n = e && o ? 'Non-MetaMask window.ethereum detected.' : 'Unable to detect window.ethereum.';
+                    !t && console.error('@metamask/detect-provider:', n), i(null);
+                } else i(o);
+            }
+            window.ethereum
+                ? r()
+                : (window.addEventListener('ethereum#initialized', r, { once: !0 }),
+                  setTimeout(() => {
+                      r();
+                  }, o));
+        });
+    };
+});
diff --git a/apps/auth/src/assets/js/vendors/fontawesome.06b7267748.js b/apps/auth/src/assets/js/vendors/fontawesome.06b7267748.js
new file mode 100644
index 000000000..07543be7d
--- /dev/null
+++ b/apps/auth/src/assets/js/vendors/fontawesome.06b7267748.js
@@ -0,0 +1,2 @@
+window.FontAwesomeKitConfig = {"asyncLoading":{"enabled":false},"autoA11y":{"enabled":true},"baseUrl":"https://ka-f.fontawesome.com","baseUrlKit":"https://kit.fontawesome.com","detectConflictsUntil":null,"iconUploads":{},"id":7947482,"license":"free","method":"css","minify":{"enabled":true},"token":"06b7267748","v4FontFaceShim":{"enabled":true},"v4shim":{"enabled":true},"v5FontFaceShim":{"enabled":false},"version":"5.15.4"};
+!function(t){"function"==typeof define&&define.amd?define("kit-loader",t):t()}((function(){"use strict";function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}function e(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function n(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(t);e&&(o=o.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,o)}return n}function o(t){for(var o=1;o<arguments.length;o++){var r=null!=arguments[o]?arguments[o]:{};o%2?n(Object(r),!0).forEach((function(n){e(t,n,r[n])})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(r)):n(Object(r)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(r,e))}))}return t}function r(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){if("undefined"==typeof Symbol||!(Symbol.iterator in Object(t)))return;var n=[],o=!0,r=!1,i=void 0;try{for(var c,a=t[Symbol.iterator]();!(o=(c=a.next()).done)&&(n.push(c.value),!e||n.length!==e);o=!0);}catch(t){r=!0,i=t}finally{try{o||null==a.return||a.return()}finally{if(r)throw i}}return n}(t,e)||function(t,e){if(!t)return;if("string"==typeof t)return i(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);"Object"===n&&t.constructor&&(n=t.constructor.name);if("Map"===n||"Set"===n)return Array.from(t);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return i(t,e)}(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function i(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,o=new Array(e);n<e;n++)o[n]=t[n];return o}function c(t,e){var n=e&&e.addOn||"",o=e&&e.baseFilename||t.license+n,r=e&&e.minify?".min":"",i=e&&e.fileSuffix||t.method,c=e&&e.subdir||t.method;return t.baseUrl+"/releases/"+("latest"===t.version?"latest":"v".concat(t.version))+"/"+c+"/"+o+r+"."+i}function a(t){return t.baseUrlKit+"/"+t.token+"/"+t.id+"/kit-upload.css"}function u(t,e){var n=e||["fa"],o="."+Array.prototype.join.call(n,",."),r=t.querySelectorAll(o);Array.prototype.forEach.call(r,(function(e){var n=e.getAttribute("title");e.setAttribute("aria-hidden","true");var o=!e.nextElementSibling||!e.nextElementSibling.classList.contains("sr-only");if(n&&o){var r=t.createElement("span");r.innerHTML=n,r.classList.add("sr-only"),e.parentNode.insertBefore(r,e.nextSibling)}}))}var f,s=function(){},d="undefined"!=typeof global&&void 0!==global.process&&"function"==typeof global.process.emit,l="undefined"==typeof setImmediate?setTimeout:setImmediate,h=[];function m(){for(var t=0;t<h.length;t++)h[t][0](h[t][1]);h=[],f=!1}function p(t,e){h.push([t,e]),f||(f=!0,l(m,0))}function v(t){var e=t.owner,n=e._state,o=e._data,r=t[n],i=t.then;if("function"==typeof r){n="fulfilled";try{o=r(o)}catch(t){w(i,t)}}y(i,o)||("fulfilled"===n&&b(i,o),"rejected"===n&&w(i,o))}function y(e,n){var o;try{if(e===n)throw new TypeError("A promises callback cannot return that same promise.");if(n&&("function"==typeof n||"object"===t(n))){var r=n.then;if("function"==typeof r)return r.call(n,(function(t){o||(o=!0,n===t?g(e,t):b(e,t))}),(function(t){o||(o=!0,w(e,t))})),!0}}catch(t){return o||w(e,t),!0}return!1}function b(t,e){t!==e&&y(t,e)||g(t,e)}function g(t,e){"pending"===t._state&&(t._state="settled",t._data=e,p(S,t))}function w(t,e){"pending"===t._state&&(t._state="settled",t._data=e,p(O,t))}function A(t){t._then=t._then.forEach(v)}function S(t){t._state="fulfilled",A(t)}function O(t){t._state="rejected",A(t),!t._handled&&d&&global.process.emit("unhandledRejection",t._data,t)}function j(t){global.process.emit("rejectionHandled",t)}function E(t){if("function"!=typeof t)throw new TypeError("Promise resolver "+t+" is not a function");if(this instanceof E==!1)throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");this._then=[],function(t,e){function n(t){w(e,t)}try{t((function(t){b(e,t)}),n)}catch(t){n(t)}}(t,this)}E.prototype={constructor:E,_state:"pending",_then:null,_data:void 0,_handled:!1,then:function(t,e){var n={owner:this,then:new this.constructor(s),fulfilled:t,rejected:e};return!e&&!t||this._handled||(this._handled=!0,"rejected"===this._state&&d&&p(j,this)),"fulfilled"===this._state||"rejected"===this._state?p(v,n):this._then.push(n),n.then},catch:function(t){return this.then(null,t)}},E.all=function(t){if(!Array.isArray(t))throw new TypeError("You must pass an array to Promise.all().");return new E((function(e,n){var o=[],r=0;function i(t){return r++,function(n){o[t]=n,--r||e(o)}}for(var c,a=0;a<t.length;a++)(c=t[a])&&"function"==typeof c.then?c.then(i(a),n):o[a]=c;r||e(o)}))},E.race=function(t){if(!Array.isArray(t))throw new TypeError("You must pass an array to Promise.race().");return new E((function(e,n){for(var o,r=0;r<t.length;r++)(o=t[r])&&"function"==typeof o.then?o.then(e,n):e(o)}))},E.resolve=function(e){return e&&"object"===t(e)&&e.constructor===E?e:new E((function(t){t(e)}))},E.reject=function(t){return new E((function(e,n){n(t)}))};var _="function"==typeof Promise?Promise:E;function F(t,e){var n=e.fetch,o=e.XMLHttpRequest,r=e.token,i=t;return"URLSearchParams"in window?(i=new URL(t)).searchParams.set("token",r):i=i+"?token="+encodeURIComponent(r),i=i.toString(),new _((function(t,e){if("function"==typeof n)n(i,{mode:"cors",cache:"default"}).then((function(t){if(t.ok)return t.text();throw new Error("")})).then((function(e){t(e)})).catch(e);else if("function"==typeof o){var r=new o;r.addEventListener("loadend",(function(){this.responseText?t(this.responseText):e(new Error(""))}));["abort","error","timeout"].map((function(t){r.addEventListener(t,(function(){e(new Error(""))}))})),r.open("GET",i),r.send()}else{e(new Error(""))}}))}function P(t,e,n){var o=t;return[[/(url\("?)\.\.\/\.\.\/\.\./g,function(t,n){return"".concat(n).concat(e)}],[/(url\("?)\.\.\/webfonts/g,function(t,o){return"".concat(o).concat(e,"/releases/v").concat(n,"/webfonts")}],[/(url\("?)https:\/\/kit-free([^.])*\.fontawesome\.com/g,function(t,n){return"".concat(n).concat(e)}]].forEach((function(t){var e=r(t,2),n=e[0],i=e[1];o=o.replace(n,i)})),o}function C(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){},r=e.document||r,i=u.bind(u,r,["fa","fab","fas","far","fal","fad","fak"]),f=Object.keys(t.iconUploads||{}).length>0;t.autoA11y.enabled&&n(i);var s=[{id:"fa-main",addOn:void 0}];t.v4shim&&t.v4shim.enabled&&s.push({id:"fa-v4-shims",addOn:"-v4-shims"}),t.v5FontFaceShim&&t.v5FontFaceShim.enabled&&s.push({id:"fa-v5-font-face",addOn:"-v5-font-face"}),t.v4FontFaceShim&&t.v4FontFaceShim.enabled&&s.push({id:"fa-v4-font-face",addOn:"-v4-font-face"}),f&&s.push({id:"fa-kit-upload",customCss:!0});var d=s.map((function(n){return new _((function(r,i){F(n.customCss?a(t):c(t,{addOn:n.addOn,minify:t.minify.enabled}),e).then((function(i){r(U(i,o(o({},e),{},{baseUrl:t.baseUrl,version:t.version,id:n.id,contentFilter:function(t,e){return P(t,e.baseUrl,e.version)}})))})).catch(i)}))}));return _.all(d)}function U(t,e){var n=e.contentFilter||function(t,e){return t},o=document.createElement("style"),r=document.createTextNode(n(t,e));return o.appendChild(r),o.media="all",e.id&&o.setAttribute("id",e.id),e&&e.detectingConflicts&&e.detectionIgnoreAttr&&o.setAttributeNode(document.createAttribute(e.detectionIgnoreAttr)),o}function k(t,e){e.autoA11y=t.autoA11y.enabled,"pro"===t.license&&(e.autoFetchSvg=!0,e.fetchSvgFrom=t.baseUrl+"/releases/"+("latest"===t.version?"latest":"v".concat(t.version))+"/svgs",e.fetchUploadedSvgFrom=t.uploadsUrl);var n=[];return t.v4shim.enabled&&n.push(new _((function(n,r){F(c(t,{addOn:"-v4-shims",minify:t.minify.enabled}),e).then((function(t){n(I(t,o(o({},e),{},{id:"fa-v4-shims"})))})).catch(r)}))),n.push(new _((function(n,r){F(c(t,{minify:t.minify.enabled}),e).then((function(t){var r=I(t,o(o({},e),{},{id:"fa-main"}));n(function(t,e){var n=e&&void 0!==e.autoFetchSvg?e.autoFetchSvg:void 0,o=e&&void 0!==e.autoA11y?e.autoA11y:void 0;void 0!==o&&t.setAttribute("data-auto-a11y",o?"true":"false");n&&(t.setAttributeNode(document.createAttribute("data-auto-fetch-svg")),t.setAttribute("data-fetch-svg-from",e.fetchSvgFrom),t.setAttribute("data-fetch-uploaded-svg-from",e.fetchUploadedSvgFrom));return t}(r,e))})).catch(r)}))),_.all(n)}function I(t,e){var n=document.createElement("SCRIPT"),o=document.createTextNode(t);return n.appendChild(o),n.referrerPolicy="strict-origin",e.id&&n.setAttribute("id",e.id),e&&e.detectingConflicts&&e.detectionIgnoreAttr&&n.setAttributeNode(document.createAttribute(e.detectionIgnoreAttr)),n}function L(t){var e,n=[],o=document,r=o.documentElement.doScroll,i=(r?/^loaded|^c/:/^loaded|^i|^c/).test(o.readyState);i||o.addEventListener("DOMContentLoaded",e=function(){for(o.removeEventListener("DOMContentLoaded",e),i=1;e=n.shift();)e()}),i?setTimeout(t,0):n.push(t)}function T(t){"undefined"!=typeof MutationObserver&&new MutationObserver(t).observe(document,{childList:!0,subtree:!0})}try{if(window.FontAwesomeKitConfig){var x=window.FontAwesomeKitConfig,M={detectingConflicts:x.detectConflictsUntil&&new Date<=new Date(x.detectConflictsUntil),detectionIgnoreAttr:"data-fa-detection-ignore",fetch:window.fetch,token:x.token,XMLHttpRequest:window.XMLHttpRequest,document:document},D=document.currentScript,N=D?D.parentElement:document.head;(function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return"js"===t.method?k(t,e):"css"===t.method?C(t,e,(function(t){L(t),T(t)})):void 0})(x,M).then((function(t){t.map((function(t){try{N.insertBefore(t,D?D.nextSibling:null)}catch(e){N.appendChild(t)}})),M.detectingConflicts&&D&&L((function(){D.setAttributeNode(document.createAttribute(M.detectionIgnoreAttr));var t=function(t,e){var n=document.createElement("script");return e&&e.detectionIgnoreAttr&&n.setAttributeNode(document.createAttribute(e.detectionIgnoreAttr)),n.src=c(t,{baseFilename:"conflict-detection",fileSuffix:"js",subdir:"js",minify:t.minify.enabled}),n}(x,M);document.body.appendChild(t)}))})).catch((function(t){console.error("".concat("Font Awesome Kit:"," ").concat(t))}))}}catch(t){console.error("".concat("Font Awesome Kit:"," ").concat(t))}}));
diff --git a/apps/auth/src/assets/js/vendors/jquery.slim.min.js b/apps/auth/src/assets/js/vendors/jquery.slim.min.js
new file mode 100644
index 000000000..36b4e1a13
--- /dev/null
+++ b/apps/auth/src/assets/js/vendors/jquery.slim.min.js
@@ -0,0 +1,2 @@
+/*! jQuery v3.5.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector | (c) JS Foundation and other contributors | jquery.org/license */
+!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(g,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,v=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,y=n.hasOwnProperty,a=y.toString,l=a.call(Object),m={},b=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},w=g.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function C(e,t,n){var r,i,o=(n=n||w).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function T(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector",E=function(e,t){return new E.fn.init(e,t)};function d(e){var t=!!e&&"length"in e&&e.length,n=T(e);return!b(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0<t&&t-1 in e)}E.fn=E.prototype={jquery:f,constructor:E,length:0,toArray:function(){return s.call(this)},get:function(e){return null==e?s.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=E.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return E.each(this,e)},map:function(n){return this.pushStack(E.map(this,function(e,t){return n.call(e,t,e)}))},slice:function(){return this.pushStack(s.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(E.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(E.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(0<=n&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:u,sort:t.sort,splice:t.splice},E.extend=E.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||b(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)r=e[t],"__proto__"!==t&&a!==r&&(l&&r&&(E.isPlainObject(r)||(i=Array.isArray(r)))?(n=a[t],o=i&&!Array.isArray(n)?[]:i||E.isPlainObject(n)?n:{},i=!1,a[t]=E.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},E.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==o.call(e))&&(!(t=r(e))||"function"==typeof(n=y.call(t,"constructor")&&t.constructor)&&a.call(n)===l)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e,t,n){C(e,{nonce:t&&t.nonce},n)},each:function(e,t){var n,r=0;if(d(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},makeArray:function(e,t){var n=t||[];return null!=e&&(d(Object(e))?E.merge(n,"string"==typeof e?[e]:e):u.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:i.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var r,i,o=0,a=[];if(d(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&a.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&a.push(i);return v(a)},guid:1,support:m}),"function"==typeof Symbol&&(E.fn[Symbol.iterator]=t[Symbol.iterator]),E.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){n["[object "+t+"]"]=t.toLowerCase()});var p=function(n){var e,p,x,o,i,h,f,g,w,u,l,C,T,a,E,v,s,c,y,A="sizzle"+1*new Date,d=n.document,N=0,r=0,m=ue(),b=ue(),S=ue(),k=ue(),D=function(e,t){return e===t&&(l=!0),0},L={}.hasOwnProperty,t=[],j=t.pop,q=t.push,O=t.push,P=t.slice,H=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},I="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",R="[\\x20\\t\\r\\n\\f]",B="(?:\\\\[\\da-fA-F]{1,6}"+R+"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",M="\\["+R+"*("+B+")(?:"+R+"*([*^$|!~]?=)"+R+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+B+"))|)"+R+"*\\]",W=":("+B+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+M+")*)|.*)\\)|)",F=new RegExp(R+"+","g"),$=new RegExp("^"+R+"+|((?:^|[^\\\\])(?:\\\\.)*)"+R+"+$","g"),z=new RegExp("^"+R+"*,"+R+"*"),_=new RegExp("^"+R+"*([>+~]|"+R+")"+R+"*"),U=new RegExp(R+"|>"),V=new RegExp(W),X=new RegExp("^"+B+"$"),Q={ID:new RegExp("^#("+B+")"),CLASS:new RegExp("^\\.("+B+")"),TAG:new RegExp("^("+B+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+R+"*(even|odd|(([+-]|)(\\d*)n|)"+R+"*(?:([+-]|)"+R+"*(\\d+)|))"+R+"*\\)|)","i"),bool:new RegExp("^(?:"+I+")$","i"),needsContext:new RegExp("^"+R+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+R+"*((?:-\\d)?\\d*)"+R+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,G=/^(?:input|select|textarea|button)$/i,K=/^h\d$/i,J=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+R+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){C()},ae=xe(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{O.apply(t=P.call(d.childNodes),d.childNodes),t[d.childNodes.length].nodeType}catch(e){O={apply:t.length?function(e,t){q.apply(e,P.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,d=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==d&&9!==d&&11!==d)return n;if(!r&&(C(e),e=e||T,E)){if(11!==d&&(u=Z.exec(t)))if(i=u[1]){if(9===d){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return O.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&p.getElementsByClassName&&e.getElementsByClassName)return O.apply(n,e.getElementsByClassName(i)),n}if(p.qsa&&!k[t+" "]&&(!v||!v.test(t))&&(1!==d||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===d&&(U.test(t)||_.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&p.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=A)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+be(l[o]);c=l.join(",")}try{return O.apply(n,f.querySelectorAll(c)),n}catch(e){k(t,!0)}finally{s===A&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>x.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[A]=!0,e}function ce(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)x.attrHandle[n[r]]=t}function de(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function pe(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in p=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},C=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:d;return r!=T&&9===r.nodeType&&r.documentElement&&(a=(T=r).documentElement,E=!i(T),d!=T&&(n=T.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),p.scope=ce(function(e){return a.appendChild(e).appendChild(T.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),p.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),p.getElementsByTagName=ce(function(e){return e.appendChild(T.createComment("")),!e.getElementsByTagName("*").length}),p.getElementsByClassName=J.test(T.getElementsByClassName),p.getById=ce(function(e){return a.appendChild(e).id=A,!T.getElementsByName||!T.getElementsByName(A).length}),p.getById?(x.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},x.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(x.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},x.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),x.find.TAG=p.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):p.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},x.find.CLASS=p.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(p.qsa=J.test(T.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="<a id='"+A+"'></a><select id='"+A+"-\r\\' msallowcapture=''><option selected=''></option></select>",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+R+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+R+"*(?:value|"+I+")"),e.querySelectorAll("[id~="+A+"-]").length||v.push("~="),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+R+"*name"+R+"*="+R+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+A+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var t=T.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+R+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(p.matchesSelector=J.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){p.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",W)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=J.test(a.compareDocumentPosition),y=t||J.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!p.sortDetached&&t.compareDocumentPosition(e)===n?e==T||e.ownerDocument==d&&y(d,e)?-1:t==T||t.ownerDocument==d&&y(d,t)?1:u?H(u,e)-H(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==T?-1:t==T?1:i?-1:o?1:u?H(u,e)-H(u,t):0;if(i===o)return de(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?de(a[r],s[r]):a[r]==d?-1:s[r]==d?1:0}),T},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(C(e),p.matchesSelector&&E&&!k[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||p.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){k(t,!0)}return 0<se(t,T,null,[e]).length},se.contains=function(e,t){return(e.ownerDocument||e)!=T&&C(e),y(e,t)},se.attr=function(e,t){(e.ownerDocument||e)!=T&&C(e);var n=x.attrHandle[t.toLowerCase()],r=n&&L.call(x.attrHandle,t.toLowerCase())?n(e,t,!E):void 0;return void 0!==r?r:p.attributes||!E?e.getAttribute(t):(r=e.getAttributeNode(t))&&r.specified?r.value:null},se.escape=function(e){return(e+"").replace(re,ie)},se.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},se.uniqueSort=function(e){var t,n=[],r=0,i=0;if(l=!p.detectDuplicates,u=!p.sortStable&&e.slice(0),e.sort(D),l){while(t=e[i++])t===e[i]&&(r=n.push(i));while(r--)e.splice(n[r],1)}return u=null,e},o=se.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else while(t=e[r++])n+=o(t);return n},(x=se.selectors={cacheLength:50,createPseudo:le,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&V.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+R+")"+e+"("+R+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1<t.indexOf(i):"$="===r?i&&t.slice(-i.length)===i:"~="===r?-1<(" "+t.replace(F," ")+" ").indexOf(i):"|="===r&&(t===i||t.slice(0,i.length+1)===i+"-"))}},CHILD:function(h,e,t,g,v){var y="nth"!==h.slice(0,3),m="last"!==h.slice(-4),b="of-type"===e;return 1===g&&0===v?function(e){return!!e.parentNode}:function(e,t,n){var r,i,o,a,s,u,l=y!==m?"nextSibling":"previousSibling",c=e.parentNode,f=b&&e.nodeName.toLowerCase(),d=!n&&!b,p=!1;if(c){if(y){while(l){a=e;while(a=a[l])if(b?a.nodeName.toLowerCase()===f:1===a.nodeType)return!1;u=l="only"===h&&!u&&"nextSibling"}return!0}if(u=[m?c.firstChild:c.lastChild],m&&d){p=(s=(r=(i=(o=(a=c)[A]||(a[A]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===N&&r[1])&&r[2],a=s&&c.childNodes[s];while(a=++s&&a&&a[l]||(p=s=0)||u.pop())if(1===a.nodeType&&++p&&a===e){i[h]=[N,s,p];break}}else if(d&&(p=s=(r=(i=(o=(a=e)[A]||(a[A]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===N&&r[1]),!1===p)while(a=++s&&a&&a[l]||(p=s=0)||u.pop())if((b?a.nodeName.toLowerCase()===f:1===a.nodeType)&&++p&&(d&&((i=(o=a[A]||(a[A]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]=[N,p]),a===e))break;return(p-=v)===g||p%g==0&&0<=p/g}}},PSEUDO:function(e,o){var t,a=x.pseudos[e]||x.setFilters[e.toLowerCase()]||se.error("unsupported pseudo: "+e);return a[A]?a(o):1<a.length?(t=[e,e,"",o],x.setFilters.hasOwnProperty(e.toLowerCase())?le(function(e,t){var n,r=a(e,o),i=r.length;while(i--)e[n=H(e,r[i])]=!(t[n]=r[i])}):function(e){return a(e,0,t)}):a}},pseudos:{not:le(function(e){var r=[],i=[],s=f(e.replace($,"$1"));return s[A]?le(function(e,t,n,r){var i,o=s(e,null,r,[]),a=e.length;while(a--)(i=o[a])&&(e[a]=!(t[a]=i))}):function(e,t,n){return r[0]=e,s(r,null,n,i),r[0]=null,!i.pop()}}),has:le(function(t){return function(e){return 0<se(t,e).length}}),contains:le(function(t){return t=t.replace(te,ne),function(e){return-1<(e.textContent||o(e)).indexOf(t)}}),lang:le(function(n){return X.test(n||"")||se.error("unsupported lang: "+n),n=n.replace(te,ne).toLowerCase(),function(e){var t;do{if(t=E?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(t=t.toLowerCase())===n||0===t.indexOf(n+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var t=n.location&&n.location.hash;return t&&t.slice(1)===e.id},root:function(e){return e===a},focus:function(e){return e===T.activeElement&&(!T.hasFocus||T.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:ge(!1),disabled:ge(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!x.pseudos.empty(e)},header:function(e){return K.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:ve(function(){return[0]}),last:ve(function(e,t){return[t-1]}),eq:ve(function(e,t,n){return[n<0?n+t:n]}),even:ve(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:ve(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:ve(function(e,t,n){for(var r=n<0?n+t:t<n?t:n;0<=--r;)e.push(r);return e}),gt:ve(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}}).pseudos.nth=x.pseudos.eq,{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})x.pseudos[e]=pe(e);for(e in{submit:!0,reset:!0})x.pseudos[e]=he(e);function me(){}function be(e){for(var t=0,n=e.length,r="";t<n;t++)r+=e[t].value;return r}function xe(s,e,t){var u=e.dir,l=e.next,c=l||u,f=t&&"parentNode"===c,d=r++;return e.first?function(e,t,n){while(e=e[u])if(1===e.nodeType||f)return s(e,t,n);return!1}:function(e,t,n){var r,i,o,a=[N,d];if(n){while(e=e[u])if((1===e.nodeType||f)&&s(e,t,n))return!0}else while(e=e[u])if(1===e.nodeType||f)if(i=(o=e[A]||(e[A]={}))[e.uniqueID]||(o[e.uniqueID]={}),l&&l===e.nodeName.toLowerCase())e=e[u]||e;else{if((r=i[c])&&r[0]===N&&r[1]===d)return a[2]=r[2];if((i[c]=a)[2]=s(e,t,n))return!0}return!1}}function we(i){return 1<i.length?function(e,t,n){var r=i.length;while(r--)if(!i[r](e,t,n))return!1;return!0}:i[0]}function Ce(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s<u;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function Te(p,h,g,v,y,e){return v&&!v[A]&&(v=Te(v)),y&&!y[A]&&(y=Te(y,e)),le(function(e,t,n,r){var i,o,a,s=[],u=[],l=t.length,c=e||function(e,t,n){for(var r=0,i=t.length;r<i;r++)se(e,t[r],n);return n}(h||"*",n.nodeType?[n]:n,[]),f=!p||!e&&h?c:Ce(c,s,p,n,r),d=g?y||(e?p:l||v)?[]:t:f;if(g&&g(f,d,n,r),v){i=Ce(d,u),v(i,[],n,r),o=i.length;while(o--)(a=i[o])&&(d[u[o]]=!(f[u[o]]=a))}if(e){if(y||p){if(y){i=[],o=d.length;while(o--)(a=d[o])&&i.push(f[o]=a);y(null,d=[],i,r)}o=d.length;while(o--)(a=d[o])&&-1<(i=y?H(e,a):s[o])&&(e[i]=!(t[i]=a))}}else d=Ce(d===t?d.splice(l,d.length):d),y?y(null,t,d,r):O.apply(t,d)})}function Ee(e){for(var i,t,n,r=e.length,o=x.relative[e[0].type],a=o||x.relative[" "],s=o?1:0,u=xe(function(e){return e===i},a,!0),l=xe(function(e){return-1<H(i,e)},a,!0),c=[function(e,t,n){var r=!o&&(n||t!==w)||((i=t).nodeType?u(e,t,n):l(e,t,n));return i=null,r}];s<r;s++)if(t=x.relative[e[s].type])c=[xe(we(c),t)];else{if((t=x.filter[e[s].type].apply(null,e[s].matches))[A]){for(n=++s;n<r;n++)if(x.relative[e[n].type])break;return Te(1<s&&we(c),1<s&&be(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace($,"$1"),t,s<n&&Ee(e.slice(s,n)),n<r&&Ee(e=e.slice(n)),n<r&&be(e))}c.push(t)}return we(c)}return me.prototype=x.filters=x.pseudos,x.setFilters=new me,h=se.tokenize=function(e,t){var n,r,i,o,a,s,u,l=b[e+" "];if(l)return t?0:l.slice(0);a=e,s=[],u=x.preFilter;while(a){for(o in n&&!(r=z.exec(a))||(r&&(a=a.slice(r[0].length)||a),s.push(i=[])),n=!1,(r=_.exec(a))&&(n=r.shift(),i.push({value:n,type:r[0].replace($," ")}),a=a.slice(n.length)),x.filter)!(r=Q[o].exec(a))||u[o]&&!(r=u[o](r))||(n=r.shift(),i.push({value:n,type:o,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?se.error(e):b(e,s).slice(0)},f=se.compile=function(e,t){var n,v,y,m,b,r,i=[],o=[],a=S[e+" "];if(!a){t||(t=h(e)),n=t.length;while(n--)(a=Ee(t[n]))[A]?i.push(a):o.push(a);(a=S(e,(v=o,m=0<(y=i).length,b=0<v.length,r=function(e,t,n,r,i){var o,a,s,u=0,l="0",c=e&&[],f=[],d=w,p=e||b&&x.find.TAG("*",i),h=N+=null==d?1:Math.random()||.1,g=p.length;for(i&&(w=t==T||t||i);l!==g&&null!=(o=p[l]);l++){if(b&&o){a=0,t||o.ownerDocument==T||(C(o),n=!E);while(s=v[a++])if(s(o,t||T,n)){r.push(o);break}i&&(N=h)}m&&((o=!s&&o)&&u--,e&&c.push(o))}if(u+=l,m&&l!==u){a=0;while(s=y[a++])s(c,f,t,n);if(e){if(0<u)while(l--)c[l]||f[l]||(f[l]=j.call(r));f=Ce(f)}O.apply(r,f),i&&!e&&0<f.length&&1<u+y.length&&se.uniqueSort(r)}return i&&(N=h,w=d),c},m?le(r):r))).selector=e}return a},g=se.select=function(e,t,n,r){var i,o,a,s,u,l="function"==typeof e&&e,c=!r&&h(e=l.selector||e);if(n=n||[],1===c.length){if(2<(o=c[0]=c[0].slice(0)).length&&"ID"===(a=o[0]).type&&9===t.nodeType&&E&&x.relative[o[1].type]){if(!(t=(x.find.ID(a.matches[0].replace(te,ne),t)||[])[0]))return n;l&&(t=t.parentNode),e=e.slice(o.shift().value.length)}i=Q.needsContext.test(e)?0:o.length;while(i--){if(a=o[i],x.relative[s=a.type])break;if((u=x.find[s])&&(r=u(a.matches[0].replace(te,ne),ee.test(o[0].type)&&ye(t.parentNode)||t))){if(o.splice(i,1),!(e=r.length&&be(o)))return O.apply(n,r),n;break}}}return(l||f(e,c))(r,t,!E,n,!t||ee.test(e)&&ye(t.parentNode)||t),n},p.sortStable=A.split("").sort(D).join("")===A,p.detectDuplicates=!!l,C(),p.sortDetached=ce(function(e){return 1&e.compareDocumentPosition(T.createElement("fieldset"))}),ce(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||fe("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),p.attributes&&ce(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||fe("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ce(function(e){return null==e.getAttribute("disabled")})||fe(I,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),se}(g);E.find=p,E.expr=p.selectors,E.expr[":"]=E.expr.pseudos,E.uniqueSort=E.unique=p.uniqueSort,E.text=p.getText,E.isXMLDoc=p.isXML,E.contains=p.contains,E.escapeSelector=p.escape;var h=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&E(e).is(n))break;r.push(e)}return r},A=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},N=E.expr.match.needsContext;function S(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var k=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return b(n)?E.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?E.grep(e,function(e){return e===n!==r}):"string"!=typeof n?E.grep(e,function(e){return-1<i.call(n,e)!==r}):E.filter(n,e,r)}E.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?E.find.matchesSelector(r,e)?[r]:[]:E.find.matches(e,E.grep(t,function(e){return 1===e.nodeType}))},E.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(E(e).filter(function(){for(t=0;t<r;t++)if(E.contains(i[t],this))return!0}));for(n=this.pushStack([]),t=0;t<r;t++)E.find(e,i[t],n);return 1<r?E.uniqueSort(n):n},filter:function(e){return this.pushStack(D(this,e||[],!1))},not:function(e){return this.pushStack(D(this,e||[],!0))},is:function(e){return!!D(this,"string"==typeof e&&N.test(e)?E(e):e||[],!1).length}});var L,j=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(E.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||L,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:j.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof E?t[0]:t,E.merge(this,E.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:w,!0)),k.test(r[1])&&E.isPlainObject(t))for(r in t)b(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=w.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):b(e)?void 0!==n.ready?n.ready(e):e(E):E.makeArray(e,this)}).prototype=E.fn,L=E(w);var q=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}E.fn.extend({has:function(e){var t=E(e,this),n=t.length;return this.filter(function(){for(var e=0;e<n;e++)if(E.contains(this,t[e]))return!0})},closest:function(e,t){var n,r=0,i=this.length,o=[],a="string"!=typeof e&&E(e);if(!N.test(e))for(;r<i;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?-1<a.index(n):1===n.nodeType&&E.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(1<o.length?E.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?i.call(E(e),this[0]):i.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(E.uniqueSort(E.merge(this.get(),E(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),E.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return h(e,"parentNode")},parentsUntil:function(e,t,n){return h(e,"parentNode",n)},next:function(e){return P(e,"nextSibling")},prev:function(e){return P(e,"previousSibling")},nextAll:function(e){return h(e,"nextSibling")},prevAll:function(e){return h(e,"previousSibling")},nextUntil:function(e,t,n){return h(e,"nextSibling",n)},prevUntil:function(e,t,n){return h(e,"previousSibling",n)},siblings:function(e){return A((e.parentNode||{}).firstChild,e)},children:function(e){return A(e.firstChild)},contents:function(e){return null!=e.contentDocument&&r(e.contentDocument)?e.contentDocument:(S(e,"template")&&(e=e.content||e),E.merge([],e.childNodes))}},function(r,i){E.fn[r]=function(e,t){var n=E.map(this,i,e);return"Until"!==r.slice(-5)&&(t=e),t&&"string"==typeof t&&(n=E.filter(t,n)),1<this.length&&(O[r]||E.uniqueSort(n),q.test(r)&&n.reverse()),this.pushStack(n)}});var H=/[^\x20\t\r\n\f]+/g;function I(e){return e}function R(e){throw e}function B(e,t,n,r){var i;try{e&&b(i=e.promise)?i.call(e).done(t).fail(n):e&&b(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}E.Callbacks=function(r){var e,n;r="string"==typeof r?(e=r,n={},E.each(e.match(H)||[],function(e,t){n[t]=!0}),n):E.extend({},r);var i,t,o,a,s=[],u=[],l=-1,c=function(){for(a=a||r.once,o=i=!0;u.length;l=-1){t=u.shift();while(++l<s.length)!1===s[l].apply(t[0],t[1])&&r.stopOnFalse&&(l=s.length,t=!1)}r.memory||(t=!1),i=!1,a&&(s=t?[]:"")},f={add:function(){return s&&(t&&!i&&(l=s.length-1,u.push(t)),function n(e){E.each(e,function(e,t){b(t)?r.unique&&f.has(t)||s.push(t):t&&t.length&&"string"!==T(t)&&n(t)})}(arguments),t&&!i&&c()),this},remove:function(){return E.each(arguments,function(e,t){var n;while(-1<(n=E.inArray(t,s,n)))s.splice(n,1),n<=l&&l--}),this},has:function(e){return e?-1<E.inArray(e,s):0<s.length},empty:function(){return s&&(s=[]),this},disable:function(){return a=u=[],s=t="",this},disabled:function(){return!s},lock:function(){return a=u=[],t||i||(s=t=""),this},locked:function(){return!!a},fireWith:function(e,t){return a||(t=[e,(t=t||[]).slice?t.slice():t],u.push(t),i||c()),this},fire:function(){return f.fireWith(this,arguments),this},fired:function(){return!!o}};return f},E.extend({Deferred:function(e){var o=[["notify","progress",E.Callbacks("memory"),E.Callbacks("memory"),2],["resolve","done",E.Callbacks("once memory"),E.Callbacks("once memory"),0,"resolved"],["reject","fail",E.Callbacks("once memory"),E.Callbacks("once memory"),1,"rejected"]],i="pending",a={state:function(){return i},always:function(){return s.done(arguments).fail(arguments),this},"catch":function(e){return a.then(null,e)},pipe:function(){var i=arguments;return E.Deferred(function(r){E.each(o,function(e,t){var n=b(i[t[4]])&&i[t[4]];s[t[1]](function(){var e=n&&n.apply(this,arguments);e&&b(e.promise)?e.promise().progress(r.notify).done(r.resolve).fail(r.reject):r[t[0]+"With"](this,n?[e]:arguments)})}),i=null}).promise()},then:function(t,n,r){var u=0;function l(i,o,a,s){return function(){var n=this,r=arguments,e=function(){var e,t;if(!(i<u)){if((e=a.apply(n,r))===o.promise())throw new TypeError("Thenable self-resolution");t=e&&("object"==typeof e||"function"==typeof e)&&e.then,b(t)?s?t.call(e,l(u,o,I,s),l(u,o,R,s)):(u++,t.call(e,l(u,o,I,s),l(u,o,R,s),l(u,o,I,o.notifyWith))):(a!==I&&(n=void 0,r=[e]),(s||o.resolveWith)(n,r))}},t=s?e:function(){try{e()}catch(e){E.Deferred.exceptionHook&&E.Deferred.exceptionHook(e,t.stackTrace),u<=i+1&&(a!==R&&(n=void 0,r=[e]),o.rejectWith(n,r))}};i?t():(E.Deferred.getStackHook&&(t.stackTrace=E.Deferred.getStackHook()),g.setTimeout(t))}}return E.Deferred(function(e){o[0][3].add(l(0,e,b(r)?r:I,e.notifyWith)),o[1][3].add(l(0,e,b(t)?t:I)),o[2][3].add(l(0,e,b(n)?n:R))}).promise()},promise:function(e){return null!=e?E.extend(e,a):a}},s={};return E.each(o,function(e,t){var n=t[2],r=t[5];a[t[1]]=n.add,r&&n.add(function(){i=r},o[3-e][2].disable,o[3-e][3].disable,o[0][2].lock,o[0][3].lock),n.add(t[3].fire),s[t[0]]=function(){return s[t[0]+"With"](this===s?void 0:this,arguments),this},s[t[0]+"With"]=n.fireWith}),a.promise(s),e&&e.call(s,s),s},when:function(e){var n=arguments.length,t=n,r=Array(t),i=s.call(arguments),o=E.Deferred(),a=function(t){return function(e){r[t]=this,i[t]=1<arguments.length?s.call(arguments):e,--n||o.resolveWith(r,i)}};if(n<=1&&(B(e,o.done(a(t)).resolve,o.reject,!n),"pending"===o.state()||b(i[t]&&i[t].then)))return o.then();while(t--)B(i[t],a(t),o.reject);return o.promise()}});var M=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;E.Deferred.exceptionHook=function(e,t){g.console&&g.console.warn&&e&&M.test(e.name)&&g.console.warn("jQuery.Deferred exception: "+e.message,e.stack,t)},E.readyException=function(e){g.setTimeout(function(){throw e})};var W=E.Deferred();function F(){w.removeEventListener("DOMContentLoaded",F),g.removeEventListener("load",F),E.ready()}E.fn.ready=function(e){return W.then(e)["catch"](function(e){E.readyException(e)}),this},E.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--E.readyWait:E.isReady)||(E.isReady=!0)!==e&&0<--E.readyWait||W.resolveWith(w,[E])}}),E.ready.then=W.then,"complete"===w.readyState||"loading"!==w.readyState&&!w.documentElement.doScroll?g.setTimeout(E.ready):(w.addEventListener("DOMContentLoaded",F),g.addEventListener("load",F));var $=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===T(n))for(s in i=!0,n)$(e,t,s,n[s],!0,o,a);else if(void 0!==r&&(i=!0,b(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(E(e),n)})),t))for(;s<u;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},z=/^-ms-/,_=/-([a-z])/g;function U(e,t){return t.toUpperCase()}function V(e){return e.replace(z,"ms-").replace(_,U)}var X=function(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType};function Q(){this.expando=E.expando+Q.uid++}Q.uid=1,Q.prototype={cache:function(e){var t=e[this.expando];return t||(t={},X(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if("string"==typeof t)i[V(t)]=n;else for(r in t)i[V(r)]=t[r];return i},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][V(t)]},access:function(e,t,n){return void 0===t||t&&"string"==typeof t&&void 0===n?this.get(e,t):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r=e[this.expando];if(void 0!==r){if(void 0!==t){n=(t=Array.isArray(t)?t.map(V):(t=V(t))in r?[t]:t.match(H)||[]).length;while(n--)delete r[t[n]]}(void 0===t||E.isEmptyObject(r))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!E.isEmptyObject(t)}};var Y=new Q,G=new Q,K=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,J=/[A-Z]/g;function Z(e,t,n){var r,i;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(J,"-$&").toLowerCase(),"string"==typeof(n=e.getAttribute(r))){try{n="true"===(i=n)||"false"!==i&&("null"===i?null:i===+i+""?+i:K.test(i)?JSON.parse(i):i)}catch(e){}G.set(e,t,n)}else n=void 0;return n}E.extend({hasData:function(e){return G.hasData(e)||Y.hasData(e)},data:function(e,t,n){return G.access(e,t,n)},removeData:function(e,t){G.remove(e,t)},_data:function(e,t,n){return Y.access(e,t,n)},_removeData:function(e,t){Y.remove(e,t)}}),E.fn.extend({data:function(n,e){var t,r,i,o=this[0],a=o&&o.attributes;if(void 0===n){if(this.length&&(i=G.get(o),1===o.nodeType&&!Y.get(o,"hasDataAttrs"))){t=a.length;while(t--)a[t]&&0===(r=a[t].name).indexOf("data-")&&(r=V(r.slice(5)),Z(o,r,i[r]));Y.set(o,"hasDataAttrs",!0)}return i}return"object"==typeof n?this.each(function(){G.set(this,n)}):$(this,function(e){var t;if(o&&void 0===e)return void 0!==(t=G.get(o,n))?t:void 0!==(t=Z(o,n))?t:void 0;this.each(function(){G.set(this,n,e)})},null,e,1<arguments.length,null,!0)},removeData:function(e){return this.each(function(){G.remove(this,e)})}}),E.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=Y.get(e,t),n&&(!r||Array.isArray(n)?r=Y.access(e,t,E.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=E.queue(e,t),r=n.length,i=n.shift(),o=E._queueHooks(e,t);"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,function(){E.dequeue(e,t)},o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return Y.get(e,n)||Y.access(e,n,{empty:E.Callbacks("once memory").add(function(){Y.remove(e,[t+"queue",n])})})}}),E.fn.extend({queue:function(t,n){var e=2;return"string"!=typeof t&&(n=t,t="fx",e--),arguments.length<e?E.queue(this[0],t):void 0===n?this:this.each(function(){var e=E.queue(this,t,n);E._queueHooks(this,t),"fx"===t&&"inprogress"!==e[0]&&E.dequeue(this,t)})},dequeue:function(e){return this.each(function(){E.dequeue(this,e)})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=E.Deferred(),o=this,a=this.length,s=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=void 0),e=e||"fx";while(a--)(n=Y.get(o[a],e+"queueHooks"))&&n.empty&&(r++,n.empty.add(s));return s(),i.promise(t)}});var ee=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,te=new RegExp("^(?:([+-])=|)("+ee+")([a-z%]*)$","i"),ne=["Top","Right","Bottom","Left"],re=w.documentElement,ie=function(e){return E.contains(e.ownerDocument,e)},oe={composed:!0};re.getRootNode&&(ie=function(e){return E.contains(e.ownerDocument,e)||e.getRootNode(oe)===e.ownerDocument});var ae=function(e,t){return"none"===(e=t||e).style.display||""===e.style.display&&ie(e)&&"none"===E.css(e,"display")};var se={};function ue(e,t){for(var n,r,i,o,a,s,u,l=[],c=0,f=e.length;c<f;c++)(r=e[c]).style&&(n=r.style.display,t?("none"===n&&(l[c]=Y.get(r,"display")||null,l[c]||(r.style.display="")),""===r.style.display&&ae(r)&&(l[c]=(u=a=o=void 0,a=(i=r).ownerDocument,s=i.nodeName,(u=se[s])||(o=a.body.appendChild(a.createElement(s)),u=E.css(o,"display"),o.parentNode.removeChild(o),"none"===u&&(u="block"),se[s]=u)))):"none"!==n&&(l[c]="none",Y.set(r,"display",n)));for(c=0;c<f;c++)null!=l[c]&&(e[c].style.display=l[c]);return e}E.fn.extend({show:function(){return ue(this,!0)},hide:function(){return ue(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){ae(this)?E(this).show():E(this).hide()})}});var le,ce,fe=/^(?:checkbox|radio)$/i,de=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i,pe=/^$|^module$|\/(?:java|ecma)script/i;le=w.createDocumentFragment().appendChild(w.createElement("div")),(ce=w.createElement("input")).setAttribute("type","radio"),ce.setAttribute("checked","checked"),ce.setAttribute("name","t"),le.appendChild(ce),m.checkClone=le.cloneNode(!0).cloneNode(!0).lastChild.checked,le.innerHTML="<textarea>x</textarea>",m.noCloneChecked=!!le.cloneNode(!0).lastChild.defaultValue,le.innerHTML="<option></option>",m.option=!!le.lastChild;var he={thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};function ge(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&S(e,t)?E.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n<r;n++)Y.set(e[n],"globalEval",!t||Y.get(t[n],"globalEval"))}he.tbody=he.tfoot=he.colgroup=he.caption=he.thead,he.th=he.td,m.option||(he.optgroup=he.option=[1,"<select multiple='multiple'>","</select>"]);var ye=/<|&#?\w+;/;function me(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),d=[],p=0,h=e.length;p<h;p++)if((o=e[p])||0===o)if("object"===T(o))E.merge(d,o.nodeType?[o]:o);else if(ye.test(o)){a=a||f.appendChild(t.createElement("div")),s=(de.exec(o)||["",""])[1].toLowerCase(),u=he[s]||he._default,a.innerHTML=u[1]+E.htmlPrefilter(o)+u[2],c=u[0];while(c--)a=a.lastChild;E.merge(d,a.childNodes),(a=f.firstChild).textContent=""}else d.push(t.createTextNode(o));f.textContent="",p=0;while(o=d[p++])if(r&&-1<E.inArray(o,r))i&&i.push(o);else if(l=ie(o),a=ge(f.appendChild(o),"script"),l&&ve(a),n){c=0;while(o=a[c++])pe.test(o.type||"")&&n.push(o)}return f}var be=/^key/,xe=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,we=/^([^.]*)(?:\.(.+)|)/;function Ce(){return!0}function Te(){return!1}function Ee(e,t){return e===function(){try{return w.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Te;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return E().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=E.guid++)),e.each(function(){E.event.add(this,t,i,r,n)})}function Ne(e,i,o){o?(Y.set(e,i,!1),E.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Y.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(E.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Y.set(this,i,r),t=o(this,i),this[i](),r!==(n=Y.get(this,i))||t?Y.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Y.set(this,i,{value:E.event.trigger(E.extend(r[0],E.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Y.get(e,i)&&E.event.add(e,i,Ce)}E.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,d,p,h,g,v=Y.get(t);if(X(t)){n.handler&&(n=(o=n).handler,i=o.selector),i&&E.find.matchesSelector(re,i),n.guid||(n.guid=E.guid++),(u=v.events)||(u=v.events=Object.create(null)),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof E&&E.event.triggered!==e.type?E.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(H)||[""]).length;while(l--)p=g=(s=we.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),p&&(f=E.event.special[p]||{},p=(i?f.delegateType:f.bindType)||p,f=E.event.special[p]||{},c=E.extend({type:p,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&E.expr.match.needsContext.test(i),namespace:h.join(".")},o),(d=u[p])||((d=u[p]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(p,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?d.splice(d.delegateCount++,0,c):d.push(c),E.event.global[p]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,d,p,h,g,v=Y.hasData(e)&&Y.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(H)||[""]).length;while(l--)if(p=g=(s=we.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),p){f=E.event.special[p]||{},d=u[p=(r?f.delegateType:f.bindType)||p]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=d.length;while(o--)c=d[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(d.splice(o,1),c.selector&&d.delegateCount--,f.remove&&f.remove.call(e,c));a&&!d.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||E.removeEvent(e,p,v.handle),delete u[p])}else for(p in u)E.event.remove(e,p+t[l],n,r,!0);E.isEmptyObject(u)&&Y.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=new Array(arguments.length),u=E.event.fix(e),l=(Y.get(this,"events")||Object.create(null))[u.type]||[],c=E.event.special[u.type]||{};for(s[0]=u,t=1;t<arguments.length;t++)s[t]=arguments[t];if(u.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,u)){a=E.event.handlers.call(this,u,l),t=0;while((i=a[t++])&&!u.isPropagationStopped()){u.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!u.isImmediatePropagationStopped())u.rnamespace&&!1!==o.namespace&&!u.rnamespace.test(o.namespace)||(u.handleObj=o,u.data=o.data,void 0!==(r=((E.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,s))&&!1===(u.result=r)&&(u.preventDefault(),u.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,u),u.result}},handlers:function(e,t){var n,r,i,o,a,s=[],u=t.delegateCount,l=e.target;if(u&&l.nodeType&&!("click"===e.type&&1<=e.button))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n<u;n++)void 0===a[i=(r=t[n]).selector+" "]&&(a[i]=r.needsContext?-1<E(i,this).index(l):E.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u<t.length&&s.push({elem:l,handlers:t.slice(u)}),s},addProp:function(t,e){Object.defineProperty(E.Event.prototype,t,{enumerable:!0,configurable:!0,get:b(e)?function(){if(this.originalEvent)return e(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[t]},set:function(e){Object.defineProperty(this,t,{enumerable:!0,configurable:!0,writable:!0,value:e})}})},fix:function(e){return e[E.expando]?e:new E.Event(e)},special:{load:{noBubble:!0},click:{setup:function(e){var t=this||e;return fe.test(t.type)&&t.click&&S(t,"input")&&Ne(t,"click",Ce),!1},trigger:function(e){var t=this||e;return fe.test(t.type)&&t.click&&S(t,"input")&&Ne(t,"click"),!0},_default:function(e){var t=e.target;return fe.test(t.type)&&t.click&&S(t,"input")&&Y.get(t,"click")||S(t,"a")}},beforeunload:{postDispatch:function(e){void 0!==e.result&&e.originalEvent&&(e.originalEvent.returnValue=e.result)}}}},E.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n)},E.Event=function(e,t){if(!(this instanceof E.Event))return new E.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||void 0===e.defaultPrevented&&!1===e.returnValue?Ce:Te,this.target=e.target&&3===e.target.nodeType?e.target.parentNode:e.target,this.currentTarget=e.currentTarget,this.relatedTarget=e.relatedTarget):this.type=e,t&&E.extend(this,t),this.timeStamp=e&&e.timeStamp||Date.now(),this[E.expando]=!0},E.Event.prototype={constructor:E.Event,isDefaultPrevented:Te,isPropagationStopped:Te,isImmediatePropagationStopped:Te,isSimulated:!1,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=Ce,e&&!this.isSimulated&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=Ce,e&&!this.isSimulated&&e.stopPropagation()},stopImmediatePropagation:function(){var e=this.originalEvent;this.isImmediatePropagationStopped=Ce,e&&!this.isSimulated&&e.stopImmediatePropagation(),this.stopPropagation()}},E.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,code:!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(e){var t=e.button;return null==e.which&&be.test(e.type)?null!=e.charCode?e.charCode:e.keyCode:!e.which&&void 0!==t&&xe.test(e.type)?1&t?1:2&t?3:4&t?2:0:e.which}},E.event.addProp),E.each({focus:"focusin",blur:"focusout"},function(e,t){E.event.special[e]={setup:function(){return Ne(this,e,Ee),!1},trigger:function(){return Ne(this,e),!0},delegateType:t}}),E.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(e,i){E.event.special[e]={delegateType:i,bindType:i,handle:function(e){var t,n=e.relatedTarget,r=e.handleObj;return n&&(n===this||E.contains(this,n))||(e.type=r.origType,t=r.handler.apply(this,arguments),e.type=i),t}}}),E.fn.extend({on:function(e,t,n,r){return Ae(this,e,t,n,r)},one:function(e,t,n,r){return Ae(this,e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,E(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return!1!==t&&"function"!=typeof t||(n=t,t=void 0),!1===n&&(n=Te),this.each(function(){E.event.remove(this,e,n,t)})}});var Se=/<script|<style|<link/i,ke=/checked\s*(?:[^=]|=\s*.checked.)/i,De=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function Le(e,t){return S(e,"table")&&S(11!==t.nodeType?t:t.firstChild,"tr")&&E(e).children("tbody")[0]||e}function je(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n<r;n++)E.event.add(t,i,s[i][n]);G.hasData(e)&&(o=G.access(e),a=E.extend({},o),G.set(t,a))}}function Pe(n,r,i,o){r=v(r);var e,t,a,s,u,l,c=0,f=n.length,d=f-1,p=r[0],h=b(p);if(h||1<f&&"string"==typeof p&&!m.checkClone&&ke.test(p))return n.each(function(e){var t=n.eq(e);h&&(r[0]=p.call(this,e,t.html())),Pe(t,r,i,o)});if(f&&(t=(e=me(r,n[0].ownerDocument,!1,n,o)).firstChild,1===e.childNodes.length&&(e=t),t||o)){for(s=(a=E.map(ge(e,"script"),je)).length;c<f;c++)u=e,c!==d&&(u=E.clone(u,!0,!0),s&&E.merge(a,ge(u,"script"))),i.call(n[c],u,c);if(s)for(l=a[a.length-1].ownerDocument,E.map(a,qe),c=0;c<s;c++)u=a[c],pe.test(u.type||"")&&!Y.access(u,"globalEval")&&E.contains(l,u)&&(u.src&&"module"!==(u.type||"").toLowerCase()?E._evalUrl&&!u.noModule&&E._evalUrl(u.src,{nonce:u.nonce||u.getAttribute("nonce")},l):C(u.textContent.replace(De,""),u,l))}return n}function He(e,t,n){for(var r,i=t?E.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||E.cleanData(ge(r)),r.parentNode&&(n&&ie(r)&&ve(ge(r,"script")),r.parentNode.removeChild(r));return e}E.extend({htmlPrefilter:function(e){return e},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=ie(e);if(!(m.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||E.isXMLDoc(e)))for(a=ge(c),r=0,i=(o=ge(e)).length;r<i;r++)s=o[r],u=a[r],void 0,"input"===(l=u.nodeName.toLowerCase())&&fe.test(s.type)?u.checked=s.checked:"input"!==l&&"textarea"!==l||(u.defaultValue=s.defaultValue);if(t)if(n)for(o=o||ge(e),a=a||ge(c),r=0,i=o.length;r<i;r++)Oe(o[r],a[r]);else Oe(e,c);return 0<(a=ge(c,"script")).length&&ve(a,!f&&ge(e,"script")),c},cleanData:function(e){for(var t,n,r,i=E.event.special,o=0;void 0!==(n=e[o]);o++)if(X(n)){if(t=n[Y.expando]){if(t.events)for(r in t.events)i[r]?E.event.remove(n,r):E.removeEvent(n,r,t.handle);n[Y.expando]=void 0}n[G.expando]&&(n[G.expando]=void 0)}}}),E.fn.extend({detach:function(e){return He(this,e,!0)},remove:function(e){return He(this,e)},text:function(e){return $(this,function(e){return void 0===e?E.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Pe(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Le(this,e).appendChild(e)})},prepend:function(){return Pe(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Le(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Pe(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Pe(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(E.cleanData(ge(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return E.clone(this,e,t)})},html:function(e){return $(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Se.test(e)&&!he[(de.exec(e)||["",""])[1].toLowerCase()]){e=E.htmlPrefilter(e);try{for(;n<r;n++)1===(t=this[n]||{}).nodeType&&(E.cleanData(ge(t,!1)),t.innerHTML=e);t=0}catch(e){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var n=[];return Pe(this,arguments,function(e){var t=this.parentNode;E.inArray(this,n)<0&&(E.cleanData(ge(this)),t&&t.replaceChild(e,this))},n)}}),E.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,a){E.fn[e]=function(e){for(var t,n=[],r=E(e),i=r.length-1,o=0;o<=i;o++)t=o===i?this:this.clone(!0),E(r[o])[a](t),u.apply(n,t.get());return this.pushStack(n)}});var Ie=new RegExp("^("+ee+")(?!px)[a-z%]+$","i"),Re=function(e){var t=e.ownerDocument.defaultView;return t&&t.opener||(t=g),t.getComputedStyle(e)},Be=function(e,t,n){var r,i,o={};for(i in t)o[i]=e.style[i],e.style[i]=t[i];for(i in r=n.call(e),t)e.style[i]=o[i];return r},Me=new RegExp(ne.join("|"),"i");function We(e,t,n){var r,i,o,a,s=e.style;return(n=n||Re(e))&&(""!==(a=n.getPropertyValue(t)||n[t])||ie(e)||(a=E.style(e,t)),!m.pixelBoxStyles()&&Ie.test(a)&&Me.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o)),void 0!==a?a+"":a}function Fe(e,t){return{get:function(){if(!e())return(this.get=t).apply(this,arguments);delete this.get}}}!function(){function e(){if(l){u.style.cssText="position:absolute;left:-11111px;width:60px;margin-top:1px;padding:0;border:0",l.style.cssText="position:relative;display:block;box-sizing:border-box;overflow:scroll;margin:auto;border:1px;padding:1px;width:60%;top:1%",re.appendChild(u).appendChild(l);var e=g.getComputedStyle(l);n="1%"!==e.top,s=12===t(e.marginLeft),l.style.right="60%",o=36===t(e.right),r=36===t(e.width),l.style.position="absolute",i=12===t(l.offsetWidth/3),re.removeChild(u),l=null}}function t(e){return Math.round(parseFloat(e))}var n,r,i,o,a,s,u=w.createElement("div"),l=w.createElement("div");l.style&&(l.style.backgroundClip="content-box",l.cloneNode(!0).style.backgroundClip="",m.clearCloneStyle="content-box"===l.style.backgroundClip,E.extend(m,{boxSizingReliable:function(){return e(),r},pixelBoxStyles:function(){return e(),o},pixelPosition:function(){return e(),n},reliableMarginLeft:function(){return e(),s},scrollboxSize:function(){return e(),i},reliableTrDimensions:function(){var e,t,n,r;return null==a&&(e=w.createElement("table"),t=w.createElement("tr"),n=w.createElement("div"),e.style.cssText="position:absolute;left:-11111px",t.style.height="1px",n.style.height="9px",re.appendChild(e).appendChild(t).appendChild(n),r=g.getComputedStyle(t),a=3<parseInt(r.height),re.removeChild(e)),a}}))}();var $e=["Webkit","Moz","ms"],ze=w.createElement("div").style,_e={};function Ue(e){var t=E.cssProps[e]||_e[e];return t||(e in ze?e:_e[e]=function(e){var t=e[0].toUpperCase()+e.slice(1),n=$e.length;while(n--)if((e=$e[n]+t)in ze)return e}(e)||e)}var Ve,Xe,Qe=/^(none|table(?!-c[ea]).+)/,Ye=/^--/,Ge={position:"absolute",visibility:"hidden",display:"block"},Ke={letterSpacing:"0",fontWeight:"400"};function Je(e,t,n){var r=te.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function Ze(e,t,n,r,i,o){var a="width"===t?1:0,s=0,u=0;if(n===(r?"border":"content"))return 0;for(;a<4;a+=2)"margin"===n&&(u+=E.css(e,n+ne[a],!0,i)),r?("content"===n&&(u-=E.css(e,"padding"+ne[a],!0,i)),"margin"!==n&&(u-=E.css(e,"border"+ne[a]+"Width",!0,i))):(u+=E.css(e,"padding"+ne[a],!0,i),"padding"!==n?u+=E.css(e,"border"+ne[a]+"Width",!0,i):s+=E.css(e,"border"+ne[a]+"Width",!0,i));return!r&&0<=o&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))||0),u}function et(e,t,n){var r=Re(e),i=(!m.boxSizingReliable()||n)&&"border-box"===E.css(e,"boxSizing",!1,r),o=i,a=We(e,t,r),s="offset"+t[0].toUpperCase()+t.slice(1);if(Ie.test(a)){if(!n)return a;a="auto"}return(!m.boxSizingReliable()&&i||!m.reliableTrDimensions()&&S(e,"tr")||"auto"===a||!parseFloat(a)&&"inline"===E.css(e,"display",!1,r))&&e.getClientRects().length&&(i="border-box"===E.css(e,"boxSizing",!1,r),(o=s in e)&&(a=e[s])),(a=parseFloat(a)||0)+Ze(e,t,n||(i?"border":"content"),o,r,a)+"px"}E.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=We(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=V(t),u=Ye.test(t),l=e.style;if(u||(t=Ue(s)),a=E.cssHooks[t]||E.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"===(o=typeof n)&&(i=te.exec(n))&&i[1]&&(n=function(e,t,n,r){var i,o,a=20,s=r?function(){return r.cur()}:function(){return E.css(e,t,"")},u=s(),l=n&&n[3]||(E.cssNumber[t]?"":"px"),c=e.nodeType&&(E.cssNumber[t]||"px"!==l&&+u)&&te.exec(E.css(e,t));if(c&&c[3]!==l){u/=2,l=l||c[3],c=+u||1;while(a--)E.style(e,t,c+l),(1-o)*(1-(o=s()/u||.5))<=0&&(a=0),c/=o;c*=2,E.style(e,t,c+l),n=n||[]}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}(e,t,i),o="number"),null!=n&&n==n&&("number"!==o||u||(n+=i&&i[3]||(E.cssNumber[s]?"":"px")),m.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=V(t);return Ye.test(t)||(t=Ue(s)),(a=E.cssHooks[t]||E.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=We(e,t,r)),"normal"===i&&t in Ke&&(i=Ke[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),E.each(["height","width"],function(e,u){E.cssHooks[u]={get:function(e,t,n){if(t)return!Qe.test(E.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,u,n):Be(e,Ge,function(){return et(e,u,n)})},set:function(e,t,n){var r,i=Re(e),o=!m.scrollboxSize()&&"absolute"===i.position,a=(o||n)&&"border-box"===E.css(e,"boxSizing",!1,i),s=n?Ze(e,u,n,a,i):0;return a&&o&&(s-=Math.ceil(e["offset"+u[0].toUpperCase()+u.slice(1)]-parseFloat(i[u])-Ze(e,u,"border",!1,i)-.5)),s&&(r=te.exec(t))&&"px"!==(r[3]||"px")&&(e.style[u]=t,t=E.css(e,u)),Je(0,t,s)}}}),E.cssHooks.marginLeft=Fe(m.reliableMarginLeft,function(e,t){if(t)return(parseFloat(We(e,"marginLeft"))||e.getBoundingClientRect().left-Be(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),E.each({margin:"",padding:"",border:"Width"},function(i,o){E.cssHooks[i+o]={expand:function(e){for(var t=0,n={},r="string"==typeof e?e.split(" "):[e];t<4;t++)n[i+ne[t]+o]=r[t]||r[t-2]||r[0];return n}},"margin"!==i&&(E.cssHooks[i+o].set=Je)}),E.fn.extend({css:function(e,t){return $(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=Re(e),i=t.length;a<i;a++)o[t[a]]=E.css(e,t[a],!1,r);return o}return void 0!==n?E.style(e,t,n):E.css(e,t)},e,t,1<arguments.length)}}),E.fn.delay=function(r,e){return r=E.fx&&E.fx.speeds[r]||r,e=e||"fx",this.queue(e,function(e,t){var n=g.setTimeout(e,r);t.stop=function(){g.clearTimeout(n)}})},Ve=w.createElement("input"),Xe=w.createElement("select").appendChild(w.createElement("option")),Ve.type="checkbox",m.checkOn=""!==Ve.value,m.optSelected=Xe.selected,(Ve=w.createElement("input")).value="t",Ve.type="radio",m.radioValue="t"===Ve.value;var tt,nt=E.expr.attrHandle;E.fn.extend({attr:function(e,t){return $(this,E.attr,e,t,1<arguments.length)},removeAttr:function(e){return this.each(function(){E.removeAttr(this,e)})}}),E.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?E.prop(e,t,n):(1===o&&E.isXMLDoc(e)||(i=E.attrHooks[t.toLowerCase()]||(E.expr.match.bool.test(t)?tt:void 0)),void 0!==n?null===n?void E.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=E.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!m.radioValue&&"radio"===t&&S(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(H);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),tt={set:function(e,t,n){return!1===t?E.removeAttr(e,n):e.setAttribute(n,n),n}},E.each(E.expr.match.bool.source.match(/\w+/g),function(e,t){var a=nt[t]||E.find.attr;nt[t]=function(e,t,n){var r,i,o=t.toLowerCase();return n||(i=nt[o],nt[o]=r,r=null!=a(e,t,n)?o:null,nt[o]=i),r}});var rt=/^(?:input|select|textarea|button)$/i,it=/^(?:a|area)$/i;function ot(e){return(e.match(H)||[]).join(" ")}function at(e){return e.getAttribute&&e.getAttribute("class")||""}function st(e){return Array.isArray(e)?e:"string"==typeof e&&e.match(H)||[]}E.fn.extend({prop:function(e,t){return $(this,E.prop,e,t,1<arguments.length)},removeProp:function(e){return this.each(function(){delete this[E.propFix[e]||e]})}}),E.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&E.isXMLDoc(e)||(t=E.propFix[t]||t,i=E.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=E.find.attr(e,"tabindex");return t?parseInt(t,10):rt.test(e.nodeName)||it.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),m.optSelected||(E.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),E.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){E.propFix[this.toLowerCase()]=this}),E.fn.extend({addClass:function(t){var e,n,r,i,o,a,s,u=0;if(b(t))return this.each(function(e){E(this).addClass(t.call(this,e,at(this)))});if((e=st(t)).length)while(n=this[u++])if(i=at(n),r=1===n.nodeType&&" "+ot(i)+" "){a=0;while(o=e[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=ot(r))&&n.setAttribute("class",s)}return this},removeClass:function(t){var e,n,r,i,o,a,s,u=0;if(b(t))return this.each(function(e){E(this).removeClass(t.call(this,e,at(this)))});if(!arguments.length)return this.attr("class","");if((e=st(t)).length)while(n=this[u++])if(i=at(n),r=1===n.nodeType&&" "+ot(i)+" "){a=0;while(o=e[a++])while(-1<r.indexOf(" "+o+" "))r=r.replace(" "+o+" "," ");i!==(s=ot(r))&&n.setAttribute("class",s)}return this},toggleClass:function(i,t){var o=typeof i,a="string"===o||Array.isArray(i);return"boolean"==typeof t&&a?t?this.addClass(i):this.removeClass(i):b(i)?this.each(function(e){E(this).toggleClass(i.call(this,e,at(this),t),t)}):this.each(function(){var e,t,n,r;if(a){t=0,n=E(this),r=st(i);while(e=r[t++])n.hasClass(e)?n.removeClass(e):n.addClass(e)}else void 0!==i&&"boolean"!==o||((e=at(this))&&Y.set(this,"__className__",e),this.setAttribute&&this.setAttribute("class",e||!1===i?"":Y.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&-1<(" "+ot(at(n))+" ").indexOf(t))return!0;return!1}});var ut=/\r/g;E.fn.extend({val:function(n){var r,e,i,t=this[0];return arguments.length?(i=b(n),this.each(function(e){var t;1===this.nodeType&&(null==(t=i?n.call(this,e,E(this).val()):n)?t="":"number"==typeof t?t+="":Array.isArray(t)&&(t=E.map(t,function(e){return null==e?"":e+""})),(r=E.valHooks[this.type]||E.valHooks[this.nodeName.toLowerCase()])&&"set"in r&&void 0!==r.set(this,t,"value")||(this.value=t))})):t?(r=E.valHooks[t.type]||E.valHooks[t.nodeName.toLowerCase()])&&"get"in r&&void 0!==(e=r.get(t,"value"))?e:"string"==typeof(e=t.value)?e.replace(ut,""):null==e?"":e:void 0}}),E.extend({valHooks:{option:{get:function(e){var t=E.find.attr(e,"value");return null!=t?t:ot(E.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r<u;r++)if(((n=i[r]).selected||r===o)&&!n.disabled&&(!n.parentNode.disabled||!S(n.parentNode,"optgroup"))){if(t=E(n).val(),a)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=E.makeArray(t),a=i.length;while(a--)((r=i[a]).selected=-1<E.inArray(E.valHooks.option.get(r),o))&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),E.each(["radio","checkbox"],function(){E.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=-1<E.inArray(E(e).val(),t)}},m.checkOn||(E.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),m.focusin="onfocusin"in g;var lt=/^(?:focusinfocus|focusoutblur)$/,ct=function(e){e.stopPropagation()};E.extend(E.event,{trigger:function(e,t,n,r){var i,o,a,s,u,l,c,f,d=[n||w],p=y.call(e,"type")?e.type:e,h=y.call(e,"namespace")?e.namespace.split("."):[];if(o=f=a=n=n||w,3!==n.nodeType&&8!==n.nodeType&&!lt.test(p+E.event.triggered)&&(-1<p.indexOf(".")&&(p=(h=p.split(".")).shift(),h.sort()),u=p.indexOf(":")<0&&"on"+p,(e=e[E.expando]?e:new E.Event(p,"object"==typeof e&&e)).isTrigger=r?2:3,e.namespace=h.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=n),t=null==t?[e]:E.makeArray(t,[e]),c=E.event.special[p]||{},r||!c.trigger||!1!==c.trigger.apply(n,t))){if(!r&&!c.noBubble&&!x(n)){for(s=c.delegateType||p,lt.test(s+p)||(o=o.parentNode);o;o=o.parentNode)d.push(o),a=o;a===(n.ownerDocument||w)&&d.push(a.defaultView||a.parentWindow||g)}i=0;while((o=d[i++])&&!e.isPropagationStopped())f=o,e.type=1<i?s:c.bindType||p,(l=(Y.get(o,"events")||Object.create(null))[e.type]&&Y.get(o,"handle"))&&l.apply(o,t),(l=u&&o[u])&&l.apply&&X(o)&&(e.result=l.apply(o,t),!1===e.result&&e.preventDefault());return e.type=p,r||e.isDefaultPrevented()||c._default&&!1!==c._default.apply(d.pop(),t)||!X(n)||u&&b(n[p])&&!x(n)&&((a=n[u])&&(n[u]=null),E.event.triggered=p,e.isPropagationStopped()&&f.addEventListener(p,ct),n[p](),e.isPropagationStopped()&&f.removeEventListener(p,ct),E.event.triggered=void 0,a&&(n[u]=a)),e.result}},simulate:function(e,t,n){var r=E.extend(new E.Event,n,{type:e,isSimulated:!0});E.event.trigger(r,null,t)}}),E.fn.extend({trigger:function(e,t){return this.each(function(){E.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return E.event.trigger(e,t,n,!0)}}),m.focusin||E.each({focus:"focusin",blur:"focusout"},function(n,r){var i=function(e){E.event.simulate(r,e.target,E.event.fix(e))};E.event.special[r]={setup:function(){var e=this.ownerDocument||this.document||this,t=Y.access(e,r);t||e.addEventListener(n,i,!0),Y.access(e,r,(t||0)+1)},teardown:function(){var e=this.ownerDocument||this.document||this,t=Y.access(e,r)-1;t?Y.access(e,r,t):(e.removeEventListener(n,i,!0),Y.remove(e,r))}}}),E.parseXML=function(e){var t;if(!e||"string"!=typeof e)return null;try{t=(new g.DOMParser).parseFromString(e,"text/xml")}catch(e){t=void 0}return t&&!t.getElementsByTagName("parsererror").length||E.error("Invalid XML: "+e),t};var ft,dt=/\[\]$/,pt=/\r?\n/g,ht=/^(?:submit|button|image|reset|file)$/i,gt=/^(?:input|select|textarea|keygen)/i;function vt(n,e,r,i){var t;if(Array.isArray(e))E.each(e,function(e,t){r||dt.test(n)?i(n,t):vt(n+"["+("object"==typeof t&&null!=t?e:"")+"]",t,r,i)});else if(r||"object"!==T(e))i(n,e);else for(t in e)vt(n+"["+t+"]",e[t],r,i)}E.param=function(e,t){var n,r=[],i=function(e,t){var n=b(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(null==e)return"";if(Array.isArray(e)||e.jquery&&!E.isPlainObject(e))E.each(e,function(){i(this.name,this.value)});else for(n in e)vt(n,e[n],t,i);return r.join("&")},E.fn.extend({serialize:function(){return E.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=E.prop(this,"elements");return e?E.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!E(this).is(":disabled")&&gt.test(this.nodeName)&&!ht.test(e)&&(this.checked||!fe.test(e))}).map(function(e,t){var n=E(this).val();return null==n?null:Array.isArray(n)?E.map(n,function(e){return{name:t.name,value:e.replace(pt,"\r\n")}}):{name:t.name,value:n.replace(pt,"\r\n")}}).get()}}),E.fn.extend({wrapAll:function(e){var t;return this[0]&&(b(e)&&(e=e.call(this[0])),t=E(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(n){return b(n)?this.each(function(e){E(this).wrapInner(n.call(this,e))}):this.each(function(){var e=E(this),t=e.contents();t.length?t.wrapAll(n):e.append(n)})},wrap:function(t){var n=b(t);return this.each(function(e){E(this).wrapAll(n?t.call(this,e):t)})},unwrap:function(e){return this.parent(e).not("body").each(function(){E(this).replaceWith(this.childNodes)}),this}}),E.expr.pseudos.hidden=function(e){return!E.expr.pseudos.visible(e)},E.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},m.createHTMLDocument=((ft=w.implementation.createHTMLDocument("").body).innerHTML="<form></form><form></form>",2===ft.childNodes.length),E.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(m.createHTMLDocument?((r=(t=w.implementation.createHTMLDocument("")).createElement("base")).href=w.location.href,t.head.appendChild(r)):t=w),o=!n&&[],(i=k.exec(e))?[t.createElement(i[1])]:(i=me([e],t,o),o&&o.length&&E(o).remove(),E.merge([],i.childNodes)));var r,i,o},E.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=E.css(e,"position"),c=E(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=E.css(e,"top"),u=E.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),b(t)&&(t=t.call(e,n,E.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},E.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){E.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===E.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===E.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=E(e).offset()).top+=E.css(e,"borderTopWidth",!0),i.left+=E.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-E.css(r,"marginTop",!0),left:t.left-i.left-E.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===E.css(e,"position"))e=e.offsetParent;return e||re})}}),E.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;E.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),E.each(["top","left"],function(e,n){E.cssHooks[n]=Fe(m.pixelPosition,function(e,t){if(t)return t=We(e,n),Ie.test(t)?E(e).position()[n]+"px":t})}),E.each({Height:"height",Width:"width"},function(a,s){E.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){E.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?E.css(e,t,i):E.style(e,t,n,i)},s,n?e:void 0,n)}})}),E.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),E.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){E.fn[n]=function(e,t){return 0<arguments.length?this.on(n,null,e,t):this.trigger(n)}});var yt=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;E.proxy=function(e,t){var n,r,i;if("string"==typeof t&&(n=e[t],t=e,e=n),b(e))return r=s.call(arguments,2),(i=function(){return e.apply(t||this,r.concat(s.call(arguments)))}).guid=e.guid=e.guid||E.guid++,i},E.holdReady=function(e){e?E.readyWait++:E.ready(!0)},E.isArray=Array.isArray,E.parseJSON=JSON.parse,E.nodeName=S,E.isFunction=b,E.isWindow=x,E.camelCase=V,E.type=T,E.now=Date.now,E.isNumeric=function(e){var t=E.type(e);return("number"===t||"string"===t)&&!isNaN(e-parseFloat(e))},E.trim=function(e){return null==e?"":(e+"").replace(yt,"")},"function"==typeof define&&define.amd&&define("jquery",[],function(){return E});var mt=g.jQuery,bt=g.$;return E.noConflict=function(e){return g.$===E&&(g.$=bt),e&&g.jQuery===E&&(g.jQuery=mt),E},"undefined"==typeof e&&(g.jQuery=g.$=E),E});
diff --git a/apps/auth/src/assets/js/vendors/popper.min.js b/apps/auth/src/assets/js/vendors/popper.min.js
new file mode 100644
index 000000000..bb1aaae3e
--- /dev/null
+++ b/apps/auth/src/assets/js/vendors/popper.min.js
@@ -0,0 +1,5 @@
+/*
+ Copyright (C) Federico Zivolo 2020
+ Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT).
+ */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=e.ownerDocument.defaultView,n=o.getComputedStyle(e,null);return t?n[t]:n}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function i(e){return e&&e.referenceNode?e.referenceNode:e}function r(e){return 11===e?re:10===e?pe:re||pe}function p(e){if(!e)return document.documentElement;for(var o=r(10)?document.body:null,n=e.offsetParent||null;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TH','TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?p(n):n:e?e.ownerDocument.documentElement:document.documentElement}function s(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||p(e.firstElementChild)===e)}function d(e){return null===e.parentNode?e:d(e.parentNode)}function a(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,r=document.createRange();r.setStart(n,0),r.setEnd(i,0);var l=r.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return s(l)?l:p(l);var f=d(e);return f.host?a(f.host,t):a(e,d(t).host)}function l(e){var t=1<arguments.length&&void 0!==arguments[1]?arguments[1]:'top',o='top'===t?'scrollTop':'scrollLeft',n=e.nodeName;if('BODY'===n||'HTML'===n){var i=e.ownerDocument.documentElement,r=e.ownerDocument.scrollingElement||i;return r[o]}return e[o]}function f(e,t){var o=2<arguments.length&&void 0!==arguments[2]&&arguments[2],n=l(t,'top'),i=l(t,'left'),r=o?-1:1;return e.top+=n*r,e.bottom+=n*r,e.left+=i*r,e.right+=i*r,e}function m(e,t){var o='x'===t?'Left':'Top',n='Left'==o?'Right':'Bottom';return parseFloat(e['border'+o+'Width'])+parseFloat(e['border'+n+'Width'])}function h(e,t,o,n){return ee(t['offset'+e],t['scroll'+e],o['client'+e],o['offset'+e],o['scroll'+e],r(10)?parseInt(o['offset'+e])+parseInt(n['margin'+('Height'===e?'Top':'Left')])+parseInt(n['margin'+('Height'===e?'Bottom':'Right')]):0)}function c(e){var t=e.body,o=e.documentElement,n=r(10)&&getComputedStyle(o);return{height:h('Height',t,o,n),width:h('Width',t,o,n)}}function g(e){return le({},e,{right:e.left+e.width,bottom:e.top+e.height})}function u(e){var o={};try{if(r(10)){o=e.getBoundingClientRect();var n=l(e,'top'),i=l(e,'left');o.top+=n,o.left+=i,o.bottom+=n,o.right+=i}else o=e.getBoundingClientRect()}catch(t){}var p={left:o.left,top:o.top,width:o.right-o.left,height:o.bottom-o.top},s='HTML'===e.nodeName?c(e.ownerDocument):{},d=s.width||e.clientWidth||p.width,a=s.height||e.clientHeight||p.height,f=e.offsetWidth-d,h=e.offsetHeight-a;if(f||h){var u=t(e);f-=m(u,'x'),h-=m(u,'y'),p.width-=f,p.height-=h}return g(p)}function b(e,o){var i=2<arguments.length&&void 0!==arguments[2]&&arguments[2],p=r(10),s='HTML'===o.nodeName,d=u(e),a=u(o),l=n(e),m=t(o),h=parseFloat(m.borderTopWidth),c=parseFloat(m.borderLeftWidth);i&&s&&(a.top=ee(a.top,0),a.left=ee(a.left,0));var b=g({top:d.top-a.top-h,left:d.left-a.left-c,width:d.width,height:d.height});if(b.marginTop=0,b.marginLeft=0,!p&&s){var w=parseFloat(m.marginTop),y=parseFloat(m.marginLeft);b.top-=h-w,b.bottom-=h-w,b.left-=c-y,b.right-=c-y,b.marginTop=w,b.marginLeft=y}return(p&&!i?o.contains(l):o===l&&'BODY'!==l.nodeName)&&(b=f(b,o)),b}function w(e){var t=1<arguments.length&&void 0!==arguments[1]&&arguments[1],o=e.ownerDocument.documentElement,n=b(e,o),i=ee(o.clientWidth,window.innerWidth||0),r=ee(o.clientHeight,window.innerHeight||0),p=t?0:l(o),s=t?0:l(o,'left'),d={top:p-n.top+n.marginTop,left:s-n.left+n.marginLeft,width:i,height:r};return g(d)}function y(e){var n=e.nodeName;if('BODY'===n||'HTML'===n)return!1;if('fixed'===t(e,'position'))return!0;var i=o(e);return!!i&&y(i)}function E(e){if(!e||!e.parentElement||r())return document.documentElement;for(var o=e.parentElement;o&&'none'===t(o,'transform');)o=o.parentElement;return o||document.documentElement}function v(e,t,r,p){var s=4<arguments.length&&void 0!==arguments[4]&&arguments[4],d={top:0,left:0},l=s?E(e):a(e,i(t));if('viewport'===p)d=w(l,s);else{var f;'scrollParent'===p?(f=n(o(t)),'BODY'===f.nodeName&&(f=e.ownerDocument.documentElement)):'window'===p?f=e.ownerDocument.documentElement:f=p;var m=b(f,l,s);if('HTML'===f.nodeName&&!y(l)){var h=c(e.ownerDocument),g=h.height,u=h.width;d.top+=m.top-m.marginTop,d.bottom=g+m.top,d.left+=m.left-m.marginLeft,d.right=u+m.left}else d=m}r=r||0;var v='number'==typeof r;return d.left+=v?r:r.left||0,d.top+=v?r:r.top||0,d.right-=v?r:r.right||0,d.bottom-=v?r:r.bottom||0,d}function x(e){var t=e.width,o=e.height;return t*o}function O(e,t,o,n,i){var r=5<arguments.length&&void 0!==arguments[5]?arguments[5]:0;if(-1===e.indexOf('auto'))return e;var p=v(o,n,r,i),s={top:{width:p.width,height:t.top-p.top},right:{width:p.right-t.right,height:p.height},bottom:{width:p.width,height:p.bottom-t.bottom},left:{width:t.left-p.left,height:p.height}},d=Object.keys(s).map(function(e){return le({key:e},s[e],{area:x(s[e])})}).sort(function(e,t){return t.area-e.area}),a=d.filter(function(e){var t=e.width,n=e.height;return t>=o.clientWidth&&n>=o.clientHeight}),l=0<a.length?a[0].key:d[0].key,f=e.split('-')[1];return l+(f?'-'+f:'')}function L(e,t,o){var n=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null,r=n?E(t):a(t,i(o));return b(o,r,n)}function S(e){var t=e.ownerDocument.defaultView,o=t.getComputedStyle(e),n=parseFloat(o.marginTop||0)+parseFloat(o.marginBottom||0),i=parseFloat(o.marginLeft||0)+parseFloat(o.marginRight||0),r={width:e.offsetWidth+i,height:e.offsetHeight+n};return r}function T(e){var t={left:'right',right:'left',bottom:'top',top:'bottom'};return e.replace(/left|right|bottom|top/g,function(e){return t[e]})}function C(e,t,o){o=o.split('-')[0];var n=S(e),i={width:n.width,height:n.height},r=-1!==['right','left'].indexOf(o),p=r?'top':'left',s=r?'left':'top',d=r?'height':'width',a=r?'width':'height';return i[p]=t[p]+t[d]/2-n[d]/2,i[s]=o===s?t[s]-n[a]:t[T(s)],i}function D(e,t){return Array.prototype.find?e.find(t):e.filter(t)[0]}function N(e,t,o){if(Array.prototype.findIndex)return e.findIndex(function(e){return e[t]===o});var n=D(e,function(e){return e[t]===o});return e.indexOf(n)}function P(t,o,n){var i=void 0===n?t:t.slice(0,N(t,'name',n));return i.forEach(function(t){t['function']&&console.warn('`modifier.function` is deprecated, use `modifier.fn`!');var n=t['function']||t.fn;t.enabled&&e(n)&&(o.offsets.popper=g(o.offsets.popper),o.offsets.reference=g(o.offsets.reference),o=n(o,t))}),o}function k(){if(!this.state.isDestroyed){var e={instance:this,styles:{},arrowStyles:{},attributes:{},flipped:!1,offsets:{}};e.offsets.reference=L(this.state,this.popper,this.reference,this.options.positionFixed),e.placement=O(this.options.placement,e.offsets.reference,this.popper,this.reference,this.options.modifiers.flip.boundariesElement,this.options.modifiers.flip.padding),e.originalPlacement=e.placement,e.positionFixed=this.options.positionFixed,e.offsets.popper=C(this.popper,e.offsets.reference,e.placement),e.offsets.popper.position=this.options.positionFixed?'fixed':'absolute',e=P(this.modifiers,e),this.state.isCreated?this.options.onUpdate(e):(this.state.isCreated=!0,this.options.onCreate(e))}}function W(e,t){return e.some(function(e){var o=e.name,n=e.enabled;return n&&o===t})}function B(e){for(var t=[!1,'ms','Webkit','Moz','O'],o=e.charAt(0).toUpperCase()+e.slice(1),n=0;n<t.length;n++){var i=t[n],r=i?''+i+o:e;if('undefined'!=typeof document.body.style[r])return r}return null}function H(){return this.state.isDestroyed=!0,W(this.modifiers,'applyStyle')&&(this.popper.removeAttribute('x-placement'),this.popper.style.position='',this.popper.style.top='',this.popper.style.left='',this.popper.style.right='',this.popper.style.bottom='',this.popper.style.willChange='',this.popper.style[B('transform')]=''),this.disableEventListeners(),this.options.removeOnDestroy&&this.popper.parentNode.removeChild(this.popper),this}function A(e){var t=e.ownerDocument;return t?t.defaultView:window}function M(e,t,o,i){var r='BODY'===e.nodeName,p=r?e.ownerDocument.defaultView:e;p.addEventListener(t,o,{passive:!0}),r||M(n(p.parentNode),t,o,i),i.push(p)}function F(e,t,o,i){o.updateBound=i,A(e).addEventListener('resize',o.updateBound,{passive:!0});var r=n(e);return M(r,'scroll',o.updateBound,o.scrollParents),o.scrollElement=r,o.eventsEnabled=!0,o}function I(){this.state.eventsEnabled||(this.state=F(this.reference,this.options,this.state,this.scheduleUpdate))}function R(e,t){return A(e).removeEventListener('resize',t.updateBound),t.scrollParents.forEach(function(e){e.removeEventListener('scroll',t.updateBound)}),t.updateBound=null,t.scrollParents=[],t.scrollElement=null,t.eventsEnabled=!1,t}function U(){this.state.eventsEnabled&&(cancelAnimationFrame(this.scheduleUpdate),this.state=R(this.reference,this.state))}function Y(e){return''!==e&&!isNaN(parseFloat(e))&&isFinite(e)}function V(e,t){Object.keys(t).forEach(function(o){var n='';-1!==['width','height','top','right','bottom','left'].indexOf(o)&&Y(t[o])&&(n='px'),e.style[o]=t[o]+n})}function j(e,t){Object.keys(t).forEach(function(o){var n=t[o];!1===n?e.removeAttribute(o):e.setAttribute(o,t[o])})}function q(e,t){var o=e.offsets,n=o.popper,i=o.reference,r=$,p=function(e){return e},s=r(i.width),d=r(n.width),a=-1!==['left','right'].indexOf(e.placement),l=-1!==e.placement.indexOf('-'),f=t?a||l||s%2==d%2?r:Z:p,m=t?r:p;return{left:f(1==s%2&&1==d%2&&!l&&t?n.left-1:n.left),top:m(n.top),bottom:m(n.bottom),right:f(n.right)}}function K(e,t,o){var n=D(e,function(e){var o=e.name;return o===t}),i=!!n&&e.some(function(e){return e.name===o&&e.enabled&&e.order<n.order});if(!i){var r='`'+t+'`';console.warn('`'+o+'`'+' modifier is required by '+r+' modifier in order to work, be sure to include it before '+r+'!')}return i}function z(e){return'end'===e?'start':'start'===e?'end':e}function G(e){var t=1<arguments.length&&void 0!==arguments[1]&&arguments[1],o=he.indexOf(e),n=he.slice(o+1).concat(he.slice(0,o));return t?n.reverse():n}function _(e,t,o,n){var i=e.match(/((?:\-|\+)?\d*\.?\d*)(.*)/),r=+i[1],p=i[2];if(!r)return e;if(0===p.indexOf('%')){var s;switch(p){case'%p':s=o;break;case'%':case'%r':default:s=n;}var d=g(s);return d[t]/100*r}if('vh'===p||'vw'===p){var a;return a='vh'===p?ee(document.documentElement.clientHeight,window.innerHeight||0):ee(document.documentElement.clientWidth,window.innerWidth||0),a/100*r}return r}function X(e,t,o,n){var i=[0,0],r=-1!==['right','left'].indexOf(n),p=e.split(/(\+|\-)/).map(function(e){return e.trim()}),s=p.indexOf(D(p,function(e){return-1!==e.search(/,|\s/)}));p[s]&&-1===p[s].indexOf(',')&&console.warn('Offsets separated by white space(s) are deprecated, use a comma (,) instead.');var d=/\s*,\s*|\s+/,a=-1===s?[p]:[p.slice(0,s).concat([p[s].split(d)[0]]),[p[s].split(d)[1]].concat(p.slice(s+1))];return a=a.map(function(e,n){var i=(1===n?!r:r)?'height':'width',p=!1;return e.reduce(function(e,t){return''===e[e.length-1]&&-1!==['+','-'].indexOf(t)?(e[e.length-1]=t,p=!0,e):p?(e[e.length-1]+=t,p=!1,e):e.concat(t)},[]).map(function(e){return _(e,i,t,o)})}),a.forEach(function(e,t){e.forEach(function(o,n){Y(o)&&(i[t]+=o*('-'===e[n-1]?-1:1))})}),i}function J(e,t){var o,n=t.offset,i=e.placement,r=e.offsets,p=r.popper,s=r.reference,d=i.split('-')[0];return o=Y(+n)?[+n,0]:X(n,p,s,d),'left'===d?(p.top+=o[0],p.left-=o[1]):'right'===d?(p.top+=o[0],p.left+=o[1]):'top'===d?(p.left+=o[0],p.top-=o[1]):'bottom'===d&&(p.left+=o[0],p.top+=o[1]),e.popper=p,e}var Q=Math.min,Z=Math.floor,$=Math.round,ee=Math.max,te='undefined'!=typeof window&&'undefined'!=typeof document&&'undefined'!=typeof navigator,oe=function(){for(var e=['Edge','Trident','Firefox'],t=0;t<e.length;t+=1)if(te&&0<=navigator.userAgent.indexOf(e[t]))return 1;return 0}(),ne=te&&window.Promise,ie=ne?function(e){var t=!1;return function(){t||(t=!0,window.Promise.resolve().then(function(){t=!1,e()}))}}:function(e){var t=!1;return function(){t||(t=!0,setTimeout(function(){t=!1,e()},oe))}},re=te&&!!(window.MSInputMethodContext&&document.documentMode),pe=te&&/MSIE 10/.test(navigator.userAgent),se=function(e,t){if(!(e instanceof t))throw new TypeError('Cannot call a class as a function')},de=function(){function e(e,t){for(var o,n=0;n<t.length;n++)o=t[n],o.enumerable=o.enumerable||!1,o.configurable=!0,'value'in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}return function(t,o,n){return o&&e(t.prototype,o),n&&e(t,n),t}}(),ae=function(e,t,o){return t in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o,e},le=Object.assign||function(e){for(var t,o=1;o<arguments.length;o++)for(var n in t=arguments[o],t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e},fe=te&&/Firefox/i.test(navigator.userAgent),me=['auto-start','auto','auto-end','top-start','top','top-end','right-start','right','right-end','bottom-end','bottom','bottom-start','left-end','left','left-start'],he=me.slice(3),ce={FLIP:'flip',CLOCKWISE:'clockwise',COUNTERCLOCKWISE:'counterclockwise'},ge=function(){function t(o,n){var i=this,r=2<arguments.length&&void 0!==arguments[2]?arguments[2]:{};se(this,t),this.scheduleUpdate=function(){return requestAnimationFrame(i.update)},this.update=ie(this.update.bind(this)),this.options=le({},t.Defaults,r),this.state={isDestroyed:!1,isCreated:!1,scrollParents:[]},this.reference=o&&o.jquery?o[0]:o,this.popper=n&&n.jquery?n[0]:n,this.options.modifiers={},Object.keys(le({},t.Defaults.modifiers,r.modifiers)).forEach(function(e){i.options.modifiers[e]=le({},t.Defaults.modifiers[e]||{},r.modifiers?r.modifiers[e]:{})}),this.modifiers=Object.keys(this.options.modifiers).map(function(e){return le({name:e},i.options.modifiers[e])}).sort(function(e,t){return e.order-t.order}),this.modifiers.forEach(function(t){t.enabled&&e(t.onLoad)&&t.onLoad(i.reference,i.popper,i.options,t,i.state)}),this.update();var p=this.options.eventsEnabled;p&&this.enableEventListeners(),this.state.eventsEnabled=p}return de(t,[{key:'update',value:function(){return k.call(this)}},{key:'destroy',value:function(){return H.call(this)}},{key:'enableEventListeners',value:function(){return I.call(this)}},{key:'disableEventListeners',value:function(){return U.call(this)}}]),t}();return ge.Utils=('undefined'==typeof window?global:window).PopperUtils,ge.placements=me,ge.Defaults={placement:'bottom',positionFixed:!1,eventsEnabled:!0,removeOnDestroy:!1,onCreate:function(){},onUpdate:function(){},modifiers:{shift:{order:100,enabled:!0,fn:function(e){var t=e.placement,o=t.split('-')[0],n=t.split('-')[1];if(n){var i=e.offsets,r=i.reference,p=i.popper,s=-1!==['bottom','top'].indexOf(o),d=s?'left':'top',a=s?'width':'height',l={start:ae({},d,r[d]),end:ae({},d,r[d]+r[a]-p[a])};e.offsets.popper=le({},p,l[n])}return e}},offset:{order:200,enabled:!0,fn:J,offset:0},preventOverflow:{order:300,enabled:!0,fn:function(e,t){var o=t.boundariesElement||p(e.instance.popper);e.instance.reference===o&&(o=p(o));var n=B('transform'),i=e.instance.popper.style,r=i.top,s=i.left,d=i[n];i.top='',i.left='',i[n]='';var a=v(e.instance.popper,e.instance.reference,t.padding,o,e.positionFixed);i.top=r,i.left=s,i[n]=d,t.boundaries=a;var l=t.priority,f=e.offsets.popper,m={primary:function(e){var o=f[e];return f[e]<a[e]&&!t.escapeWithReference&&(o=ee(f[e],a[e])),ae({},e,o)},secondary:function(e){var o='right'===e?'left':'top',n=f[o];return f[e]>a[e]&&!t.escapeWithReference&&(n=Q(f[o],a[e]-('right'===e?f.width:f.height))),ae({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=le({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]<r(n[d])&&(e.offsets.popper[d]=r(n[d])-o[a]),o[d]>r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!K(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-u<s[m]&&(e.offsets.popper[m]-=s[m]-(d[c]-u)),d[m]+u>s[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f]),E=parseFloat(w['border'+f+'Width']),v=b-e.offsets.popper[m]-y-E;return v=ee(Q(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},ae(n,m,$(v)),ae(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case ce.FLIP:p=[n,i];break;case ce.CLOCKWISE:p=G(n);break;case ce.COUNTERCLOCKWISE:p=G(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)<f(l.right)||'top'===n&&f(a.bottom)>f(l.top)||'bottom'===n&&f(a.top)<f(l.bottom),h=f(a.left)<f(o.left),c=f(a.right)>f(o.right),g=f(a.top)<f(o.top),u=f(a.bottom)>f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,w=-1!==['top','bottom'].indexOf(n),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u),E=!!t.flipVariationsByContent&&(w&&'start'===r&&c||w&&'end'===r&&h||!w&&'start'===r&&u||!w&&'end'===r&&g),v=y||E;(m||b||v)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),v&&(r=z(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=le({},e.offsets.popper,C(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport',flipVariations:!1,flipVariationsByContent:!1},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!K(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=D(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottom<o.top||t.left>o.right||t.top>o.bottom||t.right<o.left){if(!0===e.hide)return e;e.hide=!0,e.attributes['x-out-of-boundaries']=''}else{if(!1===e.hide)return e;e.hide=!1,e.attributes['x-out-of-boundaries']=!1}return e}},computeStyle:{order:850,enabled:!0,fn:function(e,t){var o=t.x,n=t.y,i=e.offsets.popper,r=D(e.instance.modifiers,function(e){return'applyStyle'===e.name}).gpuAcceleration;void 0!==r&&console.warn('WARNING: `gpuAcceleration` option moved to `computeStyle` modifier and will not be supported in future versions of Popper.js!');var s,d,a=void 0===r?t.gpuAcceleration:r,l=p(e.instance.popper),f=u(l),m={position:i.position},h=q(e,2>window.devicePixelRatio||!fe),c='bottom'===o?'top':'bottom',g='right'===n?'left':'right',b=B('transform');if(d='bottom'==c?'HTML'===l.nodeName?-l.clientHeight+h.bottom:-f.height+h.bottom:h.top,s='right'==g?'HTML'===l.nodeName?-l.clientWidth+h.right:-f.width+h.right:h.left,a&&b)m[b]='translate3d('+s+'px, '+d+'px, 0)',m[c]=0,m[g]=0,m.willChange='transform';else{var w='bottom'==c?-1:1,y='right'==g?-1:1;m[c]=d*w,m[g]=s*y,m.willChange=c+', '+g}var E={"x-placement":e.placement};return e.attributes=le({},E,e.attributes),e.styles=le({},m,e.styles),e.arrowStyles=le({},e.offsets.arrow,e.arrowStyles),e},gpuAcceleration:!0,x:'bottom',y:'right'},applyStyle:{order:900,enabled:!0,fn:function(e){return V(e.instance.popper,e.styles),j(e.instance.popper,e.attributes),e.arrowElement&&Object.keys(e.arrowStyles).length&&V(e.arrowElement,e.arrowStyles),e},onLoad:function(e,t,o,n,i){var r=L(i,t,e,o.positionFixed),p=O(o.placement,r,t,e,o.modifiers.flip.boundariesElement,o.modifiers.flip.padding);return t.setAttribute('x-placement',p),V(t,{position:o.positionFixed?'fixed':'absolute'}),o},gpuAcceleration:void 0}}},ge});
+//# sourceMappingURL=popper.min.js.map
diff --git a/apps/auth/src/assets/site.webmanifest b/apps/auth/src/assets/site.webmanifest
new file mode 100644
index 000000000..af658222d
--- /dev/null
+++ b/apps/auth/src/assets/site.webmanifest
@@ -0,0 +1,19 @@
+{
+    "name": "THX",
+    "short_name": "THX",
+    "icons": [
+        {
+            "src": "/img/icons/android-chrome-192x192.png?v=pgdmXmoa2w",
+            "sizes": "192x192",
+            "type": "image/png"
+        },
+        {
+            "src": "/img/icons/android-chrome-512x512.png?v=pgdmXmoa2w",
+            "sizes": "512x512",
+            "type": "image/png"
+        }
+    ],
+    "theme_color": "#ffffff",
+    "background_color": "#ffffff",
+    "display": "standalone"
+}
diff --git a/apps/auth/src/assets/views/account.ejs b/apps/auth/src/assets/views/account.ejs
new file mode 100644
index 000000000..7efbecf08
--- /dev/null
+++ b/apps/auth/src/assets/views/account.ejs
@@ -0,0 +1,255 @@
+<div class="col-md-6 offset-md-3 order-0 order-sm-1">
+    <div class="text-center pb-4 pt-4">
+        <img src="/img/logo.png" width="60" alt="THX Logo" />
+    </div>
+    <div class="card shadow-sm mb-5 w-100">
+        <div class="card-body p-sm-5">
+            <strong class="h3 d-block text-dark text-center mb-5">
+                Account
+            </strong>
+            <% if (alert && alert.message) { %>
+            <div class="alert alert-<%= alert.variant %>">
+                <%= alert.message %>
+            </div>
+            <% } %>
+
+            <form id="account_update" autocomplete="off" action="/oidc/<%= uid %>/account" enctype="multipart/form-data"
+                  method="POST">
+
+                <div class="row pb-3">
+                    <div class="d-flex col-4 align-items-center">
+                        Picture
+                    </div>
+                    <div class="col-8">
+                        <div class="input-group">
+                            <% if (params.profileImg) { %>
+                            <div class="input-group-prepend px-0">
+                                <div class="input-group-text">
+                                    <img width="25" src="<%= params.profileImg %>" alt="profle-picture"
+                                         class="rounded-circle" />
+                                </div>
+                            </div>
+                            <% } %>
+                            <input class="form-control" name="profile" type="file" accept="image/*">
+                        </div>
+                    </div>
+                </div>
+
+                <div class="row pb-3">
+                    <div class="d-flex col-4 align-items-center">
+                        Email
+                    </div>
+                    <div class="col-8">
+                        <div class="input-group">
+                            <div class="input-group-prepend">
+                                <span class="input-group-text px-3">
+                                    <% if (params.isEmailVerified) { %>
+                                    <i class="fas fa-check-circle text-success"></i>
+                                    <% } else { %>
+                                    <i class="fas fa-check-circle text-danger"></i>
+                                    <% }%>
+                                </span>
+                            </div>
+                            <% if (!params.email) { %>
+                            <input class="form-control" name="email" required value="" />
+                            <% } else if (!params.isEmailVerified) { %>
+                            <input class="form-control" name="email" required value="<%= params.email %>" />
+                            <% } else { %>
+                            <input class="form-control" name="email" required disabled value="<%= params.email %>" />
+                            <% }  %>
+                        </div>
+                    </div>
+                </div>
+
+                <div class="row pb-3">
+                    <div class="d-flex col-4 align-items-center">
+                        Plan
+                        <sup data-toggle="tooltip"
+                             title="Send an e-mail to info@thx.network if you want to upgrade or cancel your current plan.">
+                            <i class="fas fa-question-circle text-gray ml-1"></i>
+                        </sup>
+                    </div>
+                    <div class="col-8">
+                        <input type="text" class="form-control" disabled value="<%= params.planType %>" />
+                    </div>
+                </div>
+
+                <div class="row pb-3">
+                    <div class="d-flex col-4 align-items-center">
+                        First name
+                    </div>
+                    <div class="col-8">
+                        <input class="form-control" name="firstName" value="<%= params.firstName %>" placeholder="" />
+                    </div>
+                </div>
+
+                <div class="row pb-3">
+                    <div class="d-flex col-4 align-items-center">
+                        Last name
+                    </div>
+                    <div class="col-8">
+                        <input class="form-control" name="lastName" value="<%= params.lastName %>" placeholder="" />
+                    </div>
+                </div>
+
+                <div class="row pb-3">
+                    <div class="d-flex col-4 align-items-center ">
+                        Organisation
+                    </div>
+                    <div class="col-8">
+                        <input class="form-control" name="organisation" value="<%= params.organisation %>"
+                               placeholder="" />
+                    </div>
+                </div>
+
+                <div class="row pb-3">
+                    <div class="d-flex col-4 align-items-center">
+                        Website
+                    </div>
+                    <div class="col-8">
+                        <input class="form-control" name="website" value="<%= params.website %>" placeholder="" />
+                    </div>
+                </div>
+
+                <% if (params.googleAccess) { %>
+                <input form="account_update" type="hidden" name="googleAccess" value="false" />
+                <% } %>
+
+                <% if (params.twitterAccess) { %>
+                <input form="account_update" type="hidden" name="twitterAccess" value="false" />
+                <% } %>
+
+                <input type="hidden" name="return_url" value="<%= params.return_url %>" />
+            </form>
+            <hr>
+            <div class="row">
+                <div class="d-flex col-4 align-items-start flex-column font-weight-bold">
+                    Connect
+                    <small class="text-muted">Link your other accounts using single sign-on.</small>
+                </div>
+                <div class="d-flex col-8">
+                    <div class="w-100 d-flex flex-column">
+                        <!-- <% if (params.twitchLoginUrl) { %>
+                        <a class="btn btn-sign-in btn-twitch btn-block my-1" href="<%= params.twitchLoginUrl %>">
+                            <div class="logo">
+                                <img src="/img/twitch-logo.png" alt="twitch logo" />
+                            </div>
+                            <div class="label">Connect Twitch</div>
+                        </a>
+                        <% } %>
+                        <% if (!params.twitchLoginUrl) { %>
+                        <form method="post" action="/oidc/<%= uid %>/tokens/twitch/disconnect">
+                            <button class="btn btn-sign-in btn-discord bg-gray btn-block my-1" type="submit">
+                                <div class="logo">
+                                    <img src="/img/twitch-logo.png" alt="Twitch logo" />
+                                </div>
+                                <div class="label">Disconnect Twitch</div>
+                            </button>
+                        </form>
+                        <% } %> -->
+                        <% if (params.discordLoginUrl) { %>
+                        <a class="btn btn-sign-in btn-discord btn-block my-1" href="<%= params.discordLoginUrl %>">
+                            <div class="logo">
+                                <img src="/img/discord-logo.png" alt="Discord logo" />
+                            </div>
+                            <div class="label">Connect Discord</div>
+                        </a>
+                        <% } %>
+                        <% if (!params.discordLoginUrl) { %>
+                        <form method="post" action="/oidc/<%= uid %>/tokens/discord/disconnect">
+                            <button class="btn btn-sign-in btn-twitch bg-gray btn-block my-1" type="submit">
+                                <div class="logo">
+                                    <img src="/img/discord-logo.png" alt="Twitch logo" />
+                                </div>
+                                <div class="label">Disconnect Discord</div>
+                            </button>
+                        </form>
+                        <% } %>
+                        <!-- <% if (!params.googleLoginUrl) { %>
+                        <a class="btn btn-sign-in btn-google btn-block my-1" href="<%= params.googleLoginUrl %>">
+                            <div class="logo">
+                                <img src="/img/g-logo.png" alt="Google logo" />
+                            </div>
+                            <div class="label">Connect Google</div>
+                        </a>
+                        <% } %>
+                        <% if (params.googleLoginUrl) { %>
+                        <form method="post" action="/oidc/<%= uid %>/tokens/google/disconnect">
+                            <button class="btn btn-sign-in btn-google bg-gray btn-block my-1" type="submit">
+                                <div class="logo">
+                                    <img src="/img/g-logo.png" alt="Google logo" />
+                                </div>
+                                <div class="label">Disconnect Google</div>
+                            </button>
+                        </form>
+                        <% } %>
+                        <% if (params.githubLoginUrl) { %>
+                        <a class="btn btn-sign-in btn-github btn-block my-1" href="<%= params.githubLoginUrl %>">
+                            <div class="logo">
+                                <img src="/img/github-logo.png" alt="github logo" />
+                            </div>
+                            <div class="label">Connect Github</div>
+                        </a>
+                        <% } %>
+                        <% if (!params.githubLoginUrl) { %>
+                        <form method="post" action="/oidc/<%= uid %>/tokens/github/disconnect">
+                            <button class="btn btn-sign-in bg-gray btn-github btn-block my-1" type="submit">
+                                <div class="logo">
+                                    <img src="/img/github-logo.png" alt="github logo" />
+                                </div>
+                                <div class="label">Disconnect Github</div>
+                            </button>
+                        </form>
+                        <% } %> -->
+                        <% if (params.twitterLoginUrl) { %>
+                        <a class="btn btn-sign-in btn-twitter btn-block my-1" href="<%= params.twitterLoginUrl %>">
+                            <div class="logo">
+                                <img src="/img/t-logo.png" alt="Twitter logo" />
+                            </div>
+                            <div class="label">Connect Twitter</div>
+                        </a>
+                        <% } %>
+                        <% if (!params.twitterLoginUrl) { %>
+                        <form method="post" action="/oidc/<%= uid %>/tokens/twitter/disconnect">
+                            <button class="btn btn-sign-in bg-gray btn-twitter btn-block my-1" type="submit">
+                                <div class="logo">
+                                    <img src="/img/t-logo.png" alt="Twitter logo" />
+                                </div>
+                                <div class="label">Disconnect Twitter</div>
+                            </button>
+                        </form>
+                        <% } %>
+                    </div>
+                </div>
+            </div>
+            <hr>
+            <div class="row pb-3">
+                <div class="d-flex col-4 align-items-start justify-content-start flex-column font-weight-bold">
+                    MFA
+                    <small class="text-muted">Multi-factor Authentication using time-based one-time passwords
+                        (TOTP).</small>
+                </div>
+                <div class="col-8">
+                    <% if (params.otpSecret) { %>
+                    <form autocomplete="off" action="/oidc/<%= uid %>/account/totp" method="POST">
+                        <button class="btn btn-light text-danger btn-block">Deactivate</button>
+                        <input type="hidden" name="disable" value="true" />
+                    </form>
+                    <% } %>
+                    <% if (!params.otpSecret) { %>
+                    <form autocomplete="off" action="/oidc/<%= uid %>/account/totp" method="POST">
+                        <button type="submit" class="btn btn-light text-primary btn-block">Activate</button>
+                    </form>
+                    <% } %>
+                </div>
+            </div>
+
+            <button class="mt-5 btn btn-primary btn-block rounded-pill" type="submit" form="account_update">
+                Update Infomation
+            </button>
+            <a class="btn btn-link btn-block rounded-pill" href="<%= params.return_url %>">
+                Return to application
+            </a>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/apps/auth/src/assets/views/confirm.ejs b/apps/auth/src/assets/views/confirm.ejs
new file mode 100644
index 000000000..9ac8bde61
--- /dev/null
+++ b/apps/auth/src/assets/views/confirm.ejs
@@ -0,0 +1,29 @@
+<div class="col-md-6 offset-md-3 col-xl-4 offset-xl-4 order-0 order-sm-1">
+    <div class="text-center pb-4 pt-4">
+        <img src="/img/logo.png" width="60" alt="THX Logo" />
+    </div>
+    <div class="card shadow-sm mb-5">
+        <div class="card-body p-sm-5">
+            <% if (locals.alert && alert.variant === 'danger') { %>
+            <strong class="h2 d-block text-dark text-center mb-4">
+                Oops...
+            </strong>
+            <div class="alert alert-<%= alert.variant %>">
+                <%= alert.message %>
+            </div>
+            <% } else { %>
+            <strong class="h2 d-block text-dark text-center mb-4">
+                Congratulations!
+            </strong>
+            <div class="alert alert-success">Your e-mail address has been verified.</div>
+            <p>
+                Make sure to enable MFA for your account after signing in. This will significantly improve the security
+                of your wallet.
+            </p>
+            <a href="<%= params.return_url %>" class="btn rounded-pill btn-primary btn-block">
+                Sign in
+            </a>
+            <% } %>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/apps/auth/src/assets/views/error.ejs b/apps/auth/src/assets/views/error.ejs
new file mode 100644
index 000000000..2a2bac873
--- /dev/null
+++ b/apps/auth/src/assets/views/error.ejs
@@ -0,0 +1,55 @@
+<div class="col-md-8 offset-md-2 col-xl-4 offset-xl-4 order-0 order-sm-1">
+    <div class="text-center pb-4 pt-4">
+        <img src="/img/logo.png" width="60" alt="THX Logo" />
+    </div>
+    <div class="card shadow-sm mb-5">
+        <div class="card-body pt-sm-5 px-sm-5 pb-md-4">
+            <strong class="h3 d-block text-dark mb-4">
+                Oops, a wild issue appeared!
+            </strong>
+
+            <% if (locals.alert && alert.message) { %>
+            <div class="alert alert-<%= alert.variant %> mt-4 py-1 px-2">
+                <i class="fas fa-exclamation-circle mr-1"></i>
+                <%= alert.message %>
+            </div>
+            <% } %>
+
+            <p>Please use the buttons below to find your way back.</p>
+            <a class="btn rounded-pill btn-light btn-sm btn-block" href="<%= widgetUrl %>" target="_blank">
+                Campaigns
+                <i class="fas fa-chevron-right" aria-hidden="true"></i>
+            </a>
+            <a class="btn rounded-pill btn-light btn-sm btn-block" href="<%= publicUrl %>" target="_blank">
+                Website
+                <i class="fas fa-chevron-right" aria-hidden="true"></i>
+            </a>
+            <a class="btn rounded-pill btn-light btn-sm btn-block" href="https://docs.thx.network" target="_blank">
+                Docs
+                <i class="fas fa-chevron-right" aria-hidden="true"></i>
+            </a>
+
+            <% if (returnUrl) { %>
+            <input type="hidden" name="returnUrl" value="<%= returnUrl %>" />
+            <% } %>
+        </div>
+
+        <div class="card-footer d-flex align-items-center justify-content-end">
+            <a id="btnReturn" href="<%= returnUrl %>" class="text-muted mr-auto cursor-pointer">Return to app</a>
+            <a href="https://discord.com/invite/TzbbSmkE7Y" class="text-muted">Help</a>
+        </div>
+
+    </div>
+</div>
+<script>
+    const returnUrl = document.getElementsByName('returnUrl')[0];
+    document.getElementById('btnReturn').addEventListener('click', () => {
+        if (window.matchMedia('(pointer:coarse)').matches) {
+            if (returnUrl) {
+                window.open(returnUrl.value, '_self');
+            }
+        } else {
+            window.close();
+        }
+    })
+</script>
\ No newline at end of file
diff --git a/apps/auth/src/assets/views/layouts/default.ejs b/apps/auth/src/assets/views/layouts/default.ejs
new file mode 100644
index 000000000..2b61df4fe
--- /dev/null
+++ b/apps/auth/src/assets/views/layouts/default.ejs
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html lang="en" class="h-100">
+
+<head>
+    <meta charset="UTF-8">
+    <title>THX Network | Quest &amp; Reward Engine</title>
+    <meta name="viewport"
+          content="width=device-width,initial-scale=1,shrink-to-fit=no,user-scalable=no,maximum-scale=1">
+    <meta name="msapplication-TileColor" content="#da532c">
+    <meta name="theme-color" content="#ffffff">
+    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+    <meta http-equiv="Pragma" content="no-cache" />
+    <meta http-equiv="Expires" content="0" />
+    <link rel="stylesheet" href="/css/bootstrap.min.css">
+    <link rel="stylesheet" href="/css/main.css">
+    <link rel="apple-touch-icon" sizes="180x180" href="/img/icons/apple-touch-icon.png?v=pgdmXmoa2w">
+    <link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png?v=pgdmXmoa2w">
+    <link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png?v=pgdmXmoa2w">
+    <link rel="manifest" href="/site.webmanifest?v=pgdmXmoa2w">
+    <link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg?v=pgdmXmoa2w" color="#5bbad5">
+</head>
+
+<body class="d-lg-flex align-items-center justify-content-center">
+    <div class="container">
+        <div class="row pt-sm-3 pb-sm-3">
+            <%- body %>
+        </div>
+
+        <% if (gtm) { %>
+        <script>
+            (function (w, d, s, l, i) {
+                w[l] = w[l] || [];
+                w[l].push({
+                    'gtm.start': new Date().getTime(),
+                    'event': 'gtm.js',
+                });
+                const f = d.getElementsByTagName(s)[0],
+                    j = d.createElement(s),
+                    dl = l != 'dataLayer' ? '&l=' + l : '';
+                j.async = true;
+                j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl;
+                f.parentNode.insertBefore(j, f);
+            })(window, document, 'script', 'dataLayer', "<%= gtm %>");
+        </script>
+        <% } %>
+        <script src="/js/vendors/jquery.slim.min.js"></script>
+        <script src="/js/vendors/bootstrap.bundle.min.js"></script>
+        <script src="/js/vendors/popper.min.js"></script>
+        <script>
+            $(function () {
+                $('[data-toggle="tooltip"]').tooltip();
+                $('[data-toggle="popover"]').popover();
+                $('.collapse').collapse({
+                    toggle: false
+                });
+            })
+        </script>
+        <script src="/js/vendors/fontawesome.06b7267748.js" crossorigin="anonymous"></script>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/apps/auth/src/assets/views/mail/email-otp.ejs b/apps/auth/src/assets/views/mail/email-otp.ejs
new file mode 100644
index 000000000..928100933
--- /dev/null
+++ b/apps/auth/src/assets/views/mail/email-otp.ejs
@@ -0,0 +1,409 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <title>Activate your THX Account</title>
+  <style>
+    /* -------------------------------------
+          GLOBAL RESETS
+      ------------------------------------- */
+
+    img {
+      border: none;
+      -ms-interpolation-mode: bicubic;
+      max-width: 100%;
+    }
+
+    body {
+      background-color: #f6f6f6;
+      font-family: sans-serif;
+      -webkit-font-smoothing: antialiased;
+      font-size: 14px;
+      line-height: 1.4;
+      margin: 0;
+      padding: 0;
+      -ms-text-size-adjust: 100%;
+      -webkit-text-size-adjust: 100%;
+    }
+
+    table {
+      border-collapse: separate;
+      mso-table-lspace: 0pt;
+      mso-table-rspace: 0pt;
+      width: 100%;
+    }
+
+    table td {
+      font-family: sans-serif;
+      font-size: 14px;
+      vertical-align: top;
+    }
+
+    /* -------------------------------------
+          BODY & CONTAINER
+      ------------------------------------- */
+
+    .body {
+      background-color: #f6f6f6;
+      width: 100%;
+    }
+
+    /* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
+    .container {
+      display: block;
+      margin: 0 auto !important;
+      /* makes it centered */
+      max-width: 580px;
+      padding: 10px;
+      width: 580px;
+    }
+
+    /* This should also be a block element, so that it will fill 100% of the .container */
+    .content {
+      box-sizing: border-box;
+      display: block;
+      margin: 0 auto;
+      max-width: 580px;
+      padding: 10px;
+    }
+
+    /* -------------------------------------
+          HEADER, FOOTER, MAIN
+      ------------------------------------- */
+    .main {
+      background: #ffffff;
+      border-radius: 3px;
+      width: 100%;
+    }
+
+    .wrapper {
+      box-sizing: border-box;
+      padding: 20px;
+    }
+
+    .content-block {
+      padding-bottom: 10px;
+      padding-top: 10px;
+    }
+
+    .footer {
+      clear: both;
+      margin-top: 10px;
+      text-align: center;
+      width: 100%;
+    }
+
+    .footer td,
+    .footer p,
+    .footer span,
+    .footer a {
+      color: #999999;
+      font-size: 12px;
+      text-align: center;
+    }
+
+    /* -------------------------------------
+          TYPOGRAPHY
+      ------------------------------------- */
+    h1,
+    h2,
+    h3,
+    h4 {
+      color: #000000;
+      font-family: sans-serif;
+      font-weight: 400;
+      line-height: 1.4;
+      margin: 0;
+      margin-bottom: 30px;
+    }
+
+    h1 {
+      font-size: 35px;
+      font-weight: 300;
+      text-align: center;
+      text-transform: capitalize;
+    }
+
+    p,
+    ul,
+    ol {
+      font-family: sans-serif;
+      font-size: 16px;
+      font-weight: normal;
+      margin: 0;
+      margin-bottom: 15px;
+    }
+
+    p li,
+    ul li,
+    ol li {
+      list-style-position: inside;
+      margin-left: 5px;
+    }
+
+    a {
+      color: #3498db;
+      text-decoration: underline;
+    }
+
+    /* -------------------------------------
+          BUTTONS
+      ------------------------------------- */
+    .btn {
+      box-sizing: border-box;
+      width: 100%;
+    }
+
+    .btn>tbody>tr>td {
+      padding-bottom: 15px;
+    }
+
+    .btn table {
+      width: auto;
+    }
+
+    .btn table td {
+      background-color: #ffffff;
+      border-radius: 5px;
+      text-align: center;
+    }
+
+    .btn a {
+      display: block;
+      font-size: 16px !important;
+      width: 100%;
+      text-align: center;
+      color: #ffffff;
+      background-color: #5942c1;
+      border: 0;
+      border-radius: 50rem;
+      box-sizing: border-box;
+      cursor: pointer;
+      text-decoration: none;
+      font-size: 12px;
+      margin: 0;
+      padding: 12px 25px;
+    }
+
+    .btn-primary table td {
+      background-color: #3498db;
+    }
+
+    .btn-primary a {
+      background-color: #3498db;
+      border-color: #3498db;
+      color: #ffffff;
+    }
+
+    /* -------------------------------------
+          OTHER STYLES THAT MIGHT BE USEFUL
+      ------------------------------------- */
+    .last {
+      margin-bottom: 0;
+    }
+
+    .first {
+      margin-top: 0;
+    }
+
+    .align-center {
+      text-align: center;
+    }
+
+    .align-right {
+      text-align: right;
+    }
+
+    .align-left {
+      text-align: left;
+    }
+
+    .clear {
+      clear: both;
+    }
+
+    .mt0 {
+      margin-top: 0;
+    }
+
+    .mb0 {
+      margin-bottom: 0;
+    }
+
+    .preheader {
+      color: transparent;
+      display: none;
+      height: 0;
+      max-height: 0;
+      max-width: 0;
+      opacity: 0;
+      overflow: hidden;
+      mso-hide: all;
+      visibility: hidden;
+      width: 0;
+    }
+
+    .powered-by a {
+      text-decoration: none;
+    }
+
+    hr {
+      border: 0;
+      border-bottom: 1px solid #f6f6f6;
+      margin: 20px 0;
+    }
+
+    /* -------------------------------------
+          RESPONSIVE AND MOBILE FRIENDLY STYLES
+      ------------------------------------- */
+    @media only screen and (max-width: 620px) {
+      table.body h1 {
+        font-size: 28px !important;
+        margin-bottom: 10px !important;
+      }
+
+      table.body p,
+      table.body ul,
+      table.body ol,
+      table.body td,
+      table.body span,
+      table.body a {
+        font-size: 16px !important;
+      }
+
+      table.body .wrapper,
+      table.body .article {
+        padding: 10px !important;
+      }
+
+      table.body .content {
+        padding: 0 !important;
+      }
+
+      table.body .container {
+        padding: 0 !important;
+        width: 100% !important;
+      }
+
+      table.body .main {
+        border-left-width: 0 !important;
+        border-radius: 0 !important;
+        border-right-width: 0 !important;
+      }
+
+      table.body .btn table {
+        width: 100% !important;
+      }
+
+      table.body .btn a {
+        width: 100% !important;
+      }
+
+      table.body .img-responsive {
+        height: auto !important;
+        max-width: 100% !important;
+        width: auto !important;
+      }
+    }
+
+    /* -------------------------------------
+          PRESERVE THESE STYLES IN THE HEAD
+      ------------------------------------- */
+    @media all {
+      .ExternalClass {
+        width: 100%;
+      }
+
+      .ExternalClass,
+      .ExternalClass p,
+      .ExternalClass span,
+      .ExternalClass font,
+      .ExternalClass td,
+      .ExternalClass div {
+        line-height: 100%;
+      }
+
+      .apple-link a {
+        color: inherit !important;
+        font-family: inherit !important;
+        font-size: inherit !important;
+        font-weight: inherit !important;
+        line-height: inherit !important;
+        text-decoration: none !important;
+      }
+
+      #MessageViewBody a {
+        color: inherit;
+        text-decoration: none;
+        font-size: inherit;
+        font-family: inherit;
+        font-weight: inherit;
+        line-height: inherit;
+      }
+
+      .btn-primary table td:hover {
+        background-color: #34495e !important;
+      }
+
+      .btn-primary a:hover {
+        background-color: #34495e !important;
+        border-color: #34495e !important;
+      }
+    }
+  </style>
+</head>
+
+<body>
+  <table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body">
+    <tr>
+      <td>&nbsp;</td>
+      <td class="container">
+        <div class="content">
+          <!-- START CENTERED WHITE CONTAINER -->
+          <table role="presentation" class="main">
+            <!-- START MAIN CONTENT AREA -->
+            <tr>
+              <td class="wrapper">
+                <table role="presentation" border="0" cellpadding="0" cellspacing="0">
+                  <tr>
+                    <td>
+                      <p style="font-size: 18px;">Hi!</p>
+                      <p style="font-size: 21px;">A sign in is requested for your account.</p>
+                      <p>
+                        <strong style="font-size: 21px;"> <%= otp %> </strong>
+                      </p>
+                      <p>
+                        This one-time password will expire after 10 minutes.
+                      </p>
+                    </td>
+                  </tr>
+                </table>
+              </td>
+            </tr>
+
+            <!-- END MAIN CONTENT AREA -->
+          </table>
+          <!-- END CENTERED WHITE CONTAINER -->
+
+          <!-- START FOOTER -->
+          <div class="footer">
+            <table role="presentation" border="0" cellpadding="0" cellspacing="0">
+
+              <tr>
+                <td class="content-block powered-by">
+                  <img src="<%= baseUrl %>/img/logo.png" width="60" alt="THX Logo">
+                </td>
+              </tr>
+            </table>
+          </div>
+          <!-- END FOOTER -->
+        </div>
+      </td>
+      <td>&nbsp;</td>
+    </tr>
+  </table>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/apps/auth/src/assets/views/mail/email-verify.ejs b/apps/auth/src/assets/views/mail/email-verify.ejs
new file mode 100644
index 000000000..f6ce55b8e
--- /dev/null
+++ b/apps/auth/src/assets/views/mail/email-verify.ejs
@@ -0,0 +1,418 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <title>Confirm your E-mail Address</title>
+  <style>
+    /* -------------------------------------
+          GLOBAL RESETS
+      ------------------------------------- */
+
+    img {
+      border: none;
+      -ms-interpolation-mode: bicubic;
+      max-width: 100%;
+    }
+
+    body {
+      background-color: #f6f6f6;
+      font-family: sans-serif;
+      -webkit-font-smoothing: antialiased;
+      font-size: 14px;
+      line-height: 1.4;
+      margin: 0;
+      padding: 0;
+      -ms-text-size-adjust: 100%;
+      -webkit-text-size-adjust: 100%;
+    }
+
+    table {
+      border-collapse: separate;
+      mso-table-lspace: 0pt;
+      mso-table-rspace: 0pt;
+      width: 100%;
+    }
+
+    table td {
+      font-family: sans-serif;
+      font-size: 14px;
+      vertical-align: top;
+    }
+
+    /* -------------------------------------
+          BODY & CONTAINER
+      ------------------------------------- */
+
+    .body {
+      background-color: #f6f6f6;
+      width: 100%;
+    }
+
+    /* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
+    .container {
+      display: block;
+      margin: 0 auto !important;
+      /* makes it centered */
+      max-width: 580px;
+      padding: 10px;
+      width: 580px;
+    }
+
+    /* This should also be a block element, so that it will fill 100% of the .container */
+    .content {
+      box-sizing: border-box;
+      display: block;
+      margin: 0 auto;
+      max-width: 580px;
+      padding: 10px;
+    }
+
+    /* -------------------------------------
+          HEADER, FOOTER, MAIN
+      ------------------------------------- */
+    .main {
+      background: #ffffff;
+      border-radius: 3px;
+      width: 100%;
+    }
+
+    .wrapper {
+      box-sizing: border-box;
+      padding: 20px;
+    }
+
+    .content-block {
+      padding-bottom: 10px;
+      padding-top: 10px;
+    }
+
+    .footer {
+      clear: both;
+      margin-top: 10px;
+      text-align: center;
+      width: 100%;
+    }
+
+    .footer td,
+    .footer p,
+    .footer span,
+    .footer a {
+      color: #999999;
+      font-size: 12px;
+      text-align: center;
+    }
+
+    /* -------------------------------------
+          TYPOGRAPHY
+      ------------------------------------- */
+    h1,
+    h2,
+    h3,
+    h4 {
+      color: #000000;
+      font-family: sans-serif;
+      font-weight: 400;
+      line-height: 1.4;
+      margin: 0;
+      margin-bottom: 30px;
+    }
+
+    h1 {
+      font-size: 35px;
+      font-weight: 300;
+      text-align: center;
+      text-transform: capitalize;
+    }
+
+    p,
+    ul,
+    ol {
+      font-family: sans-serif;
+      font-size: 16px;
+      font-weight: normal;
+      margin: 0;
+      margin-bottom: 15px;
+    }
+
+    p li,
+    ul li,
+    ol li {
+      list-style-position: inside;
+      margin-left: 5px;
+    }
+
+    a {
+      color: #3498db;
+      text-decoration: underline;
+    }
+
+    /* -------------------------------------
+          BUTTONS
+      ------------------------------------- */
+    .btn {
+      box-sizing: border-box;
+      width: 100%;
+    }
+
+    .btn>tbody>tr>td {
+      padding-bottom: 15px;
+    }
+
+    .btn table {
+      width: auto;
+    }
+
+    .btn table td {
+      background-color: #ffffff;
+      border-radius: 5px;
+      text-align: center;
+    }
+
+    .btn a {
+      display: block;
+      font-size: 16px !important;
+      width: 100%;
+      text-align: center;
+      color: #ffffff;
+      background-color: #5942c1;
+      border: 0;
+      border-radius: 50rem;
+      box-sizing: border-box;
+      cursor: pointer;
+      text-decoration: none;
+      font-size: 12px;
+      margin: 0;
+      padding: 12px 25px;
+    }
+
+    .btn-primary table td {
+      background-color: #3498db;
+    }
+
+    .btn-primary a {
+      background-color: #3498db;
+      border-color: #3498db;
+      color: #ffffff;
+    }
+
+    /* -------------------------------------
+          OTHER STYLES THAT MIGHT BE USEFUL
+      ------------------------------------- */
+    .last {
+      margin-bottom: 0;
+    }
+
+    .first {
+      margin-top: 0;
+    }
+
+    .align-center {
+      text-align: center;
+    }
+
+    .align-right {
+      text-align: right;
+    }
+
+    .align-left {
+      text-align: left;
+    }
+
+    .clear {
+      clear: both;
+    }
+
+    .mt0 {
+      margin-top: 0;
+    }
+
+    .mb0 {
+      margin-bottom: 0;
+    }
+
+    .preheader {
+      color: transparent;
+      display: none;
+      height: 0;
+      max-height: 0;
+      max-width: 0;
+      opacity: 0;
+      overflow: hidden;
+      mso-hide: all;
+      visibility: hidden;
+      width: 0;
+    }
+
+    .powered-by a {
+      text-decoration: none;
+    }
+
+    hr {
+      border: 0;
+      border-bottom: 1px solid #f6f6f6;
+      margin: 20px 0;
+    }
+
+    /* -------------------------------------
+          RESPONSIVE AND MOBILE FRIENDLY STYLES
+      ------------------------------------- */
+    @media only screen and (max-width: 620px) {
+      table.body h1 {
+        font-size: 28px !important;
+        margin-bottom: 10px !important;
+      }
+
+      table.body p,
+      table.body ul,
+      table.body ol,
+      table.body td,
+      table.body span,
+      table.body a {
+        font-size: 16px !important;
+      }
+
+      table.body .wrapper,
+      table.body .article {
+        padding: 10px !important;
+      }
+
+      table.body .content {
+        padding: 0 !important;
+      }
+
+      table.body .container {
+        padding: 0 !important;
+        width: 100% !important;
+      }
+
+      table.body .main {
+        border-left-width: 0 !important;
+        border-radius: 0 !important;
+        border-right-width: 0 !important;
+      }
+
+      table.body .btn table {
+        width: 100% !important;
+      }
+
+      table.body .btn a {
+        width: 100% !important;
+      }
+
+      table.body .img-responsive {
+        height: auto !important;
+        max-width: 100% !important;
+        width: auto !important;
+      }
+    }
+
+    /* -------------------------------------
+          PRESERVE THESE STYLES IN THE HEAD
+      ------------------------------------- */
+    @media all {
+      .ExternalClass {
+        width: 100%;
+      }
+
+      .ExternalClass,
+      .ExternalClass p,
+      .ExternalClass span,
+      .ExternalClass font,
+      .ExternalClass td,
+      .ExternalClass div {
+        line-height: 100%;
+      }
+
+      .apple-link a {
+        color: inherit !important;
+        font-family: inherit !important;
+        font-size: inherit !important;
+        font-weight: inherit !important;
+        line-height: inherit !important;
+        text-decoration: none !important;
+      }
+
+      #MessageViewBody a {
+        color: inherit;
+        text-decoration: none;
+        font-size: inherit;
+        font-family: inherit;
+        font-weight: inherit;
+        line-height: inherit;
+      }
+
+      .btn-primary table td:hover {
+        background-color: #34495e !important;
+      }
+
+      .btn-primary a:hover {
+        background-color: #34495e !important;
+        border-color: #34495e !important;
+      }
+    }
+  </style>
+</head>
+
+<body>
+  <span class="preheader">This is preheader text. Some clients will show this text as a preview.</span>
+  <table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body">
+    <tr>
+      <td>&nbsp;</td>
+      <td class="container">
+        <div class="content">
+          <!-- START CENTERED WHITE CONTAINER -->
+          <table role="presentation" class="main">
+            <!-- START MAIN CONTENT AREA -->
+            <tr>
+              <td class="wrapper">
+                <table role="presentation" border="0" cellpadding="0" cellspacing="0">
+                  <tr>
+                    <td>
+                      <p style="font-size: 18px;">Hi!</p>
+                      <p style="font-size: 21px;">Please confirm your e-mail address.</p>
+                      <p>
+                        Complete the verification by clicking on the button below. If you need
+                        any help please do not hesitate to contact and hop into our Discord or
+                        Slack channels.
+                      </p>
+
+                      <p>
+                      <div class="btn">
+                        <a href="<%= verifyURL %>" target="_blank">
+                          Confirm
+                          <img width="12" height="auto" src="<%= baseUrl %>/img/mail/icons/chevron-right-solid.png" />
+                        </a>
+                      </div>
+                      </p>
+                    </td>
+                  </tr>
+                </table>
+              </td>
+            </tr>
+
+            <!-- END MAIN CONTENT AREA -->
+          </table>
+          <!-- END CENTERED WHITE CONTAINER -->
+
+          <!-- START FOOTER -->
+          <div class="footer">
+            <table role="presentation" border="0" cellpadding="0" cellspacing="0">
+
+              <tr>
+                <td class="content-block powered-by">
+                  <img src="<%= baseUrl %>/img/logo.png" width="60" alt="THX Logo">
+                </td>
+              </tr>
+            </table>
+          </div>
+          <!-- END FOOTER -->
+        </div>
+      </td>
+      <td>&nbsp;</td>
+    </tr>
+  </table>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/apps/auth/src/assets/views/otp.ejs b/apps/auth/src/assets/views/otp.ejs
new file mode 100644
index 000000000..17e92f940
--- /dev/null
+++ b/apps/auth/src/assets/views/otp.ejs
@@ -0,0 +1,90 @@
+<script type="module" src="/js/otp.js?v=<%= locals.deployedAt %>"></script>
+<% if (params.backgroundImgUrl) { %>
+<style type="text/css">
+    body {
+        background-image: url("<%- params.backgroundImgUrl %>") !important;
+        background-repeat: no-repeat;
+        background-size: cover;
+    }
+</style>
+<% } %>
+<style type="text/css">
+    @keyframes shine {
+        to {
+            background-position-x: -200%;
+        }
+    }
+
+    .card {
+        border: 0;
+    }
+
+    .card .card-header {
+        padding: 0;
+        height: 5px;
+    }
+
+    .card.is-loading .card-header {
+        background: linear-gradient(90deg, rgba(0, 0, 0, 0.03) 10%, #5942c1 10%, #5942c1 90%, rgba(0, 0, 0, 0.03) 90%);
+        background-size: 200% 100%;
+        animation: 1.5s shine linear infinite;
+    }
+
+    .form-control-otp {
+        text-align: center;
+        font-weight: bold;
+        color: #5942c1;
+    }
+</style>
+<div class="col-md-6 offset-md-3 col-xl-4 offset-xl-4 order-0 order-sm-1">
+    <div class="text-center pb-4 pt-3">
+        <% if (params.logoImgUrl) { %>
+        <img src="<%- params.logoImgUrl %>" width="60" alt="THX Logo" />
+        <% } else { %>
+        <img src="/img/logo.png" width="60" alt="THX Logo" />
+        <% } %>
+    </div>
+    <div class="card shadow-sm" :class="{ 'is-loading': isLoading }">
+        <div class="card-header"></div>
+        <div class="card-body p-sm-4 pb-sm-0">
+
+            <% if (alert && alert.message) { %>
+            <div class="alert alert-<%= alert.variant %>">
+                <i class="fas fa-<%= alert.icon %> mr-2"></i>
+                <%- alert.message %>
+            </div>
+            <% } %>
+
+            <form id="form-otp" action="/oidc/<%= uid %>/signin/otp" method="post" class="mt-3">
+                <div class="form-group">
+                    <p class="mt-2 d-flex align-items-center justify-content-between">
+                        <span class=" text-muted">
+                            One-time password
+                        </span>
+                        <button form="otp-resend" type="submit" class="btn btn-sm btn-link p-0">
+                            Send e-mail again
+                        </button>
+                    </p>
+                    <div class="d-flex justify-content-between">
+                        <input v-model="otp" @input="onInput" style="letter-spacing: 1rem;"
+                               class="form-control form-control-otp" placeholder="*****" :disabled="isLoading"
+                               type="text" name="otp" inputmode="numeric" required>
+                    </div>
+                </div>
+                <input type="hidden" name="otp" :value="otp" />
+                <input type="hidden" name="returnUrl" value="<%= params.return_url %>" />
+                <button ref="submit" form="form-otp" type="submit" class="d-none"> Sign in </button>
+            </form>
+            <form id="otp-resend" action="/oidc/<%= uid %>/signin/resend-otp" method="post"></form>
+        </div>
+        <div class="card-footer justify-content-end d-flex align-items-center">
+            <a class="text-muted mr-auto" href="/oidc/<%= uid %>/signin">Return to app</a>
+            <div>
+                <a href="https://discord.com/invite/TzbbSmkE7Y" class="text-muted">Help</a>
+                <a href="https://thx.network/privacy.pdf" target="_blank" class="text-muted ml-3">Privacy</a>
+                <a href="https://thx.network/general-terms-and-conditions.pdf" target="_blank"
+                   class="text-muted ml-3">Terms</a>
+            </div>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/apps/auth/src/assets/views/signin.ejs b/apps/auth/src/assets/views/signin.ejs
new file mode 100644
index 000000000..93e67391c
--- /dev/null
+++ b/apps/auth/src/assets/views/signin.ejs
@@ -0,0 +1,369 @@
+<script async type="module" src="/js/signin.js?v=<%= locals.deployedAt %>"></script>
+<% if (params.backgroundImgUrl) { %>
+<style type="text/css">
+  body {
+    background-image: url("<%- params.backgroundImgUrl %>") !important;
+    background-repeat: no-repeat;
+    background-size: cover;
+  }
+</style>
+<% } %>
+<style type="text/css">
+  @keyframes shine {
+    to {
+      background-position-x: -200%;
+    }
+  }
+
+  hr {
+    margin: 2rem 0;
+    position: relative;
+  }
+
+  hr.or-separator:after {
+    content: 'OR';
+    background: white;
+    color: gray;
+    width: 40px;
+    font-size: .8rem;
+    margin-top: -.7rem;
+    margin-left: -20px;
+    left: 50%;
+    text-align: center;
+    position: absolute;
+  }
+
+  .btn-discord {
+    background-color: #5865F2;
+    color: white;
+    border-radius: 25px;
+    height: 50px;
+    justify-content: center;
+  }
+
+  .btn-discord:hover {
+    background-color: #3f4abe;
+    color: white;
+  }
+
+  .card {
+    border: 0;
+  }
+
+  .fade-in {
+    opacity: 0;
+    transition: opacity ease .5s;
+  }
+
+  .card .card-header {
+    padding: 0;
+    height: 5px;
+  }
+
+  .fade-in.is-mounted {
+    opacity: 1;
+  }
+
+  .card.is-loading .card-header {
+    background: linear-gradient(90deg, rgba(0, 0, 0, 0.03) 10%, #5942c1 10%, #5942c1 90%, rgba(0, 0, 0, 0.03) 90%);
+    background-size: 200% 100%;
+    animation: 1.5s shine linear infinite;
+  }
+
+  .premium-link:hover {
+    color: white;
+    text-decoration: none;
+  }
+</style>
+<div class="<%- (params.isSignup) ? 'col-md-10 col-xl-10 offset-md-1 offset-xl-2' : 'col-md-8 offset-md-2 col-xl-4 offset-xl-4' %>  fade-in"
+     @vue:mounted="onMounted" :class="{ 'is-mounted': isMounted }">
+
+  <div class="text-center pb-4 pt-3">
+    <% if (params.logoImgUrl) { %>
+    <img src="<%- params.logoImgUrl %>" width="60" alt="THX Logo" />
+    <% } else { %>
+    <img src="/img/logo.png" width="60" alt="THX Logo" />
+    <% } %>
+  </div>
+
+  <div class="card shadow-sm" :class="{ 'is-loading': isLoading, 'is-mounted': isMounted }">
+    <div class="card-header"></div>
+
+    <% if (params.logoImgUrl) { %>
+    <div class="bg-light py-2 px-4 d-flex align-items-center">
+      <img src="/img/logo.png" width="18" alt="THX Logo" class="mr-2" />
+      <small class="text-muted">Sign in with THX Network</small>
+    </div>
+    <% } %>
+
+    <div class="row">
+      <% if(params.isSignup) { %>
+      <div class="col-md-6 order-md-1">
+        <div class="bg-dark p-sm-4 h-100">
+
+          <%
+          var premium = [];
+
+          if (params.signup_plan == 1) {
+            premium.push({
+              class: 'show',
+              iconClass: 'fa-check-circle text-success',
+              title: '€ 89 / month (14 day free trial)',
+              description:
+                  'We invite you to try out all of the above for a month, with no obligation to pay unless you\'re satisfied!',
+            });
+          };
+
+          if (params.signup_plan == 2) {
+            premium.push({
+              class: 'show',
+              iconClass: 'fa-check-circle text-success',
+              title: '&euro; 449 / month (14 day free trial)',
+              description: 'We invite you to try out all of the above for a month, with no obligation to pay unless you\'re satisfied!',
+            });
+          };
+
+
+          premium.push({
+            class: '',
+            iconClass: 'fa-check-circle text-success',
+            title: '2,5% fee on rewards',
+            description: 'We charge a small % transactional fee on coin rewards (ERC-20). Your end users pay no fees.',
+          });
+
+          premium.push({
+            class: '',
+            iconClass: 'fa-times-circle text-danger',
+            title: 'Transaction costs (gas fees)',
+            description: 'We cover all user transaction costs (gas fees) up to the plans reward limit.',
+          });
+          
+          if (params.signup_plan == 1) {
+            premium.push({
+              class: '',
+              iconClass: 'fa-times-circle text-danger',
+              title: 'Setup fee',
+              description: 'We require no setup fee. Sign-up and you\'re ready to go!',
+            });
+
+            premium.push({
+              class: '',
+              iconClass: 'fa-check-circle text-success',
+              title: 'Discord Bot',
+              description: 'Reach your community in channels that matter most. Post reward overviews and announce new rewards, all automated.',
+            });
+
+            premium.push({
+                class: '',
+                iconClass: 'fa-check-circle text-success',
+                title: 'Twitter Automation',
+                description: 'You are already very busy managing campaigns... We\'ll automate your Twitter campaigns, so you can focus on growth!',
+            });
+          };
+
+          if (params.signup_plan == 2) {
+            premium.push({
+              class: '',
+              iconClass: 'fa-times-circle text-danger',
+              title: 'Setup fee',
+              description: 'We require no setup fee. Sign-up and you\'re ready to go!',
+            });
+             
+            premium.push({
+              class: '',
+              iconClass: 'fa-check-circle text-success',
+              title: 'Dedicated Campaign Manager',
+              description:
+                  'We understand you are a busy person! That\'s why we offer you a dedicated campaign manager to during your free first month.',
+            });
+
+            premium.push({
+              class: '',
+              iconClass: 'fa-check-circle text-success',
+              title: 'Unlimited Technical Support',
+              description:
+                  'Your devs are probably working on other important tasks. Let our devs talk to your devs to make engine integration a piece of cake.',
+            });
+          };
+  
+          premium.push({
+            class: '',
+            iconClass: 'fa-check-circle text-success',
+            title: 'Weekly Performance Reports',
+            description:
+                'We know budgets and results are important to you. We send you weekly reports by mail and are happy to jump on a call to discuss results and potential improvements.',
+          });     
+        
+          %>
+
+          <ul class="text-muted list-unstyled">
+            <% for(var i=0; i < premium.length; i++) { %>
+            <li>
+              <a class="text-white d-flex premium-link py-1" data-toggle="collapse" href="#collapseExample<%= i %>"
+                 role="button" aria-expanded="false" aria-controls="collapseExample<%= i %>">
+                <div>
+                  <i class="fas mr-1 <%- premium[i].iconClass %>"></i>
+                  <span><%- premium[i].title %></span>
+                </div>
+                <i class=" fas fa-caret-down text-muted ml-auto"></i>
+              </a>
+              <div class="collapse <%- premium[i].class %>" id="collapseExample<%= i %>">
+                <div class="card card-body bg-darker my-2">
+                  <%- premium[i].description %>
+                </div>
+              </div>
+            </li>
+            <% } %>
+          </ul>
+
+          <hr class="bg-gray" />
+
+          <p class="text-muted small">
+            By continuing you accept THX Network's <a class="text-white"
+               href="https://thx.network/general-terms-and-conditions.pdf" target="_blank">Terms &amp; Conditions</a>
+            and <a class="text-white" href="https://thx.network/privacy-policy.pdf" target="_blank">Privacy
+              Policy</a>.
+          </p>
+
+        </div>
+      </div>
+      <% } %>
+
+      <div class="<%- (params.isSignup) ? 'col-md-6 order-md-0' : 'col-md-12' %>">
+        <div class="card-body p-sm-4 pb-sm-0">
+
+          <% if (params.signup_offer == 'true' && params.signup_plan == '2') { %>
+          <div class="alert alert-info">
+            <i class="fas fa-info-circle mr-1"></i>
+            You have received a
+            <strong><%- (params.signup_plan == '1') ? 'Basic' : (params.signup_plan == '2') ? 'Premium' : 'Free' %>
+              Offer 🎁 </strong>
+          </div>
+          <% } %>
+
+          <% if (params.authRequestMessage) { %>
+          <div v-if="alert && alert.message" class="alert" :class="`alert-${alert.variant}`">
+            <i class="fas fa-exclamation-circle mr-2"></i>
+            {{alert.message}}
+          </div>
+          <% } %>
+
+          <% if(params.referral_code) { %>
+          <div class="alert alert-info">
+            <i class="fas fa-comments mr-2"></i> We have detected a referral code!
+          </div>
+          <% } %>
+
+          <% if (locals.alert && alert.message) { %>
+          <div class="alert alert-<%= alert.variant %>">
+            <%- alert.message %>
+          </div>
+          <% } %>
+
+          <% if (params.isSignup) { %>
+          <h2 class="h3 mb-3 font-weight-normal text-dark">Start your <strong>free trial</strong>!</h2>
+          <% } %>
+
+          <% if (params.discordLoginUrl && params.isSignup) { %>
+          <p class="text-muted">Continue with Discord to get instant access to the <a
+               href="https://discord.com/invite/TzbbSmkE7Y">#support</a> channel 🙋.</p>
+          <a class="mb-2 d-flex align-items-center btn btn-discord" href="<%= params.discordLoginUrl %>">
+            <img width="20" class="mr-2" src="/img/discord-logo.png" alt="Discord logo" />
+            <span>Continue with Discord</span>
+            <i class="fas fa-chevron-right" aria-hidden="true"></i>
+          </a>
+          <hr class="or-separator" />
+          <% } %>
+
+          <form id="form-signin" action="/oidc/<%= uid %>/signin" method="post">
+            <% if(params.emailPasswordEnabled) { %>
+            <p class="text-muted">
+              <%- (params.isSignup) ? 'Use your work e-mail' : 'Use your e-mail' %>
+            </p>
+            <div class="form-group">
+              <input placeholder="<%- (params.isSignup) ? 'yourname@work-email.com' : 'yourname@example.com' %>"
+                     class="form-control" class="form-control" required type="email" name="email" v-model="email"
+                     :class="{
+                        'is-valid': email && !isDisabled, 'is-invalid': email && isDisabled
+                    }">
+            </div>
+            <% } %>
+
+            <% if (params.mfaEnabled) { %>
+            <label>MFA code:</label>
+            <input class="form-control mb-4" required name="code">
+            <% } %>
+
+            <input type="hidden" name="signupEmail" value="<%= params.signup_email %>" />
+            <input type="hidden" name="returnUrl" value="<%= params.return_url %>" />
+            <input type="hidden" name="isWidget" value="<%= params.isWidget %>" />
+
+            <% if(params.emailPasswordEnabled) { %>
+            <button type="submit" class="btn rounded-pill btn-dark btn- btn-block mb-4"
+                    :class="{'disabled': isDisabled || isLoading}" @click="onClickSubmit">
+              <%- (params.isSignup) ? 'Continue with e-mail' : 'Send one-time password'%>
+              <i class="fas fa-chevron-right" aria-hidden="true"></i><br />
+            </button>
+            <% } %>
+
+          </form>
+
+          <% if (params.trustedProviderAvailable) { %>
+          <hr class="or-separator" />
+          <% } %>
+
+          <% if (params.trustedProviderAvailable) { %>
+          <p class="text-muted">Use a trusted provider</p>
+          <% } %>
+
+          <div class="d-flex align-items-center justify-content-start flex-wrap">
+            <% if (params.googleLoginUrl) { %>
+            <a style="width: 40px; height: 40px;" class="mr-2 mb-2 d-block p-1 btn btn-light"
+               href="<%= params.googleLoginUrl %>" data-toggle="tooltip" title="Sign in with Google">
+              <img width="20" src="/img/g-logo.png" alt="Google logo" />
+            </a>
+            <% } %>
+
+            <% if (params.twitterLoginUrl) { %>
+            <a style="width: 40px; height: 40px;" class="mr-2 mb-2 d-block p-1 btn btn-light"
+               href="<%= params.twitterLoginUrl %>" data-toggle="tooltip" title="Sign in with Twitter">
+              <img width="20" src="/img/t-logo.png" alt="Twitter logo" />
+            </a>
+            <% } %>
+
+            <% if (params.discordLoginUrl && !params.isSignup) { %>
+            <a style="width: 40px; height: 40px;" class="mr-2 mb-2 d-block p-1 btn btn-light"
+               href="<%= params.discordLoginUrl %>" data-toggle="tooltip" title="Sign in with Discord">
+              <img width="20" src="/img/discord-logo.png" alt="Discord logo" />
+            </a>
+            <% } %>
+
+            <% if (params.githubLoginUrl) { %>
+            <a style="width: 40px; height: 40px;" class="mr-2 mb-2 d-block p-1 btn btn-light"
+               href="<%= params.githubLoginUrl %>" data-toggle="tooltip" title="Sign in with Github">
+              <img width="20" src="/img/github-logo.png" alt="github logo" />
+            </a>
+            <% } %>
+
+            <% if (params.twitchLoginUrl) { %>
+            <a style="width: 40px; height: 40px;" class="mr-2 mb-2 d-block p-1 btn btn-light"
+               href="<%= params.twitchLoginUrl %>" data-toggle="tooltip" title="Sign in with Twitch">
+              <img width="20" src="/img/twitch-logo.png" alt="twitch logo" />
+            </a>
+            <% } %>
+          </div>
+
+        </div>
+      </div>
+    </div>
+
+
+    <div class="card-footer d-flex align-items-center justify-content-end">
+      <a @click="onClickReturn" href="#" class="text-muted mr-auto cursor-pointer">Return to app</a>
+      <a href="https://discord.com/invite/TzbbSmkE7Y" class="text-muted">Help</a>
+      <a href="https://thx.network/privacy-policy.pdf" target="_blank" class="text-muted ml-3">Privacy</a>
+      <a href="https://thx.network/general-terms-and-conditions.pdf" target="_blank" class="text-muted ml-3">Terms</a>
+    </div>
+
+  </div>
+</div>
\ No newline at end of file
diff --git a/apps/auth/src/assets/views/totp.ejs b/apps/auth/src/assets/views/totp.ejs
new file mode 100644
index 000000000..edef4b75e
--- /dev/null
+++ b/apps/auth/src/assets/views/totp.ejs
@@ -0,0 +1,36 @@
+<div class="col-md-6 offset-md-3 order-0 order-sm-1">
+    <div class="text-center pb-4 pt-4">
+        <img src="/img/logo.png" width="60" alt="THX Logo" />
+    </div>
+    <div class="card shadow-sm mb-5 w-100">
+        <div class="card-body p-sm-5">
+            <strong class="h3 d-block text-dark text-center mb-5">
+                MFA Configuration
+            </strong>
+            <p>Scan this QR code with your authenticator app. E.g. <a
+                   href="https://apps.apple.com/us/app/google-authenticator/id388497605" target="_blank">Google
+                    Authenticator</a> or <a href="https://www.authy.com" target="_blank">Authy</a>.</p>
+            <hr>
+            <div class="img-responsive text-center bg-light p-3">
+                <img width="200" src="<%= params.qrCode %>" />
+            </div>
+            <hr>
+            <form autocomplete="off" action="/oidc/<%= uid %>/account/totp" method="post" class="mb-3">
+                <div class="p-2">
+                    <p>Enter the code shown in your authenticator app here:</p>
+                    <input placeholder="123123" class="form-control" autocomplete="off" required name="code" />
+                    <input type="hidden" name="otpSecret" value="<%= params.otpSecret %>" />
+                </div>
+                <hr>
+                <button type="submit" class="btn rounded-pill btn-primary btn-block">
+                    Submit code
+                    <i class="fas fa-chevron-right" aria-hidden="true"></i>
+                </button>
+            </form>
+
+            <a class="btn btn-link btn-block rounded-pill" href="/oidc/<%= uid %>/account">
+                Return to Account
+            </a>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/apps/auth/src/environments/environment.prod.ts b/apps/auth/src/environments/environment.prod.ts
new file mode 100644
index 000000000..c9669790b
--- /dev/null
+++ b/apps/auth/src/environments/environment.prod.ts
@@ -0,0 +1,3 @@
+export const environment = {
+  production: true,
+};
diff --git a/apps/auth/src/environments/environment.ts b/apps/auth/src/environments/environment.ts
new file mode 100644
index 000000000..a20cfe557
--- /dev/null
+++ b/apps/auth/src/environments/environment.ts
@@ -0,0 +1,3 @@
+export const environment = {
+  production: false,
+};
diff --git a/apps/auth/src/main.ts b/apps/auth/src/main.ts
new file mode 100644
index 000000000..896a6d371
--- /dev/null
+++ b/apps/auth/src/main.ts
@@ -0,0 +1,53 @@
+import 'newrelic';
+import http from 'http';
+import https from 'https';
+import app from './app/app';
+import db from './app/util/database';
+import { createTerminus } from '@godaddy/terminus';
+import { healthCheck } from './app/util/healthcheck';
+import { logger } from './app/util/logger';
+import fs from 'fs';
+import path from 'path';
+import { LOCAL_CERT, LOCAL_CERT_KEY } from './app/config/secrets';
+
+let server;
+if (LOCAL_CERT && LOCAL_CERT_KEY) {
+    server = https.createServer(
+        {
+            key: fs.readFileSync(path.resolve(path.dirname(__dirname), LOCAL_CERT_KEY)),
+            cert: fs.readFileSync(path.resolve(path.dirname(__dirname), LOCAL_CERT)),
+        },
+        app,
+    );
+} else {
+    server = http.createServer(app);
+}
+
+const options = {
+    healthChecks: {
+        '/healthcheck': healthCheck,
+        'verbatim': true,
+    },
+    onSignal: () => {
+        logger.info('Server shutting down');
+        return Promise.all([db.disconnect()]);
+    },
+    logger: logger.error,
+};
+
+createTerminus(server, options);
+
+process.on('uncaughtException', function (err: Error) {
+    if (err) {
+        logger.error({
+            message: 'Uncaught Exception was thrown, shutting down',
+            errorName: err.name,
+            errorMessage: err.message,
+            stack: err.stack,
+        });
+        process.exit(1);
+    }
+});
+
+logger.info(`Server is starting on port: ${app.get('port')}, env: ${app.get('env')}`);
+server.listen(app.get('port'));
diff --git a/apps/auth/tsconfig.app.json b/apps/auth/tsconfig.app.json
new file mode 100644
index 000000000..0e2bb5eac
--- /dev/null
+++ b/apps/auth/tsconfig.app.json
@@ -0,0 +1,10 @@
+{
+    "extends": "./tsconfig.json",
+    "compilerOptions": {
+        "outDir": "../../dist/out-tsc",
+        "module": "commonjs",
+        "types": ["node"]
+    },
+    "exclude": ["jest.config.ts", "src/**/*.test.ts"],
+    "include": ["package.json", "src/**/*.ts", "../../libs/common/src/**/*"]
+}
diff --git a/apps/auth/tsconfig.json b/apps/auth/tsconfig.json
new file mode 100644
index 000000000..d2b6e8e03
--- /dev/null
+++ b/apps/auth/tsconfig.json
@@ -0,0 +1,19 @@
+{
+    "extends": "../../tsconfig.base.json",
+    "compilerOptions": {
+        "esModuleInterop": true,
+        // Custom
+        "noImplicitAny": false,
+        "strict": false
+    },
+    "files": [],
+    "include": [],
+    "references": [
+        {
+            "path": "./tsconfig.app.json"
+        },
+        {
+            "path": "./tsconfig.spec.json"
+        }
+    ]
+}
diff --git a/apps/auth/tsconfig.spec.json b/apps/auth/tsconfig.spec.json
new file mode 100644
index 000000000..5692087fb
--- /dev/null
+++ b/apps/auth/tsconfig.spec.json
@@ -0,0 +1,20 @@
+{
+    "extends": "./tsconfig.json",
+    "compilerOptions": {
+        "outDir": "../../dist/out-tsc",
+        "module": "commonjs",
+        "types": ["jest", "node"],
+        // Custom
+        "allowJs": true
+    },
+    "exclude": ["../../libs/common/src/lib/scss/**/*"],
+    "include": [
+        "jest.config.ts",
+        "**/*.test.ts",
+        "**/*.d.ts",
+        "src/**/*.ts",
+        "../../libs/common/src/**/*",
+        "../../libs/sdk/src/**/*",
+        "src/app/hardhat/export/*.json"
+    ]
+}
diff --git a/apps/auth/webpack.config.js b/apps/auth/webpack.config.js
new file mode 100644
index 000000000..29f653d71
--- /dev/null
+++ b/apps/auth/webpack.config.js
@@ -0,0 +1,13 @@
+const { composePlugins, withNx } = require('@nx/webpack');
+
+// Nx plugins for webpack.
+module.exports = composePlugins(
+    withNx({
+        target: 'node',
+    }),
+    (config) => {
+        // Update the webpack config as needed here.
+        // e.g. `config.plugins.push(new MyPlugin())`
+        return config;
+    },
+);
diff --git a/apps/campaign/src/components/card/BaseCardMembership.vue b/apps/campaign/src/components/card/BaseCardMembership.vue
deleted file mode 100644
index 16dc22142..000000000
--- a/apps/campaign/src/components/card/BaseCardMembership.vue
+++ /dev/null
@@ -1,48 +0,0 @@
-<template>
-    <div class="text-center">
-        <div class="position-relative rounded-circle m-auto gradient-border-xl" style="width: 75px; height: 75px">
-            <div
-                class="position-relative bg-dark rounded-circle d-flex align-items-center justify-content-center"
-                style="z-index: 1; width: 65px; height: 65px"
-            >
-                <i class="fas fa-id-card" style="font-size: 1.5rem" />
-            </div>
-        </div>
-        <b-badge class="mt-2 p-2" variant="primary">
-            <i class="fas fa-trophy me-1 text-opaque" />
-            {{ membership }}
-        </b-badge>
-    </div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import { mapStores } from 'pinia';
-import { useLiquidityStore } from '../../stores/Liquidity';
-import { useVeStore } from '../../stores/VE';
-import { fromWei } from 'web3-utils';
-
-export default defineComponent({
-    name: 'BaseCardMembership',
-    data() {
-        return {};
-    },
-    computed: {
-        ...mapStores(useVeStore, useLiquidityStore),
-        membership() {
-            if (!this.veStore.lock) return;
-            const price = this.liquidityStore.pricing['20USDC-80THX'];
-            const amount = Number(fromWei(String(this.veStore.lock.amount)));
-            const amountInUSD = amount * price;
-
-            if (amountInUSD < 5) return 'No Rank';
-            if (amountInUSD > 5 && amountInUSD < 50) return 'Rookie';
-            if (amountInUSD > 50 && amountInUSD < 500) return 'Pro';
-            if (amountInUSD > 500 && amountInUSD < 5000) return 'Elite';
-            if (amountInUSD > 5000 && amountInUSD < 50000) return 'Master';
-            if (amountInUSD > 50000 && amountInUSD < 500000) return 'Legend';
-        },
-    },
-    methods: {},
-});
-</script>
diff --git a/docker-compose.api.yml b/docker-compose.api.yml
new file mode 100644
index 000000000..fd2a38713
--- /dev/null
+++ b/docker-compose.api.yml
@@ -0,0 +1,32 @@
+version: '3.8'
+
+# This compose file only works when used in conjunction with the default docker-compose file.
+# docker compose -f docker-compose.yml -f docker-compose.api.yml
+
+services:
+    api:
+        build:
+            context: .
+            dockerfile: apps/api/Dockerfile
+            target: develop
+        volumes:
+            - ./coverage/apps/api:/usr/src/app/coverage/apps/api
+        extra_hosts:
+            - "host.docker.internal:host-gateway"
+        env_file:
+            - ./apps/api/.env.example
+        environment:
+            MONGODB_URI: "mongodb://root:root@mongo:27017/api?authSource=admin&ssl=false"
+            MONGODB_URI_TEST_OVERRIDE: "mongodb://root:root@mongo:27017/api_test?authSource=admin&ssl=false"
+            AWS_S3_PUBLIC_BUCKET_NAME: "test-thx-storage-bucket"
+            AWS_S3_PRIVATE_BUCKET_NAME: "test-thx-private-storage-bucket"
+            HARDHAT_RPC: "http://host.docker.internal:8545"
+            HARDHAT_RPC_TEST_OVERRIDE: "http://host.docker.internal:8545"
+            SAFE_TXS_SERVICE: "http://host.docker.internal:8000/txs"
+            LOCAL_CERT: ""
+            LOCAL_CERT_KEY: ""
+            CWD: "/usr/src/app/apps/api/src/"
+        ports:
+            - 3001:3000
+        depends_on:
+            - mongo
\ No newline at end of file
diff --git a/docker-compose.auth.yml b/docker-compose.auth.yml
new file mode 100644
index 000000000..d3b618a5b
--- /dev/null
+++ b/docker-compose.auth.yml
@@ -0,0 +1,21 @@
+version: '3.8'
+
+# This compose file only works when used in conjunction with the default docker-compose file.
+# docker compose -f docker-compose.yml -r docker-compose.auth.yml
+
+services:    
+    auth:
+        container_name: thx_auth
+        build:
+            context: .
+            dockerfile: apps/auth/Dockerfile
+            target: develop
+        volumes:
+            - ./coverage/apps/auth:/usr/src/app/coverage/apps/auth
+        env_file: 
+          - apps/auth/.env.example
+          - apps/auth/.env.ci
+        ports:
+            - 3031:3030
+        depends_on:
+            - mongo
\ No newline at end of file
diff --git a/docker-compose.safe.yml b/docker-compose.safe.yml
new file mode 100644
index 000000000..e82ed7acf
--- /dev/null
+++ b/docker-compose.safe.yml
@@ -0,0 +1,115 @@
+version: '3.8'
+
+volumes:
+    nginx-shared-txs:
+    nginx-shared-cfg:
+
+services:
+    nginx:
+        image: nginx:alpine
+        ports:
+           - "${REVERSE_PROXY_PORT}:8000"
+        volumes:
+            - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
+            - nginx-shared-txs:/nginx-txs
+            - nginx-shared-cfg:/nginx-cfg
+        depends_on:
+            - txs-web
+            - cfg-web
+            - cgw-web
+
+    txs-db:
+        image: postgres:14-alpine
+        environment:
+            POSTGRES_PASSWORD: postgres
+        volumes:
+            - ./docker/data/txs-db:/var/lib/postgresql/data
+
+    cfg-db:
+        image: postgres:14-alpine
+        environment:
+            POSTGRES_PASSWORD: postgres
+        volumes:
+            - ./docker/data/cfg-db:/var/lib/postgresql/data
+
+
+    # Safe Transaction Service
+    txs-redis:
+        image: redis:alpine
+
+    txs-rabbitmq:
+        image: rabbitmq:alpine
+
+    txs-web:
+        image: safeglobal/safe-transaction-service:${TXS_VERSION}
+        env_file:
+            - ./docker/env/txs.env
+        extra_hosts:
+            - "host.docker.internal:host-gateway"
+        environment:
+            - ETHEREUM_NODE_URL=${RPC_NODE_URL}
+        depends_on:
+            - txs-db
+            - txs-redis
+        working_dir: /app
+        volumes:
+            - nginx-shared-txs:/nginx
+            - ./scripts/insert_safe_master_copy.py:/app/safe_transaction_service/history/management/commands/insert_safe_master_copy.py
+        command: docker/web/run_web.sh
+
+    txs-worker-indexer: &txs-worker
+        image: safeglobal/safe-transaction-service:${TXS_VERSION}
+        env_file:
+            - ./docker/env/txs.env
+        extra_hosts:
+            - "host.docker.internal:host-gateway"
+        environment:
+            - ETHEREUM_NODE_URL=${RPC_NODE_URL}
+            - RUN_MIGRATIONS=1
+            - WORKER_QUEUES=default,indexing
+        depends_on:
+            - txs-db
+            - txs-redis
+        command: docker/web/celery/worker/run.sh
+
+    txs-worker-contracts-tokens:
+        <<: *txs-worker
+        extra_hosts:
+            - "host.docker.internal:host-gateway"
+        environment:
+            - WORKER_QUEUES=contracts,tokens
+            - ETHEREUM_NODE_URL=${RPC_NODE_URL}
+
+    txs-worker-notifications-webhooks:
+        <<: *txs-worker
+        extra_hosts:
+            - "host.docker.internal:host-gateway"
+        environment:
+            - WORKER_QUEUES=notifications,webhooks
+            - ETHEREUM_NODE_URL=${RPC_NODE_URL}
+
+    txs-scheduler:
+        <<: *txs-worker
+        command: docker/web/celery/scheduler/run.sh
+
+    # Safe Config Service
+    cfg-web:
+        image: safeglobal/safe-config-service:${CFG_VERSION}
+        tty: true
+        volumes:
+            - nginx-shared-cfg:/nginx
+        env_file:
+            - ./docker/env/cfg.env
+        depends_on:
+            - cfg-db
+
+    # Safe Client Gateway
+    cgw-redis:
+        image: redis:alpine
+
+    cgw-web:
+        image: safeglobal/safe-client-gateway:${CGW_VERSION}
+        env_file:
+            - ./docker/env/cgw.env
+        depends_on:
+            - cgw-redis
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 000000000..9bf102b2f
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,20 @@
+version: '3.8'
+
+services:
+    mongo:
+        image: mongo
+        restart: always
+        ports:
+            - 27017:27017
+        env_file: .env.example
+        environment:
+            MONGO_INITDB_ROOT_USERNAME: root
+            MONGO_INITDB_ROOT_PASSWORD: root
+            MONGO_INITDB_DATABASE: admin
+        volumes:
+            - mongo-data:/data/db
+            - ./fixture/db:/docker-entrypoint-initdb.d/fixture
+            - ./scripts/mongo-init.sh:/docker-entrypoint-initdb.d/mongo-init.sh
+
+volumes:
+    mongo-data:
\ No newline at end of file
diff --git a/docker/env/cfg.env b/docker/env/cfg.env
new file mode 100644
index 000000000..152e15ab4
--- /dev/null
+++ b/docker/env/cfg.env
@@ -0,0 +1,42 @@
+PYTHONDONTWRITEBYTECODE=true
+
+SECRET_KEY=insecure_key_for_dev
+
+DEBUG=true
+ROOT_LOG_LEVEL=DEBUG
+
+DJANGO_ALLOWED_HOSTS="*"
+
+GUNICORN_BIND_PORT=8001
+
+DOCKER_NGINX_VOLUME_ROOT=/nginx
+
+GUNICORN_BIND_SOCKET=unix:${DOCKER_NGINX_VOLUME_ROOT}/gunicorn.socket
+
+# NGINX_HOST_PORT=8080
+
+NGINX_ENVSUBST_OUTPUT_DIR=/etc/nginx/
+
+POSTGRES_USER=postgres
+POSTGRES_PASSWORD=postgres
+POSTGRES_NAME=postgres
+POSTGRES_HOST=cfg-db
+POSTGRES_PORT=5432
+
+DOCKER_WEB_VOLUME=.:/app
+
+GUNICORN_WEB_RELOAD=false
+
+DJANGO_SUPERUSER_PASSWORD=admin
+DJANGO_SUPERUSER_USERNAME=root
+DJANGO_SUPERUSER_EMAIL=test@example.com
+DJANGO_OTP_ADMIN=false
+
+DEFAULT_FILE_STORAGE=django.core.files.storage.FileSystemStorage
+
+FORCE_SCRIPT_NAME=/cfg/
+
+CGW_URL=http://nginx:8000/cgw
+CGW_FLUSH_TOKEN=some_random_token
+
+CSRF_TRUSTED_ORIGINS="http://localhost:8000"
\ No newline at end of file
diff --git a/docker/env/cgw.env b/docker/env/cgw.env
new file mode 100644
index 000000000..726714ff6
--- /dev/null
+++ b/docker/env/cgw.env
@@ -0,0 +1,25 @@
+# More detailed description of the available values can be found:
+# https://github.com/gnosis/safe-client-gateway/blob/main/.env.sample
+CONFIG_SERVICE_URI=http://nginx:8000/cfg
+
+FEATURE_FLAG_NESTED_DECODING=true
+
+SCHEME=http
+ROCKET_SECRET_KEY=tVqiPxyM9RPkTbcrciV6ZcIzvssv+kF9YvfPy/CBjBM=
+ROCKET_LOG_LEVEL=normal
+ROCKET_PORT=3666
+ROCKET_ADDRESS=0.0.0.0
+WEBHOOK_TOKEN=some_random_token
+RUST_LOG=debug
+LOG_ALL_ERROR_RESPONSES=true
+
+INTERNAL_CLIENT_CONNECT_TIMEOUT=10000
+SAFE_APP_INFO_REQUEST_TIMEOUT=10000
+CHAIN_INFO_REQUEST_TIMEOUT=15000
+
+
+REDIS_URI=redis://cgw-redis
+REDIS_URI_MAINNET=redis://cgw-redis
+
+EXCHANGE_API_BASE_URI=http://api.exchangeratesapi.io/latest
+EXCHANGE_API_KEY=your_exchange_rate_api_token
\ No newline at end of file
diff --git a/docker/env/txs.env b/docker/env/txs.env
new file mode 100644
index 000000000..bf85a0038
--- /dev/null
+++ b/docker/env/txs.env
@@ -0,0 +1,12 @@
+PYTHONPATH=/app/
+DJANGO_SETTINGS_MODULE=config.settings.production
+DJANGO_SECRET_KEY='Very-secure-secret-string'
+DEBUG=0
+DATABASE_URL=psql://postgres:postgres@txs-db:5432/postgres
+ETH_L2_NETWORK=1
+REDIS_URL=redis://txs-redis:6379/0
+CELERY_BROKER_URL=amqp://guest:guest@txs-rabbitmq/
+DJANGO_ALLOWED_HOSTS="*"
+FORCE_SCRIPT_NAME=/txs/
+
+CSRF_TRUSTED_ORIGINS="http://localhost:8000"
\ No newline at end of file
diff --git a/docker/env/ui.env b/docker/env/ui.env
new file mode 100644
index 000000000..5041d1899
--- /dev/null
+++ b/docker/env/ui.env
@@ -0,0 +1,31 @@
+### Required variables ###
+NEXT_PUBLIC_INFURA_TOKEN=
+NEXT_PUBLIC_GATEWAY_URL_PRODUCTION=https://localhost:8000/cgw
+
+# infura token used by Safe Apps
+NEXT_PUBLIC_SAFE_APPS_INFURA_TOKEN=
+
+# Transaction simulation
+NEXT_PUBLIC_TENDERLY_SIMULATE_ENDPOINT_URL=
+NEXT_PUBLIC_TENDERLY_PROJECT_NAME=
+NEXT_PUBLIC_TENDERLY_ORG_NAME=
+
+# Flag to switch to the production environment (redirect urls, gateway url, etc)
+NEXT_PUBLIC_IS_PRODUCTION=true
+
+### Optional variables ###
+# These variables are required only if you require a certain feature in the interface (e.g. Portis wallet)
+# Or overwrite the fallback values (set a different WalletConnect bridge)
+
+# Latest supported safe version, used for upgrade prompts
+NEXT_PUBLIC_SAFE_VERSION=1.3.0
+
+# Access keys
+NEXT_PUBLIC_SENTRY_DSN=
+NEXT_PUBLIC_BEAMER_ID=
+
+# Wallet specific variables
+NEXT_PUBLIC_WC_BRIDGE=
+NEXT_PUBLIC_FORTMATIC_KEY=
+NEXT_PUBLIC_PORTIS_KEY=
+NEXT_PUBLIC_CYPRESS_MNEMONIC=
\ No newline at end of file
diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf
new file mode 100644
index 000000000..af8d45799
--- /dev/null
+++ b/docker/nginx/nginx.conf
@@ -0,0 +1,128 @@
+# https://github.com/KyleAMathews/docker-nginx/blob/master/nginx.conf
+# https://linode.com/docs/web-servers/nginx/configure-nginx-for-optimized-performance/
+# https://docs.gunicorn.org/en/stable/deploy.html
+
+worker_processes 1;
+
+events {
+  worker_connections 2000; # increase if you have lots of clients
+  accept_mutex off; # set to 'on' if nginx worker_processes > 1
+  use epoll; # Enable epoll for Linux 2.6+
+  # 'use kqueue;' to enable for FreeBSD, OSX
+}
+
+http {
+    include mime.types;
+    # fallback in case we can't determine a type
+    default_type application/octet-stream;
+    sendfile on;
+
+    ## Transaction Service
+    upstream txs_app_server {
+      # ip_hash; # For load-balancing
+      #
+      # fail_timeout=0 means we always retry an upstream even if it failed
+      # to return a good HTTP response
+      server unix:/nginx-txs/gunicorn.socket fail_timeout=0;
+
+      # for a TCP configuration
+      # server web:8000 fail_timeout=0;
+      keepalive 32;
+    }
+
+    ## Config service
+    upstream cfg_app_server {
+      ip_hash;  # For load-balancing
+      # server cfg-web:8001 fail_timeout=0;
+      server unix:/nginx-cfg/gunicorn.socket fail_timeout=0;
+      #
+      # fail_timeout=0 means we always retry an upstream even if it failed
+      # to return a good HTTP response
+      keepalive 32;
+    }
+
+    ## Client gateway
+    upstream cgw_app_server {
+      ip_hash;  # For load-balancing
+      server cgw-web:3666 fail_timeout=0;
+      #
+      # fail_timeout=0 means we always retry an upstream even if it failed
+      # to return a good HTTP response
+      keepalive 32;
+    }
+
+    server {
+        access_log off;
+        listen 8000 deferred;
+        charset utf-8;
+        keepalive_timeout 75s;
+
+        # https://thoughts.t37.net/nginx-optimization-understanding-sendfile-tcp-nodelay-and-tcp-nopush-c55cdd276765
+        # tcp_nopush on;
+        # tcp_nodelay on;
+
+        gzip             on;
+        gzip_min_length 1000;
+        gzip_comp_level  2;
+        # text/html is always included by default
+        gzip_types text/plain text/css application/json application/javascript application/x-javascript text/javascript text/xml application/xml application/rss+xml application/atom+xml application/rdf+xml;
+        gzip_disable "MSIE [1-6]\.";
+
+        ## Transaction service mounting point
+        location /txs/static {
+            alias /nginx-txs/staticfiles;
+            expires 365d;
+        }
+
+        location /txs/ {
+            proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
+            proxy_set_header    X-Forwarded-Proto   $scheme;
+            proxy_set_header    Host                $host;
+            # we don't want nginx trying to do something clever with
+            # redirects, we set the Host: header above already.
+            proxy_redirect off;
+            proxy_pass http://txs_app_server/;
+
+            proxy_set_header X-Forwarded-Host $server_name;
+            proxy_set_header X-Real-IP $remote_addr;
+            add_header              Front-End-Https   on;
+        }
+
+        ## Config service mounting point
+        location /cfg/static {
+            alias /nginx-cfg/staticfiles;
+            expires 365d;
+        }
+
+        location /cfg/ {
+            proxy_pass http://cfg_app_server/;
+            proxy_set_header Host $host;
+            proxy_set_header X-Forwarded-Host $server_name;
+            proxy_set_header X-Real-IP $remote_addr;
+            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+            proxy_set_header        X-Forwarded-Proto $http_x_forwarded_proto;
+            add_header              Front-End-Https   on;
+            # we don't want nginx trying to do something clever with
+            # redirects, we set the Host: header above already.
+            proxy_redirect off;
+            # They default to 60s. Increase to avoid WORKER TIMEOUT in web container
+            proxy_connect_timeout 60s;
+            proxy_read_timeout 60s;
+      }
+
+      ## Client gateway mounting point
+      location /cgw/ {
+            proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
+            proxy_set_header    X-Forwarded-Proto   $scheme;
+            proxy_set_header    Host                $host;
+            # we don't want nginx trying to do something clever with
+            # redirects, we set the Host: header above already.
+            proxy_redirect off;
+            proxy_pass http://cgw_app_server/;
+
+            proxy_set_header X-Forwarded-Host $server_name;
+            proxy_set_header X-Real-IP $remote_addr;
+            add_header              Front-End-Https   on;
+        }
+    }
+}
\ No newline at end of file
diff --git a/fixture/db/accounts.json b/fixture/db/accounts.json
new file mode 100644
index 000000000..2de55b9d7
--- /dev/null
+++ b/fixture/db/accounts.json
@@ -0,0 +1,11 @@
+[
+    {
+        "_id": { "$oid": "60a38b36bf804b033c5e3faa" },
+        "active": true,
+        "email": "peter@thx.network",
+        "plan": 2,
+        "createdAt": { "$date": "2021-05-18T09:39:03.446Z" },
+        "updatedAt": { "$date": "2021-05-18T09:39:23.128Z" },
+        "__v": 0
+    }
+]
diff --git a/fixture/db/client.json b/fixture/db/client.json
new file mode 100644
index 000000000..2006cf40f
--- /dev/null
+++ b/fixture/db/client.json
@@ -0,0 +1,163 @@
+[
+    {
+        "_id": "puqOQ6ZT9U8UsfyzuTIwT",
+        "payload": {
+            "application_type": "web",
+            "grant_types": ["authorization_code", "refresh_token"],
+            "id_token_signed_response_alg": "RS256",
+            "post_logout_redirect_uris": ["https://localhost:8082"],
+            "require_auth_time": false,
+            "response_types": ["code"],
+            "subject_type": "public",
+            "token_endpoint_auth_method": "client_secret_basic",
+            "introspection_endpoint_auth_method": "client_secret_basic",
+            "require_signed_request_object": false,
+            "request_uris": ["https://localhost:8082"],
+            "client_id_issued_at": 1621329421,
+            "client_id": "puqOQ6ZT9U8UsfyzuTIwT",
+            "client_name": "THX Dashboard",
+            "client_secret_expires_at": 0,
+            "client_secret": "VVrUT7n82KNcuh947RAI-dqY79K8r47k_Xk9WvdKXl_viltV5kzX5oNDjen97vr0pBRSlv3oGZvz3_ka1136LQ",
+            "redirect_uris": ["https://localhost:8082/signin-oidc", "https://localhost:8082/silent-renew.html"],
+            "scope": "openid account:read account:write pools:read pools:write erc20:read erc20:write erc721:read erc721:write erc1155:read erc1155:write rewards:read rewards:write withdrawals:read deposits:read deposits:write promotions:read promotions:write widgets:write widgets:read transactions:read members:read members:write payments:read payments:write swaprule:read swaprule:write claims:read brands:read brands:write clients:read clients:write erc20_rewards:read erc20_rewards:write erc721_rewards:read erc721_rewards:write referral_rewards:read referral_rewards:write merchants:write merchants:read webhooks:write webhooks:read custom_rewards:write custom_rewards:read coupon_rewards:write coupon_rewards:read web3_quests:write web3_quests:read discord_role_rewards:write discord_role_rewards:read"
+        }
+    },
+    {
+        "_id": "JNd4hvx0TCZvnnXiTCnar",
+        "payload": {
+            "application_type": "web",
+            "grant_types": ["authorization_code", "refresh_token"],
+            "id_token_signed_response_alg": "RS256",
+            "post_logout_redirect_uris": ["https://localhost:8083"],
+            "require_auth_time": false,
+            "response_types": ["code"],
+            "subject_type": "public",
+            "token_endpoint_auth_method": "client_secret_basic",
+            "introspection_endpoint_auth_method": "client_secret_basic",
+            "require_signed_request_object": false,
+            "request_uris": ["http://localhost:8083"],
+            "client_id_issued_at": 1621329462,
+            "client_id": "JNd4hvx0TCZvnnXiTCnar",
+            "client_name": "THX Wallet",
+            "client_secret_expires_at": 0,
+            "client_secret": "sefR_erB65hgzp18RCj4OB6ZBCAO-jUba-4M-A8zkaQftBZD2boy-ZSSbGRBHtqXUssSI6TJZVN3W8QIyS34yw",
+            "redirect_uris": ["https://localhost:8083/signin-oidc", "https://localhost:8083/silent-renew.html"],
+            "scope": "openid offline_access rewards:read erc20:read erc721:read withdrawals:read withdrawals:write deposits:read deposits:write account:read account:write memberships:read memberships:write promotions:read transactions:read relay:write swap:read swap:write swaprule:read swaprule:write claims:read wallets:read wallets:write"
+        }
+    },
+    {
+        "_id": "vdYn6w3z9wQ9TO7gbZkEO",
+        "payload": {
+            "application_type": "web",
+            "grant_types": ["client_credentials"],
+            "id_token_signed_response_alg": "RS256",
+            "post_logout_redirect_uris": [],
+            "require_auth_time": false,
+            "response_types": [],
+            "subject_type": "public",
+            "token_endpoint_auth_method": "client_secret_basic",
+            "introspection_endpoint_auth_method": "client_secret_basic",
+            "require_signed_request_object": false,
+            "request_uris": [],
+            "client_id_issued_at": 1621329896,
+            "client_id": "vdYn6w3z9wQ9TO7gbZkEO",
+            "client_name": "THX CMS",
+            "client_secret_expires_at": 0,
+            "client_secret": "gCCe45P1arpSfy9605iXiRno7yp1pmFafbXQMk_MUzE6eRNJHpSK3h63fMI_E9pChBhIBtF1cQBvFxKTCPVfjQ",
+            "redirect_uris": [],
+            "scope": "openid metrics:read"
+        }
+    },
+    {
+        "_id": "ISsh6Xw6wDISihJS2xs2_",
+        "payload": {
+            "application_type": "web",
+            "grant_types": ["client_credentials"],
+            "id_token_signed_response_alg": "RS256",
+            "post_logout_redirect_uris": [],
+            "require_auth_time": false,
+            "response_types": [],
+            "subject_type": "public",
+            "token_endpoint_auth_method": "client_secret_basic",
+            "introspection_endpoint_auth_method": "client_secret_basic",
+            "require_signed_request_object": false,
+            "request_uris": [],
+            "client_id_issued_at": 1635194521,
+            "client_id": "ISsh6Xw6wDISihJS2xs2_",
+            "client_name": "THX API",
+            "client_secret_expires_at": 0,
+            "client_secret": "BnQrAHSb4UDnpR-9klfFAQbZvq_RjVZgLTKwRD3qfOjawX21jrnbBxvSOU84EwqAy1J-fNKvxD2qZen5Gm8jXg",
+            "redirect_uris": [],
+            "scope": "openid accounts:read accounts:write"
+        }
+    },
+    {
+        "_id": "P4bLsMzQIwWlr_DOhH_qE",
+        "payload": {
+            "application_type": "web",
+            "grant_types": ["client_credentials"],
+            "id_token_signed_response_alg": "RS256",
+            "post_logout_redirect_uris": [],
+            "require_auth_time": false,
+            "response_types": [],
+            "subject_type": "public",
+            "token_endpoint_auth_method": "client_secret_basic",
+            "introspection_endpoint_auth_method": "client_secret_basic",
+            "require_signed_request_object": false,
+            "request_uris": [],
+            "client_id_issued_at": 1655295083,
+            "client_id": "P4bLsMzQIwWlr_DOhH_qE",
+            "client_name": "THX Auth",
+            "client_secret_expires_at": 0,
+            "client_secret": "o7RMGeLieULzp0bAiuq6nfukFBCLKxo8N867SzeZNrinshSb15W9NggU93CdtrE_0jTWLUl3U2YkcXYtD3hdcQ",
+            "redirect_uris": [],
+            "scope": "openid brands:read claims:write claims:read wallets:write wallets:read pools:write pools:read"
+        }
+    },
+    {
+        "_id": "8pqC-D11si73rCKz8bmNV",
+        "payload": {
+            "application_type": "web",
+            "grant_types": ["authorization_code", "refresh_token"],
+            "id_token_signed_response_alg": "RS256",
+            "post_logout_redirect_uris": ["https://localhost:8080/signout-popup.html"],
+            "require_auth_time": false,
+            "response_types": ["code"],
+            "subject_type": "public",
+            "token_endpoint_auth_method": "client_secret_basic",
+            "introspection_endpoint_auth_method": "client_secret_basic",
+            "require_signed_request_object": false,
+            "request_uris": ["https://localhost:8080"],
+            "client_id_issued_at": 1669134665,
+            "client_id": "8pqC-D11si73rCKz8bmNV",
+            "client_name": "THX Widget",
+            "client_secret_expires_at": 0,
+            "client_secret": "9rfpbMSwVxEaZUpmsCWVldpO-xy7H13YT5AoMIdFNUX9pWosR3cTfoDyJgnuqFqepqRXbgMhb0PFYwOWgwQoEQ",
+            "redirect_uris": ["https://localhost:8080/signin-popup.html", "https://localhost:8080/signin-silent.html"],
+            "scope": "openid offline_access account:read account:write erc20:read erc721:read erc1155:read point_balances:read referral_rewards:read point_rewards:read wallets:read wallets:write pool_subscription:read pool_subscription:write claims:read"
+        }
+    },
+    {
+        "_id": "9qqC-D11si12rCKz8bmNV",
+        "payload": {
+            "application_type": "web",
+            "grant_types": ["client_credentials"],
+            "id_token_signed_response_alg": "RS256",
+            "post_logout_redirect_uris": ["https://localhost:8080/signout-popup.html"],
+            "require_auth_time": false,
+            "response_types": [],
+            "subject_type": "public",
+            "token_endpoint_auth_method": "client_secret_basic",
+            "introspection_endpoint_auth_method": "client_secret_basic",
+            "require_signed_request_object": false,
+            "request_uris": ["https://localhost:8080"],
+            "client_id_issued_at": 1669134665,
+            "client_id": "9qqC-D11si12rCKz8bmNV",
+            "client_name": "THX Discord",
+            "client_secret_expires_at": 0,
+            "client_secret": "9rfpbMSwVxEaZUpmsCWVldpO-xy7H13YT5AoMIdFNUX9pWosR3cTfoDyJgnuqFqepqRXbgMhb0PFYwOWgwQoEQ",
+            "redirect_uris": ["https://localhost:8080/signin-popup.html"],
+            "scope": "openid account:read erc20:read erc721:read point_balances:read referral_rewards:read point_rewards:read wallets:read pools:read"
+        }
+    }
+]
diff --git a/fixture/db/registration_access_token.json b/fixture/db/registration_access_token.json
new file mode 100644
index 000000000..d1c170dcd
--- /dev/null
+++ b/fixture/db/registration_access_token.json
@@ -0,0 +1,47 @@
+[
+    {
+        "_id": "CbIkcp42z8bWlNEBJWVckr4Yq6yyxBsoxnOVb2dSdR1",
+        "payload": {
+            "iat": 1621329421,
+            "clientId": "puqOQ6ZT9U8UsfyzuTIwT",
+            "kind": "RegistrationAccessToken",
+            "jti": "CbIkcp42z8bWlNEBJWVckr4Yq6yyxBsoxnOVb2dSdR1"
+        }
+    },
+    {
+        "_id": "VcsLVLBur1nfu3Ruy3U1Gika9TsBuoHWyb2UMRzAhHg",
+        "payload": {
+            "iat": 1621329896,
+            "clientId": "vdYn6w3z9wQ9TO7gbZkEO",
+            "kind": "RegistrationAccessToken",
+            "jti": "VcsLVLBur1nfu3Ruy3U1Gika9TsBuoHWyb2UMRzAhHg"
+        }
+    },
+    {
+        "_id": "5oWplA44GmXBdaG94s2v4T55WXVnKzFoXcFRo6Q5x3H",
+        "payload": {
+            "iat": 1621329462,
+            "clientId": "JNd4hvx0TCZvnnXiTCnar",
+            "kind": "RegistrationAccessToken",
+            "jti": "5oWplA44GmXBdaG94s2v4T55WXVnKzFoXcFRo6Q5x3H"
+        }
+    },
+    {
+        "_id": "TRYjXYjggZXZaqIlygO04U89-3BN2zg2xkQ9ZFiJL5-",
+        "payload": {
+            "iat": 1635194521,
+            "clientId": "ISsh6Xw6wDISihJS2xs2_",
+            "kind": "RegistrationAccessToken",
+            "jti": "TRYjXYjggZXZaqIlygO04U89-3BN2zg2xkQ9ZFiJL5-"
+        }
+    },
+    {
+        "_id": "8PWFMcB1clE7TcH3_MhDTPrKm4mlzrInqbp7PdhZ226",
+        "payload": {
+            "iat": 1669134665,
+            "clientId": "8pqC-D11si73rCKz8bmNV",
+            "kind": "RegistrationAccessToken",
+            "jti": "8PWFMcB1clE7TcH3_MhDTPrKm4mlzrInqbp7PdhZ226"
+        }
+    }
+]
diff --git a/fixture/db/wallets.json b/fixture/db/wallets.json
new file mode 100644
index 000000000..8172e4599
--- /dev/null
+++ b/fixture/db/wallets.json
@@ -0,0 +1,59 @@
+[
+    {
+        "_id": {
+            "$oid": "63ca9d8a82ba39fc1d15bef5"
+        },
+        "sub": "60a38b36bf804b033c5e3faa",
+        "chainId": 31337,
+        "createdAt": {
+            "$date": {
+                "$numberLong": "1674222986757"
+            }
+        },
+        "updatedAt": {
+            "$date": {
+                "$numberLong": "1674222986996"
+            }
+        },
+        "__v": 0,
+        "address": "0xE1AEC35F626F54De2979C3297B875277D5Cf052B"
+    },
+    {
+        "_id": {
+            "$oid": "63ca9d8a82ba39fc1d15befa"
+        },
+        "sub": "60a38b36bf804b033c5e3faa",
+        "chainId": 137,
+        "createdAt": {
+            "$date": {
+                "$numberLong": "1674222986777"
+            }
+        },
+        "updatedAt": {
+            "$date": {
+                "$numberLong": "1674223002142"
+            }
+        },
+        "__v": 0,
+        "address": "0x10af5daFEE2505DD4980D98beCc253d35Bc60684"
+    },
+    {
+        "_id": {
+            "$oid": "63ca9d8a82ba39fc1d15bef6"
+        },
+        "sub": "60a38b36bf804b033c5e3faa",
+        "chainId": 80001,
+        "createdAt": {
+            "$date": {
+                "$numberLong": "1674222986757"
+            }
+        },
+        "updatedAt": {
+            "$date": {
+                "$numberLong": "1674223004075"
+            }
+        },
+        "__v": 0,
+        "address": "0xC05f86A0E4aA365E89FB951764462a7874A3B97B"
+    }
+]
diff --git a/libs/common/project.json b/libs/common/project.json
index 5552be300..e4ef77d86 100644
--- a/libs/common/project.json
+++ b/libs/common/project.json
@@ -3,6 +3,7 @@
     "$schema": "../../node_modules/nx/schemas/project-schema.json",
     "sourceRoot": "libs/common/src",
     "projectType": "library",
+    "tags": [],
     "targets": {
         "build": {
             "executor": "@nrwl/js:tsc",
@@ -21,6 +22,5 @@
                 "lintFilePatterns": ["libs/common/**/*.ts"]
             }
         }
-    },
-    "tags": []
+    }
 }
diff --git a/libs/common/src/index.ts b/libs/common/src/index.ts
index 27510c8a4..bae13b640 100644
--- a/libs/common/src/index.ts
+++ b/libs/common/src/index.ts
@@ -1,4 +1,5 @@
 import { Sentry } from './lib/sentry';
 import { track } from './lib/mixpanel';
+import { migrateMongoScript } from './lib/migrate-mongo';
 
-export { Sentry, track };
+export { Sentry, track, migrateMongoScript };
diff --git a/libs/common/src/lib/chains.ts b/libs/common/src/lib/chains.ts
new file mode 100644
index 000000000..ec46f1bb8
--- /dev/null
+++ b/libs/common/src/lib/chains.ts
@@ -0,0 +1,61 @@
+import { ChainId } from './enums';
+
+type ChainInfo = { name: string; chainId: number; blockExplorer: string; rpc: string };
+
+const chainList: { [chainId: number]: ChainInfo } = {
+    [ChainId.Ethereum]: {
+        chainId: ChainId.Ethereum,
+        name: 'Ethereum',
+        blockExplorer: 'https://etherscan.com',
+        rpc: 'https://cloudflare-eth.com',
+    },
+    [ChainId.BNBChain]: {
+        chainId: ChainId.BNBChain,
+        name: 'BNB Chain',
+        blockExplorer: 'https://bscscan.com',
+        rpc: 'https://rpc.ankr.com/bsc',
+    },
+    [ChainId.Arbitrum]: {
+        chainId: ChainId.Arbitrum,
+        name: 'Arbitrum',
+        blockExplorer: 'https://arbiscan.io',
+        rpc: 'https://arb1.arbitrum.io/rpc',
+    },
+    [ChainId.Polygon]: {
+        chainId: ChainId.Polygon,
+        name: 'Polygon',
+        blockExplorer: 'https://polygonscan.com',
+        rpc: 'https://polygon-rpc.com',
+    },
+    [ChainId.PolygonZK]: {
+        chainId: ChainId.PolygonZK,
+        name: 'Polygon zkEVM',
+        blockExplorer: 'https://zkevm.polygonscan.com',
+        rpc: 'https://zkevm-rpc.com',
+    },
+    [ChainId.Linea]: {
+        chainId: ChainId.Linea,
+        name: 'Linea',
+        blockExplorer: 'https://lineascan.build',
+        rpc: 'https://rpc.linea.build',
+    },
+};
+
+if (process.env['NODE_ENV'] !== 'production') {
+    chainList[ChainId.Hardhat] = {
+        chainId: ChainId.Hardhat,
+        name: 'Hardhat',
+        blockExplorer: 'https://hardhatscan.com',
+        rpc: 'http://127.0.0.1:8545',
+    };
+}
+
+function getTokenURL(chainId: ChainId, address: string) {
+    return `${chainList[chainId].blockExplorer}/token/${address}`;
+}
+
+function getAddressURL(chainId: ChainId, address: string) {
+    return `${chainList[chainId].blockExplorer}/address/${address}`;
+}
+
+export { chainList, getTokenURL, getAddressURL };
diff --git a/libs/common/src/lib/constants.ts b/libs/common/src/lib/constants.ts
new file mode 100644
index 000000000..b2b72a0d0
--- /dev/null
+++ b/libs/common/src/lib/constants.ts
@@ -0,0 +1,250 @@
+import { AccountPlanType, Goal, Role } from './enums';
+
+export const planPricingMap = {
+    [AccountPlanType.Lite]: {
+        subscriptionLimit: 100,
+        costSubscription: 1900,
+        costPerUnit: 8,
+    },
+    [AccountPlanType.Premium]: {
+        subscriptionLimit: 5000,
+        costSubscription: 48900,
+        costPerUnit: 5,
+    },
+};
+
+export const GITHUB_API_ENDPOINT = 'https://api.github.com';
+export const TWITTER_API_ENDPOINT = 'https://api.twitter.com/2';
+export const GOOGLE_API_ENDPOINT = 'https://www.googleapis.com';
+export const DISCORD_API_ENDPOINT = 'https://discord.com/api/v10';
+export const TWITCH_API_ENDPOINT = 'https://api.twitch.tv/helix';
+export const DEFAULT_ELEMENTS = {
+    btnBg: {
+        label: 'Button',
+        color: '#5942c1',
+    },
+    btnText: {
+        label: 'Button Text',
+        color: '#FFFFFF',
+    },
+    text: {
+        label: 'Text',
+        color: '#FFFFFF',
+    },
+    bodyBg: {
+        label: 'Background',
+        color: '#241956',
+    },
+    cardBg: {
+        label: 'Card',
+        color: '#31236d',
+    },
+    cardText: {
+        label: 'Card Text',
+        color: '#FFFFFF',
+    },
+    navbarBg: {
+        label: 'Navigation',
+        color: '#31236d',
+    },
+    navbarBtnBg: {
+        label: 'Navigation Button',
+        color: '#5942c1',
+    },
+    navbarBtnText: {
+        label: 'Navigation Button Text',
+        color: '#FFFFFF',
+    },
+    launcherBg: {
+        label: 'Launcher',
+        color: '#5942c1',
+    },
+    launcherIcon: {
+        label: 'Launcher Icon',
+        color: '#ffffff',
+    },
+};
+
+export const DEFAULT_COLORS = {
+    accent: {
+        label: 'Accent',
+        color: '#98D80D',
+    },
+    success: {
+        label: 'Success',
+        color: '#28a745',
+    },
+    warning: {
+        label: 'Warning',
+        color: '#ffe500',
+    },
+    danger: {
+        label: 'Danger',
+        color: '#dc3545',
+    },
+    info: {
+        label: 'Info',
+        color: '#17a2b8',
+    },
+};
+
+export const roleLabelMap = {
+    [Role.None]: 'Select a role',
+    [Role.GrowthHacker]: 'Growth Hacker',
+    [Role.Marketer]: 'Marketer',
+    [Role.CommunityManager]: 'Community Manager',
+    [Role.Developer]: 'Developer',
+    [Role.Other]: 'Other',
+};
+
+export const goalLabelMap = {
+    [Goal.Reward]: 'Reward users in my game or app',
+    [Goal.Retain]: 'Retain players or members',
+    [Goal.Referral]: 'Set up referrals',
+    [Goal.Social]: 'Integrate rewards in social channels',
+    [Goal.Mint]: 'Mint tokens',
+};
+
+export const contentRewards = {
+    'coin-reward': {
+        tag: 'Coin Reward',
+        title: 'Cashbacks with Coins',
+        description: 'Import your own ERC20 smart contract and let users redeem points for coins.',
+        list: ['Provide tangible value', 'Boost user retention', 'Incentivize spending'],
+        docsUrl: 'https://docs.thx.network/rewards/coins',
+        icon: 'fas fa-coins', // Suggested icon for coins
+        color: '#FFD700', // Suggested color for coins (Gold)
+    },
+    'nft-reward': {
+        tag: 'NFT Reward',
+        title: 'Exclusive NFTs',
+        description: 'Import your own ERC721 or ERC1155 smart contract and let users redeem points for exclusive NFTs.',
+        list: ['Offer unique collectibles', 'Enhance user engagement', 'Drive interest in NFTs'],
+        docsUrl: 'https://docs.thx.network/rewards/nft',
+        icon: 'fas fa-gem', // Suggested icon for gems or precious items
+        color: '#9370DB', // Suggested color for NFTs (Light Purple)
+    },
+    'custom-reward': {
+        tag: 'Custom Reward',
+        title: 'Flexible Rewards',
+        description: 'Use inbound webhooks to reward users with custom features in your application.',
+        list: ['Tailor rewards to user needs', 'Enhance user satisfaction', 'Drive app adoption'],
+        docsUrl: 'https://docs.thx.network/rewards',
+        icon: 'fas fa-cogs', // Suggested icon for customization or settings
+        color: '#808080', // Suggested color for customization (Gray)
+    },
+    'discord-role-reward': {
+        tag: 'Discord Role Reward',
+        title: 'Exclusive Discord Roles',
+        description:
+            'Grant users the ability to redeem points for exclusive Discord roles within your community server.',
+        list: ['Promote community status', 'Encourage active participation', 'Facilitate social interaction'],
+        docsUrl: 'https://docs.thx.network/rewards/discord-role',
+        icon: 'fab fa-discord', // Suggested icon for shield or protection
+        color: '#7289DA', // Suggested color for Discord roles (Discord Blue)
+    },
+    'qr-codes': {
+        tag: 'QR Codes',
+        title: 'Offline Reward Distribution',
+        description: 'Use QR codes to distribute rewards in offline environments.',
+        list: ['Expand reach to offline users', 'Facilitate in-person engagement', 'Enhance brand recognition'],
+        docsUrl: 'https://docs.thx.network',
+        icon: 'fas fa-qrcode', // Suggested icon for QR codes
+        color: '#000000', // Suggested color for QR codes (Black)
+    },
+};
+
+export const contentQuests = {
+    'steam-quest': {
+        tag: 'Steam Quest',
+        icon: 'fab fa-steam',
+        title: 'Unlock Steam engagement',
+        description: 'Embark on a gaming journey by purchasing, wishlisting games, and earning Steam achievements.',
+        list: ['Buy a game on Steam', 'Wishlist a game on Steam', 'Earn a Steam achievement'],
+        docsUrl: 'https://docs.thx.network/user-guides/quests/daily-quests',
+        color: '#171d25',
+    },
+    'twitter-quest': {
+        tag: 'Twitter Quest',
+        icon: 'fab fa-twitter',
+        title: 'Boost your Twitter presence',
+        description: 'Engage your audience on Twitter by creating exciting quests that encourage retweets and likes.',
+        list: ['Increase followers', 'Enhance brand recognition', 'Foster community engagement'],
+        docsUrl: 'https://docs.thx.network/user-guides/quests/social-quests',
+        color: '#1B95E0',
+    },
+    'daily-quest': {
+        tag: 'Daily Quest',
+        title: 'Boost user engagement',
+        icon: 'fas fa-calendar',
+        description: 'Provide daily incentives for returning to your website.',
+        list: ['Encourage regular visits', 'Enhance user loyalty', 'Foster community growth'],
+        docsUrl: 'https://docs.thx.network/user-guides/quests/daily-quests',
+        color: '#4CAF50',
+    },
+    'custom-quest': {
+        tag: 'Custom Quest',
+        icon: 'fas fa-trophy',
+        title: 'Seamless integration',
+        description: 'Integrate quests with ease using webhooks to reward important achievements in your application.',
+        list: ['Tailor rewards to your app', 'Streamline integration', 'Enhance user experience'],
+        docsUrl: 'https://docs.thx.network/user-guides/quests/custom-quests',
+        color: '#9370DB',
+    },
+    'youtube-quest': {
+        tag: 'Youtube Quest',
+        icon: 'fab fa-youtube',
+        title: 'Expand your YouTube presence',
+        description:
+            'Amplify your presence on YouTube by creating quests that encourage likes, shares, and subscriptions.',
+        list: ['Increase video views', 'Boost channel subscribers', 'Enhance YouTube community engagement'],
+        docsUrl: 'https://docs.thx.network/user-guides/quests/social-quests',
+        color: '#FF0000',
+    },
+    'invite-quest': {
+        tag: 'Invite Quest',
+        icon: 'fas fa-comments',
+        title: 'Drive user acquisition',
+        description: 'Empower your players to earn rewards for referrals.',
+        list: ['Expand your user base', 'Lower acquisition costs', 'Strengthen player networks'],
+        docsUrl: 'https://docs.thx.network/user-guides/quests/referral-quests',
+        color: '#FFA500',
+    },
+    'discord-quest': {
+        tag: 'Discord Quest',
+        icon: 'fab fa-discord',
+        title: 'Strengthen your Discord community',
+        description:
+            'Create quests on Discord to promote community interactions and build a strong, engaged user base.',
+        list: ['Grow your Discord server', 'Enhance community participation', 'Boost server activity'],
+        docsUrl: 'https://docs.thx.network/user-guides/quests',
+        color: '#5865F2',
+    },
+    'web3-quest': {
+        tag: 'Web3 Quest',
+        icon: 'fab fa-ethereum',
+        title: 'Empower with Web3 rewards',
+        description: "Reward users' coin balance or NFT ownership using smart contracts.",
+        list: ['Leverage blockchain technology', 'Enhance user ownership', 'Facilitate decentralized rewards'],
+        docsUrl: 'https://docs.thx.network/user-guides/quests',
+        color: '#3C3C3D',
+    },
+    'gitcoin-quest': {
+        tag: 'Gitcoin Quest',
+        icon: 'fas fa-fingerprint',
+        title: 'Use Gitcoin Passport for sybil resistance',
+        description: 'Use this quest to verify if wallets are owned by real humans.',
+        list: ['Increase Sybil Resistance', 'Gitcoin Unique Humanity Scorer', 'Tap into new ecosystems'],
+        docsUrl: 'https://docs.thx.network/user-guides/quests',
+        color: '#3498db',
+    },
+    'webhook-quest': {
+        tag: 'Webhook Quest',
+        icon: 'fas fa-globe',
+        title: 'Use your own API endpoints',
+        description: 'Use your API endpoint to validate quests for account identities.',
+        list: ['Track real engagement', 'Your own validation method', 'Account identities'],
+        docsUrl: 'https://docs.thx.network/user-guides/quests',
+        color: '#3498db',
+    },
+};
diff --git a/libs/common/src/lib/enums/AccessTokenKind.ts b/libs/common/src/lib/enums/AccessTokenKind.ts
new file mode 100644
index 000000000..7c0110e65
--- /dev/null
+++ b/libs/common/src/lib/enums/AccessTokenKind.ts
@@ -0,0 +1,72 @@
+export enum AccessTokenKind {
+    Auth = 'authentication',
+    Signup = 'signup',
+    VerifyEmail = 'verify-email',
+    Google = 'google',
+    Twitter = 'twitter',
+    Discord = 'discord',
+    Twitch = 'twitch',
+    Github = 'github',
+}
+
+export enum OAuthGoogleScope {
+    OpenID = 'openid',
+    Email = 'https://www.googleapis.com/auth/userinfo.email',
+    YoutubeReadOnly = 'https://www.googleapis.com/auth/youtube.readonly',
+}
+
+export enum OAuthTwitterScope {
+    OfflineAccess = 'offline.access',
+    UsersRead = 'users.read',
+    TweetRead = 'tweet.read',
+    FollowsWrite = 'follows.write',
+    LikeRead = 'like.read',
+}
+
+export enum OAuthDiscordScope {
+    Identify = 'identify',
+    Email = 'email',
+    Guilds = 'guilds',
+}
+
+export enum OAuthTwitchScope {
+    Email = 'user:read:email',
+    Follows = 'user:read:follows',
+    Broadcast = 'user:read:broadcast',
+}
+
+export enum OAuthGithubScope {
+    PublicRepo = 'public_repo',
+}
+
+// Different scope requirements should always be elevating based on the invasiveness of the required scope
+// This allows for better privacy by design
+export const OAuthRequiredScopes = {
+    GoogleAuth: [OAuthGoogleScope.OpenID, OAuthGoogleScope.Email],
+    GoogleYoutubeSubscribe: [OAuthGoogleScope.OpenID, OAuthGoogleScope.Email, OAuthGoogleScope.YoutubeReadOnly],
+    GoogleYoutubeLike: [OAuthGoogleScope.OpenID, OAuthGoogleScope.Email, OAuthGoogleScope.YoutubeReadOnly],
+    TwitterAuth: [OAuthTwitterScope.OfflineAccess, OAuthTwitterScope.UsersRead, OAuthTwitterScope.TweetRead],
+    TwitterValidateUser: [OAuthTwitterScope.OfflineAccess, OAuthTwitterScope.UsersRead, OAuthTwitterScope.TweetRead],
+    TwitterValidateRepost: [OAuthTwitterScope.OfflineAccess, OAuthTwitterScope.UsersRead, OAuthTwitterScope.TweetRead],
+    TwitterValidateMessage: [OAuthTwitterScope.OfflineAccess, OAuthTwitterScope.UsersRead, OAuthTwitterScope.TweetRead],
+    TwitterValidateLike: [
+        OAuthTwitterScope.OfflineAccess,
+        OAuthTwitterScope.UsersRead,
+        OAuthTwitterScope.TweetRead,
+        OAuthTwitterScope.LikeRead,
+    ],
+    TwitterValidateFollow: [
+        OAuthTwitterScope.OfflineAccess,
+        OAuthTwitterScope.UsersRead,
+        OAuthTwitterScope.TweetRead,
+        OAuthTwitterScope.LikeRead,
+        OAuthTwitterScope.FollowsWrite,
+    ],
+    TwitterAutoQuest: [OAuthTwitterScope.OfflineAccess, OAuthTwitterScope.UsersRead, OAuthTwitterScope.TweetRead],
+    DiscordAuth: [OAuthDiscordScope.Identify, OAuthDiscordScope.Email],
+    DiscordValidateGuild: [OAuthDiscordScope.Identify, OAuthDiscordScope.Email, OAuthDiscordScope.Guilds],
+    TwitchAuth: [OAuthTwitchScope.Email, OAuthTwitchScope.Follows, OAuthTwitchScope.Broadcast],
+    GithubAuth: [OAuthGithubScope.PublicRepo],
+};
+
+export type OAuthScope = OAuthGoogleScope | OAuthTwitterScope | OAuthDiscordScope | OAuthTwitchScope | OAuthGithubScope;
diff --git a/libs/common/src/lib/enums/AccountPlanType.ts b/libs/common/src/lib/enums/AccountPlanType.ts
new file mode 100644
index 000000000..a33e94817
--- /dev/null
+++ b/libs/common/src/lib/enums/AccountPlanType.ts
@@ -0,0 +1,4 @@
+export enum AccountPlanType {
+    Lite = 0,
+    Premium = 1,
+}
diff --git a/libs/common/src/lib/enums/AccountVariant.ts b/libs/common/src/lib/enums/AccountVariant.ts
new file mode 100644
index 000000000..059a9b7d8
--- /dev/null
+++ b/libs/common/src/lib/enums/AccountVariant.ts
@@ -0,0 +1,18 @@
+export enum AccountVariant {
+    EmailPassword = 0,
+    SSOGoogle = 1,
+    SSOTwitter = 2,
+    SSOSpotify = 3, // @dev Deprecated
+    Metamask = 4,
+    SSOGithub = 5,
+    SSODiscord = 6,
+    SSOTwitch = 7,
+}
+
+export enum ReCaptchaAction {
+    QuestDailyEntryCreate = 'QUEST_DAILY_ENTRY_CREATE',
+    QuestSocialEntryCreate = 'QUEST_SOCIAL_ENTRY_CREATE',
+    QuestCustomEntryCreate = 'QUEST_CUSTOM_ENTRY_CREATE',
+    QuestWeb3EntryCreate = 'QUEST_WEB3_ENTRY_CREATE',
+    QuestGitcoinEntryCreate = 'QUEST_GITCOIN_ENTRY_CREATE',
+}
diff --git a/libs/common/src/lib/enums/ChainId.ts b/libs/common/src/lib/enums/ChainId.ts
new file mode 100644
index 000000000..aa8f31b14
--- /dev/null
+++ b/libs/common/src/lib/enums/ChainId.ts
@@ -0,0 +1,9 @@
+export enum ChainId {
+    Ethereum = 1,
+    Arbitrum = 42161,
+    BNBChain = 56,
+    Hardhat = 31337,
+    Polygon = 137,
+    PolygonZK = 1101,
+    Linea = 59144,
+}
diff --git a/libs/common/src/lib/enums/Collaborator.ts b/libs/common/src/lib/enums/Collaborator.ts
new file mode 100644
index 000000000..8bc837427
--- /dev/null
+++ b/libs/common/src/lib/enums/Collaborator.ts
@@ -0,0 +1,5 @@
+export enum CollaboratorInviteState {
+    Pending = 0,
+    Accepted = 1,
+    Expired = 2,
+}
diff --git a/libs/common/src/lib/enums/DailyRewardClaimState.ts b/libs/common/src/lib/enums/DailyRewardClaimState.ts
new file mode 100644
index 000000000..d76454f67
--- /dev/null
+++ b/libs/common/src/lib/enums/DailyRewardClaimState.ts
@@ -0,0 +1,4 @@
+export enum DailyRewardClaimState {
+    Pending = 0,
+    Claimed = 1,
+}
diff --git a/libs/common/src/lib/enums/ERC1155.ts b/libs/common/src/lib/enums/ERC1155.ts
new file mode 100644
index 000000000..1fec73de7
--- /dev/null
+++ b/libs/common/src/lib/enums/ERC1155.ts
@@ -0,0 +1,7 @@
+export enum ERC1155TokenState {
+    Pending = 0,
+    Failed = 1,
+    Minted = 2,
+    Transferring = 3,
+    Transferred = 4,
+}
diff --git a/libs/common/src/lib/enums/ERC20Type.ts b/libs/common/src/lib/enums/ERC20Type.ts
new file mode 100644
index 000000000..b30072301
--- /dev/null
+++ b/libs/common/src/lib/enums/ERC20Type.ts
@@ -0,0 +1,5 @@
+export enum ERC20Type {
+    Unknown = -1,
+    Limited = 0,
+    Unlimited = 1,
+}
diff --git a/libs/common/src/lib/enums/ERC721Variant.ts b/libs/common/src/lib/enums/ERC721Variant.ts
new file mode 100644
index 000000000..0b8d890fa
--- /dev/null
+++ b/libs/common/src/lib/enums/ERC721Variant.ts
@@ -0,0 +1,14 @@
+export enum ERC721Variant {
+    Unknown = -1,
+    Default = 0,
+    OpenSea = 1,
+    Lottery = 2,
+}
+
+export enum ERC721TokenState {
+    Pending = 0,
+    Failed = 1,
+    Minted = 2,
+    Transferring = 3,
+    Transferred = 4,
+}
diff --git a/libs/common/src/lib/enums/Event.ts b/libs/common/src/lib/enums/Event.ts
new file mode 100644
index 000000000..d4f4baf70
--- /dev/null
+++ b/libs/common/src/lib/enums/Event.ts
@@ -0,0 +1,9 @@
+export enum Event {
+    QuestDailyComplete = 'quest_daily.complete',
+    QuestInviteComplete = 'quest_invite.complete',
+    QuestSocialComplete = 'quest_social.complete',
+    QuestCustomComplete = 'quest_custom.complete',
+    RewardCoinPayment = 'reward_coin.paid',
+    RewardNFTPayment = 'reward_nft.paid',
+    RewardCustomPayment = 'reward_custom.paid',
+}
diff --git a/libs/common/src/lib/enums/GateVariant.ts b/libs/common/src/lib/enums/GateVariant.ts
new file mode 100644
index 000000000..502bd5b9b
--- /dev/null
+++ b/libs/common/src/lib/enums/GateVariant.ts
@@ -0,0 +1,6 @@
+export enum GateVariant {
+    ERC721 = 0,
+    ERC1155 = 1,
+    ERC20 = 2,
+    UniqueHumanity = 3,
+}
diff --git a/libs/common/src/lib/enums/GrantVariant.ts b/libs/common/src/lib/enums/GrantVariant.ts
new file mode 100644
index 000000000..062c7184a
--- /dev/null
+++ b/libs/common/src/lib/enums/GrantVariant.ts
@@ -0,0 +1,4 @@
+export enum GrantVariant {
+    AuthorizationCode = 'authorization_code',
+    ClientCredentials = 'client_credentials',
+}
diff --git a/libs/common/src/lib/enums/Job.ts b/libs/common/src/lib/enums/Job.ts
new file mode 100644
index 000000000..e953817d8
--- /dev/null
+++ b/libs/common/src/lib/enums/Job.ts
@@ -0,0 +1,19 @@
+export enum JobType {
+    UpdateParticipantRanks = 'updateParticipantRanks',
+    UpdateCampaignRanks = 'updateCampaignRanks',
+    UpdatePendingTransactions = 'updatePendingTransactions',
+    CreateTwitterQuests = 'createTwitterQuests',
+    SendCampaignReport = 'sendPoolAnalyticsReport',
+    MigrateWallets = 'migrateWallets',
+    DeploySafe = 'deploySafe',
+    CreateQuestEntry = 'createQuestEntry',
+    CreateRewardPayment = 'createRewardPayment',
+    RequestAttemp = 'requestAttempt',
+    UpdateTwitterLikeCache = 'updateTwitterLikeCache',
+    UpdateTwitterRepostCache = 'updateTwitterRepostCache',
+    UpsertInvoices = 'upsertInvoices',
+    UpdatePrices = 'updatePrices',
+    UpdateAPR = 'updateAPR',
+    AssertPayments = 'assertPayments',
+    ClaimExternalRewards = 'claimExternalRewards',
+}
diff --git a/libs/common/src/lib/enums/NFTVariant.ts b/libs/common/src/lib/enums/NFTVariant.ts
new file mode 100644
index 000000000..e87cfec3e
--- /dev/null
+++ b/libs/common/src/lib/enums/NFTVariant.ts
@@ -0,0 +1,4 @@
+export enum NFTVariant {
+    ERC721 = 'erc721',
+    ERC1155 = 'erc1155',
+}
diff --git a/libs/common/src/lib/enums/PlatformVariant.ts b/libs/common/src/lib/enums/PlatformVariant.ts
new file mode 100644
index 000000000..6fda9bde0
--- /dev/null
+++ b/libs/common/src/lib/enums/PlatformVariant.ts
@@ -0,0 +1,7 @@
+export enum PlatformVariant {
+    None = 0,
+    Google = 1,
+    Twitter = 2,
+    Spotify = 3,
+    Github = 4,
+}
diff --git a/libs/common/src/lib/enums/QuestSocialRequirement.ts b/libs/common/src/lib/enums/QuestSocialRequirement.ts
new file mode 100644
index 000000000..29cd3aa69
--- /dev/null
+++ b/libs/common/src/lib/enums/QuestSocialRequirement.ts
@@ -0,0 +1,13 @@
+export enum QuestSocialRequirement {
+    YouTubeLike = 0,
+    YouTubeSubscribe = 1,
+    TwitterLike = 2,
+    TwitterRetweet = 3,
+    TwitterFollow = 4,
+    DiscordGuildJoined = 5,
+    TwitterQuery = 6,
+    TwitterLikeRetweet = 7,
+    DiscordMessage = 8,
+    DiscordMessageReaction = 9,
+    DiscordGuildRole = 10,
+}
diff --git a/libs/common/src/lib/enums/RewardConditionPlatform.ts b/libs/common/src/lib/enums/RewardConditionPlatform.ts
new file mode 100644
index 000000000..8d23b03ff
--- /dev/null
+++ b/libs/common/src/lib/enums/RewardConditionPlatform.ts
@@ -0,0 +1,10 @@
+// export enum RewardConditionPlatform {
+//     None = 0,
+//     Google = 1,
+//     Twitter = 2,
+//     Spotify = 3,
+//     Github = 4,
+//     Discord = 5,
+//     Twitch = 6,
+//     Shopify = 7,
+// }
diff --git a/libs/common/src/lib/enums/RewardVariant.ts b/libs/common/src/lib/enums/RewardVariant.ts
new file mode 100644
index 000000000..cc6ee7a8d
--- /dev/null
+++ b/libs/common/src/lib/enums/RewardVariant.ts
@@ -0,0 +1,20 @@
+export enum RewardVariant {
+    Coin = 0,
+    NFT = 1,
+    Custom = 2,
+    Coupon = 3,
+    DiscordRole = 4,
+    Galachain = 5,
+}
+
+export enum QuestVariant {
+    Daily = 0,
+    Invite = 1,
+    Twitter = 2,
+    Discord = 3,
+    YouTube = 4,
+    Custom = 5,
+    Web3 = 6,
+    Gitcoin = 7,
+    Webhook = 8,
+}
diff --git a/libs/common/src/lib/enums/Signup.ts b/libs/common/src/lib/enums/Signup.ts
new file mode 100644
index 000000000..aa565fd55
--- /dev/null
+++ b/libs/common/src/lib/enums/Signup.ts
@@ -0,0 +1,16 @@
+export enum Role {
+    None = 'none',
+    GrowthHacker = 'growth_hacker',
+    Marketer = 'marketer',
+    CommunityManager = 'community_manager',
+    Developer = 'developer',
+    Other = 'other',
+}
+
+export enum Goal {
+    Reward = 'reward',
+    Retain = 'retain',
+    Referral = 'referral',
+    Social = 'social',
+    Mint = 'mint',
+}
diff --git a/libs/common/src/lib/enums/TransactionState.ts b/libs/common/src/lib/enums/TransactionState.ts
new file mode 100644
index 000000000..c67614496
--- /dev/null
+++ b/libs/common/src/lib/enums/TransactionState.ts
@@ -0,0 +1,7 @@
+export enum TransactionState {
+    Queued = 0,
+    Mined = 1,
+    Failed = 2,
+    Sent = 3,
+    Confirmed = 4,
+}
diff --git a/libs/common/src/lib/enums/TransactionType.ts b/libs/common/src/lib/enums/TransactionType.ts
new file mode 100644
index 000000000..fd3f02700
--- /dev/null
+++ b/libs/common/src/lib/enums/TransactionType.ts
@@ -0,0 +1,4 @@
+export enum TransactionType {
+    Default = 0,
+    Relayed = 1,
+}
diff --git a/libs/common/src/lib/enums/Wallet.ts b/libs/common/src/lib/enums/Wallet.ts
new file mode 100644
index 000000000..4e151849c
--- /dev/null
+++ b/libs/common/src/lib/enums/Wallet.ts
@@ -0,0 +1,4 @@
+export enum WalletVariant {
+    Safe = 'safe',
+    WalletConnect = 'walletconnect',
+}
diff --git a/libs/common/src/lib/enums/Webhook.ts b/libs/common/src/lib/enums/Webhook.ts
new file mode 100644
index 000000000..532e3359c
--- /dev/null
+++ b/libs/common/src/lib/enums/Webhook.ts
@@ -0,0 +1,16 @@
+export enum WebhookVariant {
+    Inbound = 0,
+    Outbound = 1,
+}
+
+export enum WebhookStatus {
+    Inactive = 0,
+    Active = 1,
+}
+
+export enum WebhookRequestState {
+    Pending = 0,
+    Sent = 1,
+    Received = 2,
+    Failed = 3,
+}
diff --git a/libs/common/src/lib/enums/index.ts b/libs/common/src/lib/enums/index.ts
new file mode 100644
index 000000000..c7df43502
--- /dev/null
+++ b/libs/common/src/lib/enums/index.ts
@@ -0,0 +1,22 @@
+export * from './AccessTokenKind';
+export * from './AccountPlanType';
+export * from './AccountVariant';
+export * from './ChainId';
+export * from './ERC1155';
+export * from './ERC20Type';
+export * from './ERC721Variant';
+export * from './PlatformVariant';
+export * from './QuestSocialRequirement';
+export * from './GrantVariant';
+export * from './RewardVariant';
+export * from './TransactionState';
+export * from './TransactionType';
+export * from './DailyRewardClaimState';
+export * from './NFTVariant';
+export * from './Signup';
+export * from './GateVariant';
+export * from './Webhook';
+export * from './Event';
+export * from './Job';
+export * from './Collaborator';
+export * from './Wallet';
diff --git a/libs/common/src/lib/index.ts b/libs/common/src/lib/index.ts
new file mode 100644
index 000000000..c80fc7150
--- /dev/null
+++ b/libs/common/src/lib/index.ts
@@ -0,0 +1,8 @@
+export * from './enums';
+export * from './maps';
+export * from './chains';
+export * from './sentry';
+export * from './mixpanel';
+export * from './constants';
+export * from './mail';
+export * from './twitter';
diff --git a/libs/common/src/lib/mail.ts b/libs/common/src/lib/mail.ts
new file mode 100644
index 000000000..c850d4ca0
--- /dev/null
+++ b/libs/common/src/lib/mail.ts
@@ -0,0 +1,29 @@
+import { SES } from '@aws-sdk/client-ses';
+
+const ses = new SES({
+    region: 'eu-west-3',
+});
+
+function sendMail(to: string, subject: string, html: string) {
+    ses.sendEmail(
+        {
+            Destination: { ToAddresses: [to] },
+            Message: {
+                Body: {
+                    Html: {
+                        Charset: 'UTF-8',
+                        Data: html,
+                    },
+                },
+                Subject: {
+                    Charset: 'UTF-8',
+                    Data: subject,
+                },
+            },
+            Source: 'THX Network <noreply@thx.network>',
+        },
+        console.log,
+    );
+}
+
+export { ses, sendMail };
diff --git a/libs/common/src/lib/maps/index.ts b/libs/common/src/lib/maps/index.ts
new file mode 100644
index 000000000..403397e0b
--- /dev/null
+++ b/libs/common/src/lib/maps/index.ts
@@ -0,0 +1,2 @@
+export * from './quest';
+export * from './oauth';
diff --git a/libs/common/src/lib/maps/oauth.ts b/libs/common/src/lib/maps/oauth.ts
new file mode 100644
index 000000000..eb21ca764
--- /dev/null
+++ b/libs/common/src/lib/maps/oauth.ts
@@ -0,0 +1,39 @@
+import { AccountVariant, AccessTokenKind, QuestSocialRequirement } from '../enums';
+
+export const providerIconMap = {
+    [AccessTokenKind.Google]: 'fab fa-youtube',
+    [AccessTokenKind.Twitter]: 'fab fa-twitter',
+    [AccessTokenKind.Discord]: 'fab fa-discord',
+    [AccessTokenKind.Twitch]: 'fab fa-twitch',
+    [AccessTokenKind.Github]: 'fab fa-github',
+};
+
+export const accountVariantProviderMap = {
+    [AccountVariant.SSOGoogle]: AccessTokenKind.Google,
+    [AccountVariant.SSODiscord]: AccessTokenKind.Discord,
+    [AccountVariant.SSOTwitter]: AccessTokenKind.Twitter,
+    [AccountVariant.SSOGithub]: AccessTokenKind.Github,
+    [AccountVariant.SSOTwitch]: AccessTokenKind.Twitch,
+};
+
+export const providerAccountVariantMap = {
+    [AccessTokenKind.Google]: AccountVariant.SSOGoogle,
+    [AccessTokenKind.Discord]: AccountVariant.SSODiscord,
+    [AccessTokenKind.Twitter]: AccountVariant.SSOTwitter,
+    [AccessTokenKind.Github]: AccountVariant.SSOGithub,
+    [AccessTokenKind.Twitch]: AccountVariant.SSOTwitch,
+};
+
+export const interactionComponentMap = {
+    [QuestSocialRequirement.YouTubeSubscribe]: 'BaseDropdownYoutubeChannels',
+    [QuestSocialRequirement.YouTubeLike]: 'BaseDropdownYoutubeVideo',
+    [QuestSocialRequirement.TwitterLike]: 'BaseDropdownTwitterTweets',
+    [QuestSocialRequirement.TwitterRetweet]: 'BaseDropdownTwitterTweets',
+    [QuestSocialRequirement.TwitterLikeRetweet]: 'BaseDropdownTwitterTweets',
+    [QuestSocialRequirement.TwitterFollow]: 'BaseDropdownTwitterUsers',
+    [QuestSocialRequirement.TwitterQuery]: 'BaseDropdownTwitterQuery',
+    [QuestSocialRequirement.DiscordGuildJoined]: 'BaseDropdownDiscordGuilds',
+    [QuestSocialRequirement.DiscordGuildRole]: 'BaseDropdownDiscordRoles',
+    [QuestSocialRequirement.DiscordMessage]: 'BaseDropdownDiscordMessage',
+    [QuestSocialRequirement.DiscordMessageReaction]: 'BaseDropdownDiscordMessageReaction',
+};
diff --git a/libs/common/src/lib/maps/quest.ts b/libs/common/src/lib/maps/quest.ts
new file mode 100644
index 000000000..824b20196
--- /dev/null
+++ b/libs/common/src/lib/maps/quest.ts
@@ -0,0 +1,15 @@
+import { QuestVariant, QuestSocialRequirement } from '../enums';
+
+export const questInteractionVariantMap = {
+    [QuestSocialRequirement.TwitterFollow]: QuestVariant.Twitter,
+    [QuestSocialRequirement.TwitterLike]: QuestVariant.Twitter,
+    [QuestSocialRequirement.TwitterQuery]: QuestVariant.Twitter,
+    [QuestSocialRequirement.TwitterRetweet]: QuestVariant.Twitter,
+    [QuestSocialRequirement.TwitterLikeRetweet]: QuestVariant.Twitter,
+    [QuestSocialRequirement.YouTubeLike]: QuestVariant.YouTube,
+    [QuestSocialRequirement.YouTubeSubscribe]: QuestVariant.YouTube,
+    [QuestSocialRequirement.DiscordGuildJoined]: QuestVariant.Discord,
+    [QuestSocialRequirement.DiscordGuildRole]: QuestVariant.Discord,
+    [QuestSocialRequirement.DiscordMessage]: QuestVariant.Discord,
+    [QuestSocialRequirement.DiscordMessageReaction]: QuestVariant.Discord,
+};
diff --git a/libs/common/src/lib/migrate-mongo.ts b/libs/common/src/lib/migrate-mongo.ts
new file mode 100644
index 000000000..55a257ea6
--- /dev/null
+++ b/libs/common/src/lib/migrate-mongo.ts
@@ -0,0 +1,54 @@
+import program from 'commander';
+import migrateMongo, { config } from 'migrate-mongo';
+
+export const migrateMongoScript = (migrateMongoConfig: config.Config) => {
+    function printMigrated(migrated: string[] = []) {
+        migrated.forEach((migratedItem) => {
+            console.log(`MIGRATED UP: ${migratedItem}`);
+        });
+        console.log('DB UP TO DATE');
+    }
+
+    function handleError(err: Error) {
+        console.error(`ERROR: ${err.message}`, err.stack);
+        process.exit(1);
+    }
+
+    migrateMongo.config.set(migrateMongoConfig);
+
+    program
+        .command('up')
+        .description('run all pending database migrations')
+        .action(() => {
+            migrateMongo.database
+                .connect()
+                .then(({ db, client }) => migrateMongo.up(db, client))
+                .then((migrated) => {
+                    printMigrated(migrated);
+                    process.exit(0);
+                })
+                .catch((err) => {
+                    handleError(err);
+                    printMigrated(err.migrated);
+                });
+        });
+
+    program
+        .command('down')
+        .description('undo the last applied database migration')
+        .action(() => {
+            migrateMongo.database
+                .connect()
+                .then(({ db, client }) => migrateMongo.down(db, client))
+                .then((migrated) => {
+                    migrated.forEach((migratedItem) => {
+                        console.log(`MIGRATED DOWN: ${migratedItem}`);
+                    });
+                    process.exit(0);
+                })
+                .catch((err) => {
+                    handleError(err);
+                });
+        });
+    program.parse(process.argv);
+};
diff --git a/libs/common/src/lib/scss/_alert.scss b/libs/common/src/lib/scss/_alert.scss
new file mode 100644
index 000000000..2b799b2f1
--- /dev/null
+++ b/libs/common/src/lib/scss/_alert.scss
@@ -0,0 +1,9 @@
+.alert-top {
+    margin: 0;
+    text-align: center;
+    border-radius: 0;
+
+    a:hover {
+        text-decoration: none;
+    }
+}
diff --git a/libs/common/src/lib/scss/_buttons.scss b/libs/common/src/lib/scss/_buttons.scss
new file mode 100644
index 000000000..58ddc896d
--- /dev/null
+++ b/libs/common/src/lib/scss/_buttons.scss
@@ -0,0 +1,112 @@
+[class*='btn-outline-'] {
+    border-width: 3px;
+}
+
+.btn {
+    font-family: $font-family-sans-serif;
+    font-weight: $font-weight-normal !important;
+
+    i {
+        font-size: 0.8rem;
+        margin-left: 0.5rem;
+    }
+}
+
+.btn-chat {
+    position: fixed;
+    width: auto;
+    font-size: 1.2rem;
+    bottom: 1rem;
+    right: 1rem;
+}
+
+.btn-nav {
+    display: block;
+    color: #000;
+    font-size: 18px;
+    font-weight: 700;
+    text-transform: uppercase;
+    margin: 0;
+    position: relative;
+
+    &:focus {
+        box-shadow: none;
+    }
+
+    i {
+        margin-left: 0.5rem;
+        font-size: $font-size-sm;
+    }
+}
+
+.btn-sm {
+    font-size: 14px !important;
+}
+
+@media (max-width: 768px) {
+    .btn-nav {
+        margin: auto;
+        color: black !important;
+    }
+    .header-menu--right .btn-primary {
+        margin-top: 1rem;
+    }
+}
+
+.btn-remove {
+    height: 31px;
+    width: 31px;
+    background-color: transparent;
+    color: $gray-400;
+    border: 1px solid $gray-400;
+
+    &:hover,
+    &:focus,
+    &:focus:hover {
+        background-color: transparent;
+        color: $danger;
+        border-color: $danger;
+    }
+
+    i {
+        margin: 0;
+    }
+}
+
+.btn-tab {
+    flex: 1;
+    color: white;
+    background-color: $primary;
+    margin: 0 1rem;
+    background-size: contain;
+
+    img {
+        height: 20px;
+    }
+
+    @media (min-width: map-get($grid-breakpoints, 'md')) {
+        padding: 1.5rem 2rem;
+
+        img {
+            margin-bottom: 0.5rem;
+            height: 30px;
+        }
+    }
+
+    &:not(.router-link-exact-active) {
+        background-image: none !important;
+        background-color: white;
+        color: $primary;
+    }
+
+    &.router-link-exact-active:hover {
+        color: white;
+    }
+
+    &:first-of-type {
+        margin-left: 0;
+    }
+    &:last-of-type {
+        margin-right: 0;
+    }
+}
diff --git a/libs/common/src/lib/scss/_card.scss b/libs/common/src/lib/scss/_card.scss
new file mode 100644
index 000000000..d67942326
--- /dev/null
+++ b/libs/common/src/lib/scss/_card.scss
@@ -0,0 +1,27 @@
+.card-logo {
+    display: flex;
+    border: 0;
+    flex: 0 0 120px;
+    background-color: $light;
+    height: 120px;
+    width: auto;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    border-radius: 10px;
+    margin-right:1rem;
+    margin-bottom:1rem;
+
+    img {
+        margin: auto;
+        display: block;
+    }
+}
+
+.card-logo-lg {
+    max-width: 100%;
+    max-height: 100%;
+    width: 180px;
+    height: 180px;
+    border-radius: 15px;
+}
diff --git a/libs/common/src/lib/scss/_contact.scss b/libs/common/src/lib/scss/_contact.scss
new file mode 100644
index 000000000..d3d95b715
--- /dev/null
+++ b/libs/common/src/lib/scss/_contact.scss
@@ -0,0 +1,27 @@
+.section-contact {
+    background-color: #ffe500;
+    color: #000;
+    padding: 5rem 0;
+    width: 100%;
+    align-self: center;
+
+    a {
+        color: $black;
+    }
+}
+@media (min-width: 768px) {
+    .section-contact {
+        h2 {
+            font-size: 53px;
+            + {
+                p {
+                    font-size: 23px;
+                }
+            }
+        }
+        padding: 10rem 0;
+        .align-center {
+            text-align: center;
+        }
+    }
+}
diff --git a/libs/common/src/lib/scss/_custom-forms.scss b/libs/common/src/lib/scss/_custom-forms.scss
new file mode 100644
index 000000000..bf2f1f146
--- /dev/null
+++ b/libs/common/src/lib/scss/_custom-forms.scss
@@ -0,0 +1,57 @@
+.custom-control {
+    padding: 0;
+    margin-bottom: 1rem;
+}
+.custom-control-label {
+    display: block;
+    border: $border-width solid $border-color;
+    border-radius: $border-radius;
+    padding: 1rem 0rem;
+    padding-left: 4rem;
+    padding-right: 1rem;
+
+    p:last-of-type {
+        margin: 0;
+    }
+}
+.custom-control-label::after,
+.custom-control-label::before {
+    top: 50%;
+    left: 1.75rem;
+    margin-top: -0.5rem;
+}
+
+.custom-control-input:checked + .custom-control-label {
+    border-color: $primary;
+}
+
+.dropdown-select .dropdown-toggle {
+    border: $border-width solid $border-color;
+    border-radius: $border-radius;
+    color: $dark;
+    display: flex;
+    width: 100%;
+    text-align: left;
+    justify-content: space-between;
+    align-items: center;
+
+    &:hover,
+    &:focus,
+    &:focus:hover {
+        text-decoration: none;
+    }
+
+    & > div {
+        flex: 1;
+
+        img {
+            vertical-align: text-top;
+        }
+    }
+}
+
+.dropdown-select .dropdown-menu {
+    width: 100%;
+    max-height: 30vh;
+    overflow-y: auto;
+}
diff --git a/libs/common/src/lib/scss/_custom-switch.scss b/libs/common/src/lib/scss/_custom-switch.scss
new file mode 100644
index 000000000..9ad1b3472
--- /dev/null
+++ b/libs/common/src/lib/scss/_custom-switch.scss
@@ -0,0 +1,83 @@
+@mixin switch($res: 'sm') {
+    $index: 1rem;
+    $mainVal: 1rem;
+
+    @if $res == 'md' {
+        $index: 2rem;
+        $mainVal: 1.5rem;
+    } @else if $res == 'lg' {
+        $index: 3rem;
+        $mainVal: 2rem;
+    } @else if $res == 'xl' {
+        $index: 4rem;
+        $mainVal: 2.5rem;
+    }
+
+    .custom-control-label {
+        padding-left: #{$index};
+        padding-bottom: #{$mainVal};
+    }
+
+    .custom-control-label::before {
+        height: $mainVal;
+        width: calc(#{$index} + 0.75rem);
+        border-radius: $mainVal * 2;
+    }
+
+    .custom-control-label::after {
+        width: calc(#{$mainVal} - 4px);
+        height: calc(#{$mainVal} - 4px);
+        border-radius: calc(#{$index} - (#{$mainVal} / 2));
+    }
+
+    .custom-control-input:checked ~ .custom-control-label::after {
+        transform: translateX(calc(#{$mainVal} - 0.25rem));
+    }
+}
+
+// YOU CAN PUT ALL RESOLUTION HERE
+// sm - DEFAULT, md, lg, xl
+.custom-switch.custom-switch-sm {
+    @include switch();
+}
+
+.custom-switch.custom-switch-md {
+    @include switch('md');
+}
+
+.custom-switch.custom-switch-lg {
+    @include switch('lg');
+}
+
+.custom-switch.custom-switch-xl {
+    @include switch('xl');
+}
+
+.custom-switch .custom-control-input:checked {
+    ~ .custom-control-label::before {
+        background-color: $dark !important;
+        border-color: $dark !important;
+    }
+}
+
+.custom-switch .custom-control-input:focus {
+    ~ .custom-control-label::before {
+        box-shadow: 0 0 0 0.2rem rgb(0 0 0 / 25%);
+    }
+}
+
+.custom-switch-light .custom-control-input:checked {
+    ~ .custom-control-label::before {
+        background-color: $light !important;
+        border-color: $light !important;
+    }
+    ~ .custom-control-label::after {
+        background-color: $darker !important;
+    }
+}
+
+.custom-switch-light .custom-control-input:focus {
+    ~ .custom-control-label::before {
+        box-shadow: 0 0 0 0.2rem rgb(255 255 255 / 25%);
+    }
+}
diff --git a/libs/common/src/lib/scss/_dark-mode.scss b/libs/common/src/lib/scss/_dark-mode.scss
new file mode 100644
index 000000000..23fcf21c5
--- /dev/null
+++ b/libs/common/src/lib/scss/_dark-mode.scss
@@ -0,0 +1,253 @@
+
+
+.dark-mode {
+    background-color: $darkest !important;
+    
+    body {
+        background-color: $darkest !important;
+        color: $light !important;
+    }
+ 
+    // Views
+    .section-home > .container-md > .row:nth-child(1) > .col-md-4:before {
+        background-color: $dark !important;
+    }
+
+    // Sidebar    
+    .sidebar {
+        background-color: $darker;
+        
+        .nav-link-plain .nav-link {
+            color: $light;
+        }
+
+        .nav-link-plain {
+            .router-link-active,
+            .nav-link:not(.has-children):hover {
+                background-color: $primary;
+                
+                .nav-link-icon i,
+                span {
+                    color: $light !important;
+                }
+            }
+        }
+    }
+
+    .sidebar-sibling {
+        border-color: $gray-900;
+    }
+    
+    // Tabs
+    .nav-tabs {
+        border-color: $dark;
+        .nav-link {
+            color: $gray;
+        }
+    }
+    .nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus,
+    .nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link {
+        background-color: $dark;
+        color: $white;
+        border-color: $dark;
+    }
+
+    // Cards
+    .card,
+    .card.bg-white,
+    .card-body.bg-light,
+    .list-group-item {
+        background-color: $darker !important;
+        color: $gray-500 !important;
+        border-color: $dark;
+
+        .bg-dark,
+        .bg-darker {
+            background-color: $darkest !important;
+        }
+    }
+
+    .card-header.bg-light {
+        background-color: $darkest !important;
+        color: $gray;
+
+        .badge-dark {
+            background-color: $dark !important;
+        }
+    }
+
+    // Pagination 
+    .page-item:not(.active) .page-link {
+        background-color: $dark;
+        border-color: $darker;
+        color: white
+    }
+    .page-item.disabled .page-link {
+        background-color: $darker;
+        border-color: $dark;
+    }
+
+    // Dropdown
+    .dropdown-select .dropdown-toggle,
+    .show > .dropdown-toggle,
+    .show > .dropdown-toggle.btn-light,
+    .show > .dropdown-toggle.btn-light:active {
+        background-color: $dark;
+        border-color: $dark;
+        color: $gray;
+    }
+    .dropdown-menu  {
+        background-color: $darker;
+        color: $gray;
+        border-color: $dark;
+    }
+    .dropdown-divider {
+        border-color: $dark;
+    }
+    .dropdown-item {
+        color: $gray;
+
+        &:hover, 
+        &:focus {
+            background-color: $dark;
+        }
+    }
+
+    // Table
+    .table {
+        color: $gray-400;
+    
+        .custom-checkbox .custom-control-label {
+            background-color: $darker;
+        }
+    }
+
+    .table th, .table td {
+        border-color: $dark !important;
+    }
+    
+    .table thead tr,
+    .table thead tr td,
+    .table-hover thead tr {
+        background-color: $dark !important;
+
+        .custom-control-label {
+            background-color: $dark !important;
+        }
+    }
+    .table-hover tbody tr {
+        background-color: $darker !important;
+    }
+
+    .table-hover tbody tr:hover {
+        background-color: $dark !important;
+        color: $gray-400;
+        
+        .custom-control-label {
+            background-color: $dark !important;
+        }
+    }
+
+    tr.bg-light.text-gray {
+        color: $gray-800 !important;
+    }
+
+    // Nav
+    .nav-pills .nav-link.active, .nav-pills .show > .nav-link {
+        background-color: $primary;
+        color: $white;
+    }
+
+    // Badge 
+    .badge-light {
+        background-color: $dark;
+        color: $gray;
+    }
+
+    // Forms
+    .b-form-btn-label-control.form-control > .form-control,
+    .b-form-btn-label-control.form-control[aria-disabled=true], .b-form-btn-label-control.form-control[aria-readonly=true] {
+        border-color: $darker;
+        background-color: $darkest;
+    }
+    .col-form-label {
+        color: $gray;
+    }
+
+    .custom-checkbox {
+        .custom-control-label {
+            background-color: $darkest;
+        }
+        
+        .custom-control-label::before {
+            background-color: $darkest;
+            border-color: $dark;
+        }
+        
+        .custom-control-input:checked ~ .custom-control-label::before {
+            background-color: $primary !important;
+        }
+    }
+    
+    .custom-select,
+    .custom-file-label,
+    .custom-control-label {
+        border-color: $darker;
+        background-color: $darkest;
+    }
+    
+    .custom-file-label,
+    .custom-file-label::after {
+        border-color: $darker;
+        background-color: $darkest;
+        color: $gray;
+    }
+
+    .input-group-append button,
+    .input-group-text {
+        border-color: $darkest;
+        background-color: $primary;
+        color: $white;
+    }
+
+    .form-control {
+        border-color: $darkest;
+        background-color: $darkest;
+        color: white;
+    }
+
+    // Buttons
+    .btn .b-icon.bi, .nav-link .b-icon.bi, .dropdown-toggle .b-icon.bi, .dropdown-item .b-icon.bi, .input-group-text .b-icon.bi {
+        color: $gray;
+    }
+    .btn-light,
+    .btn-light:focus:active {
+        border-color: $dark;
+        background-color: $dark;
+        color: $gray-500;
+    }
+    
+    // Modals
+    .modal {
+        .modal-header {
+            color: white;
+            border-color: $dark;
+        }
+        .modal-content,
+        .modal-body {
+            background-color: $darker !important;
+            border-color: $dark;
+        }
+        .modal-footer {
+            border-color: $dark;
+        }
+
+        p {
+            color: white;
+        }
+
+        .close {
+            color: white;
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/common/src/lib/scss/_dropdown.scss b/libs/common/src/lib/scss/_dropdown.scss
new file mode 100644
index 000000000..9a7504638
--- /dev/null
+++ b/libs/common/src/lib/scss/_dropdown.scss
@@ -0,0 +1,24 @@
+.b-dropdown-text {
+    font-weight: bold;
+}
+
+.dropdown-select {
+    display: flex;
+}
+
+.dropdown-select .dropdown-item {
+    white-space: normal;
+    border-bottom: 1px solid $light;
+
+    &:last-child {
+        border-bottom: 0;
+    }
+}
+.dropdown-item:hover, .dropdown-item:focus {
+    background-color: $light;
+    color: $dark;
+
+    a:hover {
+        color: $primary !important;
+    }
+}
\ No newline at end of file
diff --git a/libs/common/src/lib/scss/_faq.scss b/libs/common/src/lib/scss/_faq.scss
new file mode 100644
index 000000000..e3b04ab5e
--- /dev/null
+++ b/libs/common/src/lib/scss/_faq.scss
@@ -0,0 +1,88 @@
+.section-faq {
+    h5 {
+        margin: 0;
+        border-bottom: 1px solid #efefef;
+        a {
+            display: block;
+            padding: 1rem 0;
+            color: #000;
+            font-weight: 700;
+            font-size: 1rem;
+            cursor: pointer;
+        }
+        + {
+            div {
+                padding: 0;
+                p {
+                    margin: 1rem 0 0;
+                }
+            }
+        }
+    }
+}
+
+.faq-header {
+    background-size: cover;
+    z-index: 0;
+    background-image: url(./assets/images/faq.jpg);
+    padding: 10rem 0;
+    color: #fff;
+    position: relative;
+    text-align: center;
+
+    &:after {
+        content: '';
+        width: 100%;
+        height: 100%;
+        background-color: rgba(0, 0, 0, 0.6);
+        display: block;
+        position: absolute;
+        top: 0;
+        z-index: -1;
+    }
+    h2 {
+        text-align: center;
+        font-size: 23px;
+        font-weight: 700;
+        margin: 0;
+        color: #ffe500;
+        + {
+            p {
+                font-size: 18px;
+                font-weight: 400;
+            }
+        }
+    }
+}
+.faq-content {
+    border-top: 15px solid #ffe500;
+    overflow: hidden;
+    padding: 4rem 0;
+    position: relative;
+    &:before {
+        content: '';
+        display: block;
+        -webkit-transform: rotate(-45deg);
+        transform: rotate(-45deg);
+        left: 50%;
+        width: 40px;
+        height: 40px;
+        background-color: #ffe500;
+        position: absolute;
+        margin-left: -20px;
+        top: -20px;
+    }
+}
+
+@media (min-width: 768px) {
+    .faq-header {
+        h2 {
+            font-size: 53px;
+            + {
+                p {
+                    font-size: 23px;
+                }
+            }
+        }
+    }
+}
diff --git a/libs/common/src/lib/scss/_forms.scss b/libs/common/src/lib/scss/_forms.scss
new file mode 100644
index 000000000..944c2d21d
--- /dev/null
+++ b/libs/common/src/lib/scss/_forms.scss
@@ -0,0 +1,13 @@
+a i.fa-question-circle {
+    color: $gray-400;
+}
+select[multiple='multiple'] {
+    padding: 0 !important;
+}
+select option {
+    padding: 0.5rem;
+}
+select option:checked {
+    background-color: var(--primary);
+    color: white;
+}
diff --git a/libs/common/src/lib/scss/_glossary.scss b/libs/common/src/lib/scss/_glossary.scss
new file mode 100644
index 000000000..9aba3503e
--- /dev/null
+++ b/libs/common/src/lib/scss/_glossary.scss
@@ -0,0 +1,100 @@
+.section-glossary {
+    h2 {
+        margin: 0;
+        font-size: 23px;
+        font-weight: 700;
+        text-align: center;
+        + {
+            p {
+                font-size: 18px;
+                font-weight: 400;
+            }
+        }
+    }
+    background-color: #ffe500;
+    color: #000;
+    background: #fff;
+    padding: 4rem 0;
+    .btn {
+        margin-right: 0;
+    }
+}
+.glossary-content {
+    border-top: 15px solid #ffe500;
+    overflow: hidden;
+    padding: 4rem 0 0;
+    position: relative;
+    &:before {
+        content: '';
+        display: block;
+        -webkit-transform: rotate(-45deg);
+        transform: rotate(-45deg);
+        left: 50%;
+        width: 40px;
+        height: 40px;
+        background-color: #ffe500;
+        position: absolute;
+        margin-left: -20px;
+        top: -20px;
+    }
+}
+.glossary-header {
+    background-size: cover;
+    z-index: 0;
+    background-image: url(./assets/images/glossary.jpg);
+    padding: 10rem 0;
+    color: #fff;
+    position: relative;
+    &:after {
+        content: '';
+        width: 100%;
+        height: 100%;
+        background-color: rgba(0, 0, 0, 0.6);
+        display: block;
+        position: absolute;
+        top: 0;
+        z-index: 0;
+    }
+    .container {
+        z-index: 1;
+    }
+    h2 {
+        text-align: center;
+        font-size: 23px;
+        font-weight: 700;
+        margin: 0;
+        color: #ffe500;
+        + {
+            p {
+                font-size: 18px;
+                font-weight: 400;
+            }
+        }
+    }
+}
+
+@media (min-width: 768px) {
+    .glossary-header {
+        h2 {
+            font-size: 53px;
+            + {
+                p {
+                    font-size: 23px;
+                }
+            }
+        }
+    }
+}
+
+@media (min-width: 768px) {
+    .section-glossary {
+        h2 {
+            font-size: 53px;
+            + {
+                p {
+                    font-size: 23px;
+                }
+            }
+        }
+    }
+}
diff --git a/libs/common/src/lib/scss/_hooper.scss b/libs/common/src/lib/scss/_hooper.scss
new file mode 100644
index 000000000..ced94c0a0
--- /dev/null
+++ b/libs/common/src/lib/scss/_hooper.scss
@@ -0,0 +1,119 @@
+section .hooper-pagination {
+    bottom: -4rem;
+    width: 100%;
+
+    .hooper-indicators {
+        width: 100%;
+
+        &:before {
+            content: '';
+            position: absolute;
+            display: block;
+            width: auth;
+            left: 1rem;
+            right: 1rem;
+            background-color: $gray;
+            height: 1px;
+            bottom: 24px;
+            z-index: -1;
+        }
+
+        li {
+            flex: 1;
+            text-align: center;
+        }
+    }
+
+    .hooper-indicator {
+        background-color: transparent;
+        width: 100%;
+        height: 40px;
+
+        &:after {
+            content: '';
+            display: block;
+            width: 25%;
+            height: 6px;
+            border-radius: 6px;
+        }
+
+        &.is-active:after {
+            background-color: $darker;
+        }
+    }
+}
+section .hooper-team {
+    height: auto;
+}
+
+section .hooper-use-cases {
+    height: auto;
+}
+
+.card-metrics-dark {
+    margin-top: 100%;
+    margin-bottom: 1rem;
+
+    @media (min-width: map-get($grid-breakpoints, 'lg')) {
+        margin: 0;
+        position: absolute;
+        top: 25%;
+        right: 0;
+        margin-right: -200px;
+    }
+}
+
+section .hooper-metrics {
+    height: 100%;
+
+    .card {
+        margin-right: 1rem;
+    }
+
+    .hooper-track {
+        height: auto;
+    }
+
+    @media (min-width: map-get($grid-breakpoints, 'lg')) {
+        .hooper-list {
+            position: relative;
+            overflow: visible;
+        }
+
+        .hooper-track {
+            height: 100%;
+        }
+
+        .hooper-slide {
+            position: absolute;
+            height: auto;
+
+            .card {
+                width: 400px !important;
+                margin: 0;
+            }
+
+            &:nth-child(1) {
+                bottom: 40%;
+                right: 0;
+                margin-right: 0px;
+            }
+
+            &:nth-child(2) {
+                left: 0;
+                bottom: 30%;
+                margin-left: -350px;
+
+                .card {
+                    width: 450px !important;
+                }
+            }
+
+            &:nth-child(3) {
+                left: 0;
+                bottom: 10%;
+                margin-left: -150px;
+            }
+        }
+    }
+}
diff --git a/libs/common/src/lib/scss/_identicon.scss b/libs/common/src/lib/scss/_identicon.scss
new file mode 100644
index 000000000..e3bbe9b1a
--- /dev/null
+++ b/libs/common/src/lib/scss/_identicon.scss
@@ -0,0 +1,15 @@
+.identicon {
+    position: fixed;
+    z-index: 1;
+    width: 50px;
+    height: 50px;
+    border-radius: 0.25rem;
+    float: right;
+    display: flex;
+    align-items: center;
+    padding: 0;
+    justify-content: center;
+    right: 10px;
+    top: 10px;
+    border: 0;
+}
diff --git a/libs/common/src/lib/scss/_jumbotron.scss b/libs/common/src/lib/scss/_jumbotron.scss
new file mode 100644
index 000000000..78a274504
--- /dev/null
+++ b/libs/common/src/lib/scss/_jumbotron.scss
@@ -0,0 +1,76 @@
+.jumbotron {
+    display: flex;
+    flex-direction: column;
+    padding: 0 !important;
+    background-size: cover;
+
+    &.large {
+        min-height: 85vh;
+        align-items: center;    
+    }
+
+    &:not(.jumbotron-header) .container {
+        margin: auto;
+        padding-top: 8rem;
+        padding-bottom: 5rem;
+
+        @media (min-width: map-get($grid-breakpoints, 'lg')) {
+            padding-top: 5rem;
+            padding-bottom: 0;
+        }
+    }
+
+    .brand-intro {
+        display: flex;
+        align-items: center;
+    }
+
+    .brand-text {
+        font-size: 3rem !important;
+
+        @media (min-width: map-get($grid-breakpoints, 'lg')) {
+            font-size: 3.5rem !important;
+        }
+    }
+
+    .brand-demo {
+        img {
+            padding: 1rem;
+            border-radius: 1rem !important;
+            width: 100%;
+            height: auto;
+            pointer-events: none;
+            user-select: none;
+
+            @media (min-width: map-get($grid-breakpoints, 'lg')) {
+                margin-top: 100px;
+                margin-bottom: -130px;
+                height: 850px;
+                width: auto;
+            }
+        }
+    }
+
+    .calendly-widget {
+        width: 100%;
+        min-width: 320px;
+        height: 620px;
+
+        @media (min-width: map-get($grid-breakpoints, 'lg')) {
+            height: 700px;
+        }
+    }
+}
+
+.jumbotron-header {
+    min-height: 250px;
+    max-height: none;
+    display: flex;
+    align-items: center;
+    flex-direction: row;
+    height: auto;
+    background-size: cover;
+    background-position: center bottom;
+    border-radius: 0;
+    color: white;
+}
diff --git a/libs/common/src/lib/scss/_modals.scss b/libs/common/src/lib/scss/_modals.scss
new file mode 100644
index 000000000..2a2e94a70
--- /dev/null
+++ b/libs/common/src/lib/scss/_modals.scss
@@ -0,0 +1,24 @@
+.modal-header {
+    border: 0;
+
+    h5 {
+        font-size: 1.5rem;
+        font-weight: bold;
+    }
+
+    @media (min-width: map-get($grid-breakpoints, 'md')) {
+        padding: 3rem 3rem 0 3rem;
+    }
+}
+.modal-body {
+    @media (min-width: map-get($grid-breakpoints, 'md')) {
+        padding: 2rem 3rem;
+    }
+}
+
+.modal-footer {
+    border: 0;
+    @media (min-width: map-get($grid-breakpoints, 'md')) {
+        padding: 0rem 3rem 3rem 3rem;
+    }
+}
diff --git a/libs/common/src/lib/scss/_navbar.scss b/libs/common/src/lib/scss/_navbar.scss
new file mode 100644
index 000000000..7881bc699
--- /dev/null
+++ b/libs/common/src/lib/scss/_navbar.scss
@@ -0,0 +1,284 @@
+.nav-pills .nav-link.active,
+.nav-pills .show > .nav-link {
+    color: var(--primary);
+    background-color: #e9ecef;
+}
+
+.nav-pills .nav-link {
+    padding: .5rem 1rem;
+}
+
+.navbar {
+    z-index: 1;
+    width: 100%;
+
+    .header-brand {
+        img {
+            display: block;
+            width: 40px;
+            height: auto;
+            margin: auto;
+
+            @media (min-width: 992px) {
+                width: 55px;
+            } 
+        }
+    }
+
+    #nav-collapse {
+        .header-brand {
+            margin-right: 3rem;
+        }
+
+        > .navbar-nav {
+            flex: 1 !important;
+        }
+    }
+
+    .navbar-nav-right {
+        flex: 0 auto;
+        display: flex;
+        justify-content: flex-end;
+        align-items: center;
+    }
+
+    .navbar-toggler {
+        border: 0;
+        transition: 0.2s ease color;
+
+        &:hover {
+            color: black;
+        }
+    }
+}
+
+div:not(.bg-white) > .navbar-text-white.navbar-light .fa-bars {
+    color: $white;
+}
+
+div:not(.bg-white) > .navbar-text-white.navbar-light .navbar-nav .nav-link {
+    color: $light;
+
+    &:focus {
+        color: $secondary;
+    }
+
+    &:after {
+        position: absolute;
+        content: '';
+    }
+
+    &:hover {
+        color: $light;
+    }
+
+    &.router-link-active {
+        color: $secondary;
+    }
+
+    &:hover:after,
+    &.router-link-active:after {
+        background-color: $secondary;
+    }
+}
+
+.navbar.fixed {
+    position: fixed;
+    z-index: 1;
+    background-color: $secondary;
+
+    #nav-collapse > .navbar-nav {
+        flex: 1 !important;
+    }
+}
+
+.navbar-light .navbar-nav .nav-link {
+    display: block;
+    position: relative;
+    font-weight: 400;
+    opacity: 1 !important;
+    margin: 0 1rem;
+    padding-right: 0;
+    padding-left: 0;
+    padding-bottom: 1rem;
+    color: $dark;
+
+    &:focus {
+        box-shadow: none;
+        color: $primary;
+    }
+
+    &:after {
+        position: absolute;
+        content: '';
+    }
+
+    &:hover {
+        color: $dark;
+    }
+
+    &.router-link-active {
+        color: $primary;
+    }
+
+    &:hover:after,
+    &.router-link-active:after {
+        width: 100%;
+        opacity: 1;
+    }
+}
+
+.navbar-light .navbar-nav .nav-link:after {
+    display: block;
+    height: 2px;
+    width: 0%;
+    bottom: 0;
+    opacity: 0;
+    color: $primary;
+    background-color: $primary;
+    -webkit-transition: 0.2s width, 0.3s opacity;
+    transition: 0.2s width, 0.3s opacity;
+}
+
+.nav-tabs .nav-link {
+    padding: 0.5rem 1rem;
+}
+
+.nav-link-plain a::after {
+    content: unset !important;
+    display: none !important;
+}
+
+@media (max-width: map-get($grid-breakpoints, 'lg')) {
+    
+    #nav-collapse {
+        margin: .5rem -1rem 0;
+        background: $white;
+        color: $dark;
+        padding: 0;
+        overflow: auto;
+        
+        .nav-link {
+            color: $black;
+            font-weight: bold;
+            padding: 1rem;
+            padding-left: 2rem;
+
+            &:hover,
+            &.router-link-active {
+                background-color: $primary;
+                height: 100%;
+                z-index: 0;
+                border-radius: 25px;
+                color: $white;
+            }
+
+            &:hover:after,
+            &.router-link-active:after {
+                display: none;
+            }
+        }
+
+        > .navbar-nav {
+            flex: 0 auto;
+        }
+
+
+        .navbar-nav-right {
+            flex: 0;
+            border-top: 1px solid $light;
+            padding: 1rem 2rem;
+            flex-wrap: wrap;
+
+            a {
+                flex: 0 100%;
+                margin: 0.5rem !important;
+                
+                &.btn-outline-light {
+                    color: $black;
+                    border-color: $black;
+                }
+            }
+
+            .navbar-nav {
+                flex-direction: row;
+                flex: 1;
+                justify-content: space-around;
+            }
+        }
+
+        .header-brand {
+            display: none;
+        }
+    }
+}
+
+.navbar.bg-light.navbar-white {
+    box-shadow: 0 0 50px rgba(0, 0, 0, 0.15);
+    background-color: white;
+    padding: 5px 1rem;
+
+    .nav-link {
+        border-radius: 50%;
+        height: 60px;
+        width: 60px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        margin: auto;
+
+        i {
+            font-size: 1.2rem;
+        }
+
+        &.router-link-exact-active {
+            background-color: $gray-200;
+        }
+    }
+
+    .nav-link.disabled {
+        opacity: 0.5;
+        cursor: not-allowed;
+    }
+
+    .nav-item:nth-child(2) .nav-link {
+        position: absolute;
+        background-color: $secondary;
+        width: 80px;
+        height: 80px;
+        left: 50%;
+        margin-left: -40px;
+        margin-top: -10px;
+        box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
+
+        i {
+            font-size: 1.5rem;
+        }
+
+        &:active {
+            background: darken($secondary, 5%);
+        }
+
+        &.router-link-exact-active {
+            background-color: $secondary;
+            box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
+        }
+    }
+}
+
+.navbar-text-white {
+    .navbar-nav .nav-link {
+        color: #fff;
+        font-weight: 400;
+        &:hover {
+            color: $secondary;
+        }
+    }
+}
+
+.fixed-top.bg-white.shadow .navbar-text-white {
+    .navbar-nav .nav-link {
+        color: $dark;
+        font-weight: 700;
+    }
+}
diff --git a/libs/common/src/lib/scss/_number.scss b/libs/common/src/lib/scss/_number.scss
new file mode 100644
index 000000000..d31318a88
--- /dev/null
+++ b/libs/common/src/lib/scss/_number.scss
@@ -0,0 +1,11 @@
+.number {
+    color: #ffe500;
+    display: block;
+    font-size: 3rem;
+    font-weight: 700;
+    small {
+        font-weight: 400;
+        font-size: 1rem;
+        display: block;
+    }
+}
diff --git a/libs/common/src/lib/scss/_popover.scss b/libs/common/src/lib/scss/_popover.scss
new file mode 100644
index 000000000..1c7a075aa
--- /dev/null
+++ b/libs/common/src/lib/scss/_popover.scss
@@ -0,0 +1,6 @@
+.popover {
+    min-width: 230px;
+}
+.popover-header {
+    background-color: $primary;
+}
diff --git a/libs/common/src/lib/scss/_root.scss b/libs/common/src/lib/scss/_root.scss
new file mode 100644
index 000000000..d63658c85
--- /dev/null
+++ b/libs/common/src/lib/scss/_root.scss
@@ -0,0 +1,246 @@
+html,
+body {
+    min-height: 100%;
+    font-size: 16px;
+}
+
+#app {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+}
+
+.pt-lg-10 {
+    @media (min-width: map-get($grid-breakpoints, 'lg')) {
+        padding-top: 10rem !important;
+    }
+}
+
+.pb-lg-10 {
+    @media (min-width: map-get($grid-breakpoints, 'lg')) {
+        padding-bottom: 10rem !important;
+    }
+}
+
+.container-pricing-max-height {
+    max-height: none;
+    @media (min-width: map-get($grid-breakpoints, 'md')) {
+        max-height: 750px;
+    }
+    @media (min-width: map-get($grid-breakpoints, 'xl')) {
+        max-height: 850px;
+ 
+    }
+}
+
+.table-hover tbody tr {
+    transition: .2s ease background-color;
+    
+    &:hover {
+        background-color: $light;
+    }
+}
+
+h1,
+.h1 {
+    font-family: $font-family-sans-serif;
+    font-weight: 800 !important;
+    text-transform: uppercase;
+    line-height: 1;
+    font-size: 2rem !important;
+
+    @media (min-width: map-get($grid-breakpoints, 'lg')) {
+        font-size: 2.8rem !important;
+    }
+}
+
+h3,
+.h3 {
+    color: $white;
+    font-weight: $font-weight-bold;
+    font-size: 1.5rem;
+}
+
+.h4 {
+    color: $primary;
+    text-transform: uppercase;
+    font-weight: bold;
+    font-size: 1rem;
+    border-left: 3px solid $primary;
+    padding-left: 1rem;
+    margin-bottom: 1rem;
+}
+
+.h5 {
+    display: inline-block;
+    font-size: 1.1rem;
+    padding: 0.65rem 1rem;
+    border-radius: 5px;
+    background-color: $secondary;
+
+    & + .lead {
+        font-size: 2.2rem;
+        font-weight: $font-weight-bold;
+        margin-bottom: 1rem;
+        line-height: 1.3;
+    }
+}
+
+.font-size-l {
+    font-size: 2rem !important;
+    @media (min-width: map-get($grid-breakpoints, 'lg')) {
+        font-size: 2rem !important;
+    }
+    @media (min-width: map-get($grid-breakpoints, 'xl')) {
+        font-size: 2.5rem !important;
+    }
+}
+
+.font-size-xl {
+    font-size: 4rem !important;
+}
+
+.line-height-2 {
+    line-height: 2;
+}
+
+.center-center {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.icon {
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    width: 50px;
+    height: 50px;
+    background: $dark;
+
+    i {
+        font-size: 1.2rem;
+    }
+
+    @media (min-width: map-get($grid-breakpoints, 'lg')) {
+        width: 80px;
+        height: 80px;
+
+        i {
+            font-size: 2rem;
+        }
+    }
+}
+
+.bg-before-dark {
+    position: relative;
+
+    &:before {
+        content: '';
+        background-color: $darker;
+        position: absolute;
+        width: 100%;
+        height: 325px;
+        top: -1px;
+        display: block;
+    }
+}
+
+.bg-before-dark-contact {
+    position: relative;
+
+    &:before {
+        background-color: $darker;
+        position: absolute;
+        width: 100%;
+        left: 0;
+        bottom: 0;
+        height: 100px;
+        display: block;
+
+        @media (min-width: map-get($grid-breakpoints, 'lg')) {
+            content: '';
+        }
+    }
+}
+.btn-icon {
+    width: 50px !important;
+    height: 50px !important;
+    align-items: center;
+    justify-content: center;
+    display: inline-flex;
+    margin: 0 1rem;
+
+    i {
+        margin: 0 !important;
+        font-size: 1.3rem !important;
+    }
+}
+
+.list-disc {
+    padding: 0;
+
+    li {
+        margin-bottom: 0.5rem;
+
+        a {
+            color: $white;
+
+            &:before {
+                content: '';
+                display: inline-block;
+                width: 3px;
+                height: 3px;
+                background-color: $secondary;
+                margin-right: 0.5rem;
+                margin-bottom: 0.2rem;
+            }
+        }
+    }
+}
+
+.card-about-us {
+    .card-header {
+        height: 460px;
+        background-size: cover;
+    }
+}
+
+.card-contact {
+    .card-header {
+        height: 200px;
+        border-top-right-radius: 10px;
+
+        @media (min-width: map-get($grid-breakpoints, 'lg')) {
+            height: 400px;
+        }
+    }
+}
+
+.form-control-underlined,
+.form-control-underlined:focus {
+    background: transparent;
+    box-shadow: none !important;
+    border: 0;
+    border-radius: 0;
+    border-bottom: 1px solid $gray;
+    color: $white;
+    padding-left: 0;
+    padding-right: 0;
+}
+.form-control-underlined:focus {
+    border-color: $secondary;
+}
+
+textarea.form-control-underlined {
+    resize: none;
+    height: 150px;
+}
+
+.cursor-pointer {
+    cursor: pointer;
+
+    &:hover {
+        cursor: pointer;
+    }
+}
diff --git a/libs/common/src/lib/scss/_sidebar.scss b/libs/common/src/lib/scss/_sidebar.scss
new file mode 100644
index 000000000..b2678adb7
--- /dev/null
+++ b/libs/common/src/lib/scss/_sidebar.scss
@@ -0,0 +1,127 @@
+html,
+body {
+    height: 100%;
+}
+
+@media (min-width: map-get($grid-breakpoints, 'md')) {
+    .sidebar-sibling {
+        margin-left: 260px;
+        border-left: 1px solid $gray-200;
+    }
+}
+
+.b-sidebar {
+    width: 260px;
+}
+
+.b-sidebar-outer ~ .sidebar-sibling {
+    height: 100%;
+}
+
+.sidebar {
+    display: flex;
+    height: 100%;
+    padding: 0;
+    background-color: white;
+    flex-wrap: wrap;
+    flex-direction: column;
+
+    .navbar-nav,
+    .nav-item {
+        width: 100%;
+    }
+
+    .navbar-nav {
+        flex-direction: column;
+    }
+
+    .header-brand img {
+        width: 65px;
+    }
+
+     .navbar-nav .nav-link {
+        padding: 0.75rem 1rem 0.75rem 1rem;
+        font-weight: 500;
+        color: $dark;
+    } 
+
+    .navbar-nav .nav-link i {
+        opacity: 0.75;
+    }
+
+    .navbar-nav .nav-link.router-link-exact-active,
+    .navbar-nav .nav-link.router-link-exact-active i {
+        color: $secondary;
+        opacity: 1;
+    }
+
+    .nav-link-plain {
+        .nav-link {
+            padding: 0rem;
+            margin: 0 0.5rem 1px;
+            border-radius: 5px;
+            display: block;
+            transition: 0.2s background-color ease;
+            
+            i {
+                color: $gray-500;
+            }
+
+            &:not(.has-children):hover {
+                background-color: $gray-200;
+            }
+        }
+        
+        .router-link-active {
+            background-color: $gray-200;
+            
+            span {
+                color: $primary;
+            }
+
+            i {
+                color: $primary !important;
+            }
+        }
+        
+        .nav-link-wrapper {
+            padding: 0.5rem .25rem;
+            display: flex;
+            flex-flow: column;
+
+            &.has-children {
+                padding: 0rem .25rem;
+            }
+        }
+    
+        .nav-link-icon {
+            width: 40px;
+            flex: 0 0 40px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            
+            ~ div {
+                flex-grow: 1;
+            }
+        }
+    
+        .nav-link-plain {
+            .nav-link {
+                padding: 0.7rem;
+            }
+        }
+    }
+}
+
+.navbar-expand-lg .navbar-collapse {
+    display: flex !important;
+    flex-direction: column;
+    width: 100%;
+}
+
+// @media (min-width: map-get($grid-breakpoints, 'md')) {
+//     .navbar-expand-lg .navbar-collapse {
+//         display: block !important;
+//     }
+// }
diff --git a/libs/common/src/lib/scss/_tab.scss b/libs/common/src/lib/scss/_tab.scss
new file mode 100644
index 000000000..644815ccb
--- /dev/null
+++ b/libs/common/src/lib/scss/_tab.scss
@@ -0,0 +1,12 @@
+.tab-content {
+    > .tab-pane {
+        display: none;
+    }
+    > .active {
+        display: block;
+    }
+    padding: 2rem 0;
+}
+.blog-header {
+    padding: 4rem 0;
+}
diff --git a/libs/common/src/lib/scss/_team.scss b/libs/common/src/lib/scss/_team.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/libs/common/src/lib/scss/_token-distribution.scss b/libs/common/src/lib/scss/_token-distribution.scss
new file mode 100644
index 000000000..d6803949c
--- /dev/null
+++ b/libs/common/src/lib/scss/_token-distribution.scss
@@ -0,0 +1,25 @@
+.table-token-allocation {
+    min-width: 1040px;
+    width: 100%;
+
+    tbody tr {
+        background-color: white;
+        transition: ease-in-out background-color 0.2s;
+
+        &:hover {
+            background-color: $yellow !important;
+        }
+
+        &:last-of-type {
+            td:first-child {
+                border-top-left-radius: 0.5rem;
+                border-bottom-left-radius: 0.5rem;
+            }
+
+            td:last-child {
+                border-top-right-radius: 0.5rem;
+                border-bottom-right-radius: 0.5rem;
+            }
+        }
+    }
+}
diff --git a/libs/common/src/lib/scss/_utilities.scss b/libs/common/src/lib/scss/_utilities.scss
new file mode 100644
index 000000000..b666537be
--- /dev/null
+++ b/libs/common/src/lib/scss/_utilities.scss
@@ -0,0 +1,39 @@
+.pt-10 {
+    padding-top: 5rem;
+}
+.font-size-10 {
+    font-size: 5rem !important;
+
+    @media (min-width: map-get($grid-breakpoints, 'xl')) {
+        font-size: 9rem !important;
+    }
+}
+.mt-lg-10 {
+    @media (min-width: map-get($grid-breakpoints, 'xl')) {
+        margin-top: 7rem !important;
+    }
+}
+.ml-lg-10 {
+    @media (min-width: map-get($grid-breakpoints, 'xl')) {
+        margin-left: 8rem !important;
+    }
+}
+.mt-n7 {
+    margin-top: -7rem;
+}
+.bg-gray-1000 {
+    background-color: $gray-1000;
+}
+.text-truncate {
+    width: 150px;
+    display: inline-block;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+.text-overflow-ellipsis {
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+.cursor-not-allowed {
+    cursor: not-allowed;
+}
diff --git a/libs/common/src/lib/scss/_variables.scss b/libs/common/src/lib/scss/_variables.scss
new file mode 100644
index 000000000..7ebca55c6
--- /dev/null
+++ b/libs/common/src/lib/scss/_variables.scss
@@ -0,0 +1,1125 @@
+// Variables
+//
+// Variables should follow the `$component-state-property-size` formula for
+// consistent naming. Ex: $nav-link-disabled-color and $modal-content-box-shadow-xs.
+
+// Color system
+
+$white: #fff !default;
+$gray-100: #f8f9fa !default;
+$gray-200: #e9ecef !default;
+$gray-300: #dee2e6 !default;
+$gray-400: #ced4da !default;
+$gray-500: #adb5bd !default;
+$gray-600: #6c757d !default;
+$gray-700: #495057 !default;
+$gray-800: #343a40 !default;
+$gray-900: #212529 !default;
+$gray-1000: #16131b !default;
+$black: #000 !default;
+
+$grays: () !default;
+$grays: map-merge(
+    (
+        '100': $gray-100,
+        '200': $gray-200,
+        '300': $gray-300,
+        '400': $gray-400,
+        '500': $gray-500,
+        '600': $gray-600,
+        '700': $gray-700,
+        '800': $gray-800,
+        '900': $gray-900,
+        '1000': $gray-1000,
+    ),
+    $grays
+);
+
+$blue: #007bff !default;
+$indigo: #6610f2 !default;
+$purple: #5942c1 !default;
+$pink: #e83e8c !default;
+$red: #dc3545 !default;
+$orange: #fd7e14 !default;
+$yellow: #ffe500 !default;
+$green: #28a745 !default;
+$teal: #20c997 !default;
+$cyan: #17a2b8 !default;
+
+$colors: () !default;
+$colors: map-merge(
+    (
+        'blue': $blue,
+        'indigo': $indigo,
+        'purple': $purple,
+        'pink': $pink,
+        'red': $red,
+        'orange': $orange,
+        'yellow': $yellow,
+        'green': $green,
+        'teal': $teal,
+        'cyan': $cyan,
+        'white': $white,
+        'gray': $gray-600,
+        'gray-dark': $gray-800,
+    ),
+    $colors
+);
+
+$primary: $purple !default;
+$secondary: $yellow !default;
+$success: $green !default;
+$info: $cyan !default;
+$warning: $yellow !default;
+$danger: $red !default;
+$light: $gray-100 !default;
+// $dark: $gray-800 !default;
+$gray: #9c9e9f !default;
+// $dark: #343a40 !default;
+// $darker: #212529 !default;
+
+$dark: #272934 !default;
+$darker: #1f2129 !default;
+$darkest: #17181f !default;
+
+$theme-colors: () !default;
+$theme-colors: map-merge(
+    (
+        'primary': $primary,
+        'secondary': $secondary,
+        'success': $success,
+        'info': $info,
+        'warning': $warning,
+        'danger': $danger,
+        'light': $light,
+        'gray': $gray,
+        'darker': $darker,
+    ),
+    $theme-colors
+);
+
+// Set a specific jump point for requesting color jumps
+$theme-color-interval: 8% !default;
+
+// The yiq lightness value that determines when the lightness of color changes from "dark" to "light". Acceptable values are between 0 and 255.
+$yiq-contrasted-threshold: 150 !default;
+
+// Customize the light and dark text colors for use in our YIQ color contrast function.
+$yiq-text-dark: $gray-900 !default;
+$yiq-text-light: $white !default;
+
+// Characters which are escaped by the escape-svg function
+$escaped-characters: (('<', '%3c'), ('>', '%3e'), ('#', '%23'), ('(', '%28'), (')', '%29')) !default;
+
+// Options
+//
+// Quickly modify global styling by enabling or disabling optional features.
+
+$enable-caret: true !default;
+$enable-rounded: true !default;
+$enable-shadows: false !default;
+$enable-gradients: false !default;
+$enable-transitions: true !default;
+$enable-prefers-reduced-motion-media-query: true !default;
+$enable-hover-media-query: false !default; // Deprecated, no longer affects any compiled CSS
+$enable-grid-classes: true !default;
+$enable-pointer-cursor-for-buttons: true !default;
+$enable-print-styles: true !default;
+$enable-responsive-font-sizes: false !default;
+$enable-validation-icons: true !default;
+$enable-deprecation-messages: true !default;
+
+// Spacing
+//
+// Control the default styling of most Bootstrap elements by modifying these
+// variables. Mostly focused on spacing.
+// You can add more entries to the $spacers map, should you need more variation.
+
+$spacer: 1rem !default;
+$spacers: () !default;
+$spacers: map-merge(
+    (
+        0: 0,
+        1: (
+            $spacer * 0.25,
+        ),
+        2: (
+            $spacer * 0.5,
+        ),
+        3: $spacer,
+        4: (
+            $spacer * 1.5,
+        ),
+        5: (
+            $spacer * 3,
+        ),
+    ),
+    $spacers
+);
+
+// This variable affects the `.h-*` and `.w-*` classes.
+$sizes: () !default;
+$sizes: map-merge(
+    (
+        25: 25%,
+        50: 50%,
+        75: 75%,
+        100: 100%,
+        auto: auto,
+    ),
+    $sizes
+);
+
+// Body
+//
+// Settings for the `<body>` element.
+
+$body-bg: $white !default;
+$body-color: $gray-900 !default;
+
+// Links
+//
+// Style anchor elements.
+
+$link-color: theme-color('primary') !default;
+$link-decoration: none !default;
+$link-hover-color: darken($link-color, 15%) !default;
+$link-hover-decoration: underline !default;
+// Darken percentage for links with `.text-*` class (e.g. `.text-success`)
+$emphasized-link-hover-darken-percentage: 15% !default;
+
+// Paragraphs
+//
+// Style p element.
+
+$paragraph-margin-bottom: 1rem !default;
+
+// Grid breakpoints
+//
+// Define the minimum dimensions at which your layout will change,
+// adapting to different screen sizes, for use in media queries.
+
+$grid-breakpoints: (
+    xs: 0,
+    sm: 576px,
+    md: 768px,
+    lg: 992px,
+    xl: 1200px,
+) !default;
+
+@include _assert-ascending($grid-breakpoints, '$grid-breakpoints');
+@include _assert-starts-at-zero($grid-breakpoints, '$grid-breakpoints');
+
+// Grid containers
+//
+// Define the maximum width of `.container` for different screen sizes.
+
+$container-max-widths: (
+    sm: 540px,
+    md: 720px,
+    lg: 960px,
+    xl: 1500px,
+) !default;
+
+@include _assert-ascending($container-max-widths, '$container-max-widths');
+
+// Grid columns
+//
+// Set the number of columns and specify the width of the gutters.
+
+$grid-columns: 12 !default;
+$grid-gutter-width: 20px !default;
+$grid-row-columns: 6 !default;
+
+// Components
+//
+// Define common padding and border radius sizes and more.
+
+$line-height-lg: 1.5 !default;
+$line-height-sm: 1.5 !default;
+
+$border-width: 1px !default;
+$border-color: $gray-300 !default;
+
+$border-radius: 0.3rem !default;
+$border-radius-lg: 0.3rem !default;
+$border-radius-sm: 0.2rem !default;
+
+$rounded-pill: 50rem !default;
+
+$box-shadow-sm: 0 0.125rem 0.25rem rgba($black, 0.075) !default;
+$box-shadow: 0 0.5rem 1rem rgba($black, 0.15) !default;
+$box-shadow-lg: 0 1rem 3rem rgba($black, 0.175) !default;
+
+$component-active-color: $white !default;
+$component-active-bg: theme-color('primary') !default;
+
+$caret-width: 0.3em !default;
+$caret-vertical-align: $caret-width * 0.85 !default;
+$caret-spacing: $caret-width * 0.85 !default;
+
+$transition-base: all 0.2s ease-in-out !default;
+$transition-fade: opacity 0.15s linear !default;
+$transition-collapse: height 0.35s ease !default;
+
+$embed-responsive-aspect-ratios: () !default;
+$embed-responsive-aspect-ratios: join(((21 9), (16 9), (4 3), (1 1)), $embed-responsive-aspect-ratios);
+
+// Typography
+//
+// Font, line-height, and color for body text, headings, and more.
+
+// stylelint-disable value-keyword-case
+$font-family-sans-serif: 'Exo 2', BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans',
+    'Liberation Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji' !default;
+$font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace !default;
+$font-family-base: $font-family-sans-serif !default;
+// stylelint-enable value-keyword-case
+
+$font-size-base: 1rem !default; // Assumes the browser default, typically `16px`
+$font-size-lg: $font-size-base * 1.25 !default;
+$font-size-sm: $font-size-base * 0.875 !default;
+
+$font-weight-lighter: lighter !default;
+$font-weight-light: 300 !default;
+$font-weight-normal: 400 !default;
+$font-weight-bold: 700 !default;
+$font-weight-bolder: bolder !default;
+
+$font-weight-base: $font-weight-normal !default;
+$line-height-base: 1.5 !default;
+
+$h1-font-size: $font-size-base * 3 !default;
+$h2-font-size: $font-size-base * 2 !default;
+$h3-font-size: $font-size-base * 1.75 !default;
+$h4-font-size: $font-size-base * 1.5 !default;
+$h5-font-size: $font-size-base * 1.25 !default;
+$h6-font-size: $font-size-base !default;
+
+$headings-margin-bottom: $spacer / 2 !default;
+$headings-font-family: null !default;
+$headings-font-weight: 500 !default;
+$headings-line-height: 1.2 !default;
+$headings-color: null !default;
+
+$display1-size: 6rem !default;
+$display2-size: 5.5rem !default;
+$display3-size: 4.5rem !default;
+$display4-size: 3.5rem !default;
+
+$display1-weight: 300 !default;
+$display2-weight: 300 !default;
+$display3-weight: 300 !default;
+$display4-weight: 300 !default;
+$display-line-height: $headings-line-height !default;
+
+$lead-font-size: $font-size-base * 1.25 !default;
+$lead-font-weight: $font-weight-normal !default;
+
+$small-font-size: 80% !default;
+
+$text-muted: $gray-600 !default;
+
+$blockquote-small-color: $gray-600 !default;
+$blockquote-small-font-size: $small-font-size !default;
+$blockquote-font-size: $font-size-base * 1.25 !default;
+
+$hr-border-color: rgba($black, 0.1) !default;
+$hr-border-width: $border-width !default;
+
+$mark-padding: 0.2em !default;
+
+$dt-font-weight: $font-weight-bold !default;
+
+$kbd-box-shadow: inset 0 -0.1rem 0 rgba($black, 0.25) !default;
+$nested-kbd-font-weight: $font-weight-bold !default;
+
+$list-inline-padding: 0.5rem !default;
+
+$mark-bg: #fcf8e3 !default;
+
+$hr-margin-y: $spacer !default;
+
+// Tables
+//
+// Customizes the `.table` component with basic values, each used across all table variations.
+
+$table-cell-padding: 0.75rem !default;
+$table-cell-padding-sm: 0.3rem !default;
+
+$table-color: $body-color !default;
+$table-bg: null !default;
+$table-accent-bg: rgba($black, 0.05) !default;
+$table-hover-color: $table-color !default;
+$table-hover-bg: rgba($black, 0.075) !default;
+$table-active-bg: $table-hover-bg !default;
+
+$table-border-width: $border-width !default;
+$table-border-color: $border-color !default;
+
+$table-head-bg: $gray-200 !default;
+$table-head-color: $gray-700 !default;
+$table-th-font-weight: null !default;
+
+$table-dark-color: $white !default;
+$table-dark-bg: $gray-800 !default;
+$table-dark-accent-bg: rgba($white, 0.05) !default;
+$table-dark-hover-color: $table-dark-color !default;
+$table-dark-hover-bg: rgba($white, 0.075) !default;
+$table-dark-border-color: lighten($table-dark-bg, 7.5%) !default;
+
+$table-striped-order: odd !default;
+
+$table-caption-color: $text-muted !default;
+
+$table-bg-level: -9 !default;
+$table-border-level: -6 !default;
+
+// Buttons + Forms
+//
+// Shared variables that are reassigned to `$input-` and `$btn-` specific variables.
+
+$input-btn-padding-y: 0.75rem !default;
+$input-btn-padding-x: 1.2rem !default;
+$input-btn-font-family: null !default;
+$input-btn-font-size: $font-size-base !default;
+$input-btn-line-height: $line-height-base !default;
+
+$input-btn-focus-width: 0.2rem !default;
+$input-btn-focus-color: rgba($component-active-bg, 0.25) !default;
+$input-btn-focus-box-shadow: 0 0 0 $input-btn-focus-width $input-btn-focus-color !default;
+
+$input-btn-padding-y-sm: 0.25rem !default;
+$input-btn-padding-x-sm: 0.5rem !default;
+$input-btn-font-size-sm: $font-size-sm !default;
+$input-btn-line-height-sm: $line-height-sm !default;
+
+$input-btn-padding-y-lg: 0.5rem !default;
+$input-btn-padding-x-lg: 1rem !default;
+$input-btn-font-size-lg: $font-size-lg !default;
+$input-btn-line-height-lg: $line-height-lg !default;
+
+$input-btn-border-width: $border-width !default;
+
+// Buttons
+//
+// For each of Bootstrap's buttons, define text, background, and border color.
+
+$btn-padding-y: $input-btn-padding-y !default;
+$btn-padding-x: $input-btn-padding-x !default;
+$btn-font-family: $input-btn-font-family !default;
+$btn-font-size: $input-btn-font-size !default;
+$btn-line-height: $input-btn-line-height !default;
+$btn-white-space: null !default; // Set to `nowrap` to prevent text wrapping
+
+$btn-padding-y-sm: $input-btn-padding-y-sm !default;
+$btn-padding-x-sm: $input-btn-padding-x-sm !default;
+$btn-font-size-sm: $input-btn-font-size-sm !default;
+$btn-line-height-sm: $input-btn-line-height-sm !default;
+
+$btn-padding-y-lg: $input-btn-padding-y-lg !default;
+$btn-padding-x-lg: $input-btn-padding-x-lg !default;
+$btn-font-size-lg: $input-btn-font-size-lg !default;
+$btn-line-height-lg: $input-btn-line-height-lg !default;
+
+$btn-border-width: $input-btn-border-width !default;
+
+$btn-font-weight: $font-weight-normal !default;
+$btn-box-shadow: inset 0 1px 0 rgba($white, 0.15), 0 1px 1px rgba($black, 0.075) !default;
+$btn-focus-width: $input-btn-focus-width !default;
+$btn-focus-box-shadow: $input-btn-focus-box-shadow !default;
+$btn-disabled-opacity: 0.65 !default;
+$btn-active-box-shadow: inset 0 3px 5px rgba($black, 0.125) !default;
+
+$btn-link-disabled-color: $gray-600 !default;
+
+$btn-block-spacing-y: 0.5rem !default;
+
+// Allows for customizing button radius independently from global border radius
+$btn-border-radius: $border-radius !default;
+$btn-border-radius-lg: $border-radius-lg !default;
+$btn-border-radius-sm: $border-radius-sm !default;
+
+$btn-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out,
+    box-shadow 0.15s ease-in-out !default;
+
+// Forms
+
+$label-margin-bottom: 0.5rem !default;
+
+$input-padding-y: $input-btn-padding-y !default;
+$input-padding-x: $input-btn-padding-x !default;
+$input-font-family: $input-btn-font-family !default;
+$input-font-size: $input-btn-font-size !default;
+$input-font-weight: $font-weight-base !default;
+$input-line-height: $input-btn-line-height !default;
+
+$input-padding-y-sm: $input-btn-padding-y-sm !default;
+$input-padding-x-sm: $input-btn-padding-x-sm !default;
+$input-font-size-sm: $input-btn-font-size-sm !default;
+$input-line-height-sm: $input-btn-line-height-sm !default;
+
+$input-padding-y-lg: $input-btn-padding-y-lg !default;
+$input-padding-x-lg: $input-btn-padding-x-lg !default;
+$input-font-size-lg: $input-btn-font-size-lg !default;
+$input-line-height-lg: $input-btn-line-height-lg !default;
+
+$input-bg: $white !default;
+$input-disabled-bg: $gray-200 !default;
+
+$input-color: $gray-700 !default;
+$input-border-color: $gray-400 !default;
+$input-border-width: $input-btn-border-width !default;
+$input-box-shadow: inset 0 1px 1px rgba($black, 0.075) !default;
+
+$input-border-radius: $border-radius !default;
+$input-border-radius-lg: $border-radius-lg !default;
+$input-border-radius-sm: $border-radius-sm !default;
+
+$input-focus-bg: $input-bg !default;
+$input-focus-border-color: lighten($component-active-bg, 25%) !default;
+$input-focus-color: $input-color !default;
+$input-focus-width: $input-btn-focus-width !default;
+$input-focus-box-shadow: $input-btn-focus-box-shadow !default;
+
+$input-placeholder-color: $gray-600 !default;
+$input-plaintext-color: $body-color !default;
+
+$input-height-border: $input-border-width * 2 !default;
+
+$input-height-inner: add($input-line-height * 1em, $input-padding-y * 2) !default;
+$input-height-inner-half: add($input-line-height * 0.5em, $input-padding-y) !default;
+$input-height-inner-quarter: add($input-line-height * 0.25em, $input-padding-y / 2) !default;
+
+$input-height: add($input-line-height * 1em, add($input-padding-y * 2, $input-height-border, false)) !default;
+$input-height-sm: add($input-line-height-sm * 1em, add($input-padding-y-sm * 2, $input-height-border, false)) !default;
+$input-height-lg: add($input-line-height-lg * 1em, add($input-padding-y-lg * 2, $input-height-border, false)) !default;
+
+$input-transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out !default;
+
+$form-text-margin-top: 0.25rem !default;
+
+$form-check-input-gutter: 1.25rem !default;
+$form-check-input-margin-y: 0.3rem !default;
+$form-check-input-margin-x: 0.25rem !default;
+
+$form-check-inline-margin-x: 0.75rem !default;
+$form-check-inline-input-margin-x: 0.3125rem !default;
+
+$form-grid-gutter-width: 10px !default;
+$form-group-margin-bottom: 1rem !default;
+
+$input-group-addon-color: $input-color !default;
+$input-group-addon-bg: $gray-200 !default;
+$input-group-addon-border-color: $input-border-color !default;
+
+$custom-forms-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out,
+    box-shadow 0.15s ease-in-out !default;
+
+$custom-control-gutter: 0.5rem !default;
+$custom-control-spacer-x: 1rem !default;
+$custom-control-cursor: null !default;
+
+$custom-control-indicator-size: 1rem !default;
+$custom-control-indicator-bg: $input-bg !default;
+
+$custom-control-indicator-bg-size: 50% 50% !default;
+$custom-control-indicator-box-shadow: $input-box-shadow !default;
+$custom-control-indicator-border-color: $gray-500 !default;
+$custom-control-indicator-border-width: $input-border-width !default;
+
+$custom-control-label-color: null !default;
+
+$custom-control-indicator-disabled-bg: $input-disabled-bg !default;
+$custom-control-label-disabled-color: $gray-600 !default;
+
+$custom-control-indicator-checked-color: $component-active-color !default;
+$custom-control-indicator-checked-bg: $component-active-bg !default;
+$custom-control-indicator-checked-disabled-bg: rgba(theme-color('primary'), 0.5) !default;
+$custom-control-indicator-checked-box-shadow: null !default;
+$custom-control-indicator-checked-border-color: $custom-control-indicator-checked-bg !default;
+
+$custom-control-indicator-focus-box-shadow: $input-focus-box-shadow !default;
+$custom-control-indicator-focus-border-color: $input-focus-border-color !default;
+
+$custom-control-indicator-active-color: $component-active-color !default;
+$custom-control-indicator-active-bg: lighten($component-active-bg, 35%) !default;
+$custom-control-indicator-active-box-shadow: null !default;
+$custom-control-indicator-active-border-color: $custom-control-indicator-active-bg !default;
+
+$custom-checkbox-indicator-border-radius: $border-radius !default;
+$custom-checkbox-indicator-icon-checked: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'><path fill='#{$custom-control-indicator-checked-color}' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/></svg>") !default;
+
+$custom-checkbox-indicator-indeterminate-bg: $component-active-bg !default;
+$custom-checkbox-indicator-indeterminate-color: $custom-control-indicator-checked-color !default;
+$custom-checkbox-indicator-icon-indeterminate: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'><path stroke='#{$custom-checkbox-indicator-indeterminate-color}' d='M0 2h4'/></svg>") !default;
+$custom-checkbox-indicator-indeterminate-box-shadow: null !default;
+$custom-checkbox-indicator-indeterminate-border-color: $custom-checkbox-indicator-indeterminate-bg !default;
+
+$custom-radio-indicator-border-radius: 50% !default;
+$custom-radio-indicator-icon-checked: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'><circle r='3' fill='#{$custom-control-indicator-checked-color}'/></svg>") !default;
+
+$custom-switch-width: $custom-control-indicator-size * 1.75 !default;
+$custom-switch-indicator-border-radius: $custom-control-indicator-size / 2 !default;
+$custom-switch-indicator-size: subtract(
+    $custom-control-indicator-size,
+    $custom-control-indicator-border-width * 4
+) !default;
+
+$custom-select-padding-y: $input-padding-y !default;
+$custom-select-padding-x: $input-padding-x !default;
+$custom-select-font-family: $input-font-family !default;
+$custom-select-font-size: $input-font-size !default;
+$custom-select-height: $input-height !default;
+$custom-select-indicator-padding: 1rem !default; // Extra padding to account for the presence of the background-image based indicator
+$custom-select-font-weight: $input-font-weight !default;
+$custom-select-line-height: $input-line-height !default;
+$custom-select-color: $input-color !default;
+$custom-select-disabled-color: $gray-600 !default;
+$custom-select-bg: $input-bg !default;
+$custom-select-disabled-bg: $gray-200 !default;
+$custom-select-bg-size: 8px 10px !default; // In pixels because image dimensions
+$custom-select-indicator-color: $gray-800 !default;
+$custom-select-indicator: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'><path fill='#{$custom-select-indicator-color}' d='M2 0L0 2h4zm0 5L0 3h4z'/></svg>") !default;
+$custom-select-background: escape-svg($custom-select-indicator) right $custom-select-padding-x center /
+    $custom-select-bg-size no-repeat !default; // Used so we can have multiple background elements (e.g., arrow and feedback icon)
+
+$custom-select-feedback-icon-padding-right: add(
+    1em * 0.75,
+    (2 * $custom-select-padding-y * 0.75) + $custom-select-padding-x + $custom-select-indicator-padding
+) !default;
+$custom-select-feedback-icon-position: center right ($custom-select-padding-x + $custom-select-indicator-padding) !default;
+$custom-select-feedback-icon-size: $input-height-inner-half $input-height-inner-half !default;
+
+$custom-select-border-width: $input-border-width !default;
+$custom-select-border-color: $input-border-color !default;
+$custom-select-border-radius: $border-radius !default;
+$custom-select-box-shadow: inset 0 1px 2px rgba($black, 0.075) !default;
+
+$custom-select-focus-border-color: $input-focus-border-color !default;
+$custom-select-focus-width: $input-focus-width !default;
+$custom-select-focus-box-shadow: 0 0 0 $custom-select-focus-width $input-btn-focus-color !default;
+
+$custom-select-padding-y-sm: $input-padding-y-sm !default;
+$custom-select-padding-x-sm: $input-padding-x-sm !default;
+$custom-select-font-size-sm: $input-font-size-sm !default;
+$custom-select-height-sm: $input-height-sm !default;
+
+$custom-select-padding-y-lg: $input-padding-y-lg !default;
+$custom-select-padding-x-lg: $input-padding-x-lg !default;
+$custom-select-font-size-lg: $input-font-size-lg !default;
+$custom-select-height-lg: $input-height-lg !default;
+
+$custom-range-track-width: 100% !default;
+$custom-range-track-height: 0.5rem !default;
+$custom-range-track-cursor: pointer !default;
+$custom-range-track-bg: $gray-300 !default;
+$custom-range-track-border-radius: 1rem !default;
+$custom-range-track-box-shadow: inset 0 0.25rem 0.25rem rgba($black, 0.1) !default;
+
+$custom-range-thumb-width: 1rem !default;
+$custom-range-thumb-height: $custom-range-thumb-width !default;
+$custom-range-thumb-bg: $component-active-bg !default;
+$custom-range-thumb-border: 0 !default;
+$custom-range-thumb-border-radius: 1rem !default;
+$custom-range-thumb-box-shadow: 0 0.1rem 0.25rem rgba($black, 0.1) !default;
+$custom-range-thumb-focus-box-shadow: 0 0 0 1px $body-bg, $input-focus-box-shadow !default;
+$custom-range-thumb-focus-box-shadow-width: $input-focus-width !default; // For focus box shadow issue in IE/Edge
+$custom-range-thumb-active-bg: lighten($component-active-bg, 35%) !default;
+$custom-range-thumb-disabled-bg: $gray-500 !default;
+
+$custom-file-height: $input-height !default;
+$custom-file-height-inner: $input-height-inner !default;
+$custom-file-focus-border-color: $input-focus-border-color !default;
+$custom-file-focus-box-shadow: $input-focus-box-shadow !default;
+$custom-file-disabled-bg: $input-disabled-bg !default;
+
+$custom-file-padding-y: $input-padding-y !default;
+$custom-file-padding-x: $input-padding-x !default;
+$custom-file-line-height: $input-line-height !default;
+$custom-file-font-family: $input-font-family !default;
+$custom-file-font-weight: $input-font-weight !default;
+$custom-file-color: $input-color !default;
+$custom-file-bg: $input-bg !default;
+$custom-file-border-width: $input-border-width !default;
+$custom-file-border-color: $input-border-color !default;
+$custom-file-border-radius: $input-border-radius !default;
+$custom-file-box-shadow: $input-box-shadow !default;
+$custom-file-button-color: $custom-file-color !default;
+$custom-file-button-bg: $input-group-addon-bg !default;
+$custom-file-text: (
+    en: 'Browse',
+) !default;
+
+// Form validation
+
+$form-feedback-margin-top: $form-text-margin-top !default;
+$form-feedback-font-size: $small-font-size !default;
+$form-feedback-valid-color: theme-color('success') !default;
+$form-feedback-invalid-color: theme-color('danger') !default;
+
+$form-feedback-icon-valid-color: $form-feedback-valid-color !default;
+$form-feedback-icon-valid: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'><path fill='#{$form-feedback-icon-valid-color}' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/></svg>") !default;
+$form-feedback-icon-invalid-color: $form-feedback-invalid-color !default;
+$form-feedback-icon-invalid: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='#{$form-feedback-icon-invalid-color}' viewBox='0 0 12 12'><circle cx='6' cy='6' r='4.5'/><path stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/><circle cx='6' cy='8.2' r='.6' fill='#{$form-feedback-icon-invalid-color}' stroke='none'/></svg>") !default;
+
+$form-validation-states: () !default;
+$form-validation-states: map-merge(
+    (
+        'valid': (
+            'color': $form-feedback-valid-color,
+            'icon': $form-feedback-icon-valid,
+        ),
+        'invalid': (
+            'color': $form-feedback-invalid-color,
+            'icon': $form-feedback-icon-invalid,
+        ),
+    ),
+    $form-validation-states
+);
+
+// Z-index master list
+//
+// Warning: Avoid customizing these values. They're used for a bird's eye view
+// of components dependent on the z-axis and are designed to all work together.
+
+$zindex-dropdown: 1000 !default;
+$zindex-sticky: 1020 !default;
+$zindex-fixed: 1030 !default;
+$zindex-modal-backdrop: 1040 !default;
+$zindex-modal: 1050 !default;
+$zindex-popover: 1060 !default;
+$zindex-tooltip: 1070 !default;
+
+// Navs
+
+$nav-link-padding-y: 1rem !default;
+$nav-link-padding-x: 1rem !default;
+$nav-link-disabled-color: $gray-600 !default;
+
+$nav-tabs-border-color: $gray-300 !default;
+$nav-tabs-border-width: $border-width !default;
+$nav-tabs-border-radius: $border-radius !default;
+$nav-tabs-link-hover-border-color: $gray-200 $gray-200 $nav-tabs-border-color !default;
+$nav-tabs-link-active-color: $gray-700 !default;
+$nav-tabs-link-active-bg: $body-bg !default;
+$nav-tabs-link-active-border-color: $gray-300 $gray-300 $nav-tabs-link-active-bg !default;
+
+$nav-pills-border-radius: $border-radius !default;
+$nav-pills-link-active-color: $component-active-color !default;
+$nav-pills-link-active-bg: $component-active-bg !default;
+
+$nav-divider-color: $gray-200 !default;
+$nav-divider-margin-y: $spacer / 2 !default;
+
+// Navbar
+
+$navbar-padding-y: $spacer / 2 !default;
+$navbar-padding-x: $spacer !default;
+
+$navbar-nav-link-padding-x: 0.5rem !default;
+
+$navbar-brand-font-size: $font-size-lg !default;
+// Compute the navbar-brand padding-y so the navbar-brand will have the same height as navbar-text and nav-link
+$nav-link-height: $font-size-base * $line-height-base + $nav-link-padding-y * 2 !default;
+$navbar-brand-height: $navbar-brand-font-size * $line-height-base !default;
+$navbar-brand-padding-y: ($nav-link-height - $navbar-brand-height) / 2 !default;
+
+$navbar-toggler-padding-y: 0.25rem !default;
+$navbar-toggler-padding-x: 0.75rem !default;
+$navbar-toggler-font-size: $font-size-lg !default;
+$navbar-toggler-border-radius: $btn-border-radius !default;
+
+$navbar-nav-scroll-max-height: 75vh !default;
+
+$navbar-dark-color: rgba($white, 0.5) !default;
+$navbar-dark-hover-color: rgba($white, 0.75) !default;
+$navbar-dark-active-color: $white !default;
+$navbar-dark-disabled-color: rgba($white, 0.25) !default;
+$navbar-dark-toggler-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'><path stroke='#{$navbar-dark-color}' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/></svg>") !default;
+$navbar-dark-toggler-border-color: rgba($white, 0.1) !default;
+
+$navbar-light-color: rgba($black, 0.5) !default;
+$navbar-light-hover-color: rgba($black, 0.7) !default;
+$navbar-light-active-color: rgba($black, 0.9) !default;
+$navbar-light-disabled-color: rgba($black, 0.3) !default;
+$navbar-light-toggler-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'><path stroke='#{$navbar-light-color}' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/></svg>") !default;
+$navbar-light-toggler-border-color: rgba($black, 0.1) !default;
+
+$navbar-light-brand-color: $navbar-light-active-color !default;
+$navbar-light-brand-hover-color: $navbar-light-active-color !default;
+$navbar-dark-brand-color: $navbar-dark-active-color !default;
+$navbar-dark-brand-hover-color: $navbar-dark-active-color !default;
+
+// Dropdowns
+//
+// Dropdown menu container and contents.
+
+$dropdown-min-width: 10rem !default;
+$dropdown-padding-x: 0 !default;
+$dropdown-padding-y: 0.5rem !default;
+$dropdown-spacer: 0.125rem !default;
+$dropdown-font-size: $font-size-base !default;
+$dropdown-color: $body-color !default;
+$dropdown-bg: $white !default;
+$dropdown-border-color: rgba($black, 0.15) !default;
+$dropdown-border-radius: $border-radius !default;
+$dropdown-border-width: $border-width !default;
+$dropdown-inner-border-radius: subtract($dropdown-border-radius, $dropdown-border-width) !default;
+$dropdown-divider-bg: $gray-200 !default;
+$dropdown-divider-margin-y: $nav-divider-margin-y !default;
+$dropdown-box-shadow: 0 0.5rem 1rem rgba($black, 0.175) !default;
+
+$dropdown-link-color: $gray-900 !default;
+$dropdown-link-hover-color: white !default;
+$dropdown-link-hover-bg: $primary !default;
+
+$dropdown-link-active-color: $component-active-color !default;
+$dropdown-link-active-bg: $component-active-bg !default;
+
+$dropdown-link-disabled-color: $gray-500 !default;
+
+$dropdown-item-padding-y: 0.5rem !default;
+$dropdown-item-padding-x: 1.5rem !default;
+
+$dropdown-header-color: $gray-600 !default;
+$dropdown-header-padding: $dropdown-padding-y $dropdown-item-padding-x !default;
+
+// Pagination
+
+$pagination-padding-y: 0.5rem !default;
+$pagination-padding-x: 0.75rem !default;
+$pagination-padding-y-sm: 0.25rem !default;
+$pagination-padding-x-sm: 0.5rem !default;
+$pagination-padding-y-lg: 0.75rem !default;
+$pagination-padding-x-lg: 1.5rem !default;
+$pagination-line-height: 1.25 !default;
+
+$pagination-color: $link-color !default;
+$pagination-bg: $white !default;
+$pagination-border-width: $border-width !default;
+$pagination-border-color: $gray-300 !default;
+
+$pagination-focus-box-shadow: $input-btn-focus-box-shadow !default;
+$pagination-focus-outline: 0 !default;
+
+$pagination-hover-color: $link-hover-color !default;
+$pagination-hover-bg: $gray-200 !default;
+$pagination-hover-border-color: $gray-300 !default;
+
+$pagination-active-color: $component-active-color !default;
+$pagination-active-bg: $component-active-bg !default;
+$pagination-active-border-color: $pagination-active-bg !default;
+
+$pagination-disabled-color: $gray-600 !default;
+$pagination-disabled-bg: $white !default;
+$pagination-disabled-border-color: $gray-300 !default;
+
+$pagination-border-radius-sm: $border-radius-sm !default;
+$pagination-border-radius-lg: $border-radius-lg !default;
+
+// Jumbotron
+
+$jumbotron-padding: 2rem !default;
+$jumbotron-color: null !default;
+$jumbotron-bg: $primary !default;
+
+// Cards
+
+$card-spacer-y: 0.75rem !default;
+$card-spacer-x: 1.25rem !default;
+$card-border-width: $border-width !default;
+$card-border-radius: $border-radius !default;
+$card-border-color: rgba($black, 0.125) !default;
+$card-inner-border-radius: subtract($card-border-radius, $card-border-width) !default;
+$card-cap-bg: rgba($black, 0.03) !default;
+$card-cap-color: null !default;
+$card-height: null !default;
+$card-color: null !default;
+$card-bg: $white !default;
+
+$card-img-overlay-padding: 1.25rem !default;
+
+$card-group-margin: $grid-gutter-width / 2 !default;
+$card-deck-margin: $card-group-margin !default;
+
+$card-columns-count: 3 !default;
+$card-columns-gap: 1.25rem !default;
+$card-columns-margin: $card-spacer-y !default;
+
+// Tooltips
+
+$tooltip-font-size: $font-size-sm !default;
+$tooltip-max-width: 200px !default;
+$tooltip-color: $white !default;
+$tooltip-bg: $black !default;
+$tooltip-border-radius: $border-radius !default;
+$tooltip-opacity: 0.9 !default;
+$tooltip-padding-y: 0.25rem !default;
+$tooltip-padding-x: 0.5rem !default;
+$tooltip-margin: 0 !default;
+
+$tooltip-arrow-width: 0.8rem !default;
+$tooltip-arrow-height: 0.4rem !default;
+$tooltip-arrow-color: $tooltip-bg !default;
+
+// Form tooltips must come after regular tooltips
+$form-feedback-tooltip-padding-y: $tooltip-padding-y !default;
+$form-feedback-tooltip-padding-x: $tooltip-padding-x !default;
+$form-feedback-tooltip-font-size: $tooltip-font-size !default;
+$form-feedback-tooltip-line-height: $line-height-base !default;
+$form-feedback-tooltip-opacity: $tooltip-opacity !default;
+$form-feedback-tooltip-border-radius: $tooltip-border-radius !default;
+
+// Popovers
+
+$popover-font-size: $font-size-sm !default;
+$popover-bg: $white !default;
+$popover-max-width: 276px !default;
+$popover-border-width: $border-width !default;
+$popover-border-color: rgba($black, 0.2) !default;
+$popover-border-radius: $border-radius-lg !default;
+$popover-inner-border-radius: subtract($popover-border-radius, $popover-border-width) !default;
+$popover-box-shadow: 0 0.25rem 0.5rem rgba($black, 0.2) !default;
+
+$popover-header-bg: darken($popover-bg, 3%) !default;
+$popover-header-color: $headings-color !default;
+$popover-header-padding-y: 0.5rem !default;
+$popover-header-padding-x: 0.75rem !default;
+
+$popover-body-color: $body-color !default;
+$popover-body-padding-y: $popover-header-padding-y !default;
+$popover-body-padding-x: $popover-header-padding-x !default;
+
+$popover-arrow-width: 1rem !default;
+$popover-arrow-height: 0.5rem !default;
+$popover-arrow-color: $popover-bg !default;
+
+$popover-arrow-outer-color: fade-in($popover-border-color, 0.05) !default;
+
+// Toasts
+
+$toast-max-width: 350px !default;
+$toast-padding-x: 0.75rem !default;
+$toast-padding-y: 0.25rem !default;
+$toast-font-size: 0.875rem !default;
+$toast-color: null !default;
+$toast-background-color: rgba($white, 0.85) !default;
+$toast-border-width: 1px !default;
+$toast-border-color: rgba(0, 0, 0, 0.1) !default;
+$toast-border-radius: 0.25rem !default;
+$toast-box-shadow: 0 0.25rem 0.75rem rgba($black, 0.1) !default;
+
+$toast-header-color: $gray-600 !default;
+$toast-header-background-color: rgba($white, 0.85) !default;
+$toast-header-border-color: rgba(0, 0, 0, 0.05) !default;
+
+// Badges
+
+$badge-font-size: 75% !default;
+$badge-font-weight: $font-weight-bold !default;
+$badge-padding-y: 0.25em !default;
+$badge-padding-x: 0.4em !default;
+$badge-border-radius: $border-radius !default;
+
+$badge-transition: $btn-transition !default;
+$badge-focus-width: $input-btn-focus-width !default;
+
+$badge-pill-padding-x: 0.6em !default;
+// Use a higher than normal value to ensure completely rounded edges when
+// customizing padding or font-size on labels.
+$badge-pill-border-radius: 10rem !default;
+
+// Modals
+
+// Padding applied to the modal body
+$modal-inner-padding: 1rem !default;
+
+// Margin between elements in footer, must be lower than or equal to 2 * $modal-inner-padding
+$modal-footer-margin-between: 0.5rem !default;
+
+$modal-dialog-margin: 0.5rem !default;
+$modal-dialog-margin-y-sm-up: 1.75rem !default;
+
+$modal-title-line-height: $line-height-base !default;
+
+$modal-content-color: null !default;
+$modal-content-bg: $white !default;
+$modal-content-border-color: rgba($black, 0.2) !default;
+$modal-content-border-width: $border-width !default;
+$modal-content-border-radius: $border-radius-lg !default;
+$modal-content-inner-border-radius: subtract($modal-content-border-radius, $modal-content-border-width) !default;
+$modal-content-box-shadow-xs: 0 0.25rem 0.5rem rgba($black, 0.5) !default;
+$modal-content-box-shadow-sm-up: 0 0.5rem 1rem rgba($black, 0.5) !default;
+
+$modal-backdrop-bg: $black !default;
+$modal-backdrop-opacity: 0.5 !default;
+$modal-header-border-color: $border-color !default;
+$modal-footer-border-color: $modal-header-border-color !default;
+$modal-header-border-width: $modal-content-border-width !default;
+$modal-footer-border-width: $modal-header-border-width !default;
+$modal-header-padding-y: 1rem !default;
+$modal-header-padding-x: 1rem !default;
+$modal-header-padding: $modal-header-padding-y $modal-header-padding-x !default; // Keep this for backwards compatibility
+
+$modal-xl: 1140px !default;
+$modal-lg: 800px !default;
+$modal-md: 500px !default;
+$modal-sm: 300px !default;
+
+$modal-fade-transform: translate(0, -50px) !default;
+$modal-show-transform: none !default;
+$modal-transition: transform 0.3s ease-out !default;
+$modal-scale-transform: scale(1.02) !default;
+
+// Alerts
+//
+// Define alert colors, border radius, and padding.
+
+$alert-padding-y: 0.75rem !default;
+$alert-padding-x: 1.25rem !default;
+$alert-margin-bottom: 1rem !default;
+$alert-border-radius: $border-radius !default;
+$alert-link-font-weight: $font-weight-bold !default;
+$alert-border-width: $border-width !default;
+
+$alert-bg-level: -10 !default;
+$alert-border-level: -9 !default;
+$alert-color-level: 6 !default;
+
+// Progress bars
+
+$progress-height: 1rem !default;
+$progress-font-size: $font-size-base * 0.75 !default;
+$progress-bg: $gray-200 !default;
+$progress-border-radius: $border-radius !default;
+$progress-box-shadow: inset 0 0.1rem 0.1rem rgba($black, 0.1) !default;
+$progress-bar-color: $white !default;
+$progress-bar-bg: theme-color('primary') !default;
+$progress-bar-animation-timing: 1s linear infinite !default;
+$progress-bar-transition: width 0.6s ease !default;
+
+// List group
+
+$list-group-color: null !default;
+$list-group-bg: $white !default;
+$list-group-border-color: rgba($black, 0.125) !default;
+$list-group-border-width: $border-width !default;
+$list-group-border-radius: $border-radius !default;
+
+$list-group-item-padding-y: 0.75rem !default;
+$list-group-item-padding-x: 1.25rem !default;
+
+$list-group-hover-bg: $gray-100 !default;
+$list-group-active-color: $component-active-color !default;
+$list-group-active-bg: $component-active-bg !default;
+$list-group-active-border-color: $list-group-active-bg !default;
+
+$list-group-disabled-color: $gray-600 !default;
+$list-group-disabled-bg: $list-group-bg !default;
+
+$list-group-action-color: $gray-700 !default;
+$list-group-action-hover-color: $list-group-action-color !default;
+
+$list-group-action-active-color: $body-color !default;
+$list-group-action-active-bg: $gray-200 !default;
+
+// Image thumbnails
+
+$thumbnail-padding: 0.25rem !default;
+$thumbnail-bg: $body-bg !default;
+$thumbnail-border-width: $border-width !default;
+$thumbnail-border-color: $gray-300 !default;
+$thumbnail-border-radius: $border-radius !default;
+$thumbnail-box-shadow: 0 1px 2px rgba($black, 0.075) !default;
+
+// Figures
+
+$figure-caption-font-size: 90% !default;
+$figure-caption-color: $gray-600 !default;
+
+// Breadcrumbs
+
+$breadcrumb-font-size: null !default;
+
+$breadcrumb-padding-y: 0.75rem !default;
+$breadcrumb-padding-x: 1rem !default;
+$breadcrumb-item-padding: 0.5rem !default;
+
+$breadcrumb-margin-bottom: 1rem !default;
+
+$breadcrumb-bg: $gray-200 !default;
+$breadcrumb-divider-color: $gray-600 !default;
+$breadcrumb-active-color: $gray-600 !default;
+$breadcrumb-divider: quote('/') !default;
+
+$breadcrumb-border-radius: $border-radius !default;
+
+// Carousel
+
+$carousel-control-color: $white !default;
+$carousel-control-width: 15% !default;
+$carousel-control-opacity: 0.5 !default;
+$carousel-control-hover-opacity: 0.9 !default;
+$carousel-control-transition: opacity 0.15s ease !default;
+
+$carousel-indicator-width: 30px !default;
+$carousel-indicator-height: 3px !default;
+$carousel-indicator-hit-area-height: 10px !default;
+$carousel-indicator-spacer: 3px !default;
+$carousel-indicator-active-bg: $white !default;
+$carousel-indicator-transition: opacity 0.6s ease !default;
+
+$carousel-caption-width: 70% !default;
+$carousel-caption-color: $white !default;
+
+$carousel-control-icon-width: 20px !default;
+
+$carousel-control-prev-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' width='8' height='8' viewBox='0 0 8 8'><path d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/></svg>") !default;
+$carousel-control-next-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' width='8' height='8' viewBox='0 0 8 8'><path d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/></svg>") !default;
+
+$carousel-transition-duration: 0.6s !default;
+$carousel-transition: transform $carousel-transition-duration ease-in-out !default; // Define transform transition first if using multiple transitions (e.g., `transform 2s ease, opacity .5s ease-out`)
+
+// Spinners
+
+$spinner-width: 2rem !default;
+$spinner-height: $spinner-width !default;
+$spinner-border-width: 0.25em !default;
+
+$spinner-width-sm: 1rem !default;
+$spinner-height-sm: $spinner-width-sm !default;
+$spinner-border-width-sm: 0.2em !default;
+
+// Close
+
+$close-font-size: $font-size-base * 1.5 !default;
+$close-font-weight: $font-weight-bold !default;
+$close-color: $black !default;
+$close-text-shadow: 0 1px 0 $white !default;
+
+// Code
+
+$code-font-size: 87.5% !default;
+$code-color: $pink !default;
+
+$kbd-padding-y: 0.2rem !default;
+$kbd-padding-x: 0.4rem !default;
+$kbd-font-size: $code-font-size !default;
+$kbd-color: $white !default;
+$kbd-bg: $gray-900 !default;
+
+$pre-color: $gray-900 !default;
+$pre-scrollable-max-height: 340px !default;
+
+// Utilities
+
+$displays: none, inline, inline-block, block, table, table-row, table-cell, flex, inline-flex !default;
+$overflows: auto, hidden !default;
+$positions: static, relative, absolute, fixed, sticky !default;
+$user-selects: all, auto, none !default;
+
+// Printing
+
+$print-page-size: a3 !default;
+$print-body-min-width: map-get($grid-breakpoints, 'lg') !default;
diff --git a/libs/common/src/lib/twitter.ts b/libs/common/src/lib/twitter.ts
new file mode 100644
index 000000000..57685481d
--- /dev/null
+++ b/libs/common/src/lib/twitter.ts
@@ -0,0 +1,124 @@
+export class TwitterQuery {
+    static parse(operators: { [key: string]: string }) {
+        const ops = Object.keys(operators).reduce((obj: any, key: string) => {
+            try {
+                obj[key] = JSON.parse(operators[key]);
+            } catch (error) {
+                obj[key] = operators[key];
+            }
+            return obj;
+        }, {}) as TTwitterOperators;
+        return ops;
+    }
+
+    static stringifyField(field: string[] | string | number, isURL = false) {
+        if (!field || (Array.isArray(field) && !field.length)) return;
+        if (!Array.isArray(field)) return;
+        const items = field.filter((value: string) => {
+            return isURL ? value.length && value !== 'https://' : value.length;
+        });
+        if (!items.length) return;
+        return JSON.stringify(items);
+    }
+
+    static stringifyFieldURL(field: string[] | string | number) {
+        if (!field || (Array.isArray(field) && !field.length)) return;
+        if (!Array.isArray(field)) return;
+        const items = field.filter((value: string) => value.length && value !== 'https://');
+        if (!items.length) return;
+
+        return JSON.stringify(items.map((url: string) => `"https://${url}"`));
+    }
+
+    static stringify(operators: TTwitterOperators) {
+        return {
+            from: this.stringifyField(operators['from']),
+            to: this.stringifyField(operators['to']),
+            text: this.stringifyField(operators['text']),
+            url: this.stringifyFieldURL(operators['url']),
+            hashtags: this.stringifyField(operators['hashtags']),
+            mentions: this.stringifyField(operators['mentions']),
+            media: operators['media'],
+            excludes: this.stringifyField(operators['excludes']),
+        } as { [key: string]: string };
+    }
+
+    static create(operators: TTwitterOperators) {
+        const operatorKeys = Object.keys(operators);
+        if (!operatorKeys) return;
+        const media =
+            !operators['media'] || ['ignore', null].includes(operators['media']) ? '' : ` ${operators['media']}`;
+        const query = operatorKeys
+            .map((key: string) => {
+                switch (key) {
+                    case 'from': {
+                        const items = operators[key] as string[];
+                        if (!items) return;
+                        const authors = items
+                            .map((author: string) => `from:${author}`)
+                            .filter((v) => !!v)
+                            .join(' OR ');
+                        return items.length > 1 ? `(${authors})` : authors;
+                    }
+                    case 'to': {
+                        const items = operators[key] as string[];
+                        if (!items) return;
+                        const authors = items
+                            .map((author: string) => `to:${author}`)
+                            .filter((v) => !!v)
+                            .join(' OR ');
+                        return items.length > 1 ? `(${authors})` : authors;
+                    }
+                    case 'text': {
+                        const items = operators[key] as string[];
+                        if (!items) return;
+                        const texts = items
+                            .map((value: string) => `"${value}"`)
+                            .filter((v) => !!v)
+                            .join(' OR ');
+                        return items.length > 1 ? `(${texts})` : texts;
+                    }
+                    case 'url': {
+                        const items = operators[key] as string[];
+                        if (!items) return;
+                        const urls = items
+                            .map((value: string) => `url:${value}`)
+                            .filter((v) => !!v)
+                            .join(' OR ');
+                        return items.length > 1 ? `(${urls})` : urls;
+                    }
+                    case 'hashtags': {
+                        const items = operators[key] as string[];
+                        if (!items) return;
+                        const hashtags = items
+                            .map((tag: string) => `#${tag}`)
+                            .filter((v) => !!v)
+                            .join(' OR ');
+                        return (items.length > 1 ? `(${hashtags})` : hashtags) + media;
+                    }
+                    case 'mentions': {
+                        const items = operators[key] as string[];
+                        if (!items) return;
+                        const mentions = items
+                            .map((tag: string) => `@${tag}`)
+                            .filter((v) => !!v)
+                            .join(' OR ');
+                        return (items.length > 1 ? `(${mentions})` : mentions) + media;
+                    }
+                    case 'excludes': {
+                        const items = operators[key] as string[];
+                        if (!items) return;
+                        return items
+                            .map((type: string) => type)
+                            .filter((v) => !!v)
+                            .join(' ');
+                    }
+                }
+                return;
+            })
+            .filter((query) => !!query)
+            .join(' ');
+
+        return `${query}`;
+    }
+}
diff --git a/libs/common/src/lib/types/Account.d.ts b/libs/common/src/lib/types/Account.d.ts
new file mode 100644
index 000000000..dfd12c29f
--- /dev/null
+++ b/libs/common/src/lib/types/Account.d.ts
@@ -0,0 +1,25 @@
+type TAccount = {
+    _id: string;
+    sub: string;
+    username: string;
+    firstName: string;
+    lastName: string;
+    profileImg: string;
+    plan: AccountPlanType;
+    website: string;
+    organisation: string;
+    active: boolean;
+    isEmailVerified: boolean;
+    email: string;
+    address: string;
+    variant: AccountVariant;
+    otpSecret: string;
+    acceptTermsPrivacy: boolean;
+    acceptUpdates: boolean;
+    role: Role;
+    goal: Goal[];
+    tokens: TToken[];
+    identity: string;
+    createdAt: Date;
+    updatedAt: Date;
+};
diff --git a/libs/common/src/lib/types/Brand.d.ts b/libs/common/src/lib/types/Brand.d.ts
new file mode 100644
index 000000000..2a6d7e4d8
--- /dev/null
+++ b/libs/common/src/lib/types/Brand.d.ts
@@ -0,0 +1,7 @@
+type TBrand = {
+    previewImgUrl: string;
+    logoImgUrl: string;
+    backgroundImgUrl: string;
+    widgetPreviewImgUrl: string;
+    poolId: string;
+};
diff --git a/libs/common/src/lib/types/Claim.d.ts b/libs/common/src/lib/types/Claim.d.ts
new file mode 100644
index 000000000..a19dfebc1
--- /dev/null
+++ b/libs/common/src/lib/types/Claim.d.ts
@@ -0,0 +1,11 @@
+type TQRCodeEntry = {
+    sub: string;
+    uuid: string;
+    redirectURL: string;
+    rewardId: string;
+    amount: number;
+    error: string;
+    account: TAccount;
+    claimedAt: Date;
+    createdAt: Date;
+};
diff --git a/libs/common/src/lib/types/Client.d.ts b/libs/common/src/lib/types/Client.d.ts
new file mode 100644
index 000000000..a0aa2ddde
--- /dev/null
+++ b/libs/common/src/lib/types/Client.d.ts
@@ -0,0 +1,17 @@
+type TClient = {
+    _id: string;
+    page: number;
+    name: string;
+    sub: string;
+    poolId: string;
+    grantType: GrantVariant;
+    clientId: string;
+    clientSecret: string;
+    redirectUri: string;
+    requestUri: string;
+    createdAt?: Date;
+};
+
+type TClientState = {
+    [poolId: string]: { [clientId: string]: TClient };
+};
diff --git a/libs/common/src/lib/types/Collaborator.d.ts b/libs/common/src/lib/types/Collaborator.d.ts
new file mode 100644
index 000000000..836458ef1
--- /dev/null
+++ b/libs/common/src/lib/types/Collaborator.d.ts
@@ -0,0 +1,9 @@
+type TCollaborator = {
+    account: TAccount;
+    state: CollaboratorInviteState;
+    email: string;
+    uuid: string;
+    sub: string;
+    poolId: string;
+    updatedAt: Date;
+};
diff --git a/libs/common/src/lib/types/DiscordBot.d.ts b/libs/common/src/lib/types/DiscordBot.d.ts
new file mode 100644
index 000000000..289519de3
--- /dev/null
+++ b/libs/common/src/lib/types/DiscordBot.d.ts
@@ -0,0 +1,81 @@
+type TDiscordGuild = {
+    _id: string;
+    id: string;
+    sub: string;
+    poolId: string;
+    guildId: string;
+    channelId: string;
+    adminRoleId: string;
+    name: string;
+    roles: TDiscordRole[];
+    channels: TDiscordChannel[];
+    isInvited: boolean;
+    isConnected: boolean;
+    secret: string;
+    isShownSecret: boolean;
+    icon: string;
+    permissions: any;
+};
+
+type TDiscordChannel = {
+    channelId: string;
+    name: string;
+};
+
+type TDiscordReaction = {
+    guildId: string;
+    messageId: string;
+    memberId: string;
+    content: string;
+};
+
+type TDiscordMessage = {
+    guildId: string;
+    messageId: string;
+    memberId: string;
+};
+
+type TDiscordRole = {
+    id: string;
+    name: string;
+    color: string;
+    host: boolean;
+};
+
+type TDiscordButton = {
+    label: string;
+    style: ButtonStyle;
+    emoji?: string;
+    customId?: string;
+    url?: string;
+    disabled?: boolean;
+};
+
+type TDiscordUser = {
+    userId: string;
+    guildId: string;
+    profileImgUrl: string;
+    username: string;
+    publicMetrics: {
+        joinedAt: Date;
+        reactionCount: number;
+        messageCount: number;
+    };
+};
+
+type TDiscordEmbed = {
+    title: string;
+    description: string;
+    author: {
+        name: string;
+        icon_url: string;
+        url: string;
+    };
+    image: { url: string };
+    color: number;
+    fields: {
+        name: string;
+        value: string;
+        inline?: boolean;
+    }[];
+};
diff --git a/libs/common/src/lib/types/DiscordRoleReward.d.ts b/libs/common/src/lib/types/DiscordRoleReward.d.ts
new file mode 100644
index 000000000..3d01e956e
--- /dev/null
+++ b/libs/common/src/lib/types/DiscordRoleReward.d.ts
@@ -0,0 +1,8 @@
+type TRewardDiscordRole = TReward & {
+    discordRoleId: string;
+};
+
+type TRewardDiscordRolePayment = TRewardPayment & {
+    discordRoleId: string;
+    role: { color: string; name: string };
+};
diff --git a/libs/common/src/lib/types/ERC1155.d.ts b/libs/common/src/lib/types/ERC1155.d.ts
new file mode 100644
index 000000000..00cab507c
--- /dev/null
+++ b/libs/common/src/lib/types/ERC1155.d.ts
@@ -0,0 +1,52 @@
+type TERC1155MetadataProp = {
+    name: string;
+    propType: string;
+    description: string;
+};
+
+type TERC1155Token = {
+    _id: string;
+    sub: string;
+    state: ERC1155TokenState;
+    recipient: string;
+    failReason: string;
+    transactions: string[];
+    tokenId: number;
+    tokenUri: string;
+    metadataId: string;
+    erc1155Id: string;
+    walletId: string;
+    metadata: TERC1155Metadata;
+    balance: string;
+};
+
+type TERC1155 = {
+    id?: string;
+    variant: NFTVariant;
+    sub: string;
+    chainId: ChainId;
+    name: string;
+    logoImgUrl: string;
+    transactions?: string[];
+    baseURL?: string;
+    description?: string;
+    contract?: Contract;
+    address?: string;
+    createdAt?: Date;
+    updatedAt?: Date;
+    archived?: boolean;
+};
+
+type TERC1155Metadata = {
+    _id?: string;
+    erc1155Id: string;
+    tokenId: number;
+    imageUrl: string;
+    name: string;
+    image: string;
+    description: string;
+    externalUrl: string;
+    tokens?: TERC1155Token[];
+    createdAt: Date;
+    updatedAt: Date;
+};
diff --git a/libs/common/src/lib/types/ERC1155Perk.d.ts b/libs/common/src/lib/types/ERC1155Perk.d.ts
new file mode 100644
index 000000000..4dcba5d2a
--- /dev/null
+++ b/libs/common/src/lib/types/ERC1155Perk.d.ts
@@ -0,0 +1,6 @@
+import { TBaseReward } from './BaseReward';
+
+export type TERC1155Perk = TBaseReward & {
+    erc1155Id: string;
+    erc1155metadataId: string;
+};
diff --git a/libs/common/src/lib/types/ERC1155PerkPayment.d.ts b/libs/common/src/lib/types/ERC1155PerkPayment.d.ts
new file mode 100644
index 000000000..d80d8a99e
--- /dev/null
+++ b/libs/common/src/lib/types/ERC1155PerkPayment.d.ts
@@ -0,0 +1,6 @@
+export type TERC1155PerkPayment = {
+    rewardId: string;
+    sub: string;
+    poolId: string;
+    amount: number;
+};
diff --git a/libs/common/src/lib/types/ERC20.d.ts b/libs/common/src/lib/types/ERC20.d.ts
new file mode 100644
index 000000000..8e83c5d17
--- /dev/null
+++ b/libs/common/src/lib/types/ERC20.d.ts
@@ -0,0 +1,35 @@
+type TERC20 = {
+    _id: string;
+    type: ERC20Type;
+    name: string;
+    symbol: string;
+    address: string;
+    transactions: string[];
+    chainId?: ChainId;
+    contract?: Contract;
+    contractName?: TContractName;
+    sub?: string;
+    totalSupply: number;
+    decimals?: number;
+    adminBalance?: number;
+    poolBalance?: number; // TODO Should move to TAssetPool
+    archived: boolean;
+    logoImgUrl?: string;
+};
+
+type TERC20Token = {
+    sub?: string;
+    erc20Id: string;
+    balance?: number;
+    walletId: string;
+};
+
+type TERC20Transfer = {
+    erc20Id: string;
+    erc20: string;
+    from: string;
+    to: string;
+    chainId: ChainId;
+    transactionId: string;
+    sub: string;
+};
diff --git a/libs/common/src/lib/types/ERC721.d.ts b/libs/common/src/lib/types/ERC721.d.ts
new file mode 100644
index 000000000..4e88fee62
--- /dev/null
+++ b/libs/common/src/lib/types/ERC721.d.ts
@@ -0,0 +1,56 @@
+type TERC721Token = {
+    _id: string;
+    sub: string;
+    state: ERC721TokenState;
+    recipient: string;
+    failReason: string;
+    transactions: string[];
+    tokenId: number;
+    tokenUri: string;
+    metadataId: string;
+    erc721Id: string;
+    metadata: TERC721Metadata;
+    walletId: string;
+    balance?: string;
+};
+
+type TERC721 = {
+    _id?: string;
+    variant: NFTVariant;
+    sub: string;
+    chainId: ChainId;
+    name: string;
+    symbol: string;
+    transactions?: string[];
+    baseURL?: string;
+    description?: string;
+    contract?: Contract;
+    address?: string;
+    createdAt?: Date;
+    updatedAt?: Date;
+    archived?: boolean;
+    logoImgUrl?: string;
+};
+
+type TERC721Metadata = {
+    _id: string;
+    erc721Id: string;
+    imageUrl: string;
+    name: string;
+    image: string;
+    description: string;
+    externalUrl: string;
+    tokens?: TERC721Token[];
+    createdAt?: Date;
+    updatedAt?: Date;
+};
+
+type TERC721Transfer = {
+    erc721Id: string;
+    erc721TokenId: string;
+    from: string;
+    to: string;
+    chainId: ChainId;
+    transactionId: string;
+    sub: string;
+};
diff --git a/libs/common/src/lib/types/Event.d.ts b/libs/common/src/lib/types/Event.d.ts
new file mode 100644
index 000000000..7abfc4acf
--- /dev/null
+++ b/libs/common/src/lib/types/Event.d.ts
@@ -0,0 +1,8 @@
+type TEvent = {
+    _id: string;
+    identity: TIdentity;
+    identityId: string;
+    poolId: string;
+    name: string;
+    createdAt: Date;
+};
diff --git a/libs/common/src/lib/types/Galachain.d.ts b/libs/common/src/lib/types/Galachain.d.ts
new file mode 100644
index 000000000..e870fc82a
--- /dev/null
+++ b/libs/common/src/lib/types/Galachain.d.ts
@@ -0,0 +1,13 @@
+type TGalachainContract = {
+    channelName: string;
+    chaincodeName: string;
+    contractName: string;
+    methodName?: string;
+};
+
+type TGalachainToken = {
+    collection: string;
+    category: string;
+    type: string;
+    additionalKey: string;
+};
diff --git a/libs/common/src/lib/types/Identity.d.ts b/libs/common/src/lib/types/Identity.d.ts
new file mode 100644
index 000000000..d070dadd0
--- /dev/null
+++ b/libs/common/src/lib/types/Identity.d.ts
@@ -0,0 +1,8 @@
+type TIdentity = {
+    _id: string;
+    poolId: string;
+    uuid: string;
+    sub: string;
+    createdAt: Date;
+    account?: TAccount;
+};
diff --git a/libs/common/src/lib/types/Interaction.d.ts b/libs/common/src/lib/types/Interaction.d.ts
new file mode 100644
index 000000000..3feedd187
--- /dev/null
+++ b/libs/common/src/lib/types/Interaction.d.ts
@@ -0,0 +1,22 @@
+type TInteraction = {
+    iat: number;
+    exp: number;
+    session?:
+        | {
+              accountId: string;
+              uid: string;
+              cookie: string;
+              acr?: string | undefined;
+              amr?: string[] | undefined;
+          }
+        | undefined;
+    params: any;
+    prompt: object;
+    result?: object | undefined;
+    returnTo: string;
+    deviceCode?: string | undefined;
+    trusted?: string[] | undefined;
+    uid: string;
+    lastSubmission?: object | undefined;
+    grantId?: string | undefined;
+};
diff --git a/libs/common/src/lib/types/Invoice.d.ts b/libs/common/src/lib/types/Invoice.d.ts
new file mode 100644
index 000000000..242ebbafe
--- /dev/null
+++ b/libs/common/src/lib/types/Invoice.d.ts
@@ -0,0 +1,13 @@
+type TInvoice = {
+    poolId: string;
+    additionalUnitCount: number;
+    costPerUnit: number;
+    costSubscription: number;
+    costTotal: number;
+    currency: string;
+    mapCount: number;
+    mapLimit: number;
+    plan: AccountPlanType;
+    periodStartDate: Date;
+    periodEndDate: Date;
+};
diff --git a/libs/common/src/lib/types/Job.d.ts b/libs/common/src/lib/types/Job.d.ts
new file mode 100644
index 000000000..55eccdc45
--- /dev/null
+++ b/libs/common/src/lib/types/Job.d.ts
@@ -0,0 +1 @@
+type TJob = Job;
diff --git a/libs/common/src/lib/types/Notification.d.ts b/libs/common/src/lib/types/Notification.d.ts
new file mode 100644
index 000000000..330c6dfca
--- /dev/null
+++ b/libs/common/src/lib/types/Notification.d.ts
@@ -0,0 +1,8 @@
+type TNotification = {
+    _id: string;
+    sub: string;
+    subjectId: string;
+    subject: string;
+    poolId: string;
+    message: string;
+};
diff --git a/libs/common/src/lib/types/Pagination.d.ts b/libs/common/src/lib/types/Pagination.d.ts
new file mode 100644
index 000000000..bc2f14065
--- /dev/null
+++ b/libs/common/src/lib/types/Pagination.d.ts
@@ -0,0 +1,12 @@
+type TPaginationParams = Partial<{
+    page: number;
+    limit: number;
+    total: number;
+    previous?: { page: number };
+    next?: { page: number };
+}>;
+
+type TPaginationResult = {
+    page: number;
+    total: number;
+};
diff --git a/libs/common/src/lib/types/Participant.d.ts b/libs/common/src/lib/types/Participant.d.ts
new file mode 100644
index 000000000..a49afc812
--- /dev/null
+++ b/libs/common/src/lib/types/Participant.d.ts
@@ -0,0 +1,12 @@
+type TParticipant = {
+    _id: string;
+    sub: string;
+    poolId: string;
+    rank: number;
+    score: number;
+    riskAnalysis: { score: number; reasons: string[] };
+    questEntryCount: number;
+    isSubscribed: boolean;
+    balance: number;
+    updatedAt: Date;
+};
diff --git a/libs/common/src/lib/types/Payment.d.ts b/libs/common/src/lib/types/Payment.d.ts
new file mode 100644
index 000000000..c407a6fcb
--- /dev/null
+++ b/libs/common/src/lib/types/Payment.d.ts
@@ -0,0 +1,20 @@
+type TPayment = {
+    _id: string;
+    poolId: string;
+    sub: string;
+    createdAt: Date;
+};
+
+type JoinPoolAttributes = {
+    to: string;
+    data: string;
+    minBPTOut: string;
+    attributes: { joinPoolRequest: JoinPoolRequest };
+};
+
+type JoinPoolRequest = {
+    assets: string[];
+    maxAmountsIn: string[];
+    userData: string;
+    fromInternalBalance: boolean;
+};
diff --git a/libs/common/src/lib/types/Pool.d.ts b/libs/common/src/lib/types/Pool.d.ts
new file mode 100644
index 000000000..4f46a0725
--- /dev/null
+++ b/libs/common/src/lib/types/Pool.d.ts
@@ -0,0 +1,78 @@
+type TCampaign = {
+    _id: string;
+    title: string;
+    expiryDate: Date;
+    address: string;
+    chainId: ChainId;
+    domain: string;
+    participants: number;
+    active: boolean;
+    progress: number;
+    tags: string[];
+    logoImgUrl?: string;
+    backgroundImgUrl?: string;
+    quests: { title: string; description: string; amount: number }[];
+    rewards: { title: string; description: string; amount: number }[];
+};
+
+type TPool = {
+    _id: string;
+    safeAddress: string;
+    guilds: TDiscordGuild[];
+    rank: number;
+    token: string;
+    signingSecret: string;
+    contract: Contract;
+    chainId: ChainId;
+    sub: string;
+    transactions: string[];
+    version?: string;
+    variant?: 'defaultDiamond' | 'registry' | 'factory' | 'sharedWallet';
+    events: string[];
+    brand: TBrand;
+    // wallets: TWallet[];
+    settings: TPoolSettings;
+    widget: { domain: string; active: boolean };
+    collaborators: TCollaborator[];
+    identities: TIdentity[];
+    owner: TAccount;
+    safe: WalletDocument;
+    campaignURL: string;
+    createdAt: Date;
+    trialEndsAt: Date;
+    balance: string;
+};
+
+type TPoolSettings = {
+    title: string;
+    slug: string;
+    description: string;
+    startDate: Date;
+    endDate?: Date;
+    isArchived: boolean;
+    isPublished: boolean;
+    isWeeklyDigestEnabled: boolean;
+    isTwitterSyncEnabled: boolean;
+    discordWebhookUrl: string;
+    galachainPrivateKey: string;
+    defaults: {
+        discordMessage: string;
+        conditionalRewards: TQuestSocial & { hashtag: string };
+    };
+    authenticationMethods: AccountVariant[];
+};
+
+type TPoolTransfer = {
+    sub: string;
+    poolId: string;
+    token: string;
+    expiry: Date;
+};
+
+type TPoolTransferResponse = TPoolTransfer & {
+    isExpired: boolean;
+    isTransferred: boolean;
+    isCopied: boolean;
+    url: string;
+    now: number;
+};
diff --git a/libs/common/src/lib/types/Quest.d.ts b/libs/common/src/lib/types/Quest.d.ts
new file mode 100644
index 000000000..47b379df0
--- /dev/null
+++ b/libs/common/src/lib/types/Quest.d.ts
@@ -0,0 +1,35 @@
+type TQuest = TQuestDaily | TQuestInvite | TQuestSocial | TQuestCustom | TQuestWeb3 | TQuestGitcoin | TQuestWebhook;
+type TQuestEntry =
+    | TQuestDailyClaim
+    | TQuestInviteEntry
+    | TQuestSocialEntry
+    | TQuestCustomEntry
+    | TQuestWeb3Entry
+    | TQuestGitcoinEntry
+    | TQuestWebhookEntry;
+
+type TQuestEntryMetadata = TQuestSocialEntryMetadata | TQuestWeb3EntryMetadata;
+
+type TValidationResult = {
+    reason: string;
+    result: boolean;
+};
+
+type TBaseQuest = {
+    _id: string;
+    variant: QuestVariant;
+    uuid: string;
+    poolId: string;
+    title: string;
+    description: string;
+    index: number;
+    image: string;
+    infoLinks: TInfoLink[];
+    expiryDate: Date;
+    locks: TQuestLock[];
+    isPublished: boolean;
+    createdAt: string;
+    updatedAt: string;
+    update: (payload: Partial<TQuest>) => Promise<void>;
+    delete: (payload: Partial<TQuest>) => Promise<void>;
+};
diff --git a/libs/common/src/lib/types/QuestCustom.d.ts b/libs/common/src/lib/types/QuestCustom.d.ts
new file mode 100644
index 000000000..6f6b09501
--- /dev/null
+++ b/libs/common/src/lib/types/QuestCustom.d.ts
@@ -0,0 +1,5 @@
+type TQuestCustom = TBaseQuest & {
+    amount: number;
+    limit: number;
+    eventName: string;
+};
diff --git a/libs/common/src/lib/types/QuestCustomEntry.d.ts b/libs/common/src/lib/types/QuestCustomEntry.d.ts
new file mode 100644
index 000000000..a07c73a40
--- /dev/null
+++ b/libs/common/src/lib/types/QuestCustomEntry.d.ts
@@ -0,0 +1,10 @@
+type TQuestCustomEntry = {
+    questId: string;
+    sub: string;
+    uuid: string;
+    amount: number;
+    isClaimed: boolean;
+    poolId: string;
+    createdAt: Date;
+    eventName: string;
+};
diff --git a/libs/common/src/lib/types/QuestDaily.d.ts b/libs/common/src/lib/types/QuestDaily.d.ts
new file mode 100644
index 000000000..9fa696a00
--- /dev/null
+++ b/libs/common/src/lib/types/QuestDaily.d.ts
@@ -0,0 +1,7 @@
+type TQuestDaily = TBaseQuest & {
+    amounts: number[];
+    progress?: number;
+    claims?: any[];
+    events?: any[];
+    eventName: string;
+};
diff --git a/libs/common/src/lib/types/QuestDailyEntry.d.ts b/libs/common/src/lib/types/QuestDailyEntry.d.ts
new file mode 100644
index 000000000..df13c8246
--- /dev/null
+++ b/libs/common/src/lib/types/QuestDailyEntry.d.ts
@@ -0,0 +1,14 @@
+type TQuestDailyEntry = {
+    questId: string;
+    poolId: string;
+    sub: string;
+    uuid: string;
+    amount: string;
+    createdAt: Date;
+    metadata: TQuestDailyEntryMetadata;
+};
+
+type TQuestDailyEntryMetadata = {
+    ip: string;
+    state: DailyRewardClaimState;
+};
diff --git a/libs/common/src/lib/types/QuestGitcoin.d.ts b/libs/common/src/lib/types/QuestGitcoin.d.ts
new file mode 100644
index 000000000..2342e69d8
--- /dev/null
+++ b/libs/common/src/lib/types/QuestGitcoin.d.ts
@@ -0,0 +1,19 @@
+type TQuestGitcoin = TBaseQuest & {
+    amount: number;
+    scorerId: number;
+    score: number;
+};
+
+type TQuestGitcoinEntry = {
+    poolId: string;
+    questId: string;
+    sub: string;
+    amount: number;
+    createdAt: Date;
+    metadata: TQuestGitcoinEntryMetadata;
+};
+
+type TQuestGitcoinEntryMetadata = {
+    address: string;
+    score: number;
+};
diff --git a/libs/common/src/lib/types/QuestInvite.d.ts b/libs/common/src/lib/types/QuestInvite.d.ts
new file mode 100644
index 000000000..d050e99a1
--- /dev/null
+++ b/libs/common/src/lib/types/QuestInvite.d.ts
@@ -0,0 +1,7 @@
+type TQuestInvite = TBaseQuest & {
+    successUrl: string;
+    pathname: string;
+    token: string;
+    amount: number;
+    isMandatoryReview: boolean;
+};
diff --git a/libs/common/src/lib/types/QuestInviteEntry.d.ts b/libs/common/src/lib/types/QuestInviteEntry.d.ts
new file mode 100644
index 000000000..794a5f25e
--- /dev/null
+++ b/libs/common/src/lib/types/QuestInviteEntry.d.ts
@@ -0,0 +1,10 @@
+type TQuestInviteEntry = {
+    questId: string;
+    sub: string;
+    uuid: string;
+    amount: string;
+    isApproved: boolean;
+    createdAt: Date;
+    poolId: string;
+    metadata: string;
+};
diff --git a/libs/common/src/lib/types/QuestSocial.d.ts b/libs/common/src/lib/types/QuestSocial.d.ts
new file mode 100644
index 000000000..2122e7d79
--- /dev/null
+++ b/libs/common/src/lib/types/QuestSocial.d.ts
@@ -0,0 +1,9 @@
+type TQuestSocial = TBaseQuest & {
+    amount: number;
+    platform: number; // Deprecate in favor of kind
+    kind: AccessTokenKind;
+    interaction: QuestSocialRequirement;
+    content: string;
+    contentMetadata: any;
+    entries?: TQuestSocialEntry[];
+};
diff --git a/libs/common/src/lib/types/QuestSocialEntry.d.ts b/libs/common/src/lib/types/QuestSocialEntry.d.ts
new file mode 100644
index 000000000..cd5d15ca8
--- /dev/null
+++ b/libs/common/src/lib/types/QuestSocialEntry.d.ts
@@ -0,0 +1,17 @@
+type TQuestSocialEntry = {
+    _id: string;
+    questId: string;
+    sub: string;
+    amount: string;
+    poolId: string;
+    metadata: TQuestSocialEntryMetadata;
+    createdAt: Date;
+    account?: TAccount;
+    wallet?: TWallet;
+};
+
+type TQuestSocialEntryMetadata = {
+    platformUserId: string;
+    twitter: TTwitterUserPublicMetrics;
+    discord: { guildId: string; reactionCount: number; messageCount: number };
+};
diff --git a/libs/common/src/lib/types/QuestWebhook.d.ts b/libs/common/src/lib/types/QuestWebhook.d.ts
new file mode 100644
index 000000000..7073ad83f
--- /dev/null
+++ b/libs/common/src/lib/types/QuestWebhook.d.ts
@@ -0,0 +1,5 @@
+type TQuestWebhook = TBaseQuest & {
+    amount: number;
+    webhookId: string;
+    metadata: string;
+};
diff --git a/libs/common/src/lib/types/QuestWebhookEntry.d.ts b/libs/common/src/lib/types/QuestWebhookEntry.d.ts
new file mode 100644
index 000000000..dab18b0d5
--- /dev/null
+++ b/libs/common/src/lib/types/QuestWebhookEntry.d.ts
@@ -0,0 +1,9 @@
+type TQuestWebhookEntry = {
+    poolId: string;
+    questId: string;
+    webhookId: string;
+    identityId: string;
+    sub: string;
+    amount: number;
+    createdAt: Date;
+};
diff --git a/libs/common/src/lib/types/Reward.d.ts b/libs/common/src/lib/types/Reward.d.ts
new file mode 100644
index 000000000..ed52e2dc3
--- /dev/null
+++ b/libs/common/src/lib/types/Reward.d.ts
@@ -0,0 +1,45 @@
+type TInfoLink = {
+    label: string;
+    url: string;
+};
+
+type TQuestLock = { variant: QuestVariant; questId: string };
+type TReward = TRewardCoin | TRewardNFT | TRewardCustom | TRewardCoupon | TRewardDiscordRole | TRewardGalachain;
+type TBaseReward = {
+    _id: string;
+    variant: RewardVariant;
+    uuid: string;
+    poolId: string;
+    title: string;
+    description: string;
+    expiryDate: Date;
+    claimAmount: number;
+    claimLimit: number;
+    limit: number;
+    pointPrice: number;
+    image: string;
+    index: number;
+    locks: TQuestLock[];
+    isPromoted: boolean;
+    isPublished: boolean;
+    createdAt: string;
+    updatedAt: string;
+    update: (payload: Partial<TReward>) => Promise<void>;
+    delete: (payload: Partial<TReward>) => Promise<void>;
+};
+
+type TBaseRewardPayment = {
+    _id: string;
+    rewardId: string;
+    poolId: string;
+    amount: number;
+    sub: string;
+    createdAt: Date;
+};
+type TRewardPayment =
+    | TRewardCoinPayment
+    | TRewardNFTPayment
+    | TRewardCustomPayment
+    | TRewardCouponPayment
+    | TRewardDiscordRolePayment
+    | TRewardGalachainPayment;
diff --git a/libs/common/src/lib/types/RewardCoin.d.ts b/libs/common/src/lib/types/RewardCoin.d.ts
new file mode 100644
index 000000000..73aa73b7c
--- /dev/null
+++ b/libs/common/src/lib/types/RewardCoin.d.ts
@@ -0,0 +1,4 @@
+type TRewardCoin = TReward & {
+    erc20Id: string;
+    amount: string;
+};
diff --git a/libs/common/src/lib/types/RewardCoinPayment.d.ts b/libs/common/src/lib/types/RewardCoinPayment.d.ts
new file mode 100644
index 000000000..22798e949
--- /dev/null
+++ b/libs/common/src/lib/types/RewardCoinPayment.d.ts
@@ -0,0 +1,3 @@
+type TRewardCoinPayment = TBaseRewardPayment & {
+    //
+};
diff --git a/libs/common/src/lib/types/RewardCoupon.d.ts b/libs/common/src/lib/types/RewardCoupon.d.ts
new file mode 100644
index 000000000..cfe55f11f
--- /dev/null
+++ b/libs/common/src/lib/types/RewardCoupon.d.ts
@@ -0,0 +1,19 @@
+type TRewardCoupon = TReward & {
+    codes: string[];
+    couponCodes: TCouponCode[];
+    webshopURL: string;
+};
+
+type TRewardCouponPayment = TBaseRewardPayment & {
+    couponCodeId: string;
+    code: string;
+};
+
+type TCouponCode = {
+    _id: string;
+    couponRewardId: string;
+    poolId: string;
+    code: string;
+    sub: string;
+    createdAt: Date;
+};
diff --git a/libs/common/src/lib/types/RewardCustom.d.ts b/libs/common/src/lib/types/RewardCustom.d.ts
new file mode 100644
index 000000000..29704ac1e
--- /dev/null
+++ b/libs/common/src/lib/types/RewardCustom.d.ts
@@ -0,0 +1,8 @@
+type TRewardCustom = TReward & {
+    webhookId: string;
+    metadata: string;
+};
+
+type TRewardCustomPayment = TBaseRewardPayment & {
+    //
+};
diff --git a/libs/common/src/lib/types/RewardGalachain.d.ts b/libs/common/src/lib/types/RewardGalachain.d.ts
new file mode 100644
index 000000000..b5eb381a5
--- /dev/null
+++ b/libs/common/src/lib/types/RewardGalachain.d.ts
@@ -0,0 +1,12 @@
+type TRewardGalachain = TReward & {
+    contractChannelName: string;
+    contractChaincodeName: string;
+    contractContractName: string;
+    tokenCollection: string;
+    tokenCategory: string;
+    tokenType: string;
+    tokenAdditionalKey: string;
+    tokenInstance: number;
+    amount: string;
+    privateKey: string;
+};
diff --git a/libs/common/src/lib/types/RewardGalachainPayment.d.ts b/libs/common/src/lib/types/RewardGalachainPayment.d.ts
new file mode 100644
index 000000000..dfe338574
--- /dev/null
+++ b/libs/common/src/lib/types/RewardGalachainPayment.d.ts
@@ -0,0 +1,3 @@
+type TRewardGalachainPayment = TBaseRewardPayment & {
+    amount: string;
+};
diff --git a/libs/common/src/lib/types/RewardNFT.d.ts b/libs/common/src/lib/types/RewardNFT.d.ts
new file mode 100644
index 000000000..d1d2b3ca6
--- /dev/null
+++ b/libs/common/src/lib/types/RewardNFT.d.ts
@@ -0,0 +1,13 @@
+type TRewardNFT = TReward & {
+    erc1155Id: string;
+    erc721Id: string;
+    erc1155Amount: number;
+    metadataId: string;
+    tokenId: string;
+    pointPrice: number;
+    image: string;
+    isPromoted: boolean;
+    price: number;
+    priceCurrency: string;
+    redirectUrl: string;
+};
diff --git a/libs/common/src/lib/types/RewardNFTPayment.d.ts b/libs/common/src/lib/types/RewardNFTPayment.d.ts
new file mode 100644
index 000000000..ea3cd452b
--- /dev/null
+++ b/libs/common/src/lib/types/RewardNFTPayment.d.ts
@@ -0,0 +1,3 @@
+type TRewardNFTPayment = TBaseRewardPayment & {
+    //
+};
diff --git a/libs/common/src/lib/types/Token.d.ts b/libs/common/src/lib/types/Token.d.ts
new file mode 100644
index 000000000..4f706d2fb
--- /dev/null
+++ b/libs/common/src/lib/types/Token.d.ts
@@ -0,0 +1,12 @@
+type TToken = {
+    sub: string;
+    kind: AccessTokenKind;
+    accessToken: string;
+    refreshToken: string;
+    accessTokenEncrypted: string;
+    refreshTokenEncrypted: string;
+    scopes: OAuthScope[];
+    expiry: number;
+    userId: string;
+    metadata: any;
+};
diff --git a/libs/common/src/lib/types/Transaction.d.ts b/libs/common/src/lib/types/Transaction.d.ts
new file mode 100644
index 000000000..9813e8b3f
--- /dev/null
+++ b/libs/common/src/lib/types/Transaction.d.ts
@@ -0,0 +1,245 @@
+type TTransaction = {
+    type: TransactionType;
+    state: TransactionState;
+    from: string;
+    to: string;
+    nonce: number;
+    gas: string;
+    chainId: ChainId;
+    walletId: string;
+    transactionId: string;
+    transactionHash?: string;
+    safeTxHash?: string;
+    call?: { fn: string; args: string };
+    baseFee?: string;
+    maxFeePerGas?: string;
+    maxPriorityFeePerGas?: string;
+    failReason?: string;
+    callback: TTransactionCallback;
+};
+
+type TERC20DeployCallbackArgs = {
+    erc20Id: string;
+};
+
+type ERC20DeployCallback = {
+    type: 'Erc20DeployCallback';
+    args: TERC20DeployCallbackArgs;
+};
+
+type TERC721DeployCallbackArgs = {
+    erc721Id: string;
+};
+
+type TERC1155DeployCallbackArgs = {
+    erc1155Id: string;
+};
+
+type WalletDeployCallback = {
+    type: 'walletDeployCallback';
+    args: TWalletDeployCallbackArgs;
+};
+
+type TWalletDeployCallbackArgs = {
+    walletId: string;
+    owner: string;
+    sub: string;
+};
+
+type ERC721DeployCallback = {
+    type: 'Erc721DeployCallback';
+    args: TERC721DeployCallbackArgs;
+};
+
+type ERC1155DeployCallback = {
+    type: 'ERC1155DeployCallback';
+    args: TERC1155DeployCallbackArgs;
+};
+
+type TAssetPoolDeployCallbackArgs = {
+    assetPoolId: string;
+    chainId: number;
+};
+
+type PoolDeployCallback = {
+    type: 'assetPoolDeployCallback';
+    args: TAssetPoolDeployCallbackArgs;
+};
+
+type TTopupCallbackArgs = {
+    receiver: string;
+    depositId: string;
+};
+
+type TopupCallback = {
+    type: 'topupCallback';
+    args: TTopupCallbackArgs;
+};
+
+type TDepositCallbackArgs = {
+    assetPoolId: string;
+    depositId: string;
+};
+
+type DepositCallback = {
+    type: 'depositCallback';
+    args: TDepositCallbackArgs;
+};
+
+type TSwapCreateCallbackArgs = {
+    assetPoolId: string;
+    swapId: string;
+};
+
+type SwapCreateCallback = {
+    type: 'swapCreateCallback';
+    args: TSwapCreateCallbackArgs;
+};
+
+type TERC721TokenMintCallbackArgs = {
+    erc721tokenId: string;
+};
+
+type TERC1155TokenMintCallbackArgs = {
+    erc1155tokenId: string;
+};
+
+type TERC1155TokenTransferCallbackArgs = {
+    erc1155tokenId: string;
+    assetPoolId: string;
+};
+
+type ERC721TokenMintCallback = {
+    type: 'erc721TokenMintCallback';
+    args: TERC721TokenMintCallbackArgs;
+};
+
+type ERC1155TokenMintCallback = {
+    type: 'erc1155TokenMintCallback';
+    args: TERC1155TokenMintCallbackArgs;
+};
+
+type ERC1155TokenTransferCallback = {
+    type: 'erc1155TokenTransferCallback';
+    args: TERC1155TokenTransferCallbackArgs;
+};
+
+type TPayCallbackArgs = {
+    paymentId: string;
+    contractName: TokenContractName;
+    address: string;
+};
+
+type PaymentCallback = {
+    type: 'paymentCallback';
+    args: TPayCallbackArgs;
+};
+
+type TWithdrawForCallbackArgs = {
+    withdrawalId: string;
+    assetPoolId: string;
+};
+
+type WithdrawForCallback = {
+    type: 'withdrawForCallback';
+    args: TWithdrawForCallbackArgs;
+};
+
+type TWalletGrantRoleCallBackArgs = {
+    walletId: string;
+};
+
+type WalletGrantRoleCallBack = {
+    type: 'grantRoleCallBack';
+    args: TWalletGrantRoleCallBackArgs;
+};
+
+type TWalletRevokeRoleCallBackArgs = {
+    walletManagerId: string;
+    walletId: string;
+};
+
+type WalletRevokeRoleCallBack = {
+    type: 'revokeRoleCallBack';
+    args: TWalletRevokeRoleCallBackArgs;
+};
+
+type TERC20TransferFromCallBackArgs = {
+    erc20Id: string;
+};
+
+type ERC20TransferFromCallBack = {
+    type: 'transferFromCallBack';
+    args: TERC20TransferFromCallBackArgs;
+};
+
+type ERC721TransferFromCallBack = {
+    type: 'erc721nTransferFromCallback';
+    args: TERC721TransferFromCallBackArgs;
+};
+
+type TERC721TransferFromCallBackArgs = {
+    erc721Id: string;
+    erc721TokenId: string;
+    walletId: string;
+};
+
+// This is used for non pool transfers
+type ERC721TransferFromWalletCallback = {
+    type: 'erc721TransferFromWalletCallback';
+    args: TERC721TransferFromWalletCallbackArgs;
+};
+
+type TERC721TransferFromWalletCallbackArgs = {
+    erc721Id: string;
+    erc721TokenId: string;
+    walletId: string;
+    to: string;
+};
+
+// This is used for pool transfers
+type ERC1155TransferFromCallback = {
+    type: 'erc1155TransferFromCallback';
+    args: TERC1155TransferFromCallbackArgs;
+};
+
+type TERC1155TransferFromCallbackArgs = {
+    erc1155Id: string;
+    erc1155TokenId: string;
+    walletId: string;
+};
+
+// This is used for non pool transfers
+type ERC1155TransferFromWalletCallback = {
+    type: 'erc1155TransferFromWalletCallback';
+    args: TERC1155TransferFromWalletCallbackArgs;
+};
+
+type TERC1155TransferFromWalletCallbackArgs = {
+    erc1155Id: string;
+    erc1155TokenId: string;
+    walletId: string;
+    to: string;
+};
+
+type TTransactionCallback =
+    | ERC20DeployCallback
+    | ERC721DeployCallback
+    | ERC1155DeployCallback
+    | PoolDeployCallback
+    | TopupCallback
+    | DepositCallback
+    | SwapCreateCallback
+    | ERC721TokenMintCallback
+    | ERC1155TokenMintCallback
+    | ERC1155TokenTransferCallback
+    | PaymentCallback
+    | WithdrawForCallback
+    | WalletDeployCallback
+    | WalletGrantRoleCallBack
+    | WalletRevokeRoleCallBack
+    | ERC20TransferFromCallBack
+    | ERC721TransferFromCallBack
+    | ERC721TransferFromWalletCallback
+    | ERC1155TransferFromCallback
+    | ERC1155TransferFromWalletCallback;
diff --git a/libs/common/src/lib/types/Twitter.d.ts b/libs/common/src/lib/types/Twitter.d.ts
new file mode 100644
index 000000000..a481da822
--- /dev/null
+++ b/libs/common/src/lib/types/Twitter.d.ts
@@ -0,0 +1,136 @@
+type TTwitterQuery = {
+    _id: string;
+    createdAt: Date;
+    poolId: string;
+    query: string;
+    posts: TTwitterPost[];
+    operators: TTwitterOperators;
+    defaults: {
+        title: string;
+        description: string;
+        amount: number;
+        isPublished: boolean;
+        minFollowersCount: number;
+        expiryInDays: number;
+        locks: TQuestLock[];
+    };
+};
+
+type TTwitterRequestParams = {
+    max_results: number;
+    pagination_token?: string;
+    since_id?: string;
+    max_id?: string;
+    query?: string;
+};
+
+type TTwitterLike = {
+    _id: string;
+    userId: string;
+    postId: string;
+};
+
+type TTwitterPostPublicMetrics = {
+    retweetCount: number;
+    replyCount: number;
+    likeCount: number;
+    quoteCount: number;
+    bookmarkCount: number;
+    impressionCount: number;
+};
+
+type TTwitterPostPublicMetricsResponse = {
+    retweet_count: number;
+    reply_count: number;
+    like_count: number;
+    quote_count: number;
+    bookmark_count: number;
+    impression_count: number;
+};
+
+type TTwitterPost = {
+    _id: string;
+    userId: string;
+    postId: string;
+    queryId: string;
+    text: string;
+    publicMetrics: TTwitterPostPublicMetrics;
+};
+
+type TTwitterRepost = {
+    _id: string;
+    userId: string;
+    postId: string;
+};
+
+type TTwitterFollower = {
+    _id: string;
+    userId: string;
+    targetUserId: string;
+};
+
+type TTwitterUserPublicMetrics = {
+    followersCount: number;
+    followingCount: number;
+    tweetCount: number;
+    listedCount: number;
+    likeCount: number;
+};
+
+type TTwitterUser = {
+    _id: string;
+    userId: string;
+    profileImgUrl: string;
+    name: string;
+    username: string;
+    publicMetrics: TTwitterUserPublicMetrics;
+};
+
+type TTwitterUserResponse = {
+    id: string;
+    profile_image_url: string;
+    name: string;
+    username: string;
+    public_metrics: {
+        followers_count: number;
+        following_count: number;
+        tweet_count: number;
+        listed_count: number;
+        like_count: number;
+    };
+};
+
+type TTwitterOperators = {
+    from: string[];
+    to: string[];
+    text: string[];
+    url: string[];
+    hashtags: string[];
+    mentions: string[];
+    media: string | null;
+    excludes: string[];
+};
+
+type TTwitterMediaResponse = {
+    preview_image_url: string;
+    width: number;
+    height: number;
+    type: 'video' | 'photo' | 'animated_gif';
+    url: string;
+};
+
+type TTwitterPostResponse = {
+    id: string;
+    author_id: string;
+    text: string;
+    created_at: number;
+    attachments: {
+        media_keys: string[];
+    };
+    public_metrics: TTwitterPostPublicMetricsResponse;
+};
+
+type TTwitterPostWithUserAndMedia = TTwitterPostResponse & {
+    user: TTwitterUserResponse;
+    media: TTwitterMediaResponse[];
+};
diff --git a/libs/common/src/lib/types/Wallet.d.ts b/libs/common/src/lib/types/Wallet.d.ts
new file mode 100644
index 000000000..fdbe714b1
--- /dev/null
+++ b/libs/common/src/lib/types/Wallet.d.ts
@@ -0,0 +1,13 @@
+type TWallet = {
+    _id?: string;
+    poolId: string;
+    sub: string;
+    chainId: ChainId;
+    address: string;
+    version: string;
+    variant: WalletVariant;
+    safeVersion: string;
+    uuid: string;
+    createdAt: Date;
+    expiresAt: Date;
+};
diff --git a/libs/common/src/lib/types/Web3Quest.d.ts b/libs/common/src/lib/types/Web3Quest.d.ts
new file mode 100644
index 000000000..431d9c331
--- /dev/null
+++ b/libs/common/src/lib/types/Web3Quest.d.ts
@@ -0,0 +1,22 @@
+type TQuestWeb3 = TBaseQuest & {
+    amount: number;
+    methodName: string;
+    threshold: string;
+    contracts: { chainId: ChainId; address: string }[];
+};
+
+type TQuestWeb3Entry = {
+    questId: string;
+    sub: string;
+    amount: number;
+    poolId: string;
+    createdAt: Date;
+    metadata: TQuestWeb3EntryMetadata;
+};
+
+type TQuestWeb3EntryMetadata = {
+    callResult: string;
+    chainId: ChainId;
+    address: string;
+    rpc?: string;
+};
diff --git a/libs/common/src/lib/types/Webhook.d.ts b/libs/common/src/lib/types/Webhook.d.ts
new file mode 100644
index 000000000..d236b7916
--- /dev/null
+++ b/libs/common/src/lib/types/Webhook.d.ts
@@ -0,0 +1,22 @@
+type TWebhook = {
+    _id?: string;
+    sub: string;
+    url: string;
+    poolId: string;
+    status: WebhookStatus;
+    active: boolean;
+    webhookRequests: TWebhookRequest[];
+    createdAt?: Date;
+};
+
+type TWebhookRequest = {
+    _id?: string;
+    webhookId: string;
+    payload: string;
+    payloadFormatted?: HighlightResult;
+    attempts: number;
+    httpStatus: number;
+    state: WebhookRequestState;
+    failReason: string;
+    createdAt?: Date;
+};
diff --git a/libs/common/src/lib/types/Widget.d.ts b/libs/common/src/lib/types/Widget.d.ts
new file mode 100644
index 000000000..49c1c5851
--- /dev/null
+++ b/libs/common/src/lib/types/Widget.d.ts
@@ -0,0 +1,14 @@
+type TWidget = {
+    uuid: string;
+    poolId: string;
+    iconImg: string;
+    align: string;
+    message: string;
+    domain: string;
+    color: string;
+    bgColor: string;
+    cssSelector: string;
+    theme: string;
+    active: boolean;
+    isPublished: boolean;
+};
diff --git a/libs/common/tsconfig.json b/libs/common/tsconfig.json
index 3cb66fdc3..fd5988eae 100644
--- a/libs/common/tsconfig.json
+++ b/libs/common/tsconfig.json
@@ -6,6 +6,8 @@
         "strict": true,
         "noImplicitOverride": true,
         "noPropertyAccessFromIndexSignature": true,
+        "noImplicitAny": false,
+        "resolveJsonModule": true,
         "noImplicitReturns": true,
         "noFallthroughCasesInSwitch": true,
         "types": ["node"],
diff --git a/libs/sdk/.eslintrc.json b/libs/sdk/.eslintrc.json
new file mode 100644
index 000000000..5ace9ff53
--- /dev/null
+++ b/libs/sdk/.eslintrc.json
@@ -0,0 +1,25 @@
+{
+    "extends": ["../../.eslintrc.json"],
+    "ignorePatterns": ["!**/*"],
+    "overrides": [
+        {
+            "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
+            "rules": {}
+        },
+        {
+            "files": ["*.ts", "*.tsx"],
+            "rules": {}
+        },
+        {
+            "files": ["*.js", "*.jsx"],
+            "rules": {}
+        },
+        {
+            "files": ["*.json"],
+            "parser": "jsonc-eslint-parser",
+            "rules": {
+                "@nx/dependency-checks": "error"
+            }
+        }
+    ]
+}
diff --git a/libs/sdk/README.md b/libs/sdk/README.md
new file mode 100644
index 000000000..6dd775465
--- /dev/null
+++ b/libs/sdk/README.md
@@ -0,0 +1,250 @@
+# THX Network JS SDK
+
+This SDK contains API wrappers and an OIDC OAuth manager to simplify access to THX API resources.
+
+## Prerequisites
+
+1. [Sign up for an account](https://dashboard.thx.network/)
+2. Create API keys (Developer -> API)
+3. Save your `clientId` and `clientSecret`
+
+## SDK Contents
+
+-   [1. THXWidget](#1-thxwidget)
+
+    -   [1.1 ContainerSelector](#11-containerselector)
+    -   [1.2 Identity](#12-identity)
+
+-   [2. THXAPIClient](#2-thxapiclient)
+
+    -   [2.1 Identities](#21-identities)
+    -   [2.2 Events](#22-events)
+
+-   [3. THXBrowserClient](#3-thxbrowserclient)
+
+    -   [3.1 Account](#31-account)
+    -   [3.2 Quests](#32-quests)
+    -   [3.3 Rewards](#33-rewards)
+    -   [3.4 Wallet](#34-wallet)
+
+## 1. THXWidget
+
+Meant for loading the HTML widget in a website using JavaScript.
+
+```javascript
+import { THXWidget, THXWidgetOptions } from '@thxnetwork/sdk';
+
+const options: THXWidgetOptions = {
+    campaignId: '6571c9c6b7d775decb45a8f0',
+    containerSelector: '#your-html-container', // Optional
+    identity: '36d33a59-5398-463a-ac98-0f7d9b201648', // Optional
+};
+THXWidget.create(options);
+```
+
+### 1.1 ContainerSelector
+
+Providing a `containerSelector` is optional and will inject the application in a given HTML element. Make sure to provide CSS styles for proper dimensions within your page.
+
+```html
+<div id="your-html-container" style="height: 750px;"></div>
+```
+
+No messagbox, launcher and notification elements will be injected if a container selector is specified!
+
+### 1.2 Identity
+
+Providing an identity is optional and alternatively you can set an identity at a later moment, for example after successful authentication with your app.
+
+```javascript
+window.THXWidget.setIdentity('36d33a59-5398-463a-ac98-0f7d9b201648');
+```
+
+## 2. THXAPIClient
+
+Meant for JavaScript backend applications.
+
+```javascript
+import { THXAPIClient, THXAPIClientOptions } from '@thxnetwork/sdk';
+
+const options: THXAPIClientOptions = {
+    campaignId: '65b0e27845c63cd18e0ab4a6',
+    clientId: 'msuq4Znuv3q8hLf7ATnlP',
+    clientSecret: 'YP_k8_LnPG58LHqzWGxg3EMBBGVwwJUmqsuQZdoMtEAD-85hJwRt2vxfev23T92h727bDwCqh3cIkx6meT0xxg',
+};
+const thx = new THXAPIClient(options);
+```
+
+### 2.1 Identities
+
+Identities are used to connect THX accounts to users in your database.
+
+```javascript
+const identity = await thx.identity.create();
+// 36d33a59-5398-463a-ac98-0f7d9b201648
+```
+
+### 2.2 Events
+
+Events can be used to add requirements for Daily, Invite and Custom Quests.
+
+```javascript
+thx.events.create({ name: 'level_up', identity: '36d33a59-5398-463a-ac98-0f7d9b201648' });
+```
+
+### 2.3 Quests
+
+Quests can be managed programatically. Specify `content` and `contentMetadata` according to the requirements in order to generate proper card previews.
+
+#### Twitter Post Previews
+
+Use this `content` and `contentMetadata` for these `interaction` variants: `QuestRequirement.TwitterLike`, `QuestRequirement.TwitterRetweet`, `QuestRequirement.TwitterLikeRetweet`.
+
+```javascript
+const interaction = QuestRequirement.TwitterLikeRetweet;
+const content = '46927555';
+const contentMetadata = {
+    url: 'https://twitter.com/twitter/status/1603121182101970945',
+    username: 'johndoe',
+    name: 'John Doe',
+    text: '✨ Loyalty Networks are here✨ #fintech meets #loyalty',
+    minAmountFollowers: 123,
+};
+```
+
+#### Twitter User Previews
+
+Use this `content` and `contentMetadata` for these `interaction` variants: `QuestRequirement.TwitterFollow`.
+
+```javascript
+const interaction = QuestRequirement.TwitterFollow;
+const content = '13241234';
+const contentMetadata = {
+    id: 46927555,
+    name: 'John doe',
+    profileImgUrl: 'https://picsum.com/avatar.jpg',
+    username: 'johndoe',
+    minAmountFollowers: 123,
+};
+```
+
+#### Twitter Message Preview
+
+Use this `content` and `contentMetadata` for these `interaction` variants: ` QuestRequirement.TwitterMessage`,
+
+```javascript
+const interaction = QuestRequirement.TwitterMessage;
+const content = '✨ Loyalty Networks are here✨ #fintech meets #loyalty';
+const contentMetadata = {
+    minFollowersCount: 123,
+};
+```
+
+#### Create Twitter Quest
+
+```javascript
+thx.campaigns.quests.create({
+    variant: QuestVariant.Twitter,
+    title: 'Farm along!',
+    description: 'Get these pointzz...',
+    amount: 123,
+    isPublished: true,
+    interaction,
+    content,
+    contentMetadata,
+});
+```
+
+## 3. THXBrowserClient
+
+Meant for JavaScript browser applications.
+
+```javascript
+import { THXBrowserClient, THXBrowserClientOptions } from '@thxnetwork/sdk';
+
+const options: THXBrowserClientOptions = {
+    clientId: 'chyBeltL7rmOeTwVu',
+    clientSecret: 'q4ilZuGA4VPtrGhXug3i5taXrvDtidrzyv-gJN3yVo8T2stL6RwYQjqRoK-iUiAGGvhbG_F3TEFFuD_56Q065Q'
+    redirectUri: 'https://www.yourdomain.com/auth-callback'
+    campaignId: '6571c9c6b7d775decb45a8f0', // Optional
+};
+const thx = new THXBrowserClient(options);
+```
+
+Alternatively you can set the `campaignId` at a later moment, for example after obtaining it from a url or database. The campaign is used to scope API requests to a campaign that you own.
+
+```javascript
+thx.setCampaignId('6571c9c6b7d775decb45a8f0');
+```
+
+### 3.1 Account
+
+Get account info for the authenticated user and obtain it's current point balance in your campaign.
+
+```javascript
+// Get Account
+await client.account.get();
+
+// Update Account
+await client.account.patch({
+    username: '';
+    firstName: '';
+    lastName: '';
+    email: '';
+    profileImg: ''; // Absolute URL
+});
+
+// Get Point Balance
+await client.pointBalance.list();
+```
+
+### 3.2 Quests
+
+List and complete quests in your campaign.
+
+```javascript
+// List Quests
+await client.quests.list();
+
+// Complete Quests
+await client.quests.daily.complete(uuid, {
+    sub: '',
+});
+await client.quests.invite.complete(uuid, {
+    sub: '',
+});
+await client.quests.social.complete(id);
+await client.quests.custom.complete(id);
+await client.quests.web3.complete(id);
+```
+
+### 3.3 Rewards
+
+List and redeem rewards in your campaign.
+
+```javascript
+// List Rewards
+await thx.rewards.list();
+
+// Get Rewards
+await thx.rewards.coin.get(uuid);
+await thx.rewards.nft.get(uuid);
+await thx.rewards.custom.get(uuid);
+await thx.rewards.coupon.get(uuid);
+
+// Redeem Rewards
+await thx.rewards.coin.redemption.post(uuid);
+await thx.rewards.nft.redemption.post(uuid);
+await thx.rewards.custom.redemption.post(uuid);
+await thx.rewards.coupon.redemption.post(uuid);
+```
+
+### 3.4 Wallet
+
+List tokens held in your accounts wallet.
+
+```javascript
+await thx.erc20.list({ chainId: 137 });
+await thx.erc721.list({ chainId: 137 });
+await thx.erc1155.list({ chainId: 137 });
+```
diff --git a/libs/sdk/package.json b/libs/sdk/package.json
new file mode 100644
index 000000000..b75247741
--- /dev/null
+++ b/libs/sdk/package.json
@@ -0,0 +1,11 @@
+{
+    "name": "sdk",
+    "version": "0.0.1",
+    "dependencies": {
+        "tslib": "^2.3.0"
+    },
+    "type": "commonjs",
+    "main": "./src/index.js",
+    "typings": "./src/index.d.ts",
+    "private": true
+}
diff --git a/libs/sdk/project.json b/libs/sdk/project.json
new file mode 100644
index 000000000..de1d70c52
--- /dev/null
+++ b/libs/sdk/project.json
@@ -0,0 +1,22 @@
+{
+    "name": "sdk",
+    "$schema": "../../node_modules/nx/schemas/project-schema.json",
+    "sourceRoot": "libs/sdk/src",
+    "projectType": "library",
+    "tags": [],
+    "targets": {
+        "build": {
+            "executor": "@nx/js:tsc",
+            "outputs": ["{options.outputPath}"],
+            "options": {
+                "outputPath": "dist/libs/sdk",
+                "main": "libs/sdk/src/index.ts",
+                "tsConfig": "libs/sdk/tsconfig.lib.json",
+                "assets": ["libs/sdk/*.md"]
+            }
+        },
+        "lint": {
+            "executor": "@nx/eslint:lint"
+        }
+    }
+}
diff --git a/libs/sdk/src/index.ts b/libs/sdk/src/index.ts
new file mode 100644
index 000000000..1936e4518
--- /dev/null
+++ b/libs/sdk/src/index.ts
@@ -0,0 +1,3 @@
+export * from './lib/clients';
+export * from './lib/types';
+export * from './lib/types/enums';
diff --git a/libs/sdk/src/lib/clients/API.ts b/libs/sdk/src/lib/clients/API.ts
new file mode 100644
index 000000000..e48c59240
--- /dev/null
+++ b/libs/sdk/src/lib/clients/API.ts
@@ -0,0 +1,22 @@
+import { THXAPIClientOptions } from '../types';
+import { THXOIDCGrant } from '../managers/OIDCManager';
+import RequestManager from '../managers/RequestManager';
+import EventManager from '../managers/EventManager';
+import IdentityManager from '../managers/IdentityManager';
+import CampaignManager from '../managers/CampaignManager';
+
+export default class THXAPIClient {
+    options: THXAPIClientOptions;
+    request: RequestManager;
+    identity: IdentityManager;
+    events: EventManager;
+    campaigns: CampaignManager;
+
+    constructor(options: THXAPIClientOptions) {
+        this.options = options;
+        this.request = new RequestManager(this, THXOIDCGrant.ClientCredentials);
+        this.identity = new IdentityManager(this);
+        this.events = new EventManager(this);
+        this.campaigns = new CampaignManager(this);
+    }
+}
diff --git a/libs/sdk/src/lib/clients/Browser.ts b/libs/sdk/src/lib/clients/Browser.ts
new file mode 100644
index 000000000..531492a82
--- /dev/null
+++ b/libs/sdk/src/lib/clients/Browser.ts
@@ -0,0 +1,44 @@
+import ERC20Manager from '../managers/ERC20Manager';
+import ERC721Manager from '../managers/ERC721Manager';
+import ERC1155Manager from '../managers/ERC1155Manager';
+import CouponCodeManager from '../managers/CouponCodeManager';
+import RequestManager from '../managers/RequestManager';
+import AccountManager from '../managers/AccountManager';
+import QuestManager from '../managers/QuestManager';
+import RewardManager from '../managers/RewardManager';
+import QRCodeManager from '../managers/QRCodeManager';
+import PoolManager from '../managers/PoolManager';
+import { THXOIDCGrant } from '../managers/OIDCManager';
+import { THXBrowserClientOptions } from '../types';
+
+export default class THXBrowserClient {
+    options: THXBrowserClientOptions;
+    request: RequestManager;
+    account: AccountManager;
+    erc20: ERC20Manager;
+    erc721: ERC721Manager;
+    erc1155: ERC1155Manager;
+    couponCodes: CouponCodeManager;
+    quests: QuestManager;
+    rewards: RewardManager;
+    qrCodes: QRCodeManager;
+    pools: PoolManager;
+
+    constructor(options: THXBrowserClientOptions) {
+        this.options = options;
+        this.request = new RequestManager(this, THXOIDCGrant.AuthorizationCode);
+        this.account = new AccountManager(this);
+        this.erc20 = new ERC20Manager(this);
+        this.erc721 = new ERC721Manager(this);
+        this.erc1155 = new ERC1155Manager(this);
+        this.couponCodes = new CouponCodeManager(this);
+        this.quests = new QuestManager(this);
+        this.rewards = new RewardManager(this);
+        this.qrCodes = new QRCodeManager(this);
+        this.pools = new PoolManager(this);
+    }
+
+    setCampaignId(campaignId: string) {
+        this.options.poolId = campaignId;
+    }
+}
diff --git a/libs/sdk/src/lib/clients/Widget.ts b/libs/sdk/src/lib/clients/Widget.ts
new file mode 100644
index 000000000..45d89a92b
--- /dev/null
+++ b/libs/sdk/src/lib/clients/Widget.ts
@@ -0,0 +1,27 @@
+import { THXWidgetOptions } from '../types';
+
+export default class THXWidget {
+    static create(options: THXWidgetOptions) {
+        if (document.getElementById('thx-container')) return;
+        if (!options) throw new Error("Please provide 'options'.");
+
+        const { campaignId, apiUrl, identity, containerSelector } = options;
+        if (!campaignId) throw new Error("Please provide 'options.campaignId'.");
+
+        const url = new URL(apiUrl || 'https://api.thx.network');
+        url.pathname = `/v1/widget/${campaignId}.js`;
+
+        if (identity) {
+            url.searchParams.append('identity', identity);
+        }
+
+        if (containerSelector) {
+            url.searchParams.append('containerSelector', containerSelector);
+        }
+
+        const script = document.createElement('script');
+        script.src = url.href;
+
+        document.body.appendChild(script);
+    }
+}
diff --git a/libs/sdk/src/lib/clients/index.ts b/libs/sdk/src/lib/clients/index.ts
new file mode 100644
index 000000000..c39ea201c
--- /dev/null
+++ b/libs/sdk/src/lib/clients/index.ts
@@ -0,0 +1,5 @@
+import THXBrowserClient from './Browser';
+import THXAPIClient from './API';
+import THXWidget from './Widget';
+export type THXClient = THXBrowserClient | THXAPIClient;
+export { THXBrowserClient, THXAPIClient, THXWidget };
diff --git a/libs/sdk/src/lib/managers/AccountManager.ts b/libs/sdk/src/lib/managers/AccountManager.ts
new file mode 100644
index 000000000..37b1b5d6a
--- /dev/null
+++ b/libs/sdk/src/lib/managers/AccountManager.ts
@@ -0,0 +1,29 @@
+import { THXClient } from '../clients';
+import { ChainId } from '../types/enums/ChainId';
+import BaseManager from './BaseManager';
+
+export default class AccountManager extends BaseManager {
+    constructor(client: THXClient) {
+        super(client);
+    }
+
+    async get(chainId?: ChainId) {
+        return await this.client.request.get('/v1/account' + chainId && `?chainId=${chainId}`);
+    }
+
+    async patch(body: any) {
+        return await this.client.request.patch('/v1/account', { data: body });
+    }
+
+    wallets = {
+        list: async () => {
+            return await this.client.request.get('/v1/account/wallets');
+        },
+        create: async (body: { chainId: number }) => {
+            return await this.client.request.post('/v1/account/wallets', { data: body });
+        },
+        confirm: async (body: { chainId: number }) => {
+            return await this.client.request.post(`/v1/account/wallets/confirm`, { data: body });
+        },
+    };
+}
diff --git a/libs/sdk/src/lib/managers/BaseManager.ts b/libs/sdk/src/lib/managers/BaseManager.ts
new file mode 100644
index 000000000..ac6fb920c
--- /dev/null
+++ b/libs/sdk/src/lib/managers/BaseManager.ts
@@ -0,0 +1,9 @@
+import { THXClient } from '../clients';
+
+export default class BaseManager {
+    client!: THXClient;
+
+    constructor(client: THXClient) {
+        this.client = client;
+    }
+}
diff --git a/libs/sdk/src/lib/managers/CampaignManager.ts b/libs/sdk/src/lib/managers/CampaignManager.ts
new file mode 100644
index 000000000..2e5781c48
--- /dev/null
+++ b/libs/sdk/src/lib/managers/CampaignManager.ts
@@ -0,0 +1,28 @@
+import { THXAPIClient } from '../clients';
+import { THXAPIClientOptions, THXQuestCreateData, THXQuestSocialCreateData } from '../types';
+import BaseManager from './BaseManager';
+
+class CampaignManager extends BaseManager {
+    constructor(client: THXAPIClient) {
+        super(client);
+    }
+
+    get(id: string) {
+        return this.client.request.get(`/v1/pools/${id}`);
+    }
+
+    quests = {
+        social: {
+            create: (data: THXQuestCreateData & THXQuestSocialCreateData) => {
+                const { campaignId } = this.client.options as THXAPIClientOptions;
+                const contentMetadata = JSON.stringify(data.contentMetadata);
+
+                return this.client.request.post(`/v1/pools/${campaignId}/quests`, {
+                    data: { ...data, contentMetadata },
+                });
+            },
+        },
+    };
+}
+
+export default CampaignManager;
diff --git a/libs/sdk/src/lib/managers/CouponCodeManager.ts b/libs/sdk/src/lib/managers/CouponCodeManager.ts
new file mode 100644
index 000000000..5ca5cd1f7
--- /dev/null
+++ b/libs/sdk/src/lib/managers/CouponCodeManager.ts
@@ -0,0 +1,15 @@
+import { THXClient } from '../clients';
+import BaseManager from './BaseManager';
+
+class CouponCodeManager extends BaseManager {
+    constructor(client: THXClient) {
+        super(client);
+    }
+
+    async list(params: { chainId: string }) {
+        const obj = new URLSearchParams(params);
+        return await this.client.request.get(`/v1/coupon-rewards/payments?${obj.toString()}`);
+    }
+}
+
+export default CouponCodeManager;
diff --git a/libs/sdk/src/lib/managers/ERC1155Manager.ts b/libs/sdk/src/lib/managers/ERC1155Manager.ts
new file mode 100644
index 000000000..039cc8e70
--- /dev/null
+++ b/libs/sdk/src/lib/managers/ERC1155Manager.ts
@@ -0,0 +1,31 @@
+import { THXClient } from '../clients';
+import BaseManager from './BaseManager';
+
+class ERC1155Manager extends BaseManager {
+    constructor(client: THXClient) {
+        super(client);
+    }
+
+    async list(params: { chainId: string }) {
+        const obj = new URLSearchParams(params);
+        return await this.client.request.get(`/v1/erc1155/token?${obj.toString()}`);
+    }
+
+    async get(id: string) {
+        return await this.client.request.get(`/v1/erc1155/token/${id}`);
+    }
+
+    async getContract(id: string) {
+        return await this.client.request.get(`/v1/erc1155/${id}`);
+    }
+
+    async getMetadata(erc1155Id: string, metadataId: string) {
+        return await this.client.request.get(`/v1/erc1155/${erc1155Id}/metadata/${metadataId}`);
+    }
+
+    async transfer(config: { erc1155Id: string; erc1155TokenId: string; to: string }) {
+        return await this.client.request.post(`/v1/erc1155/transfer`, { data: JSON.stringify(config) });
+    }
+}
+
+export default ERC1155Manager;
diff --git a/libs/sdk/src/lib/managers/ERC20Manager.ts b/libs/sdk/src/lib/managers/ERC20Manager.ts
new file mode 100644
index 000000000..159d59822
--- /dev/null
+++ b/libs/sdk/src/lib/managers/ERC20Manager.ts
@@ -0,0 +1,28 @@
+import { THXClient } from '../clients';
+import BaseManager from './BaseManager';
+import { ChainId } from '../types/enums/ChainId';
+
+class ERC20Manager extends BaseManager {
+    constructor(client: THXClient) {
+        super(client);
+    }
+
+    async list(props: { chainId: string }) {
+        const params = new URLSearchParams(props);
+        return await this.client.request.get(`/v1/erc20/token?${params.toString()}`);
+    }
+
+    async get(id: string) {
+        return await this.client.request.get(`/v1/erc20/token/${id}`);
+    }
+
+    async getContract(id: string) {
+        return await this.client.request.get(`/v1/erc20/${id}`);
+    }
+
+    async transfer(config: { erc20Id: string; to: string; amount: string; chainId: ChainId }) {
+        return await this.client.request.post(`/v1/erc20/transfer`, { data: JSON.stringify(config) });
+    }
+}
+
+export default ERC20Manager;
diff --git a/libs/sdk/src/lib/managers/ERC721Manager.ts b/libs/sdk/src/lib/managers/ERC721Manager.ts
new file mode 100644
index 000000000..ee4dc22a2
--- /dev/null
+++ b/libs/sdk/src/lib/managers/ERC721Manager.ts
@@ -0,0 +1,31 @@
+import { THXClient } from '../clients';
+import BaseManager from './BaseManager';
+
+class ERC721Manager extends BaseManager {
+    constructor(client: THXClient) {
+        super(client);
+    }
+
+    async list(params: { chainId: string }) {
+        const obj = new URLSearchParams(params);
+        return await this.client.request.get(`/v1/erc721/token?${obj.toString()}`);
+    }
+
+    async get(id: string) {
+        return await this.client.request.get(`/v1/erc721/token/${id}`);
+    }
+
+    async getContract(id: string) {
+        return await this.client.request.get(`/v1/erc721/${id}`);
+    }
+
+    async getMetadata(erc721Id: string, metadataId: string) {
+        return await this.client.request.get(`/v1/erc721/${erc721Id}/metadata/${metadataId}`);
+    }
+
+    async transfer(config: { erc721Id: string; erc721TokenId: string; to: string }) {
+        return await this.client.request.post(`/v1/erc721/transfer`, { data: JSON.stringify(config) });
+    }
+}
+
+export default ERC721Manager;
diff --git a/libs/sdk/src/lib/managers/EventManager.ts b/libs/sdk/src/lib/managers/EventManager.ts
new file mode 100644
index 000000000..3cfcca645
--- /dev/null
+++ b/libs/sdk/src/lib/managers/EventManager.ts
@@ -0,0 +1,28 @@
+import { THXClient } from '../clients';
+import BaseManager from './BaseManager';
+
+class EventManager extends BaseManager {
+    constructor(client: THXClient) {
+        super(client);
+    }
+
+    async create(options: { event: string; identity: string }) {
+        const { event, identity } = options;
+
+        if (!event) {
+            throw new Error("Please provide an 'event' parameter.");
+        }
+        if (event.length > 50) {
+            throw new Error("Please provide an 'event' with a string length max 50 characters.");
+        }
+        if (!identity) {
+            throw new Error("Please provide an 'identity' parameter. Create it with 'client.identity.create()'.");
+        }
+
+        await this.client.request.post('/v1/events', {
+            data: JSON.stringify({ event, identityUuid: identity }),
+        });
+    }
+}
+
+export default EventManager;
diff --git a/libs/sdk/src/lib/managers/IdentityManager.ts b/libs/sdk/src/lib/managers/IdentityManager.ts
new file mode 100644
index 000000000..849ac167c
--- /dev/null
+++ b/libs/sdk/src/lib/managers/IdentityManager.ts
@@ -0,0 +1,18 @@
+import { THXClient } from '../clients';
+import BaseManager from './BaseManager';
+
+class IdentityManager extends BaseManager {
+    constructor(client: THXClient) {
+        super(client);
+    }
+
+    create() {
+        return this.client.request.post('/v1/identity');
+    }
+
+    get(salt: string) {
+        return this.client.request.get(`/v1/identity/${salt}`);
+    }
+}
+
+export default IdentityManager;
diff --git a/libs/sdk/src/lib/managers/OIDCManager.ts b/libs/sdk/src/lib/managers/OIDCManager.ts
new file mode 100644
index 000000000..4da22dca0
--- /dev/null
+++ b/libs/sdk/src/lib/managers/OIDCManager.ts
@@ -0,0 +1,123 @@
+import axios from 'axios';
+import { THXClient } from '../clients';
+import { THXOIDCConfig, THXOIDCUser } from '../types';
+import BaseManager from './BaseManager';
+
+export enum THXOIDCGrant {
+    AuthorizationCode = 'authorization_code',
+    ClientCredentials = 'client_credentials',
+}
+
+class OIDCManager extends BaseManager {
+    user: THXOIDCUser | null;
+    expiresAt: number;
+    grantType: THXOIDCGrant;
+
+    constructor(client: THXClient, grantType: THXOIDCGrant) {
+        super(client);
+
+        this.grantType = grantType;
+        this.expiresAt = Date.now();
+        this.user = null;
+    }
+
+    get isExpired() {
+        return Date.now() > this.expiresAt;
+    }
+
+    get isAuthenticated() {
+        return this.user && !this.isExpired;
+    }
+
+    get authUrl() {
+        return this.client.options.authUrl || 'https://auth.thx.network';
+    }
+
+    setUser(user: THXOIDCUser) {
+        this.user = user;
+        this.expiresAt = Date.now() + this.user.expires_in * 1000;
+    }
+
+    getUser() {
+        return this.user;
+    }
+
+    async authenticate() {
+        const initMap = {
+            [String(THXOIDCGrant.ClientCredentials)]: this.getClientCredentialsGrant.bind(this),
+            [String(THXOIDCGrant.AuthorizationCode)]: this.getAuthorizationCodeGrant.bind(this),
+        };
+        await initMap[this.grantType](this.client.options);
+    }
+
+    async redirectCallback() {
+        // Once user is redirected back to your application with the authorization code
+        const code = new URL(window.location.href).searchParams.get('code');
+        if (!code) throw new Error("Could not find 'code' search param in url.");
+
+        const { clientId, redirectUri } = this.client.options;
+        const data = {
+            code,
+            grant_type: THXOIDCGrant.AuthorizationCode,
+            client_id: clientId,
+            redirect_uri: redirectUri,
+        };
+
+        try {
+            const response = await axios(`${this.authUrl}/token`, {
+                method: 'POST',
+                headers: { 'Content-Type': 'application/json' },
+                data: JSON.stringify(data),
+            });
+
+            this.setUser(response.data);
+        } catch (error) {
+            throw new Error('Request for ' + THXOIDCGrant.ClientCredentials + ' grant failed.');
+        }
+    }
+
+    private async getClientCredentialsGrant({ clientId, clientSecret }: THXOIDCConfig) {
+        const authHeader = 'Basic ' + Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
+        const headers = {
+            'Content-Type': 'application/x-www-form-urlencoded',
+            'Authorization': authHeader,
+        };
+        const url = `${this.authUrl}/token`;
+        const params = new URLSearchParams({
+            grant_type: THXOIDCGrant.ClientCredentials,
+            scope: 'openid events:write identities:read identities:write pools:read pools:write',
+        });
+
+        try {
+            const response = await axios({
+                method: 'POST',
+                url,
+                headers,
+                data: params,
+            });
+
+            this.setUser(response.data);
+        } catch (error) {
+            console.log(error);
+            throw new Error('Request for ' + THXOIDCGrant.ClientCredentials + ' grant failed.');
+        }
+    }
+
+    private async getAuthorizationCodeGrant({ clientId, redirectUri }: THXOIDCConfig) {
+        if (!redirectUri) throw new Error("Please, set 'options.redirectUri'.");
+
+        const authorizationEndpoint = this.authUrl + '/authorize';
+        const scope =
+            'openid offline_access account:read account:write erc20:read erc721:read erc1155:read point_balances:read referral_rewards:read point_rewards:read wallets:read wallets:write pool_subscription:read pool_subscription:write claims:read';
+        const authUrl = new URL(authorizationEndpoint);
+
+        authUrl.searchParams.append('client_id', clientId);
+        authUrl.searchParams.append('redirect_uri', redirectUri);
+        authUrl.searchParams.append('scope', scope);
+        authUrl.searchParams.append('response_type', 'code');
+
+        window.location.href = authUrl.toString();
+    }
+}
+
+export default OIDCManager;
diff --git a/libs/sdk/src/lib/managers/PointBalanceManager.ts b/libs/sdk/src/lib/managers/PointBalanceManager.ts
new file mode 100644
index 000000000..206376a38
--- /dev/null
+++ b/libs/sdk/src/lib/managers/PointBalanceManager.ts
@@ -0,0 +1,14 @@
+import { THXClient } from '../clients';
+import BaseManager from './BaseManager';
+
+class PointBalanceManager extends BaseManager {
+    constructor(client: THXClient) {
+        super(client);
+    }
+
+    async list() {
+        return await this.client.request.get('/v1/point-balances');
+    }
+}
+
+export default PointBalanceManager;
diff --git a/libs/sdk/src/lib/managers/PoolManager.ts b/libs/sdk/src/lib/managers/PoolManager.ts
new file mode 100644
index 000000000..503f3faa3
--- /dev/null
+++ b/libs/sdk/src/lib/managers/PoolManager.ts
@@ -0,0 +1,34 @@
+import { THXClient } from '../clients';
+import BaseManager from './BaseManager';
+
+class PoolManager extends BaseManager {
+    constructor(client: THXClient) {
+        super(client);
+    }
+
+    async get(id: string) {
+        return await this.client.request.get(`/v1/pools/${id}`);
+    }
+
+    async getLeaderboard(id: string) {
+        return await this.client.request.get(`/v1/pools/${id}/analytics/leaderboard/client?platform=discord`);
+    }
+
+    subscription = {
+        get: async (id: string) => {
+            return await this.client.request.get(`/v1/pools/${id}/subscription`);
+        },
+
+        post: async (payload: { poolId: string; email: string }) => {
+            return await this.client.request.post(`/v1/pools/${payload.poolId}/subscription`, {
+                data: JSON.stringify({ email: payload.email }),
+            });
+        },
+
+        delete: async (id: string) => {
+            return await this.client.request.delete(`/v1/pools/${id}/subscription`);
+        },
+    };
+}
+
+export default PoolManager;
diff --git a/libs/sdk/src/lib/managers/QRCodeManager.ts b/libs/sdk/src/lib/managers/QRCodeManager.ts
new file mode 100644
index 000000000..387748c00
--- /dev/null
+++ b/libs/sdk/src/lib/managers/QRCodeManager.ts
@@ -0,0 +1,20 @@
+import { THXClient } from '../clients';
+import BaseManager from './BaseManager';
+
+class QRCodeManager extends BaseManager {
+    constructor(client: THXClient) {
+        super(client);
+    }
+
+    get(uuid: string) {
+        return this.client.request.get(`/v1/qr-codes/${uuid}`);
+    }
+
+    entry = {
+        create: (uuid: string) => {
+            return this.client.request.post(`/v1/qr-codes/${uuid}/entries`);
+        },
+    };
+}
+
+export default QRCodeManager;
diff --git a/libs/sdk/src/lib/managers/QuestManager.ts b/libs/sdk/src/lib/managers/QuestManager.ts
new file mode 100644
index 000000000..b2ce933f6
--- /dev/null
+++ b/libs/sdk/src/lib/managers/QuestManager.ts
@@ -0,0 +1,68 @@
+import { THXClient } from '../clients';
+import BaseManager from './BaseManager';
+
+class QuestManager extends BaseManager {
+    constructor(client: THXClient) {
+        super(client);
+    }
+
+    async list(poolId?: string) {
+        return await this.client.request.get(`/v1/quests`, { poolId });
+    }
+
+    daily = {
+        entry: {
+            create: async (id: string) => {
+                return await this.client.request.post(`/v1/quests/daily/${id}/entries`);
+            },
+        },
+    };
+
+    invite = {
+        entry: {
+            create: async (id: string, payload: { sub: string }) => {
+                return await this.client.request.post(`/v1/quests/invite/${id}/entries`, {
+                    data: JSON.stringify(payload),
+                });
+            },
+        },
+    };
+
+    social = {
+        entry: {
+            create: async (id: string) => {
+                return await this.client.request.post(`/v1/quests/social/${id}/entries`);
+            },
+        },
+    };
+
+    custom = {
+        entry: {
+            create: async (id: string) => {
+                return await this.client.request.post(`/v1/quests/custom/${id}/entries`);
+            },
+        },
+    };
+
+    web3 = {
+        entry: {
+            create: async (id: string, payload: { signature: string; message: string }) => {
+                return await this.client.request.post(`/v1/quests/web3/${id}/entries`, {
+                    data: JSON.stringify(payload),
+                });
+            },
+        },
+    };
+
+    gitcoin = {
+        entry: {
+            create: async (id: string, payload: { signature: string; message: string }) => {
+                return await this.client.request.post(`/v1/quests/gitcoin/${id}/entries`, {
+                    data: JSON.stringify(payload),
+                });
+            },
+        },
+    };
+}
+
+export default QuestManager;
diff --git a/libs/sdk/src/lib/managers/RequestManager.ts b/libs/sdk/src/lib/managers/RequestManager.ts
new file mode 100644
index 000000000..ec25d9653
--- /dev/null
+++ b/libs/sdk/src/lib/managers/RequestManager.ts
@@ -0,0 +1,104 @@
+import { THXClient } from '../clients';
+import { THXBrowserClientOptions, THXRequestConfig } from '../types';
+import * as jose from 'jose';
+import axios, { AxiosError, AxiosResponse } from 'axios';
+import OIDCManager, { THXOIDCGrant } from './OIDCManager';
+
+class RequestManager extends OIDCManager {
+    constructor(client: THXClient, grantType: THXOIDCGrant) {
+        super(client, grantType);
+    }
+
+    get(path: string, config?: THXRequestConfig) {
+        return this.request(path, { ...config, method: 'GET' });
+    }
+
+    post(path: string, config?: THXRequestConfig) {
+        return this.request(path, { ...config, method: 'POST' });
+    }
+
+    patch(path: string, config?: THXRequestConfig) {
+        return this.request(path, { ...config, method: 'PATCH' });
+    }
+
+    put(path: string, config?: THXRequestConfig) {
+        return this.request(path, { ...config, method: 'PUT' });
+    }
+
+    delete(path: string, config?: THXRequestConfig) {
+        return this.request(path, { ...config, method: 'DELETE' });
+    }
+
+    get apiUrl() {
+        return this.client.options.apiUrl || 'https://api.thx.network';
+    }
+
+    private async request(path: string, config: THXRequestConfig) {
+        // Check for user to exist and token not to be expired if auth is intended
+        const { clientId, clientSecret } = this.client.options;
+        if (!this.isAuthenticated && clientId && clientSecret) {
+            await this.authenticate();
+        }
+
+        const headers = this.getHeaders(config);
+        const url = `${this.apiUrl}${path}`;
+        try {
+            const response = await axios({ ...config, url, headers });
+
+            return await this.handleResponse(response);
+        } catch (error) {
+            return await this.handleError(error as AxiosError);
+        }
+    }
+
+    private getHeaders(config?: THXRequestConfig) {
+        const headers: Record<string, string> = {
+            'Accept': 'application/json',
+            'Content-Type': 'application/json',
+        };
+
+        if (this.user && this.user.access_token) {
+            const token = this.validateToken(this.user.access_token);
+            headers['Authorization'] = `Bearer ${token}`;
+        }
+
+        // Needs refactor in places where X-PoolId is as config.poolId or config header
+        const options = this.client.options as THXBrowserClientOptions;
+        if ((config && config.poolId) || (options && options.poolId)) {
+            headers['X-PoolId'] = (config && config.poolId) || (options && options.poolId);
+        }
+
+        return { ...headers, ...config?.headers };
+    }
+
+    private async handleResponse(r: AxiosResponse) {
+        // Return response data, but throw if HTTP status with the range of 400 - 600
+        if (r.status >= 400 && r.status < 600) {
+            throw r.data;
+        } else {
+            return r.data;
+        }
+    }
+
+    private async handleError(error: AxiosError) {
+        if (!error || !error.response) throw new Error('Could not parse failed response.');
+        throw error.response.data;
+    }
+
+    private validateToken(accessToken: string) {
+        if (!accessToken) throw new Error("Please, provide an 'accessToken'.");
+
+        try {
+            const { exp } = jose.decodeJwt(accessToken);
+            if (!exp || Date.now() > Number(exp) * 1000) {
+                throw new Error('The token has expired.');
+            }
+
+            return accessToken;
+        } catch (error) {
+            throw new Error("Failed to validate this 'accessToken'.");
+        }
+    }
+}
+
+export default RequestManager;
diff --git a/libs/sdk/src/lib/managers/RewardManager.ts b/libs/sdk/src/lib/managers/RewardManager.ts
new file mode 100644
index 000000000..08c358fdb
--- /dev/null
+++ b/libs/sdk/src/lib/managers/RewardManager.ts
@@ -0,0 +1,73 @@
+import { THXClient } from '../clients';
+import BaseManager from './BaseManager';
+
+class RewardManager extends BaseManager {
+    constructor(client: THXClient) {
+        super(client);
+    }
+
+    async list(poolId?: string) {
+        return await this.client.request.get(`/v1/rewards`, { poolId });
+    }
+
+    coin = {
+        get: async (uuid: string) => {
+            return await this.client.request.get(`/v1/rewards/coin/${uuid}`);
+        },
+        redemption: {
+            post: async (uuid: string) => {
+                return await this.client.request.post(`/v1/rewards/coin/${uuid}/redemption`);
+            },
+        },
+    };
+
+    nft = {
+        get: async (uuid: string) => {
+            return await this.client.request.get(`/v1/rewards/nft/${uuid}`);
+        },
+        redemption: {
+            post: async (uuid: string) => {
+                return await this.client.request.post(`/v1/rewards/nft/${uuid}/redemption`);
+            },
+        },
+        payment: {
+            post: async (uuid: string) => {
+                return await this.client.request.post(`/v1/rewards/nft/${uuid}/payment`);
+            },
+        },
+    };
+
+    custom = {
+        get: async (uuid: string) => {
+            return await this.client.request.get(`/v1/rewards/custom/${uuid}`);
+        },
+        redemption: {
+            post: async (uuid: string) => {
+                return await this.client.request.post(`/v1/rewards/custom/${uuid}/redemption`);
+            },
+        },
+        payment: {
+            post: async (uuid: string) => {
+                return await this.client.request.post(`/v1/rewards/custom/${uuid}/payment`);
+            },
+        },
+    };
+
+    coupon = {
+        get: async (uuid: string) => {
+            return await this.client.request.get(`/v1/rewards/coupon/${uuid}`);
+        },
+        redemption: {
+            post: async (uuid: string) => {
+                return await this.client.request.post(`/v1/rewards/coupon/${uuid}/redemption`);
+            },
+        },
+        payment: {
+            post: async (uuid: string) => {
+                return await this.client.request.post(`/v1/rewards/coupon/${uuid}/payment`);
+            },
+        },
+    };
+}
+
+export default RewardManager;
diff --git a/libs/sdk/src/lib/types/enums/ChainId.ts b/libs/sdk/src/lib/types/enums/ChainId.ts
new file mode 100644
index 000000000..bac2d4548
--- /dev/null
+++ b/libs/sdk/src/lib/types/enums/ChainId.ts
@@ -0,0 +1,10 @@
+export enum ChainId {
+    Ethereum = 1,
+    Arbitrum = 42161,
+    BNBChain = 56,
+    Hardhat = 31337,
+    PolygonMumbai = 80001,
+    Polygon = 137,
+    PolygonZK = 1101,
+    Linea = 59144,
+}
diff --git a/libs/sdk/src/lib/types/enums/Quests.ts b/libs/sdk/src/lib/types/enums/Quests.ts
new file mode 100644
index 000000000..24c33349e
--- /dev/null
+++ b/libs/sdk/src/lib/types/enums/Quests.ts
@@ -0,0 +1,24 @@
+export enum QuestRequirement {
+    YouTubeLike = 0,
+    YouTubeSubscribe = 1,
+    TwitterLike = 2,
+    TwitterRetweet = 3,
+    TwitterFollow = 4,
+    DiscordGuildJoined = 5,
+    TwitterQuery = 6,
+    TwitterLikeRetweet = 7,
+    DiscordMessage = 8,
+    DiscordMessageReaction = 9,
+}
+
+export enum QuestVariant {
+    Daily = 0,
+    Invite = 1,
+    Twitter = 2,
+    Discord = 3,
+    YouTube = 4,
+    Custom = 5,
+    Web3 = 6,
+    Gitcoin = 7,
+    Webhook = 8,
+}
diff --git a/libs/sdk/src/lib/types/enums/index.ts b/libs/sdk/src/lib/types/enums/index.ts
new file mode 100644
index 000000000..552effb64
--- /dev/null
+++ b/libs/sdk/src/lib/types/enums/index.ts
@@ -0,0 +1,2 @@
+export * from './ChainId';
+export * from './Quests';
diff --git a/libs/sdk/src/lib/types/index.ts b/libs/sdk/src/lib/types/index.ts
new file mode 100644
index 000000000..90f25ec88
--- /dev/null
+++ b/libs/sdk/src/lib/types/index.ts
@@ -0,0 +1,93 @@
+import { AxiosRequestConfig } from 'axios';
+import { QuestVariant } from './enums';
+
+type THXQuestSocialYouTubePreviewData = {
+    //
+};
+
+type THXQuestSocialDiscordPreviewData = {
+    serverId: string;
+    inviteURL: string;
+    limit: number;
+    days: number;
+    channels: number;
+};
+
+type THXQuestSocialTwitterPreviewData = {
+    minFollowersCount: number;
+    url: string;
+    username: string;
+    name: string;
+    text: string;
+    id: string;
+    profileImgUrl: string;
+};
+
+type THXQuestSocialCreateData = {
+    amount: number;
+    interaction: number;
+    content: string;
+    contentMetadata:
+        | Partial<THXQuestSocialTwitterPreviewData>
+        | Partial<THXQuestSocialDiscordPreviewData>
+        | Partial<THXQuestSocialYouTubePreviewData>;
+};
+
+type THXQuestCreateData = {
+    variant: QuestVariant;
+    isPublished: boolean;
+    title: string;
+    description?: string;
+    image?: string;
+    expiryDate?: Date;
+};
+
+type THXAPIClientOptions = {
+    apiUrl?: string;
+    campaignId?: string;
+} & THXOIDCConfig;
+
+type THXBrowserClientOptions = {
+    apiUrl?: string;
+    poolId: string;
+} & THXOIDCConfig;
+
+type THXRequestConfig = {
+    poolId?: string;
+} & AxiosRequestConfig;
+
+type THXOIDCConfig = {
+    authUrl?: string;
+    clientId: string;
+    clientSecret: string;
+    redirectUri?: string;
+};
+
+type THXOIDCUser = {
+    access_token: string;
+    expires_in: number;
+    token_type: string;
+    scope: string;
+};
+
+type THXWidgetOptions = {
+    campaignId?: string;
+    poolId?: string;
+    apiUrl?: string;
+    identity?: string;
+    containerSelector?: string;
+};
+
+export type {
+    THXQuestSocialYouTubePreviewData,
+    THXQuestSocialDiscordPreviewData,
+    THXQuestSocialTwitterPreviewData,
+    THXQuestCreateData,
+    THXQuestSocialCreateData,
+    THXWidgetOptions,
+    THXAPIClientOptions,
+    THXBrowserClientOptions,
+    THXOIDCConfig,
+    THXOIDCUser,
+    THXRequestConfig,
+};
diff --git a/libs/sdk/tsconfig.json b/libs/sdk/tsconfig.json
new file mode 100644
index 000000000..62a328e94
--- /dev/null
+++ b/libs/sdk/tsconfig.json
@@ -0,0 +1,19 @@
+{
+    "extends": "../../tsconfig.base.json",
+    "compilerOptions": {
+        "module": "commonjs",
+        "forceConsistentCasingInFileNames": true,
+        "strict": true,
+        "noImplicitOverride": true,
+        "noPropertyAccessFromIndexSignature": true,
+        "noImplicitReturns": true,
+        "noFallthroughCasesInSwitch": true
+    },
+    "files": [],
+    "include": [],
+    "references": [
+        {
+            "path": "./tsconfig.lib.json"
+        }
+    ]
+}
diff --git a/libs/sdk/tsconfig.lib.json b/libs/sdk/tsconfig.lib.json
new file mode 100644
index 000000000..1912016fb
--- /dev/null
+++ b/libs/sdk/tsconfig.lib.json
@@ -0,0 +1,10 @@
+{
+    "extends": "./tsconfig.json",
+    "compilerOptions": {
+        "outDir": "../../dist/out-tsc",
+        "declaration": true,
+        "types": ["node"]
+    },
+    "include": ["src/**/*.ts"],
+    "exclude": []
+}
diff --git a/migrations.json b/migrations.json
deleted file mode 100644
index f172bd2cf..000000000
--- a/migrations.json
+++ /dev/null
@@ -1,334 +0,0 @@
-{
-    "migrations": [
-        {
-            "version": "15.7.0-beta.0",
-            "description": "Split global configuration files into individual project.json files. This migration has been added automatically to the beginning of your migration set to retroactively make them work with the new version of Nx.",
-            "cli": "nx",
-            "implementation": "./src/migrations/update-15-7-0/split-configuration-into-project-json-files",
-            "package": "@nx/workspace",
-            "name": "15-7-0-split-configuration-into-project-json-files"
-        },
-        {
-            "cli": "nx",
-            "version": "15.0.0-beta.1",
-            "description": "Replace implicitDependencies with namedInputs + target inputs",
-            "implementation": "./src/migrations/update-15-0-0/migrate-to-inputs",
-            "package": "nx",
-            "name": "15.0.0-migrate-to-inputs"
-        },
-        {
-            "cli": "nx",
-            "version": "15.0.0-beta.1",
-            "description": "Prefix outputs with {workspaceRoot}/{projectRoot} if needed",
-            "implementation": "./src/migrations/update-15-0-0/prefix-outputs",
-            "package": "nx",
-            "name": "15.0.0-prefix-outputs"
-        },
-        {
-            "cli": "nx",
-            "version": "15.0.12-beta.1",
-            "description": "Set project names in project.json files",
-            "implementation": "./src/migrations/update-15-1-0/set-project-names",
-            "package": "nx",
-            "name": "15.1.0-set-project-names"
-        },
-        {
-            "cli": "nx",
-            "version": "15.8.2-beta.0",
-            "description": "Updates the nx wrapper.",
-            "implementation": "./src/migrations/update-15-8-2/update-nxw",
-            "package": "nx",
-            "name": "15.8.2-update-nx-wrapper"
-        },
-        {
-            "cli": "nx",
-            "version": "16.0.0-beta.0",
-            "description": "Remove @nrwl/cli.",
-            "implementation": "./src/migrations/update-16-0-0/remove-nrwl-cli",
-            "package": "nx",
-            "name": "16.0.0-remove-nrwl-cli"
-        },
-        {
-            "cli": "nx",
-            "version": "16.0.0-beta.9",
-            "description": "Replace `dependsOn.projects` and `inputs` definitions with new configuration format.",
-            "implementation": "./src/migrations/update-16-0-0/update-depends-on-to-tokens",
-            "package": "nx",
-            "name": "16.0.0-tokens-for-depends-on"
-        },
-        {
-            "cli": "nx",
-            "version": "16.0.0-beta.0",
-            "description": "Replace @nrwl/nx-cloud with nx-cloud",
-            "implementation": "./src/migrations/update-16-0-0/update-nx-cloud-runner",
-            "package": "nx",
-            "name": "16.0.0-update-nx-cloud-runner"
-        },
-        {
-            "cli": "nx",
-            "version": "16.2.0-beta.0",
-            "description": "Remove outputPath from run commands",
-            "implementation": "./src/migrations/update-16-2-0/remove-run-commands-output-path",
-            "package": "nx",
-            "name": "16.2.0-remove-output-path-from-run-commands"
-        },
-        {
-            "cli": "nx",
-            "version": "16.6.0-beta.6",
-            "description": "Prefix outputs with {workspaceRoot}/{projectRoot} if needed",
-            "implementation": "./src/migrations/update-15-0-0/prefix-outputs",
-            "package": "nx",
-            "name": "16.6.0-prefix-outputs"
-        },
-        {
-            "cli": "nx",
-            "version": "16.8.0-beta.3",
-            "description": "Escape $ in env variables",
-            "implementation": "./src/migrations/update-16-8-0/escape-dollar-sign-env-variables",
-            "package": "nx",
-            "name": "16.8.0-escape-dollar-sign-env"
-        },
-        {
-            "cli": "nx",
-            "version": "17.0.0-beta.1",
-            "description": "Updates the default cache directory to .nx/cache",
-            "implementation": "./src/migrations/update-17-0-0/move-cache-directory",
-            "package": "nx",
-            "name": "17.0.0-move-cache-directory"
-        },
-        {
-            "cli": "nx",
-            "version": "17.0.0-beta.3",
-            "description": "Use minimal config for tasksRunnerOptions",
-            "implementation": "./src/migrations/update-17-0-0/use-minimal-config-for-tasks-runner-options",
-            "package": "nx",
-            "name": "17.0.0-use-minimal-config-for-tasks-runner-options"
-        },
-        {
-            "version": "17.0.0-rc.1",
-            "description": "Migration for v17.0.0-rc.1",
-            "implementation": "./src/migrations/update-17-0-0/rm-default-collection-npm-scope",
-            "package": "nx",
-            "name": "rm-default-collection-npm-scope"
-        },
-        {
-            "cli": "nx",
-            "version": "17.3.0-beta.6",
-            "description": "Updates the nx wrapper.",
-            "implementation": "./src/migrations/update-17-3-0/update-nxw",
-            "package": "nx",
-            "name": "17.3.0-update-nx-wrapper"
-        },
-        {
-            "cli": "nx",
-            "version": "18.0.0-beta.2",
-            "description": "Updates .env to disabled adding plugins when generating projects in an existing Nx workspace",
-            "implementation": "./src/migrations/update-18-0-0/disable-crystal-for-existing-workspaces",
-            "x-repair-skip": true,
-            "package": "nx",
-            "name": "18.0.0-disable-adding-plugins-for-existing-workspaces"
-        },
-        {
-            "version": "15.7.0-beta.0",
-            "description": "Split global configuration files (e.g., workspace.json) into individual project.json files.",
-            "cli": "nx",
-            "implementation": "./src/migrations/update-15-7-0/split-configuration-into-project-json-files",
-            "package": "@nx/workspace",
-            "name": "15-7-0-split-configuration-into-project-json-files"
-        },
-        {
-            "cli": "nx",
-            "version": "16.0.0-beta.1",
-            "description": "Replace @nx/workspace with @nx/workspace",
-            "implementation": "./src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages",
-            "package": "@nx/workspace",
-            "name": "update-16-0-0-add-nx-packages"
-        },
-        {
-            "version": "16.0.0-beta.4",
-            "description": "Generates a plugin called 'workspace-plugin' containing your workspace generators.",
-            "cli": "nx",
-            "implementation": "./src/migrations/update-16-0-0/move-workspace-generators-to-local-plugin",
-            "package": "@nx/workspace",
-            "name": "16-0-0-move-workspace-generators-into-local-plugin"
-        },
-        {
-            "version": "16.0.0-beta.9",
-            "description": "Fix .babelrc presets if it contains an invalid entry for @nx/web/babel.",
-            "cli": "nx",
-            "implementation": "./src/migrations/update-16-0-0/fix-invalid-babelrc",
-            "package": "@nx/workspace",
-            "name": "16-0-0-fix-invalid-babelrc"
-        },
-        {
-            "cli": "nx",
-            "version": "16.0.0-beta.1",
-            "description": "Replace @nx/eslint-plugin with @nx/eslint-plugin",
-            "implementation": "./src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages",
-            "package": "@nx/eslint-plugin",
-            "name": "update-16-0-0-add-nx-packages"
-        },
-        {
-            "cli": "nx",
-            "version": "17.2.6-beta.1",
-            "description": "Rename workspace rules from @nx/workspace/name to @nx/workspace-name",
-            "implementation": "./src/migrations/update-17-2-6-rename-workspace-rules/rename-workspace-rules",
-            "package": "@nx/eslint-plugin",
-            "name": "update-17-2-6-rename-workspace-rules"
-        },
-        {
-            "cli": "nx",
-            "version": "15.0.0-beta.0",
-            "description": "Stop hashing eslint config files for build targets and dependent tasks",
-            "factory": "./src/migrations/update-15-0-0/add-eslint-inputs",
-            "package": "@nx/eslint",
-            "name": "add-eslint-inputs"
-        },
-        {
-            "cli": "nx",
-            "version": "15.7.1-beta.0",
-            "description": "Add node_modules to root eslint ignore",
-            "factory": "./src/migrations/update-15-7-1/add-eslint-ignore",
-            "package": "@nx/eslint",
-            "name": "add-eslint-ignore"
-        },
-        {
-            "cli": "nx",
-            "version": "16.0.0-beta.1",
-            "description": "Replace @nx/eslint with @nx/eslint",
-            "implementation": "./src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages",
-            "package": "@nx/eslint",
-            "name": "update-16-0-0-add-nx-packages"
-        },
-        {
-            "version": "16.8.0",
-            "description": "update-16-8-0-add-ignored-files",
-            "implementation": "./src/migrations/update-16-8-0-add-ignored-files/update-16-8-0-add-ignored-files",
-            "package": "@nx/eslint",
-            "name": "update-16-8-0-add-ignored-files"
-        },
-        {
-            "version": "17.0.0-beta.7",
-            "description": "update-17-0-0-rename-to-eslint",
-            "implementation": "./src/migrations/update-17-0-0-rename-to-eslint/update-17-0-0-rename-to-eslint",
-            "package": "@nx/eslint",
-            "name": "update-17-0-0-rename-to-eslint"
-        },
-        {
-            "version": "17.1.0-beta.1",
-            "description": "Updates for @typescript-utils/utils v6.9.1+",
-            "implementation": "./src/migrations/update-17-1-0/update-typescript-eslint",
-            "package": "@nx/eslint",
-            "name": "update-typescript-eslint"
-        },
-        {
-            "version": "17.2.0-beta.0",
-            "description": "Simplify eslintFilePatterns",
-            "implementation": "./src/migrations/update-17-2-0/simplify-eslint-patterns",
-            "package": "@nx/eslint",
-            "name": "simplify-eslint-patterns"
-        },
-        {
-            "version": "17.2.9",
-            "description": "Move executor options to target defaults",
-            "implementation": "./src/migrations/update-17-2-9/move-options-to-target-defaults",
-            "package": "@nx/eslint",
-            "name": "move-options-to-target-defaults"
-        },
-        {
-            "cli": "nx",
-            "version": "15.1.0-beta.0",
-            "description": "Update to Cypress v11. This migration will only update if the workspace is already on v10. https://www.cypress.io/blog/2022/11/04/upcoming-changes-to-component-testing/",
-            "factory": "./src/migrations/update-15-1-0/cypress-11",
-            "package": "@nx/cypress",
-            "name": "update-to-cypress-11"
-        },
-        {
-            "cli": "nx",
-            "version": "15.5.0-beta.0",
-            "description": "Update to Cypress v12. Cypress 12 contains a handful of breaking changes that might causes tests to start failing that nx cannot directly fix. Read more Cypress 12 changes: https://docs.cypress.io/guides/references/migration-guide#Migrating-to-Cypress-12-0.This migration will only run if you are already using Cypress v11.",
-            "factory": "./src/migrations/update-15-5-0/update-to-cypress-12",
-            "package": "@nx/cypress",
-            "name": "update-to-cypress-12"
-        },
-        {
-            "cli": "nx",
-            "version": "16.0.0-beta.1",
-            "description": "Replace @nx/cypress with @nx/cypress",
-            "implementation": "./src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages",
-            "package": "@nx/cypress",
-            "name": "update-16-0-0-add-nx-packages"
-        },
-        {
-            "cli": "nx",
-            "version": "16.2.0-beta.0",
-            "description": "Normalize tsconfig.cy.json files to be located at '<projectRoot>/cypress/tsconfig.json'",
-            "implementation": "./src/migrations/update-16-2-0/update-cy-tsconfig",
-            "package": "@nx/cypress",
-            "name": "update-16-2-0-normalize-tsconfigs"
-        },
-        {
-            "cli": "nx",
-            "version": "16.4.0-beta.10",
-            "description": "Remove tsconfig.e2e.json and add settings to project tsconfig.json. tsConfigs executor option is now deprecated. The project level tsconfig.json file should be used instead.",
-            "implementation": "./src/migrations/update-16-4-0/tsconfig-sourcemaps",
-            "package": "@nx/cypress",
-            "name": "update-16-3-0-remove-old-tsconfigs"
-        },
-        {
-            "cli": "nx",
-            "version": "16.8.0-beta.4",
-            "description": "Update to Cypress v13. Most noteable change is video recording is off by default. This migration will only update if the workspace is already on Cypress v12. https://docs.cypress.io/guides/references/migration-guide#Migrating-to-Cypress-130",
-            "implementation": "./src/migrations/update-16-8-0/cypress-13",
-            "package": "@nx/cypress",
-            "name": "update-16-8-0-cypress-13"
-        },
-        {
-            "version": "15.0.0-beta.0",
-            "cli": "nx",
-            "description": "Stop hashing jest spec files and config files for build targets and dependent tasks",
-            "factory": "./src/migrations/update-15-0-0/add-jest-inputs",
-            "package": "@nx/jest",
-            "name": "add-jest-inputs"
-        },
-        {
-            "version": "15.8.0-beta.0",
-            "cli": "nx",
-            "description": "Update jest configs to support jest 29 changes (https://jestjs.io/docs/upgrading-to-jest29)",
-            "factory": "./src/migrations/update-15-8-0/update-configs-jest-29",
-            "package": "@nx/jest",
-            "name": "update-configs-jest-29"
-        },
-        {
-            "version": "15.8.0-beta.0",
-            "cli": "nx",
-            "description": "Update jest test files to support jest 29 changes (https://jestjs.io/docs/upgrading-to-jest29)",
-            "factory": "./src/migrations/update-15-8-0/update-tests-jest-29",
-            "package": "@nx/jest",
-            "name": "update-tests-jest-29"
-        },
-        {
-            "cli": "nx",
-            "version": "16.0.0-beta.1",
-            "description": "Replace @nx/jest with @nx/jest",
-            "implementation": "./src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages",
-            "package": "@nx/jest",
-            "name": "update-16-0-0-add-nx-packages"
-        },
-        {
-            "cli": "nx",
-            "version": "16.5.0-beta.2",
-            "description": "Add test-setup.ts to ignored files in production input",
-            "implementation": "./src/migrations/update-16-5-0/add-test-setup-to-inputs-ignore",
-            "package": "@nx/jest",
-            "name": "add-test-setup-to-inputs-ignore"
-        },
-        {
-            "version": "17.1.0-beta.2",
-            "description": "Move jest executor options to nx.json targetDefaults",
-            "implementation": "./src/migrations/update-17-1-0/move-options-to-target-defaults",
-            "package": "@nx/jest",
-            "name": "move-options-to-target-defaults"
-        }
-    ]
-}
diff --git a/newrelic.js b/newrelic.js
new file mode 100644
index 000000000..4e76b4b3e
--- /dev/null
+++ b/newrelic.js
@@ -0,0 +1,36 @@
+'use strict';
+/**
+ * New Relic agent configuration.
+ *
+ * See lib/config/default.js in the agent distribution for a more complete
+ * description of configuration variables and their potential values.
+ * https://docs.newrelic.com/docs/apm/agents/nodejs-agent/installation-configuration/nodejs-agent-configuration/
+ *
+ * Please be aware this file is shared between API and AUTH projects. Since this file mostly serves to disable
+ * New Relic on local runs and configuration during production runs is done through env vars this should not
+ * lead to issues.
+ */
+exports.config = {
+    agent_enabled: false,
+    logging: {
+        enabled: false,
+    },
+    rules: {
+        ignore: ['v1/ping'],
+    },
+    allow_all_headers: true,
+    attributes: {
+        exclude: [
+            'request.headers.cookie',
+            'request.headers.authorization',
+            'request.headers.proxyAuthorization',
+            'request.headers.setCookie*',
+            'request.headers.x*',
+            'response.headers.cookie',
+            'response.headers.authorization',
+            'response.headers.proxyAuthorization',
+            'response.headers.setCookie*',
+            'response.headers.x*',
+        ],
+    },
+};
diff --git a/nx.json b/nx.json
index 663d93427..c9b3d5ee1 100644
--- a/nx.json
+++ b/nx.json
@@ -1,8 +1,5 @@
 {
     "$schema": "./node_modules/nx/schemas/nx-schema.json",
-    "affected": {
-        "defaultBase": "main"
-    },
     "targetDefaults": {
         "build": {
             "dependsOn": ["^build"],
@@ -37,6 +34,16 @@
         "e2e": {
             "cache": true,
             "inputs": ["default", "^production"]
+        },
+        "@nx/js:tsc": {
+            "cache": true,
+            "dependsOn": ["^build"],
+            "inputs": ["production", "^production"]
+        },
+        "@nx/webpack:webpack": {
+            "cache": true,
+            "dependsOn": ["^build"],
+            "inputs": ["production", "^production"]
         }
     },
     "namedInputs": {
@@ -64,7 +71,7 @@
             }
         }
     ],
-    "defaultProject": "campaign",
+    "defaultProject": "app",
     "generators": {
         "@nx/web:application": {
             "style": "scss",
@@ -72,5 +79,6 @@
             "unitTestRunner": "vitest",
             "e2eTestRunner": "playwright"
         }
-    }
+    },
+    "defaultBase": "main"
 }
diff --git a/package.json b/package.json
index ad35e6cb6..1fee500d6 100644
--- a/package.json
+++ b/package.json
@@ -5,44 +5,89 @@
     "scripts": {
         "start": "nx serve",
         "build": "nx build",
-        "test": "nx test",
-        "serve:app": "nx serve campaign"
+        "build:safe": "docker compose exec txs-web python manage.py insert_safe_master_copy --address \"0xd916a690676e925Ac9Faf2d01869c13Fd9757ef2\"",
+        "ngrok": "~/ngrok http --domain=local.auth.thx.network https://localhost:3030  > /dev/null &",
+        "hardhat": "nx run api:hardhat",
+        "hardhat-deploy": "nx run api:hardhat-deploy",
+        "test": "NODE_OPTIONS='--max-old-space-size=8192' nx test",
+        "serve:docker": "docker-compose --env-file .env.example -f docker-compose.yml -f docker-compose.safe.yml up --build -d",
+        "serve:app": "nx serve app",
+        "serve:api": "NODE_TLS_REJECT_UNAUTHORIZED='0' nx serve api",
+        "serve:auth": "NODE_TLS_REJECT_UNAUTHORIZED='0' nx serve auth"
     },
     "private": true,
     "dependencies": {
+        "@aws-sdk/client-s3": "^3.576.0",
+        "@aws-sdk/client-ses": "^3.576.0",
         "@balancer-labs/sdk": "^1.1.5",
         "@balancer/sdk": "^0.14.0",
         "@ethersproject/wallet": "^5.7.0",
+        "@gala-chain/client": "^1.1.20",
+        "@godaddy/terminus": "^4.12.1",
+        "@hokify/agenda": "^6.3.0",
+        "@hubspot/api-client": "^11.1.0",
+        "@nomicfoundation/hardhat-toolbox": "2.0.2",
+        "@openzeppelin/contracts": "3.4.2",
+        "@openzeppelin/defender-relay-client": "^1.54.2",
+        "@pinata/sdk": "^2.1.0",
         "@popperjs/core": "^2.11.6",
         "@safe-global/api-kit": "^1.1.0",
-        "@safe-global/protocol-kit": "^1.0.1",
+        "@safe-global/protocol-kit": "1.2.0",
         "@safe-global/safe-core-sdk-types": "^1.9.2",
         "@sentry/vue": "^7.71.0",
-        "@thxnetwork/sdk": "1.3.5",
         "@tkey/default": "10.1.0",
         "@tkey/security-questions": "10.1.0",
         "@tkey/web-storage": "10.1.0",
+        "@types/multer": "^1.4.11",
+        "@types/oidc-provider": "^7.14.0",
         "@vuepic/vue-datepicker": "7.2.0",
         "@wagmi/connectors": "^4.1.14",
         "@wagmi/core": "^2.6.5",
         "@walletconnect/modal": "^2.6.2",
         "@web3modal/wagmi": "^4.0.9",
+        "alchemy-sdk": "^3.3.1",
         "axios": "^1.6.5",
+        "axios-better-stacktrace": "^2.1.6",
+        "bcrypt": "^5.1.1",
         "bootstrap": "5.3",
         "bootstrap-vue-next": "^0.14.10",
         "chart.js": "^4.4.0",
         "color": "^4.2.3",
         "core-js": "^3.6.5",
         "date-fns": "^2.29.3",
+        "discord.js": "^14.15.2",
         "ethers": "5.7.2",
+        "express": "~4.18.1",
+        "express-async-errors": "^3.1.1",
+        "express-ejs-layouts": "^2.5.1",
+        "express-jwt": "^8.4.1",
+        "express-jwt-permissions": "^1.3.7",
+        "express-rate-limit": "^7.2.0",
+        "express-validator": "^7.0.1",
+        "fabric-ca-client": "^2.2.20",
+        "fabric-network": "^2.2.20",
+        "googleapis": "^137.1.0",
+        "helmet": "^7.1.0",
         "jose": "^4.14.4",
+        "jszip": "^3.10.1",
+        "jwks-rsa": "^3.1.0",
+        "lusca": "^1.7.0",
+        "magic-bytes.js": "^1.10.0",
         "marked": "^12.0.2",
         "mixpanel-browser": "^2.45.0",
+        "mongoose": "^8.3.5",
+        "morgan": "^1.10.0",
+        "morgan-body": "^2.6.9",
+        "multer": "^1.4.5-lts.1",
+        "newrelic": "^11.17.0",
         "oidc-client-ts": "^2.2.4",
+        "oidc-provider": "7.14.3",
         "pinia": "^2.0.23",
         "promise-poller": "^1.9.1",
         "protobufjs": "^7.2.4",
+        "short-uuid": "^5.2.0",
         "tslib": "^2.3.0",
+        "unique-username-generator": "^1.3.0",
         "viem": "^2.7.13",
         "vue": "^3.3.4",
         "vue-chart-3": "^3.1.8",
@@ -51,35 +96,59 @@
         "vue3-clipboard": "^1.0.0",
         "vue3-toastify": "^0.1.11",
         "web3-utils": "^1.8.2",
+        "winston": "^3.13.0",
         "yarn": "^1.22.21"
     },
     "devDependencies": {
+        "@ethersproject/abi": "^5.4.7",
+        "@ethersproject/providers": "^5.4.7",
+        "@gnosis.pm/safe-contracts": "1.3.0",
+        "@nomicfoundation/hardhat-chai-matchers": "^1.0.0",
+        "@nomicfoundation/hardhat-network-helpers": "^1.0.0",
+        "@nomiclabs/hardhat-ethers": "^2.0.0",
+        "@nomiclabs/hardhat-etherscan": "^3.0.0",
+        "@nrwl/node": "^19.0.4",
         "@nx-plus/vue": "^14.1.0",
-        "@nx/cypress": "18.0.8",
-        "@nx/devkit": "18.0.8",
-        "@nx/eslint": "18.0.8",
-        "@nx/eslint-plugin": "18.0.8",
-        "@nx/jest": "18.0.8",
-        "@nx/js": "18.0.8",
-        "@nx/playwright": "18.0.8",
-        "@nx/vite": "18.0.8",
-        "@nx/web": "18.0.8",
-        "@nx/workspace": "18.0.8",
+        "@nx/cypress": "18.3.0",
+        "@nx/devkit": "18.3.0",
+        "@nx/esbuild": "18.3.0",
+        "@nx/eslint": "18.3.0",
+        "@nx/eslint-plugin": "18.3.0",
+        "@nx/jest": "18.3.0",
+        "@nx/js": "18.3.0",
+        "@nx/node": "18.3.0",
+        "@nx/playwright": "18.3.0",
+        "@nx/vite": "18.3.0",
+        "@nx/web": "18.3.0",
+        "@nx/webpack": "18.3.0",
+        "@nx/workspace": "18.3.0",
         "@playwright/test": "^1.36.0",
+        "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
         "@sentry/vite-plugin": "^2.7.1",
+        "@svgr/webpack": "^8.0.1",
         "@swc-node/register": "~1.8.0",
         "@swc/core": "~1.3.85",
         "@swc/helpers": "~0.5.2",
+        "@typechain/ethers-v5": "^10.1.0",
+        "@typechain/hardhat": "^6.1.2",
+        "@types/chai": "^4.2.0",
         "@types/color": "^3.0.3",
+        "@types/compression": "^1.7.5",
+        "@types/ejs": "^3.1.5",
+        "@types/express": "~4.17.13",
         "@types/jest": "29.4.4",
+        "@types/lusca": "^1.7.5",
+        "@types/migrate-mongo": "^10.0.4",
         "@types/mixpanel-browser": "^2.38.0",
-        "@types/node": "18.16.9",
+        "@types/mocha": ">=9.1.0",
+        "@types/node": "~18.16.9",
+        "@types/supertest": "^6.0.2",
         "@types/uuid": "^9.0.1",
-        "@typescript-eslint/eslint-plugin": "6.21.0",
-        "@typescript-eslint/parser": "6.21.0",
+        "@typescript-eslint/eslint-plugin": "7.9.0",
+        "@typescript-eslint/parser": "7.9.0",
         "@vitejs/plugin-vue": "^5.0.4",
-        "@vitest/coverage-v8": "^1.0.4",
-        "@vitest/ui": "^1.3.1",
+        "@vitest/coverage-v8": "1.6.0",
+        "@vitest/ui": "1.6.0",
         "@vue/cli-plugin-babel": "~4.5.0",
         "@vue/cli-plugin-typescript": "~4.5.0",
         "@vue/cli-service": "~4.5.0",
@@ -88,35 +157,47 @@
         "@vue/eslint-config-typescript": "^5.0.2",
         "@vue/test-utils": "^2.0.0-0",
         "c8": "^7.12.0",
-        "cypress": "13.6.4",
-        "eslint": "8.48.0",
+        "canvas": "^2.11.2",
+        "chai": "^4.2.0",
+        "cypress": "^13.6.6",
+        "esbuild": "^0.19.2",
+        "eslint": "8.57.0",
         "eslint-config-prettier": "9.1.0",
         "eslint-plugin-cypress": "2.15.1",
         "eslint-plugin-import": "^2.26.0",
         "eslint-plugin-playwright": "^0.15.3",
         "eslint-plugin-prettier": "^3.1.3",
         "eslint-plugin-vue": "^7.0.0-0",
+        "hardhat": "2.14.0",
+        "hardhat-gas-reporter": "^1.0.8",
         "jest": "29.4.3",
         "jest-environment-jsdom": "29.4.3",
+        "jest-environment-node": "^29.4.1",
         "jest-serializer-vue": "^2.0.2",
         "jest-transform-stub": "^2.0.0",
         "jsdom": "~22.1.0",
-        "nx": "18.0.8",
+        "migrate-mongo": "^11.0.0",
+        "nock": "^13.5.4",
+        "nx": "18.3.0",
         "prettier": "^2.6.2",
+        "react-refresh": "^0.10.0",
+        "solidity-coverage": "^0.8.0",
+        "supertest": "^7.0.0",
         "swc-loader": "0.1.15",
         "ts-jest": "29.1.2",
         "ts-node": "10.9.1",
-        "typescript": "^5.3.3",
+        "typechain": "^8.3.0",
+        "typescript": "5.4.5",
         "unplugin-vue-components": "^0.25.1",
         "vite": "5.1.6",
         "vite-plugin-eslint": "^1.6.0",
         "vite-plugin-mkcert": "^1.16.0",
         "vite-plugin-node-polyfills": "^0.9.0",
         "vite-tsconfig-paths": "^4.3.2",
-        "vitest": "^1.3.1",
+        "vitest": "1.6.0",
         "vue3-jest": "^27.0.0-alpha.1"
     },
     "resolutions": {
-        "@achrinza/node-ipc": "9.2.5"
+        "@achrinza/node-ipc": "9.2.7"
     }
 }
diff --git a/scripts/insert_safe_master_copy.py b/scripts/insert_safe_master_copy.py
new file mode 100644
index 000000000..ad0ca80bf
--- /dev/null
+++ b/scripts/insert_safe_master_copy.py
@@ -0,0 +1,48 @@
+import binascii
+
+from django.core.management.base import BaseCommand
+from ...models import SafeMasterCopy
+
+class Command(BaseCommand):
+    help = "Insert SafeMasterCopy objects"
+
+    def add_arguments(self, parser):
+        parser.add_argument("--address", help="SafeMasterCopy address", required=True)
+        parser.add_argument("--initial-block-number", help="Initial block number", required=False, default=0)
+        parser.add_argument("--tx-block-number", help="Transaction block number", required=False, default=None)
+        parser.add_argument("--safe-version", help="Safe Version", required=False, default="1.3.0")
+        parser.add_argument("--l2", help="Address on L2", required=False, default=True)
+        parser.add_argument("--deployer", help="Deployer", required=False, default="Gnosis")
+
+    def handle(self, *args, **options):
+        mastercopy_address = options["address"]
+        initial_block_number = options["initial_block_number"]
+        tx_block_number = options["tx_block_number"]
+        version = options["safe_version"]
+        l2 = options["l2"]
+        deployer = options["deployer"]
+
+        # Convert the mastercopy address to binary
+        address = self.ethereum_address_to_binary(mastercopy_address)
+        
+        # Create SafeMasterCopy object
+        SafeMasterCopy.objects.create(
+            address=address,
+            initial_block_number=initial_block_number,
+            tx_block_number=tx_block_number,
+            version=version,
+            l2=l2,
+            deployer=deployer,
+        )
+
+        self.stdout.write(self.style.SUCCESS(f"Created SafeMasterCopy for {mastercopy_address}"))
+
+    def ethereum_address_to_binary(self, address):
+        # Remove the '0x' prefix from the address if it exists
+        if address.startswith('0x'):
+            address = address[2:]
+
+        # Convert the hexadecimal string to binary representation
+        binary_representation = binascii.unhexlify(address)
+
+        return binary_representation
\ No newline at end of file
diff --git a/scripts/mongo-init.sh b/scripts/mongo-init.sh
new file mode 100644
index 000000000..7ec4f46be
--- /dev/null
+++ b/scripts/mongo-init.sh
@@ -0,0 +1,4 @@
+mongoimport --uri="mongodb://$MONGO_INITDB_ROOT_USERNAME:$MONGO_INITDB_ROOT_PASSWORD@localhost:27017/auth?authSource=admin&ssl=false" --file="/docker-entrypoint-initdb.d/fixture/client.json" --jsonArray;
+mongoimport --uri="mongodb://$MONGO_INITDB_ROOT_USERNAME:$MONGO_INITDB_ROOT_PASSWORD@localhost:27017/auth?authSource=admin&ssl=false" --file="/docker-entrypoint-initdb.d/fixture/accounts.json" --jsonArray;
+mongoimport --uri="mongodb://$MONGO_INITDB_ROOT_USERNAME:$MONGO_INITDB_ROOT_PASSWORD@localhost:27017/auth?authSource=admin&ssl=false" --file="/docker-entrypoint-initdb.d/fixture/registration_access_token.json"  --jsonArray;
+mongo --eval "db.auth('$MONGO_INITDB_ROOT_USERNAME', '$MONGO_INITDB_ROOT_PASSWORD'); db = db.getSiblingDB('$MONGO_INITDB_DATABASE'); db.createUser({ user: '$MONGODB_USER', pwd: '$MONGODB_PASSWORD', roles: [{ role: 'readWrite', db: '$MONGODB_NAME' }] });";
diff --git a/tsconfig.base.json b/tsconfig.base.json
index c81f08a7e..ec0d614bf 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -17,11 +17,14 @@
         "skipLibCheck": true,
         "skipDefaultLibCheck": true,
         "baseUrl": ".",
-        "isolatedModules": true,
+        "isolatedModules": false,
         "strict": true,
         "paths": {
-            "@thxnetwork/campaign*": ["apps/campaign/src*"],
-            "@thxnetwork/common*": ["libs/common/src*"]
+            "@thxnetwork/api*": ["apps/api/src/app*"],
+            "@thxnetwork/auth*": ["apps/auth/src/app*"],
+            "@thxnetwork/app*": ["apps/app/src*"],
+            "@thxnetwork/common*": ["libs/common/src/lib*"],
+            "@thxnetwork/sdk*": ["libs/sdk/src/lib*"]
         }
     },
     "exclude": ["node_modules", "tmp"]
diff --git a/yarn.lock b/yarn.lock
index 29d70ad76..d7adeefbb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7,15 +7,20 @@
   resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf"
   integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==
 
-"@achrinza/node-ipc@9.2.2", "@achrinza/node-ipc@9.2.5":
-  version "9.2.5"
-  resolved "https://registry.yarnpkg.com/@achrinza/node-ipc/-/node-ipc-9.2.5.tgz#29788e608ff41121f0543491da723b243266ac28"
-  integrity sha512-kBX7Ay911iXZ3VZ1pYltj3Rfu7Ow9H7sK4H4RSfWIfWR2JKNB40K808wppoRIEzE2j2hXLU+r6TJgCAliCGhyQ==
+"@achrinza/node-ipc@9.2.2", "@achrinza/node-ipc@9.2.7":
+  version "9.2.7"
+  resolved "https://registry.yarnpkg.com/@achrinza/node-ipc/-/node-ipc-9.2.7.tgz#cc418f9218d24d9b87f32207e5d6e71c64e241f8"
+  integrity sha512-/EvNkqB4HNxPWCZASmgrjqG8gIdPOolD67LGASvGMp/FY5ne0rbvpYg5o9x8RmgjAl8KdmNQ4YlV1et9DYiW8g==
   dependencies:
     "@node-ipc/js-queue" "2.0.3"
     event-pubsub "4.3.0"
     js-message "1.0.7"
 
+"@adobe/css-tools@^4.0.1":
+  version "4.3.3"
+  resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.3.tgz#90749bde8b89cd41764224f5aac29cd4138f75ff"
+  integrity sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==
+
 "@adraffy/ens-normalize@1.10.0":
   version "1.10.0"
   resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz#d2a39395c587e092d77cbbc80acf956a54f38bf7"
@@ -42,6 +47,768 @@
   resolved "https://registry.yarnpkg.com/@antfu/utils/-/utils-0.7.7.tgz#26ea493a831b4f3a85475e7157be02fb4eab51fb"
   integrity sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg==
 
+"@aws-crypto/crc32@3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@aws-crypto/crc32/-/crc32-3.0.0.tgz#07300eca214409c33e3ff769cd5697b57fdd38fa"
+  integrity sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==
+  dependencies:
+    "@aws-crypto/util" "^3.0.0"
+    "@aws-sdk/types" "^3.222.0"
+    tslib "^1.11.1"
+
+"@aws-crypto/crc32c@3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@aws-crypto/crc32c/-/crc32c-3.0.0.tgz#016c92da559ef638a84a245eecb75c3e97cb664f"
+  integrity sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w==
+  dependencies:
+    "@aws-crypto/util" "^3.0.0"
+    "@aws-sdk/types" "^3.222.0"
+    tslib "^1.11.1"
+
+"@aws-crypto/ie11-detection@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz#640ae66b4ec3395cee6a8e94ebcd9f80c24cd688"
+  integrity sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==
+  dependencies:
+    tslib "^1.11.1"
+
+"@aws-crypto/sha1-browser@3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@aws-crypto/sha1-browser/-/sha1-browser-3.0.0.tgz#f9083c00782b24714f528b1a1fef2174002266a3"
+  integrity sha512-NJth5c997GLHs6nOYTzFKTbYdMNA6/1XlKVgnZoaZcQ7z7UJlOgj2JdbHE8tiYLS3fzXNCguct77SPGat2raSw==
+  dependencies:
+    "@aws-crypto/ie11-detection" "^3.0.0"
+    "@aws-crypto/supports-web-crypto" "^3.0.0"
+    "@aws-crypto/util" "^3.0.0"
+    "@aws-sdk/types" "^3.222.0"
+    "@aws-sdk/util-locate-window" "^3.0.0"
+    "@aws-sdk/util-utf8-browser" "^3.0.0"
+    tslib "^1.11.1"
+
+"@aws-crypto/sha256-browser@3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz#05f160138ab893f1c6ba5be57cfd108f05827766"
+  integrity sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==
+  dependencies:
+    "@aws-crypto/ie11-detection" "^3.0.0"
+    "@aws-crypto/sha256-js" "^3.0.0"
+    "@aws-crypto/supports-web-crypto" "^3.0.0"
+    "@aws-crypto/util" "^3.0.0"
+    "@aws-sdk/types" "^3.222.0"
+    "@aws-sdk/util-locate-window" "^3.0.0"
+    "@aws-sdk/util-utf8-browser" "^3.0.0"
+    tslib "^1.11.1"
+
+"@aws-crypto/sha256-js@1.2.2":
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-js/-/sha256-js-1.2.2.tgz#02acd1a1fda92896fc5a28ec7c6e164644ea32fc"
+  integrity sha512-Nr1QJIbW/afYYGzYvrF70LtaHrIRtd4TNAglX8BvlfxJLZ45SAmueIKYl5tWoNBPzp65ymXGFK0Bb1vZUpuc9g==
+  dependencies:
+    "@aws-crypto/util" "^1.2.2"
+    "@aws-sdk/types" "^3.1.0"
+    tslib "^1.11.1"
+
+"@aws-crypto/sha256-js@3.0.0", "@aws-crypto/sha256-js@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz#f06b84d550d25521e60d2a0e2a90139341e007c2"
+  integrity sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==
+  dependencies:
+    "@aws-crypto/util" "^3.0.0"
+    "@aws-sdk/types" "^3.222.0"
+    tslib "^1.11.1"
+
+"@aws-crypto/supports-web-crypto@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz#5d1bf825afa8072af2717c3e455f35cda0103ec2"
+  integrity sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==
+  dependencies:
+    tslib "^1.11.1"
+
+"@aws-crypto/util@^1.2.2":
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/@aws-crypto/util/-/util-1.2.2.tgz#b28f7897730eb6538b21c18bd4de22d0ea09003c"
+  integrity sha512-H8PjG5WJ4wz0UXAFXeJjWCW1vkvIJ3qUUD+rGRwJ2/hj+xT58Qle2MTql/2MGzkU+1JLAFuR6aJpLAjHwhmwwg==
+  dependencies:
+    "@aws-sdk/types" "^3.1.0"
+    "@aws-sdk/util-utf8-browser" "^3.0.0"
+    tslib "^1.11.1"
+
+"@aws-crypto/util@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@aws-crypto/util/-/util-3.0.0.tgz#1c7ca90c29293f0883468ad48117937f0fe5bfb0"
+  integrity sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==
+  dependencies:
+    "@aws-sdk/types" "^3.222.0"
+    "@aws-sdk/util-utf8-browser" "^3.0.0"
+    tslib "^1.11.1"
+
+"@aws-sdk/client-cognito-identity@3.576.0":
+  version "3.576.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.576.0.tgz#bb4f71cabb9d1c12e618bfe1424855c9a678ed8b"
+  integrity sha512-SgfR1LLZWT1NrNOB968OKC8RAbaQUFG4V1eDjAeNjtuqC7iAlY9Ogrl824XJY4muz4ErVAga7A+Xn9QTOSSTBQ==
+  dependencies:
+    "@aws-crypto/sha256-browser" "3.0.0"
+    "@aws-crypto/sha256-js" "3.0.0"
+    "@aws-sdk/client-sso-oidc" "3.576.0"
+    "@aws-sdk/client-sts" "3.576.0"
+    "@aws-sdk/core" "3.576.0"
+    "@aws-sdk/credential-provider-node" "3.576.0"
+    "@aws-sdk/middleware-host-header" "3.575.0"
+    "@aws-sdk/middleware-logger" "3.575.0"
+    "@aws-sdk/middleware-recursion-detection" "3.575.0"
+    "@aws-sdk/middleware-user-agent" "3.575.0"
+    "@aws-sdk/region-config-resolver" "3.575.0"
+    "@aws-sdk/types" "3.575.0"
+    "@aws-sdk/util-endpoints" "3.575.0"
+    "@aws-sdk/util-user-agent-browser" "3.575.0"
+    "@aws-sdk/util-user-agent-node" "3.575.0"
+    "@smithy/config-resolver" "^3.0.0"
+    "@smithy/core" "^2.0.0"
+    "@smithy/fetch-http-handler" "^3.0.0"
+    "@smithy/hash-node" "^3.0.0"
+    "@smithy/invalid-dependency" "^3.0.0"
+    "@smithy/middleware-content-length" "^3.0.0"
+    "@smithy/middleware-endpoint" "^3.0.0"
+    "@smithy/middleware-retry" "^3.0.0"
+    "@smithy/middleware-serde" "^3.0.0"
+    "@smithy/middleware-stack" "^3.0.0"
+    "@smithy/node-config-provider" "^3.0.0"
+    "@smithy/node-http-handler" "^3.0.0"
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/smithy-client" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    "@smithy/url-parser" "^3.0.0"
+    "@smithy/util-base64" "^3.0.0"
+    "@smithy/util-body-length-browser" "^3.0.0"
+    "@smithy/util-body-length-node" "^3.0.0"
+    "@smithy/util-defaults-mode-browser" "^3.0.0"
+    "@smithy/util-defaults-mode-node" "^3.0.0"
+    "@smithy/util-endpoints" "^2.0.0"
+    "@smithy/util-middleware" "^3.0.0"
+    "@smithy/util-retry" "^3.0.0"
+    "@smithy/util-utf8" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/client-s3@^3.576.0":
+  version "3.576.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.576.0.tgz#0e2f8f8fdbf1548ed2bd7d782f8fdf2e90131de1"
+  integrity sha512-6Xhj8x7ijLqoLYncKMUn433QKWzEezDLR3TipKv/qHThTa8oYXkymMat/MfJ/lx3jsc8wS72i+1kTwO+AFUg6w==
+  dependencies:
+    "@aws-crypto/sha1-browser" "3.0.0"
+    "@aws-crypto/sha256-browser" "3.0.0"
+    "@aws-crypto/sha256-js" "3.0.0"
+    "@aws-sdk/client-sso-oidc" "3.576.0"
+    "@aws-sdk/client-sts" "3.576.0"
+    "@aws-sdk/core" "3.576.0"
+    "@aws-sdk/credential-provider-node" "3.576.0"
+    "@aws-sdk/middleware-bucket-endpoint" "3.575.0"
+    "@aws-sdk/middleware-expect-continue" "3.575.0"
+    "@aws-sdk/middleware-flexible-checksums" "3.575.0"
+    "@aws-sdk/middleware-host-header" "3.575.0"
+    "@aws-sdk/middleware-location-constraint" "3.575.0"
+    "@aws-sdk/middleware-logger" "3.575.0"
+    "@aws-sdk/middleware-recursion-detection" "3.575.0"
+    "@aws-sdk/middleware-sdk-s3" "3.575.0"
+    "@aws-sdk/middleware-signing" "3.575.0"
+    "@aws-sdk/middleware-ssec" "3.575.0"
+    "@aws-sdk/middleware-user-agent" "3.575.0"
+    "@aws-sdk/region-config-resolver" "3.575.0"
+    "@aws-sdk/signature-v4-multi-region" "3.575.0"
+    "@aws-sdk/types" "3.575.0"
+    "@aws-sdk/util-endpoints" "3.575.0"
+    "@aws-sdk/util-user-agent-browser" "3.575.0"
+    "@aws-sdk/util-user-agent-node" "3.575.0"
+    "@aws-sdk/xml-builder" "3.575.0"
+    "@smithy/config-resolver" "^3.0.0"
+    "@smithy/core" "^2.0.0"
+    "@smithy/eventstream-serde-browser" "^3.0.0"
+    "@smithy/eventstream-serde-config-resolver" "^3.0.0"
+    "@smithy/eventstream-serde-node" "^3.0.0"
+    "@smithy/fetch-http-handler" "^3.0.0"
+    "@smithy/hash-blob-browser" "^3.0.0"
+    "@smithy/hash-node" "^3.0.0"
+    "@smithy/hash-stream-node" "^3.0.0"
+    "@smithy/invalid-dependency" "^3.0.0"
+    "@smithy/md5-js" "^3.0.0"
+    "@smithy/middleware-content-length" "^3.0.0"
+    "@smithy/middleware-endpoint" "^3.0.0"
+    "@smithy/middleware-retry" "^3.0.0"
+    "@smithy/middleware-serde" "^3.0.0"
+    "@smithy/middleware-stack" "^3.0.0"
+    "@smithy/node-config-provider" "^3.0.0"
+    "@smithy/node-http-handler" "^3.0.0"
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/smithy-client" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    "@smithy/url-parser" "^3.0.0"
+    "@smithy/util-base64" "^3.0.0"
+    "@smithy/util-body-length-browser" "^3.0.0"
+    "@smithy/util-body-length-node" "^3.0.0"
+    "@smithy/util-defaults-mode-browser" "^3.0.0"
+    "@smithy/util-defaults-mode-node" "^3.0.0"
+    "@smithy/util-endpoints" "^2.0.0"
+    "@smithy/util-retry" "^3.0.0"
+    "@smithy/util-stream" "^3.0.0"
+    "@smithy/util-utf8" "^3.0.0"
+    "@smithy/util-waiter" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/client-ses@^3.576.0":
+  version "3.576.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/client-ses/-/client-ses-3.576.0.tgz#dcbc37c095e41842e31cfdfbbe93df1f7e02df65"
+  integrity sha512-NevkUOGvvygAhY9WAszmiokCQMhZxeasHNjt3Y0dDflgDhR16Lau3xmVsHdwLJkr0CnjcY5r5jLKpyLK4YuwaA==
+  dependencies:
+    "@aws-crypto/sha256-browser" "3.0.0"
+    "@aws-crypto/sha256-js" "3.0.0"
+    "@aws-sdk/client-sso-oidc" "3.576.0"
+    "@aws-sdk/client-sts" "3.576.0"
+    "@aws-sdk/core" "3.576.0"
+    "@aws-sdk/credential-provider-node" "3.576.0"
+    "@aws-sdk/middleware-host-header" "3.575.0"
+    "@aws-sdk/middleware-logger" "3.575.0"
+    "@aws-sdk/middleware-recursion-detection" "3.575.0"
+    "@aws-sdk/middleware-user-agent" "3.575.0"
+    "@aws-sdk/region-config-resolver" "3.575.0"
+    "@aws-sdk/types" "3.575.0"
+    "@aws-sdk/util-endpoints" "3.575.0"
+    "@aws-sdk/util-user-agent-browser" "3.575.0"
+    "@aws-sdk/util-user-agent-node" "3.575.0"
+    "@smithy/config-resolver" "^3.0.0"
+    "@smithy/core" "^2.0.0"
+    "@smithy/fetch-http-handler" "^3.0.0"
+    "@smithy/hash-node" "^3.0.0"
+    "@smithy/invalid-dependency" "^3.0.0"
+    "@smithy/middleware-content-length" "^3.0.0"
+    "@smithy/middleware-endpoint" "^3.0.0"
+    "@smithy/middleware-retry" "^3.0.0"
+    "@smithy/middleware-serde" "^3.0.0"
+    "@smithy/middleware-stack" "^3.0.0"
+    "@smithy/node-config-provider" "^3.0.0"
+    "@smithy/node-http-handler" "^3.0.0"
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/smithy-client" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    "@smithy/url-parser" "^3.0.0"
+    "@smithy/util-base64" "^3.0.0"
+    "@smithy/util-body-length-browser" "^3.0.0"
+    "@smithy/util-body-length-node" "^3.0.0"
+    "@smithy/util-defaults-mode-browser" "^3.0.0"
+    "@smithy/util-defaults-mode-node" "^3.0.0"
+    "@smithy/util-endpoints" "^2.0.0"
+    "@smithy/util-middleware" "^3.0.0"
+    "@smithy/util-retry" "^3.0.0"
+    "@smithy/util-utf8" "^3.0.0"
+    "@smithy/util-waiter" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/client-sso-oidc@3.576.0":
+  version "3.576.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.576.0.tgz#0747712005dc8c70ad7b1ecdb1fb4857106a0a69"
+  integrity sha512-6U8933O9h6iMnQDpH3OtFhS3G3FVttYZUqTpC2T0FnSSX7zgG0GnlxdQiyZh1j1aFrEB8bFw/RSmxPcMJJuSlQ==
+  dependencies:
+    "@aws-crypto/sha256-browser" "3.0.0"
+    "@aws-crypto/sha256-js" "3.0.0"
+    "@aws-sdk/client-sts" "3.576.0"
+    "@aws-sdk/core" "3.576.0"
+    "@aws-sdk/credential-provider-node" "3.576.0"
+    "@aws-sdk/middleware-host-header" "3.575.0"
+    "@aws-sdk/middleware-logger" "3.575.0"
+    "@aws-sdk/middleware-recursion-detection" "3.575.0"
+    "@aws-sdk/middleware-user-agent" "3.575.0"
+    "@aws-sdk/region-config-resolver" "3.575.0"
+    "@aws-sdk/types" "3.575.0"
+    "@aws-sdk/util-endpoints" "3.575.0"
+    "@aws-sdk/util-user-agent-browser" "3.575.0"
+    "@aws-sdk/util-user-agent-node" "3.575.0"
+    "@smithy/config-resolver" "^3.0.0"
+    "@smithy/core" "^2.0.0"
+    "@smithy/fetch-http-handler" "^3.0.0"
+    "@smithy/hash-node" "^3.0.0"
+    "@smithy/invalid-dependency" "^3.0.0"
+    "@smithy/middleware-content-length" "^3.0.0"
+    "@smithy/middleware-endpoint" "^3.0.0"
+    "@smithy/middleware-retry" "^3.0.0"
+    "@smithy/middleware-serde" "^3.0.0"
+    "@smithy/middleware-stack" "^3.0.0"
+    "@smithy/node-config-provider" "^3.0.0"
+    "@smithy/node-http-handler" "^3.0.0"
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/smithy-client" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    "@smithy/url-parser" "^3.0.0"
+    "@smithy/util-base64" "^3.0.0"
+    "@smithy/util-body-length-browser" "^3.0.0"
+    "@smithy/util-body-length-node" "^3.0.0"
+    "@smithy/util-defaults-mode-browser" "^3.0.0"
+    "@smithy/util-defaults-mode-node" "^3.0.0"
+    "@smithy/util-endpoints" "^2.0.0"
+    "@smithy/util-middleware" "^3.0.0"
+    "@smithy/util-retry" "^3.0.0"
+    "@smithy/util-utf8" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/client-sso@3.576.0":
+  version "3.576.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.576.0.tgz#10a60057a6e6fb3cae164af1f16bb20f60188746"
+  integrity sha512-xbKE4bf3HYvkdrvn5kkpUdcoi3mg7uDLLkSbGaj0tzW3vNSdx9qLrCMuwfV7KrhVKWwx+lnw/2LGuCR2B5y0IA==
+  dependencies:
+    "@aws-crypto/sha256-browser" "3.0.0"
+    "@aws-crypto/sha256-js" "3.0.0"
+    "@aws-sdk/core" "3.576.0"
+    "@aws-sdk/middleware-host-header" "3.575.0"
+    "@aws-sdk/middleware-logger" "3.575.0"
+    "@aws-sdk/middleware-recursion-detection" "3.575.0"
+    "@aws-sdk/middleware-user-agent" "3.575.0"
+    "@aws-sdk/region-config-resolver" "3.575.0"
+    "@aws-sdk/types" "3.575.0"
+    "@aws-sdk/util-endpoints" "3.575.0"
+    "@aws-sdk/util-user-agent-browser" "3.575.0"
+    "@aws-sdk/util-user-agent-node" "3.575.0"
+    "@smithy/config-resolver" "^3.0.0"
+    "@smithy/core" "^2.0.0"
+    "@smithy/fetch-http-handler" "^3.0.0"
+    "@smithy/hash-node" "^3.0.0"
+    "@smithy/invalid-dependency" "^3.0.0"
+    "@smithy/middleware-content-length" "^3.0.0"
+    "@smithy/middleware-endpoint" "^3.0.0"
+    "@smithy/middleware-retry" "^3.0.0"
+    "@smithy/middleware-serde" "^3.0.0"
+    "@smithy/middleware-stack" "^3.0.0"
+    "@smithy/node-config-provider" "^3.0.0"
+    "@smithy/node-http-handler" "^3.0.0"
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/smithy-client" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    "@smithy/url-parser" "^3.0.0"
+    "@smithy/util-base64" "^3.0.0"
+    "@smithy/util-body-length-browser" "^3.0.0"
+    "@smithy/util-body-length-node" "^3.0.0"
+    "@smithy/util-defaults-mode-browser" "^3.0.0"
+    "@smithy/util-defaults-mode-node" "^3.0.0"
+    "@smithy/util-endpoints" "^2.0.0"
+    "@smithy/util-middleware" "^3.0.0"
+    "@smithy/util-retry" "^3.0.0"
+    "@smithy/util-utf8" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/client-sts@3.576.0":
+  version "3.576.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.576.0.tgz#17aee1dad3c135730c47df8aac2adbf14fae7df6"
+  integrity sha512-GHqqfRcUW/nGE4lpRafNKRxi4K7+SaQjYLjQnTEioUhr+w1IT/fFb3rGZYHHnN9ZCzbnrBULRC+1XOPIQWyLsw==
+  dependencies:
+    "@aws-crypto/sha256-browser" "3.0.0"
+    "@aws-crypto/sha256-js" "3.0.0"
+    "@aws-sdk/client-sso-oidc" "3.576.0"
+    "@aws-sdk/core" "3.576.0"
+    "@aws-sdk/credential-provider-node" "3.576.0"
+    "@aws-sdk/middleware-host-header" "3.575.0"
+    "@aws-sdk/middleware-logger" "3.575.0"
+    "@aws-sdk/middleware-recursion-detection" "3.575.0"
+    "@aws-sdk/middleware-user-agent" "3.575.0"
+    "@aws-sdk/region-config-resolver" "3.575.0"
+    "@aws-sdk/types" "3.575.0"
+    "@aws-sdk/util-endpoints" "3.575.0"
+    "@aws-sdk/util-user-agent-browser" "3.575.0"
+    "@aws-sdk/util-user-agent-node" "3.575.0"
+    "@smithy/config-resolver" "^3.0.0"
+    "@smithy/core" "^2.0.0"
+    "@smithy/fetch-http-handler" "^3.0.0"
+    "@smithy/hash-node" "^3.0.0"
+    "@smithy/invalid-dependency" "^3.0.0"
+    "@smithy/middleware-content-length" "^3.0.0"
+    "@smithy/middleware-endpoint" "^3.0.0"
+    "@smithy/middleware-retry" "^3.0.0"
+    "@smithy/middleware-serde" "^3.0.0"
+    "@smithy/middleware-stack" "^3.0.0"
+    "@smithy/node-config-provider" "^3.0.0"
+    "@smithy/node-http-handler" "^3.0.0"
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/smithy-client" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    "@smithy/url-parser" "^3.0.0"
+    "@smithy/util-base64" "^3.0.0"
+    "@smithy/util-body-length-browser" "^3.0.0"
+    "@smithy/util-body-length-node" "^3.0.0"
+    "@smithy/util-defaults-mode-browser" "^3.0.0"
+    "@smithy/util-defaults-mode-node" "^3.0.0"
+    "@smithy/util-endpoints" "^2.0.0"
+    "@smithy/util-middleware" "^3.0.0"
+    "@smithy/util-retry" "^3.0.0"
+    "@smithy/util-utf8" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/core@3.576.0":
+  version "3.576.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.576.0.tgz#ced16ca42b615182565c6bcf4563278b30fd43bf"
+  integrity sha512-KDvDlbeipSTIf+ffKtTg1m419TK7s9mZSWC8bvuZ9qx6/sjQFOXIKOVqyuli6DnfxGbvRcwoRuY99OcCH1N/0w==
+  dependencies:
+    "@smithy/core" "^2.0.0"
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/signature-v4" "^3.0.0"
+    "@smithy/smithy-client" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    fast-xml-parser "4.2.5"
+    tslib "^2.6.2"
+
+"@aws-sdk/credential-provider-cognito-identity@3.576.0":
+  version "3.576.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.576.0.tgz#267ffc317defa239f1afd874a9364361c8a59718"
+  integrity sha512-pi5gY+VhuQk8PUskxSonRS7IZk82jbhpfLBFnbFdNDUpBPSrHAfi1AukqAgbbiB/MfJTKaI/rNg3VfwyOzPmJw==
+  dependencies:
+    "@aws-sdk/client-cognito-identity" "3.576.0"
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/property-provider" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/credential-provider-env@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.575.0.tgz#2f0238719b383e37265e736575e9a9823a562982"
+  integrity sha512-YTgpq3rvYBXzW6OTDB00cE79evQtss/lz2GlJXgqqVXD0m7i77hGA8zb44VevP/WxtDaiSW7SSjuu8VCBGsg4g==
+  dependencies:
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/property-provider" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/credential-provider-http@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.575.0.tgz#d410dba2ae89ea6c42bf30d319b98e410da14d1b"
+  integrity sha512-xQfVmYI+9KqRvhWY8fyElnpcVUBBUgi/Hoji3oU6WLrUjrX98k93He7gKDQSyHf7ykMLUAJYWwsV4AjQ2j6njA==
+  dependencies:
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/fetch-http-handler" "^3.0.0"
+    "@smithy/node-http-handler" "^3.0.0"
+    "@smithy/property-provider" "^3.0.0"
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/smithy-client" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    "@smithy/util-stream" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/credential-provider-ini@3.576.0":
+  version "3.576.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.576.0.tgz#faad19e47ae8c61997d435d6d8a148d8057cada1"
+  integrity sha512-AwH/+29SbjhxGJVYhFn6+7r0MZ7TjJClySTJzuOoyjJGPWAifTdEuFkyOw8Bs9fEvbJ0ExgFxSaa445fO56kmg==
+  dependencies:
+    "@aws-sdk/credential-provider-env" "3.575.0"
+    "@aws-sdk/credential-provider-process" "3.575.0"
+    "@aws-sdk/credential-provider-sso" "3.576.0"
+    "@aws-sdk/credential-provider-web-identity" "3.575.0"
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/credential-provider-imds" "^3.0.0"
+    "@smithy/property-provider" "^3.0.0"
+    "@smithy/shared-ini-file-loader" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/credential-provider-node@3.576.0":
+  version "3.576.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.576.0.tgz#9bd181e9cb2c3d1434df8293e6c0f271de0854bc"
+  integrity sha512-Ad244g3TJnfY1QFlZ+cywD6kgGD2yj+qg47Ryt50Y42bwmNuuqSpF9n0C71opRR68Rcl7ksOxixCJomWqpcHbA==
+  dependencies:
+    "@aws-sdk/credential-provider-env" "3.575.0"
+    "@aws-sdk/credential-provider-http" "3.575.0"
+    "@aws-sdk/credential-provider-ini" "3.576.0"
+    "@aws-sdk/credential-provider-process" "3.575.0"
+    "@aws-sdk/credential-provider-sso" "3.576.0"
+    "@aws-sdk/credential-provider-web-identity" "3.575.0"
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/credential-provider-imds" "^3.0.0"
+    "@smithy/property-provider" "^3.0.0"
+    "@smithy/shared-ini-file-loader" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/credential-provider-process@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.575.0.tgz#b1b409af833ccbce18e2806991434aa30f61ceb3"
+  integrity sha512-2/5NJV7MZysKglqJSQ/O8OELNcwLcH3xknabL9NagtzB7RNB2p1AUXR0UlTey9sSDLL4oCmNa/+unYuglW/Ahg==
+  dependencies:
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/property-provider" "^3.0.0"
+    "@smithy/shared-ini-file-loader" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/credential-provider-sso@3.576.0":
+  version "3.576.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.576.0.tgz#d3c9041fdae3513717aaea92fefc3371f64a6d83"
+  integrity sha512-1F17issiqf+mSG7KJ+D0SfZRYBZPAmRcA5+VHDUuMLozhh8tyYMe0mwzOt9IKc7ocrJA+2Wp7l7sg3h6aanedQ==
+  dependencies:
+    "@aws-sdk/client-sso" "3.576.0"
+    "@aws-sdk/token-providers" "3.575.0"
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/property-provider" "^3.0.0"
+    "@smithy/shared-ini-file-loader" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/credential-provider-web-identity@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.575.0.tgz#524ff9f944986c99486be5fa374db4d5895f63ff"
+  integrity sha512-QcvVH7wpvpFRXGAGgCBfQeiF/ptD0NJ+Hrc8dDYfPGhFeZ0EoVQBYNphLi25xe7JZ+XbaqCKrURHZtr4fAEOJw==
+  dependencies:
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/property-provider" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/credential-providers@^3.186.0":
+  version "3.576.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-providers/-/credential-providers-3.576.0.tgz#ecd1361cd2e846bf9b536079d7114c1e0ff82183"
+  integrity sha512-OoYyhSpxshmijD4aG/wDJIciFTh1DoNKyVyLaMGaJkE9nblArRCO+z0DEg9Yqlo8tLG0HLiTAJbyLxdQryKV5Q==
+  dependencies:
+    "@aws-sdk/client-cognito-identity" "3.576.0"
+    "@aws-sdk/client-sso" "3.576.0"
+    "@aws-sdk/client-sts" "3.576.0"
+    "@aws-sdk/credential-provider-cognito-identity" "3.576.0"
+    "@aws-sdk/credential-provider-env" "3.575.0"
+    "@aws-sdk/credential-provider-http" "3.575.0"
+    "@aws-sdk/credential-provider-ini" "3.576.0"
+    "@aws-sdk/credential-provider-node" "3.576.0"
+    "@aws-sdk/credential-provider-process" "3.575.0"
+    "@aws-sdk/credential-provider-sso" "3.576.0"
+    "@aws-sdk/credential-provider-web-identity" "3.575.0"
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/credential-provider-imds" "^3.0.0"
+    "@smithy/property-provider" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/middleware-bucket-endpoint@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.575.0.tgz#258c00c672179f6c038f532f9bc1ff51caba5eb0"
+  integrity sha512-ytsp7xcmbpkVk4TLoi91YyXQh/vwSIGdJ2Awo/pi6ac5Fqe6OntPijh5GHSVj5ZrxW4haPWb6HdBmKMo4liGEw==
+  dependencies:
+    "@aws-sdk/types" "3.575.0"
+    "@aws-sdk/util-arn-parser" "3.568.0"
+    "@smithy/node-config-provider" "^3.0.0"
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/types" "^3.0.0"
+    "@smithy/util-config-provider" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/middleware-expect-continue@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.575.0.tgz#d54c8b309b87347be9f3a3b566bafa0fbf8cd7bf"
+  integrity sha512-8Nq4UtEi63MJPoYBACW5YoMKQdbrkLNGIdTyrolNRNwVS+6nQqDMvBplakCzQ1nL1rHOEEsKKc8e2BlG9SkR5A==
+  dependencies:
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/middleware-flexible-checksums@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.575.0.tgz#0b06111c0cc82c279e6682acd36f96e3df56a1d5"
+  integrity sha512-UbyqN39v6s+olyuVKwX778w6J2ZuYpxb1j+KdhFtZwpMSLd/UIQ0+A71U2vB6TrC52OEW0jIXEEBv6PcMBz9nw==
+  dependencies:
+    "@aws-crypto/crc32" "3.0.0"
+    "@aws-crypto/crc32c" "3.0.0"
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/is-array-buffer" "^3.0.0"
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/types" "^3.0.0"
+    "@smithy/util-utf8" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/middleware-host-header@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.575.0.tgz#5bf24080d6a6c466cdeaff49879c77559f74a2fd"
+  integrity sha512-V2WoLBiXNCc4rIWZt6FUcP4TN0Vk02A9PPCBWkTfyOooiqfq+WZmZjRRBpwl1+5UsvARslrKWF0VzheMRXPJLQ==
+  dependencies:
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/middleware-location-constraint@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.575.0.tgz#1816fcda54bfefa4b607a0ac93273e12b2027621"
+  integrity sha512-MtQsLsEjSSSfm0OlQqg9PEzS1nxJDdApGoeCYLTbCzIp6hChdLZCCsDXwGg9S++24rjQsUglMhXh4WGXQ9FDnw==
+  dependencies:
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/middleware-logger@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.575.0.tgz#981c939cb3c10c1e3ecfa458c64f7be8c5a71307"
+  integrity sha512-7DEKx9Z11Maaye7FfhYtC8rjbM/PcFcMO2N4QEAfypcgWCj+w4gseE2OGdfAH9OFDoFc6YvLp53v16vbPjzQSg==
+  dependencies:
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/middleware-recursion-detection@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.575.0.tgz#924a4b7ca864600a202d82621bed3ddfd7819e06"
+  integrity sha512-ri89ldRFos6KZDGaknWPS2XPO9qr+gZ7+mPaoU8YkSM1W4uKqtnUSONyc+O3CFGJrqReuGHhRq0l2Sld0bjwOw==
+  dependencies:
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/middleware-sdk-s3@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.575.0.tgz#b2be802e4522ca4d7b0466b1a9b174f802afc48f"
+  integrity sha512-8cBG8/tap4F6+UigTpKu8D2bvsLgqRTmn1K86qo3LqRX0Wc5X8TVjdKA2PmG0onOOr7rqTLcP9Q02LCh3usU6Q==
+  dependencies:
+    "@aws-sdk/types" "3.575.0"
+    "@aws-sdk/util-arn-parser" "3.568.0"
+    "@smithy/node-config-provider" "^3.0.0"
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/signature-v4" "^3.0.0"
+    "@smithy/smithy-client" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    "@smithy/util-config-provider" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/middleware-signing@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.575.0.tgz#b19d2d6816770ed8645e12662d147777b271907e"
+  integrity sha512-frpGG7i3YngWwrYIeDq8/nbat3Gfl803qasaS112rmlPU0ezmYS1SPxpXjpIKxUUYofbzaFtRBAOHU1u7GnWew==
+  dependencies:
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/property-provider" "^3.0.0"
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/signature-v4" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    "@smithy/util-middleware" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/middleware-ssec@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-ssec/-/middleware-ssec-3.575.0.tgz#799650e7860a7443e95e0e72313e41b77e99ce1c"
+  integrity sha512-rEFt2w3DdlmPsHRvVXOW6rNDIPE7UaEZ5a4LAkn78XilQYuQdhm5wtw5Ao0pJpDSVYNCZDVZaAvdHKQ1dnfwCA==
+  dependencies:
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/middleware-user-agent@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.575.0.tgz#1969f8b8972ee0c02753584575dde3e3b0b204b8"
+  integrity sha512-fWlr4RfrUNS2R3PgP+WsoMYORAgv/47Lp0J0fb3dXO1YvdczNWddRbFSUX2MQxM/y9XFfQPLpLgzluhoL3Cjeg==
+  dependencies:
+    "@aws-sdk/types" "3.575.0"
+    "@aws-sdk/util-endpoints" "3.575.0"
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/region-config-resolver@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/region-config-resolver/-/region-config-resolver-3.575.0.tgz#8be6a9411ec8b5da3f3d0cac44beaa5b9eb6341f"
+  integrity sha512-sBJKwTWKCWu9y8FzXIijYGwkKr3tDkPXM7BylToe6W+tGkp4OirV4iXrWA9zReNwTTepoxHufofqjGK9BtcI8g==
+  dependencies:
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/node-config-provider" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    "@smithy/util-config-provider" "^3.0.0"
+    "@smithy/util-middleware" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/signature-v4-multi-region@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.575.0.tgz#73a639120a9bb36a067fdbaa094a54c9bcff7093"
+  integrity sha512-QMwuLuNwnEQ51RCZX8H/lXnOJgBcJJOCgClB9usW/XujNJVq8GnpZ5E7TsQLN88G6fifmcjQWonLKummuh/zVA==
+  dependencies:
+    "@aws-sdk/middleware-sdk-s3" "3.575.0"
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/signature-v4" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/token-providers@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.575.0.tgz#0d5ded8434b49cafd7303a139d09c97155138d3b"
+  integrity sha512-EPNDPQoQkjKqn4D2t70qVzbfdtlaAy9KBdG58qD1yNWVxq8Rh/lXdwmB+aE2PSahtyfVikZdCRoZiFzxDh5IUA==
+  dependencies:
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/property-provider" "^3.0.0"
+    "@smithy/shared-ini-file-loader" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/types@3.575.0", "@aws-sdk/types@^3.222.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.575.0.tgz#ed8f33e15c7ea22b5244018330475983d0558556"
+  integrity sha512-XrnolQGs0wXxdgNudirR14OgNOarH7WUif38+2Pd4onZH+L7XoILem0EgA1tRpgFpw2pFHlZCNaAHDNSBEal7g==
+  dependencies:
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/types@^3.1.0":
+  version "3.577.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.577.0.tgz#7700784d368ce386745f8c340d9d68cea4716f90"
+  integrity sha512-FT2JZES3wBKN/alfmhlo+3ZOq/XJ0C7QOZcDNrpKjB0kqYoKjhVKZ/Hx6ArR0czkKfHzBBEs6y40ebIHx2nSmA==
+  dependencies:
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/util-arn-parser@3.568.0":
+  version "3.568.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-arn-parser/-/util-arn-parser-3.568.0.tgz#6a19a8c6bbaa520b6be1c278b2b8c17875b91527"
+  integrity sha512-XUKJWWo+KOB7fbnPP0+g/o5Ulku/X53t7i/h+sPHr5xxYTJJ9CYnbToo95mzxe7xWvkLrsNtJ8L+MnNn9INs2w==
+  dependencies:
+    tslib "^2.6.2"
+
+"@aws-sdk/util-endpoints@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.575.0.tgz#370ad9b1ce7df227d44447ab2135c5e110aa72d2"
+  integrity sha512-wC5x+V6w3kRlR6X6XVINsAPDYG+Tzs3Wthlw+YLtjuPODUNZIQAqsABHahxnekFyAvse+1929Hwo+CaL+BHZGA==
+  dependencies:
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/types" "^3.0.0"
+    "@smithy/util-endpoints" "^2.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/util-locate-window@^3.0.0":
+  version "3.568.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-locate-window/-/util-locate-window-3.568.0.tgz#2acc4b2236af0d7494f7e517401ba6b3c4af11ff"
+  integrity sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==
+  dependencies:
+    tslib "^2.6.2"
+
+"@aws-sdk/util-user-agent-browser@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.575.0.tgz#3054cc42e0b386a34f7b0d4e8e9609e016c72eed"
+  integrity sha512-iADonXyaXgwvC4T0qRuDWCdKInz82GX2cyezq/oqVlL8bPY7HD8jwZZruuJdq5tkaJi1EhbO4+f1ksZqOiZKvQ==
+  dependencies:
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/types" "^3.0.0"
+    bowser "^2.11.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/util-user-agent-node@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.575.0.tgz#49a6ecc5f56297e1477de90f30f102c470d3dc78"
+  integrity sha512-kwzvBfA0LoILDOFS6BV8uOkksBHrYulP6kNXegB5eZnDSNia5DbBsXqxQ/HknNF5a429SWQw2aaQJEgQvZB1VA==
+  dependencies:
+    "@aws-sdk/types" "3.575.0"
+    "@smithy/node-config-provider" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@aws-sdk/util-utf8-browser@^3.0.0":
+  version "3.259.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz#3275a6f5eb334f96ca76635b961d3c50259fd9ff"
+  integrity sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==
+  dependencies:
+    tslib "^2.3.1"
+
+"@aws-sdk/xml-builder@3.575.0":
+  version "3.575.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/xml-builder/-/xml-builder-3.575.0.tgz#233b2aae422dd789a078073032da1bc60317aa1d"
+  integrity sha512-cWgAwmbFYNCFzPwxL705+lWps0F3ZvOckufd2KKoEZUmtpVw9/txUXNrPySUXSmRTSRhoatIMABNfStWR043bQ==
+  dependencies:
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
 "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.8.3":
   version "7.23.5"
   resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244"
@@ -50,11 +817,24 @@
     "@babel/highlight" "^7.23.4"
     chalk "^2.4.2"
 
+"@babel/code-frame@^7.24.2":
+  version "7.24.2"
+  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae"
+  integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==
+  dependencies:
+    "@babel/highlight" "^7.24.2"
+    picocolors "^1.0.0"
+
 "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.23.3", "@babel/compat-data@^7.23.5":
   version "7.23.5"
   resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98"
   integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==
 
+"@babel/compat-data@^7.24.4":
+  version "7.24.4"
+  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.4.tgz#6f102372e9094f25d908ca0d34fc74c74606059a"
+  integrity sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==
+
 "@babel/core@7.18.5":
   version "7.18.5"
   resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.5.tgz#c597fa680e58d571c28dda9827669c78cdd7f000"
@@ -97,6 +877,27 @@
     json5 "^2.2.3"
     semver "^6.3.1"
 
+"@babel/core@^7.21.3":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.5.tgz#15ab5b98e101972d171aeef92ac70d8d6718f06a"
+  integrity sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==
+  dependencies:
+    "@ampproject/remapping" "^2.2.0"
+    "@babel/code-frame" "^7.24.2"
+    "@babel/generator" "^7.24.5"
+    "@babel/helper-compilation-targets" "^7.23.6"
+    "@babel/helper-module-transforms" "^7.24.5"
+    "@babel/helpers" "^7.24.5"
+    "@babel/parser" "^7.24.5"
+    "@babel/template" "^7.24.0"
+    "@babel/traverse" "^7.24.5"
+    "@babel/types" "^7.24.5"
+    convert-source-map "^2.0.0"
+    debug "^4.1.0"
+    gensync "^1.0.0-beta.2"
+    json5 "^2.2.3"
+    semver "^6.3.1"
+
 "@babel/core@^7.23.2", "@babel/core@^7.23.9":
   version "7.24.0"
   resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.0.tgz#56cbda6b185ae9d9bed369816a8f4423c5f2ff1b"
@@ -128,6 +929,16 @@
     "@jridgewell/trace-mapping" "^0.3.17"
     jsesc "^2.5.1"
 
+"@babel/generator@^7.24.5":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.5.tgz#e5afc068f932f05616b66713e28d0f04e99daeb3"
+  integrity sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==
+  dependencies:
+    "@babel/types" "^7.24.5"
+    "@jridgewell/gen-mapping" "^0.3.5"
+    "@jridgewell/trace-mapping" "^0.3.25"
+    jsesc "^2.5.1"
+
 "@babel/helper-annotate-as-pure@^7.22.5":
   version "7.22.5"
   resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882"
@@ -183,6 +994,21 @@
     "@babel/helper-split-export-declaration" "^7.22.6"
     semver "^6.3.1"
 
+"@babel/helper-create-class-features-plugin@^7.24.1", "@babel/helper-create-class-features-plugin@^7.24.4", "@babel/helper-create-class-features-plugin@^7.24.5":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz#7d19da92c7e0cd8d11c09af2ce1b8e7512a6e723"
+  integrity sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==
+  dependencies:
+    "@babel/helper-annotate-as-pure" "^7.22.5"
+    "@babel/helper-environment-visitor" "^7.22.20"
+    "@babel/helper-function-name" "^7.23.0"
+    "@babel/helper-member-expression-to-functions" "^7.24.5"
+    "@babel/helper-optimise-call-expression" "^7.22.5"
+    "@babel/helper-replace-supers" "^7.24.1"
+    "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
+    "@babel/helper-split-export-declaration" "^7.24.5"
+    semver "^6.3.1"
+
 "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.15", "@babel/helper-create-regexp-features-plugin@^7.22.5":
   version "7.22.15"
   resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1"
@@ -203,6 +1029,17 @@
     lodash.debounce "^4.0.8"
     resolve "^1.14.2"
 
+"@babel/helper-define-polyfill-provider@^0.6.1", "@babel/helper-define-polyfill-provider@^0.6.2":
+  version "0.6.2"
+  resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz#18594f789c3594acb24cfdb4a7f7b7d2e8bd912d"
+  integrity sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==
+  dependencies:
+    "@babel/helper-compilation-targets" "^7.22.6"
+    "@babel/helper-plugin-utils" "^7.22.5"
+    debug "^4.1.1"
+    lodash.debounce "^4.0.8"
+    resolve "^1.14.2"
+
 "@babel/helper-environment-visitor@^7.22.20":
   version "7.22.20"
   resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
@@ -230,6 +1067,13 @@
   dependencies:
     "@babel/types" "^7.23.0"
 
+"@babel/helper-member-expression-to-functions@^7.24.5":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz#5981e131d5c7003c7d1fa1ad49e86c9b097ec475"
+  integrity sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==
+  dependencies:
+    "@babel/types" "^7.24.5"
+
 "@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.8.3":
   version "7.22.15"
   resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0"
@@ -237,6 +1081,13 @@
   dependencies:
     "@babel/types" "^7.22.15"
 
+"@babel/helper-module-imports@^7.24.1", "@babel/helper-module-imports@^7.24.3":
+  version "7.24.3"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz#6ac476e6d168c7c23ff3ba3cf4f7841d46ac8128"
+  integrity sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==
+  dependencies:
+    "@babel/types" "^7.24.0"
+
 "@babel/helper-module-transforms@^7.18.0", "@babel/helper-module-transforms@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1"
@@ -248,6 +1099,17 @@
     "@babel/helper-split-export-declaration" "^7.22.6"
     "@babel/helper-validator-identifier" "^7.22.20"
 
+"@babel/helper-module-transforms@^7.24.5":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz#ea6c5e33f7b262a0ae762fd5986355c45f54a545"
+  integrity sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==
+  dependencies:
+    "@babel/helper-environment-visitor" "^7.22.20"
+    "@babel/helper-module-imports" "^7.24.3"
+    "@babel/helper-simple-access" "^7.24.5"
+    "@babel/helper-split-export-declaration" "^7.24.5"
+    "@babel/helper-validator-identifier" "^7.24.5"
+
 "@babel/helper-optimise-call-expression@^7.22.5":
   version "7.22.5"
   resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e"
@@ -265,6 +1127,11 @@
   resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz#945681931a52f15ce879fd5b86ce2dae6d3d7f2a"
   integrity sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==
 
+"@babel/helper-plugin-utils@^7.24.5":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz#a924607dd254a65695e5bd209b98b902b3b2f11a"
+  integrity sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==
+
 "@babel/helper-remap-async-to-generator@^7.22.20":
   version "7.22.20"
   resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz#7b68e1cb4fa964d2996fd063723fb48eca8498e0"
@@ -283,6 +1150,15 @@
     "@babel/helper-member-expression-to-functions" "^7.22.15"
     "@babel/helper-optimise-call-expression" "^7.22.5"
 
+"@babel/helper-replace-supers@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz#7085bd19d4a0b7ed8f405c1ed73ccb70f323abc1"
+  integrity sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==
+  dependencies:
+    "@babel/helper-environment-visitor" "^7.22.20"
+    "@babel/helper-member-expression-to-functions" "^7.23.0"
+    "@babel/helper-optimise-call-expression" "^7.22.5"
+
 "@babel/helper-simple-access@^7.22.5":
   version "7.22.5"
   resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de"
@@ -290,6 +1166,13 @@
   dependencies:
     "@babel/types" "^7.22.5"
 
+"@babel/helper-simple-access@^7.24.5":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz#50da5b72f58c16b07fbd992810be6049478e85ba"
+  integrity sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==
+  dependencies:
+    "@babel/types" "^7.24.5"
+
 "@babel/helper-skip-transparent-expression-wrappers@^7.22.5":
   version "7.22.5"
   resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847"
@@ -304,16 +1187,33 @@
   dependencies:
     "@babel/types" "^7.22.5"
 
+"@babel/helper-split-export-declaration@^7.24.5":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz#b9a67f06a46b0b339323617c8c6213b9055a78b6"
+  integrity sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==
+  dependencies:
+    "@babel/types" "^7.24.5"
+
 "@babel/helper-string-parser@^7.23.4":
   version "7.23.4"
   resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83"
   integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==
 
+"@babel/helper-string-parser@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e"
+  integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==
+
 "@babel/helper-validator-identifier@^7.22.20":
   version "7.22.20"
   resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
   integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
 
+"@babel/helper-validator-identifier@^7.24.5":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz#918b1a7fa23056603506370089bd990d8720db62"
+  integrity sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==
+
 "@babel/helper-validator-option@^7.22.15", "@babel/helper-validator-option@^7.23.5":
   version "7.23.5"
   resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307"
@@ -346,6 +1246,15 @@
     "@babel/traverse" "^7.24.0"
     "@babel/types" "^7.24.0"
 
+"@babel/helpers@^7.24.5":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.5.tgz#fedeb87eeafa62b621160402181ad8585a22a40a"
+  integrity sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==
+  dependencies:
+    "@babel/template" "^7.24.0"
+    "@babel/traverse" "^7.24.5"
+    "@babel/types" "^7.24.5"
+
 "@babel/highlight@^7.23.4":
   version "7.23.4"
   resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b"
@@ -355,6 +1264,16 @@
     chalk "^2.4.2"
     js-tokens "^4.0.0"
 
+"@babel/highlight@^7.24.2":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.5.tgz#bc0613f98e1dd0720e99b2a9ee3760194a704b6e"
+  integrity sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==
+  dependencies:
+    "@babel/helper-validator-identifier" "^7.24.5"
+    chalk "^2.4.2"
+    js-tokens "^4.0.0"
+    picocolors "^1.0.0"
+
 "@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.5", "@babel/parser@^7.20.7", "@babel/parser@^7.23.6", "@babel/parser@^7.23.9":
   version "7.23.9"
   resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.9.tgz#7b903b6149b0f8fa7ad564af646c4c38a77fc44b"
@@ -365,6 +1284,19 @@
   resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.0.tgz#26a3d1ff49031c53a97d03b604375f028746a9ac"
   integrity sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==
 
+"@babel/parser@^7.24.5":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.5.tgz#4a4d5ab4315579e5398a82dcf636ca80c3392790"
+  integrity sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==
+
+"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.5":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.5.tgz#4c3685eb9cd790bcad2843900fe0250c91ccf895"
+  integrity sha512-LdXRi1wEMTrHVR4Zc9F8OewC3vdm5h4QB6L71zy6StmYeqGi1b3ttIO8UC+BfZKcH9jdr4aI249rBkm+3+YvHw==
+  dependencies:
+    "@babel/helper-environment-visitor" "^7.22.20"
+    "@babel/helper-plugin-utils" "^7.24.5"
+
 "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz#5cd1c87ba9380d0afb78469292c954fee5d2411a"
@@ -372,6 +1304,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz#b645d9ba8c2bc5b7af50f0fe949f9edbeb07c8cf"
+  integrity sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz#f6652bb16b94f8f9c20c50941e16e9756898dc5d"
@@ -381,6 +1320,15 @@
     "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
     "@babel/plugin-transform-optional-chaining" "^7.23.3"
 
+"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz#da8261f2697f0f41b0855b91d3a20a1fbfd271d3"
+  integrity sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+    "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
+    "@babel/plugin-transform-optional-chaining" "^7.24.1"
+
 "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.23.7":
   version "7.23.7"
   resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz#516462a95d10a9618f197d39ad291a9b47ae1d7b"
@@ -389,6 +1337,14 @@
     "@babel/helper-environment-visitor" "^7.22.20"
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz#1181d9685984c91d657b8ddf14f0487a6bab2988"
+  integrity sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==
+  dependencies:
+    "@babel/helper-environment-visitor" "^7.22.20"
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-proposal-class-properties@^7.8.3":
   version "7.18.6"
   resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3"
@@ -483,6 +1439,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-syntax-import-assertions@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz#db3aad724153a00eaac115a3fb898de544e34971"
+  integrity sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-syntax-import-attributes@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz#992aee922cf04512461d7dae3ff6951b90a2dc06"
@@ -490,6 +1453,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-syntax-import-attributes@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz#c66b966c63b714c4eec508fcf5763b1f2d381093"
+  integrity sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-syntax-import-meta@^7.10.4", "@babel/plugin-syntax-import-meta@^7.8.3":
   version "7.10.4"
   resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51"
@@ -511,6 +1481,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-syntax-jsx@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz#3f6ca04b8c841811dbc3c5c5f837934e0d626c10"
+  integrity sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3":
   version "7.10.4"
   resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699"
@@ -574,6 +1551,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-syntax-typescript@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz#b3bcc51f396d15f3591683f90239de143c076844"
+  integrity sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-syntax-unicode-sets-regex@^7.18.6":
   version "7.18.6"
   resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357"
@@ -589,6 +1573,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-arrow-functions@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz#2bf263617060c9cc45bcdbf492b8cc805082bf27"
+  integrity sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-transform-async-generator-functions@^7.23.9":
   version "7.23.9"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz#9adaeb66fc9634a586c5df139c6240d41ed801ce"
@@ -599,6 +1590,16 @@
     "@babel/helper-remap-async-to-generator" "^7.22.20"
     "@babel/plugin-syntax-async-generators" "^7.8.4"
 
+"@babel/plugin-transform-async-generator-functions@^7.24.3":
+  version "7.24.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz#8fa7ae481b100768cc9842c8617808c5352b8b89"
+  integrity sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg==
+  dependencies:
+    "@babel/helper-environment-visitor" "^7.22.20"
+    "@babel/helper-plugin-utils" "^7.24.0"
+    "@babel/helper-remap-async-to-generator" "^7.22.20"
+    "@babel/plugin-syntax-async-generators" "^7.8.4"
+
 "@babel/plugin-transform-async-to-generator@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz#d1f513c7a8a506d43f47df2bf25f9254b0b051fa"
@@ -608,6 +1609,15 @@
     "@babel/helper-plugin-utils" "^7.22.5"
     "@babel/helper-remap-async-to-generator" "^7.22.20"
 
+"@babel/plugin-transform-async-to-generator@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz#0e220703b89f2216800ce7b1c53cb0cf521c37f4"
+  integrity sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw==
+  dependencies:
+    "@babel/helper-module-imports" "^7.24.1"
+    "@babel/helper-plugin-utils" "^7.24.0"
+    "@babel/helper-remap-async-to-generator" "^7.22.20"
+
 "@babel/plugin-transform-block-scoped-functions@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz#fe1177d715fb569663095e04f3598525d98e8c77"
@@ -615,6 +1625,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-block-scoped-functions@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz#1c94799e20fcd5c4d4589523bbc57b7692979380"
+  integrity sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-transform-block-scoping@^7.23.4":
   version "7.23.4"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz#b2d38589531c6c80fbe25e6b58e763622d2d3cf5"
@@ -622,6 +1639,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-block-scoping@^7.24.5":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.5.tgz#89574191397f85661d6f748d4b89ee4d9ee69a2a"
+  integrity sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.5"
+
 "@babel/plugin-transform-class-properties@^7.22.5", "@babel/plugin-transform-class-properties@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz#35c377db11ca92a785a718b6aa4e3ed1eb65dc48"
@@ -630,6 +1654,14 @@
     "@babel/helper-create-class-features-plugin" "^7.22.15"
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-class-properties@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz#bcbf1aef6ba6085cfddec9fc8d58871cf011fc29"
+  integrity sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==
+  dependencies:
+    "@babel/helper-create-class-features-plugin" "^7.24.1"
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-transform-class-static-block@^7.23.4":
   version "7.23.4"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz#2a202c8787a8964dd11dfcedf994d36bfc844ab5"
@@ -639,6 +1671,15 @@
     "@babel/helper-plugin-utils" "^7.22.5"
     "@babel/plugin-syntax-class-static-block" "^7.14.5"
 
+"@babel/plugin-transform-class-static-block@^7.24.4":
+  version "7.24.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz#1a4653c0cf8ac46441ec406dece6e9bc590356a4"
+  integrity sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==
+  dependencies:
+    "@babel/helper-create-class-features-plugin" "^7.24.4"
+    "@babel/helper-plugin-utils" "^7.24.0"
+    "@babel/plugin-syntax-class-static-block" "^7.14.5"
+
 "@babel/plugin-transform-classes@^7.23.8":
   version "7.23.8"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz#d08ae096c240347badd68cdf1b6d1624a6435d92"
@@ -653,6 +1694,20 @@
     "@babel/helper-split-export-declaration" "^7.22.6"
     globals "^11.1.0"
 
+"@babel/plugin-transform-classes@^7.24.5":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.5.tgz#05e04a09df49a46348299a0e24bfd7e901129339"
+  integrity sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q==
+  dependencies:
+    "@babel/helper-annotate-as-pure" "^7.22.5"
+    "@babel/helper-compilation-targets" "^7.23.6"
+    "@babel/helper-environment-visitor" "^7.22.20"
+    "@babel/helper-function-name" "^7.23.0"
+    "@babel/helper-plugin-utils" "^7.24.5"
+    "@babel/helper-replace-supers" "^7.24.1"
+    "@babel/helper-split-export-declaration" "^7.24.5"
+    globals "^11.1.0"
+
 "@babel/plugin-transform-computed-properties@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz#652e69561fcc9d2b50ba4f7ac7f60dcf65e86474"
@@ -661,6 +1716,14 @@
     "@babel/helper-plugin-utils" "^7.22.5"
     "@babel/template" "^7.22.15"
 
+"@babel/plugin-transform-computed-properties@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz#bc7e787f8e021eccfb677af5f13c29a9934ed8a7"
+  integrity sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+    "@babel/template" "^7.24.0"
+
 "@babel/plugin-transform-destructuring@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz#8c9ee68228b12ae3dff986e56ed1ba4f3c446311"
@@ -668,6 +1731,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-destructuring@^7.24.5":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.5.tgz#80843ee6a520f7362686d1a97a7b53544ede453c"
+  integrity sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.5"
+
 "@babel/plugin-transform-dotall-regex@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz#3f7af6054882ede89c378d0cf889b854a993da50"
@@ -676,13 +1746,28 @@
     "@babel/helper-create-regexp-features-plugin" "^7.22.15"
     "@babel/helper-plugin-utils" "^7.22.5"
 
-"@babel/plugin-transform-duplicate-keys@^7.23.3":
-  version "7.23.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz#664706ca0a5dfe8d066537f99032fc1dc8b720ce"
+"@babel/plugin-transform-dotall-regex@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz#d56913d2f12795cc9930801b84c6f8c47513ac13"
+  integrity sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==
+  dependencies:
+    "@babel/helper-create-regexp-features-plugin" "^7.22.15"
+    "@babel/helper-plugin-utils" "^7.24.0"
+
+"@babel/plugin-transform-duplicate-keys@^7.23.3":
+  version "7.23.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz#664706ca0a5dfe8d066537f99032fc1dc8b720ce"
   integrity sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==
   dependencies:
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-duplicate-keys@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz#5347a797fe82b8d09749d10e9f5b83665adbca88"
+  integrity sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-transform-dynamic-import@^7.23.4":
   version "7.23.4"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz#c7629e7254011ac3630d47d7f34ddd40ca535143"
@@ -691,6 +1776,14 @@
     "@babel/helper-plugin-utils" "^7.22.5"
     "@babel/plugin-syntax-dynamic-import" "^7.8.3"
 
+"@babel/plugin-transform-dynamic-import@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz#2a5a49959201970dd09a5fca856cb651e44439dd"
+  integrity sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+    "@babel/plugin-syntax-dynamic-import" "^7.8.3"
+
 "@babel/plugin-transform-exponentiation-operator@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz#ea0d978f6b9232ba4722f3dbecdd18f450babd18"
@@ -699,6 +1792,14 @@
     "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.15"
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-exponentiation-operator@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz#6650ebeb5bd5c012d5f5f90a26613a08162e8ba4"
+  integrity sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==
+  dependencies:
+    "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.15"
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-transform-export-namespace-from@^7.23.4":
   version "7.23.4"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz#084c7b25e9a5c8271e987a08cf85807b80283191"
@@ -707,6 +1808,14 @@
     "@babel/helper-plugin-utils" "^7.22.5"
     "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
 
+"@babel/plugin-transform-export-namespace-from@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz#f033541fc036e3efb2dcb58eedafd4f6b8078acd"
+  integrity sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+    "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+
 "@babel/plugin-transform-for-of@^7.23.6":
   version "7.23.6"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz#81c37e24171b37b370ba6aaffa7ac86bcb46f94e"
@@ -715,6 +1824,14 @@
     "@babel/helper-plugin-utils" "^7.22.5"
     "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
 
+"@babel/plugin-transform-for-of@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz#67448446b67ab6c091360ce3717e7d3a59e202fd"
+  integrity sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+    "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
+
 "@babel/plugin-transform-function-name@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz#8f424fcd862bf84cb9a1a6b42bc2f47ed630f8dc"
@@ -724,6 +1841,15 @@
     "@babel/helper-function-name" "^7.23.0"
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-function-name@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz#8cba6f7730626cc4dfe4ca2fa516215a0592b361"
+  integrity sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==
+  dependencies:
+    "@babel/helper-compilation-targets" "^7.23.6"
+    "@babel/helper-function-name" "^7.23.0"
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-transform-json-strings@^7.23.4":
   version "7.23.4"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz#a871d9b6bd171976efad2e43e694c961ffa3714d"
@@ -732,6 +1858,14 @@
     "@babel/helper-plugin-utils" "^7.22.5"
     "@babel/plugin-syntax-json-strings" "^7.8.3"
 
+"@babel/plugin-transform-json-strings@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz#08e6369b62ab3e8a7b61089151b161180c8299f7"
+  integrity sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+    "@babel/plugin-syntax-json-strings" "^7.8.3"
+
 "@babel/plugin-transform-literals@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz#8214665f00506ead73de157eba233e7381f3beb4"
@@ -739,6 +1873,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-literals@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz#0a1982297af83e6b3c94972686067df588c5c096"
+  integrity sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-transform-logical-assignment-operators@^7.23.4":
   version "7.23.4"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz#e599f82c51d55fac725f62ce55d3a0886279ecb5"
@@ -747,6 +1888,14 @@
     "@babel/helper-plugin-utils" "^7.22.5"
     "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
 
+"@babel/plugin-transform-logical-assignment-operators@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz#719d8aded1aa94b8fb34e3a785ae8518e24cfa40"
+  integrity sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+    "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
+
 "@babel/plugin-transform-member-expression-literals@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz#e37b3f0502289f477ac0e776b05a833d853cabcc"
@@ -754,6 +1903,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-member-expression-literals@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz#896d23601c92f437af8b01371ad34beb75df4489"
+  integrity sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-transform-modules-amd@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz#e19b55436a1416829df0a1afc495deedfae17f7d"
@@ -762,6 +1918,14 @@
     "@babel/helper-module-transforms" "^7.23.3"
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-modules-amd@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz#b6d829ed15258536977e9c7cc6437814871ffa39"
+  integrity sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==
+  dependencies:
+    "@babel/helper-module-transforms" "^7.23.3"
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-transform-modules-commonjs@^7.2.0", "@babel/plugin-transform-modules-commonjs@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz#661ae831b9577e52be57dd8356b734f9700b53b4"
@@ -771,6 +1935,15 @@
     "@babel/helper-plugin-utils" "^7.22.5"
     "@babel/helper-simple-access" "^7.22.5"
 
+"@babel/plugin-transform-modules-commonjs@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz#e71ba1d0d69e049a22bf90b3867e263823d3f1b9"
+  integrity sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==
+  dependencies:
+    "@babel/helper-module-transforms" "^7.23.3"
+    "@babel/helper-plugin-utils" "^7.24.0"
+    "@babel/helper-simple-access" "^7.22.5"
+
 "@babel/plugin-transform-modules-systemjs@^7.23.9":
   version "7.23.9"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.9.tgz#105d3ed46e4a21d257f83a2f9e2ee4203ceda6be"
@@ -781,6 +1954,16 @@
     "@babel/helper-plugin-utils" "^7.22.5"
     "@babel/helper-validator-identifier" "^7.22.20"
 
+"@babel/plugin-transform-modules-systemjs@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz#2b9625a3d4e445babac9788daec39094e6b11e3e"
+  integrity sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==
+  dependencies:
+    "@babel/helper-hoist-variables" "^7.22.5"
+    "@babel/helper-module-transforms" "^7.23.3"
+    "@babel/helper-plugin-utils" "^7.24.0"
+    "@babel/helper-validator-identifier" "^7.22.20"
+
 "@babel/plugin-transform-modules-umd@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz#5d4395fccd071dfefe6585a4411aa7d6b7d769e9"
@@ -789,6 +1972,14 @@
     "@babel/helper-module-transforms" "^7.23.3"
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-modules-umd@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz#69220c66653a19cf2c0872b9c762b9a48b8bebef"
+  integrity sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==
+  dependencies:
+    "@babel/helper-module-transforms" "^7.23.3"
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-transform-named-capturing-groups-regex@^7.22.5":
   version "7.22.5"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f"
@@ -804,6 +1995,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-new-target@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz#29c59988fa3d0157de1c871a28cd83096363cc34"
+  integrity sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-transform-nullish-coalescing-operator@^7.23.4":
   version "7.23.4"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz#45556aad123fc6e52189ea749e33ce090637346e"
@@ -812,6 +2010,14 @@
     "@babel/helper-plugin-utils" "^7.22.5"
     "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
 
+"@babel/plugin-transform-nullish-coalescing-operator@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz#0cd494bb97cb07d428bd651632cb9d4140513988"
+  integrity sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+    "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
+
 "@babel/plugin-transform-numeric-separator@^7.23.4":
   version "7.23.4"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz#03d08e3691e405804ecdd19dd278a40cca531f29"
@@ -820,6 +2026,14 @@
     "@babel/helper-plugin-utils" "^7.22.5"
     "@babel/plugin-syntax-numeric-separator" "^7.10.4"
 
+"@babel/plugin-transform-numeric-separator@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz#5bc019ce5b3435c1cadf37215e55e433d674d4e8"
+  integrity sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+    "@babel/plugin-syntax-numeric-separator" "^7.10.4"
+
 "@babel/plugin-transform-object-rest-spread@^7.23.4":
   version "7.23.4"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz#2b9c2d26bf62710460bdc0d1730d4f1048361b83"
@@ -842,6 +2056,16 @@
     "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
     "@babel/plugin-transform-parameters" "^7.23.3"
 
+"@babel/plugin-transform-object-rest-spread@^7.24.5":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.5.tgz#f91bbcb092ff957c54b4091c86bda8372f0b10ef"
+  integrity sha512-7EauQHszLGM3ay7a161tTQH7fj+3vVM/gThlz5HpFtnygTxjrlvoeq7MPVA1Vy9Q555OB8SnAOsMkLShNkkrHA==
+  dependencies:
+    "@babel/helper-compilation-targets" "^7.23.6"
+    "@babel/helper-plugin-utils" "^7.24.5"
+    "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
+    "@babel/plugin-transform-parameters" "^7.24.5"
+
 "@babel/plugin-transform-object-super@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz#81fdb636dcb306dd2e4e8fd80db5b2362ed2ebcd"
@@ -850,6 +2074,14 @@
     "@babel/helper-plugin-utils" "^7.22.5"
     "@babel/helper-replace-supers" "^7.22.20"
 
+"@babel/plugin-transform-object-super@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz#e71d6ab13483cca89ed95a474f542bbfc20a0520"
+  integrity sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+    "@babel/helper-replace-supers" "^7.24.1"
+
 "@babel/plugin-transform-optional-catch-binding@^7.23.4":
   version "7.23.4"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz#318066de6dacce7d92fa244ae475aa8d91778017"
@@ -858,6 +2090,14 @@
     "@babel/helper-plugin-utils" "^7.22.5"
     "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
 
+"@babel/plugin-transform-optional-catch-binding@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz#92a3d0efe847ba722f1a4508669b23134669e2da"
+  integrity sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+    "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
+
 "@babel/plugin-transform-optional-chaining@^7.23.3", "@babel/plugin-transform-optional-chaining@^7.23.4":
   version "7.23.4"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz#6acf61203bdfc4de9d4e52e64490aeb3e52bd017"
@@ -867,6 +2107,15 @@
     "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
     "@babel/plugin-syntax-optional-chaining" "^7.8.3"
 
+"@babel/plugin-transform-optional-chaining@^7.24.1", "@babel/plugin-transform-optional-chaining@^7.24.5":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.5.tgz#a6334bebd7f9dd3df37447880d0bd64b778e600f"
+  integrity sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.5"
+    "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
+    "@babel/plugin-syntax-optional-chaining" "^7.8.3"
+
 "@babel/plugin-transform-parameters@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz#83ef5d1baf4b1072fa6e54b2b0999a7b2527e2af"
@@ -874,6 +2123,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-parameters@^7.24.5":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.5.tgz#5c3b23f3a6b8fed090f9b98f2926896d3153cc62"
+  integrity sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.5"
+
 "@babel/plugin-transform-private-methods@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz#b2d7a3c97e278bfe59137a978d53b2c2e038c0e4"
@@ -882,6 +2138,14 @@
     "@babel/helper-create-class-features-plugin" "^7.22.15"
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-private-methods@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz#a0faa1ae87eff077e1e47a5ec81c3aef383dc15a"
+  integrity sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==
+  dependencies:
+    "@babel/helper-create-class-features-plugin" "^7.24.1"
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-transform-private-property-in-object@^7.23.4":
   version "7.23.4"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz#3ec711d05d6608fd173d9b8de39872d8dbf68bf5"
@@ -892,6 +2156,16 @@
     "@babel/helper-plugin-utils" "^7.22.5"
     "@babel/plugin-syntax-private-property-in-object" "^7.14.5"
 
+"@babel/plugin-transform-private-property-in-object@^7.24.5":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.5.tgz#f5d1fcad36e30c960134cb479f1ca98a5b06eda5"
+  integrity sha512-JM4MHZqnWR04jPMujQDTBVRnqxpLLpx2tkn7iPn+Hmsc0Gnb79yvRWOkvqFOx3Z7P7VxiRIR22c4eGSNj87OBQ==
+  dependencies:
+    "@babel/helper-annotate-as-pure" "^7.22.5"
+    "@babel/helper-create-class-features-plugin" "^7.24.5"
+    "@babel/helper-plugin-utils" "^7.24.5"
+    "@babel/plugin-syntax-private-property-in-object" "^7.14.5"
+
 "@babel/plugin-transform-property-literals@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz#54518f14ac4755d22b92162e4a852d308a560875"
@@ -899,6 +2173,53 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-property-literals@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz#d6a9aeab96f03749f4eebeb0b6ea8e90ec958825"
+  integrity sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+
+"@babel/plugin-transform-react-constant-elements@^7.21.3":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.24.1.tgz#d493a0918b9fdad7540f5afd9b5eb5c52500d18d"
+  integrity sha512-QXp1U9x0R7tkiGB0FOk8o74jhnap0FlZ5gNkRIWdG3eP+SvMFg118e1zaWewDzgABb106QSKpVsD3Wgd8t6ifA==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+
+"@babel/plugin-transform-react-display-name@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.1.tgz#554e3e1a25d181f040cf698b93fd289a03bfdcdb"
+  integrity sha512-mvoQg2f9p2qlpDQRBC7M3c3XTr0k7cp/0+kFKKO/7Gtu0LSw16eKB+Fabe2bDT/UpsyasTBBkAnbdsLrkD5XMw==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+
+"@babel/plugin-transform-react-jsx-development@^7.22.5":
+  version "7.22.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz#e716b6edbef972a92165cd69d92f1255f7e73e87"
+  integrity sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==
+  dependencies:
+    "@babel/plugin-transform-react-jsx" "^7.22.5"
+
+"@babel/plugin-transform-react-jsx@^7.22.5", "@babel/plugin-transform-react-jsx@^7.23.4":
+  version "7.23.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz#393f99185110cea87184ea47bcb4a7b0c2e39312"
+  integrity sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==
+  dependencies:
+    "@babel/helper-annotate-as-pure" "^7.22.5"
+    "@babel/helper-module-imports" "^7.22.15"
+    "@babel/helper-plugin-utils" "^7.22.5"
+    "@babel/plugin-syntax-jsx" "^7.23.3"
+    "@babel/types" "^7.23.4"
+
+"@babel/plugin-transform-react-pure-annotations@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.24.1.tgz#c86bce22a53956331210d268e49a0ff06e392470"
+  integrity sha512-+pWEAaDJvSm9aFvJNpLiM2+ktl2Sn2U5DdyiWdZBxmLc6+xGt88dvFqsHiAiDS+8WqUwbDfkKz9jRxK3M0k+kA==
+  dependencies:
+    "@babel/helper-annotate-as-pure" "^7.22.5"
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-transform-regenerator@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz#141afd4a2057298602069fce7f2dc5173e6c561c"
@@ -907,6 +2228,14 @@
     "@babel/helper-plugin-utils" "^7.22.5"
     regenerator-transform "^0.15.2"
 
+"@babel/plugin-transform-regenerator@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz#625b7545bae52363bdc1fbbdc7252b5046409c8c"
+  integrity sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+    regenerator-transform "^0.15.2"
+
 "@babel/plugin-transform-reserved-words@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz#4130dcee12bd3dd5705c587947eb715da12efac8"
@@ -914,6 +2243,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-reserved-words@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz#8de729f5ecbaaf5cf83b67de13bad38a21be57c1"
+  integrity sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-transform-runtime@^7.11.0":
   version "7.23.9"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.9.tgz#2c64d0680fc8e09e1dfe8fd5c646fe72abd82004"
@@ -945,6 +2281,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-shorthand-properties@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz#ba9a09144cf55d35ec6b93a32253becad8ee5b55"
+  integrity sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-transform-spread@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz#41d17aacb12bde55168403c6f2d6bdca563d362c"
@@ -953,6 +2296,14 @@
     "@babel/helper-plugin-utils" "^7.22.5"
     "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
 
+"@babel/plugin-transform-spread@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz#a1acf9152cbf690e4da0ba10790b3ac7d2b2b391"
+  integrity sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+    "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
+
 "@babel/plugin-transform-sticky-regex@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz#dec45588ab4a723cb579c609b294a3d1bd22ff04"
@@ -960,6 +2311,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-sticky-regex@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz#f03e672912c6e203ed8d6e0271d9c2113dc031b9"
+  integrity sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-transform-template-literals@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz#5f0f028eb14e50b5d0f76be57f90045757539d07"
@@ -967,6 +2325,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-template-literals@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz#15e2166873a30d8617e3e2ccadb86643d327aab7"
+  integrity sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-transform-typeof-symbol@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz#9dfab97acc87495c0c449014eb9c547d8966bca4"
@@ -974,6 +2339,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-typeof-symbol@^7.24.5":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.5.tgz#703cace5ef74155fb5eecab63cbfc39bdd25fe12"
+  integrity sha512-UTGnhYVZtTAjdwOTzT+sCyXmTn8AhaxOS/MjG9REclZ6ULHWF9KoCZur0HSGU7hk8PdBFKKbYe6+gqdXWz84Jg==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.5"
+
 "@babel/plugin-transform-typescript@^7.23.3":
   version "7.23.6"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.6.tgz#aa36a94e5da8d94339ae3a4e22d40ed287feb34c"
@@ -984,6 +2356,16 @@
     "@babel/helper-plugin-utils" "^7.22.5"
     "@babel/plugin-syntax-typescript" "^7.23.3"
 
+"@babel/plugin-transform-typescript@^7.24.1":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.5.tgz#bcba979e462120dc06a75bd34c473a04781931b8"
+  integrity sha512-E0VWu/hk83BIFUWnsKZ4D81KXjN5L3MobvevOHErASk9IPwKHOkTgvqzvNo1yP/ePJWqqK2SpUR5z+KQbl6NVw==
+  dependencies:
+    "@babel/helper-annotate-as-pure" "^7.22.5"
+    "@babel/helper-create-class-features-plugin" "^7.24.5"
+    "@babel/helper-plugin-utils" "^7.24.5"
+    "@babel/plugin-syntax-typescript" "^7.24.1"
+
 "@babel/plugin-transform-unicode-escapes@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz#1f66d16cab01fab98d784867d24f70c1ca65b925"
@@ -991,6 +2373,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-unicode-escapes@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz#fb3fa16676549ac7c7449db9b342614985c2a3a4"
+  integrity sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-transform-unicode-property-regex@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz#19e234129e5ffa7205010feec0d94c251083d7ad"
@@ -999,6 +2388,14 @@
     "@babel/helper-create-regexp-features-plugin" "^7.22.15"
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-unicode-property-regex@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz#56704fd4d99da81e5e9f0c0c93cabd91dbc4889e"
+  integrity sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==
+  dependencies:
+    "@babel/helper-create-regexp-features-plugin" "^7.22.15"
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-transform-unicode-regex@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz#26897708d8f42654ca4ce1b73e96140fbad879dc"
@@ -1007,6 +2404,14 @@
     "@babel/helper-create-regexp-features-plugin" "^7.22.15"
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-unicode-regex@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz#57c3c191d68f998ac46b708380c1ce4d13536385"
+  integrity sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==
+  dependencies:
+    "@babel/helper-create-regexp-features-plugin" "^7.22.15"
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/plugin-transform-unicode-sets-regex@^7.23.3":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz#4fb6f0a719c2c5859d11f6b55a050cc987f3799e"
@@ -1015,6 +2420,14 @@
     "@babel/helper-create-regexp-features-plugin" "^7.22.15"
     "@babel/helper-plugin-utils" "^7.22.5"
 
+"@babel/plugin-transform-unicode-sets-regex@^7.24.1":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz#c1ea175b02afcffc9cf57a9c4658326625165b7f"
+  integrity sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==
+  dependencies:
+    "@babel/helper-create-regexp-features-plugin" "^7.22.15"
+    "@babel/helper-plugin-utils" "^7.24.0"
+
 "@babel/preset-env@^7.11.0":
   version "7.23.9"
   resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.23.9.tgz#beace3b7994560ed6bf78e4ae2073dff45387669"
@@ -1101,6 +2514,93 @@
     core-js-compat "^3.31.0"
     semver "^6.3.1"
 
+"@babel/preset-env@^7.20.2":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.5.tgz#6a9ac90bd5a5a9dae502af60dfc58c190551bbcd"
+  integrity sha512-UGK2ifKtcC8i5AI4cH+sbLLuLc2ktYSFJgBAXorKAsHUZmrQ1q6aQ6i3BvU24wWs2AAKqQB6kq3N9V9Gw1HiMQ==
+  dependencies:
+    "@babel/compat-data" "^7.24.4"
+    "@babel/helper-compilation-targets" "^7.23.6"
+    "@babel/helper-plugin-utils" "^7.24.5"
+    "@babel/helper-validator-option" "^7.23.5"
+    "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.24.5"
+    "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.24.1"
+    "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.24.1"
+    "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.24.1"
+    "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2"
+    "@babel/plugin-syntax-async-generators" "^7.8.4"
+    "@babel/plugin-syntax-class-properties" "^7.12.13"
+    "@babel/plugin-syntax-class-static-block" "^7.14.5"
+    "@babel/plugin-syntax-dynamic-import" "^7.8.3"
+    "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+    "@babel/plugin-syntax-import-assertions" "^7.24.1"
+    "@babel/plugin-syntax-import-attributes" "^7.24.1"
+    "@babel/plugin-syntax-import-meta" "^7.10.4"
+    "@babel/plugin-syntax-json-strings" "^7.8.3"
+    "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
+    "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
+    "@babel/plugin-syntax-numeric-separator" "^7.10.4"
+    "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
+    "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
+    "@babel/plugin-syntax-optional-chaining" "^7.8.3"
+    "@babel/plugin-syntax-private-property-in-object" "^7.14.5"
+    "@babel/plugin-syntax-top-level-await" "^7.14.5"
+    "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6"
+    "@babel/plugin-transform-arrow-functions" "^7.24.1"
+    "@babel/plugin-transform-async-generator-functions" "^7.24.3"
+    "@babel/plugin-transform-async-to-generator" "^7.24.1"
+    "@babel/plugin-transform-block-scoped-functions" "^7.24.1"
+    "@babel/plugin-transform-block-scoping" "^7.24.5"
+    "@babel/plugin-transform-class-properties" "^7.24.1"
+    "@babel/plugin-transform-class-static-block" "^7.24.4"
+    "@babel/plugin-transform-classes" "^7.24.5"
+    "@babel/plugin-transform-computed-properties" "^7.24.1"
+    "@babel/plugin-transform-destructuring" "^7.24.5"
+    "@babel/plugin-transform-dotall-regex" "^7.24.1"
+    "@babel/plugin-transform-duplicate-keys" "^7.24.1"
+    "@babel/plugin-transform-dynamic-import" "^7.24.1"
+    "@babel/plugin-transform-exponentiation-operator" "^7.24.1"
+    "@babel/plugin-transform-export-namespace-from" "^7.24.1"
+    "@babel/plugin-transform-for-of" "^7.24.1"
+    "@babel/plugin-transform-function-name" "^7.24.1"
+    "@babel/plugin-transform-json-strings" "^7.24.1"
+    "@babel/plugin-transform-literals" "^7.24.1"
+    "@babel/plugin-transform-logical-assignment-operators" "^7.24.1"
+    "@babel/plugin-transform-member-expression-literals" "^7.24.1"
+    "@babel/plugin-transform-modules-amd" "^7.24.1"
+    "@babel/plugin-transform-modules-commonjs" "^7.24.1"
+    "@babel/plugin-transform-modules-systemjs" "^7.24.1"
+    "@babel/plugin-transform-modules-umd" "^7.24.1"
+    "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5"
+    "@babel/plugin-transform-new-target" "^7.24.1"
+    "@babel/plugin-transform-nullish-coalescing-operator" "^7.24.1"
+    "@babel/plugin-transform-numeric-separator" "^7.24.1"
+    "@babel/plugin-transform-object-rest-spread" "^7.24.5"
+    "@babel/plugin-transform-object-super" "^7.24.1"
+    "@babel/plugin-transform-optional-catch-binding" "^7.24.1"
+    "@babel/plugin-transform-optional-chaining" "^7.24.5"
+    "@babel/plugin-transform-parameters" "^7.24.5"
+    "@babel/plugin-transform-private-methods" "^7.24.1"
+    "@babel/plugin-transform-private-property-in-object" "^7.24.5"
+    "@babel/plugin-transform-property-literals" "^7.24.1"
+    "@babel/plugin-transform-regenerator" "^7.24.1"
+    "@babel/plugin-transform-reserved-words" "^7.24.1"
+    "@babel/plugin-transform-shorthand-properties" "^7.24.1"
+    "@babel/plugin-transform-spread" "^7.24.1"
+    "@babel/plugin-transform-sticky-regex" "^7.24.1"
+    "@babel/plugin-transform-template-literals" "^7.24.1"
+    "@babel/plugin-transform-typeof-symbol" "^7.24.5"
+    "@babel/plugin-transform-unicode-escapes" "^7.24.1"
+    "@babel/plugin-transform-unicode-property-regex" "^7.24.1"
+    "@babel/plugin-transform-unicode-regex" "^7.24.1"
+    "@babel/plugin-transform-unicode-sets-regex" "^7.24.1"
+    "@babel/preset-modules" "0.1.6-no-external-plugins"
+    babel-plugin-polyfill-corejs2 "^0.4.10"
+    babel-plugin-polyfill-corejs3 "^0.10.4"
+    babel-plugin-polyfill-regenerator "^0.6.1"
+    core-js-compat "^3.31.0"
+    semver "^6.3.1"
+
 "@babel/preset-env@^7.23.2":
   version "7.24.0"
   resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.0.tgz#11536a7f4b977294f0bdfad780f01a8ac8e183fc"
@@ -1196,6 +2696,29 @@
     "@babel/types" "^7.4.4"
     esutils "^2.0.2"
 
+"@babel/preset-react@^7.18.6":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.24.1.tgz#2450c2ac5cc498ef6101a6ca5474de251e33aa95"
+  integrity sha512-eFa8up2/8cZXLIpkafhaADTXSnl7IsUFCYenRWrARBz0/qZwcT0RBXpys0LJU4+WfPoF2ZG6ew6s2V6izMCwRA==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+    "@babel/helper-validator-option" "^7.23.5"
+    "@babel/plugin-transform-react-display-name" "^7.24.1"
+    "@babel/plugin-transform-react-jsx" "^7.23.4"
+    "@babel/plugin-transform-react-jsx-development" "^7.22.5"
+    "@babel/plugin-transform-react-pure-annotations" "^7.24.1"
+
+"@babel/preset-typescript@^7.21.0":
+  version "7.24.1"
+  resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz#89bdf13a3149a17b3b2a2c9c62547f06db8845ec"
+  integrity sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.24.0"
+    "@babel/helper-validator-option" "^7.23.5"
+    "@babel/plugin-syntax-jsx" "^7.24.1"
+    "@babel/plugin-transform-modules-commonjs" "^7.24.1"
+    "@babel/plugin-transform-typescript" "^7.24.1"
+
 "@babel/preset-typescript@^7.22.5":
   version "7.23.3"
   resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.23.3.tgz#14534b34ed5b6d435aa05f1ae1c5e7adcc01d913"
@@ -1276,6 +2799,22 @@
     debug "^4.3.1"
     globals "^11.1.0"
 
+"@babel/traverse@^7.24.5":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.5.tgz#972aa0bc45f16983bf64aa1f877b2dd0eea7e6f8"
+  integrity sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==
+  dependencies:
+    "@babel/code-frame" "^7.24.2"
+    "@babel/generator" "^7.24.5"
+    "@babel/helper-environment-visitor" "^7.22.20"
+    "@babel/helper-function-name" "^7.23.0"
+    "@babel/helper-hoist-variables" "^7.22.5"
+    "@babel/helper-split-export-declaration" "^7.24.5"
+    "@babel/parser" "^7.24.5"
+    "@babel/types" "^7.24.5"
+    debug "^4.3.1"
+    globals "^11.1.0"
+
 "@babel/types@^7.0.0", "@babel/types@^7.18.4", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.6", "@babel/types@^7.23.9", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
   version "7.23.9"
   resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.9.tgz#1dd7b59a9a2b5c87f8b41e52770b5ecbf492e002"
@@ -1285,6 +2824,15 @@
     "@babel/helper-validator-identifier" "^7.22.20"
     to-fast-properties "^2.0.0"
 
+"@babel/types@^7.21.3", "@babel/types@^7.23.4", "@babel/types@^7.24.5":
+  version "7.24.5"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.5.tgz#7661930afc638a5383eb0c4aee59b74f38db84d7"
+  integrity sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==
+  dependencies:
+    "@babel/helper-string-parser" "^7.24.1"
+    "@babel/helper-validator-identifier" "^7.24.5"
+    to-fast-properties "^2.0.0"
+
 "@babel/types@^7.24.0":
   version "7.24.0"
   resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf"
@@ -1336,6 +2884,42 @@
   resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
   integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
 
+"@chainsafe/as-sha256@^0.3.1":
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/@chainsafe/as-sha256/-/as-sha256-0.3.1.tgz#3639df0e1435cab03f4d9870cc3ac079e57a6fc9"
+  integrity sha512-hldFFYuf49ed7DAakWVXSJODuq3pzJEguD8tQ7h+sGkM18vja+OFoJI9krnGmgzyuZC2ETX0NOIcCTy31v2Mtg==
+
+"@chainsafe/persistent-merkle-tree@^0.4.2":
+  version "0.4.2"
+  resolved "https://registry.yarnpkg.com/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.4.2.tgz#4c9ee80cc57cd3be7208d98c40014ad38f36f7ff"
+  integrity sha512-lLO3ihKPngXLTus/L7WHKaw9PnNJWizlOF1H9NNzHP6Xvh82vzg9F2bzkXhYIFshMZ2gTCEz8tq6STe7r5NDfQ==
+  dependencies:
+    "@chainsafe/as-sha256" "^0.3.1"
+
+"@chainsafe/persistent-merkle-tree@^0.5.0":
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.5.0.tgz#2b4a62c9489a5739dedd197250d8d2f5427e9f63"
+  integrity sha512-l0V1b5clxA3iwQLXP40zYjyZYospQLZXzBVIhhr9kDg/1qHZfzzHw0jj4VPBijfYCArZDlPkRi1wZaV2POKeuw==
+  dependencies:
+    "@chainsafe/as-sha256" "^0.3.1"
+
+"@chainsafe/ssz@^0.10.0":
+  version "0.10.2"
+  resolved "https://registry.yarnpkg.com/@chainsafe/ssz/-/ssz-0.10.2.tgz#c782929e1bb25fec66ba72e75934b31fd087579e"
+  integrity sha512-/NL3Lh8K+0q7A3LsiFq09YXS9fPE+ead2rr7vM2QK8PLzrNsw3uqrif9bpRX5UxgeRjM+vYi+boCM3+GM4ovXg==
+  dependencies:
+    "@chainsafe/as-sha256" "^0.3.1"
+    "@chainsafe/persistent-merkle-tree" "^0.5.0"
+
+"@chainsafe/ssz@^0.9.2":
+  version "0.9.4"
+  resolved "https://registry.yarnpkg.com/@chainsafe/ssz/-/ssz-0.9.4.tgz#696a8db46d6975b600f8309ad3a12f7c0e310497"
+  integrity sha512-77Qtg2N1ayqs4Bg/wvnWfg5Bta7iy7IRh8XqXh7oNMeP2HBbBwx8m6yTpA8p0EHItWPEBkgZd5S5/LSlp3GXuQ==
+  dependencies:
+    "@chainsafe/as-sha256" "^0.3.1"
+    "@chainsafe/persistent-merkle-tree" "^0.4.2"
+    case "^1.6.3"
+
 "@chaitanyapotti/register-service-worker@^1.7.3":
   version "1.7.3"
   resolved "https://registry.yarnpkg.com/@chaitanyapotti/register-service-worker/-/register-service-worker-1.7.3.tgz#0e670b8a4de1c319a147700d7ebb4f8a2b2e1b84"
@@ -1361,6 +2945,19 @@
   resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
   integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
 
+"@colors/colors@1.6.0", "@colors/colors@^1.6.0":
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.6.0.tgz#ec6cd237440700bc23ca23087f513c75508958b0"
+  integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==
+
+"@contrast/fn-inspect@^3.3.0":
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/@contrast/fn-inspect/-/fn-inspect-3.4.0.tgz#273b0802438c842b84fa39b16dc3a3979a96c481"
+  integrity sha512-Jw6dMFEIt/FXF1ihJri2GFNayeEKQ6r+WRjjWl7MdgMup2D4vCPu99ZV8eHSMqNNkj3BEzUNC91ZaJVB1XJmfg==
+  dependencies:
+    nan "^2.17.0"
+    node-gyp-build "^4.6.0"
+
 "@cspotcode/source-map-support@^0.8.0":
   version "0.8.1"
   resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
@@ -1400,6 +2997,80 @@
     debug "^3.1.0"
     lodash.once "^4.1.1"
 
+"@dabh/diagnostics@^2.0.2":
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a"
+  integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==
+  dependencies:
+    colorspace "1.1.x"
+    enabled "2.0.x"
+    kuler "^2.0.0"
+
+"@discordjs/builders@^1.8.1":
+  version "1.8.1"
+  resolved "https://registry.yarnpkg.com/@discordjs/builders/-/builders-1.8.1.tgz#5bca6e50a012492ecc03480ced53cbc7a1aac054"
+  integrity sha512-GkF+HM01FHy+NSoTaUPR8z44otfQgJ1AIsRxclYGUZDyUbdZEFyD/5QVv2Y1Flx6M+B0bQLzg2M9CJv5lGTqpA==
+  dependencies:
+    "@discordjs/formatters" "^0.4.0"
+    "@discordjs/util" "^1.1.0"
+    "@sapphire/shapeshift" "^3.9.7"
+    discord-api-types "0.37.83"
+    fast-deep-equal "^3.1.3"
+    ts-mixer "^6.0.4"
+    tslib "^2.6.2"
+
+"@discordjs/collection@1.5.3":
+  version "1.5.3"
+  resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-1.5.3.tgz#5a1250159ebfff9efa4f963cfa7e97f1b291be18"
+  integrity sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==
+
+"@discordjs/collection@^2.1.0":
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-2.1.0.tgz#f327d944ab2dcf9a1f674470a481f78a120a5e3b"
+  integrity sha512-mLcTACtXUuVgutoznkh6hS3UFqYirDYAg5Dc1m8xn6OvPjetnUlf/xjtqnnc47OwWdaoCQnHmHh9KofhD6uRqw==
+
+"@discordjs/formatters@^0.4.0":
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/@discordjs/formatters/-/formatters-0.4.0.tgz#066a2c2163b26ac066e6f621f17445be9690c6a9"
+  integrity sha512-fJ06TLC1NiruF35470q3Nr1bi95BdvKFAF+T5bNfZJ4bNdqZ3VZ+Ttg6SThqTxm6qumSG3choxLBHMC69WXNXQ==
+  dependencies:
+    discord-api-types "0.37.83"
+
+"@discordjs/rest@^2.3.0":
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/@discordjs/rest/-/rest-2.3.0.tgz#06d37c7fb54a9be61134b5bbb201abd760343472"
+  integrity sha512-C1kAJK8aSYRv3ZwMG8cvrrW4GN0g5eMdP8AuN8ODH5DyOCbHgJspze1my3xHOAgwLJdKUbWNVyAeJ9cEdduqIg==
+  dependencies:
+    "@discordjs/collection" "^2.1.0"
+    "@discordjs/util" "^1.1.0"
+    "@sapphire/async-queue" "^1.5.2"
+    "@sapphire/snowflake" "^3.5.3"
+    "@vladfrangu/async_event_emitter" "^2.2.4"
+    discord-api-types "0.37.83"
+    magic-bytes.js "^1.10.0"
+    tslib "^2.6.2"
+    undici "6.13.0"
+
+"@discordjs/util@^1.1.0":
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/@discordjs/util/-/util-1.1.0.tgz#dcffd2b61aab8eadd66bea67811bc34fc769bb2a"
+  integrity sha512-IndcI5hzlNZ7GS96RV3Xw1R2kaDuXEp7tRIy/KlhidpN/BQ1qh1NZt3377dMLTa44xDUNKT7hnXkA/oUAzD/lg==
+
+"@discordjs/ws@^1.1.0":
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/@discordjs/ws/-/ws-1.1.0.tgz#1fe3ab9979c77b44c9af6544b8e96c60940ad49a"
+  integrity sha512-O97DIeSvfNTn5wz5vaER6ciyUsr7nOqSEtsLoMhhIgeFkhnxLRqSr00/Fpq2/ppLgjDGLbQCDzIK7ilGoB/M7A==
+  dependencies:
+    "@discordjs/collection" "^2.1.0"
+    "@discordjs/rest" "^2.3.0"
+    "@discordjs/util" "^1.1.0"
+    "@sapphire/async-queue" "^1.5.2"
+    "@types/ws" "^8.5.10"
+    "@vladfrangu/async_event_emitter" "^2.2.4"
+    discord-api-types "0.37.83"
+    tslib "^2.6.2"
+    ws "^8.16.0"
+
 "@emotion/babel-plugin@^11.11.0":
   version "11.11.0"
   resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c"
@@ -1629,12 +3300,12 @@
   dependencies:
     eslint-visitor-keys "^3.3.0"
 
-"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1":
+"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1":
   version "4.10.0"
   resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63"
   integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==
 
-"@eslint/eslintrc@^2.1.2", "@eslint/eslintrc@^2.1.4":
+"@eslint/eslintrc@^2.1.4":
   version "2.1.4"
   resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad"
   integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==
@@ -1649,11 +3320,6 @@
     minimatch "^3.1.2"
     strip-json-comments "^3.1.1"
 
-"@eslint/js@8.48.0":
-  version "8.48.0"
-  resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.48.0.tgz#642633964e217905436033a2bd08bf322849b7fb"
-  integrity sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==
-
 "@eslint/js@8.57.0":
   version "8.57.0"
   resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f"
@@ -1707,7 +3373,7 @@
     ethereum-cryptography "^2.0.0"
     micro-ftch "^0.3.1"
 
-"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.4.0", "@ethersproject/abi@^5.6.3", "@ethersproject/abi@^5.7.0":
+"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.9", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.4.0", "@ethersproject/abi@^5.4.7", "@ethersproject/abi@^5.6.3", "@ethersproject/abi@^5.7.0":
   version "5.7.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449"
   integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==
@@ -1746,7 +3412,7 @@
     "@ethersproject/logger" "^5.7.0"
     "@ethersproject/properties" "^5.7.0"
 
-"@ethersproject/address@5.7.0", "@ethersproject/address@^5.4.0", "@ethersproject/address@^5.7.0":
+"@ethersproject/address@5.7.0", "@ethersproject/address@^5.0.2", "@ethersproject/address@^5.4.0", "@ethersproject/address@^5.7.0":
   version "5.7.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37"
   integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==
@@ -1905,7 +3571,7 @@
   dependencies:
     "@ethersproject/logger" "^5.7.0"
 
-"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.4.5":
+"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.4.5", "@ethersproject/providers@^5.4.7", "@ethersproject/providers@^5.7.0", "@ethersproject/providers@^5.7.1", "@ethersproject/providers@^5.7.2":
   version "5.7.2"
   resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb"
   integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==
@@ -2004,7 +3670,7 @@
     "@ethersproject/rlp" "^5.7.0"
     "@ethersproject/signing-key" "^5.7.0"
 
-"@ethersproject/units@5.7.0":
+"@ethersproject/units@5.7.0", "@ethersproject/units@^5.7.0":
   version "5.7.0"
   resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.7.0.tgz#637b563d7e14f42deeee39245275d477aae1d8b1"
   integrity sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==
@@ -2056,6 +3722,11 @@
     "@ethersproject/properties" "^5.7.0"
     "@ethersproject/strings" "^5.7.0"
 
+"@fastify/busboy@^2.0.0":
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d"
+  integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==
+
 "@floating-ui/core@^1.0.0":
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1"
@@ -2085,13 +3756,79 @@
     "@floating-ui/utils" "^0.2.1"
     vue-demi ">=0.13.0"
 
-"@hapi/address@2.x.x":
-  version "2.1.4"
-  resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
-  integrity sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==
+"@gala-chain/api@1.1.20":
+  version "1.1.20"
+  resolved "https://registry.yarnpkg.com/@gala-chain/api/-/api-1.1.20.tgz#acace81f278a66414d8882b65b7b8be1ea8ab025"
+  integrity sha512-6MOfdhN8IXL6uhjwo7wwPyPH+OpF8R4qv3ZA7z+1HiozvFWDK1DQKNWW3OSVKh+/yL4Z+skV0zDDfvBjdKx3Lw==
+  dependencies:
+    bignumber.js "^9.0.2"
+    bn.js "^4.12.0"
+    class-transformer "0.5.1"
+    class-validator "^0.14.0"
+    class-validator-jsonschema "^5.0.0"
+    elliptic "^6.5.4"
+    js-sha3 "^0.9.3"
+    json-stringify-deterministic "^1.0.7"
+    openapi3-ts "^3.2.0"
+    reflect-metadata "^0.1.13"
+    tslib "^2.6.2"
 
-"@hapi/bourne@1.x.x":
-  version "1.3.2"
+"@gala-chain/client@^1.1.20":
+  version "1.1.20"
+  resolved "https://registry.yarnpkg.com/@gala-chain/client/-/client-1.1.20.tgz#8138e56e15715254bb7bb902b5222c177e43413c"
+  integrity sha512-AEp4d40iEcLz22UP2JMqucywP4BYrAHJsTZlCmEt4dRpZOAyrnwPEOSMNZx+kn+8KMaoLLxbSFuBFxuZdkGt0Q==
+  dependencies:
+    "@gala-chain/api" "1.1.20"
+    axios "^1.6.0"
+    jsonschema "^1.4.1"
+    process "0.11.10"
+    tslib "^2.6.2"
+
+"@gnosis.pm/safe-contracts@1.3.0":
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-contracts/-/safe-contracts-1.3.0.tgz#316741a7690d8751a1f701538cfc9ec80866eedc"
+  integrity sha512-1p+1HwGvxGUVzVkFjNzglwHrLNA67U/axP0Ct85FzzH8yhGJb4t9jDjPYocVMzLorDoWAfKicGy1akPY9jXRVw==
+
+"@godaddy/terminus@^4.12.1":
+  version "4.12.1"
+  resolved "https://registry.yarnpkg.com/@godaddy/terminus/-/terminus-4.12.1.tgz#c4fdc280a4ac9655d4734250e22299a4ad46c54c"
+  integrity sha512-Tm+wVu1/V37uZXcT7xOhzdpFoovQReErff8x3y82k6YyWa1gzxWBjTyrx4G2enjEqoXPnUUmJ3MOmwH+TiP6Sw==
+  dependencies:
+    stoppable "^1.1.0"
+
+"@grpc/grpc-js@^1.9.4":
+  version "1.10.7"
+  resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.10.7.tgz#1abce1a8c4c90b79dbbe57d7e4310f3b0ce72899"
+  integrity sha512-ZMBVjSeDAz3tFSehyO6Pd08xZT1HfIwq3opbeM4cDlBh52gmwp0wVIPcQur53NN0ac68HMZ/7SF2rGRD5KmVmg==
+  dependencies:
+    "@grpc/proto-loader" "^0.7.13"
+    "@js-sdsl/ordered-map" "^4.4.2"
+
+"@grpc/grpc-js@~1.9.0":
+  version "1.9.14"
+  resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.9.14.tgz#236378822876cbf7903f9d61a0330410e8dcc5a1"
+  integrity sha512-nOpuzZ2G3IuMFN+UPPpKrC6NsLmWsTqSsm66IRfnBt1D4pwTqE27lmbpcPM+l2Ua4gE7PfjRHI6uedAy7hoXUw==
+  dependencies:
+    "@grpc/proto-loader" "^0.7.8"
+    "@types/node" ">=12.12.47"
+
+"@grpc/proto-loader@^0.7.0", "@grpc/proto-loader@^0.7.13", "@grpc/proto-loader@^0.7.5", "@grpc/proto-loader@^0.7.8":
+  version "0.7.13"
+  resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.13.tgz#f6a44b2b7c9f7b609f5748c6eac2d420e37670cf"
+  integrity sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==
+  dependencies:
+    lodash.camelcase "^4.3.0"
+    long "^5.0.0"
+    protobufjs "^7.2.5"
+    yargs "^17.7.2"
+
+"@hapi/address@2.x.x":
+  version "2.1.4"
+  resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
+  integrity sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==
+
+"@hapi/bourne@1.x.x":
+  version "1.3.2"
   resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-1.3.2.tgz#0a7095adea067243ce3283e1b56b8a8f453b242a"
   integrity sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA==
 
@@ -2117,7 +3854,33 @@
   dependencies:
     "@hapi/hoek" "^8.3.0"
 
-"@humanwhocodes/config-array@^0.11.10", "@humanwhocodes/config-array@^0.11.14":
+"@hokify/agenda@^6.3.0":
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/@hokify/agenda/-/agenda-6.3.0.tgz#ecc4efd0dab43983c4b74d8d4e3c046fe00a370b"
+  integrity sha512-fWrKMDe/8QHJXLOdEsMogb6cb213Z82iNsnU7nFrSIMFifEXSkXNTyCZ99FV3KLf+Du1gS/M9/8uTC6FHyWRZQ==
+  dependencies:
+    cron-parser "^4"
+    date.js "~0.3.3"
+    debug "~4"
+    human-interval "~2"
+    luxon "^3"
+    mongodb "^4"
+
+"@hubspot/api-client@^11.1.0":
+  version "11.1.0"
+  resolved "https://registry.yarnpkg.com/@hubspot/api-client/-/api-client-11.1.0.tgz#f9c6cdc2f635a799152f8f7c762ddbb9acb24126"
+  integrity sha512-HUkvUJKKvKhED0RZfpjnTX+b5+RNRlFfAllT7ozLP7gEoT5fp5aT148mnBdNZTr1UPfScBdDQa42ixHjgkIlNQ==
+  dependencies:
+    "@types/node-fetch" "^2.5.7"
+    bottleneck "^2.19.5"
+    es6-promise "^4.2.4"
+    form-data "^2.5.0"
+    lodash.get "^4.4.2"
+    lodash.merge "^4.6.2"
+    node-fetch "^2.6.0"
+    url-parse "^1.4.3"
+
+"@humanwhocodes/config-array@^0.11.14":
   version "0.11.14"
   resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b"
   integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==
@@ -2403,6 +4166,14 @@
   resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280"
   integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==
 
+"@jridgewell/source-map@^0.3.3":
+  version "0.3.6"
+  resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a"
+  integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==
+  dependencies:
+    "@jridgewell/gen-mapping" "^0.3.5"
+    "@jridgewell/trace-mapping" "^0.3.25"
+
 "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15":
   version "1.4.15"
   resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
@@ -2424,7 +4195,7 @@
     "@jridgewell/resolve-uri" "^3.1.0"
     "@jridgewell/sourcemap-codec" "^1.4.14"
 
-"@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.23", "@jridgewell/trace-mapping@^0.3.24":
+"@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.23", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
   version "0.3.25"
   resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0"
   integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==
@@ -2432,11 +4203,28 @@
     "@jridgewell/resolve-uri" "^3.1.0"
     "@jridgewell/sourcemap-codec" "^1.4.14"
 
+"@js-sdsl/ordered-map@^4.4.2":
+  version "4.4.2"
+  resolved "https://registry.yarnpkg.com/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz#9299f82874bab9e4c7f9c48d865becbfe8d6907c"
+  integrity sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==
+
+"@koa/cors@^3.3.0":
+  version "3.4.3"
+  resolved "https://registry.yarnpkg.com/@koa/cors/-/cors-3.4.3.tgz#d669ee6e8d6e4f0ec4a7a7b0a17e7a3ed3752ebb"
+  integrity sha512-WPXQUaAeAMVaLTEFpoq3T2O1C+FstkjJnDQqy95Ck1UdILajsRhu6mhJ8H2f4NFPRBoCNN+qywTJfq/gGki5mw==
+  dependencies:
+    vary "^1.1.2"
+
 "@kurkle/color@^0.3.0":
   version "0.3.2"
   resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz#5acd38242e8bde4f9986e7913c8fdf49d3aa199f"
   integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==
 
+"@leichtgewicht/ip-codec@^2.0.1":
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1"
+  integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==
+
 "@lit-labs/ssr-dom-shim@^1.0.0", "@lit-labs/ssr-dom-shim@^1.1.0", "@lit-labs/ssr-dom-shim@^1.2.0":
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz#353ce4a76c83fadec272ea5674ede767650762fd"
@@ -2456,6 +4244,21 @@
   dependencies:
     "@lit-labs/ssr-dom-shim" "^1.2.0"
 
+"@mapbox/node-pre-gyp@^1.0.0", "@mapbox/node-pre-gyp@^1.0.11":
+  version "1.0.11"
+  resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa"
+  integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==
+  dependencies:
+    detect-libc "^2.0.0"
+    https-proxy-agent "^5.0.0"
+    make-dir "^3.1.0"
+    node-fetch "^2.6.7"
+    nopt "^5.0.0"
+    npmlog "^5.0.1"
+    rimraf "^3.0.2"
+    semver "^7.3.5"
+    tar "^6.1.11"
+
 "@metamask/eth-json-rpc-provider@^1.0.0":
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/@metamask/eth-json-rpc-provider/-/eth-json-rpc-provider-1.0.1.tgz#3fd5316c767847f4ca107518b611b15396a5a32c"
@@ -2465,6 +4268,17 @@
     "@metamask/safe-event-emitter" "^3.0.0"
     "@metamask/utils" "^5.0.1"
 
+"@metamask/eth-sig-util@^4.0.0":
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz#3ad61f6ea9ad73ba5b19db780d40d9aae5157088"
+  integrity sha512-tghyZKLHZjcdlDqCA3gNZmLeR0XvOE9U1qoQO9ohyAZT6Pya+H9vkBPcsyXytmYLNgVoin7CKCmweo/R43V+tQ==
+  dependencies:
+    ethereumjs-abi "^0.6.8"
+    ethereumjs-util "^6.2.1"
+    ethjs-util "^0.1.6"
+    tweetnacl "^1.0.3"
+    tweetnacl-util "^0.15.1"
+
 "@metamask/json-rpc-engine@^7.0.0":
   version "7.3.2"
   resolved "https://registry.yarnpkg.com/@metamask/json-rpc-engine/-/json-rpc-engine-7.3.2.tgz#e8f0695811619eef7b7c894ba5cf782db9f1c2cb"
@@ -2617,6 +4431,13 @@
     semver "^7.5.4"
     superstruct "^1.0.3"
 
+"@mongodb-js/saslprep@^1.1.0", "@mongodb-js/saslprep@^1.1.5":
+  version "1.1.7"
+  resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.1.7.tgz#d1700facfd6916c50c2c88fd6d48d363a56c702f"
+  integrity sha512-dCHW/oEX0KJ4NjDULBo3JiOaK5+6axtpBbS+ao2ZInoAL9/YRQLhXzSNAFz7hP4nzLkIqsfYAK/PDE3+XHny0Q==
+  dependencies:
+    sparse-bitfield "^3.0.3"
+
 "@motionone/animation@^10.15.1", "@motionone/animation@^10.17.0":
   version "10.17.0"
   resolved "https://registry.yarnpkg.com/@motionone/animation/-/animation-10.17.0.tgz#7633c6f684b5fee2b61c405881b8c24662c68fca"
@@ -2694,6 +4515,53 @@
     call-me-maybe "^1.0.1"
     glob-to-regexp "^0.3.0"
 
+"@newrelic/native-metrics@^10.0.0":
+  version "10.1.1"
+  resolved "https://registry.yarnpkg.com/@newrelic/native-metrics/-/native-metrics-10.1.1.tgz#5e19f3fbd2e36d33b9e11d4bfa892f3966ba26b9"
+  integrity sha512-BvdTMAqS3d94ZwJ6u70dWqZVkX8ev3dybkxRInHMbKV2DE1koQR3nzH2ut3hf1MaRQh4SF6SpUNTUznzCZZtjw==
+  dependencies:
+    nan "^2.18.0"
+    node-gyp "^10.1.0"
+    node-gyp-build "^4.8.0"
+    prebuildify "^6.0.0"
+    semver "^7.5.2"
+
+"@newrelic/ritm@^7.2.0":
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/@newrelic/ritm/-/ritm-7.2.0.tgz#2d5dd52341e79ea921b7d1a57f43f9ca3b11defe"
+  integrity sha512-I4iVhm+wlTEDJXQT8EydF/U5vlR9bBHrtBGyvd/D9WCucoMtrPrCNyILQh9bZ+46E8QRE7zh6QEGyQcnc3qNMg==
+  dependencies:
+    debug "^4.1.1"
+    module-details-from-path "^1.0.3"
+    resolve "^1.22.1"
+
+"@newrelic/security-agent@^1.1.1":
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/@newrelic/security-agent/-/security-agent-1.2.0.tgz#cdfc2b8bd905c54c2b4f60d70b8f829f30712a5b"
+  integrity sha512-Snk++TQmqHKuxPYOH5bEU4GCr5xKYurUZWx3oiuoQUV73pw61qeEMrb/8iuGgAghwpCEC/8n+308efqCIZkiiQ==
+  dependencies:
+    axios "^1.6.8"
+    check-disk-space "^3.4.0"
+    content-type "^1.0.5"
+    fast-safe-stringify "^2.1.1"
+    find-package-json "^1.2.0"
+    hash.js "^1.1.7"
+    html-entities "^2.3.6"
+    is-invalid-path "^1.0.2"
+    js-yaml "^4.1.0"
+    jsonschema "^1.4.1"
+    lodash "^4.17.21"
+    log4js "^6.9.1"
+    pretty-bytes "^5.6.0"
+    request-ip "^3.3.0"
+    ringbufferjs "^2.0.0"
+    semver "^7.5.4"
+    sync-request "^6.1.0"
+    unescape "^1.0.1"
+    unescape-js "^1.1.4"
+    uuid "^9.0.1"
+    ws "^8.14.2"
+
 "@noble/curves@1.2.0", "@noble/curves@~1.2.0":
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35"
@@ -2708,6 +4576,11 @@
   dependencies:
     "@noble/hashes" "1.3.3"
 
+"@noble/hashes@1.2.0", "@noble/hashes@~1.2.0":
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12"
+  integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==
+
 "@noble/hashes@1.3.2":
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39"
@@ -2718,6 +4591,11 @@
   resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699"
   integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==
 
+"@noble/secp256k1@1.7.1", "@noble/secp256k1@~1.7.0":
+  version "1.7.1"
+  resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c"
+  integrity sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==
+
 "@node-ipc/js-queue@2.0.3":
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/@node-ipc/js-queue/-/js-queue-2.0.3.tgz#ac7fe33d766fa53e233ef8fedaf3443a01c5a4cd"
@@ -2751,19 +4629,288 @@
     "@nodelib/fs.scandir" "2.1.5"
     fastq "^1.6.0"
 
-"@nrwl/cypress@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nrwl/cypress/-/cypress-18.0.8.tgz#6f3642e48d1a0806a1228e123f59149aef4ae4a0"
-  integrity sha512-jI2QSALuLlU6YUz+KiMfAA9KRtlCELECBGzxrnZtJvQw9ln/levYzxgn4VL0HNUGa31zzRZLCPUQEQqymbawjQ==
+"@nomicfoundation/ethereumjs-block@5.0.1":
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-5.0.1.tgz#6f89664f55febbd723195b6d0974773d29ee133d"
+  integrity sha512-u1Yioemi6Ckj3xspygu/SfFvm8vZEO8/Yx5a1QLzi6nVU0jz3Pg2OmHKJ5w+D9Ogk1vhwRiqEBAqcb0GVhCyHw==
+  dependencies:
+    "@nomicfoundation/ethereumjs-common" "4.0.1"
+    "@nomicfoundation/ethereumjs-rlp" "5.0.1"
+    "@nomicfoundation/ethereumjs-trie" "6.0.1"
+    "@nomicfoundation/ethereumjs-tx" "5.0.1"
+    "@nomicfoundation/ethereumjs-util" "9.0.1"
+    ethereum-cryptography "0.1.3"
+    ethers "^5.7.1"
+
+"@nomicfoundation/ethereumjs-blockchain@7.0.1":
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-7.0.1.tgz#80e0bd3535bfeb9baa29836b6f25123dab06a726"
+  integrity sha512-NhzndlGg829XXbqJEYrF1VeZhAwSPgsK/OB7TVrdzft3y918hW5KNd7gIZ85sn6peDZOdjBsAXIpXZ38oBYE5A==
+  dependencies:
+    "@nomicfoundation/ethereumjs-block" "5.0.1"
+    "@nomicfoundation/ethereumjs-common" "4.0.1"
+    "@nomicfoundation/ethereumjs-ethash" "3.0.1"
+    "@nomicfoundation/ethereumjs-rlp" "5.0.1"
+    "@nomicfoundation/ethereumjs-trie" "6.0.1"
+    "@nomicfoundation/ethereumjs-tx" "5.0.1"
+    "@nomicfoundation/ethereumjs-util" "9.0.1"
+    abstract-level "^1.0.3"
+    debug "^4.3.3"
+    ethereum-cryptography "0.1.3"
+    level "^8.0.0"
+    lru-cache "^5.1.1"
+    memory-level "^1.0.0"
+
+"@nomicfoundation/ethereumjs-common@4.0.1":
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.1.tgz#4702d82df35b07b5407583b54a45bf728e46a2f0"
+  integrity sha512-OBErlkfp54GpeiE06brBW/TTbtbuBJV5YI5Nz/aB2evTDo+KawyEzPjBlSr84z/8MFfj8wS2wxzQX1o32cev5g==
+  dependencies:
+    "@nomicfoundation/ethereumjs-util" "9.0.1"
+    crc-32 "^1.2.0"
+
+"@nomicfoundation/ethereumjs-ethash@3.0.1":
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-3.0.1.tgz#65ca494d53e71e8415c9a49ef48bc921c538fc41"
+  integrity sha512-KDjGIB5igzWOp8Ik5I6QiRH5DH+XgILlplsHR7TEuWANZA759G6krQ6o8bvj+tRUz08YygMQu/sGd9mJ1DYT8w==
+  dependencies:
+    "@nomicfoundation/ethereumjs-block" "5.0.1"
+    "@nomicfoundation/ethereumjs-rlp" "5.0.1"
+    "@nomicfoundation/ethereumjs-util" "9.0.1"
+    abstract-level "^1.0.3"
+    bigint-crypto-utils "^3.0.23"
+    ethereum-cryptography "0.1.3"
+
+"@nomicfoundation/ethereumjs-evm@2.0.1":
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-2.0.1.tgz#f35681e203363f69ce2b3d3bf9f44d4e883ca1f1"
+  integrity sha512-oL8vJcnk0Bx/onl+TgQOQ1t/534GKFaEG17fZmwtPFeH8S5soiBYPCLUrvANOl4sCp9elYxIMzIiTtMtNNN8EQ==
+  dependencies:
+    "@ethersproject/providers" "^5.7.1"
+    "@nomicfoundation/ethereumjs-common" "4.0.1"
+    "@nomicfoundation/ethereumjs-tx" "5.0.1"
+    "@nomicfoundation/ethereumjs-util" "9.0.1"
+    debug "^4.3.3"
+    ethereum-cryptography "0.1.3"
+    mcl-wasm "^0.7.1"
+    rustbn.js "~0.2.0"
+
+"@nomicfoundation/ethereumjs-rlp@5.0.1":
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.1.tgz#0b30c1cf77d125d390408e391c4bb5291ef43c28"
+  integrity sha512-xtxrMGa8kP4zF5ApBQBtjlSbN5E2HI8m8FYgVSYAnO6ssUoY5pVPGy2H8+xdf/bmMa22Ce8nWMH3aEW8CcqMeQ==
+
+"@nomicfoundation/ethereumjs-statemanager@2.0.1":
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-2.0.1.tgz#8824a97938db4471911e2d2f140f79195def5935"
+  integrity sha512-B5ApMOnlruVOR7gisBaYwFX+L/AP7i/2oAahatssjPIBVDF6wTX1K7Qpa39E/nzsH8iYuL3krkYeUFIdO3EMUQ==
+  dependencies:
+    "@nomicfoundation/ethereumjs-common" "4.0.1"
+    "@nomicfoundation/ethereumjs-rlp" "5.0.1"
+    debug "^4.3.3"
+    ethereum-cryptography "0.1.3"
+    ethers "^5.7.1"
+    js-sdsl "^4.1.4"
+
+"@nomicfoundation/ethereumjs-trie@6.0.1":
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-6.0.1.tgz#662c55f6b50659fd4b22ea9f806a7401cafb7717"
+  integrity sha512-A64It/IMpDVODzCgxDgAAla8jNjNtsoQZIzZUfIV5AY6Coi4nvn7+VReBn5itlxMiL2yaTlQr9TRWp3CSI6VoA==
+  dependencies:
+    "@nomicfoundation/ethereumjs-rlp" "5.0.1"
+    "@nomicfoundation/ethereumjs-util" "9.0.1"
+    "@types/readable-stream" "^2.3.13"
+    ethereum-cryptography "0.1.3"
+    readable-stream "^3.6.0"
+
+"@nomicfoundation/ethereumjs-tx@5.0.1":
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.1.tgz#7629dc2036b4a33c34e9f0a592b43227ef4f0c7d"
+  integrity sha512-0HwxUF2u2hrsIM1fsasjXvlbDOq1ZHFV2dd1yGq8CA+MEYhaxZr8OTScpVkkxqMwBcc5y83FyPl0J9MZn3kY0w==
+  dependencies:
+    "@chainsafe/ssz" "^0.9.2"
+    "@ethersproject/providers" "^5.7.2"
+    "@nomicfoundation/ethereumjs-common" "4.0.1"
+    "@nomicfoundation/ethereumjs-rlp" "5.0.1"
+    "@nomicfoundation/ethereumjs-util" "9.0.1"
+    ethereum-cryptography "0.1.3"
+
+"@nomicfoundation/ethereumjs-util@9.0.1":
+  version "9.0.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.1.tgz#530cda8bae33f8b5020a8f199ed1d0a2ce48ec89"
+  integrity sha512-TwbhOWQ8QoSCFhV/DDfSmyfFIHjPjFBj957219+V3jTZYZ2rf9PmDtNOeZWAE3p3vlp8xb02XGpd0v6nTUPbsA==
+  dependencies:
+    "@chainsafe/ssz" "^0.10.0"
+    "@nomicfoundation/ethereumjs-rlp" "5.0.1"
+    ethereum-cryptography "0.1.3"
+
+"@nomicfoundation/ethereumjs-vm@7.0.1":
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-7.0.1.tgz#7d035e0993bcad10716c8b36e61dfb87fa3ca05f"
+  integrity sha512-rArhyn0jPsS/D+ApFsz3yVJMQ29+pVzNZ0VJgkzAZ+7FqXSRtThl1C1prhmlVr3YNUlfpZ69Ak+RUT4g7VoOuQ==
+  dependencies:
+    "@nomicfoundation/ethereumjs-block" "5.0.1"
+    "@nomicfoundation/ethereumjs-blockchain" "7.0.1"
+    "@nomicfoundation/ethereumjs-common" "4.0.1"
+    "@nomicfoundation/ethereumjs-evm" "2.0.1"
+    "@nomicfoundation/ethereumjs-rlp" "5.0.1"
+    "@nomicfoundation/ethereumjs-statemanager" "2.0.1"
+    "@nomicfoundation/ethereumjs-trie" "6.0.1"
+    "@nomicfoundation/ethereumjs-tx" "5.0.1"
+    "@nomicfoundation/ethereumjs-util" "9.0.1"
+    debug "^4.3.3"
+    ethereum-cryptography "0.1.3"
+    mcl-wasm "^0.7.1"
+    rustbn.js "~0.2.0"
+
+"@nomicfoundation/hardhat-chai-matchers@^1.0.0":
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-chai-matchers/-/hardhat-chai-matchers-1.0.6.tgz#72a2e312e1504ee5dd73fe302932736432ba96bc"
+  integrity sha512-f5ZMNmabZeZegEfuxn/0kW+mm7+yV7VNDxLpMOMGXWFJ2l/Ct3QShujzDRF9cOkK9Ui/hbDeOWGZqyQALDXVCQ==
+  dependencies:
+    "@ethersproject/abi" "^5.1.2"
+    "@types/chai-as-promised" "^7.1.3"
+    chai-as-promised "^7.1.1"
+    deep-eql "^4.0.1"
+    ordinal "^1.0.3"
+
+"@nomicfoundation/hardhat-network-helpers@^1.0.0":
+  version "1.0.10"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.10.tgz#c61042ceb104fdd6c10017859fdef6529c1d6585"
+  integrity sha512-R35/BMBlx7tWN5V6d/8/19QCwEmIdbnA4ZrsuXgvs8i2qFx5i7h6mH5pBS4Pwi4WigLH+upl6faYusrNPuzMrQ==
+  dependencies:
+    ethereumjs-util "^7.1.4"
+
+"@nomicfoundation/hardhat-toolbox@2.0.2":
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-toolbox/-/hardhat-toolbox-2.0.2.tgz#ec95f23b53cb4e71a1a7091380fa223aad18f156"
+  integrity sha512-vnN1AzxbvpSx9pfdRHbUzTRIXpMLPXnUlkW855VaDk6N1pwRaQ2gNzEmFAABk4lWf11E00PKwFd/q27HuwYrYg==
+
+"@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.1":
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.1.tgz#4c858096b1c17fe58a474fe81b46815f93645c15"
+  integrity sha512-KcTodaQw8ivDZyF+D76FokN/HdpgGpfjc/gFCImdLUyqB6eSWVaZPazMbeAjmfhx3R0zm/NYVzxwAokFKgrc0w==
+
+"@nomicfoundation/solidity-analyzer-darwin-x64@0.1.1":
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.1.tgz#6e25ccdf6e2d22389c35553b64fe6f3fdaec432c"
+  integrity sha512-XhQG4BaJE6cIbjAVtzGOGbK3sn1BO9W29uhk9J8y8fZF1DYz0Doj8QDMfpMu+A6TjPDs61lbsmeYodIDnfveSA==
+
+"@nomicfoundation/solidity-analyzer-freebsd-x64@0.1.1":
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.1.1.tgz#0a224ea50317139caeebcdedd435c28a039d169c"
+  integrity sha512-GHF1VKRdHW3G8CndkwdaeLkVBi5A9u2jwtlS7SLhBc8b5U/GcoL39Q+1CSO3hYqePNP+eV5YI7Zgm0ea6kMHoA==
+
+"@nomicfoundation/solidity-analyzer-linux-arm64-gnu@0.1.1":
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.1.tgz#dfa085d9ffab9efb2e7b383aed3f557f7687ac2b"
+  integrity sha512-g4Cv2fO37ZsUENQ2vwPnZc2zRenHyAxHcyBjKcjaSmmkKrFr64yvzeNO8S3GBFCo90rfochLs99wFVGT/0owpg==
+
+"@nomicfoundation/solidity-analyzer-linux-arm64-musl@0.1.1":
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.1.tgz#c9e06b5d513dd3ab02a7ac069c160051675889a4"
+  integrity sha512-WJ3CE5Oek25OGE3WwzK7oaopY8xMw9Lhb0mlYuJl/maZVo+WtP36XoQTb7bW/i8aAdHW5Z+BqrHMux23pvxG3w==
+
+"@nomicfoundation/solidity-analyzer-linux-x64-gnu@0.1.1":
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.1.tgz#8d328d16839e52571f72f2998c81e46bf320f893"
+  integrity sha512-5WN7leSr5fkUBBjE4f3wKENUy9HQStu7HmWqbtknfXkkil+eNWiBV275IOlpXku7v3uLsXTOKpnnGHJYI2qsdA==
+
+"@nomicfoundation/solidity-analyzer-linux-x64-musl@0.1.1":
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.1.tgz#9b49d0634b5976bb5ed1604a1e1b736f390959bb"
+  integrity sha512-KdYMkJOq0SYPQMmErv/63CwGwMm5XHenEna9X9aB8mQmhDBrYrlAOSsIPgFCUSL0hjxE3xHP65/EPXR/InD2+w==
+
+"@nomicfoundation/solidity-analyzer-win32-arm64-msvc@0.1.1":
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.1.1.tgz#e2867af7264ebbcc3131ef837878955dd6a3676f"
+  integrity sha512-VFZASBfl4qiBYwW5xeY20exWhmv6ww9sWu/krWSesv3q5hA0o1JuzmPHR4LPN6SUZj5vcqci0O6JOL8BPw+APg==
+
+"@nomicfoundation/solidity-analyzer-win32-ia32-msvc@0.1.1":
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.1.1.tgz#0685f78608dd516c8cdfb4896ed451317e559585"
+  integrity sha512-JnFkYuyCSA70j6Si6cS1A9Gh1aHTEb8kOTBApp/c7NRTFGNMH8eaInKlyuuiIbvYFhlXW4LicqyYuWNNq9hkpQ==
+
+"@nomicfoundation/solidity-analyzer-win32-x64-msvc@0.1.1":
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.1.tgz#c9a44f7108646f083b82e851486e0f6aeb785836"
+  integrity sha512-HrVJr6+WjIXGnw3Q9u6KQcbZCtk0caVWhCdFADySvRyUxJ8PnzlaP+MhwNE8oyT8OZ6ejHBRrrgjSqDCFXGirw==
+
+"@nomicfoundation/solidity-analyzer@^0.1.0":
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.1.tgz#f5f4d36d3f66752f59a57e7208cd856f3ddf6f2d"
+  integrity sha512-1LMtXj1puAxyFusBgUIy5pZk3073cNXYnXUpuNKFghHbIit/xZgbk0AokpUADbNm3gyD6bFWl3LRFh3dhVdREg==
+  optionalDependencies:
+    "@nomicfoundation/solidity-analyzer-darwin-arm64" "0.1.1"
+    "@nomicfoundation/solidity-analyzer-darwin-x64" "0.1.1"
+    "@nomicfoundation/solidity-analyzer-freebsd-x64" "0.1.1"
+    "@nomicfoundation/solidity-analyzer-linux-arm64-gnu" "0.1.1"
+    "@nomicfoundation/solidity-analyzer-linux-arm64-musl" "0.1.1"
+    "@nomicfoundation/solidity-analyzer-linux-x64-gnu" "0.1.1"
+    "@nomicfoundation/solidity-analyzer-linux-x64-musl" "0.1.1"
+    "@nomicfoundation/solidity-analyzer-win32-arm64-msvc" "0.1.1"
+    "@nomicfoundation/solidity-analyzer-win32-ia32-msvc" "0.1.1"
+    "@nomicfoundation/solidity-analyzer-win32-x64-msvc" "0.1.1"
+
+"@nomiclabs/hardhat-ethers@^2.0.0":
+  version "2.2.3"
+  resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.2.3.tgz#b41053e360c31a32c2640c9a45ee981a7e603fe0"
+  integrity sha512-YhzPdzb612X591FOe68q+qXVXGG2ANZRvDo0RRUtimev85rCrAlv/TLMEZw5c+kq9AbzocLTVX/h2jVIFPL9Xg==
+
+"@nomiclabs/hardhat-etherscan@^3.0.0":
+  version "3.1.8"
+  resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-3.1.8.tgz#3c12ee90b3733e0775e05111146ef9418d4f5a38"
+  integrity sha512-v5F6IzQhrsjHh6kQz4uNrym49brK9K5bYCq2zQZ729RYRaifI9hHbtmK+KkIVevfhut7huQFEQ77JLRMAzWYjQ==
+  dependencies:
+    "@ethersproject/abi" "^5.1.2"
+    "@ethersproject/address" "^5.0.2"
+    cbor "^8.1.0"
+    chalk "^2.4.2"
+    debug "^4.1.1"
+    fs-extra "^7.0.1"
+    lodash "^4.17.11"
+    semver "^6.3.0"
+    table "^6.8.0"
+    undici "^5.14.0"
+
+"@npmcli/agent@^2.0.0":
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/@npmcli/agent/-/agent-2.2.2.tgz#967604918e62f620a648c7975461c9c9e74fc5d5"
+  integrity sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==
+  dependencies:
+    agent-base "^7.1.0"
+    http-proxy-agent "^7.0.0"
+    https-proxy-agent "^7.0.1"
+    lru-cache "^10.0.1"
+    socks-proxy-agent "^8.0.3"
+
+"@npmcli/fs@^3.1.0":
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-3.1.1.tgz#59cdaa5adca95d135fc00f2bb53f5771575ce726"
+  integrity sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==
+  dependencies:
+    semver "^7.3.5"
+
+"@nrwl/cypress@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nrwl/cypress/-/cypress-18.3.0.tgz#885c1cb661f43b1aafe82d8aeaafc4db341aa9ea"
+  integrity sha512-bSzHKqjx7De+Ax0zTX5z8VHii5uLLSxzSIqh9lJET1leZkwtoGhe9MWdXYrfeJaTp+xV6DJAA11Rg1SWn2SInQ==
+  dependencies:
+    "@nx/cypress" "18.3.0"
+
+"@nrwl/devkit@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nrwl/devkit/-/devkit-18.3.0.tgz#2614467d5986c3ad98984bed2be00b747656e45a"
+  integrity sha512-JA6NJTAxxz+zZtS/jzeUMVdgXXjmWTuG8NdqJ70OxKok570afHxZSCjR32cWWmoCJRS4ASM2UpL/3292zk1wsQ==
   dependencies:
-    "@nx/cypress" "18.0.8"
+    "@nx/devkit" "18.3.0"
 
-"@nrwl/devkit@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nrwl/devkit/-/devkit-18.0.8.tgz#9f412c9793615e18f49565a0e58a28207c6d9c21"
-  integrity sha512-cKtXq2I/3y/t1I+jMn8XVfhtSjGxJHKGSmxStMdRPMcUim8iaS2V3fDUdF2CGrXrtbmDtYwBC8413YY+nVh0Gw==
+"@nrwl/devkit@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nrwl/devkit/-/devkit-19.0.4.tgz#dcfb35625ca47fd770bd1417d83e08499bb471bd"
+  integrity sha512-wOb7qiluWjVgmfhIGxWXAgJ61ZoL7rDYfx0mibPhbBlqm+86NHJ9CbKTfbfamS20fkzCYdhYeE6xd7sdpcZIZA==
   dependencies:
-    "@nx/devkit" "18.0.8"
+    "@nx/devkit" "19.0.4"
 
 "@nrwl/devkit@^14.3.6":
   version "14.8.9"
@@ -2775,55 +4922,112 @@
     ignore "^5.0.4"
     tslib "^2.3.0"
 
-"@nrwl/eslint-plugin-nx@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nrwl/eslint-plugin-nx/-/eslint-plugin-nx-18.0.8.tgz#b5b2d84e15e710ea78a46887e37acda045be5e79"
-  integrity sha512-R7Hj8jcjrxX2Lb1fLhX0EycEx8FrzC+8VdIR6cjln+3pXPLMAQTlcWmTFCzA6272atNpc0ZudZh2dShHvhYM+w==
+"@nrwl/esbuild@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nrwl/esbuild/-/esbuild-18.3.0.tgz#cb3e8264a27ff39042d613eb4664ed9506d6084e"
+  integrity sha512-wZNbVe+vdgTmFUzgiySWILFQweDlicUa9nOvy41Pufhx0SSeC5wcZRmuf5p1GgK5Srg36lAmklHIfcRX7OwRbw==
+  dependencies:
+    "@nx/esbuild" "18.3.0"
+
+"@nrwl/eslint-plugin-nx@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nrwl/eslint-plugin-nx/-/eslint-plugin-nx-18.3.0.tgz#a50cf0d5319f6355d3b6b4450f527162c148ae47"
+  integrity sha512-I+1dnG2xsHpD5ii2Ow58piXC95ob9rRZ6Yf0JfFed4sKxq6ntArdDeGpM4tCSNZvpRpR9kUi9UMaQA5JoLm8Jw==
+  dependencies:
+    "@nx/eslint-plugin" "18.3.0"
+
+"@nrwl/jest@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nrwl/jest/-/jest-18.3.0.tgz#b4e41976848077afac5bc45189eacff8e4c0946b"
+  integrity sha512-u1iGqhLedfmxXzJEWsAXUIgF8sQXzj8DTqLp6NUN8mJfPYCQjVOQirwl4lcNhs0gTvIgqr3wGIHo33ixyjMjFw==
+  dependencies:
+    "@nx/jest" "18.3.0"
+
+"@nrwl/jest@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nrwl/jest/-/jest-19.0.4.tgz#f50e0f727086ddbb9f96c4073b6011513752448c"
+  integrity sha512-47nASzd1lwP3/vBX2wp8xb0s+tZCSxmLf3jN+NUDNm/nxCf6/pUS5SdWJHABbioG8myrqKGaCOW5/4NCdlQD+Q==
+  dependencies:
+    "@nx/jest" "19.0.4"
+
+"@nrwl/js@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nrwl/js/-/js-18.3.0.tgz#4963d8231fd68b03932574bf54ee903d1b720d47"
+  integrity sha512-sLQGUkFY/9spMqe3EvkTRh9iDqIZ65HLYALaaK5RyH5z7ctXwZGgDTwvCpO9r/jEIyE1inxUNzqbYl66R7qEdQ==
+  dependencies:
+    "@nx/js" "18.3.0"
+
+"@nrwl/js@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nrwl/js/-/js-19.0.4.tgz#c9e4108266e6d166042054c582ff9f698dd058dd"
+  integrity sha512-BMH7ysvCXETrOXIYCfsubQUH6ToJk2gDcINQFXWa+m+D1KKQXXpOrMA/2nqRpP+JT2nQ770zERC7WEDxGzrNpA==
+  dependencies:
+    "@nx/js" "19.0.4"
+
+"@nrwl/node@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nrwl/node/-/node-18.3.0.tgz#f0c2ee2deeea802c305efb2449340df1637aed1e"
+  integrity sha512-N5PVvXJBycvKXqLRC1R5+WXniuynQgBHjyNOZzu9/R+yIrqbwuA+MjptpVHLGqCTtIgykPd2LUhmI6SHLrlZrQ==
   dependencies:
-    "@nx/eslint-plugin" "18.0.8"
+    "@nx/node" "18.3.0"
 
-"@nrwl/jest@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nrwl/jest/-/jest-18.0.8.tgz#bfe99f932305c52aacd45c6b432c7ab69367e11d"
-  integrity sha512-kOYx295kItv0rdNOO7+a4CvTVXvi82wp3qNDmqpYGckcFQCvgTXrMKITok67vOQlN4Iip3KL/4bqBzwR0uUuzA==
+"@nrwl/node@19.0.4", "@nrwl/node@^19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nrwl/node/-/node-19.0.4.tgz#ddb3d1efeefdb386911171f01fb706162a3ed5a6"
+  integrity sha512-9FYAS7oUDx3Mvhoa/+Cu5GwB4N19jJfVLt2QHzrzQMjNkW6DZk1N6xvyUjv41L8EbUvTYdkddZPhnXaSkzdNvQ==
   dependencies:
-    "@nx/jest" "18.0.8"
+    "@nx/node" "19.0.4"
 
-"@nrwl/js@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nrwl/js/-/js-18.0.8.tgz#ee6006c8a15c6d5b8b37534806f50489d3a0cf4b"
-  integrity sha512-5VWvJD7zWFFEVfVFpKj5NQQVn9YBiNALIcHtq1A6/GEe/SrWGjTwyeK+5uVSYegTR8BXX2SpBaE53bOunWuTrQ==
+"@nrwl/tao@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nrwl/tao/-/tao-18.3.0.tgz#2148e57d89dfcab0935f71cae3c48cc9a3349b09"
+  integrity sha512-M0m0QRiW7N+f+N+ey/gobPLYzUn932obMXDnb+6ImLsqRunFndd7YKHXUMf+y1441w7OXI5owTjE5bEKxZjOow==
   dependencies:
-    "@nx/js" "18.0.8"
+    nx "18.3.0"
+    tslib "^2.3.0"
 
-"@nrwl/tao@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nrwl/tao/-/tao-18.0.8.tgz#78b87d179e71d10bdf87778ea3e5ae4443fec9e5"
-  integrity sha512-zBzdv9mGBaWtBbujbLCVzG7ZI5npUg9fnUz8VtjN6jydAQEL/Uqj5mPlFYQPPBAw2xwF8TL9ZX/rOoAWHnJtjw==
+"@nrwl/tao@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nrwl/tao/-/tao-19.0.4.tgz#e8a3b86b7e157ff184aa6b79f5b427b11475954c"
+  integrity sha512-ZoHM5hbj0fOaWiiQoN/Wjozc6lbBCCcH7jCIX7amN6aztmcwNYk+Q3NKJE5Jh0/Js5M78VTnLRG2h4KHPzKSKg==
   dependencies:
-    nx "18.0.8"
+    nx "19.0.4"
     tslib "^2.3.0"
 
-"@nrwl/vite@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nrwl/vite/-/vite-18.0.8.tgz#7f84737ca5282eaa049648a66d22e529616da458"
-  integrity sha512-b5rsC+sjNHsBb6k2B67x3yc9MnhlIpXfPhISRjRcI0XGrjNBC2Vz5PkN6xOlV+NCy/ez1Kv3uVMBiKHcUd5x7Q==
+"@nrwl/vite@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nrwl/vite/-/vite-18.3.0.tgz#e6e48ef66f33fbb930d44e78f4837cbe37fa19ad"
+  integrity sha512-uOdSnvdIW/Rjn4tUYtU8I2vD8pFYdtI6ZJGUkB0OGjGSdGuOHPnSmOYzEt1uZKWVQe0mnJHKVd/FhMDOmSPKfA==
+  dependencies:
+    "@nx/vite" "18.3.0"
+
+"@nrwl/web@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nrwl/web/-/web-18.3.0.tgz#2e7e08b504a1a608b940b249dbfc08bd95453452"
+  integrity sha512-e3IA905VOXAm3behYIeBW6Yi9YZeNxya+RWe5kFYFR+wg/JdGNF+NrFv6IYzzY9PFo2wn0ubOrNdnCXMVz1UHA==
+  dependencies:
+    "@nx/web" "18.3.0"
+
+"@nrwl/webpack@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nrwl/webpack/-/webpack-18.3.0.tgz#f675e5363e5fa5d5742456c84c8fc554d30673c1"
+  integrity sha512-79BUpNWnDDFxd6Eoc0Q+i3WE9Fjnpjt7LIC9d52Aw6RYSv1yQLZ2D1gB9kIQo8xQrN8E5NlLZ9gwTRWYLGv+Uw==
   dependencies:
-    "@nx/vite" "18.0.8"
+    "@nx/webpack" "18.3.0"
 
-"@nrwl/web@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nrwl/web/-/web-18.0.8.tgz#6f09d611cd2ec759ace4a01b10976b76bdacf2d3"
-  integrity sha512-M6mo8zlYAmwUleVS4ONoqNc8gNvQYQuBc1PZ89vmnr5FhlHcwIiJdK9Q8xjcARe8yUEKhy8F/VgkDs8pcTWY4A==
+"@nrwl/workspace@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nrwl/workspace/-/workspace-18.3.0.tgz#ba2a03b3e9c0e4d3cc850c951bf1778ebfb8cd19"
+  integrity sha512-u0TlW2EcISfGaWug89MqCCD7DUeRfjtVnBHqbO3y3Oj19TB3QUNPhnfB/5Z+xybtAqn+bLRWZt2kpW8R5cVchQ==
   dependencies:
-    "@nx/web" "18.0.8"
+    "@nx/workspace" "18.3.0"
 
-"@nrwl/workspace@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nrwl/workspace/-/workspace-18.0.8.tgz#86938241ac3e949d38b0b96dddda9e868ca77ac9"
-  integrity sha512-K1Fhfa1OqdhlZ46nPOFka9EBGt5bChLSIeJDs/JVZHyIlmGWeIQMy78oE2DPhrpW+ZowVxwVPdFgilESdfriQQ==
+"@nrwl/workspace@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nrwl/workspace/-/workspace-19.0.4.tgz#fa0bfe2ed61461bf06b3d66a127aa4ffc90eeb30"
+  integrity sha512-aL2Z6sE2hmwnqcca2RZMbyq7gqrotvBivGONS1mRjpRaapJTkzsPDDaY3WDS/4Mmh7vbtoaINmJJeyJcllymiA==
   dependencies:
-    "@nx/workspace" "18.0.8"
+    "@nx/workspace" "19.0.4"
 
 "@nx-plus/vue@^14.1.0":
   version "14.1.0"
@@ -2845,72 +5049,134 @@
     tsconfig-paths-webpack-plugin "3.5.2"
     typescript "4.5.4"
 
-"@nx/cypress@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nx/cypress/-/cypress-18.0.8.tgz#be970cfab2aaede9688f8c446d6451f78f518163"
-  integrity sha512-3ggo5EVaZw2fnUNGai7RibPp9xe3+FH+ismhiLamRQVWX3gVIixl1lrdqIpIaSwD4RLUs6guGSrP4X5G2VaBEg==
+"@nx/cypress@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/cypress/-/cypress-18.3.0.tgz#6881a69335a7ab5a8fd70e67e11b46838e3b3a9e"
+  integrity sha512-zA3FyOe3A+TmHueVWqHaAien//FhADjwUXnvRlFun/+zGZeM/07clVaZnGMBgNttLbPuWE0HBQ4KnBXRC57bSA==
   dependencies:
-    "@nrwl/cypress" "18.0.8"
-    "@nx/devkit" "18.0.8"
-    "@nx/eslint" "18.0.8"
-    "@nx/js" "18.0.8"
+    "@nrwl/cypress" "18.3.0"
+    "@nx/devkit" "18.3.0"
+    "@nx/eslint" "18.3.0"
+    "@nx/js" "18.3.0"
     "@phenomnomnominal/tsquery" "~5.0.1"
     detect-port "^1.5.1"
     semver "^7.5.3"
     tslib "^2.3.0"
 
-"@nx/devkit@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nx/devkit/-/devkit-18.0.8.tgz#4d7ce2f31984a9e61bd18e364f85d5f45779c8ac"
-  integrity sha512-df56bzmhF8yhVCCChe0ATjCsc9r9SNcpks5/bABGqR91vHVGfsz0H33RYO7P2jrPwFBRYnf+aWWFY1d6NpEdxw==
+"@nx/devkit@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/devkit/-/devkit-18.3.0.tgz#5bdf7197d667762a423472eb548d7e066768d8e8"
+  integrity sha512-SgPPk+S8cEjNOzcvGiRPlNqAJVuPnspNrqFmBZ/ddBXQfhuS/TCr8Zi4MWEct45zd439acWDsuUVFoCxT51q4g==
+  dependencies:
+    "@nrwl/devkit" "18.3.0"
+    ejs "^3.1.7"
+    enquirer "~2.3.6"
+    ignore "^5.0.4"
+    semver "^7.5.3"
+    tmp "~0.2.1"
+    tslib "^2.3.0"
+    yargs-parser "21.1.1"
+
+"@nx/devkit@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nx/devkit/-/devkit-19.0.4.tgz#00c6ebee129bd874e143c36a65e86c3e885cdc9e"
+  integrity sha512-nsD0RaL61nZLHSJbog2XwxcI8bML5GlI69Z1k2rvd2zvylqdjNS4SXakMPl/Ar9xX2mAW3Qbup850V0jG87y/Q==
   dependencies:
-    "@nrwl/devkit" "18.0.8"
+    "@nrwl/devkit" "19.0.4"
     ejs "^3.1.7"
     enquirer "~2.3.6"
     ignore "^5.0.4"
+    minimatch "9.0.3"
     semver "^7.5.3"
     tmp "~0.2.1"
     tslib "^2.3.0"
     yargs-parser "21.1.1"
 
-"@nx/eslint-plugin@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nx/eslint-plugin/-/eslint-plugin-18.0.8.tgz#3a3c6c00b6eb6660e2d4358d4aabc4205461804d"
-  integrity sha512-w6+z7AIq2d8zbXdRSoUSwBMymXwvdF7pT1HstpTj8wQ2bolefrclOhtM7Z0l2RIyWrS61HMk0kI4mdwbO5JbrA==
+"@nx/esbuild@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/esbuild/-/esbuild-18.3.0.tgz#bd71405afb269b0887b92d88068b62c6aa509788"
+  integrity sha512-4OdOKZbn0OJBpVpcMbo1TC5xB8kqt3faiWqYHMWClnqfgB3p6q94F1+jG7FcQmNJ47m+7BrNRgUJ8jDOe+8l6w==
+  dependencies:
+    "@nrwl/esbuild" "18.3.0"
+    "@nx/devkit" "18.3.0"
+    "@nx/js" "18.3.0"
+    chalk "^4.1.0"
+    fast-glob "3.2.7"
+    fs-extra "^11.1.0"
+    tsconfig-paths "^4.1.2"
+    tslib "^2.3.0"
+
+"@nx/eslint-plugin@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/eslint-plugin/-/eslint-plugin-18.3.0.tgz#479158c16ceb9eab2910b51ca7729c1a7fdd2d43"
+  integrity sha512-IAJ3I9G811uSmkJ2K3pGg1bsesm5AJW6u1zR5ie1C4qYO2ujhMhAcBXI9P/JUgY2WlO8EoH41PhRx4XUF29ttQ==
   dependencies:
-    "@nrwl/eslint-plugin-nx" "18.0.8"
-    "@nx/devkit" "18.0.8"
-    "@nx/js" "18.0.8"
-    "@typescript-eslint/type-utils" "^6.13.2"
-    "@typescript-eslint/utils" "^6.13.2"
+    "@nrwl/eslint-plugin-nx" "18.3.0"
+    "@nx/devkit" "18.3.0"
+    "@nx/js" "18.3.0"
+    "@typescript-eslint/type-utils" "^7.3.0"
+    "@typescript-eslint/utils" "^7.3.0"
     chalk "^4.1.0"
     confusing-browser-globals "^1.0.9"
     jsonc-eslint-parser "^2.1.0"
     semver "^7.5.3"
     tslib "^2.3.0"
 
-"@nx/eslint@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nx/eslint/-/eslint-18.0.8.tgz#dfbdfdd85e912762acca49ed30d4458b06a03c8b"
-  integrity sha512-Vp8LjN5rRbg76UReHToVtBibyb3izNpn6nAvFVtpzDSpyGnqb38F4/MD8hbHU3jHyP/xdVGmL3jyOJq5Uqkl8A==
+"@nx/eslint@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/eslint/-/eslint-18.3.0.tgz#0d2929620158ecf225acccafd51902d09470d46c"
+  integrity sha512-inoFmDIycUsmIRY/iIQLxLKyJbdifyqYrsG/Hq6zmxsJOF6Q2R/Y88Zf9KET7EmN9+UEzBk70p4m8hOMVrC9eQ==
+  dependencies:
+    "@nx/devkit" "18.3.0"
+    "@nx/js" "18.3.0"
+    "@nx/linter" "18.3.0"
+    eslint "^8.0.0"
+    tslib "^2.3.0"
+    typescript "~5.4.2"
+
+"@nx/eslint@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nx/eslint/-/eslint-19.0.4.tgz#de44f68c5d98afda05e76eae0d1cc28a4fb322de"
+  integrity sha512-fcXwKyOxsCO/NtUT+fggGOGeBqK9kHA8pw/5XBnQC/yCOtWGMCdTKps0QoU5FLBq0OoquoUObYovaqAV4Cqw5g==
   dependencies:
-    "@nx/devkit" "18.0.8"
-    "@nx/js" "18.0.8"
-    "@nx/linter" "18.0.8"
+    "@nx/devkit" "19.0.4"
+    "@nx/js" "19.0.4"
+    "@nx/linter" "19.0.4"
     eslint "^8.0.0"
     tslib "^2.3.0"
-    typescript "~5.3.2"
+    typescript "~5.4.2"
+
+"@nx/jest@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/jest/-/jest-18.3.0.tgz#a66b45561b3ede31a9c995cd3f63d4ff043a44ea"
+  integrity sha512-QsawUa3OIXCV+r/fxUJCzGKEbDqDKNEsC/wYCDKl48vJEU6+KEwRUZp604mIhvP4N377DwT9JGSzOEwaSPcKbg==
+  dependencies:
+    "@jest/reporters" "^29.4.1"
+    "@jest/test-result" "^29.4.1"
+    "@nrwl/jest" "18.3.0"
+    "@nx/devkit" "18.3.0"
+    "@nx/js" "18.3.0"
+    "@phenomnomnominal/tsquery" "~5.0.1"
+    chalk "^4.1.0"
+    identity-obj-proxy "3.0.0"
+    jest-config "^29.4.1"
+    jest-resolve "^29.4.1"
+    jest-util "^29.4.1"
+    minimatch "9.0.3"
+    resolve.exports "1.1.0"
+    tslib "^2.3.0"
+    yargs-parser "21.1.1"
 
-"@nx/jest@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nx/jest/-/jest-18.0.8.tgz#da838792af673b85fa9e275bd11d7ec8ed5dbdbd"
-  integrity sha512-OE+1iB8b3z/kgdgkenXvlreBgPoe6A0b+OoN/0LmfKKC5jddTUISLTgb96L8G76XPTwFEcGPRm1BcdxAxS0M3g==
+"@nx/jest@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nx/jest/-/jest-19.0.4.tgz#e69a95db531a0211588b0df70c93f2f77b0ac88c"
+  integrity sha512-OH/brJQ2mCqN9HU3Xjc2i5kPH+6wSpjWwxXQ+rbA45hKiE7kCTjwrvEFKindjBtsRJwI/PVdcjEc0d8zbDW73w==
   dependencies:
     "@jest/reporters" "^29.4.1"
     "@jest/test-result" "^29.4.1"
-    "@nrwl/jest" "18.0.8"
-    "@nx/devkit" "18.0.8"
-    "@nx/js" "18.0.8"
+    "@nrwl/jest" "19.0.4"
+    "@nx/devkit" "19.0.4"
+    "@nx/js" "19.0.4"
     "@phenomnomnominal/tsquery" "~5.0.1"
     chalk "^4.1.0"
     identity-obj-proxy "3.0.0"
@@ -2920,11 +5186,12 @@
     minimatch "9.0.3"
     resolve.exports "1.1.0"
     tslib "^2.3.0"
+    yargs-parser "21.1.1"
 
-"@nx/js@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nx/js/-/js-18.0.8.tgz#f512227f721b36b790cf4dd7137035c91b3fcedd"
-  integrity sha512-buBHn0xUTF30Ifm6uFABltX0qwDxrxbmfTngPqs8BxFj9btYrlRz2XqtoAST/4L9AYheuY4tnwXZQvBkRh6rrw==
+"@nx/js@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/js/-/js-18.3.0.tgz#d8c22e2c7f858a76fac8868b9d14118af8db838e"
+  integrity sha512-ApxC3FdZ9ATnE6Qz932B3/L9ZqdI6pIxB+1R5J/jMK/InNlPnNStGp1+dGe5J3aQ0nWusSW9I+FjpqRMTZazvw==
   dependencies:
     "@babel/core" "^7.23.2"
     "@babel/plugin-proposal-decorators" "^7.22.7"
@@ -2933,9 +5200,9 @@
     "@babel/preset-env" "^7.23.2"
     "@babel/preset-typescript" "^7.22.5"
     "@babel/runtime" "^7.22.6"
-    "@nrwl/js" "18.0.8"
-    "@nx/devkit" "18.0.8"
-    "@nx/workspace" "18.0.8"
+    "@nrwl/js" "18.3.0"
+    "@nx/devkit" "18.3.0"
+    "@nx/workspace" "18.3.0"
     "@phenomnomnominal/tsquery" "~5.0.1"
     babel-plugin-const-enum "^1.0.1"
     babel-plugin-macros "^2.8.0"
@@ -2957,111 +5224,283 @@
     tsconfig-paths "^4.1.2"
     tslib "^2.3.0"
 
-"@nx/linter@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nx/linter/-/linter-18.0.8.tgz#7efd8241cf50ace1cf5ff2aa79a69ee2f64abaf9"
-  integrity sha512-R+hopeExRArwFDtfz8hzODf8EId24ZuSy1gB01yuKDGVFNR5WmY96v5MAjp06ZDwv/tGvTwzH+O2akc7/20VeA==
-  dependencies:
-    "@nx/eslint" "18.0.8"
-
-"@nx/nx-darwin-arm64@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nx/nx-darwin-arm64/-/nx-darwin-arm64-18.0.8.tgz#0c51e43cd7dbd7a25e63562aafdecc478661801f"
-  integrity sha512-B2vX90j1Ex9Mki/Fai45UJ0r7mPc/xLBzQYQ9MFI2XoUXKhYl5BVBfJ+EbJ2PBcIXAnp44qY0wyxEpp+8Glxcg==
-
-"@nx/nx-darwin-x64@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nx/nx-darwin-x64/-/nx-darwin-x64-18.0.8.tgz#df8d5ea87565afbf86dfe46bd1ea646ed5721e62"
-  integrity sha512-nC172j4LwOqc22BtJGsrjPYGhZ6EFXhYi0ceb6yzEA1Z32Wl98OXbAcbbhyEcuL3iYI9VrZgzAAzIUo7l4injw==
-
-"@nx/nx-freebsd-x64@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nx/nx-freebsd-x64/-/nx-freebsd-x64-18.0.8.tgz#415085b231eab5a3887e55fe5e110be681a441a8"
-  integrity sha512-Qoz668WMB6nxdMFG5X88B7W72+d5K/95XEFKY2022EPm88DQFFcJAfdkMrRkeO3yBJtwLAAK+Jyni9uAfOXzGQ==
-
-"@nx/nx-linux-arm-gnueabihf@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-18.0.8.tgz#b7aeffb7c10ca6f4169ae01179bd14891097bddd"
-  integrity sha512-0RTuJTaAmE7Xjc0P0DIbbYEhPGBILCii2nOz6vwTEzIqxSMgXW4T1g1zSDKCiUUyS6HVffGvCTNvuHuoYY2DMg==
-
-"@nx/nx-linux-arm64-gnu@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-18.0.8.tgz#8b52f40e79056ba8d1af46f7787c0d95caa55b80"
-  integrity sha512-fmwsrDeeY44f6cCnfrXNuvFEzqvD/A5yg3TVwZoKldWRAG5gexj4AWpBHqgGTcCj6ky1NGxnlaktKC5geGhJhA==
-
-"@nx/nx-linux-arm64-musl@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-18.0.8.tgz#725258e5c15c1f9f788bab0af30a56e29beb85d1"
-  integrity sha512-jz1dzQlrfZteJdsEJ1MbjI7m2jkBLhLe5y9x+96/KgmJbCV7LD9RLevWIzz7FDuhfJziMOoSrGdaW47G13p/Fw==
-
-"@nx/nx-linux-x64-gnu@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-18.0.8.tgz#98c5456e1cfdaad65bfae0c188a2f8676b634f5d"
-  integrity sha512-eq2AAZN4fsjhABtU76eroFHcNK6QWo4eMAH7tcZUoGLwfBAo+wPYggxm9LNZ5weKVxwqySHavlXd5rNA26WrbA==
-
-"@nx/nx-linux-x64-musl@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-18.0.8.tgz#53bfaf2432d1a8988995136e45d22b278927b003"
-  integrity sha512-FBHVJ0DtBqQynbQImg1kc9/WfRGSvbRNzaqI2rO/zO0j2BeT9BQ8byTn2EiMBxz72LSbqEmtQtqe5w50hAsKcA==
-
-"@nx/nx-win32-arm64-msvc@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-18.0.8.tgz#dd70a40a5f8345587eee3950adcb3d36aa396c61"
-  integrity sha512-qphQIIfwAR03s7ifPVc0XhjdOeep2hOoZN2jN5ShG1QD/DIipNnMrRK21M6JcoP7soRPpkJFlI5Yaleh9/EJhg==
-
-"@nx/nx-win32-x64-msvc@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-18.0.8.tgz#fe1ae3d1b1eec2ef4291a700500cc96bbfb81cec"
-  integrity sha512-XP8hle+cPNH5n18iTM7l0q07zEdvoPcHYVr5IoYOA54Ke9ZUxau4owUeok2HhLr61o2u0CTwf1vWoV+Y1AUAdg==
-
-"@nx/playwright@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nx/playwright/-/playwright-18.0.8.tgz#2c42111778c479a532eb9c3d1575598338afd71b"
-  integrity sha512-KbtZh/gGshIynicZB6oCl/6tgtrCoVQZfx6Fz9mVGajXNsiemSAHwopW749NjzoBne5vO7shWhRaDcCrDKDOOw==
-  dependencies:
-    "@nx/devkit" "18.0.8"
-    "@nx/eslint" "18.0.8"
-    "@nx/js" "18.0.8"
+"@nx/js@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nx/js/-/js-19.0.4.tgz#f1c0e1e30b1de491e3c9d72de4bd281ec9ae82b5"
+  integrity sha512-u6ZGuEi/GuTUU7XA2cyP+XHGvN3E1imbnoU5gv+QfjHRAQEp1mBdY5byGqx2FVsiA7yFoi60ZwSZLgIcl6V0rw==
+  dependencies:
+    "@babel/core" "^7.23.2"
+    "@babel/plugin-proposal-decorators" "^7.22.7"
+    "@babel/plugin-transform-class-properties" "^7.22.5"
+    "@babel/plugin-transform-runtime" "^7.23.2"
+    "@babel/preset-env" "^7.23.2"
+    "@babel/preset-typescript" "^7.22.5"
+    "@babel/runtime" "^7.22.6"
+    "@nrwl/js" "19.0.4"
+    "@nx/devkit" "19.0.4"
+    "@nx/workspace" "19.0.4"
+    babel-plugin-const-enum "^1.0.1"
+    babel-plugin-macros "^2.8.0"
+    babel-plugin-transform-typescript-metadata "^0.3.1"
+    chalk "^4.1.0"
+    columnify "^1.6.0"
+    detect-port "^1.5.1"
+    fast-glob "3.2.7"
+    fs-extra "^11.1.0"
+    ignore "^5.0.4"
+    js-tokens "^4.0.0"
+    minimatch "9.0.3"
+    npm-package-arg "11.0.1"
+    npm-run-path "^4.0.1"
+    ora "5.3.0"
+    semver "^7.5.3"
+    source-map-support "0.5.19"
+    ts-node "10.9.1"
+    tsconfig-paths "^4.1.2"
+    tslib "^2.3.0"
+
+"@nx/linter@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/linter/-/linter-18.3.0.tgz#08f68a82a352baf47a6cab793d5e5d9196e01ca6"
+  integrity sha512-ydTP8MFNE+KzWvIVxg7IJIMcjkt02ehwyudnkirEu5hFOUY6uA/ZQtOEk7y2ESDuF19LR11wVHPaeeSCG94Cbg==
+  dependencies:
+    "@nx/eslint" "18.3.0"
+
+"@nx/linter@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nx/linter/-/linter-19.0.4.tgz#1c6a373426b9038aa582da6295bec06e6534d4ac"
+  integrity sha512-Rdw8zUcMVG95v5EGTqYo9LkOj0XlvHhhEbKO2ts2nRwuFWDcTZGXllfyNmN0hUhzrtBivsKpeTu1dlItLJR2zA==
+  dependencies:
+    "@nx/eslint" "19.0.4"
+
+"@nx/node@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/node/-/node-18.3.0.tgz#ef5f0800bc5a1dfce682b9c15eea838a548324a5"
+  integrity sha512-zUUdIalE5lTHdubBPUpmyGXPh7rUxlJgo/8qiF+0uve2PTn/bsL+wYlClhdzYT73m0AUOPFL8wh4dEF2LPGD+w==
+  dependencies:
+    "@nrwl/node" "18.3.0"
+    "@nx/devkit" "18.3.0"
+    "@nx/eslint" "18.3.0"
+    "@nx/jest" "18.3.0"
+    "@nx/js" "18.3.0"
+    tslib "^2.3.0"
+
+"@nx/node@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nx/node/-/node-19.0.4.tgz#96858c14aa8a2b18010c850ceb1598e97b268f80"
+  integrity sha512-8C4dL+1vPdF298sf1t06uhZ/K+cLQIQZ7GUO39DyKmv11CAbYenpRLDaa9PlzAZjAslezzf6kd/YQFIG/17Q0w==
+  dependencies:
+    "@nrwl/node" "19.0.4"
+    "@nx/devkit" "19.0.4"
+    "@nx/eslint" "19.0.4"
+    "@nx/jest" "19.0.4"
+    "@nx/js" "19.0.4"
+    tslib "^2.3.0"
+
+"@nx/nx-darwin-arm64@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/nx-darwin-arm64/-/nx-darwin-arm64-18.3.0.tgz#6ddf33e6e24d180fc5bcd46685aee00406be7ea6"
+  integrity sha512-zei4C7nSCAzhigAX+3wLcHg1bokTsa/qo2OElkBiHAxs3FF7nqMLAuk0WFYi3nkvXTgiN1uEl0mOni+JPKV2vA==
+
+"@nx/nx-darwin-arm64@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nx/nx-darwin-arm64/-/nx-darwin-arm64-19.0.4.tgz#4512eca7e214f92450df7f0b6d05c24e198b6363"
+  integrity sha512-EwTMKVFdMF42b+DG3ACtrGVE3iiAgOw+VJ4Vekm59+ZkTg6GrZly2VNbthoNSJd6/uPQssoljx36NZH953ieBw==
+
+"@nx/nx-darwin-x64@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/nx-darwin-x64/-/nx-darwin-x64-18.3.0.tgz#5a92242f70837c5fd2ce0e27910c0bebc6a71835"
+  integrity sha512-nww//ea6WEfDTnqbdbCinWRhjyUJkSSnW9QgBh/Brt6DevZ7TFWfGdxD+s45pmMLFTFMgRjptRJrW/WhgmDAGg==
+
+"@nx/nx-darwin-x64@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nx/nx-darwin-x64/-/nx-darwin-x64-19.0.4.tgz#677a4210c5bc53f8dcebd0072910438d2d684c8e"
+  integrity sha512-W+SVaYOHWRHcws7wZVcWyxoT57r1qXLMUBvpTVBf5PsVfsI+t9sINwzZjcXWaGNVcPGrVYUZF6Cp3/exkPNUBw==
+
+"@nx/nx-freebsd-x64@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/nx-freebsd-x64/-/nx-freebsd-x64-18.3.0.tgz#fcdb6dbcbc8e5d5332c157420e84e666ed2e5501"
+  integrity sha512-u+XB6NQcsi7u3zhdhgJK9ZaUkzXl52WNgtDoG/6tsmbh10plypGnw+yPSKYMqv3HDzqDA76hliIFoedDbZmHFQ==
+
+"@nx/nx-freebsd-x64@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nx/nx-freebsd-x64/-/nx-freebsd-x64-19.0.4.tgz#7ef2be4044d9a1664e4b27554c4aecc6a98c56e7"
+  integrity sha512-8Wl2+TOXiRDLbI8mwsbx1sHQLKAaNvfTm2e5Kf+4ay4W/UsrHONRDRA4d/LhMOLQMo+2+q2q+u8DziqT0w0Vaw==
+
+"@nx/nx-linux-arm-gnueabihf@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-18.3.0.tgz#8c397d16ec040214d23ba0c867182c915d785795"
+  integrity sha512-nsjiJDq2B2m9NN7shJ8z/4A7bFUYGJdxk1RR6hVXY75Kpbh3HGh+fdKJrpqRzYUUmqxW/X7TRG2UD6T5lnNjWA==
+
+"@nx/nx-linux-arm-gnueabihf@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-19.0.4.tgz#8fc485f2841a2781099ab1f3f5c7ebd8b78094c0"
+  integrity sha512-C3PBsyNM5Npq8G8h/WHjUwwlKZpfWK4tK1ZeNseb6LtoNIgNF0PVrJQERqXABt29lanoQ4SeJ8RPgppB3xgCwg==
+
+"@nx/nx-linux-arm64-gnu@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-18.3.0.tgz#4f6bf538d6a0e0539d182f5a7dc2650017287cf0"
+  integrity sha512-baY3U0PudlAXHDzkJ+KdSfIcfFGKuBYXIXR1M18+Syq1kD9HDZ+sRVmosYpxVghrncN4UrcNvF/H7lgZo9x24Q==
+
+"@nx/nx-linux-arm64-gnu@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-19.0.4.tgz#ec3ac667b80582b07467f2b4c4d97db5f4d27d0e"
+  integrity sha512-d7gJv/QlaaBKTHpN+DmnQvo1FBNOGfh9b819WMaNXgDLSNpw9CpaOBZPbPgduee3OaGwbfWmll8VDYzUZgKWuw==
+
+"@nx/nx-linux-arm64-musl@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-18.3.0.tgz#1d23ee65246b8c9f24d8e82802ed59cd5525d1f3"
+  integrity sha512-nuKU4ehdKThq+Tzph2KXz2p39oBv8IrJQBONSAFzJ4zS0E/rNk2fKBeTBoqn1Psh2sNMYM8ZdlvxFK7pBmStQg==
+
+"@nx/nx-linux-arm64-musl@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-19.0.4.tgz#b4bfa2465dbd9c2cccc6297f5ebb890f6865a0c0"
+  integrity sha512-lQ76O4AtXAQJ6r1MdVDVp4GG+o2vylWFjcyZvZpclhjag+fWKSdO0igL/14HsqNwCPmcPtaHmgqQNlw3MMtL3w==
+
+"@nx/nx-linux-x64-gnu@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-18.3.0.tgz#d0a6c1aa9abba5ae724d05cf04ef7573116d9960"
+  integrity sha512-Pm7Q1hjKBJ3DFfnCLAtJVm13SkIushO3rPUdsDg5xZzOp59igNxrX2wJlwfi7U8dZMEZUPG0N1BIR3o7eEBxpQ==
+
+"@nx/nx-linux-x64-gnu@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-19.0.4.tgz#cebb20b39e670b657d4d6f44dfa45b0af45ac9a3"
+  integrity sha512-1K95WMdKHM4pMACzsO9m9TWqSXwL5cg9/1UuS9LUKhjY/bX2y3iTtzT0tFBptCVzRVLZG8wAZphxwQfBIQvnCQ==
+
+"@nx/nx-linux-x64-musl@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-18.3.0.tgz#73404e3a9a407cb32a1d0c7e26633737146d2144"
+  integrity sha512-VrspyfjIto0PtAqpjG3k8ueWsnqIOUp1gXBmlzYw0N4mjPldlhb258q1Kqyt1ykWLW79TqCjPblC6xHuOciKzQ==
+
+"@nx/nx-linux-x64-musl@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-19.0.4.tgz#0d314ed7103540ef3245008099116a18d5b73a04"
+  integrity sha512-iZ+TH/qT2R6nb+bqL8oJDDeUUEJmzYxtacFlf5yLjaiG5nvOxq7cu/lUw/LEqT+BUgK33T7acr3BDC0/q2bFZQ==
+
+"@nx/nx-win32-arm64-msvc@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-18.3.0.tgz#7c6a930c5e2fe5e0c4c0dd3ce04e517cbf00bbba"
+  integrity sha512-7C+Rk17u/CtcYq/LyG8b27MmuxjQOAqZ1yWPP5RHRr0HGB00kILkItmejs/CJAJqybPtydTR0hiF7xs7lcVOHw==
+
+"@nx/nx-win32-arm64-msvc@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-19.0.4.tgz#83fce511a288031d577e47652aeface9f16e4b4b"
+  integrity sha512-YiRyGZecH4hIy5shZz8SNX5NwY+dZC3Xs09QlMeLKNhf6klfmjJYNtd+9250V4cjJS3opKYf08uG4x+EtuEB5A==
+
+"@nx/nx-win32-x64-msvc@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-18.3.0.tgz#7cfb7033e24056a9177ccedd2a2f10906d3f7fbe"
+  integrity sha512-tRW2VZzwmdODaRXNgBJBSycVgLY269c3EwJDOCIPDIgFMTdClZNLmZbk4b7FfzyT7ezwQOD/3JgKJS6GzJdw8w==
+
+"@nx/nx-win32-x64-msvc@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-19.0.4.tgz#1cf618594e442385d6bc33411acccb7759546546"
+  integrity sha512-eHEdPjV0GlblyBM501xfe47tPRzugw2U+YOkZh++Ago9MDOrs/ULS9+RM3NhvZl2WnkpNYDbQMjzbQ0r7rxlTA==
+
+"@nx/playwright@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/playwright/-/playwright-18.3.0.tgz#e85d93a3e97261d8cd41e1fa364f640056fd422c"
+  integrity sha512-PZyPNcu0MeWgqiUIND+2Z96KAJ1ik6mxc9n+gv0CVMIy8bm1+6MxvvJF4Hh/vooPZyOb6ROlemygUOfI2xpu1w==
+  dependencies:
+    "@nx/devkit" "18.3.0"
+    "@nx/eslint" "18.3.0"
+    "@nx/js" "18.3.0"
     "@phenomnomnominal/tsquery" "~5.0.1"
     minimatch "9.0.3"
     tslib "^2.3.0"
 
-"@nx/vite@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nx/vite/-/vite-18.0.8.tgz#b91513a99e9805040b9a2d5252ee1cb78806c07f"
-  integrity sha512-DOGOMFEi+UiTklE7By29CoYDvsYJYFEHShF4x55eIri329hdjiTFUBPjuRn/M0U8rGsV3HNtghLBStPhCM+1PQ==
+"@nx/vite@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/vite/-/vite-18.3.0.tgz#081be38a2075a8dac20e10e252c76d47d2ec5836"
+  integrity sha512-u/4UlfgRaPMK7OHpJ40YGLwylPltCA7RSWN573I8sic/y8YfZgMtZ38RymVDXO//EWwy/lRqsMlaUDD2FTm+Jw==
   dependencies:
-    "@nrwl/vite" "18.0.8"
-    "@nx/devkit" "18.0.8"
-    "@nx/js" "18.0.8"
+    "@nrwl/vite" "18.3.0"
+    "@nx/devkit" "18.3.0"
+    "@nx/js" "18.3.0"
     "@phenomnomnominal/tsquery" "~5.0.1"
     "@swc/helpers" "~0.5.0"
     enquirer "~2.3.6"
     tsconfig-paths "^4.1.2"
 
-"@nx/web@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nx/web/-/web-18.0.8.tgz#de8620a5264c77a664073e5c76e5d73d278085b8"
-  integrity sha512-VpRGZ+Nxe1dldOT+dSWjjbvGO2oN/Gj0Lc6AcmKYSeBqbMnOfStylFl2vMEXISe2Flx3mHzzbRfKDC+z4U/kQA==
+"@nx/web@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/web/-/web-18.3.0.tgz#1d2f7df00e261f1707807a327a933afde8bde286"
+  integrity sha512-uEX0x96CXtiAD27XBTiFt1OV0seFuy18iJhm0wvS90VDVwAtqquBwBNX3UexHyCrIHn3qGr5tjsRBdpzQv3eCA==
   dependencies:
-    "@nrwl/web" "18.0.8"
-    "@nx/devkit" "18.0.8"
-    "@nx/js" "18.0.8"
+    "@nrwl/web" "18.3.0"
+    "@nx/devkit" "18.3.0"
+    "@nx/js" "18.3.0"
     chalk "^4.1.0"
     detect-port "^1.5.1"
     http-server "^14.1.0"
     tslib "^2.3.0"
 
-"@nx/workspace@18.0.8":
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/@nx/workspace/-/workspace-18.0.8.tgz#5d4b473365ad2f3df77214ba75872765208c6ede"
-  integrity sha512-y4s+PUgCWKd8tyvTjM0M05usIbPvl5wcTmoU9R4UriPAmNYzttvKYjweNcPJKsqdng1iwx7eECY1SmiOVRaTag==
+"@nx/webpack@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/webpack/-/webpack-18.3.0.tgz#890ff7ddf418bb713b661113ea34faf76285b44c"
+  integrity sha512-Lue+64LRdMJ+EwSDht/kkDMh8aPFRKN1+RQWhGp/ZZHeoimEeHAAXQu7WQ/QDzl+w6vWq+lQCrXENEf8lLQFhQ==
+  dependencies:
+    "@babel/core" "^7.23.2"
+    "@nrwl/webpack" "18.3.0"
+    "@nx/devkit" "18.3.0"
+    "@nx/js" "18.3.0"
+    ajv "^8.12.0"
+    autoprefixer "^10.4.9"
+    babel-loader "^9.1.2"
+    browserslist "^4.21.4"
+    chalk "^4.1.0"
+    copy-webpack-plugin "^10.2.4"
+    css-loader "^6.4.0"
+    css-minimizer-webpack-plugin "^5.0.0"
+    fork-ts-checker-webpack-plugin "7.2.13"
+    less "4.1.3"
+    less-loader "11.1.0"
+    license-webpack-plugin "^4.0.2"
+    loader-utils "^2.0.3"
+    mini-css-extract-plugin "~2.4.7"
+    parse5 "4.0.0"
+    postcss "^8.4.14"
+    postcss-import "~14.1.0"
+    postcss-loader "^6.1.1"
+    rxjs "^7.8.0"
+    sass "^1.42.1"
+    sass-loader "^12.2.0"
+    source-map-loader "^3.0.0"
+    style-loader "^3.3.0"
+    stylus "^0.59.0"
+    stylus-loader "^7.1.0"
+    terser-webpack-plugin "^5.3.3"
+    ts-loader "^9.3.1"
+    tsconfig-paths-webpack-plugin "4.0.0"
+    tslib "^2.3.0"
+    webpack "^5.80.0"
+    webpack-dev-server "^4.9.3"
+    webpack-node-externals "^3.0.0"
+    webpack-subresource-integrity "^5.1.0"
+
+"@nx/workspace@18.3.0":
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/@nx/workspace/-/workspace-18.3.0.tgz#df704109050152286acb416d1f41677bd1694196"
+  integrity sha512-gW5cR7Toki8HzO8uhEmjQYCRT17rOLcTcMSSlX2Y7VorgtL8+kUlVpqSsuGFBWiXsuSiMnATiXtHesDbSBKfYw==
+  dependencies:
+    "@nrwl/workspace" "18.3.0"
+    "@nx/devkit" "18.3.0"
+    chalk "^4.1.0"
+    enquirer "~2.3.6"
+    nx "18.3.0"
+    tslib "^2.3.0"
+    yargs-parser "21.1.1"
+
+"@nx/workspace@19.0.4":
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/@nx/workspace/-/workspace-19.0.4.tgz#042c4e7510b44633f438aeee4da0e72fc4e92c89"
+  integrity sha512-45dTDjeRDjfKG66h7E8PM13LXjnfUaTxDwYT9AqV90Uqw1EnGXte+KBNoG0IZsD3yoPpqTUIdZssl0twnVBlvQ==
   dependencies:
-    "@nrwl/workspace" "18.0.8"
-    "@nx/devkit" "18.0.8"
+    "@nrwl/workspace" "19.0.4"
+    "@nx/devkit" "19.0.4"
     chalk "^4.1.0"
     enquirer "~2.3.6"
-    nx "18.0.8"
+    nx "19.0.4"
     tslib "^2.3.0"
     yargs-parser "21.1.1"
 
@@ -3165,6 +5604,33 @@
   resolved "https://registry.yarnpkg.com/@one-ini/wasm/-/wasm-0.1.1.tgz#6013659736c9dbfccc96e8a9c2b3de317df39323"
   integrity sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==
 
+"@openzeppelin/contracts@3.4.2":
+  version "3.4.2"
+  resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2.tgz#d81f786fda2871d1eb8a8c5a73e455753ba53527"
+  integrity sha512-z0zMCjyhhp4y7XKAcDAi3Vgms4T2PstwBdahiO0+9NaGICQKjynK3wduSRplTgk4LXmoO1yfDGO5RbjKYxtuxA==
+
+"@openzeppelin/defender-base-client@1.54.2":
+  version "1.54.2"
+  resolved "https://registry.yarnpkg.com/@openzeppelin/defender-base-client/-/defender-base-client-1.54.2.tgz#60d3550d7bef4e75ac35d7de7648ecbb0ef2ad63"
+  integrity sha512-0k2Md6WKKkLTbsygz4UYWojJAkgrNLrishmosUIBCjaiGcAXMopgnRgX6V4WjnWkyI8RVEqb9H6IZY+8BNk6Bw==
+  dependencies:
+    amazon-cognito-identity-js "^6.0.1"
+    async-retry "^1.3.3"
+    axios "^1.4.0"
+    lodash "^4.17.19"
+    node-fetch "^2.6.0"
+
+"@openzeppelin/defender-relay-client@^1.54.2":
+  version "1.54.2"
+  resolved "https://registry.yarnpkg.com/@openzeppelin/defender-relay-client/-/defender-relay-client-1.54.2.tgz#82f4258eab5000a7dae87b15b528bcb4b966de96"
+  integrity sha512-S93RRaywwfNMbHtbRWisw7ZKBCzHtdoPkNC4K8QaZ8usbJQP74v6unOFJ19s62iVi++6UO5QqqUkxdcFsfe7Dg==
+  dependencies:
+    "@openzeppelin/defender-base-client" "1.54.2"
+    amazon-cognito-identity-js "^6.0.1"
+    axios "^1.4.0"
+    lodash "^4.17.19"
+    node-fetch "^2.6.0"
+
 "@parcel/watcher-android-arm64@2.4.0":
   version "2.4.0"
   resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.0.tgz#9c93763794153e4f76920994a423b6ea3257059d"
@@ -3271,6 +5737,16 @@
   dependencies:
     esquery "^1.4.0"
 
+"@pinata/sdk@^2.1.0":
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/@pinata/sdk/-/sdk-2.1.0.tgz#d61aa8f21ec1206e867f4b65996db52b70316945"
+  integrity sha512-hkS0tcKtsjf9xhsEBs2Nbey5s+Db7x5rlOH9TaWHBXkJ7IwwOs2xnEDigNaxAHKjYAwcw+m2hzpO5QgOfeF7Zw==
+  dependencies:
+    axios "^0.21.1"
+    form-data "^2.3.3"
+    is-ipfs "^0.6.0"
+    path "^0.12.7"
+
 "@pkgjs/parseargs@^0.11.0":
   version "0.11.0"
   resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
@@ -3283,6 +5759,19 @@
   dependencies:
     playwright "1.42.1"
 
+"@pmmmwh/react-refresh-webpack-plugin@^0.5.7":
+  version "0.5.13"
+  resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.13.tgz#02338a92a92f541a5189b97e922caf3215221e49"
+  integrity sha512-odZVYXly+JwzYri9rKqqUAk0cY6zLpv4dxoKinhoJNShV36Gpxf+CyDIILJ4tYsJ1ZxIWs233Y39iVnynvDA/g==
+  dependencies:
+    ansi-html-community "^0.0.8"
+    core-js-pure "^3.23.3"
+    error-stack-parser "^2.0.6"
+    html-entities "^2.1.0"
+    loader-utils "^2.0.4"
+    schema-utils "^3.0.0"
+    source-map "^0.7.3"
+
 "@polka/url@^1.0.0-next.24":
   version "1.0.0-next.25"
   resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.25.tgz#f077fdc0b5d0078d30893396ff4827a13f99e817"
@@ -3293,6 +5782,11 @@
   resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
   integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
 
+"@prisma/prisma-fmt-wasm@^4.17.0-16.27eb2449f178cd9fe1a4b892d732cc4795f75085":
+  version "4.17.0-16.27eb2449f178cd9fe1a4b892d732cc4795f75085"
+  resolved "https://registry.yarnpkg.com/@prisma/prisma-fmt-wasm/-/prisma-fmt-wasm-4.17.0-16.27eb2449f178cd9fe1a4b892d732cc4795f75085.tgz#030f8a4448892c345b3c5c0558ca0ebf4642f3de"
+  integrity sha512-zYz3rFwPB82mVlHGknAPdnSY/a308dhPOblxQLcZgZTDRtDXOE1MgxoRAys+jekwR4/bm3+rZDPs1xsFMsPZig==
+
 "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
@@ -3453,10 +5947,10 @@
     "@safe-global/safe-core-sdk-types" "^2.3.0"
     node-fetch "^2.6.6"
 
-"@safe-global/protocol-kit@^1.0.1":
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/@safe-global/protocol-kit/-/protocol-kit-1.3.0.tgz#fb84a3797a4afc74ac7fc218e796037d6e3cc3cc"
-  integrity sha512-zBhwHpaUggywmnR1Xm5RV22DpyjmVWYP3pnOl4rcf9LAc1k7IVmw6WIt2YVhHRaWGxVYMd4RitJX8Dx2+8eLZQ==
+"@safe-global/protocol-kit@1.2.0":
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/@safe-global/protocol-kit/-/protocol-kit-1.2.0.tgz#5501fa0e2a1b4ad03cd5a4ee12bb9e799e1dd5a7"
+  integrity sha512-drU2uK30AZ4tqI/9ER7PGMD/lZp/5B9T02t+noTk7WF9Xb7HxskJd8GNU01KE55oyH31Y0AfXaE68H/f9lYa4A==
   dependencies:
     "@ethersproject/address" "^5.7.0"
     "@ethersproject/bignumber" "^5.7.0"
@@ -3467,7 +5961,6 @@
     web3 "^1.8.1"
     web3-core "^1.8.1"
     web3-utils "^1.8.1"
-    zksync-web3 "^0.14.3"
 
 "@safe-global/safe-apps-provider@0.18.1":
   version "0.18.1"
@@ -3519,11 +6012,38 @@
   resolved "https://registry.yarnpkg.com/@safe-global/safe-gateway-typescript-sdk/-/safe-gateway-typescript-sdk-3.15.0.tgz#2a99e7eca7aecfad1f5e00744ffdd949cefa4f6a"
   integrity sha512-zAzhPgUwzdp89ZrZwCAOImUyAQMQE0LQKcK4vLO5eMbfAcNOxz5g4eVdBRBRa+kVXxjyW5wii58ZlGaYUVBa7g==
 
+"@sapphire/async-queue@^1.5.2":
+  version "1.5.2"
+  resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.5.2.tgz#2982dce16e5b8b1ea792604d20c23c0585877b97"
+  integrity sha512-7X7FFAA4DngXUl95+hYbUF19bp1LGiffjJtu7ygrZrbdCSsdDDBaSjB7Akw0ZbOu6k0xpXyljnJ6/RZUvLfRdg==
+
+"@sapphire/shapeshift@^3.9.7":
+  version "3.9.7"
+  resolved "https://registry.yarnpkg.com/@sapphire/shapeshift/-/shapeshift-3.9.7.tgz#43e23243cac8a0c046bf1e73baf3dbf407d33a0c"
+  integrity sha512-4It2mxPSr4OGn4HSQWGmhFMsNFGfFVhWeRPCRwbH972Ek2pzfGRZtb0pJ4Ze6oIzcyh2jw7nUDa6qGlWofgd9g==
+  dependencies:
+    fast-deep-equal "^3.1.3"
+    lodash "^4.17.21"
+
+"@sapphire/snowflake@3.5.3", "@sapphire/snowflake@^3.5.3":
+  version "3.5.3"
+  resolved "https://registry.yarnpkg.com/@sapphire/snowflake/-/snowflake-3.5.3.tgz#0c102aa2ec5b34f806e9bc8625fc6a5e1d0a0c6a"
+  integrity sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==
+
 "@scure/base@^1.1.3", "@scure/base@~1.1.0", "@scure/base@~1.1.2", "@scure/base@~1.1.4":
   version "1.1.5"
   resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.5.tgz#1d85d17269fe97694b9c592552dd9e5e33552157"
   integrity sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==
 
+"@scure/bip32@1.1.5":
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.1.5.tgz#d2ccae16dcc2e75bc1d75f5ef3c66a338d1ba300"
+  integrity sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==
+  dependencies:
+    "@noble/hashes" "~1.2.0"
+    "@noble/secp256k1" "~1.7.0"
+    "@scure/base" "~1.1.0"
+
 "@scure/bip32@1.3.2":
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.2.tgz#90e78c027d5e30f0b22c1f8d50ff12f3fb7559f8"
@@ -3542,6 +6062,14 @@
     "@noble/hashes" "~1.3.2"
     "@scure/base" "~1.1.4"
 
+"@scure/bip39@1.1.1":
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.1.1.tgz#b54557b2e86214319405db819c4b6a370cf340c5"
+  integrity sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==
+  dependencies:
+    "@noble/hashes" "~1.2.0"
+    "@scure/base" "~1.1.0"
+
 "@scure/bip39@1.2.1":
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.1.tgz#5cee8978656b272a917b7871c981e0541ad6ac2a"
@@ -3609,140 +6137,672 @@
   resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.14.2.tgz#6750c46fa4836b46ea48556b19f5e6789a428a47"
   integrity sha512-HgOFWYdq87lSmeVW1w8K2Vf2DGzRPvKzHTajZYLTPlrZ1jbajq9vwuqhrJ9AnDkjl0mjyzSPEy3ZTeG1Z7uRNA==
   dependencies:
-    "@babel/core" "7.18.5"
-    "@sentry/babel-plugin-component-annotate" "2.14.2"
-    "@sentry/cli" "^2.22.3"
-    dotenv "^16.3.1"
-    find-up "5.0.0"
-    glob "9.3.2"
-    magic-string "0.27.0"
-    unplugin "1.0.1"
+    "@babel/core" "7.18.5"
+    "@sentry/babel-plugin-component-annotate" "2.14.2"
+    "@sentry/cli" "^2.22.3"
+    dotenv "^16.3.1"
+    find-up "5.0.0"
+    glob "9.3.2"
+    magic-string "0.27.0"
+    unplugin "1.0.1"
+
+"@sentry/cli-darwin@2.28.6":
+  version "2.28.6"
+  resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.28.6.tgz#83f9127de77e2a2d25eb143d90720b3e9042adc1"
+  integrity sha512-KRf0VvTltHQ5gA7CdbUkaIp222LAk/f1+KqpDzO6nB/jC/tL4sfiy6YyM4uiH6IbVEudB8WpHCECiatmyAqMBA==
+
+"@sentry/cli-linux-arm64@2.28.6":
+  version "2.28.6"
+  resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.28.6.tgz#6bb660e5d8145270e287a9a21201d2f9576b0634"
+  integrity sha512-caMDt37FI752n4/3pVltDjlrRlPFCOxK4PHvoZGQ3KFMsai0ZhE/0CLBUMQqfZf0M0r8KB2x7wqLm7xSELjefQ==
+
+"@sentry/cli-linux-arm@2.28.6":
+  version "2.28.6"
+  resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.28.6.tgz#73d466004ac445d9258e83a7b3d4e0ee6604e0bd"
+  integrity sha512-ANG7U47yEHD1g3JrfhpT4/MclEvmDZhctWgSP5gVw5X4AlcI87E6dTqccnLgvZjiIAQTaJJAZuSHVVF3Jk403w==
+
+"@sentry/cli-linux-i686@2.28.6":
+  version "2.28.6"
+  resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.28.6.tgz#f7175ca639ee05cf12d808f7fc31d59d6e2ee3b9"
+  integrity sha512-Tj1+GMc6lFsDRquOqaGKXFpW9QbmNK4TSfynkWKiJxdTEn5jSMlXXfr0r9OQrxu3dCCqEHkhEyU63NYVpgxIPw==
+
+"@sentry/cli-linux-x64@2.28.6":
+  version "2.28.6"
+  resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.28.6.tgz#df0af8d6c8c8c880eb7345c715a4dfa509544a40"
+  integrity sha512-Dt/Xz784w/z3tEObfyJEMmRIzn0D5qoK53H9kZ6e0yNvJOSKNCSOq5cQk4n1/qeG0K/6SU9dirmvHwFUiVNyYg==
+
+"@sentry/cli-win32-i686@2.28.6":
+  version "2.28.6"
+  resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.28.6.tgz#0df19912d1823b6ec034b4c4c714c7601211c926"
+  integrity sha512-zkpWtvY3kt+ogVaAbfFr2MEkgMMHJNJUnNMO8Ixce9gh38sybIkDkZNFnVPBXMClJV0APa4QH0EwumYBFZUMuQ==
+
+"@sentry/cli-win32-x64@2.28.6":
+  version "2.28.6"
+  resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.28.6.tgz#2344a206be3b555ec6540740f93a181199962804"
+  integrity sha512-TG2YzZ9JMeNFzbicdr5fbtsusVGACbrEfHmPgzWGDeLUP90mZxiMTjkXsE1X/5jQEQjB2+fyfXloba/Ugo51hA==
+
+"@sentry/cli@^2.22.3":
+  version "2.28.6"
+  resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.28.6.tgz#645f31b9e742e7bf7668c8f867149359e79b8123"
+  integrity sha512-o2Ngz7xXuhwHxMi+4BFgZ4qjkX0tdZeOSIZkFAGnTbRhQe5T8bxq6CcQRLdPhqMgqvDn7XuJ3YlFtD3ZjHvD7g==
+  dependencies:
+    https-proxy-agent "^5.0.0"
+    node-fetch "^2.6.7"
+    progress "^2.0.3"
+    proxy-from-env "^1.1.0"
+    which "^2.0.2"
+  optionalDependencies:
+    "@sentry/cli-darwin" "2.28.6"
+    "@sentry/cli-linux-arm" "2.28.6"
+    "@sentry/cli-linux-arm64" "2.28.6"
+    "@sentry/cli-linux-i686" "2.28.6"
+    "@sentry/cli-linux-x64" "2.28.6"
+    "@sentry/cli-win32-i686" "2.28.6"
+    "@sentry/cli-win32-x64" "2.28.6"
+
+"@sentry/core@5.30.0":
+  version "5.30.0"
+  resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.30.0.tgz#6b203664f69e75106ee8b5a2fe1d717379b331f3"
+  integrity sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==
+  dependencies:
+    "@sentry/hub" "5.30.0"
+    "@sentry/minimal" "5.30.0"
+    "@sentry/types" "5.30.0"
+    "@sentry/utils" "5.30.0"
+    tslib "^1.9.3"
+
+"@sentry/core@7.102.1":
+  version "7.102.1"
+  resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.102.1.tgz#855d37b6bba9986a9380864c823e696d3fc5aa01"
+  integrity sha512-QjY+LSP3du3J/C8x/FfEbRxgZgsWd0jfTJ4P7s9f219I1csK4OeBMC3UA1HwEa0pY/9OF6H/egW2CjOcMM5Pdg==
+  dependencies:
+    "@sentry/types" "7.102.1"
+    "@sentry/utils" "7.102.1"
+
+"@sentry/hub@5.30.0":
+  version "5.30.0"
+  resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.30.0.tgz#2453be9b9cb903404366e198bd30c7ca74cdc100"
+  integrity sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==
+  dependencies:
+    "@sentry/types" "5.30.0"
+    "@sentry/utils" "5.30.0"
+    tslib "^1.9.3"
+
+"@sentry/minimal@5.30.0":
+  version "5.30.0"
+  resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.30.0.tgz#ce3d3a6a273428e0084adcb800bc12e72d34637b"
+  integrity sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==
+  dependencies:
+    "@sentry/hub" "5.30.0"
+    "@sentry/types" "5.30.0"
+    tslib "^1.9.3"
+
+"@sentry/node@^5.18.1":
+  version "5.30.0"
+  resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.30.0.tgz#4ca479e799b1021285d7fe12ac0858951c11cd48"
+  integrity sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg==
+  dependencies:
+    "@sentry/core" "5.30.0"
+    "@sentry/hub" "5.30.0"
+    "@sentry/tracing" "5.30.0"
+    "@sentry/types" "5.30.0"
+    "@sentry/utils" "5.30.0"
+    cookie "^0.4.1"
+    https-proxy-agent "^5.0.0"
+    lru_map "^0.3.3"
+    tslib "^1.9.3"
+
+"@sentry/replay@7.102.1":
+  version "7.102.1"
+  resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.102.1.tgz#d6c17332d14dc312b124bbbda8f35d6a982b893c"
+  integrity sha512-HR/j9dGIvbrId8fh8mQlODx7JrhRmawEd9e9P3laPtogWCg/5TI+XPb2VGSaXOX9VWtb/6Z2UjHsaGjgg6YcuA==
+  dependencies:
+    "@sentry-internal/tracing" "7.102.1"
+    "@sentry/core" "7.102.1"
+    "@sentry/types" "7.102.1"
+    "@sentry/utils" "7.102.1"
+
+"@sentry/tracing@5.30.0":
+  version "5.30.0"
+  resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-5.30.0.tgz#501d21f00c3f3be7f7635d8710da70d9419d4e1f"
+  integrity sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==
+  dependencies:
+    "@sentry/hub" "5.30.0"
+    "@sentry/minimal" "5.30.0"
+    "@sentry/types" "5.30.0"
+    "@sentry/utils" "5.30.0"
+    tslib "^1.9.3"
+
+"@sentry/types@5.30.0":
+  version "5.30.0"
+  resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.30.0.tgz#19709bbe12a1a0115bc790b8942917da5636f402"
+  integrity sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==
+
+"@sentry/types@7.102.1":
+  version "7.102.1"
+  resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.102.1.tgz#18c35f32ecbd12afb9860ca2de7bfff542d10b27"
+  integrity sha512-htKorf3t/D0XYtM7foTcmG+rM47rDP6XdbvCcX5gBCuCYlzpM1vqCt2rl3FLktZC6TaIpFRJw1TLfx6m+x5jdA==
+
+"@sentry/utils@5.30.0":
+  version "5.30.0"
+  resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.30.0.tgz#9a5bd7ccff85ccfe7856d493bffa64cabc41e980"
+  integrity sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==
+  dependencies:
+    "@sentry/types" "5.30.0"
+    tslib "^1.9.3"
+
+"@sentry/utils@7.102.1":
+  version "7.102.1"
+  resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.102.1.tgz#45ddcdf2e700d40160347bbdf4233aff3179d398"
+  integrity sha512-+8WcFjHVV/HROXSAwMuUzveElBFC43EiTG7SNEBNgOUeQzQVTmbUZXyTVgLrUmtoWqvnIxCacoLxtZo1o67kdg==
+  dependencies:
+    "@sentry/types" "7.102.1"
+
+"@sentry/vite-plugin@^2.7.1":
+  version "2.14.2"
+  resolved "https://registry.yarnpkg.com/@sentry/vite-plugin/-/vite-plugin-2.14.2.tgz#f17cbd5526a95de3d8a7995b4cd90e2c8a548bf1"
+  integrity sha512-t8IiRZGxivtODgabjgHlgUhOBEIJdOclJGUKLAJjJqPtYeKjPzxYOo/Z5yt7k1rhBAaMhFk3whW5o7SOq4KVOA==
+  dependencies:
+    "@sentry/bundler-plugin-core" "2.14.2"
+    unplugin "1.0.1"
+
+"@sentry/vue@^7.71.0":
+  version "7.102.1"
+  resolved "https://registry.yarnpkg.com/@sentry/vue/-/vue-7.102.1.tgz#3dea7987dae7338a428a525f94b44e29d90ff6b1"
+  integrity sha512-7sTrdAe3EL45MaA44mAgSPRg7jQ/CE6LifHl+62hjchpzsh+W+xWsN+31hbvm9ek6v/gNnQAlxyAXqXBRWtrlQ==
+  dependencies:
+    "@sentry/browser" "7.102.1"
+    "@sentry/core" "7.102.1"
+    "@sentry/types" "7.102.1"
+    "@sentry/utils" "7.102.1"
+
+"@sinclair/typebox@^0.27.8":
+  version "0.27.8"
+  resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
+  integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==
+
+"@sindresorhus/is@^4.0.0", "@sindresorhus/is@^4.6.0":
+  version "4.6.0"
+  resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f"
+  integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==
+
+"@sinonjs/commons@^3.0.0":
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd"
+  integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==
+  dependencies:
+    type-detect "4.0.8"
+
+"@sinonjs/fake-timers@^10.0.2":
+  version "10.3.0"
+  resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66"
+  integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==
+  dependencies:
+    "@sinonjs/commons" "^3.0.0"
+
+"@smithy/abort-controller@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-3.0.0.tgz#5815f5d4618e14bf8d031bb98a99adabbb831168"
+  integrity sha512-p6GlFGBt9K4MYLu72YuJ523NVR4A8oHlC5M2JO6OmQqN8kAc/uh1JqLE+FizTokrSJGg0CSvC+BrsmGzKtsZKA==
+  dependencies:
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/chunked-blob-reader-native@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.0.tgz#f1104b30030f76f9aadcbd3cdca4377bd1ba2695"
+  integrity sha512-VDkpCYW+peSuM4zJip5WDfqvg2Mo/e8yxOv3VF1m11y7B8KKMKVFtmZWDe36Fvk8rGuWrPZHHXZ7rR7uM5yWyg==
+  dependencies:
+    "@smithy/util-base64" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/chunked-blob-reader@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/chunked-blob-reader/-/chunked-blob-reader-3.0.0.tgz#e5d3b04e9b273ba8b7ede47461e2aa96c8aa49e0"
+  integrity sha512-sbnURCwjF0gSToGlsBiAmd1lRCmSn72nu9axfJu5lIx6RUEgHu6GwTMbqCdhQSi0Pumcm5vFxsi9XWXb2mTaoA==
+  dependencies:
+    tslib "^2.6.2"
+
+"@smithy/config-resolver@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/config-resolver/-/config-resolver-3.0.0.tgz#d37b31e3202c5ce54d9bd2406dcde7c7b5073cbd"
+  integrity sha512-2GzOfADwYLQugYkKQhIyZyQlM05K+tMKvRnc6eFfZcpJGRfKoMUMYdPlBKmqHwQFXQKBrGV6cxL9oymWgDzvFw==
+  dependencies:
+    "@smithy/node-config-provider" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    "@smithy/util-config-provider" "^3.0.0"
+    "@smithy/util-middleware" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/core@^2.0.0":
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/@smithy/core/-/core-2.0.1.tgz#8a7ac8faa0227912ce260bc3f976a5e254323920"
+  integrity sha512-rcMkjvwxH/bER+oZUPR0yTA0ELD6m3A+d92+CFkdF6HJFCBB1bXo7P5pm21L66XwTN01B6bUhSCQ7cymWRD8zg==
+  dependencies:
+    "@smithy/middleware-endpoint" "^3.0.0"
+    "@smithy/middleware-retry" "^3.0.1"
+    "@smithy/middleware-serde" "^3.0.0"
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/smithy-client" "^3.0.1"
+    "@smithy/types" "^3.0.0"
+    "@smithy/util-middleware" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/credential-provider-imds@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/credential-provider-imds/-/credential-provider-imds-3.0.0.tgz#a290eb0224ef045742e5c806685cf63d44a084f3"
+  integrity sha512-lfmBiFQcA3FsDAPxNfY0L7CawcWtbyWsBOHo34nF095728JLkBX4Y9q/VPPE2r7fqMVK+drmDigqE2/SSQeVRA==
+  dependencies:
+    "@smithy/node-config-provider" "^3.0.0"
+    "@smithy/property-provider" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    "@smithy/url-parser" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/eventstream-codec@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/eventstream-codec/-/eventstream-codec-3.0.0.tgz#81d30391220f73d41f432f65384b606d67673e46"
+  integrity sha512-PUtyEA0Oik50SaEFCZ0WPVtF9tz/teze2fDptW6WRXl+RrEenH8UbEjudOz8iakiMl3lE3lCVqYf2Y+znL8QFQ==
+  dependencies:
+    "@aws-crypto/crc32" "3.0.0"
+    "@smithy/types" "^3.0.0"
+    "@smithy/util-hex-encoding" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/eventstream-serde-browser@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.0.tgz#94721b01f01d8b7eb1db5814275a774ed4d38190"
+  integrity sha512-NB7AFiPN4NxP/YCAnrvYR18z2/ZsiHiF7VtG30gshO9GbFrIb1rC8ep4NGpJSWrz6P64uhPXeo4M0UsCLnZKqw==
+  dependencies:
+    "@smithy/eventstream-serde-universal" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/eventstream-serde-config-resolver@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.0.tgz#420447d1d284d41f7f070a5d92fc3686cc922581"
+  integrity sha512-RUQG3vQ3LX7peqqHAbmayhgrF5aTilPnazinaSGF1P0+tgM3vvIRWPHmlLIz2qFqB9LqFIxditxc8O2Z6psrRw==
+  dependencies:
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/eventstream-serde-node@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.0.tgz#6519523fbb429307be29b151b8ba35bcca2b6e64"
+  integrity sha512-baRPdMBDMBExZXIUAoPGm/hntixjt/VFpU6+VmCyiYJYzRHRxoaI1MN+5XE+hIS8AJ2GCHLMFEIOLzq9xx1EgQ==
+  dependencies:
+    "@smithy/eventstream-serde-universal" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/eventstream-serde-universal@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.0.tgz#cb8441a73fbde4cbaa68e4a21236f658d914a073"
+  integrity sha512-HNFfShmotWGeAoW4ujP8meV9BZavcpmerDbPIjkJbxKbN8RsUcpRQ/2OyIxWNxXNH2GWCAxuSB7ynmIGJlQ3Dw==
+  dependencies:
+    "@smithy/eventstream-codec" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/fetch-http-handler@^3.0.0", "@smithy/fetch-http-handler@^3.0.1":
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-3.0.1.tgz#dacfdf6e70d639fac4a0f57c42ce13f0ed14ff22"
+  integrity sha512-uaH74i5BDj+rBwoQaXioKpI0SHBJFtOVwzrCpxZxphOW0ki5jhj7dXvDMYM2IJem8TpdFvS2iC08sjOblfFGFg==
+  dependencies:
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/querystring-builder" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    "@smithy/util-base64" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/hash-blob-browser@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/hash-blob-browser/-/hash-blob-browser-3.0.0.tgz#63ef4c98f74c53cbcad8ec73387c68ec4708f55b"
+  integrity sha512-/Wbpdg+bwJvW7lxR/zpWAc1/x/YkcqguuF2bAzkJrvXriZu1vm8r+PUdE4syiVwQg7PPR2dXpi3CLBb9qRDaVQ==
+  dependencies:
+    "@smithy/chunked-blob-reader" "^3.0.0"
+    "@smithy/chunked-blob-reader-native" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/hash-node@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/hash-node/-/hash-node-3.0.0.tgz#f44b5fff193e241c1cdcc957b296b60f186f0e59"
+  integrity sha512-84qXstNemP3XS5jcof0el6+bDfjzuvhJPQTEfro3lgtbCtKgzPm3MgiS6ehXVPjeQ5+JS0HqmTz8f/RYfzHVxw==
+  dependencies:
+    "@smithy/types" "^3.0.0"
+    "@smithy/util-buffer-from" "^3.0.0"
+    "@smithy/util-utf8" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/hash-stream-node@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/hash-stream-node/-/hash-stream-node-3.0.0.tgz#b395a8a0d2427e4a8effc56135b37cb299339f8f"
+  integrity sha512-J0i7de+EgXDEGITD4fxzmMX8CyCNETTIRXlxjMiNUvvu76Xn3GJ31wQR85ynlPk2wI1lqoknAFJaD1fiNDlbIA==
+  dependencies:
+    "@smithy/types" "^3.0.0"
+    "@smithy/util-utf8" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/invalid-dependency@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/invalid-dependency/-/invalid-dependency-3.0.0.tgz#21cb6b5203ee15321bfcc751f21f7a19536d4ae8"
+  integrity sha512-F6wBBaEFgJzj0s4KUlliIGPmqXemwP6EavgvDqYwCH40O5Xr2iMHvS8todmGVZtuJCorBkXsYLyTu4PuizVq5g==
+  dependencies:
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/is-array-buffer@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz#9a95c2d46b8768946a9eec7f935feaddcffa5e7a"
+  integrity sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==
+  dependencies:
+    tslib "^2.6.2"
+
+"@smithy/md5-js@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/md5-js/-/md5-js-3.0.0.tgz#6a2d1c496f4d4476a0fc84f7724d79b234c3eb13"
+  integrity sha512-Tm0vrrVzjlD+6RCQTx7D3Ls58S3FUH1ZCtU1MIh/qQmaOo1H9lMN2as6CikcEwgattnA9SURSdoJJ27xMcEfMA==
+  dependencies:
+    "@smithy/types" "^3.0.0"
+    "@smithy/util-utf8" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/middleware-content-length@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/middleware-content-length/-/middleware-content-length-3.0.0.tgz#084b3d22248967885d496eb0b105d9090e8ababd"
+  integrity sha512-3C4s4d/iGobgCtk2tnWW6+zSTOBg1PRAm2vtWZLdriwTroFbbWNSr3lcyzHdrQHnEXYCC5K52EbpfodaIUY8sg==
+  dependencies:
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/middleware-endpoint@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-3.0.0.tgz#54c9e1bd8f35b7d004c803eaf3702e61e32b8295"
+  integrity sha512-aXOAWztw/5qAfp0NcA2OWpv6ZI/E+Dh9mByif7i91D/0iyYNUcKvskmXiowKESFkuZ7PIMd3VOR4fTibZDs2OQ==
+  dependencies:
+    "@smithy/middleware-serde" "^3.0.0"
+    "@smithy/node-config-provider" "^3.0.0"
+    "@smithy/shared-ini-file-loader" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    "@smithy/url-parser" "^3.0.0"
+    "@smithy/util-middleware" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/middleware-retry@^3.0.0", "@smithy/middleware-retry@^3.0.1":
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-3.0.1.tgz#167b75e9b79395f11a799f22030eaaf7d40da410"
+  integrity sha512-hBhSEuL841FhJBK/19WpaGk5YWSzFk/P2UaVjANGKRv3eYNO8Y1lANWgqnuPWjOyCEWMPr58vELFDWpxvRKANw==
+  dependencies:
+    "@smithy/node-config-provider" "^3.0.0"
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/service-error-classification" "^3.0.0"
+    "@smithy/smithy-client" "^3.0.1"
+    "@smithy/types" "^3.0.0"
+    "@smithy/util-middleware" "^3.0.0"
+    "@smithy/util-retry" "^3.0.0"
+    tslib "^2.6.2"
+    uuid "^9.0.1"
+
+"@smithy/middleware-serde@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-3.0.0.tgz#786da6a6bc0e5e51d669dac834c19965245dd302"
+  integrity sha512-I1vKG1foI+oPgG9r7IMY1S+xBnmAn1ISqployvqkwHoSb8VPsngHDTOgYGYBonuOKndaWRUGJZrKYYLB+Ane6w==
+  dependencies:
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/middleware-stack@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-3.0.0.tgz#00f112bae7af5fc3bd37d4fab95ebce0f17a7774"
+  integrity sha512-+H0jmyfAyHRFXm6wunskuNAqtj7yfmwFB6Fp37enytp2q047/Od9xetEaUbluyImOlGnGpaVGaVfjwawSr+i6Q==
+  dependencies:
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/node-config-provider@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-3.0.0.tgz#4cd5dcf6132c75d6a582fcd6243482dac703865a"
+  integrity sha512-buqfaSdDh0zo62EPLf8rGDvcpKwGpO5ho4bXS2cdFhlOta7tBkWJt+O5uiaAeICfIOfPclNOndshDNSanX2X9g==
+  dependencies:
+    "@smithy/property-provider" "^3.0.0"
+    "@smithy/shared-ini-file-loader" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/node-http-handler@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-3.0.0.tgz#e771ea95d03e259f04b7b37e8aece8a4fffc8cdc"
+  integrity sha512-3trD4r7NOMygwLbUJo4eodyQuypAWr7uvPnebNJ9a70dQhVn+US8j/lCnvoJS6BXfZeF7PkkkI0DemVJw+n+eQ==
+  dependencies:
+    "@smithy/abort-controller" "^3.0.0"
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/querystring-builder" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/property-provider@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-3.0.0.tgz#ef7a26557c855cc1471b9aa0e05529183e99b978"
+  integrity sha512-LmbPgHBswdXCrkWWuUwBm9w72S2iLWyC/5jet9/Y9cGHtzqxi+GVjfCfahkvNV4KXEwgnH8EMpcrD9RUYe0eLQ==
+  dependencies:
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/protocol-http@^4.0.0":
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-4.0.0.tgz#04df3b5674b540323f678e7c4113e8abd8b26432"
+  integrity sha512-qOQZOEI2XLWRWBO9AgIYuHuqjZ2csyr8/IlgFDHDNuIgLAMRx2Bl8ck5U5D6Vh9DPdoaVpuzwWMa0xcdL4O/AQ==
+  dependencies:
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/querystring-builder@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-3.0.0.tgz#48a9aa7b700e8409368c21bc0adf7564e001daea"
+  integrity sha512-bW8Fi0NzyfkE0TmQphDXr1AmBDbK01cA4C1Z7ggwMAU5RDz5AAv/KmoRwzQAS0kxXNf/D2ALTEgwK0U2c4LtRg==
+  dependencies:
+    "@smithy/types" "^3.0.0"
+    "@smithy/util-uri-escape" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/querystring-parser@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/querystring-parser/-/querystring-parser-3.0.0.tgz#fa1ed0cee408cd4d622070fa874bc50ac1a379b7"
+  integrity sha512-UzHwthk0UEccV4dHzPySnBy34AWw3V9lIqUTxmozQ+wPDAO9csCWMfOLe7V9A2agNYy7xE+Pb0S6K/J23JSzfQ==
+  dependencies:
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
 
-"@sentry/cli-darwin@2.28.6":
-  version "2.28.6"
-  resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.28.6.tgz#83f9127de77e2a2d25eb143d90720b3e9042adc1"
-  integrity sha512-KRf0VvTltHQ5gA7CdbUkaIp222LAk/f1+KqpDzO6nB/jC/tL4sfiy6YyM4uiH6IbVEudB8WpHCECiatmyAqMBA==
+"@smithy/service-error-classification@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/service-error-classification/-/service-error-classification-3.0.0.tgz#06a45cb91b15b8b0d5f3b1df2b3743d2ca42f5c4"
+  integrity sha512-3BsBtOUt2Gsnc3X23ew+r2M71WwtpHfEDGhHYHSDg6q1t8FrWh15jT25DLajFV1H+PpxAJ6gqe9yYeRUsmSdFA==
+  dependencies:
+    "@smithy/types" "^3.0.0"
 
-"@sentry/cli-linux-arm64@2.28.6":
-  version "2.28.6"
-  resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.28.6.tgz#6bb660e5d8145270e287a9a21201d2f9576b0634"
-  integrity sha512-caMDt37FI752n4/3pVltDjlrRlPFCOxK4PHvoZGQ3KFMsai0ZhE/0CLBUMQqfZf0M0r8KB2x7wqLm7xSELjefQ==
+"@smithy/shared-ini-file-loader@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.0.0.tgz#8739b7cd24f55fb4e276a74f00f0c2bb4e3f25d8"
+  integrity sha512-REVw6XauXk8xE4zo5aGL7Rz4ywA8qNMUn8RtWeTRQsgAlmlvbJ7CEPBcaXU2NDC3AYBgYAXrGyWD8XrN8UGDog==
+  dependencies:
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
 
-"@sentry/cli-linux-arm@2.28.6":
-  version "2.28.6"
-  resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.28.6.tgz#73d466004ac445d9258e83a7b3d4e0ee6604e0bd"
-  integrity sha512-ANG7U47yEHD1g3JrfhpT4/MclEvmDZhctWgSP5gVw5X4AlcI87E6dTqccnLgvZjiIAQTaJJAZuSHVVF3Jk403w==
+"@smithy/signature-v4@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-3.0.0.tgz#f536d0abebfeeca8e9aab846a4042658ca07d3b7"
+  integrity sha512-kXFOkNX+BQHe2qnLxpMEaCRGap9J6tUGLzc3A9jdn+nD4JdMwCKTJ+zFwQ20GkY+mAXGatyTw3HcoUlR39HwmA==
+  dependencies:
+    "@smithy/is-array-buffer" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    "@smithy/util-hex-encoding" "^3.0.0"
+    "@smithy/util-middleware" "^3.0.0"
+    "@smithy/util-uri-escape" "^3.0.0"
+    "@smithy/util-utf8" "^3.0.0"
+    tslib "^2.6.2"
 
-"@sentry/cli-linux-i686@2.28.6":
-  version "2.28.6"
-  resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.28.6.tgz#f7175ca639ee05cf12d808f7fc31d59d6e2ee3b9"
-  integrity sha512-Tj1+GMc6lFsDRquOqaGKXFpW9QbmNK4TSfynkWKiJxdTEn5jSMlXXfr0r9OQrxu3dCCqEHkhEyU63NYVpgxIPw==
+"@smithy/smithy-client@^3.0.0", "@smithy/smithy-client@^3.0.1":
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-3.0.1.tgz#c440473f6fb5dfbe86eaf015565fc56f66533bb4"
+  integrity sha512-KAiFY4Y4jdHxR+4zerH/VBhaFKM8pbaVmJZ/CWJRwtM/CmwzTfXfvYwf6GoUwiHepdv+lwiOXCuOl6UBDUEINw==
+  dependencies:
+    "@smithy/middleware-endpoint" "^3.0.0"
+    "@smithy/middleware-stack" "^3.0.0"
+    "@smithy/protocol-http" "^4.0.0"
+    "@smithy/types" "^3.0.0"
+    "@smithy/util-stream" "^3.0.1"
+    tslib "^2.6.2"
 
-"@sentry/cli-linux-x64@2.28.6":
-  version "2.28.6"
-  resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.28.6.tgz#df0af8d6c8c8c880eb7345c715a4dfa509544a40"
-  integrity sha512-Dt/Xz784w/z3tEObfyJEMmRIzn0D5qoK53H9kZ6e0yNvJOSKNCSOq5cQk4n1/qeG0K/6SU9dirmvHwFUiVNyYg==
+"@smithy/types@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/types/-/types-3.0.0.tgz#00231052945159c64ffd8b91e8909d8d3006cb7e"
+  integrity sha512-VvWuQk2RKFuOr98gFhjca7fkBS+xLLURT8bUjk5XQoV0ZLm7WPwWPPY3/AwzTLuUBDeoKDCthfe1AsTUWaSEhw==
+  dependencies:
+    tslib "^2.6.2"
 
-"@sentry/cli-win32-i686@2.28.6":
-  version "2.28.6"
-  resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.28.6.tgz#0df19912d1823b6ec034b4c4c714c7601211c926"
-  integrity sha512-zkpWtvY3kt+ogVaAbfFr2MEkgMMHJNJUnNMO8Ixce9gh38sybIkDkZNFnVPBXMClJV0APa4QH0EwumYBFZUMuQ==
+"@smithy/url-parser@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/url-parser/-/url-parser-3.0.0.tgz#5fdc77cd22051c1aac6531be0315bfcba0fa705d"
+  integrity sha512-2XLazFgUu+YOGHtWihB3FSLAfCUajVfNBXGGYjOaVKjLAuAxx3pSBY3hBgLzIgB17haf59gOG3imKqTy8mcrjw==
+  dependencies:
+    "@smithy/querystring-parser" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
 
-"@sentry/cli-win32-x64@2.28.6":
-  version "2.28.6"
-  resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.28.6.tgz#2344a206be3b555ec6540740f93a181199962804"
-  integrity sha512-TG2YzZ9JMeNFzbicdr5fbtsusVGACbrEfHmPgzWGDeLUP90mZxiMTjkXsE1X/5jQEQjB2+fyfXloba/Ugo51hA==
+"@smithy/util-base64@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/util-base64/-/util-base64-3.0.0.tgz#f7a9a82adf34e27a72d0719395713edf0e493017"
+  integrity sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==
+  dependencies:
+    "@smithy/util-buffer-from" "^3.0.0"
+    "@smithy/util-utf8" "^3.0.0"
+    tslib "^2.6.2"
 
-"@sentry/cli@^2.22.3":
-  version "2.28.6"
-  resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.28.6.tgz#645f31b9e742e7bf7668c8f867149359e79b8123"
-  integrity sha512-o2Ngz7xXuhwHxMi+4BFgZ4qjkX0tdZeOSIZkFAGnTbRhQe5T8bxq6CcQRLdPhqMgqvDn7XuJ3YlFtD3ZjHvD7g==
+"@smithy/util-body-length-browser@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz#86ec2f6256310b4845a2f064e2f571c1ca164ded"
+  integrity sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==
   dependencies:
-    https-proxy-agent "^5.0.0"
-    node-fetch "^2.6.7"
-    progress "^2.0.3"
-    proxy-from-env "^1.1.0"
-    which "^2.0.2"
-  optionalDependencies:
-    "@sentry/cli-darwin" "2.28.6"
-    "@sentry/cli-linux-arm" "2.28.6"
-    "@sentry/cli-linux-arm64" "2.28.6"
-    "@sentry/cli-linux-i686" "2.28.6"
-    "@sentry/cli-linux-x64" "2.28.6"
-    "@sentry/cli-win32-i686" "2.28.6"
-    "@sentry/cli-win32-x64" "2.28.6"
+    tslib "^2.6.2"
 
-"@sentry/core@7.102.1":
-  version "7.102.1"
-  resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.102.1.tgz#855d37b6bba9986a9380864c823e696d3fc5aa01"
-  integrity sha512-QjY+LSP3du3J/C8x/FfEbRxgZgsWd0jfTJ4P7s9f219I1csK4OeBMC3UA1HwEa0pY/9OF6H/egW2CjOcMM5Pdg==
+"@smithy/util-body-length-node@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz#99a291bae40d8932166907fe981d6a1f54298a6d"
+  integrity sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==
   dependencies:
-    "@sentry/types" "7.102.1"
-    "@sentry/utils" "7.102.1"
+    tslib "^2.6.2"
 
-"@sentry/replay@7.102.1":
-  version "7.102.1"
-  resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.102.1.tgz#d6c17332d14dc312b124bbbda8f35d6a982b893c"
-  integrity sha512-HR/j9dGIvbrId8fh8mQlODx7JrhRmawEd9e9P3laPtogWCg/5TI+XPb2VGSaXOX9VWtb/6Z2UjHsaGjgg6YcuA==
+"@smithy/util-buffer-from@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz#559fc1c86138a89b2edaefc1e6677780c24594e3"
+  integrity sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==
   dependencies:
-    "@sentry-internal/tracing" "7.102.1"
-    "@sentry/core" "7.102.1"
-    "@sentry/types" "7.102.1"
-    "@sentry/utils" "7.102.1"
+    "@smithy/is-array-buffer" "^3.0.0"
+    tslib "^2.6.2"
 
-"@sentry/types@7.102.1":
-  version "7.102.1"
-  resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.102.1.tgz#18c35f32ecbd12afb9860ca2de7bfff542d10b27"
-  integrity sha512-htKorf3t/D0XYtM7foTcmG+rM47rDP6XdbvCcX5gBCuCYlzpM1vqCt2rl3FLktZC6TaIpFRJw1TLfx6m+x5jdA==
+"@smithy/util-config-provider@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz#62c6b73b22a430e84888a8f8da4b6029dd5b8efe"
+  integrity sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==
+  dependencies:
+    tslib "^2.6.2"
 
-"@sentry/utils@7.102.1":
-  version "7.102.1"
-  resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.102.1.tgz#45ddcdf2e700d40160347bbdf4233aff3179d398"
-  integrity sha512-+8WcFjHVV/HROXSAwMuUzveElBFC43EiTG7SNEBNgOUeQzQVTmbUZXyTVgLrUmtoWqvnIxCacoLxtZo1o67kdg==
+"@smithy/util-defaults-mode-browser@^3.0.0":
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.1.tgz#0ba33ec90f6dd311599bed3a3dd604f3adba9acd"
+  integrity sha512-nW5kEzdJn1Bn5TF+gOPHh2rcPli8JU9vSSXLbfg7uPnfR1TMRQqs9zlYRhIb87NeSxIbpdXOI94tvXSy+fvDYg==
   dependencies:
-    "@sentry/types" "7.102.1"
+    "@smithy/property-provider" "^3.0.0"
+    "@smithy/smithy-client" "^3.0.1"
+    "@smithy/types" "^3.0.0"
+    bowser "^2.11.0"
+    tslib "^2.6.2"
 
-"@sentry/vite-plugin@^2.7.1":
-  version "2.14.2"
-  resolved "https://registry.yarnpkg.com/@sentry/vite-plugin/-/vite-plugin-2.14.2.tgz#f17cbd5526a95de3d8a7995b4cd90e2c8a548bf1"
-  integrity sha512-t8IiRZGxivtODgabjgHlgUhOBEIJdOclJGUKLAJjJqPtYeKjPzxYOo/Z5yt7k1rhBAaMhFk3whW5o7SOq4KVOA==
+"@smithy/util-defaults-mode-node@^3.0.0":
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.1.tgz#71242a6978240a6f559445d4cc26f2cce91c90e1"
+  integrity sha512-TFk+Qb+elLc/MOhtSp+50fstyfZ6avQbgH2d96xUBpeScu+Al9elxv+UFAjaTHe0HQe5n+wem8ZLpXvU8lwV6Q==
+  dependencies:
+    "@smithy/config-resolver" "^3.0.0"
+    "@smithy/credential-provider-imds" "^3.0.0"
+    "@smithy/node-config-provider" "^3.0.0"
+    "@smithy/property-provider" "^3.0.0"
+    "@smithy/smithy-client" "^3.0.1"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/util-endpoints@^2.0.0":
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/util-endpoints/-/util-endpoints-2.0.0.tgz#5a16a723c1220f536a9b1b3e01787e69e77b6f12"
+  integrity sha512-+exaXzEY3DNt2qtA2OtRNSDlVrE4p32j1JSsQkzA5AdP0YtJNjkYbYhJxkFmPYcjI1abuwopOZCwUmv682QkiQ==
   dependencies:
-    "@sentry/bundler-plugin-core" "2.14.2"
-    unplugin "1.0.1"
+    "@smithy/node-config-provider" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
 
-"@sentry/vue@^7.71.0":
-  version "7.102.1"
-  resolved "https://registry.yarnpkg.com/@sentry/vue/-/vue-7.102.1.tgz#3dea7987dae7338a428a525f94b44e29d90ff6b1"
-  integrity sha512-7sTrdAe3EL45MaA44mAgSPRg7jQ/CE6LifHl+62hjchpzsh+W+xWsN+31hbvm9ek6v/gNnQAlxyAXqXBRWtrlQ==
+"@smithy/util-hex-encoding@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz#32938b33d5bf2a15796cd3f178a55b4155c535e6"
+  integrity sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==
   dependencies:
-    "@sentry/browser" "7.102.1"
-    "@sentry/core" "7.102.1"
-    "@sentry/types" "7.102.1"
-    "@sentry/utils" "7.102.1"
+    tslib "^2.6.2"
 
-"@sinclair/typebox@^0.27.8":
-  version "0.27.8"
-  resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
-  integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==
+"@smithy/util-middleware@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-3.0.0.tgz#64d775628b99a495ca83ce982f5c83aa45f1e894"
+  integrity sha512-q5ITdOnV2pXHSVDnKWrwgSNTDBAMHLptFE07ua/5Ty5WJ11bvr0vk2a7agu7qRhrCFRQlno5u3CneU5EELK+DQ==
+  dependencies:
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
 
-"@sindresorhus/is@^4.0.0", "@sindresorhus/is@^4.6.0":
-  version "4.6.0"
-  resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f"
-  integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==
+"@smithy/util-retry@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/util-retry/-/util-retry-3.0.0.tgz#8a0c47496aab74e1dfde4905d462ad636a8824bb"
+  integrity sha512-nK99bvJiziGv/UOKJlDvFF45F00WgPLKVIGUfAK+mDhzVN2hb/S33uW2Tlhg5PVBoqY7tDVqL0zmu4OxAHgo9g==
+  dependencies:
+    "@smithy/service-error-classification" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
 
-"@sinonjs/commons@^3.0.0":
+"@smithy/util-stream@^3.0.0", "@smithy/util-stream@^3.0.1":
   version "3.0.1"
-  resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd"
-  integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==
+  resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-3.0.1.tgz#3cf527bcd3fec82c231c38d47dd75f3364747edb"
+  integrity sha512-7F7VNNhAsfMRA8I986YdOY5fE0/T1/ZjFF6OLsqkvQVNP3vZ/szYDfGCyphb7ioA09r32K/0qbSFfNFU68aSzA==
+  dependencies:
+    "@smithy/fetch-http-handler" "^3.0.1"
+    "@smithy/node-http-handler" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    "@smithy/util-base64" "^3.0.0"
+    "@smithy/util-buffer-from" "^3.0.0"
+    "@smithy/util-hex-encoding" "^3.0.0"
+    "@smithy/util-utf8" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/util-uri-escape@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz#e43358a78bf45d50bb736770077f0f09195b6f54"
+  integrity sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==
   dependencies:
-    type-detect "4.0.8"
+    tslib "^2.6.2"
 
-"@sinonjs/fake-timers@^10.0.2":
-  version "10.3.0"
-  resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66"
-  integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==
+"@smithy/util-utf8@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-3.0.0.tgz#1a6a823d47cbec1fd6933e5fc87df975286d9d6a"
+  integrity sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==
   dependencies:
-    "@sinonjs/commons" "^3.0.0"
+    "@smithy/util-buffer-from" "^3.0.0"
+    tslib "^2.6.2"
+
+"@smithy/util-waiter@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@smithy/util-waiter/-/util-waiter-3.0.0.tgz#26bcc5bbbf1de9360a7aeb3b3919926fc6afa2bc"
+  integrity sha512-+fEXJxGDLCoqRKVSmo0auGxaqbiCo+8oph+4auefYjaNxjOLKSY2MxVQfRzo65PaZv4fr+5lWg+au7vSuJJ/zw==
+  dependencies:
+    "@smithy/abort-controller" "^3.0.0"
+    "@smithy/types" "^3.0.0"
+    tslib "^2.6.2"
 
 "@socket.io/component-emitter@~3.1.0":
   version "3.1.0"
@@ -3764,6 +6824,18 @@
   resolved "https://registry.yarnpkg.com/@soda/get-current-script/-/get-current-script-1.0.2.tgz#a53515db25d8038374381b73af20bb4f2e508d87"
   integrity sha512-T7VNNlYVM1SgQ+VsMYhnDkcGmWhQdL0bDyGm5TlQ3GBXnJscEClUUOKduWTmm2zCnvNLC1hc3JpuXjs/nFOc5w==
 
+"@solidity-parser/parser@^0.14.0":
+  version "0.14.5"
+  resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.5.tgz#87bc3cc7b068e08195c219c91cd8ddff5ef1a804"
+  integrity sha512-6dKnHZn7fg/iQATVEzqyUOyEidbn05q7YA2mQ9hC0MMXhhV3/JrsxmFSYZAcr7j1yUP700LLhTruvJ3MiQmjJg==
+  dependencies:
+    antlr4ts "^0.5.0-alpha.4"
+
+"@solidity-parser/parser@^0.18.0":
+  version "0.18.0"
+  resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.18.0.tgz#8e77a02a09ecce957255a2f48c9a7178ec191908"
+  integrity sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==
+
 "@stablelib/aead@^1.0.1":
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/@stablelib/aead/-/aead-1.0.1.tgz#c4b1106df9c23d1b867eb9b276d8f42d5fc4c0c3"
@@ -3898,6 +6970,112 @@
     "@stablelib/random" "^1.0.2"
     "@stablelib/wipe" "^1.0.1"
 
+"@svgr/babel-plugin-add-jsx-attribute@8.0.0":
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz#4001f5d5dd87fa13303e36ee106e3ff3a7eb8b22"
+  integrity sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==
+
+"@svgr/babel-plugin-remove-jsx-attribute@8.0.0":
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz#69177f7937233caca3a1afb051906698f2f59186"
+  integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==
+
+"@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0":
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz#c2c48104cfd7dcd557f373b70a56e9e3bdae1d44"
+  integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==
+
+"@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0":
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz#8fbb6b2e91fa26ac5d4aa25c6b6e4f20f9c0ae27"
+  integrity sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==
+
+"@svgr/babel-plugin-svg-dynamic-title@8.0.0":
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz#1d5ba1d281363fc0f2f29a60d6d936f9bbc657b0"
+  integrity sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==
+
+"@svgr/babel-plugin-svg-em-dimensions@8.0.0":
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz#35e08df300ea8b1d41cb8f62309c241b0369e501"
+  integrity sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==
+
+"@svgr/babel-plugin-transform-react-native-svg@8.1.0":
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz#90a8b63998b688b284f255c6a5248abd5b28d754"
+  integrity sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==
+
+"@svgr/babel-plugin-transform-svg-component@8.0.0":
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz#013b4bfca88779711f0ed2739f3f7efcefcf4f7e"
+  integrity sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==
+
+"@svgr/babel-preset@8.1.0":
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-8.1.0.tgz#0e87119aecdf1c424840b9d4565b7137cabf9ece"
+  integrity sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==
+  dependencies:
+    "@svgr/babel-plugin-add-jsx-attribute" "8.0.0"
+    "@svgr/babel-plugin-remove-jsx-attribute" "8.0.0"
+    "@svgr/babel-plugin-remove-jsx-empty-expression" "8.0.0"
+    "@svgr/babel-plugin-replace-jsx-attribute-value" "8.0.0"
+    "@svgr/babel-plugin-svg-dynamic-title" "8.0.0"
+    "@svgr/babel-plugin-svg-em-dimensions" "8.0.0"
+    "@svgr/babel-plugin-transform-react-native-svg" "8.1.0"
+    "@svgr/babel-plugin-transform-svg-component" "8.0.0"
+
+"@svgr/core@8.1.0":
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/@svgr/core/-/core-8.1.0.tgz#41146f9b40b1a10beaf5cc4f361a16a3c1885e88"
+  integrity sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==
+  dependencies:
+    "@babel/core" "^7.21.3"
+    "@svgr/babel-preset" "8.1.0"
+    camelcase "^6.2.0"
+    cosmiconfig "^8.1.3"
+    snake-case "^3.0.4"
+
+"@svgr/hast-util-to-babel-ast@8.0.0":
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz#6952fd9ce0f470e1aded293b792a2705faf4ffd4"
+  integrity sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==
+  dependencies:
+    "@babel/types" "^7.21.3"
+    entities "^4.4.0"
+
+"@svgr/plugin-jsx@8.1.0":
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz#96969f04a24b58b174ee4cd974c60475acbd6928"
+  integrity sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==
+  dependencies:
+    "@babel/core" "^7.21.3"
+    "@svgr/babel-preset" "8.1.0"
+    "@svgr/hast-util-to-babel-ast" "8.0.0"
+    svg-parser "^2.0.4"
+
+"@svgr/plugin-svgo@8.1.0":
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz#b115b7b967b564f89ac58feae89b88c3decd0f00"
+  integrity sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==
+  dependencies:
+    cosmiconfig "^8.1.3"
+    deepmerge "^4.3.1"
+    svgo "^3.0.2"
+
+"@svgr/webpack@^8.0.1":
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-8.1.0.tgz#16f1b5346f102f89fda6ec7338b96a701d8be0c2"
+  integrity sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==
+  dependencies:
+    "@babel/core" "^7.21.3"
+    "@babel/plugin-transform-react-constant-elements" "^7.21.3"
+    "@babel/preset-env" "^7.20.2"
+    "@babel/preset-react" "^7.18.6"
+    "@babel/preset-typescript" "^7.21.0"
+    "@svgr/core" "8.1.0"
+    "@svgr/plugin-jsx" "8.1.0"
+    "@svgr/plugin-svgo" "8.1.0"
+
 "@swc-node/core@^1.12.0":
   version "1.13.0"
   resolved "https://registry.yarnpkg.com/@swc-node/core/-/core-1.13.0.tgz#209d70f6371049926915a7d23a502144cdb5cffb"
@@ -4023,11 +7201,6 @@
   dependencies:
     defer-to-connect "^2.0.1"
 
-"@thxnetwork/sdk@1.3.5":
-  version "1.3.5"
-  resolved "https://registry.yarnpkg.com/@thxnetwork/sdk/-/sdk-1.3.5.tgz#ef20dc0151b7a7a0bf5a02bee7ca4bc1cbbd5216"
-  integrity sha512-sZFXgOgd5xesygf3iGpZyzhRghrzTwkCwDEfdkqMgNHZCc6dFJAaPxzCxstWUyiilqgkQwMFk1AScBbdXP3eew==
-
 "@tkey/common-types@^10.1.0":
   version "10.1.0"
   resolved "https://registry.yarnpkg.com/@tkey/common-types/-/common-types-10.1.0.tgz#1cde24b6e0d614e2c5eaa5eeeadc2607d2b0c714"
@@ -4238,6 +7411,11 @@
     json-stable-stringify "^1.0.2"
     loglevel "^1.8.1"
 
+"@trysound/sax@0.2.0":
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
+  integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
+
 "@tsconfig/node10@^1.0.7":
   version "1.0.9"
   resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2"
@@ -4258,6 +7436,28 @@
   resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
   integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
 
+"@typechain/ethers-v5@^10.1.0":
+  version "10.2.1"
+  resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-10.2.1.tgz#50241e6957683281ecfa03fb5a6724d8a3ce2391"
+  integrity sha512-n3tQmCZjRE6IU4h6lqUGiQ1j866n5MTCBJreNEHHVWXa2u9GJTaeYyU1/k+1qLutkyw+sS6VAN+AbeiTqsxd/A==
+  dependencies:
+    lodash "^4.17.15"
+    ts-essentials "^7.0.1"
+
+"@typechain/hardhat@^6.1.2":
+  version "6.1.6"
+  resolved "https://registry.yarnpkg.com/@typechain/hardhat/-/hardhat-6.1.6.tgz#1a749eb35e5054c80df531cf440819cb347c62ea"
+  integrity sha512-BiVnegSs+ZHVymyidtK472syodx1sXYlYJJixZfRstHVGYTi8V1O7QG4nsjyb0PC/LORcq7sfBUcHto1y6UgJA==
+  dependencies:
+    fs-extra "^9.1.0"
+
+"@types/accepts@*":
+  version "1.3.7"
+  resolved "https://registry.yarnpkg.com/@types/accepts/-/accepts-1.3.7.tgz#3b98b1889d2b2386604c2bbbe62e4fb51e95b265"
+  integrity sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==
+  dependencies:
+    "@types/node" "*"
+
 "@types/babel__core@^7.1.14":
   version "7.20.5"
   resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
@@ -4291,6 +7491,13 @@
   dependencies:
     "@babel/types" "^7.20.7"
 
+"@types/bn.js@^4.11.3":
+  version "4.11.6"
+  resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c"
+  integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==
+  dependencies:
+    "@types/node" "*"
+
 "@types/bn.js@^5.1.0", "@types/bn.js@^5.1.1":
   version "5.1.5"
   resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.5.tgz#2e0dacdcce2c0f16b905d20ff87aedbc6f7b4bf0"
@@ -4306,6 +7513,13 @@
     "@types/connect" "*"
     "@types/node" "*"
 
+"@types/bonjour@^3.5.9":
+  version "3.5.13"
+  resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.13.tgz#adf90ce1a105e81dd1f9c61fdc5afda1bfb92956"
+  integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==
+  dependencies:
+    "@types/node" "*"
+
 "@types/cacheable-request@^6.0.1", "@types/cacheable-request@^6.0.2":
   version "6.0.3"
   resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183"
@@ -4316,6 +7530,18 @@
     "@types/node" "*"
     "@types/responselike" "^1.0.0"
 
+"@types/chai-as-promised@^7.1.3":
+  version "7.1.8"
+  resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.8.tgz#f2b3d82d53c59626b5d6bbc087667ccb4b677fe9"
+  integrity sha512-ThlRVIJhr69FLlh6IctTXFkmhtP3NpMZ2QGq69StYLyKZFp/HOp1VdKZj7RvfNWYYcJ1xlbLGLLWj1UvP5u/Gw==
+  dependencies:
+    "@types/chai" "*"
+
+"@types/chai@*", "@types/chai@^4.2.0":
+  version "4.3.16"
+  resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.16.tgz#b1572967f0b8b60bf3f87fe1d854a5604ea70c82"
+  integrity sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ==
+
 "@types/chrome@^0.0.136":
   version "0.0.136"
   resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.136.tgz#7c011b9f997b0156f25a140188a0c5689d3f368f"
@@ -4343,7 +7569,21 @@
   dependencies:
     "@types/color-convert" "*"
 
-"@types/connect-history-api-fallback@*":
+"@types/compression@^1.7.5":
+  version "1.7.5"
+  resolved "https://registry.yarnpkg.com/@types/compression/-/compression-1.7.5.tgz#0f80efef6eb031be57b12221c4ba6bc3577808f7"
+  integrity sha512-AAQvK5pxMpaT+nDvhHrsBhLSYG5yQdtkaJE1WYieSNY2mVFKAgmU4ks65rkZD5oqnGCFLyQpUr1CqI4DmUMyDg==
+  dependencies:
+    "@types/express" "*"
+
+"@types/concat-stream@^1.6.0":
+  version "1.6.1"
+  resolved "https://registry.yarnpkg.com/@types/concat-stream/-/concat-stream-1.6.1.tgz#24bcfc101ecf68e886aaedce60dfd74b632a1b74"
+  integrity sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==
+  dependencies:
+    "@types/node" "*"
+
+"@types/connect-history-api-fallback@*", "@types/connect-history-api-fallback@^1.3.5":
   version "1.5.4"
   resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3"
   integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==
@@ -4358,6 +7598,26 @@
   dependencies:
     "@types/node" "*"
 
+"@types/content-disposition@*":
+  version "0.5.8"
+  resolved "https://registry.yarnpkg.com/@types/content-disposition/-/content-disposition-0.5.8.tgz#6742a5971f490dc41e59d277eee71361fea0b537"
+  integrity sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==
+
+"@types/cookiejar@^2.1.5":
+  version "2.1.5"
+  resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.5.tgz#14a3e83fa641beb169a2dd8422d91c3c345a9a78"
+  integrity sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==
+
+"@types/cookies@*":
+  version "0.9.0"
+  resolved "https://registry.yarnpkg.com/@types/cookies/-/cookies-0.9.0.tgz#a2290cfb325f75f0f28720939bee854d4142aee2"
+  integrity sha512-40Zk8qR147RABiQ7NQnBzWzDcjKzNrntB5BAmeGCb2p/MIyOE+4BVvc17wumsUqUw00bJYqoXFHYygQnEFh4/Q==
+  dependencies:
+    "@types/connect" "*"
+    "@types/express" "*"
+    "@types/keygrip" "*"
+    "@types/node" "*"
+
 "@types/debug@^4.1.7":
   version "4.1.12"
   resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917"
@@ -4370,6 +7630,27 @@
   resolved "https://registry.yarnpkg.com/@types/dom-screen-wake-lock/-/dom-screen-wake-lock-1.0.3.tgz#c3588a5f6f40fae957f9ce5be9bc4927a61bb9a0"
   integrity sha512-3Iten7X3Zgwvk6kh6/NRdwN7WbZ760YgFCsF5AxDifltUQzW1RaW+WRmcVtgwFzLjaNu64H+0MPJ13yRa8g3Dw==
 
+"@types/ejs@^3.1.5":
+  version "3.1.5"
+  resolved "https://registry.yarnpkg.com/@types/ejs/-/ejs-3.1.5.tgz#49d738257cc73bafe45c13cb8ff240683b4d5117"
+  integrity sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==
+
+"@types/eslint-scope@^3.7.3":
+  version "3.7.7"
+  resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5"
+  integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==
+  dependencies:
+    "@types/eslint" "*"
+    "@types/estree" "*"
+
+"@types/eslint@*":
+  version "8.56.10"
+  resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.10.tgz#eb2370a73bf04a901eeba8f22595c7ee0f7eb58d"
+  integrity sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==
+  dependencies:
+    "@types/estree" "*"
+    "@types/json-schema" "*"
+
 "@types/eslint@^8.4.5":
   version "8.56.3"
   resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.3.tgz#d1f6b2303ac5ed53cb2cf59e0ab680cde1698f5f"
@@ -4378,7 +7659,7 @@
     "@types/estree" "*"
     "@types/json-schema" "*"
 
-"@types/estree@*", "@types/estree@1.0.5", "@types/estree@^1.0.0":
+"@types/estree@*", "@types/estree@1.0.5", "@types/estree@^1.0.0", "@types/estree@^1.0.5":
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
   integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
@@ -4393,7 +7674,7 @@
     "@types/range-parser" "*"
     "@types/send" "*"
 
-"@types/express@*":
+"@types/express@*", "@types/express@^4.17.13", "@types/express@^4.17.17", "@types/express@~4.17.13":
   version "4.17.21"
   resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d"
   integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==
@@ -4415,6 +7696,13 @@
   resolved "https://registry.yarnpkg.com/@types/filewriter/-/filewriter-0.0.33.tgz#d9d611db9d9cd99ae4e458de420eeb64ad604ea8"
   integrity sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==
 
+"@types/form-data@0.0.33":
+  version "0.0.33"
+  resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-0.0.33.tgz#c9ac85b2a5fd18435b8c85d9ecb50e6d6c893ff8"
+  integrity sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==
+  dependencies:
+    "@types/node" "*"
+
 "@types/glob@^7.1.1":
   version "7.2.0"
   resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb"
@@ -4435,6 +7723,11 @@
   resolved "https://registry.yarnpkg.com/@types/har-format/-/har-format-1.2.15.tgz#f352493638c2f89d706438a19a9eb300b493b506"
   integrity sha512-RpQH4rXLuvTXKR0zqHq3go0RVXYv/YVqv4TnPH95VbwUxZdQlK1EtcMvQvMpDngHbt13Csh9Z4qT9AbkiQH5BA==
 
+"@types/http-assert@*":
+  version "1.5.5"
+  resolved "https://registry.yarnpkg.com/@types/http-assert/-/http-assert-1.5.5.tgz#dfb1063eb7c240ee3d3fe213dac5671cfb6a8dbf"
+  integrity sha512-4+tE/lwdAahgZT1g30Jkdm9PzFRde0xwxBNUyRsCitRvCQB90iuA2uJYdUnhnANRcqGXaWOGY4FEoxeElNAK2g==
+
 "@types/http-cache-semantics@*":
   version "4.0.4"
   resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4"
@@ -4445,7 +7738,7 @@
   resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f"
   integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==
 
-"@types/http-proxy@^1.17.5":
+"@types/http-proxy@^1.17.5", "@types/http-proxy@^1.17.8":
   version "1.17.14"
   resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.14.tgz#57f8ccaa1c1c3780644f8a94f9c6b5000b5e2eec"
   integrity sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==
@@ -4488,7 +7781,7 @@
     "@types/tough-cookie" "*"
     parse5 "^7.0.0"
 
-"@types/json-schema@*", "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5":
+"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
   version "7.0.15"
   resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
   integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
@@ -4498,6 +7791,18 @@
   resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
   integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
 
+"@types/jsonwebtoken@^9", "@types/jsonwebtoken@^9.0.2":
+  version "9.0.6"
+  resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz#d1af3544d99ad992fb6681bbe60676e06b032bd3"
+  integrity sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==
+  dependencies:
+    "@types/node" "*"
+
+"@types/keygrip@*":
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.6.tgz#1749535181a2a9b02ac04a797550a8787345b740"
+  integrity sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==
+
 "@types/keyv@^3.1.4":
   version "3.1.4"
   resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6"
@@ -4505,6 +7810,52 @@
   dependencies:
     "@types/node" "*"
 
+"@types/koa-compose@*":
+  version "3.2.8"
+  resolved "https://registry.yarnpkg.com/@types/koa-compose/-/koa-compose-3.2.8.tgz#dec48de1f6b3d87f87320097686a915f1e954b57"
+  integrity sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==
+  dependencies:
+    "@types/koa" "*"
+
+"@types/koa@*":
+  version "2.15.0"
+  resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.15.0.tgz#eca43d76f527c803b491731f95df575636e7b6f2"
+  integrity sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==
+  dependencies:
+    "@types/accepts" "*"
+    "@types/content-disposition" "*"
+    "@types/cookies" "*"
+    "@types/http-assert" "*"
+    "@types/http-errors" "*"
+    "@types/keygrip" "*"
+    "@types/koa-compose" "*"
+    "@types/node" "*"
+
+"@types/lru-cache@^5.1.0":
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.1.tgz#c48c2e27b65d2a153b19bfc1a317e30872e01eef"
+  integrity sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==
+
+"@types/lusca@^1.7.5":
+  version "1.7.5"
+  resolved "https://registry.yarnpkg.com/@types/lusca/-/lusca-1.7.5.tgz#6fff257dc11176bd3150afba90790e626a12cd0f"
+  integrity sha512-l49gAf8pu2iMzbKejLcz6Pqj+51H2na6BgORv1ElnE8ByPFcBdh/eZ0WNR1Va/6ZuNSZa01Hoy1DTZ3IZ+y+kA==
+  dependencies:
+    "@types/express" "*"
+
+"@types/methods@^1.1.4":
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/@types/methods/-/methods-1.1.4.tgz#d3b7ac30ac47c91054ea951ce9eed07b1051e547"
+  integrity sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==
+
+"@types/migrate-mongo@^10.0.4":
+  version "10.0.4"
+  resolved "https://registry.yarnpkg.com/@types/migrate-mongo/-/migrate-mongo-10.0.4.tgz#5b68fb9c3ca516e4f025ebca34021880137f94a4"
+  integrity sha512-+9JAzkIbxgox33wCT18bdpZ6ASYo1qwbf4Gg8kTbqvT48ajosSbZBGg94Vsy3baFzT6+DUXWylY/DcoOWOPNRg==
+  dependencies:
+    "@types/node" "*"
+    mongodb "^6.1.0"
+
 "@types/mime@*":
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.4.tgz#2198ac274de6017b44d941e00261d5bc6a0e0a45"
@@ -4530,11 +7881,38 @@
   resolved "https://registry.yarnpkg.com/@types/mixpanel-browser/-/mixpanel-browser-2.49.0.tgz#ad92ecc36fad63b9c0aed80b6283d86dbf52e49e"
   integrity sha512-StmgUnS58d44DmIAEX9Kk8qwisAYCl6E2qulIjYyHXUPuJCPOuyUMTTKBp+aU2F2do+kxAzCxiBtsB4fnBT9Fg==
 
+"@types/mocha@>=9.1.0":
+  version "10.0.6"
+  resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.6.tgz#818551d39113081048bdddbef96701b4e8bb9d1b"
+  integrity sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==
+
 "@types/ms@*":
   version "0.7.34"
   resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433"
   integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==
 
+"@types/multer@^1.4.11":
+  version "1.4.11"
+  resolved "https://registry.yarnpkg.com/@types/multer/-/multer-1.4.11.tgz#c70792670513b4af1159a2b60bf48cc932af55c5"
+  integrity sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==
+  dependencies:
+    "@types/express" "*"
+
+"@types/node-fetch@^2.5.7":
+  version "2.6.11"
+  resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.11.tgz#9b39b78665dae0e82a08f02f4967d62c66f95d24"
+  integrity sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==
+  dependencies:
+    "@types/node" "*"
+    form-data "^4.0.0"
+
+"@types/node-forge@^1.3.0":
+  version "1.3.11"
+  resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da"
+  integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==
+  dependencies:
+    "@types/node" "*"
+
 "@types/node@*":
   version "20.11.20"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.20.tgz#f0a2aee575215149a62784210ad88b3a34843659"
@@ -4542,10 +7920,12 @@
   dependencies:
     undici-types "~5.26.4"
 
-"@types/node@18.16.9":
-  version "18.16.9"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.9.tgz#e79416d778a8714597342bb87efb5a6e914f7a73"
-  integrity sha512-IeB32oIV4oGArLrd7znD2rkHQ6EDCM+2Sr76dJnrHwv9OHBTTM6nuDLK9bmikXzPa0ZlWMWtRGo/Uw4mrzQedA==
+"@types/node@>=12.12.47":
+  version "20.12.12"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.12.tgz#7cbecdf902085cec634fdb362172dfe12b8f2050"
+  integrity sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==
+  dependencies:
+    undici-types "~5.26.4"
 
 "@types/node@>=13.7.0":
   version "20.11.25"
@@ -4554,16 +7934,38 @@
   dependencies:
     undici-types "~5.26.4"
 
+"@types/node@^10.0.3":
+  version "10.17.60"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b"
+  integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==
+
 "@types/node@^12.12.6":
   version "12.20.55"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240"
   integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==
 
+"@types/node@^8.0.0":
+  version "8.10.66"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.66.tgz#dd035d409df322acc83dff62a602f12a5783bbb3"
+  integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==
+
+"@types/node@~18.16.9":
+  version "18.16.20"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.20.tgz#b27be1ceb267bfb47d8bac024ff6379998f62207"
+  integrity sha512-nL54VfDjThdP2UXJXZao5wp76CDiDw4zSRO8d4Tk7UgDqNKGKVEQB0/t3ti63NS+YNNkIQDvwEAF04BO+WYu7Q==
+
 "@types/normalize-package-data@^2.4.0":
   version "2.4.4"
   resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901"
   integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==
 
+"@types/oidc-provider@^7.14.0":
+  version "7.14.0"
+  resolved "https://registry.yarnpkg.com/@types/oidc-provider/-/oidc-provider-7.14.0.tgz#5ca627e0b748f2a1a78a2aabbba7d57ce4c46f8e"
+  integrity sha512-zIoedB25LuuiNb0tqRQYI3BzdHXVCsZrCHm38apiLe1p6TmbZA7dCSv8rH3AR8xyBk7eNiE+iIBDEHlBx4UzPA==
+  dependencies:
+    "@types/koa" "*"
+
 "@types/parse-json@^4.0.0":
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239"
@@ -4576,6 +7978,11 @@
   dependencies:
     "@types/node" "*"
 
+"@types/prettier@^2.1.1":
+  version "2.7.3"
+  resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f"
+  integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==
+
 "@types/q@^1.5.1":
   version "1.5.8"
   resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.8.tgz#95f6c6a08f2ad868ba230ead1d2d7f7be3db3837"
@@ -4586,11 +7993,24 @@
   resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.11.tgz#208d8a30bc507bd82e03ada29e4732ea46a6bbda"
   integrity sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==
 
+"@types/qs@^6.2.31":
+  version "6.9.15"
+  resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce"
+  integrity sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==
+
 "@types/range-parser@*":
   version "1.2.7"
   resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb"
   integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==
 
+"@types/readable-stream@^2.3.13":
+  version "2.3.15"
+  resolved "https://registry.yarnpkg.com/@types/readable-stream/-/readable-stream-2.3.15.tgz#3d79c9ceb1b6a57d5f6e6976f489b9b5384321ae"
+  integrity sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==
+  dependencies:
+    "@types/node" "*"
+    safe-buffer "~5.1.1"
+
 "@types/responselike@^1.0.0":
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.3.tgz#cc29706f0a397cfe6df89debfe4bf5cea159db50"
@@ -4598,6 +8018,11 @@
   dependencies:
     "@types/node" "*"
 
+"@types/retry@0.12.0":
+  version "0.12.0"
+  resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
+  integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==
+
 "@types/secp256k1@^4.0.1", "@types/secp256k1@^4.0.4":
   version "4.0.6"
   resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.6.tgz#d60ba2349a51c2cbc5e816dcd831a42029d376bf"
@@ -4605,11 +8030,6 @@
   dependencies:
     "@types/node" "*"
 
-"@types/semver@^7.5.0":
-  version "7.5.8"
-  resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e"
-  integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==
-
 "@types/send@*":
   version "0.17.4"
   resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a"
@@ -4618,6 +8038,13 @@
     "@types/mime" "^1"
     "@types/node" "*"
 
+"@types/serve-index@^1.9.1":
+  version "1.9.4"
+  resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.4.tgz#e6ae13d5053cb06ed36392110b4f9a49ac4ec898"
+  integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==
+  dependencies:
+    "@types/express" "*"
+
 "@types/serve-static@*":
   version "1.15.5"
   resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.5.tgz#15e67500ec40789a1e8c9defc2d32a896f05b033"
@@ -4627,6 +8054,15 @@
     "@types/mime" "*"
     "@types/node" "*"
 
+"@types/serve-static@^1.13.10":
+  version "1.15.7"
+  resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714"
+  integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==
+  dependencies:
+    "@types/http-errors" "*"
+    "@types/node" "*"
+    "@types/send" "*"
+
 "@types/sinonjs__fake-timers@8.1.1":
   version "8.1.1"
   resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3"
@@ -4637,6 +8073,13 @@
   resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.8.tgz#518609aefb797da19bf222feb199e8f653ff7627"
   integrity sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==
 
+"@types/sockjs@^0.3.33":
+  version "0.3.36"
+  resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535"
+  integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==
+  dependencies:
+    "@types/node" "*"
+
 "@types/source-list-map@*":
   version "0.1.6"
   resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.6.tgz#164e169dd061795b50b83c19e4d3be09f8d3a454"
@@ -4657,6 +8100,23 @@
   resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1"
   integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==
 
+"@types/superagent@^8.1.0":
+  version "8.1.7"
+  resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-8.1.7.tgz#1153819ed4db34427409a1cc58f3e2f13eeec862"
+  integrity sha512-NmIsd0Yj4DDhftfWvvAku482PZum4DBW7U51OvS8gvOkDDY0WT1jsVyDV3hK+vplrsYw8oDwi9QxOM7U68iwww==
+  dependencies:
+    "@types/cookiejar" "^2.1.5"
+    "@types/methods" "^1.1.4"
+    "@types/node" "*"
+
+"@types/supertest@^6.0.2":
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-6.0.2.tgz#2af1c466456aaf82c7c6106c6b5cbd73a5e86588"
+  integrity sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==
+  dependencies:
+    "@types/methods" "^1.1.4"
+    "@types/superagent" "^8.1.0"
+
 "@types/tapable@^1":
   version "1.0.12"
   resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.12.tgz#bc2cab12e87978eee89fb21576b670350d6d86ab"
@@ -4667,6 +8127,11 @@
   resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304"
   integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==
 
+"@types/triple-beam@^1.3.2":
+  version "1.3.5"
+  resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.5.tgz#74fef9ffbaa198eb8b588be029f38b00299caa2c"
+  integrity sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==
+
 "@types/trusted-types@^2.0.2":
   version "2.0.7"
   resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11"
@@ -4684,11 +8149,21 @@
   resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba"
   integrity sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==
 
+"@types/validator@^13.11.8":
+  version "13.11.10"
+  resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.11.10.tgz#feb364018cdd1f3d970a9e8c7f1c314c0a264fff"
+  integrity sha512-e2PNXoXLr6Z+dbfx5zSh9TRlXJrELycxiaXznp4S5+D2M3b9bqJEitNHA5923jhnB2zzFiZHa2f0SI1HoIahpg==
+
 "@types/web-bluetooth@^0.0.20":
   version "0.0.20"
   resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz#f066abfcd1cbe66267cdbbf0de010d8a41b41597"
   integrity sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==
 
+"@types/webidl-conversions@*":
+  version "7.0.3"
+  resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz#1306dbfa53768bcbcfc95a1c8cde367975581859"
+  integrity sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==
+
 "@types/webpack-dev-server@^3.11.0":
   version "3.11.6"
   resolved "https://registry.yarnpkg.com/@types/webpack-dev-server/-/webpack-dev-server-3.11.6.tgz#d8888cfd2f0630203e13d3ed7833a4d11b8a34dc"
@@ -4726,6 +8201,28 @@
     anymatch "^3.0.0"
     source-map "^0.6.0"
 
+"@types/whatwg-url@^11.0.2":
+  version "11.0.4"
+  resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-11.0.4.tgz#ffed0dc8d89d91f62e3f368fcbda222a487c4f63"
+  integrity sha512-lXCmTWSHJvf0TRSO58nm978b8HJ/EdsSsEKLd3ODHFjo+3VGAyyTp4v50nWvwtzBxSMQrVOK7tcuN0zGPLICMw==
+  dependencies:
+    "@types/webidl-conversions" "*"
+
+"@types/whatwg-url@^8.2.1":
+  version "8.2.2"
+  resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-8.2.2.tgz#749d5b3873e845897ada99be4448041d4cc39e63"
+  integrity sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==
+  dependencies:
+    "@types/node" "*"
+    "@types/webidl-conversions" "*"
+
+"@types/ws@^8.5.10", "@types/ws@^8.5.5":
+  version "8.5.10"
+  resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787"
+  integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==
+  dependencies:
+    "@types/node" "*"
+
 "@types/yargs-parser@*":
   version "21.0.3"
   resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15"
@@ -4745,91 +8242,91 @@
   dependencies:
     "@types/node" "*"
 
-"@typescript-eslint/eslint-plugin@6.21.0":
-  version "6.21.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz#30830c1ca81fd5f3c2714e524c4303e0194f9cd3"
-  integrity sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==
+"@typescript-eslint/eslint-plugin@7.9.0":
+  version "7.9.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.9.0.tgz#093b96fc4e342226e65d5f18f9c87081e0b04a31"
+  integrity sha512-6e+X0X3sFe/G/54aC3jt0txuMTURqLyekmEHViqyA2VnxhLMpvA6nqmcjIy+Cr9tLDHPssA74BP5Mx9HQIxBEA==
   dependencies:
-    "@eslint-community/regexpp" "^4.5.1"
-    "@typescript-eslint/scope-manager" "6.21.0"
-    "@typescript-eslint/type-utils" "6.21.0"
-    "@typescript-eslint/utils" "6.21.0"
-    "@typescript-eslint/visitor-keys" "6.21.0"
-    debug "^4.3.4"
+    "@eslint-community/regexpp" "^4.10.0"
+    "@typescript-eslint/scope-manager" "7.9.0"
+    "@typescript-eslint/type-utils" "7.9.0"
+    "@typescript-eslint/utils" "7.9.0"
+    "@typescript-eslint/visitor-keys" "7.9.0"
     graphemer "^1.4.0"
-    ignore "^5.2.4"
+    ignore "^5.3.1"
     natural-compare "^1.4.0"
-    semver "^7.5.4"
-    ts-api-utils "^1.0.1"
+    ts-api-utils "^1.3.0"
 
-"@typescript-eslint/parser@6.21.0":
-  version "6.21.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b"
-  integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==
+"@typescript-eslint/parser@7.9.0":
+  version "7.9.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.9.0.tgz#fb3ba01b75e0e65cb78037a360961b00301f6c70"
+  integrity sha512-qHMJfkL5qvgQB2aLvhUSXxbK7OLnDkwPzFalg458pxQgfxKDfT1ZDbHQM/I6mDIf/svlMkj21kzKuQ2ixJlatQ==
   dependencies:
-    "@typescript-eslint/scope-manager" "6.21.0"
-    "@typescript-eslint/types" "6.21.0"
-    "@typescript-eslint/typescript-estree" "6.21.0"
-    "@typescript-eslint/visitor-keys" "6.21.0"
+    "@typescript-eslint/scope-manager" "7.9.0"
+    "@typescript-eslint/types" "7.9.0"
+    "@typescript-eslint/typescript-estree" "7.9.0"
+    "@typescript-eslint/visitor-keys" "7.9.0"
     debug "^4.3.4"
 
-"@typescript-eslint/scope-manager@6.21.0":
-  version "6.21.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz#ea8a9bfc8f1504a6ac5d59a6df308d3a0630a2b1"
-  integrity sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==
+"@typescript-eslint/scope-manager@7.9.0":
+  version "7.9.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.9.0.tgz#1dd3e63a4411db356a9d040e75864851b5f2619b"
+  integrity sha512-ZwPK4DeCDxr3GJltRz5iZejPFAAr4Wk3+2WIBaj1L5PYK5RgxExu/Y68FFVclN0y6GGwH8q+KgKRCvaTmFBbgQ==
   dependencies:
-    "@typescript-eslint/types" "6.21.0"
-    "@typescript-eslint/visitor-keys" "6.21.0"
+    "@typescript-eslint/types" "7.9.0"
+    "@typescript-eslint/visitor-keys" "7.9.0"
 
-"@typescript-eslint/type-utils@6.21.0", "@typescript-eslint/type-utils@^6.13.2":
-  version "6.21.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz#6473281cfed4dacabe8004e8521cee0bd9d4c01e"
-  integrity sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==
+"@typescript-eslint/type-utils@7.9.0", "@typescript-eslint/type-utils@^7.3.0":
+  version "7.9.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.9.0.tgz#f523262e1b66ca65540b7a65a1222db52e0a90c9"
+  integrity sha512-6Qy8dfut0PFrFRAZsGzuLoM4hre4gjzWJB6sUvdunCYZsYemTkzZNwF1rnGea326PHPT3zn5Lmg32M/xfJfByA==
   dependencies:
-    "@typescript-eslint/typescript-estree" "6.21.0"
-    "@typescript-eslint/utils" "6.21.0"
+    "@typescript-eslint/typescript-estree" "7.9.0"
+    "@typescript-eslint/utils" "7.9.0"
     debug "^4.3.4"
-    ts-api-utils "^1.0.1"
+    ts-api-utils "^1.3.0"
 
-"@typescript-eslint/types@6.21.0":
-  version "6.21.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d"
-  integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==
+"@typescript-eslint/types@7.9.0":
+  version "7.9.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.9.0.tgz#b58e485e4bfba055659c7e683ad4f5f0821ae2ec"
+  integrity sha512-oZQD9HEWQanl9UfsbGVcZ2cGaR0YT5476xfWE0oE5kQa2sNK2frxOlkeacLOTh9po4AlUT5rtkGyYM5kew0z5w==
 
-"@typescript-eslint/typescript-estree@6.21.0":
-  version "6.21.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz#c47ae7901db3b8bddc3ecd73daff2d0895688c46"
-  integrity sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==
+"@typescript-eslint/typescript-estree@7.9.0":
+  version "7.9.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.9.0.tgz#3395e27656060dc313a6b406c3a298b729685e07"
+  integrity sha512-zBCMCkrb2YjpKV3LA0ZJubtKCDxLttxfdGmwZvTqqWevUPN0FZvSI26FalGFFUZU/9YQK/A4xcQF9o/VVaCKAg==
   dependencies:
-    "@typescript-eslint/types" "6.21.0"
-    "@typescript-eslint/visitor-keys" "6.21.0"
+    "@typescript-eslint/types" "7.9.0"
+    "@typescript-eslint/visitor-keys" "7.9.0"
     debug "^4.3.4"
     globby "^11.1.0"
     is-glob "^4.0.3"
-    minimatch "9.0.3"
-    semver "^7.5.4"
-    ts-api-utils "^1.0.1"
+    minimatch "^9.0.4"
+    semver "^7.6.0"
+    ts-api-utils "^1.3.0"
 
-"@typescript-eslint/utils@6.21.0", "@typescript-eslint/utils@^6.13.2":
-  version "6.21.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.21.0.tgz#4714e7a6b39e773c1c8e97ec587f520840cd8134"
-  integrity sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==
+"@typescript-eslint/utils@7.9.0", "@typescript-eslint/utils@^7.3.0":
+  version "7.9.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.9.0.tgz#1b96a34eefdca1c820cb1bbc2751d848b4540899"
+  integrity sha512-5KVRQCzZajmT4Ep+NEgjXCvjuypVvYHUW7RHlXzNPuak2oWpVoD1jf5xCP0dPAuNIchjC7uQyvbdaSTFaLqSdA==
   dependencies:
     "@eslint-community/eslint-utils" "^4.4.0"
-    "@types/json-schema" "^7.0.12"
-    "@types/semver" "^7.5.0"
-    "@typescript-eslint/scope-manager" "6.21.0"
-    "@typescript-eslint/types" "6.21.0"
-    "@typescript-eslint/typescript-estree" "6.21.0"
-    semver "^7.5.4"
+    "@typescript-eslint/scope-manager" "7.9.0"
+    "@typescript-eslint/types" "7.9.0"
+    "@typescript-eslint/typescript-estree" "7.9.0"
 
-"@typescript-eslint/visitor-keys@6.21.0":
-  version "6.21.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz#87a99d077aa507e20e238b11d56cc26ade45fe47"
-  integrity sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==
+"@typescript-eslint/visitor-keys@7.9.0":
+  version "7.9.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.9.0.tgz#82162656e339c3def02895f5c8546f6888d9b9ea"
+  integrity sha512-iESPx2TNLDNGQLyjKhUvIKprlP49XNEK+MvIf9nIO7ZZaZdbnfWKHnXAgufpxqfA0YryH8XToi4+CjBgVnFTSQ==
   dependencies:
-    "@typescript-eslint/types" "6.21.0"
-    eslint-visitor-keys "^3.4.1"
+    "@typescript-eslint/types" "7.9.0"
+    eslint-visitor-keys "^3.4.3"
+
+"@tyriar/fibonacci-heap@^2.0.7":
+  version "2.0.9"
+  resolved "https://registry.yarnpkg.com/@tyriar/fibonacci-heap/-/fibonacci-heap-2.0.9.tgz#df3dcbdb1b9182168601f6318366157ee16666e9"
+  integrity sha512-bYuSNomfn4hu2tPiDN+JZtnzCpSpbJ/PNeulmocDy3xN2X5OkJL65zo6rPZp65cPPhLF9vfT/dgE+RtFRCSxOA==
 
 "@ungap/structured-clone@^1.2.0":
   version "1.2.0"
@@ -4841,10 +8338,10 @@
   resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz#508d6a0f2440f86945835d903fcc0d95d1bb8a37"
   integrity sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==
 
-"@vitest/coverage-v8@^1.0.4":
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-1.4.0.tgz#78ba9e182ff4cd1eba79c45cfafd2edc4c2941ec"
-  integrity sha512-4hDGyH1SvKpgZnIByr9LhGgCEuF9DKM34IBLCC/fVfy24Z3+PZ+Ii9hsVBsHvY1umM1aGPEjceRkzxCfcQ10wg==
+"@vitest/coverage-v8@1.6.0":
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-1.6.0.tgz#2f54ccf4c2d9f23a71294aba7f95b3d2e27d14e7"
+  integrity sha512-KvapcbMY/8GYIG0rlwwOKCVNRc0OL20rrhFkg/CHNzncV03TE2XWvO5w9uZYoxNiMEBacAJt3unSOiZ7svePew==
   dependencies:
     "@ampproject/remapping" "^2.2.1"
     "@bcoe/v8-coverage" "^0.2.3"
@@ -4859,48 +8356,47 @@
     std-env "^3.5.0"
     strip-literal "^2.0.0"
     test-exclude "^6.0.0"
-    v8-to-istanbul "^9.2.0"
 
-"@vitest/expect@1.4.0":
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-1.4.0.tgz#d64e17838a20007fecd252397f9b96a1ca81bfb0"
-  integrity sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==
+"@vitest/expect@1.6.0":
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-1.6.0.tgz#0b3ba0914f738508464983f4d811bc122b51fb30"
+  integrity sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==
   dependencies:
-    "@vitest/spy" "1.4.0"
-    "@vitest/utils" "1.4.0"
+    "@vitest/spy" "1.6.0"
+    "@vitest/utils" "1.6.0"
     chai "^4.3.10"
 
-"@vitest/runner@1.4.0":
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-1.4.0.tgz#907c2d17ad5975b70882c25ab7a13b73e5a28da9"
-  integrity sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==
+"@vitest/runner@1.6.0":
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-1.6.0.tgz#a6de49a96cb33b0e3ba0d9064a3e8d6ce2f08825"
+  integrity sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==
   dependencies:
-    "@vitest/utils" "1.4.0"
+    "@vitest/utils" "1.6.0"
     p-limit "^5.0.0"
     pathe "^1.1.1"
 
-"@vitest/snapshot@1.4.0":
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-1.4.0.tgz#2945b3fb53767a3f4f421919e93edfef2935b8bd"
-  integrity sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==
+"@vitest/snapshot@1.6.0":
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-1.6.0.tgz#deb7e4498a5299c1198136f56e6e0f692e6af470"
+  integrity sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==
   dependencies:
     magic-string "^0.30.5"
     pathe "^1.1.1"
     pretty-format "^29.7.0"
 
-"@vitest/spy@1.4.0":
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-1.4.0.tgz#cf953c93ae54885e801cbe6b408a547ae613f26c"
-  integrity sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==
+"@vitest/spy@1.6.0":
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-1.6.0.tgz#362cbd42ccdb03f1613798fde99799649516906d"
+  integrity sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==
   dependencies:
     tinyspy "^2.2.0"
 
-"@vitest/ui@^1.3.1":
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/@vitest/ui/-/ui-1.4.0.tgz#01d34162bcee14c7e45cbf3e9dee4a22b52d778a"
-  integrity sha512-XC6CMhN1gzYcGbpn6/Oanj4Au2EXwQEX6vpcOeLlZv8dy7g11Ukx8zwtYQbwxs9duK2s9j2o5rbQiCP5DPAcmw==
+"@vitest/ui@1.6.0":
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/@vitest/ui/-/ui-1.6.0.tgz#ffcc97ebcceca7fec840c29ab68632d0cd01db93"
+  integrity sha512-k3Lyo+ONLOgylctiGovRKy7V4+dIN2yxstX3eY5cWFXH6WP+ooVX79YSyi0GagdTQzLmT43BF27T0s6dOIPBXA==
   dependencies:
-    "@vitest/utils" "1.4.0"
+    "@vitest/utils" "1.6.0"
     fast-glob "^3.3.2"
     fflate "^0.8.1"
     flatted "^3.2.9"
@@ -4908,16 +8404,21 @@
     picocolors "^1.0.0"
     sirv "^2.0.4"
 
-"@vitest/utils@1.4.0":
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-1.4.0.tgz#ea6297e0d329f9ff0a106f4e1f6daf3ff6aad3f0"
-  integrity sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==
+"@vitest/utils@1.6.0":
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-1.6.0.tgz#5c5675ca7d6f546a7b4337de9ae882e6c57896a1"
+  integrity sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==
   dependencies:
     diff-sequences "^29.6.3"
     estree-walker "^3.0.3"
     loupe "^2.3.7"
     pretty-format "^29.7.0"
 
+"@vladfrangu/async_event_emitter@^2.2.4":
+  version "2.2.4"
+  resolved "https://registry.yarnpkg.com/@vladfrangu/async_event_emitter/-/async_event_emitter-2.2.4.tgz#d3537432c6db6444680a596271dff8ea407343b3"
+  integrity sha512-ButUPz9E9cXMLgvAW8aLAKKJJsPu1dY1/l/E8xzLFuysowXygs6GBcyunK9rnGC4zTsnIc2mQo71rGw9U+Ykug==
+
 "@vue/babel-helper-vue-jsx-merge-props@^1.4.0":
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.4.0.tgz#8d53a1e21347db8edbe54d339902583176de09f2"
@@ -5726,6 +9227,14 @@
   dependencies:
     zod "3.22.4"
 
+"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1":
+  version "1.12.1"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb"
+  integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==
+  dependencies:
+    "@webassemblyjs/helper-numbers" "1.11.6"
+    "@webassemblyjs/helper-wasm-bytecode" "1.11.6"
+
 "@webassemblyjs/ast@1.9.0":
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964"
@@ -5735,16 +9244,31 @@
     "@webassemblyjs/helper-wasm-bytecode" "1.9.0"
     "@webassemblyjs/wast-parser" "1.9.0"
 
+"@webassemblyjs/floating-point-hex-parser@1.11.6":
+  version "1.11.6"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431"
+  integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==
+
 "@webassemblyjs/floating-point-hex-parser@1.9.0":
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4"
   integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==
 
+"@webassemblyjs/helper-api-error@1.11.6":
+  version "1.11.6"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768"
+  integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==
+
 "@webassemblyjs/helper-api-error@1.9.0":
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2"
   integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==
 
+"@webassemblyjs/helper-buffer@1.12.1":
+  version "1.12.1"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz#6df20d272ea5439bf20ab3492b7fb70e9bfcb3f6"
+  integrity sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==
+
 "@webassemblyjs/helper-buffer@1.9.0":
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00"
@@ -5769,11 +9293,35 @@
   dependencies:
     "@webassemblyjs/ast" "1.9.0"
 
+"@webassemblyjs/helper-numbers@1.11.6":
+  version "1.11.6"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5"
+  integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==
+  dependencies:
+    "@webassemblyjs/floating-point-hex-parser" "1.11.6"
+    "@webassemblyjs/helper-api-error" "1.11.6"
+    "@xtuc/long" "4.2.2"
+
+"@webassemblyjs/helper-wasm-bytecode@1.11.6":
+  version "1.11.6"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9"
+  integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==
+
 "@webassemblyjs/helper-wasm-bytecode@1.9.0":
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790"
   integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==
 
+"@webassemblyjs/helper-wasm-section@1.12.1":
+  version "1.12.1"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz#3da623233ae1a60409b509a52ade9bc22a37f7bf"
+  integrity sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==
+  dependencies:
+    "@webassemblyjs/ast" "1.12.1"
+    "@webassemblyjs/helper-buffer" "1.12.1"
+    "@webassemblyjs/helper-wasm-bytecode" "1.11.6"
+    "@webassemblyjs/wasm-gen" "1.12.1"
+
 "@webassemblyjs/helper-wasm-section@1.9.0":
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346"
@@ -5784,6 +9332,13 @@
     "@webassemblyjs/helper-wasm-bytecode" "1.9.0"
     "@webassemblyjs/wasm-gen" "1.9.0"
 
+"@webassemblyjs/ieee754@1.11.6":
+  version "1.11.6"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a"
+  integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==
+  dependencies:
+    "@xtuc/ieee754" "^1.2.0"
+
 "@webassemblyjs/ieee754@1.9.0":
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4"
@@ -5791,6 +9346,13 @@
   dependencies:
     "@xtuc/ieee754" "^1.2.0"
 
+"@webassemblyjs/leb128@1.11.6":
+  version "1.11.6"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7"
+  integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==
+  dependencies:
+    "@xtuc/long" "4.2.2"
+
 "@webassemblyjs/leb128@1.9.0":
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95"
@@ -5798,6 +9360,11 @@
   dependencies:
     "@xtuc/long" "4.2.2"
 
+"@webassemblyjs/utf8@1.11.6":
+  version "1.11.6"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a"
+  integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==
+
 "@webassemblyjs/utf8@1.9.0":
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab"
@@ -5817,6 +9384,31 @@
     "@webassemblyjs/wasm-parser" "1.9.0"
     "@webassemblyjs/wast-printer" "1.9.0"
 
+"@webassemblyjs/wasm-edit@^1.12.1":
+  version "1.12.1"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz#9f9f3ff52a14c980939be0ef9d5df9ebc678ae3b"
+  integrity sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==
+  dependencies:
+    "@webassemblyjs/ast" "1.12.1"
+    "@webassemblyjs/helper-buffer" "1.12.1"
+    "@webassemblyjs/helper-wasm-bytecode" "1.11.6"
+    "@webassemblyjs/helper-wasm-section" "1.12.1"
+    "@webassemblyjs/wasm-gen" "1.12.1"
+    "@webassemblyjs/wasm-opt" "1.12.1"
+    "@webassemblyjs/wasm-parser" "1.12.1"
+    "@webassemblyjs/wast-printer" "1.12.1"
+
+"@webassemblyjs/wasm-gen@1.12.1":
+  version "1.12.1"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz#a6520601da1b5700448273666a71ad0a45d78547"
+  integrity sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==
+  dependencies:
+    "@webassemblyjs/ast" "1.12.1"
+    "@webassemblyjs/helper-wasm-bytecode" "1.11.6"
+    "@webassemblyjs/ieee754" "1.11.6"
+    "@webassemblyjs/leb128" "1.11.6"
+    "@webassemblyjs/utf8" "1.11.6"
+
 "@webassemblyjs/wasm-gen@1.9.0":
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c"
@@ -5828,6 +9420,16 @@
     "@webassemblyjs/leb128" "1.9.0"
     "@webassemblyjs/utf8" "1.9.0"
 
+"@webassemblyjs/wasm-opt@1.12.1":
+  version "1.12.1"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz#9e6e81475dfcfb62dab574ac2dda38226c232bc5"
+  integrity sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==
+  dependencies:
+    "@webassemblyjs/ast" "1.12.1"
+    "@webassemblyjs/helper-buffer" "1.12.1"
+    "@webassemblyjs/wasm-gen" "1.12.1"
+    "@webassemblyjs/wasm-parser" "1.12.1"
+
 "@webassemblyjs/wasm-opt@1.9.0":
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61"
@@ -5838,6 +9440,18 @@
     "@webassemblyjs/wasm-gen" "1.9.0"
     "@webassemblyjs/wasm-parser" "1.9.0"
 
+"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.12.1":
+  version "1.12.1"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz#c47acb90e6f083391e3fa61d113650eea1e95937"
+  integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==
+  dependencies:
+    "@webassemblyjs/ast" "1.12.1"
+    "@webassemblyjs/helper-api-error" "1.11.6"
+    "@webassemblyjs/helper-wasm-bytecode" "1.11.6"
+    "@webassemblyjs/ieee754" "1.11.6"
+    "@webassemblyjs/leb128" "1.11.6"
+    "@webassemblyjs/utf8" "1.11.6"
+
 "@webassemblyjs/wasm-parser@1.9.0":
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e"
@@ -5862,6 +9476,14 @@
     "@webassemblyjs/helper-fsm" "1.9.0"
     "@xtuc/long" "4.2.2"
 
+"@webassemblyjs/wast-printer@1.12.1":
+  version "1.12.1"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz#bcecf661d7d1abdaf989d8341a4833e33e2b31ac"
+  integrity sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==
+  dependencies:
+    "@webassemblyjs/ast" "1.12.1"
+    "@xtuc/long" "4.2.2"
+
 "@webassemblyjs/wast-printer@1.9.0":
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899"
@@ -5901,11 +9523,21 @@
   dependencies:
     argparse "^2.0.1"
 
-abab@^2.0.6:
+abab@^2.0.5, abab@^2.0.6:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
   integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==
 
+abbrev@1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
+  integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
+
+abbrev@1.0.x:
+  version "1.0.9"
+  resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
+  integrity sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==
+
 abbrev@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf"
@@ -5921,12 +9553,32 @@ abitype@1.0.0:
   resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.0.tgz#237176dace81d90d018bebf3a45cb42f2a2d9e97"
   integrity sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ==
 
+abort-controller@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
+  integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
+  dependencies:
+    event-target-shim "^5.0.0"
+
 abortcontroller-polyfill@^1.7.5:
   version "1.7.5"
   resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz#6738495f4e901fbb57b6c0611d0c75f76c485bed"
   integrity sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==
 
-accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8:
+abstract-level@^1.0.0, abstract-level@^1.0.2, abstract-level@^1.0.3, abstract-level@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/abstract-level/-/abstract-level-1.0.4.tgz#3ad8d684c51cc9cbc9cf9612a7100b716c414b57"
+  integrity sha512-eUP/6pbXBkMbXFdx4IH2fVgvB7M0JvR7/lIL33zcs0IBcwjdzSSl31TOJsaCzmKSSDF9h8QYSOJux4Nd4YJqFg==
+  dependencies:
+    buffer "^6.0.3"
+    catering "^2.1.0"
+    is-buffer "^2.0.5"
+    level-supports "^4.0.0"
+    level-transcoder "^1.0.1"
+    module-error "^1.0.1"
+    queue-microtask "^1.2.3"
+
+accepts@^1.3.5, accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8:
   version "1.3.8"
   resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
   integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
@@ -5942,6 +9594,16 @@ acorn-globals@^7.0.0:
     acorn "^8.1.0"
     acorn-walk "^8.0.2"
 
+acorn-import-assertions@^1.9.0:
+  version "1.9.0"
+  resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac"
+  integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==
+
+acorn-import-attributes@^1.9.5:
+  version "1.9.5"
+  resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef"
+  integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==
+
 acorn-jsx@^5.2.0, acorn-jsx@^5.3.2:
   version "5.3.2"
   resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
@@ -5967,7 +9629,7 @@ acorn@^7.1.1, acorn@^7.4.0:
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
   integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
 
-acorn@^8.1.0, acorn@^8.11.3, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.8.1, acorn@^8.9.0:
+acorn@^8.1.0, acorn@^8.11.3, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0:
   version "8.11.3"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a"
   integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==
@@ -5977,6 +9639,11 @@ address@^1.0.1, address@^1.1.2:
   resolved "https://registry.yarnpkg.com/address/-/address-1.2.2.tgz#2b5248dac5485a6390532c6a517fda2e3faac89e"
   integrity sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==
 
+adm-zip@^0.4.16:
+  version "0.4.16"
+  resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.16.tgz#cf4c508fdffab02c269cbc7f471a875f05570365"
+  integrity sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==
+
 aes-js@3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d"
@@ -5989,6 +9656,13 @@ agent-base@6:
   dependencies:
     debug "4"
 
+agent-base@^7.0.2, agent-base@^7.1.0, agent-base@^7.1.1:
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317"
+  integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==
+  dependencies:
+    debug "^4.3.4"
+
 aggregate-error@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
@@ -6002,12 +9676,26 @@ ajv-errors@^1.0.0:
   resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
   integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==
 
+ajv-formats@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520"
+  integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==
+  dependencies:
+    ajv "^8.0.0"
+
 ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2:
   version "3.5.2"
   resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
   integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
 
-ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4:
+ajv-keywords@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16"
+  integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==
+  dependencies:
+    fast-deep-equal "^3.1.3"
+
+ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5:
   version "6.12.6"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
   integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@@ -6017,11 +9705,62 @@ ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4:
     json-schema-traverse "^0.4.1"
     uri-js "^4.2.2"
 
+ajv@^8.0.0, ajv@^8.0.1, ajv@^8.12.0, ajv@^8.9.0:
+  version "8.13.0"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.13.0.tgz#a3939eaec9fb80d217ddf0c3376948c023f28c91"
+  integrity sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==
+  dependencies:
+    fast-deep-equal "^3.1.3"
+    json-schema-traverse "^1.0.0"
+    require-from-string "^2.0.2"
+    uri-js "^4.4.1"
+
+alchemy-sdk@^3.3.1:
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/alchemy-sdk/-/alchemy-sdk-3.3.1.tgz#a0cbd670f95c8a948966d3d4dba242b68723a071"
+  integrity sha512-iH/wIhBsHr18NTV9G9WrNsk/ofBOrhKaxH1vG9IZN3t+sTrB5uKAMMgmKvvJHDnOJ2Fo/bTnYPgUWNqhQxEfCQ==
+  dependencies:
+    "@ethersproject/abi" "^5.7.0"
+    "@ethersproject/abstract-provider" "^5.7.0"
+    "@ethersproject/bignumber" "^5.7.0"
+    "@ethersproject/bytes" "^5.7.0"
+    "@ethersproject/contracts" "^5.7.0"
+    "@ethersproject/hash" "^5.7.0"
+    "@ethersproject/networks" "^5.7.0"
+    "@ethersproject/providers" "^5.7.0"
+    "@ethersproject/units" "^5.7.0"
+    "@ethersproject/wallet" "^5.7.0"
+    "@ethersproject/web" "^5.7.0"
+    axios "^1.6.5"
+    sturdy-websocket "^0.2.1"
+    websocket "^1.0.34"
+
 alphanum-sort@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
   integrity sha512-0FcBfdcmaumGPQ0qPn7Q5qTgz/ooXgIyp1rf8ik5bGX8mpE2YHjC0P/eyQvxu1GURYQgq9ozf2mteQ5ZD9YiyQ==
 
+amazon-cognito-identity-js@^6.0.1:
+  version "6.3.12"
+  resolved "https://registry.yarnpkg.com/amazon-cognito-identity-js/-/amazon-cognito-identity-js-6.3.12.tgz#af73df033094ad4c679c19cf6122b90058021619"
+  integrity sha512-s7NKDZgx336cp+oDeUtB2ZzT8jWJp/v2LWuYl+LQtMEODe22RF1IJ4nRiDATp+rp1pTffCZcm44Quw4jx2bqNg==
+  dependencies:
+    "@aws-crypto/sha256-js" "1.2.2"
+    buffer "4.9.2"
+    fast-base64-decode "^1.0.0"
+    isomorphic-unfetch "^3.0.0"
+    js-cookie "^2.2.1"
+
+amdefine@>=0.0.4:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
+  integrity sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==
+
+ansi-colors@4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
+  integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
+
 ansi-colors@^3.0.0:
   version "3.2.4"
   resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
@@ -6039,7 +9778,7 @@ ansi-escapes@^4.2.1, ansi-escapes@^4.3.0:
   dependencies:
     type-fest "^0.21.3"
 
-ansi-html-community@0.0.8:
+ansi-html-community@0.0.8, ansi-html-community@^0.0.8:
   version "0.0.8"
   resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41"
   integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==
@@ -6049,6 +9788,11 @@ ansi-regex@^2.0.0:
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
   integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==
 
+ansi-regex@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1"
+  integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==
+
 ansi-regex@^4.1.0:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed"
@@ -6093,6 +9837,16 @@ ansi-styles@^6.1.0:
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
   integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
 
+antlr4ts@^0.5.0-alpha.4:
+  version "0.5.0-alpha.4"
+  resolved "https://registry.yarnpkg.com/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz#71702865a87478ed0b40c0709f422cf14d51652a"
+  integrity sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==
+
+any-base@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/any-base/-/any-base-1.1.0.tgz#ae101a62bc08a597b4c9ab5b7089d456630549fe"
+  integrity sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==
+
 any-promise@^1.0.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
@@ -6114,6 +9868,16 @@ anymatch@^3.0.0, anymatch@^3.0.3, anymatch@^3.1.3, anymatch@~3.1.2:
     normalize-path "^3.0.0"
     picomatch "^2.0.4"
 
+append-field@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56"
+  integrity sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==
+
+"aproba@^1.0.3 || ^2.0.0":
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc"
+  integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==
+
 aproba@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
@@ -6124,6 +9888,14 @@ arch@^2.1.1, arch@^2.2.0:
   resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11"
   integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==
 
+are-we-there-yet@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c"
+  integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==
+  dependencies:
+    delegates "^1.0.0"
+    readable-stream "^3.6.0"
+
 arg@^4.1.0:
   version "4.1.3"
   resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
@@ -6156,6 +9928,16 @@ arr-union@^3.1.0:
   resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
   integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==
 
+array-back@^3.0.1, array-back@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0"
+  integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==
+
+array-back@^4.0.1, array-back@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e"
+  integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==
+
 array-buffer-byte-length@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f"
@@ -6197,7 +9979,12 @@ array-union@^2.1.0:
   resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
   integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
 
-array-uniq@^1.0.1:
+array-union@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/array-union/-/array-union-3.0.1.tgz#da52630d327f8b88cfbfb57728e2af5cd9b6b975"
+  integrity sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==
+
+array-uniq@1.0.3, array-uniq@^1.0.1:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
   integrity sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==
@@ -6274,6 +10061,11 @@ arraybuffer.prototype.slice@^1.0.3:
     is-array-buffer "^3.0.4"
     is-shared-array-buffer "^1.0.2"
 
+asap@^2.0.0, asap@~2.0.6:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
+  integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==
+
 asn1.js@^5.2.0:
   version "5.4.1"
   resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
@@ -6347,6 +10139,18 @@ async-mutex@^0.2.6:
   dependencies:
     tslib "^2.0.0"
 
+async-retry@^1.3.3:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280"
+  integrity sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==
+  dependencies:
+    retry "0.13.1"
+
+async@1.x:
+  version "1.5.2"
+  resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
+  integrity sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==
+
 async@^2.6.4:
   version "2.6.4"
   resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
@@ -6354,7 +10158,7 @@ async@^2.6.4:
   dependencies:
     lodash "^4.17.14"
 
-async@^3.2.0, async@^3.2.3:
+async@^3.0.0, async@^3.2.0, async@^3.2.3:
   version "3.2.5"
   resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66"
   integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==
@@ -6379,6 +10183,18 @@ atomic-sleep@^1.0.0:
   resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b"
   integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==
 
+autoprefixer@^10.4.9:
+  version "10.4.19"
+  resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.19.tgz#ad25a856e82ee9d7898c59583c1afeb3fa65f89f"
+  integrity sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==
+  dependencies:
+    browserslist "^4.23.0"
+    caniuse-lite "^1.0.30001599"
+    fraction.js "^4.3.7"
+    normalize-range "^0.1.2"
+    picocolors "^1.0.0"
+    postcss-value-parser "^4.2.0"
+
 autoprefixer@^9.8.6:
   version "9.8.8"
   resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.8.tgz#fd4bd4595385fa6f06599de749a4d5f7a474957a"
@@ -6409,6 +10225,18 @@ aws4@^1.8.0:
   resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.12.0.tgz#ce1c9d143389679e253b314241ea9aa5cec980d3"
   integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==
 
+axios-better-stacktrace@^2.1.6:
+  version "2.1.6"
+  resolved "https://registry.yarnpkg.com/axios-better-stacktrace/-/axios-better-stacktrace-2.1.6.tgz#8d6586b101af56fb890e2a4e3e37963229b05b63"
+  integrity sha512-t0oR9MU9H5IHjEA2TggmwYBdeBmvJw5krrKhOipiIjwZEoUpViuuzB+4uZsOholmZnljFUPPKE2wTGxILBmB+Q==
+
+axios@^0.21.1:
+  version "0.21.4"
+  resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
+  integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
+  dependencies:
+    follow-redirects "^1.14.0"
+
 axios@^0.24.0:
   version "0.24.0"
   resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6"
@@ -6416,6 +10244,15 @@ axios@^0.24.0:
   dependencies:
     follow-redirects "^1.14.4"
 
+axios@^1.4.0, axios@^1.5.1, axios@^1.6.2, axios@^1.6.8:
+  version "1.6.8"
+  resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66"
+  integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==
+  dependencies:
+    follow-redirects "^1.15.6"
+    form-data "^4.0.0"
+    proxy-from-env "^1.1.0"
+
 axios@^1.6.0, axios@^1.6.5:
   version "1.6.7"
   resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7"
@@ -6457,6 +10294,14 @@ babel-loader@^8.1.0:
     make-dir "^3.1.0"
     schema-utils "^2.6.5"
 
+babel-loader@^9.1.2:
+  version "9.1.3"
+  resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-9.1.3.tgz#3d0e01b4e69760cc694ee306fe16d358aa1c6f9a"
+  integrity sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==
+  dependencies:
+    find-cache-dir "^4.0.0"
+    schema-utils "^4.0.0"
+
 babel-plugin-const-enum@^1.0.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-const-enum/-/babel-plugin-const-enum-1.2.0.tgz#3d25524106f68f081e187829ba736b251c289861"
@@ -6512,6 +10357,15 @@ babel-plugin-macros@^3.1.0:
     cosmiconfig "^7.0.0"
     resolve "^1.19.0"
 
+babel-plugin-polyfill-corejs2@^0.4.10:
+  version "0.4.11"
+  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz#30320dfe3ffe1a336c15afdcdafd6fd615b25e33"
+  integrity sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==
+  dependencies:
+    "@babel/compat-data" "^7.22.6"
+    "@babel/helper-define-polyfill-provider" "^0.6.2"
+    semver "^6.3.1"
+
 babel-plugin-polyfill-corejs2@^0.4.8:
   version "0.4.8"
   resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.8.tgz#dbcc3c8ca758a290d47c3c6a490d59429b0d2269"
@@ -6521,6 +10375,14 @@ babel-plugin-polyfill-corejs2@^0.4.8:
     "@babel/helper-define-polyfill-provider" "^0.5.0"
     semver "^6.3.1"
 
+babel-plugin-polyfill-corejs3@^0.10.4:
+  version "0.10.4"
+  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz#789ac82405ad664c20476d0233b485281deb9c77"
+  integrity sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==
+  dependencies:
+    "@babel/helper-define-polyfill-provider" "^0.6.1"
+    core-js-compat "^3.36.1"
+
 babel-plugin-polyfill-corejs3@^0.9.0:
   version "0.9.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz#9eea32349d94556c2ad3ab9b82ebb27d4bf04a81"
@@ -6536,6 +10398,13 @@ babel-plugin-polyfill-regenerator@^0.5.5:
   dependencies:
     "@babel/helper-define-polyfill-provider" "^0.5.0"
 
+babel-plugin-polyfill-regenerator@^0.6.1:
+  version "0.6.2"
+  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz#addc47e240edd1da1058ebda03021f382bba785e"
+  integrity sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==
+  dependencies:
+    "@babel/helper-define-polyfill-provider" "^0.6.2"
+
 babel-plugin-transform-typescript-metadata@^0.3.1:
   version "0.3.2"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-typescript-metadata/-/babel-plugin-transform-typescript-metadata-0.3.2.tgz#7a327842d8c36ffe07ee1b5276434e56c297c9b7"
@@ -6581,7 +10450,7 @@ base-x@^3.0.2, base-x@^3.0.8:
   dependencies:
     safe-buffer "^5.0.1"
 
-base64-js@^1.0.2, base64-js@^1.3.1:
+base64-js@^1.0.2, base64-js@^1.3.0, base64-js@^1.3.1:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
   integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
@@ -6599,7 +10468,7 @@ base@^0.11.1:
     mixin-deep "^1.2.0"
     pascalcase "^0.1.1"
 
-basic-auth@^2.0.1:
+basic-auth@^2.0.1, basic-auth@~2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
   integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==
@@ -6618,6 +10487,14 @@ bcrypt-pbkdf@^1.0.0:
   dependencies:
     tweetnacl "^0.14.3"
 
+bcrypt@^5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.1.1.tgz#0f732c6dcb4e12e5b70a25e326a72965879ba6e2"
+  integrity sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==
+  dependencies:
+    "@mapbox/node-pre-gyp" "^1.0.11"
+    node-addon-api "^5.0.0"
+
 bech32@1.1.4:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9"
@@ -6648,7 +10525,12 @@ big.js@^5.2.2:
   resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
   integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
 
-bignumber.js@^9.0.0:
+bigint-crypto-utils@^3.0.23:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/bigint-crypto-utils/-/bigint-crypto-utils-3.3.0.tgz#72ad00ae91062cf07f2b1def9594006c279c1d77"
+  integrity sha512-jOTSb+drvEDxEq6OuUybOAv/xxoh3cuYRUIPyu8sSHQNKM303UQ2R1DAo45o1AkcIXw6fzbaFI1+xGGdaXs2lg==
+
+bignumber.js@^9.0.0, bignumber.js@^9.0.2:
   version "9.1.2"
   resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c"
   integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==
@@ -6699,7 +10581,7 @@ bn.js@4.11.6:
   resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215"
   integrity sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==
 
-bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.6, bn.js@^4.11.9:
+bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.0, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.11.9, bn.js@^4.12.0:
   version "4.12.0"
   resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
   integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
@@ -6727,7 +10609,7 @@ body-parser@1.20.1:
     type-is "~1.6.18"
     unpipe "1.0.0"
 
-body-parser@^1.16.0:
+body-parser@1.20.2, body-parser@^1.16.0:
   version "1.20.2"
   resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
   integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
@@ -6745,6 +10627,14 @@ body-parser@^1.16.0:
     type-is "~1.6.18"
     unpipe "1.0.0"
 
+bonjour-service@^1.0.11:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.2.1.tgz#eb41b3085183df3321da1264719fbada12478d02"
+  integrity sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==
+  dependencies:
+    fast-deep-equal "^3.1.3"
+    multicast-dns "^7.2.5"
+
 bonjour@^3.5.0:
   version "3.5.0"
   resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5"
@@ -6775,6 +10665,11 @@ bootstrap@5.3:
   resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.3.tgz#de35e1a765c897ac940021900fcbb831602bac38"
   integrity sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==
 
+bottleneck@^2.19.5:
+  version "2.19.5"
+  resolved "https://registry.yarnpkg.com/bottleneck/-/bottleneck-2.19.5.tgz#5df0b90f59fd47656ebe63c78a98419205cadd91"
+  integrity sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==
+
 bowser@^2.11.0, bowser@^2.9.0:
   version "2.11.0"
   resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f"
@@ -6823,6 +10718,16 @@ brorand@^1.0.1, brorand@^1.1.0:
   resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
   integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==
 
+browser-level@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/browser-level/-/browser-level-1.0.1.tgz#36e8c3183d0fe1c405239792faaab5f315871011"
+  integrity sha512-XECYKJ+Dbzw0lbydyQuJzwNXtOpbMSq737qxJN11sIRTErOMShvDpbzTlgju7orJKvx4epULolZAuJGLzCmWRQ==
+  dependencies:
+    abstract-level "^1.0.2"
+    catering "^2.1.1"
+    module-error "^1.0.2"
+    run-parallel-limit "^1.1.0"
+
 browser-resolve@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-2.0.0.tgz#99b7304cb392f8d73dba741bb2d7da28c6d7842b"
@@ -6830,6 +10735,11 @@ browser-resolve@^2.0.0:
   dependencies:
     resolve "^1.17.0"
 
+browser-stdout@1.3.1:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
+  integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
+
 browserify-aes@^1.0.0, browserify-aes@^1.0.4, browserify-aes@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48"
@@ -6891,7 +10801,7 @@ browserify-zlib@^0.2.0:
   dependencies:
     pako "~1.0.5"
 
-browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.22.2, browserslist@^4.22.3:
+browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.21.10, browserslist@^4.21.4, browserslist@^4.22.2, browserslist@^4.22.3, browserslist@^4.23.0:
   version "4.23.0"
   resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab"
   integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==
@@ -6908,7 +10818,7 @@ bs-logger@0.x:
   dependencies:
     fast-json-stable-stringify "2.x"
 
-bs58@^4.0.0:
+bs58@^4.0.0, bs58@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a"
   integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==
@@ -6931,11 +10841,28 @@ bser@2.1.1:
   dependencies:
     node-int64 "^0.4.0"
 
+bson@^4.7.2:
+  version "4.7.2"
+  resolved "https://registry.yarnpkg.com/bson/-/bson-4.7.2.tgz#320f4ad0eaf5312dd9b45dc369cc48945e2a5f2e"
+  integrity sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==
+  dependencies:
+    buffer "^5.6.0"
+
+bson@^6.4.0, bson@^6.5.0, bson@^6.7.0:
+  version "6.7.0"
+  resolved "https://registry.yarnpkg.com/bson/-/bson-6.7.0.tgz#51973b132cdc424c8372fda3cb43e3e3e2ae2227"
+  integrity sha512-w2IquM5mYzYZv6rs3uN2DZTOBe2a0zXLj53TGDqwF4l6Sz/XsISrisXOJihArF9+BZ6Cq/GjVht7Sjfmri7ytQ==
+
 buffer-crc32@~0.2.3:
   version "0.2.13"
   resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
   integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
 
+buffer-equal-constant-time@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
+  integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
+
 buffer-from@^1.0.0:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
@@ -6961,15 +10888,7 @@ buffer-xor@^1.0.3:
   resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
   integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==
 
-buffer@6.0.3, buffer@^6.0.3:
-  version "6.0.3"
-  resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
-  integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
-  dependencies:
-    base64-js "^1.3.1"
-    ieee754 "^1.2.1"
-
-buffer@^4.3.0:
+buffer@4.9.2, buffer@^4.3.0:
   version "4.9.2"
   resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8"
   integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==
@@ -6978,6 +10897,14 @@ buffer@^4.3.0:
     ieee754 "^1.1.4"
     isarray "^1.0.0"
 
+buffer@6.0.3, buffer@^6.0.3:
+  version "6.0.3"
+  resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
+  integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
+  dependencies:
+    base64-js "^1.3.1"
+    ieee754 "^1.2.1"
+
 buffer@^5.0.5, buffer@^5.5.0, buffer@^5.6.0, buffer@^5.7.1:
   version "5.7.1"
   resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
@@ -7010,6 +10937,13 @@ builtins@^5.0.0:
   dependencies:
     semver "^7.0.0"
 
+busboy@^1.0.0:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
+  integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==
+  dependencies:
+    streamsearch "^1.1.0"
+
 bytes@3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
@@ -7064,6 +10998,24 @@ cacache@^12.0.2, cacache@^12.0.3:
     unique-filename "^1.1.1"
     y18n "^4.0.0"
 
+cacache@^18.0.0:
+  version "18.0.3"
+  resolved "https://registry.yarnpkg.com/cacache/-/cacache-18.0.3.tgz#864e2c18414e1e141ae8763f31e46c2cb96d1b21"
+  integrity sha512-qXCd4rh6I07cnDqh8V48/94Tc/WSfj+o3Gn6NZ0aZovS255bUx8O13uKxRFd2eWG0xgsco7+YItQNPaa5E85hg==
+  dependencies:
+    "@npmcli/fs" "^3.1.0"
+    fs-minipass "^3.0.0"
+    glob "^10.2.2"
+    lru-cache "^10.0.1"
+    minipass "^7.0.3"
+    minipass-collect "^2.0.1"
+    minipass-flush "^1.0.5"
+    minipass-pipeline "^1.2.4"
+    p-map "^4.0.0"
+    ssri "^10.0.0"
+    tar "^6.1.11"
+    unique-filename "^3.0.0"
+
 cache-base@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
@@ -7079,6 +11031,14 @@ cache-base@^1.0.1:
     union-value "^1.0.0"
     unset-value "^1.0.0"
 
+cache-content-type@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/cache-content-type/-/cache-content-type-1.0.1.tgz#035cde2b08ee2129f4a8315ea8f00a00dba1453c"
+  integrity sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==
+  dependencies:
+    mime-types "^2.1.18"
+    ylru "^1.2.0"
+
 cache-loader@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/cache-loader/-/cache-loader-4.1.0.tgz#9948cae353aec0a1fcb1eafda2300816ec85387e"
@@ -7149,6 +11109,11 @@ caller-path@^2.0.0:
   dependencies:
     caller-callsite "^2.0.0"
 
+callsite@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
+  integrity sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==
+
 callsites@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50"
@@ -7172,7 +11137,7 @@ camelcase@^5.0.0, camelcase@^5.3.1:
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
   integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
 
-camelcase@^6.2.0, camelcase@^6.3.0:
+camelcase@^6.0.0, camelcase@^6.2.0, camelcase@^6.3.0:
   version "6.3.0"
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
   integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
@@ -7192,17 +11157,55 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001587:
   resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001589.tgz#7ad6dba4c9bf6561aec8291976402339dc157dfb"
   integrity sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg==
 
+caniuse-lite@^1.0.30001599:
+  version "1.0.30001618"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001618.tgz#fad74fa006aef0f01e8e5c0a5540c74d8d36ec6f"
+  integrity sha512-p407+D1tIkDvsEAPS22lJxLQQaG8OTBEqo0KhzfABGk0TU4juBNDSfH0hyAp/HRyx+M8L17z/ltyhxh27FTfQg==
+
+canvas@^2.11.2:
+  version "2.11.2"
+  resolved "https://registry.yarnpkg.com/canvas/-/canvas-2.11.2.tgz#553d87b1e0228c7ac0fc72887c3adbac4abbd860"
+  integrity sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==
+  dependencies:
+    "@mapbox/node-pre-gyp" "^1.0.0"
+    nan "^2.17.0"
+    simple-get "^3.0.3"
+
 case-sensitive-paths-webpack-plugin@^2.3.0:
   version "2.4.0"
   resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4"
   integrity sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==
 
-caseless@~0.12.0:
+case@^1.6.3:
+  version "1.6.3"
+  resolved "https://registry.yarnpkg.com/case/-/case-1.6.3.tgz#0a4386e3e9825351ca2e6216c60467ff5f1ea1c9"
+  integrity sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==
+
+caseless@^0.12.0, caseless@~0.12.0:
   version "0.12.0"
   resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
   integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==
 
-chai@^4.3.10:
+catering@^2.1.0, catering@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/catering/-/catering-2.1.1.tgz#66acba06ed5ee28d5286133982a927de9a04b510"
+  integrity sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==
+
+cbor@^8.1.0:
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/cbor/-/cbor-8.1.0.tgz#cfc56437e770b73417a2ecbfc9caf6b771af60d5"
+  integrity sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==
+  dependencies:
+    nofilter "^3.1.0"
+
+chai-as-promised@^7.1.1:
+  version "7.1.2"
+  resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.2.tgz#70cd73b74afd519754161386421fb71832c6d041"
+  integrity sha512-aBDHZxRzYnUYuIAIPBH2s511DjlKPzXNlXSGFC8CwmroWQLfrW0LtE1nK3MAwwNhJPa9raEjNCmRoFpG0Hurdw==
+  dependencies:
+    check-error "^1.0.2"
+
+chai@^4.2.0, chai@^4.3.10:
   version "4.4.1"
   resolved "https://registry.yarnpkg.com/chai/-/chai-4.4.1.tgz#3603fa6eba35425b0f2ac91a009fe924106e50d1"
   integrity sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==
@@ -7256,6 +11259,11 @@ char-regex@^1.0.2:
   resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
   integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
 
+"charenc@>= 0.0.1":
+  version "0.0.2"
+  resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
+  integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==
+
 chart.js@^4.4.0:
   version "4.4.1"
   resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.1.tgz#ac5dc0e69a7758909158a96fe80ce43b3bb96a9f"
@@ -7263,7 +11271,12 @@ chart.js@^4.4.0:
   dependencies:
     "@kurkle/color" "^0.3.0"
 
-check-error@^1.0.3:
+check-disk-space@^3.4.0:
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/check-disk-space/-/check-disk-space-3.4.0.tgz#eb8e69eee7a378fd12e35281b8123a8b4c4a8ff7"
+  integrity sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==
+
+check-error@^1.0.2, check-error@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694"
   integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==
@@ -7280,7 +11293,22 @@ check-types@^8.0.3:
   resolved "https://registry.yarnpkg.com/check-types/-/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552"
   integrity sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==
 
-"chokidar@>=3.0.0 <4.0.0", chokidar@^3.3.0, chokidar@^3.4.1, chokidar@^3.5.3:
+chokidar@3.5.3:
+  version "3.5.3"
+  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
+  integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
+  dependencies:
+    anymatch "~3.1.2"
+    braces "~3.0.2"
+    glob-parent "~5.1.2"
+    is-binary-path "~2.1.0"
+    is-glob "~4.0.1"
+    normalize-path "~3.0.0"
+    readdirp "~3.6.0"
+  optionalDependencies:
+    fsevents "~2.3.2"
+
+"chokidar@>=3.0.0 <4.0.0", chokidar@^3.3.0, chokidar@^3.4.0, chokidar@^3.4.1, chokidar@^3.5.3:
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
   integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
@@ -7319,6 +11347,11 @@ chownr@^1.1.1, chownr@^1.1.4:
   resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
   integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
 
+chownr@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
+  integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
+
 chrome-trace-event@^1.0.2:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
@@ -7329,12 +11362,17 @@ ci-info@^1.5.0:
   resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497"
   integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==
 
+ci-info@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
+  integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
+
 ci-info@^3.2.0:
   version "3.9.0"
   resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
   integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
 
-cids@^0.7.1:
+cids@^0.7.1, cids@~0.7.0:
   version "0.7.5"
   resolved "https://registry.yarnpkg.com/cids/-/cids-0.7.5.tgz#60a08138a99bfb69b6be4ceb63bfef7a396b28b2"
   integrity sha512-zT7mPeghoWAu+ppn8+BS1tQ5qGmbMfB4AregnQjA/qHY3GC1m1ptI9GkWNlgeu38r7CuRdXB47uY2XgAYt6QVA==
@@ -7345,6 +11383,17 @@ cids@^0.7.1:
     multicodec "^1.0.0"
     multihashes "~0.4.15"
 
+cids@~0.8.0:
+  version "0.8.3"
+  resolved "https://registry.yarnpkg.com/cids/-/cids-0.8.3.tgz#aaf48ac8ed857c3d37dad94d8db1d8c9407b92db"
+  integrity sha512-yoXTbV3llpm+EBGWKeL9xKtksPE/s6DPoDSY4fn8I8TEW1zehWXPSB0pwAXVDlLaOlrw+sNynj995uD9abmPhA==
+  dependencies:
+    buffer "^5.6.0"
+    class-is "^1.1.0"
+    multibase "^1.0.0"
+    multicodec "^1.0.1"
+    multihashes "^1.0.1"
+
 cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
@@ -7365,11 +11414,21 @@ cjs-module-lexer@^1.0.0:
   resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107"
   integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==
 
+cjs-module-lexer@^1.2.2:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz#c485341ae8fd999ca4ee5af2d7a1c9ae01e0099c"
+  integrity sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==
+
 class-is@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/class-is/-/class-is-1.1.0.tgz#9d3c0fba0440d211d843cec3dedfa48055005825"
   integrity sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw==
 
+class-transformer@0.5.1:
+  version "0.5.1"
+  resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336"
+  integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==
+
 class-utils@^0.3.5:
   version "0.3.6"
   resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
@@ -7380,6 +11439,37 @@ class-utils@^0.3.5:
     isobject "^3.0.0"
     static-extend "^0.1.1"
 
+class-validator-jsonschema@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/class-validator-jsonschema/-/class-validator-jsonschema-5.0.0.tgz#0c7e4a1825597365a77bbe298e805dc083fce21d"
+  integrity sha512-F1skc5+NHZUxtVH56js1wdPKayUoIEZNpiZeNYIAJO0L6hCODmlX+lXwb26RRqTrjo0U24tNkSujn/G0zOvZoQ==
+  dependencies:
+    lodash.groupby "^4.6.0"
+    lodash.merge "^4.6.2"
+    openapi3-ts "^3.0.0"
+    reflect-metadata "^0.1.13"
+    tslib "^2.4.1"
+
+class-validator@^0.14.0:
+  version "0.14.1"
+  resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.14.1.tgz#ff2411ed8134e9d76acfeb14872884448be98110"
+  integrity sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==
+  dependencies:
+    "@types/validator" "^13.11.8"
+    libphonenumber-js "^1.10.53"
+    validator "^13.9.0"
+
+classic-level@^1.2.0:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/classic-level/-/classic-level-1.4.1.tgz#169ecf9f9c6200ad42a98c8576af449c1badbaee"
+  integrity sha512-qGx/KJl3bvtOHrGau2WklEZuXhS3zme+jf+fsu6Ej7W7IP/C49v7KNlWIsT1jZu0YnfzSIYDGcEWpCa1wKGWXQ==
+  dependencies:
+    abstract-level "^1.0.2"
+    catering "^2.1.0"
+    module-error "^1.0.1"
+    napi-macros "^2.2.2"
+    node-gyp-build "^4.3.0"
+
 clean-css@4.2.x:
   version "4.2.4"
   resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.4.tgz#733bf46eba4e607c6891ea57c24a989356831178"
@@ -7428,6 +11518,25 @@ cli-spinners@^2.0.0, cli-spinners@^2.5.0:
   resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41"
   integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==
 
+cli-table3@^0.5.0:
+  version "0.5.1"
+  resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202"
+  integrity sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==
+  dependencies:
+    object-assign "^4.1.0"
+    string-width "^2.1.1"
+  optionalDependencies:
+    colors "^1.1.2"
+
+cli-table3@^0.6.1:
+  version "0.6.5"
+  resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f"
+  integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==
+  dependencies:
+    string-width "^4.2.0"
+  optionalDependencies:
+    "@colors/colors" "1.5.0"
+
 cli-table3@~0.6.1:
   version "0.6.3"
   resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2"
@@ -7603,7 +11712,12 @@ color-string@^1.6.0, color-string@^1.9.0:
     color-name "^1.0.0"
     simple-swizzle "^0.2.2"
 
-color@^3.0.0:
+color-support@^1.1.2:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
+  integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
+
+color@^3.0.0, color@^3.1.3:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164"
   integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==
@@ -7619,11 +11733,34 @@ color@^4.2.3:
     color-convert "^2.0.1"
     color-string "^1.9.0"
 
-colorette@^2.0.16, colorette@^2.0.20:
+colord@^2.9.3:
+  version "2.9.3"
+  resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43"
+  integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==
+
+colorette@^2.0.10, colorette@^2.0.16, colorette@^2.0.20:
   version "2.0.20"
   resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a"
   integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==
 
+colors@1.0.x:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
+  integrity sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==
+
+colors@1.4.0, colors@^1.1.2:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
+  integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
+
+colorspace@1.1.x:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243"
+  integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==
+  dependencies:
+    color "^3.1.3"
+    text-hex "1.0.x"
+
 columnify@^1.6.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.6.0.tgz#6989531713c9008bb29735e61e37acf5bd553cf3"
@@ -7639,11 +11776,41 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
   dependencies:
     delayed-stream "~1.0.0"
 
+command-exists@^1.2.8:
+  version "1.2.9"
+  resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69"
+  integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==
+
+command-line-args@^5.1.1:
+  version "5.2.1"
+  resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.1.tgz#c44c32e437a57d7c51157696893c5909e9cec42e"
+  integrity sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==
+  dependencies:
+    array-back "^3.1.0"
+    find-replace "^3.0.0"
+    lodash.camelcase "^4.3.0"
+    typical "^4.0.0"
+
+command-line-usage@^6.1.0:
+  version "6.1.3"
+  resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-6.1.3.tgz#428fa5acde6a838779dfa30e44686f4b6761d957"
+  integrity sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==
+  dependencies:
+    array-back "^4.0.2"
+    chalk "^2.4.2"
+    table-layout "^1.0.2"
+    typical "^5.2.0"
+
 commander@2.17.x:
   version "2.17.1"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
   integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
 
+commander@3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e"
+  integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==
+
 commander@^10.0.0:
   version "10.0.1"
   resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
@@ -7659,11 +11826,26 @@ commander@^6.2.1:
   resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
   integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
 
+commander@^7.2.0:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
+  integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
+
+commander@^9.1.0:
+  version "9.5.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30"
+  integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==
+
 commander@~2.19.0:
   version "2.19.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
   integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==
 
+common-path-prefix@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0"
+  integrity sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==
+
 common-tags@^1.8.0:
   version "1.8.2"
   resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6"
@@ -7674,7 +11856,7 @@ commondir@^1.0.1:
   resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
   integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==
 
-component-emitter@^1.2.1:
+component-emitter@^1.2.1, component-emitter@^1.3.0:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17"
   integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==
@@ -7704,7 +11886,7 @@ concat-map@0.0.1:
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
   integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
 
-concat-stream@^1.5.0:
+concat-stream@^1.5.0, concat-stream@^1.5.2, concat-stream@^1.6.0, concat-stream@^1.6.2:
   version "1.6.2"
   resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
   integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
@@ -7714,6 +11896,16 @@ concat-stream@^1.5.0:
     readable-stream "^2.2.2"
     typedarray "^0.0.6"
 
+concat-stream@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1"
+  integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==
+  dependencies:
+    buffer-from "^1.0.0"
+    inherits "^2.0.3"
+    readable-stream "^3.0.2"
+    typedarray "^0.0.6"
+
 condense-newlines@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/condense-newlines/-/condense-newlines-0.2.1.tgz#3de985553139475d32502c83b02f60684d24c55f"
@@ -7741,6 +11933,11 @@ connect-history-api-fallback@^1.6.0:
   resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc"
   integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==
 
+connect-history-api-fallback@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8"
+  integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==
+
 consola@^3.2.3:
   version "3.2.3"
   resolved "https://registry.yarnpkg.com/consola/-/consola-3.2.3.tgz#0741857aa88cfa0d6fd53f1cff0375136e98502f"
@@ -7751,6 +11948,11 @@ console-browserify@^1.1.0:
   resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336"
   integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==
 
+console-control-strings@^1.0.0, console-control-strings@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
+  integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==
+
 consolidate@^0.15.1:
   version "0.15.1"
   resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.15.1.tgz#21ab043235c71a07d45d9aad98593b0dba56bab7"
@@ -7763,7 +11965,7 @@ constants-browserify@^1.0.0:
   resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
   integrity sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==
 
-content-disposition@0.5.4:
+content-disposition@0.5.4, content-disposition@~0.5.2:
   version "0.5.4"
   resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
   integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
@@ -7779,7 +11981,7 @@ content-hash@^2.5.2:
     multicodec "^0.5.5"
     multihashes "^0.4.15"
 
-content-type@~1.0.4, content-type@~1.0.5:
+content-type@^1.0.4, content-type@^1.0.5, content-type@~1.0.4, content-type@~1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
   integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
@@ -7809,6 +12011,29 @@ cookie@0.5.0:
   resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
   integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
 
+cookie@0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
+  integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
+
+cookie@^0.4.1:
+  version "0.4.2"
+  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432"
+  integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
+
+cookiejar@^2.1.4:
+  version "2.1.4"
+  resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b"
+  integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==
+
+cookies@~0.9.0:
+  version "0.9.1"
+  resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.9.1.tgz#3ffed6f60bb4fb5f146feeedba50acc418af67e3"
+  integrity sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==
+  dependencies:
+    depd "~2.0.0"
+    keygrip "~1.1.0"
+
 copy-anything@^2.0.1:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-2.0.6.tgz#092454ea9584a7b7ad5573062b2a87f5900fc480"
@@ -7833,6 +12058,18 @@ copy-descriptor@^0.1.0:
   resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
   integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==
 
+copy-webpack-plugin@^10.2.4:
+  version "10.2.4"
+  resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz#6c854be3fdaae22025da34b9112ccf81c63308fe"
+  integrity sha512-xFVltahqlsRcyyJqQbDY6EYTtyQZF9rf+JPjwHObLdPFMEISqkFkr7mFoVOC6BfYS/dNThyoQKvziugm+OnwBg==
+  dependencies:
+    fast-glob "^3.2.7"
+    glob-parent "^6.0.1"
+    globby "^12.0.2"
+    normalize-path "^3.0.0"
+    schema-utils "^4.0.0"
+    serialize-javascript "^6.0.0"
+
 copy-webpack-plugin@^5.1.1:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-5.1.2.tgz#8a889e1dcafa6c91c6cd4be1ad158f1d3823bae2"
@@ -7858,6 +12095,18 @@ core-js-compat@^3.31.0, core-js-compat@^3.34.0, core-js-compat@^3.6.5:
   dependencies:
     browserslist "^4.22.3"
 
+core-js-compat@^3.36.1:
+  version "3.37.1"
+  resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.37.1.tgz#c844310c7852f4bdf49b8d339730b97e17ff09ee"
+  integrity sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==
+  dependencies:
+    browserslist "^4.23.0"
+
+core-js-pure@^3.23.3:
+  version "3.37.1"
+  resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.37.1.tgz#2b4b34281f54db06c9a9a5bd60105046900553bd"
+  integrity sha512-J/r5JTHSmzTxbiYYrzXg9w1VpqrYt+gexenBE9pugeyhwPZTAEJddyiReJWsLO6uNQ8xJZFbod6XC7KKwatCiA==
+
 core-js@^3.6.5:
   version "3.36.0"
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.36.0.tgz#e752fa0b0b462a0787d56e9d73f80b0f7c0dde68"
@@ -7907,7 +12156,7 @@ cosmiconfig@^6.0.0:
     path-type "^4.0.0"
     yaml "^1.7.2"
 
-cosmiconfig@^7.0.0:
+cosmiconfig@^7.0.0, cosmiconfig@^7.0.1:
   version "7.1.0"
   resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6"
   integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==
@@ -7918,6 +12167,16 @@ cosmiconfig@^7.0.0:
     path-type "^4.0.0"
     yaml "^1.10.0"
 
+cosmiconfig@^8.1.3:
+  version "8.3.6"
+  resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3"
+  integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==
+  dependencies:
+    import-fresh "^3.3.0"
+    js-yaml "^4.1.0"
+    parse-json "^5.2.0"
+    path-type "^4.0.0"
+
 crc-32@^1.2.0:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff"
@@ -7972,6 +12231,13 @@ create-require@^1.1.0, create-require@^1.1.1:
   resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
   integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
 
+cron-parser@^4:
+  version "4.9.0"
+  resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-4.9.0.tgz#0340694af3e46a0894978c6f52a6dbb5c0f11ad5"
+  integrity sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==
+  dependencies:
+    luxon "^3.2.1"
+
 cross-fetch@^3.0.6, cross-fetch@^3.1.4, cross-fetch@^3.1.5:
   version "3.1.8"
   resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82"
@@ -8020,6 +12286,11 @@ crossws@^0.1.0:
   resolved "https://registry.yarnpkg.com/crossws/-/crossws-0.1.1.tgz#3a85a8140568e4828d9747a884171ea7e6a8bbe2"
   integrity sha512-c9c/o7bS3OjsdpSkvexpka0JNlesBF2JU9B2V1yNsYGwRbAafxhJQ7VI9b48D5bpONz/oxbPGMzBojy9sXoQIQ==
 
+"crypt@>= 0.0.1":
+  version "0.0.2"
+  resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
+  integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==
+
 crypto-browserify@^3.11.0:
   version "3.12.0"
   resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
@@ -8055,6 +12326,11 @@ css-declaration-sorter@^4.0.1:
     postcss "^7.0.1"
     timsort "^0.3.0"
 
+css-declaration-sorter@^7.2.0:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz#6dec1c9523bc4a643e088aab8f09e67a54961024"
+  integrity sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==
+
 css-loader@^3.5.3:
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.6.0.tgz#2e4b2c7e6e2d27f8c8f28f61bffcd2e6c91ef645"
@@ -8074,6 +12350,32 @@ css-loader@^3.5.3:
     schema-utils "^2.7.0"
     semver "^6.3.0"
 
+css-loader@^6.4.0:
+  version "6.11.0"
+  resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.11.0.tgz#33bae3bf6363d0a7c2cf9031c96c744ff54d85ba"
+  integrity sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==
+  dependencies:
+    icss-utils "^5.1.0"
+    postcss "^8.4.33"
+    postcss-modules-extract-imports "^3.1.0"
+    postcss-modules-local-by-default "^4.0.5"
+    postcss-modules-scope "^3.2.0"
+    postcss-modules-values "^4.0.0"
+    postcss-value-parser "^4.2.0"
+    semver "^7.5.4"
+
+css-minimizer-webpack-plugin@^5.0.0:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz#33effe662edb1a0bf08ad633c32fa75d0f7ec565"
+  integrity sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==
+  dependencies:
+    "@jridgewell/trace-mapping" "^0.3.18"
+    cssnano "^6.0.1"
+    jest-worker "^29.4.3"
+    postcss "^8.4.24"
+    schema-utils "^4.0.1"
+    serialize-javascript "^6.0.1"
+
 css-parse@~2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/css-parse/-/css-parse-2.0.0.tgz#a468ee667c16d81ccf05c58c38d2a97c780dbfd4"
@@ -8107,6 +12409,17 @@ css-select@^4.1.3:
     domutils "^2.8.0"
     nth-check "^2.0.1"
 
+css-select@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6"
+  integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==
+  dependencies:
+    boolbase "^1.0.0"
+    css-what "^6.1.0"
+    domhandler "^5.0.2"
+    domutils "^3.0.1"
+    nth-check "^2.0.1"
+
 css-tree@1.0.0-alpha.37:
   version "1.0.0-alpha.37"
   resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22"
@@ -8123,12 +12436,28 @@ css-tree@^1.1.2:
     mdn-data "2.0.14"
     source-map "^0.6.1"
 
+css-tree@^2.3.1:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20"
+  integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==
+  dependencies:
+    mdn-data "2.0.30"
+    source-map-js "^1.0.1"
+
+css-tree@~2.2.0:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.2.1.tgz#36115d382d60afd271e377f9c5f67d02bd48c032"
+  integrity sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==
+  dependencies:
+    mdn-data "2.0.28"
+    source-map-js "^1.0.1"
+
 css-what@^3.2.1:
   version "3.4.2"
   resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4"
   integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==
 
-css-what@^6.0.1:
+css-what@^6.0.1, css-what@^6.1.0:
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
   integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
@@ -8184,6 +12513,42 @@ cssnano-preset-default@^4.0.0, cssnano-preset-default@^4.0.8:
     postcss-svgo "^4.0.3"
     postcss-unique-selectors "^4.0.1"
 
+cssnano-preset-default@^6.1.2:
+  version "6.1.2"
+  resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz#adf4b89b975aa775f2750c89dbaf199bbd9da35e"
+  integrity sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==
+  dependencies:
+    browserslist "^4.23.0"
+    css-declaration-sorter "^7.2.0"
+    cssnano-utils "^4.0.2"
+    postcss-calc "^9.0.1"
+    postcss-colormin "^6.1.0"
+    postcss-convert-values "^6.1.0"
+    postcss-discard-comments "^6.0.2"
+    postcss-discard-duplicates "^6.0.3"
+    postcss-discard-empty "^6.0.3"
+    postcss-discard-overridden "^6.0.2"
+    postcss-merge-longhand "^6.0.5"
+    postcss-merge-rules "^6.1.1"
+    postcss-minify-font-values "^6.1.0"
+    postcss-minify-gradients "^6.0.3"
+    postcss-minify-params "^6.1.0"
+    postcss-minify-selectors "^6.0.4"
+    postcss-normalize-charset "^6.0.2"
+    postcss-normalize-display-values "^6.0.2"
+    postcss-normalize-positions "^6.0.2"
+    postcss-normalize-repeat-style "^6.0.2"
+    postcss-normalize-string "^6.0.2"
+    postcss-normalize-timing-functions "^6.0.2"
+    postcss-normalize-unicode "^6.1.0"
+    postcss-normalize-url "^6.0.2"
+    postcss-normalize-whitespace "^6.0.2"
+    postcss-ordered-values "^6.0.2"
+    postcss-reduce-initial "^6.1.0"
+    postcss-reduce-transforms "^6.0.2"
+    postcss-svgo "^6.0.3"
+    postcss-unique-selectors "^6.0.4"
+
 cssnano-util-get-arguments@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f"
@@ -8206,6 +12571,11 @@ cssnano-util-same-parent@^4.0.0:
   resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3"
   integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==
 
+cssnano-utils@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-4.0.2.tgz#56f61c126cd0f11f2eef1596239d730d9fceff3c"
+  integrity sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==
+
 cssnano@^4.0.0, cssnano@^4.1.10:
   version "4.1.11"
   resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.11.tgz#c7b5f5b81da269cb1fd982cb960c1200910c9a99"
@@ -8216,6 +12586,14 @@ cssnano@^4.0.0, cssnano@^4.1.10:
     is-resolvable "^1.0.0"
     postcss "^7.0.0"
 
+cssnano@^6.0.1:
+  version "6.1.2"
+  resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-6.1.2.tgz#4bd19e505bd37ee7cf0dc902d3d869f6d79c66b8"
+  integrity sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==
+  dependencies:
+    cssnano-preset-default "^6.1.2"
+    lilconfig "^3.1.1"
+
 csso@^4.0.2:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529"
@@ -8223,6 +12601,13 @@ csso@^4.0.2:
   dependencies:
     css-tree "^1.1.2"
 
+csso@^5.0.5:
+  version "5.0.5"
+  resolved "https://registry.yarnpkg.com/csso/-/csso-5.0.5.tgz#f9b7fe6cc6ac0b7d90781bb16d5e9874303e2ca6"
+  integrity sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==
+  dependencies:
+    css-tree "~2.2.0"
+
 cssom@^0.5.0:
   version "0.5.0"
   resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36"
@@ -8252,15 +12637,20 @@ csstype@^3.0.2, csstype@^3.1.3, csstype@latest:
   resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
   integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
 
+cycle@1.0.x:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2"
+  integrity sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA==
+
 cyclist@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.2.tgz#673b5f233bf34d8e602b949429f8171d9121bea3"
   integrity sha512-0sVXIohTfLqVIW3kb/0n6IiWF3Ifj5nm2XaSrLq2DI6fKIGa2fYAZdk917rUneaeLVpYfFcyXE2ft0fe3remsA==
 
-cypress@13.6.4:
-  version "13.6.4"
-  resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.6.4.tgz#42c88d3ee0342f1681abfacabf9c1f082676bc53"
-  integrity sha512-pYJjCfDYB+hoOoZuhysbbYhEmNW7DEDsqn+ToCLwuVowxUXppIWRr7qk4TVRIU471ksfzyZcH+mkoF0CQUKnpw==
+cypress@^13.6.6:
+  version "13.9.0"
+  resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.9.0.tgz#b529cfa8f8c39ba163ed0501a25bb5b09c143652"
+  integrity sha512-atNjmYfHsvTuCaxTxLZr9xGoHz53LLui3266WWxXJHY7+N6OdwJdg/feEa3T+buez9dmUXHT1izCOklqG82uCQ==
   dependencies:
     "@cypress/request" "^3.0.0"
     "@cypress/xvfb" "^1.2.4"
@@ -8269,7 +12659,7 @@ cypress@13.6.4:
     arch "^2.2.0"
     blob-util "^2.0.2"
     bluebird "^3.7.2"
-    buffer "^5.6.0"
+    buffer "^5.7.1"
     cachedir "^2.3.0"
     chalk "^4.1.0"
     check-more-types "^2.24.0"
@@ -8287,7 +12677,7 @@ cypress@13.6.4:
     figures "^3.2.0"
     fs-extra "^9.1.0"
     getos "^3.2.1"
-    is-ci "^3.0.0"
+    is-ci "^3.0.1"
     is-installed-globally "~0.4.0"
     lazy-ass "^1.6.0"
     listr2 "^3.8.3"
@@ -8343,18 +12733,35 @@ date-fns-tz@^1.3.7:
   resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.3.8.tgz#083e3a4e1f19b7857fa0c18deea6c2bc46ded7b9"
   integrity sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==
 
-date-fns@^2.29.3, date-fns@^2.30.0:
+date-fns@^2.28.0, date-fns@^2.29.3, date-fns@^2.30.0:
   version "2.30.0"
   resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0"
   integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
   dependencies:
     "@babel/runtime" "^7.21.0"
 
+date-format@^4.0.14:
+  version "4.0.14"
+  resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.14.tgz#7a8e584434fb169a521c8b7aa481f355810d9400"
+  integrity sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==
+
+date.js@~0.3.3:
+  version "0.3.3"
+  resolved "https://registry.yarnpkg.com/date.js/-/date.js-0.3.3.tgz#ef1e92332f507a638795dbb985e951882e50bbda"
+  integrity sha512-HgigOS3h3k6HnW011nAb43c5xx5rBXk8P2v/WIT9Zv4koIaVXiH2BURguI78VVp+5Qc076T7OR378JViCnZtBw==
+  dependencies:
+    debug "~3.1.0"
+
 dayjs@1.11.10, dayjs@^1.10.4:
   version "1.11.10"
   resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0"
   integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==
 
+death@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/death/-/death-1.1.0.tgz#01aa9c401edd92750514470b8266390c66c67318"
+  integrity sha512-vsV6S4KVHvTGxbEcij7hkWRv0It+sGGWVOM67dQde/o5Xjnr+KmLjxWJii2uEObIrt1CcM9w0Yaovx+iOlIL+w==
+
 debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -8362,7 +12769,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
   dependencies:
     ms "2.0.0"
 
-debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
+debug@4, debug@4.3.4, debug@4.x, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@~4, debug@~4.3.1, debug@~4.3.2:
   version "4.3.4"
   resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
   integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@@ -8388,6 +12795,11 @@ decamelize@^1.2.0:
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
   integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
 
+decamelize@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837"
+  integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==
+
 decimal.js-light@^2.5.1:
   version "2.5.1"
   resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934"
@@ -8410,6 +12822,13 @@ decompress-response@^3.3.0:
   dependencies:
     mimic-response "^1.0.0"
 
+decompress-response@^4.2.0:
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986"
+  integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==
+  dependencies:
+    mimic-response "^2.0.0"
+
 decompress-response@^6.0.0:
   version "6.0.0"
   resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
@@ -8422,7 +12841,7 @@ dedent@^1.0.0:
   resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff"
   integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==
 
-deep-eql@^4.1.3:
+deep-eql@^4.0.1, deep-eql@^4.1.3:
   version "4.1.3"
   resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.3.tgz#7c7775513092f7df98d8df9996dd085eb668cc6d"
   integrity sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==
@@ -8441,7 +12860,17 @@ deep-equal@^1.0.1:
     object-keys "^1.1.1"
     regexp.prototype.flags "^1.5.1"
 
-deep-is@^0.1.3:
+deep-equal@~1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
+  integrity sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==
+
+deep-extend@~0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
+  integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
+
+deep-is@^0.1.3, deep-is@~0.1.3:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
   integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
@@ -8451,7 +12880,7 @@ deepmerge@^1.5.2:
   resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-1.5.2.tgz#10499d868844cdad4fee0842df8c7f6f0c95a753"
   integrity sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==
 
-deepmerge@^4.2.2:
+deepmerge@^4.2.2, deepmerge@^4.3.1:
   version "4.3.1"
   resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a"
   integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
@@ -8471,6 +12900,13 @@ default-gateway@^5.0.5:
   dependencies:
     execa "^3.3.0"
 
+default-gateway@^6.0.3:
+  version "6.0.3"
+  resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71"
+  integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==
+  dependencies:
+    execa "^5.0.0"
+
 defaults@^1.0.3:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a"
@@ -8556,12 +12992,17 @@ delegate@^3.1.2:
   resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166"
   integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==
 
+delegates@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
+  integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==
+
 denque@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1"
   integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==
 
-depd@2.0.0:
+depd@2.0.0, depd@^2.0.0, depd@~2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
   integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
@@ -8589,7 +13030,7 @@ destr@^2.0.1, destr@^2.0.2:
   resolved "https://registry.yarnpkg.com/destr/-/destr-2.0.3.tgz#7f9e97cb3d16dbdca7be52aca1644ce402cfe449"
   integrity sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==
 
-destroy@1.2.0:
+destroy@1.2.0, destroy@^1.0.4:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
   integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
@@ -8604,6 +13045,11 @@ detect-libc@^1.0.3:
   resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
   integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==
 
+detect-libc@^2.0.0:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700"
+  integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==
+
 detect-newline@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
@@ -8622,11 +13068,24 @@ detect-port@^1.5.1:
     address "^1.0.1"
     debug "4"
 
+dezalgo@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81"
+  integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==
+  dependencies:
+    asap "^2.0.0"
+    wrappy "1"
+
 diff-sequences@^29.6.3:
   version "29.6.3"
   resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921"
   integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==
 
+diff@5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
+  integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
+
 diff@^4.0.1:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
@@ -8641,6 +13100,13 @@ diffie-hellman@^5.0.0:
     miller-rabin "^4.0.0"
     randombytes "^2.0.0"
 
+difflib@^0.2.4:
+  version "0.2.4"
+  resolved "https://registry.yarnpkg.com/difflib/-/difflib-0.2.4.tgz#b5e30361a6db023176d562892db85940a718f47e"
+  integrity sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==
+  dependencies:
+    heap ">= 0.2.0"
+
 dijkstrajs@^1.0.1:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz#4c8dbdea1f0f6478bff94d9c49c784d623e4fc23"
@@ -8660,6 +13126,29 @@ dir-glob@^3.0.1:
   dependencies:
     path-type "^4.0.0"
 
+discord-api-types@0.37.83:
+  version "0.37.83"
+  resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.37.83.tgz#a22a799729ceded8176ea747157837ddf4708b1f"
+  integrity sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==
+
+discord.js@^14.15.2:
+  version "14.15.2"
+  resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-14.15.2.tgz#ea2cacad02aff1faedf69ccea012149e06ab4277"
+  integrity sha512-wGD37YCaTUNprtpqMIRuNiswwsvSWXrHykBSm2SAosoTYut0VUDj9yo9t4iLtMKvuhI49zYkvKc2TNdzdvpJhg==
+  dependencies:
+    "@discordjs/builders" "^1.8.1"
+    "@discordjs/collection" "1.5.3"
+    "@discordjs/formatters" "^0.4.0"
+    "@discordjs/rest" "^2.3.0"
+    "@discordjs/util" "^1.1.0"
+    "@discordjs/ws" "^1.1.0"
+    "@sapphire/snowflake" "3.5.3"
+    discord-api-types "0.37.83"
+    fast-deep-equal "3.1.3"
+    lodash.snakecase "4.1.1"
+    tslib "2.6.2"
+    undici "6.13.0"
+
 dns-equal@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
@@ -8673,6 +13162,13 @@ dns-packet@^1.3.1:
     ip "^1.1.0"
     safe-buffer "^5.0.1"
 
+dns-packet@^5.2.2:
+  version "5.6.1"
+  resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f"
+  integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==
+  dependencies:
+    "@leichtgewicht/ip-codec" "^2.0.1"
+
 dns-txt@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6"
@@ -8718,6 +13214,15 @@ dom-serializer@^1.0.1:
     domhandler "^4.2.0"
     entities "^2.0.0"
 
+dom-serializer@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
+  integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
+  dependencies:
+    domelementtype "^2.3.0"
+    domhandler "^5.0.2"
+    entities "^4.2.0"
+
 dom-walk@^0.1.0:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84"
@@ -8738,7 +13243,7 @@ domelementtype@1:
   resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
   integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
 
-domelementtype@^2.0.1, domelementtype@^2.2.0:
+domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
   integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
@@ -8757,6 +13262,13 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1:
   dependencies:
     domelementtype "^2.2.0"
 
+domhandler@^5.0.2, domhandler@^5.0.3:
+  version "5.0.3"
+  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
+  integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
+  dependencies:
+    domelementtype "^2.3.0"
+
 domutils@^1.7.0:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
@@ -8774,6 +13286,23 @@ domutils@^2.5.2, domutils@^2.8.0:
     domelementtype "^2.2.0"
     domhandler "^4.2.0"
 
+domutils@^3.0.1:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e"
+  integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==
+  dependencies:
+    dom-serializer "^2.0.0"
+    domelementtype "^2.3.0"
+    domhandler "^5.0.3"
+
+dot-case@^3.0.4:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
+  integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==
+  dependencies:
+    no-case "^3.0.4"
+    tslib "^2.0.3"
+
 dot-prop@^5.2.0:
   version "5.3.0"
   resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
@@ -8849,6 +13378,13 @@ ecc-jsbn@~0.1.1:
     jsbn "~0.1.0"
     safer-buffer "^2.1.0"
 
+ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11:
+  version "1.0.11"
+  resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
+  integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
+  dependencies:
+    safe-buffer "^5.0.1"
+
 eciesjs@^0.3.15, eciesjs@^0.3.16:
   version "0.3.18"
   resolved "https://registry.yarnpkg.com/eciesjs/-/eciesjs-0.3.18.tgz#67b5d73a8466e40a45bbc2f2a3177e71e9c0643d"
@@ -8885,12 +13421,19 @@ ejs@^3.1.7:
   dependencies:
     jake "^10.8.5"
 
+ejs@^3.1.8:
+  version "3.1.10"
+  resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b"
+  integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==
+  dependencies:
+    jake "^10.8.5"
+
 electron-to-chromium@^1.4.668:
   version "1.4.680"
   resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.680.tgz#18a30d3f557993eda2d5b1e21a06c4d51875392f"
   integrity sha512-4nToZ5jlPO14W82NkF32wyjhYqQByVaDmLy4J2/tYcAbJfgO2TKJC780Az1V13gzq4l73CJ0yuyalpXvxXXD9A==
 
-elliptic@6.5.4, elliptic@^6.4.0, elliptic@^6.5.3, elliptic@^6.5.4:
+elliptic@6.5.4, elliptic@^6.5.3, elliptic@^6.5.4:
   version "6.5.4"
   resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
   integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
@@ -8903,6 +13446,19 @@ elliptic@6.5.4, elliptic@^6.4.0, elliptic@^6.5.3, elliptic@^6.5.4:
     minimalistic-assert "^1.0.1"
     minimalistic-crypto-utils "^1.0.1"
 
+elliptic@^6.4.0, elliptic@^6.5.2:
+  version "6.5.5"
+  resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.5.tgz#c715e09f78b6923977610d4c2346d6ce22e6dded"
+  integrity sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==
+  dependencies:
+    bn.js "^4.11.9"
+    brorand "^1.1.0"
+    hash.js "^1.0.0"
+    hmac-drbg "^1.0.1"
+    inherits "^2.0.4"
+    minimalistic-assert "^1.0.1"
+    minimalistic-crypto-utils "^1.0.1"
+
 emittery@^0.13.1:
   version "0.13.1"
   resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad"
@@ -8933,17 +13489,22 @@ emojis-list@^3.0.0:
   resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
   integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
 
+enabled@2.0.x:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2"
+  integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==
+
 encode-utf8@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda"
   integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==
 
-encodeurl@~1.0.2:
+encodeurl@^1.0.2, encodeurl@~1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
   integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
 
-encoding@^0.1.11:
+encoding@^0.1.11, encoding@^0.1.13:
   version "0.1.13"
   resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9"
   integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==
@@ -8982,6 +13543,14 @@ enhanced-resolve@^4.0.0, enhanced-resolve@^4.5.0:
     memory-fs "^0.5.0"
     tapable "^1.0.0"
 
+enhanced-resolve@^5.0.0, enhanced-resolve@^5.16.0:
+  version "5.16.1"
+  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz#e8bc63d51b826d6f1cbc0a150ecb5a8b0c62e567"
+  integrity sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==
+  dependencies:
+    graceful-fs "^4.2.4"
+    tapable "^2.2.0"
+
 enhanced-resolve@^5.7.0:
   version "5.15.0"
   resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35"
@@ -8990,7 +13559,7 @@ enhanced-resolve@^5.7.0:
     graceful-fs "^4.2.4"
     tapable "^2.2.0"
 
-enquirer@^2.3.6:
+enquirer@^2.3.0, enquirer@^2.3.6:
   version "2.4.1"
   resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.4.1.tgz#93334b3fbd74fc7097b224ab4a8fb7e40bf4ae56"
   integrity sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==
@@ -9010,11 +13579,21 @@ entities@^2.0.0:
   resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
   integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
 
-entities@^4.4.0, entities@^4.5.0:
+entities@^4.2.0, entities@^4.4.0, entities@^4.5.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
   integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
 
+env-paths@^2.2.0:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
+  integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
+
+err-code@^2.0.2:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9"
+  integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==
+
 errno@^0.1.1, errno@^0.1.3, errno@~0.1.7:
   version "0.1.8"
   resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f"
@@ -9100,6 +13679,11 @@ es-errors@^1.0.0, es-errors@^1.2.1, es-errors@^1.3.0:
   resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
   integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
 
+es-module-lexer@^1.2.1:
+  version "1.5.2"
+  resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.2.tgz#00b423304f2500ac59359cc9b6844951f372d497"
+  integrity sha512-l60ETUTmLqbVbVHv1J4/qj+M8nq7AwMzEcg3kmJDt9dCNrTk+yHcYFf/Kw75pMDwd9mPcIGCG5LcS20SxYRzFA==
+
 es-set-tostringtag@^2.0.2:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777"
@@ -9134,6 +13718,16 @@ es5-ext@^0.10.35, es5-ext@^0.10.50:
     es6-symbol "^3.1.3"
     next-tick "^1.1.0"
 
+es5-ext@^0.10.62, es5-ext@^0.10.63, es5-ext@~0.10.14:
+  version "0.10.64"
+  resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.64.tgz#12e4ffb48f1ba2ea777f1fcdd1918ef73ea21714"
+  integrity sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==
+  dependencies:
+    es6-iterator "^2.0.3"
+    es6-symbol "^3.1.3"
+    esniff "^2.0.1"
+    next-tick "^1.1.0"
+
 es6-iterator@^2.0.3:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
@@ -9143,7 +13737,7 @@ es6-iterator@^2.0.3:
     es5-ext "^0.10.35"
     es6-symbol "^3.1.1"
 
-es6-promise@^4.2.8:
+es6-promise@^4.2.4, es6-promise@^4.2.8:
   version "4.2.8"
   resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
   integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
@@ -9156,7 +13750,7 @@ es6-symbol@^3.1.1, es6-symbol@^3.1.3:
     d "^1.0.1"
     ext "^1.1.2"
 
-esbuild@^0.19.3:
+esbuild@^0.19.2, esbuild@^0.19.3:
   version "0.19.12"
   resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.12.tgz#dc82ee5dc79e82f5a5c3b4323a2a641827db3e04"
   integrity sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==
@@ -9190,7 +13784,7 @@ escalade@^3.1.1:
   resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27"
   integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==
 
-escape-html@~1.0.3:
+escape-html@^1.0.3, escape-html@~1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
   integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
@@ -9200,15 +13794,27 @@ escape-string-regexp@2.0.0, escape-string-regexp@^2.0.0:
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
   integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
 
+escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
+  integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
 escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
   integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
 
-escape-string-regexp@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
-  integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+escodegen@1.8.x:
+  version "1.8.1"
+  resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018"
+  integrity sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A==
+  dependencies:
+    esprima "^2.7.1"
+    estraverse "^1.9.1"
+    esutils "^2.0.2"
+    optionator "^0.8.1"
+  optionalDependencies:
+    source-map "~0.2.0"
 
 escodegen@^2.0.0:
   version "2.1.0"
@@ -9301,6 +13907,14 @@ eslint-plugin-vue@^7.0.0-0:
     semver "^6.3.0"
     vue-eslint-parser "^7.10.0"
 
+eslint-scope@5.1.1, eslint-scope@^5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
+  integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
+  dependencies:
+    esrecurse "^4.3.0"
+    estraverse "^4.1.1"
+
 eslint-scope@^4.0.3:
   version "4.0.3"
   resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
@@ -9309,14 +13923,6 @@ eslint-scope@^4.0.3:
     esrecurse "^4.1.0"
     estraverse "^4.1.1"
 
-eslint-scope@^5.1.1:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
-  integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
-  dependencies:
-    esrecurse "^4.3.0"
-    estraverse "^4.1.1"
-
 eslint-scope@^7.2.2:
   version "7.2.2"
   resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f"
@@ -9342,50 +13948,7 @@ eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4
   resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
   integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
 
-eslint@8.48.0:
-  version "8.48.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.48.0.tgz#bf9998ba520063907ba7bfe4c480dc8be03c2155"
-  integrity sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==
-  dependencies:
-    "@eslint-community/eslint-utils" "^4.2.0"
-    "@eslint-community/regexpp" "^4.6.1"
-    "@eslint/eslintrc" "^2.1.2"
-    "@eslint/js" "8.48.0"
-    "@humanwhocodes/config-array" "^0.11.10"
-    "@humanwhocodes/module-importer" "^1.0.1"
-    "@nodelib/fs.walk" "^1.2.8"
-    ajv "^6.12.4"
-    chalk "^4.0.0"
-    cross-spawn "^7.0.2"
-    debug "^4.3.2"
-    doctrine "^3.0.0"
-    escape-string-regexp "^4.0.0"
-    eslint-scope "^7.2.2"
-    eslint-visitor-keys "^3.4.3"
-    espree "^9.6.1"
-    esquery "^1.4.2"
-    esutils "^2.0.2"
-    fast-deep-equal "^3.1.3"
-    file-entry-cache "^6.0.1"
-    find-up "^5.0.0"
-    glob-parent "^6.0.2"
-    globals "^13.19.0"
-    graphemer "^1.4.0"
-    ignore "^5.2.0"
-    imurmurhash "^0.1.4"
-    is-glob "^4.0.0"
-    is-path-inside "^3.0.3"
-    js-yaml "^4.1.0"
-    json-stable-stringify-without-jsonify "^1.0.1"
-    levn "^0.4.1"
-    lodash.merge "^4.6.2"
-    minimatch "^3.1.2"
-    natural-compare "^1.4.0"
-    optionator "^0.9.3"
-    strip-ansi "^6.0.1"
-    text-table "^0.2.0"
-
-eslint@^8.0.0:
+eslint@8.57.0, eslint@^8.0.0:
   version "8.57.0"
   resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668"
   integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==
@@ -9429,6 +13992,16 @@ eslint@^8.0.0:
     strip-ansi "^6.0.1"
     text-table "^0.2.0"
 
+esniff@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/esniff/-/esniff-2.0.1.tgz#a4d4b43a5c71c7ec51c51098c1d8a29081f9b308"
+  integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==
+  dependencies:
+    d "^1.0.1"
+    es5-ext "^0.10.62"
+    event-emitter "^0.3.5"
+    type "^2.7.2"
+
 espree@^6.2.1:
   version "6.2.1"
   resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a"
@@ -9447,6 +14020,11 @@ espree@^9.0.0, espree@^9.6.0, espree@^9.6.1:
     acorn-jsx "^5.3.2"
     eslint-visitor-keys "^3.4.1"
 
+esprima@2.7.x, esprima@^2.7.1:
+  version "2.7.3"
+  resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
+  integrity sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==
+
 esprima@^4.0.0, esprima@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
@@ -9466,6 +14044,11 @@ esrecurse@^4.1.0, esrecurse@^4.3.0:
   dependencies:
     estraverse "^5.2.0"
 
+estraverse@^1.9.1:
+  version "1.9.3"
+  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44"
+  integrity sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==
+
 estraverse@^4.1.1:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
@@ -9517,6 +14100,25 @@ eth-ens-namehash@2.0.8:
     idna-uts46-hx "^2.3.1"
     js-sha3 "^0.5.7"
 
+eth-gas-reporter@^0.2.25:
+  version "0.2.27"
+  resolved "https://registry.yarnpkg.com/eth-gas-reporter/-/eth-gas-reporter-0.2.27.tgz#928de8548a674ed64c7ba0bf5795e63079150d4e"
+  integrity sha512-femhvoAM7wL0GcI8ozTdxfuBtBFJ9qsyIAsmKVjlWAHUbdnnXHt+lKzz/kmldM5lA9jLuNHGwuIxorNpLbR1Zw==
+  dependencies:
+    "@solidity-parser/parser" "^0.14.0"
+    axios "^1.5.1"
+    cli-table3 "^0.5.0"
+    colors "1.4.0"
+    ethereum-cryptography "^1.0.3"
+    ethers "^5.7.2"
+    fs-readdir-recursive "^1.1.0"
+    lodash "^4.17.14"
+    markdown-table "^1.1.3"
+    mocha "^10.2.0"
+    req-cwd "^2.0.0"
+    sha1 "^1.1.1"
+    sync-request "^6.0.0"
+
 eth-json-rpc-filters@^6.0.0:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/eth-json-rpc-filters/-/eth-json-rpc-filters-6.0.1.tgz#0b3e370f017f5c6f58d3e7bd0756d8099ed85c56"
@@ -9571,7 +14173,7 @@ ethereum-bloom-filters@^1.0.6:
   dependencies:
     js-sha3 "^0.8.0"
 
-ethereum-cryptography@^0.1.3:
+ethereum-cryptography@0.1.3, ethereum-cryptography@^0.1.3:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz#8d6143cfc3d74bf79bbd8edecdf29e4ae20dd191"
   integrity sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==
@@ -9592,6 +14194,16 @@ ethereum-cryptography@^0.1.3:
     secp256k1 "^4.0.1"
     setimmediate "^1.0.5"
 
+ethereum-cryptography@^1.0.3:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz#5ccfa183e85fdaf9f9b299a79430c044268c9b3a"
+  integrity sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==
+  dependencies:
+    "@noble/hashes" "1.2.0"
+    "@noble/secp256k1" "1.7.1"
+    "@scure/bip32" "1.1.5"
+    "@scure/bip39" "1.1.1"
+
 ethereum-cryptography@^2.0.0, ethereum-cryptography@^2.1.2:
   version "2.1.3"
   resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz#1352270ed3b339fe25af5ceeadcf1b9c8e30768a"
@@ -9602,7 +14214,28 @@ ethereum-cryptography@^2.0.0, ethereum-cryptography@^2.1.2:
     "@scure/bip32" "1.3.3"
     "@scure/bip39" "1.2.2"
 
-ethereumjs-util@^7.1.5:
+ethereumjs-abi@^0.6.8:
+  version "0.6.8"
+  resolved "https://registry.yarnpkg.com/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz#71bc152db099f70e62f108b7cdfca1b362c6fcae"
+  integrity sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==
+  dependencies:
+    bn.js "^4.11.8"
+    ethereumjs-util "^6.0.0"
+
+ethereumjs-util@^6.0.0, ethereumjs-util@^6.2.1:
+  version "6.2.1"
+  resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz#fcb4e4dd5ceacb9d2305426ab1a5cd93e3163b69"
+  integrity sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==
+  dependencies:
+    "@types/bn.js" "^4.11.3"
+    bn.js "^4.11.0"
+    create-hash "^1.1.2"
+    elliptic "^6.5.2"
+    ethereum-cryptography "^0.1.3"
+    ethjs-util "0.1.6"
+    rlp "^2.2.3"
+
+ethereumjs-util@^7.1.4, ethereumjs-util@^7.1.5:
   version "7.1.5"
   resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181"
   integrity sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==
@@ -9613,7 +14246,7 @@ ethereumjs-util@^7.1.5:
     ethereum-cryptography "^0.1.3"
     rlp "^2.2.4"
 
-ethers@5.7.2:
+ethers@5.7.2, ethers@^5.7.1, ethers@^5.7.2:
   version "5.7.2"
   resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e"
   integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==
@@ -9657,11 +14290,32 @@ ethjs-unit@0.1.6:
     bn.js "4.11.6"
     number-to-bn "1.7.0"
 
+ethjs-util@0.1.6, ethjs-util@^0.1.6:
+  version "0.1.6"
+  resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536"
+  integrity sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==
+  dependencies:
+    is-hex-prefixed "1.0.0"
+    strip-hex-prefix "1.0.0"
+
+event-emitter@^0.3.5:
+  version "0.3.5"
+  resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
+  integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==
+  dependencies:
+    d "1"
+    es5-ext "~0.10.14"
+
 event-pubsub@4.3.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/event-pubsub/-/event-pubsub-4.3.0.tgz#f68d816bc29f1ec02c539dc58c8dd40ce72cb36e"
   integrity sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==
 
+event-target-shim@^5.0.0:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
+  integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
+
 eventemitter2@6.4.7:
   version "6.4.7"
   resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.7.tgz#a7f6c4d7abf28a14c1ef3442f21cb306a054271d"
@@ -9687,7 +14341,7 @@ eventemitter3@^4.0.0:
   resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
   integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
 
-events@^3.0.0, events@^3.3.0:
+events@^3.0.0, events@^3.2.0, events@^3.3.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
   integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
@@ -9828,7 +14482,94 @@ expect@^29.0.0, expect@^29.7.0:
     jest-message-util "^29.7.0"
     jest-util "^29.7.0"
 
-express@^4.14.0, express@^4.16.3, express@^4.17.1:
+exponential-backoff@^3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6"
+  integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==
+
+express-async-errors@^3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/express-async-errors/-/express-async-errors-3.1.1.tgz#6053236d61d21ddef4892d6bd1d736889fc9da41"
+  integrity sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==
+
+express-ejs-layouts@^2.5.1:
+  version "2.5.1"
+  resolved "https://registry.yarnpkg.com/express-ejs-layouts/-/express-ejs-layouts-2.5.1.tgz#d204d9065ee2825fcbd718d820289fc81e691ccb"
+  integrity sha512-IXROv9n3xKga7FowT06n1Qn927JR8ZWDn5Dc9CJQoiiaaDqbhW5PDmWShzbpAa2wjWT1vJqaIM1S6vJwwX11gA==
+
+express-jwt-permissions@^1.3.7:
+  version "1.3.7"
+  resolved "https://registry.yarnpkg.com/express-jwt-permissions/-/express-jwt-permissions-1.3.7.tgz#af16fc6949a87f02535ed52d218f01098ad882db"
+  integrity sha512-FjmznwcOl4O2xff1gK6ASGDU92IbLAH3AsdJ5nfn0zm8B3TXMMa0Y2+5FMSxpeMt0H70y/3eRTm4KR2UBsvkQA==
+  dependencies:
+    express-unless "^2.0.0"
+    lodash.get "^4.4.2"
+
+express-jwt@^8.4.1:
+  version "8.4.1"
+  resolved "https://registry.yarnpkg.com/express-jwt/-/express-jwt-8.4.1.tgz#ba817c1ced7c6f1f7017fc2e6deac207011e8acb"
+  integrity sha512-IZoZiDv2yZJAb3QrbaSATVtTCYT11OcqgFGoTN4iKVyN6NBkBkhtVIixww5fmakF0Upt5HfOxJuS6ZmJVeOtTQ==
+  dependencies:
+    "@types/jsonwebtoken" "^9"
+    express-unless "^2.1.3"
+    jsonwebtoken "^9.0.0"
+
+express-rate-limit@^7.2.0:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-7.2.0.tgz#06ce387dd5388f429cab8263c514fc07bf90a445"
+  integrity sha512-T7nul1t4TNyfZMJ7pKRKkdeVJWa2CqB8NA1P8BwYaoDI5QSBZARv5oMS43J7b7I5P+4asjVXjb7ONuwDKucahg==
+
+express-unless@^2.0.0, express-unless@^2.1.3:
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/express-unless/-/express-unless-2.1.3.tgz#f951c6cca52a24da3de32d42cfd4db57bc0f9a2e"
+  integrity sha512-wj4tLMyCVYuIIKHGt0FhCtIViBcwzWejX0EjNxveAa6dG+0XBCQhMbx+PnkLkFCxLC69qoFrxds4pIyL88inaQ==
+
+express-validator@^7.0.1:
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/express-validator/-/express-validator-7.0.1.tgz#435fac6b5fa838763f78eca05d2206317b92106e"
+  integrity sha512-oB+z9QOzQIE8FnlINqyIFA8eIckahC6qc8KtqLdLJcU3/phVyuhXH3bA4qzcrhme+1RYaCSwrq+TlZ/kAKIARA==
+  dependencies:
+    lodash "^4.17.21"
+    validator "^13.9.0"
+
+express@^4.14.0, express@^4.17.3:
+  version "4.19.2"
+  resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465"
+  integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==
+  dependencies:
+    accepts "~1.3.8"
+    array-flatten "1.1.1"
+    body-parser "1.20.2"
+    content-disposition "0.5.4"
+    content-type "~1.0.4"
+    cookie "0.6.0"
+    cookie-signature "1.0.6"
+    debug "2.6.9"
+    depd "2.0.0"
+    encodeurl "~1.0.2"
+    escape-html "~1.0.3"
+    etag "~1.8.1"
+    finalhandler "1.2.0"
+    fresh "0.5.2"
+    http-errors "2.0.0"
+    merge-descriptors "1.0.1"
+    methods "~1.1.2"
+    on-finished "2.4.1"
+    parseurl "~1.3.3"
+    path-to-regexp "0.1.7"
+    proxy-addr "~2.0.7"
+    qs "6.11.0"
+    range-parser "~1.2.1"
+    safe-buffer "5.2.1"
+    send "0.18.0"
+    serve-static "1.15.0"
+    setprototypeof "1.2.0"
+    statuses "2.0.1"
+    type-is "~1.6.18"
+    utils-merge "1.0.1"
+    vary "~1.1.2"
+
+express@^4.16.3, express@^4.17.1:
   version "4.18.2"
   resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59"
   integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==
@@ -9865,6 +14606,43 @@ express@^4.14.0, express@^4.16.3, express@^4.17.1:
     utils-merge "1.0.1"
     vary "~1.1.2"
 
+express@~4.18.1:
+  version "4.18.3"
+  resolved "https://registry.yarnpkg.com/express/-/express-4.18.3.tgz#6870746f3ff904dee1819b82e4b51509afffb0d4"
+  integrity sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==
+  dependencies:
+    accepts "~1.3.8"
+    array-flatten "1.1.1"
+    body-parser "1.20.2"
+    content-disposition "0.5.4"
+    content-type "~1.0.4"
+    cookie "0.5.0"
+    cookie-signature "1.0.6"
+    debug "2.6.9"
+    depd "2.0.0"
+    encodeurl "~1.0.2"
+    escape-html "~1.0.3"
+    etag "~1.8.1"
+    finalhandler "1.2.0"
+    fresh "0.5.2"
+    http-errors "2.0.0"
+    merge-descriptors "1.0.1"
+    methods "~1.1.2"
+    on-finished "2.4.1"
+    parseurl "~1.3.3"
+    path-to-regexp "0.1.7"
+    proxy-addr "~2.0.7"
+    qs "6.11.0"
+    range-parser "~1.2.1"
+    safe-buffer "5.2.1"
+    send "0.18.0"
+    serve-static "1.15.0"
+    setprototypeof "1.2.0"
+    statuses "2.0.1"
+    type-is "~1.6.18"
+    utils-merge "1.0.1"
+    vary "~1.1.2"
+
 ext@^1.1.2:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f"
@@ -9887,7 +14665,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
     assign-symbols "^1.0.0"
     is-extendable "^1.0.1"
 
-extend@~3.0.2:
+extend@^3.0.2, extend@~3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
   integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
@@ -9946,16 +14724,75 @@ extsprintf@^1.2.0:
   resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07"
   integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==
 
-fast-deep-equal@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
-  integrity sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==
+eyes@0.1.x:
+  version "0.1.8"
+  resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0"
+  integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==
+
+fabric-ca-client@^2.2.20:
+  version "2.2.20"
+  resolved "https://registry.yarnpkg.com/fabric-ca-client/-/fabric-ca-client-2.2.20.tgz#c6d3d442b8d82bdf0cd65e65e2100d5d9ba3409b"
+  integrity sha512-uaVpjPU+Yar2n4sNPJZpRC/jPgeLhw7nbzGl+aInkleg8Zkor4eBfQKpNQQxEORqgqWS5hAHNYu2x0iPEHBmLw==
+  dependencies:
+    fabric-common "2.2.20"
+    jsrsasign "^10.5.25"
+    url "^0.11.0"
+    winston "^2.4.5"
+
+fabric-common@2.2.20:
+  version "2.2.20"
+  resolved "https://registry.yarnpkg.com/fabric-common/-/fabric-common-2.2.20.tgz#f96bec99e21364d832113f11e7df15e737b8374c"
+  integrity sha512-d7oPqXrEIHlN0yhno1iTSX1aRD0ews5Oq1kdjrUF5TWjV/lQ8ojFNxLb0gDICBlM5C4gYtFNfFt6rD4uvpmbuw==
+  dependencies:
+    callsite "^1.0.0"
+    elliptic "^6.5.4"
+    fabric-protos "2.2.20"
+    js-sha3 "^0.9.2"
+    jsrsasign "^10.5.25"
+    long "^5.2.3"
+    nconf "^0.12.0"
+    promise-settle "^0.3.0"
+    sjcl "^1.0.8"
+    winston "^2.4.5"
+    yn "^4.0.0"
+  optionalDependencies:
+    pkcs11js "^1.3.0"
 
-fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
+fabric-network@^2.2.20:
+  version "2.2.20"
+  resolved "https://registry.yarnpkg.com/fabric-network/-/fabric-network-2.2.20.tgz#da51ab0d871e2cdfc51643b2b58900222955ca05"
+  integrity sha512-Hdecb9UBuY/M2FZI4I7mYNRejYrULqA/VBiFIX3zLaQ7z7HVSfZOjFb9o+YepygBES1NSn/e+hSDfAFcziPSQQ==
+  dependencies:
+    fabric-common "2.2.20"
+    fabric-protos "2.2.20"
+    long "^5.2.3"
+    nano "^10.1.2"
+
+fabric-protos@2.2.20:
+  version "2.2.20"
+  resolved "https://registry.yarnpkg.com/fabric-protos/-/fabric-protos-2.2.20.tgz#6fac9d95096b40f734fab7384a3c01b574d15625"
+  integrity sha512-fTBpmR0RorMNX29Ks1I/1vpB3ktW1ooDV6mFUiPzaZTk60tpi9YVfPUIIYb3vtPxYSdjD1MZkUaxsMzujDwRoA==
+  dependencies:
+    "@grpc/grpc-js" "~1.9.0"
+    "@grpc/proto-loader" "^0.7.0"
+    long "^5.2.3"
+    protobufjs "^7.2.0"
+
+fast-base64-decode@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz#b434a0dd7d92b12b43f26819300d2dafb83ee418"
+  integrity sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q==
+
+fast-deep-equal@3.1.3, fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
   integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
 
+fast-deep-equal@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
+  integrity sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==
+
 fast-diff@^1.1.2:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0"
@@ -9984,7 +14821,7 @@ fast-glob@^2.2.6:
     merge2 "^1.2.3"
     micromatch "^3.1.10"
 
-fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.2:
+fast-glob@^3.0.3, fast-glob@^3.2.12, fast-glob@^3.2.7, fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.2:
   version "3.3.2"
   resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
   integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
@@ -10000,7 +14837,7 @@ fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-sta
   resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
   integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
 
-fast-levenshtein@^2.0.6:
+fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
   integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
@@ -10010,11 +14847,18 @@ fast-redact@^3.0.0:
   resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.3.0.tgz#7c83ce3a7be4898241a46560d51de10f653f7634"
   integrity sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==
 
-fast-safe-stringify@^2.0.6:
+fast-safe-stringify@^2.0.6, fast-safe-stringify@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884"
   integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==
 
+fast-xml-parser@4.2.5:
+  version "4.2.5"
+  resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz#a6747a09296a6cb34f2ae634019bf1738f3b421f"
+  integrity sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==
+  dependencies:
+    strnum "^1.0.5"
+
 fastq@^1.6.0:
   version "1.17.1"
   resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47"
@@ -10043,6 +14887,11 @@ fd-slicer@~1.1.0:
   dependencies:
     pend "~1.2.0"
 
+fecha@^4.2.0:
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd"
+  integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==
+
 fflate@^0.8.1:
   version "0.8.2"
   resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea"
@@ -10145,6 +14994,26 @@ find-cache-dir@^3.0.0, find-cache-dir@^3.3.1:
     make-dir "^3.0.2"
     pkg-dir "^4.1.0"
 
+find-cache-dir@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-4.0.0.tgz#a30ee0448f81a3990708f6453633c733e2f6eec2"
+  integrity sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==
+  dependencies:
+    common-path-prefix "^3.0.0"
+    pkg-dir "^7.0.0"
+
+find-package-json@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/find-package-json/-/find-package-json-1.2.0.tgz#4057d1b943f82d8445fe52dc9cf456f6b8b58083"
+  integrity sha512-+SOGcLGYDJHtyqHd87ysBhmaeQ95oWspDKnMXBrnQ9Eq4OkLNqejgoaD8xVWu6GPa0B6roa6KinCMEMcVeqONw==
+
+find-replace@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38"
+  integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==
+  dependencies:
+    array-back "^3.0.1"
+
 find-root@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
@@ -10158,6 +15027,13 @@ find-up@5.0.0, find-up@^5.0.0:
     locate-path "^6.0.0"
     path-exists "^4.0.0"
 
+find-up@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
+  integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==
+  dependencies:
+    locate-path "^2.0.0"
+
 find-up@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
@@ -10173,6 +15049,14 @@ find-up@^4.0.0, find-up@^4.1.0:
     locate-path "^5.0.0"
     path-exists "^4.0.0"
 
+find-up@^6.3.0:
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-6.3.0.tgz#2abab3d3280b2dc7ac10199ef324c4e002c8c790"
+  integrity sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==
+  dependencies:
+    locate-path "^7.1.0"
+    path-exists "^5.0.0"
+
 flat-cache@^3.0.4:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee"
@@ -10187,7 +15071,7 @@ flat@^5.0.2:
   resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
   integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
 
-flatted@^3.2.9:
+flatted@^3.2.7, flatted@^3.2.9:
   version "3.3.1"
   resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
   integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==
@@ -10200,11 +15084,26 @@ flush-write-stream@^1.0.0:
     inherits "^2.0.3"
     readable-stream "^2.3.6"
 
+fn-args@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/fn-args/-/fn-args-5.0.0.tgz#7a18e105c8fb3bf0a51c30389bf16c9ebe740bb3"
+  integrity sha512-CtbfI3oFFc3nbdIoHycrfbrxiGgxXBXXuyOl49h47JawM1mYrqpiRqnH5CB2mBatdXvHHOUO6a+RiAuuvKt0lw==
+
+fn.name@1.x.x:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc"
+  integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==
+
 follow-redirects@^1.0.0, follow-redirects@^1.14.4, follow-redirects@^1.15.4:
   version "1.15.5"
   resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020"
   integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==
 
+follow-redirects@^1.12.1, follow-redirects@^1.14.0, follow-redirects@^1.15.6:
+  version "1.15.6"
+  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
+  integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
+
 for-each@^0.3.3:
   version "0.3.3"
   resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
@@ -10255,6 +15154,24 @@ forever-agent@~0.6.1:
     semver "^7.3.2"
     tapable "^1.0.0"
 
+fork-ts-checker-webpack-plugin@7.2.13:
+  version "7.2.13"
+  resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.2.13.tgz#51ffd6a2f96f03ab64b92f8aedf305dbf3dee0f1"
+  integrity sha512-fR3WRkOb4bQdWB/y7ssDUlVdrclvwtyCUIHCfivAoYxq9dF7XfrDKbMdZIfwJ7hxIAqkYSGeU7lLJE6xrxIBdg==
+  dependencies:
+    "@babel/code-frame" "^7.16.7"
+    chalk "^4.1.2"
+    chokidar "^3.5.3"
+    cosmiconfig "^7.0.1"
+    deepmerge "^4.2.2"
+    fs-extra "^10.0.0"
+    memfs "^3.4.1"
+    minimatch "^3.0.4"
+    node-abort-controller "^3.0.1"
+    schema-utils "^3.1.1"
+    semver "^7.3.5"
+    tapable "^2.2.1"
+
 fork-ts-checker-webpack-plugin@^3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-3.1.1.tgz#a1642c0d3e65f50c2cc1742e9c0a80f441f86b19"
@@ -10274,6 +15191,15 @@ form-data-encoder@1.7.1:
   resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.1.tgz#ac80660e4f87ee0d3d3c3638b7da8278ddb8ec96"
   integrity sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg==
 
+form-data@^2.2.0, form-data@^2.3.3, form-data@^2.5.0:
+  version "2.5.1"
+  resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
+  integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==
+  dependencies:
+    asynckit "^0.4.0"
+    combined-stream "^1.0.6"
+    mime-types "^2.1.12"
+
 form-data@^3.0.0:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
@@ -10301,11 +15227,35 @@ form-data@~2.3.2:
     combined-stream "^1.0.6"
     mime-types "^2.1.12"
 
+formidable@^3.5.1:
+  version "3.5.1"
+  resolved "https://registry.yarnpkg.com/formidable/-/formidable-3.5.1.tgz#9360a23a656f261207868b1484624c4c8d06ee1a"
+  integrity sha512-WJWKelbRHN41m5dumb0/k8TeAx7Id/y3a+Z7QfhxP/htI9Js5zYaEDtG8uMgG0vM0lOlqnmjE99/kfpOYi/0Og==
+  dependencies:
+    dezalgo "^1.0.4"
+    hexoid "^1.0.0"
+    once "^1.4.0"
+
 forwarded@0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
   integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
 
+fp-ts@1.19.3:
+  version "1.19.3"
+  resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.19.3.tgz#261a60d1088fbff01f91256f91d21d0caaaaa96f"
+  integrity sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==
+
+fp-ts@^1.0.0:
+  version "1.19.5"
+  resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.19.5.tgz#3da865e585dfa1fdfd51785417357ac50afc520a"
+  integrity sha512-wDNqTimnzs8QqpldiId9OavWK2NptormjXnRJTQecNjzwfyp6P/8s/zG8e4h3ja3oqkKaY72UlTjQYt/1yXf9A==
+
+fraction.js@^4.3.7:
+  version "4.3.7"
+  resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
+  integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
+
 fragment-cache@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
@@ -10313,7 +15263,7 @@ fragment-cache@^0.2.1:
   dependencies:
     map-cache "^0.2.2"
 
-fresh@0.5.2:
+fresh@0.5.2, fresh@~0.5.2:
   version "0.5.2"
   resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
   integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
@@ -10331,7 +15281,18 @@ fs-constants@^1.0.0:
   resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
   integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
 
-fs-extra@^10.0.0:
+fs-extra@^0.30.0:
+  version "0.30.0"
+  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0"
+  integrity sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA==
+  dependencies:
+    graceful-fs "^4.1.2"
+    jsonfile "^2.1.0"
+    klaw "^1.0.0"
+    path-is-absolute "^1.0.0"
+    rimraf "^2.2.8"
+
+fs-extra@^10.0.0, fs-extra@^10.0.1:
   version "10.1.0"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
   integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
@@ -10358,7 +15319,7 @@ fs-extra@^4.0.2:
     jsonfile "^4.0.0"
     universalify "^0.1.0"
 
-fs-extra@^7.0.1:
+fs-extra@^7.0.0, fs-extra@^7.0.1:
   version "7.0.1"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
   integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==
@@ -10367,6 +15328,15 @@ fs-extra@^7.0.1:
     jsonfile "^4.0.0"
     universalify "^0.1.0"
 
+fs-extra@^8.1.0:
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
+  integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
+  dependencies:
+    graceful-fs "^4.2.0"
+    jsonfile "^4.0.0"
+    universalify "^0.1.0"
+
 fs-extra@^9.0.0, fs-extra@^9.1.0:
   version "9.1.0"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
@@ -10384,11 +15354,30 @@ fs-minipass@^1.2.7:
   dependencies:
     minipass "^2.6.0"
 
+fs-minipass@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
+  integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
+  dependencies:
+    minipass "^3.0.0"
+
+fs-minipass@^3.0.0:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-3.0.3.tgz#79a85981c4dc120065e96f62086bf6f9dc26cc54"
+  integrity sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==
+  dependencies:
+    minipass "^7.0.3"
+
 fs-monkey@^1.0.4:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.5.tgz#fe450175f0db0d7ea758102e1d84096acb925788"
   integrity sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==
 
+fs-readdir-recursive@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27"
+  integrity sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==
+
 fs-write-stream-atomic@^1.0.8:
   version "1.0.10"
   resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9"
@@ -10437,6 +15426,11 @@ function.prototype.name@^1.1.6:
     es-abstract "^1.22.1"
     functions-have-names "^1.2.3"
 
+functional-red-black-tree@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
+  integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==
+
 functions-have-names@^1.2.3:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
@@ -10447,6 +15441,40 @@ futoin-hkdf@^1.5.3:
   resolved "https://registry.yarnpkg.com/futoin-hkdf/-/futoin-hkdf-1.5.3.tgz#6c8024f2e1429da086d4e18289ef2239ad33ee35"
   integrity sha512-SewY5KdMpaoCeh7jachEWFsh1nNlaDjNHZXWqL5IGwtpEYHTgkr2+AMCgNwKWkcc0wpSYrZfR7he4WdmHFtDxQ==
 
+gauge@^3.0.0:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395"
+  integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==
+  dependencies:
+    aproba "^1.0.3 || ^2.0.0"
+    color-support "^1.1.2"
+    console-control-strings "^1.0.0"
+    has-unicode "^2.0.1"
+    object-assign "^4.1.1"
+    signal-exit "^3.0.0"
+    string-width "^4.2.3"
+    strip-ansi "^6.0.1"
+    wide-align "^1.1.2"
+
+gaxios@^6.0.0, gaxios@^6.0.3, gaxios@^6.1.1:
+  version "6.6.0"
+  resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-6.6.0.tgz#af8242fff0bbb82a682840d5feaa91b6a1c58be4"
+  integrity sha512-bpOZVQV5gthH/jVCSuYuokRo2bTKOcuBiVWpjmTn6C5Agl5zclGfTljuGsQZxwwDBkli+YhZhP4TdlqTnhOezQ==
+  dependencies:
+    extend "^3.0.2"
+    https-proxy-agent "^7.0.1"
+    is-stream "^2.0.0"
+    node-fetch "^2.6.9"
+    uuid "^9.0.1"
+
+gcp-metadata@^6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-6.1.0.tgz#9b0dd2b2445258e7597f2024332d20611cbd6b8c"
+  integrity sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==
+  dependencies:
+    gaxios "^6.0.0"
+    json-bigint "^1.0.0"
+
 gensync@^1.0.0-beta.2:
   version "1.0.0-beta.2"
   resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
@@ -10483,6 +15511,11 @@ get-port-please@^3.1.2:
   resolved "https://registry.yarnpkg.com/get-port-please/-/get-port-please-3.1.2.tgz#502795e56217128e4183025c89a48c71652f4e49"
   integrity sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ==
 
+get-port@^3.1.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc"
+  integrity sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==
+
 get-stdin@^6.0.0:
   version "6.0.0"
   resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
@@ -10545,6 +15578,14 @@ getpass@^0.1.1:
   dependencies:
     assert-plus "^1.0.0"
 
+ghost-testrpc@^0.0.2:
+  version "0.0.2"
+  resolved "https://registry.yarnpkg.com/ghost-testrpc/-/ghost-testrpc-0.0.2.tgz#c4de9557b1d1ae7b2d20bbe474a91378ca90ce92"
+  integrity sha512-i08dAEgJ2g8z5buJIrCTduwPIhih3DP+hOCTyyryikfV8T0bNvHnGXO67i0DD1H4GBDETTclPy9njZbfluQYrQ==
+  dependencies:
+    chalk "^2.4.2"
+    node-emoji "^1.10.0"
+
 glob-parent@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
@@ -10560,7 +15601,7 @@ glob-parent@^5.1.2, glob-parent@~5.1.2:
   dependencies:
     is-glob "^4.0.1"
 
-glob-parent@^6.0.2:
+glob-parent@^6.0.1, glob-parent@^6.0.2:
   version "6.0.2"
   resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
   integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
@@ -10572,6 +15613,46 @@ glob-to-regexp@^0.3.0:
   resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
   integrity sha512-Iozmtbqv0noj0uDDqoL0zNq0VBEfK2YFoMAZoxJe4cwphvLR+JskfF30QhXHOR4m3KrE6NLRYw+U9MRXvifyig==
 
+glob-to-regexp@^0.4.1:
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
+  integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
+
+glob@7.1.7:
+  version "7.1.7"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
+  integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
+  dependencies:
+    fs.realpath "^1.0.0"
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "^3.0.4"
+    once "^1.3.0"
+    path-is-absolute "^1.0.0"
+
+glob@7.2.0:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
+  integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
+  dependencies:
+    fs.realpath "^1.0.0"
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "^3.0.4"
+    once "^1.3.0"
+    path-is-absolute "^1.0.0"
+
+glob@8.1.0:
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e"
+  integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==
+  dependencies:
+    fs.realpath "^1.0.0"
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "^5.0.1"
+    once "^1.3.0"
+
 glob@9.3.2:
   version "9.3.2"
   resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.2.tgz#8528522e003819e63d11c979b30896e0eaf52eda"
@@ -10582,6 +15663,17 @@ glob@9.3.2:
     minipass "^4.2.4"
     path-scurry "^1.6.1"
 
+glob@^10.2.2, glob@^10.3.10:
+  version "10.3.15"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.15.tgz#e72bc61bc3038c90605f5dd48543dc67aaf3b50d"
+  integrity sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==
+  dependencies:
+    foreground-child "^3.1.0"
+    jackspeak "^2.3.6"
+    minimatch "^9.0.1"
+    minipass "^7.0.4"
+    path-scurry "^1.11.0"
+
 glob@^10.3.3:
   version "10.3.10"
   resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b"
@@ -10593,7 +15685,18 @@ glob@^10.3.3:
     minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
     path-scurry "^1.10.1"
 
-glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
+glob@^5.0.15:
+  version "5.0.15"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
+  integrity sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==
+  dependencies:
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "2 || 3"
+    once "^1.3.0"
+    path-is-absolute "^1.0.0"
+
+glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
   version "7.2.3"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
   integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@@ -10612,6 +15715,22 @@ global-dirs@^3.0.0:
   dependencies:
     ini "2.0.0"
 
+global-modules@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780"
+  integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==
+  dependencies:
+    global-prefix "^3.0.0"
+
+global-prefix@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97"
+  integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==
+  dependencies:
+    ini "^1.3.5"
+    kind-of "^6.0.2"
+    which "^1.3.1"
+
 global@~4.4.0:
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406"
@@ -10639,6 +15758,20 @@ globalthis@^1.0.3:
   dependencies:
     define-properties "^1.1.3"
 
+globby@^10.0.1:
+  version "10.0.2"
+  resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543"
+  integrity sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==
+  dependencies:
+    "@types/glob" "^7.1.1"
+    array-union "^2.1.0"
+    dir-glob "^3.0.1"
+    fast-glob "^3.0.3"
+    glob "^7.1.3"
+    ignore "^5.1.1"
+    merge2 "^1.2.3"
+    slash "^3.0.0"
+
 globby@^11.1.0:
   version "11.1.0"
   resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
@@ -10651,6 +15784,18 @@ globby@^11.1.0:
     merge2 "^1.4.1"
     slash "^3.0.0"
 
+globby@^12.0.2:
+  version "12.2.0"
+  resolved "https://registry.yarnpkg.com/globby/-/globby-12.2.0.tgz#2ab8046b4fba4ff6eede835b29f678f90e3d3c22"
+  integrity sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==
+  dependencies:
+    array-union "^3.0.1"
+    dir-glob "^3.0.1"
+    fast-glob "^3.2.7"
+    ignore "^5.1.9"
+    merge2 "^1.4.1"
+    slash "^4.0.0"
+
 globby@^6.1.0:
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c"
@@ -10700,6 +15845,38 @@ good-listener@^1.2.2:
   dependencies:
     delegate "^3.1.2"
 
+google-auth-library@^9.0.0, google-auth-library@^9.7.0:
+  version "9.10.0"
+  resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-9.10.0.tgz#c9fb940923f7ff2569d61982ee1748578c0bbfd4"
+  integrity sha512-ol+oSa5NbcGdDqA+gZ3G3mev59OHBZksBTxY/tYwjtcp1H/scAFwJfSQU9/1RALoyZ7FslNbke8j4i3ipwlyuQ==
+  dependencies:
+    base64-js "^1.3.0"
+    ecdsa-sig-formatter "^1.0.11"
+    gaxios "^6.1.1"
+    gcp-metadata "^6.1.0"
+    gtoken "^7.0.0"
+    jws "^4.0.0"
+
+googleapis-common@^7.0.0:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/googleapis-common/-/googleapis-common-7.2.0.tgz#5c19102c9af1e5d27560be5e69ee2ccf68755d42"
+  integrity sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==
+  dependencies:
+    extend "^3.0.2"
+    gaxios "^6.0.3"
+    google-auth-library "^9.7.0"
+    qs "^6.7.0"
+    url-template "^2.0.8"
+    uuid "^9.0.0"
+
+googleapis@^137.1.0:
+  version "137.1.0"
+  resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-137.1.0.tgz#f7896e6676e6e5ca358741bd7e837d8dfce94e96"
+  integrity sha512-2L7SzN0FLHyQtFmyIxrcXhgust77067pkkduqkbIpDuj9JzVnByxsRrcRfUMFQam3rQkWW2B0f1i40IwKDWIVQ==
+  dependencies:
+    google-auth-library "^9.0.0"
+    googleapis-common "^7.0.0"
+
 gopd@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
@@ -10743,7 +15920,7 @@ got@^11.8.5:
     p-cancelable "^2.0.0"
     responselike "^2.0.0"
 
-graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9:
+graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
   version "4.2.11"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
   integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
@@ -10767,6 +15944,14 @@ graphql@^15.6.1:
   resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.8.0.tgz#33410e96b012fa3bdb1091cc99a94769db212b38"
   integrity sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==
 
+gtoken@^7.0.0:
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-7.1.0.tgz#d61b4ebd10132222817f7222b1e6064bd463fc26"
+  integrity sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==
+  dependencies:
+    gaxios "^6.0.0"
+    jws "^4.0.0"
+
 gzip-size@^5.0.0:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274"
@@ -10795,6 +15980,18 @@ handle-thing@^2.0.0:
   resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e"
   integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==
 
+handlebars@^4.0.1:
+  version "4.7.8"
+  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9"
+  integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==
+  dependencies:
+    minimist "^1.2.5"
+    neo-async "^2.6.2"
+    source-map "^0.6.1"
+    wordwrap "^1.0.0"
+  optionalDependencies:
+    uglify-js "^3.1.4"
+
 har-schema@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
@@ -10808,6 +16005,71 @@ har-validator@~5.1.3:
     ajv "^6.12.3"
     har-schema "^2.0.0"
 
+hardhat-gas-reporter@^1.0.8:
+  version "1.0.10"
+  resolved "https://registry.yarnpkg.com/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.10.tgz#ebe5bda5334b5def312747580cd923c2b09aef1b"
+  integrity sha512-02N4+So/fZrzJ88ci54GqwVA3Zrf0C9duuTyGt0CFRIh/CdNwbnTgkXkRfojOMLBQ+6t+lBIkgbsOtqMvNwikA==
+  dependencies:
+    array-uniq "1.0.3"
+    eth-gas-reporter "^0.2.25"
+    sha1 "^1.1.1"
+
+hardhat@2.14.0:
+  version "2.14.0"
+  resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.14.0.tgz#b60c74861494aeb1b50803cf04cc47865a42b87a"
+  integrity sha512-73jsInY4zZahMSVFurSK+5TNCJTXMv+vemvGia0Ac34Mm19fYp6vEPVGF3sucbumszsYxiTT2TbS8Ii2dsDSoQ==
+  dependencies:
+    "@ethersproject/abi" "^5.1.2"
+    "@metamask/eth-sig-util" "^4.0.0"
+    "@nomicfoundation/ethereumjs-block" "5.0.1"
+    "@nomicfoundation/ethereumjs-blockchain" "7.0.1"
+    "@nomicfoundation/ethereumjs-common" "4.0.1"
+    "@nomicfoundation/ethereumjs-evm" "2.0.1"
+    "@nomicfoundation/ethereumjs-rlp" "5.0.1"
+    "@nomicfoundation/ethereumjs-statemanager" "2.0.1"
+    "@nomicfoundation/ethereumjs-trie" "6.0.1"
+    "@nomicfoundation/ethereumjs-tx" "5.0.1"
+    "@nomicfoundation/ethereumjs-util" "9.0.1"
+    "@nomicfoundation/ethereumjs-vm" "7.0.1"
+    "@nomicfoundation/solidity-analyzer" "^0.1.0"
+    "@sentry/node" "^5.18.1"
+    "@types/bn.js" "^5.1.0"
+    "@types/lru-cache" "^5.1.0"
+    abort-controller "^3.0.0"
+    adm-zip "^0.4.16"
+    aggregate-error "^3.0.0"
+    ansi-escapes "^4.3.0"
+    chalk "^2.4.2"
+    chokidar "^3.4.0"
+    ci-info "^2.0.0"
+    debug "^4.1.1"
+    enquirer "^2.3.0"
+    env-paths "^2.2.0"
+    ethereum-cryptography "^1.0.3"
+    ethereumjs-abi "^0.6.8"
+    find-up "^2.1.0"
+    fp-ts "1.19.3"
+    fs-extra "^7.0.1"
+    glob "7.2.0"
+    immutable "^4.0.0-rc.12"
+    io-ts "1.10.4"
+    keccak "^3.0.2"
+    lodash "^4.17.11"
+    mnemonist "^0.38.0"
+    mocha "^10.0.0"
+    p-map "^4.0.0"
+    qs "^6.7.0"
+    raw-body "^2.4.1"
+    resolve "1.17.0"
+    semver "^6.3.0"
+    solc "0.7.3"
+    source-map-support "^0.5.13"
+    stacktrace-parser "^0.1.10"
+    tsort "0.0.1"
+    undici "^5.14.0"
+    uuid "^8.3.2"
+    ws "^7.4.6"
+
 harmony-reflect@^1.4.6:
   version "1.6.2"
   resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.2.tgz#31ecbd32e648a34d030d86adb67d4d47547fe710"
@@ -10825,6 +16087,11 @@ has-bigints@^1.0.1, has-bigints@^1.0.2:
   resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
   integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==
 
+has-flag@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa"
+  integrity sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==
+
 has-flag@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
@@ -10859,6 +16126,11 @@ has-tostringtag@^1.0.0, has-tostringtag@^1.0.1, has-tostringtag@^1.0.2:
   dependencies:
     has-symbols "^1.0.3"
 
+has-unicode@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
+  integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==
+
 has-value@^0.3.1:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
@@ -10929,16 +16201,31 @@ hasown@^2.0.0, hasown@^2.0.1:
   dependencies:
     function-bind "^1.1.2"
 
-he@1.2.x, he@^1.2.0:
+he@1.2.0, he@1.2.x, he@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
   integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
 
+"heap@>= 0.2.0":
+  version "0.2.7"
+  resolved "https://registry.yarnpkg.com/heap/-/heap-0.2.7.tgz#1e6adf711d3f27ce35a81fe3b7bd576c2260a8fc"
+  integrity sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==
+
+helmet@^7.1.0:
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/helmet/-/helmet-7.1.0.tgz#287279e00f8a3763d5dccbaf1e5ee39b8c3784ca"
+  integrity sha512-g+HZqgfbpXdCkme/Cd/mZkV0aV3BZZZSugecH03kl38m/Kmdx8jKjBikpDj2cr+Iynv4KpYEviojNdTJActJAg==
+
 hex-color-regex@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
   integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
 
+hexoid@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18"
+  integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==
+
 hey-listen@^1.0.8:
   version "1.0.8"
   resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
@@ -11014,6 +16301,11 @@ html-entities@^1.3.1:
   resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.4.0.tgz#cfbd1b01d2afaf9adca1b10ae7dffab98c71d2dc"
   integrity sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==
 
+html-entities@^2.1.0, html-entities@^2.3.2, html-entities@^2.3.6:
+  version "2.5.2"
+  resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.5.2.tgz#201a3cf95d3a15be7099521620d19dfb4f65359f"
+  integrity sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==
+
 html-escaper@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
@@ -11072,7 +16364,25 @@ htmlparser2@^6.1.0:
     domutils "^2.5.2"
     entities "^2.0.0"
 
-http-cache-semantics@^4.0.0:
+http-assert@^1.3.0:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/http-assert/-/http-assert-1.5.0.tgz#c389ccd87ac16ed2dfa6246fd73b926aa00e6b8f"
+  integrity sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==
+  dependencies:
+    deep-equal "~1.0.1"
+    http-errors "~1.8.0"
+
+http-basic@^8.1.1:
+  version "8.1.3"
+  resolved "https://registry.yarnpkg.com/http-basic/-/http-basic-8.1.3.tgz#a7cabee7526869b9b710136970805b1004261bbf"
+  integrity sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==
+  dependencies:
+    caseless "^0.12.0"
+    concat-stream "^1.6.2"
+    http-response-object "^3.0.1"
+    parse-cache-control "^1.0.1"
+
+http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
   integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==
@@ -11093,6 +16403,17 @@ http-errors@2.0.0:
     statuses "2.0.1"
     toidentifier "1.0.1"
 
+http-errors@^1.6.3, http-errors@~1.8.0:
+  version "1.8.1"
+  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c"
+  integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==
+  dependencies:
+    depd "~1.1.2"
+    inherits "2.0.4"
+    setprototypeof "1.2.0"
+    statuses ">= 1.5.0 < 2"
+    toidentifier "1.0.1"
+
 http-errors@~1.6.2:
   version "1.6.3"
   resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
@@ -11122,6 +16443,14 @@ http-proxy-agent@^5.0.0:
     agent-base "6"
     debug "4"
 
+http-proxy-agent@^7.0.0:
+  version "7.0.2"
+  resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e"
+  integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==
+  dependencies:
+    agent-base "^7.1.0"
+    debug "^4.3.4"
+
 http-proxy-middleware@0.19.1:
   version "0.19.1"
   resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a"
@@ -11143,6 +16472,17 @@ http-proxy-middleware@^1.0.0:
     is-plain-obj "^3.0.0"
     micromatch "^4.0.2"
 
+http-proxy-middleware@^2.0.3:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f"
+  integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==
+  dependencies:
+    "@types/http-proxy" "^1.17.8"
+    http-proxy "^1.18.1"
+    is-glob "^4.0.1"
+    is-plain-obj "^3.0.0"
+    micromatch "^4.0.2"
+
 http-proxy@^1.17.0, http-proxy@^1.18.1:
   version "1.18.1"
   resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
@@ -11152,6 +16492,13 @@ http-proxy@^1.17.0, http-proxy@^1.18.1:
     follow-redirects "^1.0.0"
     requires-port "^1.0.0"
 
+http-response-object@^3.0.1:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/http-response-object/-/http-response-object-3.0.2.tgz#7f435bb210454e4360d074ef1f989d5ea8aa9810"
+  integrity sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==
+  dependencies:
+    "@types/node" "^10.0.3"
+
 http-server@^14.1.0:
   version "14.1.1"
   resolved "https://registry.yarnpkg.com/http-server/-/http-server-14.1.1.tgz#d60fbb37d7c2fdff0f0fbff0d0ee6670bd285e2e"
@@ -11223,6 +16570,21 @@ https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1:
     agent-base "6"
     debug "4"
 
+https-proxy-agent@^7.0.1:
+  version "7.0.4"
+  resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz#8e97b841a029ad8ddc8731f26595bad868cb4168"
+  integrity sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==
+  dependencies:
+    agent-base "^7.0.2"
+    debug "4"
+
+human-interval@~2:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/human-interval/-/human-interval-2.0.1.tgz#655baf606c7067bb26042dcae14ec777b099af15"
+  integrity sha512-r4Aotzf+OtKIGQCB3odUowy4GfUDTy3aTWTfLd7ZF2gBCy3XW3v/dJLRefZnOFFnjqs5B1TypvS8WarpBkYUNQ==
+  dependencies:
+    numbered "^1.1.0"
+
 human-signals@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
@@ -11259,7 +16621,7 @@ iconv-lite@0.4.24:
   dependencies:
     safer-buffer ">= 2.1.2 < 3"
 
-iconv-lite@0.6.3, iconv-lite@^0.6.2:
+iconv-lite@0.6.3, iconv-lite@^0.6.2, iconv-lite@^0.6.3:
   version "0.6.3"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
   integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
@@ -11273,6 +16635,11 @@ icss-utils@^4.0.0, icss-utils@^4.1.1:
   dependencies:
     postcss "^7.0.14"
 
+icss-utils@^5.0.0, icss-utils@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
+  integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
+
 idb-keyval@^6.2.1:
   version "6.2.1"
   resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.2.1.tgz#94516d625346d16f56f3b33855da11bfded2db33"
@@ -11312,7 +16679,7 @@ ignore@^4.0.3:
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
   integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
 
-ignore@^5.0.4, ignore@^5.2.0, ignore@^5.2.4:
+ignore@^5.0.4, ignore@^5.1.1, ignore@^5.1.9, ignore@^5.2.0, ignore@^5.3.1:
   version "5.3.1"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef"
   integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
@@ -11322,11 +16689,21 @@ image-size@~0.5.0:
   resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c"
   integrity sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==
 
+immediate@~3.0.5:
+  version "3.0.6"
+  resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
+  integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
+
 immutable@^4.0.0:
   version "4.3.5"
   resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.5.tgz#f8b436e66d59f99760dc577f5c99a4fd2a5cc5a0"
   integrity sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==
 
+immutable@^4.0.0-rc.12:
+  version "4.3.6"
+  resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.6.tgz#6a05f7858213238e587fb83586ffa3b4b27f0447"
+  integrity sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==
+
 import-cwd@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
@@ -11342,7 +16719,7 @@ import-fresh@^2.0.0:
     caller-path "^2.0.0"
     resolve-from "^3.0.0"
 
-import-fresh@^3.1.0, import-fresh@^3.2.1:
+import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.3.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
   integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
@@ -11357,6 +16734,16 @@ import-from@^2.1.0:
   dependencies:
     resolve-from "^3.0.0"
 
+import-in-the-middle@^1.6.0:
+  version "1.7.4"
+  resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.7.4.tgz#508da6e91cfa84f210dcdb6c0a91ab0c9e8b3ebc"
+  integrity sha512-Lk+qzWmiQuRPPulGQeK5qq0v32k2bHnWrRPFgqyvhw7Kkov5L6MOLOIU3pcWeujc9W4q54Cp3Q2WV16eQkc7Bg==
+  dependencies:
+    acorn "^8.8.2"
+    acorn-import-attributes "^1.9.5"
+    cjs-module-lexer "^1.2.2"
+    module-details-from-path "^1.0.3"
+
 import-local@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d"
@@ -11411,12 +16798,12 @@ inherits@2.0.3:
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
   integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==
 
-ini@2.0.0:
+ini@2.0.0, ini@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5"
   integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==
 
-ini@^1.3.4:
+ini@^1.3.4, ini@^1.3.5:
   version "1.3.8"
   resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
   integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
@@ -11438,6 +16825,11 @@ internal-slot@^1.0.7:
     hasown "^2.0.0"
     side-channel "^1.0.4"
 
+interpret@^1.0.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
+  integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
+
 invariant@2.2.4:
   version "2.2.4"
   resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
@@ -11445,6 +16837,13 @@ invariant@2.2.4:
   dependencies:
     loose-envify "^1.0.0"
 
+io-ts@1.10.4:
+  version "1.10.4"
+  resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-1.10.4.tgz#cd5401b138de88e4f920adbcb7026e2d1967e6e2"
+  integrity sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g==
+  dependencies:
+    fp-ts "^1.0.0"
+
 ioredis@^5.3.2:
   version "5.3.2"
   resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.3.2.tgz#9139f596f62fc9c72d873353ac5395bcf05709f7"
@@ -11460,11 +16859,24 @@ ioredis@^5.3.2:
     redis-parser "^3.0.0"
     standard-as-callback "^2.1.0"
 
+ip-address@^9.0.5:
+  version "9.0.5"
+  resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a"
+  integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==
+  dependencies:
+    jsbn "1.1.0"
+    sprintf-js "^1.1.3"
+
 ip-regex@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
   integrity sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==
 
+ip-regex@^4.0.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5"
+  integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==
+
 ip@^1.1.0, ip@^1.1.5:
   version "1.1.9"
   resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.9.tgz#8dfbcc99a754d07f425310b86a99546b1151e396"
@@ -11475,6 +16887,11 @@ ipaddr.js@1.9.1, ipaddr.js@^1.9.0:
   resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
   integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
 
+ipaddr.js@^2.0.1:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8"
+  integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==
+
 iron-webcrypto@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/iron-webcrypto/-/iron-webcrypto-1.0.0.tgz#e3b689c0c61b434a0a4cb82d0aeabbc8b672a867"
@@ -11557,6 +16974,11 @@ is-buffer@^1.1.5:
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
   integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
 
+is-buffer@^2.0.5:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191"
+  integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==
+
 is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7:
   version "1.2.7"
   resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
@@ -11569,7 +16991,7 @@ is-ci@^1.0.10:
   dependencies:
     ci-info "^1.5.0"
 
-is-ci@^3.0.0:
+is-ci@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867"
   integrity sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==
@@ -11723,6 +17145,35 @@ is-interactive@^1.0.0:
   resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
   integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==
 
+is-invalid-path@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/is-invalid-path/-/is-invalid-path-1.0.2.tgz#2f84731559f4936abcf1b227632719cf45c5dc0e"
+  integrity sha512-6KLcFrPCEP3AFXMfnWrIFkZpYNBVzZAoBJJDEZKtI3LXkaDjM3uFMJQjxiizUuZTZ9Oh9FNv/soXbx5TcpaDmA==
+
+is-ip@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/is-ip/-/is-ip-3.1.0.tgz#2ae5ddfafaf05cb8008a62093cf29734f657c5d8"
+  integrity sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==
+  dependencies:
+    ip-regex "^4.0.0"
+
+is-ipfs@^0.6.0:
+  version "0.6.3"
+  resolved "https://registry.yarnpkg.com/is-ipfs/-/is-ipfs-0.6.3.tgz#82a5350e0a42d01441c40b369f8791e91404c497"
+  integrity sha512-HyRot1dvLcxImtDqPxAaY1miO6WsiP/z7Yxpg2qpaLWv5UdhAPtLvHJ4kMLM0w8GSl8AFsVF23PHe1LzuWrUlQ==
+  dependencies:
+    bs58 "^4.0.1"
+    cids "~0.7.0"
+    mafmt "^7.0.0"
+    multiaddr "^7.2.1"
+    multibase "~0.6.0"
+    multihashes "~0.4.13"
+
+is-lambda@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5"
+  integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==
+
 is-nan@^1.3.2:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d"
@@ -11940,6 +17391,11 @@ isexe@^2.0.0:
   resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
   integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
 
+isexe@^3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/isexe/-/isexe-3.1.1.tgz#4a407e2bd78ddfb14bea0c27c6f7072dde775f0d"
+  integrity sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==
+
 isobject@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
@@ -11965,7 +17421,7 @@ isomorphic-timers-promises@^1.0.1:
   resolved "https://registry.yarnpkg.com/isomorphic-timers-promises/-/isomorphic-timers-promises-1.0.1.tgz#e4137c24dbc54892de8abae3a4b5c1ffff381598"
   integrity sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ==
 
-isomorphic-unfetch@3.1.0:
+isomorphic-unfetch@3.1.0, isomorphic-unfetch@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz#87341d5f4f7b63843d468438128cb087b7c3e98f"
   integrity sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==
@@ -11978,7 +17434,7 @@ isows@1.0.3:
   resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.3.tgz#93c1cf0575daf56e7120bab5c8c448b0809d0d74"
   integrity sha512-2cKei4vlmg2cxEjm3wVSqn8pcoRF/LX/wpifuuNquFO4SQmPwarClT+SUCA2lt+l581tTeZIPIZuIDo2jWN1fg==
 
-isstream@~0.1.2:
+isstream@0.1.x, isstream@~0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
   integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==
@@ -12045,7 +17501,7 @@ istanbul-reports@^3.1.3, istanbul-reports@^3.1.4, istanbul-reports@^3.1.6:
     html-escaper "^2.0.0"
     istanbul-lib-report "^3.0.0"
 
-jackspeak@^2.3.5:
+jackspeak@^2.3.5, jackspeak@^2.3.6:
   version "2.3.6"
   resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8"
   integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==
@@ -12191,7 +17647,7 @@ jest-environment-jsdom@29.4.3:
     jest-util "^29.4.3"
     jsdom "^20.0.0"
 
-jest-environment-node@^29.7.0:
+jest-environment-node@^29.4.1, jest-environment-node@^29.7.0:
   version "29.7.0"
   resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376"
   integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==
@@ -12433,7 +17889,16 @@ jest-watcher@^29.7.0:
     jest-util "^29.7.0"
     string-length "^4.0.1"
 
-jest-worker@^29.7.0:
+jest-worker@^27.4.5:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0"
+  integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==
+  dependencies:
+    "@types/node" "*"
+    merge-stream "^2.0.0"
+    supports-color "^8.0.0"
+
+jest-worker@^29.4.3, jest-worker@^29.7.0:
   version "29.7.0"
   resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a"
   integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==
@@ -12458,6 +17923,11 @@ jiti@^1.21.0:
   resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d"
   integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==
 
+jose@^4.10.3, jose@^4.14.6:
+  version "4.15.5"
+  resolved "https://registry.yarnpkg.com/jose/-/jose-4.15.5.tgz#6475d0f467ecd3c630a1b5dadd2735a7288df706"
+  integrity sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==
+
 jose@^4.14.4:
   version "4.15.4"
   resolved "https://registry.yarnpkg.com/jose/-/jose-4.15.4.tgz#02a9a763803e3872cf55f29ecef0dfdcc218cc03"
@@ -12474,6 +17944,11 @@ js-beautify@^1.14.9, js-beautify@^1.6.12:
     js-cookie "^3.0.5"
     nopt "^7.2.0"
 
+js-cookie@^2.2.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
+  integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
+
 js-cookie@^3.0.5:
   version "3.0.5"
   resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc"
@@ -12484,6 +17959,11 @@ js-message@1.0.7:
   resolved "https://registry.yarnpkg.com/js-message/-/js-message-1.0.7.tgz#fbddd053c7a47021871bb8b2c95397cc17c20e47"
   integrity sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==
 
+js-sdsl@^4.1.4:
+  version "4.4.2"
+  resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.4.2.tgz#2e3c031b1f47d3aca8b775532e3ebb0818e7f847"
+  integrity sha512-dwXFwByc/ajSV6m5bcKAPwe4yDDF6D614pxmIi5odytzxRlwqF6nwoiCek80Ixc7Cvma5awClxrzFtxCQvcM8w==
+
 js-sha3@0.8.0, js-sha3@^0.8.0:
   version "0.8.0"
   resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"
@@ -12494,6 +17974,11 @@ js-sha3@^0.5.7:
   resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7"
   integrity sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==
 
+js-sha3@^0.9.2, js-sha3@^0.9.3:
+  version "0.9.3"
+  resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.9.3.tgz#f0209432b23a66a0f6c7af592c26802291a75c2a"
+  integrity sha512-BcJPCQeLg6WjEx3FE591wVAevlli8lxsxm9/FzV4HXkV49TmBH38Yvrpce6fjbADGMKFrBMGTqrVz3qPIZ88Gg==
+
 "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -12509,6 +17994,14 @@ js-tokens@^8.0.2:
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-8.0.3.tgz#1c407ec905643603b38b6be6977300406ec48775"
   integrity sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==
 
+js-yaml@3.x, js-yaml@^3.10.0, js-yaml@^3.13.1:
+  version "3.14.1"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
+  integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
+  dependencies:
+    argparse "^1.0.7"
+    esprima "^4.0.0"
+
 js-yaml@4.1.0, js-yaml@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
@@ -12516,13 +18009,10 @@ js-yaml@4.1.0, js-yaml@^4.1.0:
   dependencies:
     argparse "^2.0.1"
 
-js-yaml@^3.10.0, js-yaml@^3.13.1:
-  version "3.14.1"
-  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
-  integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
-  dependencies:
-    argparse "^1.0.7"
-    esprima "^4.0.0"
+jsbn@1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040"
+  integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==
 
 jsbn@~0.1.0:
   version "0.1.1"
@@ -12595,11 +18085,23 @@ jsesc@^2.5.1:
   resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
   integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
 
+jsesc@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e"
+  integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==
+
 jsesc@~0.5.0:
   version "0.5.0"
   resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
   integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==
 
+json-bigint@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1"
+  integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==
+  dependencies:
+    bignumber.js "^9.0.0"
+
 json-buffer@3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
@@ -12610,7 +18112,7 @@ json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
   resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
   integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
 
-json-parse-even-better-errors@^2.3.0:
+json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1:
   version "2.3.1"
   resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
   integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
@@ -12642,6 +18144,11 @@ json-schema-traverse@^0.4.1:
   resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
   integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
 
+json-schema-traverse@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
+  integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
+
 json-schema@0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5"
@@ -12662,7 +18169,12 @@ json-stable-stringify@^1.0.2:
     jsonify "^0.0.1"
     object-keys "^1.1.1"
 
-json-stringify-safe@~5.0.1:
+json-stringify-deterministic@^1.0.7:
+  version "1.0.12"
+  resolved "https://registry.yarnpkg.com/json-stringify-deterministic/-/json-stringify-deterministic-1.0.12.tgz#aaa3f907466ed01e3afd77b898d0a2b3b132820a"
+  integrity sha512-q3PN0lbUdv0pmurkBNdJH3pfFvOTL/Zp0lquqpvcjfKzt6Y0j49EPHAmVHCAS4Ceq/Y+PejWTzyiVpoY71+D6g==
+
+json-stringify-safe@^5.0.0, json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
   integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==
@@ -12709,6 +18221,13 @@ jsonc-parser@^3.2.0:
   resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.1.tgz#031904571ccf929d7670ee8c547545081cb37f1a"
   integrity sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==
 
+jsonfile@^2.1.0:
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
+  integrity sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==
+  optionalDependencies:
+    graceful-fs "^4.1.6"
+
 jsonfile@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
@@ -12730,6 +18249,27 @@ jsonify@^0.0.1:
   resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978"
   integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==
 
+jsonschema@^1.2.4, jsonschema@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.1.tgz#cc4c3f0077fb4542982973d8a083b6b34f482dab"
+  integrity sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==
+
+jsonwebtoken@^9.0.0:
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3"
+  integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==
+  dependencies:
+    jws "^3.2.2"
+    lodash.includes "^4.3.0"
+    lodash.isboolean "^3.0.3"
+    lodash.isinteger "^4.0.4"
+    lodash.isnumber "^3.0.3"
+    lodash.isplainobject "^4.0.6"
+    lodash.isstring "^4.0.1"
+    lodash.once "^4.0.0"
+    ms "^2.1.1"
+    semver "^7.5.4"
+
 jsprim@^1.2.2:
   version "1.4.2"
   resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb"
@@ -12740,22 +18280,88 @@ jsprim@^1.2.2:
     json-schema "0.4.0"
     verror "1.10.0"
 
-jsprim@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d"
-  integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==
+jsprim@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d"
+  integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==
+  dependencies:
+    assert-plus "1.0.0"
+    extsprintf "1.3.0"
+    json-schema "0.4.0"
+    verror "1.10.0"
+
+jsrsasign@^10.5.25:
+  version "10.9.0"
+  resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-10.9.0.tgz#cc3f316e7e4c112a976193f9d2c93deb5a0745ee"
+  integrity sha512-QWLUikj1SBJGuyGK8tjKSx3K7Y69KYJnrs/pQ1KZ6wvZIkHkWjZ1PJDpuvc1/28c1uP0KW9qn1eI1LzHQqDOwQ==
+
+jszip@^3.10.1:
+  version "3.10.1"
+  resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2"
+  integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==
+  dependencies:
+    lie "~3.3.0"
+    pako "~1.0.2"
+    readable-stream "~2.3.6"
+    setimmediate "^1.0.5"
+
+jwa@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
+  integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
+  dependencies:
+    buffer-equal-constant-time "1.0.1"
+    ecdsa-sig-formatter "1.0.11"
+    safe-buffer "^5.0.1"
+
+jwa@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc"
+  integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==
+  dependencies:
+    buffer-equal-constant-time "1.0.1"
+    ecdsa-sig-formatter "1.0.11"
+    safe-buffer "^5.0.1"
+
+jwks-rsa@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/jwks-rsa/-/jwks-rsa-3.1.0.tgz#50406f23e38c9b2682cd437f824d7d61aa983171"
+  integrity sha512-v7nqlfezb9YfHHzYII3ef2a2j1XnGeSE/bK3WfumaYCqONAIstJbrEGapz4kadScZzEt7zYCN7bucj8C0Mv/Rg==
+  dependencies:
+    "@types/express" "^4.17.17"
+    "@types/jsonwebtoken" "^9.0.2"
+    debug "^4.3.4"
+    jose "^4.14.6"
+    limiter "^1.1.5"
+    lru-memoizer "^2.2.0"
+
+jws@^3.2.2:
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
+  integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
+  dependencies:
+    jwa "^1.4.1"
+    safe-buffer "^5.0.1"
+
+jws@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4"
+  integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==
   dependencies:
-    assert-plus "1.0.0"
-    extsprintf "1.3.0"
-    json-schema "0.4.0"
-    verror "1.10.0"
+    jwa "^2.0.0"
+    safe-buffer "^5.0.1"
 
 jwt-decode@^3.1.2:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59"
   integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==
 
-keccak@^3.0.0, keccak@^3.0.3:
+kareem@2.6.3:
+  version "2.6.3"
+  resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.6.3.tgz#23168ec8ffb6c1abfd31b7169a6fb1dd285992ac"
+  integrity sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==
+
+keccak@^3.0.0, keccak@^3.0.2, keccak@^3.0.3:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.4.tgz#edc09b89e633c0549da444432ecf062ffadee86d"
   integrity sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==
@@ -12764,6 +18370,13 @@ keccak@^3.0.0, keccak@^3.0.3:
     node-gyp-build "^4.2.0"
     readable-stream "^3.6.0"
 
+keygrip@~1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226"
+  integrity sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==
+  dependencies:
+    tsscmp "1.0.6"
+
 keyv@^4.0.0, keyv@^4.5.3:
   version "4.5.4"
   resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
@@ -12800,11 +18413,70 @@ kind-of@^6.0.2:
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
   integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
 
+klaw@^1.0.0:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439"
+  integrity sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==
+  optionalDependencies:
+    graceful-fs "^4.1.9"
+
 kleur@^3.0.3:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
   integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
 
+klona@^2.0.4, klona@^2.0.5:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22"
+  integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==
+
+koa-compose@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-4.1.0.tgz#507306b9371901db41121c812e923d0d67d3e877"
+  integrity sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==
+
+koa-convert@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/koa-convert/-/koa-convert-2.0.0.tgz#86a0c44d81d40551bae22fee6709904573eea4f5"
+  integrity sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==
+  dependencies:
+    co "^4.6.0"
+    koa-compose "^4.1.0"
+
+koa@^2.13.4:
+  version "2.15.3"
+  resolved "https://registry.yarnpkg.com/koa/-/koa-2.15.3.tgz#062809266ee75ce0c75f6510a005b0e38f8c519a"
+  integrity sha512-j/8tY9j5t+GVMLeioLaxweJiKUayFhlGqNTzf2ZGwL0ZCQijd2RLHK0SLW5Tsko8YyyqCZC2cojIb0/s62qTAg==
+  dependencies:
+    accepts "^1.3.5"
+    cache-content-type "^1.0.0"
+    content-disposition "~0.5.2"
+    content-type "^1.0.4"
+    cookies "~0.9.0"
+    debug "^4.3.2"
+    delegates "^1.0.0"
+    depd "^2.0.0"
+    destroy "^1.0.4"
+    encodeurl "^1.0.2"
+    escape-html "^1.0.3"
+    fresh "~0.5.2"
+    http-assert "^1.3.0"
+    http-errors "^1.6.3"
+    is-generator-function "^1.0.7"
+    koa-compose "^4.1.0"
+    koa-convert "^2.0.0"
+    on-finished "^2.3.0"
+    only "~0.0.2"
+    parseurl "^1.3.2"
+    statuses "^1.5.0"
+    type-is "^1.6.16"
+    vary "^1.1.2"
+
+kuler@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3"
+  integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==
+
 launch-editor-middleware@^2.2.1:
   version "2.6.1"
   resolved "https://registry.yarnpkg.com/launch-editor-middleware/-/launch-editor-middleware-2.6.1.tgz#7f2f400d8dda2283b69d02e9d83b1d272fef2bfb"
@@ -12812,7 +18484,7 @@ launch-editor-middleware@^2.2.1:
   dependencies:
     launch-editor "^2.6.1"
 
-launch-editor@^2.2.1, launch-editor@^2.6.1:
+launch-editor@^2.2.1, launch-editor@^2.6.0, launch-editor@^2.6.1:
   version "2.6.1"
   resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.6.1.tgz#f259c9ef95cbc9425620bbbd14b468fcdb4ffe3c"
   integrity sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==
@@ -12825,6 +18497,13 @@ lazy-ass@^1.6.0:
   resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513"
   integrity sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==
 
+less-loader@11.1.0:
+  version "11.1.0"
+  resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-11.1.0.tgz#a452384259bdf8e4f6d5fdcc39543609e6313f82"
+  integrity sha512-C+uDBV7kS7W5fJlUjq5mPBeBVhYpTIm5gB09APT9o3n/ILeaXVsiSFTbZpTJCJwQ/Crczfn3DmfQFwxYusWFug==
+  dependencies:
+    klona "^2.0.4"
+
 less-loader@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-5.0.0.tgz#498dde3a6c6c4f887458ee9ed3f086a12ad1b466"
@@ -12834,6 +18513,23 @@ less-loader@^5.0.0:
     loader-utils "^1.1.0"
     pify "^4.0.1"
 
+less@4.1.3:
+  version "4.1.3"
+  resolved "https://registry.yarnpkg.com/less/-/less-4.1.3.tgz#175be9ddcbf9b250173e0a00b4d6920a5b770246"
+  integrity sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==
+  dependencies:
+    copy-anything "^2.0.1"
+    parse-node-version "^1.0.1"
+    tslib "^2.3.0"
+  optionalDependencies:
+    errno "^0.1.1"
+    graceful-fs "^4.1.2"
+    image-size "~0.5.0"
+    make-dir "^2.1.0"
+    mime "^1.4.1"
+    needle "^3.1.0"
+    source-map "~0.6.0"
+
 less@^3.0.4:
   version "3.13.1"
   resolved "https://registry.yarnpkg.com/less/-/less-3.13.1.tgz#0ebc91d2a0e9c0c6735b83d496b0ab0583077909"
@@ -12850,6 +18546,28 @@ less@^3.0.4:
     native-request "^1.0.5"
     source-map "~0.6.0"
 
+level-supports@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/level-supports/-/level-supports-4.0.1.tgz#431546f9d81f10ff0fea0e74533a0e875c08c66a"
+  integrity sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==
+
+level-transcoder@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/level-transcoder/-/level-transcoder-1.0.1.tgz#f8cef5990c4f1283d4c86d949e73631b0bc8ba9c"
+  integrity sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==
+  dependencies:
+    buffer "^6.0.3"
+    module-error "^1.0.1"
+
+level@^8.0.0:
+  version "8.0.1"
+  resolved "https://registry.yarnpkg.com/level/-/level-8.0.1.tgz#737161db1bc317193aca4e7b6f436e7e1df64379"
+  integrity sha512-oPBGkheysuw7DmzFQYyFe8NAia5jFLAgEnkgWnK3OXAuJr8qFT+xBQIwokAZPME2bhPFzS8hlYcL16m8UZrtwQ==
+  dependencies:
+    abstract-level "^1.0.4"
+    browser-level "^1.0.1"
+    classic-level "^1.2.0"
+
 leven@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
@@ -12863,6 +18581,43 @@ levn@^0.4.1:
     prelude-ls "^1.2.1"
     type-check "~0.4.0"
 
+levn@~0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
+  integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==
+  dependencies:
+    prelude-ls "~1.1.2"
+    type-check "~0.3.2"
+
+libphonenumber-js@^1.10.53:
+  version "1.11.1"
+  resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.1.tgz#2596683e1876bfee74082bb49339fe0a85ae34f9"
+  integrity sha512-Wze1LPwcnzvcKGcRHFGFECTaLzxOtujwpf924difr5zniyYv1C2PiW0419qDR7m8lKDxsImu5mwxFuXhXpjmvw==
+
+license-webpack-plugin@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz#1e18442ed20b754b82f1adeff42249b81d11aec6"
+  integrity sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==
+  dependencies:
+    webpack-sources "^3.0.0"
+
+lie@~3.3.0:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
+  integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==
+  dependencies:
+    immediate "~3.0.5"
+
+lilconfig@^3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.1.tgz#9d8a246fa753106cfc205fd2d77042faca56e5e3"
+  integrity sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==
+
+limiter@^1.1.5:
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.5.tgz#8f92a25b3b16c6131293a0cc834b4a838a2aa7c2"
+  integrity sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==
+
 lines-and-columns@^1.1.6:
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
@@ -12966,6 +18721,11 @@ loader-runner@^2.3.1, loader-runner@^2.4.0:
   resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
   integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==
 
+loader-runner@^4.2.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1"
+  integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
+
 loader-utils@^0.2.16:
   version "0.2.17"
   resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348"
@@ -12985,7 +18745,7 @@ loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3:
     emojis-list "^3.0.0"
     json5 "^1.0.1"
 
-loader-utils@^2.0.0:
+loader-utils@^2.0.0, loader-utils@^2.0.3, loader-utils@^2.0.4:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
   integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
@@ -13007,6 +18767,14 @@ local-pkg@^0.5.0:
     mlly "^1.4.2"
     pkg-types "^1.0.3"
 
+locate-path@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
+  integrity sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==
+  dependencies:
+    p-locate "^2.0.0"
+    path-exists "^3.0.0"
+
 locate-path@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
@@ -13029,11 +18797,23 @@ locate-path@^6.0.0:
   dependencies:
     p-locate "^5.0.0"
 
+locate-path@^7.1.0:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-7.2.0.tgz#69cb1779bd90b35ab1e771e1f2f89a202c2a8a8a"
+  integrity sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==
+  dependencies:
+    p-locate "^6.0.0"
+
 lodash-es@latest:
   version "4.17.21"
   resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
   integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
 
+lodash.camelcase@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
+  integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==
+
 lodash.clonedeep@^4.5.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
@@ -13054,16 +18834,56 @@ lodash.defaultsdeep@^4.6.1:
   resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz#512e9bd721d272d94e3d3a63653fa17516741ca6"
   integrity sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==
 
+lodash.get@^4.4.2:
+  version "4.4.2"
+  resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
+  integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
+
+lodash.groupby@^4.6.0:
+  version "4.6.0"
+  resolved "https://registry.yarnpkg.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz#0b08a1dcf68397c397855c3239783832df7403d1"
+  integrity sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==
+
+lodash.includes@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
+  integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==
+
 lodash.isarguments@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
   integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==
 
+lodash.isboolean@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
+  integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==
+
 lodash.isequal@4.5.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
   integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
 
+lodash.isinteger@^4.0.4:
+  version "4.0.4"
+  resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
+  integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==
+
+lodash.isnumber@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
+  integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==
+
+lodash.isplainobject@^4.0.6:
+  version "4.0.6"
+  resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
+  integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
+
+lodash.isstring@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
+  integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
+
 lodash.kebabcase@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
@@ -13084,16 +18904,26 @@ lodash.merge@^4.6.2:
   resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
   integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
 
-lodash.once@^4.1.1:
+lodash.once@^4.0.0, lodash.once@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
   integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
 
+lodash.snakecase@4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d"
+  integrity sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==
+
 lodash.transform@^4.6.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/lodash.transform/-/lodash.transform-4.6.0.tgz#12306422f63324aed8483d3f38332b5f670547a0"
   integrity sha512-LO37ZnhmBVx0GvOU/caQuipEh4GN82TcWv3yHlebGDgOxbxiwwzW5Pcx2AcvpIv2WmvmSMoC492yQFNhy/l/UQ==
 
+lodash.truncate@^4.4.2:
+  version "4.4.2"
+  resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
+  integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==
+
 lodash.uniq@^4.5.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
@@ -13104,14 +18934,7 @@ lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
 
-log-symbols@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
-  integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==
-  dependencies:
-    chalk "^2.0.1"
-
-log-symbols@^4.0.0:
+log-symbols@4.1.0, log-symbols@^4.0.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
   integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==
@@ -13119,6 +18942,13 @@ log-symbols@^4.0.0:
     chalk "^4.1.0"
     is-unicode-supported "^0.1.0"
 
+log-symbols@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
+  integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==
+  dependencies:
+    chalk "^2.0.1"
+
 log-update@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1"
@@ -13129,12 +18959,35 @@ log-update@^4.0.0:
     slice-ansi "^4.0.0"
     wrap-ansi "^6.2.0"
 
+log4js@^6.9.1:
+  version "6.9.1"
+  resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.9.1.tgz#aba5a3ff4e7872ae34f8b4c533706753709e38b6"
+  integrity sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==
+  dependencies:
+    date-format "^4.0.14"
+    debug "^4.3.4"
+    flatted "^3.2.7"
+    rfdc "^1.3.0"
+    streamroller "^3.1.5"
+
+logform@^2.3.2, logform@^2.4.0:
+  version "2.6.0"
+  resolved "https://registry.yarnpkg.com/logform/-/logform-2.6.0.tgz#8c82a983f05d6eaeb2d75e3decae7a768b2bf9b5"
+  integrity sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==
+  dependencies:
+    "@colors/colors" "1.6.0"
+    "@types/triple-beam" "^1.3.2"
+    fecha "^4.2.0"
+    ms "^2.1.1"
+    safe-stable-stringify "^2.3.1"
+    triple-beam "^1.3.0"
+
 loglevel@^1.6.8, loglevel@^1.8.1:
   version "1.9.1"
   resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.9.1.tgz#d63976ac9bcd03c7c873116d41c2a85bafff1be7"
   integrity sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==
 
-long@^5.0.0:
+long@^5.0.0, long@^5.2.3:
   version "5.2.3"
   resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1"
   integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==
@@ -13158,6 +19011,13 @@ lower-case@^1.1.1:
   resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
   integrity sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==
 
+lower-case@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
+  integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==
+  dependencies:
+    tslib "^2.0.3"
+
 lowercase-keys@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
@@ -13168,11 +19028,23 @@ lowercase-keys@^3.0.0:
   resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2"
   integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==
 
+lru-cache@6.0.0, lru-cache@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
+  integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
+  dependencies:
+    yallist "^4.0.0"
+
 lru-cache@^10.0.1, lru-cache@^10.0.2, "lru-cache@^9.1.1 || ^10.0.0":
   version "10.2.0"
   resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3"
   integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==
 
+lru-cache@^10.2.0:
+  version "10.2.2"
+  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.2.tgz#48206bc114c1252940c41b25b41af5b545aca878"
+  integrity sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==
+
 lru-cache@^4.0.1, lru-cache@^4.1.2:
   version "4.1.5"
   resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
@@ -13188,12 +19060,42 @@ lru-cache@^5.1.1:
   dependencies:
     yallist "^3.0.2"
 
-lru-cache@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
-  integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
+lru-memoizer@^2.2.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/lru-memoizer/-/lru-memoizer-2.3.0.tgz#ef0fbc021bceb666794b145eefac6be49dc47f31"
+  integrity sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==
   dependencies:
-    yallist "^4.0.0"
+    lodash.clonedeep "^4.5.0"
+    lru-cache "6.0.0"
+
+lru_map@^0.3.3:
+  version "0.3.3"
+  resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd"
+  integrity sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==
+
+lusca@^1.7.0:
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/lusca/-/lusca-1.7.0.tgz#a5d979f1b51776e60d41e0ca98f886f1b8b95502"
+  integrity sha512-msnrplCfY7zaqlZBDEloCIKld+RUeMZVeWzSPaGUKeRXFlruNSdKg2XxCyR+zj6BqzcXhXlRnvcvx6rAGgsvMA==
+  dependencies:
+    tsscmp "^1.0.5"
+
+luxon@^3, luxon@^3.2.1:
+  version "3.4.4"
+  resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.4.4.tgz#cf20dc27dc532ba41a169c43fdcc0063601577af"
+  integrity sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==
+
+mafmt@^7.0.0:
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/mafmt/-/mafmt-7.1.0.tgz#4126f6d0eded070ace7dbbb6fb04977412d380b5"
+  integrity sha512-vpeo9S+hepT3k2h5iFxzEHvvR0GPBx9uKaErmnRzYNcaKb03DgOArjEMlgG4a9LcuZZ89a3I8xbeto487n26eA==
+  dependencies:
+    multiaddr "^7.3.0"
+
+magic-bytes.js@^1.10.0:
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz#c41cf4bc2f802992b05e64962411c9dd44fdef92"
+  integrity sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==
 
 magic-string@0.27.0:
   version "0.27.0"
@@ -13252,6 +19154,24 @@ make-error@1.x, make-error@^1.1.1:
   resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
   integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
 
+make-fetch-happen@^13.0.0:
+  version "13.0.1"
+  resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz#273ba2f78f45e1f3a6dca91cede87d9fa4821e36"
+  integrity sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==
+  dependencies:
+    "@npmcli/agent" "^2.0.0"
+    cacache "^18.0.0"
+    http-cache-semantics "^4.1.1"
+    is-lambda "^1.0.1"
+    minipass "^7.0.2"
+    minipass-fetch "^3.0.0"
+    minipass-flush "^1.0.5"
+    minipass-pipeline "^1.2.4"
+    negotiator "^0.6.3"
+    proc-log "^4.2.0"
+    promise-retry "^2.0.1"
+    ssri "^10.0.0"
+
 makeerror@1.0.12:
   version "1.0.12"
   resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a"
@@ -13271,11 +19191,21 @@ map-visit@^1.0.0:
   dependencies:
     object-visit "^1.0.0"
 
+markdown-table@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.3.tgz#9fcb69bcfdb8717bfd0398c6ec2d93036ef8de60"
+  integrity sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==
+
 marked@^12.0.2:
   version "12.0.2"
   resolved "https://registry.yarnpkg.com/marked/-/marked-12.0.2.tgz#b31578fe608b599944c69807b00f18edab84647e"
   integrity sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==
 
+mcl-wasm@^0.7.1:
+  version "0.7.9"
+  resolved "https://registry.yarnpkg.com/mcl-wasm/-/mcl-wasm-0.7.9.tgz#c1588ce90042a8700c3b60e40efb339fc07ab87f"
+  integrity sha512-iJIUcQWA88IJB/5L15GnJVnSQJmf/YaxxV6zRavv83HILHaJQb6y0iFyDMdDO0gN8X37tdxmAOrH/P8B6RB8sQ==
+
 md5.js@^1.3.4:
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
@@ -13290,6 +19220,16 @@ mdn-data@2.0.14:
   resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
   integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==
 
+mdn-data@2.0.28:
+  version "2.0.28"
+  resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba"
+  integrity sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==
+
+mdn-data@2.0.30:
+  version "2.0.30"
+  resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc"
+  integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==
+
 mdn-data@2.0.4:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"
@@ -13300,7 +19240,7 @@ media-typer@0.3.0:
   resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
   integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
 
-memfs@^3.1.2:
+memfs@^3.1.2, memfs@^3.4.1, memfs@^3.4.3:
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6"
   integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ==
@@ -13323,6 +19263,25 @@ memory-fs@^0.5.0:
     errno "^0.1.3"
     readable-stream "^2.0.1"
 
+memory-level@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/memory-level/-/memory-level-1.0.0.tgz#7323c3fd368f9af2f71c3cd76ba403a17ac41692"
+  integrity sha512-UXzwewuWeHBz5krr7EvehKcmLFNoXxGcvuYhC41tRnkrTbJohtS7kVn9akmgirtRygg+f7Yjsfi8Uu5SGSQ4Og==
+  dependencies:
+    abstract-level "^1.0.0"
+    functional-red-black-tree "^1.0.1"
+    module-error "^1.0.1"
+
+memory-pager@^1.0.2:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5"
+  integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==
+
+memorystream@^0.3.1:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2"
+  integrity sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==
+
 merge-descriptors@1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
@@ -13352,7 +19311,7 @@ merge2@^1.2.3, merge2@^1.3.0, merge2@^1.4.1:
   resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
   integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
 
-methods@~1.1.2:
+methods@^1.1.2, methods@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
   integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
@@ -13394,6 +19353,19 @@ micromatch@^4.0.0, micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5:
     braces "^3.0.2"
     picomatch "^2.3.1"
 
+migrate-mongo@^11.0.0:
+  version "11.0.0"
+  resolved "https://registry.yarnpkg.com/migrate-mongo/-/migrate-mongo-11.0.0.tgz#d1b2291624fe8e134a0666ca77ad2fa18f42e337"
+  integrity sha512-GB/gHzUwp/fL1w6ksNGihTyb+cSrm6NbVLlz1OSkQKaLlzAXMwH7iKK2ZS7W5v+I8vXiY2rL58WTUZSAL6QR+A==
+  dependencies:
+    cli-table3 "^0.6.1"
+    commander "^9.1.0"
+    date-fns "^2.28.0"
+    fn-args "^5.0.0"
+    fs-extra "^10.0.1"
+    lodash "^4.17.21"
+    p-each-series "^2.2.0"
+
 miller-rabin@^4.0.0:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
@@ -13407,7 +19379,7 @@ mime-db@1.52.0, "mime-db@>= 1.43.0 < 2":
   resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
   integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
 
-mime-types@^2.1.12, mime-types@^2.1.16, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34:
+mime-types@^2.1.12, mime-types@^2.1.16, mime-types@^2.1.18, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34:
   version "2.1.35"
   resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
   integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
@@ -13419,7 +19391,7 @@ mime@1.6.0, mime@^1.4.1, mime@^1.6.0:
   resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
   integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
 
-mime@^2.4.4:
+mime@2.6.0, mime@^2.4.4:
   version "2.6.0"
   resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
   integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
@@ -13449,6 +19421,11 @@ mimic-response@^1.0.0:
   resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
   integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
 
+mimic-response@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43"
+  integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==
+
 mimic-response@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
@@ -13471,6 +19448,13 @@ mini-css-extract-plugin@^0.9.0:
     schema-utils "^1.0.0"
     webpack-sources "^1.1.0"
 
+mini-css-extract-plugin@~2.4.7:
+  version "2.4.7"
+  resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.4.7.tgz#b9f4c4f4d727c7a3cd52a11773bb739f00177fac"
+  integrity sha512-euWmddf0sk9Nv1O0gfeeUAvAkoSlWncNLF77C0TP2+WoPvy8mAHKOzMajcCz2dzvyt3CNgxb1obIEVFIRxaipg==
+  dependencies:
+    schema-utils "^4.0.0"
+
 minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
@@ -13481,6 +19465,20 @@ minimalistic-crypto-utils@^1.0.1:
   resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
   integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==
 
+"minimatch@2 || 3", minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
+  integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+  dependencies:
+    brace-expansion "^1.1.7"
+
+minimatch@5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b"
+  integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==
+  dependencies:
+    brace-expansion "^2.0.1"
+
 minimatch@9.0.1:
   version "9.0.1"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.1.tgz#8a555f541cf976c622daf078bb28f29fb927c253"
@@ -13495,13 +19493,6 @@ minimatch@9.0.3, minimatch@^9.0.1, minimatch@^9.0.3:
   dependencies:
     brace-expansion "^2.0.1"
 
-minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
-  version "3.1.2"
-  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
-  integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
-  dependencies:
-    brace-expansion "^1.1.7"
-
 minimatch@^5.0.1:
   version "5.1.6"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
@@ -13516,11 +19507,57 @@ minimatch@^7.4.1:
   dependencies:
     brace-expansion "^2.0.1"
 
+minimatch@^9.0.4:
+  version "9.0.4"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51"
+  integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==
+  dependencies:
+    brace-expansion "^2.0.1"
+
 minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.8:
   version "1.2.8"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
   integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
 
+minipass-collect@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-2.0.1.tgz#1621bc77e12258a12c60d34e2276ec5c20680863"
+  integrity sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==
+  dependencies:
+    minipass "^7.0.3"
+
+minipass-fetch@^3.0.0:
+  version "3.0.5"
+  resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-3.0.5.tgz#f0f97e40580affc4a35cc4a1349f05ae36cb1e4c"
+  integrity sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==
+  dependencies:
+    minipass "^7.0.3"
+    minipass-sized "^1.0.3"
+    minizlib "^2.1.2"
+  optionalDependencies:
+    encoding "^0.1.13"
+
+minipass-flush@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373"
+  integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==
+  dependencies:
+    minipass "^3.0.0"
+
+minipass-pipeline@^1.2.4:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c"
+  integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==
+  dependencies:
+    minipass "^3.0.0"
+
+minipass-sized@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70"
+  integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==
+  dependencies:
+    minipass "^3.0.0"
+
 minipass@^2.6.0, minipass@^2.9.0:
   version "2.9.0"
   resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6"
@@ -13529,7 +19566,7 @@ minipass@^2.6.0, minipass@^2.9.0:
     safe-buffer "^5.1.2"
     yallist "^3.0.0"
 
-minipass@^3.1.1:
+minipass@^3.0.0, minipass@^3.1.1:
   version "3.3.6"
   resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a"
   integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==
@@ -13541,11 +19578,21 @@ minipass@^4.2.4:
   resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a"
   integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==
 
+minipass@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d"
+  integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==
+
 "minipass@^5.0.0 || ^6.0.2 || ^7.0.0":
   version "7.0.4"
   resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c"
   integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==
 
+minipass@^7.0.2, minipass@^7.0.3, minipass@^7.0.4:
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.1.tgz#f7f85aff59aa22f110b20e27692465cf3bf89481"
+  integrity sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==
+
 minizlib@^1.3.3:
   version "1.3.3"
   resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d"
@@ -13553,6 +19600,14 @@ minizlib@^1.3.3:
   dependencies:
     minipass "^2.9.0"
 
+minizlib@^2.1.1, minizlib@^2.1.2:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
+  integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
+  dependencies:
+    minipass "^3.0.0"
+    yallist "^4.0.0"
+
 mipd@0.0.5:
   version "0.0.5"
   resolved "https://registry.yarnpkg.com/mipd/-/mipd-0.0.5.tgz#367ee796531c23f0631f129038700b1406663aec"
@@ -13589,6 +19644,11 @@ mixpanel-browser@^2.45.0:
   resolved "https://registry.yarnpkg.com/mixpanel-browser/-/mixpanel-browser-2.49.0.tgz#de3f4f2d0f3a32b4babf6d827ef983a9fd48a711"
   integrity sha512-RZJCO7XXuuHBAWG5fd9Mavz994M7v7W3Qiaq8NzmN631pa4BQ0vNZQtRFqKcCCOBn4xqOZbX2GkuC7ZkQoL4cQ==
 
+mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
+  version "0.5.3"
+  resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
+  integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
+
 mkdirp-promise@^5.0.1:
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz#e9b8f68e552c68a9c1713b84883f7a1dd039b8a1"
@@ -13601,14 +19661,14 @@ mkdirp@*:
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50"
   integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==
 
-mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@^0.5.6, mkdirp@~0.5.1:
+mkdirp@0.5.x, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@^0.5.5, mkdirp@^0.5.6, mkdirp@~0.5.1:
   version "0.5.6"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
   integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
   dependencies:
     minimist "^1.2.6"
 
-mkdirp@~1.0.4:
+mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
   integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
@@ -13618,25 +19678,160 @@ mlly@^1.2.0, mlly@^1.5.0:
   resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.6.0.tgz#0ecfbddc706857f5e170ccd28c6b0b9c81d3f548"
   integrity sha512-YOvg9hfYQmnaB56Yb+KrJE2u0Yzz5zR+sLejEvF4fzwzV1Al6hkf2vyHTwqCRyv0hCi9rVCqVoXpyYevQIRwLQ==
   dependencies:
-    acorn "^8.11.3"
-    pathe "^1.1.2"
-    pkg-types "^1.0.3"
-    ufo "^1.3.2"
+    acorn "^8.11.3"
+    pathe "^1.1.2"
+    pkg-types "^1.0.3"
+    ufo "^1.3.2"
+
+mlly@^1.4.2:
+  version "1.6.1"
+  resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.6.1.tgz#0983067dc3366d6314fc5e12712884e6978d028f"
+  integrity sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==
+  dependencies:
+    acorn "^8.11.3"
+    pathe "^1.1.2"
+    pkg-types "^1.0.3"
+    ufo "^1.3.2"
+
+mnemonist@^0.38.0:
+  version "0.38.5"
+  resolved "https://registry.yarnpkg.com/mnemonist/-/mnemonist-0.38.5.tgz#4adc7f4200491237fe0fa689ac0b86539685cade"
+  integrity sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==
+  dependencies:
+    obliterator "^2.0.0"
+
+mocha@^10.0.0, mocha@^10.2.0:
+  version "10.4.0"
+  resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.4.0.tgz#ed03db96ee9cfc6d20c56f8e2af07b961dbae261"
+  integrity sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==
+  dependencies:
+    ansi-colors "4.1.1"
+    browser-stdout "1.3.1"
+    chokidar "3.5.3"
+    debug "4.3.4"
+    diff "5.0.0"
+    escape-string-regexp "4.0.0"
+    find-up "5.0.0"
+    glob "8.1.0"
+    he "1.2.0"
+    js-yaml "4.1.0"
+    log-symbols "4.1.0"
+    minimatch "5.0.1"
+    ms "2.1.3"
+    serialize-javascript "6.0.0"
+    strip-json-comments "3.1.1"
+    supports-color "8.1.1"
+    workerpool "6.2.1"
+    yargs "16.2.0"
+    yargs-parser "20.2.4"
+    yargs-unparser "2.0.0"
+
+mock-fs@^4.1.0:
+  version "4.14.0"
+  resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.14.0.tgz#ce5124d2c601421255985e6e94da80a7357b1b18"
+  integrity sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==
+
+module-details-from-path@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b"
+  integrity sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==
+
+module-error@^1.0.1, module-error@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/module-error/-/module-error-1.0.2.tgz#8d1a48897ca883f47a45816d4fb3e3c6ba404d86"
+  integrity sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==
+
+moment-timezone@^0.5.13:
+  version "0.5.45"
+  resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.45.tgz#cb685acd56bac10e69d93c536366eb65aa6bcf5c"
+  integrity sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ==
+  dependencies:
+    moment "^2.29.4"
+
+moment@^2.29.4:
+  version "2.30.1"
+  resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae"
+  integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==
+
+mongodb-connection-string-url@^2.6.0:
+  version "2.6.0"
+  resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz#57901bf352372abdde812c81be47b75c6b2ec5cf"
+  integrity sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==
+  dependencies:
+    "@types/whatwg-url" "^8.2.1"
+    whatwg-url "^11.0.0"
+
+mongodb-connection-string-url@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz#c13e6ac284ae401752ebafdb8cd7f16c6723b141"
+  integrity sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==
+  dependencies:
+    "@types/whatwg-url" "^11.0.2"
+    whatwg-url "^13.0.0"
+
+mongodb@6.5.0:
+  version "6.5.0"
+  resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-6.5.0.tgz#3735b4fba085b26ca06f7744e9639bc538e93d87"
+  integrity sha512-Fozq68InT+JKABGLqctgtb8P56pRrJFkbhW0ux+x1mdHeyinor8oNzJqwLjV/t5X5nJGfTlluxfyMnOXNggIUA==
+  dependencies:
+    "@mongodb-js/saslprep" "^1.1.5"
+    bson "^6.4.0"
+    mongodb-connection-string-url "^3.0.0"
+
+mongodb@^4:
+  version "4.17.2"
+  resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-4.17.2.tgz#237c0534e36a3449bd74c6bf6d32f87a1ca7200c"
+  integrity sha512-mLV7SEiov2LHleRJPMPrK2PMyhXFZt2UQLC4VD4pnth3jMjYKHhtqfwwkkvS/NXuo/Fp3vbhaNcXrIDaLRb9Tg==
+  dependencies:
+    bson "^4.7.2"
+    mongodb-connection-string-url "^2.6.0"
+    socks "^2.7.1"
+  optionalDependencies:
+    "@aws-sdk/credential-providers" "^3.186.0"
+    "@mongodb-js/saslprep" "^1.1.0"
+
+mongodb@^6.1.0:
+  version "6.6.2"
+  resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-6.6.2.tgz#7ecdd788e9162f6c5726cef40bdd2813cc01e56c"
+  integrity sha512-ZF9Ugo2JCG/GfR7DEb4ypfyJJyiKbg5qBYKRintebj8+DNS33CyGMkWbrS9lara+u+h+yEOGSRiLhFO/g1s1aw==
+  dependencies:
+    "@mongodb-js/saslprep" "^1.1.5"
+    bson "^6.7.0"
+    mongodb-connection-string-url "^3.0.0"
+
+mongoose@^8.3.5:
+  version "8.3.5"
+  resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-8.3.5.tgz#8eb81ecd4c2bda03678e73874594c777512e9973"
+  integrity sha512-2zqeAjHjCqT1o5HeUCvkE9tUHsXwemnwEZ2SKnUxsaP8p1a+UcSQSNbnSuOzUVePMwLETrsvLIRdFLjsNfCgWA==
+  dependencies:
+    bson "^6.5.0"
+    kareem "2.6.3"
+    mongodb "6.5.0"
+    mpath "0.9.0"
+    mquery "5.0.0"
+    ms "2.1.3"
+    sift "17.1.3"
 
-mlly@^1.4.2:
-  version "1.6.1"
-  resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.6.1.tgz#0983067dc3366d6314fc5e12712884e6978d028f"
-  integrity sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==
+morgan-body@^2.6.9:
+  version "2.6.9"
+  resolved "https://registry.yarnpkg.com/morgan-body/-/morgan-body-2.6.9.tgz#92a864c66bef644019572a6611ef702fa42f2bad"
+  integrity sha512-O0dlv/V67gSszFo4rraZ7nMhD+iuWOg2w7hOLcAC6S+nF26Q8XvikGFjoJ7+dVuh49BBsIvQqRUGbs9e8keFKw==
   dependencies:
-    acorn "^8.11.3"
-    pathe "^1.1.2"
-    pkg-types "^1.0.3"
-    ufo "^1.3.2"
+    es6-symbol "^3.1.3"
+    moment-timezone "^0.5.13"
+    on-finished "^2.3.0"
+    on-headers "^1.0.1"
 
-mock-fs@^4.1.0:
-  version "4.14.0"
-  resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.14.0.tgz#ce5124d2c601421255985e6e94da80a7357b1b18"
-  integrity sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==
+morgan@^1.10.0:
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7"
+  integrity sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==
+  dependencies:
+    basic-auth "~2.0.1"
+    debug "2.6.9"
+    depd "~2.0.0"
+    on-finished "~2.3.0"
+    on-headers "~1.0.2"
 
 motion@10.16.2:
   version "10.16.2"
@@ -13662,6 +19857,18 @@ move-concurrently@^1.0.1:
     rimraf "^2.5.4"
     run-queue "^1.0.3"
 
+mpath@0.9.0:
+  version "0.9.0"
+  resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.9.0.tgz#0c122fe107846e31fc58c75b09c35514b3871904"
+  integrity sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==
+
+mquery@5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/mquery/-/mquery-5.0.0.tgz#a95be5dfc610b23862df34a47d3e5d60e110695d"
+  integrity sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==
+  dependencies:
+    debug "4.x"
+
 mri@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
@@ -13687,6 +19894,31 @@ ms@2.1.3, ms@^2.1.1:
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
   integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
 
+multer@^1.4.5-lts.1:
+  version "1.4.5-lts.1"
+  resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.5-lts.1.tgz#803e24ad1984f58edffbc79f56e305aec5cfd1ac"
+  integrity sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==
+  dependencies:
+    append-field "^1.0.0"
+    busboy "^1.0.0"
+    concat-stream "^1.5.2"
+    mkdirp "^0.5.4"
+    object-assign "^4.1.1"
+    type-is "^1.6.4"
+    xtend "^4.0.0"
+
+multiaddr@^7.2.1, multiaddr@^7.3.0:
+  version "7.5.0"
+  resolved "https://registry.yarnpkg.com/multiaddr/-/multiaddr-7.5.0.tgz#976c88e256e512263445ab03b3b68c003d5f485e"
+  integrity sha512-GvhHsIGDULh06jyb6ev+VfREH9evJCFIRnh3jUt9iEZ6XDbyoisZRFEI9bMvK/AiR6y66y6P+eoBw9mBYMhMvw==
+  dependencies:
+    buffer "^5.5.0"
+    cids "~0.8.0"
+    class-is "^1.1.0"
+    is-ip "^3.1.0"
+    multibase "^0.7.0"
+    varint "^5.0.0"
+
 multibase@^0.7.0:
   version "0.7.0"
   resolved "https://registry.yarnpkg.com/multibase/-/multibase-0.7.0.tgz#1adfc1c50abe05eefeb5091ac0c2728d6b84581b"
@@ -13695,6 +19927,14 @@ multibase@^0.7.0:
     base-x "^3.0.8"
     buffer "^5.5.0"
 
+multibase@^1.0.0, multibase@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/multibase/-/multibase-1.0.1.tgz#4adbe1de0be8a1ab0274328b653c3f1903476724"
+  integrity sha512-KcCxpBVY8fdVKu4dJMAahq4F/2Z/9xqEjIiR7PiMe7LRGeorFn2NLmicN6nLBCqQvft6MG2Lc9X5P0IdyvnxEw==
+  dependencies:
+    base-x "^3.0.8"
+    buffer "^5.5.0"
+
 multibase@~0.6.0:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/multibase/-/multibase-0.6.1.tgz#b76df6298536cc17b9f6a6db53ec88f85f8cc12b"
@@ -13716,6 +19956,14 @@ multicast-dns@^6.0.1:
     dns-packet "^1.3.1"
     thunky "^1.0.2"
 
+multicast-dns@^7.2.5:
+  version "7.2.5"
+  resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced"
+  integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==
+  dependencies:
+    dns-packet "^5.2.2"
+    thunky "^1.0.2"
+
 multicodec@^0.5.5:
   version "0.5.7"
   resolved "https://registry.yarnpkg.com/multicodec/-/multicodec-0.5.7.tgz#1fb3f9dd866a10a55d226e194abba2dcc1ee9ffd"
@@ -13723,7 +19971,7 @@ multicodec@^0.5.5:
   dependencies:
     varint "^5.0.0"
 
-multicodec@^1.0.0:
+multicodec@^1.0.0, multicodec@^1.0.1:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/multicodec/-/multicodec-1.0.4.tgz#46ac064657c40380c28367c90304d8ed175a714f"
   integrity sha512-NDd7FeS3QamVtbgfvu5h7fd1IlbaC4EQ0/pgU4zqE2vdHCmBGsUa0TiM8/TdSeG6BMPC92OOCf8F1ocE/Wkrrg==
@@ -13736,7 +19984,7 @@ multiformats@^9.4.2:
   resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37"
   integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==
 
-multihashes@^0.4.15, multihashes@~0.4.15:
+multihashes@^0.4.15, multihashes@~0.4.13, multihashes@~0.4.15:
   version "0.4.21"
   resolved "https://registry.yarnpkg.com/multihashes/-/multihashes-0.4.21.tgz#dc02d525579f334a7909ade8a122dabb58ccfcb5"
   integrity sha512-uVSvmeCWf36pU2nB4/1kzYZjsXD9vofZKpgudqkceYY5g2aZZXJ5r9lxuzoRLl1OAp28XljXsEJ/X/85ZsKmKw==
@@ -13745,6 +19993,15 @@ multihashes@^0.4.15, multihashes@~0.4.15:
     multibase "^0.7.0"
     varint "^5.0.0"
 
+multihashes@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/multihashes/-/multihashes-1.0.1.tgz#a89415d68283cf6287c6e219e304e75ce7fb73fe"
+  integrity sha512-S27Tepg4i8atNiFaU5ZOm3+gl3KQlUanLs/jWcBxQHFttgq+5x1OgbQmf2d8axJ/48zYGBd/wT9d723USMFduw==
+  dependencies:
+    buffer "^5.6.0"
+    multibase "^1.0.1"
+    varint "^5.0.0"
+
 mz@^2.4.0:
   version "2.7.0"
   resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
@@ -13759,12 +20016,26 @@ nan@^2.12.1:
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554"
   integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==
 
+nan@^2.15.0, nan@^2.17.0, nan@^2.18.0:
+  version "2.19.0"
+  resolved "https://registry.yarnpkg.com/nan/-/nan-2.19.0.tgz#bb58122ad55a6c5bc973303908d5b16cfdd5a8c0"
+  integrity sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==
+
 nano-json-stream-parser@^0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz#0cc8f6d0e2b622b479c40d499c46d64b755c6f5f"
   integrity sha512-9MqxMH/BSJC7dnLsEMPyfN5Dvoo49IsPFYMcHw3Bcfc2kN0lpHRBSzlMSVx4HGyJ7s9B31CyBTVehWJoQ8Ctew==
 
-nanoid@^3.3.7:
+nano@^10.1.2:
+  version "10.1.3"
+  resolved "https://registry.yarnpkg.com/nano/-/nano-10.1.3.tgz#5cb1ad14add4c9c82d53a79159848dafa84e7a13"
+  integrity sha512-q/hKQJJH3FhkkuJ3ojbgDph2StlSXFBPNkpZBZlsvZDbuYfxKJ4VtunEeilthcZtuIplIk1zVX5o2RgKTUTO+Q==
+  dependencies:
+    axios "^1.6.2"
+    node-abort-controller "^3.0.1"
+    qs "^6.11.0"
+
+nanoid@^3.3.4, nanoid@^3.3.7:
   version "3.3.7"
   resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
   integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
@@ -13786,6 +20057,11 @@ nanomatch@^1.2.9:
     snapdragon "^0.8.1"
     to-regex "^3.0.1"
 
+napi-macros@^2.2.2:
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.2.2.tgz#817fef20c3e0e40a963fbf7b37d1600bd0201044"
+  integrity sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==
+
 napi-wasm@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/napi-wasm/-/napi-wasm-1.1.0.tgz#bbe617823765ae9c1bc12ff5942370eae7b2ba4e"
@@ -13801,16 +20077,58 @@ natural-compare@^1.4.0:
   resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
   integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
 
-negotiator@0.6.3:
+nconf@^0.12.0:
+  version "0.12.1"
+  resolved "https://registry.yarnpkg.com/nconf/-/nconf-0.12.1.tgz#b0b91d2e32dc588fad19ac8bc5245e7472ebb4b9"
+  integrity sha512-p2cfF+B3XXacQdswUYWZ0w6Vld0832A/tuqjLBu3H1sfUcby4N2oVbGhyuCkZv+t3iY3aiFEj7gZGqax9Q2c1w==
+  dependencies:
+    async "^3.0.0"
+    ini "^2.0.0"
+    secure-keys "^1.0.0"
+    yargs "^16.1.1"
+
+needle@^3.1.0:
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/needle/-/needle-3.3.1.tgz#63f75aec580c2e77e209f3f324e2cdf3d29bd049"
+  integrity sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==
+  dependencies:
+    iconv-lite "^0.6.3"
+    sax "^1.2.4"
+
+negotiator@0.6.3, negotiator@^0.6.3:
   version "0.6.3"
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
   integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
 
-neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1:
+neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1, neo-async@^2.6.2:
   version "2.6.2"
   resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
   integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
 
+newrelic@^11.17.0:
+  version "11.17.0"
+  resolved "https://registry.yarnpkg.com/newrelic/-/newrelic-11.17.0.tgz#10dcf25c162968235c4d4ab764b3dd4833b34599"
+  integrity sha512-gI5FGsfvHyGLUW/+q3op1SsF8jisW5wV+NVOoxV9J58GOEEsP1B4/9D5jyL3iiL5QO1REeWtK5n15d2OsiYAIg==
+  dependencies:
+    "@grpc/grpc-js" "^1.9.4"
+    "@grpc/proto-loader" "^0.7.5"
+    "@newrelic/ritm" "^7.2.0"
+    "@newrelic/security-agent" "^1.1.1"
+    "@tyriar/fibonacci-heap" "^2.0.7"
+    concat-stream "^2.0.0"
+    https-proxy-agent "^7.0.1"
+    import-in-the-middle "^1.6.0"
+    json-bigint "^1.0.0"
+    json-stringify-safe "^5.0.0"
+    module-details-from-path "^1.0.3"
+    readable-stream "^3.6.1"
+    semver "^7.5.2"
+    winston-transport "^4.5.0"
+  optionalDependencies:
+    "@contrast/fn-inspect" "^3.3.0"
+    "@newrelic/native-metrics" "^10.0.0"
+    "@prisma/prisma-fmt-wasm" "^4.17.0-16.27eb2449f178cd9fe1a4b892d732cc4795f75085"
+
 next-tick@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
@@ -13828,6 +20146,35 @@ no-case@^2.2.0:
   dependencies:
     lower-case "^1.1.1"
 
+no-case@^3.0.4:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
+  integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==
+  dependencies:
+    lower-case "^2.0.2"
+    tslib "^2.0.3"
+
+nock@^13.5.4:
+  version "13.5.4"
+  resolved "https://registry.yarnpkg.com/nock/-/nock-13.5.4.tgz#8918f0addc70a63736170fef7106a9721e0dc479"
+  integrity sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw==
+  dependencies:
+    debug "^4.1.0"
+    json-stringify-safe "^5.0.1"
+    propagate "^2.0.0"
+
+node-abi@^3.3.0:
+  version "3.62.0"
+  resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.62.0.tgz#017958ed120f89a3a14a7253da810f5d724e3f36"
+  integrity sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==
+  dependencies:
+    semver "^7.3.5"
+
+node-abort-controller@^3.0.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548"
+  integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==
+
 node-addon-api@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32"
@@ -13843,6 +20190,13 @@ node-addon-api@^7.0.0:
   resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.0.tgz#71f609369379c08e251c558527a107107b5e0fdb"
   integrity sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==
 
+node-emoji@^1.10.0:
+  version "1.11.0"
+  resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c"
+  integrity sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==
+  dependencies:
+    lodash "^4.17.21"
+
 node-fetch-native@^1.4.0, node-fetch-native@^1.4.1, node-fetch-native@^1.6.1:
   version "1.6.2"
   resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.2.tgz#f439000d972eb0c8a741b65dcda412322955e1c6"
@@ -13856,7 +20210,7 @@ node-fetch@^1.0.1:
     encoding "^0.1.11"
     is-stream "^1.0.1"
 
-node-fetch@^2.6.1, node-fetch@^2.6.12, node-fetch@^2.6.6, node-fetch@^2.6.7:
+node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.12, node-fetch@^2.6.6, node-fetch@^2.6.7, node-fetch@^2.6.9:
   version "2.7.0"
   resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
   integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
@@ -13868,7 +20222,7 @@ node-forge@^0.10.0:
   resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
   integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==
 
-node-forge@^1.3.1:
+node-forge@^1, node-forge@^1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3"
   integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==
@@ -13878,6 +20232,27 @@ node-gyp-build@^4.2.0, node-gyp-build@^4.3.0:
   resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.0.tgz#3fee9c1731df4581a3f9ead74664369ff00d26dd"
   integrity sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==
 
+node-gyp-build@^4.6.0, node-gyp-build@^4.8.0:
+  version "4.8.1"
+  resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.1.tgz#976d3ad905e71b76086f4f0b0d3637fe79b6cda5"
+  integrity sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==
+
+node-gyp@^10.1.0:
+  version "10.1.0"
+  resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-10.1.0.tgz#75e6f223f2acb4026866c26a2ead6aab75a8ca7e"
+  integrity sha512-B4J5M1cABxPc5PwfjhbV5hoy2DP9p8lFXASnEN6hugXOa61416tnTZ29x9sSwAd0o99XNIcpvDDy1swAExsVKA==
+  dependencies:
+    env-paths "^2.2.0"
+    exponential-backoff "^3.1.1"
+    glob "^10.3.10"
+    graceful-fs "^4.2.6"
+    make-fetch-happen "^13.0.0"
+    nopt "^7.0.0"
+    proc-log "^3.0.0"
+    semver "^7.3.5"
+    tar "^6.1.2"
+    which "^4.0.0"
+
 node-int64@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@@ -13955,6 +20330,32 @@ node-stdlib-browser@^1.2.0:
     util "^0.12.4"
     vm-browserify "^1.0.1"
 
+nofilter@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/nofilter/-/nofilter-3.1.0.tgz#c757ba68801d41ff930ba2ec55bab52ca184aa66"
+  integrity sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==
+
+nopt@3.x:
+  version "3.0.6"
+  resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
+  integrity sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==
+  dependencies:
+    abbrev "1"
+
+nopt@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88"
+  integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==
+  dependencies:
+    abbrev "1"
+
+nopt@^7.0.0:
+  version "7.2.1"
+  resolved "https://registry.yarnpkg.com/nopt/-/nopt-7.2.1.tgz#1cac0eab9b8e97c9093338446eddd40b2c8ca1e7"
+  integrity sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==
+  dependencies:
+    abbrev "^2.0.0"
+
 nopt@^7.2.0:
   version "7.2.0"
   resolved "https://registry.yarnpkg.com/nopt/-/nopt-7.2.0.tgz#067378c68116f602f552876194fd11f1292503d7"
@@ -14031,6 +20432,13 @@ npm-run-path@^2.0.0:
   dependencies:
     path-key "^2.0.0"
 
+npm-run-path@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-3.1.0.tgz#7f91be317f6a466efed3c9f2980ad8a4ee8b0fa5"
+  integrity sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==
+  dependencies:
+    path-key "^3.0.0"
+
 npm-run-path@^4.0.0, npm-run-path@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
@@ -14045,6 +20453,16 @@ npm-run-path@^5.1.0:
   dependencies:
     path-key "^4.0.0"
 
+npmlog@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0"
+  integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==
+  dependencies:
+    are-we-there-yet "^2.0.0"
+    console-control-strings "^1.1.0"
+    gauge "^3.0.0"
+    set-blocking "^2.0.0"
+
 nth-check@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c"
@@ -14072,17 +20490,73 @@ number-to-bn@1.7.0:
     bn.js "4.11.6"
     strip-hex-prefix "1.0.0"
 
+numbered@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/numbered/-/numbered-1.1.0.tgz#9fcd79564c73a84b9574e8370c3d8e58fe3c133c"
+  integrity sha512-pv/ue2Odr7IfYOO0byC1KgBI10wo5YDauLhxY6/saNzAdAs0r1SotGCPzzCLNPL0xtrAwWRialLu23AAu9xO1g==
+
 nwsapi@^2.2.2, nwsapi@^2.2.4:
   version "2.2.7"
   resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.7.tgz#738e0707d3128cb750dddcfe90e4610482df0f30"
   integrity sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==
 
-nx@18.0.8:
-  version "18.0.8"
-  resolved "https://registry.yarnpkg.com/nx/-/nx-18.0.8.tgz#5d0ac8b53663cc53045c63005ff3fc7592a0bc5d"
-  integrity sha512-IhzRLCZaiR9zKGJ3Jm79bhi8nOdyRORQkFc/YDO6xubLSQ5mLPAeg789Q/SlGRzU5oMwLhm5D/gvvMJCAvUmXQ==
+nx@18.3.0:
+  version "18.3.0"
+  resolved "https://registry.yarnpkg.com/nx/-/nx-18.3.0.tgz#3102a74057d19b561c57ee34a12f51b99e6fe376"
+  integrity sha512-0jIxAuRVW19uVP0xPcr9obk8YSQzh2E5Co/4AYvfuGlQegiRv/CYk5NDK3wzAe3l1rTSUhmbol7QxpZGXhk4Dw==
   dependencies:
-    "@nrwl/tao" "18.0.8"
+    "@nrwl/tao" "18.3.0"
+    "@yarnpkg/lockfile" "^1.1.0"
+    "@yarnpkg/parsers" "3.0.0-rc.46"
+    "@zkochan/js-yaml" "0.0.6"
+    axios "^1.6.0"
+    chalk "^4.1.0"
+    cli-cursor "3.1.0"
+    cli-spinners "2.6.1"
+    cliui "^8.0.1"
+    dotenv "~16.3.1"
+    dotenv-expand "~10.0.0"
+    enquirer "~2.3.6"
+    figures "3.2.0"
+    flat "^5.0.2"
+    fs-extra "^11.1.0"
+    ignore "^5.0.4"
+    jest-diff "^29.4.1"
+    js-yaml "4.1.0"
+    jsonc-parser "3.2.0"
+    lines-and-columns "~2.0.3"
+    minimatch "9.0.3"
+    node-machine-id "1.1.12"
+    npm-run-path "^4.0.1"
+    open "^8.4.0"
+    ora "5.3.0"
+    semver "^7.5.3"
+    string-width "^4.2.3"
+    strong-log-transformer "^2.1.0"
+    tar-stream "~2.2.0"
+    tmp "~0.2.1"
+    tsconfig-paths "^4.1.2"
+    tslib "^2.3.0"
+    yargs "^17.6.2"
+    yargs-parser "21.1.1"
+  optionalDependencies:
+    "@nx/nx-darwin-arm64" "18.3.0"
+    "@nx/nx-darwin-x64" "18.3.0"
+    "@nx/nx-freebsd-x64" "18.3.0"
+    "@nx/nx-linux-arm-gnueabihf" "18.3.0"
+    "@nx/nx-linux-arm64-gnu" "18.3.0"
+    "@nx/nx-linux-arm64-musl" "18.3.0"
+    "@nx/nx-linux-x64-gnu" "18.3.0"
+    "@nx/nx-linux-x64-musl" "18.3.0"
+    "@nx/nx-win32-arm64-msvc" "18.3.0"
+    "@nx/nx-win32-x64-msvc" "18.3.0"
+
+nx@19.0.4:
+  version "19.0.4"
+  resolved "https://registry.yarnpkg.com/nx/-/nx-19.0.4.tgz#c39803f6186f6b009c39f5f30f902ce8e136dcde"
+  integrity sha512-E+wkP3H+23Vu9jso6Xw7cbXPzy2PMyrPukrEUDWkQrr/eCqf0Npkj5zky1/lKFSBaLtNYgsFD21co+b4rwxtdw==
+  dependencies:
+    "@nrwl/tao" "19.0.4"
     "@yarnpkg/lockfile" "^1.1.0"
     "@yarnpkg/parsers" "3.0.0-rc.46"
     "@zkochan/js-yaml" "0.0.6"
@@ -14117,16 +20591,16 @@ nx@18.0.8:
     yargs "^17.6.2"
     yargs-parser "21.1.1"
   optionalDependencies:
-    "@nx/nx-darwin-arm64" "18.0.8"
-    "@nx/nx-darwin-x64" "18.0.8"
-    "@nx/nx-freebsd-x64" "18.0.8"
-    "@nx/nx-linux-arm-gnueabihf" "18.0.8"
-    "@nx/nx-linux-arm64-gnu" "18.0.8"
-    "@nx/nx-linux-arm64-musl" "18.0.8"
-    "@nx/nx-linux-x64-gnu" "18.0.8"
-    "@nx/nx-linux-x64-musl" "18.0.8"
-    "@nx/nx-win32-arm64-msvc" "18.0.8"
-    "@nx/nx-win32-x64-msvc" "18.0.8"
+    "@nx/nx-darwin-arm64" "19.0.4"
+    "@nx/nx-darwin-x64" "19.0.4"
+    "@nx/nx-freebsd-x64" "19.0.4"
+    "@nx/nx-linux-arm-gnueabihf" "19.0.4"
+    "@nx/nx-linux-arm64-gnu" "19.0.4"
+    "@nx/nx-linux-arm64-musl" "19.0.4"
+    "@nx/nx-linux-x64-gnu" "19.0.4"
+    "@nx/nx-linux-x64-musl" "19.0.4"
+    "@nx/nx-win32-arm64-msvc" "19.0.4"
+    "@nx/nx-win32-x64-msvc" "19.0.4"
 
 oauth-sign@~0.9.0:
   version "0.9.0"
@@ -14156,6 +20630,11 @@ object-copy@^0.1.0:
     define-property "^0.2.5"
     kind-of "^3.0.3"
 
+object-hash@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"
+  integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==
+
 object-inspect@^1.13.1:
   version "1.13.1"
   resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2"
@@ -14238,6 +20717,11 @@ object.values@^1.1.0, object.values@^1.1.7:
     define-properties "^1.2.0"
     es-abstract "^1.22.1"
 
+obliterator@^2.0.0:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-2.0.4.tgz#fa650e019b2d075d745e44f1effeb13a2adbe816"
+  integrity sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==
+
 oblivious-set@1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.1.1.tgz#d9d38e9491d51f27a5c3ec1681d2ba40aa81e98b"
@@ -14277,30 +20761,72 @@ oidc-client-ts@^2.2.4:
     crypto-js "^4.2.0"
     jwt-decode "^3.1.2"
 
+oidc-provider@7.14.3:
+  version "7.14.3"
+  resolved "https://registry.yarnpkg.com/oidc-provider/-/oidc-provider-7.14.3.tgz#8d05c60c1cb839cb25320f85565f82472a689bce"
+  integrity sha512-+TB/JSB6jXyr5Ti2bqOSwDKI6ldc23XlHkgQTFUIjFm2P6SuTkmcXpvF3KF8kDWreSxlKwXwh7ubn9+9yR7CGA==
+  dependencies:
+    "@koa/cors" "^3.3.0"
+    cacheable-lookup "^6.0.4"
+    debug "^4.3.4"
+    ejs "^3.1.8"
+    got "^11.8.5"
+    jose "^4.10.3"
+    jsesc "^3.0.2"
+    koa "^2.13.4"
+    koa-compose "^4.1.0"
+    nanoid "^3.3.4"
+    object-hash "^3.0.0"
+    oidc-token-hash "^5.0.1"
+    paseto "^2.1.3"
+    quick-lru "^5.1.1"
+    raw-body "^2.5.1"
+  optionalDependencies:
+    paseto3 "npm:paseto@^3.1.0"
+
+oidc-token-hash@^5.0.1:
+  version "5.0.3"
+  resolved "https://registry.yarnpkg.com/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz#9a229f0a1ce9d4fc89bcaee5478c97a889e7b7b6"
+  integrity sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==
+
 on-exit-leak-free@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz#b39c9e3bf7690d890f4861558b0d7b90a442d209"
   integrity sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg==
 
-on-finished@2.4.1:
+on-finished@2.4.1, on-finished@^2.3.0:
   version "2.4.1"
   resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
   integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
   dependencies:
     ee-first "1.1.1"
 
-on-headers@~1.0.2:
+on-finished@~2.3.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
+  integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==
+  dependencies:
+    ee-first "1.1.1"
+
+on-headers@^1.0.1, on-headers@~1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
   integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
 
-once@^1.3.0, once@^1.3.1, once@^1.4.0:
+once@1.x, once@^1.3.0, once@^1.3.1, once@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
   integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
   dependencies:
     wrappy "1"
 
+one-time@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45"
+  integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==
+  dependencies:
+    fn.name "1.x.x"
+
 onetime@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
@@ -14322,6 +20848,11 @@ onetime@^6.0.0:
   dependencies:
     mimic-fn "^4.0.0"
 
+only@~0.0.2:
+  version "0.0.2"
+  resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4"
+  integrity sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==
+
 open@^6.3.0:
   version "6.4.0"
   resolved "https://registry.yarnpkg.com/open/-/open-6.4.0.tgz#5c13e96d0dc894686164f18965ecfe889ecfc8a9"
@@ -14329,7 +20860,7 @@ open@^6.3.0:
   dependencies:
     is-wsl "^1.1.0"
 
-open@^8.4.0:
+open@^8.0.9, open@^8.4.0:
   version "8.4.2"
   resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9"
   integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==
@@ -14338,6 +20869,13 @@ open@^8.4.0:
     is-docker "^2.1.1"
     is-wsl "^2.2.0"
 
+openapi3-ts@^3.0.0, openapi3-ts@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/openapi3-ts/-/openapi3-ts-3.2.0.tgz#7e30d33c480e938e67e809ab16f419bc9beae3f8"
+  integrity sha512-/ykNWRV5Qs0Nwq7Pc0nJ78fgILvOT/60OxEmB3v7yQ8a8Bwcm43D4diaYazG/KBn6czA+52XYy931WFLMCUeSg==
+  dependencies:
+    yaml "^2.2.1"
+
 opener@^1.5.1:
   version "1.5.2"
   resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"
@@ -14350,6 +20888,18 @@ opn@^5.5.0:
   dependencies:
     is-wsl "^1.1.0"
 
+optionator@^0.8.1:
+  version "0.8.3"
+  resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
+  integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==
+  dependencies:
+    deep-is "~0.1.3"
+    fast-levenshtein "~2.0.6"
+    levn "~0.3.0"
+    prelude-ls "~1.1.2"
+    type-check "~0.3.2"
+    word-wrap "~1.2.3"
+
 optionator@^0.9.3:
   version "0.9.3"
   resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64"
@@ -14388,11 +20938,21 @@ ora@^3.4.0:
     strip-ansi "^5.2.0"
     wcwidth "^1.0.1"
 
+ordinal@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/ordinal/-/ordinal-1.0.3.tgz#1a3c7726a61728112f50944ad7c35c06ae3a0d4d"
+  integrity sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ==
+
 os-browserify@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
   integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==
 
+os-tmpdir@~1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
+  integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
+
 ospath@^1.2.2:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b"
@@ -14408,6 +20968,11 @@ p-cancelable@^3.0.0:
   resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-3.0.0.tgz#63826694b54d61ca1c20ebcb6d3ecf5e14cd8050"
   integrity sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==
 
+p-each-series@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a"
+  integrity sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==
+
 p-finally@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
@@ -14418,6 +20983,13 @@ p-finally@^2.0.0:
   resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561"
   integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==
 
+p-limit@^1.1.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
+  integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==
+  dependencies:
+    p-try "^1.0.0"
+
 p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.2.1:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
@@ -14432,6 +21004,13 @@ p-limit@^3.0.2, p-limit@^3.1.0:
   dependencies:
     yocto-queue "^0.1.0"
 
+p-limit@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-4.0.0.tgz#914af6544ed32bfa54670b061cafcbd04984b644"
+  integrity sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==
+  dependencies:
+    yocto-queue "^1.0.0"
+
 p-limit@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-5.0.0.tgz#6946d5b7140b649b7a33a027d89b4c625b3a5985"
@@ -14439,6 +21018,13 @@ p-limit@^5.0.0:
   dependencies:
     yocto-queue "^1.0.0"
 
+p-locate@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
+  integrity sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==
+  dependencies:
+    p-limit "^1.1.0"
+
 p-locate@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
@@ -14460,6 +21046,13 @@ p-locate@^5.0.0:
   dependencies:
     p-limit "^3.0.2"
 
+p-locate@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-6.0.0.tgz#3da9a49d4934b901089dca3302fa65dc5a05c04f"
+  integrity sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==
+  dependencies:
+    p-limit "^4.0.0"
+
 p-map@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
@@ -14479,12 +21072,25 @@ p-retry@^3.0.1:
   dependencies:
     retry "^0.12.0"
 
+p-retry@^4.5.0:
+  version "4.6.2"
+  resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16"
+  integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==
+  dependencies:
+    "@types/retry" "0.12.0"
+    retry "^0.13.1"
+
+p-try@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
+  integrity sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==
+
 p-try@^2.0.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
   integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
 
-pako@~1.0.5:
+pako@~1.0.2, pako@~1.0.5:
   version "1.0.11"
   resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
   integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
@@ -14523,6 +21129,11 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.6:
     pbkdf2 "^3.0.3"
     safe-buffer "^5.1.1"
 
+parse-cache-control@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/parse-cache-control/-/parse-cache-control-1.0.1.tgz#8eeab3e54fa56920fe16ba38f77fa21aacc2d74e"
+  integrity sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==
+
 parse-headers@^2.0.0:
   version "2.0.5"
   resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.5.tgz#069793f9356a54008571eb7f9761153e6c770da9"
@@ -14546,6 +21157,11 @@ parse-json@^5.0.0, parse-json@^5.2.0:
     json-parse-even-better-errors "^2.3.0"
     lines-and-columns "^1.1.6"
 
+parse-node-version@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b"
+  integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==
+
 parse5-htmlparser2-tree-adapter@^6.0.0:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6"
@@ -14553,6 +21169,11 @@ parse5-htmlparser2-tree-adapter@^6.0.0:
   dependencies:
     parse5 "^6.0.1"
 
+parse5@4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608"
+  integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==
+
 parse5@^5.1.1:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178"
@@ -14570,7 +21191,7 @@ parse5@^7.0.0, parse5@^7.1.1, parse5@^7.1.2:
   dependencies:
     entities "^4.4.0"
 
-parseurl@~1.3.2, parseurl@~1.3.3:
+parseurl@^1.3.2, parseurl@~1.3.2, parseurl@~1.3.3:
   version "1.3.3"
   resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
   integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
@@ -14580,6 +21201,16 @@ pascalcase@^0.1.1:
   resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
   integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==
 
+"paseto3@npm:paseto@^3.1.0":
+  version "3.1.4"
+  resolved "https://registry.yarnpkg.com/paseto/-/paseto-3.1.4.tgz#a8a448abadb20fef609e00a4fd6d82369b3ea05a"
+  integrity sha512-BifaKKu+MS9b/vTgFMC6Q8uLUMqw8VtYgl4qODJWb6Jqt+dTKn8XH9EftJZx+6wxF4ELBbKdH33DZa4inMYVcg==
+
+paseto@^2.1.3:
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/paseto/-/paseto-2.1.3.tgz#9913f9f46172ce88c397a0f035fdfedd34d827ef"
+  integrity sha512-BNkbvr0ZFDbh3oV13QzT5jXIu8xpFc9r0o5mvWBhDU1GBkVt1IzHK1N6dcYmN7XImrUmPQ0HCUXmoe2WPo8xsg==
+
 path-browserify@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a"
@@ -14605,6 +21236,11 @@ path-exists@^4.0.0:
   resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
   integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
 
+path-exists@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-5.0.0.tgz#a6aad9489200b21fab31e49cf09277e5116fb9e7"
+  integrity sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==
+
 path-is-absolute@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
@@ -14630,7 +21266,7 @@ path-key@^4.0.0:
   resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18"
   integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==
 
-path-parse@^1.0.7:
+path-parse@^1.0.6, path-parse@^1.0.7:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
   integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
@@ -14643,6 +21279,14 @@ path-scurry@^1.10.1, path-scurry@^1.6.1:
     lru-cache "^9.1.1 || ^10.0.0"
     minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
 
+path-scurry@^1.11.0:
+  version "1.11.1"
+  resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2"
+  integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==
+  dependencies:
+    lru-cache "^10.2.0"
+    minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
+
 path-to-regexp@0.1.7:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
@@ -14660,6 +21304,14 @@ path-type@^4.0.0:
   resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
   integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
 
+path@^0.12.7:
+  version "0.12.7"
+  resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f"
+  integrity sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==
+  dependencies:
+    process "^0.11.1"
+    util "^0.10.3"
+
 pathe@^1.1.0, pathe@^1.1.1, pathe@^1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec"
@@ -14706,7 +21358,7 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatc
   resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
   integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
 
-pify@^2.0.0, pify@^2.2.0:
+pify@^2.0.0, pify@^2.2.0, pify@^2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
   integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==
@@ -14781,6 +21433,13 @@ pirates@^4.0.4, pirates@^4.0.6:
   resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9"
   integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==
 
+pkcs11js@^1.3.0:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/pkcs11js/-/pkcs11js-1.3.1.tgz#2eec2c712594c088e271f04b363252d4c3df7778"
+  integrity sha512-eo7fTeQwYGzX1pFmRaf4ji/WcDW2XKpwqylOwzutsjNWECv6G9PzDHj3Yj5dX9EW/fydMnJG8xvWj/btnQT9TA==
+  dependencies:
+    nan "^2.15.0"
+
 pkg-dir@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3"
@@ -14802,6 +21461,13 @@ pkg-dir@^5.0.0:
   dependencies:
     find-up "^5.0.0"
 
+pkg-dir@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-7.0.0.tgz#8f0c08d6df4476756c5ff29b3282d0bab7517d11"
+  integrity sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==
+  dependencies:
+    find-up "^6.3.0"
+
 pkg-types@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.0.3.tgz#988b42ab19254c01614d13f4f65a2cfc7880f868"
@@ -14870,6 +21536,14 @@ postcss-calc@^7.0.1:
     postcss-selector-parser "^6.0.2"
     postcss-value-parser "^4.0.2"
 
+postcss-calc@^9.0.1:
+  version "9.0.1"
+  resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-9.0.1.tgz#a744fd592438a93d6de0f1434c572670361eb6c6"
+  integrity sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==
+  dependencies:
+    postcss-selector-parser "^6.0.11"
+    postcss-value-parser "^4.2.0"
+
 postcss-colormin@^4.0.3:
   version "4.0.3"
   resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381"
@@ -14881,6 +21555,16 @@ postcss-colormin@^4.0.3:
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
+postcss-colormin@^6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-6.1.0.tgz#076e8d3fb291fbff7b10e6b063be9da42ff6488d"
+  integrity sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==
+  dependencies:
+    browserslist "^4.23.0"
+    caniuse-api "^3.0.0"
+    colord "^2.9.3"
+    postcss-value-parser "^4.2.0"
+
 postcss-convert-values@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f"
@@ -14889,6 +21573,14 @@ postcss-convert-values@^4.0.1:
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
+postcss-convert-values@^6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz#3498387f8efedb817cbc63901d45bd1ceaa40f48"
+  integrity sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==
+  dependencies:
+    browserslist "^4.23.0"
+    postcss-value-parser "^4.2.0"
+
 postcss-discard-comments@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033"
@@ -14896,6 +21588,11 @@ postcss-discard-comments@^4.0.2:
   dependencies:
     postcss "^7.0.0"
 
+postcss-discard-comments@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz#e768dcfdc33e0216380623652b0a4f69f4678b6c"
+  integrity sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==
+
 postcss-discard-duplicates@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb"
@@ -14903,6 +21600,11 @@ postcss-discard-duplicates@^4.0.2:
   dependencies:
     postcss "^7.0.0"
 
+postcss-discard-duplicates@^6.0.3:
+  version "6.0.3"
+  resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz#d121e893c38dc58a67277f75bb58ba43fce4c3eb"
+  integrity sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==
+
 postcss-discard-empty@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765"
@@ -14910,6 +21612,11 @@ postcss-discard-empty@^4.0.1:
   dependencies:
     postcss "^7.0.0"
 
+postcss-discard-empty@^6.0.3:
+  version "6.0.3"
+  resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz#ee39c327219bb70473a066f772621f81435a79d9"
+  integrity sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==
+
 postcss-discard-overridden@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57"
@@ -14917,6 +21624,20 @@ postcss-discard-overridden@^4.0.1:
   dependencies:
     postcss "^7.0.0"
 
+postcss-discard-overridden@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz#4e9f9c62ecd2df46e8fdb44dc17e189776572e2d"
+  integrity sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==
+
+postcss-import@~14.1.0:
+  version "14.1.0"
+  resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-14.1.0.tgz#a7333ffe32f0b8795303ee9e40215dac922781f0"
+  integrity sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==
+  dependencies:
+    postcss-value-parser "^4.0.0"
+    read-cache "^1.0.0"
+    resolve "^1.1.7"
+
 postcss-load-config@^2.0.0:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.2.tgz#c5ea504f2c4aef33c7359a34de3573772ad7502a"
@@ -14935,6 +21656,15 @@ postcss-loader@^3.0.0:
     postcss-load-config "^2.0.0"
     schema-utils "^1.0.0"
 
+postcss-loader@^6.1.1:
+  version "6.2.1"
+  resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-6.2.1.tgz#0895f7346b1702103d30fdc66e4d494a93c008ef"
+  integrity sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==
+  dependencies:
+    cosmiconfig "^7.0.0"
+    klona "^2.0.5"
+    semver "^7.3.5"
+
 postcss-merge-longhand@^4.0.11:
   version "4.0.11"
   resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24"
@@ -14945,6 +21675,14 @@ postcss-merge-longhand@^4.0.11:
     postcss-value-parser "^3.0.0"
     stylehacks "^4.0.0"
 
+postcss-merge-longhand@^6.0.5:
+  version "6.0.5"
+  resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz#ba8a8d473617c34a36abbea8dda2b215750a065a"
+  integrity sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==
+  dependencies:
+    postcss-value-parser "^4.2.0"
+    stylehacks "^6.1.1"
+
 postcss-merge-rules@^4.0.3:
   version "4.0.3"
   resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650"
@@ -14957,6 +21695,16 @@ postcss-merge-rules@^4.0.3:
     postcss-selector-parser "^3.0.0"
     vendors "^1.0.0"
 
+postcss-merge-rules@^6.1.1:
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz#7aa539dceddab56019469c0edd7d22b64c3dea9d"
+  integrity sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==
+  dependencies:
+    browserslist "^4.23.0"
+    caniuse-api "^3.0.0"
+    cssnano-utils "^4.0.2"
+    postcss-selector-parser "^6.0.16"
+
 postcss-minify-font-values@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6"
@@ -14965,6 +21713,13 @@ postcss-minify-font-values@^4.0.2:
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
+postcss-minify-font-values@^6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz#a0e574c02ee3f299be2846369211f3b957ea4c59"
+  integrity sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==
+  dependencies:
+    postcss-value-parser "^4.2.0"
+
 postcss-minify-gradients@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471"
@@ -14975,6 +21730,15 @@ postcss-minify-gradients@^4.0.2:
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
+postcss-minify-gradients@^6.0.3:
+  version "6.0.3"
+  resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz#ca3eb55a7bdb48a1e187a55c6377be918743dbd6"
+  integrity sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==
+  dependencies:
+    colord "^2.9.3"
+    cssnano-utils "^4.0.2"
+    postcss-value-parser "^4.2.0"
+
 postcss-minify-params@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874"
@@ -14987,6 +21751,15 @@ postcss-minify-params@^4.0.2:
     postcss-value-parser "^3.0.0"
     uniqs "^2.0.0"
 
+postcss-minify-params@^6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz#54551dec77b9a45a29c3cb5953bf7325a399ba08"
+  integrity sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==
+  dependencies:
+    browserslist "^4.23.0"
+    cssnano-utils "^4.0.2"
+    postcss-value-parser "^4.2.0"
+
 postcss-minify-selectors@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8"
@@ -14997,6 +21770,13 @@ postcss-minify-selectors@^4.0.2:
     postcss "^7.0.0"
     postcss-selector-parser "^3.0.0"
 
+postcss-minify-selectors@^6.0.4:
+  version "6.0.4"
+  resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz#197f7d72e6dd19eed47916d575d69dc38b396aff"
+  integrity sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==
+  dependencies:
+    postcss-selector-parser "^6.0.16"
+
 postcss-modules-extract-imports@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e"
@@ -15004,6 +21784,11 @@ postcss-modules-extract-imports@^2.0.0:
   dependencies:
     postcss "^7.0.5"
 
+postcss-modules-extract-imports@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz#b4497cb85a9c0c4b5aabeb759bb25e8d89f15002"
+  integrity sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==
+
 postcss-modules-local-by-default@^3.0.2:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0"
@@ -15014,6 +21799,15 @@ postcss-modules-local-by-default@^3.0.2:
     postcss-selector-parser "^6.0.2"
     postcss-value-parser "^4.1.0"
 
+postcss-modules-local-by-default@^4.0.5:
+  version "4.0.5"
+  resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz#f1b9bd757a8edf4d8556e8d0f4f894260e3df78f"
+  integrity sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==
+  dependencies:
+    icss-utils "^5.0.0"
+    postcss-selector-parser "^6.0.2"
+    postcss-value-parser "^4.1.0"
+
 postcss-modules-scope@^2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee"
@@ -15022,6 +21816,13 @@ postcss-modules-scope@^2.2.0:
     postcss "^7.0.6"
     postcss-selector-parser "^6.0.0"
 
+postcss-modules-scope@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz#a43d28289a169ce2c15c00c4e64c0858e43457d5"
+  integrity sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==
+  dependencies:
+    postcss-selector-parser "^6.0.4"
+
 postcss-modules-values@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10"
@@ -15030,6 +21831,13 @@ postcss-modules-values@^3.0.0:
     icss-utils "^4.0.0"
     postcss "^7.0.6"
 
+postcss-modules-values@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c"
+  integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==
+  dependencies:
+    icss-utils "^5.0.0"
+
 postcss-normalize-charset@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4"
@@ -15037,6 +21845,11 @@ postcss-normalize-charset@^4.0.1:
   dependencies:
     postcss "^7.0.0"
 
+postcss-normalize-charset@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz#1ec25c435057a8001dac942942a95ffe66f721e1"
+  integrity sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==
+
 postcss-normalize-display-values@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a"
@@ -15046,6 +21859,13 @@ postcss-normalize-display-values@^4.0.2:
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
+postcss-normalize-display-values@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz#54f02764fed0b288d5363cbb140d6950dbbdd535"
+  integrity sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==
+  dependencies:
+    postcss-value-parser "^4.2.0"
+
 postcss-normalize-positions@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f"
@@ -15056,6 +21876,13 @@ postcss-normalize-positions@^4.0.2:
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
+postcss-normalize-positions@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz#e982d284ec878b9b819796266f640852dbbb723a"
+  integrity sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==
+  dependencies:
+    postcss-value-parser "^4.2.0"
+
 postcss-normalize-repeat-style@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c"
@@ -15066,6 +21893,13 @@ postcss-normalize-repeat-style@^4.0.2:
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
+postcss-normalize-repeat-style@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz#f8006942fd0617c73f049dd8b6201c3a3040ecf3"
+  integrity sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==
+  dependencies:
+    postcss-value-parser "^4.2.0"
+
 postcss-normalize-string@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c"
@@ -15075,6 +21909,13 @@ postcss-normalize-string@^4.0.2:
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
+postcss-normalize-string@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz#e3cc6ad5c95581acd1fc8774b309dd7c06e5e363"
+  integrity sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==
+  dependencies:
+    postcss-value-parser "^4.2.0"
+
 postcss-normalize-timing-functions@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9"
@@ -15084,6 +21925,13 @@ postcss-normalize-timing-functions@^4.0.2:
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
+postcss-normalize-timing-functions@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz#40cb8726cef999de984527cbd9d1db1f3e9062c0"
+  integrity sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==
+  dependencies:
+    postcss-value-parser "^4.2.0"
+
 postcss-normalize-unicode@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz#841bd48fdcf3019ad4baa7493a3d363b52ae1cfb"
@@ -15093,6 +21941,14 @@ postcss-normalize-unicode@^4.0.1:
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
+postcss-normalize-unicode@^6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz#aaf8bbd34c306e230777e80f7f12a4b7d27ce06e"
+  integrity sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==
+  dependencies:
+    browserslist "^4.23.0"
+    postcss-value-parser "^4.2.0"
+
 postcss-normalize-url@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1"
@@ -15103,6 +21959,13 @@ postcss-normalize-url@^4.0.1:
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
+postcss-normalize-url@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz#292792386be51a8de9a454cb7b5c58ae22db0f79"
+  integrity sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==
+  dependencies:
+    postcss-value-parser "^4.2.0"
+
 postcss-normalize-whitespace@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82"
@@ -15111,6 +21974,13 @@ postcss-normalize-whitespace@^4.0.2:
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
+postcss-normalize-whitespace@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz#fbb009e6ebd312f8b2efb225c2fcc7cf32b400cd"
+  integrity sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==
+  dependencies:
+    postcss-value-parser "^4.2.0"
+
 postcss-ordered-values@^4.1.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee"
@@ -15120,6 +21990,14 @@ postcss-ordered-values@^4.1.2:
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
+postcss-ordered-values@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz#366bb663919707093451ab70c3f99c05672aaae5"
+  integrity sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==
+  dependencies:
+    cssnano-utils "^4.0.2"
+    postcss-value-parser "^4.2.0"
+
 postcss-reduce-initial@^4.0.3:
   version "4.0.3"
   resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df"
@@ -15130,6 +22008,14 @@ postcss-reduce-initial@^4.0.3:
     has "^1.0.0"
     postcss "^7.0.0"
 
+postcss-reduce-initial@^6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz#4401297d8e35cb6e92c8e9586963e267105586ba"
+  integrity sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==
+  dependencies:
+    browserslist "^4.23.0"
+    caniuse-api "^3.0.0"
+
 postcss-reduce-transforms@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29"
@@ -15140,6 +22026,13 @@ postcss-reduce-transforms@^4.0.2:
     postcss "^7.0.0"
     postcss-value-parser "^3.0.0"
 
+postcss-reduce-transforms@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz#6fa2c586bdc091a7373caeee4be75a0f3e12965d"
+  integrity sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==
+  dependencies:
+    postcss-value-parser "^4.2.0"
+
 postcss-selector-parser@^3.0.0:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz#b310f5c4c0fdaf76f94902bbaa30db6aa84f5270"
@@ -15157,6 +22050,14 @@ postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2:
     cssesc "^3.0.0"
     util-deprecate "^1.0.2"
 
+postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.16, postcss-selector-parser@^6.0.4:
+  version "6.0.16"
+  resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz#3b88b9f5c5abd989ef4e2fc9ec8eedd34b20fb04"
+  integrity sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==
+  dependencies:
+    cssesc "^3.0.0"
+    util-deprecate "^1.0.2"
+
 postcss-svgo@^4.0.3:
   version "4.0.3"
   resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.3.tgz#343a2cdbac9505d416243d496f724f38894c941e"
@@ -15166,6 +22067,14 @@ postcss-svgo@^4.0.3:
     postcss-value-parser "^3.0.0"
     svgo "^1.0.0"
 
+postcss-svgo@^6.0.3:
+  version "6.0.3"
+  resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-6.0.3.tgz#1d6e180d6df1fa8a3b30b729aaa9161e94f04eaa"
+  integrity sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==
+  dependencies:
+    postcss-value-parser "^4.2.0"
+    svgo "^3.2.0"
+
 postcss-unique-selectors@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac"
@@ -15175,12 +22084,19 @@ postcss-unique-selectors@^4.0.1:
     postcss "^7.0.0"
     uniqs "^2.0.0"
 
+postcss-unique-selectors@^6.0.4:
+  version "6.0.4"
+  resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz#983ab308896b4bf3f2baaf2336e14e52c11a2088"
+  integrity sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==
+  dependencies:
+    postcss-selector-parser "^6.0.16"
+
 postcss-value-parser@^3.0.0:
   version "3.3.1"
   resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
   integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
 
-postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0:
+postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
   integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
@@ -15193,6 +22109,15 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.27, postcss@^7.0.3
     picocolors "^0.2.1"
     source-map "^0.6.1"
 
+postcss@^8.4.14, postcss@^8.4.24:
+  version "8.4.38"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e"
+  integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==
+  dependencies:
+    nanoid "^3.3.7"
+    picocolors "^1.0.0"
+    source-map-js "^1.2.0"
+
 postcss@^8.4.33, postcss@^8.4.35:
   version "8.4.35"
   resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.35.tgz#60997775689ce09011edf083a549cea44aabe2f7"
@@ -15207,11 +22132,28 @@ preact@^10.16.0:
   resolved "https://registry.yarnpkg.com/preact/-/preact-10.19.6.tgz#66007b67aad4d11899f583df1b0116d94a89b8f5"
   integrity sha512-gympg+T2Z1fG1unB8NH29yHJwnEaCH37Z32diPDku316OTnRPeMbiRV9kTrfZpocXjdfnWuFUl/Mj4BHaf6gnw==
 
+prebuildify@^6.0.0:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/prebuildify/-/prebuildify-6.0.1.tgz#655746f91fc95b68610615898678536dd303cd03"
+  integrity sha512-8Y2oOOateom/s8dNBsGIcnm6AxPmLH4/nanQzL5lQMU+sC0CMhzARZHizwr36pUPLdvBnOkCNQzxg4djuFSgIw==
+  dependencies:
+    minimist "^1.2.5"
+    mkdirp-classic "^0.5.3"
+    node-abi "^3.3.0"
+    npm-run-path "^3.1.0"
+    pump "^3.0.0"
+    tar-fs "^2.1.0"
+
 prelude-ls@^1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
   integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
 
+prelude-ls@~1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
+  integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==
+
 prepend-http@^1.0.0:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
@@ -15224,7 +22166,7 @@ prettier-linter-helpers@^1.0.0:
   dependencies:
     fast-diff "^1.1.2"
 
-"prettier@^1.18.2 || ^2.0.0", prettier@^2.6.2:
+"prettier@^1.18.2 || ^2.0.0", prettier@^2.3.1, prettier@^2.6.2:
   version "2.8.8"
   resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
   integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
@@ -15265,6 +22207,11 @@ proc-log@^3.0.0:
   resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-3.0.0.tgz#fb05ef83ccd64fd7b20bbe9c8c1070fc08338dd8"
   integrity sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==
 
+proc-log@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-4.2.0.tgz#b6f461e4026e75fdfe228b265e9f7a00779d7034"
+  integrity sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==
+
 process-nextick-args@~1.0.6:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
@@ -15280,7 +22227,7 @@ process-warning@^1.0.0:
   resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-1.0.0.tgz#980a0b25dc38cd6034181be4b7726d89066b4616"
   integrity sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==
 
-process@^0.11.10:
+process@0.11.10, process@^0.11.1, process@^0.11.10:
   version "0.11.10"
   resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
   integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
@@ -15302,6 +22249,26 @@ promise-poller@^1.9.1:
   dependencies:
     debug "^4.1.0"
 
+promise-retry@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22"
+  integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==
+  dependencies:
+    err-code "^2.0.2"
+    retry "^0.12.0"
+
+promise-settle@^0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/promise-settle/-/promise-settle-0.3.0.tgz#b4efd572a1eb74cf794f828cd349da40a08e4e96"
+  integrity sha512-sdZv9X6V2mnVSNibIHk44hI2jf6z9bhsm2OUU+hsU1JgdFuwsZVUcIGPreQ9wlu5wWlAGXzbyEbCUU+U8TQSHQ==
+
+promise@^8.0.0:
+  version "8.3.0"
+  resolved "https://registry.yarnpkg.com/promise/-/promise-8.3.0.tgz#8cb333d1edeb61ef23869fbb8a4ea0279ab60e0a"
+  integrity sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==
+  dependencies:
+    asap "~2.0.6"
+
 prompts@^2.0.1:
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
@@ -15310,11 +22277,34 @@ prompts@^2.0.1:
     kleur "^3.0.3"
     sisteransi "^1.0.5"
 
+propagate@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45"
+  integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==
+
 proto-list@~1.2.1:
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
   integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==
 
+protobufjs@^7.2.0, protobufjs@^7.2.5:
+  version "7.3.0"
+  resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.3.0.tgz#a32ec0422c039798c41a0700306a6e305b9cb32c"
+  integrity sha512-YWD03n3shzV9ImZRX3ccbjqLxj7NokGN0V/ESiBV5xWqrommYHYiihuIyavq03pWSGqlyvYUFmfoMKd+1rPA/g==
+  dependencies:
+    "@protobufjs/aspromise" "^1.1.2"
+    "@protobufjs/base64" "^1.1.2"
+    "@protobufjs/codegen" "^2.0.4"
+    "@protobufjs/eventemitter" "^1.1.0"
+    "@protobufjs/fetch" "^1.1.0"
+    "@protobufjs/float" "^1.0.2"
+    "@protobufjs/inquire" "^1.1.0"
+    "@protobufjs/path" "^1.1.2"
+    "@protobufjs/pool" "^1.1.0"
+    "@protobufjs/utf8" "^1.1.0"
+    "@types/node" ">=13.7.0"
+    long "^5.0.0"
+
 protobufjs@^7.2.4:
   version "7.2.6"
   resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.6.tgz#4a0ccd79eb292717aacf07530a07e0ed20278215"
@@ -15474,6 +22464,13 @@ qs@6.11.0:
   dependencies:
     side-channel "^1.0.4"
 
+qs@^6.11.0, qs@^6.7.0:
+  version "6.12.1"
+  resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.1.tgz#39422111ca7cbdb70425541cba20c7d7b216599a"
+  integrity sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==
+  dependencies:
+    side-channel "^1.0.6"
+
 qs@^6.11.2:
   version "6.11.2"
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9"
@@ -15530,7 +22527,7 @@ querystringify@^2.1.1:
   resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
   integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
 
-queue-microtask@^1.2.2:
+queue-microtask@^1.2.2, queue-microtask@^1.2.3:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
   integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
@@ -15580,7 +22577,7 @@ raw-body@2.5.1:
     iconv-lite "0.4.24"
     unpipe "1.0.0"
 
-raw-body@2.5.2:
+raw-body@2.5.2, raw-body@^2.4.1, raw-body@^2.5.1:
   version "2.5.2"
   resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a"
   integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==
@@ -15624,6 +22621,11 @@ react-native-webview@^11.26.0:
     escape-string-regexp "2.0.0"
     invariant "2.2.4"
 
+react-refresh@^0.10.0:
+  version "0.10.0"
+  resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.10.0.tgz#2f536c9660c0b9b1d500684d9e52a65e7404f7e3"
+  integrity sha512-PgidR3wST3dDYKr6b4pJoqQFpPGNKDSCDx4cZoshjXipw3LzO7mG1My2pwEzz2JVkF+inx3xRpDeQLFQGH/hsQ==
+
 react@>=17, react@^18.2.0:
   version "18.2.0"
   resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
@@ -15631,6 +22633,13 @@ react@>=17, react@^18.2.0:
   dependencies:
     loose-envify "^1.1.0"
 
+read-cache@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
+  integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==
+  dependencies:
+    pify "^2.3.0"
+
 read-pkg@^5.1.1:
   version "5.2.0"
   resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc"
@@ -15667,7 +22676,7 @@ readable-stream@2.3.3:
     string_decoder "~1.0.3"
     util-deprecate "~1.0.1"
 
-readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0, readable-stream@^3.6.2:
+readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0, readable-stream@^3.6.1, readable-stream@^3.6.2:
   version "3.6.2"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
   integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
@@ -15697,6 +22706,20 @@ real-require@^0.1.0:
   resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.1.0.tgz#736ac214caa20632847b7ca8c1056a0767df9381"
   integrity sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg==
 
+rechoir@^0.6.2:
+  version "0.6.2"
+  resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
+  integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==
+  dependencies:
+    resolve "^1.1.6"
+
+recursive-readdir@^2.2.2:
+  version "2.2.3"
+  resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.3.tgz#e726f328c0d69153bcabd5c322d3195252379372"
+  integrity sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==
+  dependencies:
+    minimatch "^3.0.5"
+
 redis-errors@^1.0.0, redis-errors@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad"
@@ -15709,6 +22732,16 @@ redis-parser@^3.0.0:
   dependencies:
     redis-errors "^1.0.0"
 
+reduce-flatten@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27"
+  integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==
+
+reflect-metadata@^0.1.13:
+  version "0.1.14"
+  resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.14.tgz#24cf721fe60677146bb77eeb0e1f9dece3d65859"
+  integrity sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==
+
 regenerate-unicode-properties@^10.1.0:
   version "10.1.1"
   resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz#6b0e05489d9076b04c436f318d9b067bba459480"
@@ -15801,6 +22834,25 @@ repeat-string@^1.6.1:
   resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
   integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==
 
+req-cwd@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/req-cwd/-/req-cwd-2.0.0.tgz#d4082b4d44598036640fb73ddea01ed53db49ebc"
+  integrity sha512-ueoIoLo1OfB6b05COxAA9UpeoscNpYyM+BqYlA7H6LVF4hKGPXQQSSaD2YmvDVJMkk4UDpAHIeU1zG53IqjvlQ==
+  dependencies:
+    req-from "^2.0.0"
+
+req-from@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/req-from/-/req-from-2.0.0.tgz#d74188e47f93796f4aa71df6ee35ae689f3e0e70"
+  integrity sha512-LzTfEVDVQHBRfjOUMgNBA+V6DWsSnoeKzf42J7l0xa/B4jyPOuuF5MlNSmomLNGemWTnV2TIdjSSLnEn95fOQA==
+  dependencies:
+    resolve-from "^3.0.0"
+
+request-ip@^3.3.0:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/request-ip/-/request-ip-3.3.0.tgz#863451e8fec03847d44f223e30a5d63e369fa611"
+  integrity sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA==
+
 request-progress@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe"
@@ -15839,6 +22891,11 @@ require-directory@^2.1.1:
   resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
   integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
 
+require-from-string@^2.0.0, require-from-string@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
+  integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
+
 require-main-filename@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
@@ -15898,7 +22955,19 @@ resolve.exports@^2.0.0:
   resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800"
   integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==
 
-resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.2, resolve@^1.22.4, resolve@^1.3.2:
+resolve@1.1.x:
+  version "1.1.7"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
+  integrity sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==
+
+resolve@1.17.0:
+  version "1.17.0"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
+  integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==
+  dependencies:
+    path-parse "^1.0.6"
+
+resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.1, resolve@^1.22.2, resolve@^1.22.4, resolve@^1.3.2:
   version "1.22.8"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
   integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
@@ -15935,6 +23004,11 @@ ret@~0.1.10:
   resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
   integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
 
+retry@0.13.1, retry@^0.13.1:
+  version "0.13.1"
+  resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658"
+  integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==
+
 retry@^0.12.0:
   version "0.12.0"
   resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
@@ -15960,7 +23034,7 @@ rgba-regex@^1.0.0:
   resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3"
   integrity sha512-zgn5OjNQXLUTdq8m17KdaicF6w89TZs8ZU8y0AYENIU6wG8GG6LLm0yLSiPY8DmaYmHdgRW8rnApjoT0fQRfMg==
 
-rimraf@^2.5.4, rimraf@^2.6.3:
+rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.3:
   version "2.7.1"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
   integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
@@ -15974,6 +23048,11 @@ rimraf@^3.0.0, rimraf@^3.0.2:
   dependencies:
     glob "^7.1.3"
 
+ringbufferjs@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/ringbufferjs/-/ringbufferjs-2.0.0.tgz#09f40e2675a99cfef430b7ec5815ac1bc2e24120"
+  integrity sha512-GCOqTzUsTHF7nrqcgtNGAFotXztLgiePpIDpyWZ7R5I02tmfJWV+/yuJc//Hlsd8G+WzI1t/dc2y/w2imDZdog==
+
 ripemd160@^2.0.0, ripemd160@^2.0.1:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
@@ -15982,7 +23061,7 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
     hash-base "^3.0.0"
     inherits "^2.0.1"
 
-rlp@^2.2.4:
+rlp@^2.2.3, rlp@^2.2.4:
   version "2.2.7"
   resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.7.tgz#33f31c4afac81124ac4b283e2bd4d9720b30beaf"
   integrity sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==
@@ -16033,6 +23112,13 @@ rrweb-cssom@^0.6.0:
   resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz#ed298055b97cbddcdeb278f904857629dec5e0e1"
   integrity sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==
 
+run-parallel-limit@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz#be80e936f5768623a38a963262d6bef8ff11e7ba"
+  integrity sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==
+  dependencies:
+    queue-microtask "^1.2.2"
+
 run-parallel@^1.1.9:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
@@ -16047,6 +23133,11 @@ run-queue@^1.0.0, run-queue@^1.0.3:
   dependencies:
     aproba "^1.1.1"
 
+rustbn.js@~0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/rustbn.js/-/rustbn.js-0.2.0.tgz#8082cb886e707155fd1cb6f23bd591ab8d55d0ca"
+  integrity sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA==
+
 rxjs@7.5.1:
   version "7.5.1"
   resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.1.tgz#af73df343cbcab37628197f43ea0c8256f54b157"
@@ -16054,7 +23145,7 @@ rxjs@7.5.1:
   dependencies:
     tslib "^2.1.0"
 
-rxjs@^7.5.1:
+rxjs@^7.5.1, rxjs@^7.8.0:
   version "7.8.1"
   resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543"
   integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==
@@ -16097,7 +23188,7 @@ safe-regex@^1.1.0:
   dependencies:
     ret "~0.1.10"
 
-safe-stable-stringify@^2.1.0:
+safe-stable-stringify@^2.1.0, safe-stable-stringify@^2.3.1:
   version "2.4.3"
   resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886"
   integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==
@@ -16107,6 +23198,14 @@ safe-stable-stringify@^2.1.0:
   resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
   integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
 
+sass-loader@^12.2.0:
+  version "12.6.0"
+  resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-12.6.0.tgz#5148362c8e2cdd4b950f3c63ac5d16dbfed37bcb"
+  integrity sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==
+  dependencies:
+    klona "^2.0.4"
+    neo-async "^2.6.2"
+
 sass-loader@^8.0.2:
   version "8.0.2"
   resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-8.0.2.tgz#debecd8c3ce243c76454f2e8290482150380090d"
@@ -16127,6 +23226,20 @@ sass@^1.26.5:
     immutable "^4.0.0"
     source-map-js ">=0.6.2 <2.0.0"
 
+sass@^1.42.1:
+  version "1.77.1"
+  resolved "https://registry.yarnpkg.com/sass/-/sass-1.77.1.tgz#018cdfb206afd14724030c02e9fefd8f30a76cd0"
+  integrity sha512-OMEyfirt9XEfyvocduUIOlUSkWOXS/LAt6oblR/ISXCTukyavjex+zQNm51pPCOiFKY1QpWvEH1EeCkgyV3I6w==
+  dependencies:
+    chokidar ">=3.0.0 <4.0.0"
+    immutable "^4.0.0"
+    source-map-js ">=0.6.2 <2.0.0"
+
+sax@^1.2.4:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0"
+  integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==
+
 sax@~1.2.4:
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
@@ -16139,6 +23252,26 @@ saxes@^6.0.0:
   dependencies:
     xmlchars "^2.2.0"
 
+sc-istanbul@^0.4.5:
+  version "0.4.6"
+  resolved "https://registry.yarnpkg.com/sc-istanbul/-/sc-istanbul-0.4.6.tgz#cf6784355ff2076f92d70d59047d71c13703e839"
+  integrity sha512-qJFF/8tW/zJsbyfh/iT/ZM5QNHE3CXxtLJbZsL+CzdJLBsPD7SedJZoUA4d8iAcN2IoMp/Dx80shOOd2x96X/g==
+  dependencies:
+    abbrev "1.0.x"
+    async "1.x"
+    escodegen "1.8.x"
+    esprima "2.7.x"
+    glob "^5.0.15"
+    handlebars "^4.0.1"
+    js-yaml "3.x"
+    mkdirp "0.5.x"
+    nopt "3.x"
+    once "1.x"
+    resolve "1.1.x"
+    supports-color "^3.1.0"
+    which "^1.1.1"
+    wordwrap "^1.0.0"
+
 scheduler@^0.23.0:
   version "0.23.0"
   resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
@@ -16173,6 +23306,25 @@ schema-utils@^2.0.0, schema-utils@^2.5.0, schema-utils@^2.6.1, schema-utils@^2.6
     ajv "^6.12.4"
     ajv-keywords "^3.5.2"
 
+schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.2.0:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe"
+  integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==
+  dependencies:
+    "@types/json-schema" "^7.0.8"
+    ajv "^6.12.5"
+    ajv-keywords "^3.5.2"
+
+schema-utils@^4.0.0, schema-utils@^4.0.1:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b"
+  integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==
+  dependencies:
+    "@types/json-schema" "^7.0.9"
+    ajv "^8.9.0"
+    ajv-formats "^2.1.1"
+    ajv-keywords "^5.1.0"
+
 scrypt-js@3.0.1, scrypt-js@^3.0.0, scrypt-js@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312"
@@ -16201,6 +23353,11 @@ secure-compare@3.0.1:
   resolved "https://registry.yarnpkg.com/secure-compare/-/secure-compare-3.0.1.tgz#f1a0329b308b221fae37b9974f3d578d0ca999e3"
   integrity sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==
 
+secure-keys@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/secure-keys/-/secure-keys-1.0.0.tgz#f0c82d98a3b139a8776a8808050b824431087fca"
+  integrity sha512-nZi59hW3Sl5P3+wOO89eHBAAGwmCPd2aE1+dLZV5MO+ItQctIvAqihzaAXIQhvtH4KJPxM080HsnqltR2y8cWg==
+
 select-hose@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
@@ -16218,6 +23375,14 @@ selfsigned@^1.10.8:
   dependencies:
     node-forge "^0.10.0"
 
+selfsigned@^2.1.1:
+  version "2.4.1"
+  resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0"
+  integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==
+  dependencies:
+    "@types/node-forge" "^1.3.0"
+    node-forge "^1"
+
 "semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.6.0:
   version "5.7.2"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8"
@@ -16235,6 +23400,11 @@ semver@^7.0.0, semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semve
   dependencies:
     lru-cache "^6.0.0"
 
+semver@^7.3.4, semver@^7.5.2, semver@^7.6.0:
+  version "7.6.2"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13"
+  integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==
+
 send@0.18.0:
   version "0.18.0"
   resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
@@ -16261,6 +23431,13 @@ serialize-error@^8.1.0:
   dependencies:
     type-fest "^0.20.2"
 
+serialize-javascript@6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8"
+  integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==
+  dependencies:
+    randombytes "^2.1.0"
+
 serialize-javascript@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
@@ -16268,6 +23445,13 @@ serialize-javascript@^4.0.0:
   dependencies:
     randombytes "^2.1.0"
 
+serialize-javascript@^6.0.0, serialize-javascript@^6.0.1:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2"
+  integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==
+  dependencies:
+    randombytes "^2.1.0"
+
 serve-index@^1.9.1:
   version "1.9.1"
   resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
@@ -16362,6 +23546,14 @@ sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8:
     inherits "^2.0.1"
     safe-buffer "^5.0.1"
 
+sha1@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/sha1/-/sha1-1.1.1.tgz#addaa7a93168f393f19eb2b15091618e2700f848"
+  integrity sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==
+  dependencies:
+    charenc ">= 0.0.1"
+    crypt ">= 0.0.1"
+
 shallow-clone@^3.0.0:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
@@ -16398,6 +23590,23 @@ shell-quote@^1.8.1:
   resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680"
   integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==
 
+shelljs@^0.8.3:
+  version "0.8.5"
+  resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c"
+  integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==
+  dependencies:
+    glob "^7.0.0"
+    interpret "^1.0.0"
+    rechoir "^0.6.2"
+
+short-uuid@^5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/short-uuid/-/short-uuid-5.2.0.tgz#49378f8c5335a603bc801c279ae521c5d22532dc"
+  integrity sha512-296/Nzi4DmANh93iYBwT4NoYRJuHnKEzefrkSagQbTH/A6NTaB68hSPDjm5IlbI5dx9FXdmtqPcj6N5H+CPm6w==
+  dependencies:
+    any-base "^1.1.0"
+    uuid "^9.0.1"
+
 side-channel@^1.0.4:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.5.tgz#9a84546599b48909fb6af1211708d23b1946221b"
@@ -16418,6 +23627,11 @@ side-channel@^1.0.6:
     get-intrinsic "^1.2.4"
     object-inspect "^1.13.1"
 
+sift@17.1.3:
+  version "17.1.3"
+  resolved "https://registry.yarnpkg.com/sift/-/sift-17.1.3.tgz#9d2000d4d41586880b0079b5183d839c7a142bf7"
+  integrity sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==
+
 siginfo@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30"
@@ -16447,6 +23661,15 @@ simple-get@^2.7.0:
     once "^1.3.1"
     simple-concat "^1.0.0"
 
+simple-get@^3.0.3:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.1.tgz#cc7ba77cfbe761036fbfce3d021af25fc5584d55"
+  integrity sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==
+  dependencies:
+    decompress-response "^4.2.0"
+    once "^1.3.1"
+    simple-concat "^1.0.0"
+
 simple-swizzle@^0.2.2:
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
@@ -16468,6 +23691,11 @@ sisteransi@^1.0.5:
   resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
   integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
 
+sjcl@^1.0.8:
+  version "1.0.8"
+  resolved "https://registry.yarnpkg.com/sjcl/-/sjcl-1.0.8.tgz#f2ec8d7dc1f0f21b069b8914a41a8f236b0e252a"
+  integrity sha512-LzIjEQ0S0DpIgnxMEayM1rq9aGwGRG4OnZhCdjx7glTaJtf4zRfpg87ImfjSJjoW9vKpagd82McDOwbRT5kQKQ==
+
 slash@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
@@ -16483,6 +23711,11 @@ slash@^3.0.0:
   resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
   integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
 
+slash@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7"
+  integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==
+
 slice-ansi@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787"
@@ -16501,6 +23734,19 @@ slice-ansi@^4.0.0:
     astral-regex "^2.0.0"
     is-fullwidth-code-point "^3.0.0"
 
+smart-buffer@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
+  integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
+
+snake-case@^3.0.4:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c"
+  integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==
+  dependencies:
+    dot-case "^3.0.4"
+    tslib "^2.0.3"
+
 snapdragon-node@^2.0.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
@@ -16560,7 +23806,7 @@ sockjs-client@^1.5.0:
     inherits "^2.0.4"
     url-parse "^1.5.10"
 
-sockjs@^0.3.21:
+sockjs@^0.3.21, sockjs@^0.3.24:
   version "0.3.24"
   resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce"
   integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==
@@ -16569,6 +23815,63 @@ sockjs@^0.3.21:
     uuid "^8.3.2"
     websocket-driver "^0.7.4"
 
+socks-proxy-agent@^8.0.3:
+  version "8.0.3"
+  resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz#6b2da3d77364fde6292e810b496cb70440b9b89d"
+  integrity sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==
+  dependencies:
+    agent-base "^7.1.1"
+    debug "^4.3.4"
+    socks "^2.7.1"
+
+socks@^2.7.1:
+  version "2.8.3"
+  resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.3.tgz#1ebd0f09c52ba95a09750afe3f3f9f724a800cb5"
+  integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==
+  dependencies:
+    ip-address "^9.0.5"
+    smart-buffer "^4.2.0"
+
+solc@0.7.3:
+  version "0.7.3"
+  resolved "https://registry.yarnpkg.com/solc/-/solc-0.7.3.tgz#04646961bd867a744f63d2b4e3c0701ffdc7d78a"
+  integrity sha512-GAsWNAjGzIDg7VxzP6mPjdurby3IkGCjQcM8GFYZT6RyaoUZKmMU6Y7YwG+tFGhv7dwZ8rmR4iwFDrrD99JwqA==
+  dependencies:
+    command-exists "^1.2.8"
+    commander "3.0.2"
+    follow-redirects "^1.12.1"
+    fs-extra "^0.30.0"
+    js-sha3 "0.8.0"
+    memorystream "^0.3.1"
+    require-from-string "^2.0.0"
+    semver "^5.5.0"
+    tmp "0.0.33"
+
+solidity-coverage@^0.8.0:
+  version "0.8.12"
+  resolved "https://registry.yarnpkg.com/solidity-coverage/-/solidity-coverage-0.8.12.tgz#c4fa2f64eff8ada7a1387b235d6b5b0e6c6985ed"
+  integrity sha512-8cOB1PtjnjFRqOgwFiD8DaUsYJtVJ6+YdXQtSZDrLGf8cdhhh8xzTtGzVTGeBf15kTv0v7lYPJlV/az7zLEPJw==
+  dependencies:
+    "@ethersproject/abi" "^5.0.9"
+    "@solidity-parser/parser" "^0.18.0"
+    chalk "^2.4.2"
+    death "^1.1.0"
+    difflib "^0.2.4"
+    fs-extra "^8.1.0"
+    ghost-testrpc "^0.0.2"
+    global-modules "^2.0.0"
+    globby "^10.0.1"
+    jsonschema "^1.2.4"
+    lodash "^4.17.21"
+    mocha "^10.2.0"
+    node-emoji "^1.10.0"
+    pify "^4.0.1"
+    recursive-readdir "^2.2.2"
+    sc-istanbul "^0.4.5"
+    semver "^7.3.4"
+    shelljs "^0.8.3"
+    web3-utils "^1.3.6"
+
 sonic-boom@^2.2.1:
   version "2.8.0"
   resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-2.8.0.tgz#c1def62a77425090e6ad7516aad8eb402e047611"
@@ -16593,6 +23896,20 @@ source-list-map@^2.0.0:
   resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
   integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
 
+source-map-js@^1.0.1, source-map-js@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
+  integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
+
+source-map-loader@^3.0.0:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-3.0.2.tgz#af23192f9b344daa729f6772933194cc5fa54fee"
+  integrity sha512-BokxPoLjyl3iOrgkWaakaxqnelAJSS+0V+De0kKIq6lyWrXuiPgYTGp6z3iHmqljKAaLXwZa+ctD8GccRJeVvg==
+  dependencies:
+    abab "^2.0.5"
+    iconv-lite "^0.6.3"
+    source-map-js "^1.0.1"
+
 source-map-resolve@^0.5.0, source-map-resolve@^0.5.2:
   version "0.5.3"
   resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a"
@@ -16620,7 +23937,7 @@ source-map-support@0.5.19:
     buffer-from "^1.0.0"
     source-map "^0.6.0"
 
-source-map-support@^0.5.21, source-map-support@~0.5.12:
+source-map-support@^0.5.13, source-map-support@^0.5.21, source-map-support@~0.5.12, source-map-support@~0.5.20:
   version "0.5.21"
   resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
   integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
@@ -16653,6 +23970,20 @@ source-map@^0.7.3, source-map@^0.7.4:
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656"
   integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==
 
+source-map@~0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d"
+  integrity sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==
+  dependencies:
+    amdefine ">=0.0.4"
+
+sparse-bitfield@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11"
+  integrity sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==
+  dependencies:
+    memory-pager "^1.0.2"
+
 spdx-correct@^3.0.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c"
@@ -16719,6 +24050,11 @@ split2@^4.0.0:
   resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4"
   integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==
 
+sprintf-js@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a"
+  integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==
+
 sprintf-js@~1.0.2:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
@@ -16739,6 +24075,13 @@ sshpk@^1.14.1, sshpk@^1.7.0:
     safer-buffer "^2.0.2"
     tweetnacl "~0.14.0"
 
+ssri@^10.0.0:
+  version "10.0.6"
+  resolved "https://registry.yarnpkg.com/ssri/-/ssri-10.0.6.tgz#a8aade2de60ba2bce8688e3fa349bad05c7dc1e5"
+  integrity sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==
+  dependencies:
+    minipass "^7.0.3"
+
 ssri@^6.0.1:
   version "6.0.2"
   resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5"
@@ -16758,6 +24101,11 @@ stable@^0.1.8:
   resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
   integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
 
+stack-trace@0.0.x:
+  version "0.0.10"
+  resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
+  integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==
+
 stack-utils@^2.0.3:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f"
@@ -16775,6 +24123,13 @@ stackframe@^1.3.4:
   resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310"
   integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==
 
+stacktrace-parser@^0.1.10:
+  version "0.1.10"
+  resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz#29fb0cae4e0d0b85155879402857a1639eb6051a"
+  integrity sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==
+  dependencies:
+    type-fest "^0.7.1"
+
 standard-as-callback@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45"
@@ -16793,7 +24148,7 @@ statuses@2.0.1:
   resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
   integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
 
-"statuses@>= 1.4.0 < 2":
+"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@^1.5.0:
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
   integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
@@ -16803,6 +24158,11 @@ std-env@^3.5.0, std-env@^3.7.0:
   resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2"
   integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==
 
+stoppable@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/stoppable/-/stoppable-1.1.0.tgz#32da568e83ea488b08e4d7ea2c3bcc9d75015d5b"
+  integrity sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==
+
 stream-browserify@^2.0.1:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b"
@@ -16853,6 +24213,20 @@ stream-shift@^1.0.0:
   resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.3.tgz#85b8fab4d71010fc3ba8772e8046cc49b8a3864b"
   integrity sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==
 
+streamroller@^3.1.5:
+  version "3.1.5"
+  resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-3.1.5.tgz#1263182329a45def1ffaef58d31b15d13d2ee7ff"
+  integrity sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==
+  dependencies:
+    date-format "^4.0.14"
+    debug "^4.3.4"
+    fs-extra "^8.1.0"
+
+streamsearch@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
+  integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
+
 strict-uri-encode@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
@@ -16863,6 +24237,11 @@ strict-uri-encode@^2.0.0:
   resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
   integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==
 
+string-format@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b"
+  integrity sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA==
+
 string-length@^4.0.1:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"
@@ -16871,7 +24250,7 @@ string-length@^4.0.1:
     char-regex "^1.0.2"
     strip-ansi "^6.0.0"
 
-"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
   version "4.2.3"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -16880,6 +24259,14 @@ string-length@^4.0.1:
     is-fullwidth-code-point "^3.0.0"
     strip-ansi "^6.0.1"
 
+string-width@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
+  integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
+  dependencies:
+    is-fullwidth-code-point "^2.0.0"
+    strip-ansi "^4.0.0"
+
 string-width@^3.0.0, string-width@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
@@ -16898,6 +24285,11 @@ string-width@^5.0.1, string-width@^5.1.2:
     emoji-regex "^9.2.2"
     strip-ansi "^7.0.1"
 
+string.fromcodepoint@^0.2.1:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/string.fromcodepoint/-/string.fromcodepoint-0.2.1.tgz#8d978333c0bc92538f50f383e4888f3e5619d653"
+  integrity sha512-n69H31OnxSGSZyZbgBlvYIXlrMhJQ0dQAX1js1QDhpaUH6zmU3QYlj07bCwCNlPOu3oRXIubGPl2gDGnHsiCqg==
+
 string.prototype.trim@^1.2.8:
   version "1.2.8"
   resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz#f9ac6f8af4bd55ddfa8895e6aea92a96395393bd"
@@ -16960,6 +24352,13 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1:
   dependencies:
     ansi-regex "^2.0.0"
 
+strip-ansi@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
+  integrity sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==
+  dependencies:
+    ansi-regex "^3.0.0"
+
 strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
   version "5.2.0"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
@@ -17011,16 +24410,16 @@ strip-indent@^2.0.0:
   resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68"
   integrity sha512-RsSNPLpq6YUL7QYy44RnPVTn/lcVZtb48Uof3X5JLbF4zD/Gs7ZFDv2HWol+leoQN2mT86LAzSshGfkTlSOpsA==
 
+strip-json-comments@3.1.1, strip-json-comments@^3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
+  integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+
 strip-json-comments@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
   integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==
 
-strip-json-comments@^3.1.1:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
-  integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
-
 strip-literal@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-2.0.0.tgz#5d063580933e4e03ebb669b12db64d2200687527"
@@ -17028,6 +24427,11 @@ strip-literal@^2.0.0:
   dependencies:
     js-tokens "^8.0.2"
 
+strnum@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db"
+  integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==
+
 strong-log-transformer@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz#0f5ed78d325e0421ac6f90f7f10e691d6ae3ae10"
@@ -17037,6 +24441,16 @@ strong-log-transformer@^2.1.0:
     minimist "^1.2.0"
     through "^2.3.4"
 
+sturdy-websocket@^0.2.1:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/sturdy-websocket/-/sturdy-websocket-0.2.1.tgz#20a58fd53372ef96eaa08f3c61c91a10b07c7c05"
+  integrity sha512-NnzSOEKyv4I83qbuKw9ROtJrrT6Z/Xt7I0HiP/e6H6GnpeTDvzwGIGeJ8slai+VwODSHQDooW2CAilJwT9SpRg==
+
+style-loader@^3.3.0:
+  version "3.3.4"
+  resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.4.tgz#f30f786c36db03a45cbd55b6a70d930c479090e7"
+  integrity sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==
+
 stylehacks@^4.0.0:
   version "4.0.3"
   resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5"
@@ -17046,6 +24460,14 @@ stylehacks@^4.0.0:
     postcss "^7.0.0"
     postcss-selector-parser "^3.0.0"
 
+stylehacks@^6.1.1:
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-6.1.1.tgz#543f91c10d17d00a440430362d419f79c25545a6"
+  integrity sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==
+  dependencies:
+    browserslist "^4.23.0"
+    postcss-selector-parser "^6.0.16"
+
 stylis@4.2.0:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51"
@@ -17060,6 +24482,14 @@ stylus-loader@^3.0.2:
     lodash.clonedeep "^4.5.0"
     when "~3.6.x"
 
+stylus-loader@^7.1.0:
+  version "7.1.3"
+  resolved "https://registry.yarnpkg.com/stylus-loader/-/stylus-loader-7.1.3.tgz#1fdfa0d34e8c05a569bc0902e1ecdb857d764964"
+  integrity sha512-TY0SKwiY7D2kMd3UxaWKSf3xHF0FFN/FAfsSqfrhxRT/koXTwffq2cgEWDkLQz7VojMu7qEEHt5TlMjkPx9UDw==
+  dependencies:
+    fast-glob "^3.2.12"
+    normalize-path "^3.0.0"
+
 stylus@^0.54.7:
   version "0.54.8"
   resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.54.8.tgz#3da3e65966bc567a7b044bfe0eece653e099d147"
@@ -17074,16 +24504,64 @@ stylus@^0.54.7:
     semver "^6.3.0"
     source-map "^0.7.3"
 
+stylus@^0.59.0:
+  version "0.59.0"
+  resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.59.0.tgz#a344d5932787142a141946536d6e24e6a6be7aa6"
+  integrity sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg==
+  dependencies:
+    "@adobe/css-tools" "^4.0.1"
+    debug "^4.3.2"
+    glob "^7.1.6"
+    sax "~1.2.4"
+    source-map "^0.7.3"
+
+superagent@^9.0.1:
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/superagent/-/superagent-9.0.2.tgz#a18799473fc57557289d6b63960610e358bdebc1"
+  integrity sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==
+  dependencies:
+    component-emitter "^1.3.0"
+    cookiejar "^2.1.4"
+    debug "^4.3.4"
+    fast-safe-stringify "^2.1.1"
+    form-data "^4.0.0"
+    formidable "^3.5.1"
+    methods "^1.1.2"
+    mime "2.6.0"
+    qs "^6.11.0"
+
 superstruct@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.3.tgz#de626a5b49c6641ff4d37da3c7598e7a87697046"
   integrity sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg==
 
+supertest@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/supertest/-/supertest-7.0.0.tgz#cac53b3d6872a0b317980b2b0cfa820f09cd7634"
+  integrity sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==
+  dependencies:
+    methods "^1.1.2"
+    superagent "^9.0.1"
+
+supports-color@8.1.1, supports-color@^8.0.0, supports-color@^8.1.1:
+  version "8.1.1"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
+  integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
+  dependencies:
+    has-flag "^4.0.0"
+
 supports-color@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
   integrity sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==
 
+supports-color@^3.1.0:
+  version "3.2.3"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
+  integrity sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==
+  dependencies:
+    has-flag "^1.0.0"
+
 supports-color@^5.3.0:
   version "5.5.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@@ -17105,18 +24583,16 @@ supports-color@^7.1.0:
   dependencies:
     has-flag "^4.0.0"
 
-supports-color@^8.0.0, supports-color@^8.1.1:
-  version "8.1.1"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
-  integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
-  dependencies:
-    has-flag "^4.0.0"
-
 supports-preserve-symlinks-flag@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
   integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
 
+svg-parser@^2.0.4:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5"
+  integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==
+
 svg-tags@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
@@ -17141,6 +24617,19 @@ svgo@^1.0.0:
     unquote "~1.1.1"
     util.promisify "~1.0.0"
 
+svgo@^3.0.2, svgo@^3.2.0:
+  version "3.3.2"
+  resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.3.2.tgz#ad58002652dffbb5986fc9716afe52d869ecbda8"
+  integrity sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==
+  dependencies:
+    "@trysound/sax" "0.2.0"
+    commander "^7.2.0"
+    css-select "^5.1.0"
+    css-tree "^2.3.1"
+    css-what "^6.1.0"
+    csso "^5.0.5"
+    picocolors "^1.0.0"
+
 swarm-js@^0.1.40:
   version "0.1.42"
   resolved "https://registry.yarnpkg.com/swarm-js/-/swarm-js-0.1.42.tgz#497995c62df6696f6e22372f457120e43e727979"
@@ -17170,22 +24659,69 @@ symbol-tree@^3.2.4:
   resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
   integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
 
+sync-request@^6.0.0, sync-request@^6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/sync-request/-/sync-request-6.1.0.tgz#e96217565b5e50bbffe179868ba75532fb597e68"
+  integrity sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==
+  dependencies:
+    http-response-object "^3.0.1"
+    sync-rpc "^1.2.1"
+    then-request "^6.0.0"
+
+sync-rpc@^1.2.1:
+  version "1.3.6"
+  resolved "https://registry.yarnpkg.com/sync-rpc/-/sync-rpc-1.3.6.tgz#b2e8b2550a12ccbc71df8644810529deb68665a7"
+  integrity sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==
+  dependencies:
+    get-port "^3.1.0"
+
 system-architecture@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/system-architecture/-/system-architecture-0.1.0.tgz#71012b3ac141427d97c67c56bc7921af6bff122d"
   integrity sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==
 
+table-layout@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-1.0.2.tgz#c4038a1853b0136d63365a734b6931cf4fad4a04"
+  integrity sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==
+  dependencies:
+    array-back "^4.0.1"
+    deep-extend "~0.6.0"
+    typical "^5.2.0"
+    wordwrapjs "^4.0.0"
+
+table@^6.8.0:
+  version "6.8.2"
+  resolved "https://registry.yarnpkg.com/table/-/table-6.8.2.tgz#c5504ccf201213fa227248bdc8c5569716ac6c58"
+  integrity sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==
+  dependencies:
+    ajv "^8.0.1"
+    lodash.truncate "^4.4.2"
+    slice-ansi "^4.0.0"
+    string-width "^4.2.3"
+    strip-ansi "^6.0.1"
+
 tapable@^1.0.0, tapable@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
   integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
 
-tapable@^2.2.0:
+tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
   integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
 
-tar-stream@~2.2.0:
+tar-fs@^2.1.0:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
+  integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
+  dependencies:
+    chownr "^1.1.1"
+    mkdirp-classic "^0.5.2"
+    pump "^3.0.0"
+    tar-stream "^2.1.4"
+
+tar-stream@^2.1.4, tar-stream@~2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
   integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
@@ -17209,6 +24745,18 @@ tar@^4.0.2:
     safe-buffer "^5.2.1"
     yallist "^3.1.1"
 
+tar@^6.1.11, tar@^6.1.2:
+  version "6.2.1"
+  resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a"
+  integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==
+  dependencies:
+    chownr "^2.0.0"
+    fs-minipass "^2.0.0"
+    minipass "^5.0.0"
+    minizlib "^2.1.1"
+    mkdirp "^1.0.3"
+    yallist "^4.0.0"
+
 terser-webpack-plugin@^1.4.3, terser-webpack-plugin@^1.4.4:
   version "1.4.5"
   resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz#a217aefaea330e734ffacb6120ec1fa312d6040b"
@@ -17224,6 +24772,17 @@ terser-webpack-plugin@^1.4.3, terser-webpack-plugin@^1.4.4:
     webpack-sources "^1.4.0"
     worker-farm "^1.7.0"
 
+terser-webpack-plugin@^5.3.10, terser-webpack-plugin@^5.3.3:
+  version "5.3.10"
+  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199"
+  integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==
+  dependencies:
+    "@jridgewell/trace-mapping" "^0.3.20"
+    jest-worker "^27.4.5"
+    schema-utils "^3.1.1"
+    serialize-javascript "^6.0.1"
+    terser "^5.26.0"
+
 terser@^4.1.2:
   version "4.8.1"
   resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.1.tgz#a00e5634562de2239fd404c649051bf6fc21144f"
@@ -17233,6 +24792,16 @@ terser@^4.1.2:
     source-map "~0.6.1"
     source-map-support "~0.5.12"
 
+terser@^5.26.0:
+  version "5.31.0"
+  resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.0.tgz#06eef86f17007dbad4593f11a574c7f5eb02c6a1"
+  integrity sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==
+  dependencies:
+    "@jridgewell/source-map" "^0.3.3"
+    acorn "^8.8.2"
+    commander "^2.20.0"
+    source-map-support "~0.5.20"
+
 test-exclude@^6.0.0:
   version "6.0.0"
   resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e"
@@ -17242,11 +24811,33 @@ test-exclude@^6.0.0:
     glob "^7.1.4"
     minimatch "^3.0.4"
 
+text-hex@1.0.x:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5"
+  integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==
+
 text-table@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
   integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
 
+then-request@^6.0.0:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/then-request/-/then-request-6.0.2.tgz#ec18dd8b5ca43aaee5cb92f7e4c1630e950d4f0c"
+  integrity sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==
+  dependencies:
+    "@types/concat-stream" "^1.6.0"
+    "@types/form-data" "0.0.33"
+    "@types/node" "^8.0.0"
+    "@types/qs" "^6.2.31"
+    caseless "~0.12.0"
+    concat-stream "^1.6.0"
+    form-data "^2.2.0"
+    http-basic "^8.1.1"
+    http-response-object "^3.0.1"
+    promise "^8.0.0"
+    qs "^6.4.0"
+
 thenify-all@^1.0.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
@@ -17327,16 +24918,23 @@ tinybench@^2.5.1:
   resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.6.0.tgz#1423284ee22de07c91b3752c048d2764714b341b"
   integrity sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==
 
-tinypool@^0.8.2:
-  version "0.8.2"
-  resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.8.2.tgz#84013b03dc69dacb322563a475d4c0a9be00f82a"
-  integrity sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==
+tinypool@^0.8.3:
+  version "0.8.4"
+  resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.8.4.tgz#e217fe1270d941b39e98c625dcecebb1408c9aa8"
+  integrity sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==
 
 tinyspy@^2.2.0:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-2.2.1.tgz#117b2342f1f38a0dbdcc73a50a454883adf861d1"
   integrity sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==
 
+tmp@0.0.33:
+  version "0.0.33"
+  resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
+  integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
+  dependencies:
+    os-tmpdir "~1.0.2"
+
 tmp@~0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
@@ -17443,21 +25041,41 @@ tr46@~0.0.3:
   resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
   integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
 
+triple-beam@^1.3.0:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984"
+  integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==
+
 tryer@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
   integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==
 
-ts-api-utils@^1.0.1:
+ts-api-utils@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1"
   integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==
 
+ts-command-line-args@^2.2.0:
+  version "2.5.1"
+  resolved "https://registry.yarnpkg.com/ts-command-line-args/-/ts-command-line-args-2.5.1.tgz#e64456b580d1d4f6d948824c274cf6fa5f45f7f0"
+  integrity sha512-H69ZwTw3rFHb5WYpQya40YAX2/w7Ut75uUECbgBIsLmM+BNuYnxsltfyyLMxy6sEeKxgijLTnQtLd0nKd6+IYw==
+  dependencies:
+    chalk "^4.1.0"
+    command-line-args "^5.1.1"
+    command-line-usage "^6.1.0"
+    string-format "^2.0.0"
+
 ts-custom-error@^3.3.1:
   version "3.3.1"
   resolved "https://registry.yarnpkg.com/ts-custom-error/-/ts-custom-error-3.3.1.tgz#8bd3c8fc6b8dc8e1cb329267c45200f1e17a65d1"
   integrity sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==
 
+ts-essentials@^7.0.1:
+  version "7.0.3"
+  resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-7.0.3.tgz#686fd155a02133eedcc5362dc8b5056cde3e5a38"
+  integrity sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==
+
 ts-jest@29.1.2:
   version "29.1.2"
   resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.2.tgz#7613d8c81c43c8cb312c6904027257e814c40e09"
@@ -17483,6 +25101,22 @@ ts-loader@^6.2.2:
     micromatch "^4.0.0"
     semver "^6.0.0"
 
+ts-loader@^9.3.1:
+  version "9.5.1"
+  resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.5.1.tgz#63d5912a86312f1fbe32cef0859fb8b2193d9b89"
+  integrity sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==
+  dependencies:
+    chalk "^4.1.0"
+    enhanced-resolve "^5.0.0"
+    micromatch "^4.0.0"
+    semver "^7.3.4"
+    source-map "^0.7.4"
+
+ts-mixer@^6.0.4:
+  version "6.0.4"
+  resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.4.tgz#1da39ceabc09d947a82140d9f09db0f84919ca28"
+  integrity sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==
+
 ts-node@10.9.1:
   version "10.9.1"
   resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b"
@@ -17521,6 +25155,15 @@ tsconfig-paths-webpack-plugin@3.5.2:
     enhanced-resolve "^5.7.0"
     tsconfig-paths "^3.9.0"
 
+tsconfig-paths-webpack-plugin@4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.0.0.tgz#84008fc3e3e0658fdb0262758b07b4da6265ff1a"
+  integrity sha512-fw/7265mIWukrSHd0i+wSwx64kYUSAKPfxRDksjKIYTxSAp9W9/xcZVBF4Kl0eqQd5eBpAQ/oQrc5RyM/0c1GQ==
+  dependencies:
+    chalk "^4.1.0"
+    enhanced-resolve "^5.7.0"
+    tsconfig-paths "^4.0.0"
+
 tsconfig-paths@^3.15.0, tsconfig-paths@^3.9.0:
   version "3.15.0"
   resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4"
@@ -17531,7 +25174,7 @@ tsconfig-paths@^3.15.0, tsconfig-paths@^3.9.0:
     minimist "^1.2.6"
     strip-bom "^3.0.0"
 
-tsconfig-paths@^4.1.2:
+tsconfig-paths@^4.0.0, tsconfig-paths@^4.1.2:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c"
   integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==
@@ -17550,12 +25193,12 @@ tsconfig@^7.0.0:
     strip-bom "^3.0.0"
     strip-json-comments "^2.0.0"
 
-tslib@1.14.1, tslib@^1.10.0, tslib@^1.8.0, tslib@^1.8.1:
+tslib@1.14.1, tslib@^1.10.0, tslib@^1.11.1, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.3:
   version "1.14.1"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
   integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
 
-tslib@^2.0.0, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.6.2:
+tslib@2.6.2, tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.4.1, tslib@^2.6.2:
   version "2.6.2"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
   integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
@@ -17579,6 +25222,16 @@ tslint@^5.20.1:
     tslib "^1.8.0"
     tsutils "^2.29.0"
 
+tsort@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/tsort/-/tsort-0.0.1.tgz#e2280f5e817f8bf4275657fd0f9aebd44f5a2786"
+  integrity sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==
+
+tsscmp@1.0.6, tsscmp@^1.0.5:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb"
+  integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==
+
 tsutils@^2.29.0:
   version "2.29.0"
   resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99"
@@ -17603,11 +25256,21 @@ tunnel-agent@^0.6.0:
   dependencies:
     safe-buffer "^5.0.1"
 
+tweetnacl-util@^0.15.1:
+  version "0.15.1"
+  resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz#b80fcdb5c97bcc508be18c44a4be50f022eea00b"
+  integrity sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==
+
 tweetnacl@^0.14.3, tweetnacl@~0.14.0:
   version "0.14.5"
   resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
   integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==
 
+tweetnacl@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596"
+  integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==
+
 type-check@^0.4.0, type-check@~0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
@@ -17615,6 +25278,13 @@ type-check@^0.4.0, type-check@~0.4.0:
   dependencies:
     prelude-ls "^1.2.1"
 
+type-check@~0.3.2:
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
+  integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==
+  dependencies:
+    prelude-ls "~1.1.2"
+
 type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.8:
   version "4.0.8"
   resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
@@ -17635,7 +25305,12 @@ type-fest@^0.6.0:
   resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"
   integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==
 
-type-is@~1.6.18:
+type-fest@^0.7.1:
+  version "0.7.1"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48"
+  integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==
+
+type-is@^1.6.16, type-is@^1.6.4, type-is@~1.6.18:
   version "1.6.18"
   resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
   integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
@@ -17653,6 +25328,22 @@ type@^2.7.2:
   resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0"
   integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==
 
+typechain@^8.3.0:
+  version "8.3.2"
+  resolved "https://registry.yarnpkg.com/typechain/-/typechain-8.3.2.tgz#1090dd8d9c57b6ef2aed3640a516bdbf01b00d73"
+  integrity sha512-x/sQYr5w9K7yv3es7jo4KTX05CLxOf7TRWwoHlrjRh8H82G64g+k7VuWPJlgMo6qrjfCulOdfBjiaDtmhFYD/Q==
+  dependencies:
+    "@types/prettier" "^2.1.1"
+    debug "^4.3.1"
+    fs-extra "^7.0.0"
+    glob "7.1.7"
+    js-sha3 "^0.8.0"
+    lodash "^4.17.15"
+    mkdirp "^1.0.4"
+    prettier "^2.3.1"
+    ts-command-line-args "^2.2.0"
+    ts-essentials "^7.0.1"
+
 typed-array-buffer@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3"
@@ -17697,6 +25388,11 @@ typed-array-length@^1.0.4:
     is-typed-array "^1.1.13"
     possible-typed-array-names "^1.0.0"
 
+typed-assert@^1.0.8:
+  version "1.0.9"
+  resolved "https://registry.yarnpkg.com/typed-assert/-/typed-assert-1.0.9.tgz#8af9d4f93432c4970ec717e3006f33f135b06213"
+  integrity sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==
+
 typedarray-to-buffer@^3.1.5:
   version "3.1.5"
   resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
@@ -17714,10 +25410,20 @@ typescript@4.5.4:
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8"
   integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==
 
-typescript@^5.3.3, typescript@~5.3.2:
-  version "5.3.3"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37"
-  integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
+typescript@5.4.5, typescript@~5.4.2:
+  version "5.4.5"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611"
+  integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==
+
+typical@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4"
+  integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==
+
+typical@^5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066"
+  integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==
 
 ufo@^1.3.0, ufo@^1.3.1, ufo@^1.3.2:
   version "1.4.0"
@@ -17732,6 +25438,11 @@ uglify-js@3.4.x:
     commander "~2.19.0"
     source-map "~0.6.1"
 
+uglify-js@^3.1.4:
+  version "3.17.4"
+  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c"
+  integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==
+
 uint8arrays@^3.0.0, uint8arrays@^3.1.0:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.1.1.tgz#2d8762acce159ccd9936057572dade9459f65ae0"
@@ -17764,6 +25475,18 @@ undici-types@~5.26.4:
   resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
   integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
 
+undici@6.13.0:
+  version "6.13.0"
+  resolved "https://registry.yarnpkg.com/undici/-/undici-6.13.0.tgz#7edbf4b7f3aac5f8a681d515151bf55cb3589d72"
+  integrity sha512-Q2rtqmZWrbP8nePMq7mOJIN98M0fYvSgV89vwl/BQRT4mDOeY2GXZngfGpcBBhtky3woM7G24wZV3Q304Bv6cw==
+
+undici@^5.14.0:
+  version "5.28.4"
+  resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068"
+  integrity sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==
+  dependencies:
+    "@fastify/busboy" "^2.0.0"
+
 unenv@^1.9.0:
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/unenv/-/unenv-1.9.0.tgz#469502ae85be1bd3a6aa60f810972b1a904ca312"
@@ -17775,6 +25498,20 @@ unenv@^1.9.0:
     node-fetch-native "^1.6.1"
     pathe "^1.1.1"
 
+unescape-js@^1.1.4:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/unescape-js/-/unescape-js-1.1.4.tgz#4bc6389c499cb055a98364a0b3094e1c3d5da395"
+  integrity sha512-42SD8NOQEhdYntEiUQdYq/1V/YHwr1HLwlHuTJB5InVVdOSbgI6xu8jK5q65yIzuFCfczzyDF/7hbGzVbyCw0g==
+  dependencies:
+    string.fromcodepoint "^0.2.1"
+
+unescape@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/unescape/-/unescape-1.0.1.tgz#956e430f61cad8a4d57d82c518f5e6cc5d0dda96"
+  integrity sha512-O0+af1Gs50lyH1nUu3ZyYS1cRh01Q/kUKatTOkSs7jukXE6/NebucDVxyiDsA9AQ4JC1V1jUH9EO8JX2nMDgGQ==
+  dependencies:
+    extend-shallow "^2.0.1"
+
 unfetch@^4.2.0:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be"
@@ -17837,6 +25574,13 @@ unique-filename@^1.1.1:
   dependencies:
     unique-slug "^2.0.0"
 
+unique-filename@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-3.0.0.tgz#48ba7a5a16849f5080d26c760c86cf5cf05770ea"
+  integrity sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==
+  dependencies:
+    unique-slug "^4.0.0"
+
 unique-slug@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c"
@@ -17844,6 +25588,18 @@ unique-slug@^2.0.0:
   dependencies:
     imurmurhash "^0.1.4"
 
+unique-slug@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-4.0.0.tgz#6bae6bb16be91351badd24cdce741f892a6532e3"
+  integrity sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==
+  dependencies:
+    imurmurhash "^0.1.4"
+
+unique-username-generator@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/unique-username-generator/-/unique-username-generator-1.3.0.tgz#f1da1977fcd4d9f2297795960a0cff6bbda87601"
+  integrity sha512-oPs+Jq9UlXmcrpzrpYvWOWVUVLkJ0/x8zM66ikWEBe1l/hBqXS5fofsDvdScLIlPtqQKlu8/Y91b6T2XtIFubQ==
+
 universal-user-agent@^6.0.0:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.1.tgz#15f20f55da3c930c57bddbf1734c6654d5fd35aa"
@@ -17977,7 +25733,7 @@ uqr@^0.1.2:
   resolved "https://registry.yarnpkg.com/uqr/-/uqr-0.1.2.tgz#5c6cd5dcff9581f9bb35b982cb89e2c483a41d7d"
   integrity sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA==
 
-uri-js@^4.2.2:
+uri-js@^4.2.2, uri-js@^4.4.1:
   version "4.4.1"
   resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
   integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
@@ -18003,7 +25759,7 @@ url-loader@^2.2.0:
     mime "^2.4.4"
     schema-utils "^2.5.0"
 
-url-parse@^1.5.10, url-parse@^1.5.3:
+url-parse@^1.4.3, url-parse@^1.5.10, url-parse@^1.5.3:
   version "1.5.10"
   resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
   integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
@@ -18016,6 +25772,11 @@ url-set-query@^1.0.0:
   resolved "https://registry.yarnpkg.com/url-set-query/-/url-set-query-1.0.0.tgz#016e8cfd7c20ee05cafe7795e892bd0702faa339"
   integrity sha512-3AChu4NiXquPfeckE5R5cGdiHCMWJx1dwCWOmWIL4KHAziJNOFIYJlpGFeKDvwLPHovZRCxK3cYlwzqI9Vp+Gg==
 
+url-template@^2.0.8:
+  version "2.0.8"
+  resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21"
+  integrity sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==
+
 url@^0.11.0:
   version "0.11.3"
   resolved "https://registry.yarnpkg.com/url/-/url-0.11.3.tgz#6f495f4b935de40ce4a0a52faee8954244f3d3ad"
@@ -18076,7 +25837,7 @@ util.promisify@~1.0.0:
     has-symbols "^1.0.1"
     object.getownpropertydescriptors "^2.1.0"
 
-util@^0.10.4:
+util@^0.10.3, util@^0.10.4:
   version "0.10.4"
   resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901"
   integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==
@@ -18121,7 +25882,7 @@ uuid@^8.3.2:
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
   integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
 
-uuid@^9.0.0:
+uuid@^9.0.0, uuid@^9.0.1:
   version "9.0.1"
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
   integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==
@@ -18131,7 +25892,7 @@ v8-compile-cache-lib@^3.0.1:
   resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
   integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
 
-v8-to-istanbul@^9.0.0, v8-to-istanbul@^9.0.1, v8-to-istanbul@^9.2.0:
+v8-to-istanbul@^9.0.0, v8-to-istanbul@^9.0.1:
   version "9.2.0"
   resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz#2ed7644a245cddd83d4e087b9b33b3e62dfd10ad"
   integrity sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==
@@ -18155,6 +25916,11 @@ validate-npm-package-name@^5.0.0:
   dependencies:
     builtins "^5.0.0"
 
+validator@^13.9.0:
+  version "13.12.0"
+  resolved "https://registry.yarnpkg.com/validator/-/validator-13.12.0.tgz#7d78e76ba85504da3fee4fd1922b385914d4b35f"
+  integrity sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==
+
 valtio@1.11.2:
   version "1.11.2"
   resolved "https://registry.yarnpkg.com/valtio/-/valtio-1.11.2.tgz#b8049c02dfe65620635d23ebae9121a741bb6530"
@@ -18168,7 +25934,7 @@ varint@^5.0.0:
   resolved "https://registry.yarnpkg.com/varint/-/varint-5.0.2.tgz#5b47f8a947eb668b848e034dcfa87d0ff8a7f7a4"
   integrity sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==
 
-vary@^1, vary@~1.1.2:
+vary@^1, vary@^1.1.2, vary@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
   integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
@@ -18229,10 +25995,10 @@ viem@^2.7.13:
     isows "1.0.3"
     ws "8.13.0"
 
-vite-node@1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-1.4.0.tgz#265529d60570ca695ceb69391f87f92847934ad8"
-  integrity sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==
+vite-node@1.6.0:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-1.6.0.tgz#2c7e61129bfecc759478fa592754fd9704aaba7f"
+  integrity sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==
   dependencies:
     cac "^6.7.14"
     debug "^4.3.4"
@@ -18287,16 +26053,16 @@ vite@5.1.6, vite@^5.0.0:
   optionalDependencies:
     fsevents "~2.3.3"
 
-vitest@^1.3.1:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/vitest/-/vitest-1.4.0.tgz#f5c812aaf5023818b89b7fc667fa45327396fece"
-  integrity sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==
-  dependencies:
-    "@vitest/expect" "1.4.0"
-    "@vitest/runner" "1.4.0"
-    "@vitest/snapshot" "1.4.0"
-    "@vitest/spy" "1.4.0"
-    "@vitest/utils" "1.4.0"
+vitest@1.6.0:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/vitest/-/vitest-1.6.0.tgz#9d5ad4752a3c451be919e412c597126cffb9892f"
+  integrity sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==
+  dependencies:
+    "@vitest/expect" "1.6.0"
+    "@vitest/runner" "1.6.0"
+    "@vitest/snapshot" "1.6.0"
+    "@vitest/spy" "1.6.0"
+    "@vitest/utils" "1.6.0"
     acorn-walk "^8.3.2"
     chai "^4.3.10"
     debug "^4.3.4"
@@ -18308,9 +26074,9 @@ vitest@^1.3.1:
     std-env "^3.5.0"
     strip-literal "^2.0.0"
     tinybench "^2.5.1"
-    tinypool "^0.8.2"
+    tinypool "^0.8.3"
     vite "^5.0.0"
-    vite-node "1.4.0"
+    vite-node "1.6.0"
     why-is-node-running "^2.2.2"
 
 vm-browserify@^1.0.1:
@@ -18473,6 +26239,14 @@ watchpack@^1.7.4:
     chokidar "^3.4.1"
     watchpack-chokidar2 "^2.0.1"
 
+watchpack@^2.4.1:
+  version "2.4.1"
+  resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.1.tgz#29308f2cac150fa8e4c92f90e0ec954a9fed7fff"
+  integrity sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==
+  dependencies:
+    glob-to-regexp "^0.4.1"
+    graceful-fs "^4.1.2"
+
 wbuf@^1.1.0, wbuf@^1.7.3:
   version "1.7.3"
   resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df"
@@ -18690,7 +26464,7 @@ web3-shh@1.10.4:
     web3-core-subscriptions "1.10.4"
     web3-net "1.10.4"
 
-web3-utils@1.10.4, web3-utils@^1.8.1, web3-utils@^1.8.2:
+web3-utils@1.10.4, web3-utils@^1.3.6, web3-utils@^1.8.1, web3-utils@^1.8.2:
   version "1.10.4"
   resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.10.4.tgz#0daee7d6841641655d8b3726baf33b08eda1cbec"
   integrity sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A==
@@ -18782,6 +26556,17 @@ webpack-dev-middleware@^3.7.2:
     range-parser "^1.2.1"
     webpack-log "^2.0.0"
 
+webpack-dev-middleware@^5.3.4:
+  version "5.3.4"
+  resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz#eb7b39281cbce10e104eb2b8bf2b63fce49a3517"
+  integrity sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==
+  dependencies:
+    colorette "^2.0.10"
+    memfs "^3.4.3"
+    mime-types "^2.1.31"
+    range-parser "^1.2.1"
+    schema-utils "^4.0.0"
+
 webpack-dev-server@^3.11.0:
   version "3.11.3"
   resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.3.tgz#8c86b9d2812bf135d3c9bce6f07b718e30f7c3d3"
@@ -18821,6 +26606,42 @@ webpack-dev-server@^3.11.0:
     ws "^6.2.1"
     yargs "^13.3.2"
 
+webpack-dev-server@^4.9.3:
+  version "4.15.2"
+  resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz#9e0c70a42a012560860adb186986da1248333173"
+  integrity sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==
+  dependencies:
+    "@types/bonjour" "^3.5.9"
+    "@types/connect-history-api-fallback" "^1.3.5"
+    "@types/express" "^4.17.13"
+    "@types/serve-index" "^1.9.1"
+    "@types/serve-static" "^1.13.10"
+    "@types/sockjs" "^0.3.33"
+    "@types/ws" "^8.5.5"
+    ansi-html-community "^0.0.8"
+    bonjour-service "^1.0.11"
+    chokidar "^3.5.3"
+    colorette "^2.0.10"
+    compression "^1.7.4"
+    connect-history-api-fallback "^2.0.0"
+    default-gateway "^6.0.3"
+    express "^4.17.3"
+    graceful-fs "^4.2.6"
+    html-entities "^2.3.2"
+    http-proxy-middleware "^2.0.3"
+    ipaddr.js "^2.0.1"
+    launch-editor "^2.6.0"
+    open "^8.0.9"
+    p-retry "^4.5.0"
+    rimraf "^3.0.2"
+    schema-utils "^4.0.0"
+    selfsigned "^2.1.1"
+    serve-index "^1.9.1"
+    sockjs "^0.3.24"
+    spdy "^4.0.2"
+    webpack-dev-middleware "^5.3.4"
+    ws "^8.13.0"
+
 webpack-log@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f"
@@ -18836,6 +26657,11 @@ webpack-merge@^4.2.2:
   dependencies:
     lodash "^4.17.15"
 
+webpack-node-externals@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz#1a3407c158d547a9feb4229a9e3385b7b60c9917"
+  integrity sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==
+
 webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1:
   version "1.4.3"
   resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
@@ -18844,11 +26670,18 @@ webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1:
     source-list-map "^2.0.0"
     source-map "~0.6.1"
 
-webpack-sources@^3.2.3:
+webpack-sources@^3.0.0, webpack-sources@^3.2.3:
   version "3.2.3"
   resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
   integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
 
+webpack-subresource-integrity@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz#8b7606b033c6ccac14e684267cb7fb1f5c2a132a"
+  integrity sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==
+  dependencies:
+    typed-assert "^1.0.8"
+
 webpack-virtual-modules@^0.5.0:
   version "0.5.0"
   resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz#362f14738a56dae107937ab98ea7062e8bdd3b6c"
@@ -18888,6 +26721,36 @@ webpack@^4.0.0:
     watchpack "^1.7.4"
     webpack-sources "^1.4.1"
 
+webpack@^5.80.0:
+  version "5.91.0"
+  resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.91.0.tgz#ffa92c1c618d18c878f06892bbdc3373c71a01d9"
+  integrity sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==
+  dependencies:
+    "@types/eslint-scope" "^3.7.3"
+    "@types/estree" "^1.0.5"
+    "@webassemblyjs/ast" "^1.12.1"
+    "@webassemblyjs/wasm-edit" "^1.12.1"
+    "@webassemblyjs/wasm-parser" "^1.12.1"
+    acorn "^8.7.1"
+    acorn-import-assertions "^1.9.0"
+    browserslist "^4.21.10"
+    chrome-trace-event "^1.0.2"
+    enhanced-resolve "^5.16.0"
+    es-module-lexer "^1.2.1"
+    eslint-scope "5.1.1"
+    events "^3.2.0"
+    glob-to-regexp "^0.4.1"
+    graceful-fs "^4.2.11"
+    json-parse-even-better-errors "^2.3.1"
+    loader-runner "^4.2.0"
+    mime-types "^2.1.27"
+    neo-async "^2.6.2"
+    schema-utils "^3.2.0"
+    tapable "^2.1.1"
+    terser-webpack-plugin "^5.3.10"
+    watchpack "^2.4.1"
+    webpack-sources "^3.2.3"
+
 websocket-driver@>=0.5.1, websocket-driver@^0.7.4:
   version "0.7.4"
   resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760"
@@ -18914,6 +26777,18 @@ websocket@^1.0.32:
     utf-8-validate "^5.0.2"
     yaeti "^0.0.6"
 
+websocket@^1.0.34:
+  version "1.0.35"
+  resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.35.tgz#374197207d7d4cc4c36cbf8a1bb886ee52a07885"
+  integrity sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==
+  dependencies:
+    bufferutil "^4.0.1"
+    debug "^2.2.0"
+    es5-ext "^0.10.63"
+    typedarray-to-buffer "^3.1.5"
+    utf-8-validate "^5.0.2"
+    yaeti "^0.0.6"
+
 whatwg-encoding@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53"
@@ -18947,6 +26822,14 @@ whatwg-url@^12.0.0, whatwg-url@^12.0.1:
     tr46 "^4.1.1"
     webidl-conversions "^7.0.0"
 
+whatwg-url@^13.0.0:
+  version "13.0.0"
+  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-13.0.0.tgz#b7b536aca48306394a34e44bda8e99f332410f8f"
+  integrity sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==
+  dependencies:
+    tr46 "^4.1.1"
+    webidl-conversions "^7.0.0"
+
 whatwg-url@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
@@ -18987,7 +26870,7 @@ which-typed-array@^1.1.14, which-typed-array@^1.1.2:
     gopd "^1.0.1"
     has-tostringtag "^1.0.1"
 
-which@^1.2.9:
+which@^1.1.1, which@^1.2.9, which@^1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
   integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
@@ -19001,6 +26884,13 @@ which@^2.0.1, which@^2.0.2:
   dependencies:
     isexe "^2.0.0"
 
+which@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/which/-/which-4.0.0.tgz#cd60b5e74503a3fbcfbf6cd6b4138a8bae644c1a"
+  integrity sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==
+  dependencies:
+    isexe "^3.1.1"
+
 why-is-node-running@^2.2.2:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.2.2.tgz#4185b2b4699117819e7154594271e7e344c9973e"
@@ -19009,6 +26899,69 @@ why-is-node-running@^2.2.2:
     siginfo "^2.0.0"
     stackback "0.0.2"
 
+wide-align@^1.1.2:
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3"
+  integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==
+  dependencies:
+    string-width "^1.0.2 || 2 || 3 || 4"
+
+winston-transport@^4.5.0, winston-transport@^4.7.0:
+  version "4.7.0"
+  resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.7.0.tgz#e302e6889e6ccb7f383b926df6936a5b781bd1f0"
+  integrity sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==
+  dependencies:
+    logform "^2.3.2"
+    readable-stream "^3.6.0"
+    triple-beam "^1.3.0"
+
+winston@^2.4.5:
+  version "2.4.7"
+  resolved "https://registry.yarnpkg.com/winston/-/winston-2.4.7.tgz#5791fe08ea7e90db090f1cb31ef98f32531062f1"
+  integrity sha512-vLB4BqzCKDnnZH9PHGoS2ycawueX4HLqENXQitvFHczhgW2vFpSOn31LZtVr1KU8YTw7DS4tM+cqyovxo8taVg==
+  dependencies:
+    async "^2.6.4"
+    colors "1.0.x"
+    cycle "1.0.x"
+    eyes "0.1.x"
+    isstream "0.1.x"
+    stack-trace "0.0.x"
+
+winston@^3.13.0:
+  version "3.13.0"
+  resolved "https://registry.yarnpkg.com/winston/-/winston-3.13.0.tgz#e76c0d722f78e04838158c61adc1287201de7ce3"
+  integrity sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ==
+  dependencies:
+    "@colors/colors" "^1.6.0"
+    "@dabh/diagnostics" "^2.0.2"
+    async "^3.2.3"
+    is-stream "^2.0.0"
+    logform "^2.4.0"
+    one-time "^1.0.0"
+    readable-stream "^3.4.0"
+    safe-stable-stringify "^2.3.1"
+    stack-trace "0.0.x"
+    triple-beam "^1.3.0"
+    winston-transport "^4.7.0"
+
+word-wrap@~1.2.3:
+  version "1.2.5"
+  resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
+  integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
+
+wordwrap@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
+  integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
+
+wordwrapjs@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-4.0.1.tgz#d9790bccfb110a0fc7836b5ebce0937b37a8b98f"
+  integrity sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==
+  dependencies:
+    reduce-flatten "^2.0.0"
+    typical "^5.2.0"
+
 worker-farm@^1.7.0:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8"
@@ -19023,6 +26976,11 @@ worker-rpc@^0.1.0:
   dependencies:
     microevent.ts "~0.1.1"
 
+workerpool@6.2.1:
+  version "6.2.1"
+  resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
+  integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==
+
 "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
@@ -19098,7 +27056,7 @@ ws@^6.0.0, ws@^6.2.1:
   dependencies:
     async-limiter "~1.0.0"
 
-ws@^7.5.1:
+ws@^7.4.6, ws@^7.5.1:
   version "7.5.9"
   resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591"
   integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
@@ -19108,6 +27066,11 @@ ws@^8.11.0, ws@^8.13.0:
   resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4"
   integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==
 
+ws@^8.14.2, ws@^8.16.0:
+  version "8.17.0"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.0.tgz#d145d18eca2ed25aaf791a183903f7be5e295fea"
+  integrity sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==
+
 ws@~8.11.0:
   version "8.11.0"
   resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143"
@@ -19198,6 +27161,16 @@ yaml@^1.10.0, yaml@^1.7.2:
   resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
   integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
 
+yaml@^2.2.1:
+  version "2.4.2"
+  resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.2.tgz#7a2b30f2243a5fc299e1f14ca58d475ed4bc5362"
+  integrity sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==
+
+yargs-parser@20.2.4:
+  version "20.2.4"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54"
+  integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==
+
 yargs-parser@21.1.1, yargs-parser@^21.0.1, yargs-parser@^21.1.1:
   version "21.1.1"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
@@ -19224,6 +27197,29 @@ yargs-parser@^20.2.2, yargs-parser@^20.2.9:
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
   integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
 
+yargs-unparser@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb"
+  integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==
+  dependencies:
+    camelcase "^6.0.0"
+    decamelize "^4.0.0"
+    flat "^5.0.2"
+    is-plain-obj "^2.1.0"
+
+yargs@16.2.0, yargs@^16.0.0, yargs@^16.1.1, yargs@^16.2.0:
+  version "16.2.0"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
+  integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
+  dependencies:
+    cliui "^7.0.2"
+    escalade "^3.1.1"
+    get-caller-file "^2.0.5"
+    require-directory "^2.1.1"
+    string-width "^4.2.0"
+    y18n "^5.0.5"
+    yargs-parser "^20.2.2"
+
 yargs@^13.3.2:
   version "13.3.2"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
@@ -19257,20 +27253,7 @@ yargs@^15.3.1:
     y18n "^4.0.0"
     yargs-parser "^18.1.2"
 
-yargs@^16.0.0, yargs@^16.2.0:
-  version "16.2.0"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
-  integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
-  dependencies:
-    cliui "^7.0.2"
-    escalade "^3.1.1"
-    get-caller-file "^2.0.5"
-    require-directory "^2.1.1"
-    string-width "^4.2.0"
-    y18n "^5.0.5"
-    yargs-parser "^20.2.2"
-
-yargs@^17.3.1, yargs@^17.5.1, yargs@^17.6.2:
+yargs@^17.3.1, yargs@^17.5.1, yargs@^17.6.2, yargs@^17.7.2:
   version "17.7.2"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
   integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
@@ -19296,11 +27279,21 @@ yauzl@^2.10.0:
     buffer-crc32 "~0.2.3"
     fd-slicer "~1.1.0"
 
+ylru@^1.2.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/ylru/-/ylru-1.4.0.tgz#0cf0aa57e9c24f8a2cbde0cc1ca2c9592ac4e0f6"
+  integrity sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==
+
 yn@3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
   integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
 
+yn@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/yn/-/yn-4.0.0.tgz#611480051ea43b510da1dfdbe177ed159f00a979"
+  integrity sha512-huWiiCS4TxKc4SfgmTwW1K7JmXPPAmuXWYy4j9qjQo4+27Kni8mGhAAi1cloRWmBe2EqcLgt3IGqQoRL/MtPgg==
+
 yocto-queue@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
@@ -19321,11 +27314,6 @@ yorkie@^2.0.0:
     normalize-path "^1.0.0"
     strip-indent "^2.0.0"
 
-zksync-web3@^0.14.3:
-  version "0.14.4"
-  resolved "https://registry.yarnpkg.com/zksync-web3/-/zksync-web3-0.14.4.tgz#0b70a7e1a9d45cc57c0971736079185746d46b1f"
-  integrity sha512-kYehMD/S6Uhe1g434UnaMN+sBr9nQm23Ywn0EUP5BfQCsbjcr3ORuS68PosZw8xUTu3pac7G6YMSnNHk+fwzvg==
-
 zod@3.22.4:
   version "3.22.4"
   resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff"